From f03bf86d56f91d4a28ec957a3d6a5f70a36127f2 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sun, 19 Jun 2022 12:18:19 -0700 Subject: [PATCH 001/516] Begin the Java port of the UclidGenerator --- org.lflang/src/org/lflang/Target.java | 6 + .../src/org/lflang/generator/LFGenerator.java | 2 + .../generator/uclid/UclidGenerator.java | 217 ++++++++++++++++++ 3 files changed, 225 insertions(+) create mode 100644 org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java diff --git a/org.lflang/src/org/lflang/Target.java b/org.lflang/src/org/lflang/Target.java index 84a2711311..9b759425c3 100644 --- a/org.lflang/src/org/lflang/Target.java +++ b/org.lflang/src/org/lflang/Target.java @@ -350,6 +350,10 @@ public enum Target { // are those that are a valid expression. Others may be escaped // with the syntax r#keyword. Arrays.asList("self", "true", "false") + ), + Uclid("Uclid", true, + // Use an empty list as a placeholder. + Arrays.asList("") ); /** @@ -474,6 +478,7 @@ public boolean supportsMultiports() { case Python: case Rust: case TS: + case Uclid: return true; } return false; @@ -493,6 +498,7 @@ public boolean supportsParameterizedWidths() { case Python: case Rust: case TS: + case Uclid: return true; } return false; diff --git a/org.lflang/src/org/lflang/generator/LFGenerator.java b/org.lflang/src/org/lflang/generator/LFGenerator.java index ac0c462c84..c8763a19ec 100644 --- a/org.lflang/src/org/lflang/generator/LFGenerator.java +++ b/org.lflang/src/org/lflang/generator/LFGenerator.java @@ -18,6 +18,7 @@ import org.lflang.Target; import org.lflang.generator.c.CGenerator; import org.lflang.generator.python.PythonGenerator; +import org.lflang.generator.uclid.UclidGenerator; import org.lflang.scoping.LFGlobalScopeProvider; import com.google.inject.Inject; @@ -92,6 +93,7 @@ private GeneratorBase createGenerator(Target target, FileConfig fileConfig, Erro case TS: case Rust: return createKotlinBaseGenerator(target, fileConfig, errorReporter); + case Uclid: return new UclidGenerator(fileConfig, errorReporter); } // If no case matched, then throw a runtime exception. throw new RuntimeException("Unexpected target!"); diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java new file mode 100644 index 0000000000..19bae48edf --- /dev/null +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -0,0 +1,217 @@ +/************* +Copyright (c) 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. +***************/ + +/** + * Generator for Uclid models. + * + * @author{Shaokai Lin } + */ +package org.lflang.generator.uclid; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.generator.IFileSystemAccess2; +import org.eclipse.xtext.generator.IGeneratorContext; +import org.eclipse.xtext.nodemodel.util.NodeModelUtils; +import org.eclipse.xtext.xbase.lib.Exceptions; + +import org.lflang.generator.CodeBuilder; +import org.lflang.generator.GeneratorBase; +import org.lflang.generator.LFGeneratorContext; +import org.lflang.generator.PortInstance; +import org.lflang.generator.ReactorInstance; +import org.lflang.generator.TargetTypes; +import org.lflang.ErrorReporter; +import org.lflang.FileConfig; +import org.lflang.Target; +import org.lflang.TimeUnit; +import org.lflang.TimeValue; +import org.lflang.lf.Action; +import org.lflang.lf.VarRef; + +import static org.lflang.ASTUtils.*; + +public class UclidGenerator extends GeneratorBase { + + //////////////////////////////////////////// + //// Private variables + + // Data structures for storing properties + List properties = new ArrayList(); + + //////////////////////////////////////////// + //// Protected fields + + /** The main place to put generated code. */ + protected CodeBuilder code = new CodeBuilder(); + + // Constructor + public UclidGenerator(FileConfig fileConfig, ErrorReporter errorReporter) { + super(fileConfig, errorReporter); + } + + //////////////////////////////////////////////////////////// + //// Public methods + public void doGenerate(Resource resource, LFGeneratorContext context) { + + // The following generates code needed by all the reactors. + super.doGenerate(resource, context); + + // Check for the specified k-induction steps, otherwise defaults to 1. + // FIXME: To enable. + // this.k = this.targetConfig.verification.steps; + // this.tactic = this.targetConfig.verification.tactic; + + System.out.println("*** Start generating Uclid code."); + + // Create the main reactor instance if there is a main reactor. + createMainReactorInstance(); + + // FIXME: Build reaction instance graph and causality graph + // populateGraphsAndLists() + + // Create the src-gen directory + setUpDirectories(); + + // FIXME: Identify properties in the attributes. + + // Generate a Uclid model for each property. + // for (String prop : this.properties) { + // generateUclidFile(prop); + // } + generateUclidFile("test", "bmc"); + + // FIXME: + // Generate runner script + // code = new StringBuilder() + // var filename = outputDir.resolve("run.sh").toString + // generateRunnerScript() + // JavaGeneratorUtils.writeSourceCodeToFile(getCode, filename) + } + + //////////////////////////////////////////////////////////// + //// Protected methods + + /** + * Generate the Uclid model and a runner script. + */ + protected void generateUclidFile(String property, String tactic) { + try { + // Generate main.ucl and print to file + code = new CodeBuilder(); + String filename = this.fileConfig.getSrcGenPath() + .resolve(tactic + "_" + property + ".ucl").toString(); + // generateUclidCode(property); // FIXME + code.writeToFile(filename); + } catch (IOException e) { + Exceptions.sneakyThrow(e); + } + } + + //////////////////////////////////////////////////////////// + //// 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 is done once because + // it is the same for all federates. + this.main = new ReactorInstance(toDefinition(mainDef.getReactorClass()), errorReporter, + this.unorderedReactions); + var reactionInstanceGraph = this.main.assignLevels(); + if (reactionInstanceGraph.nodeCount() > 0) { + errorReporter.reportError("Main reactor has causality cycles. Skipping code generation."); + return; + } + } + + // Force reconstruction of dependence information. + if (isFederated) { + // Avoid compile errors by removing disconnected network ports. + // This must be done after assigning levels. + removeRemoteFederateConnectionPorts(main); + // There will be AST transformations that invalidate some info + // cached in ReactorInstance. + this.main.clearCaches(false); + } + } + } + + private void setUpDirectories() { + // Make sure the target directory exists. + var targetDir = this.fileConfig.getSrcGenPath(); + try { + Files.createDirectories(targetDir); + } catch (IOException e) { + Exceptions.sneakyThrow(e); + } + System.out.println("The models will be located in: " + targetDir); + } + + ///////////////////////////////////////////////// + //// Functions from generatorBase + + @Override + public Target getTarget() { + return Target.C; // FIXME: How to make this target independent? Target.ALL does not work. + } + + @Override + public TargetTypes getTargetTypes() { + throw new UnsupportedOperationException("TODO: auto-generated method stub"); + } + + @Override + public String generateDelayBody(Action action, VarRef port) { + throw new UnsupportedOperationException("TODO: auto-generated method stub"); + } + + @Override + public String generateForwardBody(Action action, VarRef port) { + throw new UnsupportedOperationException("TODO: auto-generated method stub"); + } + + @Override + public String generateDelayGeneric() { + throw new UnsupportedOperationException("TODO: auto-generated method stub"); + } +} \ No newline at end of file From 905bbcc4cc87b4b32da9856ee90b290976b667d5 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 23 Jun 2022 23:40:35 -0700 Subject: [PATCH 002/516] Do some more porting --- .../generator/uclid/UclidGenerator.java | 209 +++++++++++++++++- 1 file changed, 208 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 19bae48edf..2b6f2bbc0a 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -134,13 +134,220 @@ protected void generateUclidFile(String property, String tactic) { code = new CodeBuilder(); String filename = this.fileConfig.getSrcGenPath() .resolve(tactic + "_" + property + ".ucl").toString(); - // generateUclidCode(property); // FIXME + generateUclidCode(); code.writeToFile(filename); } catch (IOException e) { Exceptions.sneakyThrow(e); } } + /** + * The main function that generates Uclid code. + */ + protected void generateUclidCode() { + code.pr(String.join("\n", + "/*******************************", + " * Auto-generated UCLID5 model *", + " ******************************/" + )); + + code.pr("module main {"); + code.indent(); + + // Timing semantics + generateTimingSemantics(); + + // Trace definition + generateTraceDefinition(); + + // Reaction IDs and state variables + generateReactionIdsAndStateVars(); + + // Reactor semantics + generateReactorSemantics(); + + // Connections + generateConnectionsAndActions(); + + // Topology + generateTopologicalMacros(); + generateTopology(); + + // Initial Condition + generateInitialConditions(); + + // Abstractions (i.e. contracts) + generateReactorAbstractions(); + generateReactionAbstractions(); + + // FIXME: Properties + + // Control block + generateControlBlock(); + + code.unindent(); + code.pr("}"); + } + + /** + * FIXME + */ + protected void generateTimingSemantics() { + code.pr(String.join("\n", + "/*******************************", + " * Time and Related Operations *", + " ******************************/", + "type timestamp_t = integer; // The unit is nanoseconds", + "type microstep_t = integer;", + "type tag_t = {", + " timestamp_t,", + " microstep_t", + "};", + "type interval_t = tag_t;", + "", + "// Projection macros", + "define pi1(t : tag_t) : timestamp_t = t._1; // Get timestamp from tag", + "define pi2(t : tag_t) : microstep_t = t._2; // Get microstep from tag", + "", + "// Interval constructor", + "define zero() : interval_t", + "= {0, 0};", + "define startup() : interval_t", + "= zero();", + "define mstep() : interval_t", + "= {0, 1};", + "define nsec(t : integer) : interval_t", + "= {t, 0};", + "define usec(t : integer) : interval_t", + "= {t * 1000, 0};", + "define msec(t : integer) : interval_t", + "= {t * 1000000, 0};", + "define sec(t : integer) : interval_t", + "= {t * 1000000000, 0};", + "define inf() : interval_t", + "= {-1, 0};", + "", + "// Helper function", + "define isInf(i : interval_t) : boolean", + "= pi1(i) < 0;", + "", + "// Tag comparison", + "define tag_later(t1 : tag_t, t2 : tag_t) : boolean", + "= pi1(t1) > pi1(t2)", + " || (pi1(t1) == pi1(t2) && pi2(t1) > pi2(t2))", + " || (isInf(t1) && !isInf(t2));", + "", + "define tag_same(t1 : tag_t, t2 : tag_t) : boolean", + "= t1 == t2;", + "", + "define tag_earlier(t1 : tag_t, t2 : tag_t) : boolean", + "= pi1(t1) < pi1(t2)", + " || (pi1(t1) == pi1(t2) && pi2(t1) < pi2(t2))", + " || (!isInf(t1) && isInf(t2));", + "", + "// mstep() produces a mstep delay. zero() produces no delay.", + "define tag_schedule(t : tag_t, i : interval_t) : tag_t", + "= if (!isInf(t) && !isInf(i) && pi1(i) == 0 && pi2(i) == 1)", + " then { pi1(t), pi2(t) + 1 } // microstep delay", + " else ( if (!isInf(t) && !isInf(i) && pi1(i) == 0 && pi2(i) == 0)", + " then t // no delay", + " else (", + " if (!isInf(t) && pi1(i) > 0 && !isInf(i))", + " then { pi1(t) + pi1(i), 0 }", + " else inf()", + " ));", + "", + "// Deprecated.", + "define tag_delay(t : tag_t, i : interval_t) : tag_t", + "= if (!isInf(t) && !isInf(i))", + " then { pi1(t) + pi1(i), pi2(t) + pi2(i) }", + " else inf();", + "", + "// Only consider timestamp for now.", + "define tag_diff(t1, t2: tag_t) : interval_t", + "= if (!isInf(t1) && !isInf(t2))", + " then { pi1(t1) - pi1(t2), pi2(t1) - pi2(t2) }", + " else inf();", + "" + )); + } + + /** + * FIXME + */ + protected void generateTraceDefinition() { + code.pr(String.join("\n", + "/********************", + " * Trace Definition *", + " *******************/", + "const START : integer = 0;", + "const END : integer = «traceLength-1»;" // FIXME + )); + } + + /** + * FIXME + */ + protected void generateReactionIdsAndStateVars() { + + } + + /** + * FIXME + */ + protected void generateReactorSemantics() { + + } + + /** + * FIXME + */ + protected void generateConnectionsAndActions() { + + } + + /** + * FIXME + */ + protected void generateTopologicalMacros() { + + } + + /** + * FIXME + */ + protected void generateTopology() { + + } + + /** + * FIXME + */ + protected void generateInitialConditions() { + + } + + /** + * FIXME + */ + protected void generateReactorAbstractions() { + + } + + /** + * FIXME + */ + protected void generateReactionAbstractions() { + + } + + /** + * FIXME + */ + protected void generateControlBlock() { + + } + //////////////////////////////////////////////////////////// //// Private methods From e8fe270c63f6a502cbf31ec3b351a38d9fb4d4e0 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 24 Jun 2022 11:37:05 -0700 Subject: [PATCH 003/516] Finish generating traces --- .../generator/uclid/UclidGenerator.java | 117 ++++++++++++++++-- 1 file changed, 108 insertions(+), 9 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 2b6f2bbc0a..c257bdb9d2 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -69,8 +69,13 @@ public class UclidGenerator extends GeneratorBase { //////////////////////////////////////////// //// Private variables + // String lists storing variable names of different types + List variableNames = new LinkedList(); + List triggerNames = new LinkedList(); + List triggerPresence = new LinkedList(); + // Data structures for storing properties - List properties = new ArrayList(); + List properties = new ArrayList(); //////////////////////////////////////////// //// Protected fields @@ -107,12 +112,14 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { setUpDirectories(); // FIXME: Identify properties in the attributes. + // FIXME: Calculate the completeness threshold for each property. + int CT = 5; // Placeholder // Generate a Uclid model for each property. // for (String prop : this.properties) { // generateUclidFile(prop); // } - generateUclidFile("test", "bmc"); + generateUclidFile("test", "bmc", CT); // FIXME: // Generate runner script @@ -128,13 +135,13 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { /** * Generate the Uclid model and a runner script. */ - protected void generateUclidFile(String property, String tactic) { + protected void generateUclidFile(String property, String tactic, int CT) { try { // Generate main.ucl and print to file code = new CodeBuilder(); String filename = this.fileConfig.getSrcGenPath() .resolve(tactic + "_" + property + ".ucl").toString(); - generateUclidCode(); + generateUclidCode(CT); code.writeToFile(filename); } catch (IOException e) { Exceptions.sneakyThrow(e); @@ -144,7 +151,7 @@ protected void generateUclidFile(String property, String tactic) { /** * The main function that generates Uclid code. */ - protected void generateUclidCode() { + protected void generateUclidCode(int CT) { code.pr(String.join("\n", "/*******************************", " * Auto-generated UCLID5 model *", @@ -158,7 +165,7 @@ protected void generateUclidCode() { generateTimingSemantics(); // Trace definition - generateTraceDefinition(); + generateTraceDefinition(CT); // Reaction IDs and state variables generateReactionIdsAndStateVars(); @@ -268,20 +275,112 @@ protected void generateTimingSemantics() { "= if (!isInf(t1) && !isInf(t2))", " then { pi1(t1) - pi1(t2), pi2(t1) - pi2(t2) }", " else inf();", - "" + "\n" )); } /** * FIXME */ - protected void generateTraceDefinition() { + protected void generateTraceDefinition(int CT) { + // Define various constants. code.pr(String.join("\n", "/********************", " * Trace Definition *", " *******************/", "const START : integer = 0;", - "const END : integer = «traceLength-1»;" // FIXME + "const END : integer = «traceLength-1»;", // FIXME + "", + "// trace length = k + N", + "const k : integer = 1; // 1-induction should be enough.", + "const N : integer = " + String.valueOf(CT) + "// The property bound", + "\n" + )); + + // Define trace indices as a group, + // so that we can use finite quantifiers. + code.pr("group indices : integer = {"); + code.indent(); + for (int i = 0; i < CT; i++) { + code.pr(String.valueOf(i) + (i == CT-1? "" : ",")); + } + code.unindent(); + code.pr("};\n\n"); + + // FIXME: Let's see if in_range() can be removed altogether. + // define in_range(num : integer) : boolean + // = num >= START && num <= END; + + // Define step, event, and trace types. + code.pr(String.join("\n", + "// Define step and event types.", + "type step_t = integer;", + "type event_t = { rxn_t, tag_t, state_t, trigger_t };", + "", + "// Create a bounded trace with length " + String.valueOf(CT) + )); + code.pr("type trace_t = {"); + code.indent(); + for (int i = 0; i < CT; i++) { + code.pr("event_t" + (i == CT-1? "" : ",")); + } + code.unindent(); + code.pr("};\n"); + + // Declare start time and trace. + code.pr(String.join("\n", + "// Declare start time.", + "var start_time : timestamp_t;", + "", + "// Declare trace.", + "var trace : trace_t;" + )); + + // Start generating helper macros. + code.pr(String.join("\n", + "/*****************", + " * Helper Macros *", + " ****************/" + )); + + // Define a tuple getter. + // FIXME: Support this in Uclid. + String initialStates = ""; + String initialTriggers = ""; + if (this.variableNames.size() > 0) { + initialStates = "0, ".repeat(this.variableNames.size()); + initialStates = initialStates.substring(0, initialStates.length() - 2); + } else { + // Initialize a dummy variable just to make the code compile. + initialStates = "0"; + } + if (this.triggerNames.size() > 0) { + initialTriggers = "false, ".repeat(this.triggerNames.size()); + initialTriggers = initialTriggers.substring(0, initialTriggers.length() - 2); + } else { + // Initialize a dummy variable just to make the code compile. + initialTriggers = "false"; + } + code.pr("// Helper macro that returns an element based on index."); + code.pr("define get(tr : trace_t, i : step_t) : event_t ="); + for (int i = 0; i < CT; i++) { + code.pr("if (i == " + String.valueOf(i) + ") then tr._" + String.valueOf(i+1) + " else ("); + } + code.pr("{ NULL, inf(), { " + initialStates + " }, { " + initialTriggers + " } }"); + code.pr(")".repeat(CT) + ";\n"); + + // Define an event getter from the trace. + code.pr(String.join("\n", + "define elem(i : step_t) : event_t", + "= get(trace, i);", + "", + "// projection macros", + "define rxn (i : step_t) : rxn_t = elem(i)._1;", + "define g (i : step_t) : tag_t = elem(i)._2;", + "define s (i : step_t) : state_t = elem(i)._3;", + "define t (i : step_t) : trigger_t = elem(i)._4;", + "define isNULL (i : step_t) : boolean = rxn(i) == NULL;", + "" )); } From b09633ffd77705a75e297ec82daa34ab7af79bfe Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 24 Jun 2022 11:42:06 -0700 Subject: [PATCH 004/516] Minor fixes --- org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index c257bdb9d2..4a4f4cea60 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -289,11 +289,11 @@ protected void generateTraceDefinition(int CT) { " * Trace Definition *", " *******************/", "const START : integer = 0;", - "const END : integer = «traceLength-1»;", // FIXME + "const END : integer = " + String.valueOf(CT-1) + ";", "", "// trace length = k + N", "const k : integer = 1; // 1-induction should be enough.", - "const N : integer = " + String.valueOf(CT) + "// The property bound", + "const N : integer = " + String.valueOf(CT) + ";" + "// The property bound", "\n" )); From 9a73349fe6417f7e6ac28564b187492c9117ff65 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 24 Jun 2022 12:20:21 -0700 Subject: [PATCH 005/516] Start to generate reaction and state variable declarations --- .../org/lflang/generator/NamedInstance.java | 41 ++++++++--------- .../generator/ReactionInstanceGraph.java | 7 +++ .../generator/uclid/UclidGenerator.java | 45 ++++++++++++++++++- 3 files changed, 70 insertions(+), 23 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/NamedInstance.java b/org.lflang/src/org/lflang/generator/NamedInstance.java index 78f357159c..72e7dbfee3 100644 --- a/org.lflang/src/org/lflang/generator/NamedInstance.java +++ b/org.lflang/src/org/lflang/generator/NamedInstance.java @@ -265,6 +265,25 @@ public ModeInstance getMode(boolean direct) { return mode; } + /** + * Return a string of the form + * "a.b.c", where "." is replaced by the specified joiner, + * "c" is the name of this instance, "b" is the name + * of its container, and "a" is the name of its container, stopping + * at the container in main. + * @return A string representing this instance. + */ + public String getFullNameWithJoiner(String joiner) { + // This is not cached because _uniqueID is cached. + if (parent == null) { + return this.getName(); + } else if (getMode(true) != null) { + return parent.getFullNameWithJoiner(joiner) + joiner + getMode(true).getName() + joiner + this.getName(); + } else { + return parent.getFullNameWithJoiner(joiner) + joiner + this.getName(); + } + } + ////////////////////////////////////////////////////// //// Protected fields. @@ -290,28 +309,6 @@ public ModeInstance getMode(boolean direct) { */ int width = 1; - ////////////////////////////////////////////////////// - //// Protected methods. - - /** - * Return a string of the form - * "a.b.c", where "." is replaced by the specified joiner, - * "c" is the name of this instance, "b" is the name - * of its container, and "a" is the name of its container, stopping - * at the container in main. - * @return A string representing this instance. - */ - protected String getFullNameWithJoiner(String joiner) { - // This is not cached because _uniqueID is cached. - if (parent == null) { - return this.getName(); - } else if (getMode(true) != null) { - return parent.getFullNameWithJoiner(joiner) + joiner + getMode(true).getName() + joiner + this.getName(); - } else { - return parent.getFullNameWithJoiner(joiner) + joiner + this.getName(); - } - } - ////////////////////////////////////////////////////// //// Protected fields. diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index 72c20bb63a..1b0b16efbf 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -115,6 +115,13 @@ public int getBreadth() { return maxBreadth; } + /** + * Return a set of reaction runtime instances. + */ + public Set getNodes() { + return this.nodes(); + } + /////////////////////////////////////////////////////////// //// Protected methods diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 4a4f4cea60..87a89bd762 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -52,6 +52,8 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.GeneratorBase; import org.lflang.generator.LFGeneratorContext; import org.lflang.generator.PortInstance; +import org.lflang.generator.ReactionInstance; +import org.lflang.generator.ReactionInstanceGraph; import org.lflang.generator.ReactorInstance; import org.lflang.generator.TargetTypes; import org.lflang.ErrorReporter; @@ -69,6 +71,12 @@ public class UclidGenerator extends GeneratorBase { //////////////////////////////////////////// //// Private variables + // Data structures for storing info about the runtime instances. + Set reactionInstances; + + // The reaction graph upon which the causality graph is built + ReactionInstanceGraph reactionInstanceGraph; + // String lists storing variable names of different types List variableNames = new LinkedList(); List triggerNames = new LinkedList(); @@ -106,7 +114,7 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { createMainReactorInstance(); // FIXME: Build reaction instance graph and causality graph - // populateGraphsAndLists() + populateDataStructures(); // Create the src-gen directory setUpDirectories(); @@ -388,7 +396,31 @@ protected void generateTraceDefinition(int CT) { * FIXME */ protected void generateReactionIdsAndStateVars() { + // Encode the components and the logical delays + // present in a reactor system. + code.pr(String.join("\n", + "/**********************************", + " * Reaction IDs & State Variables *", + " *********************************/", + "", + "//////////////////////////", + "// Application Specific" + )); + // Enumerate over all reactions. + code.pr(String.join("\n", + "// Reaction ids", + "type rxn_t = enum {" + )); + code.indent(); + for (ReactionInstance.Runtime rxn : this.reactionInstances) { + // Print a list of reaction IDs. + // Add a comma if not last. + // FIXME: getFullNameWithJoiner does not exist. + // code.pr(rxn.getFullNameWithJoiner("_") + ","); + } + code.unindent(); + code.pr("};\n\n"); } /** @@ -493,6 +525,17 @@ private void setUpDirectories() { System.out.println("The models will be located in: " + targetDir); } + /** + * Populate the data structures. + */ + private void populateDataStructures() { + // Construct graphs + this.reactionInstanceGraph = new ReactionInstanceGraph(this.main); + + // Collect reactions from the reaction graph. + this.reactionInstances = this.reactionInstanceGraph.getNodes(); + } + ///////////////////////////////////////////////// //// Functions from generatorBase From 7b31537cee56a47e113c5eaf5485a39208dc52bc Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sun, 26 Jun 2022 16:08:32 -0400 Subject: [PATCH 006/516] Ensure that when building the reaction instance graph, nodes are not being removed. --- .../generator/ReactionInstanceGraph.java | 36 +++++++++---------- .../org/lflang/generator/ReactorInstance.java | 2 +- .../generator/uclid/UclidGenerator.java | 13 ++++--- 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index 1b0b16efbf..2fad54b360 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -60,9 +60,9 @@ public class ReactionInstanceGraph extends PrecedenceGraph getNodes() { - return this.nodes(); - } - /////////////////////////////////////////////////////////// //// Protected methods diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index ae953e9370..754a3cd8d7 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -185,7 +185,7 @@ public ReactorInstance(Reactor reactor, ReactorInstance parent, ErrorReporter re public ReactionInstanceGraph assignLevels() { if (depth != 0) return root().assignLevels(); if (cachedReactionLoopGraph == null) { - cachedReactionLoopGraph = new ReactionInstanceGraph(this); + cachedReactionLoopGraph = new ReactionInstanceGraph(this, true); } return cachedReactionLoopGraph; } diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 87a89bd762..39ae4b458e 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -413,11 +413,12 @@ protected void generateReactionIdsAndStateVars() { "type rxn_t = enum {" )); code.indent(); + System.out.println(this.reactionInstances); for (ReactionInstance.Runtime rxn : this.reactionInstances) { // Print a list of reaction IDs. // Add a comma if not last. // FIXME: getFullNameWithJoiner does not exist. - // code.pr(rxn.getFullNameWithJoiner("_") + ","); + code.pr(rxn.getReaction().getFullNameWithJoiner("_") + "_" + String.valueOf(rxn.id) + ","); } code.unindent(); code.pr("};\n\n"); @@ -501,7 +502,8 @@ private void createMainReactorInstance() { return; } } - + + // FIXME: Is this needed? // Force reconstruction of dependence information. if (isFederated) { // Avoid compile errors by removing disconnected network ports. @@ -529,11 +531,12 @@ private void setUpDirectories() { * Populate the data structures. */ private void populateDataStructures() { + // System.out.println(this.main.children); // Construct graphs - this.reactionInstanceGraph = new ReactionInstanceGraph(this.main); - + this.reactionInstanceGraph = new ReactionInstanceGraph(this.main, false); + // Collect reactions from the reaction graph. - this.reactionInstances = this.reactionInstanceGraph.getNodes(); + this.reactionInstances = this.reactionInstanceGraph.nodes(); } ///////////////////////////////////////////////// From f3d9cc8cada92f646d186fe9278a97e866ecb861 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sun, 26 Jun 2022 16:34:48 -0400 Subject: [PATCH 007/516] Generate state variables and triggers --- .../generator/uclid/UclidGenerator.java | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 39ae4b458e..ddae4f05de 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -414,14 +414,54 @@ protected void generateReactionIdsAndStateVars() { )); code.indent(); System.out.println(this.reactionInstances); - for (ReactionInstance.Runtime rxn : this.reactionInstances) { + for (var rxn : this.reactionInstances) { // Print a list of reaction IDs. // Add a comma if not last. - // FIXME: getFullNameWithJoiner does not exist. code.pr(rxn.getReaction().getFullNameWithJoiner("_") + "_" + String.valueOf(rxn.id) + ","); } + code.pr("NULL"); code.unindent(); code.pr("};\n\n"); + + // State variables and triggers + // FIXME: expand to data types other than integer + code.pr("type state_t = {"); + code.indent(); + if (this.variableNames.size() > 0) { + for (var i = 0 ; i < this.variableNames.size(); i++) { + code.pr("integer" + ((i++ == this.variableNames.size() - 1) ? "" : ",") + "// " + this.variableNames.get(i)); + } + } else { + code.pr(String.join("\n", + "// There are no ports or state variables.", + "// Insert a dummy integer to make the model compile.", + "integer" + )); + } + code.unindent(); + code.pr("};"); + code.pr("// State variable projection macros"); + for (var i = 0; i < this.variableNames.size(); i++) { + code.pr("define " + this.variableNames.get(i) + "(s : state_t) : integer = s._" + i + ";"); + } + code.pr("\n"); // Newline + + // A boolean tuple indicating whether triggers are present. + code.pr("type trigger_t = {"); + code.indent(); + if (this.triggerNames.size() > 0) { + for (var i = 0 ; i < this.triggerNames.size(); i++) { + code.pr("boolean" + ((i++ == this.triggerNames.size() - 1) ? "" : ",") + "// " + this.variableNames.get(i)); + } + } else { + code.pr(String.join("\n", + "// There are no triggers.", + "// Insert a dummy boolean to make the model compile.", + "boolean" + )); + } + code.unindent(); + code.pr("};"); } /** @@ -531,7 +571,6 @@ private void setUpDirectories() { * Populate the data structures. */ private void populateDataStructures() { - // System.out.println(this.main.children); // Construct graphs this.reactionInstanceGraph = new ReactionInstanceGraph(this.main, false); From 21d17189b6f5ea1b526ce3c283accc113bf2b1ab Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 27 Jun 2022 01:28:12 -0400 Subject: [PATCH 008/516] Generate up to reactor semantics --- .../lflang/generator/ReactionInstance.java | 5 ++ .../generator/uclid/UclidGenerator.java | 79 ++++++++++++++++++- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index f20962d70d..b347023835 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -539,6 +539,11 @@ public class Runtime { public ReactionInstance getReaction() { return ReactionInstance.this; } + + public String getFullNameWithJoiner(String joiner) { + return this.getReaction().getFullNameWithJoiner(joiner) + joiner + "rid" + joiner + String.valueOf(this.id); + } + @Override public String toString() { String result = ReactionInstance.this + "(level: " + level; diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index ddae4f05de..7615ac8859 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -417,7 +417,7 @@ protected void generateReactionIdsAndStateVars() { for (var rxn : this.reactionInstances) { // Print a list of reaction IDs. // Add a comma if not last. - code.pr(rxn.getReaction().getFullNameWithJoiner("_") + "_" + String.valueOf(rxn.id) + ","); + code.pr(rxn.getFullNameWithJoiner("_") + ","); } code.pr("NULL"); code.unindent(); @@ -462,13 +462,57 @@ protected void generateReactionIdsAndStateVars() { } code.unindent(); code.pr("};"); + code.pr("// Trigger presence projection macros"); + for (var i = 0; i < this.triggerPresence.size(); i++) { + code.pr("define " + this.triggerPresence.get(i) + "(t : trigger_t) : boolean = t._" + i + ";"); + } } /** * FIXME */ protected void generateReactorSemantics() { - + code.pr(String.join("\n", + "/*********************", + " * Reactor Semantics *", + " *********************/", + "/** transition relation **/", + "// transition relation constrains future states", + "// based on previous states.", + "", + "// Events are ordered by \"happened-before\" relation.", + "axiom(finite_forall (i : integer) in indices :: (finite_forall (j : integer) in indices :: (in_range(i) && in_range(j))", + " ==> (hb(elem(i), elem(j)) ==> i < j)));", + "", + "// the same event can only trigger once in a logical instant", + "axiom(finite_forall (i : integer) in indices :: (finite_forall (j : integer) in indices :: (in_range(i) && in_range(j))", + " ==> ((rxn(i) == rxn(j) && i != j)", + " ==> !tag_same(g(i), g(j)))));", + "", + "// Tags should be positive", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END)", + " ==> pi1(g(i)) >= 0);", + "", + "// Microsteps should be positive", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END)", + " ==> pi2(g(i)) >= 0);", + "", + "// Begin the frame at the start time specified.", + "define start_frame(i : step_t) : boolean =", + " (tag_same(g(i), {start, 0}) || tag_later(g(i), {start, 0}));", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END)", + " ==> start_frame(i));", + "", + "// NULL events should appear in the suffix, except for START.", + "axiom(finite_forall (j : integer) in indices :: (j > START && j <= END) ==> (", + " (rxn(j)) != NULL) ==> ", + " (finite_forall (i : integer) in indices :: (i > START && i < j) ==> (rxn(i) != NULL)));", + "", + "// When a NULL event occurs, the state stays the same.", + "axiom(finite_forall (j : integer) in indices :: (j > START && j <= END) ==> (", + " (rxn(j) == NULL) ==> (s(j) == s(j-1))", + "));" + )); } /** @@ -482,7 +526,38 @@ protected void generateConnectionsAndActions() { * FIXME */ protected void generateTopologicalMacros() { + code.pr(String.join("\n", + "/***************************", + " * Topological Abstraction *", + " ***************************/" + )); + // Non-federated "happened-before" + code.pr(String.join("\n", + "// Non-federated \"happened-before\"", + "define hb(e1, e2 : event_t) : boolean", + "= tag_earlier(e1._2, e2._2)" + )); + code.indent(); + // Get happen-before relation between two reactions. + // FIXME: Can we get this from the reaction instance graph? + code.pr("|| (tag_same(e1._2, e2._2) && ( false"); + // Iterate over every pair of reactions. + for (var upstream : this.reactionInstanceGraph.nodes()) { + var downstreamList = this.reactionInstanceGraph + .getDownstreamAdjacentNodes(upstream); + for (var downstream : downstreamList) { + code.pr("|| (e1._1 == " + upstream.getFullNameWithJoiner("_") + " && e2._1 == " + downstream.getFullNameWithJoiner("_") + ")"); + } + } + code.unindent(); + code.pr("));"); + code.pr(String.join("\n", + "define startup_triggers(n : rxn_t) : boolean", + "= // if startup is within frame, put the events in the trace.", + " ((start_time == 0) ==> (finite_exists (i : integer) in indices :: in_range(i)", + " && rxn(i) == n && tag_same(g(i), zero())));" + )); } /** From c5bc4114a92b3f3db1af1ec8ad028321e4bda881 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 27 Jun 2022 12:06:00 -0400 Subject: [PATCH 009/516] Generate runner script and refactor a few things. TODO: Triggers, reactions, and DSL-related things. --- .../generator/uclid/UclidGenerator.java | 145 ++++++++++++------ 1 file changed, 96 insertions(+), 49 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 7615ac8859..1a2c716269 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -129,19 +129,15 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // } generateUclidFile("test", "bmc", CT); - // FIXME: // Generate runner script - // code = new StringBuilder() - // var filename = outputDir.resolve("run.sh").toString - // generateRunnerScript() - // JavaGeneratorUtils.writeSourceCodeToFile(getCode, filename) + generateRunnerScript(); } //////////////////////////////////////////////////////////// //// Protected methods /** - * Generate the Uclid model and a runner script. + * Generate the Uclid model. */ protected void generateUclidFile(String property, String tactic, int CT) { try { @@ -156,6 +152,37 @@ protected void generateUclidFile(String property, String tactic, int CT) { } } + /** + * Generate the Uclid model. + */ + protected void generateRunnerScript() { + try { + // Generate main.ucl and print to file + var script = new CodeBuilder(); + String filename = this.fileConfig.getSrcGenPath() + .resolve("run.sh").toString(); + script.pr(String.join("\n", + "#!/bin/bash", + "set -e # Terminate on error", + "", + "echo '*** Setting up smt directory'", + "rm -rf ./smt/ && mkdir -p smt", + "", + "echo '*** Generating SMT files from UCLID5'", + "time uclid --verbosity 3 -g \"smt/output\" $1", + "", + "echo '*** Append (get-model) to each file'", + "ls smt | xargs -I {} bash -c 'echo \"(get-model)\" >> smt/{}'", + "", + "echo '*** Running Z3'", + "ls smt | xargs -I {} bash -c 'echo \"Checking {}\" && z3 parallel.enable=true -T:300 -v:1 ./smt/{}'" + )); + script.writeToFile(filename); + } catch (IOException e) { + Exceptions.sneakyThrow(e); + } + } + /** * The main function that generates Uclid code. */ @@ -182,20 +209,18 @@ protected void generateUclidCode(int CT) { generateReactorSemantics(); // Connections - generateConnectionsAndActions(); - - // Topology - generateTopologicalMacros(); - generateTopology(); + generateTriggersAndReactions(); // Initial Condition generateInitialConditions(); + // FIXME: To support once the DSL is available. // Abstractions (i.e. contracts) - generateReactorAbstractions(); - generateReactionAbstractions(); + // generateReactorAbstractions(); + // generateReactionAbstractions(); // FIXME: Properties + // generateProperties(); // Control block generateControlBlock(); @@ -481,13 +506,13 @@ protected void generateReactorSemantics() { "// based on previous states.", "", "// Events are ordered by \"happened-before\" relation.", - "axiom(finite_forall (i : integer) in indices :: (finite_forall (j : integer) in indices :: (in_range(i) && in_range(j))", - " ==> (hb(elem(i), elem(j)) ==> i < j)));", + "axiom(finite_forall (i : integer) in indices :: (finite_forall (j : integer) in indices ::", + " hb(elem(i), elem(j)) ==> i < j));", "", "// the same event can only trigger once in a logical instant", - "axiom(finite_forall (i : integer) in indices :: (finite_forall (j : integer) in indices :: (in_range(i) && in_range(j))", - " ==> ((rxn(i) == rxn(j) && i != j)", - " ==> !tag_same(g(i), g(j)))));", + "axiom(finite_forall (i : integer) in indices :: (finite_forall (j : integer) in indices ::", + " ((rxn(i) == rxn(j) && i != j)", + " ==> !tag_same(g(i), g(j))));", "", "// Tags should be positive", "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END)", @@ -513,24 +538,7 @@ protected void generateReactorSemantics() { " (rxn(j) == NULL) ==> (s(j) == s(j-1))", "));" )); - } - /** - * FIXME - */ - protected void generateConnectionsAndActions() { - - } - - /** - * FIXME - */ - protected void generateTopologicalMacros() { - code.pr(String.join("\n", - "/***************************", - " * Topological Abstraction *", - " ***************************/" - )); // Non-federated "happened-before" code.pr(String.join("\n", "// Non-federated \"happened-before\"", @@ -551,48 +559,87 @@ protected void generateTopologicalMacros() { } code.unindent(); code.pr("));"); - - code.pr(String.join("\n", - "define startup_triggers(n : rxn_t) : boolean", - "= // if startup is within frame, put the events in the trace.", - " ((start_time == 0) ==> (finite_exists (i : integer) in indices :: in_range(i)", - " && rxn(i) == n && tag_same(g(i), zero())));" - )); } /** * FIXME */ - protected void generateTopology() { + // Previously called pr_connections_and_actions() + protected void generateTriggersAndReactions() { + code.pr(String.join("\n", + "/***************************", + " * Connections and Actions *", + " ***************************/" + )); + // For each reaction, generate axioms for its triggers. + // for (var rxn : this.reactionInstances) {} + + // FIXME: Factor the startup trigger here as well. + // code.pr(String.join("\n", + // "define startup_triggers(n : rxn_t) : boolean", + // "= // if startup is within frame, put the events in the trace.", + // " ((start_time == 0) ==> (finite_exists (i : integer) in indices :: in_range(i)", + // " && rxn(i) == n && tag_same(g(i), zero())));" + // )); + code.pr(String.join("\n", + "/********************************", + " * Reactions and Their Triggers *", + " ********************************/" + )); } /** * FIXME */ protected void generateInitialConditions() { - + code.pr(String.join("\n", + "/*********************", + " * Initial Condition *", + " *********************/", + "// FIXME: add template", + "define initial_condition() : boolean", + "= start == 0", + " && rxn(0) == NULL", + " && g(0) == {0, 0}" + )); + code.indent(); + for (var v : this.variableNames) { + code.pr("&& " + v + "(s(0)) == 0"); + } + for (var t : this.triggerPresence) { + code.pr("&& !" + t + "(t(0))"); + } + code.pr(";"); + code.unindent(); } /** * FIXME */ - protected void generateReactorAbstractions() { + // protected void generateReactorAbstractions() { - } + // } /** * FIXME */ - protected void generateReactionAbstractions() { + // protected void generateReactionAbstractions() { - } + // } /** * FIXME */ protected void generateControlBlock() { - + code.pr(String.join("\n", + "control {", + " v = bmc(0);", + " check;", + " print_results;", + " v.print_cex;", + "}" + )); } //////////////////////////////////////////////////////////// From d704e514b523c855d5c87a1a2bb0dcac674c3495 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 27 Jun 2022 22:20:13 -0400 Subject: [PATCH 010/516] Uclid bug fixes --- .../src/org/lflang/generator/uclid/UclidGenerator.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 1a2c716269..4d9082280e 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -438,7 +438,6 @@ protected void generateReactionIdsAndStateVars() { "type rxn_t = enum {" )); code.indent(); - System.out.println(this.reactionInstances); for (var rxn : this.reactionInstances) { // Print a list of reaction IDs. // Add a comma if not last. @@ -512,7 +511,7 @@ protected void generateReactorSemantics() { "// the same event can only trigger once in a logical instant", "axiom(finite_forall (i : integer) in indices :: (finite_forall (j : integer) in indices ::", " ((rxn(i) == rxn(j) && i != j)", - " ==> !tag_same(g(i), g(j))));", + " ==> !tag_same(g(i), g(j)))));", "", "// Tags should be positive", "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END)", @@ -524,7 +523,7 @@ protected void generateReactorSemantics() { "", "// Begin the frame at the start time specified.", "define start_frame(i : step_t) : boolean =", - " (tag_same(g(i), {start, 0}) || tag_later(g(i), {start, 0}));", + " (tag_same(g(i), {start_time, 0}) || tag_later(g(i), {start_time, 0}));", "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END)", " ==> start_frame(i));", "", @@ -599,7 +598,7 @@ protected void generateInitialConditions() { " *********************/", "// FIXME: add template", "define initial_condition() : boolean", - "= start == 0", + "= start_time == 0", " && rxn(0) == NULL", " && g(0) == {0, 0}" )); From 7e1a726ec8ac0fa29a5f7cef0216f74905392ba2 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Tue, 28 Jun 2022 18:34:38 -0400 Subject: [PATCH 011/516] Start to generate reactions and triggers --- .../lflang/generator/uclid/UclidGenerator.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 4d9082280e..4f525e74aa 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -121,7 +121,7 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // FIXME: Identify properties in the attributes. // FIXME: Calculate the completeness threshold for each property. - int CT = 5; // Placeholder + int CT = 10; // Placeholder. Currently up to ~50. // Generate a Uclid model for each property. // for (String prop : this.properties) { @@ -208,7 +208,7 @@ protected void generateUclidCode(int CT) { // Reactor semantics generateReactorSemantics(); - // Connections + // Triggers and reactions generateTriggersAndReactions(); // Initial Condition @@ -586,6 +586,19 @@ protected void generateTriggersAndReactions() { " * Reactions and Their Triggers *", " ********************************/" )); + // Iterate over all reactions, generate conditions for them + // to be triggered. + for (var rxn : this.reactionInstances) { + code.pr(String.join("\n", + "// " + rxn.getFullNameWithJoiner("_") + " is invoked when any of it triggers are present.", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> ((", + " false" + )); + code.indent(); + + // Iterate over the triggers of the reaction. + + } } /** From f3a54c663697e8719f75fda46f3a74983ac7a233 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 29 Jun 2022 23:37:56 -0400 Subject: [PATCH 012/516] Continue generating reactions --- .../org/lflang/generator/TriggerInstance.java | 2 +- .../generator/uclid/UclidGenerator.java | 51 ++++++++++++++++++- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/TriggerInstance.java b/org.lflang/src/org/lflang/generator/TriggerInstance.java index 11260e662a..2636915861 100644 --- a/org.lflang/src/org/lflang/generator/TriggerInstance.java +++ b/org.lflang/src/org/lflang/generator/TriggerInstance.java @@ -143,7 +143,7 @@ public boolean isBuiltinTrigger() { BuiltinTrigger builtinTriggerType = null; /** - * Reaction instances that are triggered or read by this trigger. + * Reaction instances that are triggered by or read this trigger. * If this port is an output, then the reaction instances * belong to the parent of the port's parent. If the port * is an input, then the reaction instances belong to the diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 4f525e74aa..b78df4a241 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -56,12 +56,15 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.ReactionInstanceGraph; import org.lflang.generator.ReactorInstance; import org.lflang.generator.TargetTypes; +import org.lflang.generator.TriggerInstance; import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.Target; import org.lflang.TimeUnit; import org.lflang.TimeValue; import org.lflang.lf.Action; +import org.lflang.lf.Reaction; +import org.lflang.lf.TriggerRef; import org.lflang.lf.VarRef; import static org.lflang.ASTUtils.*; @@ -588,16 +591,60 @@ protected void generateTriggersAndReactions() { )); // Iterate over all reactions, generate conditions for them // to be triggered. - for (var rxn : this.reactionInstances) { + for (ReactionInstance.Runtime reaction : this.reactionInstances) { code.pr(String.join("\n", - "// " + rxn.getFullNameWithJoiner("_") + " is invoked when any of it triggers are present.", + "// " + reaction.getFullNameWithJoiner("_") + " is invoked when any of it triggers are present.", "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> ((", " false" )); code.indent(); // Iterate over the triggers of the reaction. + for (TriggerInstance trigger : reaction.getReaction().triggers) { + String triggerPresentStr = ""; + + if (trigger.isBuiltinTrigger()) { + // Check if this trigger is a built-in trigger (startup or shutdown). + if (trigger.isStartup()) { + // FIXME: Treat startup as a variable. + triggerPresentStr = "g(i) == zero()"; + } else if (trigger.isShutdown()) { + // FIXME: For a future version. + } else { + // Unreachable. + } + } + else { + // If the trigger is a port/action/timer. + triggerPresentStr = trigger.getFullNameWithJoiner("_") + "_is_present(t(i))"; + } + + // Check if the trigger triggers other reactions. + // If so, we need to assert in the axioms that + // the other reactions are excluded (not invoked), + // to preserve the interleaving semantics. + String exclusion = ""; + + // If the current reaction is in a bank, then we need to + // exclude other bank member reactions. We are still deadling + // with once ReactionInstance here. + if (reaction.getReaction().getParent().isBank()) { + // Exclude other bank member reactions triggered by this trigger. + } + + // And if the trigger triggers another ReactionInstance, + // then we need to retrieve all runtime instances in that + // ReactionInstance and exclude them. + if (trigger.getDependentReactions().size() > 1) { + // Exclude all reactions from other dependent reactions. + } + + code.pr("|| (" + triggerPresentStr + exclusion + ")"); + } + // If any of the above trigger is present, then trigger the reaction. + code.unindent(); + code.pr(") <==> (rxn(i) == " + reaction.getFullNameWithJoiner("_") + ")));"); } } From 5d8d9a465d10ff0fd8d1bb5ec4cec50c3b4a796f Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 30 Jun 2022 16:31:07 -0400 Subject: [PATCH 013/516] Start populating state variables and triggers --- .../org/lflang/generator/ReactorInstance.java | 13 +++- .../generator/uclid/UclidGenerator.java | 67 ++++++++++--------- 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 754a3cd8d7..0dcd23827a 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -54,6 +54,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; +import org.lflang.lf.StateVar; import org.lflang.lf.Timer; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; @@ -144,6 +145,9 @@ public ReactorInstance(Reactor reactor, ReactorInstance parent, ErrorReporter re /** The output port instances belonging to this reactor instance. */ public final List outputs = new ArrayList<>(); + /** The state variable instances belonging to this reactor instance. */ + public final List states = new ArrayList<>(); + /** The parameters of this instance. */ public final List parameters = new ArrayList<>(); @@ -788,16 +792,21 @@ private ReactorInstance( this.parameters.add(new ParameterInstance(parameter, this)); } - // Instantiate inputs for this reactor instance + // 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 + // Instantiate outputs for this reactor instance. for (Output outputDecl : ASTUtils.allOutputs(reactorDefinition)) { this.outputs.add(new PortInstance(outputDecl, this, reporter)); } + // Instantiate state variables for this reactor instance. + for (StateVar state : ASTUtils.allStateVars(reactorDefinition)) { + this.states.add(new StateVariableInstance(state, this, reporter)); + } + // Do not process content (except interface above) if recursive if (!recursive && (desiredDepth < 0 || this.depth < desiredDepth)) { // Instantiate children for this reactor instance. diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index b78df4a241..ba2b4ad227 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -31,40 +31,28 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.eclipse.emf.ecore.resource.Resource; -import org.eclipse.emf.ecore.EObject; -import org.eclipse.xtext.generator.IFileSystemAccess2; -import org.eclipse.xtext.generator.IGeneratorContext; -import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import org.eclipse.xtext.xbase.lib.Exceptions; import org.lflang.generator.CodeBuilder; import org.lflang.generator.GeneratorBase; import org.lflang.generator.LFGeneratorContext; -import org.lflang.generator.PortInstance; import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactionInstanceGraph; import org.lflang.generator.ReactorInstance; +import org.lflang.generator.StateVariableInstance; import org.lflang.generator.TargetTypes; import org.lflang.generator.TriggerInstance; import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.Target; -import org.lflang.TimeUnit; -import org.lflang.TimeValue; import org.lflang.lf.Action; -import org.lflang.lf.Reaction; -import org.lflang.lf.TriggerRef; import org.lflang.lf.VarRef; import static org.lflang.ASTUtils.*; @@ -80,9 +68,10 @@ public class UclidGenerator extends GeneratorBase { // The reaction graph upon which the causality graph is built ReactionInstanceGraph reactionInstanceGraph; + // FIXME: How to elegantly populate them? // String lists storing variable names of different types - List variableNames = new LinkedList(); - List triggerNames = new LinkedList(); + List stateVariables = new LinkedList(); + List triggerNames = new LinkedList(); List triggerPresence = new LinkedList(); // Data structures for storing properties @@ -383,8 +372,8 @@ protected void generateTraceDefinition(int CT) { // FIXME: Support this in Uclid. String initialStates = ""; String initialTriggers = ""; - if (this.variableNames.size() > 0) { - initialStates = "0, ".repeat(this.variableNames.size()); + if (this.stateVarNames.size() > 0) { + initialStates = "0, ".repeat(this.stateVarNames.size()); initialStates = initialStates.substring(0, initialStates.length() - 2); } else { // Initialize a dummy variable just to make the code compile. @@ -454,9 +443,9 @@ protected void generateReactionIdsAndStateVars() { // FIXME: expand to data types other than integer code.pr("type state_t = {"); code.indent(); - if (this.variableNames.size() > 0) { - for (var i = 0 ; i < this.variableNames.size(); i++) { - code.pr("integer" + ((i++ == this.variableNames.size() - 1) ? "" : ",") + "// " + this.variableNames.get(i)); + if (this.stateVarNames.size() > 0) { + for (var i = 0 ; i < this.stateVarNames.size(); i++) { + code.pr("integer" + ((i++ == this.stateVarNames.size() - 1) ? "" : ",") + "// " + this.stateVarNames.get(i)); } } else { code.pr(String.join("\n", @@ -468,8 +457,8 @@ protected void generateReactionIdsAndStateVars() { code.unindent(); code.pr("};"); code.pr("// State variable projection macros"); - for (var i = 0; i < this.variableNames.size(); i++) { - code.pr("define " + this.variableNames.get(i) + "(s : state_t) : integer = s._" + i + ";"); + for (var i = 0; i < this.stateVarNames.size(); i++) { + code.pr("define " + this.stateVarNames.get(i) + "(s : state_t) : integer = s._" + i + ";"); } code.pr("\n"); // Newline @@ -478,7 +467,7 @@ protected void generateReactionIdsAndStateVars() { code.indent(); if (this.triggerNames.size() > 0) { for (var i = 0 ; i < this.triggerNames.size(); i++) { - code.pr("boolean" + ((i++ == this.triggerNames.size() - 1) ? "" : ",") + "// " + this.variableNames.get(i)); + code.pr("boolean" + ((i++ == this.triggerNames.size() - 1) ? "" : ",") + "// " + this.stateVarNames.get(i)); } } else { code.pr(String.join("\n", @@ -628,16 +617,27 @@ protected void generateTriggersAndReactions() { // If the current reaction is in a bank, then we need to // exclude other bank member reactions. We are still deadling // with once ReactionInstance here. - if (reaction.getReaction().getParent().isBank()) { - // Exclude other bank member reactions triggered by this trigger. - } - + // if (reaction.getReaction().getParent().isBank()) { + // // Exclude other bank member reactions triggered by this trigger. + // for (var runtime : reaction.getReaction().getRuntimeInstances()) { + // if (runtime == reaction) continue; // Skip the current reaction. + // exclusion += " && rxn(i) != " + runtime.getFullNameWithJoiner("_"); + // } + // } + + // FIXME: Check if the case above can be merged into the case below. // And if the trigger triggers another ReactionInstance, // then we need to retrieve all runtime instances in that // ReactionInstance and exclude them. - if (trigger.getDependentReactions().size() > 1) { - // Exclude all reactions from other dependent reactions. + // if (trigger.getDependentReactions().size() > 1) { + // Exclude all reactions from other dependent reactions. + for (var instance : trigger.getDependentReactions()) { + for (var runtime : ((ReactionInstance)instance).getRuntimeInstances()) { + if (runtime == reaction) continue; // Skip the current reaction. + exclusion += " && rxn(i) != " + runtime.getFullNameWithJoiner("_"); + } } + // } code.pr("|| (" + triggerPresentStr + exclusion + ")"); } @@ -663,7 +663,7 @@ protected void generateInitialConditions() { " && g(0) == {0, 0}" )); code.indent(); - for (var v : this.variableNames) { + for (var v : this.stateVarNames) { code.pr("&& " + v + "(s(0)) == 0"); } for (var t : this.triggerPresence) { @@ -759,6 +759,13 @@ private void populateDataStructures() { this.reactionInstances = this.reactionInstanceGraph.nodes(); } + private void populateStateVarsAndTriggers(ReactorInstance reactor) { + for (var state : reactor.states) { + this.stateVariables.add(state); + } + // ... ports, actions, timers + } + ///////////////////////////////////////////////// //// Functions from generatorBase From c2bccb52c07eeadfb5ff001f72d5bffc8f231a52 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 1 Jul 2022 00:41:28 -0400 Subject: [PATCH 014/516] Keep generating lists --- .../org/lflang/generator/ReactorInstance.java | 2 +- .../generator/uclid/UclidGenerator.java | 73 +++++++++++++------ 2 files changed, 50 insertions(+), 25 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 0dcd23827a..787e0fea17 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -129,7 +129,7 @@ public ReactorInstance(Reactor reactor, ReactorInstance parent, ErrorReporter re //// Public fields. /** The action instances belonging to this reactor instance. */ - public List actions = new ArrayList<>(); + public final List actions = new ArrayList<>(); /** * The contained reactor instances, in order of declaration. diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index ba2b4ad227..6cc7d68704 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -43,6 +43,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.CodeBuilder; import org.lflang.generator.GeneratorBase; import org.lflang.generator.LFGeneratorContext; +import org.lflang.generator.NamedInstance; import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactionInstanceGraph; import org.lflang.generator.ReactorInstance; @@ -71,8 +72,8 @@ public class UclidGenerator extends GeneratorBase { // FIXME: How to elegantly populate them? // String lists storing variable names of different types List stateVariables = new LinkedList(); - List triggerNames = new LinkedList(); - List triggerPresence = new LinkedList(); + List triggers = new LinkedList(); + List stateVarsAndTriggers; // Data structures for storing properties List properties = new ArrayList(); @@ -105,8 +106,10 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // Create the main reactor instance if there is a main reactor. createMainReactorInstance(); - // FIXME: Build reaction instance graph and causality graph + // Extract information from the named instances. populateDataStructures(); + System.out.println(this.stateVariables); + System.out.println(this.triggers); // Create the src-gen directory setUpDirectories(); @@ -372,15 +375,15 @@ protected void generateTraceDefinition(int CT) { // FIXME: Support this in Uclid. String initialStates = ""; String initialTriggers = ""; - if (this.stateVarNames.size() > 0) { - initialStates = "0, ".repeat(this.stateVarNames.size()); + if (this.stateVarsAndTriggers.size() > 0) { + initialStates = "0, ".repeat(this.stateVarsAndTriggers.size()); initialStates = initialStates.substring(0, initialStates.length() - 2); } else { // Initialize a dummy variable just to make the code compile. initialStates = "0"; } - if (this.triggerNames.size() > 0) { - initialTriggers = "false, ".repeat(this.triggerNames.size()); + if (this.stateVarsAndTriggers.size() > 0) { + initialTriggers = "false, ".repeat(this.stateVarsAndTriggers.size()); initialTriggers = initialTriggers.substring(0, initialTriggers.length() - 2); } else { // Initialize a dummy variable just to make the code compile. @@ -443,9 +446,9 @@ protected void generateReactionIdsAndStateVars() { // FIXME: expand to data types other than integer code.pr("type state_t = {"); code.indent(); - if (this.stateVarNames.size() > 0) { - for (var i = 0 ; i < this.stateVarNames.size(); i++) { - code.pr("integer" + ((i++ == this.stateVarNames.size() - 1) ? "" : ",") + "// " + this.stateVarNames.get(i)); + if (this.stateVarsAndTriggers.size() > 0) { + for (var i = 0 ; i < this.stateVarsAndTriggers.size(); i++) { + code.pr("integer" + ((i == this.stateVarsAndTriggers.size() - 1) ? "" : ",") + "\t// " + this.stateVarsAndTriggers.get(i)); } } else { code.pr(String.join("\n", @@ -457,17 +460,17 @@ protected void generateReactionIdsAndStateVars() { code.unindent(); code.pr("};"); code.pr("// State variable projection macros"); - for (var i = 0; i < this.stateVarNames.size(); i++) { - code.pr("define " + this.stateVarNames.get(i) + "(s : state_t) : integer = s._" + i + ";"); + for (var i = 0; i < this.stateVarsAndTriggers.size(); i++) { + code.pr("define " + this.stateVarsAndTriggers.get(i).getFullNameWithJoiner("_") + "(s : state_t) : integer = s._" + (i+1) + ";"); } code.pr("\n"); // Newline // A boolean tuple indicating whether triggers are present. code.pr("type trigger_t = {"); code.indent(); - if (this.triggerNames.size() > 0) { - for (var i = 0 ; i < this.triggerNames.size(); i++) { - code.pr("boolean" + ((i++ == this.triggerNames.size() - 1) ? "" : ",") + "// " + this.stateVarNames.get(i)); + if (this.triggers.size() > 0) { + for (var i = 0 ; i < this.triggers.size(); i++) { + code.pr("boolean" + ((i == this.triggers.size() - 1) ? "" : ",") + "\t// " + this.triggers.get(i)); } } else { code.pr(String.join("\n", @@ -479,8 +482,8 @@ protected void generateReactionIdsAndStateVars() { code.unindent(); code.pr("};"); code.pr("// Trigger presence projection macros"); - for (var i = 0; i < this.triggerPresence.size(); i++) { - code.pr("define " + this.triggerPresence.get(i) + "(t : trigger_t) : boolean = t._" + i + ";"); + for (var i = 0; i < this.triggers.size(); i++) { + code.pr("define " + this.triggers.get(i).getFullNameWithJoiner("_") + "_is_present" + "(t : trigger_t) : boolean = t._" + (i+1) + ";"); } } @@ -656,21 +659,20 @@ protected void generateInitialConditions() { "/*********************", " * Initial Condition *", " *********************/", - "// FIXME: add template", "define initial_condition() : boolean", "= start_time == 0", " && rxn(0) == NULL", " && g(0) == {0, 0}" )); code.indent(); - for (var v : this.stateVarNames) { + for (var v : this.stateVariables) { code.pr("&& " + v + "(s(0)) == 0"); } - for (var t : this.triggerPresence) { - code.pr("&& !" + t + "(t(0))"); + for (var t : this.triggers) { + code.pr("&& !" + t.getFullNameWithJoiner("_") + "_is_present" + "(t(0))"); } - code.pr(";"); code.unindent(); + code.pr(";\n"); } /** @@ -757,13 +759,36 @@ private void populateDataStructures() { // Collect reactions from the reaction graph. this.reactionInstances = this.reactionInstanceGraph.nodes(); + + // Populate lists + populateLists(this.main); + + // Join state variables and triggers + this.stateVarsAndTriggers = new ArrayList(this.stateVariables); + stateVarsAndTriggers.addAll(this.triggers); } - private void populateStateVarsAndTriggers(ReactorInstance reactor) { + private void populateLists(ReactorInstance reactor) { + // State variables, actions, ports, timers. for (var state : reactor.states) { this.stateVariables.add(state); } - // ... ports, actions, timers + for (var action : reactor.actions) { + this.triggers.add(action); + } + for (var port : reactor.inputs) { + this.triggers.add(port); + } + for (var port : reactor.outputs) { + this.triggers.add(port); + } + for (var timer : reactor.timers) { + this.triggers.add(timer); + } + // Recursion + for (var child : reactor.children) { + populateLists(child); + } } ///////////////////////////////////////////////// From 7bba94a3271c9722687c9271be0389436424c5ca Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 1 Jul 2022 00:44:59 -0400 Subject: [PATCH 015/516] Remove dead code --- .../generator/uclid/UclidGenerator.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 6cc7d68704..3e93606d83 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -616,31 +616,12 @@ protected void generateTriggersAndReactions() { // the other reactions are excluded (not invoked), // to preserve the interleaving semantics. String exclusion = ""; - - // If the current reaction is in a bank, then we need to - // exclude other bank member reactions. We are still deadling - // with once ReactionInstance here. - // if (reaction.getReaction().getParent().isBank()) { - // // Exclude other bank member reactions triggered by this trigger. - // for (var runtime : reaction.getReaction().getRuntimeInstances()) { - // if (runtime == reaction) continue; // Skip the current reaction. - // exclusion += " && rxn(i) != " + runtime.getFullNameWithJoiner("_"); - // } - // } - - // FIXME: Check if the case above can be merged into the case below. - // And if the trigger triggers another ReactionInstance, - // then we need to retrieve all runtime instances in that - // ReactionInstance and exclude them. - // if (trigger.getDependentReactions().size() > 1) { - // Exclude all reactions from other dependent reactions. for (var instance : trigger.getDependentReactions()) { for (var runtime : ((ReactionInstance)instance).getRuntimeInstances()) { if (runtime == reaction) continue; // Skip the current reaction. exclusion += " && rxn(i) != " + runtime.getFullNameWithJoiner("_"); } } - // } code.pr("|| (" + triggerPresentStr + exclusion + ")"); } From 1e8e401475f683f5e260a75f12e30db68d471c3a Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 1 Jul 2022 11:33:57 -0400 Subject: [PATCH 016/516] Finish generating actions --- .../generator/uclid/UclidGenerator.java | 142 ++++++++++++------ 1 file changed, 100 insertions(+), 42 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 3e93606d83..20d906f4ac 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -39,20 +39,23 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.xbase.lib.Exceptions; - +import org.lflang.generator.ActionInstance; import org.lflang.generator.CodeBuilder; import org.lflang.generator.GeneratorBase; import org.lflang.generator.LFGeneratorContext; import org.lflang.generator.NamedInstance; +import org.lflang.generator.PortInstance; import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactionInstanceGraph; import org.lflang.generator.ReactorInstance; import org.lflang.generator.StateVariableInstance; import org.lflang.generator.TargetTypes; +import org.lflang.generator.TimerInstance; import org.lflang.generator.TriggerInstance; import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.Target; +import org.lflang.TimeValue; import org.lflang.lf.Action; import org.lflang.lf.VarRef; @@ -69,20 +72,27 @@ public class UclidGenerator extends GeneratorBase { // The reaction graph upon which the causality graph is built ReactionInstanceGraph reactionInstanceGraph; - // FIXME: How to elegantly populate them? - // String lists storing variable names of different types - List stateVariables = new LinkedList(); - List triggers = new LinkedList(); - List stateVarsAndTriggers; + // State variables in the system + List stateVariables = new ArrayList(); + + // Triggers in the system + List actionInstances = new ArrayList(); + List portInstances = new ArrayList(); + List timerInstances = new ArrayList(); + + // Joint lists of the lists above. + // FIXME: This approach currently creates duplications in memory. + List triggerInstances; // Triggers = ports + actions + timers + List namedInstances; // Named instances = triggers + state variables // Data structures for storing properties - List properties = new ArrayList(); + List properties = new ArrayList(); //////////////////////////////////////////// //// Protected fields /** The main place to put generated code. */ - protected CodeBuilder code = new CodeBuilder(); + protected CodeBuilder code = new CodeBuilder(); // Constructor public UclidGenerator(FileConfig fileConfig, ErrorReporter errorReporter) { @@ -109,7 +119,7 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // Extract information from the named instances. populateDataStructures(); System.out.println(this.stateVariables); - System.out.println(this.triggers); + System.out.println(this.triggerInstances); // Create the src-gen directory setUpDirectories(); @@ -372,29 +382,29 @@ protected void generateTraceDefinition(int CT) { )); // Define a tuple getter. - // FIXME: Support this in Uclid. + // FIXME: Support this feature in Uclid. String initialStates = ""; - String initialTriggers = ""; - if (this.stateVarsAndTriggers.size() > 0) { - initialStates = "0, ".repeat(this.stateVarsAndTriggers.size()); - initialStates = initialStates.substring(0, initialStates.length() - 2); + String initialTriggerPresence = ""; + if (this.namedInstances.size() > 0) { + initialStates = "0, ".repeat(this.namedInstances.size()); + initialStates = initialStates.substring(0, initialStates.length()-2); } else { // Initialize a dummy variable just to make the code compile. initialStates = "0"; } - if (this.stateVarsAndTriggers.size() > 0) { - initialTriggers = "false, ".repeat(this.stateVarsAndTriggers.size()); - initialTriggers = initialTriggers.substring(0, initialTriggers.length() - 2); + if (this.triggerInstances.size() > 0) { + initialTriggerPresence = "false, ".repeat(this.triggerInstances.size()); + initialTriggerPresence = initialTriggerPresence.substring(0, initialTriggerPresence.length()-2); } else { // Initialize a dummy variable just to make the code compile. - initialTriggers = "false"; + initialTriggerPresence = "false"; } code.pr("// Helper macro that returns an element based on index."); code.pr("define get(tr : trace_t, i : step_t) : event_t ="); for (int i = 0; i < CT; i++) { code.pr("if (i == " + String.valueOf(i) + ") then tr._" + String.valueOf(i+1) + " else ("); } - code.pr("{ NULL, inf(), { " + initialStates + " }, { " + initialTriggers + " } }"); + code.pr("{ NULL, inf(), { " + initialStates + " }, { " + initialTriggerPresence + " } }"); code.pr(")".repeat(CT) + ";\n"); // Define an event getter from the trace. @@ -446,9 +456,9 @@ protected void generateReactionIdsAndStateVars() { // FIXME: expand to data types other than integer code.pr("type state_t = {"); code.indent(); - if (this.stateVarsAndTriggers.size() > 0) { - for (var i = 0 ; i < this.stateVarsAndTriggers.size(); i++) { - code.pr("integer" + ((i == this.stateVarsAndTriggers.size() - 1) ? "" : ",") + "\t// " + this.stateVarsAndTriggers.get(i)); + if (this.namedInstances.size() > 0) { + for (var i = 0 ; i < this.namedInstances.size(); i++) { + code.pr("integer" + ((i == this.namedInstances.size() - 1) ? "" : ",") + "\t// " + this.namedInstances.get(i)); } } else { code.pr(String.join("\n", @@ -460,17 +470,17 @@ protected void generateReactionIdsAndStateVars() { code.unindent(); code.pr("};"); code.pr("// State variable projection macros"); - for (var i = 0; i < this.stateVarsAndTriggers.size(); i++) { - code.pr("define " + this.stateVarsAndTriggers.get(i).getFullNameWithJoiner("_") + "(s : state_t) : integer = s._" + (i+1) + ";"); + for (var i = 0; i < this.namedInstances.size(); i++) { + code.pr("define " + this.namedInstances.get(i).getFullNameWithJoiner("_") + "(s : state_t) : integer = s._" + (i+1) + ";"); } code.pr("\n"); // Newline // A boolean tuple indicating whether triggers are present. code.pr("type trigger_t = {"); code.indent(); - if (this.triggers.size() > 0) { - for (var i = 0 ; i < this.triggers.size(); i++) { - code.pr("boolean" + ((i == this.triggers.size() - 1) ? "" : ",") + "\t// " + this.triggers.get(i)); + if (this.triggerInstances.size() > 0) { + for (var i = 0 ; i < this.triggerInstances.size(); i++) { + code.pr("boolean" + ((i == this.triggerInstances.size() - 1) ? "" : ",") + "\t// " + this.triggerInstances.get(i)); } } else { code.pr(String.join("\n", @@ -482,8 +492,8 @@ protected void generateReactionIdsAndStateVars() { code.unindent(); code.pr("};"); code.pr("// Trigger presence projection macros"); - for (var i = 0; i < this.triggers.size(); i++) { - code.pr("define " + this.triggers.get(i).getFullNameWithJoiner("_") + "_is_present" + "(t : trigger_t) : boolean = t._" + (i+1) + ";"); + for (var i = 0; i < this.triggerInstances.size(); i++) { + code.pr("define " + this.triggerInstances.get(i).getFullNameWithJoiner("_") + "_is_present" + "(t : trigger_t) : boolean = t._" + (i+1) + ";"); } } @@ -561,12 +571,55 @@ protected void generateReactorSemantics() { // Previously called pr_connections_and_actions() protected void generateTriggersAndReactions() { code.pr(String.join("\n", - "/***************************", - " * Connections and Actions *", - " ***************************/" + "/***************", + " * Connections *", + " ***************/" )); - // For each reaction, generate axioms for its triggers. - // for (var rxn : this.reactionInstances) {} + + if (this.actionInstances.size() > 0) { + code.pr(String.join("\n", + "/***********", + " * Actions *", + " ***********/" + )); + for (var action : this.actionInstances) { + Set dependsOnReactions = action.getDependsOnReactions(); + String comment = "If " + action.getFullNameWithJoiner("_") + + " is present, these reactions could schedule it: "; + String triggerStr = ""; + for (var reaction : dependsOnReactions) { + comment += reaction.getFullNameWithJoiner("_") + ", "; + triggerStr += String.join("\n", + "// " + reaction.getFullNameWithJoiner("_"), + "&& (" + action.getFullNameWithJoiner("_") + "_is_present(t(i)) ==> (", + " finite_exists (j : integer) in indices :: j >= START && j < i", + " && rxn(j) == " + reaction.getFullNameWithJoiner("_"), + " && g(i) == tag_schedule(g(j), " + + (action.getMinDelay() == TimeValue.ZERO ? "mstep()" : "nsec(" + action.getMinDelay().toNanoSeconds() + ")") + ")" + ); + } + + // After populating the string segments, + // print the generated code string. + code.pr(String.join("\n", + comment, + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> ( true", + triggerStr, + "))" + )); + + // If the action is not present, then its value resets to 0. + // FIXME: Check if this works in practice. + code.pr(String.join("\n", + "// If " + action.getFullNameWithJoiner("_") + " is not present, then its value resets to 0.", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", + " (!" + action.getFullNameWithJoiner("_") + "_is_present(t(i)) ==> (", + " " + action.getFullNameWithJoiner("_") + "(s(i)) == 0", + " ))", + "))" + )); + } + } // FIXME: Factor the startup trigger here as well. // code.pr(String.join("\n", @@ -649,7 +702,7 @@ protected void generateInitialConditions() { for (var v : this.stateVariables) { code.pr("&& " + v + "(s(0)) == 0"); } - for (var t : this.triggers) { + for (var t : this.triggerInstances) { code.pr("&& !" + t.getFullNameWithJoiner("_") + "_is_present" + "(t(0))"); } code.unindent(); @@ -741,12 +794,17 @@ private void populateDataStructures() { // Collect reactions from the reaction graph. this.reactionInstances = this.reactionInstanceGraph.nodes(); - // Populate lists + // Populate lists of state variables, actions, ports, and timers. populateLists(this.main); + // Join actions, ports, and timers into a list of triggers. + this.triggerInstances = new ArrayList(this.actionInstances); + this.triggerInstances.addAll(portInstances); + this.triggerInstances.addAll(timerInstances); + // Join state variables and triggers - this.stateVarsAndTriggers = new ArrayList(this.stateVariables); - stateVarsAndTriggers.addAll(this.triggers); + this.namedInstances = new ArrayList(this.stateVariables); + namedInstances.addAll(this.triggerInstances); } private void populateLists(ReactorInstance reactor) { @@ -755,16 +813,16 @@ private void populateLists(ReactorInstance reactor) { this.stateVariables.add(state); } for (var action : reactor.actions) { - this.triggers.add(action); + this.actionInstances.add(action); } for (var port : reactor.inputs) { - this.triggers.add(port); + this.portInstances.add(port); } for (var port : reactor.outputs) { - this.triggers.add(port); + this.portInstances.add(port); } for (var timer : reactor.timers) { - this.triggers.add(timer); + this.timerInstances.add(timer); } // Recursion for (var child : reactor.children) { From 49d11e3157fc570452660457def8bcb0dff903ba Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 1 Jul 2022 15:53:20 -0400 Subject: [PATCH 017/516] Deprecate reaction instance graph --- .../generator/uclid/UclidGenerator.java | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 20d906f4ac..283d3ca6f5 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -67,10 +67,8 @@ public class UclidGenerator extends GeneratorBase { //// Private variables // Data structures for storing info about the runtime instances. - Set reactionInstances; - - // The reaction graph upon which the causality graph is built - ReactionInstanceGraph reactionInstanceGraph; + List reactorInstances = new ArrayList(); + List reactionInstances = new ArrayList(); // State variables in the system List stateVariables = new ArrayList(); @@ -551,14 +549,15 @@ protected void generateReactorSemantics() { )); code.indent(); // Get happen-before relation between two reactions. - // FIXME: Can we get this from the reaction instance graph? code.pr("|| (tag_same(e1._2, e2._2) && ( false"); // Iterate over every pair of reactions. - for (var upstream : this.reactionInstanceGraph.nodes()) { - var downstreamList = this.reactionInstanceGraph - .getDownstreamAdjacentNodes(upstream); - for (var downstream : downstreamList) { - code.pr("|| (e1._1 == " + upstream.getFullNameWithJoiner("_") + " && e2._1 == " + downstream.getFullNameWithJoiner("_") + ")"); + for (var upstreamRuntime : this.reactionInstances) { + var downstreamReactions = upstreamRuntime.getReaction().dependentReactions(); + for (var downstream : downstreamReactions) { + for (var downstreamRuntime : downstream.getRuntimeInstances()) { + code.pr("|| (e1._1 == " + upstreamRuntime.getFullNameWithJoiner("_") + + " && e2._1 == " + downstreamRuntime.getFullNameWithJoiner("_") + ")"); + } } } code.unindent(); @@ -575,6 +574,11 @@ protected void generateTriggersAndReactions() { " * Connections *", " ***************/" )); + // Generate a set of constraints for each pair of ports connected. + // Iterate over the list of reactors, find the set of all pairs of + // port instances that are connected, and produce an axiom for each pair. + // FIXME: Support banks and multiports. Figure out how to work with ranges. + if (this.actionInstances.size() > 0) { code.pr(String.join("\n", @@ -789,12 +793,13 @@ private void setUpDirectories() { */ private void populateDataStructures() { // Construct graphs - this.reactionInstanceGraph = new ReactionInstanceGraph(this.main, false); + // this.reactionInstanceGraph = new ReactionInstanceGraph(this.main, false); // Collect reactions from the reaction graph. - this.reactionInstances = this.reactionInstanceGraph.nodes(); + // this.reactionInstances = this.reactionInstanceGraph.nodes(); - // Populate lists of state variables, actions, ports, and timers. + // Populate lists of reactor/reaction instances, + // state variables, actions, ports, and timers. populateLists(this.main); // Join actions, ports, and timers into a list of triggers. @@ -808,6 +813,12 @@ private void populateDataStructures() { } private void populateLists(ReactorInstance reactor) { + // Reactor and reaction instances + this.reactorInstances.add(reactor); + for (var reaction : reactor.reactions) { + this.reactionInstances.addAll(reaction.getRuntimeInstances()); + } + // State variables, actions, ports, timers. for (var state : reactor.states) { this.stateVariables.add(state); From 646ba727a4f7a89ed788b28051401e23df289f0f Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 1 Jul 2022 20:05:13 -0400 Subject: [PATCH 018/516] Generate connections --- .../generator/uclid/UclidGenerator.java | 96 ++++++++++++++++--- 1 file changed, 85 insertions(+), 11 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 283d3ca6f5..5499ab8511 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -33,6 +33,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.nio.file.Files; import java.util.ArrayList; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -48,15 +49,22 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactionInstanceGraph; import org.lflang.generator.ReactorInstance; +import org.lflang.generator.RuntimeRange; +import org.lflang.generator.SendRange; import org.lflang.generator.StateVariableInstance; import org.lflang.generator.TargetTypes; import org.lflang.generator.TimerInstance; import org.lflang.generator.TriggerInstance; +import org.lflang.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.Target; +import org.lflang.TimeUnit; import org.lflang.TimeValue; import org.lflang.lf.Action; +import org.lflang.lf.Connection; +import org.lflang.lf.Expression; +import org.lflang.lf.Time; import org.lflang.lf.VarRef; import static org.lflang.ASTUtils.*; @@ -574,11 +582,83 @@ protected void generateTriggersAndReactions() { " * Connections *", " ***************/" )); - // Generate a set of constraints for each pair of ports connected. - // Iterate over the list of reactors, find the set of all pairs of - // port instances that are connected, and produce an axiom for each pair. // FIXME: Support banks and multiports. Figure out how to work with ranges. + // Iterate over all the ports. Generate an axiom for each connection + // (i.e. a pair of input-output ports). + // A "range" holds the connection information. + // See connectPortInstances() in ReactorInstance.java for more details. + for (var port : this.portInstances) { + for (SendRange range : port.getDependentPorts()) { + PortInstance source = range.instance; + Connection connection = range.connection; + List> destinations = range.destinations; + + // Extract delay value + long delay = 0; + if (connection.getDelay() != null) { + // Somehow delay is an Expression, + // which makes it hard to convert to nanoseconds. + Expression delayExpr = connection.getDelay(); + if (delayExpr instanceof Time) { + long interval = ((Time) delayExpr).getInterval(); + String unit = ((Time) delayExpr).getUnit(); + TimeValue timeValue = new TimeValue(interval, TimeUnit.fromName(unit)); + delay = timeValue.toNanoSeconds(); + } + } + for (var portRange : destinations) { + var destination = portRange.instance; + + // We have extracted the source, destination, and connection AST node. + // Now we are ready to generate an axiom for this triple. + code.pr("// " + source.getFullNameWithJoiner("_") + " " + + (connection.isPhysical() ? "~>" : "->") + " " + + destination.getFullNameWithJoiner("_")); + code.pr("axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> ("); + code.pr(String.join("\n", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", + "// If " + source.getFullNameWithJoiner("_") + " is present, then " + + destination.getFullNameWithJoiner("_") + " will be present.", + "// with the same value after some fixed delay of " + delay + " nanoseconds.", + "(" + source.getFullNameWithJoiner("_") + "(t(i)) ==> ((", + " finite_exists (j : integer) in indices :: j > i && j <= END", + " && " + destination.getFullNameWithJoiner("_") + "_is_present(t(j))", + " && " + destination.getFullNameWithJoiner("_") + "(s(j)) == " + source.getFullNameWithJoiner("_") + "(s(i))", + connection.isPhysical() ? "" : "&& g(j) == tag_schedule(g(i), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")", + ")||(", + // Relaxation axioms: a port presence can not produce a downstream presence + // but it needs to guarantee that there are no trailing NULL events. + // FIXME: !«downstreamPortIsPresent»(t(k)) makes the solver timeout. + "(finite_forall (k : integer) in indices :: (k > i && k <= END) ==> (rxn(k) != NULL", + " && " + destination.getFullNameWithJoiner("_") + "(s(k)) == " + source.getFullNameWithJoiner("_") + "(s(i))", + connection.isPhysical() ? "" : " && (tag_same(g(k), tag_schedule(g(i), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")) || tag_earlier(g(k), tag_schedule(g(i), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")))", + ")) // Closes forall.", + ") // Closes ||", + ")) // Closes (" + source.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> ((.", + "// If " + destination.getFullNameWithJoiner("_") + " is present, there exists an " + source.getFullNameWithJoiner("_") + " in prior steps.", + "// This additional term establishes a one-to-one relationship between two ports' signals.", + "&& (" + destination.getFullNameWithJoiner("_") + "(t(i)) ==> (", + " finite_exists (j : integer) in indices :: j >= START && j < i", + " && " + source.getFullNameWithJoiner("_") + "(t(j))", + connection.isPhysical() ? "" : " && g(i) == tag_schedule(g(j), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")", + ")) // Closes the one-to-one relationship.", + "));" + )); + + // If destination is not present, then its value resets to 0. + // FIXME: Check if this works in practice. + code.pr(String.join("\n", + "// If " + destination.getFullNameWithJoiner("_") + " is not present, then its value resets to 0.", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", + " (!" + destination.getFullNameWithJoiner("_") + "_is_present(t(i)) ==> (", + " " + destination.getFullNameWithJoiner("_") + "(s(i)) == 0", + " ))", + "));" + )); + } + } + } if (this.actionInstances.size() > 0) { code.pr(String.join("\n", @@ -609,7 +689,7 @@ protected void generateTriggersAndReactions() { comment, "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> ( true", triggerStr, - "))" + "));" )); // If the action is not present, then its value resets to 0. @@ -620,7 +700,7 @@ protected void generateTriggersAndReactions() { " (!" + action.getFullNameWithJoiner("_") + "_is_present(t(i)) ==> (", " " + action.getFullNameWithJoiner("_") + "(s(i)) == 0", " ))", - "))" + "));" )); } } @@ -792,12 +872,6 @@ private void setUpDirectories() { * Populate the data structures. */ private void populateDataStructures() { - // Construct graphs - // this.reactionInstanceGraph = new ReactionInstanceGraph(this.main, false); - - // Collect reactions from the reaction graph. - // this.reactionInstances = this.reactionInstanceGraph.nodes(); - // Populate lists of reactor/reaction instances, // state variables, actions, ports, and timers. populateLists(this.main); From 13db2db16f6d71e11fe5ce859934c0ab8f04c670 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sat, 2 Jul 2022 13:08:25 -0400 Subject: [PATCH 019/516] Bug fix --- .../generator/uclid/UclidGenerator.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 5499ab8511..1b318b6ffc 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -615,15 +615,14 @@ protected void generateTriggersAndReactions() { code.pr("// " + source.getFullNameWithJoiner("_") + " " + (connection.isPhysical() ? "~>" : "->") + " " + destination.getFullNameWithJoiner("_")); - code.pr("axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> ("); code.pr(String.join("\n", "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", "// If " + source.getFullNameWithJoiner("_") + " is present, then " + destination.getFullNameWithJoiner("_") + " will be present.", "// with the same value after some fixed delay of " + delay + " nanoseconds.", - "(" + source.getFullNameWithJoiner("_") + "(t(i)) ==> ((", + "(" + source.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> ((", " finite_exists (j : integer) in indices :: j > i && j <= END", - " && " + destination.getFullNameWithJoiner("_") + "_is_present(t(j))", + " && " + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(j))", " && " + destination.getFullNameWithJoiner("_") + "(s(j)) == " + source.getFullNameWithJoiner("_") + "(s(i))", connection.isPhysical() ? "" : "&& g(j) == tag_schedule(g(i), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")", ")||(", @@ -638,9 +637,9 @@ protected void generateTriggersAndReactions() { ")) // Closes (" + source.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> ((.", "// If " + destination.getFullNameWithJoiner("_") + " is present, there exists an " + source.getFullNameWithJoiner("_") + " in prior steps.", "// This additional term establishes a one-to-one relationship between two ports' signals.", - "&& (" + destination.getFullNameWithJoiner("_") + "(t(i)) ==> (", + "&& (" + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", " finite_exists (j : integer) in indices :: j >= START && j < i", - " && " + source.getFullNameWithJoiner("_") + "(t(j))", + " && " + source.getFullNameWithJoiner("_") + "_is_present" + "(t(j))", connection.isPhysical() ? "" : " && g(i) == tag_schedule(g(j), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")", ")) // Closes the one-to-one relationship.", "));" @@ -649,9 +648,9 @@ protected void generateTriggersAndReactions() { // If destination is not present, then its value resets to 0. // FIXME: Check if this works in practice. code.pr(String.join("\n", - "// If " + destination.getFullNameWithJoiner("_") + " is not present, then its value resets to 0.", + "// If " + destination.getFullNameWithJoiner("_") + " is not present, then its value resets to 0.", "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", - " (!" + destination.getFullNameWithJoiner("_") + "_is_present(t(i)) ==> (", + " (!" + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", " " + destination.getFullNameWithJoiner("_") + "(s(i)) == 0", " ))", "));" @@ -675,7 +674,7 @@ protected void generateTriggersAndReactions() { comment += reaction.getFullNameWithJoiner("_") + ", "; triggerStr += String.join("\n", "// " + reaction.getFullNameWithJoiner("_"), - "&& (" + action.getFullNameWithJoiner("_") + "_is_present(t(i)) ==> (", + "&& (" + action.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", " finite_exists (j : integer) in indices :: j >= START && j < i", " && rxn(j) == " + reaction.getFullNameWithJoiner("_"), " && g(i) == tag_schedule(g(j), " @@ -697,7 +696,7 @@ protected void generateTriggersAndReactions() { code.pr(String.join("\n", "// If " + action.getFullNameWithJoiner("_") + " is not present, then its value resets to 0.", "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", - " (!" + action.getFullNameWithJoiner("_") + "_is_present(t(i)) ==> (", + " (!" + action.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", " " + action.getFullNameWithJoiner("_") + "(s(i)) == 0", " ))", "));" @@ -745,7 +744,7 @@ protected void generateTriggersAndReactions() { } else { // If the trigger is a port/action/timer. - triggerPresentStr = trigger.getFullNameWithJoiner("_") + "_is_present(t(i))"; + triggerPresentStr = trigger.getFullNameWithJoiner("_") + "_is_present" + "(t(i))"; } // Check if the trigger triggers other reactions. From 60c57009e2571d5a8227e80b4ea905e34b104604 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sat, 2 Jul 2022 15:24:52 -0400 Subject: [PATCH 020/516] Start a C subset target --- org.lflang/src/org/lflang/Target.java | 6 +++--- .../src/org/lflang/generator/LFGenerator.java | 2 +- .../org/lflang/generator/uclid/UclidGenerator.java | 13 +++++++++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/org.lflang/src/org/lflang/Target.java b/org.lflang/src/org/lflang/Target.java index 9b759425c3..fad20495bb 100644 --- a/org.lflang/src/org/lflang/Target.java +++ b/org.lflang/src/org/lflang/Target.java @@ -351,7 +351,7 @@ public enum Target { // with the syntax r#keyword. Arrays.asList("self", "true", "false") ), - Uclid("Uclid", true, + CS("CS", true, // Use an empty list as a placeholder. Arrays.asList("") ); @@ -478,7 +478,7 @@ public boolean supportsMultiports() { case Python: case Rust: case TS: - case Uclid: + case CS: return true; } return false; @@ -498,7 +498,7 @@ public boolean supportsParameterizedWidths() { case Python: case Rust: case TS: - case Uclid: + case CS: return true; } return false; diff --git a/org.lflang/src/org/lflang/generator/LFGenerator.java b/org.lflang/src/org/lflang/generator/LFGenerator.java index c8763a19ec..069940d745 100644 --- a/org.lflang/src/org/lflang/generator/LFGenerator.java +++ b/org.lflang/src/org/lflang/generator/LFGenerator.java @@ -93,7 +93,7 @@ private GeneratorBase createGenerator(Target target, FileConfig fileConfig, Erro case TS: case Rust: return createKotlinBaseGenerator(target, fileConfig, errorReporter); - case Uclid: return new UclidGenerator(fileConfig, errorReporter); + case CS: return new UclidGenerator(fileConfig, errorReporter); } // If no case matched, then throw a runtime exception. throw new RuntimeException("Unexpected target!"); diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 1b318b6ffc..b3c51e2f2a 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -231,7 +231,7 @@ protected void generateUclidCode(int CT) { // generateReactionAbstractions(); // FIXME: Properties - // generateProperties(); + generateProperty(); // Control block generateControlBlock(); @@ -806,6 +806,15 @@ protected void generateInitialConditions() { // } + protected void generateProperty() { + code.pr(String.join("\n", + "/************", + " * Property *", + " ************/" + )); + + } + /** * FIXME */ @@ -919,7 +928,7 @@ private void populateLists(ReactorInstance reactor) { @Override public Target getTarget() { - return Target.C; // FIXME: How to make this target independent? Target.ALL does not work. + return Target.CS; // CS stands for "C Subset." } @Override From 8baa6689666ad47d3936da862abdbc1a3b3a028c Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sat, 2 Jul 2022 15:53:14 -0400 Subject: [PATCH 021/516] Remove the super.doGenerate() call. --- .../src/org/lflang/generator/GeneratorBase.java | 2 +- .../org/lflang/generator/uclid/UclidGenerator.java | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.java b/org.lflang/src/org/lflang/generator/GeneratorBase.java index debfcd8334..ec9a6085f4 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.java +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.java @@ -269,7 +269,7 @@ public Reactor findDelayClass(String className) { * 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() { + protected void createMainInstantiation() { // Find the main reactor and create an AST node for its instantiation. Iterable nodes = IteratorExtensions.toIterable(fileConfig.resource.getAllContents()); for (Reactor reactor : Iterables.filter(nodes, Reactor.class)) { diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index b3c51e2f2a..191e03d178 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -43,6 +43,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.ActionInstance; import org.lflang.generator.CodeBuilder; import org.lflang.generator.GeneratorBase; +import org.lflang.generator.GeneratorUtils; import org.lflang.generator.LFGeneratorContext; import org.lflang.generator.NamedInstance; import org.lflang.generator.PortInstance; @@ -109,8 +110,15 @@ public UclidGenerator(FileConfig fileConfig, ErrorReporter errorReporter) { //// Public methods public void doGenerate(Resource resource, LFGeneratorContext context) { - // The following generates code needed by all the reactors. - super.doGenerate(resource, context); + // Inherit parts from super.doGenerate() to instantiate the main instance. + GeneratorUtils.setTargetConfig( + context, GeneratorUtils.findTarget(fileConfig.resource), targetConfig, errorReporter + ); + super.cleanIfNeeded(context); + super.printInfo(context.getMode()); + ASTUtils.setMainName(fileConfig.resource, fileConfig.name); + super.createMainInstantiation(); + //////////////////////////////////////// // Check for the specified k-induction steps, otherwise defaults to 1. // FIXME: To enable. From faa52031eed907e92bcfacbad8e0a3e6a8dafdfb Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sat, 2 Jul 2022 17:24:52 -0400 Subject: [PATCH 022/516] Remove the new target. Invoke the uclid generator when @property is in use. --- org.lflang/src/org/lflang/ASTUtils.java | 14 ++++++++++++++ org.lflang/src/org/lflang/Target.java | 6 ------ .../src/org/lflang/generator/LFGenerator.java | 17 ++++++++++++++++- .../lflang/generator/uclid/UclidGenerator.java | 2 +- .../org/lflang/validation/AttributeSpec.java | 7 +++++++ 5 files changed, 38 insertions(+), 8 deletions(-) diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index dc15877879..45d466670a 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -144,6 +144,20 @@ public static Iterable getAllReactors(Resource resource) { .collect(Collectors.toList()); } + /** + * Get the main reactor 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 Reactor getMainReactor(Resource resource) { + return StreamSupport.stream(IteratorExtensions.toIterable(resource.getAllContents()).spliterator(), false) + .filter(Reactor.class::isInstance) + .map(Reactor.class::cast) + .filter(it -> it.isMain()) + .findFirst() + .get(); + } + /** * 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/Target.java b/org.lflang/src/org/lflang/Target.java index fad20495bb..84a2711311 100644 --- a/org.lflang/src/org/lflang/Target.java +++ b/org.lflang/src/org/lflang/Target.java @@ -350,10 +350,6 @@ public enum Target { // are those that are a valid expression. Others may be escaped // with the syntax r#keyword. Arrays.asList("self", "true", "false") - ), - CS("CS", true, - // Use an empty list as a placeholder. - Arrays.asList("") ); /** @@ -478,7 +474,6 @@ public boolean supportsMultiports() { case Python: case Rust: case TS: - case CS: return true; } return false; @@ -498,7 +493,6 @@ public boolean supportsParameterizedWidths() { case Python: case Rust: case TS: - case CS: return true; } return false; diff --git a/org.lflang/src/org/lflang/generator/LFGenerator.java b/org.lflang/src/org/lflang/generator/LFGenerator.java index 069940d745..6d2318099d 100644 --- a/org.lflang/src/org/lflang/generator/LFGenerator.java +++ b/org.lflang/src/org/lflang/generator/LFGenerator.java @@ -4,8 +4,10 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.nio.file.Path; +import java.util.List; import java.util.Objects; +import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.generator.AbstractGenerator; import org.eclipse.xtext.generator.IFileSystemAccess2; @@ -13,12 +15,15 @@ import org.eclipse.xtext.util.RuntimeIOException; import org.lflang.ASTUtils; +import org.lflang.AttributeUtils; import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.Target; import org.lflang.generator.c.CGenerator; import org.lflang.generator.python.PythonGenerator; import org.lflang.generator.uclid.UclidGenerator; +import org.lflang.lf.Attribute; +import org.lflang.lf.Reactor; import org.lflang.scoping.LFGlobalScopeProvider; import com.google.inject.Inject; @@ -93,7 +98,6 @@ private GeneratorBase createGenerator(Target target, FileConfig fileConfig, Erro case TS: case Rust: return createKotlinBaseGenerator(target, fileConfig, errorReporter); - case CS: return new UclidGenerator(fileConfig, errorReporter); } // If no case matched, then throw a runtime exception. throw new RuntimeException("Unexpected target!"); @@ -162,6 +166,17 @@ public void doGenerate(Resource resource, IFileSystemAccess2 fsa, final ErrorReporter errorReporter = lfContext.constructErrorReporter(fileConfig); final GeneratorBase generator = createGenerator(target, fileConfig, errorReporter); + // Check if @property is used. If so, include UclidGenerator. + Reactor main = ASTUtils.getMainReactor(resource); + List attributes = AttributeUtils.getAttributes(main); + boolean propertyFound = + attributes.stream() + .anyMatch(attr -> attr.getAttrName().equals("property")); + if (propertyFound) { + UclidGenerator uclidGenerator = new UclidGenerator(fileConfig, errorReporter); + uclidGenerator.doGenerate(resource, lfContext); + } + if (generator != null) { generator.doGenerate(resource, lfContext); generatorErrorsOccurred = generator.errorsOccurred(); diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 191e03d178..bc1c0c60a2 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -936,7 +936,7 @@ private void populateLists(ReactorInstance reactor) { @Override public Target getTarget() { - return Target.CS; // CS stands for "C Subset." + return Target.C; // Works with a C subset. } @Override diff --git a/org.lflang/src/org/lflang/validation/AttributeSpec.java b/org.lflang/src/org/lflang/validation/AttributeSpec.java index 766621e595..f64c678248 100644 --- a/org.lflang/src/org/lflang/validation/AttributeSpec.java +++ b/org.lflang/src/org/lflang/validation/AttributeSpec.java @@ -213,6 +213,13 @@ enum AttrParamType { // @icon("value") ATTRIBUTE_SPECS_BY_NAME.put("icon", new AttributeSpec( List.of(new AttrParamSpec(AttributeSpec.VALUE_ATTR, AttrParamType.STRING, null)) + // @property(name="", tactic="", spec="") + ATTRIBUTE_SPECS_BY_NAME.put("property", new AttributeSpec( + List.of( + new AttrParamSpec("name", AttrParamType.STRING, null), + new AttrParamSpec("tactic", AttrParamType.STRING, null), + new AttrParamSpec("spec", AttrParamType.STRING, null) + ) )); } } From 7d79196c33b116fa99f9f0826dbe9e0141f23b0e Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sat, 2 Jul 2022 17:25:07 -0400 Subject: [PATCH 023/516] Add an instance class for state variables --- .../generator/StateVariableInstance.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 org.lflang/src/org/lflang/generator/StateVariableInstance.java diff --git a/org.lflang/src/org/lflang/generator/StateVariableInstance.java b/org.lflang/src/org/lflang/generator/StateVariableInstance.java new file mode 100644 index 0000000000..9ec7163cac --- /dev/null +++ b/org.lflang/src/org/lflang/generator/StateVariableInstance.java @@ -0,0 +1,79 @@ +/** A data structure for a state variable. */ + +/************* +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 org.lflang.ErrorReporter; +import org.lflang.lf.StateVar; + +/** + * Representation of a compile-time instance of a state variable. + * + * @author{Shaokai Lin } + */ +public class StateVariableInstance extends NamedInstance { + + /** + * Create a runtime instance from the specified definition + * and with the specified parent that instantiated it. + * @param definition The declaration in the AST. + * @param parent The parent. + */ + public StateVariableInstance(StateVar definition, ReactorInstance parent) { + this(definition, parent, null); + } + + /** + * Create a port instance from the specified definition + * and with the specified parent that instantiated it. + * @param definition The declaration in the AST. + * @param parent The parent. + * @param errorReporter An error reporter, or null to throw exceptions. + */ + public StateVariableInstance(StateVar definition, ReactorInstance parent, ErrorReporter errorReporter) { + super(definition, parent); + + if (parent == null) { + throw new NullPointerException("Cannot create a StateVariableInstance with no parent."); + } + } + + ////////////////////////////////////////////////////// + //// Public methods + + /** + * Return the name of this trigger. + * @return The name of this trigger. + */ + @Override + public String getName() { + return definition.getName(); + } + + @Override + public String toString() { + return "StateVariableInstance " + getFullName(); + } +} From f194a0102100df433240563cb51420e96e84356d Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 6 Jul 2022 12:43:54 -0400 Subject: [PATCH 024/516] Add Antlr4 to the gradle project --- build.gradle | 11 +++- org.lflang/build.gradle | 18 ++++++ org.lflang/src/main/antlr4/MTLLexer.g4 | 81 +++++++++++++++++++++++++ org.lflang/src/main/antlr4/MTLParser.g4 | 55 +++++++++++++++++ 4 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 org.lflang/src/main/antlr4/MTLLexer.g4 create mode 100644 org.lflang/src/main/antlr4/MTLParser.g4 diff --git a/build.gradle b/build.gradle index 6155f298d7..c011eb2c8c 100644 --- a/build.gradle +++ b/build.gradle @@ -43,7 +43,6 @@ subprojects { implementation group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlinVersion } - apply plugin: 'org.xtext.xtend' apply from: "${rootDir}/gradle/source-layout.gradle" apply plugin: 'eclipse' @@ -63,6 +62,16 @@ subprojects { project.logger.info("Deleting ${projectDir}/src-gen") delete "${projectDir}/src-gen/" } + + // Antlr4 + configurations { + antlr4 + } + + dependencies { + antlr4 'org.antlr:antlr4:4.7.2' + implementation 'org.antlr:antlr4-runtime:4.7.2' + } } // Our CI uses --tests filters, which fails if some diff --git a/org.lflang/build.gradle b/org.lflang/build.gradle index a76e26f6fc..09bde4cb64 100644 --- a/org.lflang/build.gradle +++ b/org.lflang/build.gradle @@ -133,3 +133,21 @@ task runLff(type: JavaExec) { mainClass = 'org.lflang.cli.Lff' workingDir = '..' } + +// Add Antlr4 for various DSLs, including MTL for verification. +task runAntlr4(type:JavaExec) { + //see incremental task api, prevents rerun if nothing has changed. + inputs.dir "$projectDir/src/main/antlr4/" + outputs.dir "$projectDir/build/generated/antlr/main/" + + classpath = configurations.antlr4 + + main = "org.antlr.v4.Tool" + + args = [ "-visitor", + "-o", "$projectDir/build/generated/antlr/main/", + "-package", "org.lflang", + "$projectDir/src/main/antlr4/MTLLexer.g4", + "$projectDir/src/main/antlr4/MTLParser.g4"] +} +compileJava.dependsOn(runAntlr4) diff --git a/org.lflang/src/main/antlr4/MTLLexer.g4 b/org.lflang/src/main/antlr4/MTLLexer.g4 new file mode 100644 index 0000000000..c858809044 --- /dev/null +++ b/org.lflang/src/main/antlr4/MTLLexer.g4 @@ -0,0 +1,81 @@ +lexer grammar MTLLexer; + +COMMA + : ',' + ; + +LPAREN + : '(' + ; + +RPAREN + : ')' + ; + +LBRACKET + : '[' + ; + +RBRACKET + : ']' + ; + +LAND + : '&&' + ; + +LOR + : '||' + ; + +EQUI + : '<==>' + ; + +IMPL + : '==>' + ; + +UNTIL + : 'U' + ; + +NEGATION + : '!' + ; + +NEXT + : 'X' + ; + +GLOBALLY + : 'G' + ; + +FINALLY + : 'F' + ; + +WS + : [ \t\r\n]+ -> skip + ; + +TRUE + : 'true' + ; + +FALSE + : 'false' + ; + +ZERO + : '0' + ; + +INTEGER + : [0-9][0-9]* + ; + +ID + : ([a-zA-Z0-9]|'_')+ + ; \ No newline at end of file diff --git a/org.lflang/src/main/antlr4/MTLParser.g4 b/org.lflang/src/main/antlr4/MTLParser.g4 new file mode 100644 index 0000000000..a9b84c04d2 --- /dev/null +++ b/org.lflang/src/main/antlr4/MTLParser.g4 @@ -0,0 +1,55 @@ +parser grammar MTLParser; + +options { tokenVocab=MTLLexer; } + +mtl + : equivalence + ; + +equivalence + : left=implication ( EQUI right=implication )? + ; + +implication + : left=disjunction ( IMPL right=disjunction )? + ; + +disjunction + : terms+=conjunction ( LOR terms+=conjunction )* + ; + +conjunction + : terms+=binaryOp ( LAND terms+=binaryOp )* + ; + +binaryOp + : left=unaryOp (UNTIL timeInterval=interval right=unaryOp)? + ; + +unaryOp + : formula=primary # NoUnaryOp + | NEGATION formula=primary # Negation + | NEXT timeInterval=interval formula=primary # Next + | GLOBALLY timeInterval=interval formula=primary # Globally + | FINALLY timeInterval=interval formula=primary # Finally + ; + +primary + : atom=atomicProp + | id=ID + | LPAREN formula=mtl RPAREN + ; + +atomicProp + : primitive=TRUE + | primitive=FALSE + ; + +interval + : (LPAREN|LBRACKET) lowerbound=time COMMA upperbound=time (RPAREN|RBRACKET) # Range + | LBRACKET instant=time RBRACKET # Singleton + ; + +time + : (ZERO | value=INTEGER unit=ID) + ; From 7c9274cf651f0c448a8864152c473c84b5c67ab1 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 6 Jul 2022 15:54:56 -0400 Subject: [PATCH 025/516] Move Antlr grammar --- org.lflang/build.gradle | 12 ++++++------ .../src/{main => org/lflang/dsl}/antlr4/MTLLexer.g4 | 0 .../src/{main => org/lflang/dsl}/antlr4/MTLParser.g4 | 0 3 files changed, 6 insertions(+), 6 deletions(-) rename org.lflang/src/{main => org/lflang/dsl}/antlr4/MTLLexer.g4 (100%) rename org.lflang/src/{main => org/lflang/dsl}/antlr4/MTLParser.g4 (100%) diff --git a/org.lflang/build.gradle b/org.lflang/build.gradle index 09bde4cb64..58724e9ead 100644 --- a/org.lflang/build.gradle +++ b/org.lflang/build.gradle @@ -137,17 +137,17 @@ task runLff(type: JavaExec) { // Add Antlr4 for various DSLs, including MTL for verification. task runAntlr4(type:JavaExec) { //see incremental task api, prevents rerun if nothing has changed. - inputs.dir "$projectDir/src/main/antlr4/" - outputs.dir "$projectDir/build/generated/antlr/main/" + inputs.dir "$projectDir/src/org/lflang/dsl/antlr4/" + outputs.dir "$projectDir/build/generated/antlr4/main/" classpath = configurations.antlr4 main = "org.antlr.v4.Tool" args = [ "-visitor", - "-o", "$projectDir/build/generated/antlr/main/", - "-package", "org.lflang", - "$projectDir/src/main/antlr4/MTLLexer.g4", - "$projectDir/src/main/antlr4/MTLParser.g4"] + "-o", "$projectDir/build/generated/antlr4/main/", + "-package", "org.lflang.dsl", + "$projectDir/src/org/lflang/dsl/antlr4/MTLLexer.g4", + "$projectDir/src/org/lflang/dsl/antlr4/MTLParser.g4"] } compileJava.dependsOn(runAntlr4) diff --git a/org.lflang/src/main/antlr4/MTLLexer.g4 b/org.lflang/src/org/lflang/dsl/antlr4/MTLLexer.g4 similarity index 100% rename from org.lflang/src/main/antlr4/MTLLexer.g4 rename to org.lflang/src/org/lflang/dsl/antlr4/MTLLexer.g4 diff --git a/org.lflang/src/main/antlr4/MTLParser.g4 b/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 similarity index 100% rename from org.lflang/src/main/antlr4/MTLParser.g4 rename to org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 From 301ed166660394842be7393fa97a22faed160b99 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 6 Jul 2022 16:10:38 -0400 Subject: [PATCH 026/516] Fix Antlr generated file location, start MTL transpiler --- .gitignore | 4 + org.lflang/build.gradle | 8 +- .../lflang/generator/uclid/MTLTranspiler.java | 78 +++++++++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 org.lflang/src/org/lflang/generator/uclid/MTLTranspiler.java diff --git a/.gitignore b/.gitignore index 5d10393cdb..15b59f18b8 100644 --- a/.gitignore +++ b/.gitignore @@ -149,4 +149,8 @@ gradle-app.setting ### Gradle Patch ### **/build/ +### Antlr 4 ### +**/.antlr/ +**/generated/ + # End of https://www.toptal.com/developers/gitignore/api/intellij,gradle,eclipse,maven,visualstudiocode diff --git a/org.lflang/build.gradle b/org.lflang/build.gradle index 58724e9ead..867660cdf9 100644 --- a/org.lflang/build.gradle +++ b/org.lflang/build.gradle @@ -138,16 +138,20 @@ task runLff(type: JavaExec) { task runAntlr4(type:JavaExec) { //see incremental task api, prevents rerun if nothing has changed. inputs.dir "$projectDir/src/org/lflang/dsl/antlr4/" - outputs.dir "$projectDir/build/generated/antlr4/main/" + outputs.dir "$projectDir/src/org/lflang/dsl/generated/antlr4/main/" classpath = configurations.antlr4 main = "org.antlr.v4.Tool" args = [ "-visitor", - "-o", "$projectDir/build/generated/antlr4/main/", + "-o", "$projectDir/src/org/lflang/dsl/generated/antlr4/main/", "-package", "org.lflang.dsl", "$projectDir/src/org/lflang/dsl/antlr4/MTLLexer.g4", "$projectDir/src/org/lflang/dsl/antlr4/MTLParser.g4"] } compileJava.dependsOn(runAntlr4) + +clean { + delete file("${projectDir}/src/org/lflang/dsl/generated") +} diff --git a/org.lflang/src/org/lflang/generator/uclid/MTLTranspiler.java b/org.lflang/src/org/lflang/generator/uclid/MTLTranspiler.java new file mode 100644 index 0000000000..48aceda73f --- /dev/null +++ b/org.lflang/src/org/lflang/generator/uclid/MTLTranspiler.java @@ -0,0 +1,78 @@ +package org.lflang.generator.uclid; + +import org.lflang.dsl.MTLParser; +import org.lflang.dsl.MTLParserBaseListener; + +public class MTLTranspiler extends MTLParserBaseListener { + + @Override + public void enterMtl(MTLParser.MtlContext ctx) { + System.out.println("Formula: " + ctx.getText()); + } + + @Override + public void enterEquivalence(MTLParser.EquivalenceContext ctx) { + System.out.println("Equivalence: " + ctx.getText()); + } + + @Override + public void enterImplication(MTLParser.ImplicationContext ctx) { + System.out.println("Implication: " + ctx.getText()); + } + + @Override + public void enterDisjunction(MTLParser.DisjunctionContext ctx) { + System.out.println("Disjunction: " + ctx.getText()); + } + + @Override + public void enterConjunction(MTLParser.ConjunctionContext ctx) { + System.out.println("Conjunction: " + ctx.getText()); + } + + @Override + public void enterBinaryOp(MTLParser.BinaryOpContext ctx) { + System.out.println("BinaryOp: " + ctx.getText()); + } + + @Override + public void enterNoUnaryOp(MTLParser.NoUnaryOpContext ctx) { + System.out.println("NoUnaryOp: " + ctx.getText()); + System.out.println("formula within NoUnaryOp: " + ctx.formula.getText()); + } + + @Override + public void enterNegation(MTLParser.NegationContext ctx) { + System.out.println("Negation: " + ctx.getText()); + } + + @Override + public void enterNext(MTLParser.NextContext ctx) { + System.out.println("Next: " + ctx.getText()); + } + + @Override + public void enterGlobally(MTLParser.GloballyContext ctx) { + System.out.println("Globally: " + ctx.getText()); + } + + @Override + public void enterFinally(MTLParser.FinallyContext ctx) { + System.out.println("Finally: " + ctx.getText()); + } + + @Override + public void enterPrimary(MTLParser.PrimaryContext ctx) { + System.out.println("Primary: " + ctx.getText()); + } + + @Override + public void enterAtomicProp(MTLParser.AtomicPropContext ctx) { + System.out.println("AtomicProp: " + ctx.getText()); + } + + @Override + public void enterRange(MTLParser.RangeContext ctx) { + System.out.println("Range: " + ctx.getText()); + } +} \ No newline at end of file From 52040d0cf32b68948590e4665d3058bc613c21e7 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 7 Jul 2022 00:01:39 -0400 Subject: [PATCH 027/516] Start to generate properties based on property attributes --- .../src/org/lflang/generator/LFGenerator.java | 13 ++-- .../generator/uclid/UclidGenerator.java | 63 ++++++++++++------- 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/LFGenerator.java b/org.lflang/src/org/lflang/generator/LFGenerator.java index 6d2318099d..1fbcf99552 100644 --- a/org.lflang/src/org/lflang/generator/LFGenerator.java +++ b/org.lflang/src/org/lflang/generator/LFGenerator.java @@ -6,6 +6,7 @@ import java.nio.file.Path; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; @@ -168,12 +169,12 @@ public void doGenerate(Resource resource, IFileSystemAccess2 fsa, // Check if @property is used. If so, include UclidGenerator. Reactor main = ASTUtils.getMainReactor(resource); - List attributes = AttributeUtils.getAttributes(main); - boolean propertyFound = - attributes.stream() - .anyMatch(attr -> attr.getAttrName().equals("property")); - if (propertyFound) { - UclidGenerator uclidGenerator = new UclidGenerator(fileConfig, errorReporter); + List properties = AttributeUtils.getAttributes(main) + .stream() + .filter(attr -> attr.getAttrName().equals("property")) + .collect(Collectors.toList()); + if (properties.size() > 0) { + UclidGenerator uclidGenerator = new UclidGenerator(fileConfig, errorReporter, properties); uclidGenerator.doGenerate(resource, lfContext); } diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index bc1c0c60a2..499c75a237 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -29,15 +29,18 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY */ package org.lflang.generator.uclid; +import java.io.File; import java.io.IOException; import java.nio.file.Files; - +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; +import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.xbase.lib.Exceptions; import org.lflang.generator.ActionInstance; @@ -63,10 +66,12 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.TimeUnit; import org.lflang.TimeValue; import org.lflang.lf.Action; +import org.lflang.lf.Attribute; import org.lflang.lf.Connection; import org.lflang.lf.Expression; import org.lflang.lf.Time; import org.lflang.lf.VarRef; +import org.w3c.dom.Attr; import static org.lflang.ASTUtils.*; @@ -76,7 +81,7 @@ public class UclidGenerator extends GeneratorBase { //// Private variables // Data structures for storing info about the runtime instances. - List reactorInstances = new ArrayList(); + List reactorInstances = new ArrayList(); List reactionInstances = new ArrayList(); // State variables in the system @@ -92,8 +97,11 @@ public class UclidGenerator extends GeneratorBase { List triggerInstances; // Triggers = ports + actions + timers List namedInstances; // Named instances = triggers + state variables - // Data structures for storing properties - List properties = new ArrayList(); + // A list of MTL properties represented in Attributes. + List properties; + + // The directory where the generated files are placed + Path outputDir; //////////////////////////////////////////// //// Protected fields @@ -102,8 +110,9 @@ public class UclidGenerator extends GeneratorBase { protected CodeBuilder code = new CodeBuilder(); // Constructor - public UclidGenerator(FileConfig fileConfig, ErrorReporter errorReporter) { + public UclidGenerator(FileConfig fileConfig, ErrorReporter errorReporter, List properties) { super(fileConfig, errorReporter); + this.properties = properties; } //////////////////////////////////////////////////////////// @@ -140,13 +149,11 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // FIXME: Identify properties in the attributes. // FIXME: Calculate the completeness threshold for each property. - int CT = 10; // Placeholder. Currently up to ~50. - // Generate a Uclid model for each property. - // for (String prop : this.properties) { - // generateUclidFile(prop); - // } - generateUclidFile("test", "bmc", CT); + for (Attribute prop : this.properties) { + int CT = computeCT(prop); + generateUclidFile(prop, CT); + } // Generate runner script generateRunnerScript(); @@ -158,13 +165,15 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { /** * Generate the Uclid model. */ - protected void generateUclidFile(String property, String tactic, int CT) { + protected void generateUclidFile(Attribute property, int CT) { + String name = property.getAttrParms().get(0).getValue().getStr(); + String tactic = property.getAttrParms().get(1).getValue().getStr(); try { // Generate main.ucl and print to file code = new CodeBuilder(); - String filename = this.fileConfig.getSrcGenPath() - .resolve(tactic + "_" + property + ".ucl").toString(); - generateUclidCode(CT); + String filename = this.outputDir + .resolve(tactic + "_" + name + ".ucl").toString(); + generateUclidCode(property, CT); code.writeToFile(filename); } catch (IOException e) { Exceptions.sneakyThrow(e); @@ -178,7 +187,7 @@ protected void generateRunnerScript() { try { // Generate main.ucl and print to file var script = new CodeBuilder(); - String filename = this.fileConfig.getSrcGenPath() + String filename = this.outputDir .resolve("run.sh").toString(); script.pr(String.join("\n", "#!/bin/bash", @@ -205,7 +214,7 @@ protected void generateRunnerScript() { /** * The main function that generates Uclid code. */ - protected void generateUclidCode(int CT) { + protected void generateUclidCode(Attribute property, int CT) { code.pr(String.join("\n", "/*******************************", " * Auto-generated UCLID5 model *", @@ -238,8 +247,8 @@ protected void generateUclidCode(int CT) { // generateReactorAbstractions(); // generateReactionAbstractions(); - // FIXME: Properties - generateProperty(); + // Properties + generateProperty(property, CT); // Control block generateControlBlock(); @@ -814,7 +823,7 @@ protected void generateInitialConditions() { // } - protected void generateProperty() { + protected void generateProperty(Attribute property, int CT) { code.pr(String.join("\n", "/************", " * Property *", @@ -875,13 +884,14 @@ private void createMainReactorInstance() { private void setUpDirectories() { // Make sure the target directory exists. - var targetDir = this.fileConfig.getSrcGenPath(); + Path srcgenDir = this.fileConfig.getSrcGenPath(); + this.outputDir = Paths.get(srcgenDir.toString() + File.separator + "model"); try { - Files.createDirectories(targetDir); + Files.createDirectories(outputDir); } catch (IOException e) { Exceptions.sneakyThrow(e); } - System.out.println("The models will be located in: " + targetDir); + System.out.println("The models will be located in: " + outputDir); } /** @@ -931,6 +941,13 @@ private void populateLists(ReactorInstance reactor) { } } + /** + * Compute a completeness threadhold for each property. + */ + private int computeCT(Attribute property) { + return 10; // FIXME + } + ///////////////////////////////////////////////// //// Functions from generatorBase From 21efab8273caa25058984f908ef144444f44621b Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 7 Jul 2022 00:23:28 -0400 Subject: [PATCH 028/516] Use the MTLParser to generate parse trees --- .../generator/uclid/UclidGenerator.java | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 499c75a237..1c1ad2bac3 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -40,6 +40,11 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.List; import java.util.Set; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.ParseTreeWalker; + import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.xbase.lib.Exceptions; @@ -65,6 +70,8 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.Target; import org.lflang.TimeUnit; import org.lflang.TimeValue; +import org.lflang.dsl.MTLLexer; +import org.lflang.dsl.MTLParser; import org.lflang.lf.Action; import org.lflang.lf.Attribute; import org.lflang.lf.Connection; @@ -128,11 +135,6 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { ASTUtils.setMainName(fileConfig.resource, fileConfig.name); super.createMainInstantiation(); //////////////////////////////////////// - - // Check for the specified k-induction steps, otherwise defaults to 1. - // FIXME: To enable. - // this.k = this.targetConfig.verification.steps; - // this.tactic = this.targetConfig.verification.tactic; System.out.println("*** Start generating Uclid code."); @@ -166,8 +168,18 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { * Generate the Uclid model. */ protected void generateUclidFile(Attribute property, int CT) { - String name = property.getAttrParms().get(0).getValue().getStr(); - String tactic = property.getAttrParms().get(1).getValue().getStr(); + String name = property.getAttrParms().stream() + .filter(attr -> attr.getName().equals("name")) + .findFirst() + .get() + .getValue() + .getStr(); + String tactic = property.getAttrParms().stream() + .filter(attr -> attr.getName().equals("tactic")) + .findFirst() + .get() + .getValue() + .getStr(); try { // Generate main.ucl and print to file code = new CodeBuilder(); @@ -830,6 +842,18 @@ protected void generateProperty(Attribute property, int CT) { " ************/" )); + String spec = property.getAttrParms().stream() + .filter(attr -> attr.getName().equals("spec")) + .findFirst() + .get() + .getValue() + .getStr(); + MTLLexer lexer = new MTLLexer(CharStreams.fromString(spec)); + CommonTokenStream tokens = new CommonTokenStream(lexer); + MTLParser parser = new MTLParser(tokens); + ParseTree parseTree = parser.mtl(); + ParseTreeWalker walker = new ParseTreeWalker(); + walker.walk(new MTLTranspiler(), parseTree); } /** From 38a31ff451eecd2f25b69b637c23212d4bd84048 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 7 Jul 2022 01:11:03 -0400 Subject: [PATCH 029/516] Update syntax to include expressions --- .../src/org/lflang/dsl/antlr4/MTLLexer.g4 | 40 +++++++++++++++++++ .../src/org/lflang/dsl/antlr4/MTLParser.g4 | 27 +++++++++++++ .../generator/uclid/UclidGenerator.java | 3 +- 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/dsl/antlr4/MTLLexer.g4 b/org.lflang/src/org/lflang/dsl/antlr4/MTLLexer.g4 index c858809044..138aa7742d 100644 --- a/org.lflang/src/org/lflang/dsl/antlr4/MTLLexer.g4 +++ b/org.lflang/src/org/lflang/dsl/antlr4/MTLLexer.g4 @@ -72,6 +72,46 @@ ZERO : '0' ; +PLUS + : '+' + ; + +MINUS + : '-' + ; + +TIMES + : '*' + ; + +DIV + : '/' + ; + +EQ + : '==' + ; + +NEQ + : '!=' + ; + +LT + : '<' + ; + +LE + : '<=' + ; + +GT + : '>' + ; + +GE + : '>=' + ; + INTEGER : [0-9][0-9]* ; diff --git a/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 b/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 index a9b84c04d2..3be2126710 100644 --- a/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 +++ b/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 @@ -43,6 +43,7 @@ primary atomicProp : primitive=TRUE | primitive=FALSE + | left=expr op=relOp right=expr ; interval @@ -53,3 +54,29 @@ interval time : (ZERO | value=INTEGER unit=ID) ; + +sum + : terms+=difference (PLUS terms+=difference)* + ; + +difference + : terms+=product (MINUS terms+=product)* + ; + +product + : terms+=quotient (TIMES terms+=quotient)* + ; + +quotient + : terms+=expr (DIV terms+=expr)* + ; + +relOp + : EQ | NEQ | LT | LE | GT | GE + ; + +expr + : ID + | LPAREN sum RPAREN + | INTEGER + ; diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 1c1ad2bac3..51e2c7eb6b 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -853,7 +853,8 @@ protected void generateProperty(Attribute property, int CT) { MTLParser parser = new MTLParser(tokens); ParseTree parseTree = parser.mtl(); ParseTreeWalker walker = new ParseTreeWalker(); - walker.walk(new MTLTranspiler(), parseTree); + MTLTranspiler transpiler = new MTLTranspiler(); + walker.walk(transpiler, parseTree); } /** From 47ab69656a713e660a38267e08376d533aee4832 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 7 Jul 2022 11:51:50 -0400 Subject: [PATCH 030/516] Start to transpile MTL using a visitor --- org.lflang/build.gradle | 1 + .../lflang/generator/uclid/MTLTranspiler.java | 78 ------------------- .../lflang/generator/uclid/MTLVisitor.java | 71 +++++++++++++++++ .../generator/uclid/UclidGenerator.java | 36 ++++----- 4 files changed, 90 insertions(+), 96 deletions(-) delete mode 100644 org.lflang/src/org/lflang/generator/uclid/MTLTranspiler.java create mode 100644 org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java diff --git a/org.lflang/build.gradle b/org.lflang/build.gradle index 867660cdf9..e7c8ca559a 100644 --- a/org.lflang/build.gradle +++ b/org.lflang/build.gradle @@ -150,6 +150,7 @@ task runAntlr4(type:JavaExec) { "$projectDir/src/org/lflang/dsl/antlr4/MTLLexer.g4", "$projectDir/src/org/lflang/dsl/antlr4/MTLParser.g4"] } +processResources.dependsOn(runAntlr4) compileJava.dependsOn(runAntlr4) clean { diff --git a/org.lflang/src/org/lflang/generator/uclid/MTLTranspiler.java b/org.lflang/src/org/lflang/generator/uclid/MTLTranspiler.java deleted file mode 100644 index 48aceda73f..0000000000 --- a/org.lflang/src/org/lflang/generator/uclid/MTLTranspiler.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.lflang.generator.uclid; - -import org.lflang.dsl.MTLParser; -import org.lflang.dsl.MTLParserBaseListener; - -public class MTLTranspiler extends MTLParserBaseListener { - - @Override - public void enterMtl(MTLParser.MtlContext ctx) { - System.out.println("Formula: " + ctx.getText()); - } - - @Override - public void enterEquivalence(MTLParser.EquivalenceContext ctx) { - System.out.println("Equivalence: " + ctx.getText()); - } - - @Override - public void enterImplication(MTLParser.ImplicationContext ctx) { - System.out.println("Implication: " + ctx.getText()); - } - - @Override - public void enterDisjunction(MTLParser.DisjunctionContext ctx) { - System.out.println("Disjunction: " + ctx.getText()); - } - - @Override - public void enterConjunction(MTLParser.ConjunctionContext ctx) { - System.out.println("Conjunction: " + ctx.getText()); - } - - @Override - public void enterBinaryOp(MTLParser.BinaryOpContext ctx) { - System.out.println("BinaryOp: " + ctx.getText()); - } - - @Override - public void enterNoUnaryOp(MTLParser.NoUnaryOpContext ctx) { - System.out.println("NoUnaryOp: " + ctx.getText()); - System.out.println("formula within NoUnaryOp: " + ctx.formula.getText()); - } - - @Override - public void enterNegation(MTLParser.NegationContext ctx) { - System.out.println("Negation: " + ctx.getText()); - } - - @Override - public void enterNext(MTLParser.NextContext ctx) { - System.out.println("Next: " + ctx.getText()); - } - - @Override - public void enterGlobally(MTLParser.GloballyContext ctx) { - System.out.println("Globally: " + ctx.getText()); - } - - @Override - public void enterFinally(MTLParser.FinallyContext ctx) { - System.out.println("Finally: " + ctx.getText()); - } - - @Override - public void enterPrimary(MTLParser.PrimaryContext ctx) { - System.out.println("Primary: " + ctx.getText()); - } - - @Override - public void enterAtomicProp(MTLParser.AtomicPropContext ctx) { - System.out.println("AtomicProp: " + ctx.getText()); - } - - @Override - public void enterRange(MTLParser.RangeContext ctx) { - System.out.println("Range: " + ctx.getText()); - } -} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java new file mode 100644 index 0000000000..632ecaa82a --- /dev/null +++ b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java @@ -0,0 +1,71 @@ +/************* +Copyright (c) 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. +***************/ + +/** + * Transpiler from an MTL specification to a Uclid axiom. + * + * @author{Shaokai Lin } + */ +package org.lflang.generator.uclid; + +import org.lflang.dsl.MTLParser; +import org.lflang.dsl.MTLParserBaseVisitor; +import org.lflang.generator.CodeBuilder; + +public class MTLVisitor extends MTLParserBaseVisitor { + + //////////////////////////////////////////// + //// Protected fields + + /** The main place to put generated code. */ + protected CodeBuilder code = new CodeBuilder(); + + //////////////////////////////////////////// + //// Public methods + public String visitMtl(MTLParser.MtlContext ctx, + String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + + return visitEquivalence(ctx.equivalence(), + QFPrefix, QFIdx, prevQFIdx, horizon); + } + + public String visitEquivalence(MTLParser.EquivalenceContext ctx, + String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + + if (ctx.right == null) { + return visitImplication(ctx.left, + QFPrefix, QFIdx, prevQFIdx, horizon); + } + return "(" + visitImplication(ctx.left, + QFPrefix, QFIdx, prevQFIdx, horizon) + + ")" + + " <==> " + + "(" + visitImplication(ctx.right) + ")"; + } + + public String visitImplication(MTLParser.ImplicationContext ctx, + String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + return ""; + } +} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 51e2c7eb6b..4ea22cdd9c 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -72,6 +72,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.TimeValue; import org.lflang.dsl.MTLLexer; import org.lflang.dsl.MTLParser; +import org.lflang.dsl.MTLParser.MtlContext; import org.lflang.lf.Action; import org.lflang.lf.Attribute; import org.lflang.lf.Connection; @@ -85,33 +86,30 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY public class UclidGenerator extends GeneratorBase { //////////////////////////////////////////// - //// Private variables + //// Protected fields // Data structures for storing info about the runtime instances. - List reactorInstances = new ArrayList(); - List reactionInstances = new ArrayList(); + protected List reactorInstances = new ArrayList(); + protected List reactionInstances = new ArrayList(); // State variables in the system - List stateVariables = new ArrayList(); + protected List stateVariables = new ArrayList(); // Triggers in the system - List actionInstances = new ArrayList(); - List portInstances = new ArrayList(); - List timerInstances = new ArrayList(); + protected List actionInstances = new ArrayList(); + protected List portInstances = new ArrayList(); + protected List timerInstances = new ArrayList(); // Joint lists of the lists above. // FIXME: This approach currently creates duplications in memory. - List triggerInstances; // Triggers = ports + actions + timers - List namedInstances; // Named instances = triggers + state variables + protected List triggerInstances; // Triggers = ports + actions + timers + protected List namedInstances; // Named instances = triggers + state variables // A list of MTL properties represented in Attributes. - List properties; + protected List properties; // The directory where the generated files are placed - Path outputDir; - - //////////////////////////////////////////// - //// Protected fields + protected Path outputDir; /** The main place to put generated code. */ protected CodeBuilder code = new CodeBuilder(); @@ -851,10 +849,12 @@ protected void generateProperty(Attribute property, int CT) { MTLLexer lexer = new MTLLexer(CharStreams.fromString(spec)); CommonTokenStream tokens = new CommonTokenStream(lexer); MTLParser parser = new MTLParser(tokens); - ParseTree parseTree = parser.mtl(); - ParseTreeWalker walker = new ParseTreeWalker(); - MTLTranspiler transpiler = new MTLTranspiler(); - walker.walk(transpiler, parseTree); + MtlContext mtlCtx = parser.mtl(); + MTLVisitor visitor = new MTLVisitor(); + + // The visitor transpiles the MTL into a Uclid axiom. + String transpiled = visitor.visitMtl(mtlCtx, "i", 0, "0", 0); + code.pr(transpiled); } /** From 511d3a8fe84a2d5bcd34b87c78576710fc7a1a1f Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 8 Jul 2022 00:19:06 -0400 Subject: [PATCH 031/516] Keep working on transpiling --- .../src/org/lflang/dsl/antlr4/MTLParser.g4 | 2 +- .../lflang/generator/uclid/MTLVisitor.java | 148 +++++++++++++++++- .../generator/uclid/UclidGenerator.java | 14 +- 3 files changed, 161 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 b/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 index 3be2126710..8347b6fbae 100644 --- a/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 +++ b/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 @@ -23,7 +23,7 @@ conjunction ; binaryOp - : left=unaryOp (UNTIL timeInterval=interval right=unaryOp)? + : left=unaryOp (UNTIL timeInterval=interval right=unaryOp)? # Until ; unaryOp diff --git a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java index 632ecaa82a..ca520c1851 100644 --- a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java @@ -29,6 +29,8 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY */ package org.lflang.generator.uclid; +import org.lflang.TimeUnit; +import org.lflang.TimeValue; import org.lflang.dsl.MTLParser; import org.lflang.dsl.MTLParserBaseVisitor; import org.lflang.generator.CodeBuilder; @@ -41,6 +43,14 @@ public class MTLVisitor extends MTLParserBaseVisitor { /** The main place to put generated code. */ protected CodeBuilder code = new CodeBuilder(); + /** Tactic to be used to prove the property. */ + protected String tactic; + + // Constructor + public MTLVisitor(String tactic) { + this.tactic = tactic; + } + //////////////////////////////////////////// //// Public methods public String visitMtl(MTLParser.MtlContext ctx, @@ -61,11 +71,147 @@ public String visitEquivalence(MTLParser.EquivalenceContext ctx, QFPrefix, QFIdx, prevQFIdx, horizon) + ")" + " <==> " - + "(" + visitImplication(ctx.right) + ")"; + + "(" + visitImplication(ctx.right, + QFPrefix, QFIdx, prevQFIdx, horizon) + + ")"; } public String visitImplication(MTLParser.ImplicationContext ctx, String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + + if (ctx.right == null) { + return visitDisjunction(ctx.left, + QFPrefix, QFIdx, prevQFIdx, horizon); + } + return "(" + visitDisjunction(ctx.left, + QFPrefix, QFIdx, prevQFIdx, horizon) + + ")" + + " ==> " + + "(" + visitDisjunction(ctx.right, + QFPrefix, QFIdx, prevQFIdx, horizon) + + ")"; + } + + public String visitDisjunction(MTLParser.DisjunctionContext ctx, + String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + + String str = ""; + for (int i = 0; i < ctx.terms.size(); i++) { + str += "(" + + visitConjunction(ctx.terms.get(i), + QFPrefix, QFIdx, prevQFIdx, horizon) + + ")" + + (i == ctx.terms.size()-1 ? "" : "||"); + } + return str; + } + + public String visitConjunction(MTLParser.ConjunctionContext ctx, + String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + + String str = ""; + for (int i = 0; i < ctx.terms.size(); i++) { + str += "(" + + visitUntil((MTLParser.UntilContext)ctx.terms.get(i), + QFPrefix, QFIdx, prevQFIdx, horizon) + + ")" + + (i == ctx.terms.size()-1 ? "" : "&&"); + } + return str; + } + + // A custom dispatch function + public String _visitUnaryOp(MTLParser.UnaryOpContext ctx, + String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + + // FIXME: Is there a more "antlr" way to do dispatch here? + if (ctx instanceof MTLParser.NoUnaryOpContext) { + return visitNoUnaryOp((MTLParser.NoUnaryOpContext)ctx, + QFPrefix, QFIdx, prevQFIdx, horizon); + } + if (ctx instanceof MTLParser.NegationContext) { + return visitNegation((MTLParser.NegationContext)ctx, + QFPrefix, QFIdx, prevQFIdx, horizon); + } + if (ctx instanceof MTLParser.NextContext) { + return visitNext((MTLParser.NextContext)ctx, + QFPrefix, QFIdx, prevQFIdx, horizon); + } + if (ctx instanceof MTLParser.GloballyContext) { + return visitGlobally((MTLParser.GloballyContext)ctx, + QFPrefix, QFIdx, prevQFIdx, horizon); + } + if (ctx instanceof MTLParser.FinallyContext) { + return visitFinally((MTLParser.FinallyContext)ctx, + QFPrefix, QFIdx, prevQFIdx, horizon); + } + + // FIXME: Throw an exception. + return ""; + } + + public String visitUntil(MTLParser.UntilContext ctx, + String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + + // If right is null, continue recursion. + if (ctx.right == null) { + return _visitUnaryOp(ctx.left, + QFPrefix, QFIdx, prevQFIdx, horizon); + } + + String end; + if (this.tactic.equals("induction")) { + end = "(" + QFPrefix + " + N)"; + } else { + end = "END"; + } + + // Otherwise, create the Until formula. + // Check if the time interval is a range or a singleton. + if (ctx.timeInterval instanceof MTLParser.SingletonContext) { + return ""; + } else { + String predicate = ""; + String upperBoundTimeValue = ((MTLParser.RangeContext)ctx.timeInterval).upperbound.value.getText(); + String upperBoundTimeUnit = ((MTLParser.RangeContext)ctx.timeInterval).upperbound.unit.getText(); + TimeValue timeValue = new TimeValue( + Integer.valueOf(upperBoundTimeValue), + TimeUnit.fromName(upperBoundTimeUnit)); + long maxTimeBound = timeValue.toNanoSeconds(); + long currentHorizon = horizon + maxTimeBound; + return "finite_exists (j" + QFIdx + " : integer) in indices :: " + + "j" + QFIdx + " >= " + QFPrefix + " && j" + QFIdx + " <= " + end + + " && (" + _visitUnaryOp(ctx.right, QFPrefix, QFIdx+1, ("j"+QFIdx), currentHorizon) + ")"; + } + } + + public String visitNoUnaryOp(MTLParser.NoUnaryOpContext ctx, + String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + + return ""; + } + + public String visitNegation(MTLParser.NegationContext ctx, + String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + + return ""; + } + + public String visitNext(MTLParser.NextContext ctx, + String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + + return ""; + } + + public String visitGlobally(MTLParser.GloballyContext ctx, + String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + + return ""; + } + + public String visitFinally(MTLParser.FinallyContext ctx, + String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + return ""; } } \ No newline at end of file diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 4ea22cdd9c..212d2de23d 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -840,6 +840,18 @@ protected void generateProperty(Attribute property, int CT) { " ************/" )); + String name = property.getAttrParms().stream() + .filter(attr -> attr.getName().equals("name")) + .findFirst() + .get() + .getValue() + .getStr(); + String tactic = property.getAttrParms().stream() + .filter(attr -> attr.getName().equals("tactic")) + .findFirst() + .get() + .getValue() + .getStr(); String spec = property.getAttrParms().stream() .filter(attr -> attr.getName().equals("spec")) .findFirst() @@ -850,7 +862,7 @@ protected void generateProperty(Attribute property, int CT) { CommonTokenStream tokens = new CommonTokenStream(lexer); MTLParser parser = new MTLParser(tokens); MtlContext mtlCtx = parser.mtl(); - MTLVisitor visitor = new MTLVisitor(); + MTLVisitor visitor = new MTLVisitor(tactic); // The visitor transpiles the MTL into a Uclid axiom. String transpiled = visitor.visitMtl(mtlCtx, "i", 0, "0", 0); From 59b23f5a1003e07132933015b97400cd535f90b5 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 8 Jul 2022 10:42:31 -0400 Subject: [PATCH 032/516] Transpile Until --- .../src/org/lflang/dsl/antlr4/MTLLexer.g4 | 6 +- .../src/org/lflang/dsl/antlr4/MTLParser.g4 | 4 +- .../lflang/generator/uclid/MTLVisitor.java | 98 ++++++++++++++++--- 3 files changed, 88 insertions(+), 20 deletions(-) diff --git a/org.lflang/src/org/lflang/dsl/antlr4/MTLLexer.g4 b/org.lflang/src/org/lflang/dsl/antlr4/MTLLexer.g4 index 138aa7742d..b8fe9c118d 100644 --- a/org.lflang/src/org/lflang/dsl/antlr4/MTLLexer.g4 +++ b/org.lflang/src/org/lflang/dsl/antlr4/MTLLexer.g4 @@ -68,10 +68,6 @@ FALSE : 'false' ; -ZERO - : '0' - ; - PLUS : '+' ; @@ -113,7 +109,7 @@ GE ; INTEGER - : [0-9][0-9]* + : [0-9]+ ; ID diff --git a/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 b/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 index 8347b6fbae..1a5d39b9b4 100644 --- a/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 +++ b/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 @@ -23,7 +23,7 @@ conjunction ; binaryOp - : left=unaryOp (UNTIL timeInterval=interval right=unaryOp)? # Until + : left=unaryOp ( UNTIL timeInterval=interval right=unaryOp )? # Until ; unaryOp @@ -52,7 +52,7 @@ interval ; time - : (ZERO | value=INTEGER unit=ID) + : value=INTEGER (unit=ID)? ; sum diff --git a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java index ca520c1851..23d069c055 100644 --- a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java @@ -169,19 +169,91 @@ public String visitUntil(MTLParser.UntilContext ctx, // Otherwise, create the Until formula. // Check if the time interval is a range or a singleton. if (ctx.timeInterval instanceof MTLParser.SingletonContext) { - return ""; - } else { - String predicate = ""; - String upperBoundTimeValue = ((MTLParser.RangeContext)ctx.timeInterval).upperbound.value.getText(); - String upperBoundTimeUnit = ((MTLParser.RangeContext)ctx.timeInterval).upperbound.unit.getText(); - TimeValue timeValue = new TimeValue( - Integer.valueOf(upperBoundTimeValue), - TimeUnit.fromName(upperBoundTimeUnit)); - long maxTimeBound = timeValue.toNanoSeconds(); - long currentHorizon = horizon + maxTimeBound; - return "finite_exists (j" + QFIdx + " : integer) in indices :: " - + "j" + QFIdx + " >= " + QFPrefix + " && j" + QFIdx + " <= " + end - + " && (" + _visitUnaryOp(ctx.right, QFPrefix, QFIdx+1, ("j"+QFIdx), currentHorizon) + ")"; + MTLParser.SingletonContext singletonCtx = (MTLParser.SingletonContext)ctx.timeInterval; + String timeInstantValue = singletonCtx.instant.value.getText(); + String timeInstantUnit = ""; + long timeInstantNanoSec = 0; + if (!timeInstantValue.equals("0")) { + timeInstantUnit = singletonCtx.instant.unit.getText(); + TimeValue timeValue = new TimeValue( + Integer.valueOf(timeInstantValue), + TimeUnit.fromName(timeInstantUnit)); + timeInstantNanoSec = timeValue.toNanoSeconds(); + } + + String timePredicate = "tag_same(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + timeInstantNanoSec + ")))"; + long currentHorizon = horizon + timeInstantNanoSec; + + return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " + + "j" + QFIdx + " >= " + QFPrefix + " && " + "j" + QFIdx + " <= " + end + + " && " + "(" + _visitUnaryOp(ctx.right, ("j"+QFIdx), QFIdx+1, QFPrefix, currentHorizon) + ")" + + " && " + "(" + "\n" + + "// Time Predicate\n" + + timePredicate + "\n" + + ")" + " && " + "(" + "finite_forall " + "(" + "i" + QFIdx + " : integer) in indices :: " + + "(" + "i" + QFIdx + " >= " + QFPrefix + " && " + "i" + QFIdx + " < " + "j" + QFIdx + ")" + + " ==> " + "(" + _visitUnaryOp(ctx.left, ("i"+QFIdx), QFIdx+1, ("j"+QFIdx), currentHorizon) + ")" + ")"; + } + else { + MTLParser.RangeContext rangeCtx = (MTLParser.RangeContext)ctx.timeInterval; + String lowerBoundTimeValue = rangeCtx.lowerbound.value.getText(); + String lowerBoundTimeUnit = ""; + long lowerBoundNanoSec = 0; + if (!lowerBoundTimeValue.equals("0")) { + lowerBoundTimeUnit = rangeCtx.lowerbound.unit.getText(); + TimeValue lowerTimeValue = new TimeValue( + Integer.valueOf(lowerBoundTimeValue), + TimeUnit.fromName(lowerBoundTimeUnit)); + lowerBoundNanoSec = lowerTimeValue.toNanoSeconds(); + } + + String upperBoundTimeValue = rangeCtx.upperbound.value.getText(); + String upperBoundTimeUnit = ""; + long upperBoundNanoSec = 0; + if (!upperBoundTimeValue.equals("0")) { + upperBoundTimeUnit = rangeCtx.upperbound.unit.getText(); + TimeValue upperTimeValue = new TimeValue( + Integer.valueOf(upperBoundTimeValue), + TimeUnit.fromName(upperBoundTimeUnit)); + upperBoundNanoSec = upperTimeValue.toNanoSeconds(); + } + + String timePredicate = ""; + timePredicate += "("; + if (rangeCtx.LPAREN() != null) { + timePredicate += "tag_later(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + lowerBoundNanoSec + ")))"; + } else { + // FIXME: Check if this can be replaced by a !tag_earlier. + timePredicate += "tag_later(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + lowerBoundNanoSec + ")))" + + " || " + "tag_same(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + lowerBoundNanoSec + ")))"; + } + timePredicate += ") && ("; + if (rangeCtx.RPAREN() != null) { + timePredicate += "tag_earlier(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))"; + } else { + timePredicate += "tag_earlier(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))" + + " || " + "tag_same(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))"; + } + timePredicate += ")"; + + long currentHorizon = horizon + upperBoundNanoSec; + + return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " + + "j" + QFIdx + " >= " + QFPrefix + " && " + "j" + QFIdx + " <= " + end + + " && " + "(" + _visitUnaryOp(ctx.right, ("j"+QFIdx), QFIdx+1, QFPrefix, currentHorizon) + ")" + + " && " + "(" + "\n" + + "// Time Predicate\n" + + timePredicate + "\n" + + ")" + " && " + "(" + "finite_forall " + "(" + "i" + QFIdx + " : integer) in indices :: " + + "(" + "i" + QFIdx + " >= " + QFPrefix + " && " + "i" + QFIdx + " < " + "j" + QFIdx + ")" + + " ==> " + "(" + _visitUnaryOp(ctx.left, ("i"+QFIdx), QFIdx+1, ("j"+QFIdx), currentHorizon) + ")" + ")"; } } From 66edad493d8283aebe195d4e0892ab55069bdf52 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 8 Jul 2022 15:08:43 -0400 Subject: [PATCH 033/516] Refactor timing related visitor helper functions --- .../lflang/generator/uclid/MTLVisitor.java | 199 ++++++++++++------ 1 file changed, 133 insertions(+), 66 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java index 23d069c055..968d195e5b 100644 --- a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java @@ -169,22 +169,9 @@ public String visitUntil(MTLParser.UntilContext ctx, // Otherwise, create the Until formula. // Check if the time interval is a range or a singleton. if (ctx.timeInterval instanceof MTLParser.SingletonContext) { - MTLParser.SingletonContext singletonCtx = (MTLParser.SingletonContext)ctx.timeInterval; - String timeInstantValue = singletonCtx.instant.value.getText(); - String timeInstantUnit = ""; - long timeInstantNanoSec = 0; - if (!timeInstantValue.equals("0")) { - timeInstantUnit = singletonCtx.instant.unit.getText(); - TimeValue timeValue = new TimeValue( - Integer.valueOf(timeInstantValue), - TimeUnit.fromName(timeInstantUnit)); - timeInstantNanoSec = timeValue.toNanoSeconds(); - } - - String timePredicate = "tag_same(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + timeInstantNanoSec + ")))"; + long timeInstantNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); long currentHorizon = horizon + timeInstantNanoSec; - + String timePredicate = generateTimePredicate(timeInstantNanoSec, QFPrefix, prevQFIdx); return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " + "j" + QFIdx + " >= " + QFPrefix + " && " + "j" + QFIdx + " <= " + end + " && " + "(" + _visitUnaryOp(ctx.right, ("j"+QFIdx), QFIdx+1, QFPrefix, currentHorizon) + ")" @@ -196,55 +183,11 @@ public String visitUntil(MTLParser.UntilContext ctx, + " ==> " + "(" + _visitUnaryOp(ctx.left, ("i"+QFIdx), QFIdx+1, ("j"+QFIdx), currentHorizon) + ")" + ")"; } else { - MTLParser.RangeContext rangeCtx = (MTLParser.RangeContext)ctx.timeInterval; - String lowerBoundTimeValue = rangeCtx.lowerbound.value.getText(); - String lowerBoundTimeUnit = ""; - long lowerBoundNanoSec = 0; - if (!lowerBoundTimeValue.equals("0")) { - lowerBoundTimeUnit = rangeCtx.lowerbound.unit.getText(); - TimeValue lowerTimeValue = new TimeValue( - Integer.valueOf(lowerBoundTimeValue), - TimeUnit.fromName(lowerBoundTimeUnit)); - lowerBoundNanoSec = lowerTimeValue.toNanoSeconds(); - } - - String upperBoundTimeValue = rangeCtx.upperbound.value.getText(); - String upperBoundTimeUnit = ""; - long upperBoundNanoSec = 0; - if (!upperBoundTimeValue.equals("0")) { - upperBoundTimeUnit = rangeCtx.upperbound.unit.getText(); - TimeValue upperTimeValue = new TimeValue( - Integer.valueOf(upperBoundTimeValue), - TimeUnit.fromName(upperBoundTimeUnit)); - upperBoundNanoSec = upperTimeValue.toNanoSeconds(); - } - - String timePredicate = ""; - timePredicate += "("; - if (rangeCtx.LPAREN() != null) { - timePredicate += "tag_later(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + lowerBoundNanoSec + ")))"; - } else { - // FIXME: Check if this can be replaced by a !tag_earlier. - timePredicate += "tag_later(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + lowerBoundNanoSec + ")))" - + " || " + "tag_same(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + lowerBoundNanoSec + ")))"; - } - timePredicate += ") && ("; - if (rangeCtx.RPAREN() != null) { - timePredicate += "tag_earlier(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))"; - } else { - timePredicate += "tag_earlier(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))" - + " || " + "tag_same(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))"; - } - timePredicate += ")"; - + long lowerBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); + long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); long currentHorizon = horizon + upperBoundNanoSec; - + String timePredicate = generateTimePredicate((MTLParser.RangeContext)ctx.timeInterval, + lowerBoundNanoSec, upperBoundNanoSec, QFPrefix, prevQFIdx); return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " + "j" + QFIdx + " >= " + QFPrefix + " && " + "j" + QFIdx + " <= " + end + " && " + "(" + _visitUnaryOp(ctx.right, ("j"+QFIdx), QFIdx+1, QFPrefix, currentHorizon) + ")" @@ -260,30 +203,154 @@ public String visitUntil(MTLParser.UntilContext ctx, public String visitNoUnaryOp(MTLParser.NoUnaryOpContext ctx, String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { - return ""; + return visitPrimary(ctx.formula, QFPrefix, QFIdx, prevQFIdx, horizon); } public String visitNegation(MTLParser.NegationContext ctx, String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { - return ""; + return "!(" + visitPrimary(ctx.formula, QFPrefix, QFIdx, prevQFIdx, horizon) + ")"; } public String visitNext(MTLParser.NextContext ctx, String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { - return ""; + return visitPrimary(ctx.formula, ("(" + QFPrefix + "+1)"), QFIdx, prevQFIdx, horizon); } public String visitGlobally(MTLParser.GloballyContext ctx, String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String end; + if (this.tactic.equals("induction")) { + end = "(" + QFPrefix + " + N)"; + } else { + end = "END"; + } return ""; } public String visitFinally(MTLParser.FinallyContext ctx, String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String end; + if (this.tactic.equals("induction")) { + end = "(" + QFPrefix + " + N)"; + } else { + end = "END"; + } + + if (ctx.timeInterval instanceof MTLParser.SingletonContext) { + + } + else { + + } + + return ""; + } + + public String visitPrimary(MTLParser.PrimaryContext ctx, + String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + return ""; } + + /////////////////////////////////////// + //// Private methods + + /** + * Return a time value in nanoseconds from an IntervalContext. + * + * @param ctx + * @param getUpper + * @return + */ + private long getNanoSecFromIntervalContext(MTLParser.IntervalContext ctx, boolean getUpper) { + if (ctx instanceof MTLParser.SingletonContext) { + MTLParser.SingletonContext singletonCtx = (MTLParser.SingletonContext)ctx; + String timeInstantValue = singletonCtx.instant.value.getText(); + String timeInstantUnit = ""; + long timeInstantNanoSec = 0; + if (!timeInstantValue.equals("0")) { + timeInstantUnit = singletonCtx.instant.unit.getText(); + TimeValue timeValue = new TimeValue( + Integer.valueOf(timeInstantValue), + TimeUnit.fromName(timeInstantUnit)); + timeInstantNanoSec = timeValue.toNanoSeconds(); + } + return timeInstantNanoSec; + } + + MTLParser.RangeContext rangeCtx = (MTLParser.RangeContext)ctx; + if (!getUpper) { + String lowerBoundTimeValue = rangeCtx.lowerbound.value.getText(); + String lowerBoundTimeUnit = ""; + long lowerBoundNanoSec = 0; + if (!lowerBoundTimeValue.equals("0")) { + lowerBoundTimeUnit = rangeCtx.lowerbound.unit.getText(); + TimeValue lowerTimeValue = new TimeValue( + Integer.valueOf(lowerBoundTimeValue), + TimeUnit.fromName(lowerBoundTimeUnit)); + lowerBoundNanoSec = lowerTimeValue.toNanoSeconds(); + } + return lowerBoundNanoSec; + } + + String upperBoundTimeValue = rangeCtx.upperbound.value.getText(); + String upperBoundTimeUnit = ""; + long upperBoundNanoSec = 0; + if (!upperBoundTimeValue.equals("0")) { + upperBoundTimeUnit = rangeCtx.upperbound.unit.getText(); + TimeValue upperTimeValue = new TimeValue( + Integer.valueOf(upperBoundTimeValue), + TimeUnit.fromName(upperBoundTimeUnit)); + upperBoundNanoSec = upperTimeValue.toNanoSeconds(); + } + return upperBoundNanoSec; + } + + /** + * Generate a time predicate from a range. + * + * @param ctx + * @param lowerBoundNanoSec + * @param upperBoundNanoSec + * @return + */ + private String generateTimePredicate(MTLParser.RangeContext ctx, + long lowerBoundNanoSec, long upperBoundNanoSec, + String QFPrefix, String prevQFIdx) { + String timePredicate = ""; + timePredicate += "("; + if (ctx.LBRACKET() != null) { + // FIXME: Check if this can be replaced by a !tag_earlier. + timePredicate += "tag_later(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + lowerBoundNanoSec + ")))" + + " || " + "tag_same(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + lowerBoundNanoSec + ")))"; + } else { + timePredicate += "tag_later(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + lowerBoundNanoSec + ")))"; + } + timePredicate += ") && ("; + if (ctx.RBRACKET() != null) { + timePredicate += "tag_earlier(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))" + + " || " + "tag_same(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))"; + } else { + timePredicate += "tag_earlier(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))"; + } + timePredicate += ")"; + + return timePredicate; + } + + private String generateTimePredicate(long timeInstantNanoSec, + String QFPrefix, String prevQFIdx) { + return "tag_same(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + timeInstantNanoSec + ")))"; + } } \ No newline at end of file From 666e70f43445496e99d48cb5993f41029c198247 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 8 Jul 2022 15:55:46 -0400 Subject: [PATCH 034/516] Checkpoint. Transpile up to Expr. --- .../lflang/generator/uclid/MTLVisitor.java | 185 +++++++++++------- 1 file changed, 116 insertions(+), 69 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java index 968d195e5b..632eec320e 100644 --- a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java @@ -168,11 +168,24 @@ public String visitUntil(MTLParser.UntilContext ctx, // Otherwise, create the Until formula. // Check if the time interval is a range or a singleton. - if (ctx.timeInterval instanceof MTLParser.SingletonContext) { - long timeInstantNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); - long currentHorizon = horizon + timeInstantNanoSec; - String timePredicate = generateTimePredicate(timeInstantNanoSec, QFPrefix, prevQFIdx); - return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " + long lowerBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); + long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); + long currentHorizon = horizon + upperBoundNanoSec; + String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, + upperBoundNanoSec, QFPrefix, prevQFIdx); + // if (ctx.timeInterval instanceof MTLParser.SingletonContext) { + // long timeInstantNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); + // currentHorizon = horizon + timeInstantNanoSec; + // timePredicate = generateTimePredicate(timeInstantNanoSec, QFPrefix, prevQFIdx); + // } + // else { + // long lowerBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); + // long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); + // currentHorizon = horizon + upperBoundNanoSec; + // timePredicate = generateTimePredicate((MTLParser.RangeContext)ctx.timeInterval, + // lowerBoundNanoSec, upperBoundNanoSec, QFPrefix, prevQFIdx); + // } + return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " + "j" + QFIdx + " >= " + QFPrefix + " && " + "j" + QFIdx + " <= " + end + " && " + "(" + _visitUnaryOp(ctx.right, ("j"+QFIdx), QFIdx+1, QFPrefix, currentHorizon) + ")" + " && " + "(" + "\n" @@ -181,23 +194,6 @@ public String visitUntil(MTLParser.UntilContext ctx, + ")" + " && " + "(" + "finite_forall " + "(" + "i" + QFIdx + " : integer) in indices :: " + "(" + "i" + QFIdx + " >= " + QFPrefix + " && " + "i" + QFIdx + " < " + "j" + QFIdx + ")" + " ==> " + "(" + _visitUnaryOp(ctx.left, ("i"+QFIdx), QFIdx+1, ("j"+QFIdx), currentHorizon) + ")" + ")"; - } - else { - long lowerBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); - long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); - long currentHorizon = horizon + upperBoundNanoSec; - String timePredicate = generateTimePredicate((MTLParser.RangeContext)ctx.timeInterval, - lowerBoundNanoSec, upperBoundNanoSec, QFPrefix, prevQFIdx); - return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " - + "j" + QFIdx + " >= " + QFPrefix + " && " + "j" + QFIdx + " <= " + end - + " && " + "(" + _visitUnaryOp(ctx.right, ("j"+QFIdx), QFIdx+1, QFPrefix, currentHorizon) + ")" - + " && " + "(" + "\n" - + "// Time Predicate\n" - + timePredicate + "\n" - + ")" + " && " + "(" + "finite_forall " + "(" + "i" + QFIdx + " : integer) in indices :: " - + "(" + "i" + QFIdx + " >= " + QFPrefix + " && " + "i" + QFIdx + " < " + "j" + QFIdx + ")" - + " ==> " + "(" + _visitUnaryOp(ctx.left, ("i"+QFIdx), QFIdx+1, ("j"+QFIdx), currentHorizon) + ")" + ")"; - } } public String visitNoUnaryOp(MTLParser.NoUnaryOpContext ctx, @@ -227,7 +223,19 @@ public String visitGlobally(MTLParser.GloballyContext ctx, } else { end = "END"; } - return ""; + + long lowerBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); + long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); + long currentHorizon = horizon + upperBoundNanoSec; + String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, + upperBoundNanoSec, QFPrefix, prevQFIdx); + return "!(" + "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " + + "j" + QFIdx + " >= " + QFPrefix + " && " + "j" + QFIdx + " <= " + end + + " && " + "!" + "(" + visitPrimary(ctx.formula, ("j"+QFIdx), QFIdx+1, QFPrefix, currentHorizon) + ")" + + " && " + "(" + "\n" + + "// Time Predicate\n" + + timePredicate + "\n" + + "))"; } public String visitFinally(MTLParser.FinallyContext ctx, @@ -240,19 +248,55 @@ public String visitFinally(MTLParser.FinallyContext ctx, end = "END"; } - if (ctx.timeInterval instanceof MTLParser.SingletonContext) { + long lowerBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); + long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); + long currentHorizon = horizon + upperBoundNanoSec; + String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, + upperBoundNanoSec, QFPrefix, prevQFIdx); + return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " + + "j" + QFIdx + " >= " + QFPrefix + " && " + "j" + QFIdx + " <= " + end + + " && " + "(" + visitPrimary(ctx.formula, ("j"+QFIdx), QFIdx+1, QFPrefix, currentHorizon) + ")" + + " && " + "(" + "\n" + + "// Time Predicate\n" + + timePredicate + "\n" + + ")"; + } - } - else { + public String visitPrimary(MTLParser.PrimaryContext ctx, + String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { - } + if (ctx.atom != null) + return visitAtomicProp(ctx.atom, QFPrefix, QFIdx, prevQFIdx, horizon); + else if (ctx.id != null) + return ctx.id.getText(); + else + return visitMtl(ctx.formula, QFPrefix, QFIdx, prevQFIdx, horizon); + } - return ""; + public String visitAtomicProp(MTLParser.AtomicPropContext ctx, + String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + + if (ctx.primitive != null) + return ctx.primitive.getText(); + else + return visitExpr(ctx.left, QFPrefix, QFIdx, prevQFIdx, horizon) + + " " + ctx.op.getText() + " " + + visitExpr(ctx.right, QFPrefix, QFIdx, prevQFIdx, horizon); } - public String visitPrimary(MTLParser.PrimaryContext ctx, + public String visitExpr(MTLParser.ExprContext ctx, String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + if (ctx.ID() != null) + return ctx.ID().getText(); + else if (ctx.INTEGER() != null) + return ctx.INTEGER().getText(); + else + return visitSum(ctx.sum(), QFPrefix, QFIdx, prevQFIdx, horizon); + } + + public String visitSum(MTLParser.SumContext ctx, + String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { return ""; } @@ -267,6 +311,7 @@ public String visitPrimary(MTLParser.PrimaryContext ctx, * @return */ private long getNanoSecFromIntervalContext(MTLParser.IntervalContext ctx, boolean getUpper) { + // If we have a singleton, the return value is the same regardless of getUpper. if (ctx instanceof MTLParser.SingletonContext) { MTLParser.SingletonContext singletonCtx = (MTLParser.SingletonContext)ctx; String timeInstantValue = singletonCtx.instant.value.getText(); @@ -295,19 +340,19 @@ private long getNanoSecFromIntervalContext(MTLParser.IntervalContext ctx, boolea lowerBoundNanoSec = lowerTimeValue.toNanoSeconds(); } return lowerBoundNanoSec; + } else { + String upperBoundTimeValue = rangeCtx.upperbound.value.getText(); + String upperBoundTimeUnit = ""; + long upperBoundNanoSec = 0; + if (!upperBoundTimeValue.equals("0")) { + upperBoundTimeUnit = rangeCtx.upperbound.unit.getText(); + TimeValue upperTimeValue = new TimeValue( + Integer.valueOf(upperBoundTimeValue), + TimeUnit.fromName(upperBoundTimeUnit)); + upperBoundNanoSec = upperTimeValue.toNanoSeconds(); + } + return upperBoundNanoSec; } - - String upperBoundTimeValue = rangeCtx.upperbound.value.getText(); - String upperBoundTimeUnit = ""; - long upperBoundNanoSec = 0; - if (!upperBoundTimeValue.equals("0")) { - upperBoundTimeUnit = rangeCtx.upperbound.unit.getText(); - TimeValue upperTimeValue = new TimeValue( - Integer.valueOf(upperBoundTimeValue), - TimeUnit.fromName(upperBoundTimeUnit)); - upperBoundNanoSec = upperTimeValue.toNanoSeconds(); - } - return upperBoundNanoSec; } /** @@ -318,39 +363,41 @@ private long getNanoSecFromIntervalContext(MTLParser.IntervalContext ctx, boolea * @param upperBoundNanoSec * @return */ - private String generateTimePredicate(MTLParser.RangeContext ctx, + private String generateTimePredicate(MTLParser.IntervalContext ctx, long lowerBoundNanoSec, long upperBoundNanoSec, String QFPrefix, String prevQFIdx) { String timePredicate = ""; - timePredicate += "("; - if (ctx.LBRACKET() != null) { - // FIXME: Check if this can be replaced by a !tag_earlier. - timePredicate += "tag_later(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + lowerBoundNanoSec + ")))" - + " || " + "tag_same(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + lowerBoundNanoSec + ")))"; - } else { - timePredicate += "tag_later(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + lowerBoundNanoSec + ")))"; - } - timePredicate += ") && ("; - if (ctx.RBRACKET() != null) { - timePredicate += "tag_earlier(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))" - + " || " + "tag_same(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))"; + + if (ctx instanceof MTLParser.SingletonContext) { + MTLParser.SingletonContext singletonCtx = (MTLParser.SingletonContext)ctx; + timePredicate += "tag_same(g(" + QFPrefix + "), " + "tag_schedule(g(" + + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))"; } else { - timePredicate += "tag_earlier(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))"; + MTLParser.RangeContext rangeCtx = (MTLParser.RangeContext)ctx; + timePredicate += "("; + if (rangeCtx.LBRACKET() != null) { + // FIXME: Check if this can be replaced by a !tag_earlier. + timePredicate += "tag_later(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + lowerBoundNanoSec + ")))" + + " || " + "tag_same(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + lowerBoundNanoSec + ")))"; + } else { + timePredicate += "tag_later(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + lowerBoundNanoSec + ")))"; + } + timePredicate += ") && ("; + if (rangeCtx.RBRACKET() != null) { + timePredicate += "tag_earlier(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))" + + " || " + "tag_same(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))"; + } else { + timePredicate += "tag_earlier(g(" + QFPrefix + "), " + + "tag_schedule(g(" + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))"; + } + timePredicate += ")"; } - timePredicate += ")"; return timePredicate; } - - private String generateTimePredicate(long timeInstantNanoSec, - String QFPrefix, String prevQFIdx) { - return "tag_same(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + timeInstantNanoSec + ")))"; - } -} \ No newline at end of file +} From 6e2a180c71318db37bdeb9643e97199e1d4f458a Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 8 Jul 2022 16:08:42 -0400 Subject: [PATCH 035/516] Transpile up to Quotient --- .../lflang/generator/uclid/MTLVisitor.java | 66 +++++++++++++++---- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java index 632eec320e..070e7f340c 100644 --- a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java @@ -173,18 +173,7 @@ public String visitUntil(MTLParser.UntilContext ctx, long currentHorizon = horizon + upperBoundNanoSec; String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, upperBoundNanoSec, QFPrefix, prevQFIdx); - // if (ctx.timeInterval instanceof MTLParser.SingletonContext) { - // long timeInstantNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); - // currentHorizon = horizon + timeInstantNanoSec; - // timePredicate = generateTimePredicate(timeInstantNanoSec, QFPrefix, prevQFIdx); - // } - // else { - // long lowerBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); - // long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); - // currentHorizon = horizon + upperBoundNanoSec; - // timePredicate = generateTimePredicate((MTLParser.RangeContext)ctx.timeInterval, - // lowerBoundNanoSec, upperBoundNanoSec, QFPrefix, prevQFIdx); - // } + return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " + "j" + QFIdx + " >= " + QFPrefix + " && " + "j" + QFIdx + " <= " + end + " && " + "(" + _visitUnaryOp(ctx.right, ("j"+QFIdx), QFIdx+1, QFPrefix, currentHorizon) + ")" @@ -297,7 +286,58 @@ else if (ctx.INTEGER() != null) public String visitSum(MTLParser.SumContext ctx, String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { - return ""; + + String str = ""; + for (int i = 0; i < ctx.terms.size(); i++) { + str += "(" + + visitDifference(ctx.terms.get(i), + QFPrefix, QFIdx, prevQFIdx, horizon) + + ")" + + (i == ctx.terms.size()-1 ? "" : "+"); + } + return str; + } + + public String visitDifference(MTLParser.DifferenceContext ctx, + String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + + String str = ""; + for (int i = 0; i < ctx.terms.size(); i++) { + str += "(" + + visitProduct(ctx.terms.get(i), + QFPrefix, QFIdx, prevQFIdx, horizon) + + ")" + + (i == ctx.terms.size()-1 ? "" : "-"); + } + return str; + } + + public String visitProduct(MTLParser.ProductContext ctx, + String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + + String str = ""; + for (int i = 0; i < ctx.terms.size(); i++) { + str += "(" + + visitQuotient(ctx.terms.get(i), + QFPrefix, QFIdx, prevQFIdx, horizon) + + ")" + + (i == ctx.terms.size()-1 ? "" : "*"); + } + return str; + } + + public String visitQuotient(MTLParser.QuotientContext ctx, + String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + + String str = ""; + for (int i = 0; i < ctx.terms.size(); i++) { + str += "(" + + visitExpr(ctx.terms.get(i), + QFPrefix, QFIdx, prevQFIdx, horizon) + + ")" + + (i == ctx.terms.size()-1 ? "" : "/"); + } + return str; } /////////////////////////////////////// From a5311234c8c978f5af48d3b800ac3cd79e264913 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 8 Jul 2022 23:42:19 -0400 Subject: [PATCH 036/516] Generate properties --- .../lflang/generator/ReactionInstance.java | 4 - .../lflang/generator/uclid/MTLVisitor.java | 163 +++++++++--------- .../generator/uclid/UclidGenerator.java | 45 ++++- 3 files changed, 122 insertions(+), 90 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index b347023835..0ee81cd824 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -540,10 +540,6 @@ public ReactionInstance getReaction() { return ReactionInstance.this; } - public String getFullNameWithJoiner(String joiner) { - return this.getReaction().getFullNameWithJoiner(joiner) + joiner + "rid" + joiner + String.valueOf(this.id); - } - @Override public String toString() { String result = ReactionInstance.this + "(level: " + level; diff --git a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java index 070e7f340c..66676ed236 100644 --- a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java @@ -54,52 +54,52 @@ public MTLVisitor(String tactic) { //////////////////////////////////////////// //// Public methods public String visitMtl(MTLParser.MtlContext ctx, - String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { return visitEquivalence(ctx.equivalence(), - QFPrefix, QFIdx, prevQFIdx, horizon); + prefixIdx, QFIdx, prevPrefixIdx, horizon); } public String visitEquivalence(MTLParser.EquivalenceContext ctx, - String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { if (ctx.right == null) { return visitImplication(ctx.left, - QFPrefix, QFIdx, prevQFIdx, horizon); + prefixIdx, QFIdx, prevPrefixIdx, horizon); } return "(" + visitImplication(ctx.left, - QFPrefix, QFIdx, prevQFIdx, horizon) + prefixIdx, QFIdx, prevPrefixIdx, horizon) + ")" + " <==> " + "(" + visitImplication(ctx.right, - QFPrefix, QFIdx, prevQFIdx, horizon) + prefixIdx, QFIdx, prevPrefixIdx, horizon) + ")"; } public String visitImplication(MTLParser.ImplicationContext ctx, - String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { if (ctx.right == null) { return visitDisjunction(ctx.left, - QFPrefix, QFIdx, prevQFIdx, horizon); + prefixIdx, QFIdx, prevPrefixIdx, horizon); } return "(" + visitDisjunction(ctx.left, - QFPrefix, QFIdx, prevQFIdx, horizon) + prefixIdx, QFIdx, prevPrefixIdx, horizon) + ")" + " ==> " + "(" + visitDisjunction(ctx.right, - QFPrefix, QFIdx, prevQFIdx, horizon) + prefixIdx, QFIdx, prevPrefixIdx, horizon) + ")"; } public String visitDisjunction(MTLParser.DisjunctionContext ctx, - String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { String str = ""; for (int i = 0; i < ctx.terms.size(); i++) { str += "(" + visitConjunction(ctx.terms.get(i), - QFPrefix, QFIdx, prevQFIdx, horizon) + prefixIdx, QFIdx, prevPrefixIdx, horizon) + ")" + (i == ctx.terms.size()-1 ? "" : "||"); } @@ -107,13 +107,13 @@ public String visitDisjunction(MTLParser.DisjunctionContext ctx, } public String visitConjunction(MTLParser.ConjunctionContext ctx, - String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { String str = ""; for (int i = 0; i < ctx.terms.size(); i++) { str += "(" + visitUntil((MTLParser.UntilContext)ctx.terms.get(i), - QFPrefix, QFIdx, prevQFIdx, horizon) + prefixIdx, QFIdx, prevPrefixIdx, horizon) + ")" + (i == ctx.terms.size()-1 ? "" : "&&"); } @@ -122,28 +122,28 @@ public String visitConjunction(MTLParser.ConjunctionContext ctx, // A custom dispatch function public String _visitUnaryOp(MTLParser.UnaryOpContext ctx, - String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { // FIXME: Is there a more "antlr" way to do dispatch here? if (ctx instanceof MTLParser.NoUnaryOpContext) { return visitNoUnaryOp((MTLParser.NoUnaryOpContext)ctx, - QFPrefix, QFIdx, prevQFIdx, horizon); + prefixIdx, QFIdx, prevPrefixIdx, horizon); } if (ctx instanceof MTLParser.NegationContext) { return visitNegation((MTLParser.NegationContext)ctx, - QFPrefix, QFIdx, prevQFIdx, horizon); + prefixIdx, QFIdx, prevPrefixIdx, horizon); } if (ctx instanceof MTLParser.NextContext) { return visitNext((MTLParser.NextContext)ctx, - QFPrefix, QFIdx, prevQFIdx, horizon); + prefixIdx, QFIdx, prevPrefixIdx, horizon); } if (ctx instanceof MTLParser.GloballyContext) { return visitGlobally((MTLParser.GloballyContext)ctx, - QFPrefix, QFIdx, prevQFIdx, horizon); + prefixIdx, QFIdx, prevPrefixIdx, horizon); } if (ctx instanceof MTLParser.FinallyContext) { return visitFinally((MTLParser.FinallyContext)ctx, - QFPrefix, QFIdx, prevQFIdx, horizon); + prefixIdx, QFIdx, prevPrefixIdx, horizon); } // FIXME: Throw an exception. @@ -151,17 +151,17 @@ public String _visitUnaryOp(MTLParser.UnaryOpContext ctx, } public String visitUntil(MTLParser.UntilContext ctx, - String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { // If right is null, continue recursion. if (ctx.right == null) { return _visitUnaryOp(ctx.left, - QFPrefix, QFIdx, prevQFIdx, horizon); + prefixIdx, QFIdx, prevPrefixIdx, horizon); } String end; if (this.tactic.equals("induction")) { - end = "(" + QFPrefix + " + N)"; + end = "(" + prefixIdx + " + CT)"; } else { end = "END"; } @@ -172,43 +172,43 @@ public String visitUntil(MTLParser.UntilContext ctx, long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); long currentHorizon = horizon + upperBoundNanoSec; String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, - upperBoundNanoSec, QFPrefix, prevQFIdx); + upperBoundNanoSec, prefixIdx, prevPrefixIdx); return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " - + "j" + QFIdx + " >= " + QFPrefix + " && " + "j" + QFIdx + " <= " + end - + " && " + "(" + _visitUnaryOp(ctx.right, ("j"+QFIdx), QFIdx+1, QFPrefix, currentHorizon) + ")" + + "j" + QFIdx + " >= " + prefixIdx + " && " + "j" + QFIdx + " <= " + end + + " && " + "(" + _visitUnaryOp(ctx.right, ("j"+QFIdx), QFIdx+1, prefixIdx, currentHorizon) + ")" + " && " + "(" + "\n" + "// Time Predicate\n" + timePredicate + "\n" + ")" + " && " + "(" + "finite_forall " + "(" + "i" + QFIdx + " : integer) in indices :: " - + "(" + "i" + QFIdx + " >= " + QFPrefix + " && " + "i" + QFIdx + " < " + "j" + QFIdx + ")" + + "(" + "i" + QFIdx + " >= " + prefixIdx + " && " + "i" + QFIdx + " < " + "j" + QFIdx + ")" + " ==> " + "(" + _visitUnaryOp(ctx.left, ("i"+QFIdx), QFIdx+1, ("j"+QFIdx), currentHorizon) + ")" + ")"; } public String visitNoUnaryOp(MTLParser.NoUnaryOpContext ctx, - String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - return visitPrimary(ctx.formula, QFPrefix, QFIdx, prevQFIdx, horizon); + return visitPrimary(ctx.formula, prefixIdx, QFIdx, prevPrefixIdx, horizon); } public String visitNegation(MTLParser.NegationContext ctx, - String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - return "!(" + visitPrimary(ctx.formula, QFPrefix, QFIdx, prevQFIdx, horizon) + ")"; + return "!(" + visitPrimary(ctx.formula, prefixIdx, QFIdx, prevPrefixIdx, horizon) + ")"; } public String visitNext(MTLParser.NextContext ctx, - String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - return visitPrimary(ctx.formula, ("(" + QFPrefix + "+1)"), QFIdx, prevQFIdx, horizon); + return visitPrimary(ctx.formula, ("(" + prefixIdx + "+1)"), QFIdx, prevPrefixIdx, horizon); } public String visitGlobally(MTLParser.GloballyContext ctx, - String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { String end; if (this.tactic.equals("induction")) { - end = "(" + QFPrefix + " + N)"; + end = "(" + prefixIdx + " + CT)"; } else { end = "END"; } @@ -217,10 +217,10 @@ public String visitGlobally(MTLParser.GloballyContext ctx, long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); long currentHorizon = horizon + upperBoundNanoSec; String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, - upperBoundNanoSec, QFPrefix, prevQFIdx); + upperBoundNanoSec, prefixIdx, prevPrefixIdx); return "!(" + "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " - + "j" + QFIdx + " >= " + QFPrefix + " && " + "j" + QFIdx + " <= " + end - + " && " + "!" + "(" + visitPrimary(ctx.formula, ("j"+QFIdx), QFIdx+1, QFPrefix, currentHorizon) + ")" + + "j" + QFIdx + " >= " + prefixIdx + " && " + "j" + QFIdx + " <= " + end + + " && " + "!" + "(" + visitPrimary(ctx.formula, ("j"+QFIdx), QFIdx+1, prefixIdx, currentHorizon) + ")" + " && " + "(" + "\n" + "// Time Predicate\n" + timePredicate + "\n" @@ -228,11 +228,11 @@ public String visitGlobally(MTLParser.GloballyContext ctx, } public String visitFinally(MTLParser.FinallyContext ctx, - String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { String end; if (this.tactic.equals("induction")) { - end = "(" + QFPrefix + " + N)"; + end = "(" + prefixIdx + " + CT)"; } else { end = "END"; } @@ -241,10 +241,10 @@ public String visitFinally(MTLParser.FinallyContext ctx, long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); long currentHorizon = horizon + upperBoundNanoSec; String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, - upperBoundNanoSec, QFPrefix, prevQFIdx); + upperBoundNanoSec, prefixIdx, prevPrefixIdx); return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " - + "j" + QFIdx + " >= " + QFPrefix + " && " + "j" + QFIdx + " <= " + end - + " && " + "(" + visitPrimary(ctx.formula, ("j"+QFIdx), QFIdx+1, QFPrefix, currentHorizon) + ")" + + "j" + QFIdx + " >= " + prefixIdx + " && " + "j" + QFIdx + " <= " + end + + " && " + "(" + visitPrimary(ctx.formula, ("j"+QFIdx), QFIdx+1, prefixIdx, currentHorizon) + ")" + " && " + "(" + "\n" + "// Time Predicate\n" + timePredicate + "\n" @@ -252,46 +252,55 @@ public String visitFinally(MTLParser.FinallyContext ctx, } public String visitPrimary(MTLParser.PrimaryContext ctx, - String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { if (ctx.atom != null) - return visitAtomicProp(ctx.atom, QFPrefix, QFIdx, prevQFIdx, horizon); - else if (ctx.id != null) - return ctx.id.getText(); + return visitAtomicProp(ctx.atom, prefixIdx, QFIdx, prevPrefixIdx, horizon); + else if (ctx.id != null) { + // Check if the ID is a reaction. + // FIXME: Not robust. + if (ctx.id.getText().contains("_reaction_")) { + return "rxn(" + prefixIdx + ") == " + ctx.id.getText(); + } else if (ctx.id.getText().contains("_is_present")) { + return ctx.id.getText() + "(" + "t(" + prefixIdx + ")" + ")"; + } else { + return ctx.id.getText() + "(" + "s(" + prefixIdx + ")" + ")"; + } + } else - return visitMtl(ctx.formula, QFPrefix, QFIdx, prevQFIdx, horizon); + return visitMtl(ctx.formula, prefixIdx, QFIdx, prevPrefixIdx, horizon); } public String visitAtomicProp(MTLParser.AtomicPropContext ctx, - String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { if (ctx.primitive != null) return ctx.primitive.getText(); else - return visitExpr(ctx.left, QFPrefix, QFIdx, prevQFIdx, horizon) + return visitExpr(ctx.left, prefixIdx, QFIdx, prevPrefixIdx, horizon) + " " + ctx.op.getText() + " " - + visitExpr(ctx.right, QFPrefix, QFIdx, prevQFIdx, horizon); + + visitExpr(ctx.right, prefixIdx, QFIdx, prevPrefixIdx, horizon); } public String visitExpr(MTLParser.ExprContext ctx, - String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { if (ctx.ID() != null) - return ctx.ID().getText(); + return ctx.ID().getText() + "(" + "s(" + prefixIdx + ")" + ")"; else if (ctx.INTEGER() != null) return ctx.INTEGER().getText(); else - return visitSum(ctx.sum(), QFPrefix, QFIdx, prevQFIdx, horizon); + return visitSum(ctx.sum(), prefixIdx, QFIdx, prevPrefixIdx, horizon); } public String visitSum(MTLParser.SumContext ctx, - String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { String str = ""; for (int i = 0; i < ctx.terms.size(); i++) { str += "(" + visitDifference(ctx.terms.get(i), - QFPrefix, QFIdx, prevQFIdx, horizon) + prefixIdx, QFIdx, prevPrefixIdx, horizon) + ")" + (i == ctx.terms.size()-1 ? "" : "+"); } @@ -299,13 +308,13 @@ public String visitSum(MTLParser.SumContext ctx, } public String visitDifference(MTLParser.DifferenceContext ctx, - String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { String str = ""; for (int i = 0; i < ctx.terms.size(); i++) { str += "(" + visitProduct(ctx.terms.get(i), - QFPrefix, QFIdx, prevQFIdx, horizon) + prefixIdx, QFIdx, prevPrefixIdx, horizon) + ")" + (i == ctx.terms.size()-1 ? "" : "-"); } @@ -313,13 +322,13 @@ public String visitDifference(MTLParser.DifferenceContext ctx, } public String visitProduct(MTLParser.ProductContext ctx, - String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { String str = ""; for (int i = 0; i < ctx.terms.size(); i++) { str += "(" + visitQuotient(ctx.terms.get(i), - QFPrefix, QFIdx, prevQFIdx, horizon) + prefixIdx, QFIdx, prevPrefixIdx, horizon) + ")" + (i == ctx.terms.size()-1 ? "" : "*"); } @@ -327,13 +336,13 @@ public String visitProduct(MTLParser.ProductContext ctx, } public String visitQuotient(MTLParser.QuotientContext ctx, - String QFPrefix, int QFIdx, String prevQFIdx, long horizon) { + String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { String str = ""; for (int i = 0; i < ctx.terms.size(); i++) { str += "(" + visitExpr(ctx.terms.get(i), - QFPrefix, QFIdx, prevQFIdx, horizon) + prefixIdx, QFIdx, prevPrefixIdx, horizon) + ")" + (i == ctx.terms.size()-1 ? "" : "/"); } @@ -405,35 +414,35 @@ private long getNanoSecFromIntervalContext(MTLParser.IntervalContext ctx, boolea */ private String generateTimePredicate(MTLParser.IntervalContext ctx, long lowerBoundNanoSec, long upperBoundNanoSec, - String QFPrefix, String prevQFIdx) { + String prefixIdx, String prevPrefixIdx) { String timePredicate = ""; if (ctx instanceof MTLParser.SingletonContext) { MTLParser.SingletonContext singletonCtx = (MTLParser.SingletonContext)ctx; - timePredicate += "tag_same(g(" + QFPrefix + "), " + "tag_schedule(g(" - + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))"; + timePredicate += "tag_same(g(" + prefixIdx + "), " + "tag_schedule(g(" + + prevPrefixIdx + "), nsec(" + upperBoundNanoSec + ")))"; } else { MTLParser.RangeContext rangeCtx = (MTLParser.RangeContext)ctx; timePredicate += "("; if (rangeCtx.LBRACKET() != null) { // FIXME: Check if this can be replaced by a !tag_earlier. - timePredicate += "tag_later(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + lowerBoundNanoSec + ")))" - + " || " + "tag_same(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + lowerBoundNanoSec + ")))"; + timePredicate += "tag_later(g(" + prefixIdx + "), " + + "tag_schedule(g(" + prevPrefixIdx + "), nsec(" + lowerBoundNanoSec + ")))" + + " || " + "tag_same(g(" + prefixIdx + "), " + + "tag_schedule(g(" + prevPrefixIdx + "), nsec(" + lowerBoundNanoSec + ")))"; } else { - timePredicate += "tag_later(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + lowerBoundNanoSec + ")))"; + timePredicate += "tag_later(g(" + prefixIdx + "), " + + "tag_schedule(g(" + prevPrefixIdx + "), nsec(" + lowerBoundNanoSec + ")))"; } timePredicate += ") && ("; if (rangeCtx.RBRACKET() != null) { - timePredicate += "tag_earlier(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))" - + " || " + "tag_same(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))"; + timePredicate += "tag_earlier(g(" + prefixIdx + "), " + + "tag_schedule(g(" + prevPrefixIdx + "), nsec(" + upperBoundNanoSec + ")))" + + " || " + "tag_same(g(" + prefixIdx + "), " + + "tag_schedule(g(" + prevPrefixIdx + "), nsec(" + upperBoundNanoSec + ")))"; } else { - timePredicate += "tag_earlier(g(" + QFPrefix + "), " - + "tag_schedule(g(" + prevQFIdx + "), nsec(" + upperBoundNanoSec + ")))"; + timePredicate += "tag_earlier(g(" + prefixIdx + "), " + + "tag_schedule(g(" + prevPrefixIdx + "), nsec(" + upperBoundNanoSec + ")))"; } timePredicate += ")"; } diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 212d2de23d..2e1877c778 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -362,9 +362,9 @@ protected void generateTraceDefinition(int CT) { "const START : integer = 0;", "const END : integer = " + String.valueOf(CT-1) + ";", "", - "// trace length = k + N", + "// trace length = k + CT", "const k : integer = 1; // 1-induction should be enough.", - "const N : integer = " + String.valueOf(CT) + ";" + "// The property bound", + "const CT : integer = " + String.valueOf(CT) + ";" + "// The completeness threshold", "\n" )); @@ -479,7 +479,7 @@ protected void generateReactionIdsAndStateVars() { for (var rxn : this.reactionInstances) { // Print a list of reaction IDs. // Add a comma if not last. - code.pr(rxn.getFullNameWithJoiner("_") + ","); + code.pr(rxn.getReaction().getFullNameWithJoiner("_") + ","); } code.pr("NULL"); code.unindent(); @@ -590,8 +590,8 @@ protected void generateReactorSemantics() { var downstreamReactions = upstreamRuntime.getReaction().dependentReactions(); for (var downstream : downstreamReactions) { for (var downstreamRuntime : downstream.getRuntimeInstances()) { - code.pr("|| (e1._1 == " + upstreamRuntime.getFullNameWithJoiner("_") - + " && e2._1 == " + downstreamRuntime.getFullNameWithJoiner("_") + ")"); + code.pr("|| (e1._1 == " + upstreamRuntime.getReaction().getFullNameWithJoiner("_") + + " && e2._1 == " + downstreamRuntime.getReaction().getFullNameWithJoiner("_") + ")"); } } } @@ -748,7 +748,7 @@ protected void generateTriggersAndReactions() { // to be triggered. for (ReactionInstance.Runtime reaction : this.reactionInstances) { code.pr(String.join("\n", - "// " + reaction.getFullNameWithJoiner("_") + " is invoked when any of it triggers are present.", + "// " + reaction.getReaction().getFullNameWithJoiner("_") + " is invoked when any of it triggers are present.", "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> ((", " false" )); @@ -782,7 +782,7 @@ protected void generateTriggersAndReactions() { for (var instance : trigger.getDependentReactions()) { for (var runtime : ((ReactionInstance)instance).getRuntimeInstances()) { if (runtime == reaction) continue; // Skip the current reaction. - exclusion += " && rxn(i) != " + runtime.getFullNameWithJoiner("_"); + exclusion += " && rxn(i) != " + runtime.getReaction().getFullNameWithJoiner("_"); } } @@ -791,7 +791,7 @@ protected void generateTriggersAndReactions() { // If any of the above trigger is present, then trigger the reaction. code.unindent(); - code.pr(") <==> (rxn(i) == " + reaction.getFullNameWithJoiner("_") + ")));"); + code.pr(") <==> (rxn(i) == " + reaction.getReaction().getFullNameWithJoiner("_") + ")));"); } } @@ -866,7 +866,34 @@ protected void generateProperty(Attribute property, int CT) { // The visitor transpiles the MTL into a Uclid axiom. String transpiled = visitor.visitMtl(mtlCtx, "i", 0, "0", 0); - code.pr(transpiled); + + code.pr("// The FOL property translated from user-defined MTL property:"); + code.pr("// " + spec); + code.pr("define p(i : step_t) : boolean ="); + code.indent(); + code.pr(transpiled + ";"); + code.unindent(); + + // FIXME: No need for this since we are doing 1-induction. + // code.pr(String.join("\n", + // "// Helper macro for temporal induction", + // "define Globally_p(start, end : step_t) : boolean =", + // " (finite_forall (i : integer) in indices :: (i >= start && i <= end) ==> p(i));" + // )); + + if (tactic.equals("bmc")) { + code.pr(String.join("\n", + "// BMC", + "property " + "bmc_" + name + " : " + "initial_condition() ==> p(0);" + )); + } else { + code.pr(String.join("\n", + "// Induction: initiation step", + "property " + "initiation_" + name + " : " + "initial_condition() ==> p(0);", + "// Induction: consecution step", + "property " + "consecution_" + name + " : " + "p(0) ==> p(1);" + )); + } } /** From ba15655e6d5f3eb568f0ec3c90d8cbf0379b7901 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Tue, 19 Jul 2022 17:00:13 -0700 Subject: [PATCH 037/516] Add C grammar and AST visitors --- org.lflang/src/org/lflang/dsl/antlr4/C.g4 | 908 ++++++++++++++++++ .../uclid/ast/AbstractAstVisitor.java | 17 + .../generator/uclid/ast/AstVisitor.java | 25 + .../uclid/ast/BuildAstParseTreeVisitor.java | 642 +++++++++++++ .../org/lflang/generator/uclid/ast/CAst.java | 341 +++++++ .../generator/uclid/ast/CAstVisitor.java | 74 ++ .../generator/uclid/ast/CBaseAstVisitor.java | 381 ++++++++ .../uclid/ast/IfNormalFormAstVisitor.java | 111 +++ .../lflang/generator/uclid/ast/Visitable.java | 12 + 9 files changed, 2511 insertions(+) create mode 100644 org.lflang/src/org/lflang/dsl/antlr4/C.g4 create mode 100644 org.lflang/src/org/lflang/generator/uclid/ast/AbstractAstVisitor.java create mode 100644 org.lflang/src/org/lflang/generator/uclid/ast/AstVisitor.java create mode 100644 org.lflang/src/org/lflang/generator/uclid/ast/BuildAstParseTreeVisitor.java create mode 100644 org.lflang/src/org/lflang/generator/uclid/ast/CAst.java create mode 100644 org.lflang/src/org/lflang/generator/uclid/ast/CAstVisitor.java create mode 100644 org.lflang/src/org/lflang/generator/uclid/ast/CBaseAstVisitor.java create mode 100644 org.lflang/src/org/lflang/generator/uclid/ast/IfNormalFormAstVisitor.java create mode 100644 org.lflang/src/org/lflang/generator/uclid/ast/Visitable.java diff --git a/org.lflang/src/org/lflang/dsl/antlr4/C.g4 b/org.lflang/src/org/lflang/dsl/antlr4/C.g4 new file mode 100644 index 0000000000..2fd6c3aedf --- /dev/null +++ b/org.lflang/src/org/lflang/dsl/antlr4/C.g4 @@ -0,0 +1,908 @@ +/* + [The "BSD licence"] + Copyright (c) 2013 Sam Harwell + All rights reserved. + + 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. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. +*/ + +/** C 2011 grammar built from the C11 Spec */ +grammar C; + + +primaryExpression + : Identifier + | Constant + | StringLiteral+ + | '(' expression ')' + | genericSelection + | '__extension__'? '(' compoundStatement ')' // Blocks (GCC extension) + | '__builtin_va_arg' '(' unaryExpression ',' typeName ')' + | '__builtin_offsetof' '(' typeName ',' unaryExpression ')' + ; + +genericSelection + : '_Generic' '(' assignmentExpression ',' genericAssocList ')' + ; + +genericAssocList + : genericAssociation (',' genericAssociation)* + ; + +genericAssociation + : (typeName | 'default') ':' assignmentExpression + ; + +postfixExpression + : + ( primaryExpression + | '__extension__'? '(' typeName ')' '{' initializerList ','? '}' + ) + ('[' expression ']' + | '(' argumentExpressionList? ')' + | ('.' | '->') Identifier + | ('++' | '--') + )* + ; + +argumentExpressionList + : assignmentExpression (',' assignmentExpression)* + ; + +unaryExpression + : + ('++' | '--' | 'sizeof')* + (postfixExpression + | unaryOperator castExpression + | ('sizeof' | '_Alignof') '(' typeName ')' + | '&&' Identifier // GCC extension address of label + ) + ; + +unaryOperator + : '&' | '*' | '+' | '-' | '~' | '!' + ; + +castExpression + : '__extension__'? '(' typeName ')' castExpression + | unaryExpression + | DigitSequence // for + ; + +multiplicativeExpression + : castExpression (('*'|'/'|'%') castExpression)* + ; + +additiveExpression + : multiplicativeExpression (('+'|'-') multiplicativeExpression)* + ; + +shiftExpression + : additiveExpression (('<<'|'>>') additiveExpression)* + ; + +relationalExpression + : shiftExpression (('<'|'>'|'<='|'>=') shiftExpression)* + ; + +equalityExpression + : relationalExpression (('=='| '!=') relationalExpression)* + ; + +andExpression + : equalityExpression ( '&' equalityExpression)* + ; + +exclusiveOrExpression + : andExpression ('^' andExpression)* + ; + +inclusiveOrExpression + : exclusiveOrExpression ('|' exclusiveOrExpression)* + ; + +logicalAndExpression + : inclusiveOrExpression ('&&' inclusiveOrExpression)* + ; + +logicalOrExpression + : logicalAndExpression ( '||' logicalAndExpression)* + ; + +conditionalExpression + : logicalOrExpression ('?' expression ':' conditionalExpression)? + ; + +assignmentExpression + : conditionalExpression + | unaryExpression assignmentOperator assignmentExpression + | DigitSequence // for + ; + +assignmentOperator + : '=' | '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' | '>>=' | '&=' | '^=' | '|=' + ; + +expression + : assignmentExpression (',' assignmentExpression)* + ; + +constantExpression + : conditionalExpression + ; + +declaration + : declarationSpecifiers initDeclaratorList? ';' + | staticAssertDeclaration + ; + +declarationSpecifiers + : declarationSpecifier+ + ; + +declarationSpecifiers2 + : declarationSpecifier+ + ; + +declarationSpecifier + : storageClassSpecifier + | typeSpecifier + | typeQualifier + | functionSpecifier + | alignmentSpecifier + ; + +initDeclaratorList + : initDeclarator (',' initDeclarator)* + ; + +initDeclarator + : declarator ('=' initializer)? + ; + +storageClassSpecifier + : 'typedef' + | 'extern' + | 'static' + | '_Thread_local' + | 'auto' + | 'register' + ; + +typeSpecifier + : ('void' + | 'char' + | 'short' + | 'int' + | 'long' + | 'float' + | 'double' + | 'signed' + | 'unsigned' + | '_Bool' + | '_Complex' + | '__m128' + | '__m128d' + | '__m128i') + | '__extension__' '(' ('__m128' | '__m128d' | '__m128i') ')' + | atomicTypeSpecifier + | structOrUnionSpecifier + | enumSpecifier + | typedefName + | '__typeof__' '(' constantExpression ')' // GCC extension + ; + +structOrUnionSpecifier + : structOrUnion Identifier? '{' structDeclarationList '}' + | structOrUnion Identifier + ; + +structOrUnion + : 'struct' + | 'union' + ; + +structDeclarationList + : structDeclaration+ + ; + +structDeclaration // The first two rules have priority order and cannot be simplified to one expression. + : specifierQualifierList structDeclaratorList ';' + | specifierQualifierList ';' + | staticAssertDeclaration + ; + +specifierQualifierList + : (typeSpecifier| typeQualifier) specifierQualifierList? + ; + +structDeclaratorList + : structDeclarator (',' structDeclarator)* + ; + +structDeclarator + : declarator + | declarator? ':' constantExpression + ; + +enumSpecifier + : 'enum' Identifier? '{' enumeratorList ','? '}' + | 'enum' Identifier + ; + +enumeratorList + : enumerator (',' enumerator)* + ; + +enumerator + : enumerationConstant ('=' constantExpression)? + ; + +enumerationConstant + : Identifier + ; + +atomicTypeSpecifier + : '_Atomic' '(' typeName ')' + ; + +typeQualifier + : 'const' + | 'restrict' + | 'volatile' + | '_Atomic' + ; + +functionSpecifier + : ('inline' + | '_Noreturn' + | '__inline__' // GCC extension + | '__stdcall') + | gccAttributeSpecifier + | '__declspec' '(' Identifier ')' + ; + +alignmentSpecifier + : '_Alignas' '(' (typeName | constantExpression) ')' + ; + +declarator + : pointer? directDeclarator gccDeclaratorExtension* + ; + +directDeclarator + : Identifier + | '(' declarator ')' + | directDeclarator '[' typeQualifierList? assignmentExpression? ']' + | directDeclarator '[' 'static' typeQualifierList? assignmentExpression ']' + | directDeclarator '[' typeQualifierList 'static' assignmentExpression ']' + | directDeclarator '[' typeQualifierList? '*' ']' + | directDeclarator '(' parameterTypeList ')' + | directDeclarator '(' identifierList? ')' + | Identifier ':' DigitSequence // bit field + | vcSpecificModifer Identifier // Visual C Extension + | '(' vcSpecificModifer declarator ')' // Visual C Extension + ; + +vcSpecificModifer + : ('__cdecl' + | '__clrcall' + | '__stdcall' + | '__fastcall' + | '__thiscall' + | '__vectorcall') + ; + + +gccDeclaratorExtension + : '__asm' '(' StringLiteral+ ')' + | gccAttributeSpecifier + ; + +gccAttributeSpecifier + : '__attribute__' '(' '(' gccAttributeList ')' ')' + ; + +gccAttributeList + : gccAttribute? (',' gccAttribute?)* + ; + +gccAttribute + : ~(',' | '(' | ')') // relaxed def for "identifier or reserved word" + ('(' argumentExpressionList? ')')? + ; + +nestedParenthesesBlock + : ( ~('(' | ')') + | '(' nestedParenthesesBlock ')' + )* + ; + +pointer + : (('*'|'^') typeQualifierList?)+ // ^ - Blocks language extension + ; + +typeQualifierList + : typeQualifier+ + ; + +parameterTypeList + : parameterList (',' '...')? + ; + +parameterList + : parameterDeclaration (',' parameterDeclaration)* + ; + +parameterDeclaration + : declarationSpecifiers declarator + | declarationSpecifiers2 abstractDeclarator? + ; + +identifierList + : Identifier (',' Identifier)* + ; + +typeName + : specifierQualifierList abstractDeclarator? + ; + +abstractDeclarator + : pointer + | pointer? directAbstractDeclarator gccDeclaratorExtension* + ; + +directAbstractDeclarator + : '(' abstractDeclarator ')' gccDeclaratorExtension* + | '[' typeQualifierList? assignmentExpression? ']' + | '[' 'static' typeQualifierList? assignmentExpression ']' + | '[' typeQualifierList 'static' assignmentExpression ']' + | '[' '*' ']' + | '(' parameterTypeList? ')' gccDeclaratorExtension* + | directAbstractDeclarator '[' typeQualifierList? assignmentExpression? ']' + | directAbstractDeclarator '[' 'static' typeQualifierList? assignmentExpression ']' + | directAbstractDeclarator '[' typeQualifierList 'static' assignmentExpression ']' + | directAbstractDeclarator '[' '*' ']' + | directAbstractDeclarator '(' parameterTypeList? ')' gccDeclaratorExtension* + ; + +typedefName + : Identifier + ; + +initializer + : assignmentExpression + | '{' initializerList ','? '}' + ; + +initializerList + : designation? initializer (',' designation? initializer)* + ; + +designation + : designatorList '=' + ; + +designatorList + : designator+ + ; + +designator + : '[' constantExpression ']' + | '.' Identifier + ; + +staticAssertDeclaration + : '_Static_assert' '(' constantExpression ',' StringLiteral+ ')' ';' + ; + +statement + : labeledStatement + | compoundStatement + | expressionStatement + | selectionStatement + | iterationStatement + | jumpStatement + | ('__asm' | '__asm__') ('volatile' | '__volatile__') '(' (logicalOrExpression (',' logicalOrExpression)*)? (':' (logicalOrExpression (',' logicalOrExpression)*)?)* ')' ';' + ; + +labeledStatement + : Identifier ':' statement + | 'case' constantExpression ':' statement + | 'default' ':' statement + ; + +compoundStatement + : '{' blockItemList? '}' + ; + +blockItemList + : blockItem+ + ; + +// Reaction body is a blockItem. +blockItem + : statement + | declaration + ; + +expressionStatement + : expression? ';' + ; + +selectionStatement + : 'if' '(' expression ')' statement ('else' statement)? + | 'switch' '(' expression ')' statement + ; + +iterationStatement + : While '(' expression ')' statement + | Do statement While '(' expression ')' ';' + | For '(' forCondition ')' statement + ; + +// | 'for' '(' expression? ';' expression? ';' forUpdate? ')' statement +// | For '(' declaration expression? ';' expression? ')' statement + +forCondition + : (forDeclaration | expression?) ';' forExpression? ';' forExpression? + ; + +forDeclaration + : declarationSpecifiers initDeclaratorList? + ; + +forExpression + : assignmentExpression (',' assignmentExpression)* + ; + +jumpStatement + : ('goto' Identifier + | ('continue'| 'break') + | 'return' expression? + | 'goto' unaryExpression // GCC extension + ) + ';' + ; + +compilationUnit + : translationUnit? EOF + ; + +translationUnit + : externalDeclaration+ + ; + +externalDeclaration + : functionDefinition + | declaration + | ';' // stray ; + ; + +functionDefinition + : declarationSpecifiers? declarator declarationList? compoundStatement + ; + +declarationList + : declaration+ + ; + +Auto : 'auto'; +Break : 'break'; +Case : 'case'; +Char : 'char'; +Const : 'const'; +Continue : 'continue'; +Default : 'default'; +Do : 'do'; +Double : 'double'; +Else : 'else'; +Enum : 'enum'; +Extern : 'extern'; +Float : 'float'; +For : 'for'; +Goto : 'goto'; +If : 'if'; +Inline : 'inline'; +Int : 'int'; +Long : 'long'; +Register : 'register'; +Restrict : 'restrict'; +Return : 'return'; +Short : 'short'; +Signed : 'signed'; +Sizeof : 'sizeof'; +Static : 'static'; +Struct : 'struct'; +Switch : 'switch'; +Typedef : 'typedef'; +Union : 'union'; +Unsigned : 'unsigned'; +Void : 'void'; +Volatile : 'volatile'; +While : 'while'; + +Alignas : '_Alignas'; +Alignof : '_Alignof'; +Atomic : '_Atomic'; +Bool : '_Bool'; +Complex : '_Complex'; +Generic : '_Generic'; +Imaginary : '_Imaginary'; +Noreturn : '_Noreturn'; +StaticAssert : '_Static_assert'; +ThreadLocal : '_Thread_local'; + +LeftParen : '('; +RightParen : ')'; +LeftBracket : '['; +RightBracket : ']'; +LeftBrace : '{'; +RightBrace : '}'; + +Less : '<'; +LessEqual : '<='; +Greater : '>'; +GreaterEqual : '>='; +LeftShift : '<<'; +RightShift : '>>'; + +Plus : '+'; +PlusPlus : '++'; +Minus : '-'; +MinusMinus : '--'; +Star : '*'; +Div : '/'; +Mod : '%'; + +And : '&'; +Or : '|'; +AndAnd : '&&'; +OrOr : '||'; +Caret : '^'; +Not : '!'; +Tilde : '~'; + +Question : '?'; +Colon : ':'; +Semi : ';'; +Comma : ','; + +Assign : '='; +// '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' | '>>=' | '&=' | '^=' | '|=' +StarAssign : '*='; +DivAssign : '/='; +ModAssign : '%='; +PlusAssign : '+='; +MinusAssign : '-='; +LeftShiftAssign : '<<='; +RightShiftAssign : '>>='; +AndAssign : '&='; +XorAssign : '^='; +OrAssign : '|='; + +Equal : '=='; +NotEqual : '!='; + +Arrow : '->'; +Dot : '.'; +Ellipsis : '...'; + +Identifier + : IdentifierNondigit + ( IdentifierNondigit + | Digit + )* + ; + +fragment +IdentifierNondigit + : Nondigit + | UniversalCharacterName + //| // other implementation-defined characters... + ; + +fragment +Nondigit + : [a-zA-Z_] + ; + +fragment +Digit + : [0-9] + ; + +fragment +UniversalCharacterName + : '\\u' HexQuad + | '\\U' HexQuad HexQuad + ; + +fragment +HexQuad + : HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit + ; + +Constant + : IntegerConstant + | FloatingConstant + //| EnumerationConstant + | CharacterConstant + ; + +fragment +IntegerConstant + : DecimalConstant IntegerSuffix? + | OctalConstant IntegerSuffix? + | HexadecimalConstant IntegerSuffix? + | BinaryConstant + ; + +fragment +BinaryConstant + : '0' [bB] [0-1]+ + ; + +fragment +DecimalConstant + : NonzeroDigit Digit* + ; + +fragment +OctalConstant + : '0' OctalDigit* + ; + +fragment +HexadecimalConstant + : HexadecimalPrefix HexadecimalDigit+ + ; + +fragment +HexadecimalPrefix + : '0' [xX] + ; + +fragment +NonzeroDigit + : [1-9] + ; + +fragment +OctalDigit + : [0-7] + ; + +fragment +HexadecimalDigit + : [0-9a-fA-F] + ; + +fragment +IntegerSuffix + : UnsignedSuffix LongSuffix? + | UnsignedSuffix LongLongSuffix + | LongSuffix UnsignedSuffix? + | LongLongSuffix UnsignedSuffix? + ; + +fragment +UnsignedSuffix + : [uU] + ; + +fragment +LongSuffix + : [lL] + ; + +fragment +LongLongSuffix + : 'll' | 'LL' + ; + +fragment +FloatingConstant + : DecimalFloatingConstant + | HexadecimalFloatingConstant + ; + +fragment +DecimalFloatingConstant + : FractionalConstant ExponentPart? FloatingSuffix? + | DigitSequence ExponentPart FloatingSuffix? + ; + +fragment +HexadecimalFloatingConstant + : HexadecimalPrefix (HexadecimalFractionalConstant | HexadecimalDigitSequence) BinaryExponentPart FloatingSuffix? + ; + +fragment +FractionalConstant + : DigitSequence? '.' DigitSequence + | DigitSequence '.' + ; + +fragment +ExponentPart + : [eE] Sign? DigitSequence + ; + +fragment +Sign + : [+-] + ; + +DigitSequence + : Digit+ + ; + +fragment +HexadecimalFractionalConstant + : HexadecimalDigitSequence? '.' HexadecimalDigitSequence + | HexadecimalDigitSequence '.' + ; + +fragment +BinaryExponentPart + : [pP] Sign? DigitSequence + ; + +fragment +HexadecimalDigitSequence + : HexadecimalDigit+ + ; + +fragment +FloatingSuffix + : [flFL] + ; + +fragment +CharacterConstant + : '\'' CCharSequence '\'' + | 'L\'' CCharSequence '\'' + | 'u\'' CCharSequence '\'' + | 'U\'' CCharSequence '\'' + ; + +fragment +CCharSequence + : CChar+ + ; + +fragment +CChar + : ~['\\\r\n] + | EscapeSequence + ; + +fragment +EscapeSequence + : SimpleEscapeSequence + | OctalEscapeSequence + | HexadecimalEscapeSequence + | UniversalCharacterName + ; + +fragment +SimpleEscapeSequence + : '\\' ['"?abfnrtv\\] + ; + +fragment +OctalEscapeSequence + : '\\' OctalDigit OctalDigit? OctalDigit? + ; + +fragment +HexadecimalEscapeSequence + : '\\x' HexadecimalDigit+ + ; + +StringLiteral + : EncodingPrefix? '"' SCharSequence? '"' + ; + +fragment +EncodingPrefix + : 'u8' + | 'u' + | 'U' + | 'L' + ; + +fragment +SCharSequence + : SChar+ + ; + +fragment +SChar + : ~["\\\r\n] + | EscapeSequence + | '\\\n' // Added line + | '\\\r\n' // Added line + ; + +ComplexDefine + : '#' Whitespace? 'define' ~[#\r\n]* + -> skip + ; + +IncludeDirective + : '#' Whitespace? 'include' Whitespace? (('"' ~[\r\n]* '"') | ('<' ~[\r\n]* '>' )) Whitespace? Newline + -> skip + ; + +// ignore the following asm blocks: +/* + asm + { + mfspr x, 286; + } + */ +AsmBlock + : 'asm' ~'{'* '{' ~'}'* '}' + -> skip + ; + +// ignore the lines generated by c preprocessor +// sample line : '#line 1 "/home/dm/files/dk1.h" 1' +LineAfterPreprocessing + : '#line' Whitespace* ~[\r\n]* + -> skip + ; + +LineDirective + : '#' Whitespace? DecimalConstant Whitespace? StringLiteral ~[\r\n]* + -> skip + ; + +PragmaDirective + : '#' Whitespace? 'pragma' Whitespace ~[\r\n]* + -> skip + ; + +Whitespace + : [ \t]+ + -> skip + ; + +Newline + : ( '\r' '\n'? + | '\n' + ) + -> skip + ; + +BlockComment + : '/*' .*? '*/' + -> skip + ; + +LineComment + : '//' ~[\r\n]* + -> skip + ; \ No newline at end of file diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/AbstractAstVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/AbstractAstVisitor.java new file mode 100644 index 0000000000..a31a61b199 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/uclid/ast/AbstractAstVisitor.java @@ -0,0 +1,17 @@ +package org.lflang.generator.uclid.ast; + +import java.util.List; + +/** Modeled after AbstractParseTreeVisitor.class */ +public abstract class AbstractAstVisitor implements AstVisitor { + + @Override + public T visit(CAst.AstNode tree) { + return tree.accept(this); + } + + @Override + public T visit(CAst.AstNode tree, List nodeList) { + return tree.accept(this, nodeList); + } +} diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/AstVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/AstVisitor.java new file mode 100644 index 0000000000..bc6910a670 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/uclid/ast/AstVisitor.java @@ -0,0 +1,25 @@ +package org.lflang.generator.uclid.ast; + +import java.util.List; + +/** Modeled after ParseTreeVisitor.class */ +public interface AstVisitor { + + /** + * Visit an AST, and return a user-defined result of the operation. + * + * @param tree The {@link CAst.AstNode} to visit. + * @return The result of visiting the parse tree. + */ + T visit(CAst.AstNode tree); + + /** + * Visit an AST with a list of other AST nodes holding some information, + * and return a user-defined result of the operation. + * + * @param tree The {@link CAst.AstNode} to visit. + * @param nodeList A list of {@link CAst.AstNode} passed down the recursive call. + * @return The result of visiting the parse tree. + */ + T visit(CAst.AstNode tree, List nodeList); +} diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/BuildAstParseTreeVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/BuildAstParseTreeVisitor.java new file mode 100644 index 0000000000..bec3381cd5 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/uclid/ast/BuildAstParseTreeVisitor.java @@ -0,0 +1,642 @@ +package org.lflang.generator.uclid.ast; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.lflang.dsl.CBaseVisitor; +import org.lflang.dsl.CParser.*; + +public class BuildAstParseTreeVisitor extends CBaseVisitor { + + @Override + public CAst.AstNode visitBlockItemList(BlockItemListContext ctx) { + CAst.StatementSequenceNode stmtSeq = new CAst.StatementSequenceNode(); + + // Populate the children. + for (BlockItemContext blockItem : ctx.blockItem()) { + // System.out.println(blockItem); + stmtSeq.children.add(visit(blockItem)); + } + + System.out.println(stmtSeq.children); + + return stmtSeq; + } + + @Override + public CAst.AstNode visitBlockItem(BlockItemContext ctx) { + if (ctx.statement() != null) + return visit(ctx.statement()); + else + return visit(ctx.declaration()); + } + + @Override + public CAst.AstNode visitDeclaration(DeclarationContext ctx) { + if (ctx.declarationSpecifiers() != null + && ctx.initDeclaratorList() != null) { + //// Extract type from declarationSpecifiers. + List declSpecList + = ctx.declarationSpecifiers().declarationSpecifier(); + + // Cannot handle more than 1 specifiers, e.g. static const int. + // We can augment the analytical capability later. + if (declSpecList.size() > 1) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "the analyzer cannot handle more than 1 specifiers,", + "e.g. static const int.", + "Marking the declaration as opaque." + )); + return new CAst.OpaqueNode(); + } + + // Check if the declaration specifier is a type specifier: e.g. int or long. + DeclarationSpecifierContext declSpec = declSpecList.get(0); + if (declSpec.typeSpecifier() == null) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "only type specifiers are supported.", + "e.g. \"static const int\" is not analyzable.", + "Marking the declaration as opaque." + )); + return new CAst.OpaqueNode(); + } + + // Check if the type specifier is what we currently support. + // Right now we only support int, long, & double. + CAst.VariableNode.Type type; + ArrayList supportedTypes = new ArrayList( + Arrays.asList( + "int", + "long", + "double", + "_Bool" + ) + ); + if (declSpec.typeSpecifier().Int() != null + || declSpec.typeSpecifier().Long() != null + || declSpec.typeSpecifier().Double() != null) + type = CAst.VariableNode.Type.INT; + else if (declSpec.typeSpecifier().Bool() != null) + type = CAst.VariableNode.Type.BOOLEAN; + // Mark the declaration unanalyzable if the type is unsupported. + else { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "unsupported type detected at " + declSpec.typeSpecifier(), + "Only " + supportedTypes + " are supported.", + "Marking the declaration as opaque." + )); + return new CAst.OpaqueNode(); + } + + //// Extract variable name and value from initDeclaratorList. + List initDeclList + = ctx.initDeclaratorList().initDeclarator(); + + // Cannot handle more than 1 initDeclarator: e.g. x = 1, y = 2; + // We can augment the analytical capability later. + if (initDeclList.size() > 1) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "more than 1 declarators are detected on a single line,", + "e.g. \"int x = 1, y = 2;\" is not yet analyzable.", + "Marking the declaration as opaque." + )); + return new CAst.OpaqueNode(); + } + + // Get the variable name from the declarator. + DeclaratorContext decl = initDeclList.get(0).declarator(); + if (decl.pointer() != null) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "pointers are currently not supported,", + "e.g. \"int *x;\" is not yet analyzable.", + "Marking the declaration as opaque." + )); + return new CAst.OpaqueNode(); + } + if (decl.gccDeclaratorExtension().size() > 0) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "GCC declarator extensions are currently not supported,", + "e.g. \"__asm\" and \"__attribute__\" are not yet analyzable.", + "Marking the declaration as opaque." + )); + return new CAst.OpaqueNode(); + } + DirectDeclaratorContext directDecl = decl.directDeclarator(); + if (directDecl.Identifier() == null) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "the variable identifier is missing.", + "Marking the declaration as opaque." + )); + return new CAst.OpaqueNode(); + } + + // Extract the name of the variable. + String name = directDecl.Identifier().getText(); + // Create a variable Ast node. + CAst.VariableNode variable = new CAst.VariableNode(type, name); + + + //// Convert the initializer to a value. + + // Make sure that there is an initializer. + InitDeclaratorContext initDecl = initDeclList.get(0); + if (initDecl.initializer() == null) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "the initializer is missing,", + "e.g. \"int x;\" is not yet analyzable.", + "Marking the declaration as opaque." + )); + return new CAst.OpaqueNode(); + + // FIXME: Use UninitCAst.VariableNode to perform special encoding. + // return new UninitCAst.VariableNode(type, name); + } + + // Extract the primaryExpression from the initializer. + if (initDecl.initializer().assignmentExpression() == null + || initDecl.initializer().assignmentExpression() + .conditionalExpression() == null) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "assignmentExpression or conditionalExpression is missing.", + "Marking the declaration as opaque." + )); + return new CAst.OpaqueNode(); + } + + // Finally return the assignment node. + CAst.AssignmentNode assignmentNode = new CAst.AssignmentNode(); + CAst.AstNode initNode = visitAssignmentExpression(initDecl.initializer().assignmentExpression()); + assignmentNode.left = variable; + assignmentNode.right = initNode; + return assignmentNode; + } + // Return OpaqueNode as default. + return new CAst.OpaqueNode(); + } + + /** + * This visit function builds StatementSequenceNode, AssignmentNode, + * OpaqueNode, IfBlockNode, + * AdditionNode, SubtractionNode, MultiplicationNode, DivisionNode, + * EqualNode, NotEqualNode, LessThanNode, GreaterThanNode, LessEqualNode, GreaterEqualNode, + * SetPortNode, ScheduleNode. + * + * @param ctx + * @return + */ + @Override + public CAst.AstNode visitStatement(StatementContext ctx) { + if (ctx.compoundStatement() != null) { + BlockItemListContext bilCtx = ctx.compoundStatement().blockItemList(); + if (bilCtx != null) { + return visitBlockItemList(bilCtx); + } + } else if (ctx.expressionStatement() != null) { + ExpressionContext exprCtx = ctx.expressionStatement().expression(); + if (exprCtx != null) { + return visitExpression(exprCtx); + } + } else if (ctx.selectionStatement() != null) { + return visitSelectionStatement(ctx.selectionStatement()); + } + return new CAst.OpaqueNode(); + } + + @Override + public CAst.AstNode visitExpression(ExpressionContext ctx) { + if (ctx.assignmentExpression().size() == 1) { + return visitAssignmentExpression(ctx.assignmentExpression().get(0)); + } + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "only one assignmentExpression in an expression is currently supported.", + "Marking the statement as opaque." + )); + return new CAst.OpaqueNode(); + } + + @Override + public CAst.AstNode visitPrimaryExpression(PrimaryExpressionContext ctx) { + if (ctx.Identifier() != null) { + return new CAst.VariableNode(ctx.Identifier().getText()); + } else if (ctx.Constant() != null) { + return new CAst.LiteralNode(ctx.Constant().getText()); + } else if (ctx.expression() != null) { + return visitExpression(ctx.expression()); + } + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "only identifier, constant, and expressions are supported in a primary expression.", + "Marking the declaration as opaque." + )); + return new CAst.OpaqueNode(); + } + + // FIXME: More checks needed. This implementation currently silently omit + // certain cases, such as arr[1]. + @Override + public CAst.AstNode visitPostfixExpression(PostfixExpressionContext ctx) { + if (ctx.PlusPlus().size() > 0 + || ctx.MinusMinus().size() > 0 + || ctx.Dot().size() > 0 + || (ctx.LeftBracket().size() > 0 && ctx.RightBracket().size() > 0)) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Postfix '++', '--', '.', '[]' are currently not supported.", + "Marking the statement as opaque." + )); + return new CAst.OpaqueNode(); + } + // State variables on the self struct, ports and actions. + if (ctx.primaryExpression() != null + && ctx.Identifier().size() == 1 + && ctx.Arrow().size() == 1) { + CAst.AstNode primaryExprNode = visitPrimaryExpression(ctx.primaryExpression()); + if (primaryExprNode instanceof CAst.LiteralNode) { + // Unreachable. + System.out.println("Unreachable!"); + return new CAst.OpaqueNode(); // FIXME: Throw an exception instead. + } + CAst.VariableNode varNode = (CAst.VariableNode) primaryExprNode; + if (varNode.name.equals("self")) { + // return a state variable node. + return new CAst.StateVarNode(ctx.Identifier().get(0).getText()); + } else if (ctx.Identifier().get(0).getText().equals("is_present")) { + // return a trigger present node. + return new CAst.TriggerValueNode(varNode.name); + } else if (ctx.Identifier().get(0).getText().equals("value")) { + // return a trigger value node. + return new CAst.TriggerIsPresentNode(varNode.name); + } else { + // Generic pointer dereference, unanalyzable. + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Generic pointer dereference is not supported in a postfix expression.", + "Marking the declaration as opaque." + )); + return new CAst.OpaqueNode(); + } + } + // LF built-in function calls (set or schedule) + if (ctx.primaryExpression() != null + && ctx.argumentExpressionList().size() == 1 + && ctx.LeftParen() != null && ctx.RightParen() != null) { + CAst.AstNode primaryExprNode = visitPrimaryExpression(ctx.primaryExpression()); + List params = ctx.argumentExpressionList().get(0).assignmentExpression(); + if (primaryExprNode instanceof CAst.LiteralNode) { + // Unreachable. + System.out.println("Unreachable!"); + return new CAst.OpaqueNode(); // FIXME: Throw an exception instead. + } + CAst.VariableNode varNode = (CAst.VariableNode) primaryExprNode; + if (varNode.name.equals("lf_set")) { + // return a set port node. + if (params.size() != 2) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "lf_set must have 2 arguments. Detected " + ctx.argumentExpressionList().size(), + "Marking the function call as opaque." + )); + return new CAst.OpaqueNode(); + } + CAst.SetPortNode node = new CAst.SetPortNode(); + node.left = visitAssignmentExpression(params.get(0)); + node.right = visitAssignmentExpression(params.get(1)); + return node; + } else if (varNode.name.equals("lf_schedule")) { + // return a set port node. + if (params.size() != 2 + && params.size() != 3) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "lf_schedule must have 2 or 3 arguments. Detected " + ctx.argumentExpressionList().size(), + "Marking the function call as opaque." + )); + return new CAst.OpaqueNode(); + } + CAst.ScheduleActionNode node = new CAst.ScheduleActionNode(); + for (AssignmentExpressionContext param : params) { + node.children.add(visitAssignmentExpression(param)); + } + return node; + } else { + // Generic pointer dereference, unanalyzable. + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Generic pointer dereference is not supported in a postfix expression.", + "Marking the declaration as opaque." + )); + return new CAst.OpaqueNode(); + } + } + // Variable or literal + if (ctx.primaryExpression() != null) { + return visitPrimaryExpression(ctx.primaryExpression()); + } + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "only an identifier, constant, state variable, port, and action are supported in a primary expression.", + "Marking the declaration as opaque." + )); + return new CAst.OpaqueNode(); + } + + @Override + public CAst.AstNode visitUnaryExpression(UnaryExpressionContext ctx) { + if (ctx.PlusPlus().size() > 0 + || ctx.MinusMinus().size() > 0 + || ctx.Sizeof().size() > 0) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Prefix '++', '--', and 'sizeof' are currently not supported.", + "Marking the statement as opaque." + )); + return new CAst.OpaqueNode(); + } + if (ctx.postfixExpression() != null) { + return visitPostfixExpression(ctx.postfixExpression()); + } + if (ctx.unaryOperator() != null + && ctx.unaryOperator().Not() != null + && ctx.castExpression() != null) { + CAst.LogicalNotNode node = new CAst.LogicalNotNode(); + node.child = visitCastExpression(ctx.castExpression()); + return node; + } + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "only postfixExpression and '!' in a unaryExpression is currently supported.", + "Marking the statement as opaque." + )); + return new CAst.OpaqueNode(); + } + + @Override + public CAst.AstNode visitCastExpression(CastExpressionContext ctx) { + if (ctx.unaryExpression() != null) { + return visitUnaryExpression(ctx.unaryExpression()); + } + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "only unaryExpression in a castExpression is currently supported.", + "Marking the statement as opaque." + )); + return new CAst.OpaqueNode(); + } + + @Override + public CAst.AstNode visitMultiplicativeExpression(MultiplicativeExpressionContext ctx) { + if (ctx.castExpression().size() > 1) { + CAst.AstNodeBinary node; + if (ctx.Star() != null) { + node = new CAst.MultiplicationNode(); + } else if (ctx.Div() != null) { + node = new CAst.DivisionNode(); + } else if (ctx.Mod() != null) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Mod expression '%' is currently unsupported.", + "Marking the expression as opaque." + )); + return new CAst.OpaqueNode(); + } else { + node = new CAst.AstNodeBinary(); + } + node.left = visitCastExpression(ctx.castExpression().get(0)); + node.right = visitCastExpression(ctx.castExpression().get(1)); + return node; + } + return visitCastExpression(ctx.castExpression().get(0)); + } + + @Override + public CAst.AstNode visitAdditiveExpression(AdditiveExpressionContext ctx) { + if (ctx.multiplicativeExpression().size() > 1) { + CAst.AstNodeBinary node; + if (ctx.Plus() != null) { + node = new CAst.AdditionNode(); + } else if (ctx.Minus() != null) { + node = new CAst.SubtractionNode(); + } else { + node = new CAst.AstNodeBinary(); + } + node.left = visitMultiplicativeExpression(ctx.multiplicativeExpression().get(0)); + node.right = visitMultiplicativeExpression(ctx.multiplicativeExpression().get(1)); + return node; + } + return visitMultiplicativeExpression(ctx.multiplicativeExpression().get(0)); + } + + @Override + public CAst.AstNode visitShiftExpression(ShiftExpressionContext ctx) { + if (ctx.additiveExpression().size() > 1) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Shift expression '<<' or '>>' is currently unsupported.", + "Marking the expression as opaque." + )); + return new CAst.OpaqueNode(); + } + return visitAdditiveExpression(ctx.additiveExpression().get(0)); + } + + @Override + public CAst.AstNode visitRelationalExpression(RelationalExpressionContext ctx) { + if (ctx.shiftExpression().size() > 1) { + CAst.AstNodeBinary node; + if (ctx.Less() != null) { + node = new CAst.LessThanNode(); + } else if (ctx.LessEqual() != null) { + node = new CAst.LessEqualNode(); + } else if (ctx.Greater() != null) { + node = new CAst.GreaterThanNode(); + } else if (ctx.GreaterEqual() != null) { + node = new CAst.GreaterEqualNode(); + } else { + node = new CAst.AstNodeBinary(); + } + node.left = visitShiftExpression(ctx.shiftExpression().get(0)); + node.right = visitShiftExpression(ctx.shiftExpression().get(1)); + return node; + } + return visitShiftExpression(ctx.shiftExpression().get(0)); + } + + @Override + public CAst.AstNode visitEqualityExpression(EqualityExpressionContext ctx) { + if (ctx.relationalExpression().size() > 1) { + CAst.AstNodeBinary node; + if (ctx.Equal().size() > 0) { + node = new CAst.EqualNode(); + } + else if (ctx.NotEqual().size() > 0) { + node = new CAst.NotEqualNode(); + } else { + node = new CAst.AstNodeBinary(); + } + node.left = visitRelationalExpression(ctx.relationalExpression().get(0)); + node.right = visitRelationalExpression(ctx.relationalExpression().get(1)); + return node; + } + return visitRelationalExpression(ctx.relationalExpression().get(0)); + } + + @Override + public CAst.AstNode visitAndExpression(AndExpressionContext ctx) { + if (ctx.equalityExpression().size() > 1) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "And expression '&' is currently unsupported.", + "Marking the expression as opaque." + )); + return new CAst.OpaqueNode(); + } + return visitEqualityExpression(ctx.equalityExpression().get(0)); + } + + @Override + public CAst.AstNode visitExclusiveOrExpression(ExclusiveOrExpressionContext ctx) { + if (ctx.andExpression().size() > 1) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Exclusive Or '^' is currently unsupported.", + "Marking the expression as opaque." + )); + return new CAst.OpaqueNode(); + } + return visitAndExpression(ctx.andExpression().get(0)); + } + + @Override + public CAst.AstNode visitInclusiveOrExpression(InclusiveOrExpressionContext ctx) { + if (ctx.exclusiveOrExpression().size() > 1) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Inclusive Or '|' is currently unsupported.", + "Marking the expression as opaque." + )); + return new CAst.OpaqueNode(); + } + return visitExclusiveOrExpression(ctx.exclusiveOrExpression().get(0)); + } + + @Override + public CAst.AstNode visitLogicalAndExpression(LogicalAndExpressionContext ctx) { + if (ctx.inclusiveOrExpression().size() > 1) { + CAst.LogicalAndNode node = new CAst.LogicalAndNode(); + node.left = visitInclusiveOrExpression(ctx.inclusiveOrExpression().get(0)); + node.right = visitInclusiveOrExpression(ctx.inclusiveOrExpression().get(1)); + return node; + } + return visitInclusiveOrExpression(ctx.inclusiveOrExpression().get(0)); + } + + @Override + public CAst.AstNode visitLogicalOrExpression(LogicalOrExpressionContext ctx) { + if (ctx.logicalAndExpression().size() > 1) { + CAst.LogicalOrNode node = new CAst.LogicalOrNode(); + node.left = visitLogicalAndExpression(ctx.logicalAndExpression().get(0)); + node.right = visitLogicalAndExpression(ctx.logicalAndExpression().get(1)); + return node; + } + return visitLogicalAndExpression(ctx.logicalAndExpression().get(0)); + } + + @Override + public CAst.AstNode visitConditionalExpression(ConditionalExpressionContext ctx) { + if (ctx.expression() != null) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Currently do not support inline conditional expression.", + "Marking the expression as opaque." + )); + return new CAst.OpaqueNode(); + } + return visitLogicalOrExpression(ctx.logicalOrExpression()); + } + + @Override + public CAst.AstNode visitAssignmentExpression(AssignmentExpressionContext ctx) { + if (ctx.conditionalExpression() != null) { + return visitConditionalExpression(ctx.conditionalExpression()); + } + if (ctx.unaryExpression() != null + && ctx.assignmentExpression() != null) { + CAst.AstNodeBinary assignmentNode = new CAst.AssignmentNode(); + assignmentNode.left = visitUnaryExpression(ctx.unaryExpression()); + if (ctx.assignmentOperator().getText().equals("=")) { + assignmentNode.right = visitAssignmentExpression(ctx.assignmentExpression()); + } + else if (ctx.assignmentOperator().getText().equals("+=")) { + CAst.AdditionNode subnode = new CAst.AdditionNode(); + subnode.left = visitUnaryExpression(ctx.unaryExpression()); + subnode.right = visitAssignmentExpression(ctx.assignmentExpression()); + assignmentNode.right = subnode; + } + else if (ctx.assignmentOperator().getText().equals("-=")) { + CAst.SubtractionNode subnode = new CAst.SubtractionNode(); + subnode.left = visitUnaryExpression(ctx.unaryExpression()); + subnode.right = visitAssignmentExpression(ctx.assignmentExpression()); + assignmentNode.right = subnode; + } + else if (ctx.assignmentOperator().getText().equals("*=")) { + CAst.MultiplicationNode subnode = new CAst.MultiplicationNode(); + subnode.left = visitUnaryExpression(ctx.unaryExpression()); + subnode.right = visitAssignmentExpression(ctx.assignmentExpression()); + assignmentNode.right = subnode; + } + else if (ctx.assignmentOperator().getText().equals("/=")) { + CAst.DivisionNode subnode = new CAst.DivisionNode(); + subnode.left = visitUnaryExpression(ctx.unaryExpression()); + subnode.right = visitAssignmentExpression(ctx.assignmentExpression()); + assignmentNode.right = subnode; + } + else { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Only '=', '+=', '-=', '*=', '/=' assignment operators are supported.", + "Marking the expression as opaque." + )); + return new CAst.OpaqueNode(); + } + return assignmentNode; + } + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "DigitSequence in an assignmentExpression is currently not supported.", + "Marking the expression as opaque." + )); + return new CAst.OpaqueNode(); + } + + @Override + public CAst.AstNode visitSelectionStatement(SelectionStatementContext ctx) { + if (ctx.Switch() != null) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Switch case statement is currently not supported.", + "Marking the expression as opaque." + )); + return new CAst.OpaqueNode(); + } + CAst.IfBlockNode ifBlockNode = new CAst.IfBlockNode(); + CAst.IfBodyNode ifBodyNode = new CAst.IfBodyNode(); + ifBlockNode.left = visitExpression(ctx.expression()); + ifBlockNode.right = ifBodyNode; + ifBodyNode.left = visitStatement(ctx.statement().get(0)); + if (ctx.statement().size() > 1) { + ifBodyNode.right = visitStatement(ctx.statement().get(1)); + } + return ifBlockNode; + } +} diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CAst.java b/org.lflang/src/org/lflang/generator/uclid/ast/CAst.java new file mode 100644 index 0000000000..95c0927311 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/uclid/ast/CAst.java @@ -0,0 +1,341 @@ +package org.lflang.generator.uclid.ast; + +import java.util.ArrayList; +import java.util.List; + +public class CAst { + + public static class AstNode implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitAstNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitAstNode(this, nodeList); + } + } + + public static class AstNodeUnary extends AstNode implements Visitable { + public AstNode child; + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitAstNodeUnary(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitAstNodeUnary(this, nodeList); + } + } + + public static class AstNodeBinary extends AstNode implements Visitable { + public AstNode left; + public AstNode right; + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitAstNodeBinary(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitAstNodeBinary(this, nodeList); + } + } + + public static class AstNodeDynamic extends AstNode implements Visitable { + public ArrayList children = new ArrayList<>(); + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitAstNodeDynamic(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitAstNodeDynamic(this, nodeList); + } + } + + public static class AssignmentNode extends AstNodeBinary implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitAssignmentNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitAssignmentNode(this, nodeList); + } + } + + /** + * AST node for an if block. + * The left node is the condition. + * The right node is the if body. + */ + public static class IfBlockNode extends AstNodeBinary implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitIfBlockNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitIfBlockNode(this, nodeList); + } + } + + /** + * AST node for an if block. + * The left node is the then branch. + * The right node is the else branch. + */ + public static class IfBodyNode extends AstNodeBinary implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitIfBodyNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitIfBodyNode(this, nodeList); + } + } + + public static class LiteralNode extends AstNode implements Visitable { + public String literal; + public LiteralNode(String literal) { + super(); + this.literal = literal; + } + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitLiteralNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitLiteralNode(this, nodeList); + } + } + + public static class LogicalNotNode extends AstNodeUnary implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitLogicalNotNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitLogicalNotNode(this, nodeList); + } + } + + public static class LogicalAndNode extends AstNodeBinary implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitLogicalAndNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitLogicalAndNode(this, nodeList); + } + } + + public static class LogicalOrNode extends AstNodeBinary implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitLogicalOrNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitLogicalOrNode(this, nodeList); + } + } + + /** + * An Ast node that indicates the code + * represented by this node is unanalyzable. + */ + public static class OpaqueNode extends AstNode implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitOpaqueNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitOpaqueNode(this, nodeList); + } + } + + public static class StatementSequenceNode extends AstNodeDynamic implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitStatementSequenceNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitStatementSequenceNode(this, nodeList); + } + } + + public static class VariableNode extends AstNode implements Visitable { + public enum Type { + UNKNOWN, INT, BOOLEAN + } + public Type type; + public String name; + public VariableNode(String name) { + super(); + this.type = Type.UNKNOWN; + this.name = name; + } + public VariableNode(Type type, String name) { + super(); + this.type = type; + this.name = name; + } + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitVariableNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitVariableNode(this, nodeList); + } + } + + /** Arithmetic operations */ + + public static class AdditionNode extends AstNodeBinary implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitAdditionNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitAdditionNode(this, nodeList); + } + } + + public static class SubtractionNode extends AstNodeBinary implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitSubtractionNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitSubtractionNode(this, nodeList); + } + } + + public static class MultiplicationNode extends AstNodeBinary implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitMultiplicationNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitMultiplicationNode(this, nodeList); + } + } + + public static class DivisionNode extends AstNodeBinary implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitDivisionNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitDivisionNode(this, nodeList); + } + } + + /** Comparison operators */ + + public static class EqualNode extends AstNodeBinary implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitEqualNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitEqualNode(this, nodeList); + } + } + + public static class NotEqualNode extends AstNodeBinary implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitNotEqualNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitNotEqualNode(this, nodeList); + } + } + + public static class LessThanNode extends AstNodeBinary implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitLessThanNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitLessThanNode(this, nodeList); + } + } + + public static class LessEqualNode extends AstNodeBinary implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitLessEqualNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitLessEqualNode(this, nodeList); + } + } + + public static class GreaterThanNode extends AstNodeBinary implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitGreaterThanNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitGreaterThanNode(this, nodeList); + } + } + + public static class GreaterEqualNode extends AstNodeBinary implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitGreaterEqualNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitGreaterEqualNode(this, nodeList); + } + } + + /** LF built-in operations */ + /** + * AST node for an lf_set call. The left child is the port being set. + * The right node is the value of the port. + */ + public static class SetPortNode extends AstNodeBinary implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitSetPortNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitSetPortNode(this, nodeList); + } + } + + /** + * action; // self struct variable access is a postfixExpression. + * value; // Could be literal, variable, or pointer. + * additionalDelay; + */ + public static class ScheduleActionNode extends AstNodeDynamic implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitScheduleActionNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitScheduleActionNode(this, nodeList); + } + } + + /** + * Handle state variables appearing as self-> + */ + public static class StateVarNode extends AstNode implements Visitable { + public String name; + public StateVarNode(String name) { + this.name = name; + } + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitStateVarNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitStateVarNode(this, nodeList); + } + } + + /** + * Handle trigger values appearing as ->value + */ + public static class TriggerValueNode extends AstNode implements Visitable { + public String name; + public TriggerValueNode(String name) { + this.name = name; + } + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitTriggerValueNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitTriggerValueNode(this, nodeList); + } + } + + /** + * Handle trigger presence appearing as ->is_present + */ + public static class TriggerIsPresentNode extends AstNode implements Visitable { + public String name; + public TriggerIsPresentNode(String name) { + this.name = name; + } + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitTriggerIsPresentNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitTriggerIsPresentNode(this, nodeList); + } + } +} diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CAstVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/CAstVisitor.java new file mode 100644 index 0000000000..f6efbb7946 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/uclid/ast/CAstVisitor.java @@ -0,0 +1,74 @@ +package org.lflang.generator.uclid.ast; + +import java.util.List; + +/** Modeled after CVisitor.java */ +public interface CAstVisitor extends AstVisitor { + + T visitAstNode(CAst.AstNode node); + T visitAstNodeUnary(CAst.AstNodeUnary node); + T visitAstNodeBinary(CAst.AstNodeBinary node); + T visitAstNodeDynamic(CAst.AstNodeDynamic node); + T visitAssignmentNode(CAst.AssignmentNode node); + T visitIfBlockNode(CAst.IfBlockNode node); + T visitIfBodyNode(CAst.IfBodyNode node); + T visitLiteralNode(CAst.LiteralNode node); + T visitLogicalNotNode(CAst.LogicalNotNode node); + T visitLogicalAndNode(CAst.LogicalAndNode node); + T visitLogicalOrNode(CAst.LogicalOrNode node); + T visitOpaqueNode(CAst.OpaqueNode node); + T visitStatementSequenceNode(CAst.StatementSequenceNode node); + T visitVariableNode(CAst.VariableNode node); + + T visitAdditionNode(CAst.AdditionNode node); + T visitSubtractionNode(CAst.SubtractionNode node); + T visitMultiplicationNode(CAst.MultiplicationNode node); + T visitDivisionNode(CAst.DivisionNode node); + + T visitEqualNode(CAst.EqualNode node); + T visitNotEqualNode(CAst.NotEqualNode node); + T visitLessThanNode(CAst.LessThanNode node); + T visitLessEqualNode(CAst.LessEqualNode node); + T visitGreaterThanNode(CAst.GreaterThanNode node); + T visitGreaterEqualNode(CAst.GreaterEqualNode node); + + T visitSetPortNode(CAst.SetPortNode node); + T visitScheduleActionNode(CAst.ScheduleActionNode node); + T visitStateVarNode(CAst.StateVarNode node); + T visitTriggerValueNode(CAst.TriggerValueNode node); + T visitTriggerIsPresentNode(CAst.TriggerIsPresentNode node); + + /** Used for converting an AST into If Normal Form. */ + T visitAstNode(CAst.AstNode node, List nodeList); + T visitAstNodeUnary(CAst.AstNodeUnary node, List nodeList); + T visitAstNodeBinary(CAst.AstNodeBinary node, List nodeList); + T visitAstNodeDynamic(CAst.AstNodeDynamic node, List nodeList); + T visitAssignmentNode(CAst.AssignmentNode node, List nodeList); + T visitIfBlockNode(CAst.IfBlockNode node, List nodeList); + T visitIfBodyNode(CAst.IfBodyNode node, List nodeList); + T visitLiteralNode(CAst.LiteralNode node, List nodeList); + T visitLogicalNotNode(CAst.LogicalNotNode node, List nodeList); + T visitLogicalAndNode(CAst.LogicalAndNode node, List nodeList); + T visitLogicalOrNode(CAst.LogicalOrNode node, List nodeList); + T visitOpaqueNode(CAst.OpaqueNode node, List nodeList); + T visitStatementSequenceNode(CAst.StatementSequenceNode node, List nodeList); + T visitVariableNode(CAst.VariableNode node, List nodeList); + + T visitAdditionNode(CAst.AdditionNode node, List nodeList); + T visitSubtractionNode(CAst.SubtractionNode node, List nodeList); + T visitMultiplicationNode(CAst.MultiplicationNode node, List nodeList); + T visitDivisionNode(CAst.DivisionNode node, List nodeList); + + T visitEqualNode(CAst.EqualNode node, List nodeList); + T visitNotEqualNode(CAst.NotEqualNode node, List nodeList); + T visitLessThanNode(CAst.LessThanNode node, List nodeList); + T visitLessEqualNode(CAst.LessEqualNode node, List nodeList); + T visitGreaterThanNode(CAst.GreaterThanNode node, List nodeList); + T visitGreaterEqualNode(CAst.GreaterEqualNode node, List nodeList); + + T visitSetPortNode(CAst.SetPortNode node, List nodeList); + T visitScheduleActionNode(CAst.ScheduleActionNode node, List nodeList); + T visitStateVarNode(CAst.StateVarNode node, List nodeList); + T visitTriggerValueNode(CAst.TriggerValueNode node, List nodeList); + T visitTriggerIsPresentNode(CAst.TriggerIsPresentNode node, List nodeList); +} diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CBaseAstVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/CBaseAstVisitor.java new file mode 100644 index 0000000000..fa55848d70 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/uclid/ast/CBaseAstVisitor.java @@ -0,0 +1,381 @@ +package org.lflang.generator.uclid.ast; + +import java.util.List; + +/** Modeled after CBaseVisitor.java */ +public class CBaseAstVisitor extends AbstractAstVisitor implements CAstVisitor { + + /** + * These default implementations are not meant to be used. + * They should be overriden by the child class. + * In theory, this base visitor can be deleted? + * Let's keep it here for now for consistency. + */ + @Override + public T visitAstNode(CAst.AstNode node) { + System.out.print("[visitAstNode] "); + System.out.println("Hi, I am " + node); + return null; + } + + @Override + public T visitAstNodeUnary(CAst.AstNodeUnary node) { + System.out.print("[visitAstNodeUnary] "); + System.out.println("Hi, I am " + node); + if (node.child != null) { + T result = visit(node.child); + } else { + System.out.println("*** Child is empty in " + node + "!"); + } + return null; + } + + @Override + public T visitAstNodeBinary(CAst.AstNodeBinary node) { + System.out.print("[visitAstNodeBinary] "); + System.out.println("Hi, I am " + node); + if (node.left != null) { + T leftResult = visit(node.left); + } else System.out.println("*** Left child is empty in " + node + "!"); + if (node.right != null) { + T rightResult = visit(node.right); + } else System.out.println("*** Right child is empty in " + node + "!"); + // Aggregate results... + return null; + } + + @Override + public T visitAstNodeDynamic(CAst.AstNodeDynamic node) { + System.out.print("[visitAstNodeDynamic] "); + System.out.println("Hi, I am " + node); + for (CAst.AstNode n : node.children) { + T result = visit(n); + } + return null; + } + + @Override + public T visitAssignmentNode(CAst.AssignmentNode node) { + // The default implementation reuses visitAstNodeBinary(node). + System.out.print("[visitAssignmentNode] "); + return visitAstNodeBinary(node); + } + + @Override + public T visitIfBlockNode(CAst.IfBlockNode node) { + System.out.print("[visitIfBlockNode] "); + return visitAstNodeBinary(node); + } + + @Override + public T visitIfBodyNode(CAst.IfBodyNode node) { + System.out.print("[visitIfBodyNode] "); + return visitAstNodeBinary(node); + } + + @Override + public T visitLiteralNode(CAst.LiteralNode node) { + System.out.print("[visitLiteralNode] "); + System.out.println("Hi, I am " + node + " with literal " + node.literal); + return null; + } + + @Override + public T visitLogicalNotNode(CAst.LogicalNotNode node) { + System.out.print("[visitLogicalNotNode] "); + return visitAstNodeUnary(node); + } + + @Override + public T visitLogicalAndNode(CAst.LogicalAndNode node) { + System.out.print("[visitLogicalAndNode] "); + return visitAstNodeBinary(node); + } + + @Override + public T visitLogicalOrNode(CAst.LogicalOrNode node) { + System.out.print("[visitLogicalOrNode] "); + return visitAstNodeBinary(node); + } + + @Override + public T visitOpaqueNode(CAst.OpaqueNode node) { + System.out.print("[visitOpaqueNode] "); + return visitAstNode(node); + } + + @Override + public T visitStatementSequenceNode(CAst.StatementSequenceNode node) { + System.out.print("[visitStatementSequenceNode] "); + return visitAstNodeDynamic(node); + } + + @Override + public T visitVariableNode(CAst.VariableNode node) { + System.out.print("[visitVariableNode] "); + System.out.println("Hi, I am " + node + ": (" + node.type + ", " + node.name + ")"); + return null; + } + + /** Arithmetic operators */ + + @Override + public T visitAdditionNode(CAst.AdditionNode node) { + // The default implementation reuses visitAstNodeBinary(node). + System.out.print("[visitAdditionNode] "); + return visitAstNodeBinary(node); + } + + @Override + public T visitSubtractionNode(CAst.SubtractionNode node) { + // The default implementation reuses visitAstNodeBinary(node). + System.out.print("[visitSubtractionNode] "); + return visitAstNodeBinary(node); + } + + @Override + public T visitMultiplicationNode(CAst.MultiplicationNode node) { + // The default implementation reuses visitAstNodeBinary(node). + System.out.print("[visitMultiplicationNode] "); + return visitAstNodeBinary(node); + } + + @Override + public T visitDivisionNode(CAst.DivisionNode node) { + // The default implementation reuses visitAstNodeBinary(node). + System.out.print("[visitDivisionNode] "); + return visitAstNodeBinary(node); + } + + /** Comparison operators */ + + @Override + public T visitEqualNode(CAst.EqualNode node) { + // The default implementation reuses visitAstNodeBinary(node). + System.out.print("[visitEqualNode] "); + return visitAstNodeBinary(node); + } + + @Override + public T visitNotEqualNode(CAst.NotEqualNode node) { + // The default implementation reuses visitAstNodeBinary(node). + System.out.print("[visitNotEqualNode] "); + return visitAstNodeBinary(node); + } + + @Override + public T visitLessThanNode(CAst.LessThanNode node) { + // The default implementation reuses visitAstNodeBinary(node). + System.out.print("[visitLessThanNode] "); + return visitAstNodeBinary(node); + } + + @Override + public T visitLessEqualNode(CAst.LessEqualNode node) { + // The default implementation reuses visitAstNodeBinary(node). + System.out.print("[visitLessEqualNode] "); + return visitAstNodeBinary(node); + } + + @Override + public T visitGreaterThanNode(CAst.GreaterThanNode node) { + // The default implementation reuses visitAstNodeBinary(node). + System.out.print("[visitGreaterThanNode] "); + return visitAstNodeBinary(node); + } + + @Override + public T visitGreaterEqualNode(CAst.GreaterEqualNode node) { + // The default implementation reuses visitAstNodeBinary(node). + System.out.print("[visitGreaterEqualNode] "); + return visitAstNodeBinary(node); + } + + /** LF built-in operations */ + + @Override + public T visitSetPortNode(CAst.SetPortNode node) { + System.out.print("[visitSetPortNode] "); + return visitAstNodeBinary(node); + } + + @Override + public T visitScheduleActionNode(CAst.ScheduleActionNode node) { + System.out.print("[visitScheduleActionNode] "); + return visitAstNodeDynamic(node); + } + + @Override + public T visitStateVarNode(CAst.StateVarNode node) { + System.out.print("[visitStateVarNode] "); + System.out.println("Hi, I am " + node + ": (" + node.name + ")"); + return null; + } + + @Override + public T visitTriggerValueNode(CAst.TriggerValueNode node) { + System.out.print("[visitTriggerValueNode] "); + System.out.println("Hi, I am " + node + ": (" + node.name + ")"); + return null; + } + + @Override + public T visitTriggerIsPresentNode(CAst.TriggerIsPresentNode node) { + System.out.print("[visitTriggerIsPresentNode] "); + System.out.println("Hi, I am " + node + ": (" + node.name + ")"); + return null; + } + + //// With one more parameter. + @Override + public T visitAstNode(CAst.AstNode node, List nodeList) { + return visitAstNode(node); + } + + @Override + public T visitAstNodeUnary(CAst.AstNodeUnary node, List nodeList) { + return visitAstNodeUnary(node); + } + + @Override + public T visitAstNodeBinary(CAst.AstNodeBinary node, List nodeList) { + return visitAstNodeBinary(node); + } + + @Override + public T visitAstNodeDynamic(CAst.AstNodeDynamic node, List nodeList) { + return visitAstNodeDynamic(node); + } + + @Override + public T visitAssignmentNode(CAst.AssignmentNode node, List nodeList) { + return visitAssignmentNode(node); + } + + @Override + public T visitIfBlockNode(CAst.IfBlockNode node, List nodeList) { + return visitIfBlockNode(node); + } + + @Override + public T visitIfBodyNode(CAst.IfBodyNode node, List nodeList) { + return visitIfBodyNode(node); + } + + @Override + public T visitLiteralNode(CAst.LiteralNode node, List nodeList) { + return visitLiteralNode(node); + } + + @Override + public T visitLogicalNotNode(CAst.LogicalNotNode node, List nodeList) { + return visitLogicalNotNode(node); + } + + @Override + public T visitLogicalAndNode(CAst.LogicalAndNode node, List nodeList) { + return visitLogicalAndNode(node); + } + + @Override + public T visitLogicalOrNode(CAst.LogicalOrNode node, List nodeList) { + return visitLogicalOrNode(node); + } + + @Override + public T visitOpaqueNode(CAst.OpaqueNode node, List nodeList) { + return visitOpaqueNode(node); + } + + @Override + public T visitStatementSequenceNode(CAst.StatementSequenceNode node, List nodeList) { + return visitStatementSequenceNode(node); + } + + @Override + public T visitVariableNode(CAst.VariableNode node, List nodeList) { + return visitVariableNode(node); + } + + /** Arithmetic operators */ + + @Override + public T visitAdditionNode(CAst.AdditionNode node, List nodeList) { + return visitAdditionNode(node); + } + + @Override + public T visitSubtractionNode(CAst.SubtractionNode node, List nodeList) { + return visitSubtractionNode(node); + } + + @Override + public T visitMultiplicationNode(CAst.MultiplicationNode node, List nodeList) { + return visitMultiplicationNode(node); + } + + @Override + public T visitDivisionNode(CAst.DivisionNode node, List nodeList) { + return visitDivisionNode(node); + } + + /** Comparison operators */ + + @Override + public T visitEqualNode(CAst.EqualNode node, List nodeList) { + return visitEqualNode(node); + } + + @Override + public T visitNotEqualNode(CAst.NotEqualNode node, List nodeList) { + return visitNotEqualNode(node); + } + + @Override + public T visitLessThanNode(CAst.LessThanNode node, List nodeList) { + return visitLessThanNode(node); + } + + @Override + public T visitLessEqualNode(CAst.LessEqualNode node, List nodeList) { + return visitLessEqualNode(node); + } + + @Override + public T visitGreaterThanNode(CAst.GreaterThanNode node, List nodeList) { + return visitGreaterThanNode(node); + } + + @Override + public T visitGreaterEqualNode(CAst.GreaterEqualNode node, List nodeList) { + return visitGreaterEqualNode(node); + } + + /** LF built-in operations */ + + @Override + public T visitSetPortNode(CAst.SetPortNode node, List nodeList) { + return visitSetPortNode(node); + } + + @Override + public T visitScheduleActionNode(CAst.ScheduleActionNode node, List nodeList) { + return visitScheduleActionNode(node); + } + + @Override + public T visitStateVarNode(CAst.StateVarNode node, List nodeList) { + return visitStateVarNode(node); + } + + @Override + public T visitTriggerValueNode(CAst.TriggerValueNode node, List nodeList) { + return visitTriggerValueNode(node); + } + + @Override + public T visitTriggerIsPresentNode(CAst.TriggerIsPresentNode node, List nodeList) { + return visitTriggerIsPresentNode(node); + } + +} diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/IfNormalFormAstVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/IfNormalFormAstVisitor.java new file mode 100644 index 0000000000..a6bb4ae29b --- /dev/null +++ b/org.lflang/src/org/lflang/generator/uclid/ast/IfNormalFormAstVisitor.java @@ -0,0 +1,111 @@ +package org.lflang.generator.uclid.ast; + +import java.util.ArrayList; +import java.util.List; + +/** + * An AST visitor that converts an original AST into the If Normal Form. + * See "Bounded Model Checking of Software using SMT Solvers instead of + * SAT Solvers" for details about the If Normal Form + * (https://link.springer.com/chapter/10.1007/11691617_9). + * + * There are several requirements for an AST to be in INF: + * 1. There should be a single StatementSequence (SS) root node; + * 2. Each child of the SS node should be an IfBlockNode; + * 3. Variables in a subsequent child should be marked as "primed" + * versions of those in the previous child. + * + * Limitations: + * 1. The implementation does not take the scope into account. + * E.g. "int i = 0; { int j = 0; }" is treated the same as + * "int i = 0; int j = 0;". + * 2. Due to the above limitation, the implementation assumes + * that each variable has a unique name. + * E.g. "{ int i = 0; }{ int i = 0; }" is an ill-formed program. + * + * In this program, visit() is the normalise() in the paper. + */ +public class IfNormalFormAstVisitor extends CBaseAstVisitor { + + public CAst.StatementSequenceNode INF = new CAst.StatementSequenceNode(); + + @Override + public Void visitStatementSequenceNode(CAst.StatementSequenceNode node, List conditions) { + // Create a new StatementSequenceNode. + for (CAst.AstNode child : node.children) { + visit(child, conditions); + } + return null; + } + + @Override + public Void visitAssignmentNode(CAst.AssignmentNode node, List conditions) { + this.INF.children.add(generateIfBlock(node, conditions)); + return null; + } + + @Override + public Void visitSetPortNode(CAst.SetPortNode node, List conditions) { + this.INF.children.add(generateIfBlock(node, conditions)); + return null; + } + + @Override + public Void visitScheduleActionNode(CAst.ScheduleActionNode node, List conditions) { + this.INF.children.add(generateIfBlock(node, conditions)); + return null; + } + + @Override + public Void visitIfBlockNode(CAst.IfBlockNode node, List conditions) { + List leftConditions = new ArrayList<>(conditions); + leftConditions.add(node.left); + visit(((CAst.IfBodyNode)node.right).left, leftConditions); + if (((CAst.IfBodyNode)node.right).right != null) { + List rightConditions = new ArrayList<>(conditions); + // Add a not node. + CAst.LogicalNotNode notNode = new CAst.LogicalNotNode(); + notNode.child = node.left; // Negate the condition. + rightConditions.add(notNode); + visit(((CAst.IfBodyNode)node.right).right, rightConditions); + } + return null; + } + + private CAst.AstNode takeConjunction(List conditions) { + if (conditions.size() == 0) { + return new CAst.LiteralNode("true"); + } else if (conditions.size() == 1) { + return conditions.get(0); + } else { + // Take the conjunction of all the conditions. + CAst.LogicalAndNode top = new CAst.LogicalAndNode(); + CAst.LogicalAndNode cur = top; + for (int i = 0; i < conditions.size()-1; i++) { + cur.left = conditions.get(i); + if (i == conditions.size()-2) { + cur.right = conditions.get(i+1); + } else { + cur.right = new CAst.LogicalAndNode(); + cur =(CAst.LogicalAndNode)cur.right; + } + } + return top; + } + } + + private CAst.IfBlockNode generateIfBlock(CAst.AstNode node, List conditions) { + // Create an If Block node. + CAst.IfBlockNode ifNode = new CAst.IfBlockNode(); + // Set the condition of the if block node. + CAst.AstNode conjunction = takeConjunction(conditions); + ifNode.left = conjunction; + // Create a new body node. + CAst.IfBodyNode body = new CAst.IfBodyNode(); + ifNode.right = body; + // Set the then branch to the assignment. + body.left = node; + + return ifNode; + } +} diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/Visitable.java b/org.lflang/src/org/lflang/generator/uclid/ast/Visitable.java new file mode 100644 index 0000000000..c7735057e0 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/uclid/ast/Visitable.java @@ -0,0 +1,12 @@ +package org.lflang.generator.uclid.ast; + +import java.util.List; + +public interface Visitable { + + /** The {@link AstVisitor} needs a double dispatch method. */ + T accept(AstVisitor visitor); + + /** The {@link AstVisitor} needs a double dispatch method. */ + T accept(AstVisitor visitor, List nodeList); +} From 4f8c27e3216cf03393225edf5a6cff4d1d521f21 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Tue, 19 Jul 2022 17:25:08 -0700 Subject: [PATCH 038/516] Integrate C parser with the UclidGenerator --- org.lflang/build.gradle | 3 +- .../generator/uclid/UclidGenerator.java | 64 +++++++++++++------ 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/org.lflang/build.gradle b/org.lflang/build.gradle index e7c8ca559a..f11790ac16 100644 --- a/org.lflang/build.gradle +++ b/org.lflang/build.gradle @@ -148,7 +148,8 @@ task runAntlr4(type:JavaExec) { "-o", "$projectDir/src/org/lflang/dsl/generated/antlr4/main/", "-package", "org.lflang.dsl", "$projectDir/src/org/lflang/dsl/antlr4/MTLLexer.g4", - "$projectDir/src/org/lflang/dsl/antlr4/MTLParser.g4"] + "$projectDir/src/org/lflang/dsl/antlr4/MTLParser.g4", + "$projectDir/src/org/lflang/dsl/antlr4/C.g4"] } processResources.dependsOn(runAntlr4) compileJava.dependsOn(runAntlr4) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 2e1877c778..dbaac032c5 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -64,19 +64,28 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.TargetTypes; import org.lflang.generator.TimerInstance; import org.lflang.generator.TriggerInstance; +import org.lflang.generator.uclid.ast.BuildAstParseTreeVisitor; +import org.lflang.generator.uclid.ast.CAst; +import org.lflang.generator.uclid.ast.CBaseAstVisitor; +import org.lflang.generator.uclid.ast.IfNormalFormAstVisitor; import org.lflang.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.Target; import org.lflang.TimeUnit; import org.lflang.TimeValue; +import org.lflang.dsl.CLexer; +import org.lflang.dsl.CParser; import org.lflang.dsl.MTLLexer; import org.lflang.dsl.MTLParser; +import org.lflang.dsl.CParser.BlockItemListContext; import org.lflang.dsl.MTLParser.MtlContext; import org.lflang.lf.Action; import org.lflang.lf.Attribute; +import org.lflang.lf.Code; import org.lflang.lf.Connection; import org.lflang.lf.Expression; +import org.lflang.lf.Reaction; import org.lflang.lf.Time; import org.lflang.lf.VarRef; import org.w3c.dom.Attr; @@ -252,10 +261,8 @@ protected void generateUclidCode(Attribute property, int CT) { // Initial Condition generateInitialConditions(); - // FIXME: To support once the DSL is available. - // Abstractions (i.e. contracts) - // generateReactorAbstractions(); - // generateReactionAbstractions(); + // Reaction bodies + generateReactionAxioms(); // Properties generateProperty(property, CT); @@ -551,11 +558,11 @@ protected void generateReactorSemantics() { " ((rxn(i) == rxn(j) && i != j)", " ==> !tag_same(g(i), g(j)))));", "", - "// Tags should be positive", + "// Tags should be non-negative.", "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END)", " ==> pi1(g(i)) >= 0);", "", - "// Microsteps should be positive", + "// Microsteps should be non-negative.", "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END)", " ==> pi2(g(i)) >= 0);", "", @@ -820,18 +827,39 @@ protected void generateInitialConditions() { } /** - * FIXME - */ - // protected void generateReactorAbstractions() { - - // } - - /** - * FIXME + * Lift reaction bodies into Uclid axioms. */ - // protected void generateReactionAbstractions() { - - // } + protected void generateReactionAxioms() { + for (ReactionInstance.Runtime reaction : this.reactionInstances) { + System.out.println("Printing reaction body of " + reaction); + String code = reaction.getReaction().getDefinition().getCode().getBody(); + System.out.println(code); + + // Generate a parse tree. + CLexer lexer = new CLexer(CharStreams.fromString(code)); + CommonTokenStream tokens = new CommonTokenStream(lexer); + CParser parser = new CParser(tokens); + BlockItemListContext parseTree = parser.blockItemList(); + + // Build an AST. + BuildAstParseTreeVisitor buildAstVisitor = new BuildAstParseTreeVisitor(); + CAst.AstNode ast = buildAstVisitor.visitBlockItemList(parseTree); + + // Traverse and print. + CBaseAstVisitor baseVisitor = new CBaseAstVisitor<>(); // For pretty printing. + System.out.println("***** Printing the original AST."); + baseVisitor.visit(ast); + + // Convert the AST to If Normal Form (INF). + IfNormalFormAstVisitor infVisitor = new IfNormalFormAstVisitor(); + System.out.println("***** Convert to If Normal Form."); + infVisitor.visit(ast, new ArrayList()); + CAst.StatementSequenceNode inf = infVisitor.INF; + System.out.println(inf); + System.out.println("***** Printing the AST in If Normal Form."); + baseVisitor.visit(inf); + } + } protected void generateProperty(Attribute property, int CT) { code.pr(String.join("\n", @@ -1009,7 +1037,7 @@ private void populateLists(ReactorInstance reactor) { * Compute a completeness threadhold for each property. */ private int computeCT(Attribute property) { - return 10; // FIXME + return 35; // FIXME } ///////////////////////////////////////////////// From 6bedfcc632ac6a9e196f194e09d272691d7b67c3 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Tue, 19 Jul 2022 18:24:50 -0700 Subject: [PATCH 039/516] Start generating uclid axioms. Generate up to TriggerValue. --- .../uclid/ast/BuildAstParseTreeVisitor.java | 4 +- .../generator/uclid/ast/CToUclidVisitor.java | 62 +++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/BuildAstParseTreeVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/BuildAstParseTreeVisitor.java index bec3381cd5..05628093ac 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/BuildAstParseTreeVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/ast/BuildAstParseTreeVisitor.java @@ -271,10 +271,10 @@ public CAst.AstNode visitPostfixExpression(PostfixExpressionContext ctx) { if (varNode.name.equals("self")) { // return a state variable node. return new CAst.StateVarNode(ctx.Identifier().get(0).getText()); - } else if (ctx.Identifier().get(0).getText().equals("is_present")) { + } else if (ctx.Identifier().get(0).getText().equals("value")) { // return a trigger present node. return new CAst.TriggerValueNode(varNode.name); - } else if (ctx.Identifier().get(0).getText().equals("value")) { + } else if (ctx.Identifier().get(0).getText().equals("is_present")) { // return a trigger value node. return new CAst.TriggerIsPresentNode(varNode.name); } else { diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java new file mode 100644 index 0000000000..8baab95e20 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java @@ -0,0 +1,62 @@ +package org.lflang.generator.uclid.ast; + +import java.util.List; + +import org.lflang.generator.NamedInstance; +import org.lflang.generator.PortInstance; +import org.lflang.generator.ReactionInstance; +import org.lflang.generator.ReactorInstance; +import org.lflang.generator.uclid.UclidGenerator; +import org.lflang.generator.uclid.ast.CAst.*; + +public class CToUclidVisitor extends CBaseAstVisitor { + + // The reaction instance for the generated axiom + protected ReactionInstance.Runtime reaction; + // The reactor that contains the reaction + protected ReactorInstance reactor; + + public CToUclidVisitor(ReactionInstance.Runtime reaction) { + this.reaction = reaction; + this.reactor = reaction.getReaction().getParent(); + } + + @Override + public String visitStatementSequenceNode(StatementSequenceNode node) { + String axiom = ""; + for (int i = 0; i < node.children.size(); i++) { + axiom += visit(node.children.get(i)); + if (i != node.children.size() - 1) axiom += " && "; + } + return axiom; + } + + @Override + public String visitIfBlockNode(IfBlockNode node) { + String antecedent = visit(node.left); // Process if condition + String consequent = visit(((IfBodyNode)node.right).left); + return "(" + antecedent + ")" + " ==> " + "(" + consequent + ")"; + } + + @Override + public String visitLiteralNode(LiteralNode node) { + return node.literal; + } + + @Override + public String visitTriggerValueNode(TriggerValueNode node) { + // Find the trigger instance by name. + NamedInstance instance; + System.out.println("*** Printing all the port names."); + for (PortInstance p : this.reactor.inputs) { + System.out.println(p.getDefinition().getName()); + } + instance = this.reactor.inputs.stream().filter(ins -> ins.getDefinition() + .getName().equals(node.name)).findFirst().get(); + if (instance != null) { + System.out.println(instance + " returned."); + return instance.getFullNameWithJoiner("_"); + } + return ""; + } +} From aeb98679d8890911ab71175693f5fcb7bee0faba Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Tue, 19 Jul 2022 22:40:45 -0700 Subject: [PATCH 040/516] Lift more AST nodes to Uclid --- .../generator/uclid/UclidGenerator.java | 45 ++++-- .../generator/uclid/ast/CToUclidVisitor.java | 129 +++++++++++++++--- 2 files changed, 143 insertions(+), 31 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index dbaac032c5..1afcbc3c0d 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -67,6 +67,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.uclid.ast.BuildAstParseTreeVisitor; import org.lflang.generator.uclid.ast.CAst; import org.lflang.generator.uclid.ast.CBaseAstVisitor; +import org.lflang.generator.uclid.ast.CToUclidVisitor; import org.lflang.generator.uclid.ast.IfNormalFormAstVisitor; import org.lflang.ASTUtils; import org.lflang.ErrorReporter; @@ -95,19 +96,21 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY public class UclidGenerator extends GeneratorBase { //////////////////////////////////////////// - //// Protected fields - + //// Public fields // Data structures for storing info about the runtime instances. - protected List reactorInstances = new ArrayList(); - protected List reactionInstances = new ArrayList(); + public List reactorInstances = new ArrayList(); + public List reactionInstances = new ArrayList(); // State variables in the system - protected List stateVariables = new ArrayList(); + public List stateVariables = new ArrayList(); // Triggers in the system - protected List actionInstances = new ArrayList(); - protected List portInstances = new ArrayList(); - protected List timerInstances = new ArrayList(); + public List actionInstances = new ArrayList(); + public List portInstances = new ArrayList(); + public List timerInstances = new ArrayList(); + + //////////////////////////////////////////// + //// Protected fields // Joint lists of the lists above. // FIXME: This approach currently creates duplications in memory. @@ -817,7 +820,7 @@ protected void generateInitialConditions() { )); code.indent(); for (var v : this.stateVariables) { - code.pr("&& " + v + "(s(0)) == 0"); + code.pr("&& " + v.getFullNameWithJoiner("_") + "(s(0)) == 0"); } for (var t : this.triggerInstances) { code.pr("&& !" + t.getFullNameWithJoiner("_") + "_is_present" + "(t(0))"); @@ -830,13 +833,18 @@ protected void generateInitialConditions() { * Lift reaction bodies into Uclid axioms. */ protected void generateReactionAxioms() { + code.pr(String.join("\n", + "/*************", + " * Reactions *", + " *************/" + )); for (ReactionInstance.Runtime reaction : this.reactionInstances) { System.out.println("Printing reaction body of " + reaction); - String code = reaction.getReaction().getDefinition().getCode().getBody(); - System.out.println(code); + String body = reaction.getReaction().getDefinition().getCode().getBody(); + System.out.println(body); // Generate a parse tree. - CLexer lexer = new CLexer(CharStreams.fromString(code)); + CLexer lexer = new CLexer(CharStreams.fromString(body)); CommonTokenStream tokens = new CommonTokenStream(lexer); CParser parser = new CParser(tokens); BlockItemListContext parseTree = parser.blockItemList(); @@ -858,6 +866,17 @@ protected void generateReactionAxioms() { System.out.println(inf); System.out.println("***** Printing the AST in If Normal Form."); baseVisitor.visit(inf); + + // Generate Uclid axiom for the C AST. + CToUclidVisitor c2uVisitor = new CToUclidVisitor(reaction); + System.out.println("***** Generating axioms from AST."); + String axiom = c2uVisitor.visit(inf); + code.pr(String.join("\n", + "// Reaction body of " + reaction, + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", + " " + axiom, + "));" + )); } } @@ -1037,7 +1056,7 @@ private void populateLists(ReactorInstance reactor) { * Compute a completeness threadhold for each property. */ private int computeCT(Attribute property) { - return 35; // FIXME + return 5; // FIXME } ///////////////////////////////////////////////// diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java index 8baab95e20..a8c57ad598 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java @@ -1,62 +1,155 @@ package org.lflang.generator.uclid.ast; +import java.util.ArrayList; import java.util.List; +import org.lflang.generator.ActionInstance; import org.lflang.generator.NamedInstance; import org.lflang.generator.PortInstance; import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactorInstance; -import org.lflang.generator.uclid.UclidGenerator; +import org.lflang.generator.StateVariableInstance; import org.lflang.generator.uclid.ast.CAst.*; public class CToUclidVisitor extends CBaseAstVisitor { // The reaction instance for the generated axiom protected ReactionInstance.Runtime reaction; + // The reactor that contains the reaction protected ReactorInstance reactor; + // A list of all the named instances + protected List instances = new ArrayList(); + + // Quantified variable + protected String qv = "i"; + public CToUclidVisitor(ReactionInstance.Runtime reaction) { this.reaction = reaction; this.reactor = reaction.getReaction().getParent(); + instances.addAll(this.reactor.inputs); + instances.addAll(this.reactor.outputs); + instances.addAll(this.reactor.actions); + instances.addAll(this.reactor.states); } @Override - public String visitStatementSequenceNode(StatementSequenceNode node) { - String axiom = ""; - for (int i = 0; i < node.children.size(); i++) { - axiom += visit(node.children.get(i)); - if (i != node.children.size() - 1) axiom += " && "; - } - return axiom; + public String visitAssignmentNode(AssignmentNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " == " + rhs + ")"; } @Override public String visitIfBlockNode(IfBlockNode node) { String antecedent = visit(node.left); // Process if condition String consequent = visit(((IfBodyNode)node.right).left); - return "(" + antecedent + ")" + " ==> " + "(" + consequent + ")"; + return "(" + antecedent + " ==> " + consequent + ")"; } - + @Override public String visitLiteralNode(LiteralNode node) { return node.literal; } + @Override + public String visitLogicalNotNode(LogicalNotNode node) { + return "!" + visit(node.child); + } + + @Override + public String visitNotEqualNode(NotEqualNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " != " + rhs + ")"; + } + + @Override + public String visitSetPortNode(SetPortNode node) { + String port = visit(node.left); + String value = visit(node.right); + return "(" + port + " == " + value + ")"; + } + + @Override + public String visitStateVarNode(StateVarNode node) { + NamedInstance instance = getInstanceByName(node.name); + if (instance != null) { + System.out.println(instance + " returned."); + return instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; + } + // FIXME: Throw exception + return ""; + } + + @Override + public String visitStatementSequenceNode(StatementSequenceNode node) { + String axiom = ""; + for (int i = 0; i < node.children.size(); i++) { + axiom += visit(node.children.get(i)); + if (i != node.children.size() - 1) axiom += " && "; + } + return axiom; + } + + @Override + public String visitTriggerIsPresentNode(TriggerIsPresentNode node) { + // Find the trigger instance by name. + NamedInstance instance = getInstanceByName(node.name); + if (instance != null) { + return instance.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv + ")" + ")"; + } + // FIXME: Throw exception + return ""; + } + @Override public String visitTriggerValueNode(TriggerValueNode node) { // Find the trigger instance by name. - NamedInstance instance; - System.out.println("*** Printing all the port names."); - for (PortInstance p : this.reactor.inputs) { - System.out.println(p.getDefinition().getName()); + NamedInstance instance = getInstanceByName(node.name); + if (instance != null) { + return instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; } - instance = this.reactor.inputs.stream().filter(ins -> ins.getDefinition() - .getName().equals(node.name)).findFirst().get(); + // FIXME: Throw exception + return ""; + } + + @Override + public String visitVariableNode(VariableNode node) { + NamedInstance instance = getInstanceByName(node.name); if (instance != null) { - System.out.println(instance + " returned."); - return instance.getFullNameWithJoiner("_"); + return instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; } + // FIXME: Throw exception return ""; } + + ///////////////////////////// + //// Private functions + + private NamedInstance getInstanceByName(String name) { + + // For some reason, the following one liner doesn't work. + // return this.instances.stream().filter(i -> i.getDefinition() + // .getName().equals(name)).findFirst().get(); + + for (NamedInstance i : this.instances) { + if (i instanceof ActionInstance) { + if (((ActionInstance)i).getDefinition().getName().equals(name)) { + return i; + } + } else if (i instanceof PortInstance) { + if (((PortInstance)i).getDefinition().getName().equals(name)) { + return i; + } + } else if (i instanceof StateVariableInstance) { + if (((StateVariableInstance)i).getDefinition().getName().equals(name)) { + return i; + } + } + } + System.out.println("Named instance" + "not found."); + return null; + } } From 4fc71474a23bdaab5fd6b15f869e27dba3fd76b9 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 20 Jul 2022 02:14:26 -0700 Subject: [PATCH 041/516] Generate more Uclid axioms from C --- .../generator/uclid/UclidGenerator.java | 21 ++-- .../generator/uclid/ast/CToUclidVisitor.java | 114 ++++++++++++++++-- 2 files changed, 118 insertions(+), 17 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 1afcbc3c0d..9787d206cf 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -109,13 +109,12 @@ public class UclidGenerator extends GeneratorBase { public List portInstances = new ArrayList(); public List timerInstances = new ArrayList(); + // Joint lists of the lists above. + public List triggerInstances; // Triggers = ports + actions + timers + public List namedInstances; // Named instances = triggers + state variables + //////////////////////////////////////////// //// Protected fields - - // Joint lists of the lists above. - // FIXME: This approach currently creates duplications in memory. - protected List triggerInstances; // Triggers = ports + actions + timers - protected List namedInstances; // Named instances = triggers + state variables // A list of MTL properties represented in Attributes. protected List properties; @@ -225,7 +224,7 @@ protected void generateRunnerScript() { "ls smt | xargs -I {} bash -c 'echo \"(get-model)\" >> smt/{}'", "", "echo '*** Running Z3'", - "ls smt | xargs -I {} bash -c 'echo \"Checking {}\" && z3 parallel.enable=true -T:300 -v:1 ./smt/{}'" + "ls smt | xargs -I {} bash -c 'z3 parallel.enable=true -T:300 -v:1 ./smt/{}'" )); script.writeToFile(filename); } catch (IOException e) { @@ -820,7 +819,10 @@ protected void generateInitialConditions() { )); code.indent(); for (var v : this.stateVariables) { - code.pr("&& " + v.getFullNameWithJoiner("_") + "(s(0)) == 0"); + code.pr("&& " + v.getFullNameWithJoiner("_") + "(s(0))" + " == " + "0"); + } + for (var t : this.triggerInstances) { + code.pr("&& " + t.getFullNameWithJoiner("_") + "(s(0))" + " == " + "0"); } for (var t : this.triggerInstances) { code.pr("&& !" + t.getFullNameWithJoiner("_") + "_is_present" + "(t(0))"); @@ -868,13 +870,14 @@ protected void generateReactionAxioms() { baseVisitor.visit(inf); // Generate Uclid axiom for the C AST. - CToUclidVisitor c2uVisitor = new CToUclidVisitor(reaction); + CToUclidVisitor c2uVisitor = new CToUclidVisitor(this, reaction); System.out.println("***** Generating axioms from AST."); String axiom = c2uVisitor.visit(inf); code.pr(String.join("\n", "// Reaction body of " + reaction, "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", - " " + axiom, + " (rxn(i) == " + reaction.getReaction().getFullNameWithJoiner("_") + ")", + " ==> " + axiom, "));" )); } diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java index a8c57ad598..fafe7d83fc 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java @@ -9,10 +9,15 @@ import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactorInstance; import org.lflang.generator.StateVariableInstance; +import org.lflang.generator.TriggerInstance; +import org.lflang.generator.uclid.UclidGenerator; import org.lflang.generator.uclid.ast.CAst.*; public class CToUclidVisitor extends CBaseAstVisitor { + // The Uclid generator instance + protected UclidGenerator generator; + // The reaction instance for the generated axiom protected ReactionInstance.Runtime reaction; @@ -25,7 +30,17 @@ public class CToUclidVisitor extends CBaseAstVisitor { // Quantified variable protected String qv = "i"; - public CToUclidVisitor(ReactionInstance.Runtime reaction) { + // Unchanged variables and triggers + protected List unchangedStates; + protected List unchangedTriggers; + + // FIXME: Make this more flexible and infer value from program. + // Default reset value + String defaultValue = "0"; + String defaultPresence = "false"; + + public CToUclidVisitor(UclidGenerator generator, ReactionInstance.Runtime reaction) { + this.generator = generator; this.reaction = reaction; this.reactor = reaction.getReaction().getParent(); instances.addAll(this.reactor.inputs); @@ -36,16 +51,90 @@ public CToUclidVisitor(ReactionInstance.Runtime reaction) { @Override public String visitAssignmentNode(AssignmentNode node) { - String lhs = visit(node.left); + // String lhs = visit(node.left); + String lhs = ""; + if (node.left instanceof StateVarNode) { + NamedInstance instance = getInstanceByName(((StateVarNode)node.left).name); + lhs = instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; + this.unchangedStates.remove(instance); // Remove instance from the unchanged list. + } else { + System.out.println("Unreachable!"); // FIXME: Throw exception. + } String rhs = visit(node.right); return "(" + lhs + " == " + rhs + ")"; } @Override public String visitIfBlockNode(IfBlockNode node) { + + String formula = ""; + + // In INF, there are no nested if blocks, so we can use a field + // to keep track of unchanged variables. + this.unchangedStates = new ArrayList<>(this.generator.stateVariables); + this.unchangedTriggers = new ArrayList<>(this.generator.triggerInstances); + String antecedent = visit(node.left); // Process if condition String consequent = visit(((IfBodyNode)node.right).left); - return "(" + antecedent + " ==> " + consequent + ")"; + + formula += "(" + antecedent + " ==> " + "(" + consequent; + + formula += "\n//// Unchanged variables"; + // State variables retain their previous states. + formula += "\n// State variables retain their previous states."; + for (StateVariableInstance s : this.unchangedStates) { + formula += "\n&& " + s.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")" + + " == " + + s.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + "-1" + ")" + ")"; + } + // Triggers resets to their default states if time advances. + formula += "\n// Triggers resets to their default states if time advances."; + for (TriggerInstance t : this.unchangedTriggers) { + // formula += "\n&& " + // + "(" + "(" + // + "tag_same(" + "g(" + this.qv + ")" + "," + "g(" + this.qv + "-1" + ")" + ")" + ")" + // + " ==> " + "(" + " true" + // // Retain value + // + "\n&& " + t.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")" + // + " == " + // + t.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + "-1" + ")" + ")" + // // Retain presence + // + "\n&& " + t.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv + ")" + ")" + // + " == " + // + t.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv + "-1" + ")" + ")" + // + ")" + ")" + // // If time advances. + // + "\n&& " + // + "(" + "(" + // + "tag_later(" + "g(" + this.qv + ")" + "," + "g(" + this.qv + "-1" + ")" + ")" + ")" + // + " ==> " + "(" + " true" + // // Reset value + // + "\n&& " + t.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")" + // + " == " + // + this.defaultValue + // // Reset presence + // + "\n&& " + t.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv + ")" + ")" + // + " == " + // + this.defaultPresence + // + ")" + ")"; + + formula += "\n&& " + + "(" + + " true" + // Reset value + + "\n&& " + t.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")" + + " == " + + this.defaultValue + // Reset presence + + "\n&& " + t.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv + ")" + ")" + + " == " + + this.defaultPresence + + ")"; + } + + formula += "\n))"; + + return formula; } @Override @@ -67,16 +156,24 @@ public String visitNotEqualNode(NotEqualNode node) { @Override public String visitSetPortNode(SetPortNode node) { - String port = visit(node.left); + NamedInstance port = getInstanceByName(((VariableNode)node.left).name); String value = visit(node.right); - return "(" + port + " == " + value + ")"; + // Remove this port from the unchanged list. + this.unchangedTriggers.remove(port); + return "(" + + "(" + + port.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")" + + " == " + value + + ")" + + " && " + + "(" + port.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv + ")" + ")" + ")" + + ")"; } @Override public String visitStateVarNode(StateVarNode node) { NamedInstance instance = getInstanceByName(node.name); if (instance != null) { - System.out.println(instance + " returned."); return instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; } // FIXME: Throw exception @@ -88,7 +185,7 @@ public String visitStatementSequenceNode(StatementSequenceNode node) { String axiom = ""; for (int i = 0; i < node.children.size(); i++) { axiom += visit(node.children.get(i)); - if (i != node.children.size() - 1) axiom += " && "; + if (i != node.children.size() - 1) axiom += "\n" + " " + "&& "; } return axiom; } @@ -130,7 +227,8 @@ public String visitVariableNode(VariableNode node) { private NamedInstance getInstanceByName(String name) { - // For some reason, the following one liner doesn't work. + // For some reason, the following one liner doesn't work: + // // return this.instances.stream().filter(i -> i.getDefinition() // .getName().equals(name)).findFirst().get(); From be621d5233b03cf5ab2be3a3906bc8b9f7d4335b Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 20 Jul 2022 16:39:33 -0700 Subject: [PATCH 042/516] Removed the relaxation fragment in the connection axiom. Add dedicated axioms that resets an input port to default value. Reactions only reset state variables and output ports. --- .../generator/uclid/UclidGenerator.java | 68 +++++++++++++------ .../generator/uclid/ast/CToUclidVisitor.java | 18 ++++- 2 files changed, 64 insertions(+), 22 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 9787d206cf..73e66e6d3e 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -106,6 +106,8 @@ public class UclidGenerator extends GeneratorBase { // Triggers in the system public List actionInstances = new ArrayList(); + public List inputInstances = new ArrayList(); + public List outputInstances = new ArrayList(); public List portInstances = new ArrayList(); public List timerInstances = new ArrayList(); @@ -579,10 +581,11 @@ protected void generateReactorSemantics() { " (rxn(j)) != NULL) ==> ", " (finite_forall (i : integer) in indices :: (i > START && i < j) ==> (rxn(i) != NULL)));", "", + "// FIXME: This axiom appears to be buggy.", "// When a NULL event occurs, the state stays the same.", - "axiom(finite_forall (j : integer) in indices :: (j > START && j <= END) ==> (", - " (rxn(j) == NULL) ==> (s(j) == s(j-1))", - "));" + "// axiom(finite_forall (j : integer) in indices :: (j > START && j <= END) ==> (", + "// (rxn(j) == NULL) ==> (s(j) == s(j-1))", + "// ));" )); // Non-federated "happened-before" @@ -661,15 +664,7 @@ protected void generateTriggersAndReactions() { " && " + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(j))", " && " + destination.getFullNameWithJoiner("_") + "(s(j)) == " + source.getFullNameWithJoiner("_") + "(s(i))", connection.isPhysical() ? "" : "&& g(j) == tag_schedule(g(i), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")", - ")||(", - // Relaxation axioms: a port presence can not produce a downstream presence - // but it needs to guarantee that there are no trailing NULL events. - // FIXME: !«downstreamPortIsPresent»(t(k)) makes the solver timeout. - "(finite_forall (k : integer) in indices :: (k > i && k <= END) ==> (rxn(k) != NULL", - " && " + destination.getFullNameWithJoiner("_") + "(s(k)) == " + source.getFullNameWithJoiner("_") + "(s(i))", - connection.isPhysical() ? "" : " && (tag_same(g(k), tag_schedule(g(i), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")) || tag_earlier(g(k), tag_schedule(g(i), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")))", - ")) // Closes forall.", - ") // Closes ||", + ")", ")) // Closes (" + source.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> ((.", "// If " + destination.getFullNameWithJoiner("_") + " is present, there exists an " + source.getFullNameWithJoiner("_") + " in prior steps.", "// This additional term establishes a one-to-one relationship between two ports' signals.", @@ -680,12 +675,41 @@ protected void generateTriggersAndReactions() { ")) // Closes the one-to-one relationship.", "));" )); + // code.pr(String.join("\n", + // "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", + // "// If " + source.getFullNameWithJoiner("_") + " is present, then " + // + destination.getFullNameWithJoiner("_") + " will be present.", + // "// with the same value after some fixed delay of " + delay + " nanoseconds.", + // "(" + source.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> ((", + // " finite_exists (j : integer) in indices :: j > i && j <= END", + // " && " + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(j))", + // " && " + destination.getFullNameWithJoiner("_") + "(s(j)) == " + source.getFullNameWithJoiner("_") + "(s(i))", + // connection.isPhysical() ? "" : "&& g(j) == tag_schedule(g(i), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")", + // ")||(", + // // Relaxation axioms: a port presence can not produce a downstream presence + // // but it needs to guarantee that there are no trailing NULL events. + // // FIXME: !«downstreamPortIsPresent»(t(k)) makes the solver timeout. + // "(finite_forall (k : integer) in indices :: (k > i && k <= END) ==> (rxn(k) != NULL", + // " && " + destination.getFullNameWithJoiner("_") + "(s(k)) == " + source.getFullNameWithJoiner("_") + "(s(i))", + // connection.isPhysical() ? "" : " && (tag_same(g(k), tag_schedule(g(i), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")) || tag_earlier(g(k), tag_schedule(g(i), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")))", + // ")) // Closes forall.", + // ") // Closes ||", + // ")) // Closes (" + source.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> ((.", + // "// If " + destination.getFullNameWithJoiner("_") + " is present, there exists an " + source.getFullNameWithJoiner("_") + " in prior steps.", + // "// This additional term establishes a one-to-one relationship between two ports' signals.", + // "&& (" + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", + // " finite_exists (j : integer) in indices :: j >= START && j < i", + // " && " + source.getFullNameWithJoiner("_") + "_is_present" + "(t(j))", + // connection.isPhysical() ? "" : " && g(i) == tag_schedule(g(j), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")", + // ")) // Closes the one-to-one relationship.", + // "));" + // )); // If destination is not present, then its value resets to 0. // FIXME: Check if this works in practice. code.pr(String.join("\n", "// If " + destination.getFullNameWithJoiner("_") + " is not present, then its value resets to 0.", - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END && rxn(i) != NULL) ==> (", " (!" + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", " " + destination.getFullNameWithJoiner("_") + "(s(i)) == 0", " ))", @@ -729,14 +753,14 @@ protected void generateTriggersAndReactions() { // If the action is not present, then its value resets to 0. // FIXME: Check if this works in practice. - code.pr(String.join("\n", - "// If " + action.getFullNameWithJoiner("_") + " is not present, then its value resets to 0.", - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", - " (!" + action.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", - " " + action.getFullNameWithJoiner("_") + "(s(i)) == 0", - " ))", - "));" - )); + // code.pr(String.join("\n", + // "// If " + action.getFullNameWithJoiner("_") + " is not present, then its value resets to 0.", + // "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", + // " (!" + action.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", + // " " + action.getFullNameWithJoiner("_") + "(s(i)) == 0", + // " ))", + // "));" + // )); } } @@ -1041,9 +1065,11 @@ private void populateLists(ReactorInstance reactor) { this.actionInstances.add(action); } for (var port : reactor.inputs) { + this.inputInstances.add(port); this.portInstances.add(port); } for (var port : reactor.outputs) { + this.outputInstances.add(port); this.portInstances.add(port); } for (var timer : reactor.timers) { diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java index fafe7d83fc..5c3350250d 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java @@ -64,6 +64,13 @@ public String visitAssignmentNode(AssignmentNode node) { return "(" + lhs + " == " + rhs + ")"; } + @Override + public String visitEqualNode(EqualNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " == " + rhs + ")"; + } + @Override public String visitIfBlockNode(IfBlockNode node) { @@ -72,7 +79,9 @@ public String visitIfBlockNode(IfBlockNode node) { // In INF, there are no nested if blocks, so we can use a field // to keep track of unchanged variables. this.unchangedStates = new ArrayList<>(this.generator.stateVariables); - this.unchangedTriggers = new ArrayList<>(this.generator.triggerInstances); + this.unchangedTriggers = new ArrayList<>(); + this.unchangedTriggers.addAll(this.generator.outputInstances); + this.unchangedTriggers.addAll(this.generator.actionInstances); String antecedent = visit(node.left); // Process if condition String consequent = visit(((IfBodyNode)node.right).left); @@ -147,6 +156,13 @@ public String visitLogicalNotNode(LogicalNotNode node) { return "!" + visit(node.child); } + @Override + public String visitMultiplicationNode(MultiplicationNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " * " + rhs + ")"; + } + @Override public String visitNotEqualNode(NotEqualNode node) { String lhs = visit(node.left); From d9e2b4ba272e1358454302db32729e22fadce3d2 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 20 Jul 2022 18:39:54 -0700 Subject: [PATCH 043/516] Bug fixes --- org.lflang/build.gradle | 1 + .../lflang/generator/uclid/MTLVisitor.java | 3 ++ .../generator/uclid/UclidGenerator.java | 5 ++-- .../generator/uclid/ast/CToUclidVisitor.java | 28 +++++++++++++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/org.lflang/build.gradle b/org.lflang/build.gradle index f11790ac16..d0b9730aaa 100644 --- a/org.lflang/build.gradle +++ b/org.lflang/build.gradle @@ -152,6 +152,7 @@ task runAntlr4(type:JavaExec) { "$projectDir/src/org/lflang/dsl/antlr4/C.g4"] } processResources.dependsOn(runAntlr4) +compileKotlin.dependsOn(runAntlr4) compileJava.dependsOn(runAntlr4) clean { diff --git a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java index 66676ed236..46bc4655fc 100644 --- a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java @@ -176,6 +176,7 @@ public String visitUntil(MTLParser.UntilContext ctx, return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " + "j" + QFIdx + " >= " + prefixIdx + " && " + "j" + QFIdx + " <= " + end + + " && " + "rxn" + "(" + "j" + QFIdx + ")" + " != " + "NULL" + " && " + "(" + _visitUnaryOp(ctx.right, ("j"+QFIdx), QFIdx+1, prefixIdx, currentHorizon) + ")" + " && " + "(" + "\n" + "// Time Predicate\n" @@ -220,6 +221,7 @@ public String visitGlobally(MTLParser.GloballyContext ctx, upperBoundNanoSec, prefixIdx, prevPrefixIdx); return "!(" + "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " + "j" + QFIdx + " >= " + prefixIdx + " && " + "j" + QFIdx + " <= " + end + + " && " + "rxn" + "(" + "j" + QFIdx + ")" + " != " + "NULL" + " && " + "!" + "(" + visitPrimary(ctx.formula, ("j"+QFIdx), QFIdx+1, prefixIdx, currentHorizon) + ")" + " && " + "(" + "\n" + "// Time Predicate\n" @@ -244,6 +246,7 @@ public String visitFinally(MTLParser.FinallyContext ctx, upperBoundNanoSec, prefixIdx, prevPrefixIdx); return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " + "j" + QFIdx + " >= " + prefixIdx + " && " + "j" + QFIdx + " <= " + end + + " && " + "rxn" + "(" + "j" + QFIdx + ")" + " != " + "NULL" + " && " + "(" + visitPrimary(ctx.formula, ("j"+QFIdx), QFIdx+1, prefixIdx, currentHorizon) + ")" + " && " + "(" + "\n" + "// Time Predicate\n" diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 73e66e6d3e..551d1455aa 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -738,14 +738,15 @@ protected void generateTriggersAndReactions() { " finite_exists (j : integer) in indices :: j >= START && j < i", " && rxn(j) == " + reaction.getFullNameWithJoiner("_"), " && g(i) == tag_schedule(g(j), " - + (action.getMinDelay() == TimeValue.ZERO ? "mstep()" : "nsec(" + action.getMinDelay().toNanoSeconds() + ")") + ")" + + (action.getMinDelay() == TimeValue.ZERO ? "mstep()" : "nsec(" + action.getMinDelay().toNanoSeconds() + ")") + ")", + "))" ); } // After populating the string segments, // print the generated code string. code.pr(String.join("\n", - comment, + "// " + comment, "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> ( true", triggerStr, "));" diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java index 5c3350250d..f5317266b9 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java @@ -71,6 +71,20 @@ public String visitEqualNode(EqualNode node) { return "(" + lhs + " == " + rhs + ")"; } + @Override + public String visitGreaterEqualNode(GreaterEqualNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " >= " + rhs + ")"; + } + + @Override + public String visitGreaterThanNode(GreaterThanNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " > " + rhs + ")"; + } + @Override public String visitIfBlockNode(IfBlockNode node) { @@ -146,6 +160,20 @@ public String visitIfBlockNode(IfBlockNode node) { return formula; } + @Override + public String visitLessEqualNode(LessEqualNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " <= " + rhs + ")"; + } + + @Override + public String visitLessThanNode(LessThanNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " < " + rhs + ")"; + } + @Override public String visitLiteralNode(LiteralNode node) { return node.literal; From f29cf78ad38025aeea30298c6bdb31dce8dfae99 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 20 Jul 2022 23:29:36 -0700 Subject: [PATCH 044/516] Fix bugs and generate more axioms --- .../lflang/generator/uclid/MTLVisitor.java | 6 +-- .../generator/uclid/UclidGenerator.java | 19 ++++----- .../uclid/ast/BuildAstParseTreeVisitor.java | 2 +- .../generator/uclid/ast/CToUclidVisitor.java | 39 +++++++++++++++++-- 4 files changed, 50 insertions(+), 16 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java index 46bc4655fc..c569b8ddc9 100644 --- a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java @@ -172,7 +172,7 @@ public String visitUntil(MTLParser.UntilContext ctx, long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); long currentHorizon = horizon + upperBoundNanoSec; String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, - upperBoundNanoSec, prefixIdx, prevPrefixIdx); + upperBoundNanoSec, "j" + QFIdx, prefixIdx); return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " + "j" + QFIdx + " >= " + prefixIdx + " && " + "j" + QFIdx + " <= " + end @@ -218,7 +218,7 @@ public String visitGlobally(MTLParser.GloballyContext ctx, long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); long currentHorizon = horizon + upperBoundNanoSec; String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, - upperBoundNanoSec, prefixIdx, prevPrefixIdx); + upperBoundNanoSec, "j" + QFIdx, prefixIdx); return "!(" + "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " + "j" + QFIdx + " >= " + prefixIdx + " && " + "j" + QFIdx + " <= " + end + " && " + "rxn" + "(" + "j" + QFIdx + ")" + " != " + "NULL" @@ -243,7 +243,7 @@ public String visitFinally(MTLParser.FinallyContext ctx, long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); long currentHorizon = horizon + upperBoundNanoSec; String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, - upperBoundNanoSec, prefixIdx, prevPrefixIdx); + upperBoundNanoSec, "j" + QFIdx, prefixIdx); return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " + "j" + QFIdx + " >= " + prefixIdx + " && " + "j" + QFIdx + " <= " + end + " && " + "rxn" + "(" + "j" + QFIdx + ")" + " != " + "NULL" diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 551d1455aa..bc6235aae2 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -666,6 +666,7 @@ protected void generateTriggersAndReactions() { connection.isPhysical() ? "" : "&& g(j) == tag_schedule(g(i), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")", ")", ")) // Closes (" + source.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> ((.", + //// Separator "// If " + destination.getFullNameWithJoiner("_") + " is present, there exists an " + source.getFullNameWithJoiner("_") + " in prior steps.", "// This additional term establishes a one-to-one relationship between two ports' signals.", "&& (" + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", @@ -706,7 +707,6 @@ protected void generateTriggersAndReactions() { // )); // If destination is not present, then its value resets to 0. - // FIXME: Check if this works in practice. code.pr(String.join("\n", "// If " + destination.getFullNameWithJoiner("_") + " is not present, then its value resets to 0.", "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END && rxn(i) != NULL) ==> (", @@ -734,6 +734,7 @@ protected void generateTriggersAndReactions() { comment += reaction.getFullNameWithJoiner("_") + ", "; triggerStr += String.join("\n", "// " + reaction.getFullNameWithJoiner("_"), + // FIXME: Should this be an OR? "&& (" + action.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", " finite_exists (j : integer) in indices :: j >= START && j < i", " && rxn(j) == " + reaction.getFullNameWithJoiner("_"), @@ -754,14 +755,14 @@ protected void generateTriggersAndReactions() { // If the action is not present, then its value resets to 0. // FIXME: Check if this works in practice. - // code.pr(String.join("\n", - // "// If " + action.getFullNameWithJoiner("_") + " is not present, then its value resets to 0.", - // "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", - // " (!" + action.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", - // " " + action.getFullNameWithJoiner("_") + "(s(i)) == 0", - // " ))", - // "));" - // )); + code.pr(String.join("\n", + "// If " + action.getFullNameWithJoiner("_") + " is not present, then its value resets to 0.", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END && rxn(i) != NULL) ==> (", + " (!" + action.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", + " " + action.getFullNameWithJoiner("_") + "(s(i)) == 0", + " ))", + "));" + )); } } diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/BuildAstParseTreeVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/BuildAstParseTreeVisitor.java index 05628093ac..da9cc4f33d 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/BuildAstParseTreeVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/ast/BuildAstParseTreeVisitor.java @@ -333,7 +333,7 @@ public CAst.AstNode visitPostfixExpression(PostfixExpressionContext ctx) { // Generic pointer dereference, unanalyzable. System.out.println(String.join(" ", "Warning (line " + ctx.getStart().getLine() + "):", - "Generic pointer dereference is not supported in a postfix expression.", + "Generic pointer dereference and function calls are not supported in a postfix expression.", "Marking the declaration as opaque." )); return new CAst.OpaqueNode(); diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java index f5317266b9..5cc21ad862 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java @@ -3,6 +3,8 @@ import java.util.ArrayList; import java.util.List; +import javax.swing.Action; + import org.lflang.generator.ActionInstance; import org.lflang.generator.NamedInstance; import org.lflang.generator.PortInstance; @@ -29,6 +31,7 @@ public class CToUclidVisitor extends CBaseAstVisitor { // Quantified variable protected String qv = "i"; + protected String qv2 = "j"; // Unchanged variables and triggers protected List unchangedStates; @@ -93,9 +96,7 @@ public String visitIfBlockNode(IfBlockNode node) { // In INF, there are no nested if blocks, so we can use a field // to keep track of unchanged variables. this.unchangedStates = new ArrayList<>(this.generator.stateVariables); - this.unchangedTriggers = new ArrayList<>(); - this.unchangedTriggers.addAll(this.generator.outputInstances); - this.unchangedTriggers.addAll(this.generator.actionInstances); + this.unchangedTriggers = new ArrayList<>(this.generator.outputInstances); String antecedent = visit(node.left); // Process if condition String consequent = visit(((IfBodyNode)node.right).left); @@ -179,11 +180,25 @@ public String visitLiteralNode(LiteralNode node) { return node.literal; } + @Override + public String visitLogicalAndNode(LogicalAndNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " && " + rhs + ")"; + } + @Override public String visitLogicalNotNode(LogicalNotNode node) { return "!" + visit(node.child); } + @Override + public String visitLogicalOrNode(LogicalOrNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " || " + rhs + ")"; + } + @Override public String visitMultiplicationNode(MultiplicationNode node) { String lhs = visit(node.left); @@ -198,6 +213,24 @@ public String visitNotEqualNode(NotEqualNode node) { return "(" + lhs + " != " + rhs + ")"; } + @Override + public String visitScheduleActionNode(ScheduleActionNode node) { + String name = ((VariableNode)node.children.get(0)).name; + NamedInstance instance = getInstanceByName(name); + ActionInstance action = (ActionInstance)instance; + String delay = visit(node.children.get(1)); + String str = "\n(" + + "finite_exists (" + this.qv2 + " : integer) in indices :: (i > START && i <= END) && (" + + "\n " + action.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv2 + ")" + ")" + + "\n " + "&& " + "tag_same" + "(" + "g(" + this.qv2 + ")" + ", " + "tag_schedule" + "(" + "g" + "(" + this.qv + ")" + ", " + "nsec" + "(" + action.getMinDelay().toNanoSeconds() + ")" + ")" + ")"; + if (node.children.size() == 3) { + String value = visit(node.children.get(2)); + str += "\n " + "&& " + action.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv2 + ")" + ")" + " == " + value; + } + str += "\n))"; + return str; + } + @Override public String visitSetPortNode(SetPortNode node) { NamedInstance port = getInstanceByName(((VariableNode)node.left).name); From 60df7cefe65b5617383ee82761de774ed8a853bf Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 21 Jul 2022 09:38:58 -0700 Subject: [PATCH 045/516] Fix bugs on setting a port while scheduling an action, reset unused ports, and maintain unused state variables --- .../generator/uclid/UclidGenerator.java | 101 +++++++++++++++++- .../generator/uclid/ast/CToUclidVisitor.java | 54 +++++----- .../uclid/ast/IfNormalFormAstVisitor.java | 24 +---- 3 files changed, 127 insertions(+), 52 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index bc6235aae2..13099d1bfa 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -35,6 +35,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -66,6 +67,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.TriggerInstance; import org.lflang.generator.uclid.ast.BuildAstParseTreeVisitor; import org.lflang.generator.uclid.ast.CAst; +import org.lflang.generator.uclid.ast.CAstUtils; import org.lflang.generator.uclid.ast.CBaseAstVisitor; import org.lflang.generator.uclid.ast.CToUclidVisitor; import org.lflang.generator.uclid.ast.IfNormalFormAstVisitor; @@ -895,6 +897,39 @@ protected void generateReactionAxioms() { System.out.println("***** Printing the AST in If Normal Form."); baseVisitor.visit(inf); + // For the variables that are used, extract the conditions + // for setting them, and take the negation of their conjunction + // to get the condition for resetting them. + List unusedStates = new ArrayList<>( + reaction.getReaction().getParent().states); + List unusedOutputs = new ArrayList<>( + reaction.getReaction().getParent().outputs); + HashMap> resetConditions = new HashMap<>(); + for (CAst.AstNode node : inf.children) { + CAst.IfBlockNode ifBlockNode = (CAst.IfBlockNode)node; + CAst.AstNode ifBody = ((CAst.IfBodyNode)ifBlockNode.right).left; + NamedInstance instance = null; + if ((ifBody instanceof CAst.AssignmentNode + && ((CAst.AssignmentNode)ifBody).left instanceof CAst.StateVarNode)) { + CAst.StateVarNode n = (CAst.StateVarNode)((CAst.AssignmentNode)ifBody).left; + instance = reaction.getReaction().getParent().states.stream() + .filter(s -> s.getName().equals(n.name)).findFirst().get(); + unusedStates.remove(instance); + } else if (ifBody instanceof CAst.SetPortNode) { + CAst.SetPortNode n = (CAst.SetPortNode)ifBody; + String name = ((CAst.VariableNode)n.left).name; + instance = reaction.getReaction().getParent().outputs.stream() + .filter(s -> s.getName().equals(name)).findFirst().get(); + unusedOutputs.remove(instance); + } else continue; + // Create a new entry in the list if there isn't one yet. + if (resetConditions.get(instance) == null) { + resetConditions.put(instance, new ArrayList()); + } + resetConditions.get(instance).add(ifBlockNode.left); + System.out.println("!!! Added another reset condition: " + ifBlockNode.left); + } + // Generate Uclid axiom for the C AST. CToUclidVisitor c2uVisitor = new CToUclidVisitor(this, reaction); System.out.println("***** Generating axioms from AST."); @@ -903,9 +938,71 @@ protected void generateReactionAxioms() { "// Reaction body of " + reaction, "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", " (rxn(i) == " + reaction.getReaction().getFullNameWithJoiner("_") + ")", - " ==> " + axiom, - "));" + " ==> " + "(" + "(" + axiom + ")", + " && " + "( " + "true", + " // Default behavior" )); + + for (NamedInstance key : resetConditions.keySet()) { + CAst.AstNode disjunction = CAstUtils.takeDisjunction(resetConditions.get(key)); + System.out.println("!!! Reset conditions: " + resetConditions.get(key)); + CAst.LogicalNotNode notNode = new CAst.LogicalNotNode(); + notNode.child = disjunction; + String resetCondition = c2uVisitor.visit(notNode); + System.out.println("!!! Str: " + resetCondition); + code.pr("&& " + "(" + "(" + resetCondition + ")" + " ==> " + "("); + if (key instanceof StateVariableInstance) { + StateVariableInstance n = (StateVariableInstance)key; + code.pr(n.getFullNameWithJoiner("_") + "(" + "s" + "(" + "i" + ")" + ")" + + " == " + + n.getFullNameWithJoiner("_") + "(" + "s" + "(" + "i" + "-1" + ")" + ")"); + } else if (key instanceof PortInstance) { + PortInstance n = (PortInstance)key; + code.pr( + "(" + + " true" + // Reset value + + "\n&& " + n.getFullNameWithJoiner("_") + "(" + "s" + "(" + "i" + ")" + ")" + + " == " + + "0" // Default value + // Reset presence + + "\n&& " + n.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + "i" + ")" + ")" + + " == " + + false // default presence + + ")" + ); + } else { + System.out.println("Unreachable!"); + } + code.pr("))"); + } + // Unused state variables and ports reset by default. + code.pr("// Unused state variables and ports reset by default."); + for (StateVariableInstance s : unusedStates) { + code.pr("&& (true ==> ("); + code.pr(s.getFullNameWithJoiner("_") + "(" + "s" + "(" + "i" + ")" + ")" + + " == " + + s.getFullNameWithJoiner("_") + "(" + "s" + "(" + "i" + "-1" + ")" + ")"); + code.pr("))"); + } + for (PortInstance p : unusedOutputs) { + code.pr("&& (true ==> ("); + code.pr( + "(" + + " true" + // Reset value + + "\n&& " + p.getFullNameWithJoiner("_") + "(" + "s" + "(" + "i" + ")" + ")" + + " == " + + "0" // Default value + // Reset presence + + "\n&& " + p.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + "i" + ")" + ")" + + " == " + + false // default presence + + ")" + ); + code.pr("))"); + } + code.pr("))));"); } } diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java index 5cc21ad862..5aeb51fe5c 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java @@ -59,7 +59,7 @@ public String visitAssignmentNode(AssignmentNode node) { if (node.left instanceof StateVarNode) { NamedInstance instance = getInstanceByName(((StateVarNode)node.left).name); lhs = instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; - this.unchangedStates.remove(instance); // Remove instance from the unchanged list. + // this.unchangedStates.remove(instance); // Remove instance from the unchanged list. } else { System.out.println("Unreachable!"); // FIXME: Throw exception. } @@ -95,25 +95,25 @@ public String visitIfBlockNode(IfBlockNode node) { // In INF, there are no nested if blocks, so we can use a field // to keep track of unchanged variables. - this.unchangedStates = new ArrayList<>(this.generator.stateVariables); - this.unchangedTriggers = new ArrayList<>(this.generator.outputInstances); + // this.unchangedStates = new ArrayList<>(this.generator.stateVariables); + // this.unchangedTriggers = new ArrayList<>(this.generator.outputInstances); String antecedent = visit(node.left); // Process if condition String consequent = visit(((IfBodyNode)node.right).left); formula += "(" + antecedent + " ==> " + "(" + consequent; - formula += "\n//// Unchanged variables"; - // State variables retain their previous states. - formula += "\n// State variables retain their previous states."; - for (StateVariableInstance s : this.unchangedStates) { - formula += "\n&& " + s.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")" - + " == " - + s.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + "-1" + ")" + ")"; - } + // formula += "\n//// Unchanged variables"; + // // State variables retain their previous states. + // formula += "\n// State variables retain their previous states."; + // for (StateVariableInstance s : this.unchangedStates) { + // formula += "\n&& " + s.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")" + // + " == " + // + s.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + "-1" + ")" + ")"; + // } // Triggers resets to their default states if time advances. - formula += "\n// Triggers resets to their default states if time advances."; - for (TriggerInstance t : this.unchangedTriggers) { + // formula += "\n// Triggers resets to their default states if time advances."; + // for (TriggerInstance t : this.unchangedTriggers) { // formula += "\n&& " // + "(" + "(" // + "tag_same(" + "g(" + this.qv + ")" + "," + "g(" + this.qv + "-1" + ")" + ")" + ")" @@ -142,19 +142,19 @@ public String visitIfBlockNode(IfBlockNode node) { // + this.defaultPresence // + ")" + ")"; - formula += "\n&& " - + "(" - + " true" - // Reset value - + "\n&& " + t.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")" - + " == " - + this.defaultValue - // Reset presence - + "\n&& " + t.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv + ")" + ")" - + " == " - + this.defaultPresence - + ")"; - } + // formula += "\n&& " + // + "(" + // + " true" + // // Reset value + // + "\n&& " + t.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")" + // + " == " + // + this.defaultValue + // // Reset presence + // + "\n&& " + t.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv + ")" + ")" + // + " == " + // + this.defaultPresence + // + ")"; + // } formula += "\n))"; @@ -236,7 +236,7 @@ public String visitSetPortNode(SetPortNode node) { NamedInstance port = getInstanceByName(((VariableNode)node.left).name); String value = visit(node.right); // Remove this port from the unchanged list. - this.unchangedTriggers.remove(port); + // this.unchangedTriggers.remove(port); return "(" + "(" + port.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")" diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/IfNormalFormAstVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/IfNormalFormAstVisitor.java index a6bb4ae29b..18bfe0313d 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/IfNormalFormAstVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/ast/IfNormalFormAstVisitor.java @@ -72,33 +72,11 @@ public Void visitIfBlockNode(CAst.IfBlockNode node, List condition return null; } - private CAst.AstNode takeConjunction(List conditions) { - if (conditions.size() == 0) { - return new CAst.LiteralNode("true"); - } else if (conditions.size() == 1) { - return conditions.get(0); - } else { - // Take the conjunction of all the conditions. - CAst.LogicalAndNode top = new CAst.LogicalAndNode(); - CAst.LogicalAndNode cur = top; - for (int i = 0; i < conditions.size()-1; i++) { - cur.left = conditions.get(i); - if (i == conditions.size()-2) { - cur.right = conditions.get(i+1); - } else { - cur.right = new CAst.LogicalAndNode(); - cur =(CAst.LogicalAndNode)cur.right; - } - } - return top; - } - } - private CAst.IfBlockNode generateIfBlock(CAst.AstNode node, List conditions) { // Create an If Block node. CAst.IfBlockNode ifNode = new CAst.IfBlockNode(); // Set the condition of the if block node. - CAst.AstNode conjunction = takeConjunction(conditions); + CAst.AstNode conjunction = CAstUtils.takeConjunction(conditions); ifNode.left = conjunction; // Create a new body node. CAst.IfBodyNode body = new CAst.IfBodyNode(); From 965eca54f901b57452581a078607fc2ac663116d Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 21 Jul 2022 09:45:36 -0700 Subject: [PATCH 046/516] Prune dead code, attach value to action --- .../generator/uclid/ast/CToUclidVisitor.java | 88 +++---------------- 1 file changed, 12 insertions(+), 76 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java index 5aeb51fe5c..1f6b689acc 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java @@ -54,15 +54,15 @@ public CToUclidVisitor(UclidGenerator generator, ReactionInstance.Runtime reacti @Override public String visitAssignmentNode(AssignmentNode node) { - // String lhs = visit(node.left); - String lhs = ""; - if (node.left instanceof StateVarNode) { - NamedInstance instance = getInstanceByName(((StateVarNode)node.left).name); - lhs = instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; - // this.unchangedStates.remove(instance); // Remove instance from the unchanged list. - } else { - System.out.println("Unreachable!"); // FIXME: Throw exception. - } + String lhs = visit(node.left); + // String lhs = ""; + // if (node.left instanceof StateVarNode) { + // NamedInstance instance = getInstanceByName(((StateVarNode)node.left).name); + // lhs = instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; + // // this.unchangedStates.remove(instance); // Remove instance from the unchanged list. + // } else { + // System.out.println("Unreachable!"); // FIXME: Throw exception. + // } String rhs = visit(node.right); return "(" + lhs + " == " + rhs + ")"; } @@ -90,75 +90,9 @@ public String visitGreaterThanNode(GreaterThanNode node) { @Override public String visitIfBlockNode(IfBlockNode node) { - - String formula = ""; - - // In INF, there are no nested if blocks, so we can use a field - // to keep track of unchanged variables. - // this.unchangedStates = new ArrayList<>(this.generator.stateVariables); - // this.unchangedTriggers = new ArrayList<>(this.generator.outputInstances); - String antecedent = visit(node.left); // Process if condition String consequent = visit(((IfBodyNode)node.right).left); - - formula += "(" + antecedent + " ==> " + "(" + consequent; - - // formula += "\n//// Unchanged variables"; - // // State variables retain their previous states. - // formula += "\n// State variables retain their previous states."; - // for (StateVariableInstance s : this.unchangedStates) { - // formula += "\n&& " + s.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")" - // + " == " - // + s.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + "-1" + ")" + ")"; - // } - // Triggers resets to their default states if time advances. - // formula += "\n// Triggers resets to their default states if time advances."; - // for (TriggerInstance t : this.unchangedTriggers) { - // formula += "\n&& " - // + "(" + "(" - // + "tag_same(" + "g(" + this.qv + ")" + "," + "g(" + this.qv + "-1" + ")" + ")" + ")" - // + " ==> " + "(" + " true" - // // Retain value - // + "\n&& " + t.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")" - // + " == " - // + t.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + "-1" + ")" + ")" - // // Retain presence - // + "\n&& " + t.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv + ")" + ")" - // + " == " - // + t.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv + "-1" + ")" + ")" - // + ")" + ")" - // // If time advances. - // + "\n&& " - // + "(" + "(" - // + "tag_later(" + "g(" + this.qv + ")" + "," + "g(" + this.qv + "-1" + ")" + ")" + ")" - // + " ==> " + "(" + " true" - // // Reset value - // + "\n&& " + t.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")" - // + " == " - // + this.defaultValue - // // Reset presence - // + "\n&& " + t.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv + ")" + ")" - // + " == " - // + this.defaultPresence - // + ")" + ")"; - - // formula += "\n&& " - // + "(" - // + " true" - // // Reset value - // + "\n&& " + t.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")" - // + " == " - // + this.defaultValue - // // Reset presence - // + "\n&& " + t.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv + ")" + ")" - // + " == " - // + this.defaultPresence - // + ")"; - // } - - formula += "\n))"; - - return formula; + return "(" + antecedent + " ==> " + "(" + consequent + "\n))"; } @Override @@ -226,6 +160,8 @@ public String visitScheduleActionNode(ScheduleActionNode node) { if (node.children.size() == 3) { String value = visit(node.children.get(2)); str += "\n " + "&& " + action.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv2 + ")" + ")" + " == " + value; + } else { + str += "\n " + "&& " + action.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv2 + ")" + ")" + " == " + "0"; } str += "\n))"; return str; From 712efb9752710cbf7d17fdca90bbf8bc7e643c38 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 21 Jul 2022 10:33:30 -0700 Subject: [PATCH 047/516] Add CAst utils --- .../generator/uclid/UclidGenerator.java | 4 +- .../lflang/generator/uclid/ast/CAstUtils.java | 50 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 org.lflang/src/org/lflang/generator/uclid/ast/CAstUtils.java diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 13099d1bfa..7f0925b169 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -939,8 +939,8 @@ protected void generateReactionAxioms() { "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", " (rxn(i) == " + reaction.getReaction().getFullNameWithJoiner("_") + ")", " ==> " + "(" + "(" + axiom + ")", - " && " + "( " + "true", - " // Default behavior" + "&& " + "( " + "true", + "// Default behavior" )); for (NamedInstance key : resetConditions.keySet()) { diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CAstUtils.java b/org.lflang/src/org/lflang/generator/uclid/ast/CAstUtils.java new file mode 100644 index 0000000000..ac40caed23 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/uclid/ast/CAstUtils.java @@ -0,0 +1,50 @@ +package org.lflang.generator.uclid.ast; + +import java.util.List; + +public class CAstUtils { + + public static CAst.AstNode takeConjunction(List conditions) { + if (conditions.size() == 0) { + return new CAst.LiteralNode("true"); + } else if (conditions.size() == 1) { + return conditions.get(0); + } else { + // Take the conjunction of all the conditions. + CAst.LogicalAndNode top = new CAst.LogicalAndNode(); + CAst.LogicalAndNode cur = top; + for (int i = 0; i < conditions.size()-1; i++) { + cur.left = conditions.get(i); + if (i == conditions.size()-2) { + cur.right = conditions.get(i+1); + } else { + cur.right = new CAst.LogicalAndNode(); + cur =(CAst.LogicalAndNode)cur.right; + } + } + return top; + } + } + + public static CAst.AstNode takeDisjunction(List conditions) { + if (conditions.size() == 0) { + return new CAst.LiteralNode("true"); + } else if (conditions.size() == 1) { + return conditions.get(0); + } else { + // Take the conjunction of all the conditions. + CAst.LogicalOrNode top = new CAst.LogicalOrNode(); + CAst.LogicalOrNode cur = top; + for (int i = 0; i < conditions.size()-1; i++) { + cur.left = conditions.get(i); + if (i == conditions.size()-2) { + cur.right = conditions.get(i+1); + } else { + cur.right = new CAst.LogicalOrNode(); + cur =(CAst.LogicalOrNode)cur.right; + } + } + return top; + } + } +} \ No newline at end of file From f5915f36d3882443779fd56f7b02974d674295de Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 21 Jul 2022 20:34:49 -0700 Subject: [PATCH 048/516] Fix tag_schedule, action one-to-one correspondence, and startup axioms --- .../lflang/generator/uclid/MTLVisitor.java | 14 +- .../generator/uclid/UclidGenerator.java | 182 ++++++++++-------- .../generator/uclid/ast/CToUclidVisitor.java | 19 +- 3 files changed, 112 insertions(+), 103 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java index c569b8ddc9..5e38ce18f0 100644 --- a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java @@ -423,29 +423,29 @@ private String generateTimePredicate(MTLParser.IntervalContext ctx, if (ctx instanceof MTLParser.SingletonContext) { MTLParser.SingletonContext singletonCtx = (MTLParser.SingletonContext)ctx; timePredicate += "tag_same(g(" + prefixIdx + "), " + "tag_schedule(g(" - + prevPrefixIdx + "), nsec(" + upperBoundNanoSec + ")))"; + + prevPrefixIdx + "), " + upperBoundNanoSec + "))"; } else { MTLParser.RangeContext rangeCtx = (MTLParser.RangeContext)ctx; timePredicate += "("; if (rangeCtx.LBRACKET() != null) { // FIXME: Check if this can be replaced by a !tag_earlier. timePredicate += "tag_later(g(" + prefixIdx + "), " - + "tag_schedule(g(" + prevPrefixIdx + "), nsec(" + lowerBoundNanoSec + ")))" + + "tag_schedule(g(" + prevPrefixIdx + "), " + lowerBoundNanoSec + "))" + " || " + "tag_same(g(" + prefixIdx + "), " - + "tag_schedule(g(" + prevPrefixIdx + "), nsec(" + lowerBoundNanoSec + ")))"; + + "tag_schedule(g(" + prevPrefixIdx + "), " + lowerBoundNanoSec + "))"; } else { timePredicate += "tag_later(g(" + prefixIdx + "), " - + "tag_schedule(g(" + prevPrefixIdx + "), nsec(" + lowerBoundNanoSec + ")))"; + + "tag_schedule(g(" + prevPrefixIdx + "), " + lowerBoundNanoSec + "))"; } timePredicate += ") && ("; if (rangeCtx.RBRACKET() != null) { timePredicate += "tag_earlier(g(" + prefixIdx + "), " - + "tag_schedule(g(" + prevPrefixIdx + "), nsec(" + upperBoundNanoSec + ")))" + + "tag_schedule(g(" + prevPrefixIdx + "), " + upperBoundNanoSec + "))" + " || " + "tag_same(g(" + prefixIdx + "), " - + "tag_schedule(g(" + prevPrefixIdx + "), nsec(" + upperBoundNanoSec + ")))"; + + "tag_schedule(g(" + prevPrefixIdx + "), " + upperBoundNanoSec + "))"; } else { timePredicate += "tag_earlier(g(" + prefixIdx + "), " - + "tag_schedule(g(" + prevPrefixIdx + "), nsec(" + upperBoundNanoSec + ")))"; + + "tag_schedule(g(" + prevPrefixIdx + "), " + upperBoundNanoSec + "))"; } timePredicate += ")"; } diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 7f0925b169..01f048b3ba 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -336,23 +336,8 @@ protected void generateTimingSemantics() { " || (pi1(t1) == pi1(t2) && pi2(t1) < pi2(t2))", " || (!isInf(t1) && isInf(t2));", "", - "// mstep() produces a mstep delay. zero() produces no delay.", - "define tag_schedule(t : tag_t, i : interval_t) : tag_t", - "= if (!isInf(t) && !isInf(i) && pi1(i) == 0 && pi2(i) == 1)", - " then { pi1(t), pi2(t) + 1 } // microstep delay", - " else ( if (!isInf(t) && !isInf(i) && pi1(i) == 0 && pi2(i) == 0)", - " then t // no delay", - " else (", - " if (!isInf(t) && pi1(i) > 0 && !isInf(i))", - " then { pi1(t) + pi1(i), 0 }", - " else inf()", - " ));", - "", - "// Deprecated.", - "define tag_delay(t : tag_t, i : interval_t) : tag_t", - "= if (!isInf(t) && !isInf(i))", - " then { pi1(t) + pi1(i), pi2(t) + pi2(i) }", - " else inf();", + "define tag_schedule(t : tag_t, i : integer) : tag_t", + "= if (i == 0) then { pi1(t), pi2(t)+1 } else { pi1(t)+i, 0 };", "", "// Only consider timestamp for now.", "define tag_diff(t1, t2: tag_t) : interval_t", @@ -391,15 +376,11 @@ protected void generateTraceDefinition(int CT) { code.unindent(); code.pr("};\n\n"); - // FIXME: Let's see if in_range() can be removed altogether. - // define in_range(num : integer) : boolean - // = num >= START && num <= END; - // Define step, event, and trace types. code.pr(String.join("\n", "// Define step and event types.", "type step_t = integer;", - "type event_t = { rxn_t, tag_t, state_t, trigger_t };", + "type event_t = { rxn_t, tag_t, state_t, trigger_t, sched_t };", "", "// Create a bounded trace with length " + String.valueOf(CT) )); @@ -430,7 +411,6 @@ protected void generateTraceDefinition(int CT) { // Define a tuple getter. // FIXME: Support this feature in Uclid. String initialStates = ""; - String initialTriggerPresence = ""; if (this.namedInstances.size() > 0) { initialStates = "0, ".repeat(this.namedInstances.size()); initialStates = initialStates.substring(0, initialStates.length()-2); @@ -438,6 +418,7 @@ protected void generateTraceDefinition(int CT) { // Initialize a dummy variable just to make the code compile. initialStates = "0"; } + String initialTriggerPresence = ""; if (this.triggerInstances.size() > 0) { initialTriggerPresence = "false, ".repeat(this.triggerInstances.size()); initialTriggerPresence = initialTriggerPresence.substring(0, initialTriggerPresence.length()-2); @@ -445,12 +426,20 @@ protected void generateTraceDefinition(int CT) { // Initialize a dummy variable just to make the code compile. initialTriggerPresence = "false"; } + String initialActionsScheduled = ""; + if (this.actionInstances.size() > 0) { + initialActionsScheduled = "false, ".repeat(this.actionInstances.size()); + initialActionsScheduled = initialActionsScheduled.substring(0, initialActionsScheduled.length()-2); + } else { + // Initialize a dummy variable just to make the code compile. + initialActionsScheduled = "false"; + } code.pr("// Helper macro that returns an element based on index."); code.pr("define get(tr : trace_t, i : step_t) : event_t ="); for (int i = 0; i < CT; i++) { code.pr("if (i == " + String.valueOf(i) + ") then tr._" + String.valueOf(i+1) + " else ("); } - code.pr("{ NULL, inf(), { " + initialStates + " }, { " + initialTriggerPresence + " } }"); + code.pr("{ NULL, inf(), { " + initialStates + " }, { " + initialTriggerPresence + " }, {" + initialActionsScheduled + "} }"); code.pr(")".repeat(CT) + ";\n"); // Define an event getter from the trace. @@ -463,6 +452,7 @@ protected void generateTraceDefinition(int CT) { "define g (i : step_t) : tag_t = elem(i)._2;", "define s (i : step_t) : state_t = elem(i)._3;", "define t (i : step_t) : trigger_t = elem(i)._4;", + "define d (i : step_t) : sched_t = elem(i)._5;", "define isNULL (i : step_t) : boolean = rxn(i) == NULL;", "" )); @@ -500,6 +490,7 @@ protected void generateReactionIdsAndStateVars() { // State variables and triggers // FIXME: expand to data types other than integer + code.pr("// State variables and triggers"); code.pr("type state_t = {"); code.indent(); if (this.namedInstances.size() > 0) { @@ -522,6 +513,7 @@ protected void generateReactionIdsAndStateVars() { code.pr("\n"); // Newline // A boolean tuple indicating whether triggers are present. + code.pr("// A boolean tuple indicating whether triggers are present."); code.pr("type trigger_t = {"); code.indent(); if (this.triggerInstances.size() > 0) { @@ -541,6 +533,32 @@ protected void generateReactionIdsAndStateVars() { for (var i = 0; i < this.triggerInstances.size(); i++) { code.pr("define " + this.triggerInstances.get(i).getFullNameWithJoiner("_") + "_is_present" + "(t : trigger_t) : boolean = t._" + (i+1) + ";"); } + + // A boolean tuple indicating whether actions are scheduled by reactions + // at the instant when reactions are triggered. + code.pr(String.join("\n", + "// A boolean tuple indicating whether actions are scheduled by reactions", + "// at the instant when reactions are triggered." + )); + code.pr("type sched_t = {"); + code.indent(); + if (this.actionInstances.size() > 0) { + for (var i = 0 ; i < this.actionInstances.size(); i++) { + code.pr("boolean" + ((i == this.actionInstances.size() - 1) ? "" : ",") + "\t// " + this.actionInstances.get(i)); + } + } else { + code.pr(String.join("\n", + "// There are no actions.", + "// Insert a dummy boolean to make the model compile.", + "boolean" + )); + } + code.unindent(); + code.pr("};"); + code.pr("// Projection macros for action schedule flag"); + for (var i = 0; i < this.actionInstances.size(); i++) { + code.pr("define " + this.actionInstances.get(i).getFullNameWithJoiner("_") + "_scheduled" + "(d : sched_t) : boolean = d._" + (i+1) + ";"); + } } /** @@ -582,12 +600,7 @@ protected void generateReactorSemantics() { "axiom(finite_forall (j : integer) in indices :: (j > START && j <= END) ==> (", " (rxn(j)) != NULL) ==> ", " (finite_forall (i : integer) in indices :: (i > START && i < j) ==> (rxn(i) != NULL)));", - "", - "// FIXME: This axiom appears to be buggy.", - "// When a NULL event occurs, the state stays the same.", - "// axiom(finite_forall (j : integer) in indices :: (j > START && j <= END) ==> (", - "// (rxn(j) == NULL) ==> (s(j) == s(j-1))", - "// ));" + "" )); // Non-federated "happened-before" @@ -665,7 +678,7 @@ protected void generateTriggersAndReactions() { " finite_exists (j : integer) in indices :: j > i && j <= END", " && " + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(j))", " && " + destination.getFullNameWithJoiner("_") + "(s(j)) == " + source.getFullNameWithJoiner("_") + "(s(i))", - connection.isPhysical() ? "" : "&& g(j) == tag_schedule(g(i), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")", + connection.isPhysical() ? "" : "&& g(j) == tag_schedule(g(i), " + delay + ")", ")", ")) // Closes (" + source.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> ((.", //// Separator @@ -674,39 +687,10 @@ protected void generateTriggersAndReactions() { "&& (" + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", " finite_exists (j : integer) in indices :: j >= START && j < i", " && " + source.getFullNameWithJoiner("_") + "_is_present" + "(t(j))", - connection.isPhysical() ? "" : " && g(i) == tag_schedule(g(j), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")", + connection.isPhysical() ? "" : " && g(i) == tag_schedule(g(j), " + delay + ")", ")) // Closes the one-to-one relationship.", "));" )); - // code.pr(String.join("\n", - // "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", - // "// If " + source.getFullNameWithJoiner("_") + " is present, then " - // + destination.getFullNameWithJoiner("_") + " will be present.", - // "// with the same value after some fixed delay of " + delay + " nanoseconds.", - // "(" + source.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> ((", - // " finite_exists (j : integer) in indices :: j > i && j <= END", - // " && " + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(j))", - // " && " + destination.getFullNameWithJoiner("_") + "(s(j)) == " + source.getFullNameWithJoiner("_") + "(s(i))", - // connection.isPhysical() ? "" : "&& g(j) == tag_schedule(g(i), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")", - // ")||(", - // // Relaxation axioms: a port presence can not produce a downstream presence - // // but it needs to guarantee that there are no trailing NULL events. - // // FIXME: !«downstreamPortIsPresent»(t(k)) makes the solver timeout. - // "(finite_forall (k : integer) in indices :: (k > i && k <= END) ==> (rxn(k) != NULL", - // " && " + destination.getFullNameWithJoiner("_") + "(s(k)) == " + source.getFullNameWithJoiner("_") + "(s(i))", - // connection.isPhysical() ? "" : " && (tag_same(g(k), tag_schedule(g(i), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")) || tag_earlier(g(k), tag_schedule(g(i), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")))", - // ")) // Closes forall.", - // ") // Closes ||", - // ")) // Closes (" + source.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> ((.", - // "// If " + destination.getFullNameWithJoiner("_") + " is present, there exists an " + source.getFullNameWithJoiner("_") + " in prior steps.", - // "// This additional term establishes a one-to-one relationship between two ports' signals.", - // "&& (" + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", - // " finite_exists (j : integer) in indices :: j >= START && j < i", - // " && " + source.getFullNameWithJoiner("_") + "_is_present" + "(t(j))", - // connection.isPhysical() ? "" : " && g(i) == tag_schedule(g(j), " + (delay==0 ? "zero()" : "nsec(" + delay + ")") + ")", - // ")) // Closes the one-to-one relationship.", - // "));" - // )); // If destination is not present, then its value resets to 0. code.pr(String.join("\n", @@ -740,8 +724,8 @@ protected void generateTriggersAndReactions() { "&& (" + action.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", " finite_exists (j : integer) in indices :: j >= START && j < i", " && rxn(j) == " + reaction.getFullNameWithJoiner("_"), - " && g(i) == tag_schedule(g(j), " - + (action.getMinDelay() == TimeValue.ZERO ? "mstep()" : "nsec(" + action.getMinDelay().toNanoSeconds() + ")") + ")", + " && g(i) == tag_schedule(g(j), " + action.getMinDelay().toNanoSeconds() + ")", + " && " + action.getFullNameWithJoiner("_") + "_scheduled" + "(d(j))", "))" ); } @@ -768,14 +752,6 @@ protected void generateTriggersAndReactions() { } } - // FIXME: Factor the startup trigger here as well. - // code.pr(String.join("\n", - // "define startup_triggers(n : rxn_t) : boolean", - // "= // if startup is within frame, put the events in the trace.", - // " ((start_time == 0) ==> (finite_exists (i : integer) in indices :: in_range(i)", - // " && rxn(i) == n && tag_same(g(i), zero())));" - // )); - code.pr(String.join("\n", "/********************************", " * Reactions and Their Triggers *", @@ -783,13 +759,13 @@ protected void generateTriggersAndReactions() { )); // Iterate over all reactions, generate conditions for them // to be triggered. + outerLoop: for (ReactionInstance.Runtime reaction : this.reactionInstances) { - code.pr(String.join("\n", + String str = String.join("\n", "// " + reaction.getReaction().getFullNameWithJoiner("_") + " is invoked when any of it triggers are present.", "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> ((", " false" - )); - code.indent(); + ); // Iterate over the triggers of the reaction. for (TriggerInstance trigger : reaction.getReaction().triggers) { @@ -799,11 +775,31 @@ protected void generateTriggersAndReactions() { // Check if this trigger is a built-in trigger (startup or shutdown). if (trigger.isStartup()) { // FIXME: Treat startup as a variable. - triggerPresentStr = "g(i) == zero()"; + // triggerPresentStr = "g(i) == zero()"; + + // Handle startup. + code.pr(String.join("\n", + "// If startup is within frame (and all variables have default values),", + "// " + reaction.getReaction().getFullNameWithJoiner("_") + " must be invoked.", + "axiom(", + " ((start_time == 0) ==> (", + " finite_exists (i : integer) in indices :: i > START && i <= END", + " && rxn(i) == " + reaction.getReaction().getFullNameWithJoiner("_") + " && tag_same(g(i), zero())", + " && !(", + " finite_exists (j : integer) in indices :: j > START && j <= END", + " && rxn(j) == " + reaction.getReaction().getFullNameWithJoiner("_") , + " && j != i", + " )", + " ))", + ");" + )); + continue outerLoop; } else if (trigger.isShutdown()) { // FIXME: For a future version. + System.out.println("Not implemented!"); } else { // Unreachable. + System.out.println("Unreachable!"); } } else { @@ -823,12 +819,13 @@ protected void generateTriggersAndReactions() { } } - code.pr("|| (" + triggerPresentStr + exclusion + ")"); + // code.pr("|| (" + triggerPresentStr + exclusion + ")"); + str += "\n|| (" + triggerPresentStr + exclusion + ")"; } // If any of the above trigger is present, then trigger the reaction. - code.unindent(); - code.pr(") <==> (rxn(i) == " + reaction.getReaction().getFullNameWithJoiner("_") + ")));"); + str += "\n) <==> (rxn(i) == " + reaction.getReaction().getFullNameWithJoiner("_") + ")));"; + code.pr(str); } } @@ -855,6 +852,9 @@ protected void generateInitialConditions() { for (var t : this.triggerInstances) { code.pr("&& !" + t.getFullNameWithJoiner("_") + "_is_present" + "(t(0))"); } + for (var d : this.actionInstances) { + code.pr("&& !" + d.getFullNameWithJoiner("_") + "_scheduled" + "(d(0))"); + } code.unindent(); code.pr(";\n"); } @@ -900,10 +900,9 @@ protected void generateReactionAxioms() { // For the variables that are used, extract the conditions // for setting them, and take the negation of their conjunction // to get the condition for resetting them. - List unusedStates = new ArrayList<>( - reaction.getReaction().getParent().states); - List unusedOutputs = new ArrayList<>( - reaction.getReaction().getParent().outputs); + List unusedStates = new ArrayList<>(this.stateVariables); + List unusedOutputs = new ArrayList<>(this.outputInstances); + List unusedActions = new ArrayList<>(this.actionInstances); HashMap> resetConditions = new HashMap<>(); for (CAst.AstNode node : inf.children) { CAst.IfBlockNode ifBlockNode = (CAst.IfBlockNode)node; @@ -921,6 +920,12 @@ protected void generateReactionAxioms() { instance = reaction.getReaction().getParent().outputs.stream() .filter(s -> s.getName().equals(name)).findFirst().get(); unusedOutputs.remove(instance); + } else if (ifBody instanceof CAst.ScheduleActionNode) { + CAst.ScheduleActionNode n = (CAst.ScheduleActionNode)ifBody; + String name = ((CAst.VariableNode)n.children.get(0)).name; + instance = reaction.getReaction().getParent().actions.stream() + .filter(s -> s.getName().equals(name)).findFirst().get(); + unusedActions.remove(instance); } else continue; // Create a new entry in the list if there isn't one yet. if (resetConditions.get(instance) == null) { @@ -940,9 +945,8 @@ protected void generateReactionAxioms() { " (rxn(i) == " + reaction.getReaction().getFullNameWithJoiner("_") + ")", " ==> " + "(" + "(" + axiom + ")", "&& " + "( " + "true", - "// Default behavior" + "// Default behavior of the used variables" )); - for (NamedInstance key : resetConditions.keySet()) { CAst.AstNode disjunction = CAstUtils.takeDisjunction(resetConditions.get(key)); System.out.println("!!! Reset conditions: " + resetConditions.get(key)); @@ -968,9 +972,12 @@ protected void generateReactionAxioms() { // Reset presence + "\n&& " + n.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + "i" + ")" + ")" + " == " - + false // default presence + + "false" // default presence + ")" ); + } else if (key instanceof ActionInstance) { + ActionInstance n = (ActionInstance)key; + code.pr(n.getFullNameWithJoiner("_") + "_scheduled" + "(" + "d" + "(" + "i" + ")" + ")" + " == " + "false"); } else { System.out.println("Unreachable!"); } @@ -1002,6 +1009,11 @@ protected void generateReactionAxioms() { ); code.pr("))"); } + for (ActionInstance a : unusedActions) { + code.pr("&& (true ==> ("); + code.pr(a.getFullNameWithJoiner("_") + "_scheduled" + "(" + "d" + "(" + "i" + ")" + ")" + " == " + "false"); + code.pr("))"); + } code.pr("))));"); } } diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java index 1f6b689acc..097a8e7c41 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java @@ -123,7 +123,7 @@ public String visitLogicalAndNode(LogicalAndNode node) { @Override public String visitLogicalNotNode(LogicalNotNode node) { - return "!" + visit(node.child); + return "!" + "(" + visit(node.child) + ")"; } @Override @@ -152,18 +152,15 @@ public String visitScheduleActionNode(ScheduleActionNode node) { String name = ((VariableNode)node.children.get(0)).name; NamedInstance instance = getInstanceByName(name); ActionInstance action = (ActionInstance)instance; - String delay = visit(node.children.get(1)); + String additionalDelay = visit(node.children.get(1)); // FIXME: Suppport this. String str = "\n(" - + "finite_exists (" + this.qv2 + " : integer) in indices :: (i > START && i <= END) && (" + + "(finite_exists (" + this.qv2 + " : integer) in indices :: (i > START && i <= END) && (" + "\n " + action.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv2 + ")" + ")" - + "\n " + "&& " + "tag_same" + "(" + "g(" + this.qv2 + ")" + ", " + "tag_schedule" + "(" + "g" + "(" + this.qv + ")" + ", " + "nsec" + "(" + action.getMinDelay().toNanoSeconds() + ")" + ")" + ")"; - if (node.children.size() == 3) { - String value = visit(node.children.get(2)); - str += "\n " + "&& " + action.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv2 + ")" + ")" + " == " + value; - } else { - str += "\n " + "&& " + action.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv2 + ")" + ")" + " == " + "0"; - } - str += "\n))"; + + "\n " + "&& " + "tag_same" + "(" + "g(" + this.qv2 + ")" + ", " + "tag_schedule" + "(" + "g" + "(" + this.qv + ")" + ", " + action.getMinDelay().toNanoSeconds() + ")" + ")" + + "\n " + "&& " + action.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv2 + ")" + ")" + " == " + "0" + + "\n)) // Closes finite_exists" + + "\n&& " + action.getFullNameWithJoiner("_") + "_scheduled" + "(" + "d" + "(" + this.qv + ")" + ")" + + "\n)"; return str; } From 4d20a641c3022b47f21b20c8fef939edbee5bb15 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 21 Jul 2022 23:34:38 -0700 Subject: [PATCH 049/516] Add a (temporary) fix for variable precedence. --- .../generator/uclid/UclidGenerator.java | 5 ++++ .../org/lflang/generator/uclid/ast/CAst.java | 1 + .../generator/uclid/ast/CToUclidVisitor.java | 23 ++++++++++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 01f048b3ba..5e91531f6c 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -71,6 +71,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.uclid.ast.CBaseAstVisitor; import org.lflang.generator.uclid.ast.CToUclidVisitor; import org.lflang.generator.uclid.ast.IfNormalFormAstVisitor; +import org.lflang.generator.uclid.ast.VariablePrecedenceVisitor; import org.lflang.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.FileConfig; @@ -883,6 +884,10 @@ protected void generateReactionAxioms() { BuildAstParseTreeVisitor buildAstVisitor = new BuildAstParseTreeVisitor(); CAst.AstNode ast = buildAstVisitor.visitBlockItemList(parseTree); + // VariablePrecedenceVisitor + VariablePrecedenceVisitor precVisitor = new VariablePrecedenceVisitor(); + precVisitor.visit(ast); + // Traverse and print. CBaseAstVisitor baseVisitor = new CBaseAstVisitor<>(); // For pretty printing. System.out.println("***** Printing the original AST."); diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CAst.java b/org.lflang/src/org/lflang/generator/uclid/ast/CAst.java index 95c0927311..56526901e3 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/CAst.java +++ b/org.lflang/src/org/lflang/generator/uclid/ast/CAst.java @@ -296,6 +296,7 @@ public static class ScheduleActionNode extends AstNodeDynamic implements Visitab */ public static class StateVarNode extends AstNode implements Visitable { public String name; + public boolean prev = false; // By default, this is not a previous state. public StateVarNode(String name) { this.name = name; } diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java index 097a8e7c41..efd8526167 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java @@ -52,6 +52,13 @@ public CToUclidVisitor(UclidGenerator generator, ReactionInstance.Runtime reacti instances.addAll(this.reactor.states); } + @Override + public String visitAdditionNode(AdditionNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " + " + rhs + ")"; + } + @Override public String visitAssignmentNode(AssignmentNode node) { String lhs = visit(node.left); @@ -67,6 +74,13 @@ public String visitAssignmentNode(AssignmentNode node) { return "(" + lhs + " == " + rhs + ")"; } + @Override + public String visitDivisionNode(DivisionNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " / " + rhs + ")"; + } + @Override public String visitEqualNode(EqualNode node) { String lhs = visit(node.left); @@ -184,7 +198,7 @@ public String visitSetPortNode(SetPortNode node) { public String visitStateVarNode(StateVarNode node) { NamedInstance instance = getInstanceByName(node.name); if (instance != null) { - return instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; + return instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + (node.prev ? "-1" : "") + ")" + ")"; } // FIXME: Throw exception return ""; @@ -200,6 +214,13 @@ public String visitStatementSequenceNode(StatementSequenceNode node) { return axiom; } + @Override + public String visitSubtractionNode(SubtractionNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " - " + rhs + ")"; + } + @Override public String visitTriggerIsPresentNode(TriggerIsPresentNode node) { // Find the trigger instance by name. From b64e3f4fc582cd333a8abe48cadab2d567758fde Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 21 Jul 2022 23:35:14 -0700 Subject: [PATCH 050/516] Add precedence visitor --- .../uclid/ast/VariablePrecedenceVisitor.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 org.lflang/src/org/lflang/generator/uclid/ast/VariablePrecedenceVisitor.java diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/VariablePrecedenceVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/VariablePrecedenceVisitor.java new file mode 100644 index 0000000000..749c368655 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/uclid/ast/VariablePrecedenceVisitor.java @@ -0,0 +1,48 @@ +package org.lflang.generator.uclid.ast; + +import java.util.ArrayList; +import java.util.List; + +import org.lflang.generator.uclid.ast.CAst.*; + +/** + * This visitor marks certain variable node as "previous." + */ +public class VariablePrecedenceVisitor extends CBaseAstVisitor { + + // This is a temporary solution and cannot handle, + // e.g., self->s = (self->s + 1) - (2 * 2). + @Override + public Void visitAssignmentNode(AssignmentNode node) { + System.out.println("******* In assignment!!!"); + if (node.left instanceof StateVarNode) { + if (node.right instanceof AstNodeBinary) { + AstNodeBinary n = (AstNodeBinary)node.right; + if (n.left instanceof StateVarNode) { + ((StateVarNode)n.left).prev = true; + } + if (n.right instanceof StateVarNode) { + ((StateVarNode)n.right).prev = true; + } + } + } else { + System.out.println("Unreachable!"); // FIXME: Throw exception. + } + return null; + } + + @Override + public Void visitIfBlockNode(IfBlockNode node) { + visit(((IfBodyNode)node.right).left); + visit(((IfBodyNode)node.right).right); + return null; + } + + @Override + public Void visitStatementSequenceNode(StatementSequenceNode node) { + for (int i = 0; i < node.children.size(); i++) { + visit(node.children.get(i)); + } + return null; + } +} From 5a1a2b37225eee64a224f0befb64a08a78bb2b60 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 22 Jul 2022 16:43:45 -0700 Subject: [PATCH 051/516] Update schedule action mechanism and variable precedence. Verifier v0.1. --- .../src/org/lflang/generator/uclid/UclidGenerator.java | 6 +++--- .../src/org/lflang/generator/uclid/ast/CToUclidVisitor.java | 2 +- .../generator/uclid/ast/VariablePrecedenceVisitor.java | 6 ++++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 5e91531f6c..e5962f922f 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -721,8 +721,8 @@ protected void generateTriggersAndReactions() { comment += reaction.getFullNameWithJoiner("_") + ", "; triggerStr += String.join("\n", "// " + reaction.getFullNameWithJoiner("_"), - // FIXME: Should this be an OR? - "&& (" + action.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", + // OR because only any present trigger can trigger the reaction. + "|| (" + action.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", " finite_exists (j : integer) in indices :: j >= START && j < i", " && rxn(j) == " + reaction.getFullNameWithJoiner("_"), " && g(i) == tag_schedule(g(j), " + action.getMinDelay().toNanoSeconds() + ")", @@ -735,7 +735,7 @@ protected void generateTriggersAndReactions() { // print the generated code string. code.pr(String.join("\n", "// " + comment, - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> ( true", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> ( false", triggerStr, "));" )); diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java index efd8526167..457a8795c8 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java @@ -168,7 +168,7 @@ public String visitScheduleActionNode(ScheduleActionNode node) { ActionInstance action = (ActionInstance)instance; String additionalDelay = visit(node.children.get(1)); // FIXME: Suppport this. String str = "\n(" - + "(finite_exists (" + this.qv2 + " : integer) in indices :: (i > START && i <= END) && (" + + "(finite_exists (" + this.qv2 + " : integer) in indices :: (" + this.qv2 + " > " + this.qv + " && " + this.qv2 + " <= END) && (" + "\n " + action.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv2 + ")" + ")" + "\n " + "&& " + "tag_same" + "(" + "g(" + this.qv2 + ")" + ", " + "tag_schedule" + "(" + "g" + "(" + this.qv + ")" + ", " + action.getMinDelay().toNanoSeconds() + ")" + ")" + "\n " + "&& " + action.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv2 + ")" + ")" + " == " + "0" diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/VariablePrecedenceVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/VariablePrecedenceVisitor.java index 749c368655..9ae7b9447a 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/VariablePrecedenceVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/ast/VariablePrecedenceVisitor.java @@ -33,8 +33,10 @@ public Void visitAssignmentNode(AssignmentNode node) { @Override public Void visitIfBlockNode(IfBlockNode node) { - visit(((IfBodyNode)node.right).left); - visit(((IfBodyNode)node.right).right); + if (((IfBodyNode)node.right).left != null) + visit(((IfBodyNode)node.right).left); + if (((IfBodyNode)node.right).right != null) + visit(((IfBodyNode)node.right).right); return null; } From 4bcb0cf8f79f84b8854fbde4686ec0aa1cc09065 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 22 Jul 2022 23:18:52 -0700 Subject: [PATCH 052/516] Update benchmarks --- org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index e5962f922f..08c344547a 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -1201,7 +1201,7 @@ private void populateLists(ReactorInstance reactor) { * Compute a completeness threadhold for each property. */ private int computeCT(Attribute property) { - return 5; // FIXME + return 10; // FIXME } ///////////////////////////////////////////////// From a4d373ef68a26190143e5b4110fb11ddacae13de Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Tue, 9 Aug 2022 00:03:59 +0200 Subject: [PATCH 053/516] Update comments --- .../src/org/lflang/generator/uclid/UclidGenerator.java | 7 ------- org.lflang/src/org/lflang/validation/AttributeSpec.java | 4 +++- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 08c344547a..e5263cb079 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -1064,13 +1064,6 @@ protected void generateProperty(Attribute property, int CT) { code.pr(transpiled + ";"); code.unindent(); - // FIXME: No need for this since we are doing 1-induction. - // code.pr(String.join("\n", - // "// Helper macro for temporal induction", - // "define Globally_p(start, end : step_t) : boolean =", - // " (finite_forall (i : integer) in indices :: (i >= start && i <= end) ==> p(i));" - // )); - if (tactic.equals("bmc")) { code.pr(String.join("\n", "// BMC", diff --git a/org.lflang/src/org/lflang/validation/AttributeSpec.java b/org.lflang/src/org/lflang/validation/AttributeSpec.java index f64c678248..b1373b8323 100644 --- a/org.lflang/src/org/lflang/validation/AttributeSpec.java +++ b/org.lflang/src/org/lflang/validation/AttributeSpec.java @@ -213,7 +213,9 @@ enum AttrParamType { // @icon("value") ATTRIBUTE_SPECS_BY_NAME.put("icon", new AttributeSpec( List.of(new AttrParamSpec(AttributeSpec.VALUE_ATTR, AttrParamType.STRING, null)) - // @property(name="", tactic="", spec="") + )); + // @property(name="", tactic="", spec="") + // SMTL is the safety fragment of Metric Temporal Logic (MTL). ATTRIBUTE_SPECS_BY_NAME.put("property", new AttributeSpec( List.of( new AttrParamSpec("name", AttrParamType.STRING, null), From 70511bf735052a3d11edbf37db307e4c3f05384a Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Tue, 9 Aug 2022 00:12:39 +0200 Subject: [PATCH 054/516] Add comments --- .../lflang/generator/uclid/UclidGenerator.java | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index e5263cb079..d1bd95c536 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -163,7 +163,6 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // Create the src-gen directory setUpDirectories(); - // FIXME: Identify properties in the attributes. // FIXME: Calculate the completeness threshold for each property. // Generate a Uclid model for each property. for (Attribute prop : this.properties) { @@ -282,7 +281,7 @@ protected void generateUclidCode(Attribute property, int CT) { } /** - * FIXME + * Macros for timing semantics */ protected void generateTimingSemantics() { code.pr(String.join("\n", @@ -350,7 +349,7 @@ protected void generateTimingSemantics() { } /** - * FIXME + * Macros, type definitions, and variable declarations for trace (path) */ protected void generateTraceDefinition(int CT) { // Define various constants. @@ -460,7 +459,7 @@ protected void generateTraceDefinition(int CT) { } /** - * FIXME + * Type definitions for runtime reaction Ids and state variables */ protected void generateReactionIdsAndStateVars() { // Encode the components and the logical delays @@ -563,7 +562,7 @@ protected void generateReactionIdsAndStateVars() { } /** - * FIXME + * Axioms for reactor semantics */ protected void generateReactorSemantics() { code.pr(String.join("\n", @@ -628,9 +627,8 @@ protected void generateReactorSemantics() { } /** - * FIXME + * Axioms for the trigger mechanism */ - // Previously called pr_connections_and_actions() protected void generateTriggersAndReactions() { code.pr(String.join("\n", "/***************", @@ -741,7 +739,6 @@ protected void generateTriggersAndReactions() { )); // If the action is not present, then its value resets to 0. - // FIXME: Check if this works in practice. code.pr(String.join("\n", "// If " + action.getFullNameWithJoiner("_") + " is not present, then its value resets to 0.", "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END && rxn(i) != NULL) ==> (", @@ -831,7 +828,7 @@ protected void generateTriggersAndReactions() { } /** - * FIXME + * Macros for initial conditions */ protected void generateInitialConditions() { code.pr(String.join("\n", @@ -1080,7 +1077,7 @@ protected void generateProperty(Attribute property, int CT) { } /** - * FIXME + * Uclid5 control block */ protected void generateControlBlock() { code.pr(String.join("\n", From 07a8c8ddccd5824ce777ec2c36a56045fc43c605 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Tue, 9 Aug 2022 10:56:43 -0700 Subject: [PATCH 055/516] Minor update --- .../generator/uclid/UclidGenerator.java | 89 +++++++++---------- 1 file changed, 43 insertions(+), 46 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index d1bd95c536..a7b890e3d5 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -130,6 +130,11 @@ public class UclidGenerator extends GeneratorBase { /** The main place to put generated code. */ protected CodeBuilder code = new CodeBuilder(); + /** Strings from the property attribute */ + protected String name; + protected String tactic; + protected String spec; + // Constructor public UclidGenerator(FileConfig fileConfig, ErrorReporter errorReporter, List properties) { super(fileConfig, errorReporter); @@ -147,6 +152,8 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { super.cleanIfNeeded(context); super.printInfo(context.getMode()); ASTUtils.setMainName(fileConfig.resource, fileConfig.name); + // FIXME: Perform an analysis on the property and remove unrelevant components. + // FIXME: Support multiple properties here too. We might need to have a UclidGenerator for each attribute. super.createMainInstantiation(); //////////////////////////////////////// @@ -166,8 +173,26 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // FIXME: Calculate the completeness threshold for each property. // Generate a Uclid model for each property. for (Attribute prop : this.properties) { - int CT = computeCT(prop); - generateUclidFile(prop, CT); + this.name = prop.getAttrParms().stream() + .filter(attr -> attr.getName().equals("name")) + .findFirst() + .get() + .getValue() + .getStr(); + this.tactic = prop.getAttrParms().stream() + .filter(attr -> attr.getName().equals("tactic")) + .findFirst() + .get() + .getValue() + .getStr(); + this.spec = prop.getAttrParms().stream() + .filter(attr -> attr.getName().equals("spec")) + .findFirst() + .get() + .getValue() + .getStr(); + int CT = computeCT(); + generateUclidFile(CT); } // Generate runner script @@ -180,25 +205,13 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { /** * Generate the Uclid model. */ - protected void generateUclidFile(Attribute property, int CT) { - String name = property.getAttrParms().stream() - .filter(attr -> attr.getName().equals("name")) - .findFirst() - .get() - .getValue() - .getStr(); - String tactic = property.getAttrParms().stream() - .filter(attr -> attr.getName().equals("tactic")) - .findFirst() - .get() - .getValue() - .getStr(); + protected void generateUclidFile(int CT) { try { // Generate main.ucl and print to file code = new CodeBuilder(); String filename = this.outputDir - .resolve(tactic + "_" + name + ".ucl").toString(); - generateUclidCode(property, CT); + .resolve(this.tactic + "_" + this.name + ".ucl").toString(); + generateUclidCode(CT); code.writeToFile(filename); } catch (IOException e) { Exceptions.sneakyThrow(e); @@ -239,7 +252,7 @@ protected void generateRunnerScript() { /** * The main function that generates Uclid code. */ - protected void generateUclidCode(Attribute property, int CT) { + protected void generateUclidCode(int CT) { code.pr(String.join("\n", "/*******************************", " * Auto-generated UCLID5 model *", @@ -271,7 +284,7 @@ protected void generateUclidCode(Attribute property, int CT) { generateReactionAxioms(); // Properties - generateProperty(property, CT); + generateProperty(CT); // Control block generateControlBlock(); @@ -1020,58 +1033,40 @@ protected void generateReactionAxioms() { } } - protected void generateProperty(Attribute property, int CT) { + protected void generateProperty(int CT) { code.pr(String.join("\n", "/************", " * Property *", " ************/" )); - String name = property.getAttrParms().stream() - .filter(attr -> attr.getName().equals("name")) - .findFirst() - .get() - .getValue() - .getStr(); - String tactic = property.getAttrParms().stream() - .filter(attr -> attr.getName().equals("tactic")) - .findFirst() - .get() - .getValue() - .getStr(); - String spec = property.getAttrParms().stream() - .filter(attr -> attr.getName().equals("spec")) - .findFirst() - .get() - .getValue() - .getStr(); - MTLLexer lexer = new MTLLexer(CharStreams.fromString(spec)); + MTLLexer lexer = new MTLLexer(CharStreams.fromString(this.spec)); CommonTokenStream tokens = new CommonTokenStream(lexer); MTLParser parser = new MTLParser(tokens); MtlContext mtlCtx = parser.mtl(); - MTLVisitor visitor = new MTLVisitor(tactic); + MTLVisitor visitor = new MTLVisitor(this.tactic); // The visitor transpiles the MTL into a Uclid axiom. String transpiled = visitor.visitMtl(mtlCtx, "i", 0, "0", 0); code.pr("// The FOL property translated from user-defined MTL property:"); - code.pr("// " + spec); + code.pr("// " + this.spec); code.pr("define p(i : step_t) : boolean ="); code.indent(); code.pr(transpiled + ";"); code.unindent(); - if (tactic.equals("bmc")) { + if (this.tactic.equals("bmc")) { code.pr(String.join("\n", "// BMC", - "property " + "bmc_" + name + " : " + "initial_condition() ==> p(0);" + "property " + "bmc_" + this.name + " : " + "initial_condition() ==> p(0);" )); } else { code.pr(String.join("\n", "// Induction: initiation step", - "property " + "initiation_" + name + " : " + "initial_condition() ==> p(0);", + "property " + "initiation_" + this.name + " : " + "initial_condition() ==> p(0);", "// Induction: consecution step", - "property " + "consecution_" + name + " : " + "p(0) ==> p(1);" + "property " + "consecution_" + this.name + " : " + "p(0) ==> p(1);" )); } } @@ -1190,7 +1185,9 @@ private void populateLists(ReactorInstance reactor) { /** * Compute a completeness threadhold for each property. */ - private int computeCT(Attribute property) { + private int computeCT() { + // if (this.spec.equals("bmc")) { + // } return 10; // FIXME } From f53d45d7cd870426bca77c4a6c9c147fbec2f769 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 21 Nov 2022 20:55:01 -0800 Subject: [PATCH 056/516] Generate verification models in a "model-gen" directory. --- org.lflang/src/org/lflang/FileConfig.java | 37 ++++++++++ .../org/lflang/generator/GeneratorBase.java | 16 ----- .../src/org/lflang/generator/LFGenerator.java | 17 +++++ .../generator/uclid/UclidGenerator.java | 69 ++++++++++--------- 4 files changed, 89 insertions(+), 50 deletions(-) diff --git a/org.lflang/src/org/lflang/FileConfig.java b/org.lflang/src/org/lflang/FileConfig.java index dd31182c07..08c877e065 100644 --- a/org.lflang/src/org/lflang/FileConfig.java +++ b/org.lflang/src/org/lflang/FileConfig.java @@ -37,6 +37,11 @@ public class FileConfig { */ public static final String DEFAULT_SRC_GEN_DIR = "src-gen"; + /** + * Default name of the directory to store generated verification models in. + */ + public static final String DEFAULT_MODEL_GEN_DIR = "model-gen"; + // Public fields. /** @@ -110,6 +115,16 @@ public class FileConfig { */ protected Path srcGenPath; + /** + * Path representation of the root directory for generated + * verification models. + */ + protected Path modelGenBasePath; + + /** + * The directory in which to put the generated verification models. + */ + protected Path modelGenPath; // private fields @@ -150,6 +165,9 @@ public FileConfig(Resource resource, Path srcGenBasePath, boolean useHierarchica Path binRoot = outPath.resolve(DEFAULT_BIN_DIR); this.binPath = useHierarchicalBin ? binRoot.resolve(getSubPkgPath(srcPath)) : binRoot; + this.modelGenBasePath = outPath.resolve(DEFAULT_MODEL_GEN_DIR); + this.modelGenPath = modelGenBasePath.resolve(getSubPkgPath(srcPath)).resolve(name); + this.iResource = FileUtil.getIResource(resource); } @@ -238,6 +256,24 @@ protected Path getSubPkgPath(Path srcPath) { return relSrcPath; } + /** + * Path representation of the root directory for generated + * verification models. + * This is the root, meaning that if the source file is x/y/Z.lf + * relative to the package root, then the generated sources will be put in x/y/Z + * relative to this URI. + */ + public Path getModelGenBasePath() { + return modelGenBasePath; + } + + /** + * The directory in which to put the generated verification models. + */ + public Path getModelGenPath() { + return modelGenPath; + } + /** * Clean any artifacts produced by the code generator and target compilers. * @@ -250,6 +286,7 @@ protected Path getSubPkgPath(Path srcPath) { public void doClean() throws IOException { FileUtil.deleteDirectory(binPath); FileUtil.deleteDirectory(srcGenBasePath); + FileUtil.deleteDirectory(modelGenBasePath); } private static Path getPkgPath(Resource resource) throws IOException { diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.java b/org.lflang/src/org/lflang/generator/GeneratorBase.java index ec9a6085f4..98e40197c0 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.java +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.java @@ -300,8 +300,6 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { context, GeneratorUtils.findTarget(fileConfig.resource), targetConfig, errorReporter ); - cleanIfNeeded(context); - printInfo(context.getMode()); // Clear any IDE markers that may have been created by a previous build. @@ -376,20 +374,6 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { enableSupportForSerializationIfApplicable(context.getCancelIndicator()); } - /** - * 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 { - fileConfig.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 diff --git a/org.lflang/src/org/lflang/generator/LFGenerator.java b/org.lflang/src/org/lflang/generator/LFGenerator.java index 1fbcf99552..95e0cedc38 100644 --- a/org.lflang/src/org/lflang/generator/LFGenerator.java +++ b/org.lflang/src/org/lflang/generator/LFGenerator.java @@ -167,6 +167,9 @@ public void doGenerate(Resource resource, IFileSystemAccess2 fsa, final ErrorReporter errorReporter = lfContext.constructErrorReporter(fileConfig); final GeneratorBase generator = createGenerator(target, fileConfig, errorReporter); + // If "-c" or "--clean" is specified, delete any existing generated directories. + cleanIfNeeded(lfContext, fileConfig); + // Check if @property is used. If so, include UclidGenerator. Reactor main = ASTUtils.getMainReactor(resource); List properties = AttributeUtils.getAttributes(main) @@ -191,4 +194,18 @@ public void doGenerate(Resource resource, IFileSystemAccess2 fsa, public boolean errorsOccurred() { return generatorErrorsOccurred; } + + /** + * Check if a clean was requested from the standalone compiler and perform + * the clean step. + */ + protected void cleanIfNeeded(LFGeneratorContext context, FileConfig fileConfig) { + if (context.getArgs().containsKey("clean")) { + try { + fileConfig.doClean(); + } catch (IOException e) { + System.err.println("WARNING: IO Error during clean"); + } + } + } } diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index a7b890e3d5..6903507ab8 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -49,6 +49,19 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.xbase.lib.Exceptions; + +import org.lflang.ASTUtils; +import org.lflang.ErrorReporter; +import org.lflang.FileConfig; +import org.lflang.Target; +import org.lflang.TimeUnit; +import org.lflang.TimeValue; +import org.lflang.dsl.CLexer; +import org.lflang.dsl.CParser; +import org.lflang.dsl.MTLLexer; +import org.lflang.dsl.MTLParser; +import org.lflang.dsl.CParser.BlockItemListContext; +import org.lflang.dsl.MTLParser.MtlContext; import org.lflang.generator.ActionInstance; import org.lflang.generator.CodeBuilder; import org.lflang.generator.GeneratorBase; @@ -72,18 +85,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.uclid.ast.CToUclidVisitor; import org.lflang.generator.uclid.ast.IfNormalFormAstVisitor; import org.lflang.generator.uclid.ast.VariablePrecedenceVisitor; -import org.lflang.ASTUtils; -import org.lflang.ErrorReporter; -import org.lflang.FileConfig; -import org.lflang.Target; -import org.lflang.TimeUnit; -import org.lflang.TimeValue; -import org.lflang.dsl.CLexer; -import org.lflang.dsl.CParser; -import org.lflang.dsl.MTLLexer; -import org.lflang.dsl.MTLParser; -import org.lflang.dsl.CParser.BlockItemListContext; -import org.lflang.dsl.MTLParser.MtlContext; import org.lflang.lf.Action; import org.lflang.lf.Attribute; import org.lflang.lf.Code; @@ -92,7 +93,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.Reaction; import org.lflang.lf.Time; import org.lflang.lf.VarRef; -import org.w3c.dom.Attr; +import org.lflang.util.StringUtil; import static org.lflang.ASTUtils.*; @@ -149,7 +150,6 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { GeneratorUtils.setTargetConfig( context, GeneratorUtils.findTarget(fileConfig.resource), targetConfig, errorReporter ); - super.cleanIfNeeded(context); super.printInfo(context.getMode()); ASTUtils.setMainName(fileConfig.resource, fileConfig.name); // FIXME: Perform an analysis on the property and remove unrelevant components. @@ -173,24 +173,25 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // FIXME: Calculate the completeness threshold for each property. // Generate a Uclid model for each property. for (Attribute prop : this.properties) { - this.name = prop.getAttrParms().stream() - .filter(attr -> attr.getName().equals("name")) - .findFirst() - .get() - .getValue() - .getStr(); - this.tactic = prop.getAttrParms().stream() - .filter(attr -> attr.getName().equals("tactic")) - .findFirst() - .get() - .getValue() - .getStr(); - this.spec = prop.getAttrParms().stream() - .filter(attr -> attr.getName().equals("spec")) - .findFirst() - .get() - .getValue() - .getStr(); + this.name = StringUtil.removeQuotes( + prop.getAttrParms().stream() + .filter(attr -> attr.getName().equals("name")) + .findFirst() + .get() + .getValue()); + this.tactic = StringUtil.removeQuotes( + prop.getAttrParms().stream() + .filter(attr -> attr.getName().equals("tactic")) + .findFirst() + .get() + .getValue()); + this.spec = StringUtil.removeQuotes( + prop.getAttrParms().stream() + .filter(attr -> attr.getName().equals("spec")) + .findFirst() + .get() + .getValue()); + int CT = computeCT(); generateUclidFile(CT); } @@ -1123,8 +1124,8 @@ private void createMainReactorInstance() { private void setUpDirectories() { // Make sure the target directory exists. - Path srcgenDir = this.fileConfig.getSrcGenPath(); - this.outputDir = Paths.get(srcgenDir.toString() + File.separator + "model"); + Path modelGenDir = this.fileConfig.getModelGenPath(); + this.outputDir = Paths.get(modelGenDir.toString()); try { Files.createDirectories(outputDir); } catch (IOException e) { From 984579757ab2683c2d3dcbe97538b8cc62219d4f Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 21 Nov 2022 21:40:27 -0800 Subject: [PATCH 057/516] Use arrays instead of tuples to represent traces --- .../generator/uclid/UclidGenerator.java | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 6903507ab8..73f7c87325 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -236,7 +236,7 @@ protected void generateRunnerScript() { "rm -rf ./smt/ && mkdir -p smt", "", "echo '*** Generating SMT files from UCLID5'", - "time uclid --verbosity 3 -g \"smt/output\" $1", + "time uclid -g \"smt/output\" $1", "", "echo '*** Append (get-model) to each file'", "ls smt | xargs -I {} bash -c 'echo \"(get-model)\" >> smt/{}'", @@ -384,9 +384,11 @@ protected void generateTraceDefinition(int CT) { // so that we can use finite quantifiers. code.pr("group indices : integer = {"); code.indent(); + String indices = ""; for (int i = 0; i < CT; i++) { - code.pr(String.valueOf(i) + (i == CT-1? "" : ",")); + indices += String.valueOf(i) + (i == CT-1? "" : ", "); } + code.pr(indices); code.unindent(); code.pr("};\n\n"); @@ -398,13 +400,8 @@ protected void generateTraceDefinition(int CT) { "", "// Create a bounded trace with length " + String.valueOf(CT) )); - code.pr("type trace_t = {"); - code.indent(); - for (int i = 0; i < CT; i++) { - code.pr("event_t" + (i == CT-1? "" : ",")); - } - code.unindent(); - code.pr("};\n"); + code.pr("// Potentially unbounded trace, we bound this later."); + code.pr("type trace_t = [integer]event_t;"); // Declare start time and trace. code.pr(String.join("\n", @@ -450,11 +447,8 @@ protected void generateTraceDefinition(int CT) { } code.pr("// Helper macro that returns an element based on index."); code.pr("define get(tr : trace_t, i : step_t) : event_t ="); - for (int i = 0; i < CT; i++) { - code.pr("if (i == " + String.valueOf(i) + ") then tr._" + String.valueOf(i+1) + " else ("); - } - code.pr("{ NULL, inf(), { " + initialStates + " }, { " + initialTriggerPresence + " }, {" + initialActionsScheduled + "} }"); - code.pr(")".repeat(CT) + ";\n"); + code.pr("if (i >= START || i <= END) then tr[i] else"); + code.pr("{ NULL, inf(), { " + initialStates + " }, { " + initialTriggerPresence + " }, {" + initialActionsScheduled + "} };"); // Define an event getter from the trace. code.pr(String.join("\n", @@ -1189,7 +1183,7 @@ private void populateLists(ReactorInstance reactor) { private int computeCT() { // if (this.spec.equals("bmc")) { // } - return 10; // FIXME + return 30; // FIXME } ///////////////////////////////////////////////// From cb667aa9f8a74c508474d2bd3adda3df9c3cf806 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 30 Nov 2022 18:50:39 -0800 Subject: [PATCH 058/516] Start to work on a state space explorer --- .../src/org/lflang/generator/LFGenerator.java | 3 + .../lflang/generator/ReactionInstance.java | 5 +- .../lflang/generator/uclid/MTLVisitor.java | 13 + .../generator/uclid/UclidGenerator.java | 96 +++++-- .../generator/uclid/ast/CBaseAstVisitor.java | 86 +++--- org.lflang/src/org/lflang/sim/Event.java | 37 +++ .../src/org/lflang/sim/StateSpaceDiagram.java | 49 ++++ .../org/lflang/sim/StateSpaceExplorer.java | 271 ++++++++++++++++++ .../src/org/lflang/sim/StateSpaceNode.java | 46 +++ org.lflang/src/org/lflang/sim/Tag.java | 62 ++++ 10 files changed, 595 insertions(+), 73 deletions(-) create mode 100644 org.lflang/src/org/lflang/sim/Event.java create mode 100644 org.lflang/src/org/lflang/sim/StateSpaceDiagram.java create mode 100644 org.lflang/src/org/lflang/sim/StateSpaceExplorer.java create mode 100644 org.lflang/src/org/lflang/sim/StateSpaceNode.java create mode 100644 org.lflang/src/org/lflang/sim/Tag.java diff --git a/org.lflang/src/org/lflang/generator/LFGenerator.java b/org.lflang/src/org/lflang/generator/LFGenerator.java index 95e0cedc38..cba9f39188 100644 --- a/org.lflang/src/org/lflang/generator/LFGenerator.java +++ b/org.lflang/src/org/lflang/generator/LFGenerator.java @@ -171,6 +171,8 @@ public void doGenerate(Resource resource, IFileSystemAccess2 fsa, cleanIfNeeded(lfContext, fileConfig); // Check if @property is used. If so, include UclidGenerator. + // The verification model needs to be generated before the target code + // since code generation changes LF program (desugar connections, etc.). Reactor main = ASTUtils.getMainReactor(resource); List properties = AttributeUtils.getAttributes(main) .stream() @@ -181,6 +183,7 @@ public void doGenerate(Resource resource, IFileSystemAccess2 fsa, uclidGenerator.doGenerate(resource, lfContext); } + // Generate target code from the LF program. if (generator != null) { generator.doGenerate(resource, lfContext); generatorErrorsOccurred = generator.errorsOccurred(); diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index 0ee81cd824..ead58438c6 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -128,7 +128,10 @@ public ReactionInstance( this.sources.add(timerInstance); } } else if (trigger instanceof BuiltinTriggerRef) { - this.triggers.add(parent.getOrCreateBuiltinTrigger((BuiltinTriggerRef) trigger)); + var builtinTriggerInstance + = parent.getOrCreateBuiltinTrigger((BuiltinTriggerRef) trigger); + this.triggers.add(builtinTriggerInstance); + builtinTriggerInstance.dependentReactions.add(this); } } // Next handle the ports that this reaction reads. diff --git a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java index 5e38ce18f0..73eaadb452 100644 --- a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java @@ -46,6 +46,9 @@ public class MTLVisitor extends MTLParserBaseVisitor { /** Tactic to be used to prove the property. */ protected String tactic; + /** Time horizon (in nanoseconds) of the property */ + protected long horizon = 0; + // Constructor public MTLVisitor(String tactic) { this.tactic = tactic; @@ -171,6 +174,7 @@ public String visitUntil(MTLParser.UntilContext ctx, long lowerBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); long currentHorizon = horizon + upperBoundNanoSec; + this.horizon = currentHorizon; String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, upperBoundNanoSec, "j" + QFIdx, prefixIdx); @@ -217,6 +221,7 @@ public String visitGlobally(MTLParser.GloballyContext ctx, long lowerBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); long currentHorizon = horizon + upperBoundNanoSec; + this.horizon = currentHorizon; String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, upperBoundNanoSec, "j" + QFIdx, prefixIdx); return "!(" + "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " @@ -242,6 +247,7 @@ public String visitFinally(MTLParser.FinallyContext ctx, long lowerBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); long currentHorizon = horizon + upperBoundNanoSec; + this.horizon = currentHorizon; String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, upperBoundNanoSec, "j" + QFIdx, prefixIdx); return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " @@ -452,4 +458,11 @@ private String generateTimePredicate(MTLParser.IntervalContext ctx, return timePredicate; } + + /** + * Get the time horizon (in nanoseconds) of the property. + */ + public long getHorizon() { + return this.horizon; + } } diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 73f7c87325..3dec037d8f 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -47,7 +47,9 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.eclipse.emf.common.util.EList; +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.Exceptions; import org.lflang.ASTUtils; @@ -78,6 +80,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.TargetTypes; import org.lflang.generator.TimerInstance; import org.lflang.generator.TriggerInstance; +import org.lflang.generator.c.CGenerator; import org.lflang.generator.uclid.ast.BuildAstParseTreeVisitor; import org.lflang.generator.uclid.ast.CAst; import org.lflang.generator.uclid.ast.CAstUtils; @@ -93,6 +96,11 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.Reaction; import org.lflang.lf.Time; import org.lflang.lf.VarRef; +import org.lflang.sim.Event; +import org.lflang.sim.StateSpaceDiagram; +import org.lflang.sim.StateSpaceExplorer; +import org.lflang.sim.StateSpaceNode; +import org.lflang.sim.Tag; import org.lflang.util.StringUtil; import static org.lflang.ASTUtils.*; @@ -134,7 +142,18 @@ public class UclidGenerator extends GeneratorBase { /** Strings from the property attribute */ protected String name; protected String tactic; - protected String spec; + protected String spec; // SMTL + + /** + * The horizon (the total time interval required for evaluating + * an MTL property, which is derived from the MTL spec), + * the completeness threshold (CT) (the number of transitions + * required for evaluating the FOL spec in the trace), + * and the transpiled FOL spec. + */ + protected long horizon = 0; // in nanoseconds + protected String FOLSpec = ""; + protected int CT = 0; // Constructor public UclidGenerator(FileConfig fileConfig, ErrorReporter errorReporter, List properties) { @@ -155,6 +174,7 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // FIXME: Perform an analysis on the property and remove unrelevant components. // FIXME: Support multiple properties here too. We might need to have a UclidGenerator for each attribute. super.createMainInstantiation(); + //////////////////////////////////////// System.out.println("*** Start generating Uclid code."); @@ -170,7 +190,6 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // Create the src-gen directory setUpDirectories(); - // FIXME: Calculate the completeness threshold for each property. // Generate a Uclid model for each property. for (Attribute prop : this.properties) { this.name = StringUtil.removeQuotes( @@ -192,8 +211,9 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { .get() .getValue()); - int CT = computeCT(); - generateUclidFile(CT); + processMTLSpec(); + computeCT(); + generateUclidFile(); } // Generate runner script @@ -206,13 +226,13 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { /** * Generate the Uclid model. */ - protected void generateUclidFile(int CT) { + protected void generateUclidFile() { try { // Generate main.ucl and print to file code = new CodeBuilder(); String filename = this.outputDir .resolve(this.tactic + "_" + this.name + ".ucl").toString(); - generateUclidCode(CT); + generateUclidCode(); code.writeToFile(filename); } catch (IOException e) { Exceptions.sneakyThrow(e); @@ -253,7 +273,7 @@ protected void generateRunnerScript() { /** * The main function that generates Uclid code. */ - protected void generateUclidCode(int CT) { + protected void generateUclidCode() { code.pr(String.join("\n", "/*******************************", " * Auto-generated UCLID5 model *", @@ -267,7 +287,7 @@ protected void generateUclidCode(int CT) { generateTimingSemantics(); // Trace definition - generateTraceDefinition(CT); + generateTraceDefinition(); // Reaction IDs and state variables generateReactionIdsAndStateVars(); @@ -285,7 +305,7 @@ protected void generateUclidCode(int CT) { generateReactionAxioms(); // Properties - generateProperty(CT); + generateProperty(); // Control block generateControlBlock(); @@ -365,18 +385,18 @@ protected void generateTimingSemantics() { /** * Macros, type definitions, and variable declarations for trace (path) */ - protected void generateTraceDefinition(int CT) { + protected void generateTraceDefinition() { // Define various constants. code.pr(String.join("\n", "/********************", " * Trace Definition *", " *******************/", "const START : integer = 0;", - "const END : integer = " + String.valueOf(CT-1) + ";", + "const END : integer = " + String.valueOf(this.CT-1) + ";", "", "// trace length = k + CT", "const k : integer = 1; // 1-induction should be enough.", - "const CT : integer = " + String.valueOf(CT) + ";" + "// The completeness threshold", + "const CT : integer = " + String.valueOf(this.CT) + ";" + "// The completeness threshold", "\n" )); @@ -385,8 +405,8 @@ protected void generateTraceDefinition(int CT) { code.pr("group indices : integer = {"); code.indent(); String indices = ""; - for (int i = 0; i < CT; i++) { - indices += String.valueOf(i) + (i == CT-1? "" : ", "); + for (int i = 0; i < this.CT; i++) { + indices += String.valueOf(i) + (i == this.CT-1? "" : ", "); } code.pr(indices); code.unindent(); @@ -398,7 +418,7 @@ protected void generateTraceDefinition(int CT) { "type step_t = integer;", "type event_t = { rxn_t, tag_t, state_t, trigger_t, sched_t };", "", - "// Create a bounded trace with length " + String.valueOf(CT) + "// Create a bounded trace with length " + String.valueOf(this.CT) )); code.pr("// Potentially unbounded trace, we bound this later."); code.pr("type trace_t = [integer]event_t;"); @@ -1028,27 +1048,18 @@ protected void generateReactionAxioms() { } } - protected void generateProperty(int CT) { + protected void generateProperty() { code.pr(String.join("\n", "/************", " * Property *", " ************/" )); - - MTLLexer lexer = new MTLLexer(CharStreams.fromString(this.spec)); - CommonTokenStream tokens = new CommonTokenStream(lexer); - MTLParser parser = new MTLParser(tokens); - MtlContext mtlCtx = parser.mtl(); - MTLVisitor visitor = new MTLVisitor(this.tactic); - - // The visitor transpiles the MTL into a Uclid axiom. - String transpiled = visitor.visitMtl(mtlCtx, "i", 0, "0", 0); code.pr("// The FOL property translated from user-defined MTL property:"); code.pr("// " + this.spec); code.pr("define p(i : step_t) : boolean ="); code.indent(); - code.pr(transpiled + ";"); + code.pr(this.FOLSpec + ";"); code.unindent(); if (this.tactic.equals("bmc")) { @@ -1171,6 +1182,7 @@ private void populateLists(ReactorInstance reactor) { for (var timer : reactor.timers) { this.timerInstances.add(timer); } + // Recursion for (var child : reactor.children) { populateLists(child); @@ -1178,12 +1190,34 @@ private void populateLists(ReactorInstance reactor) { } /** - * Compute a completeness threadhold for each property. + * Compute a completeness threadhold for each property + * by simulating a worst-case execution by traversing + * the reactor instance graph and building a + * state space diagram. + */ + private void computeCT() { + + StateSpaceExplorer explorer = new StateSpaceExplorer(this.main); + explorer.explore(new Tag(this.horizon, 0, false), false); + StateSpaceDiagram diagram = explorer.diagram; + diagram.display(); + + this.CT = 10; + } + + /** + * Process an MTL property. */ - private int computeCT() { - // if (this.spec.equals("bmc")) { - // } - return 30; // FIXME + private void processMTLSpec() { + MTLLexer lexer = new MTLLexer(CharStreams.fromString(this.spec)); + CommonTokenStream tokens = new CommonTokenStream(lexer); + MTLParser parser = new MTLParser(tokens); + MtlContext mtlCtx = parser.mtl(); + MTLVisitor visitor = new MTLVisitor(this.tactic); + + // The visitor transpiles the MTL into a Uclid axiom. + this.FOLSpec = visitor.visitMtl(mtlCtx, "i", 0, "0", 0); + this.horizon = visitor.getHorizon(); } ///////////////////////////////////////////////// diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CBaseAstVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/CBaseAstVisitor.java index fa55848d70..210bf06537 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/CBaseAstVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/ast/CBaseAstVisitor.java @@ -13,41 +13,45 @@ public class CBaseAstVisitor extends AbstractAstVisitor implements CAstVis */ @Override public T visitAstNode(CAst.AstNode node) { - System.out.print("[visitAstNode] "); - System.out.println("Hi, I am " + node); + // System.out.print("[visitAstNode] "); + // System.out.println("Hi, I am " + node); return null; } @Override public T visitAstNodeUnary(CAst.AstNodeUnary node) { - System.out.print("[visitAstNodeUnary] "); - System.out.println("Hi, I am " + node); + // System.out.print("[visitAstNodeUnary] "); + // System.out.println("Hi, I am " + node); if (node.child != null) { T result = visit(node.child); } else { - System.out.println("*** Child is empty in " + node + "!"); + // System.out.println("*** Child is empty in " + node + "!"); } return null; } @Override public T visitAstNodeBinary(CAst.AstNodeBinary node) { - System.out.print("[visitAstNodeBinary] "); - System.out.println("Hi, I am " + node); + // System.out.print("[visitAstNodeBinary] "); + // System.out.println("Hi, I am " + node); if (node.left != null) { T leftResult = visit(node.left); - } else System.out.println("*** Left child is empty in " + node + "!"); + } else { + // System.out.println("*** Left child is empty in " + node + "!"); + } if (node.right != null) { T rightResult = visit(node.right); - } else System.out.println("*** Right child is empty in " + node + "!"); + } else { + // System.out.println("*** Right child is empty in " + node + "!"); + } // Aggregate results... return null; } @Override public T visitAstNodeDynamic(CAst.AstNodeDynamic node) { - System.out.print("[visitAstNodeDynamic] "); - System.out.println("Hi, I am " + node); + // System.out.print("[visitAstNodeDynamic] "); + // System.out.println("Hi, I am " + node); for (CAst.AstNode n : node.children) { T result = visit(n); } @@ -57,63 +61,63 @@ public T visitAstNodeDynamic(CAst.AstNodeDynamic node) { @Override public T visitAssignmentNode(CAst.AssignmentNode node) { // The default implementation reuses visitAstNodeBinary(node). - System.out.print("[visitAssignmentNode] "); + // System.out.print("[visitAssignmentNode] "); return visitAstNodeBinary(node); } @Override public T visitIfBlockNode(CAst.IfBlockNode node) { - System.out.print("[visitIfBlockNode] "); + // System.out.print("[visitIfBlockNode] "); return visitAstNodeBinary(node); } @Override public T visitIfBodyNode(CAst.IfBodyNode node) { - System.out.print("[visitIfBodyNode] "); + // System.out.print("[visitIfBodyNode] "); return visitAstNodeBinary(node); } @Override public T visitLiteralNode(CAst.LiteralNode node) { - System.out.print("[visitLiteralNode] "); - System.out.println("Hi, I am " + node + " with literal " + node.literal); + // System.out.print("[visitLiteralNode] "); + // System.out.println("Hi, I am " + node + " with literal " + node.literal); return null; } @Override public T visitLogicalNotNode(CAst.LogicalNotNode node) { - System.out.print("[visitLogicalNotNode] "); + // System.out.print("[visitLogicalNotNode] "); return visitAstNodeUnary(node); } @Override public T visitLogicalAndNode(CAst.LogicalAndNode node) { - System.out.print("[visitLogicalAndNode] "); + // System.out.print("[visitLogicalAndNode] "); return visitAstNodeBinary(node); } @Override public T visitLogicalOrNode(CAst.LogicalOrNode node) { - System.out.print("[visitLogicalOrNode] "); + // System.out.print("[visitLogicalOrNode] "); return visitAstNodeBinary(node); } @Override public T visitOpaqueNode(CAst.OpaqueNode node) { - System.out.print("[visitOpaqueNode] "); + // System.out.print("[visitOpaqueNode] "); return visitAstNode(node); } @Override public T visitStatementSequenceNode(CAst.StatementSequenceNode node) { - System.out.print("[visitStatementSequenceNode] "); + // System.out.print("[visitStatementSequenceNode] "); return visitAstNodeDynamic(node); } @Override public T visitVariableNode(CAst.VariableNode node) { - System.out.print("[visitVariableNode] "); - System.out.println("Hi, I am " + node + ": (" + node.type + ", " + node.name + ")"); + // System.out.print("[visitVariableNode] "); + // System.out.println("Hi, I am " + node + ": (" + node.type + ", " + node.name + ")"); return null; } @@ -122,28 +126,28 @@ public T visitVariableNode(CAst.VariableNode node) { @Override public T visitAdditionNode(CAst.AdditionNode node) { // The default implementation reuses visitAstNodeBinary(node). - System.out.print("[visitAdditionNode] "); + // System.out.print("[visitAdditionNode] "); return visitAstNodeBinary(node); } @Override public T visitSubtractionNode(CAst.SubtractionNode node) { // The default implementation reuses visitAstNodeBinary(node). - System.out.print("[visitSubtractionNode] "); + // System.out.print("[visitSubtractionNode] "); return visitAstNodeBinary(node); } @Override public T visitMultiplicationNode(CAst.MultiplicationNode node) { // The default implementation reuses visitAstNodeBinary(node). - System.out.print("[visitMultiplicationNode] "); + // System.out.print("[visitMultiplicationNode] "); return visitAstNodeBinary(node); } @Override public T visitDivisionNode(CAst.DivisionNode node) { // The default implementation reuses visitAstNodeBinary(node). - System.out.print("[visitDivisionNode] "); + // System.out.print("[visitDivisionNode] "); return visitAstNodeBinary(node); } @@ -152,42 +156,42 @@ public T visitDivisionNode(CAst.DivisionNode node) { @Override public T visitEqualNode(CAst.EqualNode node) { // The default implementation reuses visitAstNodeBinary(node). - System.out.print("[visitEqualNode] "); + // System.out.print("[visitEqualNode] "); return visitAstNodeBinary(node); } @Override public T visitNotEqualNode(CAst.NotEqualNode node) { // The default implementation reuses visitAstNodeBinary(node). - System.out.print("[visitNotEqualNode] "); + // System.out.print("[visitNotEqualNode] "); return visitAstNodeBinary(node); } @Override public T visitLessThanNode(CAst.LessThanNode node) { // The default implementation reuses visitAstNodeBinary(node). - System.out.print("[visitLessThanNode] "); + // System.out.print("[visitLessThanNode] "); return visitAstNodeBinary(node); } @Override public T visitLessEqualNode(CAst.LessEqualNode node) { // The default implementation reuses visitAstNodeBinary(node). - System.out.print("[visitLessEqualNode] "); + // System.out.print("[visitLessEqualNode] "); return visitAstNodeBinary(node); } @Override public T visitGreaterThanNode(CAst.GreaterThanNode node) { // The default implementation reuses visitAstNodeBinary(node). - System.out.print("[visitGreaterThanNode] "); + // System.out.print("[visitGreaterThanNode] "); return visitAstNodeBinary(node); } @Override public T visitGreaterEqualNode(CAst.GreaterEqualNode node) { // The default implementation reuses visitAstNodeBinary(node). - System.out.print("[visitGreaterEqualNode] "); + // System.out.print("[visitGreaterEqualNode] "); return visitAstNodeBinary(node); } @@ -195,34 +199,34 @@ public T visitGreaterEqualNode(CAst.GreaterEqualNode node) { @Override public T visitSetPortNode(CAst.SetPortNode node) { - System.out.print("[visitSetPortNode] "); + // System.out.print("[visitSetPortNode] "); return visitAstNodeBinary(node); } @Override public T visitScheduleActionNode(CAst.ScheduleActionNode node) { - System.out.print("[visitScheduleActionNode] "); + // System.out.print("[visitScheduleActionNode] "); return visitAstNodeDynamic(node); } @Override public T visitStateVarNode(CAst.StateVarNode node) { - System.out.print("[visitStateVarNode] "); - System.out.println("Hi, I am " + node + ": (" + node.name + ")"); + // System.out.print("[visitStateVarNode] "); + // System.out.println("Hi, I am " + node + ": (" + node.name + ")"); return null; } @Override public T visitTriggerValueNode(CAst.TriggerValueNode node) { - System.out.print("[visitTriggerValueNode] "); - System.out.println("Hi, I am " + node + ": (" + node.name + ")"); + // System.out.print("[visitTriggerValueNode] "); + // System.out.println("Hi, I am " + node + ": (" + node.name + ")"); return null; } @Override public T visitTriggerIsPresentNode(CAst.TriggerIsPresentNode node) { - System.out.print("[visitTriggerIsPresentNode] "); - System.out.println("Hi, I am " + node + ": (" + node.name + ")"); + // System.out.print("[visitTriggerIsPresentNode] "); + // System.out.println("Hi, I am " + node + ": (" + node.name + ")"); return null; } diff --git a/org.lflang/src/org/lflang/sim/Event.java b/org.lflang/src/org/lflang/sim/Event.java new file mode 100644 index 0000000000..6f48d23c24 --- /dev/null +++ b/org.lflang/src/org/lflang/sim/Event.java @@ -0,0 +1,37 @@ +/** + * A node in the state space diagram representing a step + * in the execution of an LF program. + * + * @author{Shaokai Lin } + */ +package org.lflang.sim; + +import org.lflang.generator.TriggerInstance; + +public class Event implements Comparable { + + public TriggerInstance trigger; + public Tag tag; + + public Event(TriggerInstance trigger, Tag tag) { + this.trigger = trigger; + this.tag = tag; + } + + @Override + public int compareTo(Event e) { + return this.tag.compareTo(e.tag); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof Event) { + Event e = (Event) o; + if (this.trigger.equals(e.trigger) + && this.tag.equals(e.tag)) + return true; + } + return false; + } +} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/sim/StateSpaceDiagram.java b/org.lflang/src/org/lflang/sim/StateSpaceDiagram.java new file mode 100644 index 0000000000..2ca5b4caef --- /dev/null +++ b/org.lflang/src/org/lflang/sim/StateSpaceDiagram.java @@ -0,0 +1,49 @@ +/** + * A directed graph representing the state space of an LF program. + * + * @author{Shaokai Lin } + */ +package org.lflang.sim; + +import java.util.Set; + +import org.lflang.graph.DirectedGraph; + +public class StateSpaceDiagram extends DirectedGraph { + + /** + * The first node of the state space diagram. + */ + public StateSpaceNode head; + + /** + * The last node of the state space diagram. + */ + public StateSpaceNode tail; + + /** + * The previously encountered node which the tail node + * goes back to, i.e. the location where the back loop happens. + */ + public StateSpaceNode loopNode; + + /** + * Pretty print the diagram. + */ + public void display() { + System.out.println("Pretty printing state space diagram:"); + StateSpaceNode node = this.head; + int count = 0; + while (node != null) { + System.out.print("State " + count++ + ": "); + node.display(); + if (!node.equals(this.tail)) { + // Assume a unique next state. + Set downstream = this.getDownstreamAdjacentNodes(node); + if (downstream == null || downstream.size() == 0) break; + node = (StateSpaceNode)downstream.toArray()[0]; + } + } + } + +} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/sim/StateSpaceExplorer.java b/org.lflang/src/org/lflang/sim/StateSpaceExplorer.java new file mode 100644 index 0000000000..59a4026d39 --- /dev/null +++ b/org.lflang/src/org/lflang/sim/StateSpaceExplorer.java @@ -0,0 +1,271 @@ +/** + * Explores the state space of an LF program. + */ +package org.lflang.sim; + +import java.util.ArrayList; +import java.util.PriorityQueue; +import java.util.Set; + +import org.lflang.TimeUnit; +import org.lflang.TimeValue; + +import org.lflang.generator.ActionInstance; +import org.lflang.generator.PortInstance; +import org.lflang.generator.ReactionInstance; +import org.lflang.generator.ReactorInstance; +import org.lflang.generator.RuntimeRange; +import org.lflang.generator.SendRange; +import org.lflang.generator.TimerInstance; +import org.lflang.generator.TriggerInstance; + +import org.lflang.lf.Expression; +import org.lflang.lf.Time; +import org.lflang.lf.Variable; + +public class StateSpaceExplorer { + + // Instantiate an empty state space diagram. + public StateSpaceDiagram diagram = new StateSpaceDiagram(); + + // Indicate whether a back loop is found in the state space. + // A back loop suggests periodic behavior. + public boolean loopFound = false; + + /** + * Instantiate a global event queue. + * We will use this event queue to symbolically simulate + * the logical timeline. This simulation is also valid + * for runtime implementations that are federated or relax + * global barrier synchronization, since an LF program + * defines a unique logical timeline (assuming all reactions + * behave _consistently_ throughout the execution). + */ + public PriorityQueue eventQ = new PriorityQueue(); + + /** + * The main reactor instance based on which the state space + * is explored. + */ + public ReactorInstance main; + + // Constructor + public StateSpaceExplorer(ReactorInstance main) { + this.main = main; + } + + /** + * Recursively add the first events to the event queue. + */ + public void addInitialEvents(ReactorInstance reactor) { + // Add the startup trigger, if exists. + var startup = reactor.getStartupTrigger(); + if (startup != null) { + eventQ.add(new Event(startup, new Tag(0, 0, false))); + } + + // Add the initial timer firings, if exist. + for (TimerInstance timer : reactor.timers) { + eventQ.add( + new Event( + timer, + new Tag(timer.getOffset().toNanoSeconds(), 0, false) + ) + ); + } + + // Recursion + for (var child : reactor.children) { + addInitialEvents(child); + } + } + + /** + * Explore the state space and populate the state space diagram + * until the specified horizon (i.e. the end tag) is reached + * OR until the event queue is empty. + * + * As an optimization, if findLoop is true, the algorithm + * tries to find a loop in the state space during exploration. + * If a loop is found (i.e. a previously encountered state + * is reached again) during exploration, the function returns early. + * + * TODOs: + * 1. Handle action with 0 min delay. + * 2. Check if zero-delay connection works. + */ + public void explore(Tag horizon, boolean findLoop) { + // Traverse the main reactor instance recursively to find + // the known initial events (startup and timers' first firings). + // FIXME: It seems that we need to handle shutdown triggers + // separately, because they could break the back loop. + addInitialEvents(this.main); + System.out.println(this.eventQ); + + Tag previous_tag = null; // Tag in the previous loop ITERATION + Tag current_tag = null; // Tag in the current loop ITERATION + StateSpaceNode current_node = null; + boolean stop = true; + if (this.eventQ.size() > 0) { + stop = false; + current_tag = (eventQ.peek()).tag; + // System.out.println(current_tag); + } + + // A list of reactions invoked at the current logical tag + ArrayList reactions_invoked; + // A temporary list of reactions processed in the current LOOP ITERATION + ArrayList reactions_temp; + + while (!stop) { + // Pop the events from the earliest tag off the event queue. + ArrayList current_events = new ArrayList(); + // FIXME: Use stream methods here? + while (eventQ.size() > 0 && eventQ.peek().tag.compareTo(current_tag) == 0) { + Event e = eventQ.poll(); + current_events.add(e); + // System.out.println("Adding event to current_events: " + e); + } + System.out.println(current_events); + + // Collect all the reactions invoked in this current LOOP ITERATION. + reactions_temp = new ArrayList(); + for (Event e : current_events) { + Set dependent_reactions + = e.trigger.getDependentReactions(); + // System.out.println("Dependent reactions:"); + // for (ReactionInstance reaction : dependent_reactions) + // System.out.println(reaction); + // System.out.println(dependent_reactions); + reactions_temp.addAll(dependent_reactions); + } + + // For each reaction invoked, compute the new events produced. + for (ReactionInstance reaction : reactions_temp) { + // Iterate over all the effects produced by this reaction. + // If the effect is a port, obtain the downstream port along + // a connection and enqueue a future event for that port. + // If the effect is an action, enqueue a future event for + // this action. + for (TriggerInstance effect : reaction.effects) { + if (effect instanceof PortInstance) { + + // System.out.println("Effect: " + effect); + // System.out.print("Eventual destinations: "); + // System.out.println(((PortInstance)effect).getDependentPorts()); + + for (SendRange senderRange + : ((PortInstance)effect).getDependentPorts()) { + + // System.out.print("Sender range: "); + // System.out.println(senderRange.destinations); + + for (RuntimeRange destinationRange + : senderRange.destinations) { + PortInstance downstreamPort = destinationRange.instance; + // System.out.println("Located a destination port: " + downstreamPort); + + // Getting delay from connection + // FIXME: Is there a more concise way to do this? + long delay = 0; + Expression delayExpr = senderRange.connection.getDelay(); + if (delayExpr instanceof Time) { + long interval = ((Time) delayExpr).getInterval(); + String unit = ((Time) delayExpr).getUnit(); + TimeValue timeValue = new TimeValue(interval, TimeUnit.fromName(unit)); + delay = timeValue.toNanoSeconds(); + } + + // Create and enqueue a new event. + Event e = new Event( + downstreamPort, + new Tag(current_tag.timestamp + delay, 0, false) + ); + eventQ.add(e); + } + } + } + else if (effect instanceof ActionInstance) { + // Get the minimum delay of this action. + long min_delay = ((ActionInstance)effect).getMinDelay().toNanoSeconds(); + // Create and enqueue a new event. + Event e = new Event( + effect, + new Tag(current_tag.timestamp + min_delay, 0, false) + ); + eventQ.add(e); + } + } + } + + // When we first advance to a new tag, create a new node in the state space diagram. + if ( + previous_tag == null // The first iteration + || current_tag.compareTo(previous_tag) > 0 + ) { + // Copy the reactions in reactions_temp. + reactions_invoked = new ArrayList(reactions_temp); + + // Create a new state in the SSD for the current tag, + // add the reactions triggered to the state, + // and add a snapshot of the event queue (with new events + // generated by reaction invocations in the curren tag) + // to the state. + StateSpaceNode node = new StateSpaceNode( + current_tag, // Current tag + reactions_invoked, // Reactions invoked at this tag + new ArrayList(eventQ) // A snapshot of the event queue + ); + + // If findLoop is true, check for loops. + if (findLoop && diagram.hasNode(node)) { + loopFound = true; + // Mark the loop in the diagram. + // FIXME: Get the existing (loop) node in the graph by content matching. + // this.diagram.loopNode = ...; + // this.diagram.tail = current_node; + // this.diagram.addEdge(this.diagram.loopNode, this.diagram.tail); + break; // Exit the while loop early. + } + + // Add the new node to the state space diagram. + this.diagram.addNode(node); + System.out.println("Adding a new node to the diagram."); + node.display(); + + // If the head is not empty, add an edge from the previous state + // to the next state. Otherwise initialize the head to the new node. + if (this.diagram.head != null && current_node != null) { + // System.out.println("--- Add a new edge between " + current_node + " and " + node); + this.diagram.addEdge(node, current_node); // Sink first, then source + } + else + this.diagram.head = node; // Initialize the head. + + // Update the current node. + current_node = node; + } + // Time does not advance because we are processing + // connections with zero delay. + else { + // Add reactions explored in the current loop iteration + // to the existing state space node. + current_node.reactions_invoked.addAll(reactions_temp); + } + + // Update the current tag for the next iteration. + if (eventQ.size() > 0) { + previous_tag = current_tag; + current_tag = eventQ.peek().tag; + } + + // Stop if: + // 1. the event queue is empty, or + // 2. the horizon is reached. + if (eventQ.size() == 0 + || current_tag.compareTo(horizon) > 0) + stop = true; + } + return; + } +} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/sim/StateSpaceNode.java b/org.lflang/src/org/lflang/sim/StateSpaceNode.java new file mode 100644 index 0000000000..68955b3b39 --- /dev/null +++ b/org.lflang/src/org/lflang/sim/StateSpaceNode.java @@ -0,0 +1,46 @@ +/** + * A node in the state space diagram representing a step + * in the execution of an LF program. + * + * @author{Shaokai Lin } + */ +package org.lflang.sim; + +import java.util.ArrayList; + +import org.lflang.generator.ReactionInstance; + +public class StateSpaceNode { + + public Tag tag; + public ArrayList reactions_invoked; + public ArrayList eventQ; + + public StateSpaceNode( + Tag tag, + ArrayList reactions_invoked, + ArrayList eventQ + ) { + this.tag = tag; + this.reactions_invoked = reactions_invoked; + this.eventQ = eventQ; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof StateSpaceNode) { + StateSpaceNode node = (StateSpaceNode) o; + if (this.tag.equals(node.tag) + && this.reactions_invoked.equals(node.reactions_invoked) + && this.eventQ.equals(node.eventQ)) + return true; + } + return false; + } + + public void display() { + System.out.println("(" + tag + ", " + reactions_invoked + ", " + eventQ + ")"); + } + +} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/sim/Tag.java b/org.lflang/src/org/lflang/sim/Tag.java new file mode 100644 index 0000000000..648acc9b70 --- /dev/null +++ b/org.lflang/src/org/lflang/sim/Tag.java @@ -0,0 +1,62 @@ +/** + * Class representing a logical time tag, + * which is a pair that consists of a + * timestamp (type long) and a microstep (type long). + * + * @author{Shaokai Lin } + */ +package org.lflang.sim; + +public class Tag implements Comparable { + + public long timestamp; + public long microstep; + public boolean forever; // Whether the tag is FOREVER into the future. + + public Tag(long timestamp, long microstep, boolean forever) { + this.timestamp = timestamp; + this.microstep = microstep; + this.forever = forever; + } + + @Override + public int compareTo(Tag t) { + // If one tag is forever, and the other is not, + // then forever tag is later. If both tags are + // forever, then they are equal. + if (this.forever && !t.forever) return 1; + else if (!this.forever && t.forever) return -1; + else if (this.forever && t.forever) return 0; + + // Compare the timestamps if they are not equal. + if (this.timestamp != t.timestamp) { + if (this.timestamp > t.timestamp) return 1; + else if (this.timestamp < t.timestamp) return -1; + else return 0; + } + + // Otherwise, compare the microsteps. + if (this.microstep > t.microstep) return 1; + else if (this.microstep < t.microstep) return -1; + else return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof Tag) { + Tag t = (Tag) o; + if (this.timestamp == t.timestamp + && this.microstep == t.microstep + && this.forever == t.forever) + return true; + } + return false; + } + + @Override + public String toString() { + if (this.forever) return "(FOREVER, " + this.microstep + ")"; + else return "(" + this.timestamp + ", " + this.microstep + ")"; + } +} \ No newline at end of file From 80f7c090d7d0754a8607cd9f7cc9c455bacb1629 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 30 Nov 2022 23:17:17 -0800 Subject: [PATCH 059/516] Detect back loop in the state space diagram. --- .../generator/uclid/UclidGenerator.java | 31 +++++++++++++- org.lflang/src/org/lflang/sim/Event.java | 9 ++-- .../src/org/lflang/sim/StateSpaceDiagram.java | 29 +++++++++++-- .../org/lflang/sim/StateSpaceExplorer.java | 42 ++++++++++++++----- .../src/org/lflang/sim/StateSpaceNode.java | 14 ++++--- 5 files changed, 102 insertions(+), 23 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 3dec037d8f..d9eb1c23f1 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -1198,11 +1198,38 @@ private void populateLists(ReactorInstance reactor) { private void computeCT() { StateSpaceExplorer explorer = new StateSpaceExplorer(this.main); - explorer.explore(new Tag(this.horizon, 0, false), false); + explorer.explore( + new Tag(this.horizon, 0, false), + true // findLoop + ); StateSpaceDiagram diagram = explorer.diagram; diagram.display(); - this.CT = 10; + if (!explorer.loopFound) { + this.CT = diagram.length; + System.out.println("*** A loop is NOT found."); + System.out.println("CT: " + this.CT); + } + // Over-approximate CT by estimating the number of loop iterations required. + else { + // Subtract the non-periodic logical time + // interval from the total horizon. + long horizonRemained = + this.horizon - diagram.loopNode.tag.timestamp; + + // Check how many loop iteration is required + // to check the remaining horizon. + int loopIterations = (int) Math.ceil( + (double) horizonRemained / diagram.loopPeriod); + + // CT = steps required for the non-periodic part + // + steps required for the periodic part + this.CT = (diagram.loopNode.index + 1) + + (diagram.tail.index - diagram.loopNode.index + 1) * loopIterations; + + System.out.println("*** A loop is found!"); + System.out.println("CT: " + this.CT); + } } /** diff --git a/org.lflang/src/org/lflang/sim/Event.java b/org.lflang/src/org/lflang/sim/Event.java index 6f48d23c24..ff96b4d3c2 100644 --- a/org.lflang/src/org/lflang/sim/Event.java +++ b/org.lflang/src/org/lflang/sim/Event.java @@ -23,13 +23,16 @@ public int compareTo(Event e) { return this.tag.compareTo(e.tag); } + /** + * This equals() method does NOT compare tags, + * only compares triggers. + */ @Override public boolean equals(Object o) { - if (this == o) return true; + if (o == null) return false; if (o instanceof Event) { Event e = (Event) o; - if (this.trigger.equals(e.trigger) - && this.tag.equals(e.tag)) + if (this.trigger.equals(e.trigger)) return true; } return false; diff --git a/org.lflang/src/org/lflang/sim/StateSpaceDiagram.java b/org.lflang/src/org/lflang/sim/StateSpaceDiagram.java index 2ca5b4caef..02bc33cf78 100644 --- a/org.lflang/src/org/lflang/sim/StateSpaceDiagram.java +++ b/org.lflang/src/org/lflang/sim/StateSpaceDiagram.java @@ -27,15 +27,36 @@ public class StateSpaceDiagram extends DirectedGraph { */ public StateSpaceNode loopNode; + /** + * The logical time elapsed for each loop iteration. + */ + public long loopPeriod; + + /** + * The length of the state space diagram (not counting the loop) + */ + public int length; + + /** + * Before adding the node, assign it an index. + */ + @Override + public void addNode(StateSpaceNode node) { + node.index = this.length; + this.length++; + super.addNode(node); + } + /** * Pretty print the diagram. */ public void display() { - System.out.println("Pretty printing state space diagram:"); + System.out.println("*************************************************"); + System.out.println("* Pretty printing worst-case state space diagram:"); StateSpaceNode node = this.head; - int count = 0; while (node != null) { - System.out.print("State " + count++ + ": "); + System.out.print("* "); + System.out.print("State " + node.index + ": "); node.display(); if (!node.equals(this.tail)) { // Assume a unique next state. @@ -43,7 +64,9 @@ public void display() { if (downstream == null || downstream.size() == 0) break; node = (StateSpaceNode)downstream.toArray()[0]; } + else break; } + System.out.println("*************************************************"); } } \ No newline at end of file diff --git a/org.lflang/src/org/lflang/sim/StateSpaceExplorer.java b/org.lflang/src/org/lflang/sim/StateSpaceExplorer.java index 59a4026d39..70a370f535 100644 --- a/org.lflang/src/org/lflang/sim/StateSpaceExplorer.java +++ b/org.lflang/src/org/lflang/sim/StateSpaceExplorer.java @@ -128,7 +128,8 @@ public void explore(Tag horizon, boolean findLoop) { } System.out.println(current_events); - // Collect all the reactions invoked in this current LOOP ITERATION. + // Collect all the reactions invoked in this current LOOP ITERATION + // triggered by the earliest events. reactions_temp = new ArrayList(); for (Event e : current_events) { Set dependent_reactions @@ -138,6 +139,19 @@ public void explore(Tag horizon, boolean findLoop) { // System.out.println(reaction); // System.out.println(dependent_reactions); reactions_temp.addAll(dependent_reactions); + + // If the event is a timer firing, enqueue the next firing. + if (e.trigger instanceof TimerInstance) { + TimerInstance timer = (TimerInstance) e.trigger; + eventQ.add(new Event( + timer, + new Tag( + current_tag.timestamp + timer.getPeriod().toNanoSeconds(), + 0, // A time advancement resets microstep to 0. + false + )) + ); + } } // For each reaction invoked, compute the new events produced. @@ -218,15 +232,23 @@ else if (effect instanceof ActionInstance) { ); // If findLoop is true, check for loops. - if (findLoop && diagram.hasNode(node)) { - loopFound = true; - // Mark the loop in the diagram. - // FIXME: Get the existing (loop) node in the graph by content matching. - // this.diagram.loopNode = ...; - // this.diagram.tail = current_node; - // this.diagram.addEdge(this.diagram.loopNode, this.diagram.tail); - break; // Exit the while loop early. - } + // FIXME: For some reason, the below doesn't work. + // if (findLoop && diagram.hasNode(node)) { + if (findLoop) { + for (StateSpaceNode n : diagram.nodes()) { + if (n.equals(node)) { + loopFound = true; + System.out.println("*** A loop is found!"); + // Mark the loop in the diagram. + this.diagram.loopNode = n; + this.diagram.tail = current_node; + this.diagram.loopPeriod = current_tag.timestamp + - this.diagram.loopNode.tag.timestamp; + this.diagram.addEdge(this.diagram.loopNode, this.diagram.tail); + return; // Exit the while loop early. + } + } + } // Add the new node to the state space diagram. this.diagram.addNode(node); diff --git a/org.lflang/src/org/lflang/sim/StateSpaceNode.java b/org.lflang/src/org/lflang/sim/StateSpaceNode.java index 68955b3b39..1bf59f7ed6 100644 --- a/org.lflang/src/org/lflang/sim/StateSpaceNode.java +++ b/org.lflang/src/org/lflang/sim/StateSpaceNode.java @@ -12,6 +12,7 @@ public class StateSpaceNode { + public int index; public Tag tag; public ArrayList reactions_invoked; public ArrayList eventQ; @@ -21,18 +22,21 @@ public StateSpaceNode( ArrayList reactions_invoked, ArrayList eventQ ) { - this.tag = tag; - this.reactions_invoked = reactions_invoked; + this.tag = tag; this.eventQ = eventQ; + this.reactions_invoked = reactions_invoked; } + /** + * This equals method does NOT compare tags, + * only compares reactions_invoked and eventQ. + */ @Override public boolean equals(Object o) { - if (this == o) return true; + if (o == null) return false; if (o instanceof StateSpaceNode) { StateSpaceNode node = (StateSpaceNode) o; - if (this.tag.equals(node.tag) - && this.reactions_invoked.equals(node.reactions_invoked) + if (this.reactions_invoked.equals(node.reactions_invoked) && this.eventQ.equals(node.eventQ)) return true; } From b69209e50899b45d5211560b3144a0053bef1b74 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 1 Dec 2022 17:43:22 -0800 Subject: [PATCH 060/516] Add trace padding to account for partial trace --- .../generator/uclid/UclidGenerator.java | 61 ++++++++++++------- .../generator/uclid/ast/CToUclidVisitor.java | 2 +- .../uclid/ast/VariablePrecedenceVisitor.java | 2 +- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index d9eb1c23f1..0f98462eb9 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -386,17 +386,36 @@ protected void generateTimingSemantics() { * Macros, type definitions, and variable declarations for trace (path) */ protected void generateTraceDefinition() { + //// Why do we have a padding at the end of the trace? + // + // To handle bounded traces for a potentially unbounded execution + // (due to, for example, timers or systems forming a loop), + // we need to let certain axioms "terminate" the execution and + // put any "spilled-over" states in the trace padding. + // + // For example, an axiom could say "if a reaction is triggered + // at step i, it schedules an action that appears 1 sec later + // in some future step." Assuming our completeness threshold is 10, + // this axiom can block the model (i.e. returns TRIVIALLY true) + // at step 10 because there are not any more steps in the bounded trace. + // To avoid this, a padding of the size of the number of reactions + // is added to the trace. In addition, an antecedent of the form + // "i >= START && i <= END" is added to all the axioms. The padding + // will store all the spilled-over states in order to prevent + // model blocking. + int traceEndIndex = this.CT + this.reactionInstances.size(); + // Define various constants. code.pr(String.join("\n", "/********************", " * Trace Definition *", " *******************/", - "const START : integer = 0;", - "const END : integer = " + String.valueOf(this.CT-1) + ";", - "", - "// trace length = k + CT", - "const k : integer = 1; // 1-induction should be enough.", - "const CT : integer = " + String.valueOf(this.CT) + ";" + "// The completeness threshold", + "const START : integer = 0; // The start index of the trace.", + "const END : integer = " + String.valueOf(this.CT) + + "; // The end index of the trace (without padding)", + "const END_TRACE : integer = " + + String.valueOf(traceEndIndex) + + "; // The end index of the trace with padding", "\n" )); @@ -405,8 +424,8 @@ protected void generateTraceDefinition() { code.pr("group indices : integer = {"); code.indent(); String indices = ""; - for (int i = 0; i < this.CT; i++) { - indices += String.valueOf(i) + (i == this.CT-1? "" : ", "); + for (int i = 0; i <= traceEndIndex; i++) { + indices += String.valueOf(i) + (i == traceEndIndex ? "" : ", "); } code.pr(indices); code.unindent(); @@ -467,7 +486,7 @@ protected void generateTraceDefinition() { } code.pr("// Helper macro that returns an element based on index."); code.pr("define get(tr : trace_t, i : step_t) : event_t ="); - code.pr("if (i >= START || i <= END) then tr[i] else"); + code.pr("if (i >= START || i <= END_TRACE) then tr[i] else"); code.pr("{ NULL, inf(), { " + initialStates + " }, { " + initialTriggerPresence + " }, {" + initialActionsScheduled + "} };"); // Define an event getter from the trace. @@ -602,12 +621,12 @@ protected void generateReactorSemantics() { "// based on previous states.", "", "// Events are ordered by \"happened-before\" relation.", - "axiom(finite_forall (i : integer) in indices :: (finite_forall (j : integer) in indices ::", - " hb(elem(i), elem(j)) ==> i < j));", + "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> (finite_forall (j : integer) in indices ::", + " (j >= START && j <= END) ==> (hb(elem(i), elem(j)) ==> i < j)));", "", "// the same event can only trigger once in a logical instant", - "axiom(finite_forall (i : integer) in indices :: (finite_forall (j : integer) in indices ::", - " ((rxn(i) == rxn(j) && i != j)", + "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> (finite_forall (j : integer) in indices ::", + " (j >= START && j <= END) ==> ((rxn(i) == rxn(j) && i != j)", " ==> !tag_same(g(i), g(j)))));", "", "// Tags should be non-negative.", @@ -915,16 +934,16 @@ protected void generateReactionAxioms() { // Traverse and print. CBaseAstVisitor baseVisitor = new CBaseAstVisitor<>(); // For pretty printing. - System.out.println("***** Printing the original AST."); + // System.out.println("***** Printing the original AST."); baseVisitor.visit(ast); // Convert the AST to If Normal Form (INF). IfNormalFormAstVisitor infVisitor = new IfNormalFormAstVisitor(); - System.out.println("***** Convert to If Normal Form."); + // System.out.println("***** Convert to If Normal Form."); infVisitor.visit(ast, new ArrayList()); CAst.StatementSequenceNode inf = infVisitor.INF; - System.out.println(inf); - System.out.println("***** Printing the AST in If Normal Form."); + // System.out.println(inf); + // System.out.println("***** Printing the AST in If Normal Form."); baseVisitor.visit(inf); // For the variables that are used, extract the conditions @@ -962,12 +981,12 @@ protected void generateReactionAxioms() { resetConditions.put(instance, new ArrayList()); } resetConditions.get(instance).add(ifBlockNode.left); - System.out.println("!!! Added another reset condition: " + ifBlockNode.left); + // System.out.println("!!! Added another reset condition: " + ifBlockNode.left); } // Generate Uclid axiom for the C AST. CToUclidVisitor c2uVisitor = new CToUclidVisitor(this, reaction); - System.out.println("***** Generating axioms from AST."); + // System.out.println("***** Generating axioms from AST."); String axiom = c2uVisitor.visit(inf); code.pr(String.join("\n", "// Reaction body of " + reaction, @@ -979,11 +998,11 @@ protected void generateReactionAxioms() { )); for (NamedInstance key : resetConditions.keySet()) { CAst.AstNode disjunction = CAstUtils.takeDisjunction(resetConditions.get(key)); - System.out.println("!!! Reset conditions: " + resetConditions.get(key)); + // System.out.println("!!! Reset conditions: " + resetConditions.get(key)); CAst.LogicalNotNode notNode = new CAst.LogicalNotNode(); notNode.child = disjunction; String resetCondition = c2uVisitor.visit(notNode); - System.out.println("!!! Str: " + resetCondition); + // System.out.println("!!! Str: " + resetCondition); code.pr("&& " + "(" + "(" + resetCondition + ")" + " ==> " + "("); if (key instanceof StateVariableInstance) { StateVariableInstance n = (StateVariableInstance)key; diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java index 457a8795c8..b0bb4e9230 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java @@ -168,7 +168,7 @@ public String visitScheduleActionNode(ScheduleActionNode node) { ActionInstance action = (ActionInstance)instance; String additionalDelay = visit(node.children.get(1)); // FIXME: Suppport this. String str = "\n(" - + "(finite_exists (" + this.qv2 + " : integer) in indices :: (" + this.qv2 + " > " + this.qv + " && " + this.qv2 + " <= END) && (" + + "(finite_exists (" + this.qv2 + " : integer) in indices :: (" + this.qv2 + " > " + this.qv + " && " + this.qv2 + " <= END_TRACE) && (" + "\n " + action.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv2 + ")" + ")" + "\n " + "&& " + "tag_same" + "(" + "g(" + this.qv2 + ")" + ", " + "tag_schedule" + "(" + "g" + "(" + this.qv + ")" + ", " + action.getMinDelay().toNanoSeconds() + ")" + ")" + "\n " + "&& " + action.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv2 + ")" + ")" + " == " + "0" diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/VariablePrecedenceVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/VariablePrecedenceVisitor.java index 9ae7b9447a..104a871d78 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/VariablePrecedenceVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/ast/VariablePrecedenceVisitor.java @@ -14,7 +14,7 @@ public class VariablePrecedenceVisitor extends CBaseAstVisitor { // e.g., self->s = (self->s + 1) - (2 * 2). @Override public Void visitAssignmentNode(AssignmentNode node) { - System.out.println("******* In assignment!!!"); + // System.out.println("******* In assignment!!!"); if (node.left instanceof StateVarNode) { if (node.right instanceof AstNodeBinary) { AstNodeBinary n = (AstNodeBinary)node.right; From 5baf0c789ae3c143657a3f7ee86138e2ac82c0c9 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sat, 3 Dec 2022 00:37:00 -0800 Subject: [PATCH 061/516] Add UclidRunner that runs uclid models and parses CEX traces --- build.gradle | 5 + .../src/org/lflang/generator/LFGenerator.java | 4 + .../org/lflang/generator/uclid/StateInfo.java | 29 +++ .../generator/uclid/UclidGenerator.java | 58 +++--- .../lflang/generator/uclid/UclidRunner.java | 170 ++++++++++++++++++ 5 files changed, 240 insertions(+), 26 deletions(-) create mode 100644 org.lflang/src/org/lflang/generator/uclid/StateInfo.java create mode 100644 org.lflang/src/org/lflang/generator/uclid/UclidRunner.java diff --git a/build.gradle b/build.gradle index c011eb2c8c..3dda3f15f3 100644 --- a/build.gradle +++ b/build.gradle @@ -72,6 +72,11 @@ subprojects { antlr4 'org.antlr:antlr4:4.7.2' implementation 'org.antlr:antlr4-runtime:4.7.2' } + + // JSON + dependencies { + implementation 'org.json:json:20200518' + } } // Our CI uses --tests filters, which fails if some diff --git a/org.lflang/src/org/lflang/generator/LFGenerator.java b/org.lflang/src/org/lflang/generator/LFGenerator.java index cba9f39188..8be0731a62 100644 --- a/org.lflang/src/org/lflang/generator/LFGenerator.java +++ b/org.lflang/src/org/lflang/generator/LFGenerator.java @@ -23,6 +23,7 @@ import org.lflang.generator.c.CGenerator; import org.lflang.generator.python.PythonGenerator; import org.lflang.generator.uclid.UclidGenerator; +import org.lflang.generator.uclid.UclidRunner; import org.lflang.lf.Attribute; import org.lflang.lf.Reactor; import org.lflang.scoping.LFGlobalScopeProvider; @@ -180,7 +181,10 @@ public void doGenerate(Resource resource, IFileSystemAccess2 fsa, .collect(Collectors.toList()); if (properties.size() > 0) { UclidGenerator uclidGenerator = new UclidGenerator(fileConfig, errorReporter, properties); + // Generate uclid files. uclidGenerator.doGenerate(resource, lfContext); + // Invoke the generated uclid files. + uclidGenerator.runner.run(); } // Generate target code from the LF program. diff --git a/org.lflang/src/org/lflang/generator/uclid/StateInfo.java b/org.lflang/src/org/lflang/generator/uclid/StateInfo.java new file mode 100644 index 0000000000..0f2f4f3008 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/uclid/StateInfo.java @@ -0,0 +1,29 @@ +/** + * A class that represents information in a step + * in a counterexample trace + * + * @author{Shaokai Lin } + */ +package org.lflang.generator.uclid; + +import java.util.ArrayList; +import java.util.HashMap; + +import org.lflang.sim.Tag; + +public class StateInfo { + + public ArrayList reactions = new ArrayList<>(); + public Tag tag; + public HashMap variables = new HashMap<>(); + public HashMap triggers = new HashMap<>(); + public HashMap scheduled = new HashMap<>(); + + public void display() { + System.out.println("reactions: " + reactions); + System.out.println("tag: " + tag); + System.out.println("variables: " + variables); + System.out.println("triggers: " + triggers); + System.out.println("scheduled: " + scheduled); + } +} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 0f98462eb9..e241374279 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -110,39 +110,45 @@ public class UclidGenerator extends GeneratorBase { //////////////////////////////////////////// //// Public fields // Data structures for storing info about the runtime instances. - public List reactorInstances = new ArrayList(); + public List reactorInstances = new ArrayList(); public List reactionInstances = new ArrayList(); // State variables in the system - public List stateVariables = new ArrayList(); + public List stateVariables = new ArrayList(); // Triggers in the system - public List actionInstances = new ArrayList(); - public List inputInstances = new ArrayList(); - public List outputInstances = new ArrayList(); - public List portInstances = new ArrayList(); - public List timerInstances = new ArrayList(); + public List actionInstances = new ArrayList(); + public List inputInstances = new ArrayList(); + public List outputInstances = new ArrayList(); + public List portInstances = new ArrayList(); + public List timerInstances = new ArrayList(); // Joint lists of the lists above. - public List triggerInstances; // Triggers = ports + actions + timers - public List namedInstances; // Named instances = triggers + state variables + public List triggerInstances; // Triggers = ports + actions + timers + public List namedInstances; // Named instances = triggers + state variables + + // A list of paths to the uclid files generated + public List generatedFiles = new ArrayList(); + + // The directory where the generated files are placed + public Path outputDir; + + // A runner for the generated Uclid files + public UclidRunner runner; //////////////////////////////////////////// //// Protected fields // A list of MTL properties represented in Attributes. - protected List properties; - - // The directory where the generated files are placed - protected Path outputDir; + protected List properties; /** The main place to put generated code. */ - protected CodeBuilder code = new CodeBuilder(); + protected CodeBuilder code = new CodeBuilder(); /** Strings from the property attribute */ - protected String name; - protected String tactic; - protected String spec; // SMTL + protected String name; + protected String tactic; + protected String spec; // SMTL /** * The horizon (the total time interval required for evaluating @@ -151,14 +157,15 @@ public class UclidGenerator extends GeneratorBase { * required for evaluating the FOL spec in the trace), * and the transpiled FOL spec. */ - protected long horizon = 0; // in nanoseconds - protected String FOLSpec = ""; - protected int CT = 0; + protected long horizon = 0; // in nanoseconds + protected String FOLSpec = ""; + protected int CT = 0; // Constructor public UclidGenerator(FileConfig fileConfig, ErrorReporter errorReporter, List properties) { super(fileConfig, errorReporter); this.properties = properties; + this.runner = new UclidRunner(this, fileConfig, errorReporter); } //////////////////////////////////////////////////////////// @@ -184,8 +191,6 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // Extract information from the named instances. populateDataStructures(); - System.out.println(this.stateVariables); - System.out.println(this.triggerInstances); // Create the src-gen directory setUpDirectories(); @@ -217,7 +222,7 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { } // Generate runner script - generateRunnerScript(); + // generateRunnerScript(); } //////////////////////////////////////////////////////////// @@ -230,10 +235,11 @@ protected void generateUclidFile() { try { // Generate main.ucl and print to file code = new CodeBuilder(); - String filename = this.outputDir - .resolve(this.tactic + "_" + this.name + ".ucl").toString(); + Path file = this.outputDir.resolve(this.tactic + "_" + this.name + ".ucl"); + String filename = file.toString(); generateUclidCode(); code.writeToFile(filename); + this.generatedFiles.add(file); } catch (IOException e) { Exceptions.sneakyThrow(e); } @@ -1105,7 +1111,7 @@ protected void generateControlBlock() { " v = bmc(0);", " check;", " print_results;", - " v.print_cex;", + " v.print_cex_json;", "}" )); } diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidRunner.java b/org.lflang/src/org/lflang/generator/uclid/UclidRunner.java new file mode 100644 index 0000000000..995fddea33 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/uclid/UclidRunner.java @@ -0,0 +1,170 @@ +/** + * Runner for Uclid models. + * + * @author{Shaokai Lin } + */ +package org.lflang.generator.uclid; + +import java.io.IOException; +import java.io.OutputStream; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import org.lflang.ErrorReporter; +import org.lflang.FileConfig; +import org.lflang.generator.GeneratorCommandFactory; +import org.lflang.sim.Tag; +import org.lflang.util.LFCommand; + +public class UclidRunner { + + /** + * A list of paths to the generated files + */ + List filePaths; + + /** + * The directory where the generated files are placed + */ + public Path outputDir; + + /** + * A factory for compiler commands. + */ + GeneratorCommandFactory commandFactory; + + /** + * A UclidGenerator instance + */ + UclidGenerator generator; + + // Constructor + public UclidRunner( + UclidGenerator generator, + FileConfig fileConfig, + ErrorReporter errorReporter + ) { + this.generator = generator; + this.commandFactory = + new GeneratorCommandFactory(errorReporter, fileConfig); + } + + /** + * Parse information from an SMT model + * for a step in the trace. + */ + public StateInfo parseStateInfo(String smtStr) { + StateInfo info = new StateInfo(); + + // Reactions + Pattern p = Pattern.compile("\\(_tuple_0([^\\)]+)\\("); + Matcher m = p.matcher(smtStr); + m.find(); + String[] splited = m.group(1).split("\\s+"); + info.reactions.addAll(Arrays.asList(splited)); + + // Time tag + p = Pattern.compile("\\(_tuple_1([^\\)]+)\\)"); + m = p.matcher(smtStr); + m.find(); + splited = m.group(1).strip().split("\\s+"); + info.tag = new Tag( + Long.parseLong(splited[0]), + Long.parseLong(splited[1]), false); + + // Variables + p = Pattern.compile("\\(_tuple_2([^\\)]+)\\)"); + m = p.matcher(smtStr); + m.find(); + splited = m.group(1).strip().split("\\s+"); + for (int i = 1; i < splited.length; i++) { + info.variables.put(generator.namedInstances.get(i).getFullName(), splited[i]); + } + + // Triggers + p = Pattern.compile("\\(_tuple_3([^\\)]+)\\)"); + m = p.matcher(smtStr); + m.find(); + splited = m.group(1).strip().split("\\s+"); + for (int i = 1; i < splited.length; i++) { + info.triggers.put(generator.triggerInstances.get(i).getFullName(), splited[i]); + } + + return info; + } + + /** + * Run all the generated Uclid models, report outputs, + * and generate counterexample trace diagrams. + */ + public void run() { + for (Path path : generator.generatedFiles) { + // Execute uclid for each property. + LFCommand command = commandFactory.createCommand( + "uclid", List.of( + path.toString(), + // Any counterexample will be in .json + "--json-cex", path.toString()), + generator.outputDir); + command.run(); + + String output = command.getOutput().toString(); + boolean failed = output.contains("FAILED"); + if (failed) { + System.out.println("Not valid!"); + try { + // Read from the JSON counterexample (cex). + String cexJSONStr = Files.readString( + Paths.get(path.toString() + ".json"), + StandardCharsets.UTF_8); + System.out.println(cexJSONStr); + JSONObject cexJSON = new JSONObject(cexJSONStr); + + //// Extract the counterexample trace from JSON. + // Get the first key "property_*" + Iterator keys = cexJSON.keys(); + String firstKey = keys.next(); + JSONObject propertyObj = cexJSON.getJSONObject(firstKey); + + // Get Uclid trace. + JSONArray uclidTrace = propertyObj.getJSONArray("trace"); + + // Get the first step of the Uclid trace. + JSONObject uclidTraceStepOne = uclidTrace.getJSONObject(0); + + // Get the actual trace defined in the verification model. + JSONObject trace = uclidTraceStepOne.getJSONArray("trace").getJSONObject(0); + + String stepStr = ""; + for (int i = 0; i <= generator.CT; i++) { + try { + stepStr = trace.getString(String.valueOf(i)); + } catch(JSONException e) { + stepStr = trace.getString("-"); + } + System.out.println("============ Step " + i + " ============"); + StateInfo info = parseStateInfo(stepStr); + info.display(); + } + } catch (IOException e) { + System.out.println("ERROR: Not able to read from " + path.toString()); + } + } else { + System.out.println("Valid!"); + } + } + } +} \ No newline at end of file From 66da9c7ee738c5dbbcbe3c6c583df0c66e73a9bb Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sun, 4 Dec 2022 18:54:51 -0800 Subject: [PATCH 062/516] Start to work on the logical time based semantics. --- .../lflang/generator/uclid/MTLVisitor.java | 8 +- .../org/lflang/generator/uclid/StateInfo.java | 2 +- .../generator/uclid/UclidGenerator.java | 329 ++++++++++++------ .../lflang/generator/uclid/UclidRunner.java | 44 +-- .../src/org/lflang/sim/StateSpaceDiagram.java | 1 + .../org/lflang/sim/StateSpaceExplorer.java | 76 ++-- .../src/org/lflang/sim/StateSpaceNode.java | 12 +- 7 files changed, 295 insertions(+), 177 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java index 73eaadb452..e10cf6eec6 100644 --- a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java @@ -180,7 +180,7 @@ public String visitUntil(MTLParser.UntilContext ctx, return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " + "j" + QFIdx + " >= " + prefixIdx + " && " + "j" + QFIdx + " <= " + end - + " && " + "rxn" + "(" + "j" + QFIdx + ")" + " != " + "NULL" + + " && " + "!" + "isNULL" + "(" + "j" + QFIdx + ")" + " && " + "(" + _visitUnaryOp(ctx.right, ("j"+QFIdx), QFIdx+1, prefixIdx, currentHorizon) + ")" + " && " + "(" + "\n" + "// Time Predicate\n" @@ -226,7 +226,7 @@ public String visitGlobally(MTLParser.GloballyContext ctx, upperBoundNanoSec, "j" + QFIdx, prefixIdx); return "!(" + "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " + "j" + QFIdx + " >= " + prefixIdx + " && " + "j" + QFIdx + " <= " + end - + " && " + "rxn" + "(" + "j" + QFIdx + ")" + " != " + "NULL" + + " && " + "!" + "isNULL" + "(" + "j" + QFIdx + ")" + " && " + "!" + "(" + visitPrimary(ctx.formula, ("j"+QFIdx), QFIdx+1, prefixIdx, currentHorizon) + ")" + " && " + "(" + "\n" + "// Time Predicate\n" @@ -252,7 +252,7 @@ public String visitFinally(MTLParser.FinallyContext ctx, upperBoundNanoSec, "j" + QFIdx, prefixIdx); return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " + "j" + QFIdx + " >= " + prefixIdx + " && " + "j" + QFIdx + " <= " + end - + " && " + "rxn" + "(" + "j" + QFIdx + ")" + " != " + "NULL" + + " && " + "!" + "isNULL" + "(" + "j" + QFIdx + ")" + " && " + "(" + visitPrimary(ctx.formula, ("j"+QFIdx), QFIdx+1, prefixIdx, currentHorizon) + ")" + " && " + "(" + "\n" + "// Time Predicate\n" @@ -269,7 +269,7 @@ else if (ctx.id != null) { // Check if the ID is a reaction. // FIXME: Not robust. if (ctx.id.getText().contains("_reaction_")) { - return "rxn(" + prefixIdx + ") == " + ctx.id.getText(); + return ctx.id.getText() + "(" + "rxn(" + prefixIdx + ")" + ")"; } else if (ctx.id.getText().contains("_is_present")) { return ctx.id.getText() + "(" + "t(" + prefixIdx + ")" + ")"; } else { diff --git a/org.lflang/src/org/lflang/generator/uclid/StateInfo.java b/org.lflang/src/org/lflang/generator/uclid/StateInfo.java index 0f2f4f3008..c01841ea14 100644 --- a/org.lflang/src/org/lflang/generator/uclid/StateInfo.java +++ b/org.lflang/src/org/lflang/generator/uclid/StateInfo.java @@ -24,6 +24,6 @@ public void display() { System.out.println("tag: " + tag); System.out.println("variables: " + variables); System.out.println("triggers: " + triggers); - System.out.println("scheduled: " + scheduled); + // System.out.println("scheduled: " + scheduled); } } \ No newline at end of file diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index e241374279..9915cb1cf9 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -136,6 +136,11 @@ public class UclidGenerator extends GeneratorBase { // A runner for the generated Uclid files public UclidRunner runner; + // If true, use logical time-based semantics; + // otherwise, use event-based semantics, + // as described in Sirjani et. al (2020). + public boolean logicalTimeBased = false; + //////////////////////////////////////////// //// Protected fields @@ -464,8 +469,16 @@ protected void generateTraceDefinition() { " ****************/" )); - // Define a tuple getter. - // FIXME: Support this feature in Uclid. + // Define a getter for uclid arrays. + String initialReactions = ""; + if (this.reactionInstances.size() > 0) { + initialReactions = "false, ".repeat(this.reactionInstances.size()); + initialReactions = initialReactions.substring(0, initialReactions.length()-2); + } else { + // Initialize a dummy variable just to make the code compile. + initialReactions = "false"; + } + initialReactions = "{" + initialReactions + "}"; String initialStates = ""; if (this.namedInstances.size() > 0) { initialStates = "0, ".repeat(this.namedInstances.size()); @@ -493,7 +506,8 @@ protected void generateTraceDefinition() { code.pr("// Helper macro that returns an element based on index."); code.pr("define get(tr : trace_t, i : step_t) : event_t ="); code.pr("if (i >= START || i <= END_TRACE) then tr[i] else"); - code.pr("{ NULL, inf(), { " + initialStates + " }, { " + initialTriggerPresence + " }, {" + initialActionsScheduled + "} };"); + code.pr("{ " + initialReactions + ", inf(), { " + initialStates + " }, { " + initialTriggerPresence + + " }, {" + initialActionsScheduled + "} };"); // Define an event getter from the trace. code.pr(String.join("\n", @@ -506,8 +520,7 @@ protected void generateTraceDefinition() { "define s (i : step_t) : state_t = elem(i)._3;", "define t (i : step_t) : trigger_t = elem(i)._4;", "define d (i : step_t) : sched_t = elem(i)._5;", - "define isNULL (i : step_t) : boolean = rxn(i) == NULL;", - "" + "define isNULL (i : step_t) : boolean = rxn(i) == " + initialReactions + ";" )); } @@ -528,18 +541,26 @@ protected void generateReactionIdsAndStateVars() { // Enumerate over all reactions. code.pr(String.join("\n", - "// Reaction ids", - "type rxn_t = enum {" + "// Reactions", + "type rxn_t = {" )); code.indent(); - for (var rxn : this.reactionInstances) { + for (var i = 0 ; i < this.reactionInstances.size(); i++) { // Print a list of reaction IDs. // Add a comma if not last. - code.pr(rxn.getReaction().getFullNameWithJoiner("_") + ","); + code.pr("boolean" + ((i == this.reactionInstances.size() - 1) ? "" : ",") + + "\t// " + this.reactionInstances.get(i)); } - code.pr("NULL"); code.unindent(); - code.pr("};\n\n"); + code.pr("};\n"); + + // Generate projection macros. + code.pr("// Reaction projection macros"); + for (var i = 0 ; i < this.reactionInstances.size(); i++) { + code.pr("define " + this.reactionInstances.get(i).getReaction().getFullNameWithJoiner("_") + + "(n : rxn_t) : boolean = n._" + (i+1) + ";"); + } + code.pr("\n"); // Newline // State variables and triggers // FIXME: expand to data types other than integer @@ -548,7 +569,8 @@ protected void generateReactionIdsAndStateVars() { code.indent(); if (this.namedInstances.size() > 0) { for (var i = 0 ; i < this.namedInstances.size(); i++) { - code.pr("integer" + ((i == this.namedInstances.size() - 1) ? "" : ",") + "\t// " + this.namedInstances.get(i)); + code.pr("integer" + ((i == this.namedInstances.size() - 1) ? "" : ",") + + "\t// " + this.namedInstances.get(i)); } } else { code.pr(String.join("\n", @@ -561,7 +583,8 @@ protected void generateReactionIdsAndStateVars() { code.pr("};"); code.pr("// State variable projection macros"); for (var i = 0; i < this.namedInstances.size(); i++) { - code.pr("define " + this.namedInstances.get(i).getFullNameWithJoiner("_") + "(s : state_t) : integer = s._" + (i+1) + ";"); + code.pr("define " + this.namedInstances.get(i).getFullNameWithJoiner("_") + + "(s : state_t) : integer = s._" + (i+1) + ";"); } code.pr("\n"); // Newline @@ -571,7 +594,8 @@ protected void generateReactionIdsAndStateVars() { code.indent(); if (this.triggerInstances.size() > 0) { for (var i = 0 ; i < this.triggerInstances.size(); i++) { - code.pr("boolean" + ((i == this.triggerInstances.size() - 1) ? "" : ",") + "\t// " + this.triggerInstances.get(i)); + code.pr("boolean" + ((i == this.triggerInstances.size() - 1) ? "" : ",") + + "\t// " + this.triggerInstances.get(i)); } } else { code.pr(String.join("\n", @@ -584,7 +608,8 @@ protected void generateReactionIdsAndStateVars() { code.pr("};"); code.pr("// Trigger presence projection macros"); for (var i = 0; i < this.triggerInstances.size(); i++) { - code.pr("define " + this.triggerInstances.get(i).getFullNameWithJoiner("_") + "_is_present" + "(t : trigger_t) : boolean = t._" + (i+1) + ";"); + code.pr("define " + this.triggerInstances.get(i).getFullNameWithJoiner("_") + + "_is_present" + "(t : trigger_t) : boolean = t._" + (i+1) + ";"); } // A boolean tuple indicating whether actions are scheduled by reactions @@ -597,7 +622,8 @@ protected void generateReactionIdsAndStateVars() { code.indent(); if (this.actionInstances.size() > 0) { for (var i = 0 ; i < this.actionInstances.size(); i++) { - code.pr("boolean" + ((i == this.actionInstances.size() - 1) ? "" : ",") + "\t// " + this.actionInstances.get(i)); + code.pr("boolean" + ((i == this.actionInstances.size() - 1) ? "" : ",") + + "\t// " + this.actionInstances.get(i)); } } else { code.pr(String.join("\n", @@ -610,7 +636,8 @@ protected void generateReactionIdsAndStateVars() { code.pr("};"); code.pr("// Projection macros for action schedule flag"); for (var i = 0; i < this.actionInstances.size(); i++) { - code.pr("define " + this.actionInstances.get(i).getFullNameWithJoiner("_") + "_scheduled" + "(d : sched_t) : boolean = d._" + (i+1) + ";"); + code.pr("define " + this.actionInstances.get(i).getFullNameWithJoiner("_") + + "_scheduled" + "(d : sched_t) : boolean = d._" + (i+1) + ";"); } } @@ -622,61 +649,75 @@ protected void generateReactorSemantics() { "/*********************", " * Reactor Semantics *", " *********************/", + "" + )); + + // Non-federated "happened-before" + code.pr(String.join("\n", + "// Non-federated \"happened-before\"", + "define hb(e1, e2 : event_t) : boolean", + "= tag_earlier(e1._2, e2._2)" + )); + if (!this.logicalTimeBased) { + code.indent(); + // Get happen-before relation between two reactions. + code.pr("|| (tag_same(e1._2, e2._2) && ( false"); + // Iterate over every pair of reactions. + for (var upstreamRuntime : this.reactionInstances) { + var downstreamReactions = upstreamRuntime.getReaction().dependentReactions(); + for (var downstream : downstreamReactions) { + for (var downstreamRuntime : downstream.getRuntimeInstances()) { + code.pr("|| (" + upstreamRuntime.getReaction().getFullNameWithJoiner("_") + "(e1._1)" + + " && " + downstreamRuntime.getReaction().getFullNameWithJoiner("_") + "(e2._1)" + ")"); + } + } + } + code.unindent(); + code.pr("))"); + } + code.pr(";"); + + code.pr(String.join("\n", "/** transition relation **/", "// transition relation constrains future states", "// based on previous states.", "", "// Events are ordered by \"happened-before\" relation.", - "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> (finite_forall (j : integer) in indices ::", - " (j >= START && j <= END) ==> (hb(elem(i), elem(j)) ==> i < j)));", - "", - "// the same event can only trigger once in a logical instant", - "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> (finite_forall (j : integer) in indices ::", - " (j >= START && j <= END) ==> ((rxn(i) == rxn(j) && i != j)", - " ==> !tag_same(g(i), g(j)))));", + "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END_TRACE) ==> (finite_forall (j : integer) in indices ::", + " (j >= START && j <= END_TRACE) ==> (hb(elem(i), elem(j)) ==> i < j)));", "", "// Tags should be non-negative.", - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END)", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END_TRACE)", " ==> pi1(g(i)) >= 0);", "", "// Microsteps should be non-negative.", - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END)", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END_TRACE)", " ==> pi2(g(i)) >= 0);", "", "// Begin the frame at the start time specified.", "define start_frame(i : step_t) : boolean =", " (tag_same(g(i), {start_time, 0}) || tag_later(g(i), {start_time, 0}));", - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END)", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END_TRACE)", " ==> start_frame(i));", "", "// NULL events should appear in the suffix, except for START.", - "axiom(finite_forall (j : integer) in indices :: (j > START && j <= END) ==> (", - " (rxn(j)) != NULL) ==> ", - " (finite_forall (i : integer) in indices :: (i > START && i < j) ==> (rxn(i) != NULL)));", + "axiom(finite_forall (j : integer) in indices :: (j > START && j <= END_TRACE) ==> (", + " !isNULL(j)) ==> ", + " (finite_forall (i : integer) in indices :: (i > START && i < j) ==> (!isNULL(i))));", "" )); - // Non-federated "happened-before" - code.pr(String.join("\n", - "// Non-federated \"happened-before\"", - "define hb(e1, e2 : event_t) : boolean", - "= tag_earlier(e1._2, e2._2)" - )); - code.indent(); - // Get happen-before relation between two reactions. - code.pr("|| (tag_same(e1._2, e2._2) && ( false"); - // Iterate over every pair of reactions. - for (var upstreamRuntime : this.reactionInstances) { - var downstreamReactions = upstreamRuntime.getReaction().dependentReactions(); - for (var downstream : downstreamReactions) { - for (var downstreamRuntime : downstream.getRuntimeInstances()) { - code.pr("|| (e1._1 == " + upstreamRuntime.getReaction().getFullNameWithJoiner("_") - + " && e2._1 == " + downstreamRuntime.getReaction().getFullNameWithJoiner("_") + ")"); - } - } + // For logical time-based semantics, there is no need for this since each logical instant + // will only happen once in the trace. + if (!this.logicalTimeBased) { + code.pr(String.join("\n", + "// the same event can only trigger once in a logical instant", + "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> (finite_forall (j : integer) in indices ::", + " (j >= START && j <= END) ==> ((rxn(i) == rxn(j) && i != j)", + " ==> !tag_same(g(i), g(j)))));", + "" + )); } - code.unindent(); - code.pr("));"); } /** @@ -747,7 +788,7 @@ protected void generateTriggersAndReactions() { // If destination is not present, then its value resets to 0. code.pr(String.join("\n", "// If " + destination.getFullNameWithJoiner("_") + " is not present, then its value resets to 0.", - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END && rxn(i) != NULL) ==> (", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END && !isNULL(i)) ==> (", " (!" + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", " " + destination.getFullNameWithJoiner("_") + "(s(i)) == 0", " ))", @@ -775,7 +816,7 @@ protected void generateTriggersAndReactions() { // OR because only any present trigger can trigger the reaction. "|| (" + action.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", " finite_exists (j : integer) in indices :: j >= START && j < i", - " && rxn(j) == " + reaction.getFullNameWithJoiner("_"), + " && " + reaction.getFullNameWithJoiner("_") + "(rxn(j))", " && g(i) == tag_schedule(g(j), " + action.getMinDelay().toNanoSeconds() + ")", " && " + action.getFullNameWithJoiner("_") + "_scheduled" + "(d(j))", "))" @@ -794,7 +835,7 @@ protected void generateTriggersAndReactions() { // If the action is not present, then its value resets to 0. code.pr(String.join("\n", "// If " + action.getFullNameWithJoiner("_") + " is not present, then its value resets to 0.", - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END && rxn(i) != NULL) ==> (", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END && !isNULL(i)) ==> (", " (!" + action.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", " " + action.getFullNameWithJoiner("_") + "(s(i)) == 0", " ))", @@ -835,10 +876,10 @@ protected void generateTriggersAndReactions() { "axiom(", " ((start_time == 0) ==> (", " finite_exists (i : integer) in indices :: i > START && i <= END", - " && rxn(i) == " + reaction.getReaction().getFullNameWithJoiner("_") + " && tag_same(g(i), zero())", + " && " + reaction.getReaction().getFullNameWithJoiner("_") + "(rxn(i))" + " && tag_same(g(i), zero())", " && !(", " finite_exists (j : integer) in indices :: j > START && j <= END", - " && rxn(j) == " + reaction.getReaction().getFullNameWithJoiner("_") , + " && " + reaction.getReaction().getFullNameWithJoiner("_") + "(rxn(j))", " && j != i", " )", " ))", @@ -866,7 +907,7 @@ protected void generateTriggersAndReactions() { for (var instance : trigger.getDependentReactions()) { for (var runtime : ((ReactionInstance)instance).getRuntimeInstances()) { if (runtime == reaction) continue; // Skip the current reaction. - exclusion += " && rxn(i) != " + runtime.getReaction().getFullNameWithJoiner("_"); + exclusion += " && !" + runtime.getReaction().getFullNameWithJoiner("_") + "(rxn(i))"; } } @@ -875,7 +916,7 @@ protected void generateTriggersAndReactions() { } // If any of the above trigger is present, then trigger the reaction. - str += "\n) <==> (rxn(i) == " + reaction.getReaction().getFullNameWithJoiner("_") + ")));"; + str += "\n) <==> (" + reaction.getReaction().getFullNameWithJoiner("_") + "(rxn(i))" + ")));"; code.pr(str); } } @@ -890,7 +931,7 @@ protected void generateInitialConditions() { " *********************/", "define initial_condition() : boolean", "= start_time == 0", - " && rxn(0) == NULL", + " && isNULL(0)", " && g(0) == {0, 0}" )); code.indent(); @@ -919,6 +960,7 @@ protected void generateReactionAxioms() { " * Reactions *", " *************/" )); + for (ReactionInstance.Runtime reaction : this.reactionInstances) { System.out.println("Printing reaction body of " + reaction); String body = reaction.getReaction().getDefinition().getCode().getBody(); @@ -952,13 +994,13 @@ protected void generateReactionAxioms() { // System.out.println("***** Printing the AST in If Normal Form."); baseVisitor.visit(inf); - // For the variables that are used, extract the conditions + // For the variables that are USED inside this reaction, extract the conditions // for setting them, and take the negation of their conjunction - // to get the condition for resetting them. + // to get the condition for maintaining their values. List unusedStates = new ArrayList<>(this.stateVariables); List unusedOutputs = new ArrayList<>(this.outputInstances); List unusedActions = new ArrayList<>(this.actionInstances); - HashMap> resetConditions = new HashMap<>(); + HashMap> defaultBehaviorConditions = new HashMap<>(); for (CAst.AstNode node : inf.children) { CAst.IfBlockNode ifBlockNode = (CAst.IfBlockNode)node; CAst.AstNode ifBody = ((CAst.IfBodyNode)ifBlockNode.right).left; @@ -983,10 +1025,10 @@ protected void generateReactionAxioms() { unusedActions.remove(instance); } else continue; // Create a new entry in the list if there isn't one yet. - if (resetConditions.get(instance) == null) { - resetConditions.put(instance, new ArrayList()); + if (defaultBehaviorConditions.get(instance) == null) { + defaultBehaviorConditions.put(instance, new ArrayList()); } - resetConditions.get(instance).add(ifBlockNode.left); + defaultBehaviorConditions.get(instance).add(ifBlockNode.left); // System.out.println("!!! Added another reset condition: " + ifBlockNode.left); } @@ -997,14 +1039,14 @@ protected void generateReactionAxioms() { code.pr(String.join("\n", "// Reaction body of " + reaction, "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", - " (rxn(i) == " + reaction.getReaction().getFullNameWithJoiner("_") + ")", + " (" + reaction.getReaction().getFullNameWithJoiner("_") + "(rxn(i))" + ")", " ==> " + "(" + "(" + axiom + ")", "&& " + "( " + "true", - "// Default behavior of the used variables" + "// By default, the value of the variables used in this reaction stay the same." )); - for (NamedInstance key : resetConditions.keySet()) { - CAst.AstNode disjunction = CAstUtils.takeDisjunction(resetConditions.get(key)); - // System.out.println("!!! Reset conditions: " + resetConditions.get(key)); + for (NamedInstance key : defaultBehaviorConditions.keySet()) { + CAst.AstNode disjunction = CAstUtils.takeDisjunction(defaultBehaviorConditions.get(key)); + // System.out.println("!!! Reset conditions: " + defaultBehaviorConditions.get(key)); CAst.LogicalNotNode notNode = new CAst.LogicalNotNode(); notNode.child = disjunction; String resetCondition = c2uVisitor.visit(notNode); @@ -1038,38 +1080,71 @@ protected void generateReactionAxioms() { } code.pr("))"); } - // Unused state variables and ports reset by default. - code.pr("// Unused state variables and ports reset by default."); - for (StateVariableInstance s : unusedStates) { - code.pr("&& (true ==> ("); - code.pr(s.getFullNameWithJoiner("_") + "(" + "s" + "(" + "i" + ")" + ")" - + " == " - + s.getFullNameWithJoiner("_") + "(" + "s" + "(" + "i" + "-1" + ")" + ")"); - code.pr("))"); - } - for (PortInstance p : unusedOutputs) { - code.pr("&& (true ==> ("); - code.pr( - "(" - + " true" - // Reset value - + "\n&& " + p.getFullNameWithJoiner("_") + "(" + "s" + "(" + "i" + ")" + ")" - + " == " - + "0" // Default value - // Reset presence - + "\n&& " + p.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + "i" + ")" + ")" + + // For state variables and ports that are NOT used in this reaction, + // their values stay the same by default. + code.pr("// Unused state variables and ports are reset by default."); + if (this.logicalTimeBased) { + // If all other reactions that can modify the SAME state variable + // are not triggered, then the state variable stay the same. + // + // FIXME: What if two reactions modifying the same state variable + // are triggered at the same time? + // How to use axioms to model reaction priority? + // The main difficulty of logical time based semantics is composing + // the effect of simultaneous reactions. + // + // A path way to implement it in the future: + // 1. For each variable, port, and action, determine a list of + // reactions that can modify/schedule it. + // 2. Reaction axioms should be generated wrt each reactor. + // For example, assuming a reactor with two input ports, + // each triggering a distinct reaction. The axioms will need + // to handle four cases: i. reaction 1 gets triggered and 2 + // does not; ii. reaction 2 gets triggered and 1 does not; + // iii. both reactions get triggered; iv. none of them get + // triggered. Since it is hard to specify in an independent way, + // due to reaction priorities, + // what happens when two reactions (modifying the same state var.) + // get triggered simultaneously, some combinatorial blowup will + // be incurred. In this example, four axioms (instead of two), + // each handling one case, seems needed. The good news is that + // axioms across reactors may be specified independently. + // For example, if there is another reactor of the same class, + // Only four more axioms need to be added (in total 2^2 + 2^2), + // instead of 16 axioms (2^4). + } else { + for (StateVariableInstance s : unusedStates) { + code.pr("&& (true ==> ("); + code.pr(s.getFullNameWithJoiner("_") + "(" + "s" + "(" + "i" + ")" + ")" + " == " - + false // default presence - + ")" - ); - code.pr("))"); - } - for (ActionInstance a : unusedActions) { - code.pr("&& (true ==> ("); - code.pr(a.getFullNameWithJoiner("_") + "_scheduled" + "(" + "d" + "(" + "i" + ")" + ")" + " == " + "false"); - code.pr("))"); + + s.getFullNameWithJoiner("_") + "(" + "s" + "(" + "i" + "-1" + ")" + ")"); + code.pr("))"); + } + for (PortInstance p : unusedOutputs) { + code.pr("&& (true ==> ("); + code.pr( + "(" + + " true" + // Reset value + + "\n&& " + p.getFullNameWithJoiner("_") + "(" + "s" + "(" + "i" + ")" + ")" + + " == " + + "0" // Default value + // Reset presence + + "\n&& " + p.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + "i" + ")" + ")" + + " == " + + false // default presence + + ")" + ); + code.pr("))"); + } + for (ActionInstance a : unusedActions) { + code.pr("&& (true ==> ("); + code.pr(a.getFullNameWithJoiner("_") + "_scheduled" + "(" + "d" + "(" + "i" + ")" + ")" + " == " + "false"); + code.pr("))"); + } + code.pr("))));"); } - code.pr("))));"); } } @@ -1231,7 +1306,21 @@ private void computeCT() { diagram.display(); if (!explorer.loopFound) { - this.CT = diagram.length; + if (this.logicalTimeBased) + this.CT = diagram.length; + else { + // FIXME: This could be much more efficient with a linkedlist implementation. + StateSpaceNode node = diagram.head; + this.CT = diagram.head.reactionsInvoked.size(); + while (diagram.getDownstreamAdjacentNodes(node).size() != 0) { + Set downstreamNodes = diagram.getDownstreamAdjacentNodes(node); + for (StateSpaceNode n : downstreamNodes) { + node = n; // Go to the next node. + break; + } + this.CT += node.reactionsInvoked.size(); + } + } System.out.println("*** A loop is NOT found."); System.out.println("CT: " + this.CT); } @@ -1241,17 +1330,45 @@ private void computeCT() { // interval from the total horizon. long horizonRemained = this.horizon - diagram.loopNode.tag.timestamp; - + // Check how many loop iteration is required // to check the remaining horizon. int loopIterations = (int) Math.ceil( (double) horizonRemained / diagram.loopPeriod); - - // CT = steps required for the non-periodic part - // + steps required for the periodic part - this.CT = (diagram.loopNode.index + 1) - + (diagram.tail.index - diagram.loopNode.index + 1) * loopIterations; + if (this.logicalTimeBased) { + // CT = steps required for the non-periodic part + // + steps required for the periodic part + this.CT = (diagram.loopNode.index + 1) + + (diagram.tail.index - diagram.loopNode.index + 1) * loopIterations; + } else { + // Get the number of events before the loop. + // This stops right before the loopNode is encountered. + StateSpaceNode node = diagram.head; + int numReactionInvocationsBeforeLoop = 0; + while (node != diagram.loopNode) { + numReactionInvocationsBeforeLoop += node.reactionsInvoked.size(); + Set downstreamNodes = diagram.getDownstreamAdjacentNodes(node); + for (StateSpaceNode n : downstreamNodes) { + node = n; // Go to the next node. + break; + } + } + int numReactionInvocationsInsideLoop = node.reactionsInvoked.size(); + while (node != diagram.loopNode) { + Set downstreamNodes = diagram.getDownstreamAdjacentNodes(node); + for (StateSpaceNode n : downstreamNodes) { + node = n; // Go to the next node. + break; + } + numReactionInvocationsInsideLoop += node.reactionsInvoked.size(); + } + + // CT = steps required for the non-periodic part + // + steps required for the periodic part + this.CT = numReactionInvocationsBeforeLoop + + numReactionInvocationsInsideLoop * loopIterations; + } System.out.println("*** A loop is found!"); System.out.println("CT: " + this.CT); } diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidRunner.java b/org.lflang/src/org/lflang/generator/uclid/UclidRunner.java index 995fddea33..aa2698fead 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidRunner.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidRunner.java @@ -69,38 +69,42 @@ public UclidRunner( public StateInfo parseStateInfo(String smtStr) { StateInfo info = new StateInfo(); + // Split the counterexample string by newline. + String[] splitedSmtStr = smtStr.split("\\r?\\n"); + // Reactions - Pattern p = Pattern.compile("\\(_tuple_0([^\\)]+)\\("); - Matcher m = p.matcher(smtStr); + Pattern p = Pattern.compile("\\(_tuple_\\d+ \\(_tuple_\\d+ ([^\\)]+)\\)"); + Matcher m = p.matcher(splitedSmtStr[0].strip()); m.find(); - String[] splited = m.group(1).split("\\s+"); - info.reactions.addAll(Arrays.asList(splited)); + String[] reactions = m.group(1).strip().split("\\s+"); + for (int i = 0; i < reactions.length; i++) { + if (reactions[i].equals("true")) + info.reactions.add(generator.reactionInstances.get(i).getReaction().getFullName()); + } // Time tag - p = Pattern.compile("\\(_tuple_1([^\\)]+)\\)"); - m = p.matcher(smtStr); + p = Pattern.compile("\\(_tuple_\\d+([^\\)]+)\\)"); + m = p.matcher(splitedSmtStr[1].strip()); m.find(); - splited = m.group(1).strip().split("\\s+"); + String[] tag = m.group(1).strip().split("\\s+"); info.tag = new Tag( - Long.parseLong(splited[0]), - Long.parseLong(splited[1]), false); + Long.parseLong(tag[0]), + Long.parseLong(tag[1]), false); // Variables - p = Pattern.compile("\\(_tuple_2([^\\)]+)\\)"); - m = p.matcher(smtStr); + m = p.matcher(splitedSmtStr[2].strip()); m.find(); - splited = m.group(1).strip().split("\\s+"); - for (int i = 1; i < splited.length; i++) { - info.variables.put(generator.namedInstances.get(i).getFullName(), splited[i]); + String[] variables = m.group(1).strip().split("\\s+"); + for (int i = 0; i < variables.length; i++) { + info.variables.put(generator.namedInstances.get(i).getFullName(), variables[i]); } // Triggers - p = Pattern.compile("\\(_tuple_3([^\\)]+)\\)"); - m = p.matcher(smtStr); + m = p.matcher(splitedSmtStr[3].strip()); m.find(); - splited = m.group(1).strip().split("\\s+"); - for (int i = 1; i < splited.length; i++) { - info.triggers.put(generator.triggerInstances.get(i).getFullName(), splited[i]); + String[] triggers = m.group(1).strip().split("\\s+"); + for (int i = 0; i < triggers.length; i++) { + info.triggers.put(generator.triggerInstances.get(i).getFullName(), triggers[i]); } return info; @@ -130,7 +134,7 @@ public void run() { String cexJSONStr = Files.readString( Paths.get(path.toString() + ".json"), StandardCharsets.UTF_8); - System.out.println(cexJSONStr); + // System.out.println(cexJSONStr); JSONObject cexJSON = new JSONObject(cexJSONStr); //// Extract the counterexample trace from JSON. diff --git a/org.lflang/src/org/lflang/sim/StateSpaceDiagram.java b/org.lflang/src/org/lflang/sim/StateSpaceDiagram.java index 02bc33cf78..10bcccb505 100644 --- a/org.lflang/src/org/lflang/sim/StateSpaceDiagram.java +++ b/org.lflang/src/org/lflang/sim/StateSpaceDiagram.java @@ -9,6 +9,7 @@ import org.lflang.graph.DirectedGraph; +// FIXME: Use a linkedlist instead. public class StateSpaceDiagram extends DirectedGraph { /** diff --git a/org.lflang/src/org/lflang/sim/StateSpaceExplorer.java b/org.lflang/src/org/lflang/sim/StateSpaceExplorer.java index 70a370f535..623a2c40a1 100644 --- a/org.lflang/src/org/lflang/sim/StateSpaceExplorer.java +++ b/org.lflang/src/org/lflang/sim/StateSpaceExplorer.java @@ -102,43 +102,39 @@ public void explore(Tag horizon, boolean findLoop) { addInitialEvents(this.main); System.out.println(this.eventQ); - Tag previous_tag = null; // Tag in the previous loop ITERATION - Tag current_tag = null; // Tag in the current loop ITERATION - StateSpaceNode current_node = null; + Tag previousTag = null; // Tag in the previous loop ITERATION + Tag currentTag = null; // Tag in the current loop ITERATION + StateSpaceNode currentNode = null; boolean stop = true; if (this.eventQ.size() > 0) { stop = false; - current_tag = (eventQ.peek()).tag; - // System.out.println(current_tag); + currentTag = (eventQ.peek()).tag; + // System.out.println(currentTag); } // A list of reactions invoked at the current logical tag - ArrayList reactions_invoked; + ArrayList reactionsInvoked; // A temporary list of reactions processed in the current LOOP ITERATION - ArrayList reactions_temp; + ArrayList reactionsTemp; while (!stop) { // Pop the events from the earliest tag off the event queue. - ArrayList current_events = new ArrayList(); + ArrayList currentEvents = new ArrayList(); // FIXME: Use stream methods here? - while (eventQ.size() > 0 && eventQ.peek().tag.compareTo(current_tag) == 0) { + while (eventQ.size() > 0 && eventQ.peek().tag.compareTo(currentTag) == 0) { Event e = eventQ.poll(); - current_events.add(e); - // System.out.println("Adding event to current_events: " + e); + currentEvents.add(e); + // System.out.println("Adding event to currentEvents: " + e); } - System.out.println(current_events); + System.out.println(currentEvents); // Collect all the reactions invoked in this current LOOP ITERATION // triggered by the earliest events. - reactions_temp = new ArrayList(); - for (Event e : current_events) { - Set dependent_reactions + reactionsTemp = new ArrayList(); + for (Event e : currentEvents) { + Set dependentReactions = e.trigger.getDependentReactions(); - // System.out.println("Dependent reactions:"); - // for (ReactionInstance reaction : dependent_reactions) - // System.out.println(reaction); - // System.out.println(dependent_reactions); - reactions_temp.addAll(dependent_reactions); + reactionsTemp.addAll(dependentReactions); // If the event is a timer firing, enqueue the next firing. if (e.trigger instanceof TimerInstance) { @@ -146,7 +142,7 @@ public void explore(Tag horizon, boolean findLoop) { eventQ.add(new Event( timer, new Tag( - current_tag.timestamp + timer.getPeriod().toNanoSeconds(), + currentTag.timestamp + timer.getPeriod().toNanoSeconds(), 0, // A time advancement resets microstep to 0. false )) @@ -155,7 +151,7 @@ public void explore(Tag horizon, boolean findLoop) { } // For each reaction invoked, compute the new events produced. - for (ReactionInstance reaction : reactions_temp) { + for (ReactionInstance reaction : reactionsTemp) { // Iterate over all the effects produced by this reaction. // If the effect is a port, obtain the downstream port along // a connection and enqueue a future event for that port. @@ -193,7 +189,7 @@ public void explore(Tag horizon, boolean findLoop) { // Create and enqueue a new event. Event e = new Event( downstreamPort, - new Tag(current_tag.timestamp + delay, 0, false) + new Tag(currentTag.timestamp + delay, 0, false) ); eventQ.add(e); } @@ -205,7 +201,7 @@ else if (effect instanceof ActionInstance) { // Create and enqueue a new event. Event e = new Event( effect, - new Tag(current_tag.timestamp + min_delay, 0, false) + new Tag(currentTag.timestamp + min_delay, 0, false) ); eventQ.add(e); } @@ -214,11 +210,11 @@ else if (effect instanceof ActionInstance) { // When we first advance to a new tag, create a new node in the state space diagram. if ( - previous_tag == null // The first iteration - || current_tag.compareTo(previous_tag) > 0 + previousTag == null // The first iteration + || currentTag.compareTo(previousTag) > 0 ) { - // Copy the reactions in reactions_temp. - reactions_invoked = new ArrayList(reactions_temp); + // Copy the reactions in reactionsTemp. + reactionsInvoked = new ArrayList(reactionsTemp); // Create a new state in the SSD for the current tag, // add the reactions triggered to the state, @@ -226,8 +222,8 @@ else if (effect instanceof ActionInstance) { // generated by reaction invocations in the curren tag) // to the state. StateSpaceNode node = new StateSpaceNode( - current_tag, // Current tag - reactions_invoked, // Reactions invoked at this tag + currentTag, // Current tag + reactionsInvoked, // Reactions invoked at this tag new ArrayList(eventQ) // A snapshot of the event queue ); @@ -241,8 +237,8 @@ else if (effect instanceof ActionInstance) { System.out.println("*** A loop is found!"); // Mark the loop in the diagram. this.diagram.loopNode = n; - this.diagram.tail = current_node; - this.diagram.loopPeriod = current_tag.timestamp + this.diagram.tail = currentNode; + this.diagram.loopPeriod = currentTag.timestamp - this.diagram.loopNode.tag.timestamp; this.diagram.addEdge(this.diagram.loopNode, this.diagram.tail); return; // Exit the while loop early. @@ -257,35 +253,35 @@ else if (effect instanceof ActionInstance) { // If the head is not empty, add an edge from the previous state // to the next state. Otherwise initialize the head to the new node. - if (this.diagram.head != null && current_node != null) { - // System.out.println("--- Add a new edge between " + current_node + " and " + node); - this.diagram.addEdge(node, current_node); // Sink first, then source + if (this.diagram.head != null && currentNode != null) { + // System.out.println("--- Add a new edge between " + currentNode + " and " + node); + this.diagram.addEdge(node, currentNode); // Sink first, then source } else this.diagram.head = node; // Initialize the head. // Update the current node. - current_node = node; + currentNode = node; } // Time does not advance because we are processing // connections with zero delay. else { // Add reactions explored in the current loop iteration // to the existing state space node. - current_node.reactions_invoked.addAll(reactions_temp); + currentNode.reactionsInvoked.addAll(reactionsTemp); } // Update the current tag for the next iteration. if (eventQ.size() > 0) { - previous_tag = current_tag; - current_tag = eventQ.peek().tag; + previousTag = currentTag; + currentTag = eventQ.peek().tag; } // Stop if: // 1. the event queue is empty, or // 2. the horizon is reached. if (eventQ.size() == 0 - || current_tag.compareTo(horizon) > 0) + || currentTag.compareTo(horizon) > 0) stop = true; } return; diff --git a/org.lflang/src/org/lflang/sim/StateSpaceNode.java b/org.lflang/src/org/lflang/sim/StateSpaceNode.java index 1bf59f7ed6..d33fd99e41 100644 --- a/org.lflang/src/org/lflang/sim/StateSpaceNode.java +++ b/org.lflang/src/org/lflang/sim/StateSpaceNode.java @@ -14,29 +14,29 @@ public class StateSpaceNode { public int index; public Tag tag; - public ArrayList reactions_invoked; + public ArrayList reactionsInvoked; public ArrayList eventQ; public StateSpaceNode( Tag tag, - ArrayList reactions_invoked, + ArrayList reactionsInvoked, ArrayList eventQ ) { this.tag = tag; this.eventQ = eventQ; - this.reactions_invoked = reactions_invoked; + this.reactionsInvoked = reactionsInvoked; } /** * This equals method does NOT compare tags, - * only compares reactions_invoked and eventQ. + * only compares reactionsInvoked and eventQ. */ @Override public boolean equals(Object o) { if (o == null) return false; if (o instanceof StateSpaceNode) { StateSpaceNode node = (StateSpaceNode) o; - if (this.reactions_invoked.equals(node.reactions_invoked) + if (this.reactionsInvoked.equals(node.reactionsInvoked) && this.eventQ.equals(node.eventQ)) return true; } @@ -44,7 +44,7 @@ public boolean equals(Object o) { } public void display() { - System.out.println("(" + tag + ", " + reactions_invoked + ", " + eventQ + ")"); + System.out.println("(" + tag + ", " + reactionsInvoked + ", " + eventQ + ")"); } } \ No newline at end of file From 0c8ab2ddb2a566bede7754492cb6bf9da6a2c387 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 5 Dec 2022 12:25:48 -0800 Subject: [PATCH 063/516] More robust CEX parsing, fix an axiom bug related to END_TRACE. --- .../org/lflang/generator/uclid/StateInfo.java | 2 +- .../generator/uclid/UclidGenerator.java | 2 +- .../lflang/generator/uclid/UclidRunner.java | 22 ++++++++++++------- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/StateInfo.java b/org.lflang/src/org/lflang/generator/uclid/StateInfo.java index c01841ea14..0f2f4f3008 100644 --- a/org.lflang/src/org/lflang/generator/uclid/StateInfo.java +++ b/org.lflang/src/org/lflang/generator/uclid/StateInfo.java @@ -24,6 +24,6 @@ public void display() { System.out.println("tag: " + tag); System.out.println("variables: " + variables); System.out.println("triggers: " + triggers); - // System.out.println("scheduled: " + scheduled); + System.out.println("scheduled: " + scheduled); } } \ No newline at end of file diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 9915cb1cf9..ff408268cb 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -855,7 +855,7 @@ protected void generateTriggersAndReactions() { for (ReactionInstance.Runtime reaction : this.reactionInstances) { String str = String.join("\n", "// " + reaction.getReaction().getFullNameWithJoiner("_") + " is invoked when any of it triggers are present.", - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> ((", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END_TRACE) ==> ((", " false" ); diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidRunner.java b/org.lflang/src/org/lflang/generator/uclid/UclidRunner.java index aa2698fead..81bc78b433 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidRunner.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidRunner.java @@ -69,12 +69,15 @@ public UclidRunner( public StateInfo parseStateInfo(String smtStr) { StateInfo info = new StateInfo(); - // Split the counterexample string by newline. - String[] splitedSmtStr = smtStr.split("\\r?\\n"); + // Remove the outer tuple layer. + Pattern p = Pattern.compile("^\\(_tuple_\\d+((.|\\n)*)\\)$"); + Matcher m = p.matcher(smtStr.strip()); + m.find(); + String itemized = m.group(1).strip(); // Reactions - Pattern p = Pattern.compile("\\(_tuple_\\d+ \\(_tuple_\\d+ ([^\\)]+)\\)"); - Matcher m = p.matcher(splitedSmtStr[0].strip()); + p = Pattern.compile("\\(_tuple_\\d+([^\\)]+)\\)"); + m = p.matcher(itemized); m.find(); String[] reactions = m.group(1).strip().split("\\s+"); for (int i = 0; i < reactions.length; i++) { @@ -83,8 +86,6 @@ public StateInfo parseStateInfo(String smtStr) { } // Time tag - p = Pattern.compile("\\(_tuple_\\d+([^\\)]+)\\)"); - m = p.matcher(splitedSmtStr[1].strip()); m.find(); String[] tag = m.group(1).strip().split("\\s+"); info.tag = new Tag( @@ -92,7 +93,6 @@ public StateInfo parseStateInfo(String smtStr) { Long.parseLong(tag[1]), false); // Variables - m = p.matcher(splitedSmtStr[2].strip()); m.find(); String[] variables = m.group(1).strip().split("\\s+"); for (int i = 0; i < variables.length; i++) { @@ -100,13 +100,19 @@ public StateInfo parseStateInfo(String smtStr) { } // Triggers - m = p.matcher(splitedSmtStr[3].strip()); m.find(); String[] triggers = m.group(1).strip().split("\\s+"); for (int i = 0; i < triggers.length; i++) { info.triggers.put(generator.triggerInstances.get(i).getFullName(), triggers[i]); } + // Actions scheduled + m.find(); + String[] scheduled = m.group(1).strip().split("\\s+"); + for (int i = 0; i < scheduled.length; i++) { + info.scheduled.put(generator.actionInstances.get(i).getFullName(), scheduled[i]); + } + return info; } From 8d9d091ee6657fafce0f55752ba8060f428220ce Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 7 Dec 2022 15:24:51 -0800 Subject: [PATCH 064/516] Support timers --- .../generator/uclid/UclidGenerator.java | 112 ++++++++++++++++++ .../lflang/generator/uclid/UclidRunner.java | 10 +- 2 files changed, 118 insertions(+), 4 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index ff408268cb..33d9cc743e 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -844,6 +844,118 @@ protected void generateTriggersAndReactions() { } } + if (this.timerInstances.size() > 0) { + code.pr(String.join("\n", + "/**********", + " * Timers *", + " **********/" + )); + /** + * The timer axioms take the following form: + * + // An initial firing at {offset, 0} + axiom( + ((g(END)._1 >= 500000000) ==> ( + finite_exists (j : integer) in indices :: (j > START && j <= END) + && Timer_t_is_present(t(j)) + && tag_same(g(j), {500000000, 0}) + )) + && ((g(END)._1 < 500000000) ==> ( + finite_forall (i : integer) in indices :: (i > START && i <= END) + ==> (!isNULL(i)) + )) + ); + // Schedule subsequent firings. + axiom( + finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> ( + Timer_t_is_present(t(i)) ==> ( + ( + finite_exists (j : integer) in indices :: (j >= START && j > i) + && Timer_t_is_present(t(j)) + && (g(j) == tag_schedule(g(i), 1000000000)) + ) + ) + ) + ); + // All firings must be evenly spaced out. + axiom( + finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> ( + Timer_t_is_present(t(i)) ==> ( + // Timestamp must be offset + n * period + ( + exists (n : integer) :: ( + n >= 0 && + g(i)._1 == 500000000 + n * 1000000000 + ) + ) + // Microstep must be 0 + && ( + g(i)._2 == 0 + ) + ) + ) + ); + */ + for (var timer : this.timerInstances) { + long offset = timer.getOffset().toNanoSeconds(); + long period = timer.getPeriod().toNanoSeconds(); + + code.pr("//// Axioms for " + timer.getFullName()); + + // An initial firing at {offset, 0} + code.pr(String.join("\n", + "// " + timer.getFullName() + ": an initial firing at (" + offset + ", 0)", + "axiom(", + " ((pi1(g(END)) >= " + offset + ") ==> (", + " finite_exists (j : integer) in indices :: (j > START && j <= END)", + " && " + timer.getFullNameWithJoiner("_") + "_is_present(t(j))", + " && tag_same(g(j), {" + offset + ", 0})", + " ))", + " && ((pi1(g(END)) < " + offset + ") ==> (", + " finite_forall (i : integer) in indices :: (i > START && i <= END)", + " ==> (!isNULL(i))", + " ))", + ");" + )); + + // Schedule subsequent firings. + code.pr(String.join("\n", + "// " + timer.getFullName() + ": schedule subsequent firings every " + period + " ns", + "axiom(", + " finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> (", + " " + timer.getFullNameWithJoiner("_") + "_is_present(t(i)) ==> (", + " (", + " finite_exists (j : integer) in indices :: (j >= START && j > i)", + " && " + timer.getFullNameWithJoiner("_") + "_is_present(t(j))", + " && (g(j) == tag_schedule(g(i), " + period + "))", + " )", + " )", + " )", + ");" + )); + + // All firings must be evenly spaced out. + code.pr(String.join("\n", + "// " + timer.getFullName() + ": all firings must be evenly spaced out.", + "axiom(", + " finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> (", + " " + timer.getFullNameWithJoiner("_") + "_is_present(t(i)) ==> (", + " // Timestamp must be offset + n * period.", + " (", + " exists (n : integer) :: (", + " n >= 0 &&", + " pi1(g(i)) == " + offset + " + n * " + period, + " )", + " )", + " // Microstep must be 0.", + " && (pi2(g(i)) == 0)", + " )", + " )", + ");" + )); + } + } + code.pr(String.join("\n", "/********************************", " * Reactions and Their Triggers *", diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidRunner.java b/org.lflang/src/org/lflang/generator/uclid/UclidRunner.java index 81bc78b433..99dd20baa7 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidRunner.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidRunner.java @@ -80,7 +80,9 @@ public StateInfo parseStateInfo(String smtStr) { m = p.matcher(itemized); m.find(); String[] reactions = m.group(1).strip().split("\\s+"); - for (int i = 0; i < reactions.length; i++) { + // Iterating over generator lists avoids accounting for + // the single dummy Uclid variable inserted earlier. + for (int i = 0; i < generator.reactionInstances.size(); i++) { if (reactions[i].equals("true")) info.reactions.add(generator.reactionInstances.get(i).getReaction().getFullName()); } @@ -95,21 +97,21 @@ public StateInfo parseStateInfo(String smtStr) { // Variables m.find(); String[] variables = m.group(1).strip().split("\\s+"); - for (int i = 0; i < variables.length; i++) { + for (int i = 0; i < generator.namedInstances.size(); i++) { info.variables.put(generator.namedInstances.get(i).getFullName(), variables[i]); } // Triggers m.find(); String[] triggers = m.group(1).strip().split("\\s+"); - for (int i = 0; i < triggers.length; i++) { + for (int i = 0; i < generator.triggerInstances.size(); i++) { info.triggers.put(generator.triggerInstances.get(i).getFullName(), triggers[i]); } // Actions scheduled m.find(); String[] scheduled = m.group(1).strip().split("\\s+"); - for (int i = 0; i < scheduled.length; i++) { + for (int i = 0; i < generator.actionInstances.size(); i++) { info.scheduled.put(generator.actionInstances.get(i).getFullName(), scheduled[i]); } From 57eda6f2cb47bb0b9ca0dc7720eb896228010f65 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Tue, 13 Dec 2022 23:31:34 -0800 Subject: [PATCH 065/516] Update CT estimation, clean up println. --- .../generator/uclid/UclidGenerator.java | 37 +++++++++++-------- .../uclid/ast/BuildAstParseTreeVisitor.java | 2 +- org.lflang/src/org/lflang/sim/Event.java | 5 +++ .../org/lflang/sim/StateSpaceExplorer.java | 11 +++--- .../src/org/lflang/sim/StateSpaceNode.java | 27 +++++++++++++- 5 files changed, 58 insertions(+), 24 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java index 33d9cc743e..33faf9487e 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java @@ -1074,9 +1074,9 @@ protected void generateReactionAxioms() { )); for (ReactionInstance.Runtime reaction : this.reactionInstances) { - System.out.println("Printing reaction body of " + reaction); String body = reaction.getReaction().getDefinition().getCode().getBody(); - System.out.println(body); + // System.out.println("Printing reaction body of " + reaction); + // System.out.println(body); // Generate a parse tree. CLexer lexer = new CLexer(CharStreams.fromString(body)); @@ -1454,27 +1454,22 @@ private void computeCT() { this.CT = (diagram.loopNode.index + 1) + (diagram.tail.index - diagram.loopNode.index + 1) * loopIterations; } else { - // Get the number of events before the loop. + // Get the number of events before the loop starts. // This stops right before the loopNode is encountered. StateSpaceNode node = diagram.head; int numReactionInvocationsBeforeLoop = 0; while (node != diagram.loopNode) { numReactionInvocationsBeforeLoop += node.reactionsInvoked.size(); - Set downstreamNodes = diagram.getDownstreamAdjacentNodes(node); - for (StateSpaceNode n : downstreamNodes) { - node = n; // Go to the next node. - break; - } + node = getDownstreamNode(diagram, node); } - int numReactionInvocationsInsideLoop = node.reactionsInvoked.size(); - while (node != diagram.loopNode) { - Set downstreamNodes = diagram.getDownstreamAdjacentNodes(node); - for (StateSpaceNode n : downstreamNodes) { - node = n; // Go to the next node. - break; - } + + // Count the events from the loop node until + // loop node is reached again. + int numReactionInvocationsInsideLoop = 0; + do { + node = getDownstreamNode(diagram, node); numReactionInvocationsInsideLoop += node.reactionsInvoked.size(); - } + } while (node != diagram.loopNode); // CT = steps required for the non-periodic part // + steps required for the periodic part @@ -1501,6 +1496,16 @@ private void processMTLSpec() { this.horizon = visitor.getHorizon(); } + /** + * Get the immediately downstream node. + * FIXME: Check if there are multiple downstream nodes. + */ + private StateSpaceNode getDownstreamNode(StateSpaceDiagram diagram, StateSpaceNode node) { + Set downstreamNodes = diagram.getDownstreamAdjacentNodes(node); + Iterator iterator = downstreamNodes.iterator(); + return iterator.next(); + } + ///////////////////////////////////////////////// //// Functions from generatorBase diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/BuildAstParseTreeVisitor.java b/org.lflang/src/org/lflang/generator/uclid/ast/BuildAstParseTreeVisitor.java index da9cc4f33d..18fc25e2f4 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/BuildAstParseTreeVisitor.java +++ b/org.lflang/src/org/lflang/generator/uclid/ast/BuildAstParseTreeVisitor.java @@ -19,7 +19,7 @@ public CAst.AstNode visitBlockItemList(BlockItemListContext ctx) { stmtSeq.children.add(visit(blockItem)); } - System.out.println(stmtSeq.children); + // System.out.println(stmtSeq.children); return stmtSeq; } diff --git a/org.lflang/src/org/lflang/sim/Event.java b/org.lflang/src/org/lflang/sim/Event.java index ff96b4d3c2..7e2cee20da 100644 --- a/org.lflang/src/org/lflang/sim/Event.java +++ b/org.lflang/src/org/lflang/sim/Event.java @@ -37,4 +37,9 @@ public boolean equals(Object o) { } return false; } + + @Override + public String toString() { + return "(" + trigger.getFullName() + ", " + tag + ")"; + } } \ No newline at end of file diff --git a/org.lflang/src/org/lflang/sim/StateSpaceExplorer.java b/org.lflang/src/org/lflang/sim/StateSpaceExplorer.java index 623a2c40a1..58d6363de8 100644 --- a/org.lflang/src/org/lflang/sim/StateSpaceExplorer.java +++ b/org.lflang/src/org/lflang/sim/StateSpaceExplorer.java @@ -100,7 +100,7 @@ public void explore(Tag horizon, boolean findLoop) { // FIXME: It seems that we need to handle shutdown triggers // separately, because they could break the back loop. addInitialEvents(this.main); - System.out.println(this.eventQ); + // System.out.println(this.eventQ); Tag previousTag = null; // Tag in the previous loop ITERATION Tag currentTag = null; // Tag in the current loop ITERATION @@ -126,7 +126,7 @@ public void explore(Tag horizon, boolean findLoop) { currentEvents.add(e); // System.out.println("Adding event to currentEvents: " + e); } - System.out.println(currentEvents); + // System.out.println(currentEvents); // Collect all the reactions invoked in this current LOOP ITERATION // triggered by the earliest events. @@ -230,11 +230,14 @@ else if (effect instanceof ActionInstance) { // If findLoop is true, check for loops. // FIXME: For some reason, the below doesn't work. // if (findLoop && diagram.hasNode(node)) { + // + // The current implementation does not scale! + // Perhaps there exists a faster implementation + // using hashmaps. if (findLoop) { for (StateSpaceNode n : diagram.nodes()) { if (n.equals(node)) { loopFound = true; - System.out.println("*** A loop is found!"); // Mark the loop in the diagram. this.diagram.loopNode = n; this.diagram.tail = currentNode; @@ -248,8 +251,6 @@ else if (effect instanceof ActionInstance) { // Add the new node to the state space diagram. this.diagram.addNode(node); - System.out.println("Adding a new node to the diagram."); - node.display(); // If the head is not empty, add an edge from the previous state // to the next state. Otherwise initialize the head to the new node. diff --git a/org.lflang/src/org/lflang/sim/StateSpaceNode.java b/org.lflang/src/org/lflang/sim/StateSpaceNode.java index d33fd99e41..11cfa9e109 100644 --- a/org.lflang/src/org/lflang/sim/StateSpaceNode.java +++ b/org.lflang/src/org/lflang/sim/StateSpaceNode.java @@ -12,7 +12,7 @@ public class StateSpaceNode { - public int index; + public int index; // Set in StateSpaceDiagram.java public Tag tag; public ArrayList reactionsInvoked; public ArrayList eventQ; @@ -27,6 +27,25 @@ public StateSpaceNode( this.reactionsInvoked = reactionsInvoked; } + /** + * Assuming both eventQs have the same length, + * for each pair of events in eventQ1 and eventQ2, + * check if the time distances between the node's tag + * and the two events' tags are equal. + */ + private boolean equidistant(StateSpaceNode n1, + StateSpaceNode n2) { + if (n1.eventQ.size() != n2.eventQ.size()) + return false; + for (int i = 0; i < n1.eventQ.size(); i++) { + if (n1.eventQ.get(i).tag.timestamp - n1.tag.timestamp + != n2.eventQ.get(i).tag.timestamp - n2.tag.timestamp) { + return false; + } + } + return true; + } + /** * This equals method does NOT compare tags, * only compares reactionsInvoked and eventQ. @@ -37,7 +56,8 @@ public boolean equals(Object o) { if (o instanceof StateSpaceNode) { StateSpaceNode node = (StateSpaceNode) o; if (this.reactionsInvoked.equals(node.reactionsInvoked) - && this.eventQ.equals(node.eventQ)) + && this.eventQ.equals(node.eventQ) + && equidistant(this, node)) return true; } return false; @@ -47,4 +67,7 @@ public void display() { System.out.println("(" + tag + ", " + reactionsInvoked + ", " + eventQ + ")"); } + public String toString() { + return "(" + tag + ", " + reactionsInvoked + ", " + eventQ + ")"; + } } \ No newline at end of file From 942321293c0e4664002e061f780ec03240d69420 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 14 Dec 2022 08:30:27 -0800 Subject: [PATCH 066/516] Refactor uclid, C ast, and state space into an analyses package. --- .../cast}/AbstractAstVisitor.java | 2 +- .../ast => analyses/cast}/AstVisitor.java | 2 +- .../cast}/BuildAstParseTreeVisitor.java | 2 +- .../uclid/ast => analyses/cast}/CAst.java | 2 +- .../ast => analyses/cast}/CAstUtils.java | 2 +- .../ast => analyses/cast}/CAstVisitor.java | 2 +- .../cast}/CBaseAstVisitor.java | 2 +- .../cast}/CToUclidVisitor.java | 8 +++--- .../cast}/IfNormalFormAstVisitor.java | 2 +- .../cast}/VariablePrecedenceVisitor.java | 4 +-- .../ast => analyses/cast}/Visitable.java | 2 +- .../{sim => analyses/statespace}/Event.java | 2 +- .../statespace}/StateInfo.java | 4 +-- .../statespace}/StateSpaceDiagram.java | 2 +- .../statespace}/StateSpaceExplorer.java | 5 ++-- .../statespace}/StateSpaceNode.java | 2 +- .../{sim => analyses/statespace}/Tag.java | 2 +- .../uclid/MTLVisitor.java | 2 +- .../uclid/UclidGenerator.java | 26 +++++++++---------- .../uclid/UclidRunner.java | 5 ++-- .../src/org/lflang/generator/LFGenerator.java | 4 +-- 21 files changed, 41 insertions(+), 43 deletions(-) rename org.lflang/src/org/lflang/{generator/uclid/ast => analyses/cast}/AbstractAstVisitor.java (90%) rename org.lflang/src/org/lflang/{generator/uclid/ast => analyses/cast}/AstVisitor.java (94%) rename org.lflang/src/org/lflang/{generator/uclid/ast => analyses/cast}/BuildAstParseTreeVisitor.java (99%) rename org.lflang/src/org/lflang/{generator/uclid/ast => analyses/cast}/CAst.java (99%) rename org.lflang/src/org/lflang/{generator/uclid/ast => analyses/cast}/CAstUtils.java (97%) rename org.lflang/src/org/lflang/{generator/uclid/ast => analyses/cast}/CAstVisitor.java (98%) rename org.lflang/src/org/lflang/{generator/uclid/ast => analyses/cast}/CBaseAstVisitor.java (99%) rename org.lflang/src/org/lflang/{generator/uclid/ast => analyses/cast}/CToUclidVisitor.java (98%) rename org.lflang/src/org/lflang/{generator/uclid/ast => analyses/cast}/IfNormalFormAstVisitor.java (98%) rename org.lflang/src/org/lflang/{generator/uclid/ast => analyses/cast}/VariablePrecedenceVisitor.java (94%) rename org.lflang/src/org/lflang/{generator/uclid/ast => analyses/cast}/Visitable.java (89%) rename org.lflang/src/org/lflang/{sim => analyses/statespace}/Event.java (96%) rename org.lflang/src/org/lflang/{generator/uclid => analyses/statespace}/StateInfo.java (92%) rename org.lflang/src/org/lflang/{sim => analyses/statespace}/StateSpaceDiagram.java (98%) rename org.lflang/src/org/lflang/{sim => analyses/statespace}/StateSpaceExplorer.java (98%) rename org.lflang/src/org/lflang/{sim => analyses/statespace}/StateSpaceNode.java (98%) rename org.lflang/src/org/lflang/{sim => analyses/statespace}/Tag.java (97%) rename org.lflang/src/org/lflang/{generator => analyses}/uclid/MTLVisitor.java (99%) rename org.lflang/src/org/lflang/{generator => analyses}/uclid/UclidGenerator.java (99%) rename org.lflang/src/org/lflang/{generator => analyses}/uclid/UclidRunner.java (97%) diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/AbstractAstVisitor.java b/org.lflang/src/org/lflang/analyses/cast/AbstractAstVisitor.java similarity index 90% rename from org.lflang/src/org/lflang/generator/uclid/ast/AbstractAstVisitor.java rename to org.lflang/src/org/lflang/analyses/cast/AbstractAstVisitor.java index a31a61b199..c3483e2d3e 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/AbstractAstVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/AbstractAstVisitor.java @@ -1,4 +1,4 @@ -package org.lflang.generator.uclid.ast; +package org.lflang.analyses.cast; import java.util.List; diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/AstVisitor.java b/org.lflang/src/org/lflang/analyses/cast/AstVisitor.java similarity index 94% rename from org.lflang/src/org/lflang/generator/uclid/ast/AstVisitor.java rename to org.lflang/src/org/lflang/analyses/cast/AstVisitor.java index bc6910a670..47861e303b 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/AstVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/AstVisitor.java @@ -1,4 +1,4 @@ -package org.lflang.generator.uclid.ast; +package org.lflang.analyses.cast; import java.util.List; diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/BuildAstParseTreeVisitor.java b/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java similarity index 99% rename from org.lflang/src/org/lflang/generator/uclid/ast/BuildAstParseTreeVisitor.java rename to org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java index 18fc25e2f4..af3f02d26c 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/BuildAstParseTreeVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java @@ -1,4 +1,4 @@ -package org.lflang.generator.uclid.ast; +package org.lflang.analyses.cast; import java.util.ArrayList; import java.util.Arrays; diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CAst.java b/org.lflang/src/org/lflang/analyses/cast/CAst.java similarity index 99% rename from org.lflang/src/org/lflang/generator/uclid/ast/CAst.java rename to org.lflang/src/org/lflang/analyses/cast/CAst.java index 56526901e3..1da9b3994a 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/CAst.java +++ b/org.lflang/src/org/lflang/analyses/cast/CAst.java @@ -1,4 +1,4 @@ -package org.lflang.generator.uclid.ast; +package org.lflang.analyses.cast; import java.util.ArrayList; import java.util.List; diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CAstUtils.java b/org.lflang/src/org/lflang/analyses/cast/CAstUtils.java similarity index 97% rename from org.lflang/src/org/lflang/generator/uclid/ast/CAstUtils.java rename to org.lflang/src/org/lflang/analyses/cast/CAstUtils.java index ac40caed23..36b749e397 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/CAstUtils.java +++ b/org.lflang/src/org/lflang/analyses/cast/CAstUtils.java @@ -1,4 +1,4 @@ -package org.lflang.generator.uclid.ast; +package org.lflang.analyses.cast; import java.util.List; diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CAstVisitor.java b/org.lflang/src/org/lflang/analyses/cast/CAstVisitor.java similarity index 98% rename from org.lflang/src/org/lflang/generator/uclid/ast/CAstVisitor.java rename to org.lflang/src/org/lflang/analyses/cast/CAstVisitor.java index f6efbb7946..1fadea44d5 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/CAstVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/CAstVisitor.java @@ -1,4 +1,4 @@ -package org.lflang.generator.uclid.ast; +package org.lflang.analyses.cast; import java.util.List; diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CBaseAstVisitor.java b/org.lflang/src/org/lflang/analyses/cast/CBaseAstVisitor.java similarity index 99% rename from org.lflang/src/org/lflang/generator/uclid/ast/CBaseAstVisitor.java rename to org.lflang/src/org/lflang/analyses/cast/CBaseAstVisitor.java index 210bf06537..5273a0bc0d 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/CBaseAstVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/CBaseAstVisitor.java @@ -1,4 +1,4 @@ -package org.lflang.generator.uclid.ast; +package org.lflang.analyses.cast; import java.util.List; diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java b/org.lflang/src/org/lflang/analyses/cast/CToUclidVisitor.java similarity index 98% rename from org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java rename to org.lflang/src/org/lflang/analyses/cast/CToUclidVisitor.java index b0bb4e9230..88d73b3278 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/CToUclidVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/CToUclidVisitor.java @@ -1,10 +1,8 @@ -package org.lflang.generator.uclid.ast; +package org.lflang.analyses.cast; import java.util.ArrayList; import java.util.List; -import javax.swing.Action; - import org.lflang.generator.ActionInstance; import org.lflang.generator.NamedInstance; import org.lflang.generator.PortInstance; @@ -12,8 +10,8 @@ import org.lflang.generator.ReactorInstance; import org.lflang.generator.StateVariableInstance; import org.lflang.generator.TriggerInstance; -import org.lflang.generator.uclid.UclidGenerator; -import org.lflang.generator.uclid.ast.CAst.*; +import org.lflang.analyses.uclid.UclidGenerator; +import org.lflang.analyses.cast.CAst.*; public class CToUclidVisitor extends CBaseAstVisitor { diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/IfNormalFormAstVisitor.java b/org.lflang/src/org/lflang/analyses/cast/IfNormalFormAstVisitor.java similarity index 98% rename from org.lflang/src/org/lflang/generator/uclid/ast/IfNormalFormAstVisitor.java rename to org.lflang/src/org/lflang/analyses/cast/IfNormalFormAstVisitor.java index 18bfe0313d..bde7337565 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/IfNormalFormAstVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/IfNormalFormAstVisitor.java @@ -1,4 +1,4 @@ -package org.lflang.generator.uclid.ast; +package org.lflang.analyses.cast; import java.util.ArrayList; import java.util.List; diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/VariablePrecedenceVisitor.java b/org.lflang/src/org/lflang/analyses/cast/VariablePrecedenceVisitor.java similarity index 94% rename from org.lflang/src/org/lflang/generator/uclid/ast/VariablePrecedenceVisitor.java rename to org.lflang/src/org/lflang/analyses/cast/VariablePrecedenceVisitor.java index 104a871d78..bada1be519 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/VariablePrecedenceVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/VariablePrecedenceVisitor.java @@ -1,9 +1,9 @@ -package org.lflang.generator.uclid.ast; +package org.lflang.analyses.cast; import java.util.ArrayList; import java.util.List; -import org.lflang.generator.uclid.ast.CAst.*; +import org.lflang.analyses.cast.CAst.*; /** * This visitor marks certain variable node as "previous." diff --git a/org.lflang/src/org/lflang/generator/uclid/ast/Visitable.java b/org.lflang/src/org/lflang/analyses/cast/Visitable.java similarity index 89% rename from org.lflang/src/org/lflang/generator/uclid/ast/Visitable.java rename to org.lflang/src/org/lflang/analyses/cast/Visitable.java index c7735057e0..835894f06b 100644 --- a/org.lflang/src/org/lflang/generator/uclid/ast/Visitable.java +++ b/org.lflang/src/org/lflang/analyses/cast/Visitable.java @@ -1,4 +1,4 @@ -package org.lflang.generator.uclid.ast; +package org.lflang.analyses.cast; import java.util.List; diff --git a/org.lflang/src/org/lflang/sim/Event.java b/org.lflang/src/org/lflang/analyses/statespace/Event.java similarity index 96% rename from org.lflang/src/org/lflang/sim/Event.java rename to org.lflang/src/org/lflang/analyses/statespace/Event.java index 7e2cee20da..0ef60fad49 100644 --- a/org.lflang/src/org/lflang/sim/Event.java +++ b/org.lflang/src/org/lflang/analyses/statespace/Event.java @@ -4,7 +4,7 @@ * * @author{Shaokai Lin } */ -package org.lflang.sim; +package org.lflang.analyses.statespace; import org.lflang.generator.TriggerInstance; diff --git a/org.lflang/src/org/lflang/generator/uclid/StateInfo.java b/org.lflang/src/org/lflang/analyses/statespace/StateInfo.java similarity index 92% rename from org.lflang/src/org/lflang/generator/uclid/StateInfo.java rename to org.lflang/src/org/lflang/analyses/statespace/StateInfo.java index 0f2f4f3008..badbf25355 100644 --- a/org.lflang/src/org/lflang/generator/uclid/StateInfo.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateInfo.java @@ -4,13 +4,11 @@ * * @author{Shaokai Lin } */ -package org.lflang.generator.uclid; +package org.lflang.analyses.statespace; import java.util.ArrayList; import java.util.HashMap; -import org.lflang.sim.Tag; - public class StateInfo { public ArrayList reactions = new ArrayList<>(); diff --git a/org.lflang/src/org/lflang/sim/StateSpaceDiagram.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java similarity index 98% rename from org.lflang/src/org/lflang/sim/StateSpaceDiagram.java rename to org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java index 10bcccb505..d1fc5bc0be 100644 --- a/org.lflang/src/org/lflang/sim/StateSpaceDiagram.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java @@ -3,7 +3,7 @@ * * @author{Shaokai Lin } */ -package org.lflang.sim; +package org.lflang.analyses.statespace; import java.util.Set; diff --git a/org.lflang/src/org/lflang/sim/StateSpaceExplorer.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java similarity index 98% rename from org.lflang/src/org/lflang/sim/StateSpaceExplorer.java rename to org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java index 58d6363de8..c48a9a6fbc 100644 --- a/org.lflang/src/org/lflang/sim/StateSpaceExplorer.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java @@ -1,7 +1,7 @@ /** * Explores the state space of an LF program. */ -package org.lflang.sim; +package org.lflang.analyses.statespace; import java.util.ArrayList; import java.util.PriorityQueue; @@ -9,7 +9,8 @@ import org.lflang.TimeUnit; import org.lflang.TimeValue; - +import org.lflang.analyses.statespace.Event; +import org.lflang.analyses.statespace.Tag; import org.lflang.generator.ActionInstance; import org.lflang.generator.PortInstance; import org.lflang.generator.ReactionInstance; diff --git a/org.lflang/src/org/lflang/sim/StateSpaceNode.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java similarity index 98% rename from org.lflang/src/org/lflang/sim/StateSpaceNode.java rename to org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java index 11cfa9e109..5dcaaa011e 100644 --- a/org.lflang/src/org/lflang/sim/StateSpaceNode.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java @@ -4,7 +4,7 @@ * * @author{Shaokai Lin } */ -package org.lflang.sim; +package org.lflang.analyses.statespace; import java.util.ArrayList; diff --git a/org.lflang/src/org/lflang/sim/Tag.java b/org.lflang/src/org/lflang/analyses/statespace/Tag.java similarity index 97% rename from org.lflang/src/org/lflang/sim/Tag.java rename to org.lflang/src/org/lflang/analyses/statespace/Tag.java index 648acc9b70..dcf3103903 100644 --- a/org.lflang/src/org/lflang/sim/Tag.java +++ b/org.lflang/src/org/lflang/analyses/statespace/Tag.java @@ -5,7 +5,7 @@ * * @author{Shaokai Lin } */ -package org.lflang.sim; +package org.lflang.analyses.statespace; public class Tag implements Comparable { diff --git a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java similarity index 99% rename from org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java rename to org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java index e10cf6eec6..43c546a097 100644 --- a/org.lflang/src/org/lflang/generator/uclid/MTLVisitor.java +++ b/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java @@ -27,7 +27,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * * @author{Shaokai Lin } */ -package org.lflang.generator.uclid; +package org.lflang.analyses.uclid; import org.lflang.TimeUnit; import org.lflang.TimeValue; diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java similarity index 99% rename from org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java rename to org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java index 33faf9487e..6d0692cae5 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java @@ -27,7 +27,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * * @author{Shaokai Lin } */ -package org.lflang.generator.uclid; +package org.lflang.analyses.uclid; import java.io.File; import java.io.IOException; @@ -58,6 +58,18 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.Target; import org.lflang.TimeUnit; import org.lflang.TimeValue; +import org.lflang.analyses.cast.BuildAstParseTreeVisitor; +import org.lflang.analyses.cast.CAst; +import org.lflang.analyses.cast.CAstUtils; +import org.lflang.analyses.cast.CBaseAstVisitor; +import org.lflang.analyses.cast.CToUclidVisitor; +import org.lflang.analyses.cast.IfNormalFormAstVisitor; +import org.lflang.analyses.cast.VariablePrecedenceVisitor; +import org.lflang.analyses.statespace.Event; +import org.lflang.analyses.statespace.StateSpaceDiagram; +import org.lflang.analyses.statespace.StateSpaceExplorer; +import org.lflang.analyses.statespace.StateSpaceNode; +import org.lflang.analyses.statespace.Tag; import org.lflang.dsl.CLexer; import org.lflang.dsl.CParser; import org.lflang.dsl.MTLLexer; @@ -81,13 +93,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.c.CGenerator; -import org.lflang.generator.uclid.ast.BuildAstParseTreeVisitor; -import org.lflang.generator.uclid.ast.CAst; -import org.lflang.generator.uclid.ast.CAstUtils; -import org.lflang.generator.uclid.ast.CBaseAstVisitor; -import org.lflang.generator.uclid.ast.CToUclidVisitor; -import org.lflang.generator.uclid.ast.IfNormalFormAstVisitor; -import org.lflang.generator.uclid.ast.VariablePrecedenceVisitor; import org.lflang.lf.Action; import org.lflang.lf.Attribute; import org.lflang.lf.Code; @@ -96,11 +101,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.Reaction; import org.lflang.lf.Time; import org.lflang.lf.VarRef; -import org.lflang.sim.Event; -import org.lflang.sim.StateSpaceDiagram; -import org.lflang.sim.StateSpaceExplorer; -import org.lflang.sim.StateSpaceNode; -import org.lflang.sim.Tag; import org.lflang.util.StringUtil; import static org.lflang.ASTUtils.*; diff --git a/org.lflang/src/org/lflang/generator/uclid/UclidRunner.java b/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java similarity index 97% rename from org.lflang/src/org/lflang/generator/uclid/UclidRunner.java rename to org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java index 99dd20baa7..f3646245e0 100644 --- a/org.lflang/src/org/lflang/generator/uclid/UclidRunner.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java @@ -3,7 +3,7 @@ * * @author{Shaokai Lin } */ -package org.lflang.generator.uclid; +package org.lflang.analyses.uclid; import java.io.IOException; import java.io.OutputStream; @@ -25,8 +25,9 @@ import org.lflang.ErrorReporter; import org.lflang.FileConfig; +import org.lflang.analyses.statespace.StateInfo; +import org.lflang.analyses.statespace.Tag; import org.lflang.generator.GeneratorCommandFactory; -import org.lflang.sim.Tag; import org.lflang.util.LFCommand; public class UclidRunner { diff --git a/org.lflang/src/org/lflang/generator/LFGenerator.java b/org.lflang/src/org/lflang/generator/LFGenerator.java index 8be0731a62..0c17297173 100644 --- a/org.lflang/src/org/lflang/generator/LFGenerator.java +++ b/org.lflang/src/org/lflang/generator/LFGenerator.java @@ -20,10 +20,10 @@ import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.Target; +import org.lflang.analyses.uclid.UclidGenerator; +import org.lflang.analyses.uclid.UclidRunner; import org.lflang.generator.c.CGenerator; import org.lflang.generator.python.PythonGenerator; -import org.lflang.generator.uclid.UclidGenerator; -import org.lflang.generator.uclid.UclidRunner; import org.lflang.lf.Attribute; import org.lflang.lf.Reactor; import org.lflang.scoping.LFGlobalScopeProvider; From 72eb050c1516660f30d7807459e9516b0cf50f56 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 15 Dec 2022 14:53:27 -0800 Subject: [PATCH 067/516] Make loop detection more efficient --- .../org/lflang/analyses/statespace/Event.java | 4 + .../statespace/StateSpaceDiagram.java | 2 + .../statespace/StateSpaceExplorer.java | 83 ++++++++++--------- .../analyses/statespace/StateSpaceNode.java | 47 +++++++++-- .../lflang/analyses/uclid/UclidGenerator.java | 26 +++--- 5 files changed, 107 insertions(+), 55 deletions(-) diff --git a/org.lflang/src/org/lflang/analyses/statespace/Event.java b/org.lflang/src/org/lflang/analyses/statespace/Event.java index 0ef60fad49..ef0f004e4f 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/Event.java +++ b/org.lflang/src/org/lflang/analyses/statespace/Event.java @@ -17,6 +17,10 @@ public Event(TriggerInstance trigger, Tag tag) { this.trigger = trigger; this.tag = tag; } + + public TriggerInstance getTrigger() { + return this.trigger; + } @Override public int compareTo(Event e) { diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java index d1fc5bc0be..9fd0c1e8b9 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java @@ -67,6 +67,8 @@ public void display() { } else break; } + if (this.loopNode != null) + System.out.println("* Loop node: state " + this.loopNode.index); System.out.println("*************************************************"); } diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java index c48a9a6fbc..0a87b6bece 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java @@ -3,7 +3,10 @@ */ package org.lflang.analyses.statespace; +import java.lang.Integer; + import java.util.ArrayList; +import java.util.HashMap; import java.util.PriorityQueue; import java.util.Set; @@ -103,9 +106,11 @@ public void explore(Tag horizon, boolean findLoop) { addInitialEvents(this.main); // System.out.println(this.eventQ); - Tag previousTag = null; // Tag in the previous loop ITERATION - Tag currentTag = null; // Tag in the current loop ITERATION - StateSpaceNode currentNode = null; + Tag previousTag = null; // Tag in the previous loop ITERATION + Tag currentTag = null; // Tag in the current loop ITERATION + StateSpaceNode currentNode = null; + StateSpaceNode previousNode = null; + HashMap uniqueNodes = new HashMap<>(); boolean stop = true; if (this.eventQ.size() > 0) { stop = false; @@ -214,6 +219,42 @@ else if (effect instanceof ActionInstance) { previousTag == null // The first iteration || currentTag.compareTo(previousTag) > 0 ) { + if (previousTag != null) { + // Whenever we finish a tag, check for loops fist. + // If currentNode matches an existing node in uniqueNodes, + // duplicate is set to the existing node. + StateSpaceNode duplicate; + if (findLoop && + (duplicate = uniqueNodes.put( + currentNode.hashCode(), currentNode)) != null) { + + // Mark the loop in the diagram. + loopFound = true; + this.diagram.loopNode = duplicate; + this.diagram.tail = previousNode; + this.diagram.loopPeriod = this.diagram.tail.tag.timestamp + - this.diagram.loopNode.tag.timestamp; + this.diagram.addEdge(this.diagram.loopNode, this.diagram.tail); + System.out.println("LoopNode index " + this.diagram.loopNode.index); // Why is this 5? + return; // Exit the while loop early. + } + + // Now we are at a new tag, and a loop is not found, + // add the node to the state space diagram. + this.diagram.addNode(currentNode); + + // If the head is not empty, add an edge from the previous state + // to the next state. Otherwise initialize the head to the new node. + if (this.diagram.head != null && currentNode != null) { + // System.out.println("--- Add a new edge between " + currentNode + " and " + node); + this.diagram.addEdge(currentNode, previousNode); // Sink first, then source + } + else + this.diagram.head = currentNode; // Initialize the head. + } + + //// Now we are done with the node at the previous tag, + //// work on the new node at the current timestamp. // Copy the reactions in reactionsTemp. reactionsInvoked = new ArrayList(reactionsTemp); @@ -227,41 +268,9 @@ else if (effect instanceof ActionInstance) { reactionsInvoked, // Reactions invoked at this tag new ArrayList(eventQ) // A snapshot of the event queue ); - - // If findLoop is true, check for loops. - // FIXME: For some reason, the below doesn't work. - // if (findLoop && diagram.hasNode(node)) { - // - // The current implementation does not scale! - // Perhaps there exists a faster implementation - // using hashmaps. - if (findLoop) { - for (StateSpaceNode n : diagram.nodes()) { - if (n.equals(node)) { - loopFound = true; - // Mark the loop in the diagram. - this.diagram.loopNode = n; - this.diagram.tail = currentNode; - this.diagram.loopPeriod = currentTag.timestamp - - this.diagram.loopNode.tag.timestamp; - this.diagram.addEdge(this.diagram.loopNode, this.diagram.tail); - return; // Exit the while loop early. - } - } - } - - // Add the new node to the state space diagram. - this.diagram.addNode(node); - - // If the head is not empty, add an edge from the previous state - // to the next state. Otherwise initialize the head to the new node. - if (this.diagram.head != null && currentNode != null) { - // System.out.println("--- Add a new edge between " + currentNode + " and " + node); - this.diagram.addEdge(node, currentNode); // Sink first, then source - } - else - this.diagram.head = node; // Initialize the head. + // Update the previous node. + previousNode = currentNode; // Update the current node. currentNode = node; } diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java index 5dcaaa011e..2ac3ef1533 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java @@ -7,8 +7,11 @@ package org.lflang.analyses.statespace; import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; import org.lflang.generator.ReactionInstance; +import org.lflang.generator.TriggerInstance; public class StateSpaceNode { @@ -46,9 +49,20 @@ private boolean equidistant(StateSpaceNode n1, return true; } + /** + * Two methods for pretty printing + */ + public void display() { + System.out.println("(" + tag + ", " + reactionsInvoked + ", " + eventQ + ")"); + } + public String toString() { + return "(" + tag + ", " + reactionsInvoked + ", " + eventQ + ")"; + } + /** * This equals method does NOT compare tags, - * only compares reactionsInvoked and eventQ. + * only compares reactionsInvoked, eventQ, + * and whether future events are equally distant. */ @Override public boolean equals(Object o) { @@ -63,11 +77,30 @@ && equidistant(this, node)) return false; } - public void display() { - System.out.println("(" + tag + ", " + reactionsInvoked + ", " + eventQ + ")"); - } - - public String toString() { - return "(" + tag + ", " + reactionsInvoked + ", " + eventQ + ")"; + /** + * Generate hash code for the node. + */ + @Override + public int hashCode() { + // Initial value + int result = 17; + + // Generate hash for the reactions invoked. + result = 31 * result + reactionsInvoked.hashCode(); + + // Generate hash for the triggers in the queued events. + List eventNames = this.eventQ.stream() + .map(Event::getTrigger) + .map(TriggerInstance::getFullName) + .collect(Collectors.toList()); + result = 31 * result + eventNames.hashCode(); + + // Generate hash for the time differences. + List timeDiff = this.eventQ.stream().map(e -> { + return e.tag.timestamp - this.tag.timestamp; + }).collect(Collectors.toList()); + result = 31 * result + timeDiff.hashCode(); + + return result; } } \ No newline at end of file diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java index 6d0692cae5..8ba5605a33 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java @@ -165,6 +165,7 @@ public class UclidGenerator extends GeneratorBase { protected long horizon = 0; // in nanoseconds protected String FOLSpec = ""; protected int CT = 0; + protected static final int CT_MAX_SUPPORTED = 20; // Constructor public UclidGenerator(FileConfig fileConfig, ErrorReporter errorReporter, List properties) { @@ -184,7 +185,6 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { super.printInfo(context.getMode()); ASTUtils.setMainName(fileConfig.resource, fileConfig.name); // FIXME: Perform an analysis on the property and remove unrelevant components. - // FIXME: Support multiple properties here too. We might need to have a UclidGenerator for each attribute. super.createMainInstantiation(); //////////////////////////////////////// @@ -198,7 +198,7 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { populateDataStructures(); // Create the src-gen directory - setUpDirectories(); + setupDirectories(); // Generate a Uclid model for each property. for (Attribute prop : this.properties) { @@ -222,12 +222,17 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { .getValue()); processMTLSpec(); + computeCT(); + if (this.CT > CT_MAX_SUPPORTED) { + System.out.println("ERROR: The maximum steps supported is " + CT_MAX_SUPPORTED + + " but checking this property requires " + this.CT + " steps. " + + "This property will NOT be checked."); + continue; + } + generateUclidFile(); } - - // Generate runner script - // generateRunnerScript(); } //////////////////////////////////////////////////////////// @@ -1339,7 +1344,7 @@ private void createMainReactorInstance() { } } - private void setUpDirectories() { + private void setupDirectories() { // Make sure the target directory exists. Path modelGenDir = this.fileConfig.getModelGenPath(); this.outputDir = Paths.get(modelGenDir.toString()); @@ -1433,7 +1438,6 @@ private void computeCT() { this.CT += node.reactionsInvoked.size(); } } - System.out.println("*** A loop is NOT found."); System.out.println("CT: " + this.CT); } // Over-approximate CT by estimating the number of loop iterations required. @@ -1476,7 +1480,6 @@ private void computeCT() { this.CT = numReactionInvocationsBeforeLoop + numReactionInvocationsInsideLoop * loopIterations; } - System.out.println("*** A loop is found!"); System.out.println("CT: " + this.CT); } } @@ -1501,9 +1504,10 @@ private void processMTLSpec() { * FIXME: Check if there are multiple downstream nodes. */ private StateSpaceNode getDownstreamNode(StateSpaceDiagram diagram, StateSpaceNode node) { - Set downstreamNodes = diagram.getDownstreamAdjacentNodes(node); - Iterator iterator = downstreamNodes.iterator(); - return iterator.next(); + Set downstream = diagram.getDownstreamAdjacentNodes(node); + if (downstream == null || downstream.size() == 0) + System.out.println("There are no downstream nodes!"); + return (StateSpaceNode)downstream.toArray()[0]; } ///////////////////////////////////////////////// From 65560abae56eb633b258e36eb5cf9914d5284ada Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 15 Dec 2022 23:30:57 -0800 Subject: [PATCH 068/516] Fix loop period, generate dot file from state space diagram. --- .../statespace/StateSpaceDiagram.java | 98 ++++++++++++++++--- .../statespace/StateSpaceExplorer.java | 6 +- .../lflang/analyses/uclid/UclidGenerator.java | 30 +++--- 3 files changed, 107 insertions(+), 27 deletions(-) diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java index 9fd0c1e8b9..958231844b 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java @@ -7,6 +7,7 @@ import java.util.Set; +import org.lflang.generator.CodeBuilder; import org.lflang.graph.DirectedGraph; // FIXME: Use a linkedlist instead. @@ -28,26 +29,42 @@ public class StateSpaceDiagram extends DirectedGraph { */ public StateSpaceNode loopNode; + /** + * Store the state when the loop node is reached for + * the 2nd time. This is used to calculate the elapsed + * logical time on the back loop edge. + */ + public StateSpaceNode loopNodeNext; + /** * The logical time elapsed for each loop iteration. */ public long loopPeriod; /** - * The length of the state space diagram (not counting the loop) + * A dot file that represents the diagram */ - public int length; + private CodeBuilder dot; /** * Before adding the node, assign it an index. */ @Override public void addNode(StateSpaceNode node) { - node.index = this.length; - this.length++; + node.index = this.nodeCount(); super.addNode(node); } + /** + * Get the immediately downstream node. + */ + public StateSpaceNode getDownstreamNode(StateSpaceNode node) { + Set downstream = this.getDownstreamAdjacentNodes(node); + if (downstream == null || downstream.size() == 0) + return null; + return (StateSpaceNode)downstream.toArray()[0]; + } + /** * Pretty print the diagram. */ @@ -55,21 +72,80 @@ public void display() { System.out.println("*************************************************"); System.out.println("* Pretty printing worst-case state space diagram:"); StateSpaceNode node = this.head; + long timestamp; + long microstep; while (node != null) { - System.out.print("* "); - System.out.print("State " + node.index + ": "); + System.out.print("* State " + node.index + ": "); node.display(); + + // Store the tag of the prior step. + timestamp = node.tag.timestamp; + microstep = node.tag.microstep; + if (!node.equals(this.tail)) { // Assume a unique next state. - Set downstream = this.getDownstreamAdjacentNodes(node); - if (downstream == null || downstream.size() == 0) break; - node = (StateSpaceNode)downstream.toArray()[0]; + node = getDownstreamNode(node); + + // Compute time difference + if (node != null) { + timestamp = node.tag.timestamp - timestamp; + microstep = node.tag.microstep - microstep; + System.out.println("* => Advance time by (" + timestamp + ", " + microstep + ")"); + } } else break; } - if (this.loopNode != null) - System.out.println("* Loop node: state " + this.loopNode.index); + if (this.loopNode != null) { + // Compute time difference + timestamp = loopNodeNext.tag.timestamp - tail.tag.timestamp; + microstep = loopNodeNext.tag.microstep - tail.tag.microstep; + System.out.println("* => Advance time by (" + timestamp + ", " + microstep + ")"); + + System.out.println("* Goes back to loop node: state " + this.loopNode.index); + System.out.print("* Loop node reached 2nd time: "); + this.loopNodeNext.display(); + } System.out.println("*************************************************"); } + /** + * Generate a dot file from the state space diagram. + * + * @return a CodeBuilder with the generated code + */ + public CodeBuilder generateDot() { + if (dot == null) { + dot = new CodeBuilder(); + dot.pr("digraph G {"); + dot.indent(); + dot.pr("rankdir=\"LR\";"); + dot.pr("node [shape=Mrecord]"); + + // Generate a node for each state. + for (StateSpaceNode n : this.nodes()) { + dot.pr("S" + n.index + " [" + "label = \"" + "S" + n.index + " | " + n.tag + "\"" + "]"); + } + + StateSpaceNode current = this.head; + StateSpaceNode next = getDownstreamNode(this.head); + while (current != null && next != null && current != this.tail) { + long tsDiff = next.tag.timestamp - current.tag.timestamp; + long msDiff = next.tag.microstep - current.tag.microstep; + dot.pr("S" + current.index + " -> " + "S" + next.index + " [label = " + "\"" + "+(" + tsDiff + ", " + msDiff + ")" + "\"" + "]"); + current = next; + next = getDownstreamNode(next); + } + + if (loopNode != null) { + long tsDiff = loopNodeNext.tag.timestamp - tail.tag.timestamp; + long msDiff = loopNodeNext.tag.microstep - tail.tag.microstep; + dot.pr("S" + current.index + " -> " + "S" + next.index + + " [label = " + "\"" + "+(" + tsDiff + ", " + msDiff + ")" + "\"" + " weight = 0 " + "]"); + } + + dot.unindent(); + dot.pr("}"); + } + return this.dot; + } } \ No newline at end of file diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java index 0a87b6bece..da62b9842e 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java @@ -15,6 +15,7 @@ import org.lflang.analyses.statespace.Event; import org.lflang.analyses.statespace.Tag; import org.lflang.generator.ActionInstance; +import org.lflang.generator.CodeBuilder; import org.lflang.generator.PortInstance; import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactorInstance; @@ -231,8 +232,11 @@ else if (effect instanceof ActionInstance) { // Mark the loop in the diagram. loopFound = true; this.diagram.loopNode = duplicate; + this.diagram.loopNodeNext = currentNode; this.diagram.tail = previousNode; - this.diagram.loopPeriod = this.diagram.tail.tag.timestamp + // Loop period is the time difference between the 1st time + // the node is reached and the 2nd time the node is reached. + this.diagram.loopPeriod = this.diagram.loopNodeNext.tag.timestamp - this.diagram.loopNode.tag.timestamp; this.diagram.addEdge(this.diagram.loopNode, this.diagram.tail); System.out.println("LoopNode index " + this.diagram.loopNode.index); // Why is this 5? diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java index 8ba5605a33..4ffa7290ee 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java @@ -1420,11 +1420,22 @@ private void computeCT() { true // findLoop ); StateSpaceDiagram diagram = explorer.diagram; - diagram.display(); + diagram.display(); + // Generate a dot file. + try { + CodeBuilder dot = diagram.generateDot(); + Path file = this.outputDir.resolve(this.tactic + "_" + this.name + ".dot"); + String filename = file.toString(); + dot.writeToFile(filename); + } catch (IOException e) { + Exceptions.sneakyThrow(e); + } + + //// Compute CT if (!explorer.loopFound) { if (this.logicalTimeBased) - this.CT = diagram.length; + this.CT = diagram.nodeCount(); else { // FIXME: This could be much more efficient with a linkedlist implementation. StateSpaceNode node = diagram.head; @@ -1464,14 +1475,14 @@ private void computeCT() { int numReactionInvocationsBeforeLoop = 0; while (node != diagram.loopNode) { numReactionInvocationsBeforeLoop += node.reactionsInvoked.size(); - node = getDownstreamNode(diagram, node); + node = diagram.getDownstreamNode(node); } // Count the events from the loop node until // loop node is reached again. int numReactionInvocationsInsideLoop = 0; do { - node = getDownstreamNode(diagram, node); + node = diagram.getDownstreamNode(node); numReactionInvocationsInsideLoop += node.reactionsInvoked.size(); } while (node != diagram.loopNode); @@ -1499,17 +1510,6 @@ private void processMTLSpec() { this.horizon = visitor.getHorizon(); } - /** - * Get the immediately downstream node. - * FIXME: Check if there are multiple downstream nodes. - */ - private StateSpaceNode getDownstreamNode(StateSpaceDiagram diagram, StateSpaceNode node) { - Set downstream = diagram.getDownstreamAdjacentNodes(node); - if (downstream == null || downstream.size() == 0) - System.out.println("There are no downstream nodes!"); - return (StateSpaceNode)downstream.toArray()[0]; - } - ///////////////////////////////////////////////// //// Functions from generatorBase From 4f5e4169794cdd9e7d8dbb34e4dfb83656f04219 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sat, 17 Dec 2022 00:26:27 -0800 Subject: [PATCH 069/516] Update dot formatting --- org.lflang/src/org/lflang/TimeValue.java | 32 ++++++++++++++ .../statespace/StateSpaceDiagram.java | 43 ++++++++++++------- .../analyses/statespace/StateSpaceNode.java | 9 ++-- .../org/lflang/analyses/statespace/Tag.java | 7 ++- 4 files changed, 72 insertions(+), 19 deletions(-) diff --git a/org.lflang/src/org/lflang/TimeValue.java b/org.lflang/src/org/lflang/TimeValue.java index 95d18145e1..6c93c4e1cc 100644 --- a/org.lflang/src/org/lflang/TimeValue.java +++ b/org.lflang/src/org/lflang/TimeValue.java @@ -25,6 +25,8 @@ package org.lflang; +import java.sql.Time; + /** * Represents an amount of time (a duration). * @@ -152,6 +154,36 @@ public long toNanoSeconds() { return makeNanosecs(time, unit); } + /** + * Return a TimeValue based on a nanosecond value. + */ + public static TimeValue fromNanoSeconds(long ns) { + if (ns == 0) return ZERO; + long time; + if ((time = ns / 604_800_016_558_522L) > 0 && ns % 604_800_016_558_522L == 0) { + return new TimeValue(time, TimeUnit.WEEK); + } + if ((time = ns / 86_400_000_000_000L) > 0 && ns % 86_400_000_000_000L == 0) { + return new TimeValue(time, TimeUnit.DAY); + } + if ((time = ns / 3_600_000_000_000L) > 0 && ns % 3_600_000_000_000L == 0) { + return new TimeValue(time, TimeUnit.HOUR); + } + if ((time = ns / 60_000_000_000L) > 0 && ns % 60_000_000_000L == 0) { + return new TimeValue(time, TimeUnit.MINUTE); + } + if ((time = ns / 1_000_000_000) > 0 && ns % 1_000_000_000 == 0) { + return new TimeValue(time, TimeUnit.SECOND); + } + if ((time = ns / 1_000_000) > 0 && ns % 1_000_000 == 0) { + return new TimeValue(time, TimeUnit.MILLI); + } + if ((time = ns / 1000) > 0 && ns % 1000 == 0) { + return new TimeValue(time, TimeUnit.MICRO); + } + return new TimeValue(ns, TimeUnit.NANO); + } + /** * Return a string representation of this time value. */ diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java index 958231844b..d7c9c5b578 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java @@ -5,9 +5,14 @@ */ package org.lflang.analyses.statespace; +import java.util.List; import java.util.Set; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import org.lflang.TimeValue; import org.lflang.generator.CodeBuilder; +import org.lflang.generator.ReactionInstance; import org.lflang.graph.DirectedGraph; // FIXME: Use a linkedlist instead. @@ -73,14 +78,12 @@ public void display() { System.out.println("* Pretty printing worst-case state space diagram:"); StateSpaceNode node = this.head; long timestamp; - long microstep; while (node != null) { System.out.print("* State " + node.index + ": "); node.display(); // Store the tag of the prior step. timestamp = node.tag.timestamp; - microstep = node.tag.microstep; if (!node.equals(this.tail)) { // Assume a unique next state. @@ -88,18 +91,16 @@ public void display() { // Compute time difference if (node != null) { - timestamp = node.tag.timestamp - timestamp; - microstep = node.tag.microstep - microstep; - System.out.println("* => Advance time by (" + timestamp + ", " + microstep + ")"); + TimeValue tsDiff = TimeValue.fromNanoSeconds(node.tag.timestamp - timestamp); + System.out.println("* => Advance time by " + tsDiff + " ns"); } } else break; } if (this.loopNode != null) { // Compute time difference - timestamp = loopNodeNext.tag.timestamp - tail.tag.timestamp; - microstep = loopNodeNext.tag.microstep - tail.tag.microstep; - System.out.println("* => Advance time by (" + timestamp + ", " + microstep + ")"); + TimeValue tsDiff = TimeValue.fromNanoSeconds(loopNodeNext.tag.timestamp - tail.tag.timestamp); + System.out.println("* => Advance time by " + tsDiff + " ns"); System.out.println("* Goes back to loop node: state " + this.loopNode.index); System.out.print("* Loop node reached 2nd time: "); @@ -118,29 +119,41 @@ public CodeBuilder generateDot() { dot = new CodeBuilder(); dot.pr("digraph G {"); dot.indent(); + if (this.loopNode != null) { + dot.pr("layout=\"circo\";"); + dot.pr("mindist=\"2.5\";"); + } dot.pr("rankdir=\"LR\";"); dot.pr("node [shape=Mrecord]"); // Generate a node for each state. for (StateSpaceNode n : this.nodes()) { - dot.pr("S" + n.index + " [" + "label = \"" + "S" + n.index + " | " + n.tag + "\"" + "]"); + List reactions = n.reactionsInvoked.stream() + .map(ReactionInstance::getFullName).collect(Collectors.toList()); + String reactionsStr = String.join("\\n", reactions); + dot.pr("S" + n.index + " [" + "label = \"" + "S" + n.index + + " | " + n.tag + + " | " + reactionsStr + // + " | " + "Reactions: " + n.reactionsInvoked.size() // Simplified version + + " | " + "Pending events: " + n.eventQ.size() + + "\"" + "]"); } StateSpaceNode current = this.head; StateSpaceNode next = getDownstreamNode(this.head); while (current != null && next != null && current != this.tail) { - long tsDiff = next.tag.timestamp - current.tag.timestamp; - long msDiff = next.tag.microstep - current.tag.microstep; - dot.pr("S" + current.index + " -> " + "S" + next.index + " [label = " + "\"" + "+(" + tsDiff + ", " + msDiff + ")" + "\"" + "]"); + TimeValue tsDiff = TimeValue.fromNanoSeconds(next.tag.timestamp - current.tag.timestamp); + dot.pr("S" + current.index + " -> " + "S" + next.index + " [label = " + "\"" + "+" + tsDiff + "\"" + "]"); current = next; next = getDownstreamNode(next); } if (loopNode != null) { - long tsDiff = loopNodeNext.tag.timestamp - tail.tag.timestamp; - long msDiff = loopNodeNext.tag.microstep - tail.tag.microstep; + TimeValue tsDiff = TimeValue.fromNanoSeconds(loopNodeNext.tag.timestamp - tail.tag.timestamp); + TimeValue period = TimeValue.fromNanoSeconds(loopPeriod); dot.pr("S" + current.index + " -> " + "S" + next.index - + " [label = " + "\"" + "+(" + tsDiff + ", " + msDiff + ")" + "\"" + " weight = 0 " + "]"); + + " [label = " + "\"" + "+" + tsDiff + " -" + period + "\"" + + " weight = 0 " + "]"); } dot.unindent(); diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java index 2ac3ef1533..abfc1d5c48 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java @@ -10,13 +10,15 @@ import java.util.List; import java.util.stream.Collectors; +import org.lflang.TimeValue; import org.lflang.generator.ReactionInstance; import org.lflang.generator.TriggerInstance; public class StateSpaceNode { - public int index; // Set in StateSpaceDiagram.java + public int index; // Set in StateSpaceDiagram.java public Tag tag; + public TimeValue time; // Readable representation of tag.timestamp public ArrayList reactionsInvoked; public ArrayList eventQ; @@ -28,6 +30,7 @@ public StateSpaceNode( this.tag = tag; this.eventQ = eventQ; this.reactionsInvoked = reactionsInvoked; + this.time = TimeValue.fromNanoSeconds(tag.timestamp); } /** @@ -53,10 +56,10 @@ private boolean equidistant(StateSpaceNode n1, * Two methods for pretty printing */ public void display() { - System.out.println("(" + tag + ", " + reactionsInvoked + ", " + eventQ + ")"); + System.out.println("(" + this.time + ", " + reactionsInvoked + ", " + eventQ + ")"); } public String toString() { - return "(" + tag + ", " + reactionsInvoked + ", " + eventQ + ")"; + return "(" + this.time + ", " + reactionsInvoked + ", " + eventQ + ")"; } /** diff --git a/org.lflang/src/org/lflang/analyses/statespace/Tag.java b/org.lflang/src/org/lflang/analyses/statespace/Tag.java index dcf3103903..99210ae622 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/Tag.java +++ b/org.lflang/src/org/lflang/analyses/statespace/Tag.java @@ -7,6 +7,8 @@ */ package org.lflang.analyses.statespace; +import org.lflang.TimeValue; + public class Tag implements Comparable { public long timestamp; @@ -57,6 +59,9 @@ public boolean equals(Object o) { @Override public String toString() { if (this.forever) return "(FOREVER, " + this.microstep + ")"; - else return "(" + this.timestamp + ", " + this.microstep + ")"; + else { + TimeValue time = TimeValue.fromNanoSeconds(this.timestamp); + return "(" + time + ", " + this.microstep + ")"; + } } } \ No newline at end of file From b2fddc14e606c3aa8326d9778b454f4b1ec7500d Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 28 Dec 2022 14:32:03 +0800 Subject: [PATCH 070/516] Add an option to generate compact state space diagrams --- .../statespace/StateSpaceDiagram.java | 48 +++++++++++++------ .../statespace/StateSpaceExplorer.java | 4 +- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java index d7c9c5b578..2573b9841e 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java @@ -51,6 +51,11 @@ public class StateSpaceDiagram extends DirectedGraph { */ private CodeBuilder dot; + /** + * + */ + private final boolean compactDot = true; + /** * Before adding the node, assign it an index. */ @@ -120,23 +125,36 @@ public CodeBuilder generateDot() { dot.pr("digraph G {"); dot.indent(); if (this.loopNode != null) { - dot.pr("layout=\"circo\";"); - dot.pr("mindist=\"2.5\";"); + dot.pr("layout=circo;"); + } + dot.pr("rankdir=LR;"); + if (this.compactDot) { + dot.pr("mindist=1.5;"); + dot.pr("overlap=false"); + dot.pr("node [shape=Mrecord fontsize=40]"); + dot.pr("edge [fontsize=40 penwidth=3 arrowsize=2]"); + } else { + dot.pr("node [shape=Mrecord]"); } - dot.pr("rankdir=\"LR\";"); - dot.pr("node [shape=Mrecord]"); - // Generate a node for each state. - for (StateSpaceNode n : this.nodes()) { - List reactions = n.reactionsInvoked.stream() - .map(ReactionInstance::getFullName).collect(Collectors.toList()); - String reactionsStr = String.join("\\n", reactions); - dot.pr("S" + n.index + " [" + "label = \"" + "S" + n.index - + " | " + n.tag - + " | " + reactionsStr - // + " | " + "Reactions: " + n.reactionsInvoked.size() // Simplified version - + " | " + "Pending events: " + n.eventQ.size() - + "\"" + "]"); + if (this.compactDot) { + for (StateSpaceNode n : this.nodes()) { + dot.pr("S" + n.index + " [" + "label = \" {" + "S" + n.index + + " | " + n.reactionsInvoked.size() + " | " + n.eventQ.size() + "}" + + " | " + n.tag + + "\"" + "]"); + } + } else { + for (StateSpaceNode n : this.nodes()) { + List reactions = n.reactionsInvoked.stream() + .map(ReactionInstance::getFullName).collect(Collectors.toList()); + String reactionsStr = String.join("\\n", reactions); + dot.pr("S" + n.index + " [" + "label = \"" + "S" + n.index + + " | " + n.tag + + " | " + reactionsStr + + " | " + "Pending events: " + n.eventQ.size() + + "\"" + "]"); + } } StateSpaceNode current = this.head; diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java index da62b9842e..3a4af46ad1 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java @@ -215,7 +215,9 @@ else if (effect instanceof ActionInstance) { } } - // When we first advance to a new tag, create a new node in the state space diagram. + // When we advance to a new tag, + // create a new node in the state space diagram + // for everything processed in the previous tag. if ( previousTag == null // The first iteration || currentTag.compareTo(previousTag) > 0 From cd7fda89740593eadc5b71ee29698de38439ed15 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 28 Dec 2022 14:33:11 +0800 Subject: [PATCH 071/516] Support negative numbers in C code --- .../cast/{CAstUtils.java => AstUtils.java} | 32 ++++++++- .../cast/BuildAstParseTreeVisitor.java | 38 ++++++++-- .../src/org/lflang/analyses/cast/CAst.java | 9 +++ .../org/lflang/analyses/cast/CAstVisitor.java | 2 + .../lflang/analyses/cast/CBaseAstVisitor.java | 71 +++++-------------- .../lflang/analyses/cast/CToUclidVisitor.java | 5 ++ .../analyses/cast/IfNormalFormAstVisitor.java | 2 +- .../lflang/analyses/uclid/UclidGenerator.java | 34 ++++----- 8 files changed, 118 insertions(+), 75 deletions(-) rename org.lflang/src/org/lflang/analyses/cast/{CAstUtils.java => AstUtils.java} (59%) diff --git a/org.lflang/src/org/lflang/analyses/cast/CAstUtils.java b/org.lflang/src/org/lflang/analyses/cast/AstUtils.java similarity index 59% rename from org.lflang/src/org/lflang/analyses/cast/CAstUtils.java rename to org.lflang/src/org/lflang/analyses/cast/AstUtils.java index 36b749e397..a42198cb4b 100644 --- a/org.lflang/src/org/lflang/analyses/cast/CAstUtils.java +++ b/org.lflang/src/org/lflang/analyses/cast/AstUtils.java @@ -2,7 +2,12 @@ import java.util.List; -public class CAstUtils { +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.misc.Interval; + +import org.eclipse.xtext.xbase.lib.Exceptions; + +public class AstUtils { public static CAst.AstNode takeConjunction(List conditions) { if (conditions.size() == 0) { @@ -47,4 +52,29 @@ public static CAst.AstNode takeDisjunction(List conditions) { return top; } } + + // A handy function for debugging ASTs. + // It prints the stack trace of the visitor functions + // and shows the text matched by the ANTLR rules. + public static void printStackTraceAndMatchedText(ParserRuleContext ctx) { + System.out.println("========== AST DEBUG =========="); + + // Print matched text + int a = ctx.start.getStartIndex(); + int b = ctx.stop.getStopIndex(); + Interval interval = new Interval(a,b); + String matchedText = ctx.start.getInputStream().getText(interval); + System.out.println("Matched text: " + matchedText); + + // Print stack trace + StackTraceElement[] cause = Thread.currentThread().getStackTrace(); + System.out.print("Stack trace: "); + for (int i = 0; i < cause.length; i++) { + System.out.print(cause[i].getMethodName()); + if (i != cause.length - 1) System.out.print(", "); + } + System.out.println("."); + + System.out.println("==============================="); + } } \ No newline at end of file diff --git a/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java b/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java index af3f02d26c..3653296e88 100644 --- a/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java @@ -353,6 +353,7 @@ public CAst.AstNode visitPostfixExpression(PostfixExpressionContext ctx) { @Override public CAst.AstNode visitUnaryExpression(UnaryExpressionContext ctx) { + // Check for prefixes and mark them as opaque (unsupported for now). if (ctx.PlusPlus().size() > 0 || ctx.MinusMinus().size() > 0 || ctx.Sizeof().size() > 0) { @@ -361,23 +362,52 @@ public CAst.AstNode visitUnaryExpression(UnaryExpressionContext ctx) { "Prefix '++', '--', and 'sizeof' are currently not supported.", "Marking the statement as opaque." )); + AstUtils.printStackTraceAndMatchedText(ctx); return new CAst.OpaqueNode(); } + // Handle the postfixExpression rule + // (look up the grammar in C.g4 to get more details). if (ctx.postfixExpression() != null) { return visitPostfixExpression(ctx.postfixExpression()); } + // Handle the unary operators '!' (logical not) + // and '-' (negative). if (ctx.unaryOperator() != null - && ctx.unaryOperator().Not() != null && ctx.castExpression() != null) { - CAst.LogicalNotNode node = new CAst.LogicalNotNode(); - node.child = visitCastExpression(ctx.castExpression()); - return node; + CAst.AstNodeUnary node; + if (ctx.unaryOperator().Not() != null) { + // Handle the logical not expression. + node = new CAst.LogicalNotNode(); + node.child = visitCastExpression(ctx.castExpression()); + return node; + } + else if (ctx.unaryOperator().Minus() != null) { + // Handle negative numbers. + // -5 will be translated as -1 * (5) + // which is a NegativeNode with a + // LiteralNode inside. + // + // FIXME: Need to perform precise error handling + // because we will go from a castExpression to + // a Constant under primaryExpression and anything + // else matching besides Constant could be problematic. + // For example, we cannot have a NegativeNode with + // a StringLiteralNode inside. This can be caught by + // the GCC, but if compilation is not involved, it + // would be useful to catch it here ourselves. + node = new CAst.NegativeNode(); + node.child = visitCastExpression(ctx.castExpression()); + return node; + } } + + // Mark all the remaining cases as opaque. System.out.println(String.join(" ", "Warning (line " + ctx.getStart().getLine() + "):", "only postfixExpression and '!' in a unaryExpression is currently supported.", "Marking the statement as opaque." )); + AstUtils.printStackTraceAndMatchedText(ctx); return new CAst.OpaqueNode(); } diff --git a/org.lflang/src/org/lflang/analyses/cast/CAst.java b/org.lflang/src/org/lflang/analyses/cast/CAst.java index 1da9b3994a..e42f066311 100644 --- a/org.lflang/src/org/lflang/analyses/cast/CAst.java +++ b/org.lflang/src/org/lflang/analyses/cast/CAst.java @@ -227,6 +227,15 @@ public static class NotEqualNode extends AstNodeBinary implements Visitable { } } + public static class NegativeNode extends AstNodeUnary implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitNegativeNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitNegativeNode(this, nodeList); + } + } + public static class LessThanNode extends AstNodeBinary implements Visitable { @Override public T accept(AstVisitor visitor) { return ((CAstVisitor)visitor).visitLessThanNode(this); diff --git a/org.lflang/src/org/lflang/analyses/cast/CAstVisitor.java b/org.lflang/src/org/lflang/analyses/cast/CAstVisitor.java index 1fadea44d5..f615f6bbb7 100644 --- a/org.lflang/src/org/lflang/analyses/cast/CAstVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/CAstVisitor.java @@ -27,6 +27,7 @@ public interface CAstVisitor extends AstVisitor { T visitEqualNode(CAst.EqualNode node); T visitNotEqualNode(CAst.NotEqualNode node); + T visitNegativeNode(CAst.NegativeNode node); T visitLessThanNode(CAst.LessThanNode node); T visitLessEqualNode(CAst.LessEqualNode node); T visitGreaterThanNode(CAst.GreaterThanNode node); @@ -61,6 +62,7 @@ public interface CAstVisitor extends AstVisitor { T visitEqualNode(CAst.EqualNode node, List nodeList); T visitNotEqualNode(CAst.NotEqualNode node, List nodeList); + T visitNegativeNode(CAst.NegativeNode node, List nodeList); T visitLessThanNode(CAst.LessThanNode node, List nodeList); T visitLessEqualNode(CAst.LessEqualNode node, List nodeList); T visitGreaterThanNode(CAst.GreaterThanNode node, List nodeList); diff --git a/org.lflang/src/org/lflang/analyses/cast/CBaseAstVisitor.java b/org.lflang/src/org/lflang/analyses/cast/CBaseAstVisitor.java index 5273a0bc0d..117f1a8536 100644 --- a/org.lflang/src/org/lflang/analyses/cast/CBaseAstVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/CBaseAstVisitor.java @@ -2,7 +2,11 @@ import java.util.List; -/** Modeled after CBaseVisitor.java */ +/** + * A base class that provides default implementations + * of the visit functions. Other C AST visitors extend + * this class. + */ public class CBaseAstVisitor extends AbstractAstVisitor implements CAstVisitor { /** @@ -13,36 +17,30 @@ public class CBaseAstVisitor extends AbstractAstVisitor implements CAstVis */ @Override public T visitAstNode(CAst.AstNode node) { - // System.out.print("[visitAstNode] "); - // System.out.println("Hi, I am " + node); return null; } @Override public T visitAstNodeUnary(CAst.AstNodeUnary node) { - // System.out.print("[visitAstNodeUnary] "); - // System.out.println("Hi, I am " + node); if (node.child != null) { T result = visit(node.child); } else { - // System.out.println("*** Child is empty in " + node + "!"); + System.out.println("*** Child is empty in " + node + "!"); } return null; } @Override public T visitAstNodeBinary(CAst.AstNodeBinary node) { - // System.out.print("[visitAstNodeBinary] "); - // System.out.println("Hi, I am " + node); if (node.left != null) { T leftResult = visit(node.left); } else { - // System.out.println("*** Left child is empty in " + node + "!"); + System.out.println("*** Left child is empty in " + node + "!"); } if (node.right != null) { T rightResult = visit(node.right); } else { - // System.out.println("*** Right child is empty in " + node + "!"); + System.out.println("*** Right child is empty in " + node + "!"); } // Aggregate results... return null; @@ -50,8 +48,6 @@ public T visitAstNodeBinary(CAst.AstNodeBinary node) { @Override public T visitAstNodeDynamic(CAst.AstNodeDynamic node) { - // System.out.print("[visitAstNodeDynamic] "); - // System.out.println("Hi, I am " + node); for (CAst.AstNode n : node.children) { T result = visit(n); } @@ -60,64 +56,51 @@ public T visitAstNodeDynamic(CAst.AstNodeDynamic node) { @Override public T visitAssignmentNode(CAst.AssignmentNode node) { - // The default implementation reuses visitAstNodeBinary(node). - // System.out.print("[visitAssignmentNode] "); return visitAstNodeBinary(node); } @Override public T visitIfBlockNode(CAst.IfBlockNode node) { - // System.out.print("[visitIfBlockNode] "); return visitAstNodeBinary(node); } @Override public T visitIfBodyNode(CAst.IfBodyNode node) { - // System.out.print("[visitIfBodyNode] "); return visitAstNodeBinary(node); } @Override public T visitLiteralNode(CAst.LiteralNode node) { - // System.out.print("[visitLiteralNode] "); - // System.out.println("Hi, I am " + node + " with literal " + node.literal); return null; } @Override public T visitLogicalNotNode(CAst.LogicalNotNode node) { - // System.out.print("[visitLogicalNotNode] "); return visitAstNodeUnary(node); } @Override public T visitLogicalAndNode(CAst.LogicalAndNode node) { - // System.out.print("[visitLogicalAndNode] "); return visitAstNodeBinary(node); } @Override public T visitLogicalOrNode(CAst.LogicalOrNode node) { - // System.out.print("[visitLogicalOrNode] "); return visitAstNodeBinary(node); } @Override public T visitOpaqueNode(CAst.OpaqueNode node) { - // System.out.print("[visitOpaqueNode] "); return visitAstNode(node); } @Override public T visitStatementSequenceNode(CAst.StatementSequenceNode node) { - // System.out.print("[visitStatementSequenceNode] "); return visitAstNodeDynamic(node); } @Override public T visitVariableNode(CAst.VariableNode node) { - // System.out.print("[visitVariableNode] "); - // System.out.println("Hi, I am " + node + ": (" + node.type + ", " + node.name + ")"); return null; } @@ -125,29 +108,21 @@ public T visitVariableNode(CAst.VariableNode node) { @Override public T visitAdditionNode(CAst.AdditionNode node) { - // The default implementation reuses visitAstNodeBinary(node). - // System.out.print("[visitAdditionNode] "); return visitAstNodeBinary(node); } @Override public T visitSubtractionNode(CAst.SubtractionNode node) { - // The default implementation reuses visitAstNodeBinary(node). - // System.out.print("[visitSubtractionNode] "); return visitAstNodeBinary(node); } @Override public T visitMultiplicationNode(CAst.MultiplicationNode node) { - // The default implementation reuses visitAstNodeBinary(node). - // System.out.print("[visitMultiplicationNode] "); return visitAstNodeBinary(node); } @Override public T visitDivisionNode(CAst.DivisionNode node) { - // The default implementation reuses visitAstNodeBinary(node). - // System.out.print("[visitDivisionNode] "); return visitAstNodeBinary(node); } @@ -155,43 +130,36 @@ public T visitDivisionNode(CAst.DivisionNode node) { @Override public T visitEqualNode(CAst.EqualNode node) { - // The default implementation reuses visitAstNodeBinary(node). - // System.out.print("[visitEqualNode] "); return visitAstNodeBinary(node); } @Override public T visitNotEqualNode(CAst.NotEqualNode node) { - // The default implementation reuses visitAstNodeBinary(node). - // System.out.print("[visitNotEqualNode] "); return visitAstNodeBinary(node); } + @Override + public T visitNegativeNode(CAst.NegativeNode node) { + return visitNegativeNode(node); + } + @Override public T visitLessThanNode(CAst.LessThanNode node) { - // The default implementation reuses visitAstNodeBinary(node). - // System.out.print("[visitLessThanNode] "); return visitAstNodeBinary(node); } @Override public T visitLessEqualNode(CAst.LessEqualNode node) { - // The default implementation reuses visitAstNodeBinary(node). - // System.out.print("[visitLessEqualNode] "); return visitAstNodeBinary(node); } @Override public T visitGreaterThanNode(CAst.GreaterThanNode node) { - // The default implementation reuses visitAstNodeBinary(node). - // System.out.print("[visitGreaterThanNode] "); return visitAstNodeBinary(node); } @Override public T visitGreaterEqualNode(CAst.GreaterEqualNode node) { - // The default implementation reuses visitAstNodeBinary(node). - // System.out.print("[visitGreaterEqualNode] "); return visitAstNodeBinary(node); } @@ -199,34 +167,26 @@ public T visitGreaterEqualNode(CAst.GreaterEqualNode node) { @Override public T visitSetPortNode(CAst.SetPortNode node) { - // System.out.print("[visitSetPortNode] "); return visitAstNodeBinary(node); } @Override public T visitScheduleActionNode(CAst.ScheduleActionNode node) { - // System.out.print("[visitScheduleActionNode] "); return visitAstNodeDynamic(node); } @Override public T visitStateVarNode(CAst.StateVarNode node) { - // System.out.print("[visitStateVarNode] "); - // System.out.println("Hi, I am " + node + ": (" + node.name + ")"); return null; } @Override public T visitTriggerValueNode(CAst.TriggerValueNode node) { - // System.out.print("[visitTriggerValueNode] "); - // System.out.println("Hi, I am " + node + ": (" + node.name + ")"); return null; } @Override public T visitTriggerIsPresentNode(CAst.TriggerIsPresentNode node) { - // System.out.print("[visitTriggerIsPresentNode] "); - // System.out.println("Hi, I am " + node + ": (" + node.name + ")"); return null; } @@ -335,6 +295,11 @@ public T visitNotEqualNode(CAst.NotEqualNode node, List nodeList) return visitNotEqualNode(node); } + @Override + public T visitNegativeNode(CAst.NegativeNode node, List nodeList) { + return visitNegativeNode(node); + } + @Override public T visitLessThanNode(CAst.LessThanNode node, List nodeList) { return visitLessThanNode(node); diff --git a/org.lflang/src/org/lflang/analyses/cast/CToUclidVisitor.java b/org.lflang/src/org/lflang/analyses/cast/CToUclidVisitor.java index 88d73b3278..248eeeab91 100644 --- a/org.lflang/src/org/lflang/analyses/cast/CToUclidVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/CToUclidVisitor.java @@ -159,6 +159,11 @@ public String visitNotEqualNode(NotEqualNode node) { return "(" + lhs + " != " + rhs + ")"; } + @Override + public String visitNegativeNode(NegativeNode node) { + return "(" + "-1*(" + visit(node.child) + "))"; + } + @Override public String visitScheduleActionNode(ScheduleActionNode node) { String name = ((VariableNode)node.children.get(0)).name; diff --git a/org.lflang/src/org/lflang/analyses/cast/IfNormalFormAstVisitor.java b/org.lflang/src/org/lflang/analyses/cast/IfNormalFormAstVisitor.java index bde7337565..a8a5e8931d 100644 --- a/org.lflang/src/org/lflang/analyses/cast/IfNormalFormAstVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/IfNormalFormAstVisitor.java @@ -76,7 +76,7 @@ private CAst.IfBlockNode generateIfBlock(CAst.AstNode node, List c // Create an If Block node. CAst.IfBlockNode ifNode = new CAst.IfBlockNode(); // Set the condition of the if block node. - CAst.AstNode conjunction = CAstUtils.takeConjunction(conditions); + CAst.AstNode conjunction = AstUtils.takeConjunction(conditions); ifNode.left = conjunction; // Create a new body node. CAst.IfBodyNode body = new CAst.IfBodyNode(); diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java index 4ffa7290ee..d2f18062ee 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java @@ -58,9 +58,9 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.Target; import org.lflang.TimeUnit; import org.lflang.TimeValue; +import org.lflang.analyses.cast.AstUtils; import org.lflang.analyses.cast.BuildAstParseTreeVisitor; import org.lflang.analyses.cast.CAst; -import org.lflang.analyses.cast.CAstUtils; import org.lflang.analyses.cast.CBaseAstVisitor; import org.lflang.analyses.cast.CToUclidVisitor; import org.lflang.analyses.cast.IfNormalFormAstVisitor; @@ -1080,8 +1080,8 @@ protected void generateReactionAxioms() { for (ReactionInstance.Runtime reaction : this.reactionInstances) { String body = reaction.getReaction().getDefinition().getCode().getBody(); - // System.out.println("Printing reaction body of " + reaction); - // System.out.println(body); + System.out.println("Printing reaction body of " + reaction); + System.out.println(body); // Generate a parse tree. CLexer lexer = new CLexer(CharStreams.fromString(body)); @@ -1097,19 +1097,10 @@ protected void generateReactionAxioms() { VariablePrecedenceVisitor precVisitor = new VariablePrecedenceVisitor(); precVisitor.visit(ast); - // Traverse and print. - CBaseAstVisitor baseVisitor = new CBaseAstVisitor<>(); // For pretty printing. - // System.out.println("***** Printing the original AST."); - baseVisitor.visit(ast); - // Convert the AST to If Normal Form (INF). IfNormalFormAstVisitor infVisitor = new IfNormalFormAstVisitor(); - // System.out.println("***** Convert to If Normal Form."); infVisitor.visit(ast, new ArrayList()); CAst.StatementSequenceNode inf = infVisitor.INF; - // System.out.println(inf); - // System.out.println("***** Printing the AST in If Normal Form."); - baseVisitor.visit(inf); // For the variables that are USED inside this reaction, extract the conditions // for setting them, and take the negation of their conjunction @@ -1151,7 +1142,6 @@ protected void generateReactionAxioms() { // Generate Uclid axiom for the C AST. CToUclidVisitor c2uVisitor = new CToUclidVisitor(this, reaction); - // System.out.println("***** Generating axioms from AST."); String axiom = c2uVisitor.visit(inf); code.pr(String.join("\n", "// Reaction body of " + reaction, @@ -1162,12 +1152,24 @@ protected void generateReactionAxioms() { "// By default, the value of the variables used in this reaction stay the same." )); for (NamedInstance key : defaultBehaviorConditions.keySet()) { - CAst.AstNode disjunction = CAstUtils.takeDisjunction(defaultBehaviorConditions.get(key)); - // System.out.println("!!! Reset conditions: " + defaultBehaviorConditions.get(key)); + CAst.AstNode disjunction = AstUtils.takeDisjunction(defaultBehaviorConditions.get(key)); CAst.LogicalNotNode notNode = new CAst.LogicalNotNode(); notNode.child = disjunction; String resetCondition = c2uVisitor.visit(notNode); - // System.out.println("!!! Str: " + resetCondition); + + // Check for invalid reset conditions. + // If found, stop the execution. + // FIXME: A more systematic check is needed + // to ensure that the generated Uclid file + // is valid. + try { + if (resetCondition.contains("null")) { + throw new Exception("Null detected in a reset condition. Stop."); + } + } catch (Exception e) { + Exceptions.sneakyThrow(e); + } + code.pr("&& " + "(" + "(" + resetCondition + ")" + " ==> " + "("); if (key instanceof StateVariableInstance) { StateVariableInstance n = (StateVariableInstance)key; From 83a4f482a70968b041ef4332f9ba5f59d62791bc Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 28 Dec 2022 17:18:13 +0800 Subject: [PATCH 072/516] Address the case when the state space diagram only has one node. --- .../statespace/StateSpaceExplorer.java | 128 ++++++++++-------- .../lflang/analyses/uclid/UclidGenerator.java | 4 +- 2 files changed, 77 insertions(+), 55 deletions(-) diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java index 3a4af46ad1..3d5e32d04d 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java @@ -10,6 +10,8 @@ import java.util.PriorityQueue; import java.util.Set; +import org.eclipse.xtext.xbase.lib.Exceptions; + import org.lflang.TimeUnit; import org.lflang.TimeValue; import org.lflang.analyses.statespace.Event; @@ -65,9 +67,8 @@ public StateSpaceExplorer(ReactorInstance main) { public void addInitialEvents(ReactorInstance reactor) { // Add the startup trigger, if exists. var startup = reactor.getStartupTrigger(); - if (startup != null) { + if (startup != null) eventQ.add(new Event(startup, new Tag(0, 0, false))); - } // Add the initial timer firings, if exist. for (TimerInstance timer : reactor.timers) { @@ -105,7 +106,6 @@ public void explore(Tag horizon, boolean findLoop) { // FIXME: It seems that we need to handle shutdown triggers // separately, because they could break the back loop. addInitialEvents(this.main); - // System.out.println(this.eventQ); Tag previousTag = null; // Tag in the previous loop ITERATION Tag currentTag = null; // Tag in the current loop ITERATION @@ -116,7 +116,6 @@ public void explore(Tag horizon, boolean findLoop) { if (this.eventQ.size() > 0) { stop = false; currentTag = (eventQ.peek()).tag; - // System.out.println(currentTag); } // A list of reactions invoked at the current logical tag @@ -131,9 +130,7 @@ public void explore(Tag horizon, boolean findLoop) { while (eventQ.size() > 0 && eventQ.peek().tag.compareTo(currentTag) == 0) { Event e = eventQ.poll(); currentEvents.add(e); - // System.out.println("Adding event to currentEvents: " + e); } - // System.out.println(currentEvents); // Collect all the reactions invoked in this current LOOP ITERATION // triggered by the earliest events. @@ -166,21 +163,13 @@ public void explore(Tag horizon, boolean findLoop) { // this action. for (TriggerInstance effect : reaction.effects) { if (effect instanceof PortInstance) { - - // System.out.println("Effect: " + effect); - // System.out.print("Eventual destinations: "); - // System.out.println(((PortInstance)effect).getDependentPorts()); for (SendRange senderRange : ((PortInstance)effect).getDependentPorts()) { - // System.out.print("Sender range: "); - // System.out.println(senderRange.destinations); - for (RuntimeRange destinationRange : senderRange.destinations) { PortInstance downstreamPort = destinationRange.instance; - // System.out.println("Located a destination port: " + downstreamPort); // Getting delay from connection // FIXME: Is there a more concise way to do this? @@ -215,50 +204,70 @@ else if (effect instanceof ActionInstance) { } } + // We are at the first iteration. + // Initialize currentNode. + if (previousTag == null) { + //// Now we are done with the node at the previous tag, + //// work on the new node at the current timestamp. + // Copy the reactions in reactionsTemp. + reactionsInvoked = new ArrayList(reactionsTemp); + + // Create a new state in the SSD for the current tag, + // add the reactions triggered to the state, + // and add a snapshot of the event queue (with new events + // generated by reaction invocations in the curren tag) + // to the state. + StateSpaceNode node = new StateSpaceNode( + currentTag, // Current tag + reactionsInvoked, // Reactions invoked at this tag + new ArrayList(eventQ) // A snapshot of the event queue + ); + + // Initialize currentNode. + currentNode = node; + } // When we advance to a new tag, // create a new node in the state space diagram // for everything processed in the previous tag. - if ( - previousTag == null // The first iteration - || currentTag.compareTo(previousTag) > 0 + else if ( + previousTag != null + && currentTag.compareTo(previousTag) > 0 ) { - if (previousTag != null) { - // Whenever we finish a tag, check for loops fist. - // If currentNode matches an existing node in uniqueNodes, - // duplicate is set to the existing node. - StateSpaceNode duplicate; - if (findLoop && - (duplicate = uniqueNodes.put( - currentNode.hashCode(), currentNode)) != null) { - - // Mark the loop in the diagram. - loopFound = true; - this.diagram.loopNode = duplicate; - this.diagram.loopNodeNext = currentNode; - this.diagram.tail = previousNode; - // Loop period is the time difference between the 1st time - // the node is reached and the 2nd time the node is reached. - this.diagram.loopPeriod = this.diagram.loopNodeNext.tag.timestamp - - this.diagram.loopNode.tag.timestamp; - this.diagram.addEdge(this.diagram.loopNode, this.diagram.tail); - System.out.println("LoopNode index " + this.diagram.loopNode.index); // Why is this 5? - return; // Exit the while loop early. - } + // Whenever we finish a tag, check for loops fist. + // If currentNode matches an existing node in uniqueNodes, + // duplicate is set to the existing node. + StateSpaceNode duplicate; + if (findLoop && + (duplicate = uniqueNodes.put( + currentNode.hashCode(), currentNode)) != null) { - // Now we are at a new tag, and a loop is not found, - // add the node to the state space diagram. - this.diagram.addNode(currentNode); - - // If the head is not empty, add an edge from the previous state - // to the next state. Otherwise initialize the head to the new node. - if (this.diagram.head != null && currentNode != null) { - // System.out.println("--- Add a new edge between " + currentNode + " and " + node); - this.diagram.addEdge(currentNode, previousNode); // Sink first, then source - } - else - this.diagram.head = currentNode; // Initialize the head. + // Mark the loop in the diagram. + loopFound = true; + this.diagram.loopNode = duplicate; + this.diagram.loopNodeNext = currentNode; + this.diagram.tail = previousNode; + // Loop period is the time difference between the 1st time + // the node is reached and the 2nd time the node is reached. + this.diagram.loopPeriod = this.diagram.loopNodeNext.tag.timestamp + - this.diagram.loopNode.tag.timestamp; + this.diagram.addEdge(this.diagram.loopNode, this.diagram.tail); + System.out.println("LoopNode index " + this.diagram.loopNode.index); // Why is this 5? + return; // Exit the while loop early. } + // Now we are at a new tag, and a loop is not found, + // add the node to the state space diagram. + this.diagram.addNode(currentNode); + + // If the head is not empty, add an edge from the previous state + // to the next state. Otherwise initialize the head to the new node. + if (this.diagram.head != null) { + // System.out.println("--- Add a new edge between " + currentNode + " and " + node); + this.diagram.addEdge(currentNode, previousNode); // Sink first, then source + } + else + this.diagram.head = currentNode; // Initialize the head. + //// Now we are done with the node at the previous tag, //// work on the new node at the current timestamp. // Copy the reactions in reactionsTemp. @@ -277,16 +286,23 @@ else if (effect instanceof ActionInstance) { // Update the previous node. previousNode = currentNode; - // Update the current node. + // Update the current node to the new (potentially incomplete) node. currentNode = node; } // Time does not advance because we are processing // connections with zero delay. - else { + else if ( + previousTag != null + && currentTag.compareTo(previousTag) == 0 + ) { // Add reactions explored in the current loop iteration // to the existing state space node. currentNode.reactionsInvoked.addAll(reactionsTemp); } + else { + // Unreachable + Exceptions.sneakyThrow(new Exception("Reached an unreachable part.")); + } // Update the current tag for the next iteration. if (eventQ.size() > 0) { @@ -301,6 +317,12 @@ else if (effect instanceof ActionInstance) { || currentTag.compareTo(horizon) > 0) stop = true; } + // When we exit and we still don't have a head, + // that means there is only one node in the diagram. + // Set the current node as the head. + if (this.diagram.head == null) + this.diagram.head = currentNode; + return; } } \ No newline at end of file diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java index d2f18062ee..5ef2a2dd53 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java @@ -1080,8 +1080,8 @@ protected void generateReactionAxioms() { for (ReactionInstance.Runtime reaction : this.reactionInstances) { String body = reaction.getReaction().getDefinition().getCode().getBody(); - System.out.println("Printing reaction body of " + reaction); - System.out.println(body); + // System.out.println("Printing reaction body of " + reaction); + // System.out.println(body); // Generate a parse tree. CLexer lexer = new CLexer(CharStreams.fromString(body)); From 03ae1662236f81c72e03889d32d788f43a2b4008 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 28 Dec 2022 20:24:38 +0800 Subject: [PATCH 073/516] Update comments --- .../src/org/lflang/analyses/cast/CAst.java | 10 +++++----- .../lflang/analyses/uclid/UclidGenerator.java | 19 +++++++++++-------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/org.lflang/src/org/lflang/analyses/cast/CAst.java b/org.lflang/src/org/lflang/analyses/cast/CAst.java index e42f066311..3704406940 100644 --- a/org.lflang/src/org/lflang/analyses/cast/CAst.java +++ b/org.lflang/src/org/lflang/analyses/cast/CAst.java @@ -55,9 +55,9 @@ public static class AssignmentNode extends AstNodeBinary implements Visitable { } /** - * AST node for an if block. + * AST node for an IF block. * The left node is the condition. - * The right node is the if body. + * The right node is the IF body. */ public static class IfBlockNode extends AstNodeBinary implements Visitable { @Override public T accept(AstVisitor visitor) { @@ -69,9 +69,9 @@ public static class IfBlockNode extends AstNodeBinary implements Visitable { } /** - * AST node for an if block. - * The left node is the then branch. - * The right node is the else branch. + * AST node for the body of an IF block. + * The left node is the THEN branch. + * The right node is the ELSE branch. */ public static class IfBodyNode extends AstNodeBinary implements Visitable { @Override public T accept(AstVisitor visitor) { diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java index 5ef2a2dd53..4144cb3126 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java @@ -1111,22 +1111,25 @@ protected void generateReactionAxioms() { HashMap> defaultBehaviorConditions = new HashMap<>(); for (CAst.AstNode node : inf.children) { CAst.IfBlockNode ifBlockNode = (CAst.IfBlockNode)node; - CAst.AstNode ifBody = ((CAst.IfBodyNode)ifBlockNode.right).left; + // Under the INF form, a C statement is + // the THEN branch of an IF block. + CAst.AstNode stmt = ((CAst.IfBodyNode)ifBlockNode.right).left; NamedInstance instance = null; - if ((ifBody instanceof CAst.AssignmentNode - && ((CAst.AssignmentNode)ifBody).left instanceof CAst.StateVarNode)) { - CAst.StateVarNode n = (CAst.StateVarNode)((CAst.AssignmentNode)ifBody).left; + // Match stmt with different cases + if ((stmt instanceof CAst.AssignmentNode + && ((CAst.AssignmentNode)stmt).left instanceof CAst.StateVarNode)) { + CAst.StateVarNode n = (CAst.StateVarNode)((CAst.AssignmentNode)stmt).left; instance = reaction.getReaction().getParent().states.stream() .filter(s -> s.getName().equals(n.name)).findFirst().get(); unusedStates.remove(instance); - } else if (ifBody instanceof CAst.SetPortNode) { - CAst.SetPortNode n = (CAst.SetPortNode)ifBody; + } else if (stmt instanceof CAst.SetPortNode) { + CAst.SetPortNode n = (CAst.SetPortNode)stmt; String name = ((CAst.VariableNode)n.left).name; instance = reaction.getReaction().getParent().outputs.stream() .filter(s -> s.getName().equals(name)).findFirst().get(); unusedOutputs.remove(instance); - } else if (ifBody instanceof CAst.ScheduleActionNode) { - CAst.ScheduleActionNode n = (CAst.ScheduleActionNode)ifBody; + } else if (stmt instanceof CAst.ScheduleActionNode) { + CAst.ScheduleActionNode n = (CAst.ScheduleActionNode)stmt; String name = ((CAst.VariableNode)n.children.get(0)).name; instance = reaction.getReaction().getParent().actions.stream() .filter(s -> s.getName().equals(name)).findFirst().get(); From 0a55dda01ac63c2ce712f5cea05862d5dad656e1 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 28 Dec 2022 20:36:07 +0800 Subject: [PATCH 074/516] Differentiate how tags increment for actions and connections --- .../src/org/lflang/analyses/uclid/UclidGenerator.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java index 4144cb3126..7ecb19f17f 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java @@ -386,9 +386,14 @@ protected void generateTimingSemantics() { " || (pi1(t1) == pi1(t2) && pi2(t1) < pi2(t2))", " || (!isInf(t1) && isInf(t2));", "", + "// Used for incrementing a tag through an action", "define tag_schedule(t : tag_t, i : integer) : tag_t", "= if (i == 0) then { pi1(t), pi2(t)+1 } else { pi1(t)+i, 0 };", "", + "// Used for incrementing a tag along a connection", + "define tag_delay(t : tag_t, i : integer) : tag_t", + "= if (i == 0) then { pi1(t), pi2(t) } else { pi1(t)+i, 0 };", + "", "// Only consider timestamp for now.", "define tag_diff(t1, t2: tag_t) : interval_t", "= if (!isInf(t1) && !isInf(t2))", @@ -776,7 +781,7 @@ protected void generateTriggersAndReactions() { " finite_exists (j : integer) in indices :: j > i && j <= END", " && " + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(j))", " && " + destination.getFullNameWithJoiner("_") + "(s(j)) == " + source.getFullNameWithJoiner("_") + "(s(i))", - connection.isPhysical() ? "" : "&& g(j) == tag_schedule(g(i), " + delay + ")", + connection.isPhysical() ? "" : "&& g(j) == tag_delay(g(i), " + delay + ")", ")", ")) // Closes (" + source.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> ((.", //// Separator @@ -785,7 +790,7 @@ protected void generateTriggersAndReactions() { "&& (" + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", " finite_exists (j : integer) in indices :: j >= START && j < i", " && " + source.getFullNameWithJoiner("_") + "_is_present" + "(t(j))", - connection.isPhysical() ? "" : " && g(i) == tag_schedule(g(j), " + delay + ")", + connection.isPhysical() ? "" : " && g(i) == tag_delay(g(j), " + delay + ")", ")) // Closes the one-to-one relationship.", "));" )); From 185f6f275897734c87c9bb0d9b23f3741dae7df1 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Tue, 3 Jan 2023 15:20:54 +0800 Subject: [PATCH 075/516] Fix CT calculation, fix state space node insertion, support manually specified CT, support the --no-verify flag --- org.lflang/src/org/lflang/TargetConfig.java | 6 + org.lflang/src/org/lflang/TargetProperty.java | 9 + .../statespace/StateSpaceDiagram.java | 41 +++-- .../statespace/StateSpaceExplorer.java | 76 ++++++-- .../analyses/statespace/StateSpaceNode.java | 10 +- .../lflang/analyses/uclid/UclidGenerator.java | 174 +++++++++++------- org.lflang/src/org/lflang/cli/Lfc.java | 1 + .../org/lflang/generator/GeneratorUtils.java | 3 + .../src/org/lflang/generator/LFGenerator.java | 10 +- .../lflang/generator/LFGeneratorContext.java | 1 + .../org/lflang/validation/AttributeSpec.java | 7 +- 11 files changed, 232 insertions(+), 106 deletions(-) diff --git a/org.lflang/src/org/lflang/TargetConfig.java b/org.lflang/src/org/lflang/TargetConfig.java index 7409cd4b27..31c02742be 100644 --- a/org.lflang/src/org/lflang/TargetConfig.java +++ b/org.lflang/src/org/lflang/TargetConfig.java @@ -182,6 +182,12 @@ public class TargetConfig { */ public boolean noRuntimeValidation = false; + /** + * If true, do not check the generated verification model. + * The default is false. + */ + public boolean noVerify = false; + /** * Set the target platform config. * This tells the build system what platform-specific support diff --git a/org.lflang/src/org/lflang/TargetProperty.java b/org.lflang/src/org/lflang/TargetProperty.java index 71f6060c19..22308111c1 100644 --- a/org.lflang/src/org/lflang/TargetProperty.java +++ b/org.lflang/src/org/lflang/TargetProperty.java @@ -291,6 +291,15 @@ public enum TargetProperty { config.noRuntimeValidation = ASTUtils.toBoolean(value); }), + /** + * Directive to not check the generated verification model. + */ + NO_VERIFY("no-verify", PrimitiveType.BOOLEAN, + Arrays.asList(Target.C), + (config, value, err) -> { + config.noVerify = ASTUtils.toBoolean(value); + }), + /** * Directive to specify the platform for cross code generation. This is either a string of the platform * or a dictionary of options that includes the string name. diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java index 2573b9841e..6f25bb60d1 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java @@ -54,7 +54,7 @@ public class StateSpaceDiagram extends DirectedGraph { /** * */ - private final boolean compactDot = true; + private final boolean compactDot = false; /** * Before adding the node, assign it an index. @@ -81,31 +81,38 @@ public StateSpaceNode getDownstreamNode(StateSpaceNode node) { public void display() { System.out.println("*************************************************"); System.out.println("* Pretty printing worst-case state space diagram:"); - StateSpaceNode node = this.head; long timestamp; - while (node != null) { + StateSpaceNode node = this.head; + if (node == null) { + System.out.println("* EMPTY"); + System.out.println("*************************************************"); + return; + } + while(node != this.tail) { System.out.print("* State " + node.index + ": "); node.display(); // Store the tag of the prior step. timestamp = node.tag.timestamp; - if (!node.equals(this.tail)) { - // Assume a unique next state. - node = getDownstreamNode(node); + // Assume a unique next state. + node = getDownstreamNode(node); - // Compute time difference - if (node != null) { - TimeValue tsDiff = TimeValue.fromNanoSeconds(node.tag.timestamp - timestamp); - System.out.println("* => Advance time by " + tsDiff + " ns"); - } + // Compute time difference + if (node != null) { + TimeValue tsDiff = TimeValue.fromNanoSeconds(node.tag.timestamp - timestamp); + System.out.println("* => Advance time by " + tsDiff); } - else break; } + + // Print tail node + System.out.print("* (Tail) state " + node.index + ": "); + node.display(); + if (this.loopNode != null) { // Compute time difference TimeValue tsDiff = TimeValue.fromNanoSeconds(loopNodeNext.tag.timestamp - tail.tag.timestamp); - System.out.println("* => Advance time by " + tsDiff + " ns"); + System.out.println("* => Advance time by " + tsDiff); System.out.println("* Goes back to loop node: state " + this.loopNode.index); System.out.print("* Loop node reached 2nd time: "); @@ -145,14 +152,18 @@ public CodeBuilder generateDot() { + "\"" + "]"); } } else { + System.out.println("***** nodes: " + this.nodes()); for (StateSpaceNode n : this.nodes()) { List reactions = n.reactionsInvoked.stream() .map(ReactionInstance::getFullName).collect(Collectors.toList()); String reactionsStr = String.join("\\n", reactions); + List events = n.eventQ.stream() + .map(Event::toString).collect(Collectors.toList()); + String eventsStr = String.join("\\n", events); dot.pr("S" + n.index + " [" + "label = \"" + "S" + n.index + " | " + n.tag - + " | " + reactionsStr - + " | " + "Pending events: " + n.eventQ.size() + + " | " + "Reactions invoked:\\n" + reactionsStr + + " | " + "Pending events:\\n" + eventsStr + "\"" + "]"); } } diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java index 3d5e32d04d..e9a3d5e0ec 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java @@ -124,11 +124,16 @@ public void explore(Tag horizon, boolean findLoop) { ArrayList reactionsTemp; while (!stop) { + // Pop the events from the earliest tag off the event queue. ArrayList currentEvents = new ArrayList(); // FIXME: Use stream methods here? - while (eventQ.size() > 0 && eventQ.peek().tag.compareTo(currentTag) == 0) { + while ( + eventQ.size() > 0 + && eventQ.peek().tag.compareTo(currentTag) == 0 + ) { Event e = eventQ.poll(); + // System.out.println("DEBUG: Popped an event: " + e); currentEvents.add(e); } @@ -139,6 +144,7 @@ public void explore(Tag horizon, boolean findLoop) { Set dependentReactions = e.trigger.getDependentReactions(); reactionsTemp.addAll(dependentReactions); + // System.out.println("DEBUG: ReactionTemp: " + reactionsTemp); // If the event is a timer firing, enqueue the next firing. if (e.trigger instanceof TimerInstance) { @@ -146,7 +152,7 @@ public void explore(Tag horizon, boolean findLoop) { eventQ.add(new Event( timer, new Tag( - currentTag.timestamp + timer.getPeriod().toNanoSeconds(), + e.tag.timestamp + timer.getPeriod().toNanoSeconds(), 0, // A time advancement resets microstep to 0. false )) @@ -187,6 +193,7 @@ public void explore(Tag horizon, boolean findLoop) { downstreamPort, new Tag(currentTag.timestamp + delay, 0, false) ); + // System.out.println("DEBUG: Added a port event: " + e); eventQ.add(e); } } @@ -194,11 +201,16 @@ public void explore(Tag horizon, boolean findLoop) { else if (effect instanceof ActionInstance) { // Get the minimum delay of this action. long min_delay = ((ActionInstance)effect).getMinDelay().toNanoSeconds(); + long microstep = 0; + if (min_delay == 0) { + microstep = currentTag.microstep + 1; + } // Create and enqueue a new event. Event e = new Event( effect, - new Tag(currentTag.timestamp + min_delay, 0, false) + new Tag(currentTag.timestamp + min_delay, microstep, false) ); + // System.out.println("DEBUG: Added an action event: " + e); eventQ.add(e); } } @@ -207,6 +219,7 @@ else if (effect instanceof ActionInstance) { // We are at the first iteration. // Initialize currentNode. if (previousTag == null) { + // System.out.println("DEBUG: Case 1"); //// Now we are done with the node at the previous tag, //// work on the new node at the current timestamp. // Copy the reactions in reactionsTemp. @@ -226,20 +239,24 @@ else if (effect instanceof ActionInstance) { // Initialize currentNode. currentNode = node; } - // When we advance to a new tag, + // When we advance to a new TIMESTAMP (not a new tag), // create a new node in the state space diagram - // for everything processed in the previous tag. + // for everything processed in the previous timestamp. + // This makes sure that the granularity of nodes is + // at the timestamp-level, so that we don't have to + // worry about microsteps. else if ( previousTag != null - && currentTag.compareTo(previousTag) > 0 + && currentTag.timestamp > previousTag.timestamp ) { + // System.out.println("DEBUG: Case 2"); // Whenever we finish a tag, check for loops fist. // If currentNode matches an existing node in uniqueNodes, // duplicate is set to the existing node. StateSpaceNode duplicate; if (findLoop && (duplicate = uniqueNodes.put( - currentNode.hashCode(), currentNode)) != null) { + currentNode.hash(), currentNode)) != null) { // Mark the loop in the diagram. loopFound = true; @@ -251,19 +268,24 @@ else if ( this.diagram.loopPeriod = this.diagram.loopNodeNext.tag.timestamp - this.diagram.loopNode.tag.timestamp; this.diagram.addEdge(this.diagram.loopNode, this.diagram.tail); - System.out.println("LoopNode index " + this.diagram.loopNode.index); // Why is this 5? return; // Exit the while loop early. } // Now we are at a new tag, and a loop is not found, // add the node to the state space diagram. + // Adding a node to the graph once it is finalized + // because this makes checking duplicate nodes easier. + // We don't have to remove a node from the graph. this.diagram.addNode(currentNode); + this.diagram.tail = currentNode; // Update the current tail. // If the head is not empty, add an edge from the previous state // to the next state. Otherwise initialize the head to the new node. - if (this.diagram.head != null) { + if (previousNode != null) { // System.out.println("--- Add a new edge between " + currentNode + " and " + node); - this.diagram.addEdge(currentNode, previousNode); // Sink first, then source + // this.diagram.addEdge(currentNode, previousNode); // Sink first, then source + if (previousNode != currentNode) + this.diagram.addEdge(currentNode, previousNode); } else this.diagram.head = currentNode; // Initialize the head. @@ -289,16 +311,18 @@ else if ( // Update the current node to the new (potentially incomplete) node. currentNode = node; } - // Time does not advance because we are processing + // Timestamp does not advance because we are processing // connections with zero delay. else if ( previousTag != null - && currentTag.compareTo(previousTag) == 0 + && currentTag.timestamp == previousTag.timestamp ) { + // System.out.println("DEBUG: Case 3"); // Add reactions explored in the current loop iteration // to the existing state space node. currentNode.reactionsInvoked.addAll(reactionsTemp); - } + // Update the eventQ snapshot. + currentNode.eventQ = new ArrayList(eventQ); } else { // Unreachable Exceptions.sneakyThrow(new Exception("Reached an unreachable part.")); @@ -313,10 +337,32 @@ else if ( // Stop if: // 1. the event queue is empty, or // 2. the horizon is reached. - if (eventQ.size() == 0 - || currentTag.compareTo(horizon) > 0) + if (eventQ.size() == 0) { + // System.out.println("DEBUG: Stopping because eventQ is empty!"); + stop = true; + } + else if (currentTag.timestamp > horizon.timestamp) { + // System.out.println("DEBUG: Stopping because horizon is reached! Horizon: " + horizon + " Current Tag: " + currentTag); + // System.out.println("DEBUG: EventQ: " + eventQ); stop = true; + } } + + // Check if the last current node is added to the graph yet. + // If not, add it now. + // This could happen when condition (previousTag == null) + // or (previousTag != null + // && currentTag.compareTo(previousTag) > 0) is true and then + // the simulation ends, leaving a new node dangling. + if (previousNode == null + || previousNode.tag.timestamp < currentNode.tag.timestamp) { + this.diagram.addNode(currentNode); + this.diagram.tail = currentNode; // Update the current tail. + if (previousNode != null) { + this.diagram.addEdge(currentNode, previousNode); + } + } + // When we exit and we still don't have a head, // that means there is only one node in the diagram. // Set the current node as the head. diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java index abfc1d5c48..f1556c70e3 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java @@ -81,10 +81,14 @@ && equidistant(this, node)) } /** - * Generate hash code for the node. + * Generate hash for the node. + * This hash function is used for checking + * the uniqueness of nodes. + * It is not meant to be used as a hashCode() + * because doing so interferes with node + * insertion in the state space diagram. */ - @Override - public int hashCode() { + public int hash() { // Initial value int result = 17; diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java index 7ecb19f17f..dafaae2e0e 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java @@ -39,6 +39,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Optional; import java.util.Set; import org.antlr.v4.runtime.CharStreams; @@ -94,6 +95,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.TriggerInstance; import org.lflang.generator.c.CGenerator; import org.lflang.lf.Action; +import org.lflang.lf.AttrParm; import org.lflang.lf.Attribute; import org.lflang.lf.Code; import org.lflang.lf.Connection; @@ -165,7 +167,7 @@ public class UclidGenerator extends GeneratorBase { protected long horizon = 0; // in nanoseconds protected String FOLSpec = ""; protected int CT = 0; - protected static final int CT_MAX_SUPPORTED = 20; + protected static final int CT_MAX_SUPPORTED = 100; // Constructor public UclidGenerator(FileConfig fileConfig, ErrorReporter errorReporter, List properties) { @@ -189,8 +191,6 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { //////////////////////////////////////// - System.out.println("*** Start generating Uclid code."); - // Create the main reactor instance if there is a main reactor. createMainReactorInstance(); @@ -223,7 +223,14 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { processMTLSpec(); - computeCT(); + Optional CTattr = prop.getAttrParms().stream() + .filter(attr -> attr.getName().equals("CT")) + .findFirst(); + if (CTattr.isPresent()) { + this.CT = Integer.parseInt(CTattr.get().getValue()); + } else { + computeCT(); + } if (this.CT > CT_MAX_SUPPORTED) { System.out.println("ERROR: The maximum steps supported is " + CT_MAX_SUPPORTED + " but checking this property requires " + this.CT + " steps. " @@ -863,48 +870,48 @@ protected void generateTriggersAndReactions() { /** * The timer axioms take the following form: * - // An initial firing at {offset, 0} - axiom( - ((g(END)._1 >= 500000000) ==> ( - finite_exists (j : integer) in indices :: (j > START && j <= END) - && Timer_t_is_present(t(j)) - && tag_same(g(j), {500000000, 0}) - )) - && ((g(END)._1 < 500000000) ==> ( - finite_forall (i : integer) in indices :: (i > START && i <= END) - ==> (!isNULL(i)) - )) - ); - // Schedule subsequent firings. - axiom( - finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> ( - Timer_t_is_present(t(i)) ==> ( - ( - finite_exists (j : integer) in indices :: (j >= START && j > i) - && Timer_t_is_present(t(j)) - && (g(j) == tag_schedule(g(i), 1000000000)) - ) - ) - ) - ); - // All firings must be evenly spaced out. - axiom( - finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> ( - Timer_t_is_present(t(i)) ==> ( - // Timestamp must be offset + n * period - ( - exists (n : integer) :: ( - n >= 0 && - g(i)._1 == 500000000 + n * 1000000000 - ) - ) - // Microstep must be 0 - && ( - g(i)._2 == 0 - ) - ) - ) - ); + * // An initial firing at {offset, 0} + * axiom( + * ((g(END)._1 >= 500000000) ==> ( + * finite_exists (j : integer) in indices :: (j > START && j <= END) + * && Timer_t_is_present(t(j)) + * && tag_same(g(j), {500000000, 0}) + * )) + * && ((g(END)._1 < 500000000) ==> ( + * finite_forall (i : integer) in indices :: (i > START && i <= END) + * ==> (!isNULL(i)) + * )) + * ); + * // Schedule subsequent firings. + * axiom( + * finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> ( + * Timer_t_is_present(t(i)) ==> ( + * ( + * finite_exists (j : integer) in indices :: (j >= START && j > i) + * && Timer_t_is_present(t(j)) + * && (g(j) == tag_schedule(g(i), 1000000000)) + * ) + * ) + * ) + * ); + * // All firings must be evenly spaced out. + * axiom( + * finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> ( + * Timer_t_is_present(t(i)) ==> ( + * // Timestamp must be offset + n * period + * ( + * exists (n : integer) :: ( + * n >= 0 && + * g(i)._1 == 500000000 + n * 1000000000 + * ) + * ) + * // Microstep must be 0 + * && ( + * g(i)._2 == 0 + * ) + * ) + * ) + * ); */ for (var timer : this.timerInstances) { long offset = timer.getOffset().toNanoSeconds(); @@ -1085,7 +1092,7 @@ protected void generateReactionAxioms() { for (ReactionInstance.Runtime reaction : this.reactionInstances) { String body = reaction.getReaction().getDefinition().getCode().getBody(); - // System.out.println("Printing reaction body of " + reaction); + // System.out.println("DEBUG: Printing reaction body of " + reaction); // System.out.println(body); // Generate a parse tree. @@ -1145,7 +1152,7 @@ protected void generateReactionAxioms() { defaultBehaviorConditions.put(instance, new ArrayList()); } defaultBehaviorConditions.get(instance).add(ifBlockNode.left); - // System.out.println("!!! Added another reset condition: " + ifBlockNode.left); + // System.out.println("DEBUG: Added a reset condition: " + ifBlockNode.left); } // Generate Uclid axiom for the C AST. @@ -1447,15 +1454,13 @@ private void computeCT() { if (this.logicalTimeBased) this.CT = diagram.nodeCount(); else { - // FIXME: This could be much more efficient with a linkedlist implementation. + // FIXME: This could be much more efficient with + // a linkedlist implementation. We can go straight + // to the next node. StateSpaceNode node = diagram.head; this.CT = diagram.head.reactionsInvoked.size(); - while (diagram.getDownstreamAdjacentNodes(node).size() != 0) { - Set downstreamNodes = diagram.getDownstreamAdjacentNodes(node); - for (StateSpaceNode n : downstreamNodes) { - node = n; // Go to the next node. - break; - } + while (node != diagram.tail) { + node = diagram.getDownstreamNode(node); this.CT += node.reactionsInvoked.size(); } } @@ -1466,18 +1471,41 @@ private void computeCT() { // Subtract the non-periodic logical time // interval from the total horizon. long horizonRemained = - this.horizon - diagram.loopNode.tag.timestamp; + Math.subtractExact(this.horizon, diagram.loopNode.tag.timestamp); // Check how many loop iteration is required // to check the remaining horizon. - int loopIterations = (int) Math.ceil( - (double) horizonRemained / diagram.loopPeriod); + int loopIterations = 0; + if (diagram.loopPeriod == 0 && horizonRemained != 0) + Exceptions.sneakyThrow(new Exception( + "ERROR: Zeno behavior detected while the horizon is non-zero. The program has no finite CT.")); + else if (diagram.loopPeriod == 0 && horizonRemained == 0) { + // Handle this edge case. + Exceptions.sneakyThrow(new Exception( + "Unhandled case: both the horizon and period are 0!")); + } + else { + loopIterations = (int) Math.ceil( + (double) horizonRemained / diagram.loopPeriod); + } + if (this.logicalTimeBased) { - // CT = steps required for the non-periodic part - // + steps required for the periodic part + /* + CT = steps required for the non-periodic part + + steps required for the periodic part + this.CT = (diagram.loopNode.index + 1) + (diagram.tail.index - diagram.loopNode.index + 1) * loopIterations; + + An overflow-safe version of the line above + */ + int t0 = Math.addExact(diagram.loopNode.index, 1); + int t1 = Math.subtractExact(diagram.tail.index, diagram.loopNode.index); + int t2 = Math.addExact(t1, 1); + int t3 = Math.multiplyExact(t2, loopIterations); + this.CT = Math.addExact(t0, t3); + } else { // Get the number of events before the loop starts. // This stops right before the loopNode is encountered. @@ -1487,6 +1515,8 @@ private void computeCT() { numReactionInvocationsBeforeLoop += node.reactionsInvoked.size(); node = diagram.getDownstreamNode(node); } + // Account for the loop node in numReactionInvocationsBeforeLoop. + numReactionInvocationsBeforeLoop += node.reactionsInvoked.size(); // Count the events from the loop node until // loop node is reached again. @@ -1496,10 +1526,20 @@ private void computeCT() { numReactionInvocationsInsideLoop += node.reactionsInvoked.size(); } while (node != diagram.loopNode); - // CT = steps required for the non-periodic part - // + steps required for the periodic part + /* + CT = steps required for the non-periodic part + + steps required for the periodic part + this.CT = numReactionInvocationsBeforeLoop + numReactionInvocationsInsideLoop * loopIterations; + + An overflow-safe version of the line above + */ + // System.out.println("DEBUG: numReactionInvocationsBeforeLoop: " + numReactionInvocationsBeforeLoop); + // System.out.println("DEBUG: numReactionInvocationsInsideLoop: " + numReactionInvocationsInsideLoop); + // System.out.println("DEBUG: loopIterations: " + loopIterations); + int t0 = Math.multiplyExact(numReactionInvocationsInsideLoop, loopIterations); + this.CT = Math.addExact(numReactionInvocationsBeforeLoop, t0); } System.out.println("CT: " + this.CT); } @@ -1509,11 +1549,11 @@ private void computeCT() { * Process an MTL property. */ private void processMTLSpec() { - MTLLexer lexer = new MTLLexer(CharStreams.fromString(this.spec)); - CommonTokenStream tokens = new CommonTokenStream(lexer); - MTLParser parser = new MTLParser(tokens); - MtlContext mtlCtx = parser.mtl(); - MTLVisitor visitor = new MTLVisitor(this.tactic); + MTLLexer lexer = new MTLLexer(CharStreams.fromString(this.spec)); + CommonTokenStream tokens = new CommonTokenStream(lexer); + MTLParser parser = new MTLParser(tokens); + MtlContext mtlCtx = parser.mtl(); + MTLVisitor visitor = new MTLVisitor(this.tactic); // The visitor transpiles the MTL into a Uclid axiom. this.FOLSpec = visitor.visitMtl(mtlCtx, "i", 0, "0", 0); diff --git a/org.lflang/src/org/lflang/cli/Lfc.java b/org.lflang/src/org/lflang/cli/Lfc.java index c403d1580e..428e3195d2 100644 --- a/org.lflang/src/org/lflang/cli/Lfc.java +++ b/org.lflang/src/org/lflang/cli/Lfc.java @@ -69,6 +69,7 @@ enum CLIOption { LOGGING(BuildParm.LOGGING, null, true, false, true), LINT(BuildParm.LINT, "l",false, false, true), NO_COMPILE(BuildParm.NO_COMPILE, "n", false, false, true), + NO_VERIFY(BuildParm.NO_VERIFY, null, false, false, true), OUTPUT_PATH(BuildParm.OUTPUT_PATH, "o", true, false, false), QUIET(BuildParm.QUIET, "q", false, false, true), RTI(BuildParm.RTI, "r", true, false, true), diff --git a/org.lflang/src/org/lflang/generator/GeneratorUtils.java b/org.lflang/src/org/lflang/generator/GeneratorUtils.java index d0f6978010..2c675753fd 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorUtils.java +++ b/org.lflang/src/org/lflang/generator/GeneratorUtils.java @@ -85,6 +85,9 @@ public static void setTargetConfig( if (context.getArgs().containsKey("no-compile")) { targetConfig.noCompile = true; } + if (context.getArgs().containsKey("no-verify")) { + targetConfig.noVerify = true; + } if (context.getArgs().containsKey("build-type")) { targetConfig.cmakeBuildType = (BuildType) UnionType.BUILD_TYPE_UNION.forName(context.getArgs().getProperty("build-type")); } diff --git a/org.lflang/src/org/lflang/generator/LFGenerator.java b/org.lflang/src/org/lflang/generator/LFGenerator.java index 0c17297173..d6f97a281f 100644 --- a/org.lflang/src/org/lflang/generator/LFGenerator.java +++ b/org.lflang/src/org/lflang/generator/LFGenerator.java @@ -171,7 +171,7 @@ public void doGenerate(Resource resource, IFileSystemAccess2 fsa, // If "-c" or "--clean" is specified, delete any existing generated directories. cleanIfNeeded(lfContext, fileConfig); - // Check if @property is used. If so, include UclidGenerator. + // Check if @property is used. If so, instantiate a UclidGenerator. // The verification model needs to be generated before the target code // since code generation changes LF program (desugar connections, etc.). Reactor main = ASTUtils.getMainReactor(resource); @@ -183,8 +183,12 @@ public void doGenerate(Resource resource, IFileSystemAccess2 fsa, UclidGenerator uclidGenerator = new UclidGenerator(fileConfig, errorReporter, properties); // Generate uclid files. uclidGenerator.doGenerate(resource, lfContext); - // Invoke the generated uclid files. - uclidGenerator.runner.run(); + if (uclidGenerator.targetConfig.noVerify == false) { + // Invoke the generated uclid files. + uclidGenerator.runner.run(); + } else { + System.out.println("\"no-verify\" is set to true. Skip checking the verification model."); + } } // Generate target code from the LF program. diff --git a/org.lflang/src/org/lflang/generator/LFGeneratorContext.java b/org.lflang/src/org/lflang/generator/LFGeneratorContext.java index f1f2a49ac8..10dae5f70e 100644 --- a/org.lflang/src/org/lflang/generator/LFGeneratorContext.java +++ b/org.lflang/src/org/lflang/generator/LFGeneratorContext.java @@ -33,6 +33,7 @@ public enum BuildParm { LOGGING("The logging level to use by the generated binary"), LINT("Enable or disable linting of generated code."), NO_COMPILE("Do not invoke target compiler."), + NO_VERIFY("Do not check the generated verification model."), OUTPUT_PATH("Specify the root output directory."), QUIET("Suppress output of the target compiler and other commands"), RTI("Specify the location of the RTI."), diff --git a/org.lflang/src/org/lflang/validation/AttributeSpec.java b/org.lflang/src/org/lflang/validation/AttributeSpec.java index b1373b8323..bacd73f23c 100644 --- a/org.lflang/src/org/lflang/validation/AttributeSpec.java +++ b/org.lflang/src/org/lflang/validation/AttributeSpec.java @@ -218,9 +218,10 @@ enum AttrParamType { // SMTL is the safety fragment of Metric Temporal Logic (MTL). ATTRIBUTE_SPECS_BY_NAME.put("property", new AttributeSpec( List.of( - new AttrParamSpec("name", AttrParamType.STRING, null), - new AttrParamSpec("tactic", AttrParamType.STRING, null), - new AttrParamSpec("spec", AttrParamType.STRING, null) + new AttrParamSpec("name", AttrParamType.STRING, "test"), + new AttrParamSpec("tactic", AttrParamType.STRING, "bmc"), + new AttrParamSpec("spec", AttrParamType.STRING, "false"), + new AttrParamSpec("CT", AttrParamType.INT, null) ) )); } From a1b27eae52db9d41db40f57a40e84227ee0cb38b Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 4 Jan 2023 23:00:11 +0800 Subject: [PATCH 076/516] Compare timestamps only to match MTL semantics. --- .../statespace/StateSpaceDiagram.java | 1 - .../org/lflang/analyses/uclid/MTLVisitor.java | 24 ++++++++----------- .../lflang/analyses/uclid/UclidGenerator.java | 7 +++--- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java index 6f25bb60d1..7a0f4eff5e 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java @@ -152,7 +152,6 @@ public CodeBuilder generateDot() { + "\"" + "]"); } } else { - System.out.println("***** nodes: " + this.nodes()); for (StateSpaceNode n : this.nodes()) { List reactions = n.reactionsInvoked.stream() .map(ReactionInstance::getFullName).collect(Collectors.toList()); diff --git a/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java index 43c546a097..5a3e492263 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java +++ b/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java @@ -428,30 +428,26 @@ private String generateTimePredicate(MTLParser.IntervalContext ctx, if (ctx instanceof MTLParser.SingletonContext) { MTLParser.SingletonContext singletonCtx = (MTLParser.SingletonContext)ctx; - timePredicate += "tag_same(g(" + prefixIdx + "), " + "tag_schedule(g(" + timePredicate += "tag_same(g(" + prefixIdx + "), " + "tag_delay(g(" + prevPrefixIdx + "), " + upperBoundNanoSec + "))"; } else { + // Since MTL doesn't include microsteps, we only compare timestamps here. MTLParser.RangeContext rangeCtx = (MTLParser.RangeContext)ctx; timePredicate += "("; if (rangeCtx.LBRACKET() != null) { - // FIXME: Check if this can be replaced by a !tag_earlier. - timePredicate += "tag_later(g(" + prefixIdx + "), " - + "tag_schedule(g(" + prevPrefixIdx + "), " + lowerBoundNanoSec + "))" - + " || " + "tag_same(g(" + prefixIdx + "), " - + "tag_schedule(g(" + prevPrefixIdx + "), " + lowerBoundNanoSec + "))"; + timePredicate += "pi1(g(" + prefixIdx + "))" + " >= " + + "(" + "pi1(g(" + prevPrefixIdx + "))" + " + " + lowerBoundNanoSec + ")"; } else { - timePredicate += "tag_later(g(" + prefixIdx + "), " - + "tag_schedule(g(" + prevPrefixIdx + "), " + lowerBoundNanoSec + "))"; + timePredicate += "pi1(g(" + prefixIdx + "))" + " > " + + "(" + "pi1(g(" + prevPrefixIdx + "))" + " + " + lowerBoundNanoSec + ")"; } timePredicate += ") && ("; if (rangeCtx.RBRACKET() != null) { - timePredicate += "tag_earlier(g(" + prefixIdx + "), " - + "tag_schedule(g(" + prevPrefixIdx + "), " + upperBoundNanoSec + "))" - + " || " + "tag_same(g(" + prefixIdx + "), " - + "tag_schedule(g(" + prevPrefixIdx + "), " + upperBoundNanoSec + "))"; + timePredicate += "pi1(g(" + prefixIdx + "))" + " <= " + + "(" + "pi1(g(" + prevPrefixIdx + "))" + " + " + lowerBoundNanoSec + ")"; } else { - timePredicate += "tag_earlier(g(" + prefixIdx + "), " - + "tag_schedule(g(" + prevPrefixIdx + "), " + upperBoundNanoSec + "))"; + timePredicate += "pi1(g(" + prefixIdx + "))" + " < " + + "(" + "pi1(g(" + prevPrefixIdx + "))" + " + " + lowerBoundNanoSec + ")"; } timePredicate += ")"; } diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java index dafaae2e0e..f3ec063c0f 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java @@ -230,7 +230,9 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { this.CT = Integer.parseInt(CTattr.get().getValue()); } else { computeCT(); - } + } + // For automating data collection, print the CT to stderr. + System.err.println("CT: " + this.CT); if (this.CT > CT_MAX_SUPPORTED) { System.out.println("ERROR: The maximum steps supported is " + CT_MAX_SUPPORTED + " but checking this property requires " + this.CT + " steps. " @@ -1464,7 +1466,6 @@ private void computeCT() { this.CT += node.reactionsInvoked.size(); } } - System.out.println("CT: " + this.CT); } // Over-approximate CT by estimating the number of loop iterations required. else { @@ -1489,7 +1490,6 @@ else if (diagram.loopPeriod == 0 && horizonRemained == 0) { (double) horizonRemained / diagram.loopPeriod); } - if (this.logicalTimeBased) { /* CT = steps required for the non-periodic part @@ -1541,7 +1541,6 @@ else if (diagram.loopPeriod == 0 && horizonRemained == 0) { int t0 = Math.multiplyExact(numReactionInvocationsInsideLoop, loopIterations); this.CT = Math.addExact(numReactionInvocationsBeforeLoop, t0); } - System.out.println("CT: " + this.CT); } } From cf691238bd16e1a38013355ee13fa54306eadd5b Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 12 Jan 2023 15:38:52 +0100 Subject: [PATCH 077/516] Ensure uniqueness of reactions and events during state space exploration --- .../analyses/statespace/EventQueue.java | 24 +++++++++++++++++++ .../statespace/StateSpaceExplorer.java | 20 +++++++++------- .../analyses/statespace/StateSpaceNode.java | 5 ++-- 3 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 org.lflang/src/org/lflang/analyses/statespace/EventQueue.java diff --git a/org.lflang/src/org/lflang/analyses/statespace/EventQueue.java b/org.lflang/src/org/lflang/analyses/statespace/EventQueue.java new file mode 100644 index 0000000000..44bb371f14 --- /dev/null +++ b/org.lflang/src/org/lflang/analyses/statespace/EventQueue.java @@ -0,0 +1,24 @@ +/** + * An event queue implementation that + * sorts events by time tag order + * + * @author{Shaokai Lin } + */ +package org.lflang.analyses.statespace; + +import java.util.PriorityQueue; + +public class EventQueue extends PriorityQueue { + + /** + * Modify the original add() by enforcing uniqueness. + * There cannot be duplicate events in the event queue. + */ + @Override + public boolean add(Event e) { + if (this.contains(e)) + return false; + super.add(e); + return true; + } +} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java index e9a3d5e0ec..df708228de 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java @@ -7,7 +7,7 @@ import java.util.ArrayList; import java.util.HashMap; -import java.util.PriorityQueue; +import java.util.HashSet; import java.util.Set; import org.eclipse.xtext.xbase.lib.Exceptions; @@ -25,7 +25,6 @@ import org.lflang.generator.SendRange; import org.lflang.generator.TimerInstance; import org.lflang.generator.TriggerInstance; - import org.lflang.lf.Expression; import org.lflang.lf.Time; import org.lflang.lf.Variable; @@ -48,7 +47,7 @@ public class StateSpaceExplorer { * defines a unique logical timeline (assuming all reactions * behave _consistently_ throughout the execution). */ - public PriorityQueue eventQ = new PriorityQueue(); + public EventQueue eventQ = new EventQueue(); /** * The main reactor instance based on which the state space @@ -119,9 +118,9 @@ public void explore(Tag horizon, boolean findLoop) { } // A list of reactions invoked at the current logical tag - ArrayList reactionsInvoked; + Set reactionsInvoked; // A temporary list of reactions processed in the current LOOP ITERATION - ArrayList reactionsTemp; + Set reactionsTemp; while (!stop) { @@ -139,7 +138,10 @@ public void explore(Tag horizon, boolean findLoop) { // Collect all the reactions invoked in this current LOOP ITERATION // triggered by the earliest events. - reactionsTemp = new ArrayList(); + // Using a hash set here to make sure the reactions invoked + // are unique. Sometimes multiple events can trigger the same reaction, + // and we do not want to record duplicate reaction invocations. + reactionsTemp = new HashSet(); for (Event e : currentEvents) { Set dependentReactions = e.trigger.getDependentReactions(); @@ -223,7 +225,7 @@ else if (effect instanceof ActionInstance) { //// Now we are done with the node at the previous tag, //// work on the new node at the current timestamp. // Copy the reactions in reactionsTemp. - reactionsInvoked = new ArrayList(reactionsTemp); + reactionsInvoked = new HashSet(reactionsTemp); // Create a new state in the SSD for the current tag, // add the reactions triggered to the state, @@ -233,7 +235,7 @@ else if (effect instanceof ActionInstance) { StateSpaceNode node = new StateSpaceNode( currentTag, // Current tag reactionsInvoked, // Reactions invoked at this tag - new ArrayList(eventQ) // A snapshot of the event queue + new ArrayList(eventQ) // A snapshot of the event queue ); // Initialize currentNode. @@ -293,7 +295,7 @@ else if ( //// Now we are done with the node at the previous tag, //// work on the new node at the current timestamp. // Copy the reactions in reactionsTemp. - reactionsInvoked = new ArrayList(reactionsTemp); + reactionsInvoked = new HashSet(reactionsTemp); // Create a new state in the SSD for the current tag, // add the reactions triggered to the state, diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java index f1556c70e3..2c20f05cdd 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import org.lflang.TimeValue; @@ -19,12 +20,12 @@ public class StateSpaceNode { public int index; // Set in StateSpaceDiagram.java public Tag tag; public TimeValue time; // Readable representation of tag.timestamp - public ArrayList reactionsInvoked; + public Set reactionsInvoked; public ArrayList eventQ; public StateSpaceNode( Tag tag, - ArrayList reactionsInvoked, + Set reactionsInvoked, ArrayList eventQ ) { this.tag = tag; From 1794136ad04827ed8290f285f113756da4934e0b Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sat, 4 Feb 2023 11:00:24 -0800 Subject: [PATCH 078/516] Fix the FOL encoding of Globally. --- org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java index 5a3e492263..c1f782d16f 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java +++ b/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java @@ -224,10 +224,10 @@ public String visitGlobally(MTLParser.GloballyContext ctx, this.horizon = currentHorizon; String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, upperBoundNanoSec, "j" + QFIdx, prefixIdx); - return "!(" + "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " + return "(" + "finite_forall " + "(" + "j" + QFIdx + " : integer) in indices :: " + "j" + QFIdx + " >= " + prefixIdx + " && " + "j" + QFIdx + " <= " + end + " && " + "!" + "isNULL" + "(" + "j" + QFIdx + ")" - + " && " + "!" + "(" + visitPrimary(ctx.formula, ("j"+QFIdx), QFIdx+1, prefixIdx, currentHorizon) + ")" + + " && " + "(" + visitPrimary(ctx.formula, ("j"+QFIdx), QFIdx+1, prefixIdx, currentHorizon) + ")" + " && " + "(" + "\n" + "// Time Predicate\n" + timePredicate + "\n" From 78baf934842b7236c9cd85f475263bdea31c9f6a Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sun, 5 Feb 2023 00:00:25 -0800 Subject: [PATCH 079/516] Support lf_schedule_int() --- .../cast/BuildAstParseTreeVisitor.java | 22 +++++-- .../src/org/lflang/analyses/cast/CAst.java | 16 ++++- .../org/lflang/analyses/cast/CAstVisitor.java | 2 + .../lflang/analyses/cast/CBaseAstVisitor.java | 10 ++++ .../lflang/analyses/cast/CToUclidVisitor.java | 22 ++++++- .../analyses/cast/IfNormalFormAstVisitor.java | 6 ++ .../lflang/analyses/statespace/StateInfo.java | 2 + .../org/lflang/analyses/uclid/MTLVisitor.java | 4 +- .../lflang/analyses/uclid/UclidGenerator.java | 60 ++++++++++++++++--- .../lflang/analyses/uclid/UclidRunner.java | 7 +++ 10 files changed, 132 insertions(+), 19 deletions(-) diff --git a/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java b/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java index 3653296e88..3ab9de9cd8 100644 --- a/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java @@ -189,7 +189,7 @@ else if (declSpec.typeSpecifier().Bool() != null) * OpaqueNode, IfBlockNode, * AdditionNode, SubtractionNode, MultiplicationNode, DivisionNode, * EqualNode, NotEqualNode, LessThanNode, GreaterThanNode, LessEqualNode, GreaterEqualNode, - * SetPortNode, ScheduleNode. + * SetPortNode, ScheduleActionNode. * * @param ctx * @return @@ -315,11 +315,10 @@ public CAst.AstNode visitPostfixExpression(PostfixExpressionContext ctx) { return node; } else if (varNode.name.equals("lf_schedule")) { // return a set port node. - if (params.size() != 2 - && params.size() != 3) { + if (params.size() != 2) { System.out.println(String.join(" ", "Warning (line " + ctx.getStart().getLine() + "):", - "lf_schedule must have 2 or 3 arguments. Detected " + ctx.argumentExpressionList().size(), + "lf_schedule must have two arguments. Detected " + ctx.argumentExpressionList().size(), "Marking the function call as opaque." )); return new CAst.OpaqueNode(); @@ -329,6 +328,21 @@ public CAst.AstNode visitPostfixExpression(PostfixExpressionContext ctx) { node.children.add(visitAssignmentExpression(param)); } return node; + } else if (varNode.name.equals("lf_schedule_int")) { + // return a set port node. + if (params.size() != 3) { + System.out.println(String.join(" ", + "Warning (line " + ctx.getStart().getLine() + "):", + "lf_schedule_int must have three arguments. Detected " + ctx.argumentExpressionList().size(), + "Marking the function call as opaque." + )); + return new CAst.OpaqueNode(); + } + CAst.ScheduleActionIntNode node = new CAst.ScheduleActionIntNode(); + for (AssignmentExpressionContext param : params) { + node.children.add(visitAssignmentExpression(param)); + } + return node; } else { // Generic pointer dereference, unanalyzable. System.out.println(String.join(" ", diff --git a/org.lflang/src/org/lflang/analyses/cast/CAst.java b/org.lflang/src/org/lflang/analyses/cast/CAst.java index 3704406940..d5ce1ee896 100644 --- a/org.lflang/src/org/lflang/analyses/cast/CAst.java +++ b/org.lflang/src/org/lflang/analyses/cast/CAst.java @@ -287,9 +287,7 @@ public static class SetPortNode extends AstNodeBinary implements Visitable { } /** - * action; // self struct variable access is a postfixExpression. - * value; // Could be literal, variable, or pointer. - * additionalDelay; + * AST node for a `lf_schedule(action, additional_delay)` call. */ public static class ScheduleActionNode extends AstNodeDynamic implements Visitable { @Override public T accept(AstVisitor visitor) { @@ -300,6 +298,18 @@ public static class ScheduleActionNode extends AstNodeDynamic implements Visitab } } + /** + * AST node for a `lf_schedule_int(action, additional_delay, integer)` call. + */ + public static class ScheduleActionIntNode extends AstNodeDynamic implements Visitable { + @Override public T accept(AstVisitor visitor) { + return ((CAstVisitor)visitor).visitScheduleActionIntNode(this); + } + @Override public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor)visitor).visitScheduleActionIntNode(this, nodeList); + } + } + /** * Handle state variables appearing as self-> */ diff --git a/org.lflang/src/org/lflang/analyses/cast/CAstVisitor.java b/org.lflang/src/org/lflang/analyses/cast/CAstVisitor.java index f615f6bbb7..0f4f0763dd 100644 --- a/org.lflang/src/org/lflang/analyses/cast/CAstVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/CAstVisitor.java @@ -35,6 +35,7 @@ public interface CAstVisitor extends AstVisitor { T visitSetPortNode(CAst.SetPortNode node); T visitScheduleActionNode(CAst.ScheduleActionNode node); + T visitScheduleActionIntNode(CAst.ScheduleActionIntNode node); T visitStateVarNode(CAst.StateVarNode node); T visitTriggerValueNode(CAst.TriggerValueNode node); T visitTriggerIsPresentNode(CAst.TriggerIsPresentNode node); @@ -70,6 +71,7 @@ public interface CAstVisitor extends AstVisitor { T visitSetPortNode(CAst.SetPortNode node, List nodeList); T visitScheduleActionNode(CAst.ScheduleActionNode node, List nodeList); + T visitScheduleActionIntNode(CAst.ScheduleActionIntNode node, List nodeList); T visitStateVarNode(CAst.StateVarNode node, List nodeList); T visitTriggerValueNode(CAst.TriggerValueNode node, List nodeList); T visitTriggerIsPresentNode(CAst.TriggerIsPresentNode node, List nodeList); diff --git a/org.lflang/src/org/lflang/analyses/cast/CBaseAstVisitor.java b/org.lflang/src/org/lflang/analyses/cast/CBaseAstVisitor.java index 117f1a8536..d30d4f75e2 100644 --- a/org.lflang/src/org/lflang/analyses/cast/CBaseAstVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/CBaseAstVisitor.java @@ -175,6 +175,11 @@ public T visitScheduleActionNode(CAst.ScheduleActionNode node) { return visitAstNodeDynamic(node); } + @Override + public T visitScheduleActionIntNode(CAst.ScheduleActionIntNode node) { + return visitAstNodeDynamic(node); + } + @Override public T visitStateVarNode(CAst.StateVarNode node) { return null; @@ -332,6 +337,11 @@ public T visitScheduleActionNode(CAst.ScheduleActionNode node, List nodeList) { + return visitScheduleActionIntNode(node); + } + @Override public T visitStateVarNode(CAst.StateVarNode node, List nodeList) { return visitStateVarNode(node); diff --git a/org.lflang/src/org/lflang/analyses/cast/CToUclidVisitor.java b/org.lflang/src/org/lflang/analyses/cast/CToUclidVisitor.java index 248eeeab91..015a71b3f2 100644 --- a/org.lflang/src/org/lflang/analyses/cast/CToUclidVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/CToUclidVisitor.java @@ -169,11 +169,11 @@ public String visitScheduleActionNode(ScheduleActionNode node) { String name = ((VariableNode)node.children.get(0)).name; NamedInstance instance = getInstanceByName(name); ActionInstance action = (ActionInstance)instance; - String additionalDelay = visit(node.children.get(1)); // FIXME: Suppport this. + String additionalDelay = visit(node.children.get(1)); String str = "\n(" + "(finite_exists (" + this.qv2 + " : integer) in indices :: (" + this.qv2 + " > " + this.qv + " && " + this.qv2 + " <= END_TRACE) && (" + "\n " + action.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv2 + ")" + ")" - + "\n " + "&& " + "tag_same" + "(" + "g(" + this.qv2 + ")" + ", " + "tag_schedule" + "(" + "g" + "(" + this.qv + ")" + ", " + action.getMinDelay().toNanoSeconds() + ")" + ")" + + "\n " + "&& " + "tag_same" + "(" + "g(" + this.qv2 + ")" + ", " + "tag_schedule" + "(" + "g" + "(" + this.qv + ")" + ", " + "(" + action.getMinDelay().toNanoSeconds() + "+" + additionalDelay + ")" + ")" + ")" + "\n " + "&& " + action.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv2 + ")" + ")" + " == " + "0" + "\n)) // Closes finite_exists" + "\n&& " + action.getFullNameWithJoiner("_") + "_scheduled" + "(" + "d" + "(" + this.qv + ")" + ")" @@ -181,6 +181,24 @@ public String visitScheduleActionNode(ScheduleActionNode node) { return str; } + @Override + public String visitScheduleActionIntNode(ScheduleActionIntNode node) { + String name = ((VariableNode)node.children.get(0)).name; + NamedInstance instance = getInstanceByName(name); + ActionInstance action = (ActionInstance)instance; + String additionalDelay = visit(node.children.get(1)); + String intValue = visit(node.children.get(2)); + String str = "\n(" + + "(finite_exists (" + this.qv2 + " : integer) in indices :: (" + this.qv2 + " > " + this.qv + " && " + this.qv2 + " <= END_TRACE) && (" + + "\n " + action.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv2 + ")" + ")" + + "\n " + "&& " + "tag_same" + "(" + "g(" + this.qv2 + ")" + ", " + "tag_schedule" + "(" + "g" + "(" + this.qv + ")" + ", " + "(" + action.getMinDelay().toNanoSeconds() + "+" + additionalDelay + ")" + ")" + ")" + + "\n)) // Closes finite_exists" + + "\n&& " + action.getFullNameWithJoiner("_") + "_scheduled" + "(" + "d" + "(" + this.qv + ")" + ")" + + "\n&& " + action.getFullNameWithJoiner("_") + "_scheduled_payload" + "(" + "pl" + "(" + this.qv + ")" + ")" + " == " + intValue + + "\n)"; + return str; + } + @Override public String visitSetPortNode(SetPortNode node) { NamedInstance port = getInstanceByName(((VariableNode)node.left).name); diff --git a/org.lflang/src/org/lflang/analyses/cast/IfNormalFormAstVisitor.java b/org.lflang/src/org/lflang/analyses/cast/IfNormalFormAstVisitor.java index a8a5e8931d..31b2f4c5a7 100644 --- a/org.lflang/src/org/lflang/analyses/cast/IfNormalFormAstVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/IfNormalFormAstVisitor.java @@ -56,6 +56,12 @@ public Void visitScheduleActionNode(CAst.ScheduleActionNode node, List conditions) { + this.INF.children.add(generateIfBlock(node, conditions)); + return null; + } + @Override public Void visitIfBlockNode(CAst.IfBlockNode node, List conditions) { List leftConditions = new ArrayList<>(conditions); diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateInfo.java b/org.lflang/src/org/lflang/analyses/statespace/StateInfo.java index badbf25355..84cf25d5f2 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateInfo.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateInfo.java @@ -16,6 +16,7 @@ public class StateInfo { public HashMap variables = new HashMap<>(); public HashMap triggers = new HashMap<>(); public HashMap scheduled = new HashMap<>(); + public HashMap payloads = new HashMap<>(); public void display() { System.out.println("reactions: " + reactions); @@ -23,5 +24,6 @@ public void display() { System.out.println("variables: " + variables); System.out.println("triggers: " + triggers); System.out.println("scheduled: " + scheduled); + System.out.println("payloads: " + payloads); } } \ No newline at end of file diff --git a/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java index c1f782d16f..187caeab63 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java +++ b/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java @@ -444,10 +444,10 @@ private String generateTimePredicate(MTLParser.IntervalContext ctx, timePredicate += ") && ("; if (rangeCtx.RBRACKET() != null) { timePredicate += "pi1(g(" + prefixIdx + "))" + " <= " - + "(" + "pi1(g(" + prevPrefixIdx + "))" + " + " + lowerBoundNanoSec + ")"; + + "(" + "pi1(g(" + prevPrefixIdx + "))" + " + " + upperBoundNanoSec + ")"; } else { timePredicate += "pi1(g(" + prefixIdx + "))" + " < " - + "(" + "pi1(g(" + prevPrefixIdx + "))" + " + " + lowerBoundNanoSec + ")"; + + "(" + "pi1(g(" + prevPrefixIdx + "))" + " + " + upperBoundNanoSec + ")"; } timePredicate += ")"; } diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java index f3ec063c0f..c9b4df6d50 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java @@ -465,7 +465,7 @@ protected void generateTraceDefinition() { code.pr(String.join("\n", "// Define step and event types.", "type step_t = integer;", - "type event_t = { rxn_t, tag_t, state_t, trigger_t, sched_t };", + "type event_t = { rxn_t, tag_t, state_t, trigger_t, sched_t, payload_t };", "", "// Create a bounded trace with length " + String.valueOf(this.CT) )); @@ -522,11 +522,19 @@ protected void generateTraceDefinition() { // Initialize a dummy variable just to make the code compile. initialActionsScheduled = "false"; } + String initialActionsScheduledPayload = ""; + if (this.actionInstances.size() > 0) { + initialActionsScheduledPayload = "0, ".repeat(this.actionInstances.size()); + initialActionsScheduledPayload = initialActionsScheduledPayload.substring(0, initialActionsScheduledPayload.length()-2); + } else { + // Initialize a dummy variable just to make the code compile. + initialActionsScheduledPayload = "0"; + } code.pr("// Helper macro that returns an element based on index."); code.pr("define get(tr : trace_t, i : step_t) : event_t ="); code.pr("if (i >= START || i <= END_TRACE) then tr[i] else"); code.pr("{ " + initialReactions + ", inf(), { " + initialStates + " }, { " + initialTriggerPresence - + " }, {" + initialActionsScheduled + "} };"); + + " }, {" + initialActionsScheduled + " }, {" + initialActionsScheduledPayload + "} };"); // Define an event getter from the trace. code.pr(String.join("\n", @@ -539,6 +547,7 @@ protected void generateTraceDefinition() { "define s (i : step_t) : state_t = elem(i)._3;", "define t (i : step_t) : trigger_t = elem(i)._4;", "define d (i : step_t) : sched_t = elem(i)._5;", + "define pl (i : step_t) : payload_t = elem(i)._6;", "define isNULL (i : step_t) : boolean = rxn(i) == " + initialReactions + ";" )); } @@ -632,10 +641,10 @@ protected void generateReactionIdsAndStateVars() { } // A boolean tuple indicating whether actions are scheduled by reactions - // at the instant when reactions are triggered. + // at the instant when they are triggered. code.pr(String.join("\n", "// A boolean tuple indicating whether actions are scheduled by reactions", - "// at the instant when reactions are triggered." + "// at the instant when they are triggered." )); code.pr("type sched_t = {"); code.indent(); @@ -658,6 +667,34 @@ protected void generateReactionIdsAndStateVars() { code.pr("define " + this.actionInstances.get(i).getFullNameWithJoiner("_") + "_scheduled" + "(d : sched_t) : boolean = d._" + (i+1) + ";"); } + + // A integer tuple indicating the integer payloads scheduled by reactions + // at the instant when they are triggered. + code.pr(String.join("\n", + "// A integer tuple indicating the integer payloads scheduled by reactions", + "// at the instant when they are triggered." + )); + code.pr("type payload_t = {"); + code.indent(); + if (this.actionInstances.size() > 0) { + for (var i = 0 ; i < this.actionInstances.size(); i++) { + code.pr("integer" + ((i == this.actionInstances.size() - 1) ? "" : ",") + + "\t// " + this.actionInstances.get(i)); + } + } else { + code.pr(String.join("\n", + "// There are no actions.", + "// Insert a dummy integer to make the model compile.", + "integer" + )); + } + code.unindent(); + code.pr("};"); + code.pr("// Projection macros for scheduled payloads"); + for (var i = 0; i < this.actionInstances.size(); i++) { + code.pr("define " + this.actionInstances.get(i).getFullNameWithJoiner("_") + + "_scheduled_payload" + "(payload : payload_t) : integer = payload._" + (i+1) + ";"); + } } /** @@ -838,6 +875,7 @@ protected void generateTriggersAndReactions() { " && " + reaction.getFullNameWithJoiner("_") + "(rxn(j))", " && g(i) == tag_schedule(g(j), " + action.getMinDelay().toNanoSeconds() + ")", " && " + action.getFullNameWithJoiner("_") + "_scheduled" + "(d(j))", + " && " + action.getFullNameWithJoiner("_") + "(s(i))" + " == " + action.getFullNameWithJoiner("_") + "_scheduled_payload" + "(pl(j))", "))" ); } @@ -1148,6 +1186,13 @@ protected void generateReactionAxioms() { instance = reaction.getReaction().getParent().actions.stream() .filter(s -> s.getName().equals(name)).findFirst().get(); unusedActions.remove(instance); + } else if (stmt instanceof CAst.ScheduleActionIntNode) { + CAst.ScheduleActionIntNode n = (CAst.ScheduleActionIntNode)stmt; + String name = ((CAst.VariableNode)n.children.get(0)).name; + instance = reaction.getReaction().getParent().actions.stream() + .filter(s -> s.getName().equals(name)).findFirst().get(); + unusedStates.remove(instance); + unusedActions.remove(instance); } else continue; // Create a new entry in the list if there isn't one yet. if (defaultBehaviorConditions.get(instance) == null) { @@ -1165,8 +1210,7 @@ protected void generateReactionAxioms() { "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", " (" + reaction.getReaction().getFullNameWithJoiner("_") + "(rxn(i))" + ")", " ==> " + "(" + "(" + axiom + ")", - "&& " + "( " + "true", - "// By default, the value of the variables used in this reaction stay the same." + "&& " + "( " + "true" )); for (NamedInstance key : defaultBehaviorConditions.keySet()) { CAst.AstNode disjunction = AstUtils.takeDisjunction(defaultBehaviorConditions.get(key)); @@ -1186,7 +1230,7 @@ protected void generateReactionAxioms() { } catch (Exception e) { Exceptions.sneakyThrow(e); } - + code.pr("// Unused state variables and ports are reset by default."); code.pr("&& " + "(" + "(" + resetCondition + ")" + " ==> " + "("); if (key instanceof StateVariableInstance) { StateVariableInstance n = (StateVariableInstance)key; @@ -1219,7 +1263,7 @@ protected void generateReactionAxioms() { // For state variables and ports that are NOT used in this reaction, // their values stay the same by default. - code.pr("// Unused state variables and ports are reset by default."); + code.pr("// By default, the value of the variables used in this reaction stay the same."); if (this.logicalTimeBased) { // If all other reactions that can modify the SAME state variable // are not triggered, then the state variable stay the same. diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java b/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java index f3646245e0..ae928772be 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java @@ -116,6 +116,13 @@ public StateInfo parseStateInfo(String smtStr) { info.scheduled.put(generator.actionInstances.get(i).getFullName(), scheduled[i]); } + // Scheduled payloads + m.find(); + String[] payloads = m.group(1).strip().split("\\s+"); + for (int i = 0; i < generator.actionInstances.size(); i++) { + info.payloads.put(generator.actionInstances.get(i).getFullName(), payloads[i]); + } + return info; } From 344c34af8854f198cde82e497eb61d96478cbdd2 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 6 Feb 2023 19:07:19 -0800 Subject: [PATCH 080/516] Add a uniqueness axiom, update CEX parser to support let bindings --- .../lflang/analyses/uclid/UclidGenerator.java | 23 ++++- .../lflang/analyses/uclid/UclidRunner.java | 88 ++++++++++++++++--- 2 files changed, 97 insertions(+), 14 deletions(-) diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java index c9b4df6d50..53e43a8250 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java @@ -763,9 +763,11 @@ protected void generateReactorSemantics() { "" )); - // For logical time-based semantics, there is no need for this since each logical instant - // will only happen once in the trace. + //// Axioms for the event-based semantics if (!this.logicalTimeBased) { + code.pr("//// Axioms for the event-based semantics"); + + // the same event can only trigger once in a logical instant code.pr(String.join("\n", "// the same event can only trigger once in a logical instant", "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> (finite_forall (j : integer) in indices ::", @@ -773,6 +775,23 @@ protected void generateReactorSemantics() { " ==> !tag_same(g(i), g(j)))));", "" )); + + // Only one reaction gets triggered at a time. + ArrayList reactionsStatus = new ArrayList<>(); + for (int i = 0; i < this.reactionInstances.size(); i++) + reactionsStatus.add("false"); + code.pr(String.join("\n", + "// Only one reaction gets triggered at a time.", + "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> (", + " isNULL(i)")); + code.indent(); + for (int i = 0; i < this.reactionInstances.size(); i++) { + String[] li = reactionsStatus.toArray(String[]::new); + li[i] = "true"; + code.pr("|| rxn(i) == " + "{" + String.join(", ", li) + "}"); + } + code.unindent(); + code.pr("));"); } } diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java b/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java index ae928772be..05d4485826 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java @@ -14,6 +14,7 @@ import java.nio.file.Paths; import java.util.Arrays; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; @@ -70,17 +71,43 @@ public UclidRunner( public StateInfo parseStateInfo(String smtStr) { StateInfo info = new StateInfo(); - // Remove the outer tuple layer. - Pattern p = Pattern.compile("^\\(_tuple_\\d+((.|\\n)*)\\)$"); + // Check for any let bindings. + Pattern p = Pattern.compile("\\(let \\(\\((a!\\d+) \\(_tuple_\\d+ ((.)+?)\\)\\)\\)(\\\\n|\\s)+\\(_tuple_\\d+ ((.|\\n)+)\\)"); + HashMap symbolTable = new HashMap<>(); Matcher m = p.matcher(smtStr.strip()); - m.find(); - String itemized = m.group(1).strip(); + String itemized = ""; + if (m.find()) { + // FIXME: Handle multiple let bindings. + String symbol = m.group(1).strip(); + String value = m.group(2).strip(); + symbolTable.put(symbol, value); + itemized = m.group(5).strip(); + } + else { + // Remove the outer tuple layer. + p = Pattern.compile("\\(_tuple_\\d+((.|\\n)*)\\)"); + m = p.matcher(smtStr.strip()); + m.find(); + itemized = m.group(1).strip(); + } - // Reactions - p = Pattern.compile("\\(_tuple_\\d+([^\\)]+)\\)"); + // Process each sub-tuple by matching (_tuple_n ...) + // or matching a!n, where n is an integer. + p = Pattern.compile("(a!\\d+)|\\(_tuple_\\d+((\\s|\\\\n|\\d|\\(- \\d+\\)|true|false)+)\\)"); m = p.matcher(itemized); + + // Reactions m.find(); - String[] reactions = m.group(1).strip().split("\\s+"); + String reactionsStr = ""; + // Found a let binding. + if (m.group(1) != null) { + reactionsStr = symbolTable.get(m.group(1)).strip(); + } + // The rest falls into group 2. + else { + reactionsStr = m.group(2).strip(); + } + String[] reactions = reactionsStr.split("\\s+"); // Iterating over generator lists avoids accounting for // the single dummy Uclid variable inserted earlier. for (int i = 0; i < generator.reactionInstances.size(); i++) { @@ -90,35 +117,72 @@ public StateInfo parseStateInfo(String smtStr) { // Time tag m.find(); - String[] tag = m.group(1).strip().split("\\s+"); + String tagStr = ""; + // Found a let binding. + if (m.group(1) != null) + tagStr = symbolTable.get(m.group(1)).strip(); + // The rest falls into group 2. + else tagStr = m.group(2).strip(); + String[] tag = tagStr.split("\\s+"); info.tag = new Tag( Long.parseLong(tag[0]), Long.parseLong(tag[1]), false); // Variables + // Currently all integers. + // Negative numbers could appear. m.find(); - String[] variables = m.group(1).strip().split("\\s+"); + String variablesStr = ""; + // Found a let binding. + if (m.group(1) != null) + variablesStr = symbolTable.get(m.group(1)).strip(); + // The rest falls into group 2. + else variablesStr = m.group(2).strip(); + String[] variables = variablesStr.replaceAll("\\(-\\s(.*?)\\)", "-$1") + .split("\\s+"); for (int i = 0; i < generator.namedInstances.size(); i++) { info.variables.put(generator.namedInstances.get(i).getFullName(), variables[i]); } // Triggers m.find(); - String[] triggers = m.group(1).strip().split("\\s+"); + String triggersStr = ""; + // Found a let binding. + if (m.group(1) != null) + triggersStr = symbolTable.get(m.group(1)).strip(); + // The rest falls into group 2. + else triggersStr = m.group(2).strip(); + String[] triggers = triggersStr.split("\\s+"); for (int i = 0; i < generator.triggerInstances.size(); i++) { info.triggers.put(generator.triggerInstances.get(i).getFullName(), triggers[i]); } // Actions scheduled m.find(); - String[] scheduled = m.group(1).strip().split("\\s+"); + String scheduledStr = ""; + // Found a let binding. + if (m.group(1) != null) + scheduledStr = symbolTable.get(m.group(1)).strip(); + // The rest falls into group 2. + else scheduledStr = m.group(2).strip(); + String[] scheduled = scheduledStr.split("\\s+"); for (int i = 0; i < generator.actionInstances.size(); i++) { info.scheduled.put(generator.actionInstances.get(i).getFullName(), scheduled[i]); } // Scheduled payloads + // Currently all integers. + // Negative numbers could appear. m.find(); - String[] payloads = m.group(1).strip().split("\\s+"); + String payloadsStr = ""; + // Found a let binding. + if (m.group(1) != null) + payloadsStr = symbolTable.get(m.group(1)).strip(); + // The rest falls into group 2. + else payloadsStr = m.group(2).strip(); + String[] payloads = payloadsStr + .replaceAll("\\(-\\s(.*?)\\)", "-$1") + .split("\\s+"); for (int i = 0; i < generator.actionInstances.size(); i++) { info.payloads.put(generator.actionInstances.get(i).getFullName(), payloads[i]); } From 2b99656493affba5d3805fcb3ad68a15ee2b3c22 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 6 Feb 2023 22:19:59 -0800 Subject: [PATCH 081/516] Add happen-before relations based on priorities, fix C AST generation. --- .../cast/BuildAstParseTreeVisitor.java | 18 +++++++++--------- .../lflang/analyses/uclid/UclidGenerator.java | 15 ++++++++++++++- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java b/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java index 3ab9de9cd8..4807713e83 100644 --- a/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java @@ -442,11 +442,11 @@ public CAst.AstNode visitCastExpression(CastExpressionContext ctx) { public CAst.AstNode visitMultiplicativeExpression(MultiplicativeExpressionContext ctx) { if (ctx.castExpression().size() > 1) { CAst.AstNodeBinary node; - if (ctx.Star() != null) { + if (ctx.Star().size() > 0) { node = new CAst.MultiplicationNode(); - } else if (ctx.Div() != null) { + } else if (ctx.Div().size() > 0) { node = new CAst.DivisionNode(); - } else if (ctx.Mod() != null) { + } else if (ctx.Mod().size() > 0) { System.out.println(String.join(" ", "Warning (line " + ctx.getStart().getLine() + "):", "Mod expression '%' is currently unsupported.", @@ -467,9 +467,9 @@ public CAst.AstNode visitMultiplicativeExpression(MultiplicativeExpressionContex public CAst.AstNode visitAdditiveExpression(AdditiveExpressionContext ctx) { if (ctx.multiplicativeExpression().size() > 1) { CAst.AstNodeBinary node; - if (ctx.Plus() != null) { + if (ctx.Plus().size() > 0) { node = new CAst.AdditionNode(); - } else if (ctx.Minus() != null) { + } else if (ctx.Minus().size() > 0) { node = new CAst.SubtractionNode(); } else { node = new CAst.AstNodeBinary(); @@ -498,13 +498,13 @@ public CAst.AstNode visitShiftExpression(ShiftExpressionContext ctx) { public CAst.AstNode visitRelationalExpression(RelationalExpressionContext ctx) { if (ctx.shiftExpression().size() > 1) { CAst.AstNodeBinary node; - if (ctx.Less() != null) { + if (ctx.Less().size() > 0) { node = new CAst.LessThanNode(); - } else if (ctx.LessEqual() != null) { + } else if (ctx.LessEqual().size() > 0) { node = new CAst.LessEqualNode(); - } else if (ctx.Greater() != null) { + } else if (ctx.Greater().size() > 0) { node = new CAst.GreaterThanNode(); - } else if (ctx.GreaterEqual() != null) { + } else if (ctx.GreaterEqual().size() > 0) { node = new CAst.GreaterEqualNode(); } else { node = new CAst.AstNodeBinary(); diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java index 53e43a8250..6b961bdc8e 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java @@ -718,16 +718,29 @@ protected void generateReactorSemantics() { code.indent(); // Get happen-before relation between two reactions. code.pr("|| (tag_same(e1._2, e2._2) && ( false"); - // Iterate over every pair of reactions. + // Iterate over reactions based on upstream/downstream relations. for (var upstreamRuntime : this.reactionInstances) { var downstreamReactions = upstreamRuntime.getReaction().dependentReactions(); for (var downstream : downstreamReactions) { + // If the downstream reaction and the upstream + // reaction are in the same reactor, skip, since + // they will be accounted for in the for loop below. + if (downstream.getParent().equals(upstreamRuntime.getReaction().getParent())) continue; for (var downstreamRuntime : downstream.getRuntimeInstances()) { code.pr("|| (" + upstreamRuntime.getReaction().getFullNameWithJoiner("_") + "(e1._1)" + " && " + downstreamRuntime.getReaction().getFullNameWithJoiner("_") + "(e2._1)" + ")"); } } } + // Iterate over reactions based on priorities. + for (var reactor : this.reactorInstances) { + for (int i = 0; i < reactor.reactions.size(); i++) { + for (int j = i+1; j < reactor.reactions.size(); j++) { + code.pr("|| (" + reactor.reactions.get(i).getFullNameWithJoiner("_") + "(e1._1)" + + " && " + reactor.reactions.get(j).getFullNameWithJoiner("_") + "(e2._1)" + ")"); + } + } + } code.unindent(); code.pr("))"); } From f5e12dae3633a2abf171ff4a7c922677857d550a Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 17 Feb 2023 17:07:38 -0800 Subject: [PATCH 082/516] Add preliminary github action workflow --- .github/workflows/ci.yml | 7 +++ .github/workflows/uclid-verifier-c-tests.yml | 56 ++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 .github/workflows/uclid-verifier-c-tests.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f339b9aa9e..44e04aba48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -107,3 +107,10 @@ jobs: serialization-tests: uses: lf-lang/lingua-franca/.github/workflows/serialization-tests.yml@master needs: cancel + + # Run the Uclid benchmark tests. + uclid-verifier-c-tests: + uses: lf-lang/lingua-franca/.github/workflows/uclid-verifier-c-tests.yml@master + with: + target: 'C' + needs: cancel diff --git a/.github/workflows/uclid-verifier-c-tests.yml b/.github/workflows/uclid-verifier-c-tests.yml new file mode 100644 index 0000000000..f9ee060c06 --- /dev/null +++ b/.github/workflows/uclid-verifier-c-tests.yml @@ -0,0 +1,56 @@ +name: Uclid5-based Verifier Tests + +on: + workflow_call: + +env: + ACTIONS_STEP_DEBUG: TRUE + ACTIONS_RUNNER_DEBUG: TRUE + +jobs: + run: + strategy: + matrix: + platform: [ubuntu-latest] + runs-on: ${{ matrix.platform }} + steps: + - name: Check out lingua-franca repository + uses: actions/checkout@v3 + - name: Check out lf-verifer-benchmarks repository + uses: actions/checkout@v3 + with: + repository: lsk567/lf-verifer-benchmarks + path: ./lf-verifer-benchmarks + - name: Check out Uclid5 repository + uses: actions/checkout@v3 + with: + repository: uclid-org/uclid + path: ./uclid + - name: See where we are + run: | + echo "Where am I?" + pwd + ls + - name: Install Z3 + run: | + cd uclid + ./get-z3-linux.sh + ./setup-z3-linux.sh + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '11' + cache: 'sbt' + - name: Test sbt + run: | + sbt --version + - name: Install Uclid5 + run: | + sbt compile universal:packageBin + cd target/universal/ + unzip uclid-0.9.5.zip + export PATH="$(pwd)/uclid-0.9.5/bin:$PATH" + + + \ No newline at end of file From ff47ed03a05cf785c7aa561e6aac735fc2b39496 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sat, 18 Feb 2023 12:10:17 -0800 Subject: [PATCH 083/516] Try to trigger CI --- .github/workflows/ci.yml | 2 +- .github/workflows/uclid-verifier-c-tests.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e78fabb39a..70c772ae0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,7 +131,7 @@ jobs: # Run the Uclid benchmark tests. uclid-verifier-c-tests: - uses: lf-lang/lingua-franca/.github/workflows/uclid-verifier-c-tests.yml@master + uses: ./.github/workflows/uclid-verifier-c-tests.yml with: target: 'C' needs: cancel diff --git a/.github/workflows/uclid-verifier-c-tests.yml b/.github/workflows/uclid-verifier-c-tests.yml index f9ee060c06..3dd62e36dc 100644 --- a/.github/workflows/uclid-verifier-c-tests.yml +++ b/.github/workflows/uclid-verifier-c-tests.yml @@ -25,6 +25,7 @@ jobs: uses: actions/checkout@v3 with: repository: uclid-org/uclid + ref: 4fd5e566c5f87b052f92e9b23723a85e1c4d8c1c path: ./uclid - name: See where we are run: | From fdff6b55b417c7b39f3842ff3151141db8c69db8 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sat, 18 Feb 2023 12:11:36 -0800 Subject: [PATCH 084/516] Try again --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 70c772ae0d..0404b47d65 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,6 +132,4 @@ jobs: # Run the Uclid benchmark tests. uclid-verifier-c-tests: uses: ./.github/workflows/uclid-verifier-c-tests.yml - with: - target: 'C' needs: cancel From 5a13516c361689452c46f661a813ac24d0c15c62 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sun, 19 Feb 2023 11:06:38 -0800 Subject: [PATCH 085/516] Debug workflows --- .github/workflows/ci.yml | 178 +++++++++---------- .github/workflows/uclid-verifier-c-tests.yml | 5 +- 2 files changed, 93 insertions(+), 90 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0404b47d65..132f443520 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,114 +20,114 @@ jobs: cancel: uses: lf-lang/lingua-franca/.github/workflows/cancel.yml@master - # Test the Gradle and Maven build. - build: - uses: lf-lang/lingua-franca/.github/workflows/build.yml@master - needs: cancel - - # Build the tools used for processing execution traces - build-tracing-tools: - uses: lf-lang/lingua-franca/.github/workflows/build-trace-tools.yml@master - needs: cancel + # # Test the Gradle and Maven build. + # build: + # uses: lf-lang/lingua-franca/.github/workflows/build.yml@master + # needs: cancel - # Check that automatic code formatting works. - # TODO: Uncomment after fed-gen is merged. - # format: - # uses: lf-lang/lingua-franca/.github/workflows/format.yml@master + # # Build the tools used for processing execution traces + # build-tracing-tools: + # uses: lf-lang/lingua-franca/.github/workflows/build-trace-tools.yml@master # needs: cancel - # Run the unit tests. - unit-tests: - uses: lf-lang/lingua-franca/.github/workflows/unit-tests.yml@master - needs: cancel + # # Check that automatic code formatting works. + # # TODO: Uncomment after fed-gen is merged. + # # format: + # # uses: lf-lang/lingua-franca/.github/workflows/format.yml@master + # # needs: cancel - # Run tests for the standalone compiler. - cli-tests: - uses: lf-lang/lingua-franca/.github/workflows/cli-tests.yml@master - needs: cancel + # # Run the unit tests. + # unit-tests: + # uses: lf-lang/lingua-franca/.github/workflows/unit-tests.yml@master + # needs: cancel - # Run the C benchmark tests. - c-benchmark-tests: - uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main - with: - target: 'C' - needs: cancel + # # Run tests for the standalone compiler. + # cli-tests: + # uses: lf-lang/lingua-franca/.github/workflows/cli-tests.yml@master + # needs: cancel - # Run tests for Eclipse. - eclipse-tests: - uses: lf-lang/lingua-franca/.github/workflows/eclipse-tests.yml@master - needs: cancel + # # Run the C benchmark tests. + # c-benchmark-tests: + # uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main + # with: + # target: 'C' + # needs: cancel - # Run language server tests. - lsp-tests: - uses: lf-lang/lingua-franca/.github/workflows/lsp-tests.yml@master - needs: cancel + # # Run tests for Eclipse. + # eclipse-tests: + # uses: lf-lang/lingua-franca/.github/workflows/eclipse-tests.yml@master + # needs: cancel - # Run the C integration tests. - c-tests: - uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master - needs: cancel + # # Run language server tests. + # lsp-tests: + # uses: lf-lang/lingua-franca/.github/workflows/lsp-tests.yml@master + # needs: cancel + + # # Run the C integration tests. + # c-tests: + # uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master + # needs: cancel - # Run the C Arduino integration tests. - c-arduino-tests: - uses: lf-lang/lingua-franca/.github/workflows/c-arduino-tests.yml@master - needs: cancel + # # Run the C Arduino integration tests. + # c-arduino-tests: + # uses: lf-lang/lingua-franca/.github/workflows/c-arduino-tests.yml@master + # needs: cancel - # Run the C Zephyr integration tests. - c-zephyr-tests: - uses: lf-lang/lingua-franca/.github/workflows/c-zephyr-tests.yml@master - needs: cancel + # # Run the C Zephyr integration tests. + # c-zephyr-tests: + # uses: lf-lang/lingua-franca/.github/workflows/c-zephyr-tests.yml@master + # needs: cancel - # Run the CCpp integration tests. - ccpp-tests: - uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master - with: - use-cpp: true - needs: cancel + # # Run the CCpp integration tests. + # ccpp-tests: + # uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master + # with: + # use-cpp: true + # needs: cancel - # Run the C++ benchmark tests. - cpp-benchmark-tests: - uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main - with: - target: 'Cpp' - needs: cancel + # # Run the C++ benchmark tests. + # cpp-benchmark-tests: + # uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main + # with: + # target: 'Cpp' + # needs: cancel - # Run the C++ integration tests. - cpp-tests: - uses: lf-lang/lingua-franca/.github/workflows/cpp-tests.yml@master - needs: cancel + # # Run the C++ integration tests. + # cpp-tests: + # uses: lf-lang/lingua-franca/.github/workflows/cpp-tests.yml@master + # needs: cancel - # Run the C++ integration tests on ROS2. - cpp-ros2-tests: - uses: lf-lang/lingua-franca/.github/workflows/cpp-ros2-tests.yml@master - needs: cancel + # # Run the C++ integration tests on ROS2. + # cpp-ros2-tests: + # uses: lf-lang/lingua-franca/.github/workflows/cpp-ros2-tests.yml@master + # needs: cancel - # Run the Python integration tests. - py-tests: - uses: lf-lang/lingua-franca/.github/workflows/py-tests.yml@master - needs: cancel + # # Run the Python integration tests. + # py-tests: + # uses: lf-lang/lingua-franca/.github/workflows/py-tests.yml@master + # needs: cancel - # Run the Rust integration tests. - rs-tests: - uses: lf-lang/lingua-franca/.github/workflows/rs-tests.yml@master - needs: cancel + # # Run the Rust integration tests. + # rs-tests: + # uses: lf-lang/lingua-franca/.github/workflows/rs-tests.yml@master + # needs: cancel - # Run the Rust benchmark tests. - rs-benchmark-tests: - uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main - with: - target: 'Rust' - needs: cancel + # # Run the Rust benchmark tests. + # rs-benchmark-tests: + # uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main + # with: + # target: 'Rust' + # needs: cancel - # Run the TypeScript integration tests. - ts-tests: - uses: lf-lang/lingua-franca/.github/workflows/ts-tests.yml@master - needs: cancel + # # Run the TypeScript integration tests. + # ts-tests: + # uses: lf-lang/lingua-franca/.github/workflows/ts-tests.yml@master + # needs: cancel - # Run the serialization tests - serialization-tests: - uses: lf-lang/lingua-franca/.github/workflows/serialization-tests.yml@master - needs: cancel + # # Run the serialization tests + # serialization-tests: + # uses: lf-lang/lingua-franca/.github/workflows/serialization-tests.yml@master + # needs: cancel # Run the Uclid benchmark tests. uclid-verifier-c-tests: diff --git a/.github/workflows/uclid-verifier-c-tests.yml b/.github/workflows/uclid-verifier-c-tests.yml index 3dd62e36dc..e694e74d34 100644 --- a/.github/workflows/uclid-verifier-c-tests.yml +++ b/.github/workflows/uclid-verifier-c-tests.yml @@ -16,6 +16,8 @@ jobs: steps: - name: Check out lingua-franca repository uses: actions/checkout@v3 + - name: Setup upterm session + uses: lhotari/action-upterm@v1 - name: Check out lf-verifer-benchmarks repository uses: actions/checkout@v3 with: @@ -48,7 +50,8 @@ jobs: sbt --version - name: Install Uclid5 run: | - sbt compile universal:packageBin + sbt update clean compile + sbt universal:packageBin cd target/universal/ unzip uclid-0.9.5.zip export PATH="$(pwd)/uclid-0.9.5/bin:$PATH" From a23224478f29addf7bffa081de58807b399bcab0 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sun, 19 Feb 2023 11:14:18 -0800 Subject: [PATCH 086/516] Setup ssh after installing sbt --- .github/workflows/uclid-verifier-c-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/uclid-verifier-c-tests.yml b/.github/workflows/uclid-verifier-c-tests.yml index e694e74d34..6638c68600 100644 --- a/.github/workflows/uclid-verifier-c-tests.yml +++ b/.github/workflows/uclid-verifier-c-tests.yml @@ -16,8 +16,6 @@ jobs: steps: - name: Check out lingua-franca repository uses: actions/checkout@v3 - - name: Setup upterm session - uses: lhotari/action-upterm@v1 - name: Check out lf-verifer-benchmarks repository uses: actions/checkout@v3 with: @@ -48,6 +46,8 @@ jobs: - name: Test sbt run: | sbt --version + - name: Setup upterm session + uses: lhotari/action-upterm@v1 - name: Install Uclid5 run: | sbt update clean compile From 22ec9ff44eff4652b9a4294996a80d3447e9aeb5 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sun, 19 Feb 2023 11:44:54 -0800 Subject: [PATCH 087/516] Attempt to fix a directory issue --- .github/workflows/uclid-verifier-c-tests.yml | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/.github/workflows/uclid-verifier-c-tests.yml b/.github/workflows/uclid-verifier-c-tests.yml index 6638c68600..b82be05460 100644 --- a/.github/workflows/uclid-verifier-c-tests.yml +++ b/.github/workflows/uclid-verifier-c-tests.yml @@ -27,16 +27,6 @@ jobs: repository: uclid-org/uclid ref: 4fd5e566c5f87b052f92e9b23723a85e1c4d8c1c path: ./uclid - - name: See where we are - run: | - echo "Where am I?" - pwd - ls - - name: Install Z3 - run: | - cd uclid - ./get-z3-linux.sh - ./setup-z3-linux.sh - name: Set up JDK 11 uses: actions/setup-java@v3 with: @@ -48,8 +38,16 @@ jobs: sbt --version - name: Setup upterm session uses: lhotari/action-upterm@v1 - - name: Install Uclid5 + - name: See where we are + run: | + echo "Where am I?" + pwd + ls + - name: Install Z3 and Uclid5 run: | + cd uclid + ./get-z3-linux.sh + ./setup-z3-linux.sh sbt update clean compile sbt universal:packageBin cd target/universal/ From d208f4cbded2e9984dec36471deecf589fbad7cb Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 20 Feb 2023 17:21:12 -0800 Subject: [PATCH 088/516] Add an 'expect' field to @property --- .../lflang/analyses/uclid/UclidGenerator.java | 19 ++++++++++++---- .../lflang/analyses/uclid/UclidRunner.java | 22 ++++++++++++++----- .../org/lflang/validation/AttributeSpec.java | 3 ++- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java index 07f9feccae..1d8d76276d 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java @@ -39,6 +39,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; @@ -130,7 +131,8 @@ public class UclidGenerator extends GeneratorBase { public List namedInstances; // Named instances = triggers + state variables // A list of paths to the uclid files generated - public List generatedFiles = new ArrayList(); + public List generatedFiles = new ArrayList<>(); + public Map expectations = new HashMap<>(); // The directory where the generated files are placed public Path outputDir; @@ -156,6 +158,7 @@ public class UclidGenerator extends GeneratorBase { protected String name; protected String tactic; protected String spec; // SMTL + protected String expect; /** * The horizon (the total time interval required for evaluating @@ -222,11 +225,11 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { processMTLSpec(); - Optional CTattr = prop.getAttrParms().stream() + Optional CTAttr = prop.getAttrParms().stream() .filter(attr -> attr.getName().equals("CT")) .findFirst(); - if (CTattr.isPresent()) { - this.CT = Integer.parseInt(CTattr.get().getValue()); + if (CTAttr.isPresent()) { + this.CT = Integer.parseInt(CTAttr.get().getValue()); } else { computeCT(); } @@ -239,6 +242,12 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { continue; } + Optional ExpectAttr = prop.getAttrParms().stream() + .filter(attr -> attr.getName().equals("expect")) + .findFirst(); + if (ExpectAttr.isPresent()) + this.expect = ExpectAttr.get().getValue(); + generateUclidFile(); } } @@ -258,6 +267,8 @@ protected void generateUclidFile() { generateUclidCode(); code.writeToFile(filename); this.generatedFiles.add(file); + if (this.expect != null) + this.expectations.put(file, this.expect); } catch (IOException e) { Exceptions.sneakyThrow(e); } diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java b/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java index fc334336e6..be28c77665 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java @@ -204,15 +204,16 @@ public void run() { command.run(); String output = command.getOutput().toString(); - boolean failed = output.contains("FAILED"); - if (failed) { + boolean valid = !output.contains("FAILED"); + if (valid) { + System.out.println("Valid!"); + } else { System.out.println("Not valid!"); try { // Read from the JSON counterexample (cex). String cexJSONStr = Files.readString( Paths.get(path.toString() + ".json"), StandardCharsets.UTF_8); - // System.out.println(cexJSONStr); JSONObject cexJSON = new JSONObject(cexJSONStr); //// Extract the counterexample trace from JSON. @@ -244,8 +245,19 @@ public void run() { } catch (IOException e) { System.out.println("ERROR: Not able to read from " + path.toString()); } - } else { - System.out.println("Valid!"); + } + + // If "expect" is set, check if the result matches it. + // If not, exit with error code 1. + String expect = generator.expectations.get(path); + if (expect != null) { + boolean expectValid = Boolean.parseBoolean(expect); + if (expectValid != valid) { + System.out.println( + "ERROR: The expected result does not match the actual result. Expected: " + + expectValid + ", Result: " + valid); + System.exit(1); + } } } } diff --git a/org.lflang/src/org/lflang/validation/AttributeSpec.java b/org.lflang/src/org/lflang/validation/AttributeSpec.java index 93950c2788..006e6c228c 100644 --- a/org.lflang/src/org/lflang/validation/AttributeSpec.java +++ b/org.lflang/src/org/lflang/validation/AttributeSpec.java @@ -222,7 +222,8 @@ enum AttrParamType { new AttrParamSpec("name", AttrParamType.STRING, false), new AttrParamSpec("tactic", AttrParamType.STRING, false), new AttrParamSpec("spec", AttrParamType.STRING, false), - new AttrParamSpec("CT", AttrParamType.INT, true) + new AttrParamSpec("CT", AttrParamType.INT, true), + new AttrParamSpec("expect", AttrParamType.BOOLEAN, true) ) )); // @enclave(each=boolean) From 6b760c8ff1cea5663374947c096ad2e614e3ae26 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 20 Feb 2023 17:21:30 -0800 Subject: [PATCH 089/516] Update CI to run the test script --- .github/workflows/uclid-verifier-c-tests.yml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/uclid-verifier-c-tests.yml b/.github/workflows/uclid-verifier-c-tests.yml index b82be05460..762bf6765a 100644 --- a/.github/workflows/uclid-verifier-c-tests.yml +++ b/.github/workflows/uclid-verifier-c-tests.yml @@ -33,16 +33,8 @@ jobs: distribution: 'temurin' java-version: '11' cache: 'sbt' - - name: Test sbt - run: | - sbt --version - name: Setup upterm session uses: lhotari/action-upterm@v1 - - name: See where we are - run: | - echo "Where am I?" - pwd - ls - name: Install Z3 and Uclid5 run: | cd uclid @@ -53,6 +45,8 @@ jobs: cd target/universal/ unzip uclid-0.9.5.zip export PATH="$(pwd)/uclid-0.9.5/bin:$PATH" - - - \ No newline at end of file + uclid --version + - name: Run the test script + run: | + cd ../lf-verifer-benchmarks + ./scripts/test-lf-verifier \ No newline at end of file From d23906f367dbcdc6c0af1c880447e8494a44ea6b Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 20 Feb 2023 18:29:52 -0800 Subject: [PATCH 090/516] Relocate the CI script. --- .github/workflows/ci.yml | 180 +++++++++---------- .github/workflows/uclid-verifier-c-tests.yml | 52 ------ 2 files changed, 90 insertions(+), 142 deletions(-) delete mode 100644 .github/workflows/uclid-verifier-c-tests.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 132f443520..9f67aed00d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,116 +20,116 @@ jobs: cancel: uses: lf-lang/lingua-franca/.github/workflows/cancel.yml@master - # # Test the Gradle and Maven build. - # build: - # uses: lf-lang/lingua-franca/.github/workflows/build.yml@master - # needs: cancel - - # # Build the tools used for processing execution traces - # build-tracing-tools: - # uses: lf-lang/lingua-franca/.github/workflows/build-trace-tools.yml@master - # needs: cancel + # Test the Gradle and Maven build. + build: + uses: lf-lang/lingua-franca/.github/workflows/build.yml@master + needs: cancel - # # Check that automatic code formatting works. - # # TODO: Uncomment after fed-gen is merged. - # # format: - # # uses: lf-lang/lingua-franca/.github/workflows/format.yml@master - # # needs: cancel + # Build the tools used for processing execution traces + build-tracing-tools: + uses: lf-lang/lingua-franca/.github/workflows/build-trace-tools.yml@master + needs: cancel - # # Run the unit tests. - # unit-tests: - # uses: lf-lang/lingua-franca/.github/workflows/unit-tests.yml@master + # Check that automatic code formatting works. + # TODO: Uncomment after fed-gen is merged. + # format: + # uses: lf-lang/lingua-franca/.github/workflows/format.yml@master # needs: cancel - # # Run tests for the standalone compiler. - # cli-tests: - # uses: lf-lang/lingua-franca/.github/workflows/cli-tests.yml@master - # needs: cancel + # Run the unit tests. + unit-tests: + uses: lf-lang/lingua-franca/.github/workflows/unit-tests.yml@master + needs: cancel - # # Run the C benchmark tests. - # c-benchmark-tests: - # uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main - # with: - # target: 'C' - # needs: cancel + # Run tests for the standalone compiler. + cli-tests: + uses: lf-lang/lingua-franca/.github/workflows/cli-tests.yml@master + needs: cancel - # # Run tests for Eclipse. - # eclipse-tests: - # uses: lf-lang/lingua-franca/.github/workflows/eclipse-tests.yml@master - # needs: cancel + # Run the C benchmark tests. + c-benchmark-tests: + uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main + with: + target: 'C' + needs: cancel - # # Run language server tests. - # lsp-tests: - # uses: lf-lang/lingua-franca/.github/workflows/lsp-tests.yml@master - # needs: cancel + # Run tests for Eclipse. + eclipse-tests: + uses: lf-lang/lingua-franca/.github/workflows/eclipse-tests.yml@master + needs: cancel - # # Run the C integration tests. - # c-tests: - # uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master - # needs: cancel + # Run language server tests. + lsp-tests: + uses: lf-lang/lingua-franca/.github/workflows/lsp-tests.yml@master + needs: cancel + + # Run the C integration tests. + c-tests: + uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master + needs: cancel - # # Run the C Arduino integration tests. - # c-arduino-tests: - # uses: lf-lang/lingua-franca/.github/workflows/c-arduino-tests.yml@master - # needs: cancel + # Run the C Arduino integration tests. + c-arduino-tests: + uses: lf-lang/lingua-franca/.github/workflows/c-arduino-tests.yml@master + needs: cancel - # # Run the C Zephyr integration tests. - # c-zephyr-tests: - # uses: lf-lang/lingua-franca/.github/workflows/c-zephyr-tests.yml@master - # needs: cancel + # Run the C Zephyr integration tests. + c-zephyr-tests: + uses: lf-lang/lingua-franca/.github/workflows/c-zephyr-tests.yml@master + needs: cancel - # # Run the CCpp integration tests. - # ccpp-tests: - # uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master - # with: - # use-cpp: true - # needs: cancel + # Run the CCpp integration tests. + ccpp-tests: + uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master + with: + use-cpp: true + needs: cancel - # # Run the C++ benchmark tests. - # cpp-benchmark-tests: - # uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main - # with: - # target: 'Cpp' - # needs: cancel + # Run the C++ benchmark tests. + cpp-benchmark-tests: + uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main + with: + target: 'Cpp' + needs: cancel - # # Run the C++ integration tests. - # cpp-tests: - # uses: lf-lang/lingua-franca/.github/workflows/cpp-tests.yml@master - # needs: cancel + # Run the C++ integration tests. + cpp-tests: + uses: lf-lang/lingua-franca/.github/workflows/cpp-tests.yml@master + needs: cancel - # # Run the C++ integration tests on ROS2. - # cpp-ros2-tests: - # uses: lf-lang/lingua-franca/.github/workflows/cpp-ros2-tests.yml@master - # needs: cancel + # Run the C++ integration tests on ROS2. + cpp-ros2-tests: + uses: lf-lang/lingua-franca/.github/workflows/cpp-ros2-tests.yml@master + needs: cancel - # # Run the Python integration tests. - # py-tests: - # uses: lf-lang/lingua-franca/.github/workflows/py-tests.yml@master - # needs: cancel + # Run the Python integration tests. + py-tests: + uses: lf-lang/lingua-franca/.github/workflows/py-tests.yml@master + needs: cancel - # # Run the Rust integration tests. - # rs-tests: - # uses: lf-lang/lingua-franca/.github/workflows/rs-tests.yml@master - # needs: cancel + # Run the Rust integration tests. + rs-tests: + uses: lf-lang/lingua-franca/.github/workflows/rs-tests.yml@master + needs: cancel - # # Run the Rust benchmark tests. - # rs-benchmark-tests: - # uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main - # with: - # target: 'Rust' - # needs: cancel + # Run the Rust benchmark tests. + rs-benchmark-tests: + uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main + with: + target: 'Rust' + needs: cancel - # # Run the TypeScript integration tests. - # ts-tests: - # uses: lf-lang/lingua-franca/.github/workflows/ts-tests.yml@master - # needs: cancel + # Run the TypeScript integration tests. + ts-tests: + uses: lf-lang/lingua-franca/.github/workflows/ts-tests.yml@master + needs: cancel - # # Run the serialization tests - # serialization-tests: - # uses: lf-lang/lingua-franca/.github/workflows/serialization-tests.yml@master - # needs: cancel + # Run the serialization tests + serialization-tests: + uses: lf-lang/lingua-franca/.github/workflows/serialization-tests.yml@master + needs: cancel # Run the Uclid benchmark tests. uclid-verifier-c-tests: - uses: ./.github/workflows/uclid-verifier-c-tests.yml + uses: lsk567/lf-verifer-benchmarks/.github/workflows/uclid-verifier-c-tests.yml needs: cancel diff --git a/.github/workflows/uclid-verifier-c-tests.yml b/.github/workflows/uclid-verifier-c-tests.yml deleted file mode 100644 index 762bf6765a..0000000000 --- a/.github/workflows/uclid-verifier-c-tests.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Uclid5-based Verifier Tests - -on: - workflow_call: - -env: - ACTIONS_STEP_DEBUG: TRUE - ACTIONS_RUNNER_DEBUG: TRUE - -jobs: - run: - strategy: - matrix: - platform: [ubuntu-latest] - runs-on: ${{ matrix.platform }} - steps: - - name: Check out lingua-franca repository - uses: actions/checkout@v3 - - name: Check out lf-verifer-benchmarks repository - uses: actions/checkout@v3 - with: - repository: lsk567/lf-verifer-benchmarks - path: ./lf-verifer-benchmarks - - name: Check out Uclid5 repository - uses: actions/checkout@v3 - with: - repository: uclid-org/uclid - ref: 4fd5e566c5f87b052f92e9b23723a85e1c4d8c1c - path: ./uclid - - name: Set up JDK 11 - uses: actions/setup-java@v3 - with: - distribution: 'temurin' - java-version: '11' - cache: 'sbt' - - name: Setup upterm session - uses: lhotari/action-upterm@v1 - - name: Install Z3 and Uclid5 - run: | - cd uclid - ./get-z3-linux.sh - ./setup-z3-linux.sh - sbt update clean compile - sbt universal:packageBin - cd target/universal/ - unzip uclid-0.9.5.zip - export PATH="$(pwd)/uclid-0.9.5/bin:$PATH" - uclid --version - - name: Run the test script - run: | - cd ../lf-verifer-benchmarks - ./scripts/test-lf-verifier \ No newline at end of file From ac43a76f99a46071ddb2fb1e8d50c8270dcf17fe Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 20 Feb 2023 18:31:25 -0800 Subject: [PATCH 091/516] Specify version --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f67aed00d..64095e8363 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,5 +131,5 @@ jobs: # Run the Uclid benchmark tests. uclid-verifier-c-tests: - uses: lsk567/lf-verifer-benchmarks/.github/workflows/uclid-verifier-c-tests.yml + uses: lsk567/lf-verifer-benchmarks/.github/workflows/uclid-verifier-c-tests.yml@master needs: cancel From e77e60ca5d0645af6ee53da28e301d9effaee994 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 20 Feb 2023 18:34:45 -0800 Subject: [PATCH 092/516] Fix version --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64095e8363..3733768bf4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,5 +131,5 @@ jobs: # Run the Uclid benchmark tests. uclid-verifier-c-tests: - uses: lsk567/lf-verifer-benchmarks/.github/workflows/uclid-verifier-c-tests.yml@master + uses: lsk567/lf-verifer-benchmarks/.github/workflows/uclid-verifier-c-tests.yml@main needs: cancel From 489a563f374744bb263fd638c99a0ad6cdff42f3 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 20 Feb 2023 22:01:03 -0800 Subject: [PATCH 093/516] Fix typo --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3733768bf4..65abbd7751 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,5 +131,5 @@ jobs: # Run the Uclid benchmark tests. uclid-verifier-c-tests: - uses: lsk567/lf-verifer-benchmarks/.github/workflows/uclid-verifier-c-tests.yml@main + uses: lsk567/lf-verifier-benchmarks/.github/workflows/uclid-verifier-c-tests.yml@main needs: cancel From 11d985f603b2954731d9de231b33623cb31a65a0 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 20 Feb 2023 23:13:07 -0800 Subject: [PATCH 094/516] Relocate the yml file. --- .github/workflows/ci.yml | 180 +++++++++---------- .github/workflows/uclid-verifier-c-tests.yml | 76 ++++++++ 2 files changed, 166 insertions(+), 90 deletions(-) create mode 100644 .github/workflows/uclid-verifier-c-tests.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65abbd7751..ee3fa6ef18 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,116 +20,116 @@ jobs: cancel: uses: lf-lang/lingua-franca/.github/workflows/cancel.yml@master - # Test the Gradle and Maven build. - build: - uses: lf-lang/lingua-franca/.github/workflows/build.yml@master - needs: cancel - - # Build the tools used for processing execution traces - build-tracing-tools: - uses: lf-lang/lingua-franca/.github/workflows/build-trace-tools.yml@master - needs: cancel + # # Test the Gradle and Maven build. + # build: + # uses: lf-lang/lingua-franca/.github/workflows/build.yml@master + # needs: cancel - # Check that automatic code formatting works. - # TODO: Uncomment after fed-gen is merged. - # format: - # uses: lf-lang/lingua-franca/.github/workflows/format.yml@master + # # Build the tools used for processing execution traces + # build-tracing-tools: + # uses: lf-lang/lingua-franca/.github/workflows/build-trace-tools.yml@master # needs: cancel - # Run the unit tests. - unit-tests: - uses: lf-lang/lingua-franca/.github/workflows/unit-tests.yml@master - needs: cancel + # # Check that automatic code formatting works. + # # TODO: Uncomment after fed-gen is merged. + # # format: + # # uses: lf-lang/lingua-franca/.github/workflows/format.yml@master + # # needs: cancel - # Run tests for the standalone compiler. - cli-tests: - uses: lf-lang/lingua-franca/.github/workflows/cli-tests.yml@master - needs: cancel + # # Run the unit tests. + # unit-tests: + # uses: lf-lang/lingua-franca/.github/workflows/unit-tests.yml@master + # needs: cancel - # Run the C benchmark tests. - c-benchmark-tests: - uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main - with: - target: 'C' - needs: cancel + # # Run tests for the standalone compiler. + # cli-tests: + # uses: lf-lang/lingua-franca/.github/workflows/cli-tests.yml@master + # needs: cancel - # Run tests for Eclipse. - eclipse-tests: - uses: lf-lang/lingua-franca/.github/workflows/eclipse-tests.yml@master - needs: cancel + # # Run the C benchmark tests. + # c-benchmark-tests: + # uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main + # with: + # target: 'C' + # needs: cancel - # Run language server tests. - lsp-tests: - uses: lf-lang/lingua-franca/.github/workflows/lsp-tests.yml@master - needs: cancel + # # Run tests for Eclipse. + # eclipse-tests: + # uses: lf-lang/lingua-franca/.github/workflows/eclipse-tests.yml@master + # needs: cancel - # Run the C integration tests. - c-tests: - uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master - needs: cancel + # # Run language server tests. + # lsp-tests: + # uses: lf-lang/lingua-franca/.github/workflows/lsp-tests.yml@master + # needs: cancel + + # # Run the C integration tests. + # c-tests: + # uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master + # needs: cancel - # Run the C Arduino integration tests. - c-arduino-tests: - uses: lf-lang/lingua-franca/.github/workflows/c-arduino-tests.yml@master - needs: cancel + # # Run the C Arduino integration tests. + # c-arduino-tests: + # uses: lf-lang/lingua-franca/.github/workflows/c-arduino-tests.yml@master + # needs: cancel - # Run the C Zephyr integration tests. - c-zephyr-tests: - uses: lf-lang/lingua-franca/.github/workflows/c-zephyr-tests.yml@master - needs: cancel + # # Run the C Zephyr integration tests. + # c-zephyr-tests: + # uses: lf-lang/lingua-franca/.github/workflows/c-zephyr-tests.yml@master + # needs: cancel - # Run the CCpp integration tests. - ccpp-tests: - uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master - with: - use-cpp: true - needs: cancel + # # Run the CCpp integration tests. + # ccpp-tests: + # uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master + # with: + # use-cpp: true + # needs: cancel - # Run the C++ benchmark tests. - cpp-benchmark-tests: - uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main - with: - target: 'Cpp' - needs: cancel + # # Run the C++ benchmark tests. + # cpp-benchmark-tests: + # uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main + # with: + # target: 'Cpp' + # needs: cancel - # Run the C++ integration tests. - cpp-tests: - uses: lf-lang/lingua-franca/.github/workflows/cpp-tests.yml@master - needs: cancel + # # Run the C++ integration tests. + # cpp-tests: + # uses: lf-lang/lingua-franca/.github/workflows/cpp-tests.yml@master + # needs: cancel - # Run the C++ integration tests on ROS2. - cpp-ros2-tests: - uses: lf-lang/lingua-franca/.github/workflows/cpp-ros2-tests.yml@master - needs: cancel + # # Run the C++ integration tests on ROS2. + # cpp-ros2-tests: + # uses: lf-lang/lingua-franca/.github/workflows/cpp-ros2-tests.yml@master + # needs: cancel - # Run the Python integration tests. - py-tests: - uses: lf-lang/lingua-franca/.github/workflows/py-tests.yml@master - needs: cancel + # # Run the Python integration tests. + # py-tests: + # uses: lf-lang/lingua-franca/.github/workflows/py-tests.yml@master + # needs: cancel - # Run the Rust integration tests. - rs-tests: - uses: lf-lang/lingua-franca/.github/workflows/rs-tests.yml@master - needs: cancel + # # Run the Rust integration tests. + # rs-tests: + # uses: lf-lang/lingua-franca/.github/workflows/rs-tests.yml@master + # needs: cancel - # Run the Rust benchmark tests. - rs-benchmark-tests: - uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main - with: - target: 'Rust' - needs: cancel + # # Run the Rust benchmark tests. + # rs-benchmark-tests: + # uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main + # with: + # target: 'Rust' + # needs: cancel - # Run the TypeScript integration tests. - ts-tests: - uses: lf-lang/lingua-franca/.github/workflows/ts-tests.yml@master - needs: cancel + # # Run the TypeScript integration tests. + # ts-tests: + # uses: lf-lang/lingua-franca/.github/workflows/ts-tests.yml@master + # needs: cancel - # Run the serialization tests - serialization-tests: - uses: lf-lang/lingua-franca/.github/workflows/serialization-tests.yml@master - needs: cancel + # # Run the serialization tests + # serialization-tests: + # uses: lf-lang/lingua-franca/.github/workflows/serialization-tests.yml@master + # needs: cancel # Run the Uclid benchmark tests. uclid-verifier-c-tests: - uses: lsk567/lf-verifier-benchmarks/.github/workflows/uclid-verifier-c-tests.yml@main + uses: .github/workflows/uclid-verifier-c-tests.yml needs: cancel diff --git a/.github/workflows/uclid-verifier-c-tests.yml b/.github/workflows/uclid-verifier-c-tests.yml new file mode 100644 index 0000000000..f23bcd8545 --- /dev/null +++ b/.github/workflows/uclid-verifier-c-tests.yml @@ -0,0 +1,76 @@ +name: Uclid5-based Verifier Tests + +on: + # Trigger this workflow also on workflow_call events. + workflow_call: + +env: + ACTIONS_STEP_DEBUG: TRUE + ACTIONS_RUNNER_DEBUG: TRUE + +jobs: + run: + strategy: + matrix: + platform: [ubuntu-latest] + runs-on: ${{ matrix.platform }} + steps: + - name: Check out lingua-franca repository + uses: actions/checkout@v3 + with: + path: lingua-franca + - name: Check out lf-verifier-benchmarks repository + uses: actions/checkout@v3 + with: + repository: lsk567/lf-verifier-benchmarks + ref: master + path: lf-verifier-benchmarks + - name: Check out Uclid5 repository + uses: actions/checkout@v3 + with: + repository: uclid-org/uclid + ref: 4fd5e566c5f87b052f92e9b23723a85e1c4d8c1c + path: uclid + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: 17 + - name: Install Lingua Franca + working-directory: lingua-franca/ + run: | + ./bin/build-lf-cli + ./bin/lfc --version + echo "$(pwd)/bin" >> $GITHUB_PATH + - name: Cache Z3 + working-directory: uclid/ + id: cache-z3 + uses: actions/cache@v3 + with: + path: z3/ + key: ${{ runner.os }}-z3-${{ hashFiles('get-z3-linux.sh') }}-1 + - name: Download Z3 + working-directory: uclid/ + if: steps.cache-z3.outputs.cache-hit != 'true' + run: ./get-z3-linux.sh + - name: Add Z3 to Path + working-directory: uclid/ + run: | + echo "$(pwd)/z3/bin/" >> $GITHUB_PATH + echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/z3/bin/" >> $GITHUB_ENV + - name: Print Z3 Version + run: z3 --version + - name: Install Uclid5 + working-directory: uclid/ + run: | + sbt update clean compile + sbt universal:packageBin + cd target/universal/ + unzip uclid-0.9.5.zip + ./uclid-0.9.5/bin/uclid --help + echo "$(pwd)/uclid-0.9.5/bin" >> $GITHUB_PATH + - name: Setup upterm session + uses: lhotari/action-upterm@v1 + - name: Run the test script + working-directory: lf-verifier-benchmarks/ + run: ./scripts/test-lf-verifier \ No newline at end of file From c02b3b11d2a748225fe0a2783bec73c310edcd21 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 20 Feb 2023 23:17:57 -0800 Subject: [PATCH 095/516] Emphasize local workflow --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee3fa6ef18..132f443520 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,5 +131,5 @@ jobs: # Run the Uclid benchmark tests. uclid-verifier-c-tests: - uses: .github/workflows/uclid-verifier-c-tests.yml + uses: ./.github/workflows/uclid-verifier-c-tests.yml needs: cancel From 5c725d898bce3ab6d13ec5598d0b62de0748f5fb Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 20 Feb 2023 23:19:52 -0800 Subject: [PATCH 096/516] Remove caching --- .github/workflows/uclid-verifier-c-tests.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/uclid-verifier-c-tests.yml b/.github/workflows/uclid-verifier-c-tests.yml index f23bcd8545..37b647aefa 100644 --- a/.github/workflows/uclid-verifier-c-tests.yml +++ b/.github/workflows/uclid-verifier-c-tests.yml @@ -42,13 +42,6 @@ jobs: ./bin/build-lf-cli ./bin/lfc --version echo "$(pwd)/bin" >> $GITHUB_PATH - - name: Cache Z3 - working-directory: uclid/ - id: cache-z3 - uses: actions/cache@v3 - with: - path: z3/ - key: ${{ runner.os }}-z3-${{ hashFiles('get-z3-linux.sh') }}-1 - name: Download Z3 working-directory: uclid/ if: steps.cache-z3.outputs.cache-hit != 'true' From 276811b1c9fbcc36736bb1271dfca368187b6924 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 20 Feb 2023 23:23:30 -0800 Subject: [PATCH 097/516] Fix branch name --- .github/workflows/uclid-verifier-c-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/uclid-verifier-c-tests.yml b/.github/workflows/uclid-verifier-c-tests.yml index 37b647aefa..e6e2ff4ad9 100644 --- a/.github/workflows/uclid-verifier-c-tests.yml +++ b/.github/workflows/uclid-verifier-c-tests.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@v3 with: repository: lsk567/lf-verifier-benchmarks - ref: master + ref: main path: lf-verifier-benchmarks - name: Check out Uclid5 repository uses: actions/checkout@v3 From 662867e1b60553388cea5c51bb823abf0fc3d67d Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 20 Feb 2023 23:26:43 -0800 Subject: [PATCH 098/516] Fetch history --- .github/workflows/uclid-verifier-c-tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/uclid-verifier-c-tests.yml b/.github/workflows/uclid-verifier-c-tests.yml index e6e2ff4ad9..fafb19c9de 100644 --- a/.github/workflows/uclid-verifier-c-tests.yml +++ b/.github/workflows/uclid-verifier-c-tests.yml @@ -19,6 +19,8 @@ jobs: uses: actions/checkout@v3 with: path: lingua-franca + submodules: true + fetch-depth: 0 - name: Check out lf-verifier-benchmarks repository uses: actions/checkout@v3 with: From 6ad8f4bf0d6fb941282a70799add17356718904f Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 20 Feb 2023 23:37:52 -0800 Subject: [PATCH 099/516] Remove SSH and update CI --- .github/workflows/ci.yml | 178 +++++++++---------- .github/workflows/uclid-verifier-c-tests.yml | 2 - 2 files changed, 89 insertions(+), 91 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 132f443520..0404b47d65 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,114 +20,114 @@ jobs: cancel: uses: lf-lang/lingua-franca/.github/workflows/cancel.yml@master - # # Test the Gradle and Maven build. - # build: - # uses: lf-lang/lingua-franca/.github/workflows/build.yml@master - # needs: cancel - - # # Build the tools used for processing execution traces - # build-tracing-tools: - # uses: lf-lang/lingua-franca/.github/workflows/build-trace-tools.yml@master - # needs: cancel + # Test the Gradle and Maven build. + build: + uses: lf-lang/lingua-franca/.github/workflows/build.yml@master + needs: cancel - # # Check that automatic code formatting works. - # # TODO: Uncomment after fed-gen is merged. - # # format: - # # uses: lf-lang/lingua-franca/.github/workflows/format.yml@master - # # needs: cancel + # Build the tools used for processing execution traces + build-tracing-tools: + uses: lf-lang/lingua-franca/.github/workflows/build-trace-tools.yml@master + needs: cancel - # # Run the unit tests. - # unit-tests: - # uses: lf-lang/lingua-franca/.github/workflows/unit-tests.yml@master + # Check that automatic code formatting works. + # TODO: Uncomment after fed-gen is merged. + # format: + # uses: lf-lang/lingua-franca/.github/workflows/format.yml@master # needs: cancel - # # Run tests for the standalone compiler. - # cli-tests: - # uses: lf-lang/lingua-franca/.github/workflows/cli-tests.yml@master - # needs: cancel + # Run the unit tests. + unit-tests: + uses: lf-lang/lingua-franca/.github/workflows/unit-tests.yml@master + needs: cancel - # # Run the C benchmark tests. - # c-benchmark-tests: - # uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main - # with: - # target: 'C' - # needs: cancel + # Run tests for the standalone compiler. + cli-tests: + uses: lf-lang/lingua-franca/.github/workflows/cli-tests.yml@master + needs: cancel - # # Run tests for Eclipse. - # eclipse-tests: - # uses: lf-lang/lingua-franca/.github/workflows/eclipse-tests.yml@master - # needs: cancel + # Run the C benchmark tests. + c-benchmark-tests: + uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main + with: + target: 'C' + needs: cancel - # # Run language server tests. - # lsp-tests: - # uses: lf-lang/lingua-franca/.github/workflows/lsp-tests.yml@master - # needs: cancel + # Run tests for Eclipse. + eclipse-tests: + uses: lf-lang/lingua-franca/.github/workflows/eclipse-tests.yml@master + needs: cancel - # # Run the C integration tests. - # c-tests: - # uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master - # needs: cancel + # Run language server tests. + lsp-tests: + uses: lf-lang/lingua-franca/.github/workflows/lsp-tests.yml@master + needs: cancel + + # Run the C integration tests. + c-tests: + uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master + needs: cancel - # # Run the C Arduino integration tests. - # c-arduino-tests: - # uses: lf-lang/lingua-franca/.github/workflows/c-arduino-tests.yml@master - # needs: cancel + # Run the C Arduino integration tests. + c-arduino-tests: + uses: lf-lang/lingua-franca/.github/workflows/c-arduino-tests.yml@master + needs: cancel - # # Run the C Zephyr integration tests. - # c-zephyr-tests: - # uses: lf-lang/lingua-franca/.github/workflows/c-zephyr-tests.yml@master - # needs: cancel + # Run the C Zephyr integration tests. + c-zephyr-tests: + uses: lf-lang/lingua-franca/.github/workflows/c-zephyr-tests.yml@master + needs: cancel - # # Run the CCpp integration tests. - # ccpp-tests: - # uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master - # with: - # use-cpp: true - # needs: cancel + # Run the CCpp integration tests. + ccpp-tests: + uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master + with: + use-cpp: true + needs: cancel - # # Run the C++ benchmark tests. - # cpp-benchmark-tests: - # uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main - # with: - # target: 'Cpp' - # needs: cancel + # Run the C++ benchmark tests. + cpp-benchmark-tests: + uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main + with: + target: 'Cpp' + needs: cancel - # # Run the C++ integration tests. - # cpp-tests: - # uses: lf-lang/lingua-franca/.github/workflows/cpp-tests.yml@master - # needs: cancel + # Run the C++ integration tests. + cpp-tests: + uses: lf-lang/lingua-franca/.github/workflows/cpp-tests.yml@master + needs: cancel - # # Run the C++ integration tests on ROS2. - # cpp-ros2-tests: - # uses: lf-lang/lingua-franca/.github/workflows/cpp-ros2-tests.yml@master - # needs: cancel + # Run the C++ integration tests on ROS2. + cpp-ros2-tests: + uses: lf-lang/lingua-franca/.github/workflows/cpp-ros2-tests.yml@master + needs: cancel - # # Run the Python integration tests. - # py-tests: - # uses: lf-lang/lingua-franca/.github/workflows/py-tests.yml@master - # needs: cancel + # Run the Python integration tests. + py-tests: + uses: lf-lang/lingua-franca/.github/workflows/py-tests.yml@master + needs: cancel - # # Run the Rust integration tests. - # rs-tests: - # uses: lf-lang/lingua-franca/.github/workflows/rs-tests.yml@master - # needs: cancel + # Run the Rust integration tests. + rs-tests: + uses: lf-lang/lingua-franca/.github/workflows/rs-tests.yml@master + needs: cancel - # # Run the Rust benchmark tests. - # rs-benchmark-tests: - # uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main - # with: - # target: 'Rust' - # needs: cancel + # Run the Rust benchmark tests. + rs-benchmark-tests: + uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main + with: + target: 'Rust' + needs: cancel - # # Run the TypeScript integration tests. - # ts-tests: - # uses: lf-lang/lingua-franca/.github/workflows/ts-tests.yml@master - # needs: cancel + # Run the TypeScript integration tests. + ts-tests: + uses: lf-lang/lingua-franca/.github/workflows/ts-tests.yml@master + needs: cancel - # # Run the serialization tests - # serialization-tests: - # uses: lf-lang/lingua-franca/.github/workflows/serialization-tests.yml@master - # needs: cancel + # Run the serialization tests + serialization-tests: + uses: lf-lang/lingua-franca/.github/workflows/serialization-tests.yml@master + needs: cancel # Run the Uclid benchmark tests. uclid-verifier-c-tests: diff --git a/.github/workflows/uclid-verifier-c-tests.yml b/.github/workflows/uclid-verifier-c-tests.yml index fafb19c9de..cb1c9e7e1b 100644 --- a/.github/workflows/uclid-verifier-c-tests.yml +++ b/.github/workflows/uclid-verifier-c-tests.yml @@ -64,8 +64,6 @@ jobs: unzip uclid-0.9.5.zip ./uclid-0.9.5/bin/uclid --help echo "$(pwd)/uclid-0.9.5/bin" >> $GITHUB_PATH - - name: Setup upterm session - uses: lhotari/action-upterm@v1 - name: Run the test script working-directory: lf-verifier-benchmarks/ run: ./scripts/test-lf-verifier \ No newline at end of file From 4a38b8f9262c30c82ef53f2fd4e8a012dc6a40e4 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Tue, 21 Feb 2023 10:28:32 -0800 Subject: [PATCH 100/516] Fix Globally --- org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java index 187caeab63..0f40df73d8 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java +++ b/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java @@ -225,13 +225,14 @@ public String visitGlobally(MTLParser.GloballyContext ctx, String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, upperBoundNanoSec, "j" + QFIdx, prefixIdx); return "(" + "finite_forall " + "(" + "j" + QFIdx + " : integer) in indices :: " - + "j" + QFIdx + " >= " + prefixIdx + " && " + "j" + QFIdx + " <= " + end + + "(" + "j" + QFIdx + " >= " + prefixIdx + " && " + "j" + QFIdx + " <= " + end + " && " + "!" + "isNULL" + "(" + "j" + QFIdx + ")" - + " && " + "(" + visitPrimary(ctx.formula, ("j"+QFIdx), QFIdx+1, prefixIdx, currentHorizon) + ")" + " && " + "(" + "\n" + "// Time Predicate\n" + timePredicate + "\n" - + "))"; + + ")" + ")" + + " ==> " + "(" + visitPrimary(ctx.formula, ("j"+QFIdx), QFIdx+1, prefixIdx, currentHorizon) + ")" + + ")"; } public String visitFinally(MTLParser.FinallyContext ctx, From 17828a2d5f2cc4c172cbcacfd0d0b72762e06d24 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 3 Mar 2023 11:45:36 -0800 Subject: [PATCH 101/516] Let the event queue sort based on both tags and trigger names --- org.lflang/src/org/lflang/analyses/statespace/Event.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/analyses/statespace/Event.java b/org.lflang/src/org/lflang/analyses/statespace/Event.java index ef0f004e4f..cea24280ac 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/Event.java +++ b/org.lflang/src/org/lflang/analyses/statespace/Event.java @@ -24,7 +24,13 @@ public TriggerInstance getTrigger() { @Override public int compareTo(Event e) { - return this.tag.compareTo(e.tag); + // Compare tags first. + int ret = this.tag.compareTo(e.tag); + // If tags match, compare trigger names. + if (ret == 0) + ret = this.trigger.getFullName() + .compareTo(e.trigger.getFullName()); + return ret; } /** From 22a7b113aad5bc4cec610d2c69c2ed5f2b71d855 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 3 Mar 2023 12:08:03 -0800 Subject: [PATCH 102/516] Prune dead code --- .../org/lflang/analyses/cast/AstUtils.java | 2 -- .../cast/BuildAstParseTreeVisitor.java | 5 --- .../lflang/analyses/cast/CToUclidVisitor.java | 14 -------- .../org/lflang/analyses/uclid/MTLVisitor.java | 2 +- .../lflang/analyses/uclid/UclidGenerator.java | 33 +------------------ .../lflang/analyses/uclid/UclidRunner.java | 2 +- 6 files changed, 3 insertions(+), 55 deletions(-) diff --git a/org.lflang/src/org/lflang/analyses/cast/AstUtils.java b/org.lflang/src/org/lflang/analyses/cast/AstUtils.java index a42198cb4b..bed532e424 100644 --- a/org.lflang/src/org/lflang/analyses/cast/AstUtils.java +++ b/org.lflang/src/org/lflang/analyses/cast/AstUtils.java @@ -5,8 +5,6 @@ import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.misc.Interval; -import org.eclipse.xtext.xbase.lib.Exceptions; - public class AstUtils { public static CAst.AstNode takeConjunction(List conditions) { diff --git a/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java b/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java index 4807713e83..450a4798e4 100644 --- a/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java @@ -12,15 +12,10 @@ public class BuildAstParseTreeVisitor extends CBaseVisitor { @Override public CAst.AstNode visitBlockItemList(BlockItemListContext ctx) { CAst.StatementSequenceNode stmtSeq = new CAst.StatementSequenceNode(); - // Populate the children. for (BlockItemContext blockItem : ctx.blockItem()) { - // System.out.println(blockItem); stmtSeq.children.add(visit(blockItem)); } - - // System.out.println(stmtSeq.children); - return stmtSeq; } diff --git a/org.lflang/src/org/lflang/analyses/cast/CToUclidVisitor.java b/org.lflang/src/org/lflang/analyses/cast/CToUclidVisitor.java index 015a71b3f2..c794186cbe 100644 --- a/org.lflang/src/org/lflang/analyses/cast/CToUclidVisitor.java +++ b/org.lflang/src/org/lflang/analyses/cast/CToUclidVisitor.java @@ -60,14 +60,6 @@ public String visitAdditionNode(AdditionNode node) { @Override public String visitAssignmentNode(AssignmentNode node) { String lhs = visit(node.left); - // String lhs = ""; - // if (node.left instanceof StateVarNode) { - // NamedInstance instance = getInstanceByName(((StateVarNode)node.left).name); - // lhs = instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; - // // this.unchangedStates.remove(instance); // Remove instance from the unchanged list. - // } else { - // System.out.println("Unreachable!"); // FIXME: Throw exception. - // } String rhs = visit(node.right); return "(" + lhs + " == " + rhs + ")"; } @@ -278,12 +270,6 @@ public String visitVariableNode(VariableNode node) { //// Private functions private NamedInstance getInstanceByName(String name) { - - // For some reason, the following one liner doesn't work: - // - // return this.instances.stream().filter(i -> i.getDefinition() - // .getName().equals(name)).findFirst().get(); - for (NamedInstance i : this.instances) { if (i instanceof ActionInstance) { if (((ActionInstance)i).getDefinition().getName().equals(name)) { diff --git a/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java index 0f40df73d8..79e7e4e2fc 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java +++ b/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java @@ -23,7 +23,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ***************/ /** - * Transpiler from an MTL specification to a Uclid axiom. + * (EXPERIMENTAL) Transpiler from an MTL specification to a Uclid axiom. * * @author{Shaokai Lin } */ diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java index 1d8d76276d..7ba6c9a8af 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java @@ -23,7 +23,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ***************/ /** - * Generator for Uclid models. + * (EXPERIMENTAL) Generator for Uclid5 models. * * @author{Shaokai Lin } */ @@ -274,37 +274,6 @@ protected void generateUclidFile() { } } - /** - * Generate the Uclid model. - */ - protected void generateRunnerScript() { - try { - // Generate main.ucl and print to file - var script = new CodeBuilder(); - String filename = this.outputDir - .resolve("run.sh").toString(); - script.pr(String.join("\n", - "#!/bin/bash", - "set -e # Terminate on error", - "", - "echo '*** Setting up smt directory'", - "rm -rf ./smt/ && mkdir -p smt", - "", - "echo '*** Generating SMT files from UCLID5'", - "time uclid -g \"smt/output\" $1", - "", - "echo '*** Append (get-model) to each file'", - "ls smt | xargs -I {} bash -c 'echo \"(get-model)\" >> smt/{}'", - "", - "echo '*** Running Z3'", - "ls smt | xargs -I {} bash -c 'z3 parallel.enable=true -T:300 -v:1 ./smt/{}'" - )); - script.writeToFile(filename); - } catch (IOException e) { - Exceptions.sneakyThrow(e); - } - } - /** * The main function that generates Uclid code. */ diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java b/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java index be28c77665..85ad15361f 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java @@ -1,5 +1,5 @@ /** - * Runner for Uclid models. + * (EXPERIMENTAL) Runner for Uclid5 models. * * @author{Shaokai Lin } */ From 46e84c6be6b1556f317cd112b17bc5d5b0aa750f Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 23 Mar 2023 10:29:18 -0700 Subject: [PATCH 103/516] Temporarily remove the author tag for Shaokai. --- org.lflang/src/org/lflang/analyses/statespace/Event.java | 2 +- org.lflang/src/org/lflang/analyses/statespace/EventQueue.java | 2 +- org.lflang/src/org/lflang/analyses/statespace/StateInfo.java | 2 +- .../src/org/lflang/analyses/statespace/StateSpaceDiagram.java | 2 +- .../src/org/lflang/analyses/statespace/StateSpaceNode.java | 2 +- org.lflang/src/org/lflang/analyses/statespace/Tag.java | 2 +- org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java | 2 +- org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java | 2 +- org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java | 2 +- org.lflang/src/org/lflang/generator/StateVariableInstance.java | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/org.lflang/src/org/lflang/analyses/statespace/Event.java b/org.lflang/src/org/lflang/analyses/statespace/Event.java index cea24280ac..dc8613af92 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/Event.java +++ b/org.lflang/src/org/lflang/analyses/statespace/Event.java @@ -2,7 +2,7 @@ * A node in the state space diagram representing a step * in the execution of an LF program. * - * @author{Shaokai Lin } + * */ package org.lflang.analyses.statespace; diff --git a/org.lflang/src/org/lflang/analyses/statespace/EventQueue.java b/org.lflang/src/org/lflang/analyses/statespace/EventQueue.java index 44bb371f14..4c809d091a 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/EventQueue.java +++ b/org.lflang/src/org/lflang/analyses/statespace/EventQueue.java @@ -2,7 +2,7 @@ * An event queue implementation that * sorts events by time tag order * - * @author{Shaokai Lin } + * */ package org.lflang.analyses.statespace; diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateInfo.java b/org.lflang/src/org/lflang/analyses/statespace/StateInfo.java index 84cf25d5f2..7930336d7e 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateInfo.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateInfo.java @@ -2,7 +2,7 @@ * A class that represents information in a step * in a counterexample trace * - * @author{Shaokai Lin } + * */ package org.lflang.analyses.statespace; diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java index 7a0f4eff5e..4615dececd 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java @@ -1,7 +1,7 @@ /** * A directed graph representing the state space of an LF program. * - * @author{Shaokai Lin } + * */ package org.lflang.analyses.statespace; diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java index 2c20f05cdd..35c588bcbc 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java +++ b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java @@ -2,7 +2,7 @@ * A node in the state space diagram representing a step * in the execution of an LF program. * - * @author{Shaokai Lin } + * */ package org.lflang.analyses.statespace; diff --git a/org.lflang/src/org/lflang/analyses/statespace/Tag.java b/org.lflang/src/org/lflang/analyses/statespace/Tag.java index 99210ae622..362632ea3d 100644 --- a/org.lflang/src/org/lflang/analyses/statespace/Tag.java +++ b/org.lflang/src/org/lflang/analyses/statespace/Tag.java @@ -3,7 +3,7 @@ * which is a pair that consists of a * timestamp (type long) and a microstep (type long). * - * @author{Shaokai Lin } + * */ package org.lflang.analyses.statespace; diff --git a/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java index 79e7e4e2fc..381a98a49b 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java +++ b/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java @@ -25,7 +25,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY /** * (EXPERIMENTAL) Transpiler from an MTL specification to a Uclid axiom. * - * @author{Shaokai Lin } + * */ package org.lflang.analyses.uclid; diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java index 7ba6c9a8af..e484cf606e 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java @@ -25,7 +25,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY /** * (EXPERIMENTAL) Generator for Uclid5 models. * - * @author{Shaokai Lin } + * */ package org.lflang.analyses.uclid; diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java b/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java index 85ad15361f..2469e2da8c 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java @@ -1,7 +1,7 @@ /** * (EXPERIMENTAL) Runner for Uclid5 models. * - * @author{Shaokai Lin } + * */ package org.lflang.analyses.uclid; diff --git a/org.lflang/src/org/lflang/generator/StateVariableInstance.java b/org.lflang/src/org/lflang/generator/StateVariableInstance.java index 9ec7163cac..661602b01c 100644 --- a/org.lflang/src/org/lflang/generator/StateVariableInstance.java +++ b/org.lflang/src/org/lflang/generator/StateVariableInstance.java @@ -31,7 +31,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY /** * Representation of a compile-time instance of a state variable. * - * @author{Shaokai Lin } + * */ public class StateVariableInstance extends NamedInstance { From 8ba0f961265ef8e47b938c25e2eeafb1f4f71ad9 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 23 Mar 2023 10:29:43 -0700 Subject: [PATCH 104/516] Minor MTL grammar update --- org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 b/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 index 1a5d39b9b4..f22a6a2696 100644 --- a/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 +++ b/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 @@ -47,7 +47,7 @@ atomicProp ; interval - : (LPAREN|LBRACKET) lowerbound=time COMMA upperbound=time (RPAREN|RBRACKET) # Range + : LBRACKET lowerbound=time COMMA upperbound=time RBRACKET # Range | LBRACKET instant=time RBRACKET # Singleton ; From 7802314f17ecddd1b39d8d1e666e8850640d8bf2 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 23 Mar 2023 16:51:19 -0700 Subject: [PATCH 105/516] Minor update --- org.lflang/src/org/lflang/cli/Lfc.java | 2 +- org.lflang/src/org/lflang/generator/LFGenerator.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/cli/Lfc.java b/org.lflang/src/org/lflang/cli/Lfc.java index 7a73a10204..e04b84ce5b 100644 --- a/org.lflang/src/org/lflang/cli/Lfc.java +++ b/org.lflang/src/org/lflang/cli/Lfc.java @@ -113,7 +113,7 @@ public Lfc() { @Option( names = {"--no-verify"}, arity = "0", - description = "Do not generate verification models.") + description = "Do not run the generated verification models.") private boolean noVerify; @Option( diff --git a/org.lflang/src/org/lflang/generator/LFGenerator.java b/org.lflang/src/org/lflang/generator/LFGenerator.java index dd5ab47a2d..c96dfa95cc 100644 --- a/org.lflang/src/org/lflang/generator/LFGenerator.java +++ b/org.lflang/src/org/lflang/generator/LFGenerator.java @@ -196,7 +196,7 @@ public void doGenerate(Resource resource, IFileSystemAccess2 fsa, UclidGenerator uclidGenerator = new UclidGenerator(lfContext, properties); // Generate uclid files. uclidGenerator.doGenerate(resource, lfContext); - if (uclidGenerator.targetConfig.noVerify == false) { + if (!uclidGenerator.targetConfig.noVerify) { // Invoke the generated uclid files. uclidGenerator.runner.run(); } else { From 7672fb22d3fb4008382a7d749875926d28ba56b9 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 23 Mar 2023 17:18:26 -0700 Subject: [PATCH 106/516] Make --no-verify work again --- org.lflang/src/org/lflang/TargetConfig.java | 3 ++ .../lflang/analyses/uclid/UclidGenerator.java | 54 +++++++++---------- org.lflang/src/org/lflang/cli/Lfc.java | 3 ++ 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/org.lflang/src/org/lflang/TargetConfig.java b/org.lflang/src/org/lflang/TargetConfig.java index 33db77dff3..3f401427fb 100644 --- a/org.lflang/src/org/lflang/TargetConfig.java +++ b/org.lflang/src/org/lflang/TargetConfig.java @@ -86,6 +86,9 @@ public TargetConfig( if (cliArgs.containsKey("no-compile")) { this.noCompile = true; } + if (cliArgs.containsKey("no-verify")) { + this.noVerify = true; + } if (cliArgs.containsKey("docker")) { var arg = cliArgs.getProperty("docker"); if (Boolean.parseBoolean(arg)) { diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java index e484cf606e..2b3ec21dfc 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java +++ b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java @@ -1043,36 +1043,30 @@ protected void generateTriggersAndReactions() { for (TriggerInstance trigger : reaction.getReaction().triggers) { String triggerPresentStr = ""; - if (trigger.isBuiltinTrigger()) { - // Check if this trigger is a built-in trigger (startup or shutdown). - if (trigger.isStartup()) { - // FIXME: Treat startup as a variable. - // triggerPresentStr = "g(i) == zero()"; - - // Handle startup. - code.pr(String.join("\n", - "// If startup is within frame (and all variables have default values),", - "// " + reaction.getReaction().getFullNameWithJoiner("_") + " must be invoked.", - "axiom(", - " ((start_time == 0) ==> (", - " finite_exists (i : integer) in indices :: i > START && i <= END", - " && " + reaction.getReaction().getFullNameWithJoiner("_") + "(rxn(i))" + " && tag_same(g(i), zero())", - " && !(", - " finite_exists (j : integer) in indices :: j > START && j <= END", - " && " + reaction.getReaction().getFullNameWithJoiner("_") + "(rxn(j))", - " && j != i", - " )", - " ))", - ");" - )); - continue outerLoop; - } else if (trigger.isShutdown()) { - // FIXME: For a future version. - System.out.println("Not implemented!"); - } else { - // Unreachable. - System.out.println("Unreachable!"); - } + if (trigger.isStartup()) { + // FIXME: Treat startup as a variable. + // triggerPresentStr = "g(i) == zero()"; + + // Handle startup. + code.pr(String.join("\n", + "// If startup is within frame (and all variables have default values),", + "// " + reaction.getReaction().getFullNameWithJoiner("_") + " must be invoked.", + "axiom(", + " ((start_time == 0) ==> (", + " finite_exists (i : integer) in indices :: i > START && i <= END", + " && " + reaction.getReaction().getFullNameWithJoiner("_") + "(rxn(i))" + " && tag_same(g(i), zero())", + " && !(", + " finite_exists (j : integer) in indices :: j > START && j <= END", + " && " + reaction.getReaction().getFullNameWithJoiner("_") + "(rxn(j))", + " && j != i", + " )", + " ))", + ");" + )); + continue outerLoop; + } else if (trigger.isShutdown()) { + // FIXME: For a future version. + System.out.println("Not implemented!"); } else { // If the trigger is a port/action/timer. diff --git a/org.lflang/src/org/lflang/cli/Lfc.java b/org.lflang/src/org/lflang/cli/Lfc.java index d5a90c7ec9..adf04be4e5 100644 --- a/org.lflang/src/org/lflang/cli/Lfc.java +++ b/org.lflang/src/org/lflang/cli/Lfc.java @@ -256,6 +256,9 @@ protected Properties filterPassOnProps() { if (noCompile) { props.setProperty(BuildParm.NO_COMPILE.getKey(), "true"); } + if (noVerify) { + props.setProperty(BuildParm.NO_VERIFY.getKey(), "true"); + } if (targetCompiler != null) { props.setProperty(BuildParm.TARGET_COMPILER.getKey(), targetCompiler); } From c07932721872f41ccfccaf65a5248838e31fc20d Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 23 Mar 2023 17:20:51 -0700 Subject: [PATCH 107/516] Remove unused lib --- org.lflang/src/org/lflang/TimeValue.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/org.lflang/src/org/lflang/TimeValue.java b/org.lflang/src/org/lflang/TimeValue.java index 3d511aa5ad..9eab5aa68b 100644 --- a/org.lflang/src/org/lflang/TimeValue.java +++ b/org.lflang/src/org/lflang/TimeValue.java @@ -25,8 +25,6 @@ package org.lflang; -import java.sql.Time; - /** * Represents an amount of time (a duration). * From 95e87433523d17e84e8976eaed39c8eb30ef364a Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 23 Mar 2023 18:09:00 -0700 Subject: [PATCH 108/516] Revert "Minor MTL grammar update" This reverts commit 8ba0f961265ef8e47b938c25e2eeafb1f4f71ad9. --- org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 b/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 index f22a6a2696..1a5d39b9b4 100644 --- a/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 +++ b/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 @@ -47,7 +47,7 @@ atomicProp ; interval - : LBRACKET lowerbound=time COMMA upperbound=time RBRACKET # Range + : (LPAREN|LBRACKET) lowerbound=time COMMA upperbound=time (RPAREN|RBRACKET) # Range | LBRACKET instant=time RBRACKET # Singleton ; From 7717121bbc71c476e17df143a1f060bb68b1785e Mon Sep 17 00:00:00 2001 From: Anirudh Rengarajan <44007330+arengarajan99@users.noreply.github.com> Date: Wed, 29 Mar 2023 20:40:28 -0700 Subject: [PATCH 109/516] Remove unordered attribute from code generation --- org.lflang/src/lib/c/reactor-c | 2 +- org.lflang/src/org/lflang/AttributeUtils.java | 10 ------ .../federated/extensions/CExtension.java | 15 --------- .../federated/extensions/PythonExtension.java | 8 ----- .../federated/generator/FedASTUtils.java | 9 ------ .../org/lflang/generator/GeneratorBase.java | 32 ------------------- .../lflang/generator/ReactionInstance.java | 32 +++---------------- .../generator/ReactionInstanceGraph.java | 32 ++++++++----------- .../org/lflang/generator/ReactorInstance.java | 16 +--------- .../src/org/lflang/graph/TopologyGraph.java | 14 +++----- .../org/lflang/validation/AttributeSpec.java | 2 -- 11 files changed, 25 insertions(+), 147 deletions(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index d43e973780..37293c04ba 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit d43e9737804f2d984d52a99cac20d8e57adad543 +Subproject commit 37293c04bafdd630341df82fc02ddf37a45a06f3 diff --git a/org.lflang/src/org/lflang/AttributeUtils.java b/org.lflang/src/org/lflang/AttributeUtils.java index ec5edc4b9d..adcd30022e 100644 --- a/org.lflang/src/org/lflang/AttributeUtils.java +++ b/org.lflang/src/org/lflang/AttributeUtils.java @@ -191,16 +191,6 @@ public static boolean isSparse(EObject node) { return findAttributeByName(node, "sparse") != null; } - /** - * Return true if the reaction is unordered. - * - * Currently, this is only used for synthesized reactions in the context of - * federated execution. - */ - public static boolean isUnordered(Reaction reaction) { - return findAttributeByName(reaction, "_unordered") != null; - } - /** * Return true if the reactor is marked to be a federate. */ diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtension.java b/org.lflang/src/org/lflang/federated/extensions/CExtension.java index fda727d735..e2a991e0de 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtension.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtension.java @@ -137,9 +137,6 @@ public String generateNetworkReceiverBody( ) { var receiveRef = CUtil.portRefInReaction(receivingPort, connection.getDstBank(), connection.getDstChannel()); var result = new CodeBuilder(); - // We currently have no way to mark a reaction "unordered" - // in the AST, so we use a magic string at the start of the body. - result.pr("// " + ReactionInstance.UNORDERED_REACTION_MARKER); // Transfer the physical time of arrival from the action to the port result.pr(receiveRef+"->physical_time_of_arrival = self->_lf__"+action.getName()+".physical_time_of_arrival;"); if (coordinationType == CoordinationType.DECENTRALIZED && !connection.getDefinition().isPhysical()) { @@ -255,10 +252,6 @@ public String generateNetworkSenderBody( // of the action in this list. int receivingPortID = connection.getDstFederate().networkMessageActions.size(); - // We currently have no way to mark a reaction "unordered" - // in the AST, so we use a magic string at the start of the body. - result.pr("// " + ReactionInstance.UNORDERED_REACTION_MARKER + "\n"); - result.pr("// Sending from " + sendRef + " in federate " + connection.getSrcFederate().name + " to " + receiveRef + " in federate " + connection.getDstFederate().name); @@ -416,10 +409,6 @@ public String generateNetworkInputControlReactionBody( ) { // Store the code var result = new CodeBuilder(); - - // We currently have no way to mark a reaction "unordered" - // in the AST, so we use a magic string at the start of the body. - result.pr("// " + ReactionInstance.UNORDERED_REACTION_MARKER + "\n"); result.pr("interval_t max_STP = 0LL;"); // Find the maximum STP for decentralized coordination @@ -447,10 +436,6 @@ public String generateNetworkOutputControlReactionBody( // The ID of the receiving port (rightPort) is the position // of the networkAction (see below) in this list. int receivingPortID = connection.getDstFederate().networkMessageActions.size(); - - // We currently have no way to mark a reaction "unordered" - // in the AST, so we use a magic string at the start of the body. - result.pr("// " + ReactionInstance.UNORDERED_REACTION_MARKER + "\n"); var sendRef = CUtil.portRefInReaction(srcOutputPort, connection.getSrcBank(), connection.getSrcChannel()); // Get the delay literal var additionalDelayString = CExtensionUtils.getNetworkDelayLiteral(connection.getDefinition().getDelay()); diff --git a/org.lflang/src/org/lflang/federated/extensions/PythonExtension.java b/org.lflang/src/org/lflang/federated/extensions/PythonExtension.java index 73bed06f7b..8cdc7eeb7a 100644 --- a/org.lflang/src/org/lflang/federated/extensions/PythonExtension.java +++ b/org.lflang/src/org/lflang/federated/extensions/PythonExtension.java @@ -86,10 +86,6 @@ public String generateNetworkSenderBody( ErrorReporter errorReporter ) { var result = new CodeBuilder(); - - // We currently have no way to mark a reaction "unordered" - // in the AST, so we use a magic string at the start of the body. - result.pr("// " + ReactionInstance.UNORDERED_REACTION_MARKER + "\n"); result.pr(PyUtil.generateGILAcquireCode() + "\n"); result.pr( super.generateNetworkSenderBody( @@ -116,10 +112,6 @@ public String generateNetworkReceiverBody( ErrorReporter errorReporter ) { var result = new CodeBuilder(); - - // We currently have no way to mark a reaction "unordered" - // in the AST, so we use a magic string at the start of the body. - result.pr("// " + ReactionInstance.UNORDERED_REACTION_MARKER + "\n"); result.pr(PyUtil.generateGILAcquireCode() + "\n"); result.pr( super.generateNetworkReceiverBody( diff --git a/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java b/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java index 7a7eb1f183..bbbc063ca9 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java +++ b/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java @@ -308,9 +308,6 @@ private static void addNetworkReceiverReaction( errorReporter )); - - ASTUtils.addReactionAttribute(networkReceiverReaction, "_unordered"); - // Add the receiver reaction to the parent parent.getReactions().add(networkReceiverReaction); @@ -405,8 +402,6 @@ private static void addNetworkInputControlReaction( ) ); - ASTUtils.addReactionAttribute(reaction, "_unordered"); - // Insert the reaction top.getReactions().add(reaction); @@ -760,8 +755,6 @@ private static void addNetworkSenderReaction( errorReporter )); - ASTUtils.addReactionAttribute(networkSenderReaction, "_unordered"); - // Add the sending reaction to the parent. parent.getReactions().add(networkSenderReaction); @@ -848,8 +841,6 @@ private static void addNetworkOutputControlReaction(FedConnectionInstance connec FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) .generateNetworkOutputControlReactionBody(newPortRef, connection)); - ASTUtils.addReactionAttribute(reaction, "_unordered"); - // Insert the newly generated reaction after the generated sender and // receiver top-level reactions. diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.java b/org.lflang/src/org/lflang/generator/GeneratorBase.java index ef35d22be1..a6853ec9b1 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.java +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.java @@ -139,19 +139,6 @@ public abstract class GeneratorBase extends AbstractLFValidator { */ 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 */ @@ -368,25 +355,6 @@ public boolean errorsOccurred() { */ 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); - } - /** * Mark the specified reaction to belong to only the specified * bank index. This is needed because reactions cannot declare diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index cf9dce7013..ec9ced971f 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -64,27 +64,18 @@ public class ReactionInstance extends NamedInstance { * only by the ReactorInstance class, but it is public to enable unit tests. * @param definition A reaction definition. * @param parent The parent reactor instance, which cannot be null. - * @param isUnordered Indicator that this reaction is unordered w.r.t. other reactions. * @param index The index of the reaction within the reactor (0 for the * first reaction, 1 for the second, etc.). */ public ReactionInstance( Reaction definition, ReactorInstance parent, - boolean isUnordered, int index ) { super(definition, parent); this.index = index; - this.isUnordered = isUnordered; - // If the reaction body starts with the magic string - // UNORDERED_REACTION_MARKER, then mark it unordered, - // overriding the argument. String body = ASTUtils.toText(definition.getCode()); - if (body.contains(UNORDERED_REACTION_MARKER)) { - this.isUnordered = true; - } // Identify the dependencies for this reaction. // First handle the triggers. @@ -222,26 +213,12 @@ public ReactionInstance( */ 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. - * This is that magic comment. - */ - public static String UNORDERED_REACTION_MARKER - = "**** This reaction is unordered."; - /** * Index of order of occurrence within the reactor definition. * The first reaction has index 0, the second index 1, etc. */ public int index; - /** - * Whether or not this reaction is ordered with respect to other - * reactions in the same reactor. - */ - public boolean isUnordered; - /** * The ports that this reaction reads but that do not trigger it. */ @@ -274,7 +251,7 @@ public void clearCaches(boolean includingRuntimes) { * Return the set of immediate downstream reactions, which are reactions * that receive data produced by this reaction plus * at most one reaction in the same reactor whose definition - * lexically follows this one (unless this reaction is unordered). + * lexically follows this one. */ public Set dependentReactions() { // Cache the result. @@ -282,7 +259,7 @@ public Set dependentReactions() { dependentReactionsCache = new LinkedHashSet<>(); // First, add the next lexical reaction, if appropriate. - if (!isUnordered && parent.reactions.size() > index + 1) { + if (parent.reactions.size() > index + 1) { // Find the next reaction in the parent's reaction list. dependentReactionsCache.add(parent.reactions.get(index + 1)); } @@ -307,7 +284,6 @@ public Set dependentReactions() { * Return the set of immediate upstream reactions, which are reactions * that send data to this one plus at most one reaction in the same * reactor whose definition immediately precedes the definition of this one - * (unless this reaction is unordered). */ public Set dependsOnReactions() { // Cache the result. @@ -315,11 +291,11 @@ public Set dependsOnReactions() { dependsOnReactionsCache = new LinkedHashSet<>(); // First, add the previous lexical reaction, if appropriate. - if (!isUnordered && index > 0) { + if (index > 0) { // Find the previous ordered reaction in the parent's reaction list. int earlierIndex = index - 1; ReactionInstance earlierOrderedReaction = parent.reactions.get(earlierIndex); - while (earlierOrderedReaction.isUnordered && --earlierIndex >= 0) { + while (--earlierIndex >= 0) { earlierOrderedReaction = parent.reactions.get(earlierIndex); } if (earlierIndex >= 0) { diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index 116cbf3046..8e87f6db4e 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -182,8 +182,7 @@ protected void addDownstreamReactions(PortInstance port, ReactionInstance reacti // If another upstream reaction shows up, then this will be // reset to null. if (this.getUpstreamAdjacentNodes(dstRuntime).size() == 1 - && (dstRuntime.getReaction().isUnordered - || dstRuntime.getReaction().index == 0)) { + && (dstRuntime.getReaction().index == 0)) { dstRuntime.dominating = srcRuntime; } else { dstRuntime.dominating = null; @@ -217,25 +216,22 @@ protected void addNodesAndEdges(ReactorInstance reactor) { this.addNode(runtime); } - // If this is not an unordered reaction, then create a dependency - // on any previously defined reaction. - if (!reaction.isUnordered) { - // If there is an earlier reaction in this same reactor, then - // create a link in the reaction graph for all runtime instances. - if (previousReaction != null) { - List previousRuntimes = previousReaction.getRuntimeInstances(); - int count = 0; - for (Runtime runtime : runtimes) { - // Only add the reaction order edge if previous reaction is outside of a mode or both are in the same mode - // This allows modes to break cycles since modes are always mutually exclusive. - if (runtime.getReaction().getMode(true) == null || runtime.getReaction().getMode(true) == reaction.getMode(true)) { - this.addEdge(runtime, previousRuntimes.get(count)); - count++; - } + // If there is an earlier reaction in this same reactor, then + // create a link in the reaction graph for all runtime instances. + if (previousReaction != null) { + List previousRuntimes = previousReaction.getRuntimeInstances(); + int count = 0; + for (Runtime runtime : runtimes) { + // Only add the reaction order edge if previous reaction is outside of a mode or both are in the same mode + // This allows modes to break cycles since modes are always mutually exclusive. + if (runtime.getReaction().getMode(true) == null || runtime.getReaction().getMode(true) == reaction.getMode(true)) { + this.addEdge(runtime, previousRuntimes.get(count)); + count++; } } - previousReaction = reaction; } + previousReaction = reaction; + // Add downstream reactions. Note that this is sufficient. // We don't need to also add upstream reactions because this reaction diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index c7f59211f7..7c485b0a81 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -700,15 +700,6 @@ public TimeValue getTimeValue(Expression expr) { /** 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; @@ -729,13 +720,8 @@ protected void createReactionInstances() { // 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++); - + var reactionInstance = new ReactionInstance(reaction, this, count++); // Add the reaction instance to the map of reactions for this // reactor. this.reactions.add(reactionInstance); diff --git a/org.lflang/src/org/lflang/graph/TopologyGraph.java b/org.lflang/src/org/lflang/graph/TopologyGraph.java index f790de65a7..c680c5f7e1 100644 --- a/org.lflang/src/org/lflang/graph/TopologyGraph.java +++ b/org.lflang/src/org/lflang/graph/TopologyGraph.java @@ -86,16 +86,12 @@ public void collectNodesFrom(ReactorInstance reactor) { this.addSources(reaction); this.addEffects(reaction); - // If this is not an unordered reaction, then create a dependency - // on any previously defined reaction. - if (!reaction.isUnordered) { - // If there is an earlier reaction in this same reactor, then - // create a link in the reaction graph. - if (previousReaction != null) { - this.addEdge(reaction, previousReaction); - } - previousReaction = reaction; + // If there is an earlier reaction in this same reactor, then + // create a link in the reaction graph. + if (previousReaction != null) { + this.addEdge(reaction, previousReaction); } + previousReaction = reaction; } // Recursively add nodes and edges from contained reactors. for (var child : reactor.children) { diff --git a/org.lflang/src/org/lflang/validation/AttributeSpec.java b/org.lflang/src/org/lflang/validation/AttributeSpec.java index c5a530581b..a7a95e852c 100644 --- a/org.lflang/src/org/lflang/validation/AttributeSpec.java +++ b/org.lflang/src/org/lflang/validation/AttributeSpec.java @@ -220,8 +220,6 @@ enum AttrParamType { List.of(new AttrParamSpec(EACH_ATTR, AttrParamType.BOOLEAN, true)) )); - // attributes that are used internally only by the federated code generation - ATTRIBUTE_SPECS_BY_NAME.put("_unordered", new AttributeSpec(null)); ATTRIBUTE_SPECS_BY_NAME.put("_fed_config", new AttributeSpec( List.of(new AttrParamSpec(AttributeSpec.NETWORK_MESSAGE_ACTIONS, AttrParamType.STRING, false)))); From ebba81f8d1a6f23ff00668ace8060dee5440e5b4 Mon Sep 17 00:00:00 2001 From: Anirudh Rengarajan <44007330+arengarajan99@users.noreply.github.com> Date: Thu, 30 Mar 2023 23:34:52 -0700 Subject: [PATCH 110/516] Added AST transformations for network reactions to be contained in their own reactors --- .../federated/extensions/CExtension.java | 5 +- .../federated/extensions/CExtensionUtils.java | 9 +- .../federated/generator/FedASTUtils.java | 217 ++++++++++++------ .../federated/generator/FedEmitter.java | 1 + .../federated/generator/FedGenerator.java | 23 +- .../federated/generator/FedMainEmitter.java | 4 +- .../federated/generator/FederateInstance.java | 34 ++- 7 files changed, 210 insertions(+), 83 deletions(-) diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtension.java b/org.lflang/src/org/lflang/federated/extensions/CExtension.java index e2a991e0de..93c661e3c9 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtension.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtension.java @@ -542,9 +542,8 @@ private String generateInitializeTriggers(FederateInstance federate, ErrorReport var federatedReactor = FedASTUtils.findFederatedReactor(federate.instantiation.eResource()); var oldFederatedReactorName = federatedReactor.getName(); federatedReactor.setName(federate.name); - var main = new ReactorInstance(federatedReactor, errorReporter, 1); - code.pr(CExtensionUtils.initializeTriggersForNetworkActions(federate, main)); - code.pr(CExtensionUtils.initializeTriggerForControlReactions(main, main, federate)); + code.pr(CExtensionUtils.initializeTriggersForNetworkActions(federate, errorReporter)); + //code.pr(CExtensionUtils.initializeTriggerForControlReactions(main, main, federate)); federatedReactor.setName(oldFederatedReactorName); return """ diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java b/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java index aabc77ecd5..9992302b88 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java @@ -9,6 +9,7 @@ import java.util.regex.Pattern; import org.lflang.ASTUtils; +import org.lflang.ErrorReporter; import org.lflang.InferredType; import org.lflang.TargetConfig.ClockSyncOptions; import org.lflang.TargetProperty; @@ -82,7 +83,7 @@ public static String allocateTriggersForFederate( * @param main The main reactor that contains the federate (used to lookup references). * @return */ - public static String initializeTriggersForNetworkActions(FederateInstance federate, ReactorInstance main) { + public static String initializeTriggersForNetworkActions(FederateInstance federate, ErrorReporter errorReporter) { CodeBuilder code = new CodeBuilder(); if (federate.networkMessageActions.size() > 0) { // Create a static array of trigger_t pointers. @@ -91,9 +92,11 @@ public static String initializeTriggersForNetworkActions(FederateInstance federa // There should be exactly one ActionInstance in the // main reactor for each Action. var triggers = new LinkedList(); - for (Action action : federate.networkMessageActions) { + for (int i = 0; i < federate.networkMessageActions.size(); ++i) { // Find the corresponding ActionInstance. - var actionInstance = main.lookupActionInstance(action); + Action action = federate.networkMessageActions.get(i); + var reactor = new ReactorInstance(federate.networkMessageActionReactors.get(i), errorReporter, 1); + var actionInstance = reactor.lookupActionInstance(action); triggers.add(CUtil.actionRef(actionInstance, null)); } var actionTableCount = 0; diff --git a/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java b/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java index bbbc063ca9..4ad3fca0f2 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java +++ b/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java @@ -39,6 +39,7 @@ import java.util.Set; import java.util.stream.Collectors; +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.IteratorExtensions; @@ -58,9 +59,11 @@ import org.lflang.lf.BuiltinTriggerRef; import org.lflang.lf.Connection; import org.lflang.lf.Expression; +import org.lflang.lf.Input; import org.lflang.lf.Instantiation; import org.lflang.lf.LfFactory; import org.lflang.lf.Model; +import org.lflang.lf.Output; import org.lflang.lf.ParameterReference; import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; @@ -132,55 +135,51 @@ public static Reactor findFederatedReactor(Resource resource) { * Replace the specified connection with communication between federates. * * @param connection Network connection between two federates. + * @param resource + * @param mainDef * @param coordination One of CoordinationType.DECENTRALIZED or * CoordinationType.CENTRALIZED. * @param errorReporter Used to report errors encountered. */ public static void makeCommunication( FedConnectionInstance connection, - CoordinationType coordination, + Resource resource, CoordinationType coordination, ErrorReporter errorReporter ) { - // Add the sender reaction. - addNetworkSenderReaction( + // Add the sender reactor. + addNetworkSenderReactor( connection, coordination, + resource, errorReporter ); // Next, generate control reactions - if ( - !connection.getDefinition().isPhysical() && - // Connections that are physical don't need control reactions - connection.getDefinition().getDelay() - == null // Connections that have delays don't need control reactions - ) { - // Add the network output control reaction to the parent - FedASTUtils.addNetworkOutputControlReaction(connection); - - // Add the network input control reaction to the parent - FedASTUtils.addNetworkInputControlReaction(connection, coordination, errorReporter); - } - - // Create the network action (@see createNetworkAction) - Action networkAction = createNetworkAction(connection); - - // Keep track of this action in the destination federate. - connection.dstFederate.networkMessageActions.add(networkAction); - - // Add the action definition to the parent reactor. - ((Reactor) connection.getDefinition().eContainer()).getActions().add(networkAction); - - // Add the network receiver reaction in the destinationFederate - addNetworkReceiverReaction( - networkAction, + // if ( + // !connection.getDefinition().isPhysical() && + // // Connections that are physical don't need control reactions + // connection.getDefinition().getDelay() + // == null // Connections that have delays don't need control reactions + // ) { + // // Add the network output control reaction to the parent + // FedASTUtils.addNetworkOutputControlReaction(connection); + + // // Add the network input control reaction to the parent + // FedASTUtils.addNetworkInputControlReaction(connection, coordination, errorReporter); + // } + + // Add the network receiver reactor in the destinationFederate + addNetworkReceiverReactor( connection, coordination, + resource, errorReporter ); } + public static int networkMessageActionID = 0; + /** * Create a "network action" in the reactor that contains the given * connection and return it. @@ -196,12 +195,12 @@ public static void makeCommunication( * @return The newly created action. */ private static Action createNetworkAction(FedConnectionInstance connection) { - Reactor top = (Reactor) connection.getDefinition().eContainer(); + //Reactor top = (Reactor) connection.getDefinition().eContainer(); LfFactory factory = LfFactory.eINSTANCE; Action action = factory.createAction(); // Name the newly created action; set its delay and type. - action.setName(ASTUtils.getUniqueIdentifier(top, "networkMessage")); + action.setName("networkMessage_" + networkMessageActionID++); if (connection.serializer == SupportedSerializers.NATIVE) { action.setType(EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getType())); } else { @@ -232,7 +231,7 @@ private static Action createNetworkAction(FedConnectionInstance connection) { } /** - * Add a network receiver reaction for a given input port 'destination' to + * Add a network receiver reactor for a given input port 'destination' to * destination's parent reactor. This reaction will react to a generated * 'networkAction' (triggered asynchronously, e.g., by federate.c). This * 'networkAction' will contain the actual message that is sent by the @@ -241,24 +240,64 @@ private static Action createNetworkAction(FedConnectionInstance connection) { * network * receiver reaction. * - * @param networkAction The network action (also, @see createNetworkAction) * @param connection FIXME * @param coordination One of CoordinationType.DECENTRALIZED or * CoordinationType.CENTRALIZED. + * @param resource * @note: Used in federated execution */ - private static void addNetworkReceiverReaction( - Action networkAction, + private static void addNetworkReceiverReactor( FedConnectionInstance connection, + CoordinationType coordination, - ErrorReporter errorReporter + Resource resource, ErrorReporter errorReporter ) { LfFactory factory = LfFactory.eINSTANCE; - VarRef sourceRef = factory.createVarRef(); - VarRef destRef = factory.createVarRef(); - Reactor parent = (Reactor) connection.getDefinition().eContainer(); + Type type = EcoreUtil.copy(connection.getDestinationPortInstance().getDefinition().getType()); + + VarRef sourceRef = factory.createVarRef(); //source fed + VarRef instRef = factory.createVarRef(); //instantiation connection + VarRef destRef = factory.createVarRef(); //destination connection + + Reactor receiver = factory.createReactor(); Reaction networkReceiverReaction = factory.createReaction(); + + Output out = factory.createOutput(); + VarRef outRef = factory.createVarRef(); //out port + Connection receiverFromReaction = factory.createConnection(); + Instantiation networkInstance = factory.createInstantiation(); + + Reactor top = connection.getSourcePortInstance() + .getParent() + .getParent().reactorDefinition; // Top-level reactor. + + receiver.getReactions().add(networkReceiverReaction); + receiver.getOutputs().add(out); + EObject node = IteratorExtensions.findFirst(resource.getAllContents(), Model.class::isInstance); + ((Model) node).getReactors().add(receiver); + receiver.setName("NetworkReceiver_" + networkIDReceiver++); + + + networkInstance.setReactorClass(receiver); + networkInstance.setName(ASTUtils.getUniqueIdentifier(top, "nr_" + connection.getDstFederate().name)); + top.getInstantiations().add(networkInstance); + + receiverFromReaction.getLeftPorts().add(instRef); + receiverFromReaction.getRightPorts().add(destRef); + + // Create the network action (@see createNetworkAction) + Action networkAction = createNetworkAction(connection); + + // Keep track of this action in the destination federate. + connection.dstFederate.networkMessageActions.add(networkAction); + + // Keep track of the in the destination federate. + connection.dstFederate.networkMessageActionReactors.add(receiver); + + // Add the action definition to the parent reactor. + receiver.getActions().add(networkAction); + // If the sender or receiver is in a bank of reactors, then we want // these reactions to appear only in the federate whose bank ID matches. setReactionBankIndex(networkReceiverReaction, connection.getDstBank()); @@ -284,15 +323,23 @@ private static void addNetworkReceiverReaction( sourceRef.setVariable(connection.getSourcePortInstance().getDefinition()); destRef.setContainer(connection.getDestinationPortInstance().getParent().getDefinition()); destRef.setVariable(connection.getDestinationPortInstance().getDefinition()); + instRef.setContainer(networkInstance); + instRef.setVariable(out); + + out.setName("msg"); + out.setType(type); + out.setWidthSpec(EcoreUtil.copy(connection.getDestinationPortInstance().getDefinition().getWidthSpec())); + outRef.setVariable(out); - // Add the input port at the receiver federate reactor as an effect - networkReceiverReaction.getEffects().add(destRef); + // Add the output port at the receiver reactor as an effect + //networkReceiverReaction.getEffects().add(outRef); VarRef triggerRef = factory.createVarRef(); // Establish references to the action. triggerRef.setVariable(networkAction); // Add the action as a trigger to the receiver reaction networkReceiverReaction.getTriggers().add(triggerRef); + networkReceiverReaction.getEffects().add(outRef); // Generate code for the network receiver reaction networkReceiverReaction.setCode(factory.createCode()); @@ -301,7 +348,7 @@ private static void addNetworkReceiverReaction( connection.dstFederate.targetConfig.target).generateNetworkReceiverBody( networkAction, sourceRef, - destRef, + outRef, connection, ASTUtils.getInferredType(networkAction), coordination, @@ -309,23 +356,26 @@ private static void addNetworkReceiverReaction( )); // Add the receiver reaction to the parent - parent.getReactions().add(networkReceiverReaction); + // parent.getReactions().add(networkReceiverReaction); // Add the network receiver reaction to the federate instance's list // of network reactions connection.dstFederate.networkReactions.add(networkReceiverReaction); - - - if ( - !connection.getDefinition().isPhysical() && - // Connections that are physical don't need control reactions - connection.getDefinition().getDelay() - == null // Connections that have delays don't need control reactions - ) { - // Add necessary dependencies to reaction to ensure that it executes correctly - // relative to other network input control reactions in the federate. - addRelativeDependency(connection, networkReceiverReaction, errorReporter); - } + connection.dstFederate.networkReactors.add(receiver); + connection.dstFederate.networkConnections.add(receiverFromReaction); + connection.dstFederate.networkInstantiations.add(networkInstance); + + + // if ( + // !connection.getDefinition().isPhysical() && + // // Connections that are physical don't need control reactions + // connection.getDefinition().getDelay() + // == null // Connections that have delays don't need control reactions + // ) { + // // Add necessary dependencies to reaction to ensure that it executes correctly + // // relative to other network input control reactions in the federate. + // //addRelativeDependency(connection, networkReceiverReaction, errorReporter); + // } } /** @@ -415,7 +465,7 @@ private static void addNetworkInputControlReaction( // Add necessary dependencies to reaction to ensure that it executes correctly // relative to other network input control reactions in the federate. - addRelativeDependency(connection, reaction, errorReporter); + // addRelativeDependency(connection, reaction, errorReporter); } /** @@ -690,8 +740,11 @@ public static List safe(List list) { return list == null ? Collections.emptyList() : list; } + public static int networkIDSender = 0; + public static int networkIDReceiver = 0; + /** - * Add a network sender reaction for a given input port 'source' to + * Add a network sender reactor for a given input port 'source' to * source's parent reactor. This reaction will react to the 'source' * and then send a message on the network destined for the * destinationFederate. @@ -699,21 +752,46 @@ public static List safe(List list) { * @param connection Network connection between two federates. * @param coordination One of CoordinationType.DECENTRALIZED or * CoordinationType.CENTRALIZED. + * @param resource + * @param mainDef * @param errorReporter FIXME * @note Used in federated execution */ - private static void addNetworkSenderReaction( + private static void addNetworkSenderReactor( FedConnectionInstance connection, CoordinationType coordination, - ErrorReporter errorReporter + Resource resource, ErrorReporter errorReporter ) { LfFactory factory = LfFactory.eINSTANCE; // Assume all the types are the same, so just use the first on the right. Type type = EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getType()); - VarRef sourceRef = factory.createVarRef(); - VarRef destRef = factory.createVarRef(); - Reactor parent = (Reactor) connection.getDefinition().eContainer(); + VarRef sourceRef = factory.createVarRef(); //out connection + VarRef instRef = factory.createVarRef(); //instantiation connection + VarRef destRef = factory.createVarRef(); //destination fed + Reactor sender = factory.createReactor(); Reaction networkSenderReaction = factory.createReaction(); + Input in = factory.createInput(); + VarRef inRef = factory.createVarRef(); //in port + Connection senderToReaction = factory.createConnection(); + Instantiation networkInstance = factory.createInstantiation(); + + Reactor top = connection.getSourcePortInstance() + .getParent() + .getParent().reactorDefinition; // Top-level reactor. + + + sender.getReactions().add(networkSenderReaction); + sender.getInputs().add(in); + EObject node = IteratorExtensions.findFirst(resource.getAllContents(), Model.class::isInstance); + ((Model) node).getReactors().add(sender); + sender.setName("NetworkSender_" + networkIDSender++); + + networkInstance.setReactorClass(sender); + networkInstance.setName(ASTUtils.getUniqueIdentifier(top, "ns_" + connection.getDstFederate().name)); + top.getInstantiations().add(networkInstance); + + senderToReaction.getLeftPorts().add(sourceRef); + senderToReaction.getRightPorts().add(instRef); // FIXME: do not create a new extension every time it is used FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) @@ -738,16 +816,24 @@ private static void addNetworkSenderReaction( // Establish references to the involved ports. sourceRef.setContainer(connection.getSourcePortInstance().getParent().getDefinition()); sourceRef.setVariable(connection.getSourcePortInstance().getDefinition()); + instRef.setContainer(networkInstance); + instRef.setVariable(in); destRef.setContainer(connection.getDestinationPortInstance().getParent().getDefinition()); destRef.setVariable(connection.getDestinationPortInstance().getDefinition()); + in.setName("msg"); + in.setType(type); + in.setWidthSpec(EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getWidthSpec())); + inRef.setVariable(in); + + // Configure the sending reaction. - networkSenderReaction.getTriggers().add(sourceRef); + networkSenderReaction.getTriggers().add(inRef); networkSenderReaction.setCode(factory.createCode()); networkSenderReaction.getCode().setBody( FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) .generateNetworkSenderBody( - sourceRef, + inRef, destRef, connection, InferredType.fromAST(type), @@ -756,11 +842,14 @@ private static void addNetworkSenderReaction( )); // Add the sending reaction to the parent. - parent.getReactions().add(networkSenderReaction); + //networkSenderReaction.getTriggers().add(inRef); // Add the network sender reaction to the federate instance's list // of network reactions connection.srcFederate.networkReactions.add(networkSenderReaction); + connection.srcFederate.networkReactors.add(sender); + connection.srcFederate.networkConnections.add(senderToReaction); + connection.srcFederate.networkInstantiations.add(networkInstance); } /** diff --git a/org.lflang/src/org/lflang/federated/generator/FedEmitter.java b/org.lflang/src/org/lflang/federated/generator/FedEmitter.java index 9ae5c9d2cc..6b24debe67 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedEmitter.java +++ b/org.lflang/src/org/lflang/federated/generator/FedEmitter.java @@ -11,6 +11,7 @@ import org.lflang.federated.launcher.RtiConfig; import org.lflang.generator.CodeMap; import org.lflang.generator.LFGeneratorContext; +import org.lflang.lf.Model; import org.lflang.lf.Reactor; /** diff --git a/org.lflang/src/org/lflang/federated/generator/FedGenerator.java b/org.lflang/src/org/lflang/federated/generator/FedGenerator.java index 18d5770165..d846113e79 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedGenerator.java +++ b/org.lflang/src/org/lflang/federated/generator/FedGenerator.java @@ -148,7 +148,7 @@ public boolean doGenerate(Resource resource, LFGeneratorContext context) throws // 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(federation); + replaceFederateConnectionsWithProxies(federation, resource); FedEmitter fedEmitter = new FedEmitter( fileConfig, @@ -479,8 +479,9 @@ private List getFederateInstances(Instantiation instantiation, * handle sending and receiving data. * * @param federation Reactor class of the federation. + * @param resource */ - private void replaceFederateConnectionsWithProxies(Reactor federation) { + private void replaceFederateConnectionsWithProxies(Reactor federation, Resource resource) { // Each connection in the AST may represent more than one connection between // 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 @@ -491,7 +492,7 @@ private void replaceFederateConnectionsWithProxies(Reactor federation) { for (ReactorInstance child : mainInstance.children) { for (PortInstance output : child.outputs) { - replaceConnectionFromOutputPort(output); + replaceConnectionFromOutputPort(output, resource); } } @@ -507,8 +508,9 @@ private void replaceFederateConnectionsWithProxies(Reactor federation) { * Replace the connections from the specified output port. * * @param output The output port instance. + * @param resource */ - private void replaceConnectionFromOutputPort(PortInstance output) { + private void replaceConnectionFromOutputPort(PortInstance output, Resource resource) { // Iterate through ranges of the output port for (SendRange srcRange : output.getDependentPorts()) { if (srcRange.connection == null) { @@ -523,7 +525,8 @@ private void replaceConnectionFromOutputPort(PortInstance output) { for (RuntimeRange dstRange : srcRange.destinations) { replaceOneToManyConnection( srcRange, - dstRange + dstRange, + resource ); } } @@ -536,10 +539,11 @@ private void replaceConnectionFromOutputPort(PortInstance output) { * @param srcRange A range of an output port that sources data for this * connection. * @param dstRange A range of input ports that receive the data. + * @param resource */ private void replaceOneToManyConnection( SendRange srcRange, - RuntimeRange dstRange + RuntimeRange dstRange, Resource resource ) { MixedRadixInt srcID = srcRange.startMR(); MixedRadixInt dstID = dstRange.startMR(); @@ -575,7 +579,7 @@ private void replaceOneToManyConnection( FedUtils.getSerializer(srcRange.connection, srcFederate, dstFederate) ); - replaceFedConnection(fedConnection); + replaceFedConnection(fedConnection, resource); dstID.increment(); srcID.increment(); @@ -590,8 +594,9 @@ private void replaceOneToManyConnection( * Replace a one-to-one federated connection with proxies. * * @param connection A connection between two federates. + * @param resource */ - private void replaceFedConnection(FedConnectionInstance connection) { + private void replaceFedConnection(FedConnectionInstance connection, Resource resource) { if (!connection.getDefinition().isPhysical() && targetConfig.coordination != CoordinationType.DECENTRALIZED) { // Map the delays on connections between federates. @@ -621,6 +626,6 @@ private void replaceFedConnection(FedConnectionInstance connection) { } } - FedASTUtils.makeCommunication(connection, targetConfig.coordination, errorReporter); + FedASTUtils.makeCommunication(connection, resource, targetConfig.coordination, errorReporter); } } diff --git a/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java b/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java index 54e215264f..43c1b14eb8 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java +++ b/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java @@ -54,7 +54,9 @@ String generateMainReactor(FederateInstance federate, Reactor originalMainReacto ASTUtils.allActions(originalMainReactor).stream().filter(federate::contains).map(renderer).collect(Collectors.joining("\n")), ASTUtils.allTimers(originalMainReactor).stream().filter(federate::contains).map(renderer).collect(Collectors.joining("\n")), ASTUtils.allMethods(originalMainReactor).stream().filter(federate::contains).map(renderer).collect(Collectors.joining("\n")), - ASTUtils.allReactions(originalMainReactor).stream().filter(federate::contains).map(renderer).collect(Collectors.joining("\n")) + ASTUtils.allReactions(originalMainReactor).stream().filter(federate::contains).map(renderer).collect(Collectors.joining("\n")), + federate.networkInstantiations.stream().map(renderer).collect(Collectors.joining("\n")), + federate.networkConnections.stream().map(renderer).collect(Collectors.joining("\n")) ).indent(4).stripTrailing(), "}" ); diff --git a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java index 92faefe400..c805ae097f 100644 --- a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java +++ b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java @@ -40,8 +40,6 @@ 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; @@ -54,6 +52,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.TriggerInstance; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; +import org.lflang.lf.Connection; import org.lflang.lf.Expression; import org.lflang.lf.Import; import org.lflang.lf.ImportedReactor; @@ -202,6 +201,12 @@ public Instantiation getInstantiation() { * The sending federate needs to specify this ID. */ public List networkMessageActions = new ArrayList<>(); + + + /** + * List of networkMessage reactors corresponding to actions. + */ + public List networkMessageActionReactors = new ArrayList<>(); /** * A set of federates with which this federate has an inbound connection @@ -250,6 +255,26 @@ public Instantiation getInstantiation() { */ public List networkReactions = new ArrayList<>(); + /** + * List of generated network reactors (network input and outputs) that + * belong to this federate instance. + */ + public List networkReactors = new ArrayList<>(); + + + /** + * List of generated network connections (network input and outputs) that + * belong to this federate instance. + */ + public List networkConnections = new ArrayList<>(); + + + /** + * List of generated network instantiations (network input and outputs) that + * belong to this federate instance. + */ + public List networkInstantiations = new ArrayList<>(); + /** * Parsed target config of the federate. */ @@ -295,18 +320,21 @@ private boolean contains( Instantiation instantiation, ReactorDecl reactor ) { + if (instantiation.getReactorClass().equals(ASTUtils.toDefinition(reactor))) { return true; } boolean instantiationsCheck = false; + if (networkReactors.contains(ASTUtils.toDefinition(reactor))){ + return true; + } // 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 instantiationsCheck; } From d713aeb8758426f22448f8f326f4c7642c7028cc Mon Sep 17 00:00:00 2001 From: Anirudh Rengarajan <44007330+arengarajan99@users.noreply.github.com> Date: Sat, 1 Apr 2023 10:55:13 -0700 Subject: [PATCH 111/516] Fix LFC to pass compilation stage --- .../federated/extensions/CExtension.java | 3 +- .../federated/extensions/CExtensionUtils.java | 4 +- .../federated/generator/FedASTUtils.java | 147 +++++++++++------- .../federated/generator/FedMainEmitter.java | 3 +- .../federated/generator/FederateInstance.java | 7 +- 5 files changed, 99 insertions(+), 65 deletions(-) diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtension.java b/org.lflang/src/org/lflang/federated/extensions/CExtension.java index 93c661e3c9..bdebae8cd0 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtension.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtension.java @@ -542,7 +542,8 @@ private String generateInitializeTriggers(FederateInstance federate, ErrorReport var federatedReactor = FedASTUtils.findFederatedReactor(federate.instantiation.eResource()); var oldFederatedReactorName = federatedReactor.getName(); federatedReactor.setName(federate.name); - code.pr(CExtensionUtils.initializeTriggersForNetworkActions(federate, errorReporter)); + var main = new ReactorInstance(federatedReactor, errorReporter, -1); + code.pr(CExtensionUtils.initializeTriggersForNetworkActions(federate, main, errorReporter)); //code.pr(CExtensionUtils.initializeTriggerForControlReactions(main, main, federate)); federatedReactor.setName(oldFederatedReactorName); diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java b/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java index 9992302b88..5f287be6c5 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java @@ -83,7 +83,7 @@ public static String allocateTriggersForFederate( * @param main The main reactor that contains the federate (used to lookup references). * @return */ - public static String initializeTriggersForNetworkActions(FederateInstance federate, ErrorReporter errorReporter) { + public static String initializeTriggersForNetworkActions(FederateInstance federate, ReactorInstance main, ErrorReporter errorReporter) { CodeBuilder code = new CodeBuilder(); if (federate.networkMessageActions.size() > 0) { // Create a static array of trigger_t pointers. @@ -95,7 +95,7 @@ public static String initializeTriggersForNetworkActions(FederateInstance federa for (int i = 0; i < federate.networkMessageActions.size(); ++i) { // Find the corresponding ActionInstance. Action action = federate.networkMessageActions.get(i); - var reactor = new ReactorInstance(federate.networkMessageActionReactors.get(i), errorReporter, 1); + var reactor = main.lookupReactorInstance(federate.networkReceiverInstantiations.get(i)); var actionInstance = reactor.lookupActionInstance(action); triggers.add(CUtil.actionRef(actionInstance, null)); } diff --git a/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java b/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java index 4ad3fca0f2..f5fc841be6 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java +++ b/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java @@ -29,6 +29,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; @@ -363,7 +364,7 @@ private static void addNetworkReceiverReactor( connection.dstFederate.networkReactions.add(networkReceiverReaction); connection.dstFederate.networkReactors.add(receiver); connection.dstFederate.networkConnections.add(receiverFromReaction); - connection.dstFederate.networkInstantiations.add(networkInstance); + connection.dstFederate.networkReceiverInstantiations.add(networkInstance); // if ( @@ -743,6 +744,76 @@ public static List safe(List list) { public static int networkIDSender = 0; public static int networkIDReceiver = 0; + private static Map networkSenderReactors = new HashMap<>(); + private static Map networkSenderInstantiations = new HashMap<>(); + + + private static Reactor getNetworkSenderReactor(FedConnectionInstance connection, + CoordinationType coordination, Resource resource, ErrorReporter errorReporter) { + LfFactory factory = LfFactory.eINSTANCE; + // Reactor classDef = networkSenderReactors.getOrDefault(connection.srcFederate, null); + // if (classDef != null) { + // return classDef; + // } + Type type = EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getType()); + + //Initialize Reactor and Reaction AST Nodes + Reactor sender = factory.createReactor(); + Reaction networkSenderReaction = factory.createReaction(); + + VarRef inRef = factory.createVarRef(); //in port to network reaction + VarRef destRef = factory.createVarRef(); //destination fed + + Input in = factory.createInput(); + + sender.getReactions().add(networkSenderReaction); + sender.getInputs().add(in); + + EObject node = IteratorExtensions.findFirst(resource.getAllContents(), Model.class::isInstance); + ((Model) node).getReactors().add(sender); + sender.setName("NetworkSender_" + networkIDSender++); + + // FIXME: do not create a new extension every time it is used + FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) + .annotateReaction(networkSenderReaction); + + // If the sender or receiver is in a bank of reactors, then we want + // these reactions to appear only in the federate whose bank ID matches. + setReactionBankIndex(networkSenderReaction, connection.getSrcBank()); + + + in.setName("msg"); + in.setType(type); + in.setWidthSpec(EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getWidthSpec())); + inRef.setVariable(in); + + destRef.setContainer(connection.getDestinationPortInstance().getParent().getDefinition()); + destRef.setVariable(connection.getDestinationPortInstance().getDefinition()); + + // Configure the sending reaction. + networkSenderReaction.getTriggers().add(inRef); + networkSenderReaction.setCode(factory.createCode()); + networkSenderReaction.getCode().setBody( + FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) + .generateNetworkSenderBody( + inRef, + destRef, + connection, + InferredType.fromAST(type), + coordination, + errorReporter + )); + + // Add the network sender reaction to the federate instance's list + // of network reactions + connection.srcFederate.networkReactions.add(networkSenderReaction); + connection.srcFederate.networkReactors.add(sender); + + networkSenderReactors.put(connection.srcFederate, sender); + return sender; + + } + /** * Add a network sender reactor for a given input port 'source' to * source's parent reactor. This reaction will react to the 'source' @@ -764,42 +835,33 @@ private static void addNetworkSenderReactor( ) { LfFactory factory = LfFactory.eINSTANCE; // Assume all the types are the same, so just use the first on the right. - Type type = EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getType()); - VarRef sourceRef = factory.createVarRef(); //out connection - VarRef instRef = factory.createVarRef(); //instantiation connection - VarRef destRef = factory.createVarRef(); //destination fed - Reactor sender = factory.createReactor(); - Reaction networkSenderReaction = factory.createReaction(); - Input in = factory.createInput(); - VarRef inRef = factory.createVarRef(); //in port - Connection senderToReaction = factory.createConnection(); + + Reactor sender = getNetworkSenderReactor(connection, coordination, resource, errorReporter); + Instantiation networkInstance = factory.createInstantiation(); + VarRef sourceRef = factory.createVarRef(); //out port from federate + VarRef instRef = factory.createVarRef(); //out port from federate + Reactor top = connection.getSourcePortInstance() .getParent() .getParent().reactorDefinition; // Top-level reactor. - sender.getReactions().add(networkSenderReaction); - sender.getInputs().add(in); - EObject node = IteratorExtensions.findFirst(resource.getAllContents(), Model.class::isInstance); - ((Model) node).getReactors().add(sender); - sender.setName("NetworkSender_" + networkIDSender++); - networkInstance.setReactorClass(sender); networkInstance.setName(ASTUtils.getUniqueIdentifier(top, "ns_" + connection.getDstFederate().name)); top.getInstantiations().add(networkInstance); - senderToReaction.getLeftPorts().add(sourceRef); - senderToReaction.getRightPorts().add(instRef); + Connection senderToReaction = factory.createConnection(); - // FIXME: do not create a new extension every time it is used - FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) - .annotateReaction(networkSenderReaction); + // Establish references to the involved ports. + sourceRef.setContainer(connection.getSourcePortInstance().getParent().getDefinition()); + sourceRef.setVariable(connection.getSourcePortInstance().getDefinition()); + instRef.setContainer(networkInstance); + instRef.setVariable(sender.getInputs().get(0)); - // If the sender or receiver is in a bank of reactors, then we want - // these reactions to appear only in the federate whose bank ID matches. - setReactionBankIndex(networkSenderReaction, connection.getSrcBank()); + senderToReaction.getLeftPorts().add(sourceRef); + senderToReaction.getRightPorts().add(instRef); // The connection is 'physical' if it uses the ~> notation. if (connection.getDefinition().isPhysical()) { @@ -813,43 +875,8 @@ private static void addNetworkSenderReactor( } } - // Establish references to the involved ports. - sourceRef.setContainer(connection.getSourcePortInstance().getParent().getDefinition()); - sourceRef.setVariable(connection.getSourcePortInstance().getDefinition()); - instRef.setContainer(networkInstance); - instRef.setVariable(in); - destRef.setContainer(connection.getDestinationPortInstance().getParent().getDefinition()); - destRef.setVariable(connection.getDestinationPortInstance().getDefinition()); - - in.setName("msg"); - in.setType(type); - in.setWidthSpec(EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getWidthSpec())); - inRef.setVariable(in); - - - // Configure the sending reaction. - networkSenderReaction.getTriggers().add(inRef); - networkSenderReaction.setCode(factory.createCode()); - networkSenderReaction.getCode().setBody( - FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) - .generateNetworkSenderBody( - inRef, - destRef, - connection, - InferredType.fromAST(type), - coordination, - errorReporter - )); - - // Add the sending reaction to the parent. - //networkSenderReaction.getTriggers().add(inRef); - - // Add the network sender reaction to the federate instance's list - // of network reactions - connection.srcFederate.networkReactions.add(networkSenderReaction); - connection.srcFederate.networkReactors.add(sender); connection.srcFederate.networkConnections.add(senderToReaction); - connection.srcFederate.networkInstantiations.add(networkInstance); + connection.srcFederate.networkSenderInstantiations.add(networkInstance); } /** diff --git a/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java b/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java index 43c1b14eb8..fec90b6992 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java +++ b/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java @@ -55,7 +55,8 @@ String generateMainReactor(FederateInstance federate, Reactor originalMainReacto ASTUtils.allTimers(originalMainReactor).stream().filter(federate::contains).map(renderer).collect(Collectors.joining("\n")), ASTUtils.allMethods(originalMainReactor).stream().filter(federate::contains).map(renderer).collect(Collectors.joining("\n")), ASTUtils.allReactions(originalMainReactor).stream().filter(federate::contains).map(renderer).collect(Collectors.joining("\n")), - federate.networkInstantiations.stream().map(renderer).collect(Collectors.joining("\n")), + federate.networkSenderInstantiations.stream().map(renderer).collect(Collectors.joining("\n")), + federate.networkReceiverInstantiations.stream().map(renderer).collect(Collectors.joining("\n")), federate.networkConnections.stream().map(renderer).collect(Collectors.joining("\n")) ).indent(4).stripTrailing(), "}" diff --git a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java index c805ae097f..1a912d06ae 100644 --- a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java +++ b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java @@ -273,7 +273,12 @@ public Instantiation getInstantiation() { * List of generated network instantiations (network input and outputs) that * belong to this federate instance. */ - public List networkInstantiations = new ArrayList<>(); + public List networkSenderInstantiations = new ArrayList<>(); + /** + * List of generated network instantiations (network input and outputs) that + * belong to this federate instance. + */ + public List networkReceiverInstantiations = new ArrayList<>(); /** * Parsed target config of the federate. From a47b795d04233fa7fd5fb6a79b514b6941a57264 Mon Sep 17 00:00:00 2001 From: Anirudh Rengarajan <44007330+arengarajan99@users.noreply.github.com> Date: Sat, 1 Apr 2023 23:16:09 -0700 Subject: [PATCH 112/516] Added support for referencing upstream reactors for port statuses --- .../federated/extensions/CExtension.java | 10 ++++++-- .../federated/generator/FedASTUtils.java | 11 +++----- .../federated/generator/FederateInstance.java | 25 +++++++++---------- .../lflang/generator/c/CCmakeGenerator.java | 1 + 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtension.java b/org.lflang/src/org/lflang/federated/extensions/CExtension.java index bdebae8cd0..7e9828e01f 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtension.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtension.java @@ -508,10 +508,16 @@ protected String makePreamble( size_t _lf_action_table_size = %1$s; """.formatted(numOfNetworkActions)); + int numOfNetworkReactions = federate.networkReceiverReactions.size(); + code.pr(""" + reaction_t* upstreamPortReactions[%1$s]; + size_t num_upstream_port_dependencies = %1$s; + """.formatted(numOfNetworkReactions)); + code.pr(generateSerializationPreamble(federate, fileConfig)); code.pr(generateExecutablePreamble(federate, rtiConfig, errorReporter)); - + code.pr(generateInitializeTriggers(federate, errorReporter)); code.pr(CExtensionUtils.generateFederateNeighborStructure(federate)); @@ -544,7 +550,7 @@ private String generateInitializeTriggers(FederateInstance federate, ErrorReport federatedReactor.setName(federate.name); var main = new ReactorInstance(federatedReactor, errorReporter, -1); code.pr(CExtensionUtils.initializeTriggersForNetworkActions(federate, main, errorReporter)); - //code.pr(CExtensionUtils.initializeTriggerForControlReactions(main, main, federate)); + code.pr(CExtensionUtils.upstreamPortReactions(federate, main)); federatedReactor.setName(oldFederatedReactorName); return """ diff --git a/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java b/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java index f5fc841be6..3d0b2731fd 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java +++ b/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java @@ -293,9 +293,6 @@ private static void addNetworkReceiverReactor( // Keep track of this action in the destination federate. connection.dstFederate.networkMessageActions.add(networkAction); - // Keep track of the in the destination federate. - connection.dstFederate.networkMessageActionReactors.add(receiver); - // Add the action definition to the parent reactor. receiver.getActions().add(networkAction); @@ -361,7 +358,7 @@ private static void addNetworkReceiverReactor( // Add the network receiver reaction to the federate instance's list // of network reactions - connection.dstFederate.networkReactions.add(networkReceiverReaction); + connection.dstFederate.networkReceiverReactions.add(networkReceiverReaction); connection.dstFederate.networkReactors.add(receiver); connection.dstFederate.networkConnections.add(receiverFromReaction); connection.dstFederate.networkReceiverInstantiations.add(networkInstance); @@ -462,7 +459,7 @@ private static void addNetworkInputControlReaction( // Add the network input control reaction to the federate instance's list // of network reactions - connection.dstFederate.networkReactions.add(reaction); + //connection.dstFederate.networkReactions.add(reaction); // Add necessary dependencies to reaction to ensure that it executes correctly // relative to other network input control reactions in the federate. @@ -806,7 +803,7 @@ private static Reactor getNetworkSenderReactor(FedConnectionInstance connection, // Add the network sender reaction to the federate instance's list // of network reactions - connection.srcFederate.networkReactions.add(networkSenderReaction); + connection.srcFederate.networkSenderReactions.add(networkSenderReaction); connection.srcFederate.networkReactors.add(sender); networkSenderReactors.put(connection.srcFederate, sender); @@ -964,6 +961,6 @@ private static void addNetworkOutputControlReaction(FedConnectionInstance connec // Add the network output control reaction to the federate instance's list // of network reactions - connection.srcFederate.networkReactions.add(reaction); + //connection.srcFederate.networkReactions.add(reaction); } } diff --git a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java index 1a912d06ae..e76f465796 100644 --- a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java +++ b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java @@ -201,12 +201,6 @@ public Instantiation getInstantiation() { * The sending federate needs to specify this ID. */ public List networkMessageActions = new ArrayList<>(); - - - /** - * List of networkMessage reactors corresponding to actions. - */ - public List networkMessageActionReactors = new ArrayList<>(); /** * A set of federates with which this federate has an inbound connection @@ -249,11 +243,14 @@ public Instantiation getInstantiation() { 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. + * List of generated network reactions (network receivers) that belong to this federate instance. */ - public List networkReactions = new ArrayList<>(); + public List networkReceiverReactions = new ArrayList<>(); + + /** + * List of generated network reactions (network sender) that belong to this federate instance. + */ + public List networkSenderReactions = new ArrayList<>(); /** * List of generated network reactors (network input and outputs) that @@ -378,7 +375,7 @@ private boolean contains(Parameter param) { // the parameters, so we need to include the parameter. var topLevelUserDefinedReactions = ((Reactor) instantiation.eContainer()) .getReactions().stream().filter( - r -> !networkReactions.contains(r) && contains(r) + r -> !networkReceiverReactions.contains(r) && !networkSenderReactions.contains(r) && contains(r) ).collect(Collectors.toCollection(ArrayList::new)); returnValue |= !topLevelUserDefinedReactions.isEmpty(); return returnValue; @@ -445,11 +442,12 @@ private boolean contains(Reaction reaction) { assert reactor != null; if (!reactor.getReactions().contains(reaction)) return false; - if (networkReactions.contains(reaction)) { + if (networkReceiverReactions.contains(reaction) || networkSenderReactions.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; @@ -543,7 +541,8 @@ private void indexExcludedTopLevelReactions(Reactor federatedReactor) { // 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()); + Iterable reactions = ASTUtils.allReactions(federatedReactor).stream().filter(it -> !networkReceiverReactions.contains(it)) + .filter(it -> !networkSenderReactions.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. diff --git a/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java b/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java index f5e7187233..75a04ad9b5 100644 --- a/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java @@ -218,6 +218,7 @@ CodeBuilder generateCMakeCode( cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/core/platform)"); cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/core/modal_models)"); cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/core/utils)"); + if(targetConfig.auth) { // If security is requested, add the auth option. From 37d1dbf2e00b2ce6715c30197547cc713a40e0cf Mon Sep 17 00:00:00 2001 From: Anirudh Rengarajan <44007330+arengarajan99@users.noreply.github.com> Date: Sat, 1 Apr 2023 23:21:54 -0700 Subject: [PATCH 113/516] Add generation of upstream port reactor array generation for checking upstream dependencies using chain id --- .../federated/extensions/CExtensionUtils.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java b/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java index 5f287be6c5..7e0d68cc25 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java @@ -582,4 +582,28 @@ public static String generateSerializationCMakeExtension( } return code.getCode(); } + + public static CharSequence upstreamPortReactions(FederateInstance federate, ReactorInstance main) { + CodeBuilder code = new CodeBuilder(); + if (!federate.networkMessageActions.isEmpty()) { + // Create a static array of trigger_t pointers. + // networkMessageActions is a list of Actions, but we + // need a list of trigger struct names for ActionInstances. + // There should be exactly one ActionInstance in the + // main reactor for each Action. + var reactions = new LinkedList(); + for (int i = 0; i < federate.networkMessageActions.size(); ++i) { + // Find the corresponding ActionInstance. + var reaction = federate.networkReceiverReactions.get(i); + var reactor = main.lookupReactorInstance(federate.networkReceiverInstantiations.get(i)); + var reactionInstance = reactor.lookupReactionInstance(reaction); + reactions.add(CUtil.reactionRef(reactionInstance)); + } + var tableCount = 0; + for (String react: reactions) { + code.pr("upstreamPortReactions[" + (tableCount++) + "] = (reaction_t*)&" + react + "; \\"); + } + } + return code.getCode(); + } } From 67ee262ae5b575f674635fe33ec7475edafae0b1 Mon Sep 17 00:00:00 2001 From: Anirudh Rengarajan <44007330+arengarajan99@users.noreply.github.com> Date: Thu, 4 May 2023 21:14:48 -0700 Subject: [PATCH 114/516] Add pair class to prevent reliance on Kotlin --- org.lflang/src/lib/c/reactor-c | 2 +- org.lflang/src/org/lflang/util/Pair.java | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 org.lflang/src/org/lflang/util/Pair.java diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 06f40da4b4..c25cd601c8 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 06f40da4b4f89ea5695c3f2970e6342c4af8426a +Subproject commit c25cd601c817596cc24d75e365820d1bf5f3e32d diff --git a/org.lflang/src/org/lflang/util/Pair.java b/org.lflang/src/org/lflang/util/Pair.java new file mode 100644 index 0000000000..2de5923a30 --- /dev/null +++ b/org.lflang/src/org/lflang/util/Pair.java @@ -0,0 +1,21 @@ +package org.lflang.util; + +public class Pair { + private final F first; + private final S second; + + public Pair(F first, S second) { + this.first = first; + this.second = second; + } + + public F getFirst() { + return first; + } + + public S getSecond() { + return second; + } +} + +//TimeValue maxSTP = findMaxSTP(connection, coordination); \ No newline at end of file From 10d6103940567afe3a3ead09563e855694048449 Mon Sep 17 00:00:00 2001 From: Anirudh Rengarajan <44007330+arengarajan99@users.noreply.github.com> Date: Thu, 4 May 2023 21:15:00 -0700 Subject: [PATCH 115/516] Add attributespec for dependency pairs --- org.lflang/src/org/lflang/validation/AttributeSpec.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/validation/AttributeSpec.java b/org.lflang/src/org/lflang/validation/AttributeSpec.java index a7a95e852c..78f8624a63 100644 --- a/org.lflang/src/org/lflang/validation/AttributeSpec.java +++ b/org.lflang/src/org/lflang/validation/AttributeSpec.java @@ -49,6 +49,7 @@ public class AttributeSpec { public static final String VALUE_ATTR = "value"; public static final String NETWORK_MESSAGE_ACTIONS = "network_message_actions"; + public static final String DEPENDENCY_PAIRS = "dependencyPairs"; public static final String EACH_ATTR = "each"; /** A map from a string to a supported AttributeSpec */ @@ -222,7 +223,9 @@ enum AttrParamType { ATTRIBUTE_SPECS_BY_NAME.put("_fed_config", new AttributeSpec( List.of(new AttrParamSpec(AttributeSpec.NETWORK_MESSAGE_ACTIONS, - AttrParamType.STRING, false)))); + AttrParamType.STRING, false), + new AttrParamSpec(AttributeSpec.DEPENDENCY_PAIRS, + AttrParamType.STRING, false)))); ATTRIBUTE_SPECS_BY_NAME.put("_c_body", new AttributeSpec(null)); } } From 0d95385b6fada94e4a16a1b3e2f45935789e74ca Mon Sep 17 00:00:00 2001 From: Anirudh Rengarajan <44007330+arengarajan99@users.noreply.github.com> Date: Thu, 4 May 2023 21:15:27 -0700 Subject: [PATCH 116/516] Add support for creating phantom edges for intrafederate dependencies through the network --- .../generator/ReactionInstanceGraph.java | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index 2012b7f53b..a43e5a7eda 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -27,15 +27,21 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.generator; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import org.eclipse.xtend.lib.macro.services.UpstreamTypeLookup; +import org.lflang.AttributeUtils; import org.lflang.generator.ReactionInstance.Runtime; import org.lflang.generator.c.CUtil; import org.lflang.graph.PrecedenceGraph; +import org.lflang.lf.Attribute; import org.lflang.lf.Variable; +import org.lflang.validation.AttributeSpec; /** * This class analyzes the dependencies between reaction runtime instances. @@ -86,8 +92,10 @@ public void rebuild() { this.clear(); addNodesAndEdges(main); + // FIXME: Use {@link TargetProperty#EXPORT_DEPENDENCY_GRAPH}. // System.out.println(toDOT()); + addDependentNetworkEdges(main); // Assign a level to each reaction. // If there are cycles present in the graph, it will be detected here. @@ -99,6 +107,39 @@ public void rebuild() { // throw new InvalidSourceException("Reactions form a cycle!"); } } + /** + * Adds manually a set of dependent network edges as needed to nudge the level assignment + * algorithm into creating a correct level assignment. + * @param main + */ + private void addDependentNetworkEdges(ReactorInstance main) { + + Attribute attribute = AttributeUtils.findAttributeByName(main.definition.getReactorClass(), "_fed_config"); + String actionsStr = AttributeUtils.getAttributeParameter(attribute, AttributeSpec.DEPENDENCY_PAIRS); + if (actionsStr == null) return; //No dependent network edges, the levels algorithm has enough information + List dependencies = List.of(actionsStr.split(";", -1)); + // Recursively add nodes and edges from contained reactors. + Map m = new HashMap<>(); + for (ReactorInstance child : main.children) { + m.put(child.getName(), child); + } + for(String dependency: dependencies){ + List dep = List.of(dependency.split(",", 2)); + ReactorInstance downStream = m.getOrDefault(dep.get(0), null); + ReactorInstance upStream = m.getOrDefault(dep.get(1), null); + if(downStream == null || upStream == null) { + System.out.println("Downstream or Upstream reaction pair is undefined. Continuing."); + continue; + } + ReactionInstance down = downStream.reactions.get(0); + Runtime downRuntime = down.getRuntimeInstances().get(0); + for(ReactionInstance up: upStream.reactions){ + Runtime upRuntime = up.getRuntimeInstances().get(0); + addEdge(downRuntime, upRuntime); + } + } + + } /** * This function rebuilds the graph and propagates and assigns deadlines * to all reactions. @@ -106,6 +147,7 @@ public void rebuild() { public void rebuildAndAssignDeadlines() { this.clear(); addNodesAndEdges(main); + addDependentNetworkEdges(main); assignInferredDeadlines(); this.clear(); } @@ -298,7 +340,7 @@ private void assignLevels() { removeEdge(effect, origin); // If the effect node has no more incoming edges, // then move it in the start set. - if (getUpstreamAdjacentNodes(effect).size() == 0) { + if (getUpstreamAdjacentNodes(effect).isEmpty()) { start.add(effect); } } From 627c21a99069019bd08f0575e071a40cc712de7c Mon Sep 17 00:00:00 2001 From: Anirudh Rengarajan <44007330+arengarajan99@users.noreply.github.com> Date: Thu, 4 May 2023 21:16:00 -0700 Subject: [PATCH 117/516] Add support for annotating the dependency list into the emitter --- .../federated/generator/FedMainEmitter.java | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java b/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java index fec90b6992..bed2379f52 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java +++ b/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java @@ -15,11 +15,15 @@ import org.lflang.ErrorReporter; import org.lflang.ast.FormattingUtils; import org.lflang.generator.CodeBuilder; +import org.lflang.generator.PortInstance; import org.lflang.generator.ReactorInstance; import org.lflang.lf.Instantiation; import org.lflang.lf.Reactor; import org.lflang.lf.Variable; +import org.lflang.util.Pair; + + /** * Helper class to generate a main reactor */ @@ -61,6 +65,26 @@ String generateMainReactor(FederateInstance federate, Reactor originalMainReacto ).indent(4).stripTrailing(), "}" ); + } + + private static String getDependencyList(FederateInstance federate, Pair p){ + //StringBuilder lst = new StringBuilder(); + var inputPort = p.getFirst(); + var outputPort = p.getSecond(); + var inputPortInstance = federate.networkPortToInstantiation.getOrDefault(inputPort, null); + var outputPortInstance = federate.networkPortToInstantiation.getOrDefault(outputPort, null); + //var outputPortControlReaction = federate.networkPortToInstantiation.getOrDefault(outputPort, null); + if(inputPortInstance == null) return ""; + //System.out.println("IP: " + inputPortReaction.getCode()); + if(outputPortInstance != null){ + //System.out.println("OP: " + outputPortReaction.toString()); + return inputPortInstance.getName() + "," + outputPortInstance.getName(); + } + return ""; + + + + } /** @@ -85,12 +109,19 @@ private CharSequence generateMainSignature(FederateInstance federate, Reactor or .stream() .map(Variable::getName) .collect(Collectors.joining(",")); + + List vals = new ArrayList<>(); + for (var pair: federate.networkReactionDependencyPairs){ + vals.add(getDependencyList(federate, pair)); + } + String intraDependencies = String.join(";", vals); return """ - @_fed_config(network_message_actions="%s") + @_fed_config(network_message_actions="%s", dependencyPairs="%s") main reactor %s { """.formatted(networkMessageActionsListString, + intraDependencies, paramList.equals("()") ? "" : paramList); } } From a28ee7f78b66f6d14a5e56d2261ffc817abeb761 Mon Sep 17 00:00:00 2001 From: Anirudh Rengarajan <44007330+arengarajan99@users.noreply.github.com> Date: Thu, 4 May 2023 21:16:39 -0700 Subject: [PATCH 118/516] Clean up triggers and add additional pointers for network reactions --- .../federated/extensions/CExtension.java | 19 ++- .../federated/extensions/CExtensionUtils.java | 143 +++++++++++------- 2 files changed, 106 insertions(+), 56 deletions(-) diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtension.java b/org.lflang/src/org/lflang/federated/extensions/CExtension.java index 3800ec6628..29230663d7 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtension.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtension.java @@ -523,10 +523,16 @@ protected String makePreamble( int numOfNetworkReactions = federate.networkReceiverReactions.size(); code.pr(""" - reaction_t* upstreamPortReactions[%1$s]; - size_t num_upstream_port_dependencies = %1$s; + reaction_t* networkInputReactions[%1$s]; + size_t numNetworkInputReactions = %1$s; """.formatted(numOfNetworkReactions)); + int numOfNetworkSenderControlReactions = federate.networkSenderControlReactions.size(); + code.pr(""" + reaction_t* portAbsentReaction[%1$s]; + size_t numSenderReactions = %1$s; + """.formatted(numOfNetworkSenderControlReactions)); + code.pr(generateSerializationPreamble(federate, fileConfig)); code.pr(generateExecutablePreamble(federate, rtiConfig, errorReporter)); @@ -563,7 +569,9 @@ private String generateInitializeTriggers(FederateInstance federate, ErrorReport federatedReactor.setName(federate.name); var main = new ReactorInstance(federatedReactor, errorReporter, -1); code.pr(CExtensionUtils.initializeTriggersForNetworkActions(federate, main, errorReporter)); - code.pr(CExtensionUtils.upstreamPortReactions(federate, main)); + //code.pr(CExtensionUtils.initializeTriggersForControlReactions(federate, main, errorReporter)); + //code.pr(CExtensionUtils.networkInputReactions(federate, main)); + //code.pr(CExtensionUtils.portAbsentReaction(federate, main)); federatedReactor.setName(oldFederatedReactorName); return """ @@ -586,7 +594,7 @@ private String generateExecutablePreamble(FederateInstance federate, RtiConfig r code.pr(generateCodeToInitializeFederate(federate, rtiConfig)); - code.pr(CExtensionUtils.allocateTriggersForFederate(federate)); + //code.pr(CExtensionUtils.allocateTriggersForFederate(federate)); return """ void _lf_executable_preamble() { @@ -606,7 +614,8 @@ private String generateCodeToInitializeFederate(FederateInstance federate, RtiCo code.pr(String.join("\n", "// Initialize the socket mutex", "lf_mutex_init(&outbound_socket_mutex);", - "lf_cond_init(&port_status_changed, &mutex);" + "lf_cond_init(&port_status_changed, &mutex);", + CExtensionUtils.surroundWithIfFederatedDecentralized("lf_cond_init(&logical_time_changed, &mutex);") )); // Find the STA (A.K.A. the global STP offset) for this federate. diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java b/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java index 2c762cfef8..d05c563755 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java @@ -122,59 +122,75 @@ public static String initializeTriggersForNetworkActions(FederateInstance federa * * @param instance The reactor instance that is at any level of the * hierarchy within the federate. - * @param federate The top-level federate + * @param errorReporter The top-level federate * @return A string that initializes the aforementioned three structures. */ - public static String initializeTriggerForControlReactions( - ReactorInstance instance, - ReactorInstance main, - FederateInstance federate - ) { - CodeBuilder builder = new CodeBuilder(); - // The network control reactions are always in the main federated - // reactor - if (instance != main) { - return ""; - } - - ReactorDecl reactorClass = instance.getDefinition().getReactorClass(); - Reactor reactor = ASTUtils.toDefinition(reactorClass); - String nameOfSelfStruct = CUtil.reactorRef(instance); - - // Initialize triggers for network input control reactions - for (Action trigger : federate.networkInputControlReactionsTriggers) { - // Check if the trigger belongs to this reactor instance - if (ASTUtils.allReactions(reactor).stream().anyMatch(r -> { - return r.getTriggers().stream().anyMatch(t -> { - if (t instanceof VarRef) { - return ((VarRef) t).getVariable().equals(trigger); - } else { - return false; - } - }); - })) { - // Initialize the triggers_for_network_input_control_reactions for the input - builder.pr( - String.join("\n", - "/* Add trigger " + nameOfSelfStruct + "->_lf__"+trigger.getName()+" to the global list of network input ports. */ \\", - "_fed.triggers_for_network_input_control_reactions["+federate.networkInputControlReactionsTriggers.indexOf(trigger)+"]= \\", - " &"+nameOfSelfStruct+"->_lf__"+trigger.getName()+"; \\" - ) - ); - } - } - - nameOfSelfStruct = CUtil.reactorRef(instance); - - // Initialize the trigger for network output control reactions if it doesn't exist. - if (federate.networkOutputControlReactionsTrigger != null) { - builder.pr("_fed.trigger_for_network_output_control_reactions=&" - + nameOfSelfStruct - + "->_lf__outputControlReactionTrigger; \\"); - } - - return builder.getCode(); - } + // public static String initializeTriggersForControlReactions( + // FederateInstance instance, + // ReactorInstance main, + // ErrorReporter errorReporter + // ) { + // CodeBuilder builder = new CodeBuilder(); + + // if (federate.networkSenderControlReactions.size() > 0) { + // // Create a static array of trigger_t pointers. + // // networkMessageActions is a list of Actions, but we + // // need a list of trigger struct names for ActionInstances. + // // There should be exactly one ActionInstance in the + // // main reactor for each Action. + // var triggers = new LinkedList(); + // for (int i = 0; i < federate.networkSenderControlReactions.size(); ++i) { + // // Find the corresponding ActionInstance. + // Action action = federate.networkMessageActions.get(i); + // var reactor = main.lookupReactorInstance(federate.networkReceiverInstantiations.get(i)); + // var actionInstance = reactor.lookupActionInstance(action); + // triggers.add(CUtil.actionRef(actionInstance, null)); + // } + // var actionTableCount = 0; + // for (String trigger : triggers) { + // code.pr("_lf_action_table[" + (actionTableCount++) + "] = (lf_action_base_t*)&" + // + trigger + "; \\"); + // } + // } + + // ReactorDecl reactorClass = instance.getDefinition().getReactorClass(); + // Reactor reactor = ASTUtils.toDefinition(reactorClass); + // String nameOfSelfStruct = CUtil.reactorRef(instance); + + // // Initialize triggers for network input control reactions + // for (Action trigger : errorReporter.networkInputControlReactionsTriggers) { + // // Check if the trigger belongs to this reactor instance + // if (ASTUtils.allReactions(reactor).stream().anyMatch(r -> { + // return r.getTriggers().stream().anyMatch(t -> { + // if (t instanceof VarRef) { + // return ((VarRef) t).getVariable().equals(trigger); + // } else { + // return false; + // } + // }); + // })) { + // // Initialize the triggers_for_network_input_control_reactions for the input + // builder.pr( + // String.join("\n", + // "/* Add trigger " + nameOfSelfStruct + "->_lf__"+trigger.getName()+" to the global list of network input ports. */ \\", + // "_fed.triggers_for_network_input_control_reactions["+errorReporter.networkInputControlReactionsTriggers.indexOf(trigger)+"]= \\", + // " &"+nameOfSelfStruct+"->_lf__"+trigger.getName()+"; \\" + // ) + // ); + // } + // } + + // nameOfSelfStruct = CUtil.reactorRef(instance); + + // // Initialize the trigger for network output control reactions if it doesn't exist. + // if (errorReporter.networkOutputControlReactionsTrigger != null) { + // builder.pr("_fed.trigger_for_network_output_control_reactions=&" + // + nameOfSelfStruct + // + "->_lf__outputControlReactionTrigger; \\"); + // } + + // return builder.getCode(); + // } /** * Create a port status field variable for a network input port "input" in @@ -606,4 +622,29 @@ public static CharSequence upstreamPortReactions(FederateInstance federate, Reac } return code.getCode(); } + + public static CharSequence downstreamControlPortReactions(FederateInstance federate, ReactorInstance main) { + CodeBuilder code = new CodeBuilder(); + if (!federate.networkSenderControlReactions.isEmpty()) { + // Create a static array of trigger_t pointers. + // networkMessageActions is a list of Actions, but we + // need a list of trigger struct names for ActionInstances. + // There should be exactly one ActionInstance in the + // main reactor for each Action. + var reactions = new LinkedList(); + for (int i = 0; i < federate.networkSenderControlReactions.size(); ++i) { + // Find the corresponding ActionInstance. + var reaction = federate.networkSenderControlReactions.get(i); + var reactor = main.lookupReactorInstance(federate.networkSenderInstantiations.get(i)); + var reactionInstance = reactor.lookupReactionInstance(reaction); + reactions.add(CUtil.reactionRef(reactionInstance)); + } + var tableCount = 0; + for (String react: reactions) { + code.pr("downstreamControlPortReactions[" + (tableCount++) + "] = (reaction_t*)&" + react + "; \\"); + } + } + return code.getCode(); + } } + From 4185b3ccda0c781608af0bf39ee2f9398a15c440 Mon Sep 17 00:00:00 2001 From: Anirudh Rengarajan <44007330+arengarajan99@users.noreply.github.com> Date: Sat, 6 May 2023 20:30:56 -0700 Subject: [PATCH 119/516] Add additional maps to support adding phantom dependencies in the level assignment --- .../federated/generator/FedASTUtils.java | 153 ++++++++++-------- .../federated/generator/FederateInstance.java | 26 ++- 2 files changed, 107 insertions(+), 72 deletions(-) diff --git a/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java b/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java index 3d0b2731fd..65fc968968 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java +++ b/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java @@ -71,9 +71,11 @@ import org.lflang.lf.Type; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; +import org.lflang.util.Pair; import com.google.common.collect.Iterators; + /** * A helper class for AST transformations needed for federated * execution. @@ -163,12 +165,12 @@ public static void makeCommunication( // connection.getDefinition().getDelay() // == null // Connections that have delays don't need control reactions // ) { - // // Add the network output control reaction to the parent - // FedASTUtils.addNetworkOutputControlReaction(connection); + // Add the network output control reaction to the parent + FedASTUtils.addNetworkOutputControlReaction(connection); - // // Add the network input control reaction to the parent - // FedASTUtils.addNetworkInputControlReaction(connection, coordination, errorReporter); - // } + // Add the network input control reaction to the parent + //FedASTUtils.addNetworkInputControlReaction(connection, coordination, errorReporter); + //} // Add the network receiver reactor in the destinationFederate addNetworkReceiverReactor( @@ -177,6 +179,8 @@ public static void makeCommunication( resource, errorReporter ); + + TimeValue maxSTP = findMaxSTP(connection, coordination); } public static int networkMessageActionID = 0; @@ -278,6 +282,7 @@ private static void addNetworkReceiverReactor( EObject node = IteratorExtensions.findFirst(resource.getAllContents(), Model.class::isInstance); ((Model) node).getReactors().add(receiver); receiver.setName("NetworkReceiver_" + networkIDReceiver++); + //networkReceiverReaction.setName("NetworkReceiverReaction_" + networkIDReceiver++); networkInstance.setReactorClass(receiver); @@ -362,18 +367,19 @@ private static void addNetworkReceiverReactor( connection.dstFederate.networkReactors.add(receiver); connection.dstFederate.networkConnections.add(receiverFromReaction); connection.dstFederate.networkReceiverInstantiations.add(networkInstance); - - - // if ( - // !connection.getDefinition().isPhysical() && - // // Connections that are physical don't need control reactions - // connection.getDefinition().getDelay() - // == null // Connections that have delays don't need control reactions - // ) { - // // Add necessary dependencies to reaction to ensure that it executes correctly - // // relative to other network input control reactions in the federate. - // //addRelativeDependency(connection, networkReceiverReaction, errorReporter); - // } + connection.dstFederate.networkPortToInstantiation.put(connection.getDestinationPortInstance(), networkInstance); + //System.out.println(connection.getSourcePortInstance()); + + if ( + !connection.getDefinition().isPhysical() && + // Connections that are physical don't need control reactions + connection.getDefinition().getDelay() + == null // Connections that have delays don't need control reactions + ) { + // Add necessary dependency annotations to federate to ensure the level + // assigner has enough information to correctly assign levels without introducing deadlock + addRelativeDependencyAnnotation(connection, networkReceiverReaction, errorReporter); + } } /** @@ -475,7 +481,7 @@ private static void addNetworkInputControlReaction( * zero-delay cycle inside the federate by adding an artificial dependency from the output port of this federate * that is involved in the cycle to the signature of {@code networkInputReaction} as a source. */ - private static void addRelativeDependency( + private static void addRelativeDependencyAnnotation( FedConnectionInstance connection, Reaction networkInputReaction, ErrorReporter errorReporter) { @@ -489,21 +495,25 @@ private static void addRelativeDependency( ModelInfo info = new ModelInfo(); for (var port: upstreamOutputPortsInFederate) { - VarRef sourceRef = ASTUtils.factory.createVarRef(); - - sourceRef.setContainer(port.getParent().getDefinition()); - sourceRef.setVariable(port.getDefinition()); - networkInputReaction.getSources().add(sourceRef); + //VarRef sourceRef = ASTUtils.factory.createVarRef(); + connection.dstFederate.networkReactionDependencyPairs.add( + new Pair(connection.getDestinationPortInstance(), port) + ); - // Remove the port if it introduces cycles - info.update( - (Model)networkInputReaction.eContainer().eContainer(), - errorReporter - ); - if (!info.topologyCycles().isEmpty()) { - networkInputReaction.getSources().remove(sourceRef); - } + //sourceRef.setContainer(port.getParent().getDefinition()); + //sourceRef.setVariable(port.getDefinition()); + // networkInputReaction.getSources().add(sourceRef); + + // // Remove the port if it introduces cycles + // info.update( + // (Model)networkInputReaction.eContainer().eContainer(), + // errorReporter + // ); + // if (!info.topologyCycles().isEmpty()) { + // networkInputReaction.getSources().remove(sourceRef); + // } } + //System.out.println(connection.dstFederate.networkReactionDependencyPairs); } @@ -741,17 +751,13 @@ public static List safe(List list) { public static int networkIDSender = 0; public static int networkIDReceiver = 0; - private static Map networkSenderReactors = new HashMap<>(); + private static Map networkSenderReactors = new HashMap<>(); private static Map networkSenderInstantiations = new HashMap<>(); private static Reactor getNetworkSenderReactor(FedConnectionInstance connection, CoordinationType coordination, Resource resource, ErrorReporter errorReporter) { LfFactory factory = LfFactory.eINSTANCE; - // Reactor classDef = networkSenderReactors.getOrDefault(connection.srcFederate, null); - // if (classDef != null) { - // return classDef; - // } Type type = EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getType()); //Initialize Reactor and Reaction AST Nodes @@ -769,6 +775,7 @@ private static Reactor getNetworkSenderReactor(FedConnectionInstance connection, EObject node = IteratorExtensions.findFirst(resource.getAllContents(), Model.class::isInstance); ((Model) node).getReactors().add(sender); sender.setName("NetworkSender_" + networkIDSender++); + //networkSenderReaction.setName("NetworkSenderReaction_" + networkIDSender++); // FIXME: do not create a new extension every time it is used FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) @@ -806,7 +813,7 @@ private static Reactor getNetworkSenderReactor(FedConnectionInstance connection, connection.srcFederate.networkSenderReactions.add(networkSenderReaction); connection.srcFederate.networkReactors.add(sender); - networkSenderReactors.put(connection.srcFederate, sender); + networkSenderReactors.put(connection, sender); return sender; } @@ -874,6 +881,7 @@ private static void addNetworkSenderReactor( connection.srcFederate.networkConnections.add(senderToReaction); connection.srcFederate.networkSenderInstantiations.add(networkInstance); + connection.srcFederate.networkPortToInstantiation.put(connection.getSourcePortInstance(), networkInstance); } /** @@ -887,15 +895,12 @@ private static void addNetworkSenderReactor( private static void addNetworkOutputControlReaction(FedConnectionInstance connection) { LfFactory factory = LfFactory.eINSTANCE; Reaction reaction = factory.createReaction(); - Reactor top = connection.getSourcePortInstance() - .getParent() - .getParent().reactorDefinition; // Top-level reactor. - + Reactor top = networkSenderReactors.getOrDefault(connection, null); + // Add the output from the contained reactor as a source to // the reaction to preserve precedence order. VarRef newPortRef = factory.createVarRef(); - newPortRef.setContainer(connection.getSourcePortInstance().getParent().getDefinition()); - newPortRef.setVariable(connection.getSourcePortInstance().getDefinition()); + newPortRef.setVariable(top.getInputs().get(0)); reaction.getSources().add(newPortRef); // If the sender or receiver is in a bank of reactors, then we want @@ -910,42 +915,44 @@ private static void addNetworkOutputControlReaction(FedConnectionInstance connec // trigger output control reactions. That action is created once // and recorded in the federate instance. // Check whether the action already has been created. - if (connection.srcFederate.networkOutputControlReactionsTrigger == null) { - // The port has not been created. - String triggerName = "outputControlReactionTrigger"; - - // Find the trigger definition in the reactor definition, which could have been - // generated for another federate instance if there are multiple instances - // of the same reactor that are each distinct federates. - Optional optTriggerInput - = top.getActions().stream().filter( - I -> I.getName().equals(triggerName)).findFirst(); - - if (optTriggerInput.isEmpty()) { + // if (connection.srcFederate.networkOutputControlReactionsTrigger == null) { + // // The port has not been created. + // String triggerName = "outputControlReactionTrigger"; + + // // Find the trigger definition in the reactor definition, which could have been + // // generated for another federate instance if there are multiple instances + // // of the same reactor that are each distinct federates. + // Optional optTriggerInput + // = top.getActions().stream().filter( + // I -> I.getName().equals(triggerName)).findFirst(); + + // if (optTriggerInput.isEmpty()) { // If no trigger with the name "outputControlReactionTrigger" is // already added to the reactor definition, we need to create it // for the first time. The trigger is a logical action. - Action newTriggerForControlReactionVariable = factory.createAction(); - newTriggerForControlReactionVariable.setName(triggerName); - newTriggerForControlReactionVariable.setOrigin(ActionOrigin.LOGICAL); - top.getActions().add(newTriggerForControlReactionVariable); - - // Now that the variable is created, store it in the federate instance - connection.srcFederate.networkOutputControlReactionsTrigger - = newTriggerForControlReactionVariable; - } else { + Action newTriggerForControlReactionVariable = factory.createAction(); + newTriggerForControlReactionVariable.setName("outputControlReactionTrigger"); + newTriggerForControlReactionVariable.setOrigin(ActionOrigin.LOGICAL); + top.getActions().add(newTriggerForControlReactionVariable); + + // // Now that the variable is created, store it in the federate instance + // connection.srcFederate.networkOutputControlReactionsTrigger + // = newTriggerForControlReactionVariable; + // } else { // If the "outputControlReactionTrigger" trigger is already // there, we can re-use it for this new reaction since a single trigger // will trigger - // all network output control reactions. - connection.srcFederate.networkOutputControlReactionsTrigger = optTriggerInput.get(); - } - } + // // all network output control reactions. + // connection.srcFederate.networkOutputControlReactionsTrigger = optTriggerInput.get(); + // } + //} // Add the trigger for all output control reactions to the list of triggers VarRef triggerRef = factory.createVarRef(); - triggerRef.setVariable(connection.srcFederate.networkOutputControlReactionsTrigger); + triggerRef.setVariable(newTriggerForControlReactionVariable); reaction.getTriggers().add(triggerRef); + //int val = networkIDSender-1; + //reaction.setName("NetworkSenderControlReaction_" + val); // Generate the code reaction.setCode(factory.createCode()); @@ -961,6 +968,12 @@ private static void addNetworkOutputControlReaction(FedConnectionInstance connec // Add the network output control reaction to the federate instance's list // of network reactions - //connection.srcFederate.networkReactions.add(reaction); + connection.srcFederate.networkSenderReactions.add(reaction); + connection.srcFederate.networkSenderControlReactions.add(reaction); + + //connection.srcFederate.networkPortToControlReaction.put(connection.getSourcePortInstance(), reaction); + //connection.srcFederate.networkOutputControlReactionsTriggers.add(newTriggerForControlReactionVariable); + + } } diff --git a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java index d44fcc5f62..f0197083bc 100644 --- a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java +++ b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java @@ -27,6 +27,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.io.File; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -69,10 +70,12 @@ 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.util.Pair; 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. @@ -227,7 +230,7 @@ public Instantiation getInstantiation() { public List networkInputControlReactionsTriggers = new ArrayList<>(); /** - * The trigger that triggers the output control reaction of this + * The triggers that trigger the output control reactions of this * federate. * * The network output control reactions send a PORT_ABSENT message for a network output port, @@ -235,7 +238,7 @@ public Instantiation getInstantiation() { * be present on the given network port, allowing input control reactions on those federates * to stop blocking. */ - public Variable networkOutputControlReactionsTrigger = null; + public List networkOutputControlReactionsTriggers = new ArrayList<>(); /** * Indicates whether the federate is remote or local @@ -252,6 +255,12 @@ public Instantiation getInstantiation() { */ public List networkSenderReactions = new ArrayList<>(); + /** + * List of generated network control reactions (network sender) that belong to this federate instance. + */ + public List networkSenderControlReactions = new ArrayList<>(); + + /** * List of generated network reactors (network input and outputs) that * belong to this federate instance. @@ -259,6 +268,18 @@ public Instantiation getInstantiation() { public List networkReactors = new ArrayList<>(); + /** + * List of relative dependencies between network input and output reactions belonging to + * the same federate that have zero logical delay between them. + */ + public List> networkReactionDependencyPairs = new ArrayList<>(); + + /** + * Mapping from a port instance of a connection to its associated network reaction. We populate + * this map as we process connections as a means of annotating intra-federate dependencies + */ + public Map networkPortToInstantiation = new HashMap<>(); + /** * List of generated network connections (network input and outputs) that * belong to this federate instance. @@ -271,6 +292,7 @@ public Instantiation getInstantiation() { * belong to this federate instance. */ public List networkSenderInstantiations = new ArrayList<>(); + /** * List of generated network instantiations (network input and outputs) that * belong to this federate instance. From 58d86a188d20462584eae764712dc3214ae2125d Mon Sep 17 00:00:00 2001 From: Anirudh Rengarajan <44007330+arengarajan99@users.noreply.github.com> Date: Sat, 6 May 2023 20:31:00 -0700 Subject: [PATCH 120/516] Fixed comment --- org.lflang/src/org/lflang/federated/generator/FedGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/federated/generator/FedGenerator.java b/org.lflang/src/org/lflang/federated/generator/FedGenerator.java index c0627a3b77..2142804f0e 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedGenerator.java +++ b/org.lflang/src/org/lflang/federated/generator/FedGenerator.java @@ -145,7 +145,7 @@ public boolean doGenerate(Resource resource, LFGeneratorContext context) throws // 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. + // AST with an action (which inherits the delay) and three reactions. // The action will be physical for physical connections and logical // for logical connections. replaceFederateConnectionsWithProxies(federation, resource); From 37f9641ff1482ed49a8679afd60a299ccf957030 Mon Sep 17 00:00:00 2001 From: Anirudh Rengarajan <44007330+arengarajan99@users.noreply.github.com> Date: Sat, 6 May 2023 22:45:59 -0700 Subject: [PATCH 121/516] Add STAA support in LFC --- .../federated/extensions/CExtension.java | 28 +++++++++++++ .../federated/extensions/CExtensionUtils.java | 40 +++++++++++++++++++ .../federated/generator/FedASTUtils.java | 21 +++++++++- .../federated/generator/FederateInstance.java | 18 +++++++++ 4 files changed, 106 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtension.java b/org.lflang/src/org/lflang/federated/extensions/CExtension.java index 29230663d7..3f84050ee9 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtension.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtension.java @@ -533,10 +533,20 @@ protected String makePreamble( size_t numSenderReactions = %1$s; """.formatted(numOfNetworkSenderControlReactions)); + + int numOfSTAAOffsets = federate.stpOffsets.size(); + code.pr(CExtensionUtils.surroundWithIfFederatedDecentralized(""" + staa_t* staa_lst[%1$s]; + size_t staa_lst_size = %1$s; + """.formatted(numOfSTAAOffsets))); + + code.pr(generateSerializationPreamble(federate, fileConfig)); code.pr(generateExecutablePreamble(federate, rtiConfig, errorReporter)); + code.pr(generateSTAAInitialization(federate, rtiConfig, errorReporter)); + code.pr(generateInitializeTriggers(federate, errorReporter)); code.pr(CExtensionUtils.generateFederateNeighborStructure(federate)); @@ -569,6 +579,7 @@ private String generateInitializeTriggers(FederateInstance federate, ErrorReport federatedReactor.setName(federate.name); var main = new ReactorInstance(federatedReactor, errorReporter, -1); code.pr(CExtensionUtils.initializeTriggersForNetworkActions(federate, main, errorReporter)); + code.pr("staa_initialization(); \\"); //code.pr(CExtensionUtils.initializeTriggersForControlReactions(federate, main, errorReporter)); //code.pr(CExtensionUtils.networkInputReactions(federate, main)); //code.pr(CExtensionUtils.portAbsentReaction(federate, main)); @@ -603,6 +614,23 @@ void _lf_executable_preamble() { """.formatted(code.toString().indent(4).stripTrailing()); } + /** + * Generate code for an executed preamble. + * + */ + private String generateSTAAInitialization(FederateInstance federate, RtiConfig rtiConfig, ErrorReporter errorReporter) { + CodeBuilder code = new CodeBuilder(); + code.pr(CExtensionUtils.surroundWithIfFederatedDecentralized(CExtensionUtils.stpStructs(federate, errorReporter))); + + //code.pr(CExtensionUtils.allocateTriggersForFederate(federate)); + + return """ + void staa_initialization() { + %s + } + """.formatted(code.toString().indent(4).stripTrailing()); + } + /** * Generate code to initialize the {@code federate}. * @param rtiConfig diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java b/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java index d05c563755..67442c0cd2 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.regex.Pattern; @@ -108,6 +109,45 @@ public static String initializeTriggersForNetworkActions(FederateInstance federa return code.getCode(); } + /** + * Generate C code that holds a sorted list of STP structs by time. + * + * For decentralized execution, on every logical timestep, a thread will iterate through + * each staa struct, wait for the designated offset time, and set the associated port status to absent + * if it isn't known. + * @param federate The federate. + * @param main The main reactor that contains the federate (used to lookup references). + * @return + */ + public static String stpStructs(FederateInstance federate, ErrorReporter errorReporter) { + CodeBuilder code = new CodeBuilder(); + Collections.sort(federate.stpOffsets, (d1, d2) -> { + return (int) (d1.time - d2.time); + }); + if (!federate.stpOffsets.isEmpty()) { + // Create a static array of trigger_t pointers. + // networkMessageActions is a list of Actions, but we + // need a list of trigger struct names for ActionInstances. + // There should be exactly one ActionInstance in the + // main reactor for each Action. + for (int i = 0; i < federate.stpOffsets.size(); ++i) { + // Find the corresponding ActionInstance. + List networkActions = federate.stpToNetworkActionMap.get(federate.stpOffsets.get(i)); + + code.pr("staa_lst[" + i + "] = (staa_t*) malloc(sizeof(staa_t));"); + code.pr("staa_lst[" + i + "]->STAA = " + CTypes.getInstance().getTargetTimeExpr(federate.stpOffsets.get(i)) + ";"); + code.pr("staa_lst[" + i + "]->numActions = " + networkActions.size() + ";"); + code.pr("staa_lst[" + i + "]->actions = (lf_action_base_t**) malloc(sizeof(lf_action_base_t*) * " + networkActions.size() + ");"); + var tableCount = 0; + for(Action action: networkActions){ + code.pr("staa_lst[" + i + "]->actions[" + (tableCount++) + "] = _lf_action_table[" + + federate.networkMessageActions.indexOf(action) + "];"); + } + } + } + return code.getCode(); + } + /** * Generate C code that initializes three critical structures that support * network control reactions: diff --git a/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java b/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java index 65fc968968..11d6b53350 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java +++ b/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java @@ -180,7 +180,6 @@ public static void makeCommunication( errorReporter ); - TimeValue maxSTP = findMaxSTP(connection, coordination); } public static int networkMessageActionID = 0; @@ -298,6 +297,25 @@ private static void addNetworkReceiverReactor( // Keep track of this action in the destination federate. connection.dstFederate.networkMessageActions.add(networkAction); + + TimeValue maxSTP = findMaxSTP(connection, coordination); + + if(!connection.dstFederate.currentSTPOffsets.contains(maxSTP.time)) { + connection.dstFederate.currentSTPOffsets.add(maxSTP.time); + connection.dstFederate.stpOffsets.add(maxSTP); + connection.dstFederate.stpToNetworkActionMap.put(maxSTP, new ArrayList<>()); + } else { + // TODO: Find more efficient way to reuse timevalues + for(var offset: connection.dstFederate.stpOffsets){ + if(maxSTP.time == offset.time) { + maxSTP = offset; + break; + } + } + } + + connection.dstFederate.stpToNetworkActionMap.get(maxSTP).add(networkAction); + // Add the action definition to the parent reactor. receiver.getActions().add(networkAction); @@ -368,6 +386,7 @@ private static void addNetworkReceiverReactor( connection.dstFederate.networkConnections.add(receiverFromReaction); connection.dstFederate.networkReceiverInstantiations.add(networkInstance); connection.dstFederate.networkPortToInstantiation.put(connection.getDestinationPortInstance(), networkInstance); + connection.dstFederate.networkActionToInstantiation.put(networkAction, networkInstance); //System.out.println(connection.getSourcePortInstance()); if ( diff --git a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java index f0197083bc..101ebf4766 100644 --- a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java +++ b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java @@ -309,6 +309,23 @@ public Instantiation getInstantiation() { */ public HashSet enabledSerializers = new HashSet<>(); + /** + * Keep a unique list of enabled serializers + */ + public List stpOffsets = new ArrayList<>(); + + public Set currentSTPOffsets = new HashSet<>(); + + /** + * Keep a map of STP values to a list of network actions + */ + public HashMap> stpToNetworkActionMap = new HashMap<>(); + + /** + * Keep a map of network actions to their associated instantiations + */ + public HashMap networkActionToInstantiation = new HashMap<>(); + /** * Return true if the specified EObject should be included in the code * generated for this federate. @@ -669,6 +686,7 @@ public String toString() { * An error reporter */ private final ErrorReporter errorReporter; + /** * Find the nearest (shortest) path to a physical action trigger from this From 214211a803005219ed890b78d005ea1e2c1857c6 Mon Sep 17 00:00:00 2001 From: Anirudh Rengarajan <44007330+arengarajan99@users.noreply.github.com> Date: Sun, 7 May 2023 19:01:23 -0700 Subject: [PATCH 122/516] Change ref 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 c25cd601c8..366c6d4fa3 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit c25cd601c817596cc24d75e365820d1bf5f3e32d +Subproject commit 366c6d4fa3067b7d15f2c846e190d29d7582bae9 From 99e73960dc45a5a96cb0319bd2bf7ff29aff8499 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 2 Jun 2023 12:57:08 -0700 Subject: [PATCH 123/516] Fix compilation errors from merge. --- .../src/org/lflang/federated/extensions/CExtension.java | 7 ++----- .../org/lflang/federated/extensions/CExtensionUtils.java | 2 +- .../src/org/lflang/federated/generator/FedMainEmitter.java | 6 ++++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtension.java b/org.lflang/src/org/lflang/federated/extensions/CExtension.java index f2b30caac3..a38b557ed5 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtension.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtension.java @@ -541,12 +541,9 @@ protected String makePreamble( staa_t* staa_lst[%1$s]; size_t staa_lst_size = %1$s; """.formatted(numOfSTAAOffsets))); - - - code.pr(generateSerializationPreamble(federate, fileConfig)); code.pr(generateExecutablePreamble(federate, rtiConfig, errorReporter)); - + code.pr(generateSTAAInitialization(federate, rtiConfig, errorReporter)); code.pr(generateInitializeTriggers(federate, errorReporter)); @@ -623,7 +620,7 @@ void _lf_executable_preamble() { private String generateSTAAInitialization(FederateInstance federate, RtiConfig rtiConfig, ErrorReporter errorReporter) { CodeBuilder code = new CodeBuilder(); code.pr(CExtensionUtils.surroundWithIfFederatedDecentralized(CExtensionUtils.stpStructs(federate, errorReporter))); - + //code.pr(CExtensionUtils.allocateTriggersForFederate(federate)); return """ diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java b/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java index fd01e850aa..b8f3e2cd11 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java @@ -9,7 +9,7 @@ import java.util.List; import java.util.regex.Pattern; -import org.lflang.ASTUtils; +import org.lflang.ast.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.InferredType; import org.lflang.TargetConfig.ClockSyncOptions; diff --git a/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java b/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java index 486b6548b3..d54d7fb068 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java +++ b/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java @@ -1,5 +1,7 @@ package org.lflang.federated.generator; +import java.util.ArrayList; +import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; @@ -78,7 +80,7 @@ private static String getDependencyList(FederateInstance federate, Pair vals = new ArrayList<>(); for (var pair: federate.networkReactionDependencyPairs){ vals.add(getDependencyList(federate, pair)); From 9c810600d83a62f833fe23e37a6b0699306f9f59 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 2 Jun 2023 13:10:35 -0700 Subject: [PATCH 124/516] Format all Java files. --- .../src/org/lflang/tests/Configurators.java | 107 +- .../src/org/lflang/tests/LFParsingTest.java | 227 +- .../src/org/lflang/tests/LFTest.java | 480 +- .../src/org/lflang/tests/LfParsingUtil.java | 129 +- .../org/lflang/tests/RunSingleTestMain.java | 77 +- .../src/org/lflang/tests/RuntimeTest.java | 391 +- .../src/org/lflang/tests/TestBase.java | 1193 ++--- .../src/org/lflang/tests/TestError.java | 44 +- .../src/org/lflang/tests/TestRegistry.java | 647 ++- .../src/org/lflang/tests/TestUtils.java | 242 +- .../lflang/tests/cli/CliToolTestFixture.java | 186 +- .../src/org/lflang/tests/cli/LfcCliTest.java | 439 +- .../src/org/lflang/tests/cli/LffCliTest.java | 167 +- .../tests/compiler/EquivalenceUnitTests.java | 97 +- .../tests/compiler/FormattingUnitTests.java | 89 +- .../tests/compiler/LetInferenceTests.java | 205 +- .../compiler/LinguaFrancaASTUtilsTest.java | 301 +- .../LinguaFrancaDependencyAnalysisTest.java | 206 +- .../compiler/LinguaFrancaParsingTest.java | 126 +- .../compiler/LinguaFrancaScopingTest.java | 302 +- .../compiler/LinguaFrancaValidationTest.java | 2665 ++++++----- .../tests/compiler/MixedRadixIntTest.java | 191 +- .../tests/compiler/PortInstanceTests.java | 398 +- .../org/lflang/tests/compiler/RangeTests.java | 169 +- .../lflang/tests/compiler/RoundTripTests.java | 74 +- .../tests/compiler/TargetConfigTests.java | 125 +- .../org/lflang/tests/lsp/ErrorInserter.java | 616 +-- .../src/org/lflang/tests/lsp/LspTests.java | 385 +- .../lflang/tests/lsp/MockLanguageClient.java | 79 +- .../lflang/tests/lsp/MockReportProgress.java | 43 +- .../lflang/tests/runtime/CArduinoTest.java | 28 +- .../org/lflang/tests/runtime/CCppTest.java | 64 +- .../lflang/tests/runtime/CSchedulerTest.java | 103 +- .../src/org/lflang/tests/runtime/CTest.java | 230 +- .../org/lflang/tests/runtime/CZephyrTest.java | 99 +- .../org/lflang/tests/runtime/CppRos2Test.java | 35 +- .../src/org/lflang/tests/runtime/CppTest.java | 138 +- .../org/lflang/tests/runtime/PythonTest.java | 213 +- .../org/lflang/tests/runtime/RustTest.java | 20 +- .../lflang/tests/runtime/TypeScriptTest.java | 110 +- .../serialization/SerializationTest.java | 59 +- .../org/lflang/tests/util/StringUtilTest.java | 38 +- org.lflang/src/org/lflang/AttributeUtils.java | 345 +- .../src/org/lflang/DefaultErrorReporter.java | 104 +- org.lflang/src/org/lflang/ErrorReporter.java | 311 +- org.lflang/src/org/lflang/FileConfig.java | 545 ++- org.lflang/src/org/lflang/InferredType.java | 349 +- .../lflang/LFResourceDescriptionStrategy.java | 118 +- .../src/org/lflang/LFResourceProvider.java | 30 +- .../src/org/lflang/LFRuntimeModule.java | 72 +- .../src/org/lflang/LFStandaloneSetup.java | 31 +- .../lflang/LFSyntaxErrorMessageProvider.java | 124 +- org.lflang/src/org/lflang/LocalStrings.java | 8 +- .../src/org/lflang/MainConflictChecker.java | 147 +- org.lflang/src/org/lflang/ModelInfo.java | 422 +- org.lflang/src/org/lflang/Target.java | 1131 +++-- org.lflang/src/org/lflang/TargetConfig.java | 716 ++- org.lflang/src/org/lflang/TargetProperty.java | 3332 +++++++------- org.lflang/src/org/lflang/TimeUnit.java | 127 +- org.lflang/src/org/lflang/TimeValue.java | 340 +- org.lflang/src/org/lflang/ast/ASTUtils.java | 3468 ++++++++------- .../src/org/lflang/ast/AstTransformation.java | 11 +- .../ast/DelayedConnectionTransformation.java | 690 +-- .../src/org/lflang/ast/FormattingUtils.java | 427 +- org.lflang/src/org/lflang/ast/IsEqual.java | 1201 +++-- org.lflang/src/org/lflang/ast/ToLf.java | 7 +- org.lflang/src/org/lflang/ast/ToText.java | 204 +- org.lflang/src/org/lflang/cli/CliBase.java | 582 ++- .../org/lflang/cli/LFStandaloneModule.java | 58 +- org.lflang/src/org/lflang/cli/Lfc.java | 515 +-- org.lflang/src/org/lflang/cli/Lff.java | 306 +- .../lflang/cli/StandaloneErrorReporter.java | 143 +- .../lflang/cli/StandaloneIssueAcceptor.java | 186 +- .../src/org/lflang/cli/VersionProvider.java | 30 +- .../lflang/diagram/lsp/LFLanguageServer.java | 36 +- .../lsp/LFLanguageServerExtension.java | 196 +- .../diagram/lsp/LanguageDiagramServer.java | 162 +- .../src/org/lflang/diagram/lsp/Progress.java | 156 +- .../AbstractSynthesisExtensions.java | 98 +- .../synthesis/LinguaFrancaSynthesis.java | 2773 ++++++------ .../ReactorParameterDisplayModes.java | 76 +- .../synthesis/SynthesisRegistration.java | 69 +- .../synthesis/action/AbstractAction.java | 79 +- .../action/CollapseAllReactorsAction.java | 88 +- .../action/ExpandAllReactorsAction.java | 84 +- .../synthesis/action/FilterCycleAction.java | 238 +- .../MemorizingExpandCollapseAction.java | 204 +- .../synthesis/action/ShowCycleAction.java | 131 +- .../postprocessor/ReactionPortAdjustment.java | 315 +- .../styles/LinguaFrancaShapeExtensions.java | 2091 +++++---- .../styles/LinguaFrancaStyleExtensions.java | 862 ++-- .../styles/ReactorFigureComponents.java | 170 +- .../synthesis/util/CycleVisualization.java | 243 +- .../InterfaceDependenciesVisualization.java | 340 +- .../synthesis/util/LayoutPostProcessing.java | 697 +-- .../diagram/synthesis/util/ModeDiagrams.java | 1206 ++--- .../synthesis/util/NamedInstanceUtil.java | 78 +- .../diagram/synthesis/util/ReactorIcons.java | 254 +- .../util/SynthesisErrorReporter.java | 138 +- .../synthesis/util/UtilityExtensions.java | 311 +- .../federated/extensions/CExtension.java | 1382 +++--- .../federated/extensions/CExtensionUtils.java | 1282 +++--- .../extensions/FedTargetExtension.java | 199 +- .../extensions/FedTargetExtensionFactory.java | 26 +- .../federated/extensions/PythonExtension.java | 254 +- .../federated/extensions/TSExtension.java | 277 +- .../federated/generator/FedASTUtils.java | 1723 ++++---- .../generator/FedConnectionInstance.java | 140 +- .../federated/generator/FedEmitter.java | 105 +- .../federated/generator/FedFileConfig.java | 196 +- .../federated/generator/FedGenerator.java | 1059 +++-- .../federated/generator/FedImportEmitter.java | 77 +- .../federated/generator/FedMainEmitter.java | 202 +- .../generator/FedPreambleEmitter.java | 62 +- .../generator/FedReactorEmitter.java | 20 +- .../federated/generator/FedTargetConfig.java | 104 +- .../federated/generator/FedTargetEmitter.java | 48 +- .../lflang/federated/generator/FedUtils.java | 46 +- .../federated/generator/FederateInstance.java | 1256 +++--- .../generator/LineAdjustingErrorReporter.java | 210 +- .../generator/SynchronizedErrorReporter.java | 144 +- .../federated/launcher/BuildConfig.java | 93 +- .../federated/launcher/CBuildConfig.java | 68 +- .../launcher/FedLauncherGenerator.java | 898 ++-- .../federated/launcher/PyBuildConfig.java | 37 +- .../lflang/federated/launcher/RtiConfig.java | 115 +- .../federated/launcher/TsBuildConfig.java | 64 +- .../FedNativePythonSerialization.java | 172 +- .../FedROS2CPPSerialization.java | 348 +- .../serialization/FedSerialization.java | 133 +- .../serialization/SupportedSerializers.java | 32 +- .../federated/validation/FedValidator.java | 95 +- .../org/lflang/formatting2/LFFormatter.java | 48 +- .../org/lflang/generator/ActionInstance.java | 150 +- .../src/org/lflang/generator/CodeBuilder.java | 1018 ++--- .../src/org/lflang/generator/CodeMap.java | 554 ++- .../lflang/generator/DeadlineInstance.java | 100 +- .../lflang/generator/DelayBodyGenerator.java | 93 +- .../lflang/generator/DiagnosticReporting.java | 89 +- .../generator/DockerComposeGenerator.java | 156 +- .../src/org/lflang/generator/DockerData.java | 55 +- .../org/lflang/generator/DockerGenerator.java | 82 +- .../generator/FedDockerComposeGenerator.java | 79 +- .../lflang/generator/GenerationException.java | 68 +- .../org/lflang/generator/GeneratorBase.java | 1091 +++-- .../generator/GeneratorCommandFactory.java | 260 +- .../org/lflang/generator/GeneratorResult.java | 188 +- .../org/lflang/generator/GeneratorUtils.java | 299 +- .../HumanReadableReportingStrategy.java | 282 +- .../lflang/generator/IntegratedBuilder.java | 248 +- .../generator/InvalidLfSourceException.java | 37 +- .../generator/InvalidSourceException.java | 18 +- .../src/org/lflang/generator/LFGenerator.java | 320 +- .../lflang/generator/LFGeneratorContext.java | 258 +- .../src/org/lflang/generator/LFResource.java | 65 +- .../LanguageServerErrorReporter.java | 349 +- .../lflang/generator/LfExpressionVisitor.java | 209 +- .../src/org/lflang/generator/MainContext.java | 290 +- .../org/lflang/generator/MixedRadixInt.java | 418 +- .../org/lflang/generator/ModeInstance.java | 365 +- .../org/lflang/generator/NamedInstance.java | 600 ++- .../lflang/generator/ParameterInstance.java | 193 +- .../org/lflang/generator/PortInstance.java | 801 ++-- .../src/org/lflang/generator/Position.java | 435 +- .../src/org/lflang/generator/Range.java | 223 +- .../lflang/generator/ReactionInstance.java | 991 ++--- .../generator/ReactionInstanceGraph.java | 820 ++-- .../org/lflang/generator/ReactorInstance.java | 2081 +++++---- .../org/lflang/generator/RuntimeRange.java | 839 ++-- .../src/org/lflang/generator/SendRange.java | 491 +-- .../src/org/lflang/generator/SubContext.java | 148 +- .../src/org/lflang/generator/TargetTypes.java | 457 +- .../org/lflang/generator/TimerInstance.java | 129 +- .../org/lflang/generator/TriggerInstance.java | 299 +- .../UnsupportedGeneratorFeatureException.java | 17 +- .../lflang/generator/ValidationStrategy.java | 67 +- .../src/org/lflang/generator/Validator.java | 329 +- .../lflang/generator/WatchdogInstance.java | 4 +- .../lflang/generator/c/CActionGenerator.java | 290 +- .../lflang/generator/c/CCmakeGenerator.java | 673 +-- .../src/org/lflang/generator/c/CCompiler.java | 729 ++- .../generator/c/CConstructorGenerator.java | 52 +- .../lflang/generator/c/CCoreFilesUtils.java | 34 +- .../generator/c/CDelayBodyGenerator.java | 97 +- .../lflang/generator/c/CDockerGenerator.java | 112 +- .../org/lflang/generator/c/CFileConfig.java | 33 +- .../org/lflang/generator/c/CGenerator.java | 3907 +++++++++-------- .../generator/c/CMainFunctionGenerator.java | 203 +- .../lflang/generator/c/CMethodGenerator.java | 280 +- .../generator/c/CMixedRadixGenerator.java | 24 +- .../lflang/generator/c/CModesGenerator.java | 386 +- .../generator/c/CParameterGenerator.java | 61 +- .../lflang/generator/c/CPortGenerator.java | 496 ++- .../generator/c/CPreambleGenerator.java | 137 +- .../generator/c/CReactionGenerator.java | 2326 +++++----- .../c/CReactorHeaderFileGenerator.java | 376 +- .../lflang/generator/c/CStateGenerator.java | 213 +- .../lflang/generator/c/CTimerGenerator.java | 111 +- .../lflang/generator/c/CTracingGenerator.java | 92 +- .../generator/c/CTriggerObjectsGenerator.java | 2005 +++++---- .../src/org/lflang/generator/c/CTypes.java | 286 +- .../src/org/lflang/generator/c/CUtil.java | 1510 ++++--- .../generator/c/CWatchdogGenerator.java | 98 +- .../c/InteractingContainedReactors.java | 229 +- .../generator/c/TypeParameterizedReactor.java | 111 +- .../lflang/generator/python/PyFileConfig.java | 38 +- .../org/lflang/generator/python/PyUtil.java | 262 +- .../python/PythonActionGenerator.java | 15 +- .../python/PythonDelayBodyGenerator.java | 126 +- .../python/PythonDockerGenerator.java | 41 +- .../generator/python/PythonGenerator.java | 1142 +++-- .../generator/python/PythonInfoGenerator.java | 90 +- .../python/PythonMainFunctionGenerator.java | 37 +- .../python/PythonMethodGenerator.java | 58 +- .../generator/python/PythonModeGenerator.java | 147 +- .../python/PythonParameterGenerator.java | 170 +- .../generator/python/PythonPortGenerator.java | 443 +- .../python/PythonPreambleGenerator.java | 71 +- .../python/PythonReactionGenerator.java | 1100 ++--- .../python/PythonReactorGenerator.java | 273 +- .../python/PythonStateGenerator.java | 50 +- .../lflang/generator/python/PythonTypes.java | 104 +- .../generator/python/PythonValidator.java | 647 +-- .../generator/rust/CargoDependencySpec.java | 16 +- .../generator/rust/RustTargetConfig.java | 142 +- .../generator/ts/TSDockerGenerator.java | 21 +- .../src/org/lflang/generator/ts/TSTypes.java | 118 +- .../src/org/lflang/graph/DirectedGraph.java | 556 ++- org.lflang/src/org/lflang/graph/Graph.java | 101 +- .../org/lflang/graph/InstantiationGraph.java | 251 +- .../src/org/lflang/graph/NodeAnnotation.java | 100 +- .../src/org/lflang/graph/NodeAnnotations.java | 52 +- .../src/org/lflang/graph/PrecedenceGraph.java | 413 +- .../src/org/lflang/graph/TopologyGraph.java | 238 +- .../src/org/lflang/ide/LFIdeModule.java | 8 +- org.lflang/src/org/lflang/ide/LFIdeSetup.java | 17 +- .../lflang/scoping/LFGlobalScopeProvider.java | 207 +- .../org/lflang/scoping/LFScopeProvider.java | 11 +- .../lflang/scoping/LFScopeProviderImpl.java | 425 +- .../src/org/lflang/util/ArduinoUtil.java | 224 +- org.lflang/src/org/lflang/util/Averager.java | 33 +- .../src/org/lflang/util/CollectionUtil.java | 307 +- org.lflang/src/org/lflang/util/FileUtil.java | 1744 ++++---- .../src/org/lflang/util/IteratorUtil.java | 96 +- org.lflang/src/org/lflang/util/LFCommand.java | 715 ++- org.lflang/src/org/lflang/util/Pair.java | 26 +- .../src/org/lflang/util/StringUtil.java | 234 +- .../util/TargetResourceNotFoundException.java | 13 +- .../org/lflang/validation/AttributeSpec.java | 346 +- .../lflang/validation/BaseLFValidator.java | 64 +- .../LFNamesAreUniqueValidationHelper.java | 33 +- .../org/lflang/validation/LFValidator.java | 3325 +++++++------- .../validation/ValidatorErrorReporter.java | 297 +- 253 files changed, 48052 insertions(+), 47458 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/Configurators.java b/org.lflang.tests/src/org/lflang/tests/Configurators.java index e3fe6c5508..24ee6f38f3 100644 --- a/org.lflang.tests/src/org/lflang/tests/Configurators.java +++ b/org.lflang.tests/src/org/lflang/tests/Configurators.java @@ -26,7 +26,6 @@ import org.lflang.TargetProperty; import org.lflang.TargetProperty.Platform; -import org.lflang.generator.LFGeneratorContext.BuildParm; import org.lflang.tests.TestRegistry.TestCategory; /** @@ -37,62 +36,60 @@ */ public class Configurators { - /** Test configuration function. */ - @FunctionalInterface - public interface Configurator { - - /** - * Apply a side effect to the given test case to change its default configuration. - * Return true if configuration succeeded, false otherwise. - */ - boolean configure(LFTest test); - } + /** Test configuration function. */ + @FunctionalInterface + public interface Configurator { /** - * Configure the given test to use single-threaded execution. - * - * For targets that provide a threaded and an unthreaded runtime, - * this configures using the unthreaded runtime. For targets that - * do not distinguish threaded and unthreaded runtime, the number - * of workers is set to 1. - * - * @param test The test to configure. - * @return True if successful, false otherwise. + * Apply a side effect to the given test case to change its default configuration. Return true + * if configuration succeeded, false otherwise. */ - public static boolean disableThreading(LFTest test) { - test.getContext().getArgs().setProperty("threading", "false"); - test.getContext().getArgs().setProperty("workers", "1"); - return true; - } + boolean configure(LFTest test); + } - public static boolean makeZephyrCompatible(LFTest test) { - test.getContext().getArgs().setProperty("tracing", "false"); - test.getContext().getTargetConfig().threading = false; - test.getContext().getTargetConfig().setByUser.add(TargetProperty.THREADING); - test.getContext().getTargetConfig().platformOptions.platform = Platform.ZEPHYR; - test.getContext().getTargetConfig().platformOptions.flash = false; - test.getContext().getTargetConfig().platformOptions.board = "qemu_cortex_a53"; - return true; - } - /** - * Make no changes to the configuration. - * - * @param ignoredTest The test to configure. - * @return True - */ - public static boolean noChanges(LFTest ignoredTest) { - return true; - } + /** + * Configure the given test to use single-threaded execution. + * + *

For targets that provide a threaded and an unthreaded runtime, this configures using the + * unthreaded runtime. For targets that do not distinguish threaded and unthreaded runtime, the + * number of workers is set to 1. + * + * @param test The test to configure. + * @return True if successful, false otherwise. + */ + public static boolean disableThreading(LFTest test) { + test.getContext().getArgs().setProperty("threading", "false"); + test.getContext().getArgs().setProperty("workers", "1"); + return true; + } - /** - * Given a test category, return true if it is compatible with single-threaded execution. - */ - public static boolean compatibleWithThreadingOff(TestCategory category) { + public static boolean makeZephyrCompatible(LFTest test) { + test.getContext().getArgs().setProperty("tracing", "false"); + test.getContext().getTargetConfig().threading = false; + test.getContext().getTargetConfig().setByUser.add(TargetProperty.THREADING); + test.getContext().getTargetConfig().platformOptions.platform = Platform.ZEPHYR; + test.getContext().getTargetConfig().platformOptions.flash = false; + test.getContext().getTargetConfig().platformOptions.board = "qemu_cortex_a53"; + return true; + } + /** + * Make no changes to the configuration. + * + * @param ignoredTest The test to configure. + * @return True + */ + public static boolean noChanges(LFTest ignoredTest) { + return true; + } + + /** Given a test category, return true if it is compatible with single-threaded execution. */ + public static boolean compatibleWithThreadingOff(TestCategory category) { - // CONCURRENT, FEDERATED, DOCKER_FEDERATED, DOCKER - // are not compatible with single-threaded execution. - // ARDUINO and ZEPHYR have their own test suites, so we don't need to rerun. - boolean excluded = category == TestCategory.CONCURRENT + // CONCURRENT, FEDERATED, DOCKER_FEDERATED, DOCKER + // are not compatible with single-threaded execution. + // ARDUINO and ZEPHYR have their own test suites, so we don't need to rerun. + boolean excluded = + category == TestCategory.CONCURRENT || category == TestCategory.SERIALIZATION || category == TestCategory.FEDERATED || category == TestCategory.DOCKER_FEDERATED @@ -100,8 +97,8 @@ public static boolean compatibleWithThreadingOff(TestCategory category) { || category == TestCategory.ARDUINO || category == TestCategory.ZEPHYR; - // SERIALIZATION and TARGET tests are excluded on Windows. - excluded |= TestBase.isWindows() && (category == TestCategory.TARGET); - return !excluded; - } + // SERIALIZATION and TARGET tests are excluded on Windows. + excluded |= TestBase.isWindows() && (category == TestCategory.TARGET); + return !excluded; + } } diff --git a/org.lflang.tests/src/org/lflang/tests/LFParsingTest.java b/org.lflang.tests/src/org/lflang/tests/LFParsingTest.java index 487c3b21e5..f0e96ee7d0 100644 --- a/org.lflang.tests/src/org/lflang/tests/LFParsingTest.java +++ b/org.lflang.tests/src/org/lflang/tests/LFParsingTest.java @@ -3,8 +3,8 @@ */ package org.lflang.tests; +import com.google.inject.Inject; import java.util.List; - import org.eclipse.emf.ecore.resource.Resource.Diagnostic; import org.eclipse.xtext.testing.InjectWith; import org.eclipse.xtext.testing.extensions.InjectionExtension; @@ -13,127 +13,118 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; - import org.lflang.lf.Model; -import com.google.inject.Inject; - @ExtendWith(InjectionExtension.class) @InjectWith(LFInjectorProvider.class) public class LFParsingTest { - @Inject - private ParseHelper parseHelper; - - - @Test - public void testLexingEmptyTargetProperties() throws Exception { - assertNoParsingErrorsIn("target C { }; \nreactor Foo {}"); - assertNoParsingErrorsIn("target C {a:b,}; \nreactor Foo {}"); - expectParsingErrorIn("target C {,}; \nreactor Foo {}"); - - // array elements - // assertNoParsingErrorsIn("target C {x:[ ]}; \nreactor Foo {}"); - // assertNoParsingErrorsIn("target C {x:[]}; \nreactor Foo {}"); - // assertNoParsingErrorsIn("target C {x:[,]}; \nreactor Foo {}"); - } - - @Test - public void testLexingLifetimeAnnots() throws Exception { - assertNoParsingErrorsIn(makeLfTargetCode("Rust", - " struct Hello<'a> { \n" - + " r: &'a str,\n" - + " r2: &'a Box>,\n" - + " }")); - } - - - @Test - public void testLexingSingleLifetimeAnnot() throws Exception { - // just to be sure, have a single lifetime annot. - assertNoParsingErrorsIn(makeLfTargetCode("Rust", - " struct Hello { \n" - + " r: &'static str,\n" - + " }")); - } - - - @Test - public void testLexingNewlineCont() throws Exception { - /* - This example looks like this: - "a\ - bcde" - - This is valid C++ to escape a newline. - */ - - assertNoParsingErrorsIn(makeLfTargetCode("Cpp", - " \"a\\\n" - + " bcde\"\n" - )); - } - - - @Test - public void testLexingSquotedString() throws Exception { - // we can't do that anymore - expectParsingErrorIn(makeLfTargetCode("Python", "a = ' a string '")); - } - - - @Test - public void testLexingDquotedString() throws Exception { - assertNoParsingErrorsIn(makeLfTargetCode("Python", "a = \" a string \"")); - } - - @Test - public void testLexingMultilineString() throws Exception { - assertNoParsingErrorsIn(makeLfTargetCode("Python", "a = \"\"\" a 'string' \"\"\"")); - assertNoParsingErrorsIn(makeLfTargetCode("Python", "a = \"\"\" a 'strin\ng' \"\"\"")); - assertNoParsingErrorsIn(makeLfTargetCode("Python", "a = \"\"\" \na 'string'\n \"\"\"")); - } - - @Test - public void testLexingDquotedStringWithEscape() throws Exception { - assertNoParsingErrorsIn(makeLfTargetCode("C", "printf(\"Hello World.\\n\");\n")); - } - - - @Test - public void testLexingCharLiteral() throws Exception { - assertNoParsingErrorsIn(makeLfTargetCode("C", "char c0 = 'c';")); - } - - @Test - public void testLexingEscapedCharLiteral() throws Exception { - assertNoParsingErrorsIn(makeLfTargetCode("C", "char c0 = '\\n';")); - } - - private String makeLfTargetCode(final String target, final String code) { - return "target " + target + ";\n" - + "reactor Foo {\n" - + " preamble {=\n" - + " " + code + "\n" - + " =}\n" - + "}"; - } - - private void assertNoParsingErrorsIn(String source) throws Exception { - List errors = doParse(source); - Assertions.assertTrue(errors.isEmpty(), "Unexpected errors: " + IterableExtensions.join(errors, ", ")); - } - - - private void expectParsingErrorIn(String source) throws Exception { - List errors = doParse(source); - Assertions.assertFalse(errors.isEmpty(), "Expected a parsing error, none occurred"); - } - - - private List doParse(String source) throws Exception { - Model result = parseHelper.parse(source); - Assertions.assertNotNull(result); - return result.eResource().getErrors(); - } + @Inject private ParseHelper parseHelper; + + @Test + public void testLexingEmptyTargetProperties() throws Exception { + assertNoParsingErrorsIn("target C { }; \nreactor Foo {}"); + assertNoParsingErrorsIn("target C {a:b,}; \nreactor Foo {}"); + expectParsingErrorIn("target C {,}; \nreactor Foo {}"); + + // array elements + // assertNoParsingErrorsIn("target C {x:[ ]}; \nreactor Foo {}"); + // assertNoParsingErrorsIn("target C {x:[]}; \nreactor Foo {}"); + // assertNoParsingErrorsIn("target C {x:[,]}; \nreactor Foo {}"); + } + + @Test + public void testLexingLifetimeAnnots() throws Exception { + assertNoParsingErrorsIn( + makeLfTargetCode( + "Rust", + " struct Hello<'a> { \n" + + " r: &'a str,\n" + + " r2: &'a Box>,\n" + + " }")); + } + + @Test + public void testLexingSingleLifetimeAnnot() throws Exception { + // just to be sure, have a single lifetime annot. + assertNoParsingErrorsIn( + makeLfTargetCode( + "Rust", " struct Hello { \n" + " r: &'static str,\n" + " }")); + } + + @Test + public void testLexingNewlineCont() throws Exception { + /* + This example looks like this: + "a\ + bcde" + + This is valid C++ to escape a newline. + */ + + assertNoParsingErrorsIn(makeLfTargetCode("Cpp", " \"a\\\n" + " bcde\"\n")); + } + + @Test + public void testLexingSquotedString() throws Exception { + // we can't do that anymore + expectParsingErrorIn(makeLfTargetCode("Python", "a = ' a string '")); + } + + @Test + public void testLexingDquotedString() throws Exception { + assertNoParsingErrorsIn(makeLfTargetCode("Python", "a = \" a string \"")); + } + + @Test + public void testLexingMultilineString() throws Exception { + assertNoParsingErrorsIn(makeLfTargetCode("Python", "a = \"\"\" a 'string' \"\"\"")); + assertNoParsingErrorsIn(makeLfTargetCode("Python", "a = \"\"\" a 'strin\ng' \"\"\"")); + assertNoParsingErrorsIn(makeLfTargetCode("Python", "a = \"\"\" \na 'string'\n \"\"\"")); + } + + @Test + public void testLexingDquotedStringWithEscape() throws Exception { + assertNoParsingErrorsIn(makeLfTargetCode("C", "printf(\"Hello World.\\n\");\n")); + } + + @Test + public void testLexingCharLiteral() throws Exception { + assertNoParsingErrorsIn(makeLfTargetCode("C", "char c0 = 'c';")); + } + + @Test + public void testLexingEscapedCharLiteral() throws Exception { + assertNoParsingErrorsIn(makeLfTargetCode("C", "char c0 = '\\n';")); + } + + private String makeLfTargetCode(final String target, final String code) { + return "target " + + target + + ";\n" + + "reactor Foo {\n" + + " preamble {=\n" + + " " + + code + + "\n" + + " =}\n" + + "}"; + } + + private void assertNoParsingErrorsIn(String source) throws Exception { + List errors = doParse(source); + Assertions.assertTrue( + errors.isEmpty(), "Unexpected errors: " + IterableExtensions.join(errors, ", ")); + } + + private void expectParsingErrorIn(String source) throws Exception { + List errors = doParse(source); + Assertions.assertFalse(errors.isEmpty(), "Expected a parsing error, none occurred"); + } + + private List doParse(String source) throws Exception { + Model result = parseHelper.parse(source); + Assertions.assertNotNull(result); + return result.eResource().getErrors(); + } } diff --git a/org.lflang.tests/src/org/lflang/tests/LFTest.java b/org.lflang.tests/src/org/lflang/tests/LFTest.java index cc69dea77a..58b3cbf846 100644 --- a/org.lflang.tests/src/org/lflang/tests/LFTest.java +++ b/org.lflang.tests/src/org/lflang/tests/LFTest.java @@ -8,286 +8,286 @@ import java.io.Reader; import java.nio.file.Path; import java.nio.file.Paths; - import org.eclipse.xtext.util.RuntimeIOException; - import org.lflang.FileConfig; import org.lflang.Target; import org.lflang.generator.LFGeneratorContext; /** * Information about an indexed Lingua Franca test program. - * - * @author Marten Lohstroh * + * @author Marten Lohstroh */ public class LFTest implements Comparable { - /** The path to the test. */ - private final Path srcPath; - - /** The name of the test. */ - private final String name; - - /** The result of the test. */ - private Result result = Result.UNKNOWN; - - /** Context provided to the code generators */ - private LFGeneratorContext context; - - /** Path of the test program relative to the package root. */ - private final Path relativePath; - - /** Records compilation stdout/stderr. */ - private final ByteArrayOutputStream compilationLog = new ByteArrayOutputStream(); - - /** Specialized object for capturing output streams while executing the test. */ - private final ExecutionLogger execLog = new ExecutionLogger(); - - /** String builder for collecting issues encountered during test execution. */ - private final StringBuilder issues = new StringBuilder(); - - /** The target of the test program. */ - private final Target target; - - /** - * Create a new test. - * - * @param target The target of the test program. - * @param srcFile The path to the file of the test program. - */ - public LFTest(Target target, Path srcFile) { - this.target = target; - this.srcPath = srcFile; - this.name = FileConfig.findPackageRoot(srcFile, s -> {}).relativize(srcFile).toString(); - this.relativePath = Paths.get(name); + /** The path to the test. */ + private final Path srcPath; + + /** The name of the test. */ + private final String name; + + /** The result of the test. */ + private Result result = Result.UNKNOWN; + + /** Context provided to the code generators */ + private LFGeneratorContext context; + + /** Path of the test program relative to the package root. */ + private final Path relativePath; + + /** Records compilation stdout/stderr. */ + private final ByteArrayOutputStream compilationLog = new ByteArrayOutputStream(); + + /** Specialized object for capturing output streams while executing the test. */ + private final ExecutionLogger execLog = new ExecutionLogger(); + + /** String builder for collecting issues encountered during test execution. */ + private final StringBuilder issues = new StringBuilder(); + + /** The target of the test program. */ + private final Target target; + + /** + * Create a new test. + * + * @param target The target of the test program. + * @param srcFile The path to the file of the test program. + */ + public LFTest(Target target, Path srcFile) { + this.target = target; + this.srcPath = srcFile; + this.name = FileConfig.findPackageRoot(srcFile, s -> {}).relativize(srcFile).toString(); + this.relativePath = Paths.get(name); + } + + /** Copy constructor */ + public LFTest(LFTest test) { + this(test.target, test.srcPath); + } + + /** Stream object for capturing standard and error output. */ + public OutputStream getOutputStream() { + return compilationLog; + } + + public FileConfig getFileConfig() { + return context.getFileConfig(); + } + + public LFGeneratorContext getContext() { + return context; + } + + public Path getSrcPath() { + return srcPath; + } + + /** + * Comparison implementation to allow for tests to be sorted (e.g., when added to a tree set) + * based on their path (relative to the root of the test directory). + */ + public int compareTo(LFTest t) { + return this.relativePath.compareTo(t.relativePath); + } + + /** + * Return true if the given object is an LFTest instance with a name identical to this test. + * + * @param o The object to test for equality with respect to this one. + * @return True if the given object is equal to this one, false otherwise. + */ + @Override + public boolean equals(Object o) { + return o instanceof LFTest && ((LFTest) o).name.equals(this.name); + } + + /** + * Return a string representing the name of this test. + * + * @return The name of this test. + */ + @Override + public String toString() { + return this.name; + } + + /** + * Identify tests uniquely on the basis of their name. + * + * @return The hash code of the name of this test. + */ + @Override + public int hashCode() { + return this.name.hashCode(); + } + + /** + * Report whether this test has failed. + * + * @return True if the test has failed, false otherwise. + */ + public boolean hasFailed() { + return result != Result.TEST_PASS; + } + + public boolean hasPassed() { + return result == Result.TEST_PASS; + } + + /** + * Compile a string that contains all collected errors and return it. + * + * @return A string that contains all collected errors. + */ + public void reportErrors() { + if (this.hasFailed()) { + System.out.println( + "+---------------------------------------------------------------------------+"); + System.out.println("Failed: " + this); + System.out.println( + "-----------------------------------------------------------------------------"); + System.out.println("Reason: " + this.result.message); + printIfNotEmpty("Reported issues", this.issues.toString()); + printIfNotEmpty("Compilation output", this.compilationLog.toString()); + printIfNotEmpty("Execution output", this.execLog.toString()); + System.out.println( + "+---------------------------------------------------------------------------+"); } + } - /** Copy constructor */ - public LFTest(LFTest test) { - this(test.target, test.srcPath); + public void handleTestError(TestError e) { + result = e.getResult(); + if (e.getMessage() != null) { + issues.append(e.getMessage()); } - - /** Stream object for capturing standard and error output. */ - public OutputStream getOutputStream() { - return compilationLog; + if (e.getException() != null) { + issues.append(System.lineSeparator()); + issues.append(TestBase.stackTraceToString(e.getException())); } - - public FileConfig getFileConfig() { return context.getFileConfig(); } - - public LFGeneratorContext getContext() { return context; } - - public Path getSrcPath() { return srcPath; } - - /** - * Comparison implementation to allow for tests to be sorted (e.g., when added to a - * tree set) based on their path (relative to the root of the test directory). - */ - public int compareTo(LFTest t) { - return this.relativePath.compareTo(t.relativePath); + } + + public void markPassed() { + result = Result.TEST_PASS; + // clear the execution log to free memory + execLog.clear(); + } + + void configure(LFGeneratorContext context) { + this.context = context; + } + + /** + * Print the message to the system output, but only if the message is not empty. + * + * @param header Header for the message to be printed. + * @param message The log message to print. + */ + private static void printIfNotEmpty(String header, String message) { + if (!message.isEmpty()) { + System.out.println(header + ":"); + System.out.println(message); } + } + + /** Enumeration of test outcomes. */ + public enum Result { + UNKNOWN("No information available."), + CONFIG_FAIL("Could not apply configuration."), + PARSE_FAIL("Unable to parse test."), + VALIDATE_FAIL("Unable to validate test."), + CODE_GEN_FAIL("Error while generating code for test."), + NO_EXEC_FAIL("Did not execute test."), + TEST_FAIL("Test did not pass."), + TEST_EXCEPTION("Test exited with an exception."), + TEST_TIMEOUT("Test timed out."), + TEST_PASS("Test passed."); + + /** Description of the outcome. */ + public final String message; /** - * Return true if the given object is an LFTest instance with a name identical to this test. - * @param o The object to test for equality with respect to this one. - * @return True if the given object is equal to this one, false otherwise. - */ - @Override - public boolean equals(Object o) { - return o instanceof LFTest && ((LFTest) o).name.equals(this.name); - } - - /** - * Return a string representing the name of this test. - * @return The name of this test. - */ - @Override - public String toString() { - return this.name; - } - - /** - * Identify tests uniquely on the basis of their name. + * Private constructor. * - * @return The hash code of the name of this test. + * @param message Description of the test outcome. */ - @Override - public int hashCode() { - return this.name.hashCode(); + Result(String message) { + this.message = message; } + } - /** - * Report whether this test has failed. - * @return True if the test has failed, false otherwise. - */ - public boolean hasFailed() { - return result != Result.TEST_PASS; - } - - public boolean hasPassed() { - return result == Result.TEST_PASS; - } + /** + * Inner class for capturing streams during execution of a test, capable of recording output + * streams up until the moment that a test is interrupted upon timing out. + * + * @author Marten Lohstroh + */ + public static final class ExecutionLogger { /** - * Compile a string that contains all collected errors and return it. - * @return A string that contains all collected errors. + * String buffer used to record the standard output and error streams from the input process. */ - public void reportErrors() { - if (this.hasFailed()) { - System.out.println("+---------------------------------------------------------------------------+"); - System.out.println("Failed: " + this); - System.out.println("-----------------------------------------------------------------------------"); - System.out.println("Reason: " + this.result.message); - printIfNotEmpty("Reported issues", this.issues.toString()); - printIfNotEmpty("Compilation output", this.compilationLog.toString()); - printIfNotEmpty("Execution output", this.execLog.toString()); - System.out.println("+---------------------------------------------------------------------------+"); - } - } - - public void handleTestError(TestError e) { - result = e.getResult(); - if (e.getMessage() != null) { - issues.append(e.getMessage()); - } - if (e.getException() != null) { - issues.append(System.lineSeparator()); - issues.append(TestBase.stackTraceToString(e.getException())); - } - } - - public void markPassed() { - result = Result.TEST_PASS; - // clear the execution log to free memory - execLog.clear(); - } - - void configure(LFGeneratorContext context) { - this.context = context; - } + StringBuffer buffer = new StringBuffer(); /** - * Print the message to the system output, but only if the message is not empty. - * - * @param header Header for the message to be printed. - * @param message The log message to print. + * Return a thread responsible for recording the standard output stream of the given process. A + * separate thread is used so that the activity can be preempted. */ - private static void printIfNotEmpty(String header, String message) { - if (!message.isEmpty()) { - System.out.println(header + ":"); - System.out.println(message); - } + public Thread recordStdOut(Process process) { + return recordStream(buffer, process.getInputStream()); } /** - * Enumeration of test outcomes. + * Return a thread responsible for recording the error stream of the given process. A separate + * thread is used so that the activity can be preempted. */ - public enum Result { - UNKNOWN("No information available."), - CONFIG_FAIL("Could not apply configuration."), - PARSE_FAIL("Unable to parse test."), - VALIDATE_FAIL("Unable to validate test."), - CODE_GEN_FAIL("Error while generating code for test."), - NO_EXEC_FAIL("Did not execute test."), - TEST_FAIL("Test did not pass."), - TEST_EXCEPTION("Test exited with an exception."), - TEST_TIMEOUT("Test timed out."), - TEST_PASS("Test passed."); - - /** - * Description of the outcome. - */ - public final String message; - - /** - * Private constructor. - * @param message Description of the test outcome. - */ - Result(String message) { - this.message = message; - } + public Thread recordStdErr(Process process) { + return recordStream(buffer, process.getErrorStream()); } - /** - * Inner class for capturing streams during execution of a test, capable of - * recording output streams up until the moment that a test is interrupted - * upon timing out. - * - * @author Marten Lohstroh + * Return a thread responsible for recording the given stream. * + * @param builder The builder to append to. + * @param inputStream The stream to read from. */ - public static final class ExecutionLogger { - - /** - * String buffer used to record the standard output and error - * streams from the input process. - */ - StringBuffer buffer = new StringBuffer(); - - /** - * Return a thread responsible for recording the standard output stream - * of the given process. - * A separate thread is used so that the activity can be preempted. - */ - public Thread recordStdOut(Process process) { - return recordStream(buffer, process.getInputStream()); - } - - /** - * Return a thread responsible for recording the error stream of the - * given process. - * A separate thread is used so that the activity can be preempted. - */ - public Thread recordStdErr(Process process) { - return recordStream(buffer, process.getErrorStream()); - } - - /** - * Return a thread responsible for recording the given stream. - * - * @param builder The builder to append to. - * @param inputStream The stream to read from. - */ - private Thread recordStream(StringBuffer builder, InputStream inputStream) { - return new Thread(() -> { - try (Reader reader = new InputStreamReader(inputStream)) { - int len; - char[] buf = new char[1024]; - while ((len = reader.read(buf)) > 0) { - builder.append(buf, 0, len); - } - } catch (IOException e) { - throw new RuntimeIOException(e); - } - - }); - } - - @Override - public String toString() { - return buffer.toString(); - } - - public void clear() { - buffer = null; - } + private Thread recordStream(StringBuffer builder, InputStream inputStream) { + return new Thread( + () -> { + try (Reader reader = new InputStreamReader(inputStream)) { + int len; + char[] buf = new char[1024]; + while ((len = reader.read(buf)) > 0) { + builder.append(buf, 0, len); + } + } catch (IOException e) { + throw new RuntimeIOException(e); + } + }); } - - /** - * Return a thread responsible for recording the standard output stream of the given process. - * A separate thread is used so that the activity can be preempted. - */ - public Thread recordStdOut(Process process) { - return execLog.recordStdOut(process); + @Override + public String toString() { + return buffer.toString(); } - /** - * Return a thread responsible for recording the error stream of the given process. - * A separate thread is used so that the activity can be preempted. - */ - public Thread recordStdErr(Process process) { - return execLog.recordStdErr(process); + public void clear() { + buffer = null; } + } + + /** + * Return a thread responsible for recording the standard output stream of the given process. A + * separate thread is used so that the activity can be preempted. + */ + public Thread recordStdOut(Process process) { + return execLog.recordStdOut(process); + } + + /** + * Return a thread responsible for recording the error stream of the given process. A separate + * thread is used so that the activity can be preempted. + */ + public Thread recordStdErr(Process process) { + return execLog.recordStdErr(process); + } } diff --git a/org.lflang.tests/src/org/lflang/tests/LfParsingUtil.java b/org.lflang.tests/src/org/lflang/tests/LfParsingUtil.java index fd9ffa2a2e..ae45bf4e7c 100644 --- a/org.lflang.tests/src/org/lflang/tests/LfParsingUtil.java +++ b/org.lflang.tests/src/org/lflang/tests/LfParsingUtil.java @@ -1,100 +1,87 @@ package org.lflang.tests; +import com.google.inject.Injector; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; - import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.resource.XtextResourceSet; import org.junit.jupiter.api.Assertions; - import org.lflang.LFStandaloneSetup; import org.lflang.lf.Model; -import com.google.inject.Injector; - /** * @author Clément Fournier */ public class LfParsingUtil { - /** - * Parse the given file, asserts that there are no parsing errors. - */ - public static Model parseValidModel( - String fileName, - String reformattedTestCase - ) { - Model resultingModel = parse(reformattedTestCase); - checkValid(fileName, resultingModel); - return resultingModel; - } + /** Parse the given file, asserts that there are no parsing errors. */ + public static Model parseValidModel(String fileName, String reformattedTestCase) { + Model resultingModel = parse(reformattedTestCase); + checkValid(fileName, resultingModel); + return resultingModel; + } - private static void checkValid(String fileName, Model resultingModel) { - Assertions.assertNotNull(resultingModel); - if (!resultingModel.eResource().getErrors().isEmpty()) { - resultingModel.eResource().getErrors().forEach(System.err::println); - Assertions.assertTrue(resultingModel.eResource().getErrors().isEmpty(), - "Parsing errors in " + fileName); - } + private static void checkValid(String fileName, Model resultingModel) { + Assertions.assertNotNull(resultingModel); + if (!resultingModel.eResource().getErrors().isEmpty()) { + resultingModel.eResource().getErrors().forEach(System.err::println); + Assertions.assertTrue( + resultingModel.eResource().getErrors().isEmpty(), "Parsing errors in " + fileName); } + } - public static Model parseSourceAsIfInDirectory( - Path directory, - String sourceText - ) { - int num = 0; - while (Files.exists(directory.resolve("file" + num + ".lf"))) { - num++; - } - Path file = directory.resolve("file" + num + ".lf"); - try { - Files.writeString(file, sourceText); - Model resultingModel = parse(file); - checkValid("file in " + directory, resultingModel); - return resultingModel; - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - try { - Files.deleteIfExists(file); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - + public static Model parseSourceAsIfInDirectory(Path directory, String sourceText) { + int num = 0; + while (Files.exists(directory.resolve("file" + num + ".lf"))) { + num++; + } + Path file = directory.resolve("file" + num + ".lf"); + try { + Files.writeString(file, sourceText); + Model resultingModel = parse(file); + checkValid("file in " + directory, resultingModel); + return resultingModel; + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + try { + Files.deleteIfExists(file); + } catch (IOException e) { + throw new RuntimeException(e); + } } + } - public static Model parse(String fileContents) { - Path file = null; + public static Model parse(String fileContents) { + Path file = null; + try { + file = Files.createTempFile("lftests", ".lf"); + Files.writeString(file, fileContents); + return parse(file); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if (file != null) { try { - file = Files.createTempFile("lftests", ".lf"); - Files.writeString(file, fileContents); - return parse(file); + Files.deleteIfExists(file); } catch (IOException e) { - throw new RuntimeException(e); - } finally { - if (file != null) { - try { - Files.deleteIfExists(file); - } catch (IOException e) { - throw new RuntimeException(e); - } - } + throw new RuntimeException(e); } + } } + } - public static Model parse(Path file) { - // Source: https://wiki.eclipse.org/Xtext/FAQ#How_do_I_load_my_model_in_a_standalone_Java_application_.3F - Injector injector = new LFStandaloneSetup().createInjectorAndDoEMFRegistration(); - XtextResourceSet resourceSet = injector.getInstance(XtextResourceSet.class); - resourceSet.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.TRUE); - Resource resource = resourceSet.getResource( - URI.createFileURI(file.toFile().getAbsolutePath()), - true - ); - return (Model) resource.getContents().get(0); - } + public static Model parse(Path file) { + // Source: + // https://wiki.eclipse.org/Xtext/FAQ#How_do_I_load_my_model_in_a_standalone_Java_application_.3F + Injector injector = new LFStandaloneSetup().createInjectorAndDoEMFRegistration(); + XtextResourceSet resourceSet = injector.getInstance(XtextResourceSet.class); + resourceSet.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.TRUE); + Resource resource = + resourceSet.getResource(URI.createFileURI(file.toFile().getAbsolutePath()), true); + return (Model) resource.getContents().get(0); + } } diff --git a/org.lflang.tests/src/org/lflang/tests/RunSingleTestMain.java b/org.lflang.tests/src/org/lflang/tests/RunSingleTestMain.java index 0f7e919174..fdb26de7f5 100644 --- a/org.lflang.tests/src/org/lflang/tests/RunSingleTestMain.java +++ b/org.lflang.tests/src/org/lflang/tests/RunSingleTestMain.java @@ -29,7 +29,6 @@ import java.nio.file.Paths; import java.util.regex.Matcher; import java.util.regex.Pattern; - import org.lflang.Target; import org.lflang.tests.runtime.CCppTest; import org.lflang.tests.runtime.CTest; @@ -39,55 +38,55 @@ import org.lflang.tests.runtime.TypeScriptTest; /** - * Execute a single test case. Use it with the gradle task - * {@code gradle runSingleTest --args test/Python/src/Minimal.lf} + * Execute a single test case. Use it with the gradle task {@code gradle runSingleTest --args + * test/Python/src/Minimal.lf} * * @author Clément Fournier */ public class RunSingleTestMain { + private static final Pattern TEST_FILE_PATTERN = + Pattern.compile("(test/(\\w+))/src/([^/]++/)*(\\w+.lf)"); - private static final Pattern TEST_FILE_PATTERN = Pattern.compile("(test/(\\w+))/src/([^/]++/)*(\\w+.lf)"); - - public static void main(String[] args) throws FileNotFoundException { - if (args.length != 1) { - throw new IllegalArgumentException("Expected 1 path to an LF file"); - } - var path = Paths.get(args[0]); - if (!Files.exists(path)) { - throw new FileNotFoundException("No such test file: " + path); - } + public static void main(String[] args) throws FileNotFoundException { + if (args.length != 1) { + throw new IllegalArgumentException("Expected 1 path to an LF file"); + } + var path = Paths.get(args[0]); + if (!Files.exists(path)) { + throw new FileNotFoundException("No such test file: " + path); + } - Matcher matcher = TEST_FILE_PATTERN.matcher(args[0]); - if (!matcher.matches()) { - throw new FileNotFoundException("Not a test: " + path); - } + Matcher matcher = TEST_FILE_PATTERN.matcher(args[0]); + if (!matcher.matches()) { + throw new FileNotFoundException("Not a test: " + path); + } - Target target = Target.forName(matcher.group(2)).get(); + Target target = Target.forName(matcher.group(2)).get(); - Class testClass = getTestInstance(target); + Class testClass = getTestInstance(target); - LFTest testCase = new LFTest(target, path.toAbsolutePath()); + LFTest testCase = new LFTest(target, path.toAbsolutePath()); - TestBase.runSingleTestAndPrintResults(testCase, testClass, TestBase.pathToLevel(path)); - } + TestBase.runSingleTestAndPrintResults(testCase, testClass, TestBase.pathToLevel(path)); + } - private static Class getTestInstance(Target target) { - switch (target) { - case C: - return CTest.class; - case CCPP: - return CCppTest.class; - case CPP: - return CppTest.class; - case TS: - return TypeScriptTest.class; - case Python: - return PythonTest.class; - case Rust: - return RustTest.class; - default: - throw new IllegalArgumentException(); - } + private static Class getTestInstance(Target target) { + switch (target) { + case C: + return CTest.class; + case CCPP: + return CCppTest.class; + case CPP: + return CppTest.class; + case TS: + return TypeScriptTest.class; + case Python: + return PythonTest.class; + case Rust: + return RustTest.class; + default: + throw new IllegalArgumentException(); } + } } diff --git a/org.lflang.tests/src/org/lflang/tests/RuntimeTest.java b/org.lflang.tests/src/org/lflang/tests/RuntimeTest.java index 77bc6d8034..2dfd42d38b 100644 --- a/org.lflang.tests/src/org/lflang/tests/RuntimeTest.java +++ b/org.lflang.tests/src/org/lflang/tests/RuntimeTest.java @@ -2,207 +2,216 @@ import java.util.EnumSet; import java.util.List; - import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; -import org.lflang.ast.ASTUtils; import org.lflang.Target; +import org.lflang.ast.ASTUtils; import org.lflang.tests.TestRegistry.TestCategory; /** * A collection of JUnit tests to perform on a given set of targets. * * @author Marten Lohstroh - * */ public abstract class RuntimeTest extends TestBase { - /** - * Construct a test instance that runs tests for a single target. - * - * @param target The target to run tests for. - */ - protected RuntimeTest(Target target) { - super(target); - } - - /** - * Construct a test instance that runs tests for a list of targets. - * @param targets The targets to run tests for. - */ - protected RuntimeTest(List targets) { - super(targets); - } - - /** - * Whether to enable {@link #runEnclaveTests()}. - */ - protected boolean supportsEnclaves() { return false; } - - /** - * Whether to enable {@link #runFederatedTests()}. - */ - protected boolean supportsFederatedExecution() { - return false; - } - - /** - * Whether to enable {@link #runTypeParameterTests()}. - */ - protected boolean supportsGenericTypes() { - return false; - } - - /** - * Whether to enable {@link #runDockerTests()} and {@link #runDockerFederatedTests()}. - */ - protected boolean supportsDockerOption() { - return false; - } - - @Test - public void runGenericTests() { - runTestsForTargets(Message.DESC_GENERIC, - TestCategory.GENERIC::equals, Configurators::noChanges, - TestLevel.EXECUTION, - false); - } - - @Test - public void runTargetSpecificTests() { - runTestsForTargets(Message.DESC_TARGET_SPECIFIC, - TestCategory.TARGET::equals, Configurators::noChanges, - TestLevel.EXECUTION, - false); - } - - @Test - public void runMultiportTests() { - runTestsForTargets(Message.DESC_MULTIPORT, - TestCategory.MULTIPORT::equals, Configurators::noChanges, - TestLevel.EXECUTION, - false); - } - - @Test - public void runTypeParameterTests() { - Assumptions.assumeTrue(supportsGenericTypes(), Message.NO_GENERICS_SUPPORT); - runTestsForTargets(Message.DESC_TYPE_PARMS, - TestCategory.GENERICS::equals, Configurators::noChanges, - TestLevel.EXECUTION, - false); - } - - @Test - public void runAsFederated() { - Assumptions.assumeTrue(supportsFederatedExecution(), Message.NO_FEDERATION_SUPPORT); - - EnumSet categories = EnumSet.allOf(TestCategory.class); - categories.removeAll(EnumSet.of(TestCategory.CONCURRENT, - TestCategory.FEDERATED, - // FIXME: also run the multiport tests once these are supported. - TestCategory.MULTIPORT)); - - runTestsFor(List.of(Target.C), - Message.DESC_AS_FEDERATED, - categories::contains, - it -> ASTUtils.makeFederated(it.getFileConfig().resource), - TestLevel.EXECUTION, - true); - } - - @Test - public void runConcurrentTests() { - runTestsForTargets(Message.DESC_CONCURRENT, - TestCategory.CONCURRENT::equals, Configurators::noChanges, - TestLevel.EXECUTION, - false); - - } - - @Test - public void runFederatedTests() { - Assumptions.assumeTrue(supportsFederatedExecution(), Message.NO_FEDERATION_SUPPORT); - runTestsForTargets(Message.DESC_FEDERATED, - TestCategory.FEDERATED::equals, Configurators::noChanges, - TestLevel.EXECUTION, - false); - } - - /** - * Run the tests for modal reactors. - */ - @Test - public void runModalTests() { - runTestsForTargets(Message.DESC_MODAL, - TestCategory.MODAL_MODELS::equals, Configurators::noChanges, - TestLevel.EXECUTION, - false); - } - - /** - * Run the tests for non-inlined reaction bodies. - */ - @Test - public void runNoInliningTests() { - runTestsForTargets(Message.DESC_MODAL, - TestCategory.NO_INLINING::equals, Configurators::noChanges, - TestLevel.EXECUTION, - false); - } - - /** - * Run docker tests, provided that the platform is Linux and the target supports Docker. - * Skip if platform is not Linux or target does not support Docker. - */ - @Test - public void runDockerTests() { - Assumptions.assumeTrue(isLinux(), Message.NO_DOCKER_TEST_SUPPORT); - Assumptions.assumeTrue(supportsDockerOption(), Message.NO_DOCKER_SUPPORT); - runTestsForTargets(Message.DESC_DOCKER, - TestCategory.DOCKER::equals, Configurators::noChanges, - TestLevel.EXECUTION, - false); - } - - /** - * Run federated docker tests, provided that the platform is Linux, the target supports Docker, - * and the target supports federated execution. If any of these requirements are not met, skip - * the tests. - */ - @Test - public void runDockerFederatedTests() { - Assumptions.assumeTrue(isLinux(), Message.NO_DOCKER_TEST_SUPPORT); - Assumptions.assumeTrue(supportsDockerOption(), Message.NO_DOCKER_SUPPORT); - Assumptions.assumeTrue(supportsFederatedExecution(), Message.NO_FEDERATION_SUPPORT); - runTestsForTargets(Message.DESC_DOCKER_FEDERATED, - TestCategory.DOCKER_FEDERATED::equals, Configurators::noChanges, - TestLevel.EXECUTION, - false); - } - - @Test - public void runWithThreadingOff() { - Assumptions.assumeTrue(supportsSingleThreadedExecution(), Message.NO_SINGLE_THREADED_SUPPORT); - this.runTestsForTargets( - Message.DESC_SINGLE_THREADED, - Configurators::compatibleWithThreadingOff, - Configurators::disableThreading, - TestLevel.EXECUTION, - true - ); - } - - /** - * Run enclave tests if the target supports enclaves. - */ - @Test - public void runEnclaveTests() { - Assumptions.assumeTrue(supportsEnclaves(), Message.NO_ENCLAVE_SUPPORT); - runTestsForTargets(Message.DESC_ENCLAVE, - TestCategory.ENCLAVE::equals, Configurators::noChanges, - TestLevel.EXECUTION, - false); - } - + /** + * Construct a test instance that runs tests for a single target. + * + * @param target The target to run tests for. + */ + protected RuntimeTest(Target target) { + super(target); + } + + /** + * Construct a test instance that runs tests for a list of targets. + * + * @param targets The targets to run tests for. + */ + protected RuntimeTest(List targets) { + super(targets); + } + + /** Whether to enable {@link #runEnclaveTests()}. */ + protected boolean supportsEnclaves() { + return false; + } + + /** Whether to enable {@link #runFederatedTests()}. */ + protected boolean supportsFederatedExecution() { + return false; + } + + /** Whether to enable {@link #runTypeParameterTests()}. */ + protected boolean supportsGenericTypes() { + return false; + } + + /** Whether to enable {@link #runDockerTests()} and {@link #runDockerFederatedTests()}. */ + protected boolean supportsDockerOption() { + return false; + } + + @Test + public void runGenericTests() { + runTestsForTargets( + Message.DESC_GENERIC, + TestCategory.GENERIC::equals, + Configurators::noChanges, + TestLevel.EXECUTION, + false); + } + + @Test + public void runTargetSpecificTests() { + runTestsForTargets( + Message.DESC_TARGET_SPECIFIC, + TestCategory.TARGET::equals, + Configurators::noChanges, + TestLevel.EXECUTION, + false); + } + + @Test + public void runMultiportTests() { + runTestsForTargets( + Message.DESC_MULTIPORT, + TestCategory.MULTIPORT::equals, + Configurators::noChanges, + TestLevel.EXECUTION, + false); + } + + @Test + public void runTypeParameterTests() { + Assumptions.assumeTrue(supportsGenericTypes(), Message.NO_GENERICS_SUPPORT); + runTestsForTargets( + Message.DESC_TYPE_PARMS, + TestCategory.GENERICS::equals, + Configurators::noChanges, + TestLevel.EXECUTION, + false); + } + + @Test + public void runAsFederated() { + Assumptions.assumeTrue(supportsFederatedExecution(), Message.NO_FEDERATION_SUPPORT); + + EnumSet categories = EnumSet.allOf(TestCategory.class); + categories.removeAll( + EnumSet.of( + TestCategory.CONCURRENT, + TestCategory.FEDERATED, + // FIXME: also run the multiport tests once these are supported. + TestCategory.MULTIPORT)); + + runTestsFor( + List.of(Target.C), + Message.DESC_AS_FEDERATED, + categories::contains, + it -> ASTUtils.makeFederated(it.getFileConfig().resource), + TestLevel.EXECUTION, + true); + } + + @Test + public void runConcurrentTests() { + runTestsForTargets( + Message.DESC_CONCURRENT, + TestCategory.CONCURRENT::equals, + Configurators::noChanges, + TestLevel.EXECUTION, + false); + } + + @Test + public void runFederatedTests() { + Assumptions.assumeTrue(supportsFederatedExecution(), Message.NO_FEDERATION_SUPPORT); + runTestsForTargets( + Message.DESC_FEDERATED, + TestCategory.FEDERATED::equals, + Configurators::noChanges, + TestLevel.EXECUTION, + false); + } + + /** Run the tests for modal reactors. */ + @Test + public void runModalTests() { + runTestsForTargets( + Message.DESC_MODAL, + TestCategory.MODAL_MODELS::equals, + Configurators::noChanges, + TestLevel.EXECUTION, + false); + } + + /** Run the tests for non-inlined reaction bodies. */ + @Test + public void runNoInliningTests() { + runTestsForTargets( + Message.DESC_MODAL, + TestCategory.NO_INLINING::equals, + Configurators::noChanges, + TestLevel.EXECUTION, + false); + } + + /** + * Run docker tests, provided that the platform is Linux and the target supports Docker. Skip if + * platform is not Linux or target does not support Docker. + */ + @Test + public void runDockerTests() { + Assumptions.assumeTrue(isLinux(), Message.NO_DOCKER_TEST_SUPPORT); + Assumptions.assumeTrue(supportsDockerOption(), Message.NO_DOCKER_SUPPORT); + runTestsForTargets( + Message.DESC_DOCKER, + TestCategory.DOCKER::equals, + Configurators::noChanges, + TestLevel.EXECUTION, + false); + } + + /** + * Run federated docker tests, provided that the platform is Linux, the target supports Docker, + * and the target supports federated execution. If any of these requirements are not met, skip the + * tests. + */ + @Test + public void runDockerFederatedTests() { + Assumptions.assumeTrue(isLinux(), Message.NO_DOCKER_TEST_SUPPORT); + Assumptions.assumeTrue(supportsDockerOption(), Message.NO_DOCKER_SUPPORT); + Assumptions.assumeTrue(supportsFederatedExecution(), Message.NO_FEDERATION_SUPPORT); + runTestsForTargets( + Message.DESC_DOCKER_FEDERATED, + TestCategory.DOCKER_FEDERATED::equals, + Configurators::noChanges, + TestLevel.EXECUTION, + false); + } + + @Test + public void runWithThreadingOff() { + Assumptions.assumeTrue(supportsSingleThreadedExecution(), Message.NO_SINGLE_THREADED_SUPPORT); + this.runTestsForTargets( + Message.DESC_SINGLE_THREADED, + Configurators::compatibleWithThreadingOff, + Configurators::disableThreading, + TestLevel.EXECUTION, + true); + } + + /** Run enclave tests if the target supports enclaves. */ + @Test + public void runEnclaveTests() { + Assumptions.assumeTrue(supportsEnclaves(), Message.NO_ENCLAVE_SUPPORT); + runTestsForTargets( + Message.DESC_ENCLAVE, + TestCategory.ENCLAVE::equals, + Configurators::noChanges, + TestLevel.EXECUTION, + false); + } } diff --git a/org.lflang.tests/src/org/lflang/tests/TestBase.java b/org.lflang.tests/src/org/lflang/tests/TestBase.java index 192a531828..9ae075780a 100644 --- a/org.lflang.tests/src/org/lflang/tests/TestBase.java +++ b/org.lflang.tests/src/org/lflang/tests/TestBase.java @@ -3,7 +3,11 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.io.FileWriter; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Provider; +import java.io.BufferedWriter; +import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.io.PrintWriter; @@ -12,8 +16,6 @@ import java.lang.reflect.InvocationTargetException; import java.nio.file.Files; import java.nio.file.Path; -import java.io.File; -import java.io.BufferedWriter; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -24,7 +26,6 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; import java.util.stream.Collectors; - import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource.Diagnostic; import org.eclipse.emf.ecore.resource.ResourceSet; @@ -37,7 +38,6 @@ import org.eclipse.xtext.validation.CheckMode; import org.eclipse.xtext.validation.IResourceValidator; import org.junit.jupiter.api.extension.ExtendWith; - import org.lflang.DefaultErrorReporter; import org.lflang.FileConfig; import org.lflang.LFRuntimeModule; @@ -48,18 +48,11 @@ import org.lflang.generator.LFGeneratorContext; import org.lflang.generator.LFGeneratorContext.BuildParm; import org.lflang.generator.MainContext; -import org.lflang.generator.GeneratorCommandFactory; import org.lflang.tests.Configurators.Configurator; import org.lflang.tests.LFTest.Result; import org.lflang.tests.TestRegistry.TestCategory; import org.lflang.util.FileUtil; import org.lflang.util.LFCommand; -import org.lflang.util.ArduinoUtil; - - -import com.google.inject.Inject; -import com.google.inject.Injector; -import com.google.inject.Provider; /** * Base class for test classes that define JUnit tests. @@ -70,501 +63,518 @@ @InjectWith(LFInjectorProvider.class) public abstract class TestBase { - @Inject - IResourceValidator validator; - @Inject - LFGenerator generator; - @Inject - JavaIoFileSystemAccess fileAccess; - @Inject - Provider resourceSetProvider; - - - /** Reference to System.out. */ - private static final PrintStream out = System.out; - - /** Reference to System.err. */ - private static final PrintStream err = System.err; - - /** Execution timeout enforced for all tests. */ - private static final long MAX_EXECUTION_TIME_SECONDS = 180; - - /** Content separator used in test output, 78 characters wide. */ - public static final String THIN_LINE = - "------------------------------------------------------------------------------" + - System.lineSeparator(); - - /** Content separator used in test output, 78 characters wide. */ - public static final String THICK_LINE = - "==============================================================================" + - System.lineSeparator(); - - /** The targets for which to run the tests. */ - private final List targets; - - /** - * An enumeration of test levels. - * @author Marten Lohstroh - * - */ - public enum TestLevel {VALIDATION, CODE_GEN, BUILD, EXECUTION} - - /** - * Static function for converting a path to its associated test level. - * @author Anirudh Rengarajan - */ - public static TestLevel pathToLevel(Path path) { - while(path.getParent() != null) { - String name = path.getFileName().toString(); - for (var category: TestCategory.values()) { - if (category.name().equalsIgnoreCase(name)) { - return category.level; - } - } - path = path.getParent(); + @Inject IResourceValidator validator; + @Inject LFGenerator generator; + @Inject JavaIoFileSystemAccess fileAccess; + @Inject Provider resourceSetProvider; + + /** Reference to System.out. */ + private static final PrintStream out = System.out; + + /** Reference to System.err. */ + private static final PrintStream err = System.err; + + /** Execution timeout enforced for all tests. */ + private static final long MAX_EXECUTION_TIME_SECONDS = 180; + + /** Content separator used in test output, 78 characters wide. */ + public static final String THIN_LINE = + "------------------------------------------------------------------------------" + + System.lineSeparator(); + + /** Content separator used in test output, 78 characters wide. */ + public static final String THICK_LINE = + "==============================================================================" + + System.lineSeparator(); + + /** The targets for which to run the tests. */ + private final List targets; + + /** + * An enumeration of test levels. + * + * @author Marten Lohstroh + */ + public enum TestLevel { + VALIDATION, + CODE_GEN, + BUILD, + EXECUTION + } + + /** + * Static function for converting a path to its associated test level. + * + * @author Anirudh Rengarajan + */ + public static TestLevel pathToLevel(Path path) { + while (path.getParent() != null) { + String name = path.getFileName().toString(); + for (var category : TestCategory.values()) { + if (category.name().equalsIgnoreCase(name)) { + return category.level; } - return TestLevel.EXECUTION; + } + path = path.getParent(); } - - /** - * A collection messages often used throughout the test package. - * - * @author Marten Lohstroh - * - */ - public static class Message { - /* Reasons for not running tests. */ - public static final String NO_WINDOWS_SUPPORT = "Not (yet) supported on Windows."; - public static final String NO_SINGLE_THREADED_SUPPORT = "Target does not support single-threaded execution."; - public static final String NO_FEDERATION_SUPPORT = "Target does not support federated execution."; - public static final String NO_ENCLAVE_SUPPORT = "Targeet does not support the enclave feature."; - public static final String NO_DOCKER_SUPPORT = "Target does not support the 'docker' property."; - public static final String NO_DOCKER_TEST_SUPPORT = "Docker tests are only supported on Linux."; - public static final String NO_GENERICS_SUPPORT = "Target does not support generic types."; - - /* Descriptions of collections of tests. */ - public static final String DESC_SERIALIZATION = "Run serialization tests."; - public static final String DESC_GENERIC = "Run generic tests."; - public static final String DESC_TYPE_PARMS = "Run tests for reactors with type parameters."; - public static final String DESC_MULTIPORT = "Run multiport tests."; - public static final String DESC_AS_FEDERATED = "Run non-federated tests in federated mode."; - public static final String DESC_FEDERATED = "Run federated tests."; - public static final String DESC_DOCKER = "Run docker tests."; - public static final String DESC_DOCKER_FEDERATED = "Run docker federated tests."; - public static final String DESC_ENCLAVE = "Run enclave tests."; - public static final String DESC_CONCURRENT = "Run concurrent tests."; - public static final String DESC_TARGET_SPECIFIC = "Run target-specific tests"; - public static final String DESC_ARDUINO = "Running Arduino tests."; - public static final String DESC_ZEPHYR = "Running Zephyr tests."; - public static final String DESC_AS_CCPP = "Running C tests as CCpp."; - public static final String DESC_SINGLE_THREADED = "Run non-concurrent and non-federated tests with threading = off."; - public static final String DESC_SCHED_SWAPPING = "Running with non-default runtime scheduler "; - public static final String DESC_ROS2 = "Running tests using ROS2."; - public static final String DESC_MODAL = "Run modal reactor tests."; - - /* Missing dependency messages */ - public static final String MISSING_DOCKER = "Executable 'docker' not found or 'docker' daemon thread not running"; - public static final String MISSING_ARDUINO_CLI = "Executable 'arduino-cli' not found"; + return TestLevel.EXECUTION; + } + + /** + * A collection messages often used throughout the test package. + * + * @author Marten Lohstroh + */ + public static class Message { + /* Reasons for not running tests. */ + public static final String NO_WINDOWS_SUPPORT = "Not (yet) supported on Windows."; + public static final String NO_SINGLE_THREADED_SUPPORT = + "Target does not support single-threaded execution."; + public static final String NO_FEDERATION_SUPPORT = + "Target does not support federated execution."; + public static final String NO_ENCLAVE_SUPPORT = "Targeet does not support the enclave feature."; + public static final String NO_DOCKER_SUPPORT = "Target does not support the 'docker' property."; + public static final String NO_DOCKER_TEST_SUPPORT = "Docker tests are only supported on Linux."; + public static final String NO_GENERICS_SUPPORT = "Target does not support generic types."; + + /* Descriptions of collections of tests. */ + public static final String DESC_SERIALIZATION = "Run serialization tests."; + public static final String DESC_GENERIC = "Run generic tests."; + public static final String DESC_TYPE_PARMS = "Run tests for reactors with type parameters."; + public static final String DESC_MULTIPORT = "Run multiport tests."; + public static final String DESC_AS_FEDERATED = "Run non-federated tests in federated mode."; + public static final String DESC_FEDERATED = "Run federated tests."; + public static final String DESC_DOCKER = "Run docker tests."; + public static final String DESC_DOCKER_FEDERATED = "Run docker federated tests."; + public static final String DESC_ENCLAVE = "Run enclave tests."; + public static final String DESC_CONCURRENT = "Run concurrent tests."; + public static final String DESC_TARGET_SPECIFIC = "Run target-specific tests"; + public static final String DESC_ARDUINO = "Running Arduino tests."; + public static final String DESC_ZEPHYR = "Running Zephyr tests."; + public static final String DESC_AS_CCPP = "Running C tests as CCpp."; + public static final String DESC_SINGLE_THREADED = + "Run non-concurrent and non-federated tests with threading = off."; + public static final String DESC_SCHED_SWAPPING = "Running with non-default runtime scheduler "; + public static final String DESC_ROS2 = "Running tests using ROS2."; + public static final String DESC_MODAL = "Run modal reactor tests."; + + /* Missing dependency messages */ + public static final String MISSING_DOCKER = + "Executable 'docker' not found or 'docker' daemon thread not running"; + public static final String MISSING_ARDUINO_CLI = "Executable 'arduino-cli' not found"; + } + + /** Constructor for test classes that test a single target. */ + protected TestBase(Target first) { + this(Collections.singletonList(first)); + } + + /** Special ctor for the code coverage test */ + protected TestBase(List targets) { + assertFalse(targets.isEmpty(), "empty target list"); + this.targets = Collections.unmodifiableList(targets); + TestRegistry.initialize(); + } + + /** + * Run selected tests for a given target and configurator up to the specified level. + * + * @param target The target to run tests for. + * @param selected A predicate that given a test category returns whether it should be included in + * this test run or not. + * @param configurator A procedure for configuring the tests. + * @param copy Whether or not to work on copies of tests in the test. registry. + */ + protected final void runTestsAndPrintResults( + Target target, + Predicate selected, + TestLevel level, + Configurator configurator, + boolean copy) { + var categories = Arrays.stream(TestCategory.values()).filter(selected).toList(); + for (var category : categories) { + System.out.println(category.getHeader()); + var tests = TestRegistry.getRegisteredTests(target, category, copy); + try { + validateAndRun(tests, configurator, level); + } catch (IOException e) { + throw new RuntimeIOException(e); + } + System.out.println(TestRegistry.getCoverageReport(target, category)); + checkAndReportFailures(tests); } - - /** Constructor for test classes that test a single target. */ - protected TestBase(Target first) { - this(Collections.singletonList(first)); + } + + /** + * Run tests in the given selection for all targets enabled in this class. + * + * @param description A string that describes the collection of tests. + * @param selected A predicate that given a test category returns whether it should be included in + * this test run or not. + * @param configurator A procedure for configuring the tests. + * @param copy Whether or not to work on copies of tests in the test. registry. + */ + protected void runTestsForTargets( + String description, + Predicate selected, + Configurator configurator, + TestLevel level, + boolean copy) { + for (Target target : this.targets) { + runTestsFor(List.of(target), description, selected, configurator, level, copy); } - - /** Special ctor for the code coverage test */ - protected TestBase(List targets) { - assertFalse(targets.isEmpty(), "empty target list"); - this.targets = Collections.unmodifiableList(targets); - TestRegistry.initialize(); + } + + /** + * Run tests in the given selection for a subset of given targets. + * + * @param subset The subset of targets to run the selected tests for. + * @param description A string that describes the collection of tests. + * @param selected A predicate that given a test category returns whether it should be included in + * this test run or not. + * @param configurator A procedure for configuring the tests. + * @param copy Whether to work on copies of tests in the test. registry. + */ + protected void runTestsFor( + List subset, + String description, + Predicate selected, + Configurator configurator, + TestLevel level, + boolean copy) { + for (Target target : subset) { + printTestHeader(target, description); + runTestsAndPrintResults(target, selected, level, configurator, copy); } - - /** - * Run selected tests for a given target and configurator up to the specified level. - * - * @param target The target to run tests for. - * @param selected A predicate that given a test category returns whether - * it should be included in this test run or not. - * @param configurator A procedure for configuring the tests. - * @param copy Whether or not to work on copies of tests in the test. - * registry. - */ - protected final void runTestsAndPrintResults(Target target, - Predicate selected, - TestLevel level, - Configurator configurator, - boolean copy) { - var categories = Arrays.stream(TestCategory.values()).filter(selected).toList(); - for (var category : categories) { - System.out.println(category.getHeader()); - var tests = TestRegistry.getRegisteredTests(target, category, copy); - try { - validateAndRun(tests, configurator, level); - } catch (IOException e) { - throw new RuntimeIOException(e); - } - System.out.println(TestRegistry.getCoverageReport(target, category)); - checkAndReportFailures(tests); - } + } + + /** Whether to enable threading. */ + protected boolean supportsSingleThreadedExecution() { + return false; + } + + /** + * Determine whether the current platform is Windows. + * + * @return true if the current platform is Windwos, false otherwise. + */ + protected static boolean isWindows() { + String OS = System.getProperty("os.name").toLowerCase(); + return OS.contains("win"); + } + + /** + * Determine whether the current platform is MacOS. + * + * @return true if the current platform is MacOS, false otherwise. + */ + protected static boolean isMac() { + String OS = System.getProperty("os.name").toLowerCase(); + return OS.contains("mac"); + } + + /** + * Determine whether the current platform is Linux. + * + * @return true if the current platform is Linux, false otherwise. + */ + protected static boolean isLinux() { + String OS = System.getProperty("os.name").toLowerCase(); + return OS.contains("linux"); + } + + /** End output redirection. */ + private static void restoreOutputs() { + System.out.flush(); + System.err.flush(); + System.setOut(out); + System.setErr(err); + } + + /** + * Redirect outputs to the given tests for recording. + * + * @param test The test to redirect outputs to. + */ + private static void redirectOutputs(LFTest test) { + System.setOut(new PrintStream(test.getOutputStream())); + System.setErr(new PrintStream(test.getOutputStream())); + } + + /** + * Run a test, print results on stderr. + * + * @param test Test case. + * @param testClass The test class that will execute the test. This is target-specific, it may + * provide some target-specific configuration. We pass a class and not a new instance because + * this method needs to ensure the object is properly injected, and so, it needs to control + * its entire lifecycle. + * @param level Level to which to run the test. + */ + public static void runSingleTestAndPrintResults( + LFTest test, Class testClass, TestLevel level) { + Injector injector = + new LFStandaloneSetup(new LFRuntimeModule()).createInjectorAndDoEMFRegistration(); + TestBase runner; + try { + @SuppressWarnings("unchecked") + Constructor constructor = + (Constructor) testClass.getConstructors()[0]; + runner = constructor.newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new IllegalStateException(e); } + injector.injectMembers(runner); - /** - * Run tests in the given selection for all targets enabled in this class. - * - * @param description A string that describes the collection of tests. - * @param selected A predicate that given a test category returns whether - * it should be included in this test run or not. - * @param configurator A procedure for configuring the tests. - * @param copy Whether or not to work on copies of tests in the test. - * registry. - */ - protected void runTestsForTargets(String description, - Predicate selected, - Configurator configurator, - TestLevel level, - boolean copy) { - for (Target target : this.targets) { - runTestsFor(List.of(target), description, selected, - configurator, level, copy); - } + Set tests = Set.of(test); + try { + runner.validateAndRun(tests, t -> true, level); + } catch (IOException e) { + throw new RuntimeIOException(e); } - - /** - * Run tests in the given selection for a subset of given targets. - * - * @param subset The subset of targets to run the selected tests for. - * @param description A string that describes the collection of tests. - * @param selected A predicate that given a test category returns whether - * it should be included in this test run or not. - * @param configurator A procedure for configuring the tests. - * @param copy Whether to work on copies of tests in the test. - * registry. - */ - protected void runTestsFor(List subset, - String description, - Predicate selected, - Configurator configurator, - TestLevel level, - boolean copy) { - for (Target target : subset) { - printTestHeader(target, description); - runTestsAndPrintResults(target, selected, level, configurator, copy); - } + checkAndReportFailures(tests); + } + + /** + * Print a header that describes a collection of tests. + * + * @param target The target for which the tests are being performed. + * @param description A string the describes the collection of tests. + */ + protected static void printTestHeader(Target target, String description) { + System.out.print(TestBase.THICK_LINE); + System.out.println("Target: " + target); + if (description.startsWith("Description: ")) { + System.out.println(description); + } else { + System.out.println("Description: " + description); } - - /** - * Whether to enable threading. - */ - protected boolean supportsSingleThreadedExecution() { - return false; + System.out.println(TestBase.THICK_LINE); + } + + /** + * Iterate over given tests and evaluate their outcome, report errors if there are any. + * + * @param tests The tests to inspect the results of. + */ + private static void checkAndReportFailures(Set tests) { + var passed = tests.stream().filter(it -> it.hasPassed()).collect(Collectors.toList()); + var s = new StringBuffer(); + s.append(THIN_LINE); + s.append("Passing: " + passed.size() + "/" + tests.size() + "\n"); + s.append(THIN_LINE); + passed.forEach(test -> s.append("Passed: ").append(test).append("\n")); + s.append(THIN_LINE); + System.out.print(s.toString()); + + for (var test : tests) { + test.reportErrors(); } - - /** - * Determine whether the current platform is Windows. - * @return true if the current platform is Windwos, false otherwise. - */ - protected static boolean isWindows() { - String OS = System.getProperty("os.name").toLowerCase(); - return OS.contains("win"); - } - - /** - * Determine whether the current platform is MacOS. - * @return true if the current platform is MacOS, false otherwise. - */ - protected static boolean isMac() { - String OS = System.getProperty("os.name").toLowerCase(); - return OS.contains("mac"); - } - - /** - * Determine whether the current platform is Linux. - * @return true if the current platform is Linux, false otherwise. - */ - protected static boolean isLinux() { - String OS = System.getProperty("os.name").toLowerCase(); - return OS.contains("linux"); + for (LFTest lfTest : tests) { + assertTrue(lfTest.hasPassed()); } - - /** - * End output redirection. - */ - private static void restoreOutputs() { - System.out.flush(); - System.err.flush(); - System.setOut(out); - System.setErr(err); + } + + /** + * Configure a test by applying the given configurator and return a generator context. Also, if + * the given level is less than {@code TestLevel.BUILD}, add a {@code no-compile} flag to the + * generator context. If the configurator was not applied successfully, throw an AssertionError. + * + * @param test the test to configure. + * @param configurator The configurator to apply to the test. + * @param level The level of testing in which the generator context will be used. + */ + private void configure(LFTest test, Configurator configurator, TestLevel level) + throws IOException, TestError { + var props = new Properties(); + props.setProperty("hierarchical-bin", "true"); + addExtraLfcArgs(props); + + var sysProps = System.getProperties(); + // Set the external-runtime-path property if it was specified. + if (sysProps.containsKey("runtime")) { + var rt = sysProps.get("runtime").toString(); + if (!rt.isEmpty()) { + props.setProperty(BuildParm.EXTERNAL_RUNTIME_PATH.getKey(), rt); + System.out.println("Using runtime: " + sysProps.get("runtime").toString()); + } + } else { + System.out.println("Using default runtime."); } - /** - * Redirect outputs to the given tests for recording. - * - * @param test The test to redirect outputs to. - */ - private static void redirectOutputs(LFTest test) { - System.setOut(new PrintStream(test.getOutputStream())); - System.setErr(new PrintStream(test.getOutputStream())); + var r = + resourceSetProvider + .get() + .getResource(URI.createFileURI(test.getSrcPath().toFile().getAbsolutePath()), true); + + if (r.getErrors().size() > 0) { + String message = + r.getErrors().stream() + .map(Diagnostic::toString) + .collect(Collectors.joining(System.lineSeparator())); + throw new TestError(message, Result.PARSE_FAIL); } - - /** - * Run a test, print results on stderr. - * - * @param test Test case. - * @param testClass The test class that will execute the test. This is target-specific, - * it may provide some target-specific configuration. We pass a class - * and not a new instance because this method needs to ensure the object - * is properly injected, and so, it needs to control its entire lifecycle. - * @param level Level to which to run the test. - */ - public static void runSingleTestAndPrintResults(LFTest test, Class testClass, TestLevel level) { - Injector injector = new LFStandaloneSetup(new LFRuntimeModule()).createInjectorAndDoEMFRegistration(); - TestBase runner; - try { - @SuppressWarnings("unchecked") - Constructor constructor = (Constructor) testClass.getConstructors()[0]; - runner = constructor.newInstance(); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException(e); - } - injector.injectMembers(runner); - - Set tests = Set.of(test); - try { - runner.validateAndRun(tests, t -> true, level); - } catch (IOException e) { - throw new RuntimeIOException(e); - } - checkAndReportFailures(tests); + fileAccess.setOutputPath( + FileConfig.findPackageRoot(test.getSrcPath(), s -> {}) + .resolve(FileConfig.DEFAULT_SRC_GEN_DIR) + .toString()); + var context = + new MainContext( + LFGeneratorContext.Mode.STANDALONE, + CancelIndicator.NullImpl, + (m, p) -> {}, + props, + r, + fileAccess, + fileConfig -> new DefaultErrorReporter()); + + test.configure(context); + + // Set the no-compile flag the test is not supposed to reach the build stage. + if (level.compareTo(TestLevel.BUILD) < 0) { + context.getArgs().setProperty("no-compile", ""); } - /** - * Print a header that describes a collection of tests. - * @param target The target for which the tests are being performed. - * @param description A string the describes the collection of tests. - */ - protected static void printTestHeader(Target target, String description) { - System.out.print(TestBase.THICK_LINE); - System.out.println("Target: " + target); - if (description.startsWith("Description: ")) { - System.out.println(description); - } else { - System.out.println("Description: " + description); - } - System.out.println(TestBase.THICK_LINE); + // Reload in case target properties have changed. + context.loadTargetConfig(); + // Update the test by applying the configuration. E.g., to carry out an AST transformation. + if (configurator != null) { + if (!configurator.configure(test)) { + throw new TestError("Test configuration unsuccessful.", Result.CONFIG_FAIL); + } } - - /** - * Iterate over given tests and evaluate their outcome, report errors if - * there are any. - * - * @param tests The tests to inspect the results of. - */ - private static void checkAndReportFailures(Set tests) { - var passed = tests.stream().filter(it -> it.hasPassed()).collect(Collectors.toList()); - var s = new StringBuffer(); - s.append(THIN_LINE); - s.append("Passing: " + passed.size() + "/" + tests.size() + "\n"); - s.append(THIN_LINE); - passed.forEach(test -> s.append("Passed: ").append(test).append("\n")); - s.append(THIN_LINE); - System.out.print(s.toString()); - - for (var test : tests) { - test.reportErrors(); - } - for (LFTest lfTest : tests) { - assertTrue(lfTest.hasPassed()); + } + + /** Validate the given test. Throw an TestError if validation failed. */ + private void validate(LFTest test) throws TestError { + // Validate the resource and store issues in the test object. + try { + var context = test.getContext(); + var issues = + validator.validate( + context.getFileConfig().resource, CheckMode.ALL, context.getCancelIndicator()); + if (issues != null && !issues.isEmpty()) { + if (issues.stream().anyMatch(it -> it.getSeverity() == Severity.ERROR)) { + String message = + issues.stream() + .map(Objects::toString) + .collect(Collectors.joining(System.lineSeparator())); + throw new TestError(message, Result.VALIDATE_FAIL); } + } + } catch (TestError e) { + throw e; + } catch (Throwable e) { + throw new TestError("Exception during validation.", Result.VALIDATE_FAIL, e); } - - /** - * Configure a test by applying the given configurator and return a - * generator context. Also, if the given level is less than - * {@code TestLevel.BUILD}, add a {@code no-compile} flag to the generator context. If - * the configurator was not applied successfully, throw an AssertionError. - * - * @param test the test to configure. - * @param configurator The configurator to apply to the test. - * @param level The level of testing in which the generator context will be - * used. - */ - private void configure(LFTest test, Configurator configurator, TestLevel level) throws IOException, TestError { - var props = new Properties(); - props.setProperty("hierarchical-bin", "true"); - addExtraLfcArgs(props); - - var sysProps = System.getProperties(); - // Set the external-runtime-path property if it was specified. - if (sysProps.containsKey("runtime")) { - var rt = sysProps.get("runtime").toString(); - if (!rt.isEmpty()) { - props.setProperty(BuildParm.EXTERNAL_RUNTIME_PATH.getKey(), rt); - System.out.println("Using runtime: " + sysProps.get("runtime").toString()); - } - } else { - System.out.println("Using default runtime."); - } - - var r = resourceSetProvider.get().getResource( - URI.createFileURI(test.getSrcPath().toFile().getAbsolutePath()), - true); - - if (r.getErrors().size() > 0) { - String message = r.getErrors().stream().map(Diagnostic::toString).collect(Collectors.joining(System.lineSeparator())); - throw new TestError(message, Result.PARSE_FAIL); - } - - fileAccess.setOutputPath(FileConfig.findPackageRoot(test.getSrcPath(), s -> {}).resolve(FileConfig.DEFAULT_SRC_GEN_DIR).toString()); - var context = new MainContext( - LFGeneratorContext.Mode.STANDALONE, CancelIndicator.NullImpl, (m, p) -> {}, props, r, fileAccess, - fileConfig -> new DefaultErrorReporter() - ); - - test.configure(context); - - // Set the no-compile flag the test is not supposed to reach the build stage. - if (level.compareTo(TestLevel.BUILD) < 0) { - context.getArgs().setProperty("no-compile", ""); - } - - // Reload in case target properties have changed. - context.loadTargetConfig(); - // Update the test by applying the configuration. E.g., to carry out an AST transformation. - if (configurator != null) { - if (!configurator.configure(test)) { - throw new TestError("Test configuration unsuccessful.", Result.CONFIG_FAIL); - } - } + } + + /** Override to add some LFC arguments to all runs of this test class. */ + protected void addExtraLfcArgs(Properties args) { + args.setProperty("build-type", "Test"); + args.setProperty("logging", "Debug"); + } + + /** + * Invoke the code generator for the given test. + * + * @param test The test to generate code for. + */ + private GeneratorResult generateCode(LFTest test) throws TestError { + if (test.getFileConfig().resource == null) { + return GeneratorResult.NOTHING; } - - /** - * Validate the given test. Throw an TestError if validation failed. - */ - private void validate(LFTest test) throws TestError { - // Validate the resource and store issues in the test object. - try { - var context = test.getContext(); - var issues = validator.validate(context.getFileConfig().resource, - CheckMode.ALL, context.getCancelIndicator()); - if (issues != null && !issues.isEmpty()) { - if (issues.stream().anyMatch(it -> it.getSeverity() == Severity.ERROR)) { - String message = issues.stream().map(Objects::toString).collect(Collectors.joining(System.lineSeparator())); - throw new TestError(message, Result.VALIDATE_FAIL); - } - } - } catch (TestError e) { - throw e; - } catch (Throwable e) { - throw new TestError("Exception during validation.", Result.VALIDATE_FAIL, e); - } + try { + generator.doGenerate(test.getFileConfig().resource, fileAccess, test.getContext()); + } catch (Throwable e) { + throw new TestError("Code generation unsuccessful.", Result.CODE_GEN_FAIL, e); } - - - /** - * Override to add some LFC arguments to all runs of this test class. - */ - protected void addExtraLfcArgs(Properties args) { - args.setProperty("build-type", "Test"); - args.setProperty("logging", "Debug"); + if (generator.errorsOccurred()) { + throw new TestError("Code generation unsuccessful.", Result.CODE_GEN_FAIL); } - /** - * Invoke the code generator for the given test. - * - * @param test The test to generate code for. - */ - private GeneratorResult generateCode(LFTest test) throws TestError { - if (test.getFileConfig().resource == null) { - return GeneratorResult.NOTHING; - } - try { - generator.doGenerate(test.getFileConfig().resource, fileAccess, test.getContext()); - } catch (Throwable e) { - throw new TestError("Code generation unsuccessful.", Result.CODE_GEN_FAIL, e); - } - if (generator.errorsOccurred()) { - throw new TestError("Code generation unsuccessful.", Result.CODE_GEN_FAIL); + return test.getContext().getResult(); + } + + /** + * Given an indexed test, execute it and label the test as failing if it did not execute, took too + * long to execute, or executed but exited with an error code. + */ + private void execute(LFTest test) throws TestError { + final var pb = getExecCommand(test); + try { + var p = pb.start(); + var stdout = test.recordStdOut(p); + var stderr = test.recordStdErr(p); + + var stdoutException = new AtomicReference(null); + var stderrException = new AtomicReference(null); + + stdout.setUncaughtExceptionHandler((thread, throwable) -> stdoutException.set(throwable)); + stderr.setUncaughtExceptionHandler((thread, throwable) -> stderrException.set(throwable)); + + stderr.start(); + stdout.start(); + + if (!p.waitFor(MAX_EXECUTION_TIME_SECONDS, TimeUnit.SECONDS)) { + stdout.interrupt(); + stderr.interrupt(); + p.destroyForcibly(); + throw new TestError(Result.TEST_TIMEOUT); + } else { + if (stdoutException.get() != null || stderrException.get() != null) { + StringBuffer sb = new StringBuffer(); + if (stdoutException.get() != null) { + sb.append("Error during stdout handling:" + System.lineSeparator()); + sb.append(stackTraceToString(stdoutException.get())); + } + if (stderrException.get() != null) { + sb.append("Error during stderr handling:" + System.lineSeparator()); + sb.append(stackTraceToString(stderrException.get())); + } + throw new TestError(sb.toString(), Result.TEST_EXCEPTION); } - - return test.getContext().getResult(); - } - - - /** - * Given an indexed test, execute it and label the test as failing if it - * did not execute, took too long to execute, or executed but exited with - * an error code. - */ - private void execute(LFTest test) throws TestError { - final var pb = getExecCommand(test); - try { - var p = pb.start(); - var stdout = test.recordStdOut(p); - var stderr = test.recordStdErr(p); - - var stdoutException = new AtomicReference(null); - var stderrException = new AtomicReference(null); - - stdout.setUncaughtExceptionHandler((thread, throwable) -> stdoutException.set(throwable)); - stderr.setUncaughtExceptionHandler((thread, throwable) -> stderrException.set(throwable)); - - stderr.start(); - stdout.start(); - - if (!p.waitFor(MAX_EXECUTION_TIME_SECONDS, TimeUnit.SECONDS)) { - stdout.interrupt(); - stderr.interrupt(); - p.destroyForcibly(); - throw new TestError(Result.TEST_TIMEOUT); - } else { - if (stdoutException.get() != null || stderrException.get() != null) { - StringBuffer sb = new StringBuffer(); - if (stdoutException.get() != null) { - sb.append("Error during stdout handling:" + System.lineSeparator()); - sb.append(stackTraceToString(stdoutException.get())); - } - if (stderrException.get() != null) { - sb.append("Error during stderr handling:" + System.lineSeparator()); - sb.append(stackTraceToString(stderrException.get())); - } - throw new TestError(sb.toString(), Result.TEST_EXCEPTION); - } - if (p.exitValue() != 0) { - String message = "Exit code: " + p.exitValue(); - if (p.exitValue() == 139) { - // The java ProcessBuilder and Process interface does not allow us to reliably retrieve stderr and stdout - // from a process that segfaults. We can only print a message indicating that the putput is incomplete. - message += System.lineSeparator() + - "This exit code typically indicates a segfault. In this case, the execution output is likely missing or incomplete."; - } - throw new TestError(message, Result.TEST_FAIL); - } - } - } catch (TestError e) { - throw e; - } catch (Throwable e) { - e.printStackTrace(); - throw new TestError("Exception during test execution.", Result.TEST_EXCEPTION, e); + if (p.exitValue() != 0) { + String message = "Exit code: " + p.exitValue(); + if (p.exitValue() == 139) { + // The java ProcessBuilder and Process interface does not allow us to reliably retrieve + // stderr and stdout + // from a process that segfaults. We can only print a message indicating that the putput + // is incomplete. + message += + System.lineSeparator() + + "This exit code typically indicates a segfault. In this case, the execution" + + " output is likely missing or incomplete."; + } + throw new TestError(message, Result.TEST_FAIL); } + } + } catch (TestError e) { + throw e; + } catch (Throwable e) { + e.printStackTrace(); + throw new TestError("Exception during test execution.", Result.TEST_EXCEPTION, e); } - - static public String stackTraceToString(Throwable t) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - t.printStackTrace(pw); - pw.flush(); - pw.close(); - return sw.toString(); - } - - /** Bash script that is used to execute docker tests. */ - static private String DOCKER_RUN_SCRIPT = """ + } + + public static String stackTraceToString(Throwable t) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + t.printStackTrace(pw); + pw.flush(); + pw.close(); + return sw.toString(); + } + + /** Bash script that is used to execute docker tests. */ + private static String DOCKER_RUN_SCRIPT = + """ #!/bin/bash # exit when any command fails set -e - + docker compose -f "$1" rm -f docker compose -f "$1" up --build | tee docker_log.txt docker compose -f "$1" down --rmi local @@ -583,129 +593,128 @@ static public String stackTraceToString(Throwable t) { exit 0 """; - /** - * Path to a bash script containing DOCKER_RUN_SCRIPT. - */ - private static Path dockerRunScript = null; - - /** - * Return the path to a bash script containing DOCKER_RUN_SCRIPT. - * - * If the script does not yet exist, it is created. - */ - private Path getDockerRunScript() throws TestError { - if (dockerRunScript != null) { - return dockerRunScript; - } - - try { - var file = File.createTempFile("run_docker_test", "sh"); - file.deleteOnExit(); - file.setExecutable(true); - var path = file.toPath(); - try (BufferedWriter writer = Files.newBufferedWriter(path)) { - writer.write(DOCKER_RUN_SCRIPT); - } - dockerRunScript = path; - } catch (IOException e) { - throw new TestError("IO Error during test preparation.", Result.TEST_EXCEPTION, e); - } - - return dockerRunScript; + /** Path to a bash script containing DOCKER_RUN_SCRIPT. */ + private static Path dockerRunScript = null; + + /** + * Return the path to a bash script containing DOCKER_RUN_SCRIPT. + * + *

If the script does not yet exist, it is created. + */ + private Path getDockerRunScript() throws TestError { + if (dockerRunScript != null) { + return dockerRunScript; } - /** - * Throws TestError if docker does not exist. Does nothing otherwise. - */ - private void checkDockerExists() throws TestError { - if (LFCommand.get("docker", List.of()) == null) { - throw new TestError("Executable 'docker' not found" , Result.NO_EXEC_FAIL); - } - if (LFCommand.get("docker-compose", List.of()) == null) { - throw new TestError("Executable 'docker-compose' not found" , Result.NO_EXEC_FAIL); - } + try { + var file = File.createTempFile("run_docker_test", "sh"); + file.deleteOnExit(); + file.setExecutable(true); + var path = file.toPath(); + try (BufferedWriter writer = Files.newBufferedWriter(path)) { + writer.write(DOCKER_RUN_SCRIPT); + } + dockerRunScript = path; + } catch (IOException e) { + throw new TestError("IO Error during test preparation.", Result.TEST_EXCEPTION, e); } - /** - * Return a ProcessBuilder used to test the docker execution. - * @param test The test to get the execution command for. - */ - private ProcessBuilder getDockerExecCommand(LFTest test) throws TestError { - checkDockerExists(); - var srcGenPath = test.getFileConfig().getSrcGenPath(); - var dockerComposeFile = FileUtil.globFilesEndsWith(srcGenPath, "docker-compose.yml").get(0); - return new ProcessBuilder(getDockerRunScript().toString(), dockerComposeFile.toString()); - } + return dockerRunScript; + } - /** - * Return a preconfigured ProcessBuilder for executing the test program. - * @param test The test to get the execution command for. - */ - private ProcessBuilder getExecCommand(LFTest test) throws TestError { - - var srcBasePath = test.getFileConfig().srcPkgPath.resolve("src"); - var relativePathName = srcBasePath.relativize(test.getFileConfig().srcPath).toString(); - - // special case to test docker file generation - if (relativePathName.equalsIgnoreCase(TestCategory.DOCKER.getPath()) || - relativePathName.equalsIgnoreCase(TestCategory.DOCKER_FEDERATED.getPath())) { - return getDockerExecCommand(test); - } else { - LFCommand command = test.getFileConfig().getCommand(); - if (command == null) { - throw new TestError("File: " + test.getFileConfig().getExecutable(), Result.NO_EXEC_FAIL); - } - return new ProcessBuilder(command.command()).directory(command.directory()); - } + /** Throws TestError if docker does not exist. Does nothing otherwise. */ + private void checkDockerExists() throws TestError { + if (LFCommand.get("docker", List.of()) == null) { + throw new TestError("Executable 'docker' not found", Result.NO_EXEC_FAIL); } - - /** - * Validate and run the given tests, using the specified configuratator and level. - * - * While performing tests, this method prints a header that reaches completion - * once all tests have been run. - * - * @param tests A set of tests to run. - * @param configurator A procedure for configuring the tests. - * @param level The level of testing. - * @throws IOException If initial file configuration fails - */ - private void validateAndRun(Set tests, Configurator configurator, TestLevel level) throws IOException { - final var x = 78f / tests.size(); - var marks = 0; - var done = 0; - - for (var test : tests) { - try { - redirectOutputs(test); - configure(test, configurator, level); - validate(test); - if (level.compareTo(TestLevel.CODE_GEN) >= 0) { - generateCode(test); - } - if (level == TestLevel.EXECUTION) { - execute(test); - } - test.markPassed(); - } catch (TestError e) { - test.handleTestError(e); - } catch (Throwable e) { - test.handleTestError(new TestError( - "Unknown exception during test execution", Result.TEST_EXCEPTION, e)); - } finally { - restoreOutputs(); - } - done++; - while (Math.floor(done * x) >= marks && marks < 78) { - System.out.print("="); - marks++; - } + if (LFCommand.get("docker-compose", List.of()) == null) { + throw new TestError("Executable 'docker-compose' not found", Result.NO_EXEC_FAIL); + } + } + + /** + * Return a ProcessBuilder used to test the docker execution. + * + * @param test The test to get the execution command for. + */ + private ProcessBuilder getDockerExecCommand(LFTest test) throws TestError { + checkDockerExists(); + var srcGenPath = test.getFileConfig().getSrcGenPath(); + var dockerComposeFile = FileUtil.globFilesEndsWith(srcGenPath, "docker-compose.yml").get(0); + return new ProcessBuilder(getDockerRunScript().toString(), dockerComposeFile.toString()); + } + + /** + * Return a preconfigured ProcessBuilder for executing the test program. + * + * @param test The test to get the execution command for. + */ + private ProcessBuilder getExecCommand(LFTest test) throws TestError { + + var srcBasePath = test.getFileConfig().srcPkgPath.resolve("src"); + var relativePathName = srcBasePath.relativize(test.getFileConfig().srcPath).toString(); + + // special case to test docker file generation + if (relativePathName.equalsIgnoreCase(TestCategory.DOCKER.getPath()) + || relativePathName.equalsIgnoreCase(TestCategory.DOCKER_FEDERATED.getPath())) { + return getDockerExecCommand(test); + } else { + LFCommand command = test.getFileConfig().getCommand(); + if (command == null) { + throw new TestError("File: " + test.getFileConfig().getExecutable(), Result.NO_EXEC_FAIL); + } + return new ProcessBuilder(command.command()).directory(command.directory()); + } + } + + /** + * Validate and run the given tests, using the specified configuratator and level. + * + *

While performing tests, this method prints a header that reaches completion once all tests + * have been run. + * + * @param tests A set of tests to run. + * @param configurator A procedure for configuring the tests. + * @param level The level of testing. + * @throws IOException If initial file configuration fails + */ + private void validateAndRun(Set tests, Configurator configurator, TestLevel level) + throws IOException { + final var x = 78f / tests.size(); + var marks = 0; + var done = 0; + + for (var test : tests) { + try { + redirectOutputs(test); + configure(test, configurator, level); + validate(test); + if (level.compareTo(TestLevel.CODE_GEN) >= 0) { + generateCode(test); } - while (marks < 78) { - System.out.print("="); - marks++; + if (level == TestLevel.EXECUTION) { + execute(test); } - - System.out.print(System.lineSeparator()); + test.markPassed(); + } catch (TestError e) { + test.handleTestError(e); + } catch (Throwable e) { + test.handleTestError( + new TestError("Unknown exception during test execution", Result.TEST_EXCEPTION, e)); + } finally { + restoreOutputs(); + } + done++; + while (Math.floor(done * x) >= marks && marks < 78) { + System.out.print("="); + marks++; + } } + while (marks < 78) { + System.out.print("="); + marks++; + } + + System.out.print(System.lineSeparator()); + } } diff --git a/org.lflang.tests/src/org/lflang/tests/TestError.java b/org.lflang.tests/src/org/lflang/tests/TestError.java index 0c261c6865..efb7d50048 100644 --- a/org.lflang.tests/src/org/lflang/tests/TestError.java +++ b/org.lflang.tests/src/org/lflang/tests/TestError.java @@ -5,24 +5,28 @@ /// Indicates an error during test execution public class TestError extends Exception { - private Throwable exception; - private Result result; - - public TestError(String errorMessage, Result result, Throwable exception) { - super(errorMessage); - this.exception = exception; - this.result = result; - } - - public TestError(String errorMessage, Result result) { - this(errorMessage, result, null); - } - - public TestError(Result result) { - this(null, result, null); - } - - public Result getResult() {return result;} - - public Throwable getException() {return exception;} + private Throwable exception; + private Result result; + + public TestError(String errorMessage, Result result, Throwable exception) { + super(errorMessage); + this.exception = exception; + this.result = result; + } + + public TestError(String errorMessage, Result result) { + this(errorMessage, result, null); + } + + public TestError(Result result) { + this(null, result, null); + } + + public Result getResult() { + return result; + } + + public Throwable getException() { + return exception; + } } diff --git a/org.lflang.tests/src/org/lflang/tests/TestRegistry.java b/org.lflang.tests/src/org/lflang/tests/TestRegistry.java index 1249ea25be..2c59740e0a 100644 --- a/org.lflang.tests/src/org/lflang/tests/TestRegistry.java +++ b/org.lflang.tests/src/org/lflang/tests/TestRegistry.java @@ -18,12 +18,10 @@ import java.util.Set; import java.util.Stack; import java.util.TreeSet; - import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.xtext.xbase.lib.IteratorExtensions; - import org.lflang.LFResourceProvider; import org.lflang.LFStandaloneSetup; import org.lflang.Target; @@ -37,378 +35,349 @@ */ public class TestRegistry { - static class TestMap { - /** - * Registry that maps targets to maps from categories to sets of tests. - */ - protected final Map>> map = new HashMap<>(); - - /** - * Create a new test map. - */ - public TestMap() { - // Populate the internal datastructures. - for (Target target : Target.values()) { - Map> categories = new HashMap<>(); - for (TestCategory cat : TestCategory.values()) { - categories.put(cat, new TreeSet<>()); - } - map.put(target, categories); - } - } - - /** - * Return a set of tests given a target and test category. - * @param t The target. - * @param c The test category. - * @return A set of tests for the given target and test category. - */ - public Set getTests(Target t, TestCategory c) { - return this.map.get(t).get(c); + static class TestMap { + /** Registry that maps targets to maps from categories to sets of tests. */ + protected final Map>> map = new HashMap<>(); + + /** Create a new test map. */ + public TestMap() { + // Populate the internal datastructures. + for (Target target : Target.values()) { + Map> categories = new HashMap<>(); + for (TestCategory cat : TestCategory.values()) { + categories.put(cat, new TreeSet<>()); } + map.put(target, categories); + } } /** - * 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. + * Return a set of tests given a target and test category. + * + * @param t The target. + * @param c The test category. + * @return A set of tests for the given target and test category. */ - public static final Path LF_TEST_PATH = LF_REPO_PATH.resolve("test"); + 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. */ + protected static final TestMap registered = new TestMap(); + + /** + * Internal data structure that stores ignored tests. For instance, 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/foo/threaded/Bar.lf + *
  • C/foo/bar/threaded/Threaded.lf + *
  • C/federated/threaded/bar.lf 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 { + /** Tests about concurrent execution. */ + CONCURRENT(true), + /** Test about enclaves */ + ENCLAVE(false), + /** Generic tests, ie, tests that all targets are supposed to implement. */ + GENERIC(true), + /** Tests about generics, not to confuse with {@link #GENERIC}. */ + GENERICS(true), + /** Tests about multiports and banks of reactors. */ + MULTIPORT(true), + /** Tests about federated execution. */ + FEDERATED(true), + /** Tests about specific target properties. */ + PROPERTIES(true), + /** Tests concerning modal reactors */ + MODAL_MODELS(true), + NO_INLINING(false), + // non-shared tests + DOCKER(true), + DOCKER_FEDERATED(true, "docker" + File.separator + "federated"), + SERIALIZATION(false), + ARDUINO(false, TestLevel.BUILD), + ZEPHYR(false, TestLevel.BUILD), + TARGET(false); + + /** Whether we should compare coverage against other targets. */ + public final boolean isCommon; + + public final String path; + public final TestLevel level; + + /** Create a new test category. */ + TestCategory(boolean isCommon) { + this.isCommon = isCommon; + this.path = this.name().toLowerCase(); + this.level = TestLevel.EXECUTION; + } - /** - * Internal data structure that stores registered tests. - */ - protected static final TestMap registered = new TestMap(); + /** Create a new test category. */ + TestCategory(boolean isCommon, TestLevel level) { + this.isCommon = isCommon; + this.path = this.name().toLowerCase(); + this.level = level; + } - /** - * Internal data structure that stores ignored tests. For instance, - * source files with no main reactor are indexed here. - */ - protected static final TestMap ignored = new TestMap(); + /** Create a new test category. */ + TestCategory(boolean isCommon, String path) { + this.isCommon = isCommon; + this.path = path; + this.level = TestLevel.EXECUTION; + } - /** - * 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<>(); + public String getPath() { + return path; + } /** - * 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/foo/threaded/Bar.lf
  • - *
  • C/foo/bar/threaded/Threaded.lf
  • - *
  • C/federated/threaded/bar.lf - * 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)
  • - *
+ * Return a header associated with the category. * - * @author Marten Lohstroh + * @return A header to print in the test report. */ - public enum TestCategory { - /** Tests about concurrent execution. */ - CONCURRENT(true), - /** Test about enclaves */ - ENCLAVE(false), - /** Generic tests, ie, tests that all targets are supposed to implement. */ - GENERIC(true), - /** Tests about generics, not to confuse with {@link #GENERIC}. */ - GENERICS(true), - /** Tests about multiports and banks of reactors. */ - MULTIPORT(true), - /** Tests about federated execution. */ - FEDERATED(true), - /** Tests about specific target properties. */ - PROPERTIES(true), - /** Tests concerning modal reactors */ - MODAL_MODELS(true), - NO_INLINING(false), - // non-shared tests - DOCKER(true), - DOCKER_FEDERATED(true, "docker" + File.separator + "federated"), - SERIALIZATION(false), - ARDUINO(false, TestLevel.BUILD), - ZEPHYR(false, TestLevel.BUILD), - TARGET(false); - - /** - * Whether we should compare coverage against other targets. - */ - public final boolean isCommon; - public final String path; - public final TestLevel level ; - - /** - * Create a new test category. - */ - TestCategory(boolean isCommon) { - this.isCommon = isCommon; - this.path = this.name().toLowerCase(); - this.level = TestLevel.EXECUTION; - } - - /** - * Create a new test category. - */ - TestCategory(boolean isCommon, TestLevel level) { - this.isCommon = isCommon; - this.path = this.name().toLowerCase(); - this.level = level; - } - - /** - * Create a new test category. - */ - TestCategory(boolean isCommon, String path) { - this.isCommon = isCommon; - this.path = path; - this.level = TestLevel.EXECUTION; - } - - public String getPath() { - return path; - } - - /** - * 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(); - } + 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 { - System.out.println("Indexing..."); - ResourceSet rs = new LFStandaloneSetup() + } + + // Static code that performs the file system traversal and discovers + // all .lf files to be included in the registry. + static { + System.out.println("Indexing..."); + ResourceSet rs = + new LFStandaloneSetup() .createInjectorAndDoEMFRegistration() - .getInstance(LFResourceProvider.class).getResourceSet(); + .getInstance(LFResourceProvider.class) + .getResourceSet(); - // Prepare for the collection of tests per category. - for (TestCategory t: TestCategory.values()) { - allTargets.put(t, new TreeSet<>()); - } - // Populate the registry. - for (Target target : Target.values()) { - - // Walk the tree. - try { - Path dir = LF_TEST_PATH.resolve(target.getDirectoryName()).resolve("src"); - if (Files.exists(dir)) { - new TestDirVisitor(rs, target, dir).walk(); - } 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); - e.printStackTrace(); - } - // Record the tests for later use when reporting coverage. - Arrays.asList(TestCategory.values()).forEach( - c -> allTargets.get(c).addAll(getRegisteredTests(target, c, false))); + // Prepare for the collection of tests per category. + for (TestCategory t : TestCategory.values()) { + allTargets.put(t, new TreeSet<>()); + } + // Populate the registry. + for (Target target : Target.values()) { + + // Walk the tree. + try { + Path dir = LF_TEST_PATH.resolve(target.getDirectoryName()).resolve("src"); + if (Files.exists(dir)) { + new TestDirVisitor(rs, target, dir).walk(); + } 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); + e.printStackTrace(); + } + // Record the tests for later use when reporting coverage. + Arrays.asList(TestCategory.values()) + .forEach(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 other code that prints to standard out so that + * any error messages 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. + * @return A set of tests for the given target/category. + */ + public static Set getRegisteredTests(Target target, TestCategory category, boolean copy) { + if (copy) { + Set copies = new TreeSet<>(); + for (LFTest test : registered.getTests(target, category)) { + copies.add(new LFTest(test)); + } + return copies; + } else { + return registered.getTests(target, category); + } + } + + /** Return the test that were found but not indexed because they did not have a main reactor. */ + public static Set getIgnoredTests(Target target, TestCategory category) { + return ignored.getTests(target, category); + } + + public static String getCoverageReport(Target target, TestCategory category) { + StringBuilder s = new StringBuilder(); + Set ignored = TestRegistry.getIgnoredTests(target, 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"); } - /** - * Calling this function forces the lazy initialization of the static code - * that indexes all files. It is advisable to do this prior to executing - * other code that prints to standard out so that any error messages - * printed while indexing are printed first. - */ - public static void initialize() {} + Set own = getRegisteredTests(target, category, false); + if (category.isCommon) { + Set all = allTargets.get(category); + s.append("\n").append(TestBase.THIN_LINE); + s.append("Covered: ").append(own.size()).append("/").append(all.size()).append("\n"); + s.append(TestBase.THIN_LINE); + int missing = all.size() - own.size(); + if (missing > 0) { + all.stream() + .filter(test -> !own.contains(test)) + .forEach(test -> s.append("Missing: ").append(test).append("\n")); + } + } else { + s.append("\n").append(TestBase.THIN_LINE); + s.append("Covered: ").append(own.size()).append("/").append(own.size()).append("\n"); + s.append(TestBase.THIN_LINE); + } + return s.toString(); + } + + /** + * FileVisitor implementation that maintains a stack to map found tests to 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 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 { + + /** The stack of which the top element indicates the "current" category. */ + protected Stack stack = new Stack<>(); + + /** The target that all encountered tests belong to. */ + protected Target target; + + protected ResourceSet rs; + + protected Path srcBasePath; /** - * Return the tests that were indexed for a given target and category. + * Create a new file visitor based on a given target. * - * @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. - * @return A set of tests for the given target/category. + * @param target The target that all encountered tests belong to. + * @param srcBasePath The test sources directory */ - public static Set getRegisteredTests(Target target, - TestCategory category, boolean copy) { - if (copy) { - Set copies = new TreeSet<>(); - for (LFTest test : registered.getTests(target, category)) { - copies.add(new LFTest(test)); - } - return copies; - } else { - return registered.getTests(target, category); - } + public TestDirVisitor(ResourceSet rs, Target target, Path srcBasePath) { + stack.push(TestCategory.GENERIC); + this.rs = rs; + this.target = target; + this.srcBasePath = srcBasePath; } /** - * Return the test that were found but not indexed because they did not - * have a main reactor. + * Push categories onto the stack as appropriate and skip directories that should be ignored. */ - public static Set getIgnoredTests(Target target, TestCategory category) { - return ignored.getTests(target, category); - } - - public static String getCoverageReport(Target target, TestCategory category) { - StringBuilder s = new StringBuilder(); - Set ignored = TestRegistry.getIgnoredTests(target, 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"); + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { + for (String ignored : IGNORED_DIRECTORIES) { + if (dir.getFileName().toString().equalsIgnoreCase(ignored)) { + return SKIP_SUBTREE; } + } + for (TestCategory category : TestCategory.values()) { + var relativePathName = srcBasePath.relativize(dir).toString(); + if (relativePathName.equalsIgnoreCase(category.getPath())) { + stack.push(category); + } + } + return CONTINUE; + } - Set own = getRegisteredTests(target, category, false); - if (category.isCommon) { - Set all = allTargets.get(category); - s.append("\n").append(TestBase.THIN_LINE); - s.append("Covered: ").append(own.size()).append("/").append(all.size()).append("\n"); - s.append(TestBase.THIN_LINE); - int missing = all.size() - own.size(); - if (missing > 0) { - all.stream().filter(test -> !own.contains(test)) - .forEach(test -> s.append("Missing: ").append(test).append("\n")); - } - } else { - s.append("\n").append(TestBase.THIN_LINE); - s.append("Covered: ").append(own.size()).append("/").append(own.size()).append("\n"); - s.append(TestBase.THIN_LINE); + /** Pop categories from the stack as appropriate. */ + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) { + for (TestCategory category : TestCategory.values()) { + var relativePathName = srcBasePath.relativize(dir).toString(); + if (relativePathName.equalsIgnoreCase(category.getPath())) { + this.stack.pop(); } - return s.toString(); + } + return CONTINUE; } /** - * FileVisitor implementation that maintains a stack to map found tests to - * 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 - * 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 + * Add test files to the registry if they end with ".lf", but only if they have a main reactor. */ - public static class TestDirVisitor extends SimpleFileVisitor { - - /** - * The stack of which the top element indicates the "current" category. - */ - protected Stack stack = new Stack<>(); - - /** - * The target that all encountered tests belong to. - */ - protected Target target; - - protected ResourceSet rs; - - protected Path srcBasePath; - - /** - * Create a new file visitor based on a given target. - * - * @param target The target that all encountered tests belong to. - * @param srcBasePath The test sources directory - */ - public TestDirVisitor(ResourceSet rs, Target target, Path srcBasePath) { - stack.push(TestCategory.GENERIC); - this.rs = rs; - this.target = target; - this.srcBasePath = srcBasePath; + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attr) { + if (attr.isRegularFile() && path.toString().endsWith(".lf")) { + // Try to parse the file. + Resource r = rs.getResource(URI.createFileURI(path.toFile().getAbsolutePath()), true); + // FIXME: issue warning if target doesn't match! + LFTest test = new LFTest(target, path); + + Iterator reactors = IteratorExtensions.filter(r.getAllContents(), Reactor.class); + + if (r.getErrors().isEmpty() + && !IteratorExtensions.exists(reactors, it -> it.isMain() || it.isFederated())) { + // If the test compiles but doesn't have a main reactor, + // _do not add the file_. We assume it is a library + // file. + ignored.getTests(this.target, this.stack.peek()).add(test); + return CONTINUE; } - /** - * Push categories onto the stack as appropriate and skip directories - * that should be ignored. - */ - @Override - public FileVisitResult preVisitDirectory(Path dir, - BasicFileAttributes attrs) { - for (String ignored : IGNORED_DIRECTORIES) { - if (dir.getFileName().toString().equalsIgnoreCase(ignored)) { - return SKIP_SUBTREE; - } - } - for (TestCategory category : TestCategory.values()) { - var relativePathName = srcBasePath.relativize(dir).toString(); - if (relativePathName.equalsIgnoreCase(category.getPath())) { - stack.push(category); - } - } - return CONTINUE; - } - - /** - * Pop categories from the stack as appropriate. - */ - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) { - for (TestCategory category : TestCategory.values()) { - var relativePathName = srcBasePath.relativize(dir).toString(); - if (relativePathName.equalsIgnoreCase(category.getPath())) { - this.stack.pop(); - } - } - return CONTINUE; - } - - /** - * Add test files to the registry if they end with ".lf", but only if they have a main reactor. - */ - @Override - public FileVisitResult visitFile(Path path, BasicFileAttributes attr) { - if (attr.isRegularFile() && path.toString().endsWith(".lf")) { - // Try to parse the file. - Resource r = rs.getResource(URI.createFileURI(path.toFile().getAbsolutePath()),true); - // FIXME: issue warning if target doesn't match! - LFTest test = new LFTest(target, path); - - Iterator reactors = IteratorExtensions.filter(r.getAllContents(), Reactor.class); - - if (r.getErrors().isEmpty() && !IteratorExtensions.exists(reactors, - it -> it.isMain() || it.isFederated())) { - // If the test compiles but doesn't have a main reactor, - // _do not add the file_. We assume it is a library - // file. - ignored.getTests(this.target, this.stack.peek()).add(test); - return CONTINUE; - } - - registered.getTests(this.target, this.stack.peek()).add(test); - } - return CONTINUE; - } + registered.getTests(this.target, this.stack.peek()).add(test); + } + return CONTINUE; + } - public void walk() throws IOException { - Files.walkFileTree(srcBasePath, this); - } + public void walk() throws IOException { + Files.walkFileTree(srcBasePath, this); } + } } diff --git a/org.lflang.tests/src/org/lflang/tests/TestUtils.java b/org.lflang.tests/src/org/lflang/tests/TestUtils.java index c868acb771..9e7cb4218a 100644 --- a/org.lflang.tests/src/org/lflang/tests/TestUtils.java +++ b/org.lflang.tests/src/org/lflang/tests/TestUtils.java @@ -31,7 +31,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.function.Predicate; - import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; @@ -41,139 +40,136 @@ */ public class TestUtils { - private static Matcher pathMatcher(String description, Predicate predicate) { - return new BaseMatcher<>() { - @Override - public boolean matches(Object item) { - return item instanceof Path && predicate.test((Path) item); - } - - @Override - public void describeTo(Description describer) { - describer.appendText(description); - } - }; + private static Matcher pathMatcher(String description, Predicate predicate) { + return new BaseMatcher<>() { + @Override + public boolean matches(Object item) { + return item instanceof Path && predicate.test((Path) item); + } + + @Override + public void describeTo(Description describer) { + describer.appendText(description); + } + }; + } + + public static Matcher isDirectory() { + return pathMatcher("is a directory", Files::isDirectory); + } + + public static Matcher isRegularFile() { + return pathMatcher("is a regular file", Files::isRegularFile); + } + + public static Matcher exists() { + return pathMatcher("exists", Files::exists); + } + + /** Builder for a directory. Useful to create a fake LF project. */ + public static class TempDirBuilder { + + private final Path curDir; + + private TempDirBuilder(Path path) { + this.curDir = path; + if (!Files.isDirectory(path)) { + throw new IllegalArgumentException("Not a directory: " + path); + } } - public static Matcher isDirectory() { - return pathMatcher("is a directory", Files::isDirectory); + public static TempDirBuilder dirBuilder(Path path) { + return new TempDirBuilder(path); } - public static Matcher isRegularFile() { - return pathMatcher("is a regular file", Files::isRegularFile); + /** Create a directory at the given path. Return a new temp dir builder for that subdir. */ + public TempDirBuilder cd(String relativePath) throws IOException { + Path relPath = Paths.get(relativePath); + if (relPath.isAbsolute()) { + throw new IllegalArgumentException("Should be a relative path: " + relativePath); + } + Path dir = curDir.resolve(relPath); + Files.createDirectories(dir); + return new TempDirBuilder(dir); } - public static Matcher exists() { - return pathMatcher("exists", Files::exists); + /** Create a directory at the given path. Return this instance. */ + public TempDirBuilder mkdirs(String relativePath) throws IOException { + Path relPath = Paths.get(relativePath); + if (relPath.isAbsolute()) { + throw new IllegalArgumentException("Should be a relative path: " + relativePath); + } + Path dir = curDir.resolve(relPath); + Files.createDirectories(dir); + return this; } - /** - * Builder for a directory. Useful to create a fake LF project. - */ - public static class TempDirBuilder { - - private final Path curDir; - - private TempDirBuilder(Path path) { - this.curDir = path; - if (!Files.isDirectory(path)) { - throw new IllegalArgumentException("Not a directory: " + path); - } - } - - public static TempDirBuilder dirBuilder(Path path) { - return new TempDirBuilder(path); - } - - /** Create a directory at the given path. Return a new temp dir builder for that subdir. */ - public TempDirBuilder cd(String relativePath) throws IOException { - Path relPath = Paths.get(relativePath); - if (relPath.isAbsolute()) { - throw new IllegalArgumentException("Should be a relative path: " + relativePath); - } - Path dir = curDir.resolve(relPath); - Files.createDirectories(dir); - return new TempDirBuilder(dir); - } - - /** Create a directory at the given path. Return this instance. */ - public TempDirBuilder mkdirs(String relativePath) throws IOException { - Path relPath = Paths.get(relativePath); - if (relPath.isAbsolute()) { - throw new IllegalArgumentException("Should be a relative path: " + relativePath); - } - Path dir = curDir.resolve(relPath); - Files.createDirectories(dir); - return this; - } - - /** Create a file in the given subpath. Return this instance. */ - public TempDirBuilder file(String relativePath, String contents) throws IOException { - Path relPath = Paths.get(relativePath); - if (relPath.isAbsolute()) { - throw new IllegalArgumentException("Should be a relative path: " + relativePath); - } - Path filePath = curDir.resolve(relPath); - Files.createDirectories(filePath.getParent()); - Files.writeString(filePath, contents); - return this; - } + /** Create a file in the given subpath. Return this instance. */ + public TempDirBuilder file(String relativePath, String contents) throws IOException { + Path relPath = Paths.get(relativePath); + if (relPath.isAbsolute()) { + throw new IllegalArgumentException("Should be a relative path: " + relativePath); + } + Path filePath = curDir.resolve(relPath); + Files.createDirectories(filePath.getParent()); + Files.writeString(filePath, contents); + return this; + } + } + + /** Builder for a directory. Useful to create a fake LF project. */ + public static class TempDirChecker { + + private final Path curDir; + + private TempDirChecker(Path path) { + this.curDir = path; + if (!Files.isDirectory(path)) { + throw new IllegalArgumentException("Not a directory: " + path); + } + } + + public static TempDirChecker dirChecker(Path path) { + return new TempDirChecker(path); + } + + /** Create a directory at the given path. Return a new temp dir builder for that subdir. */ + public TempDirBuilder cd(String relativePath) throws IOException { + Path relPath = Paths.get(relativePath); + if (relPath.isAbsolute()) { + throw new IllegalArgumentException("Should be a relative path: " + relativePath); + } + Path dir = curDir.resolve(relPath); + Files.createDirectories(dir); + return new TempDirBuilder(dir); } /** - * Builder for a directory. Useful to create a fake LF project. + * Check the contents of the file match the matcher. The file should be a UTF-8 encoded text + * file. Return this instance. */ - public static class TempDirChecker { - - private final Path curDir; - - private TempDirChecker(Path path) { - this.curDir = path; - if (!Files.isDirectory(path)) { - throw new IllegalArgumentException("Not a directory: " + path); - } - } - - public static TempDirChecker dirChecker(Path path) { - return new TempDirChecker(path); - } - - /** Create a directory at the given path. Return a new temp dir builder for that subdir. */ - public TempDirBuilder cd(String relativePath) throws IOException { - Path relPath = Paths.get(relativePath); - if (relPath.isAbsolute()) { - throw new IllegalArgumentException("Should be a relative path: " + relativePath); - } - Path dir = curDir.resolve(relPath); - Files.createDirectories(dir); - return new TempDirBuilder(dir); - } - - /** - * Check the contents of the file match the matcher. - * The file should be a UTF-8 encoded text file. Return - * this instance. - */ - public TempDirChecker checkContentsOf(String relativePath, Matcher contentsMatcher) throws IOException { - Path relPath = Paths.get(relativePath); - if (relPath.isAbsolute()) { - throw new IllegalArgumentException("Should be a relative path: " + relativePath); - } - Path filePath = curDir.resolve(relPath); - - assertThat(Files.readString(filePath), contentsMatcher); - return this; - } - - public TempDirChecker check(String relativePath, Matcher pathMatcher) throws IOException { - Path relPath = Paths.get(relativePath); - if (relPath.isAbsolute()) { - throw new IllegalArgumentException("Should be a relative path: " + relativePath); - } - Path filePath = curDir.resolve(relPath); - - assertThat(filePath, pathMatcher); - return this; - } + public TempDirChecker checkContentsOf( + String relativePath, Matcher contentsMatcher) throws IOException { + Path relPath = Paths.get(relativePath); + if (relPath.isAbsolute()) { + throw new IllegalArgumentException("Should be a relative path: " + relativePath); + } + Path filePath = curDir.resolve(relPath); + + assertThat(Files.readString(filePath), contentsMatcher); + return this; + } + + public TempDirChecker check(String relativePath, Matcher pathMatcher) + throws IOException { + Path relPath = Paths.get(relativePath); + if (relPath.isAbsolute()) { + throw new IllegalArgumentException("Should be a relative path: " + relativePath); + } + Path filePath = curDir.resolve(relPath); + + assertThat(filePath, pathMatcher); + return this; } + } } diff --git a/org.lflang.tests/src/org/lflang/tests/cli/CliToolTestFixture.java b/org.lflang.tests/src/org/lflang/tests/cli/CliToolTestFixture.java index 83e7817618..257da7ad06 100644 --- a/org.lflang.tests/src/org/lflang/tests/cli/CliToolTestFixture.java +++ b/org.lflang.tests/src/org/lflang/tests/cli/CliToolTestFixture.java @@ -32,130 +32,108 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.nio.file.Path; -import java.util.concurrent.Callable; -import java.util.function.Consumer; - import org.hamcrest.Matcher; -import org.opentest4j.AssertionFailedError; - import org.lflang.cli.Io; +import org.opentest4j.AssertionFailedError; /** - * Test utilities for a CLI tool, eg {@link org.lflang.cli.Lfc}, - * {@link org.lflang.cli.Lff}. + * Test utilities for a CLI tool, eg {@link org.lflang.cli.Lfc}, {@link org.lflang.cli.Lff}. * * @author Clément Fournier */ abstract class CliToolTestFixture { - /** - * Override to call the relevant main. - */ - protected abstract void runCliProgram(Io io, String[] args); - - - /** - * Run the tool with the given arguments, in the system - * working directory. - * - * @param args Arguments - * @return The execution result - */ - public ExecutionResult run(String... args) { - return run(Io.SYSTEM.getWd(), args); + /** Override to call the relevant main. */ + protected abstract void runCliProgram(Io io, String[] args); + + /** + * Run the tool with the given arguments, in the system working directory. + * + * @param args Arguments + * @return The execution result + */ + public ExecutionResult run(String... args) { + return run(Io.SYSTEM.getWd(), args); + } + + /** + * Run the tool with the given arguments, in the given working directory. + * + * @param wd working directory + * @param args Arguments + * @return The execution result + */ + public ExecutionResult run(Path wd, String... args) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + + Io testIo = new Io(new PrintStream(err), new PrintStream(out), wd); + int exitCode = testIo.fakeSystemExit(io -> runCliProgram(io, args)); + + return new ExecutionResult(out, err, exitCode); + } + + /** + * The result of an execution of a CLI program like LFC. + * + * @param out Output stream + * @param err Error stream + * @param exitCode Exit code of the process + */ + record ExecutionResult(ByteArrayOutputStream out, ByteArrayOutputStream err, int exitCode) { + + public String getOut() { + return out.toString(); } - /** - * Run the tool with the given arguments, in the given - * working directory. - * - * @param wd working directory - * @param args Arguments - * @return The execution result - */ - public ExecutionResult run(Path wd, String... args) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ByteArrayOutputStream err = new ByteArrayOutputStream(); - - Io testIo = new Io( - new PrintStream(err), - new PrintStream(out), - wd - ); - int exitCode = testIo.fakeSystemExit(io -> runCliProgram(io, args)); - - return new ExecutionResult(out, err, exitCode); + public String getErr() { + return err.toString(); } - /** - * The result of an execution of a CLI program like LFC. - * - * @param out Output stream - * @param err Error stream - * @param exitCode Exit code of the process - */ - record ExecutionResult( - ByteArrayOutputStream out, - ByteArrayOutputStream err, - int exitCode - ) { - - public String getOut() { - return out.toString(); - } - - public String getErr() { - return err.toString(); - } - - - public void checkOk() { - assertEquals(0, exitCode); - } + public void checkOk() { + assertEquals(0, exitCode); + } - public void checkFailed() { - assertTrue(exitCode > 0); - } + public void checkFailed() { + assertTrue(exitCode > 0); + } - public void checkNoErrorOutput() { - checkStdErr(equalTo("")); - } + public void checkNoErrorOutput() { + checkStdErr(equalTo("")); + } - public void checkStdOut(Matcher matcher) { - assertThat(getOut(), matcher); - } + public void checkStdOut(Matcher matcher) { + assertThat(getOut(), matcher); + } - public void checkStdErr(Matcher matcher) { - assertThat(getErr(), matcher); - } + public void checkStdErr(Matcher matcher) { + assertThat(getErr(), matcher); + } - /** - * Use this method to wrap assertions. - */ - public void verify(ThrowingConsumer actions) { - try { - actions.accept(this); - } catch (Exception | AssertionFailedError e) { - System.out.println("TEST FAILED"); - System.out.println("> Return code: " + exitCode); - System.out.println("> Standard output -------------------------"); - System.err.println(out.toString()); - System.out.println("> Standard error --------------------------"); - System.err.println(err.toString()); - System.out.println("> -----------------------------------------"); - - if (e instanceof Exception) { - throw new AssertionFailedError("Expected no exception to be thrown", e); - } - throw (AssertionFailedError) e; - } + /** Use this method to wrap assertions. */ + public void verify(ThrowingConsumer actions) { + try { + actions.accept(this); + } catch (Exception | AssertionFailedError e) { + System.out.println("TEST FAILED"); + System.out.println("> Return code: " + exitCode); + System.out.println("> Standard output -------------------------"); + System.err.println(out.toString()); + System.out.println("> Standard error --------------------------"); + System.err.println(err.toString()); + System.out.println("> -----------------------------------------"); + + if (e instanceof Exception) { + throw new AssertionFailedError("Expected no exception to be thrown", e); } + throw (AssertionFailedError) e; + } + } + @FunctionalInterface + interface ThrowingConsumer { - @FunctionalInterface - interface ThrowingConsumer { - - void accept(T t) throws Exception; - } + void accept(T t) throws Exception; } + } } 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 d16193faad..d9e81a0e68 100644 --- a/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java +++ b/org.lflang.tests/src/org/lflang/tests/cli/LfcCliTest.java @@ -33,15 +33,12 @@ import static org.lflang.tests.TestUtils.isRegularFile; import com.google.inject.Injector; - import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.util.Properties; - import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; - import org.lflang.LocalStrings; import org.lflang.cli.Io; import org.lflang.cli.Lfc; @@ -54,16 +51,18 @@ */ public class LfcCliTest { - LfcTestFixture lfcTester = new LfcTestFixture(); + LfcTestFixture lfcTester = new LfcTestFixture(); - static final String LF_PYTHON_FILE = """ + static final String LF_PYTHON_FILE = + """ target Python main reactor { reaction(startup) {==} } """; - static final String JSON_STRING = """ + static final String JSON_STRING = + """ { "src": "src/File.lf", "out": "src", @@ -87,230 +86,262 @@ public class LfcCliTest { } """; - @Test - public void testHelpArg() { - lfcTester.run("--help", "--version") - .verify(result -> { - result.checkOk(); - result.checkNoErrorOutput(); - result.checkStdOut(containsString("Usage: lfc")); + @Test + public void testHelpArg() { + lfcTester + .run("--help", "--version") + .verify( + result -> { + result.checkOk(); + result.checkNoErrorOutput(); + result.checkStdOut(containsString("Usage: lfc")); }); - } - - @Test - public void testMutuallyExclusiveCliArgs() { - lfcTester.run("File.lf", "--json", JSON_STRING) - .verify(result -> { - result.checkStdErr(containsString( - "are mutually exclusive (specify only one)")); - result.checkFailed(); + } + + @Test + public void testMutuallyExclusiveCliArgs() { + lfcTester + .run("File.lf", "--json", JSON_STRING) + .verify( + result -> { + result.checkStdErr(containsString("are mutually exclusive (specify only one)")); + result.checkFailed(); }); - lfcTester.run("File.lf", "--json-file", "test.json") - .verify(result -> { - result.checkStdErr(containsString( - "are mutually exclusive (specify only one)")); - result.checkFailed(); + lfcTester + .run("File.lf", "--json-file", "test.json") + .verify( + result -> { + result.checkStdErr(containsString("are mutually exclusive (specify only one)")); + result.checkFailed(); }); - lfcTester.run("--json", JSON_STRING, "--json-file", "test.json") - .verify(result -> { - result.checkStdErr(containsString( - "are mutually exclusive (specify only one)")); - result.checkFailed(); + lfcTester + .run("--json", JSON_STRING, "--json-file", "test.json") + .verify( + result -> { + result.checkStdErr(containsString("are mutually exclusive (specify only one)")); + result.checkFailed(); }); - } - - @Test - public void testVersion() { - lfcTester.run("--version") - .verify(result -> { - result.checkOk(); - result.checkNoErrorOutput(); - result.checkStdOut(equalTo( - "lfc " + LocalStrings.VERSION + System.lineSeparator())); + } + + @Test + public void testVersion() { + lfcTester + .run("--version") + .verify( + result -> { + result.checkOk(); + result.checkNoErrorOutput(); + result.checkStdOut(equalTo("lfc " + LocalStrings.VERSION + System.lineSeparator())); }); - } - - - @Test - public void testWrongCliArg() { - lfcTester.run("--notanargument", "File.lf") - .verify(result -> { - result.checkStdErr(containsString("Unknown option: '--notanargument'")); - result.checkFailed(); + } + + @Test + public void testWrongCliArg() { + lfcTester + .run("--notanargument", "File.lf") + .verify( + result -> { + result.checkStdErr(containsString("Unknown option: '--notanargument'")); + result.checkFailed(); }); - } - - @Test - public void testInvalidArgs(@TempDir Path tempDir) throws IOException { - dirBuilder(tempDir).file("src/File.lf", LF_PYTHON_FILE); - LfcOneShotTestFixture fixture = new LfcOneShotTestFixture(); - - // Invalid src file. - fixture.run(tempDir, "unknown.lf") - .verify(result -> { - result.checkStdErr(containsString("No such file or directory.")); - result.checkFailed(); + } + + @Test + public void testInvalidArgs(@TempDir Path tempDir) throws IOException { + dirBuilder(tempDir).file("src/File.lf", LF_PYTHON_FILE); + LfcOneShotTestFixture fixture = new LfcOneShotTestFixture(); + + // Invalid src file. + fixture + .run(tempDir, "unknown.lf") + .verify( + result -> { + result.checkStdErr(containsString("No such file or directory.")); + result.checkFailed(); }); - // Invalid output path. - fixture.run(tempDir, "--output-path", "unknown/output/path", "src/File.lf") - .verify(result -> { - result.checkStdErr(containsString("Output location does not exist.")); - result.checkFailed(); + // Invalid output path. + fixture + .run(tempDir, "--output-path", "unknown/output/path", "src/File.lf") + .verify( + result -> { + result.checkStdErr(containsString("Output location does not exist.")); + result.checkFailed(); }); - // Invalid build type. - fixture.run(tempDir, "--build-type", "unknown-build-type", "src/File.lf") - .verify(result -> { - result.checkStdErr(containsString("Invalid build type.")); - result.checkFailed(); + // Invalid build type. + fixture + .run(tempDir, "--build-type", "unknown-build-type", "src/File.lf") + .verify( + result -> { + result.checkStdErr(containsString("Invalid build type.")); + result.checkFailed(); }); - // Invalid logging level. - fixture.run(tempDir, "--logging", "unknown_level", "src/File.lf") - .verify(result -> { - result.checkStdErr(containsString("Invalid log level.")); - result.checkFailed(); + // Invalid logging level. + fixture + .run(tempDir, "--logging", "unknown_level", "src/File.lf") + .verify( + result -> { + result.checkStdErr(containsString("Invalid log level.")); + result.checkFailed(); }); - // Invalid RTI path. - fixture.run(tempDir, "--rti", "unknown/rti/path", "src/File.lf") - .verify(result -> { - result.checkStdErr(containsString("Invalid RTI path.")); - result.checkFailed(); + // Invalid RTI path. + fixture + .run(tempDir, "--rti", "unknown/rti/path", "src/File.lf") + .verify( + result -> { + result.checkStdErr(containsString("Invalid RTI path.")); + result.checkFailed(); }); - // Invalid scheduler. - fixture.run(tempDir, "--scheduler", "unknown-scheduler", "src/File.lf") - .verify(result -> { - result.checkStdErr(containsString("Invalid scheduler.")); - result.checkFailed(); + // Invalid scheduler. + fixture + .run(tempDir, "--scheduler", "unknown-scheduler", "src/File.lf") + .verify( + result -> { + result.checkStdErr(containsString("Invalid scheduler.")); + result.checkFailed(); }); - // Invalid workers. - fixture.run(tempDir, "--workers", "notaninteger", "src/File.lf") - .verify(result -> { - result.checkStdErr(containsString("Invalid value for option '--workers'")); - result.checkStdErr(containsString("is not an int")); - result.checkFailed(); + // Invalid workers. + fixture + .run(tempDir, "--workers", "notaninteger", "src/File.lf") + .verify( + result -> { + result.checkStdErr(containsString("Invalid value for option '--workers'")); + result.checkStdErr(containsString("is not an int")); + result.checkFailed(); }); - - } - - @Test - public void testGenInSrcDir(@TempDir Path tempDir) throws IOException { - dirBuilder(tempDir).file("src/File.lf", LF_PYTHON_FILE); - - lfcTester.run(tempDir, "src/File.lf", "--no-compile") - .verify(result -> { - result.checkOk(); - dirChecker(tempDir) - .check("src-gen", isDirectory()) - .check("bin", isDirectory()) - .check("src-gen/File/File.py", isRegularFile()); + } + + @Test + public void testGenInSrcDir(@TempDir Path tempDir) throws IOException { + dirBuilder(tempDir).file("src/File.lf", LF_PYTHON_FILE); + + lfcTester + .run(tempDir, "src/File.lf", "--no-compile") + .verify( + result -> { + result.checkOk(); + dirChecker(tempDir) + .check("src-gen", isDirectory()) + .check("bin", isDirectory()) + .check("src-gen/File/File.py", isRegularFile()); }); - } - - // Helper method for comparing argument values in tests testGeneratorArgs, - // testGeneratorArgsJsonString and testGeneratorArgsJsonFile. - public void verifyGeneratorArgs(Path tempDir, String[] args) { - LfcOneShotTestFixture fixture = new LfcOneShotTestFixture(); - - fixture.run(tempDir, args) - .verify(result -> { - // Don't validate execution because args are dummy args. - Properties properties = fixture.lfc.getGeneratorArgs(); - assertEquals(properties.getProperty(BuildParm.BUILD_TYPE.getKey()), "Release"); - assertEquals(properties.getProperty(BuildParm.CLEAN.getKey()), "true"); - assertEquals(properties.getProperty(BuildParm.TARGET_COMPILER.getKey()), "gcc"); - assertEquals(properties.getProperty(BuildParm.EXTERNAL_RUNTIME_PATH.getKey()), "src"); - assertEquals(properties.getProperty(BuildParm.LOGGING.getKey()), "info"); - assertEquals(properties.getProperty(BuildParm.LINT.getKey()), "true"); - assertEquals(properties.getProperty(BuildParm.NO_COMPILE.getKey()), "true"); - assertEquals(properties.getProperty(BuildParm.PRINT_STATISTICS.getKey()), "true"); - assertEquals(properties.getProperty(BuildParm.QUIET.getKey()), "true"); - 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"); - assertEquals(properties.getProperty(BuildParm.WORKERS.getKey()), "1"); + } + + // Helper method for comparing argument values in tests testGeneratorArgs, + // testGeneratorArgsJsonString and testGeneratorArgsJsonFile. + public void verifyGeneratorArgs(Path tempDir, String[] args) { + LfcOneShotTestFixture fixture = new LfcOneShotTestFixture(); + + fixture + .run(tempDir, args) + .verify( + result -> { + // Don't validate execution because args are dummy args. + Properties properties = fixture.lfc.getGeneratorArgs(); + assertEquals(properties.getProperty(BuildParm.BUILD_TYPE.getKey()), "Release"); + assertEquals(properties.getProperty(BuildParm.CLEAN.getKey()), "true"); + assertEquals(properties.getProperty(BuildParm.TARGET_COMPILER.getKey()), "gcc"); + assertEquals(properties.getProperty(BuildParm.EXTERNAL_RUNTIME_PATH.getKey()), "src"); + assertEquals(properties.getProperty(BuildParm.LOGGING.getKey()), "info"); + assertEquals(properties.getProperty(BuildParm.LINT.getKey()), "true"); + assertEquals(properties.getProperty(BuildParm.NO_COMPILE.getKey()), "true"); + assertEquals(properties.getProperty(BuildParm.PRINT_STATISTICS.getKey()), "true"); + assertEquals(properties.getProperty(BuildParm.QUIET.getKey()), "true"); + 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"); + assertEquals(properties.getProperty(BuildParm.WORKERS.getKey()), "1"); }); + } + + @Test + public void testGeneratorArgs(@TempDir Path tempDir) throws IOException { + TempDirBuilder dir = dirBuilder(tempDir); + dir.file("src/File.lf", LF_PYTHON_FILE); + dir.mkdirs("path//to/rti"); + + String[] args = { + "src/File.lf", + "--output-path", + "src", + "--build-type", + "Release", + "--clean", + "--target-compiler", + "gcc", + "--external-runtime-path", + "src", + "--federated", + "--logging", + "info", + "--lint", + "--no-compile", + "--print-statistics", + "--quiet", + "--rti", + "path/to/rti", + "--runtime-version", + "rs", + "--scheduler", + "GEDF_NP", + "--threading", + "false", + "--workers", + "1", + }; + verifyGeneratorArgs(tempDir, args); + } + + @Test + public void testGeneratorArgsJsonString(@TempDir Path tempDir) throws IOException { + TempDirBuilder dir = dirBuilder(tempDir); + dir.file("src/File.lf", LF_PYTHON_FILE); + dir.mkdirs("path/to/rti"); + + String[] args = {"--json", JSON_STRING}; + verifyGeneratorArgs(tempDir, args); + } + + @Test + public void testGeneratorArgsJsonFile(@TempDir Path tempDir) throws IOException { + TempDirBuilder dir = dirBuilder(tempDir); + dir.file("src/File.lf", LF_PYTHON_FILE); + dir.file("src/test.json", JSON_STRING); + dir.mkdirs("path/to/rti"); + + String[] args = {"--json-file", "src/test.json"}; + verifyGeneratorArgs(tempDir, args); + } + + static class LfcTestFixture extends CliToolTestFixture { + + @Override + protected void runCliProgram(Io io, String[] args) { + Lfc.main(io, args); } + } - @Test - public void testGeneratorArgs(@TempDir Path tempDir) - throws IOException { - TempDirBuilder dir = dirBuilder(tempDir); - dir.file("src/File.lf", LF_PYTHON_FILE); - dir.mkdirs("path//to/rti"); - - String[] args = { - "src/File.lf", - "--output-path", "src", - "--build-type", "Release", - "--clean", - "--target-compiler", "gcc", - "--external-runtime-path", "src", - "--federated", - "--logging", "info", - "--lint", - "--no-compile", - "--print-statistics", - "--quiet", - "--rti", "path/to/rti", - "--runtime-version", "rs", - "--scheduler", "GEDF_NP", - "--threading", "false", - "--workers", "1", - }; - verifyGeneratorArgs(tempDir, args); - } - - @Test - public void testGeneratorArgsJsonString(@TempDir Path tempDir) - throws IOException { - TempDirBuilder dir = dirBuilder(tempDir); - dir.file("src/File.lf", LF_PYTHON_FILE); - dir.mkdirs("path/to/rti"); + static class LfcOneShotTestFixture extends CliToolTestFixture { - String[] args = {"--json", JSON_STRING}; - verifyGeneratorArgs(tempDir, args); - } - - @Test - public void testGeneratorArgsJsonFile(@TempDir Path tempDir) - throws IOException { - TempDirBuilder dir = dirBuilder(tempDir); - dir.file("src/File.lf", LF_PYTHON_FILE); - dir.file("src/test.json", JSON_STRING); - dir.mkdirs("path/to/rti"); - - String[] args = {"--json-file", "src/test.json"}; - verifyGeneratorArgs(tempDir, args); - } + private Lfc lfc; - static class LfcTestFixture extends CliToolTestFixture { - - @Override - protected void runCliProgram(Io io, String[] args) { - Lfc.main(io, args); - } - } - - static class LfcOneShotTestFixture extends CliToolTestFixture { - - private Lfc lfc; - - @Override - protected void runCliProgram(Io io, String[] args) { - // Injector used to obtain Main instance. - final Injector injector = Lfc.getInjector("lfc", io); - // Main instance. - this.lfc = injector.getInstance(Lfc.class); - lfc.doExecute(io, args); - } + @Override + protected void runCliProgram(Io io, String[] args) { + // Injector used to obtain Main instance. + final Injector injector = Lfc.getInjector("lfc", io); + // Main instance. + this.lfc = injector.getInstance(Lfc.class); + lfc.doExecute(io, args); } + } } 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 16a4e9b32d..a9da20239f 100644 --- a/org.lflang.tests/src/org/lflang/tests/cli/LffCliTest.java +++ b/org.lflang.tests/src/org/lflang/tests/cli/LffCliTest.java @@ -26,153 +26,136 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.MatcherAssert.assertThat; 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; - import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; - import org.lflang.LocalStrings; -import org.lflang.tests.cli.CliToolTestFixture.ExecutionResult; import org.lflang.cli.Io; import org.lflang.cli.Lff; +import org.lflang.tests.cli.CliToolTestFixture.ExecutionResult; /** * @author Clément Fournier */ public class LffCliTest { - private static final String FILE_BEFORE_REFORMAT = """ + private static final String FILE_BEFORE_REFORMAT = + """ target Python; main reactor { reaction(startup) {= =} } """; - private static final String FILE_AFTER_REFORMAT = """ + private static final String FILE_AFTER_REFORMAT = + """ target Python - + main reactor { reaction(startup) {= =} } """; - LffTestFixture lffTester = new LffTestFixture(); + LffTestFixture lffTester = new LffTestFixture(); + @Test + public void testHelpArg() { + ExecutionResult result = lffTester.run("--help", "--version"); + result.checkOk(); + result.checkNoErrorOutput(); + result.checkStdOut(containsString("Usage: lff")); + } - @Test - public void testHelpArg() { - ExecutionResult result = lffTester.run("--help", "--version"); - result.checkOk(); - result.checkNoErrorOutput(); - result.checkStdOut(containsString("Usage: lff")); - } + @Test + public void testVersion() { + ExecutionResult result = lffTester.run("--version"); + result.checkOk(); + result.checkNoErrorOutput(); + result.checkStdOut(equalTo("lff " + LocalStrings.VERSION + System.lineSeparator())); + } - @Test - public void testVersion() { - ExecutionResult result = lffTester.run("--version"); - result.checkOk(); - result.checkNoErrorOutput(); - result.checkStdOut( - equalTo("lff " + LocalStrings.VERSION + System.lineSeparator())); - } + @Test + public void testWrongCliArg() { + ExecutionResult result = lffTester.run("--notanargument", "File.lf"); + result.checkStdErr(containsString("Unknown option: '--notanargument'")); + result.checkFailed(); + } + @Test + public void testFormatSingleFileInPlace(@TempDir Path tempDir) throws IOException { + dirBuilder(tempDir).file("src/File.lf", FILE_BEFORE_REFORMAT); - @Test - public void testWrongCliArg() { - ExecutionResult result = lffTester.run("--notanargument", "File.lf"); - result.checkStdErr(containsString("Unknown option: '--notanargument'")); - result.checkFailed(); - } + ExecutionResult result = lffTester.run(tempDir, "src/File.lf"); - @Test - public void testFormatSingleFileInPlace(@TempDir Path tempDir) throws IOException { - dirBuilder(tempDir).file("src/File.lf", FILE_BEFORE_REFORMAT); + result.checkOk(); - ExecutionResult result = lffTester.run(tempDir, "src/File.lf"); + dirChecker(tempDir).checkContentsOf("src/File.lf", equalTo(FILE_AFTER_REFORMAT)); + } - result.checkOk(); + @Test + public void testFormatDirectory(@TempDir Path tempDir) throws IOException { + dirBuilder(tempDir).file("src/File.lf", FILE_BEFORE_REFORMAT); - dirChecker(tempDir).checkContentsOf("src/File.lf", equalTo(FILE_AFTER_REFORMAT)); - } + ExecutionResult result = lffTester.run(tempDir, "src"); + result.checkOk(); - @Test - public void testFormatDirectory(@TempDir Path tempDir) throws IOException { - dirBuilder(tempDir).file("src/File.lf", FILE_BEFORE_REFORMAT); + dirChecker(tempDir).checkContentsOf("src/File.lf", equalTo(FILE_AFTER_REFORMAT)); + } - ExecutionResult result = lffTester.run(tempDir, "src"); + @Test + public void testFormatDirectoryVerbose(@TempDir Path tempDir) throws IOException { + dirBuilder(tempDir).file("src/File.lf", FILE_BEFORE_REFORMAT); - result.checkOk(); + ExecutionResult result = lffTester.run(tempDir, "-v", "src"); - dirChecker(tempDir).checkContentsOf("src/File.lf", equalTo(FILE_AFTER_REFORMAT)); - } + result.checkOk(); + result.checkStdOut(containsString("Formatted src" + File.separator + "File.lf")); + dirChecker(tempDir).checkContentsOf("src/File.lf", equalTo(FILE_AFTER_REFORMAT)); + } - @Test - public void testFormatDirectoryVerbose(@TempDir Path tempDir) throws IOException { - dirBuilder(tempDir).file("src/File.lf", FILE_BEFORE_REFORMAT); + @Test + public void testNoSuchFile(@TempDir Path tempDir) { + ExecutionResult result = lffTester.run(tempDir, "-v", "nosuchdir"); - ExecutionResult result = lffTester.run(tempDir, "-v", "src"); + result.checkFailed(); - result.checkOk(); + result.checkStdErr( + containsString(tempDir.resolve("nosuchdir") + ": No such file or directory.")); + } - result.checkStdOut(containsString( - "Formatted src" + File.separator + "File.lf")); - dirChecker(tempDir).checkContentsOf("src/File.lf", equalTo(FILE_AFTER_REFORMAT)); - } + @Test + public void testOutputPathWithDirArg(@TempDir Path tempDir) throws IOException { + dirBuilder(tempDir).file("src/a/File.lf", FILE_BEFORE_REFORMAT).mkdirs("out/"); - @Test - public void testNoSuchFile(@TempDir Path tempDir) { - ExecutionResult result = lffTester.run(tempDir, "-v", "nosuchdir"); + ExecutionResult result = lffTester.run(tempDir, "src", "--output-path", "out"); - result.checkFailed(); + result.checkOk(); - result.checkStdErr(containsString( - tempDir.resolve("nosuchdir") + ": No such file or directory.")); - } + dirChecker(tempDir).checkContentsOf("out/a/File.lf", equalTo(FILE_AFTER_REFORMAT)); + } + @Test + public void testOutputPathWithFileArg(@TempDir Path tempDir) throws IOException { + dirBuilder(tempDir).file("src/a/File.lf", FILE_BEFORE_REFORMAT).mkdirs("out/"); - @Test - public void testOutputPathWithDirArg(@TempDir Path tempDir) throws IOException { - dirBuilder(tempDir) - .file("src/a/File.lf", FILE_BEFORE_REFORMAT) - .mkdirs("out/"); + ExecutionResult result = lffTester.run(tempDir, "src/a/File.lf", "--output-path", "out"); - ExecutionResult result = lffTester.run(tempDir, "src", "--output-path", "out"); + result.checkOk(); - result.checkOk(); + dirChecker(tempDir).checkContentsOf("out/File.lf", equalTo(FILE_AFTER_REFORMAT)); + } - dirChecker(tempDir) - .checkContentsOf("out/a/File.lf", equalTo(FILE_AFTER_REFORMAT)); - } - - @Test - public void testOutputPathWithFileArg(@TempDir Path tempDir) throws IOException { - dirBuilder(tempDir) - .file("src/a/File.lf", FILE_BEFORE_REFORMAT) - .mkdirs("out/"); - - ExecutionResult result = lffTester.run(tempDir, "src/a/File.lf", "--output-path", "out"); + static class LffTestFixture extends CliToolTestFixture { - result.checkOk(); - - dirChecker(tempDir) - .checkContentsOf("out/File.lf", equalTo(FILE_AFTER_REFORMAT)); + @Override + protected void runCliProgram(Io io, String[] args) { + Lff.main(io, args); } - - - static class LffTestFixture extends CliToolTestFixture { - - - @Override - protected void runCliProgram(Io io, String[] args) { - Lff.main(io, args); - } - } - + } } diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/EquivalenceUnitTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/EquivalenceUnitTests.java index 931a0edae0..5d4fe9940c 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/EquivalenceUnitTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/EquivalenceUnitTests.java @@ -5,7 +5,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; - import org.lflang.ast.IsEqual; import org.lflang.lf.Model; import org.lflang.tests.LFInjectorProvider; @@ -15,10 +14,10 @@ @InjectWith(LFInjectorProvider.class) public class EquivalenceUnitTests { - @Test - public void testSimple() { - assertSelfEquivalence( - """ + @Test + public void testSimple() { + assertSelfEquivalence( + """ target C reactor Destination { @@ -26,14 +25,13 @@ public void testSimple() { input in: int state last_invoked: tag_t({= NEVER_TAG_INITIALIZER =}) } - """ - ); - } + """); + } - @Test - public void testCodeExprEqItselfModuloIndent() { - assertEquivalent( - """ + @Test + public void testCodeExprEqItselfModuloIndent() { + assertEquivalent( + """ target C reactor Destination { state s: tag_t({= @@ -41,67 +39,56 @@ public void testCodeExprEqItselfModuloIndent() { =}) } """, - """ + """ target C reactor Destination { state s: tag_t({= NEVER_TAG_INITIALIZER =}) } - """ - ); - } + """); + } - @Test - public void testInitializerParensAreIrrelevantInAssignment() { - assertEquivalent( - """ + @Test + public void testInitializerParensAreIrrelevantInAssignment() { + assertEquivalent( + """ target C reactor A(a: int(0)) {} main reactor { a = new A(a = 1) } """, - """ + """ target C reactor A(a: int(0)) {} main reactor { a = new A(a = (1)) // mind the parens here. } - """ - ); - } - - private void assertSelfEquivalence(String input) { - Model inputModel = LfParsingUtil.parseValidModel("test input", input); - // need to parse twice otherwise they are trivially equivalent - // because they're the same object. - Model otherModel = LfParsingUtil.parseValidModel("other", input); - - // test equivalence of the models. - Assertions.assertTrue( - new IsEqual(inputModel).doSwitch(otherModel), - String.format( - "Model is not equivalent to itself. Source:%n%s", - input - ) - ); - } + """); + } - private void assertEquivalent(String input, String other) { - Model inputModel = LfParsingUtil.parseValidModel("test input", input); - Model outputModel = LfParsingUtil.parseValidModel("other", other); + private void assertSelfEquivalence(String input) { + Model inputModel = LfParsingUtil.parseValidModel("test input", input); + // need to parse twice otherwise they are trivially equivalent + // because they're the same object. + Model otherModel = LfParsingUtil.parseValidModel("other", input); - // test equivalence of the models. - Assertions.assertTrue( - new IsEqual(inputModel).doSwitch(outputModel), - String.format( - "The reformatted model is not equivalent to the original file.%n" - + "Input file:%n%s%n%n" - + "Comparand file:%n%s%n%n", - input, - other - ) - ); - } + // test equivalence of the models. + Assertions.assertTrue( + new IsEqual(inputModel).doSwitch(otherModel), + String.format("Model is not equivalent to itself. Source:%n%s", input)); + } + private void assertEquivalent(String input, String other) { + Model inputModel = LfParsingUtil.parseValidModel("test input", input); + Model outputModel = LfParsingUtil.parseValidModel("other", other); + // test equivalence of the models. + Assertions.assertTrue( + new IsEqual(inputModel).doSwitch(outputModel), + String.format( + "The reformatted model is not equivalent to the original file.%n" + + "Input file:%n%s%n%n" + + "Comparand file:%n%s%n%n", + input, other)); + } } 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 d7862e5d25..79aa6ac1f2 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/FormattingUnitTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/FormattingUnitTests.java @@ -5,9 +5,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; - import org.lflang.ast.FormattingUtils; -import org.lflang.ast.IsEqual; import org.lflang.lf.Model; import org.lflang.tests.LFInjectorProvider; import org.lflang.tests.LfParsingUtil; @@ -16,26 +14,25 @@ @InjectWith(LFInjectorProvider.class) public class FormattingUnitTests { - @Test - public void testSimple() { - assertFormatsTo( - """ + @Test + public void testSimple() { + assertFormatsTo( + """ target C reactor Main{ } """, - """ + """ target C - + reactor Main { } - """ - ); - } + """); + } - @Test - public void testAssignments() { - assertFormatsTo( - """ + @Test + public void testAssignments() { + assertFormatsTo( + """ target C reactor Destination { @@ -44,7 +41,7 @@ public void testAssignments() { state last_invoked: tag_t({= NEVER_TAG_INITIALIZER =}) } """, - """ + """ target C reactor Destination { @@ -52,14 +49,13 @@ public void testAssignments() { input in: int state last_invoked: tag_t = {= NEVER_TAG_INITIALIZER =} } - """ - ); - } + """); + } - @Test - public void testState() { - assertFormatsTo( - """ + @Test + public void testState() { + assertFormatsTo( + """ target Python reactor Destination { @@ -68,7 +64,7 @@ public void testState() { state list_init(1,2) // this syntax is deprecated } """, - """ + """ target Python reactor Destination { @@ -76,14 +72,13 @@ state list_init(1,2) // this syntax is deprecated state no_init: tag_t state list_init(1, 2) # this syntax is deprecated } - """ - ); - } + """); + } - @Test - public void testCppInits() { - assertIsFormatted( - """ + @Test + public void testCppInits() { + assertIsFormatted( + """ target Cpp reactor Destination { @@ -94,23 +89,17 @@ public void testCppInits() { state brace: std::vector{1, 2} state paren_list: std::vector(1, 2) } - """ - ); - } - - private void assertIsFormatted(String input) { - assertFormatsTo(input, input); - } - - private void assertFormatsTo(String input, String expectedOutput) { - Model inputModel = LfParsingUtil.parseValidModel("test input", input); - String formattedString = FormattingUtils.render(inputModel); - Assertions.assertEquals( - expectedOutput, - formattedString, - "Formatted output is different from what was expected" - ); - } - - + """); + } + + private void assertIsFormatted(String input) { + assertFormatsTo(input, input); + } + + private void assertFormatsTo(String input, String expectedOutput) { + Model inputModel = LfParsingUtil.parseValidModel("test input", input); + String formattedString = FormattingUtils.render(inputModel); + Assertions.assertEquals( + expectedOutput, formattedString, "Formatted output is different from what was expected"); + } } 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 b3e7814f96..38387ac8a4 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LetInferenceTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LetInferenceTests.java @@ -1,34 +1,33 @@ -package org.lflang.tests.compiler;/* Parsing unit tests. */ +package org.lflang.tests.compiler; /* Parsing unit tests. */ /************* - Copyright (c) 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) 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. ***************/ import static org.lflang.ast.ASTUtils.toDefinition; import javax.inject.Inject; - import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.testing.InjectWith; @@ -37,11 +36,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; - -import org.lflang.ast.ASTUtils; import org.lflang.DefaultErrorReporter; import org.lflang.TimeUnit; import org.lflang.TimeValue; +import org.lflang.ast.ASTUtils; import org.lflang.ast.DelayedConnectionTransformation; import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactorInstance; @@ -49,7 +47,6 @@ import org.lflang.generator.c.CTypes; import org.lflang.lf.Instantiation; import org.lflang.lf.LfFactory; - import org.lflang.lf.Model; import org.lflang.lf.Reactor; import org.lflang.tests.LFInjectorProvider; @@ -58,89 +55,93 @@ @InjectWith(LFInjectorProvider.class) /** - * Test for getting minimum delay in reactions. - * Checking the actions and port's delay,then get the minimum reaction delay. + * Test for getting minimum delay in reactions. Checking the actions and port's delay,then get the + * minimum reaction delay. + * * @author Wonseo Choi * @author Yunsang Cho * @author Marten Lohstroh * @author Hokeun Kim */ -class LetInferenceTest { - - @Inject - ParseHelper parser; - - - @Test - public void testLet() throws Exception { - Model model = parser.parse(String.join( - System.getProperty("line.separator"), - "target C;", - "main reactor {", - " ramp = new Ramp();", - " print = new Print();", - " print2 = new Print();", - " ramp.y -> print.x after 20 msec;", - " ramp.y -> print2.x after 30 msec;", - "}", - "reactor Ramp {", - " logical action a(60 msec):int;", - " logical action b(100 msec):int;", - " input x:int;", - " output y:int;", - " output z:int;", - " reaction(startup) -> y, z, a, b{=", - " =}", - "}", - "reactor Print {", - " input x:int;", - " output z:int;", - " reaction(x) -> z {=", - " =}", - "}" - )); - - Assertions.assertNotNull(model); - 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)); - - Assertions.assertTrue(model.eResource().getErrors().isEmpty(), - "Encountered unexpected error while parsing: " + - model.eResource().getErrors()); +class LetInferenceTest { + + @Inject ParseHelper parser; + + @Test + public void testLet() throws Exception { + Model model = + parser.parse( + String.join( + System.getProperty("line.separator"), + "target C;", + "main reactor {", + " ramp = new Ramp();", + " print = new Print();", + " print2 = new Print();", + " ramp.y -> print.x after 20 msec;", + " ramp.y -> print2.x after 30 msec;", + "}", + "reactor Ramp {", + " logical action a(60 msec):int;", + " logical action b(100 msec):int;", + " input x:int;", + " output y:int;", + " output z:int;", + " reaction(startup) -> y, z, a, b{=", + " =}", + "}", + "reactor Print {", + " input x:int;", + " output z:int;", + " reaction(x) -> z {=", + " =}", + "}")); + + Assertions.assertNotNull(model); + 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)); + + Assertions.assertTrue( + model.eResource().getErrors().isEmpty(), + "Encountered unexpected error while parsing: " + model.eResource().getErrors()); + + Instantiation mainDef = null; + + TreeIterator it = model.eResource().getAllContents(); + while (it.hasNext()) { + EObject obj = it.next(); + if (!(obj instanceof Reactor reactor)) { + continue; + } + if (reactor.isMain()) { + mainDef = LfFactory.eINSTANCE.createInstantiation(); + mainDef.setName(reactor.getName()); + mainDef.setReactorClass(reactor); + } + } - Instantiation mainDef = null; + ReactorInstance mainInstance = + new ReactorInstance(toDefinition(mainDef.getReactorClass()), new DefaultErrorReporter()); - TreeIterator it = model.eResource().getAllContents(); - while (it.hasNext()) { - EObject obj = it.next(); - if (!(obj instanceof Reactor reactor)) { - continue; - } - if (reactor.isMain()) { - mainDef = LfFactory.eINSTANCE.createInstantiation(); - mainDef.setName(reactor.getName()); - mainDef.setReactorClass(reactor); - } + for (ReactorInstance reactorInstance : mainInstance.children) { + if (reactorInstance.isGeneratedDelay()) { + for (ReactionInstance reactionInstance : reactorInstance.reactions) { + Assertions.assertEquals(reactionInstance.assignLogicalExecutionTime(), TimeValue.ZERO); } - - ReactorInstance mainInstance = new ReactorInstance(toDefinition(mainDef.getReactorClass()), new DefaultErrorReporter()); - - for (ReactorInstance reactorInstance : mainInstance.children) { - if (reactorInstance.isGeneratedDelay()) { - for (ReactionInstance reactionInstance : reactorInstance.reactions) { - Assertions.assertEquals(reactionInstance.assignLogicalExecutionTime(), TimeValue.ZERO); - } - } else if (reactorInstance.getName().contains("ramp")) { - for (ReactionInstance reactionInstance : reactorInstance.reactions) { - Assertions.assertEquals(new TimeValue(20L, TimeUnit.MILLI), reactionInstance.assignLogicalExecutionTime()); - } - } else if (reactorInstance.getName().contains("print")) { - for (ReactionInstance reactionInstance : reactorInstance.reactions) { - Assertions.assertEquals(TimeValue.ZERO, reactionInstance.assignLogicalExecutionTime()); - } - } + } else if (reactorInstance.getName().contains("ramp")) { + for (ReactionInstance reactionInstance : reactorInstance.reactions) { + Assertions.assertEquals( + new TimeValue(20L, TimeUnit.MILLI), reactionInstance.assignLogicalExecutionTime()); + } + } else if (reactorInstance.getName().contains("print")) { + for (ReactionInstance reactionInstance : reactorInstance.reactions) { + Assertions.assertEquals(TimeValue.ZERO, reactionInstance.assignLogicalExecutionTime()); } + } } + } } diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java index 0122e03d8d..61fcf74328 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java @@ -1,29 +1,29 @@ /* ASTUtils 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; @@ -34,14 +34,12 @@ import java.util.Map; import java.util.stream.Collectors; import javax.inject.Inject; - 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.ast.ASTUtils; import org.lflang.lf.Instantiation; import org.lflang.lf.Literal; @@ -55,20 +53,17 @@ * * @author Christian Menard */ - @ExtendWith(InjectionExtension.class) @InjectWith(LFInjectorProvider.class) - class LinguaFrancaASTUtilsTest { - @Inject - ParseHelper parser; - - /** - * Test that isInititialized returns true for inititialized state variables - */ - @Test - public void initializedState() throws Exception { - Model model = parser.parse(""" + @Inject ParseHelper parser; + + /** Test that isInititialized returns true for inititialized state variables */ + @Test + public void initializedState() throws Exception { + Model model = + parser.parse( + """ target Cpp; main reactor Foo { state a(); @@ -79,26 +74,27 @@ public void initializedState() throws Exception { } """); - - - Assertions.assertNotNull(model); - Assertions.assertTrue(model.eResource().getErrors().isEmpty(), - "Encountered unexpected error while parsing: " + - model.eResource().getErrors()); - - model.eAllContents().forEachRemaining((obj) -> { - if (obj instanceof StateVar) { - Assertions.assertTrue(isInitialized((StateVar)obj)); - } - }); - } - - /** - * Test that isInititialized returns false for uninititialized state variables - */ - @Test - public void uninitializedState() throws Exception { - Model model = parser.parse(""" + Assertions.assertNotNull(model); + Assertions.assertTrue( + model.eResource().getErrors().isEmpty(), + "Encountered unexpected error while parsing: " + model.eResource().getErrors()); + + model + .eAllContents() + .forEachRemaining( + (obj) -> { + if (obj instanceof StateVar) { + Assertions.assertTrue(isInitialized((StateVar) obj)); + } + }); + } + + /** Test that isInititialized returns false for uninititialized state variables */ + @Test + public void uninitializedState() throws Exception { + Model model = + parser.parse( + """ target Cpp; main reactor Foo { state a; @@ -109,39 +105,42 @@ public void uninitializedState() throws Exception { } """); - Assertions.assertNotNull(model); - Assertions.assertTrue(model.eResource().getErrors().isEmpty(), - "Encountered unexpected error while parsing: " + - model.eResource().getErrors()); - - model.eAllContents().forEachRemaining((obj) -> { - if (obj instanceof StateVar) { - Assertions.assertFalse(isInitialized((StateVar)obj)); - } - }); - - } - /** - * Return a map from strings to instantiations given a model. - * - * @param model The model to discover instantiations in. - */ - private Map getInsts(Model model) { - return asStream(model.eAllContents()) - .filter(obj -> obj instanceof Instantiation) - .map(obj -> (Instantiation) obj) - .collect(Collectors.toMap(Instantiation::getName, it -> it)); - } - - /** - * Test reading initial values of parameters. - * This checks that the example given in the documentation of the - * ASTUtils.initialValue() function behaves as stated in the docs. - */ - @Test - public void initialValue() throws Exception { - - Model model = parser.parse(""" + Assertions.assertNotNull(model); + Assertions.assertTrue( + model.eResource().getErrors().isEmpty(), + "Encountered unexpected error while parsing: " + model.eResource().getErrors()); + + model + .eAllContents() + .forEachRemaining( + (obj) -> { + if (obj instanceof StateVar) { + Assertions.assertFalse(isInitialized((StateVar) obj)); + } + }); + } + /** + * Return a map from strings to instantiations given a model. + * + * @param model The model to discover instantiations in. + */ + private Map getInsts(Model model) { + return asStream(model.eAllContents()) + .filter(obj -> obj instanceof Instantiation) + .map(obj -> (Instantiation) obj) + .collect(Collectors.toMap(Instantiation::getName, it -> it)); + } + + /** + * Test reading initial values of parameters. This checks that the example given in the + * documentation of the ASTUtils.initialValue() function behaves as stated in the docs. + */ + @Test + public void initialValue() throws Exception { + + Model model = + parser.parse( + """ target C; reactor A(x:int(1)) {} reactor B(y:int(2)) { @@ -154,76 +153,80 @@ reactor C(z:int(3)) { } """); - Assertions.assertNotNull(model); - Assertions.assertTrue(model.eResource().getErrors().isEmpty(), - "Encountered unexpected error while parsing: " + - model.eResource().getErrors()); - - var map = getInsts(model); - - /* Check for this: - * 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 - * - * initialValue(y, null) returns 2 - * initialValue(y, [a1]) throws an IllegalArgumentException - * initialValue(y, [b1]) returns 3 - * initialValue(y, [b2]) returns -2 - */ - - model.eAllContents().forEachRemaining((obj) -> { - if (obj instanceof Parameter) { - Parameter parameter = (Parameter)obj; + Assertions.assertNotNull(model); + Assertions.assertTrue( + model.eResource().getErrors().isEmpty(), + "Encountered unexpected error while parsing: " + model.eResource().getErrors()); + + var map = getInsts(model); + + /* Check for this: + * 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 + * + * initialValue(y, null) returns 2 + * initialValue(y, [a1]) throws an IllegalArgumentException + * initialValue(y, [b1]) returns 3 + * initialValue(y, [b2]) returns -2 + */ + + model + .eAllContents() + .forEachRemaining( + (obj) -> { + if (obj instanceof Parameter) { + Parameter parameter = (Parameter) obj; if (parameter.getName() == "x") { - var values = ASTUtils.initialValue(parameter, null); - Assertions.assertInstanceOf(Literal.class, values.get(0)); - Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "1"); + var values = ASTUtils.initialValue(parameter, null); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal) values.get(0)).getLiteral(), "1"); - values = ASTUtils.initialValue(parameter, List.of(map.get("a1"))); - Assertions.assertInstanceOf(Literal.class, values.get(0)); - Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "2"); + values = ASTUtils.initialValue(parameter, List.of(map.get("a1"))); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal) values.get(0)).getLiteral(), "2"); - values = ASTUtils.initialValue(parameter, List.of(map.get("a2"))); - Assertions.assertInstanceOf(Literal.class, values.get(0)); - Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "-1"); + values = ASTUtils.initialValue(parameter, List.of(map.get("a2"))); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal) values.get(0)).getLiteral(), "-1"); - values = ASTUtils.initialValue(parameter, List.of(map.get("a1"), map.get("b1"))); - Assertions.assertInstanceOf(Literal.class, values.get(0)); - Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "3"); + values = ASTUtils.initialValue(parameter, List.of(map.get("a1"), map.get("b1"))); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal) values.get(0)).getLiteral(), "3"); - values = ASTUtils.initialValue(parameter, List.of(map.get("a2"), map.get("b1"))); - Assertions.assertInstanceOf(Literal.class, values.get(0)); - Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "-1"); + values = ASTUtils.initialValue(parameter, List.of(map.get("a2"), map.get("b1"))); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal) values.get(0)).getLiteral(), "-1"); - values = ASTUtils.initialValue(parameter, List.of(map.get("a1"), map.get("b2"))); - Assertions.assertInstanceOf(Literal.class, values.get(0)); - Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "-2"); + values = ASTUtils.initialValue(parameter, List.of(map.get("a1"), map.get("b2"))); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal) values.get(0)).getLiteral(), "-2"); - values = ASTUtils.initialValue(parameter, List.of(map.get("a2"), map.get("b2"))); - Assertions.assertInstanceOf(Literal.class, values.get(0)); - Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "-1"); + values = ASTUtils.initialValue(parameter, List.of(map.get("a2"), map.get("b2"))); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal) values.get(0)).getLiteral(), "-1"); } else if (parameter.getName() == "y") { - var values = ASTUtils.initialValue(parameter, null); - Assertions.assertInstanceOf(Literal.class, values.get(0)); - Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "2"); + var values = ASTUtils.initialValue(parameter, null); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal) values.get(0)).getLiteral(), "2"); - Assertions.assertThrows(IllegalArgumentException.class, - () -> ASTUtils.initialValue(parameter, List.of(map.get("a1")))); + Assertions.assertThrows( + IllegalArgumentException.class, + () -> ASTUtils.initialValue(parameter, List.of(map.get("a1")))); - values = ASTUtils.initialValue(parameter, List.of(map.get("b1"))); - Assertions.assertInstanceOf(Literal.class, values.get(0)); - Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "3"); + values = ASTUtils.initialValue(parameter, List.of(map.get("b1"))); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal) values.get(0)).getLiteral(), "3"); - values = ASTUtils.initialValue(parameter, List.of(map.get("b2"))); - Assertions.assertInstanceOf(Literal.class, values.get(0)); - Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "-2"); + values = ASTUtils.initialValue(parameter, List.of(map.get("b2"))); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal) values.get(0)).getLiteral(), "-2"); } - } - }); - } + } + }); + } } diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.java index d7e2af48fc..516c712498 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.java @@ -1,33 +1,34 @@ /* Dependency analysis 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 com.google.inject.Inject; +import static org.lflang.ast.ASTUtils.*; +import com.google.inject.Inject; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.testing.InjectWith; @@ -44,57 +45,56 @@ import org.lflang.lf.Model; import org.lflang.lf.Reactor; import org.lflang.tests.LFInjectorProvider; -import static org.lflang.ast.ASTUtils.*; @ExtendWith(InjectionExtension.class) @InjectWith(LFInjectorProvider.class) /** * A collection of tests to ensure dependency analysis is done correctly. + * * @author Marten Lohstroh */ class LinguaFrancaDependencyAnalysisTest { - @Inject - ParseHelper parser; - - /** - * Check that circular dependencies between reactions are detected. - */ - @Test - public void cyclicDependency() throws Exception { - // Java 17: -// String testCase = """ -// target C; -// -// reactor Clock { -// timer t(0, 10 msec); -// input x:int; -// output y:int; -// reaction(t) -> y {= -// -// =} -// reaction(x) -> y {= -// -// =} -// } -// -// reactor A { -// input x:int; -// output y:int; -// reaction(x) -> y {= -// -// =} -// } -// -// main reactor Loop { -// c = new Clock(); -// a = new A(); -// c.y -> a.x; -// a.y -> c.x; -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), + @Inject ParseHelper parser; + + /** Check that circular dependencies between reactions are detected. */ + @Test + public void cyclicDependency() throws Exception { + // Java 17: + // String testCase = """ + // target C; + // + // reactor Clock { + // timer t(0, 10 msec); + // input x:int; + // output y:int; + // reaction(t) -> y {= + // + // =} + // reaction(x) -> y {= + // + // =} + // } + // + // reactor A { + // input x:int; + // output y:int; + // reaction(x) -> y {= + // + // =} + // } + // + // main reactor Loop { + // c = new Clock(); + // a = new A(); + // c.y -> a.x; + // a.y -> c.x; + // } + // """ + // Java 11: + String testCase = + String.join( + System.getProperty("line.separator"), "target C;", "", "reactor Clock {", @@ -122,38 +122,37 @@ public void cyclicDependency() throws Exception { " a = new A();", " c.y -> a.x;", " a.y -> c.x;", - "}" - ); - Model model = parser.parse(testCase); - - Assertions.assertNotNull(model); - Instantiation mainDef = null; - TreeIterator it = model.eResource().getAllContents(); - while (it.hasNext()) { - EObject obj = it.next(); - if (!(obj instanceof Reactor)) { - continue; - } - Reactor reactor = (Reactor) obj; - if (reactor.isMain()) { - // Creating an definition for the main reactor because - // there isn't one. - mainDef = LfFactory.eINSTANCE.createInstantiation(); - mainDef.setName(reactor.getName()); - mainDef.setReactorClass(reactor); - } - } - - ReactorInstance instance = new ReactorInstance(toDefinition(mainDef.getReactorClass()), new DefaultErrorReporter()); - Assertions.assertFalse(instance.getCycles().isEmpty()); + "}"); + Model model = parser.parse(testCase); + + Assertions.assertNotNull(model); + Instantiation mainDef = null; + TreeIterator it = model.eResource().getAllContents(); + while (it.hasNext()) { + EObject obj = it.next(); + if (!(obj instanceof Reactor)) { + continue; + } + Reactor reactor = (Reactor) obj; + if (reactor.isMain()) { + // Creating an definition for the main reactor because + // there isn't one. + mainDef = LfFactory.eINSTANCE.createInstantiation(); + mainDef.setName(reactor.getName()); + mainDef.setReactorClass(reactor); + } } - /** - * Check that circular instantiations are detected. - */ - @Test - public void circularInstantiation() throws Exception { - String testCase = """ + ReactorInstance instance = + new ReactorInstance(toDefinition(mainDef.getReactorClass()), new DefaultErrorReporter()); + Assertions.assertFalse(instance.getCycles().isEmpty()); + } + + /** Check that circular instantiations are detected. */ + @Test + public void circularInstantiation() throws Exception { + String testCase = + """ target C; reactor X { @@ -167,14 +166,13 @@ public void circularInstantiation() throws Exception { q = new X(); } """; - Model model = parser.parse(testCase); - - Assertions.assertNotNull(model); + Model model = parser.parse(testCase); - ModelInfo info = new ModelInfo(); - info.update(model, new DefaultErrorReporter()); - Assertions.assertTrue(info.instantiationGraph.hasCycles() == true, - "Did not detect cyclic instantiation."); - } + Assertions.assertNotNull(model); + ModelInfo info = new ModelInfo(); + info.update(model, new DefaultErrorReporter()); + Assertions.assertTrue( + info.instantiationGraph.hasCycles() == true, "Did not detect cyclic instantiation."); + } } diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java index fc66c7ed57..377e3e7a32 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java @@ -1,66 +1,63 @@ /************* -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 com.google.inject.Inject; 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.lf.Model; import org.lflang.tests.LFInjectorProvider; -import com.google.inject.Inject; - -/** - * Test harness for ensuring that grammar captures - * all corner cases. - */ +/** Test harness for ensuring that grammar captures all corner cases. */ @ExtendWith(InjectionExtension.class) @InjectWith(LFInjectorProvider.class) class LinguaFrancaParsingTest { - @Inject - ParseHelper parser; + @Inject ParseHelper parser; - @Test - public void checkForTarget() throws Exception { - String testCase = """ + @Test + public void checkForTarget() throws Exception { + String testCase = + """ targett C; reactor Foo { } """; - Model result = parser.parse(testCase); - Assertions.assertNotNull(result); - Assertions.assertFalse(result.eResource().getErrors().isEmpty(), "Failed to catch misspelled target keyword."); - } + Model result = parser.parse(testCase); + Assertions.assertNotNull(result); + Assertions.assertFalse( + result.eResource().getErrors().isEmpty(), "Failed to catch misspelled target keyword."); + } - @Test - public void testAttributes() throws Exception { - String testCase = """ + @Test + public void testAttributes() throws Exception { + String testCase = + """ target C; @label("somethign", "else") @ohio() @@ -74,16 +71,17 @@ public void testAttributes() throws Exception { } """; - parseWithoutError(testCase); - } + parseWithoutError(testCase); + } - @Test - public void testAttributeContexts() throws Exception { - String testCase = """ + @Test + public void testAttributeContexts() throws Exception { + String testCase = + """ target C; @a main reactor(@b parm: int) { - + @ohio reaction() {==} @ohio logical action f; @ohio timer t; @@ -91,28 +89,28 @@ main reactor(@b parm: int) { @ohio output q2: int; } """; - parseWithoutError(testCase); - } + parseWithoutError(testCase); + } - @Test - public void testTokenizeEmptyWidth() throws Exception { - String testCase = """ + @Test + public void testTokenizeEmptyWidth() throws Exception { + String testCase = + """ target C; main reactor { state foo: int[]; state foo: int[ ]; //spaces are allowed } """; - parseWithoutError(testCase); - } - - private Model parseWithoutError(String s) throws Exception { - Model model = parser.parse(s); - Assertions.assertNotNull(model); - Assertions.assertTrue(model.eResource().getErrors().isEmpty(), - "Encountered unexpected error while parsing: " + - model.eResource().getErrors()); - return model; - } + parseWithoutError(testCase); + } + private Model parseWithoutError(String s) throws Exception { + Model model = parser.parse(s); + Assertions.assertNotNull(model); + Assertions.assertTrue( + model.eResource().getErrors().isEmpty(), + "Encountered unexpected error while parsing: " + model.eResource().getErrors()); + return model; + } } diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.java index 8587675479..404d948264 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.java @@ -1,129 +1,125 @@ /* 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 com.google.inject.Inject; +import com.google.inject.Provider; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.xtext.generator.JavaIoFileSystemAccess; +import org.eclipse.xtext.linking.impl.XtextLinkingDiagnostic; import org.eclipse.xtext.testing.InjectWith; import org.eclipse.xtext.testing.extensions.InjectionExtension; import org.eclipse.xtext.testing.util.ParseHelper; -import org.lflang.lf.Model; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Assertions; import org.eclipse.xtext.testing.validation.ValidationTestHelper; -import org.lflang.lf.LfPackage; -import org.eclipse.xtext.linking.impl.XtextLinkingDiagnostic; -import org.lflang.tests.LFInjectorProvider; -import org.eclipse.emf.ecore.resource.ResourceSet; -import org.eclipse.xtext.generator.JavaIoFileSystemAccess; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.lflang.generator.LFGenerator; -import com.google.inject.Provider; +import org.lflang.lf.LfPackage; +import org.lflang.lf.Model; +import org.lflang.tests.LFInjectorProvider; @ExtendWith(InjectionExtension.class) @InjectWith(LFInjectorProvider.class) /** - * Test harness for ensuring that cross-references are - * established correctly and reported when faulty. + * Test harness for ensuring that cross-references are established correctly and reported when + * faulty. */ public class LinguaFrancaScopingTest { - @Inject - ParseHelper parser; - - @Inject - LFGenerator generator; - - @Inject - JavaIoFileSystemAccess fileAccess; - - @Inject - Provider resourceSetProvider; - - @Inject - ValidationTestHelper validator; - - /** - * Ensure that invalid references to contained reactors are reported. - */ - @Test - public void unresolvedReactorReference() throws Exception { -// Java 17: -// Model model = """ -// target C; -// reactor From { -// output y:int; -// } -// reactor To { -// input x:int; -// } -// -// main reactor { -// a = new From(); -// d = new To(); -// s.y -> d.x; -// } -// """; -// Java 11: - Model model = parser.parse(String.join( - System.getProperty("line.separator"), - "target C;", - "reactor From {", - " output y:int;", - "}", - "reactor To {", - " input x:int;", - "}", - "", - "main reactor {", - " a = new From();", - " d = new To();", - " s.y -> d.x;", - "}" - )); - - Assertions.assertNotNull(model); - Assertions.assertTrue(model.eResource().getErrors().isEmpty(), - "Encountered unexpected error while parsing."); - validator.assertError(model, LfPackage.eINSTANCE.getVarRef(), - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Instantiation 's'"); - validator.assertError(model, LfPackage.eINSTANCE.getVarRef(), - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Variable 'y'"); - } - - - /** - * Ensure that invalid references to ports - * of contained reactors are reported. - */ - @Test - public void unresolvedHierarchicalPortReference() throws Exception { - Model model = parser.parse(""" + @Inject ParseHelper parser; + + @Inject LFGenerator generator; + + @Inject JavaIoFileSystemAccess fileAccess; + + @Inject Provider resourceSetProvider; + + @Inject ValidationTestHelper validator; + + /** Ensure that invalid references to contained reactors are reported. */ + @Test + public void unresolvedReactorReference() throws Exception { + // Java 17: + // Model model = """ + // target C; + // reactor From { + // output y:int; + // } + // reactor To { + // input x:int; + // } + // + // main reactor { + // a = new From(); + // d = new To(); + // s.y -> d.x; + // } + // """; + // Java 11: + Model model = + parser.parse( + String.join( + System.getProperty("line.separator"), + "target C;", + "reactor From {", + " output y:int;", + "}", + "reactor To {", + " input x:int;", + "}", + "", + "main reactor {", + " a = new From();", + " d = new To();", + " s.y -> d.x;", + "}")); + + Assertions.assertNotNull(model); + Assertions.assertTrue( + model.eResource().getErrors().isEmpty(), "Encountered unexpected error while parsing."); + validator.assertError( + model, + LfPackage.eINSTANCE.getVarRef(), + XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, + "Couldn't resolve reference to Instantiation 's'"); + validator.assertError( + model, + LfPackage.eINSTANCE.getVarRef(), + XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, + "Couldn't resolve reference to Variable 'y'"); + } + + /** Ensure that invalid references to ports of contained reactors are reported. */ + @Test + public void unresolvedHierarchicalPortReference() throws Exception { + Model model = + parser.parse( + """ target C; reactor From { output y:int; @@ -138,59 +134,73 @@ public void unresolvedHierarchicalPortReference() throws Exception { a.x -> d.y; } """); - - Assertions.assertNotNull(model); - Assertions.assertTrue(model.eResource().getErrors().isEmpty(), - "Encountered unexpected error while parsing."); - validator.assertError(model, LfPackage.eINSTANCE.getVarRef(), - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Variable 'x'"); - validator.assertError(model, LfPackage.eINSTANCE.getVarRef(), - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Variable 'y'"); - } - - @Test - public void unresolvedReferenceInTriggerClause() throws Exception { - Model model = parser.parse(""" + + Assertions.assertNotNull(model); + Assertions.assertTrue( + model.eResource().getErrors().isEmpty(), "Encountered unexpected error while parsing."); + validator.assertError( + model, + LfPackage.eINSTANCE.getVarRef(), + XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, + "Couldn't resolve reference to Variable 'x'"); + validator.assertError( + model, + LfPackage.eINSTANCE.getVarRef(), + XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, + "Couldn't resolve reference to Variable 'y'"); + } + + @Test + public void unresolvedReferenceInTriggerClause() throws Exception { + Model model = + parser.parse( + """ target C; main reactor { reaction(unknown) {==} } """); - - validator.assertError(model, LfPackage.eINSTANCE.getVarRef(), - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Variable 'unknown'."); - } - - @Test - public void unresolvedReferenceInUseClause() throws Exception { - Model model = parser.parse(""" + + validator.assertError( + model, + LfPackage.eINSTANCE.getVarRef(), + XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, + "Couldn't resolve reference to Variable 'unknown'."); + } + + @Test + public void unresolvedReferenceInUseClause() throws Exception { + Model model = + parser.parse( + """ target C; main reactor { reaction() unknown {==} } """); - - - validator.assertError(model, LfPackage.eINSTANCE.getVarRef(), - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Variable 'unknown'."); - } - - @Test - public void unresolvedReferenceInEffectsClause() throws Exception { - Model model = parser.parse(""" + + validator.assertError( + model, + LfPackage.eINSTANCE.getVarRef(), + XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, + "Couldn't resolve reference to Variable 'unknown'."); + } + + @Test + public void unresolvedReferenceInEffectsClause() throws Exception { + Model model = + parser.parse( + """ target C; main reactor { reaction() -> unknown {==} } """); - validator.assertError(model, LfPackage.eINSTANCE.getVarRef(), - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Variable 'unknown'."); - } - + validator.assertError( + model, + LfPackage.eINSTANCE.getVarRef(), + XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, + "Couldn't resolve reference to Variable 'unknown'."); + } } 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 aa285a2e32..72e3574b33 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -1,36 +1,36 @@ /* 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 com.google.inject.Inject; import java.util.LinkedList; import java.util.List; import java.util.Map; - import org.eclipse.xtext.testing.InjectWith; import org.eclipse.xtext.testing.extensions.InjectionExtension; import org.eclipse.xtext.testing.util.ParseHelper; @@ -39,7 +39,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; - import org.lflang.Target; import org.lflang.TargetProperty; import org.lflang.TargetProperty.ArrayType; @@ -56,12 +55,9 @@ import org.lflang.tests.LFInjectorProvider; import org.lflang.util.StringUtil; -import com.google.inject.Inject; - @ExtendWith(InjectionExtension.class) @InjectWith(LFInjectorProvider.class) - /** * Collection of unit tests to ensure validation is done correctly. * @@ -73,75 +69,76 @@ */ public class LinguaFrancaValidationTest { - @Inject - ParseHelper parser; - - @Inject - ValidationTestHelper validator; - - /** - * 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 { - Model model = parser.parse(s); - Assertions.assertNotNull(model); - Assertions.assertTrue(model.eResource().getErrors().isEmpty(), - "Encountered unexpected error while parsing: " + - model.eResource().getErrors()); - return model; - } - - /** - * 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 { - Model model = parser.parse(s); - Assertions.assertNotNull(model); - Assertions.assertFalse(model.eResource().getErrors().isEmpty()); - return model; - } - - /** - * Ensure that duplicate identifiers for actions reported. - */ - @Test - public void duplicateVariable() throws Exception { - String testCase = """ + @Inject ParseHelper parser; + + @Inject ValidationTestHelper validator; + + /** + * 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 { + Model model = parser.parse(s); + Assertions.assertNotNull(model); + Assertions.assertTrue( + model.eResource().getErrors().isEmpty(), + "Encountered unexpected error while parsing: " + model.eResource().getErrors()); + return model; + } + + /** + * 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 { + Model model = parser.parse(s); + Assertions.assertNotNull(model); + Assertions.assertFalse(model.eResource().getErrors().isEmpty()); + return model; + } + + /** Ensure that duplicate identifiers for actions reported. */ + @Test + public void duplicateVariable() throws Exception { + 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'"); - } - + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getAction(), + null, + "Duplicate Variable 'bar' in Reactor 'Foo'"); + } - /** - * Check that reactors in C++ cannot be named preamble - */ - @Test - public void disallowReactorCalledPreamble() throws Exception { - Model model_no_errors = parser.parse(""" + /** Check that reactors in C++ cannot be named preamble */ + @Test + public void disallowReactorCalledPreamble() throws Exception { + Model model_no_errors = + parser.parse( + """ target Cpp; main reactor { } """); - Model model_error_1 = parser.parse(""" + Model model_error_1 = + parser.parse( + """ target Cpp; reactor Preamble { } """); - Model model_error_2 = parser.parse(""" + Model model_error_2 = + parser.parse( + """ target Cpp; reactor Preamble { } @@ -149,151 +146,177 @@ public void disallowReactorCalledPreamble() throws Exception { } """); - 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(), - "Encountered unexpected error while parsing: " + model_error_1.eResource().getErrors()); - 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'"); - } - - - /** - * Ensure that "__" is not allowed at the start of an input name. - */ - @Test - public void disallowUnderscoreInputs() throws Exception { - String testCase = """ + 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(), + "Encountered unexpected error while parsing: " + model_error_1.eResource().getErrors()); + 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'"); + } + + /** Ensure that "__" is not allowed at the start of an input name. */ + @Test + public void disallowUnderscoreInputs() throws Exception { + 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"); - } + 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 { - String testCase = """ + @Test + public void disallowMainWithDifferentNameThanFile() throws Exception { + 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)"); - } - + 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 { - String testCase = """ + /** Ensure that "__" is not allowed at the start of an output name. */ + @Test + public void disallowUnderscoreOutputs() throws Exception { + 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"); - } + 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"); + } - /** - * Ensure that "__" is not allowed at the start of an action name. - */ - @Test - public void disallowUnderscoreActions() throws Exception { - String testCase = """ + /** Ensure that "__" is not allowed at the start of an action name. */ + @Test + public void disallowUnderscoreActions() throws Exception { + 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"); - } + 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"); + } - /** - * Ensure that "__" is not allowed at the start of a timer name. - */ - @Test - public void disallowUnderscoreTimers() throws Exception { - String testCase = """ + /** Ensure that "__" is not allowed at the start of a timer name. */ + @Test + public void disallowUnderscoreTimers() throws Exception { + 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"); - } + 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"); + } - /** - * Ensure that "__" is not allowed at the start of a parameter name. - */ - @Test - public void disallowUnderscoreParameters() throws Exception { - String testCase = """ + /** Ensure that "__" is not allowed at the start of a parameter name. */ + @Test + public void disallowUnderscoreParameters() throws Exception { + 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"); - } + 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 { - String testCase = """ + /** Ensure that "__" is not allowed at the start of an state name. */ + @Test + public void disallowUnderscoreStates() throws Exception { + 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"); - } + 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 { - String testCase = """ + /** Ensure that "__" is not allowed at the start of a reactor definition name. */ + @Test + public void disallowUnderscoreReactorDef() throws Exception { + String testCase = + """ target TypeScript; 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"); - } + 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 { - String testCase = """ + /** Ensure that "__" is not allowed at the start of a reactor instantiation name. */ + @Test + public void disallowUnderscoreReactorInstantiation() throws Exception { + String testCase = + """ target TypeScript; reactor Foo { } @@ -301,16 +324,19 @@ public void disallowUnderscoreReactorInstantiation() throws Exception { __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"); - } + 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 { - String testCase = """ + /** Disallow connection to port that is effect of reaction. */ + @Test + public void connectionToEffectPort() throws Exception { + String testCase = + """ target C; reactor Foo { output out:int; @@ -319,20 +345,22 @@ public void connectionToEffectPort() throws Exception { output out:int; x = new Foo(); x.out -> out; - reaction(startup) -> out {= + reaction(startup) -> out {= =} } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getConnection(), null, - "Cannot connect: Port named 'out' is already effect of a reaction."); - } + 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 { - String testCase = """ + /** Disallow connection to port that is effect of reaction. */ + @Test + public void connectionToEffectPort2() throws Exception { + String testCase = + """ target C; reactor Foo { input inp:int; @@ -344,21 +372,25 @@ public void connectionToEffectPort2() throws Exception { y = new Foo(); y.out -> x.inp; - reaction(startup) -> 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. - */ - @Test - public void connectionToEffectPort3() throws Exception { - String testCase = """ + 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. + */ + @Test + public void connectionToEffectPort3() throws Exception { + String testCase = + """ target C; reactor Foo { @@ -373,16 +405,17 @@ public void connectionToEffectPort3() throws Exception { =} } """; - validator.assertNoErrors(parseWithoutError(testCase)); - } + validator.assertNoErrors(parseWithoutError(testCase)); + } - /** - * 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 { - String testCase = """ + /** + * 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 { + String testCase = + """ target C; reactor Foo { @@ -397,19 +430,23 @@ public void connectionToEffectPort3_5() throws Exception { =} } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getVariable(), null, - "Main reactor cannot have inputs."); - } - - /** - * Disallow connection to the port of a contained reactor if the same port is effect of a - * reaction. - */ - @Test - public void connectionToEffectPort4() throws Exception { - String testCase = """ + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getVariable(), + null, + "Main reactor cannot have inputs."); + } + + /** + * Disallow connection to the port of a contained reactor if the same port is effect of a + * reaction. + */ + @Test + public void connectionToEffectPort4() throws Exception { + String testCase = + """ target C; - + reactor Foo { input in:int; } @@ -421,16 +458,18 @@ public void connectionToEffectPort4() throws Exception { =} } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getConnection(), null, - "Cannot connect: Port named 'in' is already effect of a reaction."); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getConnection(), + null, + "Cannot connect: Port named 'in' is already effect of a reaction."); + } - /** - * Disallow connection of multiple ports to the same input port. - */ - @Test - public void multipleConnectionsToInputTest() throws Exception { - String testCase = """ + /** Disallow connection of multiple ports to the same input port. */ + @Test + public void multipleConnectionsToInputTest() throws Exception { + String testCase = + """ target C; reactor Source { output out:int; @@ -446,32 +485,35 @@ public void multipleConnectionsToInputTest() throws Exception { 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."); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getConnection(), + null, + "Cannot connect: Port named 'in' may only appear once on the right side of a connection."); + } - /** - * Detect cycles in the instantiation graph. - */ - @Test - public void detectInstantiationCycle() throws Exception { - String testCase = """ + /** Detect cycles in the instantiation graph. */ + @Test + public void detectInstantiationCycle() throws Exception { + String testCase = + """ target C; reactor Contained { x = new Contained(); } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getInstantiation(), - null, "Instantiation is part of a cycle: 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 { - String testCase = """ + /** Detect cycles in the instantiation graph. */ + @Test + public void detectInstantiationCycle2() throws Exception { + String testCase = + """ target C; reactor Intermediate { x = new Contained(); @@ -481,20 +523,25 @@ public void detectInstantiationCycle2() throws Exception { } """; - 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."); - } + 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 { + /** Detect causality loop. */ + @Test + public void detectCausalityLoop() throws Exception { - String testCase = """ + String testCase = + """ target C; reactor X { @@ -511,19 +558,24 @@ public void detectCausalityLoop() throws Exception { 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."); - } - - /** - * Let cyclic dependencies be broken by "after" clauses. - */ - @Test - public void afterBreaksCycle() throws Exception { - String testCase = """ + 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."); + } + + /** Let cyclic dependencies be broken by "after" clauses. */ + @Test + public void afterBreaksCycle() throws Exception { + String testCase = + """ target C reactor X { @@ -541,16 +593,14 @@ public void afterBreaksCycle() throws Exception { } """; - validator.assertNoErrors(parseWithoutError(testCase)); - } - + validator.assertNoErrors(parseWithoutError(testCase)); + } - /** - * Let cyclic dependencies be broken by "after" clauses with zero delay. - */ - @Test - public void afterBreaksCycle2() throws Exception { - String testCase = """ + /** Let cyclic dependencies be broken by "after" clauses with zero delay. */ + @Test + public void afterBreaksCycle2() throws Exception { + String testCase = + """ target C reactor X { @@ -567,16 +617,14 @@ public void afterBreaksCycle2() throws Exception { b.y -> a.x } """; - validator.assertNoErrors(parseWithoutError(testCase)); - } + validator.assertNoErrors(parseWithoutError(testCase)); + } - - /** - * Let cyclic dependencies be broken by "after" clauses with zero delay and no units. - */ - @Test - public void afterBreaksCycle3() throws Exception { - String testCase = """ + /** Let cyclic dependencies be broken by "after" clauses with zero delay and no units. */ + @Test + public void afterBreaksCycle3() throws Exception { + String testCase = + """ target C reactor X { @@ -593,15 +641,14 @@ public void afterBreaksCycle3() throws Exception { b.y -> a.x } """; - validator.assertNoErrors(parseWithoutError(testCase)); - } + validator.assertNoErrors(parseWithoutError(testCase)); + } - /** - * Detect missing units in "after" clauses with delay greater than zero. - */ - @Test - public void nonzeroAfterMustHaveUnits() throws Exception { - String testCase = """ + /** Detect missing units in "after" clauses with delay greater than zero. */ + @Test + public void nonzeroAfterMustHaveUnits() throws Exception { + String testCase = + """ target C reactor X { @@ -617,17 +664,18 @@ public void nonzeroAfterMustHaveUnits() throws Exception { a.y -> b.x after 1 } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getConnection(), - null, "Missing time unit."); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getConnection(), + null, + "Missing time unit."); + } - - /** - * Report non-zero time value without units. - */ - @Test - public void nonZeroTimeValueWithoutUnits() throws Exception { - String testCase = """ + /** Report non-zero time value without units. */ + @Test + public void nonZeroTimeValueWithoutUnits() throws Exception { + String testCase = + """ target C; main reactor { timer t(42, 1 sec); @@ -636,15 +684,15 @@ public void nonZeroTimeValueWithoutUnits() throws Exception { =} } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getTimer(), null, "Missing time unit."); - } + 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 { - String testCase = """ + /** Report reference to non-time parameter in time argument. */ + @Test + public void parameterTypeMismatch() throws Exception { + String testCase = + """ target C; main reactor (p:int(0)) { timer t(p, 1 sec); @@ -653,16 +701,18 @@ main reactor (p:int(0)) { =} } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getTimer(), - null, "Referenced parameter is not of time type."); - } + 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 { - String testCase = """ + /** Report inappropriate literal in time argument. */ + @Test + public void targetCodeInTimeArgument() throws Exception { + String testCase = + """ target C; main reactor { timer t({=foo()=}, 1 sec); @@ -671,17 +721,15 @@ public void targetCodeInTimeArgument() throws Exception { =} } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getTimer(), - null, "Invalid time value."); - } - + validator.assertError( + parseWithoutError(testCase), LfPackage.eINSTANCE.getTimer(), null, "Invalid time value."); + } - /** - * Report overflowing deadline. - */ - @Test - public void overflowingDeadlineC() throws Exception { - String testCase = """ + /** Report overflowing deadline. */ + @Test + public void overflowingDeadlineC() throws Exception { + String testCase = + """ target C; main reactor { timer t; @@ -691,18 +739,18 @@ public void overflowingDeadlineC() throws Exception { =} } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getDeadline(), null, - "Deadline exceeds the maximum of " + TimeValue.MAX_LONG_DEADLINE + - " nanoseconds."); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getDeadline(), + null, + "Deadline exceeds the maximum of " + TimeValue.MAX_LONG_DEADLINE + " nanoseconds."); + } - - /** - * Report overflowing parameter. - */ - @Test - public void overflowingParameterC() throws Exception { - String testCase = """ + /** Report overflowing parameter. */ + @Test + public void overflowingParameterC() throws Exception { + String testCase = + """ target C; main reactor(d:time(40 hours)) { timer t; @@ -712,18 +760,20 @@ main reactor(d:time(40 hours)) { =} } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getParameter(), null, - "Time value used to specify a deadline exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds."); - } - - - /** - * Report overflowing assignment. - */ - @Test - public void overflowingAssignmentC() throws Exception { - String testCase = """ + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getParameter(), + null, + "Time value used to specify a deadline exceeds the maximum of " + + TimeValue.MAX_LONG_DEADLINE + + " nanoseconds."); + } + + /** Report overflowing assignment. */ + @Test + public void overflowingAssignmentC() throws Exception { + String testCase = + """ target C; reactor Print(d:time(39 hours)) { timer t; @@ -736,17 +786,20 @@ reactor Print(d:time(39 hours)) { 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."); - } - - /** - * Report missing trigger. - */ - @Test - public void missingTrigger() throws Exception { - String testCase = """ + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getAssignment(), + null, + "Time value used to specify a deadline exceeds the maximum of " + + TimeValue.MAX_LONG_DEADLINE + + " nanoseconds."); + } + + /** Report missing trigger. */ + @Test + public void missingTrigger() throws Exception { + String testCase = + """ target C; reactor X { reaction() {= @@ -754,112 +807,148 @@ public void missingTrigger() throws Exception { =} } """; - validator.assertWarning(parseWithoutError(testCase), LfPackage.eINSTANCE.getReaction(), null, - "Reaction has no trigger."); - } + validator.assertWarning( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getReaction(), + null, + "Reaction has no trigger."); + } - /** - * 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()) { - Model model_reactor_scope = parseWithoutError(""" + /** 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()) { + Model model_reactor_scope = + parseWithoutError( + """ target %s; reactor Foo { %spreamble {==} } - """.formatted(target, visibility != Visibility.NONE ? visibility + " " : "")); + """ + .formatted(target, visibility != Visibility.NONE ? visibility + " " : "")); - Model model_file_scope = parseWithoutError(""" + Model model_file_scope = + parseWithoutError( + """ target %s; %spreamble {==} reactor Foo { } - """.formatted(target, visibility != Visibility.NONE ? visibility + " " : "")); + """ + .formatted(target, visibility != Visibility.NONE ? visibility + " " : "")); - Model model_no_preamble = parseWithoutError(""" + 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)!"); - } else { - validator.assertNoIssues(model_file_scope); - validator.assertNoIssues(model_reactor_scope); - } - } else { - if (visibility == Visibility.NONE) { - validator.assertNoIssues(model_file_scope); - validator.assertNoIssues(model_reactor_scope); - } else { - validator.assertWarning(model_file_scope, LfPackage.eINSTANCE.getPreamble(), null, - String.format("The %s qualifier has no meaning for the %s target. It should be removed.", visibility, target.name())); - validator.assertWarning(model_reactor_scope, LfPackage.eINSTANCE.getPreamble(), null, - String.format("The %s qualifier has no meaning for the %s target. It should be removed.", visibility, target.name())); - } - } - } + """ + .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)!"); + } else { + validator.assertNoIssues(model_file_scope); + validator.assertNoIssues(model_reactor_scope); + } + } else { + if (visibility == Visibility.NONE) { + validator.assertNoIssues(model_file_scope); + validator.assertNoIssues(model_reactor_scope); + } else { + validator.assertWarning( + model_file_scope, + LfPackage.eINSTANCE.getPreamble(), + null, + String.format( + "The %s qualifier has no meaning for the %s target. It should be removed.", + visibility, target.name())); + validator.assertWarning( + model_reactor_scope, + LfPackage.eINSTANCE.getPreamble(), + null, + String.format( + "The %s qualifier has no meaning for the %s target. It should be removed.", + visibility, target.name())); + } } + } } + } - @Test - public void testInheritanceSupport() throws Exception { - for (Target target : Target.values()) { - Model model = parseWithoutError(""" + @Test + public void testInheritanceSupport() throws Exception { + for (Target target : Target.values()) { + Model model = + parseWithoutError( + """ target %s reactor A{} reactor B extends A{} - """.formatted(target)); - - if (target.supportsInheritance()) { - validator.assertNoIssues(model); - } else { - validator.assertError(model, LfPackage.eINSTANCE.getReactor(), null, - "The " + target.getDisplayName() + " target does not support reactor inheritance."); - } - } + """ + .formatted(target)); + + if (target.supportsInheritance()) { + validator.assertNoIssues(model); + } else { + validator.assertError( + model, + LfPackage.eINSTANCE.getReactor(), + null, + "The " + target.getDisplayName() + " target does not support reactor inheritance."); + } } + } - @Test - public void testFederationSupport() throws Exception { - for (Target target : Target.values()) { - Model model = parseWithoutError(""" + @Test + public void testFederationSupport() throws Exception { + for (Target target : Target.values()) { + Model model = + parseWithoutError( + """ target %s federated reactor {} - """.formatted(target)); - - if (target.supportsFederated()) { - validator.assertNoIssues(model); - } else { - validator.assertError(model, LfPackage.eINSTANCE.getReactor(), null, - "The " + target.getDisplayName() + " target does not support federated execution."); - } - } + """ + .formatted(target)); + + if (target.supportsFederated()) { + validator.assertNoIssues(model); + } else { + validator.assertError( + model, + LfPackage.eINSTANCE.getReactor(), + null, + "The " + target.getDisplayName() + " target does not support federated execution."); + } } + } - - /** - * Tests for state and parameter declarations, including native lists. - */ - @Test - public void stateAndParameterDeclarationsInC() throws Exception { - String testCase = """ + /** Tests for state and parameter declarations, including native lists. */ + @Test + public void stateAndParameterDeclarationsInC() throws Exception { + 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 + h:time = "bla", // ERROR: not a type q:time(1 msec, 2 msec), // ERROR: not a list y:int = t // ERROR: init using parameter ) { @@ -870,878 +959,1039 @@ reactor Bar(a(0), // ERROR: type missing } """; - Model model = parseWithoutError(testCase); - - - validator.assertError(model, LfPackage.eINSTANCE.getParameter(), null, - "Type declaration missing."); - validator.assertError(model, LfPackage.eINSTANCE.getParameter(), null, - "Parameter must have a default value."); - validator.assertError(model, LfPackage.eINSTANCE.getParameter(), null, - "Missing time unit."); - validator.assertError(model, LfPackage.eINSTANCE.getParameter(), null, - "Invalid time value."); - validator.assertError(model, LfPackage.eINSTANCE.getParameter(), null, - "Expected exactly one time value."); - validator.assertError(model, LfPackage.eINSTANCE.getParameter(), null, - "Parameter cannot be initialized using parameter."); - validator.assertError(model, LfPackage.eINSTANCE.getStateVar(), null, - "Missing time unit."); - validator.assertError(model, LfPackage.eINSTANCE.getStateVar(), null, - "Referenced parameter is not of time type."); - validator.assertError(model, LfPackage.eINSTANCE.getStateVar(), null, - "Invalid time value."); - validator.assertError(model, LfPackage.eINSTANCE.getTimer(), null, - "Missing time unit."); - } - - - /** - * Recognize valid IPV4 addresses, report invalid ones. - */ - @Test - public void recognizeIPV4() throws Exception { - List correct = List.of("127.0.0.1", "10.0.0.1", "192.168.1.1", "0.0.0.0", "192.168.1.1"); - List parseError = List.of("10002.3.4", "1.2.3.4.5"); - List validationError = List.of("256.0.0.0", "260.0.0.0"); - - // Correct IP addresses. - for (String addr : correct) { - - String testCase = """ + Model model = parseWithoutError(testCase); + + validator.assertError( + model, LfPackage.eINSTANCE.getParameter(), null, "Type declaration missing."); + validator.assertError( + model, LfPackage.eINSTANCE.getParameter(), null, "Parameter must have a default value."); + validator.assertError(model, LfPackage.eINSTANCE.getParameter(), null, "Missing time unit."); + validator.assertError(model, LfPackage.eINSTANCE.getParameter(), null, "Invalid time value."); + validator.assertError( + model, LfPackage.eINSTANCE.getParameter(), null, "Expected exactly one time value."); + validator.assertError( + model, + LfPackage.eINSTANCE.getParameter(), + null, + "Parameter cannot be initialized using parameter."); + validator.assertError(model, LfPackage.eINSTANCE.getStateVar(), null, "Missing time unit."); + validator.assertError( + model, + LfPackage.eINSTANCE.getStateVar(), + null, + "Referenced parameter is not of time type."); + validator.assertError(model, LfPackage.eINSTANCE.getStateVar(), null, "Invalid time value."); + validator.assertError(model, LfPackage.eINSTANCE.getTimer(), null, "Missing time unit."); + } + + /** Recognize valid IPV4 addresses, report invalid ones. */ + @Test + public void recognizeIPV4() throws Exception { + List correct = + List.of("127.0.0.1", "10.0.0.1", "192.168.1.1", "0.0.0.0", "192.168.1.1"); + List parseError = List.of("10002.3.4", "1.2.3.4.5"); + List validationError = List.of("256.0.0.0", "260.0.0.0"); + + // Correct IP addresses. + for (String addr : correct) { + + String testCase = + """ target C; reactor Y {} federated reactor X at foo@%s:4242 { - y = new Y() at %s:2424; + y = new Y() at %s:2424; } - """.formatted(addr, addr); - parseWithoutError( - testCase - ); - } + """ + .formatted(addr, addr); + parseWithoutError(testCase); + } - // IP addresses that don't parse. - for (String addr : parseError) { - String testCase = """ + // IP addresses that don't parse. + for (String addr : parseError) { + String testCase = + """ target C; reactor Y {} federated reactor X at foo@%s:4242 { - y = new Y() at %s:2424; + y = new Y() at %s:2424; } - """.formatted(addr, addr); - parseWithError(testCase); - } + """ + .formatted(addr, addr); + parseWithError(testCase); + } - // IP addresses that parse but are invalid. - for (String addr : validationError) { - Model model = parseWithoutError(""" + // IP addresses that parse but are invalid. + for (String addr : validationError) { + Model model = + parseWithoutError( + """ target C; reactor Y {} federated reactor X at foo@%s:4242 { - y = new Y() at %s:2424; + 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. - */ - @Test - public void recognizeIPV6() throws Exception { - List correct = List.of("1:2:3:4:5:6:7:8", "1:2:3:4:5:6:7::", "1:2:3:4:5:6::8", - "1:2:3:4:5::8", "1:2:3:4::8", "1:2:3::8", "1:2::8", "1::8", "::8", - "::", "1::3:4:5:6:7:8", "1::4:5:6:7:8", "1::5:6:7:8", "1::6:7:8", - "1::7:8", "1::8", "1::", "1:2:3:4:5::7:8", "1:2:3:4::6:7:8", - "1:2:3::5:6:7:8", "1:2::4:5:6:7:8", "1::3:4:5:6:7:8", - "::2:3:4:5:6:7:8", "fe80::7:8", "fe80::7:8%eth0", "fe80::7:8%1", - "::255.255.255.255", "::ffff:255.255.255.255", - "::ffff:0:255.255.255.0", "::ffff:00:255.255.255.0", - "::ffff:000:255.255.255.0", "::ffff:0000:255.255.255.0", - "::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) { - String testCase = """ + """ + .formatted(addr, addr)); + validator.assertWarning(model, LfPackage.eINSTANCE.getHost(), null, "Invalid IP address."); + } + } + + /** Recognize valid IPV6 addresses, report invalid ones. */ + @Test + public void recognizeIPV6() throws Exception { + List correct = + List.of( + "1:2:3:4:5:6:7:8", + "1:2:3:4:5:6:7::", + "1:2:3:4:5:6::8", + "1:2:3:4:5::8", + "1:2:3:4::8", + "1:2:3::8", + "1:2::8", + "1::8", + "::8", + "::", + "1::3:4:5:6:7:8", + "1::4:5:6:7:8", + "1::5:6:7:8", + "1::6:7:8", + "1::7:8", + "1::8", + "1::", + "1:2:3:4:5::7:8", + "1:2:3:4::6:7:8", + "1:2:3::5:6:7:8", + "1:2::4:5:6:7:8", + "1::3:4:5:6:7:8", + "::2:3:4:5:6:7:8", + "fe80::7:8", + "fe80::7:8%eth0", + "fe80::7:8%1", + "::255.255.255.255", + "::ffff:255.255.255.255", + "::ffff:0:255.255.255.0", + "::ffff:00:255.255.255.0", + "::ffff:000:255.255.255.0", + "::ffff:0000:255.255.255.0", + "::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) { + String testCase = + """ target C; reactor Y {} federated reactor at [foo@%s]:4242 { - y = new Y() at [%s]:2424; + y = new Y() at [%s]:2424; } - """.formatted(addr, addr); - Model model = parseWithoutError(testCase); - validator.assertNoIssues(model); - } - + """ + .formatted(addr, addr); + Model model = parseWithoutError(testCase); + validator.assertNoIssues(model); + } - // IP addresses that don't parse. - for (String addr : parseError) { - String testCase = """ + // IP addresses that don't parse. + for (String addr : parseError) { + String testCase = + """ target C; reactor Y {} federated reactor X at [foo@%s]:4242 { - y = new Y() at [%s]:2424; + y = new Y() at [%s]:2424; } - """.formatted(addr, addr); - parseWithError(testCase); - } + """ + .formatted(addr, addr); + parseWithError(testCase); + } - // IP addresses that parse but are invalid. - for (String addr : validationError) { - String testCase = """ + // IP addresses that parse but are invalid. + for (String addr : validationError) { + String testCase = + """ target C; reactor Y {} federated reactor X at [foo@%s]:4242 { - y = new Y() at [%s]:2424; + y = new Y() at [%s]:2424; } - """.formatted(addr, addr); - Model model = parseWithoutError( - testCase - ); - validator.assertWarning(model, LfPackage.eINSTANCE.getHost(), null, "Invalid IP address."); - } + """ + .formatted(addr, addr); + Model model = parseWithoutError(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 + /** Recognize valid host names and fully qualified names, report invalid ones. */ + @Test + public void recognizeHostNames() throws Exception { - List validationError = List.of("x.y.z"); // FIXME: add more + List correct = List.of("localhost"); // FIXME: add more - List parseError = List.of("..xyz"); // 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) { - String testCase = """ + // Correct names. + for (String addr : correct) { + String testCase = + """ target C; reactor Y {} federated reactor X at foo@%s:4242 { - y = new Y() at %s:2424; + y = new Y() at %s:2424; } - """.formatted(addr, addr); - parseWithoutError( - testCase - ); - } + """ + .formatted(addr, addr); + parseWithoutError(testCase); + } - // Names that don't parse. - for (String addr : parseError) { - String testCase = """ + // Names that don't parse. + for (String addr : parseError) { + String testCase = + """ target C; reactor Y {} federated reactor X at foo@%s:4242 { - y = new Y() at %s:2424; + y = new Y() at %s:2424; } - """.formatted(addr, addr); - parseWithError( - testCase - ); - } + """ + .formatted(addr, addr); + parseWithError(testCase); + } - // Names that parse but are invalid. - for (String addr : validationError) { - String testCase = """ + // Names that parse but are invalid. + for (String addr : validationError) { + String testCase = + """ target C; reactor Y {} federated reactor X at foo@%s:4242 { - y = new Y() at %s:2424; + y = new Y() at %s:2424; } - """.formatted(addr, addr); - Model model = parseWithoutError( - testCase - ); - 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. - */ - Map> primitiveTypeToKnownGood = Map.of( - PrimitiveType.BOOLEAN, List.of("true", "\"true\"", "false", "\"false\""), - PrimitiveType.INTEGER, List.of("0", "1", "\"42\"", "\"-1\"", "-2"), - PrimitiveType.NON_NEGATIVE_INTEGER, List.of("0", "1", "42"), - PrimitiveType.TIME_VALUE, List.of("1 msec", "2 sec"), - PrimitiveType.STRING, List.of("1", "\"foo\"", "bar"), - PrimitiveType.FILE, List.of("valid.file", "something.json", "\"foobar.proto\"") - ); - - /** - * Maps a type to a list of known bad values. - */ - Map> primitiveTypeToKnownBad = Map.of( - PrimitiveType.BOOLEAN, List.of("1 sec", "foo", "\"foo\"", "[1]", "{baz: 42}", "'c'"), - PrimitiveType.INTEGER, List.of("foo", "\"bar\"", "1 sec", "[1, 2]", "{foo: \"bar\"}", "'c'"), - PrimitiveType.NON_NEGATIVE_INTEGER, List.of("-42", "foo", "\"bar\"", "1 sec", "[1, 2]", "{foo: \"bar\"}", "'c'"), - PrimitiveType.TIME_VALUE, List.of("foo", "\"bar\"", "\"3 sec\"", "\"4 weeks\"", "[1, 2]", "{foo: \"bar\"}", "'c'"), - PrimitiveType.STRING, List.of("1 msec", "[1, 2]", "{foo: \"bar\"}", "'c'") - ); - - /** - * 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. - */ - Map>> compositeTypeToKnownBad = Map.of( - ArrayType.STRING_ARRAY, List.of( - List.of("[1 msec]", "[0]", PrimitiveType.STRING), - List.of("[foo, {bar: baz}]", "[1]", PrimitiveType.STRING), - List.of("{bar: baz}", "", ArrayType.STRING_ARRAY) - ), - UnionType.STRING_OR_STRING_ARRAY, List.of( - List.of("[1 msec]", "[0]", PrimitiveType.STRING), - List.of("[foo, {bar: baz}]", "[1]", PrimitiveType.STRING), - List.of("{bar: baz}", "", UnionType.STRING_OR_STRING_ARRAY) - ), - UnionType.PLATFORM_STRING_OR_DICTIONARY, List.of( - List.of("[bar, baz]", "", UnionType.PLATFORM_STRING_OR_DICTIONARY), - List.of("{name: [1, 2, 3]}", ".name", PrimitiveType.STRING), - List.of("{name: {bar: baz}}", ".name", PrimitiveType.STRING), - List.of("{board: [1, 2, 3]}", ".board", PrimitiveType.STRING), - List.of("{board: {bar: baz}}", ".board", PrimitiveType.STRING), - List.of("{baud-rate: [1, 2, 3]}", ".baud-rate", PrimitiveType.NON_NEGATIVE_INTEGER), - List.of("{baud-rate: {bar: baz}}", ".baud-rate", PrimitiveType.NON_NEGATIVE_INTEGER) - ), - UnionType.FILE_OR_FILE_ARRAY, List.of( - List.of("[1 msec]", "[0]", PrimitiveType.FILE), - List.of("[foo, {bar: baz}]", "[1]", PrimitiveType.FILE), - List.of("{bar: baz}", "", UnionType.FILE_OR_FILE_ARRAY) - ), - UnionType.DOCKER_UNION, List.of( - List.of("foo", "", UnionType.DOCKER_UNION), - List.of("[1]", "", UnionType.DOCKER_UNION), - List.of("{bar: baz}", "", DictionaryType.DOCKER_DICT), - List.of("{FROM: [1, 2, 3]}", ".FROM", PrimitiveType.STRING) - ), - UnionType.TRACING_UNION, List.of( - List.of("foo", "", UnionType.TRACING_UNION), - List.of("[1]", "", UnionType.TRACING_UNION), - List.of("{bar: baz}", "", DictionaryType.TRACING_DICT), - 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, - * depending on the value of the second parameter. - */ - private List synthesizeExamples(ArrayType type, boolean correct) { - Map> values = correct ? primitiveTypeToKnownGood - : primitiveTypeToKnownBad; - List examples = new LinkedList<>(); - if (correct) { - // Produce an array that has an entry for each value. - List entries = values.get(type.type); - if (!(entries == null || entries.isEmpty())) { - examples.add(String.format("[%s]", String.join(", ", entries))); - } - } - return 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) { - List examples = new LinkedList<>(); - if (correct) { - for (Enum it : type.options) { - if (it instanceof TargetPropertyType) { - synthesizeExamples((TargetPropertyType) it, correct).forEach(ex -> examples.add(ex)); - } else { - examples.add(it.toString()); - } - } - } 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)) { - 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, - * depending on the value of the second parameter. - */ - private List synthesizeExamples(DictionaryType type, boolean correct) { - List examples = new LinkedList<>(); - // 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 + "}")); - } - return examples; - } - - private List synthesizeExamples(StringDictionaryType type, boolean correct) { - List examples = new LinkedList<>(); - // Produce a set of singleton dictionaries. - // If incorrect examples are wanted, use non-strings for values. - List goodStrs = synthesizeExamples(PrimitiveType.STRING, true); - List badStrs = synthesizeExamples(PrimitiveType.STRING, false); - List goodIDs = List.of("foo", "Bar", "__ab0_9fC", "f1o_O2B_a3r"); - if (correct) { - for (String gs : goodStrs) { - goodIDs.forEach(it -> examples.add("{" + it + ": " + gs + "}")); - } - } else { - for (String bs : badStrs) { - goodIDs.forEach(it -> examples.add("{" + it + ": " + bs + "}")); - } - } - 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 - * (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. - */ - private List synthesizeExamples(TargetPropertyType type, boolean correct) { - if (type instanceof PrimitiveType) { - Map> values = correct ? primitiveTypeToKnownGood - : primitiveTypeToKnownBad; - List examples = values.get(type); - Assertions.assertNotNull(examples); - return examples; + """ + .formatted(addr, addr); + Model model = parseWithoutError(testCase); + 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. */ + Map> primitiveTypeToKnownGood = + Map.of( + PrimitiveType.BOOLEAN, List.of("true", "\"true\"", "false", "\"false\""), + PrimitiveType.INTEGER, List.of("0", "1", "\"42\"", "\"-1\"", "-2"), + PrimitiveType.NON_NEGATIVE_INTEGER, List.of("0", "1", "42"), + PrimitiveType.TIME_VALUE, List.of("1 msec", "2 sec"), + PrimitiveType.STRING, List.of("1", "\"foo\"", "bar"), + PrimitiveType.FILE, List.of("valid.file", "something.json", "\"foobar.proto\"")); + + /** Maps a type to a list of known bad values. */ + Map> primitiveTypeToKnownBad = + Map.of( + PrimitiveType.BOOLEAN, List.of("1 sec", "foo", "\"foo\"", "[1]", "{baz: 42}", "'c'"), + PrimitiveType.INTEGER, + List.of("foo", "\"bar\"", "1 sec", "[1, 2]", "{foo: \"bar\"}", "'c'"), + PrimitiveType.NON_NEGATIVE_INTEGER, + List.of("-42", "foo", "\"bar\"", "1 sec", "[1, 2]", "{foo: \"bar\"}", "'c'"), + PrimitiveType.TIME_VALUE, + List.of( + "foo", "\"bar\"", "\"3 sec\"", "\"4 weeks\"", "[1, 2]", "{foo: \"bar\"}", "'c'"), + PrimitiveType.STRING, List.of("1 msec", "[1, 2]", "{foo: \"bar\"}", "'c'")); + + /** + * 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. + */ + Map>> compositeTypeToKnownBad = + Map.of( + ArrayType.STRING_ARRAY, + List.of( + List.of("[1 msec]", "[0]", PrimitiveType.STRING), + List.of("[foo, {bar: baz}]", "[1]", PrimitiveType.STRING), + List.of("{bar: baz}", "", ArrayType.STRING_ARRAY)), + UnionType.STRING_OR_STRING_ARRAY, + List.of( + List.of("[1 msec]", "[0]", PrimitiveType.STRING), + List.of("[foo, {bar: baz}]", "[1]", PrimitiveType.STRING), + List.of("{bar: baz}", "", UnionType.STRING_OR_STRING_ARRAY)), + UnionType.PLATFORM_STRING_OR_DICTIONARY, + List.of( + List.of("[bar, baz]", "", UnionType.PLATFORM_STRING_OR_DICTIONARY), + List.of("{name: [1, 2, 3]}", ".name", PrimitiveType.STRING), + List.of("{name: {bar: baz}}", ".name", PrimitiveType.STRING), + List.of("{board: [1, 2, 3]}", ".board", PrimitiveType.STRING), + List.of("{board: {bar: baz}}", ".board", PrimitiveType.STRING), + List.of( + "{baud-rate: [1, 2, 3]}", ".baud-rate", PrimitiveType.NON_NEGATIVE_INTEGER), + List.of( + "{baud-rate: {bar: baz}}", ".baud-rate", PrimitiveType.NON_NEGATIVE_INTEGER)), + UnionType.FILE_OR_FILE_ARRAY, + List.of( + List.of("[1 msec]", "[0]", PrimitiveType.FILE), + List.of("[foo, {bar: baz}]", "[1]", PrimitiveType.FILE), + List.of("{bar: baz}", "", UnionType.FILE_OR_FILE_ARRAY)), + UnionType.DOCKER_UNION, + List.of( + List.of("foo", "", UnionType.DOCKER_UNION), + List.of("[1]", "", UnionType.DOCKER_UNION), + List.of("{bar: baz}", "", DictionaryType.DOCKER_DICT), + List.of("{FROM: [1, 2, 3]}", ".FROM", PrimitiveType.STRING)), + UnionType.TRACING_UNION, + List.of( + List.of("foo", "", UnionType.TRACING_UNION), + List.of("[1]", "", UnionType.TRACING_UNION), + List.of("{bar: baz}", "", DictionaryType.TRACING_DICT), + 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, depending on the value of the + * second parameter. + */ + private List synthesizeExamples(ArrayType type, boolean correct) { + Map> values = + correct ? primitiveTypeToKnownGood : primitiveTypeToKnownBad; + List examples = new LinkedList<>(); + if (correct) { + // Produce an array that has an entry for each value. + List entries = values.get(type.type); + if (!(entries == null || entries.isEmpty())) { + examples.add(String.format("[%s]", String.join(", ", entries))); + } + } + return 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) { + List examples = new LinkedList<>(); + if (correct) { + for (Enum it : type.options) { + if (it instanceof TargetPropertyType) { + synthesizeExamples((TargetPropertyType) it, correct).forEach(ex -> examples.add(ex)); } else { - if (type instanceof UnionType) { - return synthesizeExamples((UnionType) type, correct); - } else if (type instanceof ArrayType) { - return synthesizeExamples((ArrayType) type, correct); - } else if (type instanceof DictionaryType) { - return synthesizeExamples((DictionaryType) type, correct); - } else if (type instanceof StringDictionaryType) { - return synthesizeExamples((StringDictionaryType) type, correct); - } else { - Assertions.fail("Encountered an unknown type: " + type); - } + examples.add(it.toString()); } - 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.printf("%s: %s%n", key, value); - return parseWithoutError(""" + } + } 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)) { + 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, depending on the value of the + * second parameter. + */ + private List synthesizeExamples(DictionaryType type, boolean correct) { + List examples = new LinkedList<>(); + // 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 + "}")); + } + return examples; + } + + private List synthesizeExamples(StringDictionaryType type, boolean correct) { + List examples = new LinkedList<>(); + // Produce a set of singleton dictionaries. + // If incorrect examples are wanted, use non-strings for values. + List goodStrs = synthesizeExamples(PrimitiveType.STRING, true); + List badStrs = synthesizeExamples(PrimitiveType.STRING, false); + List goodIDs = List.of("foo", "Bar", "__ab0_9fC", "f1o_O2B_a3r"); + if (correct) { + for (String gs : goodStrs) { + goodIDs.forEach(it -> examples.add("{" + it + ": " + gs + "}")); + } + } else { + for (String bs : badStrs) { + goodIDs.forEach(it -> examples.add("{" + it + ": " + bs + "}")); + } + } + 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 (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. + */ + private List synthesizeExamples(TargetPropertyType type, boolean correct) { + if (type instanceof PrimitiveType) { + Map> values = + correct ? primitiveTypeToKnownGood : primitiveTypeToKnownBad; + List examples = values.get(type); + Assertions.assertNotNull(examples); + return examples; + } else { + if (type instanceof UnionType) { + return synthesizeExamples((UnionType) type, correct); + } else if (type instanceof ArrayType) { + return synthesizeExamples((ArrayType) type, correct); + } else if (type instanceof DictionaryType) { + return synthesizeExamples((DictionaryType) type, correct); + } else if (type instanceof StringDictionaryType) { + return synthesizeExamples((StringDictionaryType) type, correct); + } else { + Assertions.fail("Encountered an unknown type: " + type); + } + } + 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.printf("%s: %s%n", key, value); + return parseWithoutError( + """ target %s {%s: %s}; reactor Y {} main reactor { - y = new Y() - } - """.formatted(target, key, value)); - } - - /** - * Perform checks on target properties. - */ - @Test - public void checkTargetProperties() throws Exception { - for (TargetProperty prop : TargetProperty.getOptions()) { - if (prop == TargetProperty.CARGO_DEPENDENCIES) { - // we test that separately as it has better error messages - return; - } - 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))); - } - } - - // 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 || -// prop.type == UnionType.FILE_OR_FILE_ARRAY) { -// val model = prop.createModel( -// synthesizeExamples(ArrayType.FILE_ARRAY, true).get(0)) -// primitiveTypeToKnownGood.get(PrimitiveType.FILE).forEach [ -// model.assertWarning( -// LfPackage.eINSTANCE.keyValuePair, -// 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."); - } else { - validator.assertError(createModel(prop, it), - 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.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, it.get(1), it.get(2))); - } - } - } - System.out.println("===="); + y = new Y() + } + """ + .formatted(target, key, value)); + } + + /** Perform checks on target properties. */ + @Test + public void checkTargetProperties() throws Exception { + for (TargetProperty prop : TargetProperty.getOptions()) { + if (prop == TargetProperty.CARGO_DEPENDENCIES) { + // we test that separately as it has better error messages + return; + } + 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))); } - System.out.println("Done!"); - } - - - @Test - public void checkCargoDependencyProperty() throws Exception { - TargetProperty prop = TargetProperty.CARGO_DEPENDENCIES; - List knownCorrect = List.of("{}", "{ dep: \"8.2\" }", "{ dep: { version: \"8.2\"} }", "{ dep: { version: \"8.2\", features: [\"foo\"]} }"); - for (String it : knownCorrect) { - validator.assertNoErrors(createModel(prop, 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 || + // prop.type == UnionType.FILE_OR_FILE_ARRAY) { + // val model = prop.createModel( + // synthesizeExamples(ArrayType.FILE_ARRAY, true).get(0)) + // primitiveTypeToKnownGood.get(PrimitiveType.FILE).forEach [ + // model.assertWarning( + // LfPackage.eINSTANCE.keyValuePair, + // 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."); + } else { + validator.assertError( + createModel(prop, it), + LfPackage.eINSTANCE.getKeyValuePair(), + null, + String.format("Target property '%s' is required to be %s.", prop, prop.type)); + } } - - // vvvvvvvvvvv - 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'" - ); - - // vvvv - validator.assertError(createModel(prop, "{ dep: { features: \"\" } }"), - LfPackage.eINSTANCE.getElement(), null, "Expected an array of strings for key 'features'" - ); - } - - @Test - public void testImportedCyclicReactor() throws Exception { - // File tempFile = File.createTempFile("lf-validation", ".lf"); - // tempFile.deleteOnExit(); - // // Java 17: - // // String fileToBeImported = """ - // // target C; - // // reactor A { - // // a = new A(); - // // } - // // """ - // // Java 11: - // String fileToBeImported = String.join(System.getProperty("line.separator"), - // "target C;", - // "reactor A {", - // " a = new A();", - // "}" - // ); - // BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile)); - // writer.write(fileToBeImported); - // writer.close(); - - // // Java 17: - // // String testCase = """ - // // target C; - // // import A from ... - // // main reactor { - // // } - // // """ - // // Java 11: - // String testCase = String.join(System.getProperty("line.separator"), - // "target C;", - // String.format("import A from \"%s\"", tempFile.getAbsolutePath()), - // "main reactor {", - // "}" - // ); - // Model model = parseWithoutError(testCase); - // TODO: Uncomment the lines below and resolve the weird error. (java.lang.IllegalArgumentException: resolve against non-hierarchical or relative base) - // validator.assertError(model, LfPackage.eINSTANCE.getImportedReactor(), null, "Imported reactor 'A' has cyclic instantiation in it."); - } - - @Test - public void testUnusedImport() throws Exception { - // File tempFile = File.createTempFile("lf-validation", ".lf"); - // tempFile.deleteOnExit(); - // // Java 17: - // // String fileToBeImported = """ - // // target C; - // // reactor A {} - // // """ - // // Java 11: - // String fileToBeImported = String.join(System.getProperty("line.separator"), - // "target C;", - // "reactor A{}" - // ); - // BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile)); - // writer.write(fileToBeImported); - // writer.close(); - - // // Java 17: - // // String testCase = """ - // // target C; - // // import A from ... - // // main reactor {} - // // """ - // // Java 11: - // String testCase = String.join(System.getProperty("line.separator"), - // "target C;", - // String.format("import A from \"%s\"", tempFile.getAbsolutePath()), - // "main reactor{}" - // ); - // Model model = parseWithoutError(testCase); - // TODO: Uncomment the lines below and resolve the weird error. (java.lang.IllegalArgumentException: resolve against non-hierarchical or relative base) - // validator.assertWarning(model, LfPackage.eINSTANCE.getImport(), null, "Unused import."); - // validator.assertWarning(parseWithoutError(testCase), LfPackage.eINSTANCE.getImportedReactor(), null, "Unused reactor class."); - } - - @Test - public void testMissingInputType() throws Exception { - String testCase = """ + } else { + // No type was synthesized. It must be a composite type. + List> list = compositeTypeToKnownBad.get(prop.type); + if (list == null) { + 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, it.get(1), it.get(2))); + } + } + } + System.out.println("===="); + } + System.out.println("Done!"); + } + + @Test + public void checkCargoDependencyProperty() throws Exception { + TargetProperty prop = TargetProperty.CARGO_DEPENDENCIES; + List knownCorrect = + List.of( + "{}", + "{ dep: \"8.2\" }", + "{ dep: { version: \"8.2\"} }", + "{ dep: { version: \"8.2\", features: [\"foo\"]} }"); + for (String it : knownCorrect) { + validator.assertNoErrors(createModel(prop, it)); + } + + // vvvvvvvvvvv + 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'"); + + // vvvv + validator.assertError( + createModel(prop, "{ dep: { features: \"\" } }"), + LfPackage.eINSTANCE.getElement(), + null, + "Expected an array of strings for key 'features'"); + } + + @Test + public void testImportedCyclicReactor() throws Exception { + // File tempFile = File.createTempFile("lf-validation", ".lf"); + // tempFile.deleteOnExit(); + // // Java 17: + // // String fileToBeImported = """ + // // target C; + // // reactor A { + // // a = new A(); + // // } + // // """ + // // Java 11: + // String fileToBeImported = String.join(System.getProperty("line.separator"), + // "target C;", + // "reactor A {", + // " a = new A();", + // "}" + // ); + // BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile)); + // writer.write(fileToBeImported); + // writer.close(); + + // // Java 17: + // // String testCase = """ + // // target C; + // // import A from ... + // // main reactor { + // // } + // // """ + // // Java 11: + // String testCase = String.join(System.getProperty("line.separator"), + // "target C;", + // String.format("import A from \"%s\"", tempFile.getAbsolutePath()), + // "main reactor {", + // "}" + // ); + // Model model = parseWithoutError(testCase); + // TODO: Uncomment the lines below and resolve the weird error. + // (java.lang.IllegalArgumentException: resolve against non-hierarchical or relative base) + // validator.assertError(model, LfPackage.eINSTANCE.getImportedReactor(), null, "Imported + // reactor 'A' has cyclic instantiation in it."); + } + + @Test + public void testUnusedImport() throws Exception { + // File tempFile = File.createTempFile("lf-validation", ".lf"); + // tempFile.deleteOnExit(); + // // Java 17: + // // String fileToBeImported = """ + // // target C; + // // reactor A {} + // // """ + // // Java 11: + // String fileToBeImported = String.join(System.getProperty("line.separator"), + // "target C;", + // "reactor A{}" + // ); + // BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile)); + // writer.write(fileToBeImported); + // writer.close(); + + // // Java 17: + // // String testCase = """ + // // target C; + // // import A from ... + // // main reactor {} + // // """ + // // Java 11: + // String testCase = String.join(System.getProperty("line.separator"), + // "target C;", + // String.format("import A from \"%s\"", tempFile.getAbsolutePath()), + // "main reactor{}" + // ); + // Model model = parseWithoutError(testCase); + // TODO: Uncomment the lines below and resolve the weird error. + // (java.lang.IllegalArgumentException: resolve against non-hierarchical or relative base) + // validator.assertWarning(model, LfPackage.eINSTANCE.getImport(), null, "Unused import."); + // validator.assertWarning(parseWithoutError(testCase), + // LfPackage.eINSTANCE.getImportedReactor(), null, "Unused reactor class."); + } + + @Test + public void testMissingInputType() throws Exception { + String testCase = + """ target C; main reactor { input i; } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getInput(), null, - "Input must have a type."); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getInput(), + null, + "Input must have a type."); + } - @Test - public void testMissingOutputType() throws Exception { - String testCase = """ + @Test + public void testMissingOutputType() throws Exception { + String testCase = + """ target C; main reactor { output i; } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getOutput(), null, - "Output must have a type."); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getOutput(), + null, + "Output must have a type."); + } - @Test - public void testMissingStateType() throws Exception { - String testCase = """ + @Test + public void testMissingStateType() throws Exception { + String testCase = + """ target C; main reactor { state i; } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getStateVar(), null, - "State must have a type."); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getStateVar(), + null, + "State must have a type."); + } - @Test - public void testListWithParam() throws Exception { - String testCase = """ + @Test + public void testListWithParam() throws Exception { + 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 { - String testCase = """ + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getStateVar(), + null, + "List items cannot refer to a parameter."); + } + + @Test + public void testCppMutableInput() throws Exception { + 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()."); - } + 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()."); + } - @Test - public void testOverflowingSTP() throws Exception { - String testCase = """ + @Test + public void testOverflowingSTP() throws Exception { + 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."); - } + // 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."); + } - @Test - public void testOverflowingDeadline() throws Exception { - String testCase = """ + @Test + public void testOverflowingDeadline() throws Exception { + 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."); - } + // 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."); + } - @Test - public void testInvalidTargetParam() throws Exception { - String testCase = """ + @Test + public void testInvalidTargetParam() throws Exception { + String testCase = + """ target C { beefyDesktop: true } main reactor {} """; - List issues = validator.validate(parseWithoutError(testCase)); - Assertions.assertTrue(issues.size() == 1 + List issues = validator.validate(parseWithoutError(testCase)); + Assertions.assertTrue( + issues.size() == 1 && issues.get(0).getMessage().contains("Unrecognized target parameter: beefyDesktop")); - } + } - @Test - public void testTargetParamNotSupportedForTarget() throws Exception { - String testCase = """ + @Test + public void testTargetParamNotSupportedForTarget() throws Exception { + 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.")); - } - - @Test - public void testUnnamedReactor() throws Exception { - String testCase = """ + 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.")); + } + + @Test + public void testUnnamedReactor() throws Exception { + String testCase = """ target C; reactor {} """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, - "Reactor must be named."); - } - - @Test - public void testMainHasInput() throws Exception { - String testCase = """ + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getReactor(), + null, + "Reactor must be named."); + } + + @Test + public void testMainHasInput() throws Exception { + String testCase = + """ target C; main reactor { input x:int; } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getInput(), null, - "Main reactor cannot have inputs."); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getInput(), + null, + "Main reactor cannot have inputs."); + } - @Test - public void testFederatedHasInput() throws Exception { + @Test + public void testFederatedHasInput() throws Exception { - String testCase = """ + String testCase = + """ target C; federated reactor { input x:int; } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getInput(), null, - "Main reactor cannot have inputs."); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getInput(), + null, + "Main reactor cannot have inputs."); + } - @Test - public void testMainHasOutput() throws Exception { + @Test + public void testMainHasOutput() throws Exception { - String testCase = """ + String testCase = + """ target C; main reactor { output x:int; } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getOutput(), null, - "Main reactor cannot have outputs."); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getOutput(), + null, + "Main reactor cannot have outputs."); + } - @Test - public void testFederatedHasOutput() throws Exception { + @Test + public void testFederatedHasOutput() throws Exception { - String testCase = """ + String testCase = + """ target C; federated reactor { output x:int; } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getOutput(), null, - "Main reactor cannot have outputs."); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getOutput(), + null, + "Main reactor cannot have outputs."); + } - @Test - public void testMultipleMainReactor() throws Exception { + @Test + public void testMultipleMainReactor() throws Exception { - String testCase = """ + 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."); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getReactor(), + null, + "Multiple definitions of main or federated reactor."); + } - @Test - public void testMultipleMainReactorUnnamed() throws Exception { + @Test + public void testMultipleMainReactorUnnamed() throws Exception { - String testCase = """ + 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 { - String testCase = """ + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getReactor(), + null, + "Multiple definitions of main or federated reactor."); + } + + @Test + public void testMultipleFederatedReactor() throws Exception { + 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."); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getReactor(), + null, + "Multiple definitions of main or federated reactor."); + } - @Test - public void testMultipleMainOrFederatedReactor() throws Exception { + @Test + public void testMultipleMainOrFederatedReactor() throws Exception { - String testCase = """ + 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 { - String testCase = """ + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getReactor(), + null, + "Multiple definitions of main or federated reactor."); + } + + @Test + public void testMainReactorHasHost() throws Exception { + 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 && - // issues.get(0).getMessage().contains("Cannot assign a host to reactor '") && - // issues.get(0).getMessage().contains("' because it is not federated.")); - } + // TODO: Uncomment and fix test + // List issues = validator.validate(parseWithoutError(testCase)); + // 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.")); + } - @Test - public void testUnrecognizedTarget() throws Exception { + @Test + public void testUnrecognizedTarget() throws Exception { - String testCase = """ + String testCase = + """ target Pjthon; main reactor {} """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getTargetDecl(), null, - "Unrecognized target: Pjthon"); - } - - - @Test - public void testWrongStructureForLabelAttribute() throws Exception { - String testCase = """ + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getTargetDecl(), + null, + "Unrecognized target: Pjthon"); + } + + @Test + public void testWrongStructureForLabelAttribute() throws Exception { + String testCase = + """ target C; @label(name="something") main reactor { } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getAttribute(), null, - "Unknown attribute parameter."); - } - - @Test - public void testMissingName() throws Exception { - String testCase = """ + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getAttribute(), + null, + "Unknown attribute parameter."); + } + + @Test + public void testMissingName() throws Exception { + String testCase = + """ target C; @label("something", "else") main reactor { } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getAttribute(), null, - "Missing name for attribute parameter."); - } - - @Test - public void testWrongParamType() throws Exception { - String testCase = """ + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getAttribute(), + null, + "Missing name for attribute parameter."); + } + + @Test + public void testWrongParamType() throws Exception { + String testCase = + """ target C; @label(value=1) main reactor { } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getAttribute(), null, - "Incorrect type: \"value\" should have type String."); - } - - @Test - public void testInitialMode() throws Exception { - String testCase = """ + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getAttribute(), + null, + "Incorrect type: \"value\" should have type String."); + } + + @Test + public void testInitialMode() throws Exception { + String testCase = + """ target C; main reactor { mode M {} } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, - "Every modal reactor requires one initial mode."); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getReactor(), + null, + "Every modal reactor requires one initial mode."); + } - @Test - public void testInitialModes() throws Exception { - String testCase = """ + @Test + public void testInitialModes() throws Exception { + String testCase = + """ 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."); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getReactor(), + null, + "A modal reactor can only have one initial mode."); + } - @Test - public void testModeStateNamespace() throws Exception { - String testCase = """ + @Test + public void testModeStateNamespace() throws Exception { + String testCase = + """ target C; main reactor { initial mode IM { @@ -1752,13 +2002,18 @@ public void testModeStateNamespace() throws Exception { } } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getStateVar(), null, - "Duplicate state variable 's'. (State variables are currently scoped on reactor level not modes)"); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getStateVar(), + null, + "Duplicate state variable 's'. (State variables are currently scoped on reactor level not" + + " modes)"); + } - @Test - public void testModeTimerNamespace() throws Exception { - String testCase = """ + @Test + public void testModeTimerNamespace() throws Exception { + String testCase = + """ target C; main reactor { initial mode IM { @@ -1769,13 +2024,17 @@ public void testModeTimerNamespace() throws Exception { } } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getTimer(), null, - "Duplicate Timer 't'. (Timers are currently scoped on reactor level not modes)"); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getTimer(), + null, + "Duplicate Timer 't'. (Timers are currently scoped on reactor level not modes)"); + } - @Test - public void testModeActionNamespace() throws Exception { - String testCase = """ + @Test + public void testModeActionNamespace() throws Exception { + String testCase = + """ target C; main reactor { initial mode IM { @@ -1786,13 +2045,17 @@ public void testModeActionNamespace() throws Exception { } } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getAction(), null, - "Duplicate Action 'a'. (Actions are currently scoped on reactor level not modes)"); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getAction(), + null, + "Duplicate Action 'a'. (Actions are currently scoped on reactor level not modes)"); + } - @Test - public void testModeInstanceNamespace() throws Exception { - String testCase = """ + @Test + public void testModeInstanceNamespace() throws Exception { + String testCase = + """ target C; reactor R {} main reactor { @@ -1804,13 +2067,18 @@ public void testModeInstanceNamespace() throws Exception { } } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getInstantiation(), null, - "Duplicate Instantiation 'r'. (Instantiations are currently scoped on reactor level not modes)"); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getInstantiation(), + null, + "Duplicate Instantiation 'r'. (Instantiations are currently scoped on reactor level not" + + " modes)"); + } - @Test - public void testMissingModeStateReset() throws Exception { - String testCase = """ + @Test + public void testMissingModeStateReset() throws Exception { + String testCase = + """ target C; main reactor { initial mode IM { @@ -1821,13 +2089,18 @@ public void testMissingModeStateReset() throws Exception { } } """; - 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."); - } + 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."); + } - @Test - public void testMissingModeStateResetInstance() throws Exception { - String testCase = """ + @Test + public void testMissingModeStateResetInstance() throws Exception { + String testCase = + """ target C; reactor R { state s:int = 0; @@ -1841,16 +2114,20 @@ public void testMissingModeStateResetInstance() throws Exception { } } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getMode(), null, - "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 unsafe to instantiate this reactor inside a mode entered with reset."); - } - - @Test - public void testModeStateResetWithoutInitialValue() throws Exception { - String testCase = """ + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getMode(), + null, + "This reactor contains state variables that are not reset upon mode entry: s in R.\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."); + } + + @Test + public void testModeStateResetWithoutInitialValue() throws Exception { + String testCase = + """ target C; main reactor { initial mode IM { @@ -1858,13 +2135,17 @@ public void testModeStateResetWithoutInitialValue() throws Exception { } } """; - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getStateVar(), null, - "The state variable can not be automatically reset without an initial value."); - } + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getStateVar(), + null, + "The state variable can not be automatically reset without an initial value."); + } - @Test - public void testUnspecifiedTransitionType() throws Exception { - String testCase = """ + @Test + public void testUnspecifiedTransitionType() throws Exception { + String testCase = + """ target C; main reactor { initial mode IM { @@ -1875,10 +2156,12 @@ public void testUnspecifiedTransitionType() throws Exception { } } """; - validator.assertWarning(parseWithoutError(testCase), LfPackage.eINSTANCE.getReaction(), null, - "You should specify a transition type! " - + "Reset and history transitions have different effects on this target mode. " - + "Currently, a reset type is implicitly assumed."); - } - + validator.assertWarning( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getReaction(), + null, + "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/MixedRadixIntTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/MixedRadixIntTest.java index 2cee2468c8..b119da05c2 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/MixedRadixIntTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/MixedRadixIntTest.java @@ -2,111 +2,110 @@ import java.util.Arrays; import java.util.List; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.lflang.generator.MixedRadixInt; public class MixedRadixIntTest { - - // Constants used many times below. - List radixes = Arrays.asList(2, 3, 4, 5); - List digits = Arrays.asList(1, 2, 3, 4); - @Test - public void create() throws Exception { - MixedRadixInt num = new MixedRadixInt(digits, radixes, null); - Assertions.assertEquals("1%2, 2%3, 3%4, 4%5", num.toString()); - Assertions.assertEquals(1 + 2*2 + 3*6 + 4*24, num.get()); - - List altDigits = List.of(1, 2, 1); - MixedRadixInt altNum = new MixedRadixInt(altDigits, radixes, null); - Assertions.assertEquals(11, altNum.get()); - } + // Constants used many times below. + List radixes = Arrays.asList(2, 3, 4, 5); + List digits = Arrays.asList(1, 2, 3, 4); - @Test - public void createWithInfinity() throws Exception { - List radixes = List.of(2, 3, 4, 10000); - MixedRadixInt num = new MixedRadixInt(digits, radixes, null); - Assertions.assertEquals(1 + 2*2 + 3*6 + 4*24, num.get()); - } + @Test + public void create() throws Exception { + MixedRadixInt num = new MixedRadixInt(digits, radixes, null); + Assertions.assertEquals("1%2, 2%3, 3%4, 4%5", num.toString()); + Assertions.assertEquals(1 + 2 * 2 + 3 * 6 + 4 * 24, num.get()); - @Test - public void createWithError() throws Exception { - List radixes = List.of(2, 3); - try { - new MixedRadixInt(digits, radixes, null); - } catch (IllegalArgumentException ex) { - Assertions.assertTrue(ex.getMessage().startsWith("Invalid")); - return; - } - Assertions.assertTrue(false, "Expected exception did not occur."); - } - - @Test - public void createWithNullAndSet() throws Exception { - MixedRadixInt num = new MixedRadixInt(null, radixes, null); - Assertions.assertEquals(0, num.get()); - Assertions.assertEquals("0%2", num.toString()); - num.set(1 + 2*2 + 3*6 + 4*24); - Assertions.assertEquals(1 + 2*2 + 3*6 + 4*24, num.get()); - int mag = num.magnitude(); - Assertions.assertEquals(1 + 2*2 + 3*6 + 4*24, mag); - num.increment(); // wrap to zero. - Assertions.assertEquals(0, num.get()); - Assertions.assertEquals(0, num.magnitude()); - - num = new MixedRadixInt(null, radixes, null); - num.setMagnitude(mag); - Assertions.assertEquals(mag, num.magnitude()); - } - - @Test - public void testPermutation() throws Exception { - List radixes = Arrays.asList(2, 5); - List digits = Arrays.asList(1, 2); - List permutation = Arrays.asList(1, 0); - MixedRadixInt num = new MixedRadixInt(digits, radixes, permutation); - Assertions.assertEquals(5, num.get()); - Assertions.assertEquals(2, num.get(1)); - Assertions.assertEquals(7, num.magnitude()); - num.increment(); - Assertions.assertEquals(7, num.get()); - Assertions.assertEquals(8, num.magnitude()); - - num = new MixedRadixInt(null, radixes, permutation); - num.setMagnitude(8); - Assertions.assertEquals(8, num.magnitude()); - Assertions.assertEquals(7, num.get()); + List altDigits = List.of(1, 2, 1); + MixedRadixInt altNum = new MixedRadixInt(altDigits, radixes, null); + Assertions.assertEquals(11, altNum.get()); + } - // Test radix infinity digit (effectively). - digits = Arrays.asList(1, 2, 1); - radixes = Arrays.asList(2, 5, 1000000); - num = new MixedRadixInt(digits, radixes, null); - Assertions.assertEquals(15, num.get()); - Assertions.assertEquals(7, num.get(1)); - Assertions.assertEquals(15, num.magnitude()); - - permutation = Arrays.asList(1, 0, 2); - num = new MixedRadixInt(digits, radixes, permutation); - num.increment(); - Assertions.assertEquals(17, num.get()); - Assertions.assertEquals(18, num.magnitude()); + @Test + public void createWithInfinity() throws Exception { + List radixes = List.of(2, 3, 4, 10000); + MixedRadixInt num = new MixedRadixInt(digits, radixes, null); + Assertions.assertEquals(1 + 2 * 2 + 3 * 6 + 4 * 24, num.get()); + } - num = new MixedRadixInt(null, radixes, permutation); - num.setMagnitude(18); - Assertions.assertEquals(18, num.magnitude()); - Assertions.assertEquals(17, num.get()); - } - - @Test - public void testIncrement() throws Exception { - List radixes = Arrays.asList(2, 3); - List digits = Arrays.asList(0, 2); - MixedRadixInt num = new MixedRadixInt(digits, radixes, null); - num.increment(); - Assertions.assertEquals(5, num.get()); - num.increment(); // Wrap around to zero. - Assertions.assertEquals(0, num.get()); + @Test + public void createWithError() throws Exception { + List radixes = List.of(2, 3); + try { + new MixedRadixInt(digits, radixes, null); + } catch (IllegalArgumentException ex) { + Assertions.assertTrue(ex.getMessage().startsWith("Invalid")); + return; } + Assertions.assertTrue(false, "Expected exception did not occur."); + } + + @Test + public void createWithNullAndSet() throws Exception { + MixedRadixInt num = new MixedRadixInt(null, radixes, null); + Assertions.assertEquals(0, num.get()); + Assertions.assertEquals("0%2", num.toString()); + num.set(1 + 2 * 2 + 3 * 6 + 4 * 24); + Assertions.assertEquals(1 + 2 * 2 + 3 * 6 + 4 * 24, num.get()); + int mag = num.magnitude(); + Assertions.assertEquals(1 + 2 * 2 + 3 * 6 + 4 * 24, mag); + num.increment(); // wrap to zero. + Assertions.assertEquals(0, num.get()); + Assertions.assertEquals(0, num.magnitude()); + + num = new MixedRadixInt(null, radixes, null); + num.setMagnitude(mag); + Assertions.assertEquals(mag, num.magnitude()); + } + + @Test + public void testPermutation() throws Exception { + List radixes = Arrays.asList(2, 5); + List digits = Arrays.asList(1, 2); + List permutation = Arrays.asList(1, 0); + MixedRadixInt num = new MixedRadixInt(digits, radixes, permutation); + Assertions.assertEquals(5, num.get()); + Assertions.assertEquals(2, num.get(1)); + Assertions.assertEquals(7, num.magnitude()); + num.increment(); + Assertions.assertEquals(7, num.get()); + Assertions.assertEquals(8, num.magnitude()); + + num = new MixedRadixInt(null, radixes, permutation); + num.setMagnitude(8); + Assertions.assertEquals(8, num.magnitude()); + Assertions.assertEquals(7, num.get()); + + // Test radix infinity digit (effectively). + digits = Arrays.asList(1, 2, 1); + radixes = Arrays.asList(2, 5, 1000000); + num = new MixedRadixInt(digits, radixes, null); + Assertions.assertEquals(15, num.get()); + Assertions.assertEquals(7, num.get(1)); + Assertions.assertEquals(15, num.magnitude()); + + permutation = Arrays.asList(1, 0, 2); + num = new MixedRadixInt(digits, radixes, permutation); + num.increment(); + Assertions.assertEquals(17, num.get()); + Assertions.assertEquals(18, num.magnitude()); + + num = new MixedRadixInt(null, radixes, permutation); + num.setMagnitude(18); + Assertions.assertEquals(18, num.magnitude()); + Assertions.assertEquals(17, num.get()); + } + + @Test + public void testIncrement() throws Exception { + List radixes = Arrays.asList(2, 3); + List digits = Arrays.asList(0, 2); + MixedRadixInt num = new MixedRadixInt(digits, radixes, null); + num.increment(); + Assertions.assertEquals(5, num.get()); + num.increment(); // Wrap around to zero. + Assertions.assertEquals(0, num.get()); + } } diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java index f5f2ba387b..8b8ef52a8f 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java @@ -1,15 +1,14 @@ package org.lflang.tests.compiler; import java.util.List; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.lflang.DefaultErrorReporter; import org.lflang.ErrorReporter; import org.lflang.generator.PortInstance; -import org.lflang.generator.RuntimeRange; import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactorInstance; +import org.lflang.generator.RuntimeRange; import org.lflang.generator.SendRange; import org.lflang.lf.LfFactory; import org.lflang.lf.Port; @@ -18,204 +17,205 @@ public class PortInstanceTests { - private ErrorReporter reporter = new DefaultErrorReporter(); - private static LfFactory factory = LfFactory.eINSTANCE; - - @Test - public void createRange() throws Exception { - Reactor main = factory.createReactor(); - ReactorInstance maini = new ReactorInstance(main, reporter); - - ReactorInstance a = newReactor("A", maini); - ReactorInstance b = newReactor("B", maini); - ReactorInstance c = newReactor("C", maini); - - PortInstance p = newOutputPort("p", a); - PortInstance q = newInputPort("q", b); - PortInstance r = newInputPort("r", c); - - Assertions.assertEquals(".A.p", p.getFullName()); - - connect(p, q); - connect(p, r); - - List sr = p.eventualDestinations(); - // Destinations should be empty because there are no reactions. - Assertions.assertEquals("[]", sr.toString()); - - // Clear caches to make a mutation. - maini.clearCaches(); - newReaction(q); - // Re-retrieve destinations. - sr = p.eventualDestinations(); - Assertions.assertEquals("[.A.p(0,1)->[.B.q(0,1)]]", sr.toString()); - - maini.clearCaches(); - newReaction(r); - // Re-retrieve destinations. - sr = p.eventualDestinations(); - Assertions.assertEquals("[.A.p(0,1)->[.B.q(0,1), .C.r(0,1)]]", sr.toString()); - - // Now test multiports. - p.setWidth(3); - r.setWidth(2); - // Have to redo the connections. - clearConnections(maini); - maini.clearCaches(); - connect(p, 0, 1, q, 0, 1); - connect(p, 1, 2, r, 0, 2); - - // Re-retrieve destinations. - sr = p.eventualDestinations(); - Assertions.assertEquals("[.A.p(0,1)->[.B.q(0,1)], .A.p(1,2)->[.C.r(0,2)]]", sr.toString()); - - // More complicated multiport connection. - clearConnections(maini); - maini.clearCaches(); - - ReactorInstance d = newReactor("D", maini); - PortInstance v = newOutputPort("v", d); - v.setWidth(2); - q.setWidth(3); - connect(v, 0, 2, q, 0, 2); - connect(p, 0, 1, q, 2, 1); - connect(p, 1, 2, r, 0, 2); - - sr = p.eventualDestinations(); - Assertions.assertEquals("[.A.p(0,1)->[.B.q(2,1)], .A.p(1,2)->[.C.r(0,2)]]", sr.toString()); - - // Additional multicast connection. - maini.clearCaches(); - ReactorInstance e = newReactor("E", maini); - PortInstance s = newPort("s", e); - s.setWidth(3); - newReaction(s); - connect(p, s); - - sr = p.eventualDestinations(); - Assertions.assertEquals("[.A.p(0,1)->[.B.q(2,1), .E.s(0,1)], .A.p(1,2)->[.C.r(0,2), .E.s(1,2)]]", sr.toString()); - - // Add hierarchical reactors that further split the ranges. - maini.clearCaches(); - ReactorInstance f = newReactor("F", e); - PortInstance t = newPort("t", f); - newReaction(t); - ReactorInstance g = newReactor("G", e); - PortInstance u = newPort("u", g); - u.setWidth(2); - newReaction(u); - connect(s, 0, 1, t, 0, 1); - connect(s, 1, 2, u, 0, 2); - - sr = p.eventualDestinations(); - // FIXME: Multicast destinations should be able to be reported in arbitrary order. - Assertions.assertEquals("[.A.p(0,1)->[.E.F.t(0,1), .E.s(0,1), .B.q(2,1)], .A.p(1,2)->[.E.G.u(0,2), .E.s(1,2), .C.r(0,2)]]", sr.toString()); - } - - @Test - public void multiportDestination() throws Exception { - Reactor main = factory.createReactor(); - ReactorInstance maini = new ReactorInstance(main, reporter); - - ReactorInstance a = newReactor("A", maini); - ReactorInstance b = newReactor("B", maini); - b.setWidth(4); - - PortInstance p = newOutputPort("p", a); - PortInstance q = newInputPort("q", b); - - connect(p, 0, 1, q, 0, 4); - - List sr = p.eventualDestinations(); - // Destination has no reactions, so empty list is right. - Assertions.assertEquals("[]", sr.toString()); - - maini.clearCaches(); - newReaction(q); - sr = p.eventualDestinations(); - Assertions.assertEquals("[.A.p(0,1)->[.B.q(0,4)]]", sr.toString()); -} - - /** - * Clear connections. This recursively clears them for all contained reactors. - */ - protected void clearConnections(ReactorInstance r) { - for (PortInstance p: r.inputs) { - p.getDependentPorts().clear(); - } - for (PortInstance p: r.outputs) { - p.getDependentPorts().clear(); - } - for (ReactorInstance child: r.children) { - clearConnections(child); - } - } - - /** - * Simple connection of two ports. This should be used only - * for connections that would be allowed in the syntax (i.e., no - * cross-hierarchy connections), but this is not checked. - * @param src The sending port. - * @param dst The receiving port. - */ - protected void connect(PortInstance src, PortInstance dst) { - RuntimeRange srcRange = new RuntimeRange.Port(src); - RuntimeRange dstRange = new RuntimeRange.Port(dst); - ReactorInstance.connectPortInstances(srcRange, dstRange, null); + private ErrorReporter reporter = new DefaultErrorReporter(); + private static LfFactory factory = LfFactory.eINSTANCE; + + @Test + public void createRange() throws Exception { + Reactor main = factory.createReactor(); + ReactorInstance maini = new ReactorInstance(main, reporter); + + ReactorInstance a = newReactor("A", maini); + ReactorInstance b = newReactor("B", maini); + ReactorInstance c = newReactor("C", maini); + + PortInstance p = newOutputPort("p", a); + PortInstance q = newInputPort("q", b); + PortInstance r = newInputPort("r", c); + + Assertions.assertEquals(".A.p", p.getFullName()); + + connect(p, q); + connect(p, r); + + List sr = p.eventualDestinations(); + // Destinations should be empty because there are no reactions. + Assertions.assertEquals("[]", sr.toString()); + + // Clear caches to make a mutation. + maini.clearCaches(); + newReaction(q); + // Re-retrieve destinations. + sr = p.eventualDestinations(); + Assertions.assertEquals("[.A.p(0,1)->[.B.q(0,1)]]", sr.toString()); + + maini.clearCaches(); + newReaction(r); + // Re-retrieve destinations. + sr = p.eventualDestinations(); + Assertions.assertEquals("[.A.p(0,1)->[.B.q(0,1), .C.r(0,1)]]", sr.toString()); + + // Now test multiports. + p.setWidth(3); + r.setWidth(2); + // Have to redo the connections. + clearConnections(maini); + maini.clearCaches(); + connect(p, 0, 1, q, 0, 1); + connect(p, 1, 2, r, 0, 2); + + // Re-retrieve destinations. + sr = p.eventualDestinations(); + Assertions.assertEquals("[.A.p(0,1)->[.B.q(0,1)], .A.p(1,2)->[.C.r(0,2)]]", sr.toString()); + + // More complicated multiport connection. + clearConnections(maini); + maini.clearCaches(); + + ReactorInstance d = newReactor("D", maini); + PortInstance v = newOutputPort("v", d); + v.setWidth(2); + q.setWidth(3); + connect(v, 0, 2, q, 0, 2); + connect(p, 0, 1, q, 2, 1); + connect(p, 1, 2, r, 0, 2); + + sr = p.eventualDestinations(); + Assertions.assertEquals("[.A.p(0,1)->[.B.q(2,1)], .A.p(1,2)->[.C.r(0,2)]]", sr.toString()); + + // Additional multicast connection. + maini.clearCaches(); + ReactorInstance e = newReactor("E", maini); + PortInstance s = newPort("s", e); + s.setWidth(3); + newReaction(s); + connect(p, s); + + sr = p.eventualDestinations(); + Assertions.assertEquals( + "[.A.p(0,1)->[.B.q(2,1), .E.s(0,1)], .A.p(1,2)->[.C.r(0,2), .E.s(1,2)]]", sr.toString()); + + // Add hierarchical reactors that further split the ranges. + maini.clearCaches(); + ReactorInstance f = newReactor("F", e); + PortInstance t = newPort("t", f); + newReaction(t); + ReactorInstance g = newReactor("G", e); + PortInstance u = newPort("u", g); + u.setWidth(2); + newReaction(u); + connect(s, 0, 1, t, 0, 1); + connect(s, 1, 2, u, 0, 2); + + sr = p.eventualDestinations(); + // FIXME: Multicast destinations should be able to be reported in arbitrary order. + Assertions.assertEquals( + "[.A.p(0,1)->[.E.F.t(0,1), .E.s(0,1), .B.q(2,1)], .A.p(1,2)->[.E.G.u(0,2), .E.s(1,2)," + + " .C.r(0,2)]]", + sr.toString()); + } + + @Test + public void multiportDestination() throws Exception { + Reactor main = factory.createReactor(); + ReactorInstance maini = new ReactorInstance(main, reporter); + + ReactorInstance a = newReactor("A", maini); + ReactorInstance b = newReactor("B", maini); + b.setWidth(4); + + PortInstance p = newOutputPort("p", a); + PortInstance q = newInputPort("q", b); + + connect(p, 0, 1, q, 0, 4); + + List sr = p.eventualDestinations(); + // Destination has no reactions, so empty list is right. + Assertions.assertEquals("[]", sr.toString()); + + maini.clearCaches(); + newReaction(q); + sr = p.eventualDestinations(); + Assertions.assertEquals("[.A.p(0,1)->[.B.q(0,4)]]", sr.toString()); + } + + /** Clear connections. This recursively clears them for all contained reactors. */ + protected void clearConnections(ReactorInstance r) { + for (PortInstance p : r.inputs) { + p.getDependentPorts().clear(); } - - /** - * Connection between multiports. This should be used only - * for connections that would be allowed in the syntax (i.e., no - * cross-hierarchy connections), but this is not checked. - * @param src The sending port. - * @param dst The receiving port. - */ - protected void connect( - PortInstance src, int srcStart, int srcWidth, - PortInstance dst, int dstStart, int dstWidth - ) { - RuntimeRange srcRange = new RuntimeRange.Port(src, srcStart, srcWidth, null); - RuntimeRange dstRange = new RuntimeRange.Port(dst, dstStart, dstWidth, null); - ReactorInstance.connectPortInstances(srcRange, dstRange, null); + for (PortInstance p : r.outputs) { + p.getDependentPorts().clear(); } - - protected PortInstance newPort(String name, ReactorInstance container) { - Port p = factory.createPort(); - p.setName(name); - return new PortInstance(p, container, reporter); - } - - protected PortInstance newInputPort(String name, ReactorInstance container) { - PortInstance pi = newPort(name, container); - container.inputs.add(pi); - return pi; - } - - protected PortInstance newOutputPort(String name, ReactorInstance container) { - PortInstance pi = newPort(name, container); - container.outputs.add(pi); - return pi; - } - - /** - * Return a new reaction triggered by the specified port. - * @param trigger The triggering port. - */ - protected ReactionInstance newReaction(PortInstance trigger) { - Reaction r = factory.createReaction(); - ReactionInstance result = new ReactionInstance( - r, trigger.getParent(), false, trigger.getDependentReactions().size()); - trigger.getDependentReactions().add(result); - trigger.getParent().reactions.add(result); - return result; - } - - protected ReactorInstance newReactor(String name, ReactorInstance container) { - Reactor r = factory.createReactor(); - r.setName(name); - ReactorInstance ri = new ReactorInstance(r, container, reporter); - container.children.add(ri); - return ri; + for (ReactorInstance child : r.children) { + clearConnections(child); } + } + + /** + * Simple connection of two ports. This should be used only for connections that would be allowed + * in the syntax (i.e., no cross-hierarchy connections), but this is not checked. + * + * @param src The sending port. + * @param dst The receiving port. + */ + protected void connect(PortInstance src, PortInstance dst) { + RuntimeRange srcRange = new RuntimeRange.Port(src); + RuntimeRange dstRange = new RuntimeRange.Port(dst); + ReactorInstance.connectPortInstances(srcRange, dstRange, null); + } + + /** + * Connection between multiports. This should be used only for connections that would be allowed + * in the syntax (i.e., no cross-hierarchy connections), but this is not checked. + * + * @param src The sending port. + * @param dst The receiving port. + */ + protected void connect( + PortInstance src, int srcStart, int srcWidth, PortInstance dst, int dstStart, int dstWidth) { + RuntimeRange srcRange = new RuntimeRange.Port(src, srcStart, srcWidth, null); + RuntimeRange dstRange = new RuntimeRange.Port(dst, dstStart, dstWidth, null); + ReactorInstance.connectPortInstances(srcRange, dstRange, null); + } + + protected PortInstance newPort(String name, ReactorInstance container) { + Port p = factory.createPort(); + p.setName(name); + return new PortInstance(p, container, reporter); + } + + protected PortInstance newInputPort(String name, ReactorInstance container) { + PortInstance pi = newPort(name, container); + container.inputs.add(pi); + return pi; + } + + protected PortInstance newOutputPort(String name, ReactorInstance container) { + PortInstance pi = newPort(name, container); + container.outputs.add(pi); + return pi; + } + + /** + * Return a new reaction triggered by the specified port. + * + * @param trigger The triggering port. + */ + protected ReactionInstance newReaction(PortInstance trigger) { + Reaction r = factory.createReaction(); + ReactionInstance result = + new ReactionInstance(r, trigger.getParent(), false, trigger.getDependentReactions().size()); + trigger.getDependentReactions().add(result); + trigger.getParent().reactions.add(result); + return result; + } + + protected ReactorInstance newReactor(String name, ReactorInstance container) { + Reactor r = factory.createReactor(); + r.setName(name); + ReactorInstance ri = new ReactorInstance(r, container, reporter); + container.children.add(ri); + return ri; + } } diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java index 5cd489d0f7..2c17145aaa 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java @@ -2,14 +2,13 @@ import java.util.List; import java.util.Set; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.lflang.DefaultErrorReporter; import org.lflang.ErrorReporter; import org.lflang.generator.PortInstance; -import org.lflang.generator.RuntimeRange; import org.lflang.generator.ReactorInstance; +import org.lflang.generator.RuntimeRange; import org.lflang.generator.SendRange; import org.lflang.lf.LfFactory; import org.lflang.lf.Port; @@ -17,87 +16,87 @@ public class RangeTests { - private ErrorReporter reporter = new DefaultErrorReporter(); - private static LfFactory factory = LfFactory.eINSTANCE; - - @Test - public void createRange() throws Exception { - Reactor main = factory.createReactor(); - ReactorInstance maini = new ReactorInstance(main, reporter); - - Reactor a = factory.createReactor(); - a.setName("A"); - ReactorInstance ai = new ReactorInstance(a, maini, reporter); - ai.setWidth(2); - - Reactor b = factory.createReactor(); - b.setName("B"); - ReactorInstance bi = new ReactorInstance(b, ai, reporter); - bi.setWidth(2); - - Port p = factory.createPort(); - p.setName("P"); - PortInstance pi = new PortInstance(p, bi, reporter); - pi.setWidth(2); - - Assertions.assertEquals(".A.B.P", pi.getFullName()); - - RuntimeRange range = new RuntimeRange.Port(pi, 3, 4, null); - - Assertions.assertEquals(8, range.maxWidth); - - Assertions.assertEquals(".A.B.P(3,4)", range.toString()); - - // The results expected below are derived from the class comment for RuntimeRange, - // which includes this example. - List instances = range.instances(); - Assertions.assertEquals(List.of(3, 4, 5, 6), instances); - Set parents = range.parentInstances(1); - Assertions.assertEquals(Set.of(1, 2, 3), parents); - - parents = range.parentInstances(2); - Assertions.assertEquals(Set.of(0, 1), parents); - - // Test startMR().getDigits. - Assertions.assertEquals(List.of(1, 1, 0), range.startMR().getDigits()); - - // Create a SendRange sending from and to this range. - SendRange sendRange = new SendRange(pi, 3, 4, null, null); - sendRange.destinations.add(range); - - // Test getNumberOfDestinationReactors. - Assertions.assertEquals(3, sendRange.getNumberOfDestinationReactors()); - - // Make first interleaved version. - range = range.toggleInterleaved(bi); - instances = range.instances(); - Assertions.assertEquals(List.of(3, 4, 6, 5), instances); - - // Test startMR().getDigits. - Assertions.assertEquals(List.of(1, 1, 0), range.startMR().getDigits()); - - // Make second interleaved version. - range = range.toggleInterleaved(ai); - instances = range.instances(); - Assertions.assertEquals(List.of(6, 1, 5, 3), instances); - - // Test startMR().getDigits. - Assertions.assertEquals(List.of(0, 1, 1), range.startMR().getDigits()); - - // Test instances of the parent. - Assertions.assertEquals(Set.of(3, 0, 2, 1), range.parentInstances(1)); - - // Add this range to the sendRange destinations and verify - // that the number of destination reactors becomes 4. - sendRange.addDestination(range); - Assertions.assertEquals(4, sendRange.getNumberOfDestinationReactors()); - - // Make third interleaved version. - range = range.toggleInterleaved(bi); - instances = range.instances(); - Assertions.assertEquals(List.of(5, 2, 6, 3), instances); - - // Test startMR().getDigits. - Assertions.assertEquals(List.of(1, 0, 1), range.startMR().getDigits()); - } + private ErrorReporter reporter = new DefaultErrorReporter(); + private static LfFactory factory = LfFactory.eINSTANCE; + + @Test + public void createRange() throws Exception { + Reactor main = factory.createReactor(); + ReactorInstance maini = new ReactorInstance(main, reporter); + + Reactor a = factory.createReactor(); + a.setName("A"); + ReactorInstance ai = new ReactorInstance(a, maini, reporter); + ai.setWidth(2); + + Reactor b = factory.createReactor(); + b.setName("B"); + ReactorInstance bi = new ReactorInstance(b, ai, reporter); + bi.setWidth(2); + + Port p = factory.createPort(); + p.setName("P"); + PortInstance pi = new PortInstance(p, bi, reporter); + pi.setWidth(2); + + Assertions.assertEquals(".A.B.P", pi.getFullName()); + + RuntimeRange range = new RuntimeRange.Port(pi, 3, 4, null); + + Assertions.assertEquals(8, range.maxWidth); + + Assertions.assertEquals(".A.B.P(3,4)", range.toString()); + + // The results expected below are derived from the class comment for RuntimeRange, + // which includes this example. + List instances = range.instances(); + Assertions.assertEquals(List.of(3, 4, 5, 6), instances); + Set parents = range.parentInstances(1); + Assertions.assertEquals(Set.of(1, 2, 3), parents); + + parents = range.parentInstances(2); + Assertions.assertEquals(Set.of(0, 1), parents); + + // Test startMR().getDigits. + Assertions.assertEquals(List.of(1, 1, 0), range.startMR().getDigits()); + + // Create a SendRange sending from and to this range. + SendRange sendRange = new SendRange(pi, 3, 4, null, null); + sendRange.destinations.add(range); + + // Test getNumberOfDestinationReactors. + Assertions.assertEquals(3, sendRange.getNumberOfDestinationReactors()); + + // Make first interleaved version. + range = range.toggleInterleaved(bi); + instances = range.instances(); + Assertions.assertEquals(List.of(3, 4, 6, 5), instances); + + // Test startMR().getDigits. + Assertions.assertEquals(List.of(1, 1, 0), range.startMR().getDigits()); + + // Make second interleaved version. + range = range.toggleInterleaved(ai); + instances = range.instances(); + Assertions.assertEquals(List.of(6, 1, 5, 3), instances); + + // Test startMR().getDigits. + Assertions.assertEquals(List.of(0, 1, 1), range.startMR().getDigits()); + + // Test instances of the parent. + Assertions.assertEquals(Set.of(3, 0, 2, 1), range.parentInstances(1)); + + // Add this range to the sendRange destinations and verify + // that the number of destination reactors becomes 4. + sendRange.addDestination(range); + Assertions.assertEquals(4, sendRange.getNumberOfDestinationReactors()); + + // Make third interleaved version. + range = range.toggleInterleaved(bi); + instances = range.instances(); + Assertions.assertEquals(List.of(5, 2, 6, 3), instances); + + // Test startMR().getDigits. + Assertions.assertEquals(List.of(1, 0, 1), range.startMR().getDigits()); + } } 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 801211be23..8087d552a8 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/RoundTripTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/RoundTripTests.java @@ -7,13 +7,11 @@ import java.nio.file.Files; import java.nio.file.Path; - import org.eclipse.xtext.testing.InjectWith; import org.eclipse.xtext.testing.extensions.InjectionExtension; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; - import org.lflang.Target; import org.lflang.ast.FormattingUtils; import org.lflang.ast.IsEqual; @@ -28,46 +26,42 @@ @InjectWith(LFInjectorProvider.class) public class RoundTripTests { - @Test - public void roundTripTest() { - for (Target target : Target.values()) { - for (TestCategory category : TestCategory.values()) { - for (LFTest test : TestRegistry.getRegisteredTests(target, category, false)) { - try { - run(test.getSrcPath()); - } catch (Throwable thrown) { - fail("Test case " + test.getSrcPath() + " failed", thrown); - } - } - } + @Test + public void roundTripTest() { + for (Target target : Target.values()) { + for (TestCategory category : TestCategory.values()) { + for (LFTest test : TestRegistry.getRegisteredTests(target, category, false)) { + try { + run(test.getSrcPath()); + } catch (Throwable thrown) { + fail("Test case " + test.getSrcPath() + " failed", thrown); + } } + } } + } - 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; - final String squishedTestCase = FormattingUtils.render(originalModel, smallLineLength); - final Model resultingModel = LfParsingUtil.parseSourceAsIfInDirectory(file.getParent(), squishedTestCase); - - assertThat(resultingModel.eResource().getErrors(), equalTo(emptyList())); - Assertions.assertTrue( - new IsEqual(originalModel).doSwitch(resultingModel), - String.format( - "The reformatted version of %s was not equivalent to the original file.%n" - + "Formatted file:%n%s%n%n", - file, - squishedTestCase - ) - ); - final String normalTestCase = FormattingUtils.render(originalModel); - Assertions.assertEquals( - Files.readString(file).replaceAll("\\r\\n?", "\n"), - normalTestCase, - "File is not formatted properly, or formatter is bugged. Check " + file - ); - } + 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; + final String squishedTestCase = FormattingUtils.render(originalModel, smallLineLength); + final Model resultingModel = + LfParsingUtil.parseSourceAsIfInDirectory(file.getParent(), squishedTestCase); + assertThat(resultingModel.eResource().getErrors(), equalTo(emptyList())); + Assertions.assertTrue( + new IsEqual(originalModel).doSwitch(resultingModel), + String.format( + "The reformatted version of %s was not equivalent to the original file.%n" + + "Formatted file:%n%s%n%n", + file, squishedTestCase)); + final String normalTestCase = FormattingUtils.render(originalModel); + Assertions.assertEquals( + Files.readString(file).replaceAll("\\r\\n?", "\n"), + normalTestCase, + "File is not formatted properly, or formatter is bugged. Check " + file); + } } diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/TargetConfigTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/TargetConfigTests.java index eede6a6831..e949ab6f9f 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/TargetConfigTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/TargetConfigTests.java @@ -12,7 +12,6 @@ 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; @@ -24,79 +23,81 @@ @ExtendWith(InjectionExtension.class) @InjectWith(LFInjectorProvider.class) -/** - * Tests for checking that target properties adequately translate into the target configuration. - */ +/** 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(""" + @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(""" + """), + "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("federate__a.lf") - ).stream().reduce("\n", String::concat); - Model federate = parser.parse(lfSrc); - assertHasTargetProperty(federate, "tracing"); - } + """, + 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("federate__a.lf")) + .stream() + .reduce("\n", String::concat); + Model federate = parser.parse(lfSrc); + assertHasTargetProperty(federate, "tracing"); + } } diff --git a/org.lflang.tests/src/org/lflang/tests/lsp/ErrorInserter.java b/org.lflang.tests/src/org/lflang/tests/lsp/ErrorInserter.java index a7b1c9d8ec..a1948821eb 100644 --- a/org.lflang.tests/src/org/lflang/tests/lsp/ErrorInserter.java +++ b/org.lflang.tests/src/org/lflang/tests/lsp/ErrorInserter.java @@ -1,5 +1,6 @@ package org.lflang.tests.lsp; +import com.google.common.collect.ImmutableList; import java.io.Closeable; import java.io.IOException; import java.io.PrintWriter; @@ -17,339 +18,364 @@ import java.util.function.Predicate; import java.util.stream.Stream; -import com.google.common.collect.ImmutableList; - /** * Insert problems into integration tests. + * * @author Peter Donovan */ @SuppressWarnings("ClassCanBeRecord") class ErrorInserter { - /** A basic error inserter builder on which more specific error inserters can be built. */ - private static final Builder BASE_ERROR_INSERTER = new Builder() - .insertCondition((s0, s1) -> Stream.of(s0, s1).allMatch(it -> Stream.of(";", "}", "{").anyMatch(it::endsWith))) - .insertCondition((s0, s1) -> !s1.trim().startsWith("else")) - .insertable(" 0 = 1;").insertable("some_undeclared_var1524263 = 9;").insertable(" ++;"); - public static final Builder C = BASE_ERROR_INSERTER - .replacer("lf_set(", "UNDEFINED_NAME2828376(") - .replacer("lf_schedule(", "undefined_name15291838("); - public static final Builder CPP = BASE_ERROR_INSERTER - .replacer(".get", ".undefined_name15291838") - .replacer("std::", "undefined_name3286634::"); - public static final Builder PYTHON_SYNTAX_ONLY = new Builder() - .insertable(" +++++;").insertable(" .."); - public static final Builder PYTHON = PYTHON_SYNTAX_ONLY - .replacer("print(", "undefined_name15291838("); - public static final Builder RUST = BASE_ERROR_INSERTER - .replacer("println!", "undefined_name15291838!") - .replacer("ctx.", "undefined_name3286634."); - public static final Builder TYPESCRIPT = BASE_ERROR_INSERTER - .replacer("requestErrorStop(", "not_an_attribute_of_util9764(") - .replacer("const ", "var "); - - /** An {@code AlteredTest} represents an altered version of what was a valid LF file. */ - static class AlteredTest implements Closeable { - - /** A {@code OnceTrue} is randomly true once, and then never again. */ - private static class OnceTrue { - boolean beenTrue; - Random random; - private OnceTrue(Random random) { - this.beenTrue = false; - this.random = random; - } - private boolean get() { - if (beenTrue) return false; - return beenTrue = random.nextBoolean() && random.nextBoolean(); - } - } - - /** The zero-based indices of the touched lines. */ - private final List badLines; - /** The original test on which this is based. */ - private final Path srcFile; - /** The content of this test. */ - private final LinkedList lines; - /** Whether the error inserter is permitted to insert a line before the current line. */ - private final Predicate> insertCondition; - - /** - * Initialize a possibly altered copy of {@code originalTest}. - * @param originalTest A path to an LF file that serves as a test. - * @param insertCondition Whether the error inserter is permitted to insert a line between two given lines. - * @throws IOException if the content of {@code originalTest} cannot be read. - */ - private AlteredTest(Path originalTest, BiPredicate insertCondition) throws IOException { - this.badLines = new ArrayList<>(); - this.srcFile = originalTest; - this.lines = new LinkedList<>(); // Constant-time insertion during iteration is desired. - this.lines.addAll(Files.readAllLines(srcFile)); - this.insertCondition = it -> { - it.previous(); - String s0 = it.previous(); - it.next(); - String s1 = it.next(); - return insertCondition.test(s0, s1); - }; - } + /** A basic error inserter builder on which more specific error inserters can be built. */ + private static final Builder BASE_ERROR_INSERTER = + new Builder() + .insertCondition( + (s0, s1) -> + Stream.of(s0, s1).allMatch(it -> Stream.of(";", "}", "{").anyMatch(it::endsWith))) + .insertCondition((s0, s1) -> !s1.trim().startsWith("else")) + .insertable(" 0 = 1;") + .insertable("some_undeclared_var1524263 = 9;") + .insertable(" ++;"); + + public static final Builder C = + BASE_ERROR_INSERTER + .replacer("lf_set(", "UNDEFINED_NAME2828376(") + .replacer("lf_schedule(", "undefined_name15291838("); + public static final Builder CPP = + BASE_ERROR_INSERTER + .replacer(".get", ".undefined_name15291838") + .replacer("std::", "undefined_name3286634::"); + public static final Builder PYTHON_SYNTAX_ONLY = + new Builder().insertable(" +++++;").insertable(" .."); + public static final Builder PYTHON = + PYTHON_SYNTAX_ONLY.replacer("print(", "undefined_name15291838("); + public static final Builder RUST = + BASE_ERROR_INSERTER + .replacer("println!", "undefined_name15291838!") + .replacer("ctx.", "undefined_name3286634."); + public static final Builder TYPESCRIPT = + BASE_ERROR_INSERTER + .replacer("requestErrorStop(", "not_an_attribute_of_util9764(") + .replacer("const ", "var "); + + /** An {@code AlteredTest} represents an altered version of what was a valid LF file. */ + static class AlteredTest implements Closeable { + + /** A {@code OnceTrue} is randomly true once, and then never again. */ + private static class OnceTrue { + boolean beenTrue; + Random random; + + private OnceTrue(Random random) { + this.beenTrue = false; + this.random = random; + } - /** Return the location where the content of {@code this} lives. */ - public Path getSrcFile() { - return srcFile; - } + private boolean get() { + if (beenTrue) return false; + return beenTrue = random.nextBoolean() && random.nextBoolean(); + } + } - /** - * Write the altered version of the test to the file system. - * @throws IOException If an I/O error occurred. - */ - public void write() throws IOException { - Path src = srcFile; - if (!src.toFile().renameTo(swapFile(src).toFile())) { - throw new IOException("Failed to create a swap file."); - } - try (PrintWriter writer = new PrintWriter(src.toFile())) { - lines.forEach(writer::println); - } - } + /** The zero-based indices of the touched lines. */ + private final List badLines; + /** The original test on which this is based. */ + private final Path srcFile; + /** The content of this test. */ + private final LinkedList lines; + /** Whether the error inserter is permitted to insert a line before the current line. */ + private final Predicate> insertCondition; - /** - * Restore the file associated with this test to its original state. - */ - @Override - public void close() throws IOException { - Path src = srcFile; - if (!swapFile(src).toFile().exists()) throw new IllegalStateException("Swap file does not exist."); - if (!src.toFile().delete()) { - throw new IOException("Failed to delete the file associated with the original test."); - } - if (!swapFile(src).toFile().renameTo(src.toFile())) { - throw new IOException("Failed to restore the altered LF file to its original state."); - } - } + /** + * Initialize a possibly altered copy of {@code originalTest}. + * + * @param originalTest A path to an LF file that serves as a test. + * @param insertCondition Whether the error inserter is permitted to insert a line between two + * given lines. + * @throws IOException if the content of {@code originalTest} cannot be read. + */ + private AlteredTest(Path originalTest, BiPredicate insertCondition) + throws IOException { + this.badLines = new ArrayList<>(); + this.srcFile = originalTest; + this.lines = new LinkedList<>(); // Constant-time insertion during iteration is desired. + this.lines.addAll(Files.readAllLines(srcFile)); + this.insertCondition = + it -> { + it.previous(); + String s0 = it.previous(); + it.next(); + String s1 = it.next(); + return insertCondition.test(s0, s1); + }; + } - @Override - public String toString() { - int lengthOfPrefix = 6; - StringBuilder ret = new StringBuilder( - lines.stream().mapToInt(String::length).reduce(0, Integer::sum) - + lines.size() * lengthOfPrefix - ); - for (int i = 0; i < lines.size(); i++) { - ret.append(badLines.contains(i) ? "-> " : " ") - .append(String.format("%1$2s ", i)) - .append(lines.get(i)).append("\n"); - } - return ret.toString(); - } + /** Return the location where the content of {@code this} lives. */ + public Path getSrcFile() { + return srcFile; + } - /** Return the lines where this differs from the test from which it was derived. */ - public ImmutableList getBadLines() { - return ImmutableList.copyOf(badLines); - } + /** + * Write the altered version of the test to the file system. + * + * @throws IOException If an I/O error occurred. + */ + public void write() throws IOException { + Path src = srcFile; + if (!src.toFile().renameTo(swapFile(src).toFile())) { + throw new IOException("Failed to create a swap file."); + } + try (PrintWriter writer = new PrintWriter(src.toFile())) { + lines.forEach(writer::println); + } + } - /** - * Attempt to replace a line of this test with a different line of target language code. - * @param replacer A function that replaces lines of code with possibly different lines. - */ - public void replace(Function replacer, Random random) { - OnceTrue onceTrue = new OnceTrue(random); - alter((it, current) -> { - if (!onceTrue.get()) return false; - String newLine = replacer.apply(current); - it.remove(); - it.add(newLine); - return !newLine.equals(current); - }); - } + /** Restore the file associated with this test to its original state. */ + @Override + public void close() throws IOException { + Path src = srcFile; + if (!swapFile(src).toFile().exists()) + throw new IllegalStateException("Swap file does not exist."); + if (!src.toFile().delete()) { + throw new IOException("Failed to delete the file associated with the original test."); + } + if (!swapFile(src).toFile().renameTo(src.toFile())) { + throw new IOException("Failed to restore the altered LF file to its original state."); + } + } - /** - * Attempt to insert a new line of target language code into this test. - * @param line The line to be inserted. - */ - public void insert(String line, Random random) { - OnceTrue onceTrue = new OnceTrue(random); - alter((it, current) -> { - if (insertCondition.test(it) && onceTrue.get()) { - it.previous(); - it.add(line); - it.next(); - return true; - } - return false; - }); - } + @Override + public String toString() { + int lengthOfPrefix = 6; + StringBuilder ret = + new StringBuilder( + lines.stream().mapToInt(String::length).reduce(0, Integer::sum) + + lines.size() * lengthOfPrefix); + for (int i = 0; i < lines.size(); i++) { + ret.append(badLines.contains(i) ? "-> " : " ") + .append(String.format("%1$2s ", i)) + .append(lines.get(i)) + .append("\n"); + } + return ret.toString(); + } - /** - * Alter the content of this test. - * @param alterer A function whose first argument is an iterator over the lines of {@code this}, whose second - * argument is the line most recently returned by that iterator, and whose return value is - * whether an alteration was successfully performed. This function is only applied within - * multiline code blocks. - */ - private void alter(BiFunction, String, Boolean> alterer) { - ListIterator it = lines.listIterator(); - boolean inCodeBlock = false; - int lineNumber = 0; - while (it.hasNext()) { - String current = it.next(); - String uncommented = current.contains("//") ? - current.substring(0, current.indexOf("//")) : current; - uncommented = uncommented.contains("#") ? - uncommented.substring(0, uncommented.indexOf("#")) : current; - if (uncommented.contains("=}")) inCodeBlock = false; - if (inCodeBlock && alterer.apply(it, current)) badLines.add(lineNumber); - if (uncommented.contains("{=")) inCodeBlock = true; - if (uncommented.contains("{=") && uncommented.contains("=}")) { - inCodeBlock = uncommented.lastIndexOf("{=") > uncommented.lastIndexOf("=}"); - } - lineNumber++; - } - } + /** Return the lines where this differs from the test from which it was derived. */ + public ImmutableList getBadLines() { + return ImmutableList.copyOf(badLines); + } - /** Return the swap file associated with {@code f}. */ - private static Path swapFile(Path p) { - return p.getParent().resolve("." + p.getFileName() + ".swp"); - } + /** + * Attempt to replace a line of this test with a different line of target language code. + * + * @param replacer A function that replaces lines of code with possibly different lines. + */ + public void replace(Function replacer, Random random) { + OnceTrue onceTrue = new OnceTrue(random); + alter( + (it, current) -> { + if (!onceTrue.get()) return false; + String newLine = replacer.apply(current); + it.remove(); + it.add(newLine); + return !newLine.equals(current); + }); } - /** A builder for an error inserter. */ - public static class Builder { - private static class Node implements Iterable { - private final Node previous; - private final T item; - private Node(Node previous, T item) { - this.previous = previous; - this.item = item; + /** + * Attempt to insert a new line of target language code into this test. + * + * @param line The line to be inserted. + */ + public void insert(String line, Random random) { + OnceTrue onceTrue = new OnceTrue(random); + alter( + (it, current) -> { + if (insertCondition.test(it) && onceTrue.get()) { + it.previous(); + it.add(line); + it.next(); + return true; } + return false; + }); + } - @Override - public Iterator iterator() { - NodeIterator ret = new NodeIterator<>(); - ret.current = this; - return ret; - } + /** + * Alter the content of this test. + * + * @param alterer A function whose first argument is an iterator over the lines of {@code this}, + * whose second argument is the line most recently returned by that iterator, and whose + * return value is whether an alteration was successfully performed. This function is only + * applied within multiline code blocks. + */ + private void alter(BiFunction, String, Boolean> alterer) { + ListIterator it = lines.listIterator(); + boolean inCodeBlock = false; + int lineNumber = 0; + while (it.hasNext()) { + String current = it.next(); + String uncommented = + current.contains("//") ? current.substring(0, current.indexOf("//")) : current; + uncommented = + uncommented.contains("#") + ? uncommented.substring(0, uncommented.indexOf("#")) + : current; + if (uncommented.contains("=}")) inCodeBlock = false; + if (inCodeBlock && alterer.apply(it, current)) badLines.add(lineNumber); + if (uncommented.contains("{=")) inCodeBlock = true; + if (uncommented.contains("{=") && uncommented.contains("=}")) { + inCodeBlock = uncommented.lastIndexOf("{=") > uncommented.lastIndexOf("=}"); + } + lineNumber++; + } + } - private static class NodeIterator implements Iterator { - private Node current; + /** Return the swap file associated with {@code f}. */ + private static Path swapFile(Path p) { + return p.getParent().resolve("." + p.getFileName() + ".swp"); + } + } - @Override - public boolean hasNext() { - return current != null; - } + /** A builder for an error inserter. */ + public static class Builder { + private static class Node implements Iterable { + private final Node previous; + private final T item; - @Override - public T next() { - T ret = current.item; - current = current.previous; - return ret; - } - } - } - private final Node> replacers; - private final Node insertables; - private final BiPredicate insertCondition; + private Node(Node previous, T item) { + this.previous = previous; + this.item = item; + } - /** Initializes a builder for error inserters. */ - public Builder() { - this(null, null, (s0, s1) -> true); - } + @Override + public Iterator iterator() { + NodeIterator ret = new NodeIterator<>(); + ret.current = this; + return ret; + } - /** Construct a builder with the given replacers and insertables. */ - private Builder( - Node> replacers, - Node insertables, - BiPredicate insertCondition - ) { - this.replacers = replacers; - this.insertables = insertables; - this.insertCondition = insertCondition; - } + private static class NodeIterator implements Iterator { + private Node current; - /** - * Record that the resulting {@code ErrorInserter} may replace {@code phrase} with {@code alternativePhrase}. - * @param phrase A phrase in target language code. - * @param alternativePhrase A phrase that {@code phrase} may be replaced with in order to introduce an error. - * @return A {@code Builder} that knows about all the edits that {@code this} knows about, plus the edit that - * replaces {@code phrase} with {@code alternativePhrase}. - */ - public Builder replacer(String phrase, String alternativePhrase) { - return new Builder( - new Node<>( - replacers, - line -> { - int changeableEnd = line.length(); - for (String bad : new String[]{"#", "//", "\""}) { - if (line.contains(bad)) changeableEnd = Math.min(changeableEnd, line.indexOf(bad)); - } - return line.substring(0, changeableEnd).replace(phrase, alternativePhrase) - + line.substring(changeableEnd); - } - ), - insertables, - insertCondition - ); + @Override + public boolean hasNext() { + return current != null; } - /** Record that {@code} line may be inserted in order to introduce an error. */ - public Builder insertable(String line) { - return new Builder(replacers, new Node<>(insertables, line), insertCondition); + @Override + public T next() { + T ret = current.item; + current = current.previous; + return ret; } + } + } - /** - * Record that for any lines X, Y, insertCondition(X, Y) is a necessary condition that a line may be inserted - * between X and Y. - */ - public Builder insertCondition(BiPredicate insertCondition) { - return new Builder(replacers, insertables, this.insertCondition.and(insertCondition)); - } + private final Node> replacers; + private final Node insertables; + private final BiPredicate insertCondition; - /** Get the error inserter generated by {@code this}. */ - public ErrorInserter get(Random random) { - return new ErrorInserter( - random, - replacers == null ? ImmutableList.of() : ImmutableList.copyOf(replacers), - insertables == null ? ImmutableList.of() : ImmutableList.copyOf(insertables), - insertCondition - ); - } + /** Initializes a builder for error inserters. */ + public Builder() { + this(null, null, (s0, s1) -> true); } - private static final int MAX_ALTERATION_ATTEMPTS = 100; + /** Construct a builder with the given replacers and insertables. */ + private Builder( + Node> replacers, + Node insertables, + BiPredicate insertCondition) { + this.replacers = replacers; + this.insertables = insertables; + this.insertCondition = insertCondition; + } - private final Random random; - private final ImmutableList> replacers; - private final ImmutableList insertables; - private final BiPredicate insertCondition; + /** + * Record that the resulting {@code ErrorInserter} may replace {@code phrase} with {@code + * alternativePhrase}. + * + * @param phrase A phrase in target language code. + * @param alternativePhrase A phrase that {@code phrase} may be replaced with in order to + * introduce an error. + * @return A {@code Builder} that knows about all the edits that {@code this} knows about, plus + * the edit that replaces {@code phrase} with {@code alternativePhrase}. + */ + public Builder replacer(String phrase, String alternativePhrase) { + return new Builder( + new Node<>( + replacers, + line -> { + int changeableEnd = line.length(); + for (String bad : new String[] {"#", "//", "\""}) { + if (line.contains(bad)) + changeableEnd = Math.min(changeableEnd, line.indexOf(bad)); + } + return line.substring(0, changeableEnd).replace(phrase, alternativePhrase) + + line.substring(changeableEnd); + }), + insertables, + insertCondition); + } - private ErrorInserter( - Random random, - ImmutableList> replacers, - ImmutableList insertables, - BiPredicate insertCondition - ) { - this.random = random; - this.replacers = replacers; - this.insertables = insertables; - this.insertCondition = insertCondition; + /** Record that {@code} line may be inserted in order to introduce an error. */ + public Builder insertable(String line) { + return new Builder(replacers, new Node<>(insertables, line), insertCondition); } /** - * Alter the given test and return the altered version. - * @param test The path to the test. - * @return An {@code AlteredTest} that is based on {@code test}. + * Record that for any lines X, Y, insertCondition(X, Y) is a necessary condition that a line + * may be inserted between X and Y. */ - public AlteredTest alterTest(Path test) throws IOException { - AlteredTest alterable = new AlteredTest(test, insertCondition); - int remainingAlterationAttempts = MAX_ALTERATION_ATTEMPTS; - while (alterable.getBadLines().isEmpty() && remainingAlterationAttempts-- > 0) { - if (random.nextBoolean() && !replacers.isEmpty()) { - alterable.replace(replacers.get(random.nextInt(replacers.size())), random); - } else if (!insertables.isEmpty()) { - alterable.insert(insertables.get(random.nextInt(insertables.size())), random); - } - } - alterable.write(); - return alterable; + public Builder insertCondition(BiPredicate insertCondition) { + return new Builder(replacers, insertables, this.insertCondition.and(insertCondition)); + } + + /** Get the error inserter generated by {@code this}. */ + public ErrorInserter get(Random random) { + return new ErrorInserter( + random, + replacers == null ? ImmutableList.of() : ImmutableList.copyOf(replacers), + insertables == null ? ImmutableList.of() : ImmutableList.copyOf(insertables), + insertCondition); + } + } + + private static final int MAX_ALTERATION_ATTEMPTS = 100; + + private final Random random; + private final ImmutableList> replacers; + private final ImmutableList insertables; + private final BiPredicate insertCondition; + + private ErrorInserter( + Random random, + ImmutableList> replacers, + ImmutableList insertables, + BiPredicate insertCondition) { + this.random = random; + this.replacers = replacers; + this.insertables = insertables; + this.insertCondition = insertCondition; + } + + /** + * Alter the given test and return the altered version. + * + * @param test The path to the test. + * @return An {@code AlteredTest} that is based on {@code test}. + */ + public AlteredTest alterTest(Path test) throws IOException { + AlteredTest alterable = new AlteredTest(test, insertCondition); + int remainingAlterationAttempts = MAX_ALTERATION_ATTEMPTS; + while (alterable.getBadLines().isEmpty() && remainingAlterationAttempts-- > 0) { + if (random.nextBoolean() && !replacers.isEmpty()) { + alterable.replace(replacers.get(random.nextInt(replacers.size())), random); + } else if (!insertables.isEmpty()) { + alterable.insert(insertables.get(random.nextInt(insertables.size())), random); + } } + alterable.write(); + return alterable; + } } diff --git a/org.lflang.tests/src/org/lflang/tests/lsp/LspTests.java b/org.lflang.tests/src/org/lflang/tests/lsp/LspTests.java index 99c1260d44..fe52d6016c 100644 --- a/org.lflang.tests/src/org/lflang/tests/lsp/LspTests.java +++ b/org.lflang.tests/src/org/lflang/tests/lsp/LspTests.java @@ -9,12 +9,10 @@ import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; - -import org.eclipse.lsp4j.Diagnostic; import org.eclipse.emf.common.util.URI; +import org.eclipse.lsp4j.Diagnostic; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - import org.lflang.LFRuntimeModule; import org.lflang.LFStandaloneSetup; import org.lflang.Target; @@ -33,188 +31,211 @@ */ class LspTests { - /** The test categories that should be excluded from LSP tests. */ - private static final TestCategory[] EXCLUDED_CATEGORIES = { - TestCategory.SERIALIZATION, TestCategory.DOCKER, TestCategory.DOCKER_FEDERATED - }; - private static final Predicate> NOT_SUPPORTED = diagnosticsHaveKeyword("supported"); - private static final Predicate> MISSING_DEPENDENCY = diagnosticsHaveKeyword("libprotoc") - .or(diagnosticsHaveKeyword("protoc-c")).or(diagnosticsIncludeText("could not be found")); - /** The number of samples to take from each test category (with replacement) when doing validation tests. */ - private static final int SAMPLES_PER_CATEGORY_VALIDATION_TESTS = 3; - - /** The {@code IntegratedBuilder} instance whose behavior is to be tested. */ - private static final IntegratedBuilder builder = new LFStandaloneSetup(new LFRuntimeModule()) - .createInjectorAndDoEMFRegistration().getInstance(IntegratedBuilder.class); - - /** Test for false negatives in Python syntax-only validation. */ - @Test - void pythonValidationTestSyntaxOnly() throws IOException { - targetLanguageValidationTest(Target.Python, ErrorInserter.PYTHON_SYNTAX_ONLY); - } - - /** Test for false negatives in C++ validation. */ - @Test - void cppValidationTest() throws IOException { - targetLanguageValidationTest(Target.CPP, ErrorInserter.CPP); - } - - /** Test for false negatives in Python validation. */ - @Test - void pythonValidationTest() throws IOException { - targetLanguageValidationTest(Target.Python, ErrorInserter.PYTHON); - } - - /** Test for false negatives in Rust validation. */ - @Test - void rustValidationTest() throws IOException { - targetLanguageValidationTest(Target.Rust, ErrorInserter.RUST); - } - - /** Test for false negatives in TypeScript validation. */ - @Test - void typescriptValidationTest() throws IOException { - targetLanguageValidationTest(Target.TS, ErrorInserter.TYPESCRIPT); - } - - /** - * Test for false negatives in the validation of LF files. - * @param target The target language of the LF files to be validated. - * @param builder A builder for the error inserter that will be used. - */ - private void targetLanguageValidationTest(Target target, ErrorInserter.Builder builder) throws IOException { - long seed = new Random().nextLong(); - System.out.printf("Running validation tests for %s with random seed %d.%n", target.getDisplayName(), seed); - Random random = new Random(seed); - int i = SAMPLES_PER_CATEGORY_VALIDATION_TESTS; - while (i-- > 0) checkDiagnostics( - target, - alteredTest -> MISSING_DEPENDENCY.or(diagnostics -> alteredTest.getBadLines().stream().allMatch( - badLine -> { - System.out.print("Expecting an error to be reported at line " + badLine + "..."); - boolean result = NOT_SUPPORTED.test(diagnostics) || diagnostics.stream().anyMatch( - diagnostic -> diagnostic.getRange().getStart().getLine() == badLine - ); - if (result) { - System.out.println(" Success."); - } else { - System.out.println(" but the expected error could not be found."); - System.out.printf( - "%s failed. Content of altered version of %s:%n%s%n", - alteredTest.getSrcFile(), - alteredTest.getSrcFile(), - TestBase.THIN_LINE - ); - System.out.println(alteredTest + "\n" + TestBase.THIN_LINE); - } - return result; - } - )), - builder.get(random), - random - ); - } - - /** - * Verify that the diagnostics that result from fully validating tests associated with {@code target} satisfy - * {@code requirementGetter}. - * @param target Any target language. - * @param requirementGetter A map from altered tests to the requirements that diagnostics regarding those tests - * must meet. - * @param alterer The means of inserting problems into the tests, or {@code null} if problems are not to be - * inserted. - * @param random The {@code Random} instance that determines which tests are selected. - * @throws IOException upon failure to write an altered copy of some test to storage. - */ - private void checkDiagnostics( - Target target, - Function>> requirementGetter, - ErrorInserter alterer, - Random random - ) throws IOException { - MockLanguageClient client = new MockLanguageClient(); - LanguageServerErrorReporter.setClient(client); - for (LFTest test : selectTests(target, random)) { - client.clearDiagnostics(); - if (alterer != null) { - try (AlteredTest altered = alterer.alterTest(test.getSrcPath())) { - runTest(altered.getSrcFile()); - Assertions.assertTrue(requirementGetter.apply(altered).test(client.getReceivedDiagnostics())); - } - } else { - runTest(test.getSrcPath()); - Assertions.assertTrue(requirementGetter.apply(null).test(client.getReceivedDiagnostics())); - } + /** The test categories that should be excluded from LSP tests. */ + private static final TestCategory[] EXCLUDED_CATEGORIES = { + TestCategory.SERIALIZATION, TestCategory.DOCKER, TestCategory.DOCKER_FEDERATED + }; + + private static final Predicate> NOT_SUPPORTED = + diagnosticsHaveKeyword("supported"); + private static final Predicate> MISSING_DEPENDENCY = + diagnosticsHaveKeyword("libprotoc") + .or(diagnosticsHaveKeyword("protoc-c")) + .or(diagnosticsIncludeText("could not be found")); + /** + * The number of samples to take from each test category (with replacement) when doing validation + * tests. + */ + private static final int SAMPLES_PER_CATEGORY_VALIDATION_TESTS = 3; + + /** The {@code IntegratedBuilder} instance whose behavior is to be tested. */ + private static final IntegratedBuilder builder = + new LFStandaloneSetup(new LFRuntimeModule()) + .createInjectorAndDoEMFRegistration() + .getInstance(IntegratedBuilder.class); + + /** Test for false negatives in Python syntax-only validation. */ + @Test + void pythonValidationTestSyntaxOnly() throws IOException { + targetLanguageValidationTest(Target.Python, ErrorInserter.PYTHON_SYNTAX_ONLY); + } + + /** Test for false negatives in C++ validation. */ + @Test + void cppValidationTest() throws IOException { + targetLanguageValidationTest(Target.CPP, ErrorInserter.CPP); + } + + /** Test for false negatives in Python validation. */ + @Test + void pythonValidationTest() throws IOException { + targetLanguageValidationTest(Target.Python, ErrorInserter.PYTHON); + } + + /** Test for false negatives in Rust validation. */ + @Test + void rustValidationTest() throws IOException { + targetLanguageValidationTest(Target.Rust, ErrorInserter.RUST); + } + + /** Test for false negatives in TypeScript validation. */ + @Test + void typescriptValidationTest() throws IOException { + targetLanguageValidationTest(Target.TS, ErrorInserter.TYPESCRIPT); + } + + /** + * Test for false negatives in the validation of LF files. + * + * @param target The target language of the LF files to be validated. + * @param builder A builder for the error inserter that will be used. + */ + private void targetLanguageValidationTest(Target target, ErrorInserter.Builder builder) + throws IOException { + long seed = new Random().nextLong(); + System.out.printf( + "Running validation tests for %s with random seed %d.%n", target.getDisplayName(), seed); + Random random = new Random(seed); + int i = SAMPLES_PER_CATEGORY_VALIDATION_TESTS; + while (i-- > 0) + checkDiagnostics( + target, + alteredTest -> + MISSING_DEPENDENCY.or( + diagnostics -> + alteredTest.getBadLines().stream() + .allMatch( + badLine -> { + System.out.print( + "Expecting an error to be reported at line " + badLine + "..."); + boolean result = + NOT_SUPPORTED.test(diagnostics) + || diagnostics.stream() + .anyMatch( + diagnostic -> + diagnostic.getRange().getStart().getLine() + == badLine); + if (result) { + System.out.println(" Success."); + } else { + System.out.println(" but the expected error could not be found."); + System.out.printf( + "%s failed. Content of altered version of %s:%n%s%n", + alteredTest.getSrcFile(), + alteredTest.getSrcFile(), + TestBase.THIN_LINE); + System.out.println(alteredTest + "\n" + TestBase.THIN_LINE); + } + return result; + })), + builder.get(random), + random); + } + + /** + * Verify that the diagnostics that result from fully validating tests associated with {@code + * target} satisfy {@code requirementGetter}. + * + * @param target Any target language. + * @param requirementGetter A map from altered tests to the requirements that diagnostics + * regarding those tests must meet. + * @param alterer The means of inserting problems into the tests, or {@code null} if problems are + * not to be inserted. + * @param random The {@code Random} instance that determines which tests are selected. + * @throws IOException upon failure to write an altered copy of some test to storage. + */ + private void checkDiagnostics( + Target target, + Function>> requirementGetter, + ErrorInserter alterer, + Random random) + throws IOException { + MockLanguageClient client = new MockLanguageClient(); + LanguageServerErrorReporter.setClient(client); + for (LFTest test : selectTests(target, random)) { + client.clearDiagnostics(); + if (alterer != null) { + try (AlteredTest altered = alterer.alterTest(test.getSrcPath())) { + runTest(altered.getSrcFile()); + Assertions.assertTrue( + requirementGetter.apply(altered).test(client.getReceivedDiagnostics())); } + } else { + runTest(test.getSrcPath()); + Assertions.assertTrue(requirementGetter.apply(null).test(client.getReceivedDiagnostics())); + } } - - /** - * Select a test from each test category. - * @param target The target language of the desired tests. - * @param random The {@code Random} instance that determines which tests are selected. - * @return A sample of one integration test per target, per category. - */ - private Set selectTests(Target target, Random random) { - Set ret = new HashSet<>(); - for (TestCategory category : selectedCategories()) { - Set registeredTests = TestRegistry.getRegisteredTests(target, category, false); - if (registeredTests.size() == 0) continue; - int relativeIndex = random.nextInt(registeredTests.size()); - for (LFTest t : registeredTests) { - if (relativeIndex-- == 0) { - ret.add(t); - break; - } - } + } + + /** + * Select a test from each test category. + * + * @param target The target language of the desired tests. + * @param random The {@code Random} instance that determines which tests are selected. + * @return A sample of one integration test per target, per category. + */ + private Set selectTests(Target target, Random random) { + Set ret = new HashSet<>(); + for (TestCategory category : selectedCategories()) { + Set registeredTests = TestRegistry.getRegisteredTests(target, category, false); + if (registeredTests.size() == 0) continue; + int relativeIndex = random.nextInt(registeredTests.size()); + for (LFTest t : registeredTests) { + if (relativeIndex-- == 0) { + ret.add(t); + break; } - return ret; - } - - /** Return the non-excluded categories. */ - private Iterable selectedCategories() { - return () -> Arrays.stream(TestCategory.values()).filter( - category -> Arrays.stream(EXCLUDED_CATEGORIES).noneMatch(category::equals) - ).iterator(); + } } - - /** - * Return the predicate that a list of diagnostics contains the given keyword. - * @param keyword A keyword that a list of diagnostics should be searched for. - * @return The predicate, "X mentions {@code keyword}." - */ - private static Predicate> diagnosticsHaveKeyword(String keyword) { - return diagnostics -> diagnostics.stream().anyMatch( - d -> Arrays.asList(d.getMessage().toLowerCase().split("\\b")).contains(keyword) - ); - } - - /** - * Return the predicate that a list of diagnostics contains the given text. - * @param requiredText A keyword that a list of diagnostics should be searched for. - * @return The predicate, "X includes {@code requiredText}." - */ - private static Predicate> diagnosticsIncludeText(@SuppressWarnings("SameParameterValue") String requiredText) { - return diagnostics -> diagnostics.stream().anyMatch( - d -> d.getMessage().toLowerCase().contains(requiredText) - ); - } - - /** - * Run the given test. - * @param test The test b - */ - private void runTest(Path test) { - MockReportProgress reportProgress = new MockReportProgress(); - try { - builder.run( - URI.createFileURI(test.toString()), - false, reportProgress, - () -> false - ); - } catch (Exception e) { - e.printStackTrace(); - throw e; - } - Assertions.assertFalse(reportProgress.failed()); + return ret; + } + + /** Return the non-excluded categories. */ + private Iterable selectedCategories() { + return () -> + Arrays.stream(TestCategory.values()) + .filter(category -> Arrays.stream(EXCLUDED_CATEGORIES).noneMatch(category::equals)) + .iterator(); + } + + /** + * Return the predicate that a list of diagnostics contains the given keyword. + * + * @param keyword A keyword that a list of diagnostics should be searched for. + * @return The predicate, "X mentions {@code keyword}." + */ + private static Predicate> diagnosticsHaveKeyword(String keyword) { + return diagnostics -> + diagnostics.stream() + .anyMatch( + d -> Arrays.asList(d.getMessage().toLowerCase().split("\\b")).contains(keyword)); + } + + /** + * Return the predicate that a list of diagnostics contains the given text. + * + * @param requiredText A keyword that a list of diagnostics should be searched for. + * @return The predicate, "X includes {@code requiredText}." + */ + private static Predicate> diagnosticsIncludeText( + @SuppressWarnings("SameParameterValue") String requiredText) { + return diagnostics -> + diagnostics.stream().anyMatch(d -> d.getMessage().toLowerCase().contains(requiredText)); + } + + /** + * Run the given test. + * + * @param test The test b + */ + private void runTest(Path test) { + MockReportProgress reportProgress = new MockReportProgress(); + try { + builder.run(URI.createFileURI(test.toString()), false, reportProgress, () -> false); + } catch (Exception e) { + e.printStackTrace(); + throw e; } + Assertions.assertFalse(reportProgress.failed()); + } } diff --git a/org.lflang.tests/src/org/lflang/tests/lsp/MockLanguageClient.java b/org.lflang.tests/src/org/lflang/tests/lsp/MockLanguageClient.java index 8aad48c547..1e3e06ef05 100644 --- a/org.lflang.tests/src/org/lflang/tests/lsp/MockLanguageClient.java +++ b/org.lflang.tests/src/org/lflang/tests/lsp/MockLanguageClient.java @@ -4,7 +4,6 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; - import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.DiagnosticSeverity; import org.eclipse.lsp4j.MessageActionItem; @@ -20,49 +19,53 @@ */ public class MockLanguageClient implements LanguageClient { - private List receivedDiagnostics = new ArrayList<>(); + private List receivedDiagnostics = new ArrayList<>(); - @Override - public void telemetryEvent(Object object) { - // Do nothing. - } + @Override + public void telemetryEvent(Object object) { + // Do nothing. + } - @Override - public void publishDiagnostics(PublishDiagnosticsParams diagnostics) { - receivedDiagnostics.addAll(diagnostics.getDiagnostics()); - for (Diagnostic d : diagnostics.getDiagnostics()) { - ( - (d.getSeverity() == DiagnosticSeverity.Error || d.getSeverity() == DiagnosticSeverity.Warning) ? - System.err : System.out - ).println( - "Test client received diagnostic at line " + d.getRange().getStart().getLine() + ": " + d.getMessage() - ); - } + @Override + public void publishDiagnostics(PublishDiagnosticsParams diagnostics) { + receivedDiagnostics.addAll(diagnostics.getDiagnostics()); + for (Diagnostic d : diagnostics.getDiagnostics()) { + ((d.getSeverity() == DiagnosticSeverity.Error + || d.getSeverity() == DiagnosticSeverity.Warning) + ? System.err + : System.out) + .println( + "Test client received diagnostic at line " + + d.getRange().getStart().getLine() + + ": " + + d.getMessage()); } + } - @Override - public void showMessage(MessageParams messageParams) { - System.out.println("Test client received message: " + messageParams.getMessage()); - } + @Override + public void showMessage(MessageParams messageParams) { + System.out.println("Test client received message: " + messageParams.getMessage()); + } - @Override - public CompletableFuture showMessageRequest(ShowMessageRequestParams requestParams) { - showMessage(requestParams); - return null; - } + @Override + public CompletableFuture showMessageRequest( + ShowMessageRequestParams requestParams) { + showMessage(requestParams); + return null; + } - @Override - public void logMessage(MessageParams message) { - showMessage(message); - } + @Override + public void logMessage(MessageParams message) { + showMessage(message); + } - /** Return the diagnostics that {@code this} has received. */ - public List getReceivedDiagnostics() { - return Collections.unmodifiableList(receivedDiagnostics); - } + /** Return the diagnostics that {@code this} has received. */ + public List getReceivedDiagnostics() { + return Collections.unmodifiableList(receivedDiagnostics); + } - /** Clear the diagnostics recorded by {@code this}. */ - public void clearDiagnostics() { - receivedDiagnostics.clear(); - } + /** Clear the diagnostics recorded by {@code this}. */ + public void clearDiagnostics() { + receivedDiagnostics.clear(); + } } 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 014fcc7276..f0cfb088c1 100644 --- a/org.lflang.tests/src/org/lflang/tests/lsp/MockReportProgress.java +++ b/org.lflang.tests/src/org/lflang/tests/lsp/MockReportProgress.java @@ -8,26 +8,29 @@ * @author Peter Donovan */ public class MockReportProgress implements IntegratedBuilder.ReportProgress { - private int previousPercentProgress; - private boolean failed; - public MockReportProgress() { - previousPercentProgress = 0; - failed = false; - } + private int previousPercentProgress; + private boolean failed; - @Override - public void apply(String message, Integer 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; - } + public MockReportProgress() { + previousPercentProgress = 0; + failed = false; + } - /** - * Returns whether an invalid sequence of progress reports was received. - * @return whether an invalid sequence of progress reports was received - */ - public boolean failed() { - return failed; - } + @Override + public void apply(String message, Integer 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; + } + + /** + * Returns whether an invalid sequence of progress reports was received. + * + * @return whether an invalid sequence of progress reports was received + */ + public boolean failed() { + return failed; + } } diff --git a/org.lflang.tests/src/org/lflang/tests/runtime/CArduinoTest.java b/org.lflang.tests/src/org/lflang/tests/runtime/CArduinoTest.java index e783c335a0..95363fd30b 100644 --- a/org.lflang.tests/src/org/lflang/tests/runtime/CArduinoTest.java +++ b/org.lflang.tests/src/org/lflang/tests/runtime/CArduinoTest.java @@ -2,9 +2,7 @@ import java.util.List; import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; - import org.lflang.Target; import org.lflang.tests.Configurators; import org.lflang.tests.RuntimeTest; @@ -17,17 +15,19 @@ */ public class CArduinoTest extends RuntimeTest { - public CArduinoTest() { - super(Target.C); - } + public CArduinoTest() { + super(Target.C); + } - @Test - public void buildArduinoTests() { - Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); - super.runTestsFor(List.of(Target.C), - Message.DESC_ARDUINO, - TestCategory.ARDUINO::equals, Configurators::noChanges, - TestLevel.BUILD, - false); - } + @Test + public void buildArduinoTests() { + Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); + super.runTestsFor( + List.of(Target.C), + Message.DESC_ARDUINO, + TestCategory.ARDUINO::equals, + Configurators::noChanges, + TestLevel.BUILD, + false); + } } 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 c8c2fc5b93..ba1865e870 100644 --- a/org.lflang.tests/src/org/lflang/tests/runtime/CCppTest.java +++ b/org.lflang.tests/src/org/lflang/tests/runtime/CCppTest.java @@ -2,51 +2,49 @@ import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; -import org.lflang.ast.ASTUtils; import org.lflang.Target; +import org.lflang.ast.ASTUtils; import org.lflang.tests.TestBase; import org.lflang.tests.TestRegistry.TestCategory; /** * Collection of tests for the CCpp target. * - * NOTE: This test does not inherit any tests because it directly extends TestBase. + *

NOTE: This test does not inherit any tests because it directly extends TestBase. * * @author Marten Lohstroh */ public class CCppTest extends TestBase { - /** - * This target selects the C target it has no tests defined for it. - * Instead, it reconfigures existing C tests to adopt the CCpp target. - */ - public CCppTest() { - super(Target.C); - } + /** + * This target selects the C target it has no tests defined for it. Instead, it reconfigures + * existing C tests to adopt the CCpp target. + */ + public CCppTest() { + super(Target.C); + } - /** - * Run C tests with the target CCpp. - */ - @Test - public void runAsCCpp() { - Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); - runTestsForTargets(Message.DESC_AS_CCPP, CCppTest::isExcludedFromCCpp, - it -> ASTUtils.changeTargetName(it.getFileConfig().resource, - Target.CCPP.getDisplayName()), - TestLevel.EXECUTION, - true); - } + /** Run C tests with the target CCpp. */ + @Test + public void runAsCCpp() { + Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); + runTestsForTargets( + Message.DESC_AS_CCPP, + CCppTest::isExcludedFromCCpp, + it -> ASTUtils.changeTargetName(it.getFileConfig().resource, Target.CCPP.getDisplayName()), + TestLevel.EXECUTION, + true); + } - /** - * Exclusion function for runAsCCpp test - */ - private static boolean isExcludedFromCCpp(TestCategory category) { - boolean excluded = category == TestCategory.SERIALIZATION; - excluded |= isWindows() && (category == TestCategory.DOCKER_FEDERATED); - excluded |= isMac() && (category == TestCategory.DOCKER_FEDERATED || category == TestCategory.DOCKER); - excluded |= category == TestCategory.ZEPHYR; - excluded |= category == TestCategory.ARDUINO; - excluded |= category == TestCategory.NO_INLINING; - return !excluded; - } + /** Exclusion function for runAsCCpp test */ + private static boolean isExcludedFromCCpp(TestCategory category) { + boolean excluded = category == TestCategory.SERIALIZATION; + excluded |= isWindows() && (category == TestCategory.DOCKER_FEDERATED); + 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.tests/src/org/lflang/tests/runtime/CSchedulerTest.java b/org.lflang.tests/src/org/lflang/tests/runtime/CSchedulerTest.java index fa387d8a00..fd2345723f 100644 --- a/org.lflang.tests/src/org/lflang/tests/runtime/CSchedulerTest.java +++ b/org.lflang.tests/src/org/lflang/tests/runtime/CSchedulerTest.java @@ -1,74 +1,65 @@ package org.lflang.tests.runtime; import java.util.EnumSet; - import org.junit.jupiter.api.Test; - import org.lflang.Target; import org.lflang.TargetProperty.SchedulerOption; import org.lflang.tests.Configurators; import org.lflang.tests.TestBase; import org.lflang.tests.TestRegistry.TestCategory; -/** - */ +/** */ public class CSchedulerTest extends TestBase { + public CSchedulerTest() { + super(Target.C); + } - public CSchedulerTest() { - super(Target.C); - } - - /** - * Swap the default runtime scheduler with other supported versions and - * run all the supported tests. Only run tests for a specific non-default - * scheduler if specified using a system property (e.g., -Dscheduler=GEDF_NP). - */ - @Test - public void runWithNonDefaultSchedulers() { - EnumSet categories = EnumSet.of(TestCategory.CONCURRENT, - TestCategory.MULTIPORT); - - // Add federated and docker tests if supported - if (!isWindows()) { - categories.add(TestCategory.FEDERATED); - if (isLinux()) { - categories.add(TestCategory.DOCKER_FEDERATED); - } - } - var name = System.getProperty("scheduler"); + /** + * Swap the default runtime scheduler with other supported versions and run all the supported + * tests. Only run tests for a specific non-default scheduler if specified using a system property + * (e.g., -Dscheduler=GEDF_NP). + */ + @Test + public void runWithNonDefaultSchedulers() { + EnumSet categories = EnumSet.of(TestCategory.CONCURRENT, TestCategory.MULTIPORT); - if (name != null) { - var option = EnumSet.allOf(SchedulerOption.class).stream() - .filter(it -> it.name().equals(name)).findFirst(); - if (option.isPresent()) { - this.runTest(option.get(), categories); - } else { - throw new RuntimeException("Cannot find runtime scheduler called " + name); - } - } else { - for (SchedulerOption scheduler: EnumSet.allOf(SchedulerOption.class)) { - if (scheduler == SchedulerOption.getDefault()) continue; - this.runTest(scheduler, categories); - } - } + // Add federated and docker tests if supported + if (!isWindows()) { + categories.add(TestCategory.FEDERATED); + if (isLinux()) { + categories.add(TestCategory.DOCKER_FEDERATED); + } } + var name = System.getProperty("scheduler"); - private void runTest(SchedulerOption scheduler, EnumSet categories) { - this.runTestsForTargets( - Message.DESC_SCHED_SWAPPING + scheduler.toString() +".", - categories::contains, - test -> { - test.getContext().getArgs() - .setProperty( - "scheduler", - scheduler.toString() - ); - return Configurators.noChanges(test); - }, - TestLevel.EXECUTION, - true - ); + if (name != null) { + var option = + EnumSet.allOf(SchedulerOption.class).stream() + .filter(it -> it.name().equals(name)) + .findFirst(); + if (option.isPresent()) { + this.runTest(option.get(), categories); + } else { + throw new RuntimeException("Cannot find runtime scheduler called " + name); + } + } else { + for (SchedulerOption scheduler : EnumSet.allOf(SchedulerOption.class)) { + if (scheduler == SchedulerOption.getDefault()) continue; + this.runTest(scheduler, categories); + } } -} + } + private void runTest(SchedulerOption scheduler, EnumSet categories) { + this.runTestsForTargets( + Message.DESC_SCHED_SWAPPING + scheduler.toString() + ".", + categories::contains, + test -> { + test.getContext().getArgs().setProperty("scheduler", scheduler.toString()); + return Configurators.noChanges(test); + }, + TestLevel.EXECUTION, + true); + } +} diff --git a/org.lflang.tests/src/org/lflang/tests/runtime/CTest.java b/org.lflang.tests/src/org/lflang/tests/runtime/CTest.java index eba4f7bc71..4902550661 100644 --- a/org.lflang.tests/src/org/lflang/tests/runtime/CTest.java +++ b/org.lflang.tests/src/org/lflang/tests/runtime/CTest.java @@ -1,133 +1,131 @@ /************* -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.runtime; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; - import org.lflang.Target; import org.lflang.tests.RuntimeTest; /** * Collection of tests for the C target. * - * Tests that are implemented in the base class are still overridden so that - * each test can be easily invoked individually from IDEs with JUnit support - * like Eclipse and IntelliJ. - * This is typically done by right-clicking on the name of the test method and - * then clicking "Run".* + *

Tests that are implemented in the base class are still overridden so that each test can be + * easily invoked individually from IDEs with JUnit support like Eclipse and IntelliJ. This is + * typically done by right-clicking on the name of the test method and then clicking "Run".* + * * @author Marten Lohstroh */ public class CTest extends RuntimeTest { - public CTest() { - super(Target.C); - } - - @Override - protected boolean supportsSingleThreadedExecution() { - return true; - } - - @Override - protected boolean supportsFederatedExecution() { - return true; - } - - @Override - protected boolean supportsDockerOption() { - return true; - } - - @Test - @Override - public void runGenericTests() { - super.runGenericTests(); - } - - @Test - @Override - public void runTargetSpecificTests() { - Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); - super.runTargetSpecificTests(); - } - - @Test - @Override - public void runMultiportTests() { - super.runMultiportTests(); - } - - @Test - @Override - public void runWithThreadingOff() { - super.runWithThreadingOff(); - } - - @Test - @Disabled("TODO only 27/96 tests pass") - @Override - public void runAsFederated() { - Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); - super.runAsFederated(); - } - - @Test - @Override - public void runConcurrentTests() { - super.runConcurrentTests(); - } - - @Test - @Override - public void runFederatedTests() { - Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); - super.runFederatedTests(); - } - - @Test - public void runModalTests() { - super.runModalTests(); - } - - @Test - public void runNoInliningTests() { - super.runNoInliningTests(); - } - - @Test - @Override - public void runDockerTests() { - super.runDockerTests(); - } - - @Test - @Override - public void runDockerFederatedTests() { - Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); - super.runDockerFederatedTests(); - } + public CTest() { + super(Target.C); + } + + @Override + protected boolean supportsSingleThreadedExecution() { + return true; + } + + @Override + protected boolean supportsFederatedExecution() { + return true; + } + + @Override + protected boolean supportsDockerOption() { + return true; + } + + @Test + @Override + public void runGenericTests() { + super.runGenericTests(); + } + + @Test + @Override + public void runTargetSpecificTests() { + Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); + super.runTargetSpecificTests(); + } + + @Test + @Override + public void runMultiportTests() { + super.runMultiportTests(); + } + + @Test + @Override + public void runWithThreadingOff() { + super.runWithThreadingOff(); + } + + @Test + @Disabled("TODO only 27/96 tests pass") + @Override + public void runAsFederated() { + Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); + super.runAsFederated(); + } + + @Test + @Override + public void runConcurrentTests() { + super.runConcurrentTests(); + } + + @Test + @Override + public void runFederatedTests() { + Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); + super.runFederatedTests(); + } + + @Test + public void runModalTests() { + super.runModalTests(); + } + + @Test + public void runNoInliningTests() { + super.runNoInliningTests(); + } + + @Test + @Override + public void runDockerTests() { + super.runDockerTests(); + } + + @Test + @Override + public void runDockerFederatedTests() { + Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); + super.runDockerFederatedTests(); + } } diff --git a/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java b/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java index afe1646829..c151c1bb76 100644 --- a/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java +++ b/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java @@ -1,35 +1,32 @@ /************* -Copyright (c) 2023, 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) 2023, 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.runtime; import java.util.List; - import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; - import org.lflang.Target; import org.lflang.tests.Configurators; import org.lflang.tests.RuntimeTest; @@ -42,29 +39,31 @@ */ public class CZephyrTest extends RuntimeTest { - public CZephyrTest() { - super(Target.C); - } + public CZephyrTest() { + super(Target.C); + } - @Test - public void buildZephyrTests() { - Assumptions.assumeTrue(isLinux(), "Zephyr tests only run on Linux"); - super.runTestsFor(List.of(Target.C), - Message.DESC_ZEPHYR, - TestCategory.ZEPHYR::equals, - Configurators::makeZephyrCompatible, - TestLevel.BUILD, - false); - } - @Test - public void buildGenericTests() { - Assumptions.assumeTrue(isLinux(), "Zephyr tests only run on Linux"); - super.runTestsFor(List.of(Target.C), - Message.DESC_GENERIC, - TestCategory.GENERIC::equals, - Configurators::makeZephyrCompatible, - TestLevel.BUILD, - false); - } -} + @Test + public void buildZephyrTests() { + Assumptions.assumeTrue(isLinux(), "Zephyr tests only run on Linux"); + super.runTestsFor( + List.of(Target.C), + Message.DESC_ZEPHYR, + TestCategory.ZEPHYR::equals, + Configurators::makeZephyrCompatible, + TestLevel.BUILD, + false); + } + @Test + public void buildGenericTests() { + Assumptions.assumeTrue(isLinux(), "Zephyr tests only run on Linux"); + super.runTestsFor( + List.of(Target.C), + Message.DESC_GENERIC, + TestCategory.GENERIC::equals, + Configurators::makeZephyrCompatible, + TestLevel.BUILD, + false); + } +} diff --git a/org.lflang.tests/src/org/lflang/tests/runtime/CppRos2Test.java b/org.lflang.tests/src/org/lflang/tests/runtime/CppRos2Test.java index 2e4f6b78d3..dc2612c14b 100644 --- a/org.lflang.tests/src/org/lflang/tests/runtime/CppRos2Test.java +++ b/org.lflang.tests/src/org/lflang/tests/runtime/CppRos2Test.java @@ -2,9 +2,8 @@ import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; - -import org.lflang.ast.ASTUtils; import org.lflang.Target; +import org.lflang.ast.ASTUtils; import org.lflang.lf.Element; import org.lflang.lf.LfFactory; import org.lflang.tests.TestBase; @@ -12,25 +11,27 @@ /** * Run C++ tests using the ROS2 platform. * - * NOTE: This test does not inherit any tests because it directly extends TestBase. + *

NOTE: This test does not inherit any tests because it directly extends TestBase. * * @author Christian Menard */ public class CppRos2Test extends TestBase { - public CppRos2Test() { super(Target.CPP); } + public CppRos2Test() { + super(Target.CPP); + } - /** - * Run C++ tests with the ros2 target property set - */ - @Test - public void runWithRos2() { - Assumptions.assumeTrue(isLinux(), "Only supported on Linux"); - Element trueLiteral = LfFactory.eINSTANCE.createElement(); - trueLiteral.setLiteral("true"); - runTestsForTargets(Message.DESC_ROS2, it -> true, - it -> ASTUtils.addTargetProperty(it.getFileConfig().resource, "ros2", trueLiteral), - TestLevel.EXECUTION, - true); - } + /** Run C++ tests with the ros2 target property set */ + @Test + public void runWithRos2() { + Assumptions.assumeTrue(isLinux(), "Only supported on Linux"); + Element trueLiteral = LfFactory.eINSTANCE.createElement(); + trueLiteral.setLiteral("true"); + runTestsForTargets( + Message.DESC_ROS2, + it -> true, + it -> ASTUtils.addTargetProperty(it.getFileConfig().resource, "ros2", trueLiteral), + TestLevel.EXECUTION, + true); + } } diff --git a/org.lflang.tests/src/org/lflang/tests/runtime/CppTest.java b/org.lflang.tests/src/org/lflang/tests/runtime/CppTest.java index 77bf4e664a..20973189b9 100644 --- a/org.lflang.tests/src/org/lflang/tests/runtime/CppTest.java +++ b/org.lflang.tests/src/org/lflang/tests/runtime/CppTest.java @@ -1,86 +1,84 @@ /* Integration tests for the C++ target. */ /************* -Copyright (c) 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) 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.tests.runtime; -import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; import org.lflang.Target; import org.lflang.tests.RuntimeTest; /** - * Collection of tests for the Cpp target. - * Even though all tests are implemented in the base class, we override them - * here so that each test can be easily invoked individually from IDEs with - * JUnit support like Eclipse and IntelliJ. - * This is typically done by right-clicking on the name of the test method and - * then clicking "Run". + * Collection of tests for the Cpp target. Even though all tests are implemented in the base class, + * we override them here so that each test can be easily invoked individually from IDEs with JUnit + * support like Eclipse and IntelliJ. This is typically done by right-clicking on the name of the + * test method and then clicking "Run". * * @author Marten Lohstroh */ public class CppTest extends RuntimeTest { - public CppTest() { - super(Target.CPP); - } - - @Override - protected boolean supportsEnclaves() { return true; } - - @Test - @Override - public void runGenericTests() { - super.runGenericTests(); - } - - @Test - @Override - public void runTargetSpecificTests() { - super.runTargetSpecificTests(); - } - - @Test - @Override - public void runMultiportTests() { - super.runMultiportTests(); - } - - @Test - @Override - public void runConcurrentTests() { - super.runConcurrentTests(); - } - - @Test - @Override - public void runFederatedTests() { - super.runFederatedTests(); - } - - @Test - public void runRos2Tests() { } - + public CppTest() { + super(Target.CPP); + } + + @Override + protected boolean supportsEnclaves() { + return true; + } + + @Test + @Override + public void runGenericTests() { + super.runGenericTests(); + } + + @Test + @Override + public void runTargetSpecificTests() { + super.runTargetSpecificTests(); + } + + @Test + @Override + public void runMultiportTests() { + super.runMultiportTests(); + } + + @Test + @Override + public void runConcurrentTests() { + super.runConcurrentTests(); + } + + @Test + @Override + public void runFederatedTests() { + super.runFederatedTests(); + } + + @Test + public void runRos2Tests() {} } diff --git a/org.lflang.tests/src/org/lflang/tests/runtime/PythonTest.java b/org.lflang.tests/src/org/lflang/tests/runtime/PythonTest.java index e028e6ac4d..1c258b7481 100644 --- a/org.lflang.tests/src/org/lflang/tests/runtime/PythonTest.java +++ b/org.lflang.tests/src/org/lflang/tests/runtime/PythonTest.java @@ -1,31 +1,30 @@ /************* -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.runtime; import java.util.Properties; - import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -35,94 +34,92 @@ /** * Collection of tests for the Python target. * - * Even though all tests are implemented in the base class, we override them - * here so that each test can be easily invoked individually from IDEs with - * JUnit support like Eclipse and IntelliJ. - * This is typically done by right-clicking on the name of the test method and - * then clicking "Run". + *

Even though all tests are implemented in the base class, we override them here so that each + * test can be easily invoked individually from IDEs with JUnit support like Eclipse and IntelliJ. + * This is typically done by right-clicking on the name of the test method and then clicking "Run". * * @author Marten Lohstroh */ public class PythonTest extends RuntimeTest { - public PythonTest() { - super(Target.Python); - } - - @Override - protected void addExtraLfcArgs(Properties args) { - super.addExtraLfcArgs(args); - if (System.getProperty("os.name").startsWith("Windows")) { - // Use the RelWithDebInfo build type on Windows as the Debug/Test build type produces linker Errors in CI - args.setProperty("build-type", "RelWithDebInfo"); - } - } - - @Override - protected boolean supportsFederatedExecution() { - return true; - } - - @Override - protected boolean supportsSingleThreadedExecution() { - return true; - } - - @Override - protected boolean supportsDockerOption() { - return false; // FIXME: https://issues.lf-lang.org/1564 - } - - @Test - @Override - public void runGenericTests() { - super.runGenericTests(); - } - - @Test - @Override - public void runTargetSpecificTests() { - super.runTargetSpecificTests(); - } - - @Test - @Override - public void runMultiportTests() { - super.runMultiportTests(); - } - - @Test - @Disabled("TODO") - @Override - public void runAsFederated() { - Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); - super.runAsFederated(); - } - - - @Test - @Override - public void runConcurrentTests() { - super.runConcurrentTests(); - } - - @Test - @Override - public void runFederatedTests() { - Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); - super.runFederatedTests(); - } - - @Test - @Override - public void runDockerTests() { - super.runDockerTests(); - } - - @Test - @Override - public void runDockerFederatedTests() { - Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); - super.runDockerFederatedTests(); + public PythonTest() { + super(Target.Python); + } + + @Override + protected void addExtraLfcArgs(Properties args) { + super.addExtraLfcArgs(args); + if (System.getProperty("os.name").startsWith("Windows")) { + // Use the RelWithDebInfo build type on Windows as the Debug/Test build type produces linker + // Errors in CI + args.setProperty("build-type", "RelWithDebInfo"); } + } + + @Override + protected boolean supportsFederatedExecution() { + return true; + } + + @Override + protected boolean supportsSingleThreadedExecution() { + return true; + } + + @Override + protected boolean supportsDockerOption() { + return false; // FIXME: https://issues.lf-lang.org/1564 + } + + @Test + @Override + public void runGenericTests() { + super.runGenericTests(); + } + + @Test + @Override + public void runTargetSpecificTests() { + super.runTargetSpecificTests(); + } + + @Test + @Override + public void runMultiportTests() { + super.runMultiportTests(); + } + + @Test + @Disabled("TODO") + @Override + public void runAsFederated() { + Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); + super.runAsFederated(); + } + + @Test + @Override + public void runConcurrentTests() { + super.runConcurrentTests(); + } + + @Test + @Override + public void runFederatedTests() { + Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); + super.runFederatedTests(); + } + + @Test + @Override + public void runDockerTests() { + super.runDockerTests(); + } + + @Test + @Override + public void runDockerFederatedTests() { + Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); + super.runDockerFederatedTests(); + } } diff --git a/org.lflang.tests/src/org/lflang/tests/runtime/RustTest.java b/org.lflang.tests/src/org/lflang/tests/runtime/RustTest.java index 2601fe79d0..e41d358ac0 100644 --- a/org.lflang.tests/src/org/lflang/tests/runtime/RustTest.java +++ b/org.lflang.tests/src/org/lflang/tests/runtime/RustTest.java @@ -24,22 +24,18 @@ package org.lflang.tests.runtime; -import java.util.Properties; - import org.lflang.Target; import org.lflang.tests.RuntimeTest; -/** - * - */ +/** */ public class RustTest extends RuntimeTest { - public RustTest() { - super(Target.Rust); - } + public RustTest() { + super(Target.Rust); + } - @Override - protected boolean supportsGenericTypes() { - return true; - } + @Override + protected boolean supportsGenericTypes() { + return true; + } } diff --git a/org.lflang.tests/src/org/lflang/tests/runtime/TypeScriptTest.java b/org.lflang.tests/src/org/lflang/tests/runtime/TypeScriptTest.java index 295a1961ed..036211607a 100644 --- a/org.lflang.tests/src/org/lflang/tests/runtime/TypeScriptTest.java +++ b/org.lflang.tests/src/org/lflang/tests/runtime/TypeScriptTest.java @@ -8,74 +8,72 @@ /** * Collection of tests for the TypeScript target. * - * Even though all tests are implemented in the base class, we override them - * here so that each test can be easily invoked individually from IDEs with - * JUnit support like Eclipse and IntelliJ. - * This is typically done by right-clicking on the name of the test method and - * then clicking "Run". + *

Even though all tests are implemented in the base class, we override them here so that each + * test can be easily invoked individually from IDEs with JUnit support like Eclipse and IntelliJ. + * This is typically done by right-clicking on the name of the test method and then clicking "Run". * * @author Marten Lohstroh */ public class TypeScriptTest extends RuntimeTest { - public TypeScriptTest() { - super(Target.TS); - } + public TypeScriptTest() { + super(Target.TS); + } - @Override - protected boolean supportsDockerOption() { - return true; - } + @Override + protected boolean supportsDockerOption() { + return true; + } - @Override - protected boolean supportsFederatedExecution() { - return true; - } + @Override + protected boolean supportsFederatedExecution() { + return true; + } - @Test - @Override - public void runGenericTests() { - super.runGenericTests(); - } + @Test + @Override + public void runGenericTests() { + super.runGenericTests(); + } - @Test - @Override - public void runTargetSpecificTests() { - super.runTargetSpecificTests(); - } + @Test + @Override + public void runTargetSpecificTests() { + super.runTargetSpecificTests(); + } - @Test - @Override - public void runMultiportTests() { - super.runMultiportTests(); - } + @Test + @Override + public void runMultiportTests() { + super.runMultiportTests(); + } - @Test - @Override - public void runConcurrentTests() { - super.runConcurrentTests(); - } + @Test + @Override + public void runConcurrentTests() { + super.runConcurrentTests(); + } - @Test - @Override - public void runFederatedTests() { - Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); - super.runFederatedTests(); - } + @Test + @Override + public void runFederatedTests() { + Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); + super.runFederatedTests(); + } - @Test - @Override - public void runDockerTests() { - super.runDockerTests(); - } + @Test + @Override + public void runDockerTests() { + super.runDockerTests(); + } - @Test - @Override - public void runDockerFederatedTests() { - Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); - super.runDockerFederatedTests(); - } + @Test + @Override + public void runDockerFederatedTests() { + Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); + super.runDockerFederatedTests(); + } - @Test - @Override - public void runAsFederated() {} + @Test + @Override + public void runAsFederated() {} } diff --git a/org.lflang.tests/src/org/lflang/tests/serialization/SerializationTest.java b/org.lflang.tests/src/org/lflang/tests/serialization/SerializationTest.java index 35dfa9f6de..26f3ec37af 100644 --- a/org.lflang.tests/src/org/lflang/tests/serialization/SerializationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/serialization/SerializationTest.java @@ -1,7 +1,6 @@ package org.lflang.tests.serialization; import java.util.Properties; - import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; import org.lflang.Target; @@ -11,32 +10,36 @@ public class SerializationTest extends TestBase { - protected SerializationTest() { - super(Target.ALL); - } + protected SerializationTest() { + super(Target.ALL); + } + + @Override + protected void addExtraLfcArgs(Properties args) { + super.addExtraLfcArgs(args); + // Use the Debug build type as coverage generation does not work for the serialization tests + args.setProperty("build-type", "Debug"); + } + + @Test + public void runSerializationTestsWithThreadingOff() { + Assumptions.assumeTrue(supportsSingleThreadedExecution(), Message.NO_SINGLE_THREADED_SUPPORT); + runTestsForTargets( + Message.DESC_SERIALIZATION, + TestCategory.SERIALIZATION::equals, + Configurators::disableThreading, + TestLevel.EXECUTION, + false); + } - @Override - protected void addExtraLfcArgs(Properties args) { - super.addExtraLfcArgs(args); - // Use the Debug build type as coverage generation does not work for the serialization tests - args.setProperty("build-type", "Debug"); - } - - @Test - public void runSerializationTestsWithThreadingOff() { - Assumptions.assumeTrue(supportsSingleThreadedExecution(), Message.NO_SINGLE_THREADED_SUPPORT); - runTestsForTargets(Message.DESC_SERIALIZATION, - TestCategory.SERIALIZATION::equals, Configurators::disableThreading, - TestLevel.EXECUTION, - false); - } - - @Test - public void runSerializationTests() { - Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); - runTestsForTargets(Message.DESC_SERIALIZATION, - TestCategory.SERIALIZATION::equals, Configurators::noChanges, - TestLevel.EXECUTION, - false); - } + @Test + public void runSerializationTests() { + Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT); + runTestsForTargets( + Message.DESC_SERIALIZATION, + TestCategory.SERIALIZATION::equals, + Configurators::noChanges, + TestLevel.EXECUTION, + false); + } } diff --git a/org.lflang.tests/src/org/lflang/tests/util/StringUtilTest.java b/org.lflang.tests/src/org/lflang/tests/util/StringUtilTest.java index fb15cda64c..7c8ca60b83 100644 --- a/org.lflang.tests/src/org/lflang/tests/util/StringUtilTest.java +++ b/org.lflang.tests/src/org/lflang/tests/util/StringUtilTest.java @@ -33,24 +33,24 @@ public class StringUtilTest { - @Test - public void testRemoveQuotes() { - assertEquals("abc", removeQuotes("\"abc\"")); - assertEquals("a", removeQuotes("'a'")); - assertEquals("'a", removeQuotes("'a")); - assertEquals("a\"", removeQuotes("a\"")); - assertEquals("\"", removeQuotes("\"")); - assertEquals("'", removeQuotes("'")); - assertEquals("", removeQuotes("")); - assertNull(removeQuotes(null)); - } + @Test + public void testRemoveQuotes() { + assertEquals("abc", removeQuotes("\"abc\"")); + assertEquals("a", removeQuotes("'a'")); + assertEquals("'a", removeQuotes("'a")); + assertEquals("a\"", removeQuotes("a\"")); + assertEquals("\"", removeQuotes("\"")); + assertEquals("'", removeQuotes("'")); + assertEquals("", removeQuotes("")); + assertNull(removeQuotes(null)); + } - @Test - public void testCamelToSnakeCase() { - assertEquals("some_string", camelToSnakeCase("someString")); - assertEquals("abc_str", camelToSnakeCase("AbcStr")); - assertEquals("ast", camelToSnakeCase("AST")); - assertEquals("ast_builder", camelToSnakeCase("ASTBuilder")); - assertEquals("something_with_a_preamble", camelToSnakeCase("SomethingWithAPreamble")); - } + @Test + public void testCamelToSnakeCase() { + assertEquals("some_string", camelToSnakeCase("someString")); + assertEquals("abc_str", camelToSnakeCase("AbcStr")); + assertEquals("ast", camelToSnakeCase("AST")); + assertEquals("ast_builder", camelToSnakeCase("ASTBuilder")); + assertEquals("something_with_a_preamble", camelToSnakeCase("SomethingWithAPreamble")); + } } diff --git a/org.lflang/src/org/lflang/AttributeUtils.java b/org.lflang/src/org/lflang/AttributeUtils.java index 0c95134539..e956cfc0c4 100644 --- a/org.lflang/src/org/lflang/AttributeUtils.java +++ b/org.lflang/src/org/lflang/AttributeUtils.java @@ -27,12 +27,10 @@ import java.util.List; import java.util.Objects; - import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.nodemodel.ICompositeNode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import org.eclipse.xtext.resource.XtextResource; - import org.lflang.ast.ASTUtils; import org.lflang.lf.Action; import org.lflang.lf.AttrParm; @@ -56,187 +54,184 @@ */ public class AttributeUtils { - /** - * Return the attributes declared on the given node. Throws - * if the node does not support declaring attributes. - * - * @throws IllegalArgumentException If the node cannot have attributes - */ - public static List getAttributes(EObject node) { - if (node instanceof Reactor) { - return ((Reactor) node).getAttributes(); - } else if (node instanceof Reaction) { - return ((Reaction) node).getAttributes(); - } else if (node instanceof Action) { - return ((Action) node).getAttributes(); - } else if (node instanceof Timer) { - return ((Timer) node).getAttributes(); - } else if (node instanceof StateVar) { - return ((StateVar) node).getAttributes(); - } else if (node instanceof Parameter) { - return ((Parameter) node).getAttributes(); - } else if (node instanceof Input) { - return ((Input) node).getAttributes(); - } else if (node instanceof Output) { - return ((Output) node).getAttributes(); - } else if (node instanceof Instantiation) { - return ((Instantiation) node).getAttributes(); - } - throw new IllegalArgumentException("Not annotatable: " + node); + /** + * Return the attributes declared on the given node. Throws if the node does not support declaring + * attributes. + * + * @throws IllegalArgumentException If the node cannot have attributes + */ + public static List getAttributes(EObject node) { + if (node instanceof Reactor) { + return ((Reactor) node).getAttributes(); + } else if (node instanceof Reaction) { + return ((Reaction) node).getAttributes(); + } else if (node instanceof Action) { + return ((Action) node).getAttributes(); + } else if (node instanceof Timer) { + return ((Timer) node).getAttributes(); + } else if (node instanceof StateVar) { + return ((StateVar) node).getAttributes(); + } else if (node instanceof Parameter) { + return ((Parameter) node).getAttributes(); + } else if (node instanceof Input) { + return ((Input) node).getAttributes(); + } else if (node instanceof Output) { + return ((Output) node).getAttributes(); + } else if (node instanceof Instantiation) { + return ((Instantiation) node).getAttributes(); } - - /** - * Return the attribute with the given name - * if present, otherwise return null. - * - * @throws IllegalArgumentException If the node cannot have attributes - */ - public static Attribute findAttributeByName(EObject node, String name) { - List attrs = getAttributes(node); - return attrs.stream() - .filter(it -> it.getAttrName().equalsIgnoreCase(name)) // case-insensitive search (more user-friendly) - .findFirst() - .orElse(null); + throw new IllegalArgumentException("Not annotatable: " + node); + } + + /** + * Return the attribute with the given name if present, otherwise return null. + * + * @throws IllegalArgumentException If the node cannot have attributes + */ + public static Attribute findAttributeByName(EObject node, String name) { + List attrs = getAttributes(node); + return attrs.stream() + .filter( + it -> + it.getAttrName() + .equalsIgnoreCase(name)) // case-insensitive search (more user-friendly) + .findFirst() + .orElse(null); + } + + /** + * Return the first argument specified for the attribute. + * + *

This should be used if the attribute is expected to have a single argument. If there is no + * argument, null is returned. + */ + public static String getFirstArgumentValue(Attribute attr) { + if (attr == null || attr.getAttrParms().isEmpty()) { + return null; } - - /** - * Return the first argument specified for the attribute. - * - * This should be used if the attribute is expected to have a single argument. - * If there is no argument, null is returned. - */ - public static String getFirstArgumentValue(Attribute attr) { - if (attr == null || attr.getAttrParms().isEmpty()) { - return null; - } - return StringUtil.removeQuotes(attr.getAttrParms().get(0).getValue()); + return StringUtil.removeQuotes(attr.getAttrParms().get(0).getValue()); + } + + /** + * Search for an attribute with the given name on the given AST node and return its first argument + * as a String. + * + *

This should only be used on attributes that are expected to have a single argument. + * + *

Returns null if the attribute is not found or if it does not have any arguments. + */ + public static String getAttributeValue(EObject node, String attrName) { + final var attr = findAttributeByName(node, attrName); + String value = getFirstArgumentValue(attr); + // Attribute annotations in comments are deprecated, but we still check for then for backwards + // compatibility + if (value == null) { + return findAnnotationInComments(node, "@" + attrName); } - - /** - * Search for an attribute with the given name on the given AST node and return its first - * argument as a String. - * - * This should only be used on attributes that are expected to have a single argument. - * - * Returns null if the attribute is not found or if it does not have any arguments. - */ - public static String getAttributeValue(EObject node, String attrName) { - final var attr = findAttributeByName(node, attrName); - String value = getFirstArgumentValue(attr); - // Attribute annotations in comments are deprecated, but we still check for then for backwards - // compatibility - if (value == null) { - return findAnnotationInComments(node, "@" + attrName); - } - return value; - } - - /** - * Retrieve a specific annotation in a comment associated with the given model element in the AST. - * - * This will look for a comment. If one is found, it searches for the given annotation {@code key}. - * and extracts any string that follows the annotation marker. - * - * @param object the AST model element to search a comment for - * @param key the specific annotation key to be extracted - * @return {@code null} if no JavaDoc style comment was found or if it does not contain the given key. - * The string immediately following the annotation marker otherwise. - */ - public static String findAnnotationInComments(EObject object, String key) { - if (!(object.eResource() instanceof XtextResource)) return null; - ICompositeNode node = NodeModelUtils.findActualNodeFor(object); - return ASTUtils.getPrecedingComments(node, n -> true).flatMap(String::lines) - .filter(line -> line.contains(key)) - .map(String::trim) - .map(it -> it.substring(it.indexOf(key) + key.length())) - .map(it -> it.endsWith("*/") ? it.substring(0, it.length() - "*/".length()) : it) - .findFirst().orElse(null); - } - - /** - * Return the parameter of the given attribute with the given name. - * - * Returns null if no such parameter is found. - */ - public static String getAttributeParameter(Attribute attribute, String parameterName) { - return (attribute == null) ? null : attribute.getAttrParms().stream() + return value; + } + + /** + * Retrieve a specific annotation in a comment associated with the given model element in the AST. + * + *

This will look for a comment. If one is found, it searches for the given annotation {@code + * key}. and extracts any string that follows the annotation marker. + * + * @param object the AST model element to search a comment for + * @param key the specific annotation key to be extracted + * @return {@code null} if no JavaDoc style comment was found or if it does not contain the given + * key. The string immediately following the annotation marker otherwise. + */ + public static String findAnnotationInComments(EObject object, String key) { + if (!(object.eResource() instanceof XtextResource)) return null; + ICompositeNode node = NodeModelUtils.findActualNodeFor(object); + return ASTUtils.getPrecedingComments(node, n -> true) + .flatMap(String::lines) + .filter(line -> line.contains(key)) + .map(String::trim) + .map(it -> it.substring(it.indexOf(key) + key.length())) + .map(it -> it.endsWith("*/") ? it.substring(0, it.length() - "*/".length()) : it) + .findFirst() + .orElse(null); + } + + /** + * Return the parameter of the given attribute with the given name. + * + *

Returns null if no such parameter is found. + */ + public static String getAttributeParameter(Attribute attribute, String parameterName) { + return (attribute == null) + ? null + : attribute.getAttrParms().stream() .filter(param -> Objects.equals(param.getName(), parameterName)) .map(AttrParm::getValue) .map(StringUtil::removeQuotes) .findFirst() .orElse(null); + } + + /** + * Return the parameter of the given attribute with the given name and interpret it as a boolean. + * + *

Returns null if no such parameter is found. + */ + public static Boolean getBooleanAttributeParameter(Attribute attribute, String parameterName) { + if (attribute == null || parameterName == null) { + return null; } - - /** - * Return the parameter of the given attribute with the given name and interpret it as a boolean. - * - * Returns null if no such parameter is found. - */ - public static Boolean getBooleanAttributeParameter(Attribute attribute, String parameterName) { - if (attribute == null || parameterName == null) { - return null; - } - final var param = getAttributeParameter(attribute, parameterName); - if (param == null) { - return null; - } - return param.equalsIgnoreCase("true"); - } - - /** - * Return true if the specified node is an Input and has an {@code @sparse} - * attribute. - * @param node An AST node. - */ - public static boolean isSparse(EObject node) { - return findAttributeByName(node, "sparse") != null; + final var param = getAttributeParameter(attribute, parameterName); + if (param == null) { + return null; } - - /** - * Return true if the reactor is marked to be a federate. - */ - public static boolean isFederate(Reactor reactor) { - return findAttributeByName(reactor, "_fed_config") != null; - } - - /** - * Return true if the reaction is marked to have a C code body. - * - * Currently, this is only used for synthesized reactions in the context of - * federated execution in Python. - */ - public static boolean hasCBody(Reaction reaction) { - return findAttributeByName(reaction, "_c_body") != null; - } - - /** - * Return the declared label of the node, as given by the @label annotation. - */ - public static String getLabel(EObject node) { - return getAttributeValue(node, "label"); - } - - /** - * Return the declared icon of the node, as given by the @icon annotation. - */ - public static String getIconPath(EObject node) { - return getAttributeValue(node, "icon"); - } - - /** - * Return the {@code @enclave} attribute annotated on the given node. - * - * Returns null if there is no such attribute. - */ - public static Attribute getEnclaveAttribute(Instantiation node) { - return findAttributeByName(node, "enclave"); - } - - /** - * Return true if the specified instance has an {@code @enclave} attribute. - */ - public static boolean isEnclave(Instantiation node) { - return getEnclaveAttribute(node) != null; - } - + return param.equalsIgnoreCase("true"); + } + + /** + * Return true if the specified node is an Input and has an {@code @sparse} attribute. + * + * @param node An AST node. + */ + public static boolean isSparse(EObject node) { + return findAttributeByName(node, "sparse") != null; + } + + /** Return true if the reactor is marked to be a federate. */ + public static boolean isFederate(Reactor reactor) { + return findAttributeByName(reactor, "_fed_config") != null; + } + + /** + * Return true if the reaction is marked to have a C code body. + * + *

Currently, this is only used for synthesized reactions in the context of federated execution + * in Python. + */ + public static boolean hasCBody(Reaction reaction) { + return findAttributeByName(reaction, "_c_body") != null; + } + + /** Return the declared label of the node, as given by the @label annotation. */ + public static String getLabel(EObject node) { + return getAttributeValue(node, "label"); + } + + /** Return the declared icon of the node, as given by the @icon annotation. */ + public static String getIconPath(EObject node) { + return getAttributeValue(node, "icon"); + } + + /** + * Return the {@code @enclave} attribute annotated on the given node. + * + *

Returns null if there is no such attribute. + */ + public static Attribute getEnclaveAttribute(Instantiation node) { + return findAttributeByName(node, "enclave"); + } + + /** Return true if the specified instance has an {@code @enclave} attribute. */ + public static boolean isEnclave(Instantiation node) { + return getEnclaveAttribute(node) != null; + } } diff --git a/org.lflang/src/org/lflang/DefaultErrorReporter.java b/org.lflang/src/org/lflang/DefaultErrorReporter.java index 704e19951c..2698d2b582 100644 --- a/org.lflang/src/org/lflang/DefaultErrorReporter.java +++ b/org.lflang/src/org/lflang/DefaultErrorReporter.java @@ -1,72 +1,68 @@ package org.lflang; -import org.eclipse.emf.ecore.EObject; - import java.nio.file.Path; +import org.eclipse.emf.ecore.EObject; -/** - * Simple implementation of the ErrorReport interface that simply prints to - * standard out. - */ +/** Simple implementation of the ErrorReport interface that simply prints to standard out. */ public class DefaultErrorReporter implements ErrorReporter { - private boolean errorsOccurred = false; + private boolean errorsOccurred = false; - private String println(String s) { - System.out.println(s); - return s; - } + private String println(String s) { + System.out.println(s); + return s; + } - @Override - public String reportError(String message) { - errorsOccurred = true; - return println("ERROR: " + message); - } + @Override + public String reportError(String message) { + errorsOccurred = true; + return println("ERROR: " + message); + } - @Override - public String reportError(EObject object, String message) { - errorsOccurred = true; - return println("ERROR: " + message); - } + @Override + public String reportError(EObject object, String message) { + errorsOccurred = true; + return println("ERROR: " + message); + } - @Override - public String reportError(Path file, Integer line, String message) { - errorsOccurred = true; - return println("ERROR: " + message); - } + @Override + public String reportError(Path file, Integer line, String message) { + errorsOccurred = true; + return println("ERROR: " + message); + } - @Override - public String reportWarning(String message) { - return println("WARNING: " + message); - } + @Override + public String reportWarning(String message) { + return println("WARNING: " + message); + } - @Override - public String reportInfo(String message) { - return println("INFO: " + message); - } + @Override + public String reportInfo(String message) { + return println("INFO: " + message); + } - @Override - public String reportWarning(EObject object, String message) { - return println("WARNING: " + message); - } + @Override + public String reportWarning(EObject object, String message) { + return println("WARNING: " + message); + } - @Override - public String reportInfo(EObject object, String message) { - return println("INFO: " + message); - } + @Override + public String reportInfo(EObject object, String message) { + return println("INFO: " + message); + } - @Override - public String reportWarning(Path file, Integer line, String message) { - return println("WARNING: " + message); - } + @Override + public String reportWarning(Path file, Integer line, String message) { + return println("WARNING: " + message); + } - @Override - public String reportInfo(Path file, Integer line, String message) { - return println("INFO: " + message); - } + @Override + public String reportInfo(Path file, Integer line, String message) { + return println("INFO: " + message); + } - @Override - public boolean getErrorsOccurred() { - return errorsOccurred; - } + @Override + public boolean getErrorsOccurred() { + return errorsOccurred; + } } diff --git a/org.lflang/src/org/lflang/ErrorReporter.java b/org.lflang/src/org/lflang/ErrorReporter.java index 0a01e252ee..f7fe6dcb31 100644 --- a/org.lflang/src/org/lflang/ErrorReporter.java +++ b/org.lflang/src/org/lflang/ErrorReporter.java @@ -1,10 +1,8 @@ package org.lflang; import java.nio.file.Path; - import org.eclipse.emf.ecore.EObject; import org.eclipse.lsp4j.DiagnosticSeverity; - import org.lflang.generator.Position; /** @@ -16,164 +14,157 @@ */ public interface ErrorReporter { - /** - * Report an error. - * - * @param message The error message. - * @return a string that describes the error. - */ - String reportError(String message); - - - /** - * Report a warning. - * - * @param message The warning message. - * @return a string that describes the warning. - */ - String reportWarning(String message); - - /** - * Report an informational message. - * - * @param message The message to report - * @return a string that describes the error - */ - String reportInfo(String message); - - - /** - * Report an error on the specified parse tree object. - * - * @param object The parse tree object. - * @param message The error message. - * @return a string that describes the error. - */ - String reportError(EObject object, String message); - - - /** - * Report a warning on the specified parse tree object. - * - * @param object The parse tree object. - * @param message The error message. - * @return a string that describes the warning. - */ - String reportWarning(EObject object, String message); - - /** - * Report an informational message on the specified parse tree object. - * - * @param object The parse tree object. - * @param message The informational message - * @return a string that describes the info - */ - String reportInfo(EObject object, String message); - - - /** - * Report an error at the specified line within a file. - * - * @param message The error message. - * @param line The one-based line number to report at. - * @param file The file to report at. - * @return a string that describes the error. - */ - String reportError(Path file, Integer line, String message); - - - /** - * Report a warning at the specified line within a file. - * - * @param message The error message. - * @param line The one-based line number to report at. - * @param file The file to report at. - * @return a string that describes the warning. - */ - String reportWarning(Path file, Integer line, String message); - - - /** - * Report an informational message at the specified line within a file. - * - * @param file The file to report at. - * @param line The one-based line number to report at. - * @param message The error message. - * @return - */ - String reportInfo(Path file, Integer line, String message); - - /** - * Report a message of severity {@code severity}. - * @param file The file to which the message pertains, or {@code null} if the file is unknown. - * @param severity the severity of the message - * @param message the message to send to the IDE - * @return a string that describes the diagnostic - */ - default String report(Path file, DiagnosticSeverity severity, String message) { - switch (severity) { - case Error: - return reportError(message); - case Warning: - case Hint: - case Information: - return reportInfo(message); - default: - return reportWarning(message); - } - } - - /** - * Report a message of severity {@code severity} that - * pertains to line {@code line} of an LF source file. - * @param file The file to which the message pertains, or {@code null} if the file is unknown. - * @param severity the severity of the message - * @param message the message to send to the IDE - * @param line the one-based line number associated - * with the message - * @return a string that describes the diagnostic - */ - default String report(Path file, DiagnosticSeverity severity, String message, int line) { - switch (severity) { - case Error: - return reportError(file, line, message); - case Warning: - case Hint: - case Information: - return reportInfo(file, line, message); - default: - return reportWarning(file, line, message); - } + /** + * Report an error. + * + * @param message The error message. + * @return a string that describes the error. + */ + String reportError(String message); + + /** + * Report a warning. + * + * @param message The warning message. + * @return a string that describes the warning. + */ + String reportWarning(String message); + + /** + * Report an informational message. + * + * @param message The message to report + * @return a string that describes the error + */ + String reportInfo(String message); + + /** + * Report an error on the specified parse tree object. + * + * @param object The parse tree object. + * @param message The error message. + * @return a string that describes the error. + */ + String reportError(EObject object, String message); + + /** + * Report a warning on the specified parse tree object. + * + * @param object The parse tree object. + * @param message The error message. + * @return a string that describes the warning. + */ + String reportWarning(EObject object, String message); + + /** + * Report an informational message on the specified parse tree object. + * + * @param object The parse tree object. + * @param message The informational message + * @return a string that describes the info + */ + String reportInfo(EObject object, String message); + + /** + * Report an error at the specified line within a file. + * + * @param message The error message. + * @param line The one-based line number to report at. + * @param file The file to report at. + * @return a string that describes the error. + */ + String reportError(Path file, Integer line, String message); + + /** + * Report a warning at the specified line within a file. + * + * @param message The error message. + * @param line The one-based line number to report at. + * @param file The file to report at. + * @return a string that describes the warning. + */ + String reportWarning(Path file, Integer line, String message); + + /** + * Report an informational message at the specified line within a file. + * + * @param file The file to report at. + * @param line The one-based line number to report at. + * @param message The error message. + * @return + */ + String reportInfo(Path file, Integer line, String message); + + /** + * Report a message of severity {@code severity}. + * + * @param file The file to which the message pertains, or {@code null} if the file is unknown. + * @param severity the severity of the message + * @param message the message to send to the IDE + * @return a string that describes the diagnostic + */ + default String report(Path file, DiagnosticSeverity severity, String message) { + switch (severity) { + case Error: + return reportError(message); + case Warning: + case Hint: + case Information: + return reportInfo(message); + default: + return reportWarning(message); } - - /** - * Report a message of severity {@code severity} that - * pertains to the range [{@code startPos}, {@code endPos}) - * of an LF source file. - * @param file The file to which the message pertains, or {@code null} if the file is unknown. - * @param severity the severity of the message - * @param message the message to send to the IDE - * @param startPos the position of the first character - * of the range of interest - * @param endPos the position immediately AFTER the - * final character of the range of - * interest - * @return a string that describes the diagnostic - */ - default String report(Path file, DiagnosticSeverity severity, String message, Position startPos, Position endPos) { - return report(file, severity, message, startPos.getOneBasedLine()); + } + + /** + * Report a message of severity {@code severity} that pertains to line {@code line} of an LF + * source file. + * + * @param file The file to which the message pertains, or {@code null} if the file is unknown. + * @param severity the severity of the message + * @param message the message to send to the IDE + * @param line the one-based line number associated with the message + * @return a string that describes the diagnostic + */ + default String report(Path file, DiagnosticSeverity severity, String message, int line) { + switch (severity) { + case Error: + return reportError(file, line, message); + case Warning: + case Hint: + case Information: + return reportInfo(file, line, message); + default: + return reportWarning(file, line, message); } - - /** - * Check if errors where reported. - * - * @return true if errors where reported - */ - boolean getErrorsOccurred(); - - /** - * Clear error history, if exists. - * This is usually only the case for error markers in Epoch (Eclipse). - */ - default void clearHistory() {} + } + + /** + * Report a message of severity {@code severity} that pertains to the range [{@code startPos}, + * {@code endPos}) of an LF source file. + * + * @param file The file to which the message pertains, or {@code null} if the file is unknown. + * @param severity the severity of the message + * @param message the message to send to the IDE + * @param startPos the position of the first character of the range of interest + * @param endPos the position immediately AFTER the final character of the range of interest + * @return a string that describes the diagnostic + */ + default String report( + Path file, DiagnosticSeverity severity, String message, Position startPos, Position endPos) { + return report(file, severity, message, startPos.getOneBasedLine()); + } + + /** + * Check if errors where reported. + * + * @return true if errors where reported + */ + boolean getErrorsOccurred(); + + /** + * Clear error history, if exists. This is usually only the case for error markers in Epoch + * (Eclipse). + */ + default void clearHistory() {} } diff --git a/org.lflang/src/org/lflang/FileConfig.java b/org.lflang/src/org/lflang/FileConfig.java index e2d3313a61..831cb7957f 100644 --- a/org.lflang/src/org/lflang/FileConfig.java +++ b/org.lflang/src/org/lflang/FileConfig.java @@ -5,15 +5,12 @@ import java.nio.file.Paths; import java.util.List; import java.util.function.Consumer; - import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.generator.IFileSystemAccess2; - - import org.lflang.generator.GeneratorUtils; import org.lflang.util.FileUtil; import org.lflang.util.LFCommand; @@ -22,297 +19,267 @@ * Base class that governs the interactions between code generators and the file system. * * @author Marten Lohstroh - * */ public abstract class FileConfig { - // Public static fields. - - public static final String DEFAULT_SRC_DIR = "src"; - - /** - * Default name of the directory to store binaries in. - */ - public static final String DEFAULT_BIN_DIR = "bin"; - - /** - * Default name of the directory to store generated sources in. - */ - public static final String DEFAULT_SRC_GEN_DIR = "src-gen"; - - // Public fields. - - /** - * The directory in which to put binaries, if the code generator produces any. - */ - public final Path binPath; - - /** - * The name of the main reactor, which has to match the file name (without - * the '.lf' extension). - */ - public final String name; - - /** - * The directory that is the root of the package in which the .lf source file resides. This path is determined - * differently depending on whether the compiler is invoked through the IDE or from the command line. In the former - * case, the package is the project root that the source resides in. In the latter case, it is the parent directory - * of the nearest {@code src} directory up the hierarchy, if there is one, or just the {@code outPath} if there is none. It is - * recommended to always keep the sources in a {@code src} directory regardless of the workflow, in which case the - * output behavior will be identical irrespective of the way the compiler is invoked. - */ - public final Path srcPkgPath; - - /** - * The file containing the main source code. - * This is the Eclipse eCore view of the file, which is distinct - * from the XText view of the file and the OS view of the file. - */ - public final Resource resource; - - /** - * If running in an Eclipse IDE, the iResource refers to the - * IFile representing the Lingua Franca program. - * This is the XText view of the file, which is distinct - * from the Eclipse eCore view of the file and the OS view of the file. - *

- * This is null if running outside an Eclipse IDE. - */ - public final IResource iResource; - - /** - * The full path to the file containing the .lf file including the - * full filename with the .lf extension. - */ - public final Path srcFile; - - /** - * The directory in which the source .lf file was found. - */ - public final Path srcPath; // FIXME: rename this to srcDir? - - /** - * Indicate whether the bin directory should be hierarchical. - */ - public final boolean useHierarchicalBin; - - // Protected fields. - - /** - * Path representation of srcGenRoot, the root directory for generated - * sources. - */ - protected Path srcGenBasePath; - - /** - * The directory in which to put the generated sources. - * This takes into account the location of the source file relative to the - * package root. Specifically, if the source file is x/y/Z.lf relative - * to the package root, then the generated sources will be put in x/y/Z - * relative to srcGenBasePath. - */ - protected Path srcGenPath; - - // private fields - - /** - * The parent of the directory designated for placing generated sources into ({@code ./src-gen} by default). Additional - * directories (such as {@code bin} or {@code build}) should be created as siblings of the directory for generated sources, - * which means that such directories should be created relative to the path assigned to this class variable. - *

- * The generated source directory is specified in the IDE (Project Properties->LF->Compiler->Output Folder). When - * invoking the standalone compiler, the output path is specified directly using the {@code -o} or {@code --output-path} option. - */ - private final Path outPath; - - /** - * The directory that denotes the root of the package to which the - * generated sources belong. Even if the target language does not have a - * notion of packages, this directory groups all files associated with a - * single main reactor. - * of packages. - */ - private final Path srcGenPkgPath; - - public FileConfig(Resource resource, Path srcGenBasePath, boolean useHierarchicalBin) throws IOException { - this.resource = resource; - this.useHierarchicalBin = useHierarchicalBin; - - this.srcFile = FileUtil.toPath(this.resource); - - this.srcPath = srcFile.getParent(); - this.srcPkgPath = getPkgPath(resource); - - this.srcGenBasePath = srcGenBasePath; - this.name = FileUtil.nameWithoutExtension(this.srcFile); - this.srcGenPath = srcGenBasePath.resolve(getSubPkgPath(srcPath)).resolve(name); - this.srcGenPkgPath = this.srcGenPath; - this.outPath = srcGenBasePath.getParent(); - - Path binRoot = outPath.resolve(DEFAULT_BIN_DIR); - this.binPath = useHierarchicalBin ? binRoot.resolve(getSubPkgPath(srcPath)) : binRoot; - - this.iResource = FileUtil.getIResource(resource); - } - - /** - * Get the directory a resource is located in relative to the root package - */ - public Path getDirectory(Resource r) throws IOException { - return getSubPkgPath(FileUtil.toPath(r).getParent()); - } - - /** - * The parent of the directory designated for placing generated sources into ({@code ./src-gen} by default). Additional - * directories (such as {@code bin} or {@code build}) should be created as siblings of the directory for generated sources, - * which means that such directories should be created relative to the path assigned to this class variable. - *

- * The generated source directory is specified in the IDE (Project Properties->LF->Compiler->Output Folder). When - * invoking the standalone compiler, the output path is specified directly using the {@code -o} or {@code --output-path} option. - */ - public Path getOutPath() { - return outPath; + // Public static fields. + + public static final String DEFAULT_SRC_DIR = "src"; + + /** Default name of the directory to store binaries in. */ + public static final String DEFAULT_BIN_DIR = "bin"; + + /** Default name of the directory to store generated sources in. */ + public static final String DEFAULT_SRC_GEN_DIR = "src-gen"; + + // Public fields. + + /** The directory in which to put binaries, if the code generator produces any. */ + public final Path binPath; + + /** + * The name of the main reactor, which has to match the file name (without the '.lf' extension). + */ + public final String name; + + /** + * The directory that is the root of the package in which the .lf source file resides. This path + * is determined differently depending on whether the compiler is invoked through the IDE or from + * the command line. In the former case, the package is the project root that the source resides + * in. In the latter case, it is the parent directory of the nearest {@code src} directory up the + * hierarchy, if there is one, or just the {@code outPath} if there is none. It is recommended to + * always keep the sources in a {@code src} directory regardless of the workflow, in which case + * the output behavior will be identical irrespective of the way the compiler is invoked. + */ + public final Path srcPkgPath; + + /** + * The file containing the main source code. This is the Eclipse eCore view of the file, which is + * distinct from the XText view of the file and the OS view of the file. + */ + public final Resource resource; + + /** + * If running in an Eclipse IDE, the iResource refers to the IFile representing the Lingua Franca + * program. This is the XText view of the file, which is distinct from the Eclipse eCore view of + * the file and the OS view of the file. + * + *

This is null if running outside an Eclipse IDE. + */ + public final IResource iResource; + + /** + * The full path to the file containing the .lf file including the full filename with the .lf + * extension. + */ + public final Path srcFile; + + /** The directory in which the source .lf file was found. */ + public final Path srcPath; // FIXME: rename this to srcDir? + + /** Indicate whether the bin directory should be hierarchical. */ + public final boolean useHierarchicalBin; + + // Protected fields. + + /** Path representation of srcGenRoot, the root directory for generated sources. */ + protected Path srcGenBasePath; + + /** + * The directory in which to put the generated sources. This takes into account the location of + * the source file relative to the package root. Specifically, if the source file is x/y/Z.lf + * relative to the package root, then the generated sources will be put in x/y/Z relative to + * srcGenBasePath. + */ + protected Path srcGenPath; + + // private fields + + /** + * The parent of the directory designated for placing generated sources into ({@code ./src-gen} by + * default). Additional directories (such as {@code bin} or {@code build}) should be created as + * siblings of the directory for generated sources, which means that such directories should be + * created relative to the path assigned to this class variable. + * + *

The generated source directory is specified in the IDE (Project + * Properties->LF->Compiler->Output Folder). When invoking the standalone compiler, the output + * path is specified directly using the {@code -o} or {@code --output-path} option. + */ + private final Path outPath; + + /** + * The directory that denotes the root of the package to which the generated sources belong. Even + * if the target language does not have a notion of packages, this directory groups all files + * associated with a single main reactor. of packages. + */ + private final Path srcGenPkgPath; + + public FileConfig(Resource resource, Path srcGenBasePath, boolean useHierarchicalBin) + throws IOException { + this.resource = resource; + this.useHierarchicalBin = useHierarchicalBin; + + this.srcFile = FileUtil.toPath(this.resource); + + this.srcPath = srcFile.getParent(); + this.srcPkgPath = getPkgPath(resource); + + this.srcGenBasePath = srcGenBasePath; + this.name = FileUtil.nameWithoutExtension(this.srcFile); + this.srcGenPath = srcGenBasePath.resolve(getSubPkgPath(srcPath)).resolve(name); + this.srcGenPkgPath = this.srcGenPath; + this.outPath = srcGenBasePath.getParent(); + + Path binRoot = outPath.resolve(DEFAULT_BIN_DIR); + this.binPath = useHierarchicalBin ? binRoot.resolve(getSubPkgPath(srcPath)) : binRoot; + + this.iResource = FileUtil.getIResource(resource); + } + + /** Get the directory a resource is located in relative to the root package */ + public Path getDirectory(Resource r) throws IOException { + return getSubPkgPath(FileUtil.toPath(r).getParent()); + } + + /** + * The parent of the directory designated for placing generated sources into ({@code ./src-gen} by + * default). Additional directories (such as {@code bin} or {@code build}) should be created as + * siblings of the directory for generated sources, which means that such directories should be + * created relative to the path assigned to this class variable. + * + *

The generated source directory is specified in the IDE (Project + * Properties->LF->Compiler->Output Folder). When invoking the standalone compiler, the output + * path is specified directly using the {@code -o} or {@code --output-path} option. + */ + public Path getOutPath() { + return outPath; + } + + /** + * The directory in which to put the generated sources. This takes into account the location of + * the source file relative to the package root. Specifically, if the source file is x/y/Z.lf + * relative to the package root, then the generated sources will be put in x/y/Z relative to + * srcGenBasePath. + */ + public Path getSrcGenPath() { + return srcGenPath; + } + + /** + * Path representation of srcGenRoot, the root directory for generated sources. This is the root, + * meaning that if the source file is x/y/Z.lf relative to the package root, then the generated + * sources will be put in x/y/Z relative to this URI. + */ + public Path getSrcGenBasePath() { + return srcGenBasePath; + } + + /** + * The directory that denotes the root of the package to which the generated sources belong. Even + * if the target language does not have a notion of packages, this directory groups all files + * associated with a single main reactor. + */ + public Path getSrcGenPkgPath() { + return srcGenPkgPath; + } + + /** Returns the root directory for generated sources. */ + public static Path getSrcGenRoot(IFileSystemAccess2 fsa) throws IOException { + URI srcGenURI = fsa.getURI(""); + if (srcGenURI.hasTrailingPathSeparator()) { + srcGenURI = srcGenURI.trimSegments(1); } - - /** - * The directory in which to put the generated sources. - * This takes into account the location of the source file relative to the - * package root. Specifically, if the source file is x/y/Z.lf relative - * to the package root, then the generated sources will be put in x/y/Z - * relative to srcGenBasePath. - */ - public Path getSrcGenPath() { - return srcGenPath; - } - - - /** - * Path representation of srcGenRoot, the root directory for generated - * sources. This is the root, meaning that if the source file is x/y/Z.lf - * relative to the package root, then the generated sources will be put in x/y/Z - * relative to this URI. - */ - public Path getSrcGenBasePath() { - return srcGenBasePath; + return FileUtil.toPath(srcGenURI); + } + + /** + * Given a path that denotes the full path to a source file (not including the file itself), + * return the relative path from the root of the 'src' directory, or, if there is no 'src' + * directory, the relative path from the root of the package. + * + * @param srcPath The path to the source. + * @return the relative path from the root of the 'src' directory, or, if there is no 'src' + * directory, the relative path from the root of the package + */ + protected Path getSubPkgPath(Path srcPath) { + Path relSrcPath = srcPkgPath.relativize(srcPath); + if (relSrcPath.startsWith(DEFAULT_SRC_DIR)) { + int segments = relSrcPath.getNameCount(); + if (segments == 1) { + return Paths.get(""); + } else { + relSrcPath = relSrcPath.subpath(1, segments); + } } - - /** - * The directory that denotes the root of the package to which the - * generated sources belong. Even if the target language does not have a - * notion of packages, this directory groups all files associated with a - * single main reactor. - */ - public Path getSrcGenPkgPath() { - return srcGenPkgPath; - } - - /** - * Returns the root directory for generated sources. - */ - public static Path getSrcGenRoot(IFileSystemAccess2 fsa) throws IOException { - URI srcGenURI = fsa.getURI(""); - if (srcGenURI.hasTrailingPathSeparator()) { - srcGenURI = srcGenURI.trimSegments(1); + return relSrcPath; + } + + /** + * Clean any artifacts produced by the code generator and target compilers. + * + *

The base implementation deletes the bin and src-gen directories. If the target code + * generator creates additional files or directories, the corresponding generator should override + * this method. + * + * @throws IOException If an I/O error occurs. + */ + public void doClean() throws IOException { + FileUtil.deleteDirectory(binPath); + FileUtil.deleteDirectory(srcGenBasePath); + } + + private static Path getPkgPath(Resource resource) throws IOException { + if (resource.getURI().isPlatform()) { + // We are in the RCA. + Path srcFile = FileUtil.toPath(resource); + for (IProject r : ResourcesPlugin.getWorkspace().getRoot().getProjects()) { + Path p = Paths.get(r.getLocation().toFile().getAbsolutePath()); + Path f = srcFile.toAbsolutePath(); + if (f.startsWith(p)) { + return p; } - return FileUtil.toPath(srcGenURI); - } - - /** - * Given a path that denotes the full path to a source file (not including the - * file itself), return the relative path from the root of the 'src' - * directory, or, if there is no 'src' directory, the relative path - * from the root of the package. - * @param srcPath The path to the source. - * @return the relative path from the root of the 'src' - * directory, or, if there is no 'src' directory, the relative path - * from the root of the package - */ - protected Path getSubPkgPath(Path srcPath) { - Path relSrcPath = srcPkgPath.relativize(srcPath); - if (relSrcPath.startsWith(DEFAULT_SRC_DIR)) { - int segments = relSrcPath.getNameCount(); - if (segments == 1) { - return Paths.get(""); - } else { - relSrcPath = relSrcPath.subpath(1, segments); - } - } - return relSrcPath; - } - - /** - * Clean any artifacts produced by the code generator and target compilers. - *

- * The base implementation deletes the bin and src-gen directories. If the - * target code generator creates additional files or directories, the - * corresponding generator should override this method. - * - * @throws IOException If an I/O error occurs. - */ - public void doClean() throws IOException { - FileUtil.deleteDirectory(binPath); - FileUtil.deleteDirectory(srcGenBasePath); - } - - private static Path getPkgPath(Resource resource) throws IOException { - if (resource.getURI().isPlatform()) { - // We are in the RCA. - Path srcFile = FileUtil.toPath(resource); - for (IProject r : ResourcesPlugin.getWorkspace().getRoot().getProjects()) { - Path p = Paths.get(r.getLocation().toFile().getAbsolutePath()); - Path f = srcFile.toAbsolutePath(); - if (f.startsWith(p)) { - return p; - } - } - } - return findPackageRoot(FileUtil.toPath(resource), s -> {}); - } - - /** - * Find the package root by looking for an 'src' - * directory. If none can be found, return the current - * working directory instead. - * - * @param input The *.lf file to find the package root - * for. - * @return The package root, or the current working - * directory if none exists. - */ - public static Path findPackageRoot(final Path input, final Consumer printWarning) { - Path p = input; - do { - p = p.getParent(); - if (p == null) { - printWarning.accept("File '" + input.getFileName() + "' is not located in an 'src' directory."); - printWarning.accept("Adopting the current working directory as the package root."); - return Paths.get(".").toAbsolutePath(); // todo #1478 replace with Io::getWd - } - } while (!p.toFile().getName().equals("src")); - return p.getParent(); - } - - /** - * Return an LFCommand instance that can be used to execute the program under compilation. - */ - public LFCommand getCommand() { - String cmd = GeneratorUtils.isHostWindows() ? - getExecutable().toString() : - srcPkgPath.relativize(getExecutable()).toString(); - return LFCommand.get(cmd, List.of(), true, srcPkgPath); - } - - /** - * Return the extension used for binaries on the platform on which compilation takes place. - */ - protected String getExecutableExtension() { - return GeneratorUtils.isHostWindows() ? ".exe" : ""; - } - - /** - * Return a path to an executable version of the program under compilation. - */ - public Path getExecutable() { - return binPath.resolve(name + getExecutableExtension()); + } } + return findPackageRoot(FileUtil.toPath(resource), s -> {}); + } + + /** + * Find the package root by looking for an 'src' directory. If none can be found, return the + * current working directory instead. + * + * @param input The *.lf file to find the package root for. + * @return The package root, or the current working directory if none exists. + */ + public static Path findPackageRoot(final Path input, final Consumer printWarning) { + Path p = input; + do { + p = p.getParent(); + if (p == null) { + printWarning.accept( + "File '" + input.getFileName() + "' is not located in an 'src' directory."); + printWarning.accept("Adopting the current working directory as the package root."); + return Paths.get(".").toAbsolutePath(); // todo #1478 replace with Io::getWd + } + } while (!p.toFile().getName().equals("src")); + return p.getParent(); + } + + /** Return an LFCommand instance that can be used to execute the program under compilation. */ + public LFCommand getCommand() { + String cmd = + GeneratorUtils.isHostWindows() + ? getExecutable().toString() + : srcPkgPath.relativize(getExecutable()).toString(); + return LFCommand.get(cmd, List.of(), true, srcPkgPath); + } + + /** Return the extension used for binaries on the platform on which compilation takes place. */ + protected String getExecutableExtension() { + return GeneratorUtils.isHostWindows() ? ".exe" : ""; + } + + /** Return a path to an executable version of the program under compilation. */ + public Path getExecutable() { + return binPath.resolve(name + getExecutableExtension()); + } } diff --git a/org.lflang/src/org/lflang/InferredType.java b/org.lflang/src/org/lflang/InferredType.java index 41931e0e32..5e9b4a8aa3 100644 --- a/org.lflang/src/org/lflang/InferredType.java +++ b/org.lflang/src/org/lflang/InferredType.java @@ -1,209 +1,184 @@ /************* -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; import java.util.function.Function; - import org.eclipse.emf.ecore.EObject; - import org.lflang.ast.ASTUtils; import org.lflang.lf.Type; /** * A helper class that represents an inferred type. * - *

This class helps to separate the rules of type inference from code generation - * for types. It is particularly useful in cases when the type is not given directly - * in LF code, but is inferred from the context. In this case it could happen that - * no ASTNode representing the type does not exist. For instance when a - * parameter type is inferred from a time value. All in all, this class provides a - * clean interface between type inference in ASTUtils and code generation. + *

This class helps to separate the rules of type inference from code generation for types. It is + * particularly useful in cases when the type is not given directly in LF code, but is inferred from + * the context. In this case it could happen that no ASTNode representing the type does not exist. + * For instance when a parameter type is inferred from a time value. All in all, this class provides + * a clean interface between type inference in ASTUtils and code generation. * - *

ASTUtils provides functionality to create an inferred type from - * Lingua Franca variables (getInferredType). This inferred type can than be - * translated to target code using the code generators or be converted to a general - * textual representation using toText(). + *

ASTUtils provides functionality to create an inferred type from Lingua Franca variables + * (getInferredType). This inferred type can than be translated to target code using the code + * generators or be converted to a general textual representation using toText(). * * @author Christian Menard */ public class InferredType { - /** - * The AST node representing the inferred type if such a node exists. - */ - public final Type astType; - /** - * A flag indicating whether the inferred type has the base type time. - */ - public final boolean isTime; - /** - * A flag indicating whether the inferred type is a list. - */ - public final boolean isList; - /** - * A flag indicating whether the inferred type is a list of variable size. - */ - public final boolean isVariableSizeList; - /** - * A flag indicating whether the inferred type is a list of fixed size. - */ - public final boolean isFixedSizeList; - /** - * The list size if the inferred type is a fixed size list. - * Otherwise, null. - */ - public final Integer listSize; - - - /** - * Private constructor - */ - private InferredType(Type astType, boolean isTime, boolean isVariableSizeList, - boolean isFixedSizeList, Integer listSize) { - this.astType = astType; - this.isTime = isTime; - this.isList = isVariableSizeList || isFixedSizeList; - this.isVariableSizeList = isVariableSizeList; - this.isFixedSizeList = isFixedSizeList; - this.listSize = listSize; + /** The AST node representing the inferred type if such a node exists. */ + public final Type astType; + /** A flag indicating whether the inferred type has the base type time. */ + public final boolean isTime; + /** A flag indicating whether the inferred type is a list. */ + public final boolean isList; + /** A flag indicating whether the inferred type is a list of variable size. */ + public final boolean isVariableSizeList; + /** A flag indicating whether the inferred type is a list of fixed size. */ + public final boolean isFixedSizeList; + /** The list size if the inferred type is a fixed size list. Otherwise, null. */ + public final Integer listSize; + + /** Private constructor */ + private InferredType( + Type astType, + boolean isTime, + boolean isVariableSizeList, + boolean isFixedSizeList, + Integer listSize) { + this.astType = astType; + this.isTime = isTime; + this.isList = isVariableSizeList || isFixedSizeList; + this.isVariableSizeList = isVariableSizeList; + this.isFixedSizeList = isFixedSizeList; + this.listSize = listSize; + } + + /** Check if the inferred type is undefined. */ + public boolean isUndefined() { + return astType == null && !isTime; + } + + /** + * Convert the inferred type to its textual representation as it would appear in LF code, with + * CodeMap tags inserted. + */ + public String toText() { + return toTextHelper(ASTUtils::toText); + } + + /** + * Convert the inferred type to its textual representation as it would appear in LF code, without + * CodeMap tags inserted. + */ + public String toOriginalText() { + return toTextHelper(ASTUtils::toOriginalText); + } + + private String toTextHelper(Function toText) { + if (astType != null) { + return toText.apply(astType); + } else if (isTime) { + if (isFixedSizeList) { + return "time[" + listSize + "]"; + } else if (isVariableSizeList) { + return "time[]"; + } else { + return "time"; + } } - - - /** - * Check if the inferred type is undefined. - */ - public boolean isUndefined() { - return astType == null && !isTime; - } - - /** - * Convert the inferred type to its textual representation as it would appear in LF code, - * with CodeMap tags inserted. - */ - public String toText() { return toTextHelper(ASTUtils::toText); } - - /** - * Convert the inferred type to its textual representation as it would appear in LF code, - * without CodeMap tags inserted. - */ - public String toOriginalText() { return toTextHelper(ASTUtils::toOriginalText); } - - private String toTextHelper(Function toText) { - if (astType != null) { - return toText.apply(astType); - } else if (isTime) { - if (isFixedSizeList) { - return "time[" + listSize + "]"; - } else if (isVariableSizeList) { - return "time[]"; - } else { - return "time"; - } - } - return ""; - } - - /** - * Convert the inferred type to its textual representation - * while ignoring any list qualifiers or type arguments. - * - * @return Textual representation of this inferred type without list qualifiers - */ - public String baseType() { - if (astType != null) { - return ASTUtils.baseType(astType); - } else if (isTime) { - return "time"; - } - return ""; + return ""; + } + + /** + * Convert the inferred type to its textual representation while ignoring any list qualifiers or + * type arguments. + * + * @return Textual representation of this inferred type without list qualifiers + */ + public String baseType() { + if (astType != null) { + return ASTUtils.baseType(astType); + } else if (isTime) { + return "time"; } - - /** - * Create an inferred type from an AST node. - * - * @param type an AST node - * @return A new inferred type representing the given AST node - */ - public static InferredType fromAST(Type type) { - if (type == null) { - return undefined(); - } - return new InferredType( - type, - type.isTime(), - type.getArraySpec() != null && type.getArraySpec().isOfVariableLength(), - type.getArraySpec() != null && !type.getArraySpec().isOfVariableLength(), - type.getArraySpec() != null ? type.getArraySpec().getLength() : null - ); - } - - /** - * Create an undefined inferred type. - */ - public static InferredType undefined() { - return new InferredType(null, false, false, false, null); - } - - /** - * Create an inferred type representing time. - */ - public static InferredType time() { - return new InferredType(null, true, false, false, null); - } - - /** - * Create an inferred type representing a list of time values. - * - *

This creates a fixed size list if size is non-null, - * otherwise a variable size list. - * - * @param size The list size, may be null - */ - public static InferredType timeList(Integer size) { - return new InferredType(null, true, size == null, size != null, size); - } - - /** - * Create an inferred type representing a variable size time list. - */ - public static InferredType timeList() { - return timeList(null); - } - - /** - * Returns the component type, if this is a first-class - * list type. Eg returns {@code time} for the type {@code time[]}. - * If this is not a first-class list type, returns null. - * - *

Todo this does not return int for int[], we need to - * make the parser production left-recursive. OTOH only - * time and time[] are first class in our framework so - * this doesn't contradict the contract of this method. - */ - public InferredType getComponentType() { - return isTime && isList ? time() : null; + return ""; + } + + /** + * Create an inferred type from an AST node. + * + * @param type an AST node + * @return A new inferred type representing the given AST node + */ + public static InferredType fromAST(Type type) { + if (type == null) { + return undefined(); } + return new InferredType( + type, + type.isTime(), + type.getArraySpec() != null && type.getArraySpec().isOfVariableLength(), + type.getArraySpec() != null && !type.getArraySpec().isOfVariableLength(), + type.getArraySpec() != null ? type.getArraySpec().getLength() : null); + } + + /** Create an undefined inferred type. */ + public static InferredType undefined() { + return new InferredType(null, false, false, false, null); + } + + /** Create an inferred type representing time. */ + public static InferredType time() { + return new InferredType(null, true, false, false, null); + } + + /** + * Create an inferred type representing a list of time values. + * + *

This creates a fixed size list if size is non-null, otherwise a variable size list. + * + * @param size The list size, may be null + */ + public static InferredType timeList(Integer size) { + return new InferredType(null, true, size == null, size != null, size); + } + + /** Create an inferred type representing a variable size time list. */ + public static InferredType timeList() { + return timeList(null); + } + + /** + * Returns the component type, if this is a first-class list type. Eg returns {@code time} for the + * type {@code time[]}. If this is not a first-class list type, returns null. + * + *

Todo this does not return int for int[], we need to make the parser production + * left-recursive. OTOH only time and time[] are first class in our framework so this doesn't + * contradict the contract of this method. + */ + public InferredType getComponentType() { + return isTime && isList ? time() : null; + } } diff --git a/org.lflang/src/org/lflang/LFResourceDescriptionStrategy.java b/org.lflang/src/org/lflang/LFResourceDescriptionStrategy.java index 548d5e0ea6..487e63374b 100644 --- a/org.lflang/src/org/lflang/LFResourceDescriptionStrategy.java +++ b/org.lflang/src/org/lflang/LFResourceDescriptionStrategy.java @@ -1,32 +1,32 @@ /************* -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; +import com.google.inject.Inject; import java.util.Map; import java.util.stream.Collectors; - import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.naming.QualifiedName; import org.eclipse.xtext.resource.EObjectDescription; @@ -34,56 +34,52 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy; import org.eclipse.xtext.scoping.impl.ImportUriResolver; import org.eclipse.xtext.util.IAcceptor; - import org.lflang.lf.Model; -import com.google.inject.Inject; - /** - * Resource description strategy designed to limit global scope to only those - * files that were explicitly imported. - *

- * Adapted from example provided by Itemis. + * Resource description strategy designed to limit global scope to only those files that were + * explicitly imported. + * + *

Adapted from example provided by Itemis. * * @author Marten Lohstroh * @see "https://blogs.itemis.com/en/in-five-minutes-to-transitive-imports-within-a-dsl-with-xtext" */ public class LFResourceDescriptionStrategy extends DefaultResourceDescriptionStrategy { - /** - * Key used in user data attached to description of a Model. - */ - public static final String INCLUDES = "includes"; + /** Key used in user data attached to description of a Model. */ + public static final String INCLUDES = "includes"; - /** - * Delimiter used in the values associated with INCLUDES keys in the - * user-data descriptions of Models. - */ - public static final String DELIMITER = ","; + /** + * Delimiter used in the values associated with INCLUDES keys in the user-data descriptions of + * Models. + */ + public static final String DELIMITER = ","; - @Inject - private ImportUriResolver uriResolver; + @Inject private ImportUriResolver uriResolver; - @Override - public boolean createEObjectDescriptions(EObject eObject, IAcceptor acceptor) { - if (eObject instanceof Model) { - this.createEObjectDescriptionForModel((Model) eObject, acceptor); - return true; - } else { - return super.createEObjectDescriptions(eObject, acceptor); - } + @Override + public boolean createEObjectDescriptions( + EObject eObject, IAcceptor acceptor) { + if (eObject instanceof Model) { + this.createEObjectDescriptionForModel((Model) eObject, acceptor); + return true; + } else { + return super.createEObjectDescriptions(eObject, acceptor); } + } - /** - * Build an index containing the strings of the URIs imported resources. - *

- * All the URIs are added to comma-separated string and stored under the - * key "includes" in the userData map of the object description. - **/ - private void createEObjectDescriptionForModel(Model model, IAcceptor acceptor) { - var uris = model.getImports().stream().map(uriResolver).collect(Collectors.joining(DELIMITER)); - var userData = Map.of(INCLUDES, uris); - QualifiedName qname = QualifiedName.create(model.eResource().getURI().toString()); - acceptor.accept(EObjectDescription.create(qname, model, userData)); - } + /** + * Build an index containing the strings of the URIs imported resources. + * + *

All the URIs are added to comma-separated string and stored under the key "includes" in the + * userData map of the object description. + */ + private void createEObjectDescriptionForModel( + Model model, IAcceptor acceptor) { + var uris = model.getImports().stream().map(uriResolver).collect(Collectors.joining(DELIMITER)); + var userData = Map.of(INCLUDES, uris); + QualifiedName qname = QualifiedName.create(model.eResource().getURI().toString()); + acceptor.accept(EObjectDescription.create(qname, model, userData)); + } } diff --git a/org.lflang/src/org/lflang/LFResourceProvider.java b/org.lflang/src/org/lflang/LFResourceProvider.java index 51db96a6d5..03c7b9ea47 100644 --- a/org.lflang/src/org/lflang/LFResourceProvider.java +++ b/org.lflang/src/org/lflang/LFResourceProvider.java @@ -1,28 +1,24 @@ package org.lflang; -import org.eclipse.emf.ecore.resource.ResourceSet; - import com.google.inject.Inject; import com.google.inject.Provider; +import org.eclipse.emf.ecore.resource.ResourceSet; /** * Class that provides access to a resource set. - * - * @author Marten Lohstroh * + * @author Marten Lohstroh */ public class LFResourceProvider { - /** - * Injected resource set provider. - */ - @Inject - private Provider resourceSetProvider; - - /** - * Return a resource set obtained from the injected provider. - * @return - */ - public ResourceSet getResourceSet() { - return this.resourceSetProvider.get(); - } + /** Injected resource set provider. */ + @Inject private Provider resourceSetProvider; + + /** + * Return a resource set obtained from the injected provider. + * + * @return + */ + public ResourceSet getResourceSet() { + return this.resourceSetProvider.get(); + } } diff --git a/org.lflang/src/org/lflang/LFRuntimeModule.java b/org.lflang/src/org/lflang/LFRuntimeModule.java index a7c1e80a19..a01986dfb3 100644 --- a/org.lflang/src/org/lflang/LFRuntimeModule.java +++ b/org.lflang/src/org/lflang/LFRuntimeModule.java @@ -8,50 +8,50 @@ import org.eclipse.xtext.resource.IDefaultResourceDescriptionStrategy; import org.eclipse.xtext.scoping.IGlobalScopeProvider; import org.eclipse.xtext.validation.INamesAreUniqueValidationHelper; - import org.lflang.formatting2.LFFormatter; import org.lflang.scoping.LFGlobalScopeProvider; import org.lflang.validation.LFNamesAreUniqueValidationHelper; /** - * Binds services that are available both when running LFC - * standalone, and when running within the IDE. + * Binds services that are available both when running LFC standalone, and when running within the + * IDE. + * *

    - *
  • LfIdeModule overrides this module with additional - * bindings when running in the IDE. - *
  • {@code org.lflang.lfc.LFStandaloneModule} overrides this module when - * running LFC standalone. + *
  • LfIdeModule overrides this module with additional bindings when running in the IDE. + *
  • {@code org.lflang.lfc.LFStandaloneModule} overrides this module when running LFC + * standalone. *
*/ public class LFRuntimeModule extends AbstractLFRuntimeModule { - /** Establish a binding to our custom resource description strategy. */ - public Class bindIDefaultResourceDescriptionStrategy() { - return LFResourceDescriptionStrategy.class; - } - - /** Establish a binding to our custom global scope provider. */ - @Override - public Class bindIGlobalScopeProvider() { - return LFGlobalScopeProvider.class; - } - - /** Establish a binding to a helper that checks that names are unique. */ - public Class bindNamesAreUniqueValidationHelper() { - return LFNamesAreUniqueValidationHelper.class; - } - - public Class bindISyntaxErrorMessageProvider() { - return LFSyntaxErrorMessageProvider.class; - } - - /** The error reporter. {@code org.lflang.lfc.LFStandaloneModule} overrides this binding. */ - public Class bindErrorReporter() { - return DefaultErrorReporter.class; - } - - @Override - public Class bindIFormatter2() { - return LFFormatter.class; - } + /** Establish a binding to our custom resource description strategy. */ + public Class + bindIDefaultResourceDescriptionStrategy() { + return LFResourceDescriptionStrategy.class; + } + + /** Establish a binding to our custom global scope provider. */ + @Override + public Class bindIGlobalScopeProvider() { + return LFGlobalScopeProvider.class; + } + + /** Establish a binding to a helper that checks that names are unique. */ + public Class bindNamesAreUniqueValidationHelper() { + return LFNamesAreUniqueValidationHelper.class; + } + + public Class bindISyntaxErrorMessageProvider() { + return LFSyntaxErrorMessageProvider.class; + } + + /** The error reporter. {@code org.lflang.lfc.LFStandaloneModule} overrides this binding. */ + public Class bindErrorReporter() { + return DefaultErrorReporter.class; + } + + @Override + public Class bindIFormatter2() { + return LFFormatter.class; + } } diff --git a/org.lflang/src/org/lflang/LFStandaloneSetup.java b/org.lflang/src/org/lflang/LFStandaloneSetup.java index c1b5e85ecf..13264adabe 100644 --- a/org.lflang/src/org/lflang/LFStandaloneSetup.java +++ b/org.lflang/src/org/lflang/LFStandaloneSetup.java @@ -4,33 +4,30 @@ package org.lflang; - -import org.eclipse.xtext.util.Modules2; - import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Module; +import org.eclipse.xtext.util.Modules2; /** - * Initialization support for running Xtext languages without - * Equinox extension registry. + * Initialization support for running Xtext languages without Equinox extension registry. * - * See {@link LFRuntimeModule}, the base Guice module for LF services. + *

See {@link LFRuntimeModule}, the base Guice module for LF services. */ public class LFStandaloneSetup extends LFStandaloneSetupGenerated { - private final Module module; + private final Module module; - public LFStandaloneSetup() { - this.module = new LFRuntimeModule(); - } + public LFStandaloneSetup() { + this.module = new LFRuntimeModule(); + } - public LFStandaloneSetup(Module... modules) { - this.module = Modules2.mixin(modules); - } + public LFStandaloneSetup(Module... modules) { + this.module = Modules2.mixin(modules); + } - @Override - public Injector createInjector() { - return Guice.createInjector(this.module); - } + @Override + public Injector createInjector() { + return Guice.createInjector(this.module); + } } diff --git a/org.lflang/src/org/lflang/LFSyntaxErrorMessageProvider.java b/org.lflang/src/org/lflang/LFSyntaxErrorMessageProvider.java index ac5511980a..d1ddb312e1 100644 --- a/org.lflang/src/org/lflang/LFSyntaxErrorMessageProvider.java +++ b/org.lflang/src/org/lflang/LFSyntaxErrorMessageProvider.java @@ -1,8 +1,8 @@ package org.lflang; +import com.google.inject.Inject; import java.util.Set; import java.util.stream.Collectors; - import org.antlr.runtime.RecognitionException; import org.antlr.runtime.Token; import org.eclipse.xtext.GrammarUtil; @@ -10,78 +10,70 @@ import org.eclipse.xtext.nodemodel.SyntaxErrorMessage; import org.eclipse.xtext.parser.antlr.SyntaxErrorMessageProvider; -import com.google.inject.Inject; - /** * Custom error message provider that intercepts syntax errors. - * + * * @author Marten Lohstroh */ public class LFSyntaxErrorMessageProvider extends SyntaxErrorMessageProvider { - - /** - * Issue code for syntax error due to misused keyword. - */ - public static String USED_RESERVED_KEYWORD = "USED_RESERVED_KEYWORD"; - - /** - * Issue code for syntax error due to misused single quotes. - */ - public static String SINGLY_QUOTED_STRING = "SINGLE_QUOTED_STRING"; - - /** - * Helper that provides access to the grammar. - */ - @Inject IGrammarAccess grammarAccess; - - /** - * Set of keywords that otherwise would be valid identifiers. - * For example, 'reaction' is part of this set, but '{=' is not. - */ - public Set keywords; - - /** - * Customize intercepted error messages. - * - * @Override - */ - public SyntaxErrorMessage getSyntaxErrorMessage(IParserErrorContext context) { - - if (context != null) { - String message = context.getDefaultMessage(); - - // Describe situation where an unexpected token was encountered where a closing single quote was expected. - if (message.contains("expecting '''")) { - return new SyntaxErrorMessage("Single quotes can only be used around single characters, not strings. " - + "Please use double quotes instead.", SINGLY_QUOTED_STRING); + + /** Issue code for syntax error due to misused keyword. */ + public static String USED_RESERVED_KEYWORD = "USED_RESERVED_KEYWORD"; + + /** Issue code for syntax error due to misused single quotes. */ + public static String SINGLY_QUOTED_STRING = "SINGLE_QUOTED_STRING"; + + /** Helper that provides access to the grammar. */ + @Inject IGrammarAccess grammarAccess; + + /** + * Set of keywords that otherwise would be valid identifiers. For example, 'reaction' is part of + * this set, but '{=' is not. + */ + public Set keywords; + + /** Customize intercepted error messages. @Override */ + public SyntaxErrorMessage getSyntaxErrorMessage(IParserErrorContext context) { + + if (context != null) { + String message = context.getDefaultMessage(); + + // Describe situation where an unexpected token was encountered where a closing single quote + // was expected. + if (message.contains("expecting '''")) { + return new SyntaxErrorMessage( + "Single quotes can only be used around single characters, not strings. " + + "Please use double quotes instead.", + SINGLY_QUOTED_STRING); + } + + RecognitionException e = context.getRecognitionException(); + if (e != null) { + Token token = e.token; + if (token != null) { + String text = token.getText(); + if (text != null) { + // Update keywords if not yet set. + if (keywords == null) { + // ('a'..'z'|'A'..'Z'|'_')('a'..'z'|'A'..'Z'|'_'|'0'..'9')* + this.keywords = + GrammarUtil.getAllKeywords(grammarAccess.getGrammar()).stream() + .filter(k -> k.matches("^([a-z]|[A-Z]|_)+(\\w|_)*$")) + .collect(Collectors.toSet()); } - - RecognitionException e = context.getRecognitionException(); - if (e != null) { - Token token = e.token; - if (token != null) { - String text = token.getText(); - if (text != null) { - // Update keywords if not yet set. - if (keywords == null) { - // ('a'..'z'|'A'..'Z'|'_')('a'..'z'|'A'..'Z'|'_'|'0'..'9')* - this.keywords = GrammarUtil - .getAllKeywords(grammarAccess.getGrammar()) - .stream() - .filter(k -> k - .matches("^([a-z]|[A-Z]|_)+(\\w|_)*$")) - .collect(Collectors.toSet()); - - } - // Describe situation where a keyword is used as an identifier. - if (keywords.contains(text)) { - return new SyntaxErrorMessage("'" + text + "' is a reserved keyword " - + "which cannot be used as an identifier.", USED_RESERVED_KEYWORD); - } - } - } + // Describe situation where a keyword is used as an identifier. + if (keywords.contains(text)) { + return new SyntaxErrorMessage( + "'" + + text + + "' is a reserved keyword " + + "which cannot be used as an identifier.", + USED_RESERVED_KEYWORD); } + } } - return super.getSyntaxErrorMessage(context); + } } + return super.getSyntaxErrorMessage(context); + } } diff --git a/org.lflang/src/org/lflang/LocalStrings.java b/org.lflang/src/org/lflang/LocalStrings.java index 79f338b39d..314839c3f1 100644 --- a/org.lflang/src/org/lflang/LocalStrings.java +++ b/org.lflang/src/org/lflang/LocalStrings.java @@ -2,10 +2,8 @@ import java.util.ResourceBundle; -/** - * Static class for managing strings. - */ +/** Static class for managing strings. */ public class LocalStrings { - public static final ResourceBundle res = ResourceBundle.getBundle("org.lflang.StringsBundle"); - public static final String VERSION = res.getString("VERSION"); + public static final ResourceBundle res = ResourceBundle.getBundle("org.lflang.StringsBundle"); + public static final String VERSION = res.getString("VERSION"); } diff --git a/org.lflang/src/org/lflang/MainConflictChecker.java b/org.lflang/src/org/lflang/MainConflictChecker.java index 6ee3ebf569..1805cda5d7 100644 --- a/org.lflang/src/org/lflang/MainConflictChecker.java +++ b/org.lflang/src/org/lflang/MainConflictChecker.java @@ -2,6 +2,7 @@ import static java.nio.file.FileVisitResult.CONTINUE; +import com.google.common.collect.Iterables; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.Files; @@ -11,7 +12,6 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; - import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; @@ -20,92 +20,79 @@ import org.lflang.lf.Reactor; import org.lflang.util.FileUtil; -import com.google.common.collect.Iterables; - /** - * Class that (upon instantiation) determines whether there are any conflicting main reactors in the current package. - * Conflicts are reported in the instance's conflicts list. - * - * @author Marten Lohstroh + * Class that (upon instantiation) determines whether there are any conflicting main reactors in the + * current package. Conflicts are reported in the instance's conflicts list. * + * @author Marten Lohstroh */ public class MainConflictChecker { - /** - * List of conflict encountered while traversing the package. - */ - public final List conflicts = new LinkedList(); - - /** - * The current file configuration. - */ - protected FileConfig fileConfig; - - /** - * Resource set used to obtain resources from. - */ - protected static final ResourceSet rs = new LFStandaloneSetup() - .createInjectorAndDoEMFRegistration() - .getInstance(LFResourceProvider.class).getResourceSet(); - - /** - * Create a new instance that walks the file tree of the package to find conflicts. - * - * @param fileConfig The current file configuration. - */ - public MainConflictChecker(FileConfig fileConfig) { - // FIXME: See if there is a faster way to do this check that does not involve parsing all - // files in the current package (which happens when we get the resources) - this.fileConfig = fileConfig; - try { - Files.walkFileTree(fileConfig.srcPkgPath, new PackageVisitor()); - } catch (IOException e) { - System.err.println("Error while checking for name conflicts in package."); - e.printStackTrace(); - } + /** List of conflict encountered while traversing the package. */ + public final List conflicts = new LinkedList(); + + /** The current file configuration. */ + protected FileConfig fileConfig; + + /** Resource set used to obtain resources from. */ + protected static final ResourceSet rs = + new LFStandaloneSetup() + .createInjectorAndDoEMFRegistration() + .getInstance(LFResourceProvider.class) + .getResourceSet(); + + /** + * Create a new instance that walks the file tree of the package to find conflicts. + * + * @param fileConfig The current file configuration. + */ + public MainConflictChecker(FileConfig fileConfig) { + // FIXME: See if there is a faster way to do this check that does not involve parsing all + // files in the current package (which happens when we get the resources) + this.fileConfig = fileConfig; + try { + Files.walkFileTree(fileConfig.srcPkgPath, new PackageVisitor()); + } catch (IOException e) { + System.err.println("Error while checking for name conflicts in package."); + e.printStackTrace(); } - - /** - * Extension of a SimpleFileVisitor that adds entries to the conflicts list in the outer class. - * - * Specifically, each visited file is checked against the name present in the current file configuration. - * If the name matches the current file (but is not the file itself), then parse that file and determine whether - * there is a main reactor declared. If there is one, report a conflict. - * - * @author Marten Lohstroh - * - */ - public class PackageVisitor extends SimpleFileVisitor { + } + + /** + * Extension of a SimpleFileVisitor that adds entries to the conflicts list in the outer class. + * + *

Specifically, each visited file is checked against the name present in the current file + * configuration. If the name matches the current file (but is not the file itself), then parse + * that file and determine whether there is a main reactor declared. If there is one, report a + * conflict. + * + * @author Marten Lohstroh + */ + public class PackageVisitor extends SimpleFileVisitor { - /** - * Report a conflicting main reactors in visited files. - */ - @Override - public FileVisitResult visitFile(Path path, BasicFileAttributes attr) { - path = path.normalize(); - if (attr.isRegularFile() && path.toString().endsWith(".lf")) { - // Parse the file. - Resource r = rs.getResource( - URI.createFileURI(path.toFile().getAbsolutePath()), - true); - if (r.getErrors().isEmpty()) { - // No errors. Find the main reactor. - Iterator reactors = Iterables - .filter(IteratorExtensions - .toIterable(r.getAllContents()), - Reactor.class) - .iterator(); - // If this is not the same file, but it has a main reactor - // and the name matches, then report the conflict. - if (!fileConfig.srcFile.equals(path) - && IteratorExtensions.exists(reactors, it -> it.isMain() || it.isFederated()) - && fileConfig.name.equals(FileUtil.nameWithoutExtension(path))) { - conflicts.add( - fileConfig.srcPath.relativize(path).toString()); - } - } - } - return CONTINUE; + /** Report a conflicting main reactors in visited files. */ + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attr) { + path = path.normalize(); + if (attr.isRegularFile() && path.toString().endsWith(".lf")) { + // Parse the file. + Resource r = rs.getResource(URI.createFileURI(path.toFile().getAbsolutePath()), true); + if (r.getErrors().isEmpty()) { + // No errors. Find the main reactor. + Iterator reactors = + Iterables.filter( + IteratorExtensions.toIterable(r.getAllContents()), Reactor.class) + .iterator(); + // If this is not the same file, but it has a main reactor + // and the name matches, then report the conflict. + if (!fileConfig.srcFile.equals(path) + && IteratorExtensions.exists(reactors, it -> it.isMain() || it.isFederated()) + && fileConfig.name.equals(FileUtil.nameWithoutExtension(path))) { + conflicts.add(fileConfig.srcPath.relativize(path).toString()); + } } + } + return CONTINUE; } + } } diff --git a/org.lflang/src/org/lflang/ModelInfo.java b/org.lflang/src/org/lflang/ModelInfo.java index 24d5c447c2..17ee7cfb76 100644 --- a/org.lflang/src/org/lflang/ModelInfo.java +++ b/org.lflang/src/org/lflang/ModelInfo.java @@ -1,27 +1,27 @@ /************* -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; @@ -35,7 +35,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Set; - import org.lflang.ast.ASTUtils; import org.lflang.generator.NamedInstance; import org.lflang.generator.ReactorInstance; @@ -51,214 +50,209 @@ import org.lflang.lf.STP; import org.lflang.util.FileUtil; - /** * A helper class for analyzing the AST. This class is instantiated once for each compilation. - *

- * NOTE: the validator used on imported files uses the same instance! Hence, this class should not contain any info - * specific to any particular resource that is involved in the compilation. + * + *

NOTE: the validator used on imported files uses the same instance! Hence, this class should + * not contain any info specific to any particular resource that is involved in the compilation. * * @author Marten Lohstroh */ public class ModelInfo { - /** - * Data structure for tracking dependencies between reactor classes. An - * instantiation of class A inside of class B implies that B depends on A. - */ - public InstantiationGraph instantiationGraph; - - /** - * The AST that the info gathered in this class pertains to. - */ - public Model model; - - /** - * The set of assignments that assign a too-large constant to a parameter - * that is used to specify a deadline. These assignments are to be reported - * during validation. - */ - public Set overflowingAssignments; - - /** - * The set of deadlines that use a too-large constant to specify their time - * interval. - */ - public Set overflowingDeadlines; - - /** - * The set of STP offsets that use a too-large constant to specify their time - * interval. - */ - public Set overflowingSTP; - - /** - * The set of parameters used to specify a deadline while having been - * assigned a default value the is too large for this purpose. These - * parameters are to be reported during validation. - */ - public Set overflowingParameters; - - /** Cycles found during topology analysis. */ - private Set> topologyCycles = new LinkedHashSet>(); - - /** - * Whether or not the model information has been updated at least once. - */ - public boolean updated; - - /** - * Redo all analysis based on the given model. - * - * @param model the model to analyze. - */ - public void update(Model model, ErrorReporter reporter) { - this.updated = true; - this.model = model; - this.instantiationGraph = new InstantiationGraph(model, true); - - if (this.instantiationGraph.getCycles().size() == 0) { - List topLevelReactorInstances = new LinkedList<>(); - var main = model.getReactors().stream().filter(it -> it.isMain() || it.isFederated()).findFirst(); - if (main.isPresent()) { - var inst = new ReactorInstance(main.get(), reporter); - topLevelReactorInstances.add(inst); - } else { - model.getReactors().forEach( - it -> topLevelReactorInstances.add(new ReactorInstance(it, reporter)) - ); - } - // don't store the graph into a field, only the cycles. - for (ReactorInstance top : topLevelReactorInstances) { - this.topologyCycles.addAll(top.getCycles()); - } - } - - // may be null if the target is invalid - var target = Target.forName(model.getTarget().getName()).orElse(null); + /** + * Data structure for tracking dependencies between reactor classes. An instantiation of class A + * inside of class B implies that B depends on A. + */ + public InstantiationGraph instantiationGraph; + + /** The AST that the info gathered in this class pertains to. */ + public Model model; + + /** + * The set of assignments that assign a too-large constant to a parameter that is used to specify + * a deadline. These assignments are to be reported during validation. + */ + public Set overflowingAssignments; + + /** The set of deadlines that use a too-large constant to specify their time interval. */ + public Set overflowingDeadlines; + + /** The set of STP offsets that use a too-large constant to specify their time interval. */ + public Set overflowingSTP; + + /** + * The set of parameters used to specify a deadline while having been assigned a default value the + * is too large for this purpose. These parameters are to be reported during validation. + */ + public Set overflowingParameters; + + /** Cycles found during topology analysis. */ + private Set> topologyCycles = new LinkedHashSet>(); + + /** Whether or not the model information has been updated at least once. */ + public boolean updated; + + /** + * Redo all analysis based on the given model. + * + * @param model the model to analyze. + */ + public void update(Model model, ErrorReporter reporter) { + this.updated = true; + this.model = model; + this.instantiationGraph = new InstantiationGraph(model, true); + + if (this.instantiationGraph.getCycles().size() == 0) { + List topLevelReactorInstances = new LinkedList<>(); + var main = + model.getReactors().stream().filter(it -> it.isMain() || it.isFederated()).findFirst(); + if (main.isPresent()) { + var inst = new ReactorInstance(main.get(), reporter); + topLevelReactorInstances.add(inst); + } else { + model + .getReactors() + .forEach(it -> topLevelReactorInstances.add(new ReactorInstance(it, reporter))); + } + // don't store the graph into a field, only the cycles. + for (ReactorInstance top : topLevelReactorInstances) { + this.topologyCycles.addAll(top.getCycles()); + } + } - // Perform C-specific traversals. - if (target == Target.C) { - this.collectOverflowingNodes(); - } + // may be null if the target is invalid + var target = Target.forName(model.getTarget().getName()).orElse(null); - checkCaseInsensitiveNameCollisions(model, reporter); + // Perform C-specific traversals. + if (target == Target.C) { + this.collectOverflowingNodes(); } - public void checkCaseInsensitiveNameCollisions(Model model, ErrorReporter reporter) { - var reactorNames = new HashSet<>(); - var bad = new ArrayList<>(); - for (var reactor : model.getReactors()) { - var lowerName = getName(reactor).toLowerCase(); - if (reactorNames.contains(lowerName)) bad.add(lowerName); - reactorNames.add(lowerName); - } - for (var badName : bad) { - model.getReactors().stream() - .filter(it -> getName(it).toLowerCase().equals(badName)) - .forEach(it -> reporter.reportError(it, "Multiple reactors have the same name up to case differences.")); - } - } + checkCaseInsensitiveNameCollisions(model, reporter); + } - private String getName(Reactor r) { - return r.getName() != null ? r.getName() : FileUtil.nameWithoutExtension(Path.of(model.eResource().getURI().toFileString())); + public void checkCaseInsensitiveNameCollisions(Model model, ErrorReporter reporter) { + var reactorNames = new HashSet<>(); + var bad = new ArrayList<>(); + for (var reactor : model.getReactors()) { + var lowerName = getName(reactor).toLowerCase(); + if (reactorNames.contains(lowerName)) bad.add(lowerName); + reactorNames.add(lowerName); } - - public Set> topologyCycles() { - return this.topologyCycles; + for (var badName : bad) { + model.getReactors().stream() + .filter(it -> getName(it).toLowerCase().equals(badName)) + .forEach( + it -> + reporter.reportError( + it, "Multiple reactors have the same name up to case differences.")); } - - /** - * Collect all assignments, deadlines, and parameters that can cause the - * time interval of a deadline to overflow. In the C target, only 48 bits - * are allotted for deadline intervals, which are specified in nanosecond - * precision. - */ - private void collectOverflowingNodes() { - - this.overflowingAssignments = new HashSet<>(); - this.overflowingDeadlines = new HashSet<>(); - this.overflowingParameters = new HashSet<>(); - this.overflowingSTP = new HashSet<>(); - - // Visit all deadlines in the model; detect possible overflow. - for (var deadline : filter(toIterable(model.eAllContents()), Deadline.class)) { - // If the time value overflows, mark this deadline as overflowing. - if (isTooLarge(ASTUtils.getLiteralTimeValue(deadline.getDelay()))) { - this.overflowingDeadlines.add(deadline); - } - - // If any of the upstream parameters overflow, report this deadline. - final var delay = deadline.getDelay(); - if (delay instanceof ParameterReference - && detectOverflow(new HashSet<>(), ((ParameterReference) deadline.getDelay()).getParameter())) { - this.overflowingDeadlines.add(deadline); - } - } - // Visit all STP offsets in the model; detect possible overflow. - for (var stp : filter(toIterable(model.eAllContents()), STP.class)) { - // If the time value overflows, mark this deadline as overflowing. - if (isTooLarge(ASTUtils.getLiteralTimeValue(stp.getValue()))) { - this.overflowingSTP.add(stp); - } - } + } + + private String getName(Reactor r) { + return r.getName() != null + ? r.getName() + : FileUtil.nameWithoutExtension(Path.of(model.eResource().getURI().toFileString())); + } + + public Set> topologyCycles() { + return this.topologyCycles; + } + + /** + * Collect all assignments, deadlines, and parameters that can cause the time interval of a + * deadline to overflow. In the C target, only 48 bits are allotted for deadline intervals, which + * are specified in nanosecond precision. + */ + private void collectOverflowingNodes() { + + this.overflowingAssignments = new HashSet<>(); + this.overflowingDeadlines = new HashSet<>(); + this.overflowingParameters = new HashSet<>(); + this.overflowingSTP = new HashSet<>(); + + // Visit all deadlines in the model; detect possible overflow. + for (var deadline : filter(toIterable(model.eAllContents()), Deadline.class)) { + // If the time value overflows, mark this deadline as overflowing. + if (isTooLarge(ASTUtils.getLiteralTimeValue(deadline.getDelay()))) { + this.overflowingDeadlines.add(deadline); + } + + // If any of the upstream parameters overflow, report this deadline. + final var delay = deadline.getDelay(); + if (delay instanceof ParameterReference + && detectOverflow( + new HashSet<>(), ((ParameterReference) deadline.getDelay()).getParameter())) { + this.overflowingDeadlines.add(deadline); + } } - - /** - * In the C target, only 48 bits are allotted for deadline intervals, which - * are specified in nanosecond precision. Check whether the given time value - * exceeds the maximum specified value. - * - * @return true if the time value is greater than the specified maximum, - * false otherwise. - */ - private boolean isTooLarge(TimeValue time) { - return time != null && time.toNanoSeconds() > TimeValue.MAX_LONG_DEADLINE; + // Visit all STP offsets in the model; detect possible overflow. + for (var stp : filter(toIterable(model.eAllContents()), STP.class)) { + // If the time value overflows, mark this deadline as overflowing. + if (isTooLarge(ASTUtils.getLiteralTimeValue(stp.getValue()))) { + this.overflowingSTP.add(stp); + } + } + } + + /** + * In the C target, only 48 bits are allotted for deadline intervals, which are specified in + * nanosecond precision. Check whether the given time value exceeds the maximum specified value. + * + * @return true if the time value is greater than the specified maximum, false otherwise. + */ + private boolean isTooLarge(TimeValue time) { + return time != null && time.toNanoSeconds() > TimeValue.MAX_LONG_DEADLINE; + } + + /** + * Given a parameter that is used in a deadline specification, recursively track down its + * definition and check whether it is overflowing. Also detect and report overrides that are + * overflowing. + * + * @return true if there exists a parameter corresponding to a value that does not fit in the + * available bits. + */ + private boolean detectOverflow(Set visited, Parameter current) { + var overflow = false; + + // Determine whether the parameter's default value overflows or not. + if (isTooLarge(ASTUtils.getDefaultAsTimeValue(current))) { + this.overflowingParameters.add(current); + overflow = true; } - /** - * Given a parameter that is used in a deadline specification, recursively - * track down its definition and check whether it is overflowing. Also - * detect and report overrides that are overflowing. - * @return true if there exists a parameter corresponding to a value that - * does not fit in the available bits. - */ - private boolean detectOverflow(Set visited, Parameter current) { - var overflow = false; - - // Determine whether the parameter's default value overflows or not. - if (isTooLarge(ASTUtils.getDefaultAsTimeValue(current))) { - this.overflowingParameters.add(current); - overflow = true; - } - - // Iterate over the instantiations of the reactor in which the - // current parameter was found. - Set instantiations = this.instantiationGraph.getInstantiations((Reactor) current.eContainer()); - for (var instantiation : instantiations) { - // Only visit each instantiation once per deadline to avoid cycles. - if (!visited.contains(instantiation)) { - visited.add(instantiation); - // Find assignments that override the current parameter. - for (var assignment : instantiation.getParameters()) { - if (assignment.getLhs().equals(current)) { - if (assignment.getRhs().getExprs().isEmpty()) continue; // This error should be caught elsewhere. - Expression expr = ASTUtils.asSingleExpr(assignment.getRhs()); - if (expr instanceof ParameterReference) { - // Check for overflow in the referenced parameter. - overflow = detectOverflow(visited, ((ParameterReference)expr).getParameter()) || overflow; - } else { - // The right-hand side of the assignment is a - // constant; check whether it is too large. - if (isTooLarge(ASTUtils.getLiteralTimeValue(expr))) { - this.overflowingAssignments.add(assignment); - overflow = true; - } - } - } - } + // Iterate over the instantiations of the reactor in which the + // current parameter was found. + Set instantiations = + this.instantiationGraph.getInstantiations((Reactor) current.eContainer()); + for (var instantiation : instantiations) { + // Only visit each instantiation once per deadline to avoid cycles. + if (!visited.contains(instantiation)) { + visited.add(instantiation); + // Find assignments that override the current parameter. + for (var assignment : instantiation.getParameters()) { + if (assignment.getLhs().equals(current)) { + if (assignment.getRhs().getExprs().isEmpty()) + continue; // This error should be caught elsewhere. + Expression expr = ASTUtils.asSingleExpr(assignment.getRhs()); + if (expr instanceof ParameterReference) { + // Check for overflow in the referenced parameter. + overflow = + detectOverflow(visited, ((ParameterReference) expr).getParameter()) || overflow; + } else { + // The right-hand side of the assignment is a + // constant; check whether it is too large. + if (isTooLarge(ASTUtils.getLiteralTimeValue(expr))) { + this.overflowingAssignments.add(assignment); + overflow = true; + } } + } } - return overflow; + } } + return overflow; + } } diff --git a/org.lflang/src/org/lflang/Target.java b/org.lflang/src/org/lflang/Target.java index 2a0e0e8700..8f08e89fcc 100644 --- a/org.lflang/src/org/lflang/Target.java +++ b/org.lflang/src/org/lflang/Target.java @@ -1,22 +1,19 @@ /* Static information about targets. */ /** - * 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; @@ -27,574 +24,552 @@ import java.util.List; import java.util.Optional; import java.util.Set; - import org.lflang.lf.TargetDecl; -/** - * Enumeration of targets and their associated properties. These classes are - * written in Java, not Xtend, because the enum implementation in Xtend more - * primitive. It is safer to use enums rather than string values since it allows - * faulty references to be caught at compile time. Switch statements that take - * as input an enum but do not have cases for all members of the enum are also +/** + * Enumeration of targets and their associated properties. These classes are written in Java, not + * Xtend, because the enum implementation in Xtend more primitive. It is safer to use enums rather + * than string values since it allows faulty references to be caught at compile time. Switch + * statements that take as input an enum but do not have cases for all members of the enum are also * reported by Xtend with a warning message. - * + * * @author Marten Lohstroh */ public enum Target { - C("C", true, Arrays.asList( - // List via: https://en.cppreference.com/w/c/keyword - "auto", - "break", - "case", - "char", - "const", - "continue", - "default", - "do", - "double", - "else", - "enum", - "extern", - "float", - "for", - "goto", - "if", - "inline", // (since C99) - "int", - "long", - "register", - "restrict", // (since C99) - "return", - "short", - "signed", - "sizeof", - "static", - "struct", - "switch", - "typedef", - "union", - "unsigned", - "void", - "volatile", - "while", - "_Alignas", // (since C11) - "_Alignof", // (since C11) - "_Atomic", // (since C11) - "_Bool", // (since C99) - "_Complex", // (since C99) - "_Generic", // (since C11) - "_Imaginary", // (since C99) - "_Noreturn", // (since C11) - "_Static_assert", // (since C11) - "_Thread_local" // (since C11) - ) - ), - CCPP("CCpp", true, Target.C.keywords), - CPP("Cpp", true, "cpp", "Cpp", Arrays.asList( - // List via: https://en.cppreference.com/w/cpp/keyword - "alignas", // (since C++11) - "alignof", // (since C++11) - "and", - "and_eq", - "asm", - "atomic_cancel", // (TM TS) - "atomic_commit", // (TM TS) - "atomic_noexcept", // (TM TS) - "auto(1)", - "bitand", - "bitor", - "bool", - "break", - "case", - "catch", - "char", - "char8_t", // (since C++20) - "char16_t", // (since C++11) - "char32_t", // (since C++11) - "class(1)", - "compl", - "concept", // (since C++20) - "const", - "consteval", // (since C++20) - "constexpr", // (since C++11) - "constinit", // (since C++20) - "const_cast", - "continue", - "co_await", // (since C++20) - "co_return", // (since C++20) - "co_yield", // (since C++20) - "decltype", // (since C++11) - "default(1)", - "delete(1)", - "do", - "double", - "dynamic_cast", - "else", - "enum", - "explicit", - "export(1)(3)", - "extern(1)", - "false", - "float", - "for", - "friend", - "goto", - "if", - "inline(1)", - "int", - "long", - "mutable(1)", - "namespace", - "new", - "noexcept", // (since C++11) - "not", - "not_eq", - "nullptr", // (since C++11) - "operator", - "or", - "or_eq", - "private", - "protected", - "public", - "reflexpr", // (reflection TS) - "register(2)", - "reinterpret_cast", - "requires", // (since C++20) - "return", - "short", - "signed", - "sizeof(1)", - "static", - "static_assert", // (since C++11) - "static_cast", - "struct(1)", - "switch", - "synchronized", // (TM TS) - "template", - "this", - "thread_local", // (since C++11) - "throw", - "true", - "try", - "typedef", - "typeid", - "typename", - "union", - "unsigned", - "using(1)", - "virtual", - "void", - "volatile", - "wchar_t", - "while", - "xor", - "xor_eq" - ) - ), - TS("TypeScript", false, "ts", "TS", Arrays.asList( - // List via: https://github.com/Microsoft/TypeScript/issues/2536 - // Reserved words - "break", - "case", - "catch", - "class", - "const", - "continue", - "debugger", - "default", - "delete", - "do", - "else", - "enum", - "export", - "extends", - "false", - "finally", - "for", - "function", - "if", - "import", - "in", - "instanceof", - "new", - "null", - "return", - "super", - "switch", - "this", - "throw", - "true", - "try", - "typeof", - "var", - "void", - "while", - "with", - - // Strict Mode Reserved Words - "as", - "implements", - "interface", - "let", - "package", - "private", - "protected", - "public", - "static", - "yield", - - // Contextual Keywords - "any", - "boolean", - "constructor", - "declare", - "get", - "module", - "require", - "number", - "set", - "string", - "symbol", - "type", - "from", - "of", - - // Reactor-TS specific keywords (other classes, which are less user-facing, have double underscores) - "TimeUnit", - "TimeValue", - "Present", - "Sched", - "Read", - "Write", - "ReadWrite" - ) - ), - Python("Python", false, Arrays.asList( - // List via: https://www.w3schools.com/python/python_ref_keywords.asp - // and https://en.cppreference.com/w/c/keyword (due to reliance on the C lib). - "and", - "as", - "assert", - "auto", - "break", - "case", - "char", - "class", - "const", - "continue", - "def", - "default", - "del", - "do", - "double", - "elif", - "else", - "enum", - "except", - "extern", - "False", - "finally", - "float", - "for", - "from", - "global", - "goto", - "if", - "import", - "inline", // (since C99) - "int", - "in", - "is", - "lambda", - "long", - "None", - "nonlocal", - "not", - "or", - "pass", - "raise", - "register", - "restrict", // (since C99) - "return", - "short", - "signed", - "sizeof", - "static", - "struct", - "switch", - "True", - "try", - "typedef", - "union", - "unsigned", - "void", - "volatile", - "while", - "with", - "yield", - "_Alignas", // (since C11) - "_Alignof", // (since C11) - "_Atomic", // (since C11) - "_Bool", // (since C99) - "_Complex", // (since C99) - "_Generic", // (since C11) - "_Imaginary", // (since C99) - "_Noreturn", // (since C11) - "_Static_assert", // (since C11) - "_Thread_local" // (since C11) - ) - ), - Rust("Rust", true, - "rust", "Rust", - // In our Rust implementation, the only reserved keywords - // are those that are a valid expression. Others may be escaped - // with the syntax r#keyword. - Arrays.asList("self", "true", "false") - ); - - /** - * String representation of this target. - */ - private final String displayName; - - /** - * Name of package containing Kotlin classes for the target language. - */ - public final String packageName; - - /** - * Prefix of names of Kotlin classes for the target language. - */ - public final String classNamePrefix; - - /** - * Whether or not this target requires types. - */ - public final boolean requiresTypes; - - /** - * Reserved words in the target language. - */ - public final Set keywords; - - /** - *An unmodifiable list of all known targets. - */ - public static final List ALL = List.of(Target.values()); - - /** - * Private constructor for targets. - * - * @param displayName String representation of this target. - * @param requiresTypes Types Whether this target requires type annotations or not. - * @param packageName Name of package containing Kotlin classes for the target language. - * @param classNamePrefix Prefix of names of Kotlin classes for the target language. - * @param keywords List of reserved strings in the target language. - */ - Target(String displayName, boolean requiresTypes, String packageName, - String classNamePrefix, Collection keywords) { - this.displayName = displayName; - this.requiresTypes = requiresTypes; - this.keywords = Collections.unmodifiableSet(new LinkedHashSet<>(keywords)); - this.packageName = packageName; - this.classNamePrefix = classNamePrefix; - } - - - /** - * Private constructor for targets without packageName and classNamePrefix. - */ - Target(String displayName, boolean requiresTypes, Collection keywords) { - this(displayName, requiresTypes, "N/A", "N/A", keywords); // FIXME: prefix - } - - - /** - * Return the target whose {@linkplain #getDisplayName() display name} - * is the given string (modulo character case), or an empty - * optional if there is no such target. - */ - public static Optional forName(String name) { - return Arrays.stream(Target.values()) - .filter(it -> it.getDisplayName().equalsIgnoreCase(name)) - .findFirst(); - } - - /** - * Return the display name of the target, as it should be - * written in LF code. This is hence a single identifier. - * Eg for {@link #CPP} returns {@code "Cpp"}, for {@link #Python} - * returns {@code "Python"}. Avoid using either {@link #name()} - * or {@link #toString()}, which have unrelated contracts. - */ - public String getDisplayName() { - return displayName; - } - - /** - * Returns the conventional directory name for this target. - * This is used to divide e.g. the {@code test} and {@code example} - * directories by target language. For instance, {@code test/Cpp} - * is the path of {@link #CPP}'s test directory, and this - * method returns {@code "Cpp"}. - */ - public String getDirectoryName() { - return displayName; - } - - /** - * Return the description. Avoid depending on this, toString - * is supposed to be debug information. Prefer {@link #getDisplayName()}. - */ - @Override - public String toString() { - return displayName; - } - - /** - * Returns whether the given identifier is invalid as the - * name of an LF construct. This usually means that the identifier - * is a keyword in the target language. In Rust, many - * keywords may be escaped with the syntax {@code r#keyword}, - * and they are considered valid identifiers. - */ - public boolean isReservedIdent(String ident) { - return this.keywords.contains(ident); - } - - /** - * Return true if the target supports federated execution. - */ - public boolean supportsFederated() { - return switch (this) { - case C, CCPP, Python, TS -> true; - default -> false; - }; - } - - /** - * Return true if the target supports reactor inheritance (extends keyword). - */ - public boolean supportsInheritance() { - return switch (this) { - case C, CCPP, Python -> true; - default -> false; - }; - } - - /** - * Return true if the target supports multiports and banks - * of reactors. - */ - public boolean supportsMultiports() { - switch (this) { - case C: - case CCPP: - case CPP: - case Python: - case Rust: - case TS: - return true; - } - return false; - } - - - /** - * Return true if the target supports widths of banks and - * multiports that depend on reactor parameters (not only - * on constants). - */ - public boolean supportsParameterizedWidths() { + C( + "C", + true, + Arrays.asList( + // List via: https://en.cppreference.com/w/c/keyword + "auto", + "break", + "case", + "char", + "const", + "continue", + "default", + "do", + "double", + "else", + "enum", + "extern", + "float", + "for", + "goto", + "if", + "inline", // (since C99) + "int", + "long", + "register", + "restrict", // (since C99) + "return", + "short", + "signed", + "sizeof", + "static", + "struct", + "switch", + "typedef", + "union", + "unsigned", + "void", + "volatile", + "while", + "_Alignas", // (since C11) + "_Alignof", // (since C11) + "_Atomic", // (since C11) + "_Bool", // (since C99) + "_Complex", // (since C99) + "_Generic", // (since C11) + "_Imaginary", // (since C99) + "_Noreturn", // (since C11) + "_Static_assert", // (since C11) + "_Thread_local" // (since C11) + )), + CCPP("CCpp", true, Target.C.keywords), + CPP( + "Cpp", + true, + "cpp", + "Cpp", + Arrays.asList( + // List via: https://en.cppreference.com/w/cpp/keyword + "alignas", // (since C++11) + "alignof", // (since C++11) + "and", + "and_eq", + "asm", + "atomic_cancel", // (TM TS) + "atomic_commit", // (TM TS) + "atomic_noexcept", // (TM TS) + "auto(1)", + "bitand", + "bitor", + "bool", + "break", + "case", + "catch", + "char", + "char8_t", // (since C++20) + "char16_t", // (since C++11) + "char32_t", // (since C++11) + "class(1)", + "compl", + "concept", // (since C++20) + "const", + "consteval", // (since C++20) + "constexpr", // (since C++11) + "constinit", // (since C++20) + "const_cast", + "continue", + "co_await", // (since C++20) + "co_return", // (since C++20) + "co_yield", // (since C++20) + "decltype", // (since C++11) + "default(1)", + "delete(1)", + "do", + "double", + "dynamic_cast", + "else", + "enum", + "explicit", + "export(1)(3)", + "extern(1)", + "false", + "float", + "for", + "friend", + "goto", + "if", + "inline(1)", + "int", + "long", + "mutable(1)", + "namespace", + "new", + "noexcept", // (since C++11) + "not", + "not_eq", + "nullptr", // (since C++11) + "operator", + "or", + "or_eq", + "private", + "protected", + "public", + "reflexpr", // (reflection TS) + "register(2)", + "reinterpret_cast", + "requires", // (since C++20) + "return", + "short", + "signed", + "sizeof(1)", + "static", + "static_assert", // (since C++11) + "static_cast", + "struct(1)", + "switch", + "synchronized", // (TM TS) + "template", + "this", + "thread_local", // (since C++11) + "throw", + "true", + "try", + "typedef", + "typeid", + "typename", + "union", + "unsigned", + "using(1)", + "virtual", + "void", + "volatile", + "wchar_t", + "while", + "xor", + "xor_eq")), + TS( + "TypeScript", + false, + "ts", + "TS", + Arrays.asList( + // List via: https://github.com/Microsoft/TypeScript/issues/2536 + // Reserved words + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "else", + "enum", + "export", + "extends", + "false", + "finally", + "for", + "function", + "if", + "import", + "in", + "instanceof", + "new", + "null", + "return", + "super", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "var", + "void", + "while", + "with", + + // Strict Mode Reserved Words + "as", + "implements", + "interface", + "let", + "package", + "private", + "protected", + "public", + "static", + "yield", + + // Contextual Keywords + "any", + "boolean", + "constructor", + "declare", + "get", + "module", + "require", + "number", + "set", + "string", + "symbol", + "type", + "from", + "of", + + // Reactor-TS specific keywords (other classes, which are less user-facing, have double + // underscores) + "TimeUnit", + "TimeValue", + "Present", + "Sched", + "Read", + "Write", + "ReadWrite")), + Python( + "Python", + false, + Arrays.asList( + // List via: https://www.w3schools.com/python/python_ref_keywords.asp + // and https://en.cppreference.com/w/c/keyword (due to reliance on the C lib). + "and", + "as", + "assert", + "auto", + "break", + "case", + "char", + "class", + "const", + "continue", + "def", + "default", + "del", + "do", + "double", + "elif", + "else", + "enum", + "except", + "extern", + "False", + "finally", + "float", + "for", + "from", + "global", + "goto", + "if", + "import", + "inline", // (since C99) + "int", + "in", + "is", + "lambda", + "long", + "None", + "nonlocal", + "not", + "or", + "pass", + "raise", + "register", + "restrict", // (since C99) + "return", + "short", + "signed", + "sizeof", + "static", + "struct", + "switch", + "True", + "try", + "typedef", + "union", + "unsigned", + "void", + "volatile", + "while", + "with", + "yield", + "_Alignas", // (since C11) + "_Alignof", // (since C11) + "_Atomic", // (since C11) + "_Bool", // (since C99) + "_Complex", // (since C99) + "_Generic", // (since C11) + "_Imaginary", // (since C99) + "_Noreturn", // (since C11) + "_Static_assert", // (since C11) + "_Thread_local" // (since C11) + )), + Rust( + "Rust", + true, + "rust", + "Rust", + // In our Rust implementation, the only reserved keywords + // are those that are a valid expression. Others may be escaped + // with the syntax r#keyword. + Arrays.asList("self", "true", "false")); + + /** String representation of this target. */ + private final String displayName; + + /** Name of package containing Kotlin classes for the target language. */ + public final String packageName; + + /** Prefix of names of Kotlin classes for the target language. */ + public final String classNamePrefix; + + /** Whether or not this target requires types. */ + public final boolean requiresTypes; + + /** Reserved words in the target language. */ + public final Set keywords; + + /** An unmodifiable list of all known targets. */ + public static final List ALL = List.of(Target.values()); + + /** + * Private constructor for targets. + * + * @param displayName String representation of this target. + * @param requiresTypes Types Whether this target requires type annotations or not. + * @param packageName Name of package containing Kotlin classes for the target language. + * @param classNamePrefix Prefix of names of Kotlin classes for the target language. + * @param keywords List of reserved strings in the target language. + */ + Target( + String displayName, + boolean requiresTypes, + String packageName, + String classNamePrefix, + Collection keywords) { + this.displayName = displayName; + this.requiresTypes = requiresTypes; + this.keywords = Collections.unmodifiableSet(new LinkedHashSet<>(keywords)); + this.packageName = packageName; + this.classNamePrefix = classNamePrefix; + } + + /** Private constructor for targets without packageName and classNamePrefix. */ + Target(String displayName, boolean requiresTypes, Collection keywords) { + this(displayName, requiresTypes, "N/A", "N/A", keywords); // FIXME: prefix + } + + /** + * Return the target whose {@linkplain #getDisplayName() display name} is the given string (modulo + * character case), or an empty optional if there is no such target. + */ + public static Optional forName(String name) { + return Arrays.stream(Target.values()) + .filter(it -> it.getDisplayName().equalsIgnoreCase(name)) + .findFirst(); + } + + /** + * Return the display name of the target, as it should be written in LF code. This is hence a + * single identifier. Eg for {@link #CPP} returns {@code "Cpp"}, for {@link #Python} returns + * {@code "Python"}. Avoid using either {@link #name()} or {@link #toString()}, which have + * unrelated contracts. + */ + public String getDisplayName() { + return displayName; + } + + /** + * Returns the conventional directory name for this target. This is used to divide e.g. the {@code + * test} and {@code example} directories by target language. For instance, {@code test/Cpp} is the + * path of {@link #CPP}'s test directory, and this method returns {@code "Cpp"}. + */ + public String getDirectoryName() { + return displayName; + } + + /** + * Return the description. Avoid depending on this, toString is supposed to be debug information. + * Prefer {@link #getDisplayName()}. + */ + @Override + public String toString() { + return displayName; + } + + /** + * Returns whether the given identifier is invalid as the name of an LF construct. This usually + * means that the identifier is a keyword in the target language. In Rust, many keywords may be + * escaped with the syntax {@code r#keyword}, and they are considered valid identifiers. + */ + public boolean isReservedIdent(String ident) { + return this.keywords.contains(ident); + } + + /** Return true if the target supports federated execution. */ + public boolean supportsFederated() { + return switch (this) { + case C, CCPP, Python, TS -> true; + default -> false; + }; + } + + /** Return true if the target supports reactor inheritance (extends keyword). */ + public boolean supportsInheritance() { + return switch (this) { + case C, CCPP, Python -> true; + default -> false; + }; + } + + /** Return true if the target supports multiports and banks of reactors. */ + public boolean supportsMultiports() { + switch (this) { + case C: + case CCPP: + case CPP: + case Python: + case Rust: + case TS: return true; } - - /** - * Return true if this code for this target should be built using Docker if Docker is used. - * @return - */ - public boolean buildsUsingDocker() { - return switch (this) { - case TS -> false; - case C, CCPP, CPP, Python, Rust -> true; - }; - } - - /** - * 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; + return false; + } + + /** + * Return true if the target supports widths of banks and multiports that depend on reactor + * parameters (not only on constants). + */ + public boolean supportsParameterizedWidths() { + return true; + } + + /** + * Return true if this code for this target should be built using Docker if Docker is used. + * + * @return + */ + public boolean buildsUsingDocker() { + return switch (this) { + case TS -> false; + case C, CCPP, CPP, Python, Rust -> true; + }; + } + + /** + * 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. */ + public String getSingleLineCommentPrefix() { + return this.equals(Target.Python) ? "#" : "//"; + } + + /** + * Return true if the keepalive option is set automatically for this target if physical actions + * are detected in the program (and keepalive was not explicitly unset by the user). + */ + public boolean setsKeepAliveOptionAutomatically() { + return this != Rust && this != CPP; + } + + /** + * Given a string and a list of candidate objects, return the first candidate that matches, or + * null if no candidate matches. + * + *

todo move to CollectionUtil (introduced in #442) + * + * @param string The string to match against candidates. + * @param candidates The candidates to match the string against. + */ + public static T match(final String string, final Iterable candidates) { + // kotlin: candidates.firstOrNull { it.toString().equalsIgnoreCase(string) } + for (T candidate : candidates) { + if (candidate.toString().equalsIgnoreCase(string)) { + return candidate; + } } - - /** 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. - */ - public String getSingleLineCommentPrefix() { - return this.equals(Target.Python) ? "#" : "//"; - } - - /** - * Return true if the keepalive option is set automatically - * for this target if physical actions are detected in the - * program (and keepalive was not explicitly unset by the user). - */ - public boolean setsKeepAliveOptionAutomatically() { - return this != Rust && this != CPP; - } - - /** - * Given a string and a list of candidate objects, return the first - * candidate that matches, or null if no candidate matches. - * - * todo move to CollectionUtil (introduced in #442) - * - * @param string The string to match against candidates. - * @param candidates The candidates to match the string against. - */ - public static T match(final String string, final Iterable candidates) { - // kotlin: candidates.firstOrNull { it.toString().equalsIgnoreCase(string) } - for (T candidate : candidates) { - if (candidate.toString().equalsIgnoreCase(string)) { - return candidate; - } - } - return null; - } - - - /** - * Given a string and a list of candidate objects, return the first - * candidate that matches, or null if no candidate matches. - * - * todo move to CollectionUtil (introduced in #442) - * - * @param string The string to match against candidates. - * @param candidates The candidates to match the string against. - */ - public static T match(final String string, final T[] candidates) { - return match(string, Arrays.asList(candidates)); - } - - - /** - * Return the target constant corresponding to given target - * declaration among. Return a non-null result, will throw - * if invalid. - * - * @throws RuntimeException If no {@link TargetDecl} is present or if it is invalid. - */ - public static Target fromDecl(TargetDecl targetDecl) { - String name = targetDecl.getName(); - return Target.forName(name) - .orElseThrow(() -> new RuntimeException("Invalid target name '" + name + "'")); - } - + return null; + } + + /** + * Given a string and a list of candidate objects, return the first candidate that matches, or + * null if no candidate matches. + * + *

todo move to CollectionUtil (introduced in #442) + * + * @param string The string to match against candidates. + * @param candidates The candidates to match the string against. + */ + public static T match(final String string, final T[] candidates) { + return match(string, Arrays.asList(candidates)); + } + + /** + * Return the target constant corresponding to given target declaration among. Return a non-null + * result, will throw if invalid. + * + * @throws RuntimeException If no {@link TargetDecl} is present or if it is invalid. + */ + public static Target fromDecl(TargetDecl targetDecl) { + String name = targetDecl.getName(); + return Target.forName(name) + .orElseThrow(() -> new RuntimeException("Invalid target name '" + name + "'")); + } } diff --git a/org.lflang/src/org/lflang/TargetConfig.java b/org.lflang/src/org/lflang/TargetConfig.java index 84770be5d1..c4794b4b1a 100644 --- a/org.lflang/src/org/lflang/TargetConfig.java +++ b/org.lflang/src/org/lflang/TargetConfig.java @@ -1,27 +1,27 @@ /************* -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; import java.util.ArrayList; @@ -32,7 +32,6 @@ import java.util.Objects; import java.util.Properties; import java.util.Set; - import org.lflang.TargetProperty.BuildType; import org.lflang.TargetProperty.ClockSyncMode; import org.lflang.TargetProperty.CoordinationType; @@ -48,453 +47,376 @@ /** * A class for keeping the current target configuration. * - * Class members of type String are initialized as empty strings, - * unless otherwise stated. + *

Class members of type String are initialized as empty strings, unless otherwise stated. + * * @author Marten Lohstroh */ public class TargetConfig { - /** - * The target of this configuration (e.g., C, TypeScript, Python). - */ - public final Target 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); + /** The target of this configuration (e.g., C, TypeScript, Python). */ + public final Target 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); } - - /** - * 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)); - } - if (cliArgs.containsKey(BuildParm.PRINT_STATISTICS.getKey())) { - this.printStatistics = true; - } + 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)); } + if (cliArgs.containsKey(BuildParm.PRINT_STATISTICS.getKey())) { + this.printStatistics = true; + } + } - /** - * Keep track of every target property that is explicitly set by the user. - */ - public Set setByUser = new HashSet<>(); + /** Keep track of every target property that is explicitly set by the user. */ + public Set setByUser = new HashSet<>(); - /** - * A list of custom build commands that replace the default build process of - * directly invoking a designated compiler. A common usage of this target - * property is to set the command to build on the basis of a Makefile. - */ - public List buildCommands = new ArrayList<>(); + /** + * A list of custom build commands that replace the default build process of directly invoking a + * designated compiler. A common usage of this target property is to set the command to build on + * the basis of a Makefile. + */ + public List buildCommands = new ArrayList<>(); - /** - * The mode of clock synchronization to be used in federated programs. - * The default is 'initial'. - */ - public ClockSyncMode clockSync = ClockSyncMode.INIT; + /** + * The mode of clock synchronization to be used in federated programs. The default is 'initial'. + */ + public ClockSyncMode clockSync = ClockSyncMode.INIT; - /** - * Clock sync options. - */ - public ClockSyncOptions clockSyncOptions = new ClockSyncOptions(); + /** Clock sync options. */ + public ClockSyncOptions clockSyncOptions = new ClockSyncOptions(); - /** - * Parameter passed to cmake. The default is 'Release'. - */ - public BuildType cmakeBuildType = BuildType.RELEASE; + /** Parameter passed to cmake. The default is 'Release'. */ + public BuildType cmakeBuildType = BuildType.RELEASE; - /** - * Optional additional extensions to include in the generated CMakeLists.txt. - */ - public List cmakeIncludes = new ArrayList<>(); + /** Optional additional extensions to include in the generated CMakeLists.txt. */ + public List cmakeIncludes = new ArrayList<>(); - /** - * The compiler to invoke, unless a build command has been specified. - */ - public String compiler = ""; + /** The compiler to invoke, unless a build command has been specified. */ + public String compiler = ""; - /** - * Additional sources to add to the compile command if appropriate. - */ - public List compileAdditionalSources = new ArrayList<>(); + /** Additional sources to add to the compile command if appropriate. */ + public List compileAdditionalSources = new ArrayList<>(); - /** - * Additional (preprocessor) definitions to add to the compile command if appropriate. - * - * The first string is the definition itself, and the second string is the value to attribute to that definition, if any. - * The second value could be left empty. - */ - public Map compileDefinitions = new HashMap<>(); + /** + * Additional (preprocessor) definitions to add to the compile command if appropriate. + * + *

The first string is the definition itself, and the second string is the value to attribute + * to that definition, if any. The second value could be left empty. + */ + public Map compileDefinitions = new HashMap<>(); - /** - * Additional libraries to add to the compile command using the "-l" command-line option. - */ - public List compileLibraries = new ArrayList<>(); + /** Additional libraries to add to the compile command using the "-l" command-line option. */ + public List compileLibraries = new ArrayList<>(); - /** - * Flags to pass to the compiler, unless a build command has been specified. - */ - public List compilerFlags = new ArrayList<>(); + /** Flags to pass to the compiler, unless a build command has been specified. */ + public List compilerFlags = new ArrayList<>(); - /** - * The type of coordination used during the execution of a federated program. - * The default is 'centralized'. - */ - public CoordinationType coordination = CoordinationType.CENTRALIZED; + /** + * The type of coordination used during the execution of a federated program. The default is + * 'centralized'. + */ + public CoordinationType coordination = CoordinationType.CENTRALIZED; + + /** Docker options. */ + public DockerOptions dockerOptions = null; - /** - * Docker options. - */ - public DockerOptions dockerOptions = null; + /** Coordination options. */ + public CoordinationOptions coordinationOptions = new CoordinationOptions(); - /** - * Coordination options. - */ - public CoordinationOptions coordinationOptions = new CoordinationOptions(); + /** Link to an external runtime library instead of the default one. */ + public String externalRuntimePath = null; - /** - * Link to an external runtime library instead of the default one. - */ - public String externalRuntimePath = null; + /** + * If true, configure the execution environment such that it does not wait for physical time to + * match logical time. The default is false. + */ + public boolean fastMode = false; - /** - * If true, configure the execution environment such that it does not - * wait for physical time to match logical time. The default is false. - */ - public boolean fastMode = false; + /** List of files to be copied to src-gen. */ + public List files = new ArrayList<>(); - /** - * List of files to be copied to src-gen. - */ - public List files = new ArrayList<>(); + /** + * If true, configure the execution environment to keep executing if there are no more events on + * the event queue. The default is false. + */ + public boolean keepalive = false; - /** - * If true, configure the execution environment to keep executing if there - * are no more events on the event queue. The default is false. - */ - public boolean keepalive = false; + /** The level of logging during execution. The default is INFO. */ + public LogLevel logLevel = LogLevel.INFO; - /** - * The level of logging during execution. The default is INFO. - */ - public LogLevel logLevel = LogLevel.INFO; + /** Flags to pass to the linker, unless a build command has been specified. */ + public String linkerFlags = ""; - /** - * Flags to pass to the linker, unless a build command has been specified. - */ - public String linkerFlags = ""; + /** If true, do not invoke the target compiler or build command. The default is false. */ + public boolean noCompile = false; - /** - * If true, do not invoke the target compiler or build command. - * The default is false. - */ - public boolean noCompile = false; + /** If true, do not perform runtime validation. The default is false. */ + public boolean noRuntimeValidation = false; - /** - * If true, do not perform runtime validation. The default is false. - */ - public boolean noRuntimeValidation = false; + /** + * Set the target platform config. This tells the build system what platform-specific support + * files it needs to incorporate at compile time. + * + *

This is now a wrapped class to account for overloaded definitions of defining platform + * (either a string or dictionary of values) + */ + public PlatformOptions platformOptions = new PlatformOptions(); - /** - * Set the target platform config. - * This tells the build system what platform-specific support - * files it needs to incorporate at compile time. - * - * This is now a wrapped class to account for overloaded definitions - * of defining platform (either a string or dictionary of values) - */ - public PlatformOptions platformOptions = new PlatformOptions(); + /** If true, instruct the runtime to collect and print execution statistics. */ + public boolean printStatistics = false; - /** - * If true, instruct the runtime to collect and print execution statistics. - */ - public boolean printStatistics = false; + /** List of proto files to be processed by the code generator. */ + public List protoFiles = new ArrayList<>(); - /** - * List of proto files to be processed by the code generator. - */ - public List protoFiles = new ArrayList<>(); + /** If true, generate ROS2 specific code. */ + public boolean ros2 = false; - /** - * If true, generate ROS2 specific code. - */ - public boolean ros2 = false; + /** Additional ROS2 packages that the LF program depends on. */ + public List ros2Dependencies = null; - /** - * Additional ROS2 packages that the LF program depends on. - */ - public List ros2Dependencies = null; + /** The version of the runtime library to be used in the generated target. */ + public String runtimeVersion = null; - /** - * The version of the runtime library to be used in the generated target. - */ - public String runtimeVersion = null; + /** Whether all reactors are to be generated into a single target language file. */ + public boolean singleFileProject = false; + + /** What runtime scheduler to use. */ + public SchedulerOption schedulerType = SchedulerOption.getDefault(); + + /** + * The number of worker threads to deploy. The default is zero, which indicates that the runtime + * is allowed to freely choose the number of workers. + */ + public int workers = 0; + + /** Indicate whether HMAC authentication is used. */ + public boolean auth = false; + + /** Indicate whether the runtime should use multithreaded execution. */ + public boolean threading = true; + + /** The timeout to be observed during execution of the program. */ + public TimeValue timeout; + + /** If non-null, configure the runtime environment to perform tracing. The default is null. */ + public TracingOptions tracing = null; + + /** + * If true, the resulting binary will output a graph visualizing all reaction dependencies. + * + *

This option is currently only used for C++ and Rust. This export function is a valuable tool + * for debugging LF programs and helps to understand the dependencies inferred by the runtime. + */ + public boolean exportDependencyGraph = false; - /** Whether all reactors are to be generated into a single target language file. */ - public boolean singleFileProject = false; + /** + * If true, the resulting binary will output a yaml file describing the whole reactor structure of + * the program. + * + *

This option is currently only used for C++. This export function is a valuable tool for + * debugging LF programs and performing external analysis. + */ + public boolean exportToYaml = false; - /** What runtime scheduler to use. */ - public SchedulerOption schedulerType = SchedulerOption.getDefault(); + /** Rust-specific configuration. */ + public final RustTargetConfig rust = + new RustTargetConfig(); // FIXME: https://issue.lf-lang.org/1558 + + /** Path to a C file used by the Python target to setup federated execution. */ + public String fedSetupPreamble = null; // FIXME: https://issue.lf-lang.org/1558 + + /** Settings related to clock synchronization. */ + public static class ClockSyncOptions { /** - * The number of worker threads to deploy. The default is zero, which indicates that - * the runtime is allowed to freely choose the number of workers. + * Dampen the adjustments to the clock synchronization offset by this rate. The default is 10. */ - public int workers = 0; + public int attenuation = 10; /** - * Indicate whether HMAC authentication is used. + * Whether to collect statistics while performing clock synchronization. This setting is only + * considered when clock synchronization has been activated. The default is true. */ - public boolean auth = false; + public boolean collectStats = true; + + /** Enable clock synchronization for federates on the same machine. Default is false. */ + public boolean localFederatesOn = false; /** - * Indicate whether the runtime should use multithreaded execution. + * Interval at which clock synchronization is initiated by the RTI (will be passed to it as an + * argument on the command-line). The default is 5 milliseconds. */ - public boolean threading = true; + public TimeValue period = new TimeValue(5, TimeUnit.MILLI); /** - * The timeout to be observed during execution of the program. + * Indicate the number of exchanges to be had per each clock synchronization round. See + * /lib/core/federated/clock-sync.h for more details. The default is 10. */ - public TimeValue timeout; + public int trials = 10; /** - * If non-null, configure the runtime environment to perform tracing. - * The default is null. + * Used to create an artificial clock synchronization error for the purpose of testing. The + * default is null. */ - public TracingOptions tracing = null; + public TimeValue testOffset; + } + /** Settings related to coordination of federated execution. */ + public static class CoordinationOptions { /** - * If true, the resulting binary will output a graph visualizing all reaction dependencies. - * - * This option is currently only used for C++ and Rust. This export function is a valuable tool - * for debugging LF programs and helps to understand the dependencies inferred by the runtime. + * For centralized coordination, if a federate has a physical action that can trigger an output, + * directly or indirectly, then it will send NET (next event tag) messages to the RTI + * periodically as its physical clock advances. This option sets the amount of time to wait + * between sending such messages. Increasing this value results in downstream federates that lag + * further behind physical time (if the "after" delays are insufficient). The default is null, + * which means it is up the implementation to choose an interval. */ - public boolean exportDependencyGraph = false; - + public TimeValue advance_message_interval = null; + } + /** Settings related to Docker options. */ + public static class DockerOptions { /** - * If true, the resulting binary will output a yaml file describing the whole reactor structure - * of the program. - * - * This option is currently only used for C++. This export function is a valuable tool for debugging - * LF programs and performing external analysis. + * The base image and tag from which to build the Docker image. The default is "alpine:latest". */ - public boolean exportToYaml = false; + public String from = "alpine:latest"; - /** Rust-specific configuration. */ - public final RustTargetConfig rust = new RustTargetConfig(); // FIXME: https://issue.lf-lang.org/1558 + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DockerOptions that = (DockerOptions) o; + return from.equals(that.from); + } + } - /** Path to a C file used by the Python target to setup federated execution. */ - public String fedSetupPreamble = null; // FIXME: https://issue.lf-lang.org/1558 + /** Settings related to Platform Options. */ + public static class PlatformOptions { /** - * Settings related to clock synchronization. + * The base platform we build our LF Files on. Should be set to AUTO by default unless + * developing for specific OS/Embedded Platform */ - public static class ClockSyncOptions { - - /** - * Dampen the adjustments to the clock synchronization offset by this rate. - * The default is 10. - */ - public int attenuation = 10; - - /** - * Whether to collect statistics while performing clock synchronization. - * This setting is only considered when clock synchronization has been activated. - * The default is true. - */ - public boolean collectStats = true; - - /** - * Enable clock synchronization for federates on the same machine. - * Default is false. - */ - public boolean localFederatesOn = false; - - - /** - * Interval at which clock synchronization is initiated by the RTI (will be passed - * to it as an argument on the command-line). - * The default is 5 milliseconds. - */ - public TimeValue period = new TimeValue(5, TimeUnit.MILLI); - - /** - * Indicate the number of exchanges to be had per each clock synchronization round. - * See /lib/core/federated/clock-sync.h for more details. - * The default is 10. - */ - public int trials = 10; - - /** - * Used to create an artificial clock synchronization error for the purpose of testing. - * The default is null. - */ - public TimeValue testOffset; - } + public Platform platform = Platform.AUTO; /** - * Settings related to coordination of federated execution. + * The string value used to determine what type of embedded board we work with and can be used + * to simplify the build process. For example, when we want to flash to an Arduino Nano 33 BLE + * board, we can use the string arduino:mbed_nano:nano33ble */ - public static class CoordinationOptions { - - /** - * For centralized coordination, if a federate has a physical action that can trigger - * an output, directly or indirectly, then it will send NET (next event tag) messages - * to the RTI periodically as its physical clock advances. This option sets the amount - * of time to wait between sending such messages. Increasing this value results in - * downstream federates that lag further behind physical time (if the "after" delays - * are insufficient). - * The default is null, which means it is up the implementation to choose an interval. - */ - public TimeValue advance_message_interval = null; - } + public String board = null; /** - * Settings related to Docker options. + * The string value used to determine the port on which to flash the compiled program (i.e. + * /dev/cu.usbmodem21301) */ - public static class DockerOptions { - /** - * The base image and tag from which to build the Docker image. The default is "alpine:latest". - */ - public String from = "alpine:latest"; - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - DockerOptions that = (DockerOptions) o; - return from.equals(that.from); - } - } + public String port = null; + + /** + * The baud rate used as a parameter to certain embedded platforms. 9600 is a standard rate + * amongst systems like Arduino, so it's the default value. + */ + public int baudRate = 9600; /** - * Settings related to Platform Options. + * The boolean statement used to determine whether we should automatically attempt to flash once + * we compile. This may require the use of board and port values depending on the infrastructure + * you use to flash the boards. */ - public static class PlatformOptions { - - /** - * The base platform we build our LF Files on. Should be set to AUTO by default unless developing for specific OS/Embedded Platform - */ - public Platform platform = Platform.AUTO; - - /** - * The string value used to determine what type of embedded board we work with and can be used to simplify the build process. For example, - * when we want to flash to an Arduino Nano 33 BLE board, we can use the string arduino:mbed_nano:nano33ble - */ - public String board = null; - - - /** - * The string value used to determine the port on which to flash the compiled program (i.e. /dev/cu.usbmodem21301) - */ - public String port = null; - - /** - * The baud rate used as a parameter to certain embedded platforms. 9600 is a standard rate amongst systems like Arduino, so it's the default value. - */ - public int baudRate = 9600; - - /** - * The boolean statement used to determine whether we should automatically attempt to flash once we compile. This may require the use of board and - * port values depending on the infrastructure you use to flash the boards. - */ - public boolean flash = false; - } + public boolean flash = false; + } + /** Settings related to tracing options. */ + public static class TracingOptions { /** - * Settings related to tracing options. + * The name to use as the root of the trace file produced. This defaults to the name of the .lf + * file. */ - public static class TracingOptions { - /** - * The name to use as the root of the trace file produced. - * This defaults to the name of the .lf file. - */ - public String traceFileName = null; - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - TracingOptions that = (TracingOptions) o; - return Objects.equals(traceFileName, that.traceFileName); // traceFileName may be null - } + public String traceFileName = null; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TracingOptions that = (TracingOptions) o; + return Objects.equals(traceFileName, that.traceFileName); // traceFileName may be null } + } } diff --git a/org.lflang/src/org/lflang/TargetProperty.java b/org.lflang/src/org/lflang/TargetProperty.java index fe5c7dd0b2..b3abf815a3 100644 --- a/org.lflang/src/org/lflang/TargetProperty.java +++ b/org.lflang/src/org/lflang/TargetProperty.java @@ -1,30 +1,31 @@ /************* -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; +import com.google.common.collect.ImmutableList; import java.io.IOException; import java.nio.file.Path; import java.util.Arrays; @@ -33,7 +34,6 @@ import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Collectors; - import org.eclipse.xtext.util.RuntimeIOException; import org.lflang.TargetConfig.DockerOptions; import org.lflang.TargetConfig.PlatformOptions; @@ -52,1819 +52,1761 @@ import org.lflang.util.StringUtil; import org.lflang.validation.LFValidator; -import com.google.common.collect.ImmutableList; - /** - * A target properties along with a type and a list of supporting targets - * that supports it, as well as a function for configuration updates. + * A target properties along with a type and a list of supporting targets that supports it, as well + * as a function for configuration updates. * * @author Marten Lohstroh */ public enum TargetProperty { - /** - * Directive to allow including OpenSSL libraries and process HMAC authentication. - */ - AUTH("auth", PrimitiveType.BOOLEAN, - Arrays.asList(Target.C, Target.CCPP), - (config) -> ASTUtils.toElement(config.auth), - (config, value, err) -> { - config.auth = ASTUtils.toBoolean(value); - }), - /** - * Directive to let the generator use the custom build command. - */ - BUILD("build", UnionType.STRING_OR_STRING_ARRAY, - Arrays.asList(Target.C, Target.CCPP), - (config) -> ASTUtils.toElement(config.buildCommands), - (config, value, err) -> { - config.buildCommands = ASTUtils.elementToListOfStrings(value); - }), - - /** - * Directive to specify the target build type such as 'Release' or 'Debug'. - * This is also used in the Rust target to select a Cargo profile. - */ - BUILD_TYPE("build-type", UnionType.BUILD_TYPE_UNION, - Arrays.asList(Target.C, Target.CCPP, Target.CPP, Target.Rust), - (config) -> ASTUtils.toElement(config.cmakeBuildType.toString()), - (config, value, err) -> { - config.cmakeBuildType = (BuildType) UnionType.BUILD_TYPE_UNION - .forName(ASTUtils.elementToSingleString(value)); - // set it there too, because the default is different. - config.rust.setBuildType(config.cmakeBuildType); - }), - - /** - * Directive to let the federate execution handle clock synchronization in software. - */ - CLOCK_SYNC("clock-sync", UnionType.CLOCK_SYNC_UNION, - Arrays.asList(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.clockSync.toString()), - (config, value, err) -> { - config.clockSync = (ClockSyncMode) UnionType.CLOCK_SYNC_UNION - .forName(ASTUtils.elementToSingleString(value)); - }), - /** - * Key-value pairs giving options for clock synchronization. - */ - CLOCK_SYNC_OPTIONS("clock-sync-options", - DictionaryType.CLOCK_SYNC_OPTION_DICT, Arrays.asList(Target.C, Target.CCPP, Target.Python), - (config) -> { - Element e = LfFactory.eINSTANCE.createElement(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (ClockSyncOption opt : ClockSyncOption.values()) { - KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); - pair.setName(opt.toString()); - switch (opt) { - case ATTENUATION: - pair.setValue(ASTUtils.toElement(config.clockSyncOptions.attenuation)); - break; - case COLLECT_STATS: - pair.setValue(ASTUtils.toElement(config.clockSyncOptions.collectStats)); - break; - case LOCAL_FEDERATES_ON: - pair.setValue(ASTUtils.toElement(config.clockSyncOptions.localFederatesOn)); - break; - case PERIOD: - if (config.clockSyncOptions.period == null) continue; // don't set if null - pair.setValue(ASTUtils.toElement(config.clockSyncOptions.period)); - break; - case TEST_OFFSET: - if (config.clockSyncOptions.testOffset == null) continue; // don't set if null - pair.setValue(ASTUtils.toElement(config.clockSyncOptions.testOffset)); - break; - case TRIALS: - pair.setValue(ASTUtils.toElement(config.clockSyncOptions.trials)); - break; - } - kvp.getPairs().add(pair); - } - e.setKeyvalue(kvp); - // kvp will never be empty - return e; - }, - (config, value, err) -> { - for (KeyValuePair entry : value.getKeyvalue().getPairs()) { - ClockSyncOption option = (ClockSyncOption) DictionaryType.CLOCK_SYNC_OPTION_DICT - .forName(entry.getName()); - switch (option) { - case ATTENUATION: - config.clockSyncOptions.attenuation = ASTUtils - .toInteger(entry.getValue()); - break; - case COLLECT_STATS: - config.clockSyncOptions.collectStats = ASTUtils - .toBoolean(entry.getValue()); - break; - case LOCAL_FEDERATES_ON: - config.clockSyncOptions.localFederatesOn = ASTUtils - .toBoolean(entry.getValue()); - break; - case PERIOD: - config.clockSyncOptions.period = ASTUtils - .toTimeValue(entry.getValue()); - break; - case TEST_OFFSET: - config.clockSyncOptions.testOffset = ASTUtils - .toTimeValue(entry.getValue()); - break; - case TRIALS: - config.clockSyncOptions.trials = ASTUtils - .toInteger(entry.getValue()); - break; - default: - break; - } - } - }), - - /** - * Directive to specify a cmake to be included by the generated build - * systems. - * - * This gives full control over the C/C++ build as any cmake parameters - * can be adjusted in the included file. - */ - CMAKE_INCLUDE("cmake-include", UnionType.FILE_OR_FILE_ARRAY, - Arrays.asList(Target.CPP, Target.C, Target.CCPP), - (config) -> ASTUtils.toElement(config.cmakeIncludes), - (config, value, err) -> { - config.cmakeIncludes = ASTUtils.elementToListOfStrings(value); - }, - // FIXME: This merging of lists is potentially dangerous since - // the incoming list of cmake-includes 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.cmakeIncludes.addAll(ASTUtils.elementToListOfStrings(value)); - }), - /** - * Directive to specify the target compiler. - */ - COMPILER("compiler", PrimitiveType.STRING, Target.ALL, - (config) -> ASTUtils.toElement(config.compiler), - (config, value, err) -> { - config.compiler = ASTUtils.elementToSingleString(value); - }), - - /** - * Directive to specify compile-time definitions. - */ - COMPILE_DEFINITIONS("compile-definitions", StringDictionaryType.COMPILE_DEFINITION, - Arrays.asList(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.compileDefinitions), - (config, value, err) -> { - config.compileDefinitions = ASTUtils.elementToStringMaps(value); - }), - - /** - * Directive to generate a Dockerfile. This is either a boolean, - * true or false, or a dictionary of options. - */ - DOCKER("docker", UnionType.DOCKER_UNION, - Arrays.asList(Target.C, Target.CCPP, Target.Python, Target.TS), - (config) -> { - if (config.dockerOptions == null) { - return null; - } else if(config.dockerOptions.equals(new DockerOptions())) { - // default configuration - return ASTUtils.toElement(true); - } else { - Element e = LfFactory.eINSTANCE.createElement(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (DockerOption opt : DockerOption.values()) { - KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); - pair.setName(opt.toString()); - switch (opt) { - case FROM: - if (config.dockerOptions.from == null) continue; - pair.setValue(ASTUtils.toElement(config.dockerOptions.from)); - break; - } - kvp.getPairs().add(pair); - } - e.setKeyvalue(kvp); - if (kvp.getPairs().isEmpty()) return null; - return e; - } - }, - (config, value, err) -> setDockerProperty(config, value), - (config, value, err) -> setDockerProperty(config, value)), - - /** - * Directive for specifying a path to an external runtime to be used for the - * compiled binary. - */ - EXTERNAL_RUNTIME_PATH("external-runtime-path", PrimitiveType.STRING, - List.of(Target.CPP), - (config) -> ASTUtils.toElement(config.externalRuntimePath), - (config, value, err) -> { - config.externalRuntimePath = ASTUtils.elementToSingleString(value); - }), - - /** - * Directive to let the execution engine allow logical time to elapse - * faster than physical time. - */ - FAST("fast", PrimitiveType.BOOLEAN, Target.ALL, - (config) -> ASTUtils.toElement(config.fastMode), - (config, value, err) -> { - config.fastMode = ASTUtils.toBoolean(value); - }), - - /** - * Directive to stage particular files on the class path to be - * processed by the code generator. - */ - FILES("files", UnionType.FILE_OR_FILE_ARRAY, List.of(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.files), - (config, value, err) -> { - 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.files.addAll(ASTUtils.elementToListOfStrings(value)); - }), - - /** - * Flags to be passed on to the target compiler. - */ - FLAGS("flags", UnionType.STRING_OR_STRING_ARRAY, - Arrays.asList(Target.C, Target.CCPP), - (config) -> ASTUtils.toElement(config.compilerFlags), - (config, value, err) -> { - config.compilerFlags = ASTUtils.elementToListOfStrings(value); - }), - - /** - * Directive to specify the coordination mode - */ - COORDINATION("coordination", UnionType.COORDINATION_UNION, - Arrays.asList(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.coordination.toString()), - (config, value, err) -> { - config.coordination = (CoordinationType) UnionType.COORDINATION_UNION - .forName(ASTUtils.elementToSingleString(value)); - }, - (config, value, err) -> { - config.coordination = (CoordinationType) UnionType.COORDINATION_UNION - .forName(ASTUtils.elementToSingleString(value)); - }), - - /** - * Key-value pairs giving options for clock synchronization. - */ - COORDINATION_OPTIONS("coordination-options", - DictionaryType.COORDINATION_OPTION_DICT, Arrays.asList(Target.C, Target.CCPP, Target.Python, Target.TS), - (config) -> { - Element e = LfFactory.eINSTANCE.createElement(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (CoordinationOption opt : CoordinationOption.values()) { - KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); - pair.setName(opt.toString()); - switch (opt) { - case ADVANCE_MESSAGE_INTERVAL: - if (config.coordinationOptions.advance_message_interval == null) continue; - pair.setValue(ASTUtils.toElement(config.coordinationOptions.advance_message_interval)); - break; - } - kvp.getPairs().add(pair); - } - e.setKeyvalue(kvp); - if (kvp.getPairs().isEmpty()) return null; - return e; - }, - (config, value, err) -> { - for (KeyValuePair entry : value.getKeyvalue().getPairs()) { - CoordinationOption option = (CoordinationOption) DictionaryType.COORDINATION_OPTION_DICT - .forName(entry.getName()); - switch (option) { - case ADVANCE_MESSAGE_INTERVAL: - config.coordinationOptions.advance_message_interval = ASTUtils - .toTimeValue(entry.getValue()); - break; - default: - break; - } - } - }, - (config, value, err) -> { - for (KeyValuePair entry : value.getKeyvalue().getPairs()) { - CoordinationOption option = (CoordinationOption) DictionaryType.COORDINATION_OPTION_DICT - .forName(entry.getName()); - switch (option) { - case ADVANCE_MESSAGE_INTERVAL: - config.coordinationOptions.advance_message_interval = ASTUtils - .toTimeValue(entry.getValue()); - break; - default: - break; - } - } - }), - - /** - * Directive to let the execution engine remain active also if there - * are no more events in the event queue. - */ - KEEPALIVE("keepalive", PrimitiveType.BOOLEAN, Target.ALL, - (config) -> ASTUtils.toElement(config.keepalive), - (config, value, err) -> { - config.keepalive = ASTUtils.toBoolean(value); - }), - - /** - * Directive to specify the grain at which to report log messages during execution. - */ - LOGGING("logging", UnionType.LOGGING_UNION, Target.ALL, - (config) -> ASTUtils.toElement(config.logLevel.toString()), - (config, value, err) -> { - config.logLevel = (LogLevel) UnionType.LOGGING_UNION - .forName(ASTUtils.elementToSingleString(value)); - }), - - /** - * Directive to not invoke the target compiler. - */ - NO_COMPILE("no-compile", PrimitiveType.BOOLEAN, - Arrays.asList(Target.C, Target.CPP, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.noCompile), - (config, value, err) -> { - config.noCompile = ASTUtils.toBoolean(value); - }), - - /** - * Directive to disable validation of reactor rules at runtime. - */ - NO_RUNTIME_VALIDATION("no-runtime-validation", PrimitiveType.BOOLEAN, - Arrays.asList(Target.CPP), - (config) -> ASTUtils.toElement(config.noRuntimeValidation), - (config, value, err) -> { - config.noRuntimeValidation = ASTUtils.toBoolean(value); - }), - - /** - * Directive to specify the platform for cross code generation. This is either a string of the platform - * or a dictionary of options that includes the string name. - */ - PLATFORM("platform", UnionType.PLATFORM_STRING_OR_DICTIONARY, Target.ALL, - (config) -> { - Element e = LfFactory.eINSTANCE.createElement(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (PlatformOption opt : PlatformOption.values()) { - KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); - pair.setName(opt.toString()); - switch (opt) { - case NAME: - pair.setValue(ASTUtils.toElement(config.platformOptions.platform.toString())); - break; - case BAUDRATE: - pair.setValue(ASTUtils.toElement(config.platformOptions.baudRate)); - break; - case BOARD: - pair.setValue(ASTUtils.toElement(config.platformOptions.board)); - break; - case FLASH: - pair.setValue(ASTUtils.toElement(config.platformOptions.flash)); - break; - case PORT: - pair.setValue(ASTUtils.toElement(config.platformOptions.port)); - break; - } - kvp.getPairs().add(pair); - } - e.setKeyvalue(kvp); - if (kvp.getPairs().isEmpty()) return null; - return e; - }, - (config, value, err) -> { - if (value.getLiteral() != null) { - config.platformOptions = new PlatformOptions(); - config.platformOptions.platform = (Platform) UnionType.PLATFORM_UNION - .forName(ASTUtils.elementToSingleString(value)); - } else { - config.platformOptions= new PlatformOptions(); - for (KeyValuePair entry : value.getKeyvalue().getPairs()) { - PlatformOption option = (PlatformOption) DictionaryType.PLATFORM_DICT - .forName(entry.getName()); - switch (option) { - case NAME: - Platform p = (Platform) UnionType.PLATFORM_UNION - .forName(ASTUtils.elementToSingleString(entry.getValue())); - if(p == null) { - String s = "Unidentified Platform Type, LF supports the following platform types: " + Arrays.asList(Platform.values()).toString(); - err.reportError(s); - throw new AssertionError(s); - } - config.platformOptions.platform = p; - break; - case BAUDRATE: - config.platformOptions.baudRate = ASTUtils.toInteger(entry.getValue()); - break; - case BOARD: - config.platformOptions.board = ASTUtils.elementToSingleString(entry.getValue()); - break; - case FLASH: - config.platformOptions.flash = ASTUtils.toBoolean(entry.getValue()); - break; - case PORT: - config.platformOptions.port = ASTUtils.elementToSingleString(entry.getValue()); - break; - default: - break; - } - } - } - }), - - /** - * Directive to instruct the runtime to collect and print execution statistics. - */ - PRINT_STATISTICS("print-statistics", PrimitiveType.BOOLEAN, - Arrays.asList(Target.CPP), - (config) -> ASTUtils.toElement(config.printStatistics), - (config, value, err) -> { - config.printStatistics = ASTUtils.toBoolean(value); - }), - - /** - * Directive for specifying .proto files that need to be compiled and their - * code included in the sources. - */ - PROTOBUFS("protobufs", UnionType.FILE_OR_FILE_ARRAY, - Arrays.asList(Target.C, Target.CCPP, Target.TS, Target.Python), - (config) -> ASTUtils.toElement(config.protoFiles), - (config, value, err) -> { - config.protoFiles = ASTUtils.elementToListOfStrings(value); - }), - - - /** - * Directive to specify that ROS2 specific code is generated, - */ - ROS2("ros2", PrimitiveType.BOOLEAN, - List.of(Target.CPP), - (config) -> ASTUtils.toElement(config.ros2), - (config, value, err) -> { - config.ros2 = ASTUtils.toBoolean(value); - }), - - /** - * Directive to specify additional ROS2 packages that this LF program depends on. - */ - ROS2_DEPENDENCIES("ros2-dependencies", ArrayType.STRING_ARRAY, - List.of(Target.CPP), - (config) -> ASTUtils.toElement(config.ros2Dependencies), - (config, value, err) -> { - config.ros2Dependencies = ASTUtils.elementToListOfStrings(value); - }), - - /** - * Directive for specifying a specific version of the reactor runtime library. - */ - RUNTIME_VERSION("runtime-version", PrimitiveType.STRING, - Arrays.asList(Target.CPP), - (config) -> ASTUtils.toElement(config.runtimeVersion), - (config, value, err) -> { - config.runtimeVersion = ASTUtils.elementToSingleString(value); - }), - - - /** - * Directive for specifying a specific runtime scheduler, if supported. - */ - SCHEDULER("scheduler", UnionType.SCHEDULER_UNION, - Arrays.asList(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.schedulerType.toString()), - (config, value, err) -> { - config.schedulerType = (SchedulerOption) UnionType.SCHEDULER_UNION - .forName(ASTUtils.elementToSingleString(value)); - }), - - /** - * Directive to specify that all code is generated in a single file. - */ - SINGLE_FILE_PROJECT("single-file-project", PrimitiveType.BOOLEAN, - List.of(Target.Rust), - (config) -> ASTUtils.toElement(config.singleFileProject), - (config, value, err) -> { - config.singleFileProject = ASTUtils.toBoolean(value); - }), - - /** - * Directive to indicate whether the runtime should use multi-threading. - */ - THREADING("threading", PrimitiveType.BOOLEAN, - List.of(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.threading), - (config, value, err) -> { - config.threading = ASTUtils.toBoolean(value); - }), - - /** - * Directive to specify the number of worker threads used by the runtime. - */ - WORKERS("workers", PrimitiveType.NON_NEGATIVE_INTEGER, - List.of(Target.C, Target.CCPP, Target.Python, Target.CPP, Target.Rust), - (config) -> ASTUtils.toElement(config.workers), - (config, value, err) -> { - config.workers = ASTUtils.toInteger(value); - }), - - /** - * Directive to specify the execution timeout. - */ - TIMEOUT("timeout", PrimitiveType.TIME_VALUE, Target.ALL, - (config) -> ASTUtils.toElement(config.timeout), - (config, value, err) -> { - config.timeout = ASTUtils.toTimeValue(value); - }, - (config, value, err) -> { - config.timeout = ASTUtils.toTimeValue(value); - }), - - /** - * Directive to enable tracing. - */ - TRACING("tracing", UnionType.TRACING_UNION, - Arrays.asList(Target.C, Target.CCPP, Target.CPP, Target.Python), - (config) -> { - if (config.tracing == null) { - return null; - } else if (config.tracing.equals(new TracingOptions())) { - // default values - return ASTUtils.toElement(true); - } else { - Element e = LfFactory.eINSTANCE.createElement(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (TracingOption opt : TracingOption.values()) { - KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); - pair.setName(opt.toString()); - switch (opt) { - case TRACE_FILE_NAME: - if (config.tracing.traceFileName == null) continue; - pair.setValue(ASTUtils.toElement(config.tracing.traceFileName)); - } - kvp.getPairs().add(pair); - } - e.setKeyvalue(kvp); - if (kvp.getPairs().isEmpty()) return null; - return e; - } - }, - (config, value, err) -> { - if (value.getLiteral() != null) { - if (ASTUtils.toBoolean(value)) { - config.tracing = new TracingOptions(); - } else { - config.tracing = null; - } - } else { - config.tracing = new TracingOptions(); - for (KeyValuePair entry : value.getKeyvalue().getPairs()) { - TracingOption option = (TracingOption) DictionaryType.TRACING_DICT - .forName(entry.getName()); - switch (option) { - case TRACE_FILE_NAME: - config.tracing.traceFileName = ASTUtils.elementToSingleString(entry.getValue()); - break; - default: - break; - } - } - } - }), - - /** - * Directive to let the runtime export its internal dependency graph. - * - * This is a debugging feature and currently only used for C++ and Rust programs. - */ - EXPORT_DEPENDENCY_GRAPH("export-dependency-graph", PrimitiveType.BOOLEAN, - List.of(Target.CPP, Target.Rust), - (config) -> ASTUtils.toElement(config.exportDependencyGraph), - (config, value, err) -> { - config.exportDependencyGraph = ASTUtils.toBoolean(value); - }), - - /** - * Directive to let the runtime export the program structure to a yaml file. - * - * This is a debugging feature and currently only used for C++ programs. - */ - EXPORT_TO_YAML("export-to-yaml", PrimitiveType.BOOLEAN, - List.of(Target.CPP), - (config) -> ASTUtils.toElement(config.exportToYaml), - (config, value, err) -> { - config.exportToYaml = ASTUtils.toBoolean(value); - }), - - /** - * List of module files to link into the crate as top-level. - * For instance, a {@code target Rust { rust-modules: [ "foo.rs" ] }} - * will cause the file to be copied into the generated project, - * and the generated {@code main.rs} will include it with a {@code mod foo;}. - * If one of the paths is a directory, it must contain a {@code mod.rs} - * file, and all its contents are copied. - */ - RUST_INCLUDE("rust-include", - UnionType.FILE_OR_FILE_ARRAY, - List.of(Target.Rust), - (config) -> { - // do not check paths here, and simply copy the absolute path over - List paths = config.rust.getRustTopLevelModules(); - if (paths.isEmpty()) return null; - else if (paths.size() == 1) { - return ASTUtils.toElement(paths.get(0).toString()); - } else { - Element e = LfFactory.eINSTANCE.createElement(); - Array arr = LfFactory.eINSTANCE.createArray(); - for (Path p : paths) { - arr.getElements().add(ASTUtils.toElement(p.toString())); - } - e.setArray(arr); - return e; - } - }, - (config, value, err) -> { - Path referencePath; - try { - referencePath = FileUtil.toPath(value.eResource().getURI()).toAbsolutePath(); - } catch (IOException e) { - err.reportError(value, "Invalid path? " + e.getMessage()); - throw new RuntimeIOException(e); - } - - // we'll resolve relative paths to check that the files - // are as expected. - - if (value.getLiteral() != null) { - Path resolved = referencePath.resolveSibling(StringUtil.removeQuotes(value.getLiteral())); - - config.rust.addAndCheckTopLevelModule(resolved, value, err); - } else if (value.getArray() != null) { - for (Element element : value.getArray().getElements()) { - String literal = StringUtil.removeQuotes(element.getLiteral()); - Path resolved = referencePath.resolveSibling(literal); - config.rust.addAndCheckTopLevelModule(resolved, element, err); - } - } - }), - - /** - * Directive for specifying Cargo features of the generated - * program to enable. - */ - CARGO_FEATURES("cargo-features", ArrayType.STRING_ARRAY, - List.of(Target.Rust), - (config) -> ASTUtils.toElement(config.rust.getCargoFeatures()), - (config, value, err) -> { - config.rust.setCargoFeatures(ASTUtils.elementToListOfStrings(value)); - }), - - /** - * Dependency specifications for Cargo. This property looks like this: - *

{@code
-     * cargo-dependencies: {
-     *    // Name-of-the-crate: "version"
-     *    rand: "0.8",
-     *    // Equivalent to using an explicit map:
-     *    rand: {
-     *      version: "0.8"
-     *    },
-     *    // The map allows specifying more details
-     *    rand: {
-     *      // A path to a local unpublished crate.
-     *      // Note 'path' is mutually exclusive with 'version'.
-     *      path: "/home/me/Git/local-rand-clone"
-     *    },
-     *    rand: {
-     *      version: "0.8",
-     *      // you can specify cargo features
-     *      features: ["some-cargo-feature",]
-     *    }
-     * }
-     * }
- */ - CARGO_DEPENDENCIES("cargo-dependencies", - CargoDependenciesPropertyType.INSTANCE, - List.of(Target.Rust), - (config) -> { - var deps = config.rust.getCargoDependencies(); - if (deps.size() == 0) return null; - else { - Element e = LfFactory.eINSTANCE.createElement(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (var ent : deps.entrySet()) { - KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); - pair.setName(ent.getKey()); - pair.setValue(CargoDependencySpec.extractSpec(ent.getValue())); - kvp.getPairs().add(pair); - } - e.setKeyvalue(kvp); - return e; - } - }, - (config, value, err) -> { - config.rust.setCargoDependencies(CargoDependencySpec.parseAll(value)); - }), - - /** - * Directs the C or Python target to include the associated C file used for - * setting up federated execution before processing the first tag. - */ - FED_SETUP("_fed_setup", PrimitiveType.FILE, - Arrays.asList(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.fedSetupPreamble), - (config, value, err) -> - config.fedSetupPreamble = StringUtil.removeQuotes(ASTUtils.elementToSingleString(value)) - ) - ; - - /** - * Update {@code config}.dockerOptions based on value. - */ - private static void setDockerProperty(TargetConfig config, Element value) { - if (value.getLiteral() != null) { - if (ASTUtils.toBoolean(value)) { - config.dockerOptions = new DockerOptions(); - } else { - config.dockerOptions = null; + /** Directive to allow including OpenSSL libraries and process HMAC authentication. */ + AUTH( + "auth", + PrimitiveType.BOOLEAN, + Arrays.asList(Target.C, Target.CCPP), + (config) -> ASTUtils.toElement(config.auth), + (config, value, err) -> { + config.auth = ASTUtils.toBoolean(value); + }), + /** Directive to let the generator use the custom build command. */ + BUILD( + "build", + UnionType.STRING_OR_STRING_ARRAY, + Arrays.asList(Target.C, Target.CCPP), + (config) -> ASTUtils.toElement(config.buildCommands), + (config, value, err) -> { + config.buildCommands = ASTUtils.elementToListOfStrings(value); + }), + + /** + * Directive to specify the target build type such as 'Release' or 'Debug'. This is also used in + * the Rust target to select a Cargo profile. + */ + BUILD_TYPE( + "build-type", + UnionType.BUILD_TYPE_UNION, + Arrays.asList(Target.C, Target.CCPP, Target.CPP, Target.Rust), + (config) -> ASTUtils.toElement(config.cmakeBuildType.toString()), + (config, value, err) -> { + config.cmakeBuildType = + (BuildType) UnionType.BUILD_TYPE_UNION.forName(ASTUtils.elementToSingleString(value)); + // set it there too, because the default is different. + config.rust.setBuildType(config.cmakeBuildType); + }), + + /** Directive to let the federate execution handle clock synchronization in software. */ + CLOCK_SYNC( + "clock-sync", + UnionType.CLOCK_SYNC_UNION, + Arrays.asList(Target.C, Target.CCPP, Target.Python), + (config) -> ASTUtils.toElement(config.clockSync.toString()), + (config, value, err) -> { + config.clockSync = + (ClockSyncMode) + UnionType.CLOCK_SYNC_UNION.forName(ASTUtils.elementToSingleString(value)); + }), + /** Key-value pairs giving options for clock synchronization. */ + CLOCK_SYNC_OPTIONS( + "clock-sync-options", + DictionaryType.CLOCK_SYNC_OPTION_DICT, + Arrays.asList(Target.C, Target.CCPP, Target.Python), + (config) -> { + Element e = LfFactory.eINSTANCE.createElement(); + KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); + for (ClockSyncOption opt : ClockSyncOption.values()) { + KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); + pair.setName(opt.toString()); + switch (opt) { + case ATTENUATION: + pair.setValue(ASTUtils.toElement(config.clockSyncOptions.attenuation)); + break; + case COLLECT_STATS: + pair.setValue(ASTUtils.toElement(config.clockSyncOptions.collectStats)); + break; + case LOCAL_FEDERATES_ON: + pair.setValue(ASTUtils.toElement(config.clockSyncOptions.localFederatesOn)); + break; + case PERIOD: + if (config.clockSyncOptions.period == null) continue; // don't set if null + pair.setValue(ASTUtils.toElement(config.clockSyncOptions.period)); + break; + case TEST_OFFSET: + if (config.clockSyncOptions.testOffset == null) continue; // don't set if null + pair.setValue(ASTUtils.toElement(config.clockSyncOptions.testOffset)); + break; + case TRIALS: + pair.setValue(ASTUtils.toElement(config.clockSyncOptions.trials)); + break; + } + kvp.getPairs().add(pair); + } + e.setKeyvalue(kvp); + // kvp will never be empty + return e; + }, + (config, value, err) -> { + for (KeyValuePair entry : value.getKeyvalue().getPairs()) { + ClockSyncOption option = + (ClockSyncOption) DictionaryType.CLOCK_SYNC_OPTION_DICT.forName(entry.getName()); + switch (option) { + case ATTENUATION: + config.clockSyncOptions.attenuation = ASTUtils.toInteger(entry.getValue()); + break; + case COLLECT_STATS: + config.clockSyncOptions.collectStats = ASTUtils.toBoolean(entry.getValue()); + break; + case LOCAL_FEDERATES_ON: + config.clockSyncOptions.localFederatesOn = ASTUtils.toBoolean(entry.getValue()); + break; + case PERIOD: + config.clockSyncOptions.period = ASTUtils.toTimeValue(entry.getValue()); + break; + case TEST_OFFSET: + config.clockSyncOptions.testOffset = ASTUtils.toTimeValue(entry.getValue()); + break; + case TRIALS: + config.clockSyncOptions.trials = ASTUtils.toInteger(entry.getValue()); + break; + default: + break; + } + } + }), + + /** + * Directive to specify a cmake to be included by the generated build systems. + * + *

This gives full control over the C/C++ build as any cmake parameters can be adjusted in the + * included file. + */ + CMAKE_INCLUDE( + "cmake-include", + UnionType.FILE_OR_FILE_ARRAY, + Arrays.asList(Target.CPP, Target.C, Target.CCPP), + (config) -> ASTUtils.toElement(config.cmakeIncludes), + (config, value, err) -> { + config.cmakeIncludes = ASTUtils.elementToListOfStrings(value); + }, + // FIXME: This merging of lists is potentially dangerous since + // the incoming list of cmake-includes 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.cmakeIncludes.addAll(ASTUtils.elementToListOfStrings(value)); + }), + /** Directive to specify the target compiler. */ + COMPILER( + "compiler", + PrimitiveType.STRING, + Target.ALL, + (config) -> ASTUtils.toElement(config.compiler), + (config, value, err) -> { + config.compiler = ASTUtils.elementToSingleString(value); + }), + + /** Directive to specify compile-time definitions. */ + COMPILE_DEFINITIONS( + "compile-definitions", + StringDictionaryType.COMPILE_DEFINITION, + Arrays.asList(Target.C, Target.CCPP, Target.Python), + (config) -> ASTUtils.toElement(config.compileDefinitions), + (config, value, err) -> { + config.compileDefinitions = ASTUtils.elementToStringMaps(value); + }), + + /** + * Directive to generate a Dockerfile. This is either a boolean, true or false, or a dictionary of + * options. + */ + DOCKER( + "docker", + UnionType.DOCKER_UNION, + Arrays.asList(Target.C, Target.CCPP, Target.Python, Target.TS), + (config) -> { + if (config.dockerOptions == null) { + return null; + } else if (config.dockerOptions.equals(new DockerOptions())) { + // default configuration + return ASTUtils.toElement(true); + } else { + Element e = LfFactory.eINSTANCE.createElement(); + KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); + for (DockerOption opt : DockerOption.values()) { + KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); + pair.setName(opt.toString()); + switch (opt) { + case FROM: + if (config.dockerOptions.from == null) continue; + pair.setValue(ASTUtils.toElement(config.dockerOptions.from)); + break; } + kvp.getPairs().add(pair); + } + e.setKeyvalue(kvp); + if (kvp.getPairs().isEmpty()) return null; + return e; + } + }, + (config, value, err) -> setDockerProperty(config, value), + (config, value, err) -> setDockerProperty(config, value)), + + /** Directive for specifying a path to an external runtime to be used for the compiled binary. */ + EXTERNAL_RUNTIME_PATH( + "external-runtime-path", + PrimitiveType.STRING, + List.of(Target.CPP), + (config) -> ASTUtils.toElement(config.externalRuntimePath), + (config, value, err) -> { + config.externalRuntimePath = ASTUtils.elementToSingleString(value); + }), + + /** + * Directive to let the execution engine allow logical time to elapse faster than physical time. + */ + FAST( + "fast", + PrimitiveType.BOOLEAN, + Target.ALL, + (config) -> ASTUtils.toElement(config.fastMode), + (config, value, err) -> { + config.fastMode = ASTUtils.toBoolean(value); + }), + + /** + * Directive to stage particular files on the class path to be processed by the code generator. + */ + FILES( + "files", + UnionType.FILE_OR_FILE_ARRAY, + List.of(Target.C, Target.CCPP, Target.Python), + (config) -> ASTUtils.toElement(config.files), + (config, value, err) -> { + 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.files.addAll(ASTUtils.elementToListOfStrings(value)); + }), + + /** Flags to be passed on to the target compiler. */ + FLAGS( + "flags", + UnionType.STRING_OR_STRING_ARRAY, + Arrays.asList(Target.C, Target.CCPP), + (config) -> ASTUtils.toElement(config.compilerFlags), + (config, value, err) -> { + config.compilerFlags = ASTUtils.elementToListOfStrings(value); + }), + + /** Directive to specify the coordination mode */ + COORDINATION( + "coordination", + UnionType.COORDINATION_UNION, + Arrays.asList(Target.C, Target.CCPP, Target.Python), + (config) -> ASTUtils.toElement(config.coordination.toString()), + (config, value, err) -> { + config.coordination = + (CoordinationType) + UnionType.COORDINATION_UNION.forName(ASTUtils.elementToSingleString(value)); + }, + (config, value, err) -> { + config.coordination = + (CoordinationType) + UnionType.COORDINATION_UNION.forName(ASTUtils.elementToSingleString(value)); + }), + + /** Key-value pairs giving options for clock synchronization. */ + COORDINATION_OPTIONS( + "coordination-options", + DictionaryType.COORDINATION_OPTION_DICT, + Arrays.asList(Target.C, Target.CCPP, Target.Python, Target.TS), + (config) -> { + Element e = LfFactory.eINSTANCE.createElement(); + KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); + for (CoordinationOption opt : CoordinationOption.values()) { + KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); + pair.setName(opt.toString()); + switch (opt) { + case ADVANCE_MESSAGE_INTERVAL: + if (config.coordinationOptions.advance_message_interval == null) continue; + pair.setValue( + ASTUtils.toElement(config.coordinationOptions.advance_message_interval)); + break; + } + kvp.getPairs().add(pair); + } + e.setKeyvalue(kvp); + if (kvp.getPairs().isEmpty()) return null; + return e; + }, + (config, value, err) -> { + for (KeyValuePair entry : value.getKeyvalue().getPairs()) { + CoordinationOption option = + (CoordinationOption) DictionaryType.COORDINATION_OPTION_DICT.forName(entry.getName()); + switch (option) { + case ADVANCE_MESSAGE_INTERVAL: + config.coordinationOptions.advance_message_interval = + ASTUtils.toTimeValue(entry.getValue()); + break; + default: + break; + } + } + }, + (config, value, err) -> { + for (KeyValuePair entry : value.getKeyvalue().getPairs()) { + CoordinationOption option = + (CoordinationOption) DictionaryType.COORDINATION_OPTION_DICT.forName(entry.getName()); + switch (option) { + case ADVANCE_MESSAGE_INTERVAL: + config.coordinationOptions.advance_message_interval = + ASTUtils.toTimeValue(entry.getValue()); + break; + default: + break; + } + } + }), + + /** + * Directive to let the execution engine remain active also if there are no more events in the + * event queue. + */ + KEEPALIVE( + "keepalive", + PrimitiveType.BOOLEAN, + Target.ALL, + (config) -> ASTUtils.toElement(config.keepalive), + (config, value, err) -> { + config.keepalive = ASTUtils.toBoolean(value); + }), + + /** Directive to specify the grain at which to report log messages during execution. */ + LOGGING( + "logging", + UnionType.LOGGING_UNION, + Target.ALL, + (config) -> ASTUtils.toElement(config.logLevel.toString()), + (config, value, err) -> { + config.logLevel = + (LogLevel) UnionType.LOGGING_UNION.forName(ASTUtils.elementToSingleString(value)); + }), + + /** Directive to not invoke the target compiler. */ + NO_COMPILE( + "no-compile", + PrimitiveType.BOOLEAN, + Arrays.asList(Target.C, Target.CPP, Target.CCPP, Target.Python), + (config) -> ASTUtils.toElement(config.noCompile), + (config, value, err) -> { + config.noCompile = ASTUtils.toBoolean(value); + }), + + /** Directive to disable validation of reactor rules at runtime. */ + NO_RUNTIME_VALIDATION( + "no-runtime-validation", + PrimitiveType.BOOLEAN, + Arrays.asList(Target.CPP), + (config) -> ASTUtils.toElement(config.noRuntimeValidation), + (config, value, err) -> { + config.noRuntimeValidation = ASTUtils.toBoolean(value); + }), + + /** + * Directive to specify the platform for cross code generation. This is either a string of the + * platform or a dictionary of options that includes the string name. + */ + PLATFORM( + "platform", + UnionType.PLATFORM_STRING_OR_DICTIONARY, + Target.ALL, + (config) -> { + Element e = LfFactory.eINSTANCE.createElement(); + KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); + for (PlatformOption opt : PlatformOption.values()) { + KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); + pair.setName(opt.toString()); + switch (opt) { + case NAME: + pair.setValue(ASTUtils.toElement(config.platformOptions.platform.toString())); + break; + case BAUDRATE: + pair.setValue(ASTUtils.toElement(config.platformOptions.baudRate)); + break; + case BOARD: + pair.setValue(ASTUtils.toElement(config.platformOptions.board)); + break; + case FLASH: + pair.setValue(ASTUtils.toElement(config.platformOptions.flash)); + break; + case PORT: + pair.setValue(ASTUtils.toElement(config.platformOptions.port)); + break; + } + kvp.getPairs().add(pair); + } + e.setKeyvalue(kvp); + if (kvp.getPairs().isEmpty()) return null; + return e; + }, + (config, value, err) -> { + if (value.getLiteral() != null) { + config.platformOptions = new PlatformOptions(); + config.platformOptions.platform = + (Platform) UnionType.PLATFORM_UNION.forName(ASTUtils.elementToSingleString(value)); } else { - config.dockerOptions = new DockerOptions(); - for (KeyValuePair entry : value.getKeyvalue().getPairs()) { - DockerOption option = (DockerOption) DictionaryType.DOCKER_DICT - .forName(entry.getName()); - switch (option) { - case FROM: - config.dockerOptions.from = ASTUtils.elementToSingleString(entry.getValue()); - break; - default: - break; + config.platformOptions = new PlatformOptions(); + for (KeyValuePair entry : value.getKeyvalue().getPairs()) { + PlatformOption option = + (PlatformOption) DictionaryType.PLATFORM_DICT.forName(entry.getName()); + switch (option) { + case NAME: + Platform p = + (Platform) + UnionType.PLATFORM_UNION.forName( + ASTUtils.elementToSingleString(entry.getValue())); + if (p == null) { + String s = + "Unidentified Platform Type, LF supports the following platform types: " + + Arrays.asList(Platform.values()).toString(); + err.reportError(s); + throw new AssertionError(s); } + config.platformOptions.platform = p; + break; + case BAUDRATE: + config.platformOptions.baudRate = ASTUtils.toInteger(entry.getValue()); + break; + case BOARD: + config.platformOptions.board = ASTUtils.elementToSingleString(entry.getValue()); + break; + case FLASH: + config.platformOptions.flash = ASTUtils.toBoolean(entry.getValue()); + break; + case PORT: + config.platformOptions.port = ASTUtils.elementToSingleString(entry.getValue()); + break; + default: + break; } + } + } + }), + + /** Directive to instruct the runtime to collect and print execution statistics. */ + PRINT_STATISTICS( + "print-statistics", + PrimitiveType.BOOLEAN, + Arrays.asList(Target.CPP), + (config) -> ASTUtils.toElement(config.printStatistics), + (config, value, err) -> { + config.printStatistics = ASTUtils.toBoolean(value); + }), + + /** + * Directive for specifying .proto files that need to be compiled and their code included in the + * sources. + */ + PROTOBUFS( + "protobufs", + UnionType.FILE_OR_FILE_ARRAY, + Arrays.asList(Target.C, Target.CCPP, Target.TS, Target.Python), + (config) -> ASTUtils.toElement(config.protoFiles), + (config, value, err) -> { + config.protoFiles = ASTUtils.elementToListOfStrings(value); + }), + + /** Directive to specify that ROS2 specific code is generated, */ + ROS2( + "ros2", + PrimitiveType.BOOLEAN, + List.of(Target.CPP), + (config) -> ASTUtils.toElement(config.ros2), + (config, value, err) -> { + config.ros2 = ASTUtils.toBoolean(value); + }), + + /** Directive to specify additional ROS2 packages that this LF program depends on. */ + ROS2_DEPENDENCIES( + "ros2-dependencies", + ArrayType.STRING_ARRAY, + List.of(Target.CPP), + (config) -> ASTUtils.toElement(config.ros2Dependencies), + (config, value, err) -> { + config.ros2Dependencies = ASTUtils.elementToListOfStrings(value); + }), + + /** Directive for specifying a specific version of the reactor runtime library. */ + RUNTIME_VERSION( + "runtime-version", + PrimitiveType.STRING, + Arrays.asList(Target.CPP), + (config) -> ASTUtils.toElement(config.runtimeVersion), + (config, value, err) -> { + config.runtimeVersion = ASTUtils.elementToSingleString(value); + }), + + /** Directive for specifying a specific runtime scheduler, if supported. */ + SCHEDULER( + "scheduler", + UnionType.SCHEDULER_UNION, + Arrays.asList(Target.C, Target.CCPP, Target.Python), + (config) -> ASTUtils.toElement(config.schedulerType.toString()), + (config, value, err) -> { + config.schedulerType = + (SchedulerOption) + UnionType.SCHEDULER_UNION.forName(ASTUtils.elementToSingleString(value)); + }), + + /** Directive to specify that all code is generated in a single file. */ + SINGLE_FILE_PROJECT( + "single-file-project", + PrimitiveType.BOOLEAN, + List.of(Target.Rust), + (config) -> ASTUtils.toElement(config.singleFileProject), + (config, value, err) -> { + config.singleFileProject = ASTUtils.toBoolean(value); + }), + + /** Directive to indicate whether the runtime should use multi-threading. */ + THREADING( + "threading", + PrimitiveType.BOOLEAN, + List.of(Target.C, Target.CCPP, Target.Python), + (config) -> ASTUtils.toElement(config.threading), + (config, value, err) -> { + config.threading = ASTUtils.toBoolean(value); + }), + + /** Directive to specify the number of worker threads used by the runtime. */ + WORKERS( + "workers", + PrimitiveType.NON_NEGATIVE_INTEGER, + List.of(Target.C, Target.CCPP, Target.Python, Target.CPP, Target.Rust), + (config) -> ASTUtils.toElement(config.workers), + (config, value, err) -> { + config.workers = ASTUtils.toInteger(value); + }), + + /** Directive to specify the execution timeout. */ + TIMEOUT( + "timeout", + PrimitiveType.TIME_VALUE, + Target.ALL, + (config) -> ASTUtils.toElement(config.timeout), + (config, value, err) -> { + config.timeout = ASTUtils.toTimeValue(value); + }, + (config, value, err) -> { + config.timeout = ASTUtils.toTimeValue(value); + }), + + /** Directive to enable tracing. */ + TRACING( + "tracing", + UnionType.TRACING_UNION, + Arrays.asList(Target.C, Target.CCPP, Target.CPP, Target.Python), + (config) -> { + if (config.tracing == null) { + return null; + } else if (config.tracing.equals(new TracingOptions())) { + // default values + return ASTUtils.toElement(true); + } else { + Element e = LfFactory.eINSTANCE.createElement(); + KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); + for (TracingOption opt : TracingOption.values()) { + KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); + pair.setName(opt.toString()); + switch (opt) { + case TRACE_FILE_NAME: + if (config.tracing.traceFileName == null) continue; + pair.setValue(ASTUtils.toElement(config.tracing.traceFileName)); + } + kvp.getPairs().add(pair); + } + e.setKeyvalue(kvp); + if (kvp.getPairs().isEmpty()) return null; + return e; + } + }, + (config, value, err) -> { + if (value.getLiteral() != null) { + if (ASTUtils.toBoolean(value)) { + config.tracing = new TracingOptions(); + } else { + config.tracing = null; + } + } else { + config.tracing = new TracingOptions(); + for (KeyValuePair entry : value.getKeyvalue().getPairs()) { + TracingOption option = + (TracingOption) DictionaryType.TRACING_DICT.forName(entry.getName()); + switch (option) { + case TRACE_FILE_NAME: + config.tracing.traceFileName = ASTUtils.elementToSingleString(entry.getValue()); + break; + default: + break; + } + } + } + }), + + /** + * Directive to let the runtime export its internal dependency graph. + * + *

This is a debugging feature and currently only used for C++ and Rust programs. + */ + EXPORT_DEPENDENCY_GRAPH( + "export-dependency-graph", + PrimitiveType.BOOLEAN, + List.of(Target.CPP, Target.Rust), + (config) -> ASTUtils.toElement(config.exportDependencyGraph), + (config, value, err) -> { + config.exportDependencyGraph = ASTUtils.toBoolean(value); + }), + + /** + * Directive to let the runtime export the program structure to a yaml file. + * + *

This is a debugging feature and currently only used for C++ programs. + */ + EXPORT_TO_YAML( + "export-to-yaml", + PrimitiveType.BOOLEAN, + List.of(Target.CPP), + (config) -> ASTUtils.toElement(config.exportToYaml), + (config, value, err) -> { + config.exportToYaml = ASTUtils.toBoolean(value); + }), + + /** + * List of module files to link into the crate as top-level. For instance, a {@code target Rust { + * rust-modules: [ "foo.rs" ] }} will cause the file to be copied into the generated project, and + * the generated {@code main.rs} will include it with a {@code mod foo;}. If one of the paths is a + * directory, it must contain a {@code mod.rs} file, and all its contents are copied. + */ + RUST_INCLUDE( + "rust-include", + UnionType.FILE_OR_FILE_ARRAY, + List.of(Target.Rust), + (config) -> { + // do not check paths here, and simply copy the absolute path over + List paths = config.rust.getRustTopLevelModules(); + if (paths.isEmpty()) return null; + else if (paths.size() == 1) { + return ASTUtils.toElement(paths.get(0).toString()); + } else { + Element e = LfFactory.eINSTANCE.createElement(); + Array arr = LfFactory.eINSTANCE.createArray(); + for (Path p : paths) { + arr.getElements().add(ASTUtils.toElement(p.toString())); + } + e.setArray(arr); + return e; + } + }, + (config, value, err) -> { + Path referencePath; + try { + referencePath = FileUtil.toPath(value.eResource().getURI()).toAbsolutePath(); + } catch (IOException e) { + err.reportError(value, "Invalid path? " + e.getMessage()); + throw new RuntimeIOException(e); } - } - - /** - * String representation of this target property. - */ - public final String description; - - /** - * List of targets that support this property. If a property is used for - * a target that does not support it, a warning reported during - * validation. - */ - public final List supportedBy; - - /** - * The type of values that can be assigned to this property. - */ - public final TargetPropertyType type; - /** - * Function that given a configuration object and an Element AST node - * sets the configuration. It is assumed that validation already - * occurred, so this code should be straightforward. - */ - public final PropertyParser setter; + // we'll resolve relative paths to check that the files + // are as expected. - /** - * Function that given a configuration object and an Element AST node - * sets the configuration. It is assumed that validation already - * occurred, so this code should be straightforward. - */ - public final PropertyParser updater; + if (value.getLiteral() != null) { + Path resolved = referencePath.resolveSibling(StringUtil.removeQuotes(value.getLiteral())); + + config.rust.addAndCheckTopLevelModule(resolved, value, err); + } else if (value.getArray() != null) { + for (Element element : value.getArray().getElements()) { + String literal = StringUtil.removeQuotes(element.getLiteral()); + Path resolved = referencePath.resolveSibling(literal); + config.rust.addAndCheckTopLevelModule(resolved, element, err); + } + } + }), + + /** Directive for specifying Cargo features of the generated program to enable. */ + CARGO_FEATURES( + "cargo-features", + ArrayType.STRING_ARRAY, + List.of(Target.Rust), + (config) -> ASTUtils.toElement(config.rust.getCargoFeatures()), + (config, value, err) -> { + config.rust.setCargoFeatures(ASTUtils.elementToListOfStrings(value)); + }), + + /** + * Dependency specifications for Cargo. This property looks like this: + * + *

{@code
+   * cargo-dependencies: {
+   *    // Name-of-the-crate: "version"
+   *    rand: "0.8",
+   *    // Equivalent to using an explicit map:
+   *    rand: {
+   *      version: "0.8"
+   *    },
+   *    // The map allows specifying more details
+   *    rand: {
+   *      // A path to a local unpublished crate.
+   *      // Note 'path' is mutually exclusive with 'version'.
+   *      path: "/home/me/Git/local-rand-clone"
+   *    },
+   *    rand: {
+   *      version: "0.8",
+   *      // you can specify cargo features
+   *      features: ["some-cargo-feature",]
+   *    }
+   * }
+   * }
+ */ + CARGO_DEPENDENCIES( + "cargo-dependencies", + CargoDependenciesPropertyType.INSTANCE, + List.of(Target.Rust), + (config) -> { + var deps = config.rust.getCargoDependencies(); + if (deps.size() == 0) return null; + else { + Element e = LfFactory.eINSTANCE.createElement(); + KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); + for (var ent : deps.entrySet()) { + KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); + pair.setName(ent.getKey()); + pair.setValue(CargoDependencySpec.extractSpec(ent.getValue())); + kvp.getPairs().add(pair); + } + e.setKeyvalue(kvp); + return e; + } + }, + (config, value, err) -> { + config.rust.setCargoDependencies(CargoDependencySpec.parseAll(value)); + }), + + /** + * Directs the C or Python target to include the associated C file used for setting up federated + * execution before processing the first tag. + */ + FED_SETUP( + "_fed_setup", + PrimitiveType.FILE, + Arrays.asList(Target.C, Target.CCPP, Target.Python), + (config) -> ASTUtils.toElement(config.fedSetupPreamble), + (config, value, err) -> + config.fedSetupPreamble = StringUtil.removeQuotes(ASTUtils.elementToSingleString(value))); + + /** Update {@code config}.dockerOptions based on value. */ + private static void setDockerProperty(TargetConfig config, Element value) { + if (value.getLiteral() != null) { + if (ASTUtils.toBoolean(value)) { + config.dockerOptions = new DockerOptions(); + } else { + config.dockerOptions = null; + } + } else { + config.dockerOptions = new DockerOptions(); + for (KeyValuePair entry : value.getKeyvalue().getPairs()) { + DockerOption option = (DockerOption) DictionaryType.DOCKER_DICT.forName(entry.getName()); + switch (option) { + case FROM: + config.dockerOptions.from = ASTUtils.elementToSingleString(entry.getValue()); + break; + default: + break; + } + } + } + } + + /** String representation of this target property. */ + public final String description; + + /** + * List of targets that support this property. If a property is used for a target that does not + * support it, a warning reported during validation. + */ + public final List supportedBy; + + /** The type of values that can be assigned to this property. */ + public final TargetPropertyType type; + + /** + * Function that given a configuration object and an Element AST node sets the configuration. It + * is assumed that validation already occurred, so this code should be straightforward. + */ + public final PropertyParser setter; + + /** + * Function that given a configuration object and an Element AST node sets the configuration. It + * is assumed that validation already occurred, so this code should be straightforward. + */ + public final PropertyParser updater; + + @FunctionalInterface + private interface PropertyParser { + + /** + * Parse the given element into the given target config. May use the error reporter to report + * format errors. + */ + void parseIntoTargetConfig(TargetConfig config, Element element, ErrorReporter err); + } + + public final PropertyGetter getter; + + @FunctionalInterface + private interface PropertyGetter { + + /** + * Read this property from the target config and build an element which represents it for the + * AST. May return null if the given value of this property is the same as the default. + */ + Element getPropertyElement(TargetConfig config); + } + + /** + * Private constructor for target properties. + * + * @param description String representation of this property. + * @param type The type that values assigned to this property should conform to. + * @param supportedBy List of targets that support this property. + * @param setter Function for configuration updates. + */ + TargetProperty( + String description, + TargetPropertyType type, + List supportedBy, + PropertyGetter getter, + PropertyParser setter) { + this.description = description; + this.type = type; + this.supportedBy = supportedBy; + this.getter = getter; + this.setter = setter; + this.updater = setter; // (Re)set by default + } + + /** + * Private constructor for target properties. This will take an additional {@code updater}, which + * will be used to merge target properties from imported resources. + * + * @param description String representation of this property. + * @param type The type that values assigned to this property should conform to. + * @param supportedBy List of targets that support this property. + * @param setter Function for setting configuration values. + * @param updater Function for updating configuration values. + */ + TargetProperty( + String description, + TargetPropertyType type, + List supportedBy, + PropertyGetter getter, + PropertyParser setter, + PropertyParser updater) { + this.description = description; + this.type = type; + this.supportedBy = supportedBy; + this.getter = getter; + this.setter = setter; + this.updater = updater; + } + + /** + * Return the name of the property in lingua franca. This is suitable for use as a key in a target + * properties block. It may be an invalid identifier in other languages (may contains dashes + * {@code -}). + */ + public String getDisplayName() { + return description; + } + + /** + * Set the given configuration using the given target properties. + * + * @param config The configuration object to update. + * @param properties AST node that holds all the target properties. + * @param err Error reporter on which property format errors will be reported + */ + public static void set(TargetConfig config, List properties, ErrorReporter err) { + properties.forEach( + property -> { + TargetProperty p = forName(property.getName()); + if (p != null) { + // Mark the specified target property as set by the user + config.setByUser.add(p); + try { + p.setter.parseIntoTargetConfig(config, property.getValue(), err); + } catch (InvalidLfSourceException e) { + err.reportError(e.getNode(), e.getProblem()); + } + } + }); + } + + /** + * Extracts all properties as a list of key-value pairs from a TargetConfig. Only extracts + * properties explicitly set by user. + * + * @param config The TargetConfig to extract from. + * @return The extracted properties. + */ + public static List extractProperties(TargetConfig config) { + var res = new LinkedList(); + for (TargetProperty p : config.setByUser) { + KeyValuePair kv = LfFactory.eINSTANCE.createKeyValuePair(); + kv.setName(p.toString()); + kv.setValue(p.getter.getPropertyElement(config)); + if (kv.getValue() != null) res.add(kv); + } + return res; + } + + /** + * Constructs a TargetDecl by extracting the fields of the given TargetConfig. + * + * @param target The target to generate for. + * @param config The TargetConfig to extract from. + * @return A generated TargetDecl. + */ + public static TargetDecl extractTargetDecl(Target target, TargetConfig config) { + TargetDecl decl = LfFactory.eINSTANCE.createTargetDecl(); + KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); + for (KeyValuePair p : extractProperties(config)) { + kvp.getPairs().add(p); + } + decl.setName(target.toString()); + decl.setConfig(kvp); + return decl; + } + + /** + * Update the given configuration using the given target properties. + * + * @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) { + properties.forEach( + property -> { + TargetProperty p = forName(property.getName()); + if (p != null) { + // Mark the specified target property as set by the user + config.setByUser.add(p); + p.updater.parseIntoTargetConfig(config, property.getValue(), err); + } + }); + } + + /** + * Update one of the target properties, given by 'propertyName'. For convenience, a list of target + * properties (e.g., taken from a file or resource) can be passed without any filtering. This + * function will do nothing if the list of target properties doesn't include the property given by + * 'propertyName'. + * + * @param config The target config to apply the update to. + * @param property The target property. + * @param properties AST node that holds all the target properties. + * @param err Error reporter on which property format errors will be reported + */ + public static void updateOne( + TargetConfig config, + TargetProperty property, + List properties, + ErrorReporter err) { + properties.stream() + .filter(p -> p.getName().equals(property.getDisplayName())) + .findFirst() + .map(KeyValuePair::getValue) + .ifPresent(value -> property.updater.parseIntoTargetConfig(config, value, err)); + } + + /** + * Return the entry that matches the given string. + * + * @param name The string to match against. + */ + public static TargetProperty forName(String name) { + return Target.match(name, TargetProperty.values()); + } + + /** + * Return a list with all target properties. + * + * @return All existing target properties. + */ + public static List getOptions() { + return Arrays.asList(TargetProperty.values()); + } + + /** Return the description. */ + @Override + public String toString() { + return this.description; + } + + // Inner classes for the various supported types. + + /** Dictionary type that allows for keys that will be interpreted as strings and string values. */ + public enum StringDictionaryType implements TargetPropertyType { + COMPILE_DEFINITION(); - @FunctionalInterface - private interface PropertyParser { + @Override + public boolean validate(Element e) { + if (e.getKeyvalue() != null) { + return true; + } + return false; + } - /** - * Parse the given element into the given target config. - * May use the error reporter to report format errors. - */ - void parseIntoTargetConfig(TargetConfig config, Element element, ErrorReporter err); + @Override + public void check(Element e, String name, LFValidator v) { + KeyValuePairs kv = e.getKeyvalue(); + if (kv == null) { + TargetPropertyType.produceError(name, this.toString(), v); + } else { + for (KeyValuePair pair : kv.getPairs()) { + String key = pair.getName(); + Element val = pair.getValue(); + + // Make sure the type is string + PrimitiveType.STRING.check(val, name + "." + key, v); + } + } } + } - public final PropertyGetter getter; + /** Interface for dictionary elements. It associates an entry with a type. */ + public interface DictionaryElement { - @FunctionalInterface - private interface PropertyGetter { + TargetPropertyType getType(); + } - /** - * Read this property from the target config and build an element which represents it for the AST. - * May return null if the given value of this property is the same as the default. - */ - Element getPropertyElement(TargetConfig config); - } + /** + * A dictionary type with a predefined set of possible keys and assignable types. + * + * @author Marten Lohstroh + */ + public enum DictionaryType implements TargetPropertyType { + CLOCK_SYNC_OPTION_DICT(Arrays.asList(ClockSyncOption.values())), + DOCKER_DICT(Arrays.asList(DockerOption.values())), + PLATFORM_DICT(Arrays.asList(PlatformOption.values())), + COORDINATION_OPTION_DICT(Arrays.asList(CoordinationOption.values())), + TRACING_DICT(Arrays.asList(TracingOption.values())); - /** - * Private constructor for target properties. - * - * @param description String representation of this property. - * @param type The type that values assigned to this property - * should conform to. - * @param supportedBy List of targets that support this property. - * @param setter Function for configuration updates. - */ - TargetProperty(String description, TargetPropertyType type, - List supportedBy, - PropertyGetter getter, - PropertyParser setter) { - this.description = description; - this.type = type; - this.supportedBy = supportedBy; - this.getter = getter; - this.setter = setter; - this.updater = setter; // (Re)set by default - } + /** The keys and assignable types that are allowed in this dictionary. */ + public List options; /** - * Private constructor for target properties. This will take an additional - * {@code updater}, which will be used to merge target properties from imported resources. + * A dictionary type restricted to sets of predefined keys and types of values. * - * @param description String representation of this property. - * @param type The type that values assigned to this property - * should conform to. - * @param supportedBy List of targets that support this property. - * @param setter Function for setting configuration values. - * @param updater Function for updating configuration values. - */ - TargetProperty(String description, TargetPropertyType type, - List supportedBy, - PropertyGetter getter, - PropertyParser setter, - PropertyParser updater) { - this.description = description; - this.type = type; - this.supportedBy = supportedBy; - this.getter = getter; - this.setter = setter; - this.updater = updater; - } - - /** - * Return the name of the property in lingua franca. This - * is suitable for use as a key in a target properties block. - * It may be an invalid identifier in other languages (may - * contains dashes {@code -}). + * @param options The dictionary elements allowed by this type. */ - public String getDisplayName() { - return description; + private DictionaryType(List options) { + this.options = options; } /** - * Set the given configuration using the given target properties. + * Return the dictionary element of which the key matches the given string. * - * @param config The configuration object to update. - * @param properties AST node that holds all the target properties. - * @param err Error reporter on which property format errors will be reported + * @param name The string to match against. + * @return The matching dictionary element (or null if there is none). */ - public static void set(TargetConfig config, List properties, ErrorReporter err) { - properties.forEach(property -> { - TargetProperty p = forName(property.getName()); - if (p != null) { - // Mark the specified target property as set by the user - config.setByUser.add(p); - try { - p.setter.parseIntoTargetConfig(config, property.getValue(), err); - } catch (InvalidLfSourceException e) { - err.reportError(e.getNode(), e.getProblem()); - } - } - }); + public DictionaryElement forName(String name) { + return Target.match(name, options); } - /** - * Extracts all properties as a list of key-value pairs from a TargetConfig. Only extracts properties explicitly set by user. - * @param config The TargetConfig to extract from. - * @return The extracted properties. - */ - public static List extractProperties(TargetConfig config) { - var res = new LinkedList(); - for (TargetProperty p : config.setByUser) { - KeyValuePair kv = LfFactory.eINSTANCE.createKeyValuePair(); - kv.setName(p.toString()); - kv.setValue(p.getter.getPropertyElement(config)); - if (kv.getValue() != null) - res.add(kv); + /** Recursively check that the passed in element conforms to the rules of this dictionary. */ + @Override + public void check(Element e, String name, LFValidator v) { + KeyValuePairs kv = e.getKeyvalue(); + if (kv == null) { + TargetPropertyType.produceError(name, this.toString(), v); + } else { + for (KeyValuePair pair : kv.getPairs()) { + String key = pair.getName(); + Element val = pair.getValue(); + Optional match = + this.options.stream() + .filter(element -> key.equalsIgnoreCase(element.toString())) + .findAny(); + if (match.isPresent()) { + // Make sure the type is correct, too. + TargetPropertyType type = match.get().getType(); + type.check(val, name + "." + key, v); + } else { + // No match found; report error. + TargetPropertyType.produceError(name, this.toString(), v); + } } - return res; + } } - /** - * Constructs a TargetDecl by extracting the fields of the given TargetConfig. - * @param target The target to generate for. - * @param config The TargetConfig to extract from. - * @return A generated TargetDecl. - */ - public static TargetDecl extractTargetDecl(Target target, TargetConfig config) { - TargetDecl decl = LfFactory.eINSTANCE.createTargetDecl(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (KeyValuePair p : extractProperties(config)) { - kvp.getPairs().add(p); - } - decl.setName(target.toString()); - decl.setConfig(kvp); - return decl; + /** Return true if the given element represents a dictionary, false otherwise. */ + @Override + public boolean validate(Element e) { + if (e.getKeyvalue() != null) { + return true; + } + return false; } - /** - * Update the given configuration using the given target properties. - * - * @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) { - properties.forEach(property -> { - TargetProperty p = forName(property.getName()); - if (p != null) { - // Mark the specified target property as set by the user - config.setByUser.add(p); - p.updater.parseIntoTargetConfig(config, property.getValue(), err); - } - }); + /** Return a human-readable description of this type. */ + @Override + public String toString() { + return "a dictionary with one or more of the following keys: " + + options.stream().map(option -> option.toString()).collect(Collectors.joining(", ")); } - - /** - * Update one of the target properties, given by 'propertyName'. - * For convenience, a list of target properties (e.g., taken from - * a file or resource) can be passed without any filtering. This - * function will do nothing if the list of target properties doesn't - * include the property given by 'propertyName'. + } + /** + * A type that can assume one of several types. + * + * @author Marten Lohstroh + */ + public enum UnionType implements TargetPropertyType { + STRING_OR_STRING_ARRAY(Arrays.asList(PrimitiveType.STRING, ArrayType.STRING_ARRAY), null), + PLATFORM_STRING_OR_DICTIONARY( + Arrays.asList(PrimitiveType.STRING, DictionaryType.PLATFORM_DICT), null), + FILE_OR_FILE_ARRAY(Arrays.asList(PrimitiveType.FILE, ArrayType.FILE_ARRAY), null), + BUILD_TYPE_UNION(Arrays.asList(BuildType.values()), null), + COORDINATION_UNION(Arrays.asList(CoordinationType.values()), CoordinationType.CENTRALIZED), + SCHEDULER_UNION(Arrays.asList(SchedulerOption.values()), SchedulerOption.getDefault()), + LOGGING_UNION(Arrays.asList(LogLevel.values()), LogLevel.INFO), + PLATFORM_UNION(Arrays.asList(Platform.values()), Platform.AUTO), + CLOCK_SYNC_UNION(Arrays.asList(ClockSyncMode.values()), ClockSyncMode.INIT), + DOCKER_UNION(Arrays.asList(PrimitiveType.BOOLEAN, DictionaryType.DOCKER_DICT), null), + TRACING_UNION(Arrays.asList(PrimitiveType.BOOLEAN, DictionaryType.TRACING_DICT), null); + + /** The constituents of this type union. */ + public final List> options; + + /** The default type, if there is one. */ + private final Enum defaultOption; + + /** + * Private constructor for creating unions types. * - * @param config The target config to apply the update to. - * @param property The target property. - * @param properties AST node that holds all the target properties. - * @param err Error reporter on which property format errors will be reported + * @param options The types that that are part of the union. + * @param defaultOption The default type. */ - public static void updateOne(TargetConfig config, TargetProperty property, List properties, ErrorReporter err) { - properties.stream() - .filter(p -> p.getName().equals(property.getDisplayName())) - .findFirst() - .map(KeyValuePair::getValue) - .ifPresent(value -> property.updater.parseIntoTargetConfig( - config, - value, - err - )); + private UnionType(List> options, Enum defaultOption) { + this.options = options; + this.defaultOption = defaultOption; } /** - * Return the entry that matches the given string. - * @param name The string to match against. - */ - public static TargetProperty forName(String name) { - return Target.match(name, TargetProperty.values()); - } - - /** - * Return a list with all target properties. + * Return the type among those in this type union that matches the given name. * - * @return All existing target properties. + * @param name The string to match against. + * @return The matching dictionary element (or null if there is none). */ - public static List getOptions() { - return Arrays.asList(TargetProperty.values()); + public Enum forName(String name) { + return Target.match(name, options); } - /** - * Return the description. - */ + /** Recursively check that the passed in element conforms to the rules of this union. */ @Override - public String toString() { - return this.description; + public void check(Element e, String name, LFValidator v) { + Optional> match = this.match(e); + if (match.isPresent()) { + // Go deeper if the element is an array or dictionary. + Enum type = match.get(); + if (type instanceof DictionaryType) { + ((DictionaryType) type).check(e, name, v); + } else if (type instanceof ArrayType) { + ((ArrayType) type).check(e, name, v); + } else if (type instanceof PrimitiveType) { + ((PrimitiveType) type).check(e, name, v); + } else if (!(type instanceof Enum)) { + throw new RuntimeException("Encountered an unknown type."); + } + } else { + // No match found; report error. + TargetPropertyType.produceError(name, this.toString(), v); + } } - // Inner classes for the various supported types. - /** - * Dictionary type that allows for keys that will be interpreted as strings - * and string values. + * Internal method for matching a given element against the allowable types. + * + * @param e AST node that represents the value of a target property. + * @return The matching type wrapped in an Optional object. */ - public enum StringDictionaryType implements TargetPropertyType { - COMPILE_DEFINITION(); - @Override - public boolean validate(Element e) { - if (e.getKeyvalue() != null) { - return true; - } - return false; - } - - @Override - public void check(Element e, String name, LFValidator v) { - KeyValuePairs kv = e.getKeyvalue(); - if (kv == null) { - TargetPropertyType.produceError(name, this.toString(), v); - } else { - for (KeyValuePair pair : kv.getPairs()) { - String key = pair.getName(); - Element val = pair.getValue(); - - // Make sure the type is string - PrimitiveType.STRING.check(val, name + "." + key, v); + private Optional> match(Element e) { + return this.options.stream() + .filter( + option -> { + if (option instanceof TargetPropertyType) { + return ((TargetPropertyType) option).validate(e); + } else { + return ASTUtils.elementToSingleString(e).equalsIgnoreCase(option.toString()); } - } - - } + }) + .findAny(); } /** - * Interface for dictionary elements. It associates an entry with a type. + * Return true if this union has an option that matches the given element. + * + * @param e The element to match against this type. */ - public interface DictionaryElement { - - TargetPropertyType getType(); + @Override + public boolean validate(Element e) { + if (this.match(e).isPresent()) { + return true; + } + return false; } /** - * A dictionary type with a predefined set of possible keys and assignable - * types. - * - * @author Marten Lohstroh - * + * Return a human-readable description of this type. If three is a default option, then indicate + * it. */ - public enum DictionaryType implements TargetPropertyType { - CLOCK_SYNC_OPTION_DICT(Arrays.asList(ClockSyncOption.values())), - DOCKER_DICT(Arrays.asList(DockerOption.values())), - PLATFORM_DICT(Arrays.asList(PlatformOption.values())), - COORDINATION_OPTION_DICT(Arrays.asList(CoordinationOption.values())), - TRACING_DICT(Arrays.asList(TracingOption.values())); - - /** - * The keys and assignable types that are allowed in this dictionary. - */ - public List options; - - /** - * A dictionary type restricted to sets of predefined keys and types of - * values. - * - * @param options The dictionary elements allowed by this type. - */ - private DictionaryType(List options) { - this.options = options; - } - - /** - * Return the dictionary element of which the key matches the given - * string. - * - * @param name The string to match against. - * @return The matching dictionary element (or null if there is none). - */ - public DictionaryElement forName(String name) { - return Target.match(name, options); - } - - /** - * Recursively check that the passed in element conforms to the rules of - * this dictionary. - */ - @Override - public void check(Element e, String name, LFValidator v) { - KeyValuePairs kv = e.getKeyvalue(); - if (kv == null) { - TargetPropertyType.produceError(name, this.toString(), v); - } else { - for (KeyValuePair pair : kv.getPairs()) { - String key = pair.getName(); - Element val = pair.getValue(); - Optional match = this.options.stream() - .filter(element -> key.equalsIgnoreCase(element.toString())).findAny(); - if (match.isPresent()) { - // Make sure the type is correct, too. - TargetPropertyType type = match.get().getType(); - type.check(val, name + "." + key, v); + @Override + public String toString() { + return "one of the following: " + + options.stream() + .map( + option -> { + if (option == this.defaultOption) { + return option.toString() + " (default)"; } else { - // No match found; report error. - TargetPropertyType.produceError(name, - this.toString(), v); + return option.toString(); } - } - } - } - - /** - * Return true if the given element represents a dictionary, false - * otherwise. - */ - @Override - public boolean validate(Element e) { - if (e.getKeyvalue() != null) { - return true; - } - return false; - } - - /** - * Return a human-readable description of this type. - */ - @Override - public String toString() { - return "a dictionary with one or more of the following keys: " - + options.stream().map(option -> option.toString()) - .collect(Collectors.joining(", ")); - } + }) + .collect(Collectors.joining(", ")); } - /** - * A type that can assume one of several types. - * - * @author Marten Lohstroh - * - */ - public enum UnionType implements TargetPropertyType { - STRING_OR_STRING_ARRAY( - Arrays.asList(PrimitiveType.STRING, ArrayType.STRING_ARRAY), - null), - PLATFORM_STRING_OR_DICTIONARY( - Arrays.asList(PrimitiveType.STRING, DictionaryType.PLATFORM_DICT), - null), - FILE_OR_FILE_ARRAY( - Arrays.asList(PrimitiveType.FILE, ArrayType.FILE_ARRAY), null), - BUILD_TYPE_UNION(Arrays.asList(BuildType.values()), null), - COORDINATION_UNION(Arrays.asList(CoordinationType.values()), - CoordinationType.CENTRALIZED), - SCHEDULER_UNION(Arrays.asList(SchedulerOption.values()), SchedulerOption.getDefault()), - LOGGING_UNION(Arrays.asList(LogLevel.values()), LogLevel.INFO), - PLATFORM_UNION(Arrays.asList(Platform.values()), Platform.AUTO), - CLOCK_SYNC_UNION(Arrays.asList(ClockSyncMode.values()), - ClockSyncMode.INIT), - DOCKER_UNION(Arrays.asList(PrimitiveType.BOOLEAN, DictionaryType.DOCKER_DICT), - null), - TRACING_UNION(Arrays.asList(PrimitiveType.BOOLEAN, DictionaryType.TRACING_DICT), - null); - - /** - * The constituents of this type union. - */ - public final List> options; - - /** - * The default type, if there is one. - */ - private final Enum defaultOption; - - /** - * Private constructor for creating unions types. - * - * @param options The types that that are part of the union. - * @param defaultOption The default type. - */ - private UnionType(List> options, Enum defaultOption) { - this.options = options; - this.defaultOption = defaultOption; - } - - /** - * Return the type among those in this type union that matches the given - * name. - * - * @param name The string to match against. - * @return The matching dictionary element (or null if there is none). - */ - public Enum forName(String name) { - return Target.match(name, options); - } - - /** - * Recursively check that the passed in element conforms to the rules of - * this union. - */ - @Override - public void check(Element e, String name, LFValidator v) { - Optional> match = this.match(e); - if (match.isPresent()) { - // Go deeper if the element is an array or dictionary. - Enum type = match.get(); - if (type instanceof DictionaryType) { - ((DictionaryType) type).check(e, name, v); - } else if (type instanceof ArrayType) { - ((ArrayType) type).check(e, name, v); - } else if (type instanceof PrimitiveType) { - ((PrimitiveType) type).check(e, name, v); - } else if (!(type instanceof Enum)) { - throw new RuntimeException("Encountered an unknown type."); - } - } else { - // No match found; report error. - TargetPropertyType.produceError(name, this.toString(), v); - } - } - - /** - * Internal method for matching a given element against the allowable - * types. - * - * @param e AST node that represents the value of a target property. - * @return The matching type wrapped in an Optional object. - */ - private Optional> match(Element e) { - return this.options.stream().filter(option -> { - if (option instanceof TargetPropertyType) { - return ((TargetPropertyType) option).validate(e); - } else { - return ASTUtils.elementToSingleString(e) - .equalsIgnoreCase(option.toString()); - } - }).findAny(); - } + } - /** - * Return true if this union has an option that matches the given - * element. - * @param e The element to match against this type. - */ - @Override - public boolean validate(Element e) { - if (this.match(e).isPresent()) { - return true; - } - return false; - } - - /** - * Return a human-readable description of this type. If three is a - * default option, then indicate it. - */ - @Override - public String toString() { - return "one of the following: " + options.stream().map(option -> { - if (option == this.defaultOption) { - return option.toString() + " (default)"; - } else { - return option.toString(); - } - }).collect(Collectors.joining(", ")); - } + /** + * An array type of which the elements confirm to a given type. + * + * @author Marten Lohstroh + */ + public enum ArrayType implements TargetPropertyType { + STRING_ARRAY(PrimitiveType.STRING), + FILE_ARRAY(PrimitiveType.FILE); - } + /** Type parameter of this array type. */ + public TargetPropertyType type; /** - * An array type of which the elements confirm to a given type. - * - * @author Marten Lohstroh + * Private constructor to create a new array type. * + * @param type The type of elements in the array. */ - public enum ArrayType implements TargetPropertyType { - STRING_ARRAY(PrimitiveType.STRING), - FILE_ARRAY(PrimitiveType.FILE); - - /** - * Type parameter of this array type. - */ - public TargetPropertyType type; - - /** - * Private constructor to create a new array type. - * - * @param type The type of elements in the array. - */ - private ArrayType(TargetPropertyType type) { - this.type = type; - } - - /** - * Check that the passed in element represents an array and ensure that - * its elements are all of the correct type. - */ - @Override - public void check(Element e, String name, LFValidator v) { - Array array = e.getArray(); - if (array == null) { - TargetPropertyType.produceError(name, this.toString(), v); - } else { - List elements = array.getElements(); - for (int i = 0; i < elements.size(); i++) { - this.type.check(elements.get(i), name + "[" + i + "]", v); - } - } - } - - /** - * Return true of the given element is an array. - */ - @Override - public boolean validate(Element e) { - if (e.getArray() != null) { - return true; - } - return false; - } - - /** - * Return a human-readable description of this type. - */ - @Override - public String toString() { - return "an array of which each element is " + this.type.toString(); - } + private ArrayType(TargetPropertyType type) { + this.type = type; } /** - * Enumeration of Cmake build types. These are also mapped - * to Cargo profiles for the Rust target (see {@link org.lflang.generator.rust.RustTargetConfig}) - * - * @author Christian Menard + * Check that the passed in element represents an array and ensure that its elements are all of + * the correct type. */ - public enum BuildType { - RELEASE("Release"), - DEBUG("Debug"), - TEST("Test"), - REL_WITH_DEB_INFO("RelWithDebInfo"), - MIN_SIZE_REL("MinSizeRel"); - - /** - * Alias used in toString method. - */ - private final String alias; - - /** - * Private constructor for Cmake build types. - */ - BuildType(String alias) { - this.alias = alias; + @Override + public void check(Element e, String name, LFValidator v) { + Array array = e.getArray(); + if (array == null) { + TargetPropertyType.produceError(name, this.toString(), v); + } else { + List elements = array.getElements(); + for (int i = 0; i < elements.size(); i++) { + this.type.check(elements.get(i), name + "[" + i + "]", v); } + } + } - /** - * Return the alias. - */ - @Override - public String toString() { - return this.alias; - } + /** Return true of the given element is an array. */ + @Override + public boolean validate(Element e) { + if (e.getArray() != null) { + return true; + } + return false; } - /** - * Enumeration of coordination types. - * - * @author Marten Lohstroh - */ - public enum CoordinationType { - CENTRALIZED, DECENTRALIZED; - - /** - * Return the name in lower case. - */ - @Override - public String toString() { - return this.name().toLowerCase(); - } + /** Return a human-readable description of this type. */ + @Override + public String toString() { + return "an array of which each element is " + this.type.toString(); + } + } + + /** + * Enumeration of Cmake build types. These are also mapped to Cargo profiles for the Rust target + * (see {@link org.lflang.generator.rust.RustTargetConfig}) + * + * @author Christian Menard + */ + public enum BuildType { + RELEASE("Release"), + DEBUG("Debug"), + TEST("Test"), + REL_WITH_DEB_INFO("RelWithDebInfo"), + MIN_SIZE_REL("MinSizeRel"); + + /** Alias used in toString method. */ + private final String alias; + + /** Private constructor for Cmake build types. */ + BuildType(String alias) { + this.alias = alias; + } + + /** Return the alias. */ + @Override + public String toString() { + return this.alias; + } + } + + /** + * Enumeration of coordination types. + * + * @author Marten Lohstroh + */ + public enum CoordinationType { + CENTRALIZED, + DECENTRALIZED; + + /** Return the name in lower case. */ + @Override + public String toString() { + return this.name().toLowerCase(); + } + } + + /** + * Enumeration of clock synchronization modes. + * + *
    + *
  • OFF: The clock synchronization is universally off. + *
  • STARTUP: Clock synchronization occurs at startup only. + *
  • ON: Clock synchronization occurs at startup and at runtime. + *
+ * + * @author Edward A. Lee + */ + public enum ClockSyncMode { + OFF, + INIT, + ON; // TODO Discuss initial in now a mode keyword (same as startup) and cannot be used as target + // property value, thus changed it to init + // FIXME I could not test if this change breaks anything + + /** Return the name in lower case. */ + @Override + public String toString() { + return this.name().toLowerCase(); } + } + /** + * An interface for types associated with target properties. + * + * @author Marten Lohstroh + */ + public interface TargetPropertyType { /** - * Enumeration of clock synchronization modes. + * Return true if the the given Element is a valid instance of this type. * - *
    - *
  • OFF: The clock synchronization is universally off.
  • - *
  • STARTUP: Clock synchronization occurs at startup only.
  • - *
  • ON: Clock synchronization occurs at startup and at runtime.
  • - *
- * - * @author Edward A. Lee + * @param e The Element to validate. + * @return True if the element conforms to this type, false otherwise. */ - public enum ClockSyncMode { - OFF, INIT, ON; // TODO Discuss initial in now a mode keyword (same as startup) and cannot be used as target property value, thus changed it to init - // FIXME I could not test if this change breaks anything - - /** - * Return the name in lower case. - */ - @Override - public String toString() { - return this.name().toLowerCase(); - } - } + public boolean validate(Element e); /** - * An interface for types associated with target properties. + * Check (recursively) the given Element against its associated type(s) and add found problems + * to the given list of errors. * - * @author Marten Lohstroh + * @param e The Element to type check. + * @param name The name of the target property. + * @param v A reference to the validator to report errors to. */ - public interface TargetPropertyType { - - /** - * Return true if the the given Element is a valid instance of this - * type. - * - * @param e The Element to validate. - * @return True if the element conforms to this type, false otherwise. - */ - public boolean validate(Element e); - - /** - * Check (recursively) the given Element against its associated type(s) - * and add found problems to the given list of errors. - * - * @param e The Element to type check. - * @param name The name of the target property. - * @param v A reference to the validator to report errors to. - */ - public void check(Element e, String name, LFValidator v); - - /** - * Helper function to produce an error during type checking. - * - * @param name The description of the target property. - * @param description The description of the type. - * @param v A reference to the validator to report errors to. - */ - public static void produceError(String name, String description, - LFValidator v) { - v.getTargetPropertyErrors().add("Target property '" + name - + "' is required to be " + description + "."); - } - } + public void check(Element e, String name, LFValidator v); /** - * Primitive types for target properties, each with a description used in - * error messages and predicate used for validating values. + * Helper function to produce an error during type checking. * - * @author Marten Lohstroh + * @param name The description of the target property. + * @param description The description of the type. + * @param v A reference to the validator to report errors to. */ - public enum PrimitiveType implements TargetPropertyType { - BOOLEAN("'true' or 'false'", - v -> ASTUtils.elementToSingleString(v).equalsIgnoreCase("true") - || ASTUtils.elementToSingleString(v).equalsIgnoreCase("false")), - INTEGER("an integer", v -> { - try { - Integer.parseInt(ASTUtils.elementToSingleString(v)); - } catch (NumberFormatException e) { - return false; - } - return true; + public static void produceError(String name, String description, LFValidator v) { + v.getTargetPropertyErrors() + .add("Target property '" + name + "' is required to be " + description + "."); + } + } + + /** + * Primitive types for target properties, each with a description used in error messages and + * predicate used for validating values. + * + * @author Marten Lohstroh + */ + public enum PrimitiveType implements TargetPropertyType { + BOOLEAN( + "'true' or 'false'", + v -> + ASTUtils.elementToSingleString(v).equalsIgnoreCase("true") + || ASTUtils.elementToSingleString(v).equalsIgnoreCase("false")), + INTEGER( + "an integer", + v -> { + try { + Integer.parseInt(ASTUtils.elementToSingleString(v)); + } catch (NumberFormatException e) { + return false; + } + return true; }), - NON_NEGATIVE_INTEGER("a non-negative integer", v -> { - try { - int result = Integer.parseInt(ASTUtils.elementToSingleString(v)); - if (result < 0) - return false; - } catch (NumberFormatException e) { - return false; - } - return true; + NON_NEGATIVE_INTEGER( + "a non-negative integer", + v -> { + try { + int result = Integer.parseInt(ASTUtils.elementToSingleString(v)); + if (result < 0) return false; + } catch (NumberFormatException e) { + return false; + } + return true; }), - TIME_VALUE("a time value with units", v -> - v.getKeyvalue() == null && v.getArray() == null - && v.getLiteral() == null && v.getId() == null + TIME_VALUE( + "a time value with units", + v -> + v.getKeyvalue() == null + && v.getArray() == null + && v.getLiteral() == null + && v.getId() == null && (v.getTime() == 0 || v.getUnit() != null)), - STRING("a string", v -> v.getLiteral() != null && !isCharLiteral(v.getLiteral()) || v.getId() != null), - FILE("a path to a file", STRING.validator); - - /** - * A description of this type, featured in error messages. - */ - private final String description; - - /** - * A predicate for determining whether a given Element conforms to this - * type. - */ - public final Predicate validator; - - /** - * Private constructor to create a new primitive type. - * @param description A textual description of the type that should - * start with "a/an". - * @param validator A predicate that returns true if a given Element - * conforms to this type. - */ - private PrimitiveType(String description, - Predicate validator) { - this.description = description; - this.validator = validator; - } + STRING( + "a string", + v -> v.getLiteral() != null && !isCharLiteral(v.getLiteral()) || v.getId() != null), + FILE("a path to a file", STRING.validator); - /** - * Return true if the the given Element is a valid instance of this type. - */ - public boolean validate(Element e) { - return this.validator.test(e); - } + /** A description of this type, featured in error messages. */ + private final String description; - /** - * Check (recursively) the given Element against its associated type(s) - * and add found problems to the given list of errors. - * - * @param e The element to type check. - * @param name The name of the target property. - * @param v The LFValidator to append errors to. - */ - public void check(Element e, String name, LFValidator v) { - if (!this.validate(e)) { - TargetPropertyType.produceError(name, this.description, v); - } - // If this is a file, perform an additional check to make sure - // the file actually exists. - // FIXME: premature because we first need a mechanism for looking up files! - // Looking in the same directory is too restrictive. Disabling this check for now. - /* - if (this == FILE) { - String file = ASTUtils.toSingleString(e); - - if (!FileConfig.fileExists(file, FileConfig.toPath(e.eResource().getURI()).toFile().getParent())) { - v.targetPropertyWarnings - .add("Could not find file: '" + file + "'."); - } - } - */ - } - - /** - * Return a textual description of this type. - */ - @Override - public String toString() { - return this.description; - } - - - private static boolean isCharLiteral(String s) { - return s.length() > 2 - && '\'' == s.charAt(0) - && '\'' == s.charAt(s.length() - 1); - } - } + /** A predicate for determining whether a given Element conforms to this type. */ + public final Predicate validator; /** - * Clock synchronization options. - * @author Marten Lohstroh + * Private constructor to create a new primitive type. + * + * @param description A textual description of the type that should start with "a/an". + * @param validator A predicate that returns true if a given Element conforms to this type. */ - public enum ClockSyncOption implements DictionaryElement { - ATTENUATION("attenuation", PrimitiveType.NON_NEGATIVE_INTEGER), - LOCAL_FEDERATES_ON("local-federates-on", PrimitiveType.BOOLEAN), - PERIOD("period", PrimitiveType.TIME_VALUE), - TEST_OFFSET("test-offset", PrimitiveType.TIME_VALUE), - TRIALS("trials", PrimitiveType.NON_NEGATIVE_INTEGER), - COLLECT_STATS("collect-stats", PrimitiveType.BOOLEAN); - - public final PrimitiveType type; - - private final String description; - - private ClockSyncOption(String alias, PrimitiveType type) { - this.description = alias; - this.type = type; - } - - - /** - * Return the description of this dictionary element. - */ - @Override - public String toString() { - return this.description; - } + private PrimitiveType(String description, Predicate validator) { + this.description = description; + this.validator = validator; + } - /** - * Return the type associated with this dictionary element. - */ - public TargetPropertyType getType() { - return this.type; - } + /** Return true if the the given Element is a valid instance of this type. */ + public boolean validate(Element e) { + return this.validator.test(e); } /** - * Docker options. - * @author Edward A. Lee - */ - public enum DockerOption implements DictionaryElement { - FROM("FROM", PrimitiveType.STRING); - - public final PrimitiveType type; - - private final String description; - - private DockerOption(String alias, PrimitiveType type) { - this.description = alias; - this.type = type; - } - - /** - * Return the description of this dictionary element. - */ - @Override - public String toString() { - return this.description; - } + * Check (recursively) the given Element against its associated type(s) and add found problems + * to the given list of errors. + * + * @param e The element to type check. + * @param name The name of the target property. + * @param v The LFValidator to append errors to. + */ + public void check(Element e, String name, LFValidator v) { + if (!this.validate(e)) { + TargetPropertyType.produceError(name, this.description, v); + } + // If this is a file, perform an additional check to make sure + // the file actually exists. + // FIXME: premature because we first need a mechanism for looking up files! + // Looking in the same directory is too restrictive. Disabling this check for now. + /* + if (this == FILE) { + String file = ASTUtils.toSingleString(e); + + if (!FileConfig.fileExists(file, FileConfig.toPath(e.eResource().getURI()).toFile().getParent())) { + v.targetPropertyWarnings + .add("Could not find file: '" + file + "'."); + } + } + */ + } - /** - * Return the type associated with this dictionary element. - */ - public TargetPropertyType getType() { - return this.type; - } + /** Return a textual description of this type. */ + @Override + public String toString() { + return this.description; } + private static boolean isCharLiteral(String s) { + return s.length() > 2 && '\'' == s.charAt(0) && '\'' == s.charAt(s.length() - 1); + } + } + + /** + * Clock synchronization options. + * + * @author Marten Lohstroh + */ + public enum ClockSyncOption implements DictionaryElement { + ATTENUATION("attenuation", PrimitiveType.NON_NEGATIVE_INTEGER), + LOCAL_FEDERATES_ON("local-federates-on", PrimitiveType.BOOLEAN), + PERIOD("period", PrimitiveType.TIME_VALUE), + TEST_OFFSET("test-offset", PrimitiveType.TIME_VALUE), + TRIALS("trials", PrimitiveType.NON_NEGATIVE_INTEGER), + COLLECT_STATS("collect-stats", PrimitiveType.BOOLEAN); + + public final PrimitiveType type; + + private final String description; + + private ClockSyncOption(String alias, PrimitiveType type) { + this.description = alias; + this.type = type; + } - /** - * Platform options. - * @author Anirudh Rengarajan - */ - public enum PlatformOption implements DictionaryElement { - NAME("name", PrimitiveType.STRING), - BAUDRATE("baud-rate", PrimitiveType.NON_NEGATIVE_INTEGER), - BOARD("board", PrimitiveType.STRING), - FLASH("flash", PrimitiveType.BOOLEAN), - PORT("port", PrimitiveType.STRING); + /** Return the description of this dictionary element. */ + @Override + public String toString() { + return this.description; + } - public final PrimitiveType type; + /** Return the type associated with this dictionary element. */ + public TargetPropertyType getType() { + return this.type; + } + } - private final String description; + /** + * Docker options. + * + * @author Edward A. Lee + */ + public enum DockerOption implements DictionaryElement { + FROM("FROM", PrimitiveType.STRING); - private PlatformOption(String alias, PrimitiveType type) { - this.description = alias; - this.type = type; - } + public final PrimitiveType type; + private final String description; - /** - * Return the description of this dictionary element. - */ - @Override - public String toString() { - return this.description; - } + private DockerOption(String alias, PrimitiveType type) { + this.description = alias; + this.type = type; + } - /** - * Return the type associated with this dictionary element. - */ - public TargetPropertyType getType() { - return this.type; - } + /** Return the description of this dictionary element. */ + @Override + public String toString() { + return this.description; } - /** - * Coordination options. - * @author Edward A. Lee - */ - public enum CoordinationOption implements DictionaryElement { - ADVANCE_MESSAGE_INTERVAL("advance-message-interval", PrimitiveType.TIME_VALUE); + /** Return the type associated with this dictionary element. */ + public TargetPropertyType getType() { + return this.type; + } + } + + /** + * Platform options. + * + * @author Anirudh Rengarajan + */ + public enum PlatformOption implements DictionaryElement { + NAME("name", PrimitiveType.STRING), + BAUDRATE("baud-rate", PrimitiveType.NON_NEGATIVE_INTEGER), + BOARD("board", PrimitiveType.STRING), + FLASH("flash", PrimitiveType.BOOLEAN), + PORT("port", PrimitiveType.STRING); + + public final PrimitiveType type; + + private final String description; + + private PlatformOption(String alias, PrimitiveType type) { + this.description = alias; + this.type = type; + } - public final PrimitiveType type; + /** Return the description of this dictionary element. */ + @Override + public String toString() { + return this.description; + } - private final String description; + /** Return the type associated with this dictionary element. */ + public TargetPropertyType getType() { + return this.type; + } + } - private CoordinationOption(String alias, PrimitiveType type) { - this.description = alias; - this.type = type; - } + /** + * Coordination options. + * + * @author Edward A. Lee + */ + public enum CoordinationOption implements DictionaryElement { + ADVANCE_MESSAGE_INTERVAL("advance-message-interval", PrimitiveType.TIME_VALUE); + public final PrimitiveType type; - /** - * Return the description of this dictionary element. - */ - @Override - public String toString() { - return this.description; - } + private final String description; - /** - * Return the type associated with this dictionary element. - */ - public TargetPropertyType getType() { - return this.type; - } + private CoordinationOption(String alias, PrimitiveType type) { + this.description = alias; + this.type = type; } - /** - * Log levels in descending order of severity. - * @author Marten Lohstroh - */ - public enum LogLevel { - ERROR, WARN, INFO, LOG, DEBUG; - - /** - * Return the name in lower case. - */ - @Override - public String toString() { - return this.name().toLowerCase(); - } + /** Return the description of this dictionary element. */ + @Override + public String toString() { + return this.description; } - /** - * Enumeration of supported platforms - */ - public enum Platform { - AUTO, - ARDUINO, - NRF52("Nrf52"), - LINUX("Linux"), - MAC("Darwin"), - ZEPHYR("Zephyr"), - WINDOWS("Windows"); - - String cMakeName; - Platform() { - this.cMakeName = this.toString(); - } - Platform(String cMakeName) { - this.cMakeName = cMakeName; - } + /** Return the type associated with this dictionary element. */ + public TargetPropertyType getType() { + return this.type; + } + } + + /** + * Log levels in descending order of severity. + * + * @author Marten Lohstroh + */ + public enum LogLevel { + ERROR, + WARN, + INFO, + LOG, + DEBUG; + + /** Return the name in lower case. */ + @Override + public String toString() { + return this.name().toLowerCase(); + } + } + + /** Enumeration of supported platforms */ + public enum Platform { + AUTO, + ARDUINO, + NRF52("Nrf52"), + LINUX("Linux"), + MAC("Darwin"), + ZEPHYR("Zephyr"), + WINDOWS("Windows"); + + String cMakeName; + + Platform() { + this.cMakeName = this.toString(); + } - /** - * Return the name in lower case. - */ - @Override - public String toString() { - return this.name().toLowerCase(); - } + Platform(String cMakeName) { + this.cMakeName = cMakeName; + } - /** - * Get the CMake name for the platform. - */ - public String getcMakeName() { - return this.cMakeName; - } + /** Return the name in lower case. */ + @Override + public String toString() { + return this.name().toLowerCase(); } - /** - * Supported schedulers. - * @author Soroush Bateni - */ - public enum SchedulerOption { - NP(false), // Non-preemptive - ADAPTIVE(false, List.of( + /** Get the CMake name for the platform. */ + public String getcMakeName() { + return this.cMakeName; + } + } + + /** + * Supported schedulers. + * + * @author Soroush Bateni + */ + public enum SchedulerOption { + NP(false), // Non-preemptive + ADAPTIVE( + false, + List.of( Path.of("scheduler_adaptive.c"), Path.of("worker_assignments.h"), Path.of("worker_states.h"), - Path.of("data_collection.h") - )), - GEDF_NP(true), // Global EDF non-preemptive - GEDF_NP_CI(true); // Global EDF non-preemptive with chain ID - // PEDF_NP(true); // Partitioned EDF non-preemptive (FIXME: To be re-added in a future PR) - - /** - * Indicate whether or not the scheduler prioritizes reactions by deadline. - */ - private final boolean prioritizesDeadline; - - /** Relative paths to files required by this scheduler. */ - private final List relativePaths; - - SchedulerOption(boolean prioritizesDeadline) { - this(prioritizesDeadline, null); - } + Path.of("data_collection.h"))), + GEDF_NP(true), // Global EDF non-preemptive + GEDF_NP_CI(true); // Global EDF non-preemptive with chain ID + // PEDF_NP(true); // Partitioned EDF non-preemptive (FIXME: To be re-added in a future PR) - SchedulerOption(boolean prioritizesDeadline, List relativePaths) { - this.prioritizesDeadline = prioritizesDeadline; - this.relativePaths = relativePaths; - } + /** Indicate whether or not the scheduler prioritizes reactions by deadline. */ + private final boolean prioritizesDeadline; - /** - * Return true if the scheduler prioritizes reactions by deadline. - */ - public boolean prioritizesDeadline() { - return this.prioritizesDeadline; - } + /** Relative paths to files required by this scheduler. */ + private final List relativePaths; - public List getRelativePaths() { - return relativePaths != null ? ImmutableList.copyOf(relativePaths) : - List.of(Path.of("scheduler_" + this + ".c")); - } + SchedulerOption(boolean prioritizesDeadline) { + this(prioritizesDeadline, null); + } - public static SchedulerOption getDefault() { - return NP; - } + SchedulerOption(boolean prioritizesDeadline, List relativePaths) { + this.prioritizesDeadline = prioritizesDeadline; + this.relativePaths = relativePaths; } - /** - * Tracing options. - * @author Edward A. Lee - */ - public enum TracingOption implements DictionaryElement { - TRACE_FILE_NAME("trace-file-name", PrimitiveType.STRING); + /** Return true if the scheduler prioritizes reactions by deadline. */ + public boolean prioritizesDeadline() { + return this.prioritizesDeadline; + } - public final PrimitiveType type; + public List getRelativePaths() { + return relativePaths != null + ? ImmutableList.copyOf(relativePaths) + : List.of(Path.of("scheduler_" + this + ".c")); + } - private final String description; + public static SchedulerOption getDefault() { + return NP; + } + } - private TracingOption(String alias, PrimitiveType type) { - this.description = alias; - this.type = type; - } + /** + * Tracing options. + * + * @author Edward A. Lee + */ + public enum TracingOption implements DictionaryElement { + TRACE_FILE_NAME("trace-file-name", PrimitiveType.STRING); - /** - * Return the description of this dictionary element. - */ - @Override - public String toString() { - return this.description; - } + public final PrimitiveType type; - /** - * Return the type associated with this dictionary element. - */ - public TargetPropertyType getType() { - return this.type; - } + private final String description; + + private TracingOption(String alias, PrimitiveType type) { + this.description = alias; + this.type = type; } + /** Return the description of this dictionary element. */ + @Override + public String toString() { + return this.description; + } + + /** Return the type associated with this dictionary element. */ + public TargetPropertyType getType() { + return this.type; + } + } } diff --git a/org.lflang/src/org/lflang/TimeUnit.java b/org.lflang/src/org/lflang/TimeUnit.java index 00c6314e51..a39bd505e8 100644 --- a/org.lflang/src/org/lflang/TimeUnit.java +++ b/org.lflang/src/org/lflang/TimeUnit.java @@ -38,82 +38,73 @@ * @author Clément Fournier, TU Dresden, INSA Rennes */ public enum TimeUnit { - /** Nanoseconds. */ - NANO("nsec", "ns", "nsecs"), - /** Microseconds. */ - MICRO("usec", "us", "usecs"), - /** Milliseconds. */ - MILLI("msec", "ms", "msecs"), - /** Seconds. */ - SECOND("sec", "s", "secs", "second", "seconds"), - /** Minute. */ // NOTE: Do not use MIN as the first entry. Common macro for minimum. - MINUTE("minute", "min", "mins", "minutes"), - /** Hour. */ - HOUR("hour", "h", "hours"), - /** Day. */ - DAY("day", "d", "days"), - WEEK("week", "weeks"), - ; + /** Nanoseconds. */ + NANO("nsec", "ns", "nsecs"), + /** Microseconds. */ + MICRO("usec", "us", "usecs"), + /** Milliseconds. */ + MILLI("msec", "ms", "msecs"), + /** Seconds. */ + SECOND("sec", "s", "secs", "second", "seconds"), + /** Minute. */ + // NOTE: Do not use MIN as the first entry. Common macro for minimum. + MINUTE("minute", "min", "mins", "minutes"), + /** Hour. */ + HOUR("hour", "h", "hours"), + /** Day. */ + DAY("day", "d", "days"), + WEEK("week", "weeks"), + ; - private final Set allNames; - private final String canonicalName; + private final Set allNames; + private final String canonicalName; - TimeUnit(String canonicalName, String... aliases) { - this.canonicalName = canonicalName; - this.allNames = immutableSetOf(canonicalName, aliases); - } + TimeUnit(String canonicalName, String... aliases) { + this.canonicalName = canonicalName; + this.allNames = immutableSetOf(canonicalName, aliases); + } + /** Returns the name that is preferred when displaying this unit. */ + public String getCanonicalName() { + return canonicalName; + } - /** - * Returns the name that is preferred when displaying this unit. - */ - public String getCanonicalName() { - return canonicalName; - } + /** Returns true if the given name is one of the aliases of this unit. */ + public boolean hasAlias(String name) { + return allNames.contains(name); + } - /** Returns true if the given name is one of the aliases of this unit. */ - public boolean hasAlias(String name) { - return allNames.contains(name); + /** + * Returns the constant corresponding to the given name. The comparison is case-sensitive. + * + * @return Null if the parameter is null, otherwise a non-null constant + * @throws IllegalArgumentException If the name doesn't correspond to any constant + */ + public static TimeUnit fromName(String name) { + if (name == null) { + return null; } + return Arrays.stream(values()) + .filter(it -> it.hasAlias(name)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("invalid name '" + name + "'")); + } - - /** - * Returns the constant corresponding to the given name. - * The comparison is case-sensitive. - * - * @return Null if the parameter is null, otherwise a non-null constant - * @throws IllegalArgumentException If the name doesn't correspond to any constant - */ - public static TimeUnit fromName(String name) { - if (name == null) { - return null; - } - return Arrays.stream(values()) - .filter(it -> it.hasAlias(name)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("invalid name '" + name + "'")); + /** Returns true if the parameter is null, or it is the alias of a valid time unit. */ + public static boolean isValidUnit(String name) { + if (name == null) { + return false; } + return Arrays.stream(values()).anyMatch(it -> it.hasAlias(name)); + } - /** - * Returns true if the parameter is null, or it is the - * alias of a valid time unit. - */ - public static boolean isValidUnit(String name) { - if (name == null) { - return false; - } - return Arrays.stream(values()).anyMatch(it -> it.hasAlias(name)); - } + /** Returns a list of all possible aliases for time values. */ + public static List list() { + return Arrays.stream(values()).flatMap(it -> it.allNames.stream()).collect(Collectors.toList()); + } - /** - * Returns a list of all possible aliases for time values. - */ - public static List list() { - return Arrays.stream(values()).flatMap(it -> it.allNames.stream()).collect(Collectors.toList()); - } - - @Override - public String toString() { - return this.canonicalName; - } + @Override + public String toString() { + return this.canonicalName; + } } diff --git a/org.lflang/src/org/lflang/TimeValue.java b/org.lflang/src/org/lflang/TimeValue.java index f008a0ae4b..f0bb94820c 100644 --- a/org.lflang/src/org/lflang/TimeValue.java +++ b/org.lflang/src/org/lflang/TimeValue.java @@ -1,27 +1,27 @@ /************* -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; @@ -33,172 +33,152 @@ */ public final class TimeValue implements Comparable { - /** - * The maximum value of this type. This is approximately equal to 292 years. - */ - public static final TimeValue MAX_VALUE = new TimeValue(Long.MAX_VALUE, TimeUnit.NANO); - /** - * A time value equal to zero. - */ - public static final TimeValue ZERO = new TimeValue(0, null); - - - /** - * Primitive numerical representation of this time value, - * to be interpreted in terms the associated time unit. - */ - public final long time; - - /** - * Units associated with this time value. May be null. - */ - public final TimeUnit unit; - - /** - * Maximum size of a deadline in primitive representation. - * NOTE: if we were to use an unsigned data type this would be - * 0xFFFFFFFFFFFF - */ - public static final long MAX_LONG_DEADLINE = Long.decode("0x7FFFFFFFFFFF"); - - /** - * Create a new time value. - * - * @throws IllegalArgumentException If time is non-zero and the unit is null - */ - public TimeValue(long time, TimeUnit unit) { - if (unit == null && time != 0) { - throw new IllegalArgumentException("Non-zero time values must have a unit"); - } - this.time = time; - this.unit = unit; - } - - @Override - public boolean equals(Object t1) { - if (t1 instanceof TimeValue) { - return this.compareTo((TimeValue) t1) == 0; - } - return false; - } - - public static int compare(TimeValue t1, TimeValue t2) { - if (t1.isEarlierThan(t2)) { - return -1; - } - if (t2.isEarlierThan(t1)) { - return 1; - } - return 0; - } - - private static long makeNanosecs(long time, TimeUnit unit) { - if (unit == null) { - return time; // == 0, see constructor. - } - switch (unit) { - case NANO: - return time; - case MICRO: - return time * 1000; - case MILLI: - return time * 1_000_000; - case SECOND: - return time * 1_000_000_000; - case MINUTE: - return time * 60_000_000_000L; - case HOUR: - return time * 3_600_000_000_000L; - case DAY: - return time * 86_400_000_000_000L; - case WEEK: - return time * 604_800_016_558_522L; - } - throw new AssertionError("unreachable"); - } - - /** - * Returns whether this time value is earlier than another. - */ - public boolean isEarlierThan(TimeValue other) { - return this.compareTo(other) < 0; + /** The maximum value of this type. This is approximately equal to 292 years. */ + public static final TimeValue MAX_VALUE = new TimeValue(Long.MAX_VALUE, TimeUnit.NANO); + /** A time value equal to zero. */ + public static final TimeValue ZERO = new TimeValue(0, null); + + /** + * Primitive numerical representation of this time value, to be interpreted in terms the + * associated time unit. + */ + public final long time; + + /** Units associated with this time value. May be null. */ + public final TimeUnit unit; + + /** + * Maximum size of a deadline in primitive representation. NOTE: if we were to use an unsigned + * data type this would be 0xFFFFFFFFFFFF + */ + public static final long MAX_LONG_DEADLINE = Long.decode("0x7FFFFFFFFFFF"); + + /** + * Create a new time value. + * + * @throws IllegalArgumentException If time is non-zero and the unit is null + */ + public TimeValue(long time, TimeUnit unit) { + if (unit == null && time != 0) { + throw new IllegalArgumentException("Non-zero time values must have a unit"); } - - @Override - public int compareTo(TimeValue o) { - return Long.compare(this.toNanoSeconds(), o.toNanoSeconds()); + this.time = time; + this.unit = unit; + } + + @Override + public boolean equals(Object t1) { + if (t1 instanceof TimeValue) { + return this.compareTo((TimeValue) t1) == 0; } + return false; + } - /** - * Return the magnitude of this value, as expressed in the - * {@linkplain #getUnit() unit} of this value. - */ - public long getMagnitude() { - return time; + public static int compare(TimeValue t1, TimeValue t2) { + if (t1.isEarlierThan(t2)) { + return -1; } - - /** - * Units associated with this time value. May be null, - * but only if the magnitude is zero. - */ - public TimeUnit getUnit() { - return unit; + if (t2.isEarlierThan(t1)) { + return 1; } + return 0; + } - /** - * Get this time value in number of nanoseconds. - */ - public long toNanoSeconds() { - return makeNanosecs(time, unit); + private static long makeNanosecs(long time, TimeUnit unit) { + if (unit == null) { + return time; // == 0, see constructor. } - - /** - * Return a string representation of this time value. - */ - public String toString() { - return unit != null ? time + " " + unit.getCanonicalName() - : Long.toString(time); + switch (unit) { + case NANO: + return time; + case MICRO: + return time * 1000; + case MILLI: + return time * 1_000_000; + case SECOND: + return time * 1_000_000_000; + case MINUTE: + return time * 60_000_000_000L; + case HOUR: + return time * 3_600_000_000_000L; + case DAY: + return time * 86_400_000_000_000L; + case WEEK: + return time * 604_800_016_558_522L; } - - /** Return the latest of both values. */ - public static TimeValue max(TimeValue t1, TimeValue t2) { - return t1.isEarlierThan(t2) ? t2 : t1; + throw new AssertionError("unreachable"); + } + + /** Returns whether this time value is earlier than another. */ + public boolean isEarlierThan(TimeValue other) { + return this.compareTo(other) < 0; + } + + @Override + public int compareTo(TimeValue o) { + return Long.compare(this.toNanoSeconds(), o.toNanoSeconds()); + } + + /** + * Return the magnitude of this value, as expressed in the {@linkplain #getUnit() unit} of this + * value. + */ + public long getMagnitude() { + return time; + } + + /** Units associated with this time value. May be null, but only if the magnitude is zero. */ + public TimeUnit getUnit() { + return unit; + } + + /** Get this time value in number of nanoseconds. */ + public long toNanoSeconds() { + return makeNanosecs(time, unit); + } + + /** Return a string representation of this time value. */ + public String toString() { + return unit != null ? time + " " + unit.getCanonicalName() : Long.toString(time); + } + + /** Return the latest of both values. */ + public static TimeValue max(TimeValue t1, TimeValue t2) { + return t1.isEarlierThan(t2) ? t2 : t1; + } + + /** Return the earliest of both values. */ + public static TimeValue min(TimeValue t1, TimeValue t2) { + return t1.isEarlierThan(t2) ? t1 : t2; + } + + /** + * Return the sum of this duration and the one represented by b. + * + *

The unit of the returned TimeValue will be the minimum of the units of both operands except + * if only one of the units is TimeUnit.NONE. In that case, the unit of the other input is used. + * + * @param b The right operand + * @return A new TimeValue (the current value will not be affected) + */ + public TimeValue add(TimeValue b) { + // Figure out the actual sum + final long sumOfNumbers; + try { + sumOfNumbers = Math.addExact(this.toNanoSeconds(), b.toNanoSeconds()); + } catch (ArithmeticException overflow) { + return MAX_VALUE; } - /** Return the earliest of both values. */ - public static TimeValue min(TimeValue t1, TimeValue t2) { - return t1.isEarlierThan(t2) ? t1 : t2; + if (this.unit == null || b.unit == null) { + // A time value with no unit is necessarily zero. So + // if this is null, (this + b) == b, if b is none, (this+b) == this. + return b.unit == null ? this : b; } - - /** - * Return the sum of this duration and the one represented by b. - *

- * The unit of the returned TimeValue will be the minimum - * of the units of both operands except if only one of the units - * is TimeUnit.NONE. In that case, the unit of the other input is used. - * - * @param b The right operand - * @return A new TimeValue (the current value will not be affected) - */ - public TimeValue add(TimeValue b) { - // Figure out the actual sum - final long sumOfNumbers; - try { - sumOfNumbers = Math.addExact(this.toNanoSeconds(), b.toNanoSeconds()); - } catch (ArithmeticException overflow) { - return MAX_VALUE; - } - - if (this.unit == null || b.unit == null) { - // A time value with no unit is necessarily zero. So - // if this is null, (this + b) == b, if b is none, (this+b) == this. - return b.unit == null ? this : b; - } - boolean isThisUnitSmallerThanBUnit = this.unit.compareTo(b.unit) <= 0; - TimeUnit smallestUnit = isThisUnitSmallerThanBUnit ? this.unit : b.unit; - // Find the appropriate divider to bring sumOfNumbers from nanoseconds to returnUnit - var unitDivider = makeNanosecs(1, smallestUnit); - return new TimeValue(sumOfNumbers / unitDivider, smallestUnit); - } - + boolean isThisUnitSmallerThanBUnit = this.unit.compareTo(b.unit) <= 0; + TimeUnit smallestUnit = isThisUnitSmallerThanBUnit ? this.unit : b.unit; + // Find the appropriate divider to bring sumOfNumbers from nanoseconds to returnUnit + var unitDivider = makeNanosecs(1, smallestUnit); + return new TimeValue(sumOfNumbers / unitDivider, smallestUnit); + } } diff --git a/org.lflang/src/org/lflang/ast/ASTUtils.java b/org.lflang/src/org/lflang/ast/ASTUtils.java index 26d4e0d6b0..91ff7ac69a 100644 --- a/org.lflang/src/org/lflang/ast/ASTUtils.java +++ b/org.lflang/src/org/lflang/ast/ASTUtils.java @@ -25,6 +25,9 @@ package org.lflang.ast; +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; @@ -54,7 +56,6 @@ import org.eclipse.xtext.xbase.lib.IterableExtensions; import org.eclipse.xtext.xbase.lib.IteratorExtensions; import org.eclipse.xtext.xbase.lib.StringExtensions; - import org.lflang.InferredType; import org.lflang.Target; import org.lflang.TimeUnit; @@ -100,1793 +101,1768 @@ import org.lflang.lf.WidthTerm; import org.lflang.util.StringUtil; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Iterables; -import com.google.common.collect.Iterators; - /** * A helper class for modifying and analyzing the AST. + * * @author Marten Lohstroh * @author Edward A. Lee * @author Christian Menard */ public class ASTUtils { - /** - * The Lingua Franca factory for creating new AST nodes. - */ - public static final LfFactory factory = LfFactory.eINSTANCE; - - /** - * The Lingua Franca feature package. - */ - public static final LfPackage featurePackage = LfPackage.eINSTANCE; - - /* Match an abbreviated form of a float literal. */ - private static final Pattern ABBREVIATED_FLOAT = Pattern.compile("[+\\-]?\\.\\d+[\\deE+\\-]*"); - - /** - * A mapping from Reactor features to corresponding Mode features for collecting contained elements. - */ - private static final Map reactorModeFeatureMap = Map.of( - featurePackage.getReactor_Actions(), featurePackage.getMode_Actions(), - featurePackage.getReactor_Connections(), featurePackage.getMode_Connections(), - featurePackage.getReactor_Instantiations(), featurePackage.getMode_Instantiations(), - featurePackage.getReactor_Reactions(), featurePackage.getMode_Reactions(), - featurePackage.getReactor_StateVars(), featurePackage.getMode_StateVars(), - featurePackage.getReactor_Timers(), featurePackage.getMode_Timers() - ); - - - /** - * Get all reactors defined in the given resource. - * @param resource the resource to extract reactors from - * @return An iterable over all reactors found in the resource - */ - public static List getAllReactors(Resource resource) { - return StreamSupport.stream(IteratorExtensions.toIterable(resource.getAllContents()).spliterator(), false) - .filter(Reactor.class::isInstance) - .map(Reactor.class::cast) - .collect(Collectors.toList()); - } - - /** - * Find connections in the given resource that would be conflicting writes if they were not located in mutually - * exclusive modes. - * - * @param resource The AST. - * @return a list of connections being able to be transformed - */ - public static Collection findConflictingConnectionsInModalReactors(Resource resource) { - var transform = new HashSet(); - - for (Reactor reactor : getAllReactors(resource)) { - if (!reactor.getModes().isEmpty()) { // Only for modal reactors - var allWriters = HashMultimap., EObject>create(); - - // Collect destinations - for (var rea : allReactions(reactor)) { - for (var eff : rea.getEffects()) { - if (eff.getVariable() instanceof Port) { - allWriters.put(Tuples.pair(eff.getContainer(), eff.getVariable()), rea); - } - } - } - for (var con : ASTUtils.collectElements(reactor, featurePackage.getReactor_Connections(), false, true)) { - for (var port : con.getRightPorts()) { - allWriters.put(Tuples.pair(port.getContainer(), port.getVariable()), con); - } - } - - // Handle conflicting writers - for (var key : allWriters.keySet()) { - var writers = allWriters.get(key); - if (writers.size() > 1) { // has multiple sources - var writerModes = HashMultimap.create(); - // find modes - for (var writer : writers) { - if (writer.eContainer() instanceof Mode) { - writerModes.put((Mode) writer.eContainer(), writer); - } else { - writerModes.put(null, writer); - } - } - // Conflicting connection can only be handled if.. - if (!writerModes.containsKey(null) && // no writer is on root level (outside of modes) and... - writerModes.keySet().stream().map(writerModes::get).allMatch(writersInMode -> // all writers in a mode are either... - writersInMode.size() == 1 || // the only writer or... - writersInMode.stream().allMatch(w -> w instanceof Reaction) // all are reactions and hence ordered - )) { - // Add connections to transform list - writers.stream().filter(w -> w instanceof Connection).forEach(c -> transform.add((Connection) c)); - } - } - } - } - } - - return transform; - } - - /** - * Return the enclosing reactor of an LF EObject in a reactor or mode. - * @param obj the LF model element - * @return the reactor or null - */ - public static Reactor getEnclosingReactor(EObject obj) { - if (obj.eContainer() instanceof Reactor) { - return (Reactor) obj.eContainer(); - } else if (obj.eContainer() instanceof Mode) { - return (Reactor) obj.eContainer().eContainer(); - } - return null; - } - - /** - * Return the main reactor in the given resource if there is one, null otherwise. - */ - public static Reactor findMainReactor(Resource resource) { - return IteratorExtensions.findFirst( - Iterators.filter(resource.getAllContents(), Reactor.class), - Reactor::isMain - ); - } - - /** - * Find the main reactor and change it to a federated reactor. - * Return true if the transformation was successful (or the given resource - * already had a federated reactor); return false otherwise. - */ - public static boolean makeFederated(Resource resource) { - // Find the main reactor - Reactor r = findMainReactor(resource); - if (r == null) { - return false; - } - r.setMain(false); - r.setFederated(true); - return true; - } - - /** - * Change the target name to 'newTargetName'. - * For example, change C to CCpp. - */ - public static boolean changeTargetName(Resource resource, String newTargetName) { - targetDecl(resource).setName(newTargetName); - return true; - } - - /** - * Return the target of the file in which the given node lives. - */ - public static Target getTarget(EObject object) { - TargetDecl targetDecl = targetDecl(object.eResource()); - return Target.fromDecl(targetDecl); - } - - /** - * Add a new target property to the given resource. - * This also creates a config object if the resource does not yey have one. - * - * @param resource The resource to modify - * @param name Name of the property to add - * @param value Value to be assigned to the property - */ - public static boolean addTargetProperty(final Resource resource, final String name, final Element value) { - var config = targetDecl(resource).getConfig(); - if (config == null) { - config = LfFactory.eINSTANCE.createKeyValuePairs(); - targetDecl(resource).setConfig(config); - } - final var newProperty = LfFactory.eINSTANCE.createKeyValuePair(); - newProperty.setName(name); - newProperty.setValue(value); - config.getPairs().add(newProperty); - return true; - } - - /** - * Return true if the connection involves multiple ports on the left or right side of the connection, or - * if the port on the left or right of the connection involves a bank of reactors or a multiport. - * @param connection The connection. - */ - public static boolean hasMultipleConnections(Connection connection) { - if (connection.getLeftPorts().size() > 1 || connection.getRightPorts().size() > 1) { - return true; - } - VarRef leftPort = connection.getLeftPorts().get(0); - VarRef rightPort = connection.getRightPorts().get(0); - Instantiation leftContainer = leftPort.getContainer(); - Instantiation rightContainer = rightPort.getContainer(); - Port leftPortAsPort = (Port) leftPort.getVariable(); - Port rightPortAsPort = (Port) rightPort.getVariable(); - return leftPortAsPort.getWidthSpec() != null - || leftContainer != null && leftContainer.getWidthSpec() != null - || rightPortAsPort.getWidthSpec() != null - || rightContainer != null && rightContainer.getWidthSpec() != null; - } - - /** - * Produce a unique identifier within a reactor based on a - * given based name. If the name does not exists, it is returned; - * if does exist, an index is appended that makes the name unique. - * @param reactor The reactor to find a unique identifier within. - * @param name The name to base the returned identifier on. - */ - public static String getUniqueIdentifier(Reactor reactor, String name) { - LinkedHashSet vars = new LinkedHashSet<>(); - allActions(reactor).forEach(it -> vars.add(it.getName())); - allTimers(reactor).forEach(it -> vars.add(it.getName())); - allParameters(reactor).forEach(it -> vars.add(it.getName())); - allInputs(reactor).forEach(it -> vars.add(it.getName())); - allOutputs(reactor).forEach(it -> vars.add(it.getName())); - allStateVars(reactor).forEach(it -> vars.add(it.getName())); - allInstantiations(reactor).forEach(it -> vars.add(it.getName())); - - int index = 0; - String suffix = ""; - boolean exists = true; - while (exists) { - String id = name + suffix; - if (IterableExtensions.exists(vars, it -> it.equals(id))) { - suffix = "_" + index; - index++; - } else { - exists = false; - } - } - return name + suffix; - } - - //////////////////////////////// - //// Utility functions for supporting inheritance and modes - - /** - * Given a reactor class, return a list of all its actions, - * which includes actions of base classes that it extends. - * This also includes actions in modes, returning a flattened - * view over all modes. - * @param definition Reactor class definition. - */ - public static List allActions(Reactor definition) { - return ASTUtils.collectElements(definition, featurePackage.getReactor_Actions()); - } - - /** - * Given a reactor class, return a list of all its connections, - * which includes connections of base classes that it extends. - * This also includes connections in modes, returning a flattened - * view over all modes. - * @param definition Reactor class definition. - */ - public static List allConnections(Reactor definition) { - return ASTUtils.collectElements(definition, featurePackage.getReactor_Connections()); - } - - /** - * Given a reactor class, return a list of all its inputs, - * which includes inputs of base classes that it extends. - * If the base classes include a cycle, where X extends Y and Y extends X, - * then return only the input defined in the base class. - * The returned list may be empty. - * @param definition Reactor class definition. - */ - public static List allInputs(Reactor definition) { - return ASTUtils.collectElements(definition, featurePackage.getReactor_Inputs()); - } - - /** A list of all ports of {@code definition}, in an unspecified order. */ - public static List allPorts(Reactor definition) { - return Stream.concat(ASTUtils.allInputs(definition).stream(), ASTUtils.allOutputs(definition).stream()).toList(); - } - - /** - * Given a reactor class, return a list of all its preambles, - * which includes preambles 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 allPreambles(Reactor definition) { - return ASTUtils.collectElements(definition, featurePackage.getReactor_Preambles()); - } - - /** - * 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 set of include names for - * interacting reactors which includes all instantiations of base class that it extends. - * - * @param r Reactor Class - * */ - public static HashSet allIncludes(Reactor r) { - var set = new HashSet(); - for (var i : allInstantiations(r)) - { - set.add(CUtil.getName(new TypeParameterizedReactor(i))); - } - return set; - } - - /* - * Given a reactor class, return a stream of reactor classes that it instantiates. - * @param definition Reactor class definition. - * @return A stream of reactor classes. - */ - 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()); - } - - public static List recursiveChildren(ReactorInstance r) { - List ret = new ArrayList<>(); - ret.add(r); - 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 file-level preambles in the files that define the - * specified class and its superclasses in deepest-first order. - * Duplicates are removed. If there are no file-level preambles, - * 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 allFileLevelPreambles(Reactor reactor) { - return allFileLevelPreambles(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)); - } + /** 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); } + } } - - // 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 + 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); + } } - 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); - } + // 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); + } } - } - 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); + // Conflicting connection can only be handled if.. + if (!writerModes.containsKey(null) + && // no writer is on root level (outside of modes) and... + writerModes.keySet().stream() + .map(writerModes::get) + .allMatch( + writersInMode -> // all writers in a mode are either... + writersInMode.size() == 1 + || // the only writer or... + writersInMode.stream() + .allMatch( + w -> + w + instanceof + Reaction) // all are reactions and hence ordered + )) { + // Add connections to transform list + writers.stream() + .filter(w -> w instanceof Connection) + .forEach(c -> transform.add((Connection) c)); } + } } - return elements; - } - - /** - * Convert key-value pairs in an Element to a map, assuming that both the key - * and the value are strings. - */ - public static Map elementToStringMaps(Element value) { - Map elements = new HashMap<>(); - for (var element: value.getKeyvalue().getPairs()) { - elements.put( - element.getName().trim(), - StringUtil.removeQuotes(elementToSingleString(element.getValue())) - ); - } - return 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 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 preambles, which includes preambles 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 allPreambles(Reactor definition) { + return ASTUtils.collectElements(definition, featurePackage.getReactor_Preambles()); + } + + /** + * 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 set of include names for interacting reactors which includes + * all instantiations of base class that it extends. + * + * @param r Reactor Class + */ + public static HashSet allIncludes(Reactor r) { + var set = new HashSet(); + for (var i : allInstantiations(r)) { + set.add(CUtil.getName(new TypeParameterizedReactor(i))); + } + return set; + } + + /* + * Given a reactor class, return a stream of reactor classes that it instantiates. + * @param definition Reactor class definition. + * @return A stream of reactor classes. + */ + 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()); + } + + public static List recursiveChildren(ReactorInstance r) { + List ret = new ArrayList<>(); + ret.add(r); + 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 file-level preambles in the files that define the specified class and its + * superclasses in deepest-first order. Duplicates are removed. If there are no file-level + * preambles, 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 allFileLevelPreambles(Reactor reactor) { + return allFileLevelPreambles(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 e; - } - - /** - * Convert a TimeValue to its AST representation. The value is type-cast to int in order to fit inside an Element. - */ - public static Element toElement(TimeValue tv) { - Element e = LfFactory.eINSTANCE.createElement(); - e.setTime((int)tv.time); - if (tv.unit != null) { - e.setUnit(tv.unit.toString()); - } - return e; - } - - public static Element toElement(boolean val) { - return toElement(Boolean.toString(val), false); - } - - public static Element toElement(int val) { - return toElement(Integer.toString(val), false); - } - - /** - * Translate the given type into its textual representation, but - * do not append any array specifications or type arguments. - * @param type AST node to render as string. - * @return Textual representation of the given argument. - */ - public static String baseType(Type type) { - if (type != null) { - if (type.getCode() != null) { - return toText(type.getCode()); + } + } + + // Add elements of the current reactor. + result.addAll((EList) definition.eGet(feature)); + + if (includeModes && reactorModeFeatureMap.containsKey(feature)) { + var modeFeature = reactorModeFeatureMap.get(feature); + // Add elements of elements defined in modes. + for (Mode mode : includeSuperClasses ? allModes(definition) : definition.getModes()) { + insertModeElementsAtTextualPosition(result, (EList) mode.eGet(modeFeature), mode); + } + } + + return result; + } + + /** + * Adds the elements into the given list at a location matching to their textual position. + * + *

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

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

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

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

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

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

If a list of instantiations is given, then the first instantiation is required to be an + * instantiation of the reactor class that is parameterized by the parameter. I.e., + * + *

+   *      parameter.eContainer == instantiations.get(0).reactorClass
+   * 
+ * + *

If a second instantiation is given, then it is required to be an instantiation of a reactor + * class that contains the first instantiation. That is, + * + *

+   *      instantiations.get(0).eContainer == instantiations.get(1).reactorClass
+   * 
+ * + *

More generally, for all 0 <= i < instantiations.size - 1, + * + *

+   *      instantiations.get(i).eContainer == instantiations.get(i + 1).reactorClass
+   * 
+ * + *

If any of these conditions is not satisfied, then an IllegalArgumentException will be + * thrown. + * + *

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

For example, consider the following program: + * + *

     reactor A(x:int(1)) {}
+   *      reactor B(y:int(2)) {
+   *          a1 = new A(x = y);
+   *          a2 = new A(x = -1);
+   *      }
+   *      reactor C(z:int(3)) {
+   *          b1 = new B(y = z);
+   *          b2 = new B(y = -2);
+   *      }
+   * 
+ * + *

Notice that there are a total of four instances of reactor class A. Then + * + *

+   *      initialValue(x, null) returns 1
+   *      initialValue(x, [a1]) returns 2
+   *      initialValue(x, [a2]) returns -1
+   *      initialValue(x, [a1, b1]) returns 3
+   *      initialValue(x, [a2, b1]) returns -1
+   *      initialValue(x, [a1, b2]) returns -2
+   *      initialValue(x, [a2, b2]) returns -1
+   * 
+ * + *

(Actually, in each of the above cases, the returned value is a list with one entry, a + * Literal, e.g. ["1"]). + * + *

There are two instances of reactor class B. + * + *

+   *      initialValue(y, null) returns 2
+   *      initialValue(y, [a1]) throws an IllegalArgumentException
+   *      initialValue(y, [b1]) returns 3
+   *      initialValue(y, [b2]) returns -2
+   * 
+ * + * @param parameter The parameter. + * @param instantiations The (optional) list of instantiations. + * @return The value of the parameter. + * @throws IllegalArgumentException If an instantiation provided is not an instantiation of the + * reactor class that is parameterized by the respective parameter or if the chain of + * instantiations is not nested. + */ + public static List initialValue( + Parameter parameter, List instantiations) { + // If instantiations are given, then check to see whether this parameter gets overridden in + // the first of those instantiations. + if (instantiations != null && instantiations.size() > 0) { + // Check to be sure that the instantiation is in fact an instantiation + // of the reactor class for which this is a parameter. + Instantiation instantiation = instantiations.get(0); + + if (!belongsTo(parameter, instantiation)) { + throw new IllegalArgumentException( + "Parameter " + + parameter.getName() + + " is not a parameter of reactor instance " + + instantiation.getName() + + "."); + } + // In case there is more than one assignment to this parameter, we need to + // find the last one. + Assignment lastAssignment = null; + for (Assignment assignment : instantiation.getParameters()) { + if (assignment.getLhs().equals(parameter)) { + lastAssignment = assignment; } - var 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() + "."; - } - 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 (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; - } - } 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(); - } + } + } + // 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; - } - - /** - * 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(""); - } - + } + 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 { - inst.setName(reactor.getName()); + return -1; } - 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); + } 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 all the file-level preambles in the files that define the - * specified class and its superclasses in deepest-first order. - * Duplicates are removed. If there are no file-level preambles, - * 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 allFileLevelPreambles(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 basePreambles = allFileLevelPreambles(r, extensions); - extensions.remove(r); - if (basePreambles == null) return null; - result.addAll(basePreambles); + } + } + 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); } - result.addAll(((Model) reactor.eContainer()).getPreambles()); - 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 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 : 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; + } + + /** + * Return all the file-level preambles in the files that define the specified class and its + * superclasses in deepest-first order. Duplicates are removed. If there are no file-level + * preambles, 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 allFileLevelPreambles( + 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 basePreambles = allFileLevelPreambles(r, extensions); + extensions.remove(r); + if (basePreambles == null) return null; + result.addAll(basePreambles); + } + result.addAll(((Model) reactor.eContainer()).getPreambles()); + 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/AstTransformation.java b/org.lflang/src/org/lflang/ast/AstTransformation.java index 80a93da4b9..cf5c5843ff 100644 --- a/org.lflang/src/org/lflang/ast/AstTransformation.java +++ b/org.lflang/src/org/lflang/ast/AstTransformation.java @@ -1,16 +1,11 @@ package org.lflang.ast; import java.util.List; - import org.lflang.lf.Reactor; -/** - * Interface for AST Transfomations - */ +/** Interface for AST Transfomations */ public interface AstTransformation { - /** - * Apply the AST transformation to all given reactors. - */ - void applyTransformation(List reactors); + /** Apply the AST transformation to all given reactors. */ + void applyTransformation(List reactors); } diff --git a/org.lflang/src/org/lflang/ast/DelayedConnectionTransformation.java b/org.lflang/src/org/lflang/ast/DelayedConnectionTransformation.java index 953b9bdfdb..882019ac44 100644 --- a/org.lflang/src/org/lflang/ast/DelayedConnectionTransformation.java +++ b/org.lflang/src/org/lflang/ast/DelayedConnectionTransformation.java @@ -6,13 +6,11 @@ import java.util.List; import java.util.Map; import java.util.Objects; - 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.InferredType; import org.lflang.generator.DelayBodyGenerator; import org.lflang.generator.TargetTypes; @@ -40,365 +38,371 @@ import org.lflang.lf.WidthTerm; /** - This class implements AST transformations for delayed connections. - There are two types of delayed connections: - 1) Connections with {@code after}-delays - 2) Physical connections + * This class implements AST transformations for delayed connections. There are two types of delayed + * connections: 1) Connections with {@code after}-delays 2) Physical connections */ public class DelayedConnectionTransformation implements AstTransformation { - /** - * The Lingua Franca factory for creating new AST nodes. - */ - public static final LfFactory factory = ASTUtils.factory; - - /** - * A code generator used to insert reaction bodies for the generated delay reactors. - */ - private final DelayBodyGenerator generator; - - /** - * A target type instance that is used during the transformation to manage target specific types - */ - private final TargetTypes targetTypes; - - /** - * The Eclipse eCore view of the main LF file. - */ - private final Resource mainResource; - - private boolean transformAfterDelays = false; - private boolean transformPhysicalConnection = false; - /** - * Collection of generated delay classes. - */ - private final LinkedHashSet delayClasses = new LinkedHashSet<>(); - - public DelayedConnectionTransformation(DelayBodyGenerator generator, TargetTypes targetTypes, Resource mainResource, boolean transformAfterDelays, boolean transformPhysicalConnections) { - this.generator = generator; - this.targetTypes = targetTypes; - this.mainResource = mainResource; - this.transformAfterDelays = transformAfterDelays; - this.transformPhysicalConnection = transformPhysicalConnections; - } - - /** - * Transform all after delay connections by inserting generated delay reactors. - */ - @Override - public void applyTransformation(List reactors) { - insertGeneratedDelays(reactors); - } - - /** - * Find connections in the given resource that have a delay associated with them, - * and reroute them via a generated delay reactor. - * @param reactors A list of reactors to apply the transformation to. - */ - private void insertGeneratedDelays(List reactors) { - // The resulting changes to the AST are performed _after_ iterating - // in order to avoid concurrent modification problems. - List oldConnections = new ArrayList<>(); - Map> newConnections = new LinkedHashMap<>(); - Map> delayInstances = new LinkedHashMap<>(); - - // Iterate over the connections in the tree. - for (Reactor container : reactors) { - for (Connection connection : ASTUtils.allConnections(container)) { - if ( transformAfterDelays && connection.getDelay() != null || - transformPhysicalConnection && connection.isPhysical()) { - EObject parent = connection.eContainer(); - // Assume all the types are the same, so just use the first on the right. - Type type = ((Port) connection.getRightPorts().get(0).getVariable()).getType(); - Reactor delayClass = getDelayClass(type, connection.isPhysical()); - String generic = targetTypes.supportsGenerics() - ? targetTypes.getTargetType(type) : null; - - Instantiation delayInstance = getDelayInstance(delayClass, connection, generic, - !generator.generateAfterDelaysWithVariableWidth(), connection.isPhysical()); - - // Stage the new connections for insertion into the tree. - List connections = ASTUtils.convertToEmptyListIfNull(newConnections.get(parent)); - connections.addAll(rerouteViaDelay(connection, delayInstance)); - newConnections.put(parent, connections); - // Stage the original connection for deletion from the tree. - oldConnections.add(connection); - - // Stage the newly created delay reactor instance for insertion - List instances = ASTUtils.convertToEmptyListIfNull(delayInstances.get(parent)); - instances.add(delayInstance); - delayInstances.put(parent, instances); - } - } + /** The Lingua Franca factory for creating new AST nodes. */ + public static final LfFactory factory = ASTUtils.factory; + + /** A code generator used to insert reaction bodies for the generated delay reactors. */ + private final DelayBodyGenerator generator; + + /** + * A target type instance that is used during the transformation to manage target specific types + */ + private final TargetTypes targetTypes; + + /** The Eclipse eCore view of the main LF file. */ + private final Resource mainResource; + + private boolean transformAfterDelays = false; + private boolean transformPhysicalConnection = false; + /** Collection of generated delay classes. */ + private final LinkedHashSet delayClasses = new LinkedHashSet<>(); + + public DelayedConnectionTransformation( + DelayBodyGenerator generator, + TargetTypes targetTypes, + Resource mainResource, + boolean transformAfterDelays, + boolean transformPhysicalConnections) { + this.generator = generator; + this.targetTypes = targetTypes; + this.mainResource = mainResource; + this.transformAfterDelays = transformAfterDelays; + this.transformPhysicalConnection = transformPhysicalConnections; + } + + /** Transform all after delay connections by inserting generated delay reactors. */ + @Override + public void applyTransformation(List reactors) { + insertGeneratedDelays(reactors); + } + + /** + * Find connections in the given resource that have a delay associated with them, and reroute them + * via a generated delay reactor. + * + * @param reactors A list of reactors to apply the transformation to. + */ + private void insertGeneratedDelays(List reactors) { + // The resulting changes to the AST are performed _after_ iterating + // in order to avoid concurrent modification problems. + List oldConnections = new ArrayList<>(); + Map> newConnections = new LinkedHashMap<>(); + Map> delayInstances = new LinkedHashMap<>(); + + // Iterate over the connections in the tree. + for (Reactor container : reactors) { + for (Connection connection : ASTUtils.allConnections(container)) { + if (transformAfterDelays && connection.getDelay() != null + || transformPhysicalConnection && connection.isPhysical()) { + EObject parent = connection.eContainer(); + // Assume all the types are the same, so just use the first on the right. + Type type = ((Port) connection.getRightPorts().get(0).getVariable()).getType(); + Reactor delayClass = getDelayClass(type, connection.isPhysical()); + String generic = targetTypes.supportsGenerics() ? targetTypes.getTargetType(type) : null; + + Instantiation delayInstance = + getDelayInstance( + delayClass, + connection, + generic, + !generator.generateAfterDelaysWithVariableWidth(), + connection.isPhysical()); + + // Stage the new connections for insertion into the tree. + List connections = + ASTUtils.convertToEmptyListIfNull(newConnections.get(parent)); + connections.addAll(rerouteViaDelay(connection, delayInstance)); + newConnections.put(parent, connections); + // Stage the original connection for deletion from the tree. + oldConnections.add(connection); + + // Stage the newly created delay reactor instance for insertion + List instances = + ASTUtils.convertToEmptyListIfNull(delayInstances.get(parent)); + instances.add(delayInstance); + delayInstances.put(parent, instances); } + } + } - // Remove old connections; insert new ones. - oldConnections.forEach(connection -> { - var container = connection.eContainer(); - if (container instanceof Reactor) { - ((Reactor) container).getConnections().remove(connection); - } else if (container instanceof Mode) { - ((Mode) container).getConnections().remove(connection); - } + // Remove old connections; insert new ones. + oldConnections.forEach( + connection -> { + var container = connection.eContainer(); + if (container instanceof Reactor) { + ((Reactor) container).getConnections().remove(connection); + } else if (container instanceof Mode) { + ((Mode) container).getConnections().remove(connection); + } }); - newConnections.forEach((container, connections) -> { - if (container instanceof Reactor) { - ((Reactor) container).getConnections().addAll(connections); - } else if (container instanceof Mode) { - ((Mode) container).getConnections().addAll(connections); - } + newConnections.forEach( + (container, connections) -> { + if (container instanceof Reactor) { + ((Reactor) container).getConnections().addAll(connections); + } else if (container instanceof Mode) { + ((Mode) container).getConnections().addAll(connections); + } }); - // Finally, insert the instances and, before doing so, assign them a unique name. - delayInstances.forEach((container, instantiations) -> - instantiations.forEach(instantiation -> { - if (container instanceof Reactor) { - instantiation.setName(ASTUtils.getUniqueIdentifier((Reactor) container, "delay")); + // Finally, insert the instances and, before doing so, assign them a unique name. + delayInstances.forEach( + (container, instantiations) -> + instantiations.forEach( + instantiation -> { + if (container instanceof Reactor) { + instantiation.setName( + ASTUtils.getUniqueIdentifier((Reactor) container, "delay")); ((Reactor) container).getInstantiations().add(instantiation); - } else if (container instanceof Mode) { - instantiation.setName(ASTUtils.getUniqueIdentifier((Reactor) container.eContainer(), "delay")); + } else if (container instanceof Mode) { + instantiation.setName( + ASTUtils.getUniqueIdentifier((Reactor) container.eContainer(), "delay")); ((Mode) container).getInstantiations().add(instantiation); - } - }) - ); - } - - /** - * Take a connection and reroute it via an instance of a generated delay - * reactor. This method returns a list to new connections to substitute - * the original one. - * @param connection The connection to reroute. - * @param delayInstance The delay instance to route the connection through. - */ - private static List rerouteViaDelay(Connection connection, - Instantiation delayInstance) { - List connections = new ArrayList<>(); - Connection upstream = factory.createConnection(); - Connection downstream = factory.createConnection(); - VarRef input = factory.createVarRef(); - VarRef output = factory.createVarRef(); - - Reactor delayClass = ASTUtils.toDefinition(delayInstance.getReactorClass()); - - // Establish references to the involved ports. - input.setContainer(delayInstance); - input.setVariable(delayClass.getInputs().get(0)); - output.setContainer(delayInstance); - output.setVariable(delayClass.getOutputs().get(0)); - upstream.getLeftPorts().addAll(connection.getLeftPorts()); - upstream.getRightPorts().add(input); - downstream.getLeftPorts().add(output); - downstream.getRightPorts().addAll(connection.getRightPorts()); - downstream.setIterated(connection.isIterated()); - connections.add(upstream); - connections.add(downstream); - return connections; + } + })); + } + + /** + * Take a connection and reroute it via an instance of a generated delay reactor. This method + * returns a list to new connections to substitute the original one. + * + * @param connection The connection to reroute. + * @param delayInstance The delay instance to route the connection through. + */ + private static List rerouteViaDelay( + Connection connection, Instantiation delayInstance) { + List connections = new ArrayList<>(); + Connection upstream = factory.createConnection(); + Connection downstream = factory.createConnection(); + VarRef input = factory.createVarRef(); + VarRef output = factory.createVarRef(); + + Reactor delayClass = ASTUtils.toDefinition(delayInstance.getReactorClass()); + + // Establish references to the involved ports. + input.setContainer(delayInstance); + input.setVariable(delayClass.getInputs().get(0)); + output.setContainer(delayInstance); + output.setVariable(delayClass.getOutputs().get(0)); + upstream.getLeftPorts().addAll(connection.getLeftPorts()); + upstream.getRightPorts().add(input); + downstream.getLeftPorts().add(output); + downstream.getRightPorts().addAll(connection.getRightPorts()); + downstream.setIterated(connection.isIterated()); + connections.add(upstream); + connections.add(downstream); + return connections; + } + + /** + * Create a new instance delay instances using the given reactor class. The supplied time value is + * used to override the default interval (which is zero). If the target supports parametric + * polymorphism, then a single class may be used for each instantiation, in which case a non-empty + * string must be supplied to parameterize the instance. A default name ("delay") is assigned to + * the instantiation, but this name must be overridden at the call site, where checks can be done + * to avoid name collisions in the container in which the instantiation is to be placed. Such + * checks (or modifications of the AST) are not performed in this method in order to avoid causing + * concurrent modification exceptions. + * + * @param delayClass The class to create an instantiation for + * @param connection The connection to create a delay instantiation foe + * @param genericArg A string that denotes the appropriate type parameter, which should be null or + * empty if the target does not support generics. + * @param defineWidthFromConnection If this is true and if the connection is a wide connection, + * then instantiate a bank of delays where the width is given by ports involved in the + * connection. Otherwise, the width will be unspecified indicating a variable length. + * @param isPhysical Is this a delay instance using a physical action. These are used for + * implementing Physical Connections. If true we will accept zero delay on the connection. + */ + private static Instantiation getDelayInstance( + Reactor delayClass, + Connection connection, + String genericArg, + Boolean defineWidthFromConnection, + Boolean isPhysical) { + Instantiation delayInstance = factory.createInstantiation(); + delayInstance.setReactorClass(delayClass); + if (genericArg != null) { + Code code = factory.createCode(); + code.setBody(genericArg); + Type type = factory.createType(); + type.setCode(code); + delayInstance.getTypeArgs().add(type); } - - /** - * Create a new instance delay instances using the given reactor class. - * The supplied time value is used to override the default interval (which - * is zero). - * If the target supports parametric polymorphism, then a single class may - * be used for each instantiation, in which case a non-empty string must - * be supplied to parameterize the instance. - * A default name ("delay") is assigned to the instantiation, but this - * name must be overridden at the call site, where checks can be done to - * avoid name collisions in the container in which the instantiation is - * to be placed. Such checks (or modifications of the AST) are not - * performed in this method in order to avoid causing concurrent - * modification exceptions. - * @param delayClass The class to create an instantiation for - * @param connection The connection to create a delay instantiation foe - * @param genericArg A string that denotes the appropriate type parameter, - * which should be null or empty if the target does not support generics. - * @param defineWidthFromConnection If this is true and if the connection - * is a wide connection, then instantiate a bank of delays where the width - * is given by ports involved in the connection. Otherwise, the width will - * be unspecified indicating a variable length. - * @param isPhysical Is this a delay instance using a physical action. - * These are used for implementing Physical Connections. If true - * we will accept zero delay on the connection. - */ - private static Instantiation getDelayInstance(Reactor delayClass, - Connection connection, String genericArg, Boolean defineWidthFromConnection, Boolean isPhysical) { - Instantiation delayInstance = factory.createInstantiation(); - delayInstance.setReactorClass(delayClass); - if (genericArg != null) { - Code code = factory.createCode(); - code.setBody(genericArg); - Type type = factory.createType(); - type.setCode(code); - delayInstance.getTypeArgs().add(type); - } - if (ASTUtils.hasMultipleConnections(connection)) { - WidthSpec widthSpec = factory.createWidthSpec(); - if (defineWidthFromConnection) { - // Add all left ports of the connection to the WidthSpec of the generated delay instance. - // This allows the code generator to later infer the width from the involved ports. - // We only consider the left ports here, as they could be part of a broadcast. In this case, we want - // to delay the ports first, and then broadcast the output of the delays. - for (VarRef port : connection.getLeftPorts()) { - WidthTerm term = factory.createWidthTerm(); - term.setPort(EcoreUtil.copy(port)); - widthSpec.getTerms().add(term); - } - } else { - widthSpec.setOfVariableLength(true); - } - delayInstance.setWidthSpec(widthSpec); - } - // Allow physical connections with no after delay - // they will use the default min_delay of 0. - if (!isPhysical || connection.getDelay() != null) { - Assignment assignment = factory.createAssignment(); - assignment.setLhs(delayClass.getParameters().get(0)); - Initializer init = factory.createInitializer(); - init.getExprs().add(Objects.requireNonNull(connection.getDelay(), "null delay")); - assignment.setRhs(init); - delayInstance.getParameters().add(assignment); + if (ASTUtils.hasMultipleConnections(connection)) { + WidthSpec widthSpec = factory.createWidthSpec(); + if (defineWidthFromConnection) { + // Add all left ports of the connection to the WidthSpec of the generated delay instance. + // This allows the code generator to later infer the width from the involved ports. + // We only consider the left ports here, as they could be part of a broadcast. In this case, + // we want + // to delay the ports first, and then broadcast the output of the delays. + for (VarRef port : connection.getLeftPorts()) { + WidthTerm term = factory.createWidthTerm(); + term.setPort(EcoreUtil.copy(port)); + widthSpec.getTerms().add(term); } - - delayInstance.setName("delay"); // This has to be overridden. - return delayInstance; + } else { + widthSpec.setOfVariableLength(true); + } + delayInstance.setWidthSpec(widthSpec); + } + // Allow physical connections with no after delay + // they will use the default min_delay of 0. + if (!isPhysical || connection.getDelay() != null) { + Assignment assignment = factory.createAssignment(); + assignment.setLhs(delayClass.getParameters().get(0)); + Initializer init = factory.createInitializer(); + init.getExprs().add(Objects.requireNonNull(connection.getDelay(), "null delay")); + assignment.setRhs(init); + delayInstance.getParameters().add(assignment); } - /** - * Return a synthesized AST node that represents the definition of a delay - * reactor. Depending on whether the target supports generics, either this - * method will synthesize a generic definition and keep returning it upon - * subsequent calls, or otherwise, it will synthesize a new definition for - * each new type it hasn't yet created a compatible delay reactor for. - * @param type The type the delay class must be compatible with. - * @param isPhysical Is this delay reactor using a physical action. - */ - private Reactor getDelayClass(Type type, boolean isPhysical) { - String className; - if (targetTypes.supportsGenerics()) { - className = DelayBodyGenerator.GEN_DELAY_CLASS_NAME; - } else { - String id = Integer.toHexString(InferredType.fromAST(type).toText().hashCode()); - className = String.format("%s_%s", DelayBodyGenerator.GEN_DELAY_CLASS_NAME, id); - } - - // Only add class definition if it is not already there. - Reactor classDef = findDelayClass(className); - if (classDef != null) { - return classDef; - } - - Reactor delayClass = factory.createReactor(); - Parameter delayParameter = factory.createParameter(); - Action action = factory.createAction(); - VarRef triggerRef = factory.createVarRef(); - VarRef effectRef = factory.createVarRef(); - Input input = factory.createInput(); - Output output = factory.createOutput(); - VarRef inRef = factory.createVarRef(); - VarRef outRef = factory.createVarRef(); - - Reaction r1 = factory.createReaction(); - Reaction r2 = factory.createReaction(); - - delayParameter.setName("delay"); - delayParameter.setType(factory.createType()); - delayParameter.getType().setId("time"); - delayParameter.getType().setTime(true); - Time defaultTime = factory.createTime(); - defaultTime.setUnit(null); - defaultTime.setInterval(0); - Initializer init = factory.createInitializer(); - init.setParens(true); - init.setBraces(false); - init.getExprs().add(defaultTime); - delayParameter.setInit(init); - - // Name the newly created action; set its delay and type. - action.setName("act"); - var paramRef = factory.createParameterReference(); - paramRef.setParameter(delayParameter); - action.setMinDelay(paramRef); - if (isPhysical) { - action.setOrigin(ActionOrigin.PHYSICAL); - } else { - action.setOrigin(ActionOrigin.LOGICAL); - } + delayInstance.setName("delay"); // This has to be overridden. + return delayInstance; + } + + /** + * Return a synthesized AST node that represents the definition of a delay reactor. Depending on + * whether the target supports generics, either this method will synthesize a generic definition + * and keep returning it upon subsequent calls, or otherwise, it will synthesize a new definition + * for each new type it hasn't yet created a compatible delay reactor for. + * + * @param type The type the delay class must be compatible with. + * @param isPhysical Is this delay reactor using a physical action. + */ + private Reactor getDelayClass(Type type, boolean isPhysical) { + String className; + if (targetTypes.supportsGenerics()) { + className = DelayBodyGenerator.GEN_DELAY_CLASS_NAME; + } else { + String id = Integer.toHexString(InferredType.fromAST(type).toText().hashCode()); + className = String.format("%s_%s", DelayBodyGenerator.GEN_DELAY_CLASS_NAME, id); + } - if (targetTypes.supportsGenerics()) { - action.setType(factory.createType()); - action.getType().setId("T"); - } else { - action.setType(EcoreUtil.copy(type)); - } + // Only add class definition if it is not already there. + Reactor classDef = findDelayClass(className); + if (classDef != null) { + return classDef; + } - input.setName("inp"); - input.setType(EcoreUtil.copy(action.getType())); - - output.setName("out"); - output.setType(EcoreUtil.copy(action.getType())); - - // Establish references to the involved ports. - inRef.setVariable(input); - outRef.setVariable(output); - - // Establish references to the action. - triggerRef.setVariable(action); - effectRef.setVariable(action); - - // Add the action to the reactor. - delayClass.setName(className); - delayClass.getActions().add(action); - - // Configure the second reaction, which reads the input. - r1.getTriggers().add(inRef); - r1.getEffects().add(effectRef); - r1.setCode(factory.createCode()); - r1.getCode().setBody(generator.generateDelayBody(action, inRef)); - - // Configure the first reaction, which produces the output. - r2.getTriggers().add(triggerRef); - r2.getEffects().add(outRef); - r2.setCode(factory.createCode()); - r2.getCode().setBody(generator.generateForwardBody(action, outRef)); - - generator.finalizeReactions(r1, r2); - - // Add the reactions to the newly created reactor class. - // These need to go in the opposite order in case - // a new input arrives at the same time the delayed - // output is delivered! - delayClass.getReactions().add(r2); - delayClass.getReactions().add(r1); - - // Add a type parameter if the target supports it. - if (targetTypes.supportsGenerics()) { - TypeParm parm = factory.createTypeParm(); - parm.setLiteral(generator.generateDelayGeneric()); - delayClass.getTypeParms().add(parm); - } - delayClass.getInputs().add(input); - delayClass.getOutputs().add(output); - delayClass.getParameters().add(delayParameter); - addDelayClass(delayClass); - return delayClass; + Reactor delayClass = factory.createReactor(); + Parameter delayParameter = factory.createParameter(); + Action action = factory.createAction(); + VarRef triggerRef = factory.createVarRef(); + VarRef effectRef = factory.createVarRef(); + Input input = factory.createInput(); + Output output = factory.createOutput(); + VarRef inRef = factory.createVarRef(); + VarRef outRef = factory.createVarRef(); + + Reaction r1 = factory.createReaction(); + Reaction r2 = factory.createReaction(); + + delayParameter.setName("delay"); + delayParameter.setType(factory.createType()); + delayParameter.getType().setId("time"); + delayParameter.getType().setTime(true); + Time defaultTime = factory.createTime(); + defaultTime.setUnit(null); + defaultTime.setInterval(0); + Initializer init = factory.createInitializer(); + init.setParens(true); + init.setBraces(false); + init.getExprs().add(defaultTime); + delayParameter.setInit(init); + + // Name the newly created action; set its delay and type. + action.setName("act"); + var paramRef = factory.createParameterReference(); + paramRef.setParameter(delayParameter); + action.setMinDelay(paramRef); + if (isPhysical) { + action.setOrigin(ActionOrigin.PHYSICAL); + } else { + action.setOrigin(ActionOrigin.LOGICAL); } - /** - * Store the given reactor in the collection of generated delay classes - * and insert it in the AST under the top-level reactor's node. - */ - private void addDelayClass(Reactor generatedDelay) { - // Record this class, so it can be reused. - delayClasses.add(generatedDelay); - // And hook it into the AST. - EObject node = IteratorExtensions.findFirst(mainResource.getAllContents(), Model.class::isInstance); - ((Model) node).getReactors().add(generatedDelay); + if (targetTypes.supportsGenerics()) { + action.setType(factory.createType()); + action.getType().setId("T"); + } else { + action.setType(EcoreUtil.copy(type)); } - /** - * Return the generated delay reactor that corresponds to the given class - * name if it had been created already, {@code null} otherwise. - */ - private Reactor findDelayClass(String className) { - return IterableExtensions.findFirst(delayClasses, it -> it.getName().equals(className)); + input.setName("inp"); + input.setType(EcoreUtil.copy(action.getType())); + + output.setName("out"); + output.setType(EcoreUtil.copy(action.getType())); + + // Establish references to the involved ports. + inRef.setVariable(input); + outRef.setVariable(output); + + // Establish references to the action. + triggerRef.setVariable(action); + effectRef.setVariable(action); + + // Add the action to the reactor. + delayClass.setName(className); + delayClass.getActions().add(action); + + // Configure the second reaction, which reads the input. + r1.getTriggers().add(inRef); + r1.getEffects().add(effectRef); + r1.setCode(factory.createCode()); + r1.getCode().setBody(generator.generateDelayBody(action, inRef)); + + // Configure the first reaction, which produces the output. + r2.getTriggers().add(triggerRef); + r2.getEffects().add(outRef); + r2.setCode(factory.createCode()); + r2.getCode().setBody(generator.generateForwardBody(action, outRef)); + + generator.finalizeReactions(r1, r2); + + // Add the reactions to the newly created reactor class. + // These need to go in the opposite order in case + // a new input arrives at the same time the delayed + // output is delivered! + delayClass.getReactions().add(r2); + delayClass.getReactions().add(r1); + + // Add a type parameter if the target supports it. + if (targetTypes.supportsGenerics()) { + TypeParm parm = factory.createTypeParm(); + parm.setLiteral(generator.generateDelayGeneric()); + delayClass.getTypeParms().add(parm); } + delayClass.getInputs().add(input); + delayClass.getOutputs().add(output); + delayClass.getParameters().add(delayParameter); + addDelayClass(delayClass); + return delayClass; + } + + /** + * Store the given reactor in the collection of generated delay classes and insert it in the AST + * under the top-level reactor's node. + */ + private void addDelayClass(Reactor generatedDelay) { + // Record this class, so it can be reused. + delayClasses.add(generatedDelay); + // And hook it into the AST. + EObject node = + IteratorExtensions.findFirst(mainResource.getAllContents(), Model.class::isInstance); + ((Model) node).getReactors().add(generatedDelay); + } + + /** + * Return the generated delay reactor that corresponds to the given class name if it had been + * created already, {@code null} otherwise. + */ + private Reactor findDelayClass(String className) { + return IterableExtensions.findFirst(delayClasses, it -> it.getName().equals(className)); + } } diff --git a/org.lflang/src/org/lflang/ast/FormattingUtils.java b/org.lflang/src/org/lflang/ast/FormattingUtils.java index 860e3d6bb2..9b47440391 100644 --- a/org.lflang/src/org/lflang/ast/FormattingUtils.java +++ b/org.lflang/src/org/lflang/ast/FormattingUtils.java @@ -8,264 +8,241 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; - import org.eclipse.emf.ecore.EObject; - import org.lflang.Target; import org.lflang.lf.Model; /** * Utility functions that determine the specific behavior of the LF formatter. + * * @author Peter Donovan * @author Billy Bao */ public class FormattingUtils { - /** - * The minimum number of columns that should be allotted to a comment. - * This is relevant in case of high indentation/small wrapLength. - */ - private static final int MINIMUM_COMMENT_WIDTH_IN_COLUMNS = 15; + /** + * The minimum number of columns that should be allotted to a comment. This is relevant in case of + * high indentation/small wrapLength. + */ + private static final int MINIMUM_COMMENT_WIDTH_IN_COLUMNS = 15; - /** Match a multiline comment. */ - private static final Pattern MULTILINE_COMMENT = Pattern.compile( - "\\s*/\\*\\v?(\\V*\\v+)*\\V*" - ); + /** Match a multiline comment. */ + private static final Pattern MULTILINE_COMMENT = Pattern.compile("\\s*/\\*\\v?(\\V*\\v+)*\\V*"); - /** The number of spaces to prepend to a line per indentation level. */ - private static final int INDENTATION = 4; + /** The number of spaces to prepend to a line per indentation level. */ + private static final int INDENTATION = 4; - public static final int DEFAULT_LINE_LENGTH = 80; + public static final int DEFAULT_LINE_LENGTH = 80; - static final int MAX_WHITESPACE_USED_FOR_ALIGNMENT = 20; + static final int MAX_WHITESPACE_USED_FOR_ALIGNMENT = 20; - static final long BADNESS_PER_CHARACTER_VIOLATING_LINE_LENGTH = 20; + static final long BADNESS_PER_CHARACTER_VIOLATING_LINE_LENGTH = 20; - static final long BADNESS_PER_LEVEL_OF_COMMENT_DISPLACEMENT = 1000; + static final long BADNESS_PER_LEVEL_OF_COMMENT_DISPLACEMENT = 1000; - static final long BADNESS_PER_NEWLINE = 1; + static final long BADNESS_PER_NEWLINE = 1; - /** - * Return a String representation of {@code object}, with lines wrapped at - * {@code lineLength}. - */ - public static String render(EObject object, int lineLength) { - return render(object, lineLength, inferTarget(object), false); - } + /** Return a String representation of {@code object}, with lines wrapped at {@code lineLength}. */ + public static String render(EObject object, int lineLength) { + return render(object, lineLength, inferTarget(object), false); + } - /** Return a function that renders AST nodes for the given target. */ - public static Function renderer(Target target) { - return object -> render(object, DEFAULT_LINE_LENGTH, target, true); - } + /** Return a function that renders AST nodes for the given target. */ + public static Function renderer(Target target) { + return object -> render(object, DEFAULT_LINE_LENGTH, target, true); + } - /** - * Return a String representation of {@code object}, with lines wrapped at - * {@code lineLength}, with the assumption that the target language is - * {@code target}. - */ - public static String render(EObject object, int lineLength, Target target, boolean codeMapTags) { - MalleableString ms = ToLf.instance.doSwitch(object); - String singleLineCommentPrefix = target.getSingleLineCommentPrefix(); - ms.findBestRepresentation( - () -> ms.render(INDENTATION, singleLineCommentPrefix, codeMapTags, null), - r -> r.levelsOfCommentDisplacement() * BADNESS_PER_LEVEL_OF_COMMENT_DISPLACEMENT + /** + * Return a String representation of {@code object}, with lines wrapped at {@code lineLength}, + * with the assumption that the target language is {@code target}. + */ + public static String render(EObject object, int lineLength, Target target, boolean codeMapTags) { + MalleableString ms = ToLf.instance.doSwitch(object); + String singleLineCommentPrefix = target.getSingleLineCommentPrefix(); + ms.findBestRepresentation( + () -> ms.render(INDENTATION, singleLineCommentPrefix, codeMapTags, null), + r -> + r.levelsOfCommentDisplacement() * BADNESS_PER_LEVEL_OF_COMMENT_DISPLACEMENT + countCharactersViolatingLineLength(lineLength).applyAsLong(r.rendering()) * BADNESS_PER_CHARACTER_VIOLATING_LINE_LENGTH + countNewlines(r.rendering()) * BADNESS_PER_NEWLINE, - lineLength, - INDENTATION, - singleLineCommentPrefix - ); - var optimizedRendering = ms.render(INDENTATION, singleLineCommentPrefix, codeMapTags, null); - List comments = optimizedRendering.unplacedComments().toList(); - return comments.stream().allMatch(String::isBlank) ? optimizedRendering.rendering() - : lineWrapComments(comments, lineLength, singleLineCommentPrefix) - + "\n" + optimizedRendering.rendering(); + lineLength, + INDENTATION, + singleLineCommentPrefix); + var optimizedRendering = ms.render(INDENTATION, singleLineCommentPrefix, codeMapTags, null); + List comments = optimizedRendering.unplacedComments().toList(); + return comments.stream().allMatch(String::isBlank) + ? optimizedRendering.rendering() + : lineWrapComments(comments, lineLength, singleLineCommentPrefix) + + "\n" + + optimizedRendering.rendering(); + } + + /** Infer the target language of the object. */ + private static Target inferTarget(EObject object) { + if (object instanceof Model model) { + var targetDecl = ASTUtils.targetDecl(model); + if (targetDecl != null) { + return Target.fromDecl(targetDecl); + } } - - /** - * Infer the target language of the object. - */ - private static Target inferTarget(EObject object) { - if (object instanceof Model model) { - var targetDecl = ASTUtils.targetDecl(model); - if (targetDecl != null) { - return Target.fromDecl(targetDecl); - } - } - throw new IllegalArgumentException("Unable to determine target based on given EObject."); - } - - /** - * Return a String representation of {@code object} using a reasonable - * default line length. - */ - public static String render(EObject object) { return render(object, DEFAULT_LINE_LENGTH); } - - /** - * Return the number of characters appearing in columns exceeding {@code lineLength}. - */ - private static ToLongFunction countCharactersViolatingLineLength(int lineLength) { - return s -> s.lines().mapToInt(it -> Math.max(0, it.length() - lineLength)).sum(); - } - - private static long countNewlines(String s) { - return s.lines().count(); - } - - /** - * Break lines at spaces so that each line is no more than {@code width} - * columns long, if possible. Normalize whitespace. Merge consecutive - * single-line comments. - */ - static String lineWrapComments( - List comments, - int width, - String singleLineCommentPrefix - ) { - StringBuilder ret = new StringBuilder(); - StringBuilder current = new StringBuilder(); - for (String comment : comments) { - if (comment.stripLeading().startsWith("/*")) { - if (!ret.isEmpty() && !current.isEmpty()) ret.append("\n"); - ret.append(lineWrapComment(current.toString(), width, singleLineCommentPrefix)); - current.setLength(0); - if (!ret.isEmpty()) ret.append("\n"); - ret.append(lineWrapComment(comment.strip(), width, singleLineCommentPrefix)); - } else { - if (!current.isEmpty()) current.append("\n"); - current.append(comment.strip()); - } - } + throw new IllegalArgumentException("Unable to determine target based on given EObject."); + } + + /** Return a String representation of {@code object} using a reasonable default line length. */ + public static String render(EObject object) { + return render(object, DEFAULT_LINE_LENGTH); + } + + /** Return the number of characters appearing in columns exceeding {@code lineLength}. */ + private static ToLongFunction countCharactersViolatingLineLength(int lineLength) { + return s -> s.lines().mapToInt(it -> Math.max(0, it.length() - lineLength)).sum(); + } + + private static long countNewlines(String s) { + return s.lines().count(); + } + + /** + * Break lines at spaces so that each line is no more than {@code width} columns long, if + * possible. Normalize whitespace. Merge consecutive single-line comments. + */ + static String lineWrapComments(List comments, int width, String singleLineCommentPrefix) { + StringBuilder ret = new StringBuilder(); + StringBuilder current = new StringBuilder(); + for (String comment : comments) { + if (comment.stripLeading().startsWith("/*")) { if (!ret.isEmpty() && !current.isEmpty()) ret.append("\n"); ret.append(lineWrapComment(current.toString(), width, singleLineCommentPrefix)); - return ret.toString(); + current.setLength(0); + if (!ret.isEmpty()) ret.append("\n"); + ret.append(lineWrapComment(comment.strip(), width, singleLineCommentPrefix)); + } else { + if (!current.isEmpty()) current.append("\n"); + current.append(comment.strip()); + } } - /** Wrap lines. Do not merge lines that start with weird characters. */ - private static String lineWrapComment( - String comment, - int width, - String singleLineCommentPrefix - ) { - width = Math.max(width, MINIMUM_COMMENT_WIDTH_IN_COLUMNS); - List> paragraphs = Arrays.stream( - comment.strip() - .replaceAll("^/?((\\*|//|#)\\s*)+", "") - .replaceAll("\\s*\\*/$", "") - .replaceAll("(?<=(\\r\\n|\\r|\\n))\\h*(\\*|//|#)\\h*", "") - .split("(\n\\s*){2,}") - ).map(s -> Arrays.stream(s.split("(\\r\\n|\\r|\\n)\\h*(?=[@#$%^&*\\-+=:;<>/])"))) + if (!ret.isEmpty() && !current.isEmpty()) ret.append("\n"); + ret.append(lineWrapComment(current.toString(), width, singleLineCommentPrefix)); + return ret.toString(); + } + /** Wrap lines. Do not merge lines that start with weird characters. */ + private static String lineWrapComment(String comment, int width, String singleLineCommentPrefix) { + width = Math.max(width, MINIMUM_COMMENT_WIDTH_IN_COLUMNS); + List> paragraphs = + Arrays.stream( + comment + .strip() + .replaceAll("^/?((\\*|//|#)\\s*)+", "") + .replaceAll("\\s*\\*/$", "") + .replaceAll("(?<=(\\r\\n|\\r|\\n))\\h*(\\*|//|#)\\h*", "") + .split("(\n\\s*){2,}")) + .map(s -> Arrays.stream(s.split("(\\r\\n|\\r|\\n)\\h*(?=[@#$%^&*\\-+=:;<>/])"))) .map(stream -> stream.map(s -> s.replaceAll("\\s+", " "))) .map(Stream::toList) .toList(); - if (MULTILINE_COMMENT.matcher(comment).matches()) { - if (paragraphs.size() == 1 && paragraphs.get(0).size() == 1) { - String singleLineRepresentation = String.format( - "/** %s */", paragraphs.get(0).get(0) - ); - if (singleLineRepresentation.length() <= width) return singleLineRepresentation; - } - return String.format("/**\n%s\n */", lineWrapComment(paragraphs, width, " * ")); - } - return lineWrapComment(paragraphs, width, singleLineCommentPrefix + " "); - } - - /** - * Wrap lines. - * @param paragraphs A list of lists of subparagraphs. - * @param width The preferred maximum number of columns per line. - * @param linePrefix A string to prepend to each line of comment. - */ - private static String lineWrapComment( - List> paragraphs, - int width, - String linePrefix - ) { - int widthAfterPrefix = Math.max( - width - linePrefix.length(), - MINIMUM_COMMENT_WIDTH_IN_COLUMNS - ); - return paragraphs.stream() - .map(paragraph -> wrapLines(paragraph, widthAfterPrefix) - .map(s -> (linePrefix + s.stripLeading()).stripTrailing()) - .collect(Collectors.joining("\n")) - ).collect(Collectors.joining(String.format("\n%s\n", linePrefix.stripTrailing()))); + if (MULTILINE_COMMENT.matcher(comment).matches()) { + if (paragraphs.size() == 1 && paragraphs.get(0).size() == 1) { + String singleLineRepresentation = String.format("/** %s */", paragraphs.get(0).get(0)); + if (singleLineRepresentation.length() <= width) return singleLineRepresentation; + } + return String.format("/**\n%s\n */", lineWrapComment(paragraphs, width, " * ")); } - - /** Wrap a given paragraph. */ - private static Stream wrapLines(List subparagraphs, int width) { - var ret = new ArrayList(); - for (String s : subparagraphs) { - int numCharactersProcessed = 0; - while (numCharactersProcessed + width < s.length()) { - // try to wrap at space - int breakAt = s.lastIndexOf(' ', numCharactersProcessed + width); - // if unable to find space in limit, extend to the first space we find - if (breakAt < numCharactersProcessed) { - breakAt = s.indexOf(' ', numCharactersProcessed + width); - } - if (breakAt < numCharactersProcessed) breakAt = s.length(); - ret.add(s.substring(numCharactersProcessed, breakAt)); - numCharactersProcessed = breakAt + 1; - } - if (numCharactersProcessed < s.length()) ret.add(s.substring(numCharactersProcessed)); + return lineWrapComment(paragraphs, width, singleLineCommentPrefix + " "); + } + + /** + * Wrap lines. + * + * @param paragraphs A list of lists of subparagraphs. + * @param width The preferred maximum number of columns per line. + * @param linePrefix A string to prepend to each line of comment. + */ + private static String lineWrapComment( + List> paragraphs, int width, String linePrefix) { + int widthAfterPrefix = Math.max(width - linePrefix.length(), MINIMUM_COMMENT_WIDTH_IN_COLUMNS); + return paragraphs.stream() + .map( + paragraph -> + wrapLines(paragraph, widthAfterPrefix) + .map(s -> (linePrefix + s.stripLeading()).stripTrailing()) + .collect(Collectors.joining("\n"))) + .collect(Collectors.joining(String.format("\n%s\n", linePrefix.stripTrailing()))); + } + + /** Wrap a given paragraph. */ + private static Stream wrapLines(List subparagraphs, int width) { + var ret = new ArrayList(); + for (String s : subparagraphs) { + int numCharactersProcessed = 0; + while (numCharactersProcessed + width < s.length()) { + // try to wrap at space + int breakAt = s.lastIndexOf(' ', numCharactersProcessed + width); + // if unable to find space in limit, extend to the first space we find + if (breakAt < numCharactersProcessed) { + breakAt = s.indexOf(' ', numCharactersProcessed + width); } - return ret.stream(); + if (breakAt < numCharactersProcessed) breakAt = s.length(); + ret.add(s.substring(numCharactersProcessed, breakAt)); + numCharactersProcessed = breakAt + 1; + } + if (numCharactersProcessed < s.length()) ret.add(s.substring(numCharactersProcessed)); } - - /** - * Merge {@code comment} into the given list of strings without changing the - * length of the list, preferably in a place that indicates that - * {@code comment} is associated with the {@code i}th string. - * @param comment A comment associated with an element of - * {@code components}. - * @param components A list of strings that will be rendered in sequence. - * @param i The position of the component associated with {@code comment}. - * @param width The ideal number of columns available for comments that - * appear on their own line. - * @param keepCommentsOnSameLine Whether to make a best-effort attempt to - * keep the comment on the same line as the associated string. - * @param singleLineCommentPrefix The prefix that marks the start of a - * single-line comment. - * @param startColumn The ideal starting column of a comment - * @return Whether the comment placement succeeded. - */ - static boolean placeComment( - List comment, - List components, - int i, - int width, - boolean keepCommentsOnSameLine, - String singleLineCommentPrefix, - int startColumn - ) { - if (comment.stream().allMatch(String::isBlank)) return true; - String wrapped = FormattingUtils.lineWrapComments(comment, width, singleLineCommentPrefix); - if (keepCommentsOnSameLine && wrapped.lines().count() == 1 && !wrapped.startsWith("/**")) { - int sum = 0; - for (int j = 0; j < components.size(); j++) { - String current = components.get(j); - if (j >= i && current.contains("\n")) { - components.set(j, components.get(j).replaceFirst( - "\n", - " ".repeat(Math.max( - 2, - startColumn - sum - components.get(j).indexOf("\n") - )) + wrapped + "\n" - )); - return true; - } else if (current.contains("\n")) { - sum = current.length() - current.lastIndexOf("\n") - 1; - } else { - sum += current.length(); - } - } + return ret.stream(); + } + + /** + * Merge {@code comment} into the given list of strings without changing the length of the list, + * preferably in a place that indicates that {@code comment} is associated with the {@code i}th + * string. + * + * @param comment A comment associated with an element of {@code components}. + * @param components A list of strings that will be rendered in sequence. + * @param i The position of the component associated with {@code comment}. + * @param width The ideal number of columns available for comments that appear on their own line. + * @param keepCommentsOnSameLine Whether to make a best-effort attempt to keep the comment on the + * same line as the associated string. + * @param singleLineCommentPrefix The prefix that marks the start of a single-line comment. + * @param startColumn The ideal starting column of a comment + * @return Whether the comment placement succeeded. + */ + static boolean placeComment( + List comment, + List components, + int i, + int width, + boolean keepCommentsOnSameLine, + String singleLineCommentPrefix, + int startColumn) { + if (comment.stream().allMatch(String::isBlank)) return true; + String wrapped = FormattingUtils.lineWrapComments(comment, width, singleLineCommentPrefix); + if (keepCommentsOnSameLine && wrapped.lines().count() == 1 && !wrapped.startsWith("/**")) { + int sum = 0; + for (int j = 0; j < components.size(); j++) { + String current = components.get(j); + if (j >= i && current.contains("\n")) { + components.set( + j, + components + .get(j) + .replaceFirst( + "\n", + " ".repeat(Math.max(2, startColumn - sum - components.get(j).indexOf("\n"))) + + wrapped + + "\n")); + return true; + } else if (current.contains("\n")) { + sum = current.length() - current.lastIndexOf("\n") - 1; + } else { + sum += current.length(); } - for (int j = i - 1; j >= 0; j--) { - if (components.get(j).endsWith("\n")) { - components.set(j, String.format("%s%s\n", components.get(j), wrapped)); - return true; - } - } - return false; + } + } + for (int j = i - 1; j >= 0; j--) { + if (components.get(j).endsWith("\n")) { + components.set(j, String.format("%s%s\n", components.get(j), wrapped)); + return true; + } } + return false; + } } diff --git a/org.lflang/src/org/lflang/ast/IsEqual.java b/org.lflang/src/org/lflang/ast/IsEqual.java index af0274ea2a..bc0fc31abf 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; @@ -66,620 +64,603 @@ import org.lflang.lf.util.LfSwitch; /** - * Switch class that checks if subtrees of the AST are semantically equivalent - * to each other. Return {@code false} if they are not equivalent; return - * {@code true} or {@code false} (but preferably {@code true}) if they are - * equivalent. + * Switch class that checks if subtrees of the AST are semantically equivalent to each other. Return + * {@code false} if they are not equivalent; return {@code true} or {@code false} (but preferably + * {@code true}) if they are equivalent. */ public class IsEqual extends LfSwitch { - private final EObject otherObject; - - public IsEqual(EObject other) { - this.otherObject = other; - } - - @Override - public Boolean doSwitch(EObject eObject) { - if (otherObject == eObject) return true; - if (eObject == null) return false; - return super.doSwitch(eObject); - } - - @Override - public Boolean caseModel(Model object) { - return new ComparisonMachine<>(object, Model.class) - .equivalent(Model::getTarget) - .listsEquivalent(Model::getImports) - .listsEquivalent(Model::getPreambles) - .listsEquivalent(Model::getReactors).conclusion; - } - - @Override - public Boolean caseImport(Import object) { - return new ComparisonMachine<>(object, Import.class) - .equalAsObjects(Import::getImportURI) - .listsEquivalent(Import::getReactorClasses).conclusion; - } - - @Override - public Boolean caseReactorDecl(ReactorDecl object) { - return new ComparisonMachine<>(object, ReactorDecl.class) - .equalAsObjects(ReactorDecl::getName) - .conclusion; - } - - @Override - public Boolean caseImportedReactor(ImportedReactor object) { - return new ComparisonMachine<>(object, ImportedReactor.class) - .equalAsObjects(ImportedReactor::getName) - .equivalent(ImportedReactor::getReactorClass) - .conclusion; - } - - @Override - public Boolean caseReactor(Reactor object) { - return new ComparisonMachine<>(object, Reactor.class) - .listsEquivalent(Reactor::getAttributes) - .equalAsObjects(Reactor::isFederated) - .equalAsObjects(Reactor::isRealtime) - .equalAsObjects(Reactor::isMain) - .equalAsObjects(Reactor::getName) - .listsEquivalent(Reactor::getTypeParms) - .listsEquivalent(Reactor::getParameters) - .equivalent(Reactor::getHost) - .listsEquivalent(Reactor::getSuperClasses) - .listsEquivalent(Reactor::getPreambles) - .listsEquivalent(Reactor::getInputs) - .listsEquivalent(Reactor::getOutputs) - .listsEquivalent(Reactor::getTimers) - .listsEquivalent(Reactor::getActions) - .listsEquivalent(Reactor::getInstantiations) - .listsEquivalent(Reactor::getConnections) - .listsEquivalent(Reactor::getStateVars) - .listsEquivalent(Reactor::getReactions) - .listsEquivalent(Reactor::getMethods) - .listsEquivalent(Reactor::getModes) - .conclusion; - } - - @Override - public Boolean caseTypeParm(TypeParm object) { - return new ComparisonMachine<>(object, TypeParm.class) - .equalAsObjects(TypeParm::getLiteral) - .equivalent(TypeParm::getCode) - .conclusion; - } - - @Override - public Boolean caseTargetDecl(TargetDecl object) { - return new ComparisonMachine<>(object, TargetDecl.class) - .equalAsObjects(TargetDecl::getName) - .equivalentModulo( - TargetDecl::getConfig, - (KeyValuePairs it) -> it != null && it.getPairs().isEmpty() ? null : it - ) - .conclusion; - } - - @Override - public Boolean caseStateVar(StateVar object) { - return new ComparisonMachine<>(object, StateVar.class) - .listsEquivalent(StateVar::getAttributes) - .equalAsObjects(StateVar::getName) - .equivalent(StateVar::getType) - .equivalent(StateVar::getInit) - .conclusion; - } - - @Override - public Boolean caseInitializer(Initializer object) { - // Empty braces are not equivalent to no init. - return new ComparisonMachine<>(object, Initializer.class) - .equalAsObjects(Initializer::isBraces) - // An initializer with no parens is equivalent to one with parens, - // if the list has a single element. This is probably going to change - // when we introduce equals initializers. - // .equalAsObjects(Initializer::isParens) - .listsEquivalent(Initializer::getExprs) - .conclusion; - } - - @Override - public Boolean caseMethod(Method object) { - return new ComparisonMachine<>(object, Method.class) - .equalAsObjects(Method::isConst) - .equalAsObjects(Method::getName) - .listsEquivalent(Method::getArguments) - .equivalent(Method::getReturn) - .equivalent(Method::getCode) - .conclusion; - } - - @Override - public Boolean caseMethodArgument(MethodArgument object) { - return new ComparisonMachine<>(object, MethodArgument.class) - .equalAsObjects(MethodArgument::getName) - .equivalent(MethodArgument::getType) - .conclusion; - } - - @Override - public Boolean caseInput(Input object) { - return new ComparisonMachine<>(object, Input.class) - .listsEquivalent(Input::getAttributes) - .equalAsObjects(Input::isMutable) - .equivalent(Input::getWidthSpec) - .equivalent(Input::getType) - .conclusion; - } - - @Override - public Boolean caseOutput(Output object) { - return new ComparisonMachine<>(object, Output.class) - .listsEquivalent(Output::getAttributes) - .equivalent(Output::getWidthSpec) - .equalAsObjects(Output::getName) - .equivalent(Output::getType) - .conclusion; - } - - @Override - public Boolean caseTimer(Timer object) { - return new ComparisonMachine<>(object, Timer.class) - .listsEquivalent(Timer::getAttributes) - .equalAsObjects(Timer::getName) - .equivalent(Timer::getOffset) - .equivalent(Timer::getPeriod) - .conclusion; - } - - @Override - public Boolean caseMode(Mode object) { - return new ComparisonMachine<>(object, Mode.class) - .equalAsObjects(Mode::isInitial) - .equalAsObjects(Mode::getName) - .listsEquivalent(Mode::getStateVars) - .listsEquivalent(Mode::getTimers) - .listsEquivalent(Mode::getActions) - .listsEquivalent(Mode::getInstantiations) - .listsEquivalent(Mode::getConnections) - .listsEquivalent(Mode::getReactions) - .conclusion; - } - - @Override - public Boolean caseAction(Action object) { - return new ComparisonMachine<>(object, Action.class) - .listsEquivalent(Action::getAttributes) - .equalAsObjects(Action::getOrigin) // This is an enum - .equalAsObjects(Action::getName) - .equivalent(Action::getMinDelay) - .equivalent(Action::getMinSpacing) - .equalAsObjects(Action::getPolicy) - .equivalent(Action::getType) - .conclusion; - } - - @Override - public Boolean caseAttribute(Attribute object) { - return new ComparisonMachine<>(object, Attribute.class) - .equalAsObjects(Attribute::getAttrName) - .listsEquivalent(Attribute::getAttrParms) - .conclusion; - } - - @Override - public Boolean caseAttrParm(AttrParm object) { - return new ComparisonMachine<>(object, AttrParm.class) - .equalAsObjects(AttrParm::getName) - .equalAsObjects(AttrParm::getValue) - .conclusion; - } - - @Override - public Boolean caseReaction(Reaction object) { - return new ComparisonMachine<>(object, Reaction.class) - .listsEquivalent(Reaction::getAttributes) - .listsEquivalent(Reaction::getTriggers) - .listsEquivalent(Reaction::getSources) - .listsEquivalent(Reaction::getEffects) - .equalAsObjects(Reaction::isMutation) - .equalAsObjects(Reaction::getName) - .equivalent(Reaction::getCode) - .equivalent(Reaction::getStp) - .equivalent(Reaction::getDeadline) - .conclusion; - } - - @Override - public Boolean caseTriggerRef(TriggerRef object) { - throw thereIsAMoreSpecificCase(TriggerRef.class, BuiltinTriggerRef.class, VarRef.class); - } - - @Override - public Boolean caseBuiltinTriggerRef(BuiltinTriggerRef object) { - return new ComparisonMachine<>(object, BuiltinTriggerRef.class) - .equalAsObjects(BuiltinTriggerRef::getType) // This is an enum - .conclusion; - } - - @Override - public Boolean caseDeadline(Deadline object) { - return new ComparisonMachine<>(object, Deadline.class) - .equivalent(Deadline::getDelay) - .equivalent(Deadline::getCode) - .conclusion; - } - - @Override - public Boolean caseSTP(STP object) { - return new ComparisonMachine<>(object, STP.class) - .equivalent(STP::getValue) - .equivalent(STP::getCode) - .conclusion; - } - - @Override - public Boolean casePreamble(Preamble object) { - return new ComparisonMachine<>(object, Preamble.class) - .equalAsObjects(Preamble::getVisibility) // This is an enum - .equivalent(Preamble::getCode) - .conclusion; - } - - @Override - public Boolean caseInstantiation(Instantiation object) { - return new ComparisonMachine<>(object, Instantiation.class) - .equalAsObjects(Instantiation::getName) - .equivalent(Instantiation::getWidthSpec) - .equivalent(Instantiation::getReactorClass) - .listsEquivalent(Instantiation::getTypeArgs) - .listsEquivalent(Instantiation::getParameters) - .equivalent(Instantiation::getHost) - .conclusion; - } - - @Override - public Boolean caseConnection(Connection object) { - return new ComparisonMachine<>(object, Connection.class) - .listsEquivalent(Connection::getLeftPorts) - .equalAsObjects(Connection::isIterated) - .equalAsObjects(Connection::isPhysical) - .listsEquivalent(Connection::getRightPorts) - .equivalent(Connection::getDelay) - .equivalent(Connection::getSerializer) - .conclusion; - } - - @Override - public Boolean caseSerializer(Serializer object) { - return new ComparisonMachine<>(object, Serializer.class) - .equalAsObjects(Serializer::getType) - .conclusion; - } - - @Override - public Boolean caseKeyValuePairs(KeyValuePairs object) { - return new ComparisonMachine<>(object, KeyValuePairs.class) - .listsEquivalent(KeyValuePairs::getPairs) - .conclusion; - } - - @Override - public Boolean caseKeyValuePair(KeyValuePair object) { - return new ComparisonMachine<>(object, KeyValuePair.class) - .equalAsObjects(KeyValuePair::getName) - .equivalent(KeyValuePair::getValue) - .conclusion; - } - - @Override - public Boolean caseArray(Array object) { - return new ComparisonMachine<>(object, Array.class) - .listsEquivalent(Array::getElements) - .conclusion; - } - - @Override - public Boolean caseElement(Element object) { - return new ComparisonMachine<>(object, Element.class) - .equivalent(Element::getKeyvalue) - .equivalent(Element::getArray) - .equalAsObjects(Element::getLiteral) - .equalAsObjects(Element::getId) - .equalAsObjects(Element::getUnit) - .conclusion; - } - - @Override - public Boolean caseTypedVariable(TypedVariable object) { - throw thereIsAMoreSpecificCase(TypedVariable.class, Port.class, Action.class); - } - - @Override - public Boolean caseVariable(Variable object) { - throw thereIsAMoreSpecificCase(Variable.class, TypedVariable.class, Timer.class, Mode.class); - } - - @Override - public Boolean caseVarRef(VarRef object) { - return new ComparisonMachine<>(object, VarRef.class) - .equalAsObjects(varRef -> varRef.getVariable() instanceof Mode ? null : varRef.getVariable().getName()) - .equivalent(varRef -> varRef.getVariable() instanceof Mode ? null : varRef.getVariable()) - .equalAsObjects(varRef -> varRef.getContainer() == null ? null : varRef.getContainer().getName()) - .equalAsObjects(VarRef::isInterleaved) - .equalAsObjects(VarRef::getTransition) - .conclusion; - } - - @Override - public Boolean caseAssignment(Assignment object) { - return new ComparisonMachine<>(object, Assignment.class) - .equivalent(Assignment::getLhs) - .equivalent(Assignment::getRhs) - .conclusion; - } - - @Override - public Boolean caseParameter(Parameter object) { - return new ComparisonMachine<>(object, Parameter.class) - .listsEquivalent(Parameter::getAttributes) - .equalAsObjects(Parameter::getName) - .equivalent(Parameter::getType) - .equivalent(Parameter::getInit) - .conclusion; - } - - @Override - public Boolean caseExpression(Expression object) { - throw thereIsAMoreSpecificCase( - Expression.class, - Literal.class, - Time.class, - ParameterReference.class, - Code.class, - BracedListExpression.class - ); - } - - @Override - public Boolean caseBracedListExpression(BracedListExpression object) { - return new ComparisonMachine<>(object, BracedListExpression.class) - .listsEquivalent(BracedListExpression::getItems) - .conclusion; - } - - @Override - public Boolean caseParameterReference(ParameterReference object) { - return new ComparisonMachine<>(object, ParameterReference.class) - .equivalent(ParameterReference::getParameter) - .conclusion; - } - - @Override - public Boolean caseTime(Time object) { - return new ComparisonMachine<>(object, Time.class) - .equalAsObjects(Time::getInterval) - .equalAsObjectsModulo( - Time::getUnit, - ((Function) TimeUnit::getCanonicalName) - .compose(TimeUnit::fromName) - ) - .conclusion; - } - - @Override - public Boolean casePort(Port object) { - throw thereIsAMoreSpecificCase(Port.class, Input.class, Output.class); - } - - @Override - public Boolean caseType(Type object) { - return new ComparisonMachine<>(object, Type.class) - .equivalent(Type::getCode) - .equalAsObjects(Type::isTime) - .equivalent(Type::getArraySpec) - .equalAsObjects(Type::getId) - .listsEquivalent(Type::getTypeArgs) - .listsEqualAsObjects(Type::getStars) - .equivalent(Type::getArraySpec) - .equivalent(Type::getCode) - .conclusion; - } - - @Override - public Boolean caseArraySpec(ArraySpec object) { - return new ComparisonMachine<>(object, ArraySpec.class) - .equalAsObjects(ArraySpec::isOfVariableLength) - .equalAsObjects(ArraySpec::getLength) - .conclusion; - } - - @Override - public Boolean 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; - } - } - return true; - } - - /** Conclude false if the two properties are not equal as objects. */ - ComparisonMachine equalAsObjects(Function propertyGetter) { - return equalAsObjectsModulo(propertyGetter, Function.identity()); - } - - /** - * Conclude false if the two properties are not equal as objects, - * given that {@code projectionToClassRepresentatives} maps each - * object to some semantically equivalent object. - */ - ComparisonMachine equalAsObjectsModulo( - Function propertyGetter, - Function projectionToClassRepresentatives - ) { - if (propertyGetter.apply(object) instanceof EObject) { - throw new IllegalArgumentException( - "EObjects should be compared for semantic equivalence, not object equality." - ); - } - propertyGetter = projectionToClassRepresentatives.compose(propertyGetter); - if (conclusion) conclusion = Objects.equals(propertyGetter.apply(object), propertyGetter.apply(other)); - return this; - } - - /** - * Conclude false if the two properties are not semantically equivalent - * parse nodes. - */ - ComparisonMachine equivalent(Function propertyGetter) { - return equivalentModulo(propertyGetter, Function.identity()); - } - - /** - * Conclude false if the two properties are not semantically equivalent - * parse nodes, given that {@code projectionToClassRepresentatives} - * maps each parse node to some semantically equivalent node. - */ - ComparisonMachine equivalentModulo( - Function propertyGetter, - Function projectionToClassRepresentatives - ) { - propertyGetter = projectionToClassRepresentatives.compose(propertyGetter); - if (conclusion) conclusion = new IsEqual(propertyGetter.apply(object)) - .doSwitch(propertyGetter.apply(other)); - return this; + .map(Class::getName) + .map(it -> it + (it.endsWith("s") ? "es" : "s")) + .collect(Collectors.joining(" or ")))); + } + + /** Fluently compare a pair of parse tree nodes for equivalence. */ + private class ComparisonMachine { + private final E object; + private final E other; + private boolean conclusion; + + ComparisonMachine(E object, Class clz) { + this.object = object; + this.conclusion = clz.isInstance(otherObject); + this.other = conclusion ? clz.cast(otherObject) : null; + } + + /** Conclude false if the two given Lists are different as EObject sequences. Order matters. */ + ComparisonMachine listsEquivalent(Function> listGetter) { + if (conclusion) + conclusion = listsEqualish(listGetter, (T a, T b) -> new IsEqual(a).doSwitch(b)); + return this; + } + + /** Conclude false if the two given Lists are different as object sequences. Order matters. */ + ComparisonMachine listsEqualAsObjects(Function> listGetter) { + if (conclusion) conclusion = listsEqualish(listGetter, Objects::equals); + return this; + } + + boolean listsEqualish( + Function> listGetter, BiPredicate equalish) { + if (!conclusion) return false; + List list0 = listGetter.apply(object); + List list1 = listGetter.apply(other); + if (list0 == list1) return true; // e.g., they are both null + if (list0.size() != list1.size()) return false; + for (int i = 0; i < list0.size(); i++) { + if (!equalish.test(list0.get(i), list1.get(i))) { + return false; } - } + } + return true; + } + + /** Conclude false if the two properties are not equal as objects. */ + ComparisonMachine equalAsObjects(Function propertyGetter) { + return equalAsObjectsModulo(propertyGetter, Function.identity()); + } + + /** + * Conclude false if the two properties are not equal as objects, given that {@code + * projectionToClassRepresentatives} maps each object to some semantically equivalent object. + */ + ComparisonMachine equalAsObjectsModulo( + Function propertyGetter, Function projectionToClassRepresentatives) { + if (propertyGetter.apply(object) instanceof EObject) { + throw new IllegalArgumentException( + "EObjects should be compared for semantic equivalence, not object equality."); + } + propertyGetter = projectionToClassRepresentatives.compose(propertyGetter); + if (conclusion) + conclusion = Objects.equals(propertyGetter.apply(object), propertyGetter.apply(other)); + return this; + } + + /** Conclude false if the two properties are not semantically equivalent parse nodes. */ + ComparisonMachine equivalent(Function propertyGetter) { + return equivalentModulo(propertyGetter, Function.identity()); + } + + /** + * Conclude false if the two properties are not semantically equivalent parse nodes, given that + * {@code projectionToClassRepresentatives} maps each parse node to some semantically equivalent + * node. + */ + ComparisonMachine equivalentModulo( + Function propertyGetter, Function projectionToClassRepresentatives) { + propertyGetter = projectionToClassRepresentatives.compose(propertyGetter); + if (conclusion) + conclusion = + new IsEqual(propertyGetter.apply(object)).doSwitch(propertyGetter.apply(other)); + return this; + } + } } diff --git a/org.lflang/src/org/lflang/ast/ToLf.java b/org.lflang/src/org/lflang/ast/ToLf.java index 7ecf04684f..9505c57478 100644 --- a/org.lflang/src/org/lflang/ast/ToLf.java +++ b/org.lflang/src/org/lflang/ast/ToLf.java @@ -20,7 +20,6 @@ import org.eclipse.xtext.nodemodel.INode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import org.eclipse.xtext.xbase.lib.StringExtensions; - import org.lflang.Target; import org.lflang.ast.MalleableString.Builder; import org.lflang.ast.MalleableString.Joiner; @@ -219,11 +218,7 @@ public MalleableString caseCode(Code code) { .map(String::stripTrailing) .collect(Collectors.joining("\n")); MalleableString singleLineRepresentation = - new Builder() - .append("{= ") - .append(content.strip()) - .append(" =}") - .get(); + new Builder().append("{= ").append(content.strip()).append(" =}").get(); MalleableString multilineRepresentation = new Builder() .append(String.format("{=%n")) diff --git a/org.lflang/src/org/lflang/ast/ToText.java b/org.lflang/src/org/lflang/ast/ToText.java index 4d02d4ed08..f1d47b7c69 100644 --- a/org.lflang/src/org/lflang/ast/ToText.java +++ b/org.lflang/src/org/lflang/ast/ToText.java @@ -4,7 +4,6 @@ import org.eclipse.xtext.nodemodel.ICompositeNode; import org.eclipse.xtext.nodemodel.ILeafNode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; - import org.lflang.lf.ArraySpec; import org.lflang.lf.BracedListExpression; import org.lflang.lf.Code; @@ -19,112 +18,115 @@ import org.lflang.lf.util.LfSwitch; import org.lflang.util.StringUtil; - /** - * Switch class for converting AST nodes to some textual representation that seems likely - * to be useful for as many code generators as possible. + * Switch class for converting AST nodes to some textual representation that seems likely to be + * useful for as many code generators as possible. */ public class ToText extends LfSwitch { - /// public instance initialized when loading the class - public static final ToText instance = new ToText(); - - // private constructor - private ToText() { super(); } - - @Override - public String caseArraySpec(ArraySpec spec) { - return ToLf.instance.doSwitch(spec).toString(); - } - - @Override - public String caseCodeExpr(CodeExpr object) { - return caseCode(object.getCode()); + /// public instance initialized when loading the class + public static final ToText instance = new ToText(); + + // private constructor + private ToText() { + super(); + } + + @Override + public String caseArraySpec(ArraySpec spec) { + return ToLf.instance.doSwitch(spec).toString(); + } + + @Override + public String caseCodeExpr(CodeExpr object) { + return caseCode(object.getCode()); + } + + @Override + public String caseCode(Code code) { + ICompositeNode node = NodeModelUtils.getNode(code); + if (node != null) { + StringBuilder builder = new StringBuilder(Math.max(node.getTotalLength(), 1)); + for (ILeafNode leaf : node.getLeafNodes()) { + builder.append(leaf.getText()); + } + String str = builder.toString().trim(); + // Remove the code delimiters (and any surrounding comments). + // This assumes any comment before {= does not include {=. + int start = str.indexOf("{="); + int end = str.lastIndexOf("=}"); + if (start == -1 || end == -1) { + // Silent failure is needed here because toText is needed to create the intermediate + // representation, + // which the validator uses. + return str; + } + str = str.substring(start + 2, end); + if (str.split("\n").length > 1) { + // multi line code + return StringUtil.trimCodeBlock(str, 1); + } else { + // single line code + return str.trim(); + } + } else if (code.getBody() != null) { + // Code must have been added as a simple string. + return code.getBody(); } - - @Override - public String caseCode(Code code) { - ICompositeNode node = NodeModelUtils.getNode(code); - if (node != null) { - StringBuilder builder = new StringBuilder(Math.max(node.getTotalLength(), 1)); - for (ILeafNode leaf : node.getLeafNodes()) { - builder.append(leaf.getText()); - } - String str = builder.toString().trim(); - // Remove the code delimiters (and any surrounding comments). - // This assumes any comment before {= does not include {=. - int start = str.indexOf("{="); - int end = str.lastIndexOf("=}"); - if (start == -1 || end == -1) { - // Silent failure is needed here because toText is needed to create the intermediate representation, - // which the validator uses. - return str; - } - str = str.substring(start + 2, end); - if (str.split("\n").length > 1) { - // multi line code - return StringUtil.trimCodeBlock(str, 1); - } else { - // single line code - return str.trim(); - } - } else if (code.getBody() != null) { - // Code must have been added as a simple string. - return code.getBody(); - } - return ""; - } - - @Override - public String caseBracedListExpression(BracedListExpression object) { - return ToLf.instance.caseBracedListExpression(object).toString(); + 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(); + } + + @Override + public String caseLiteral(Literal l) { + return ToLf.instance.caseLiteral(l).toString(); + } + + @Override + public String caseParameterReference(ParameterReference p) { + return ToLf.instance.caseParameterReference(p).toString(); + } + + @Override + public String caseTime(Time t) { + return ToLf.instance.caseTime(t).toString(); + } + + @Override + public String caseType(Type type) { + if (type.getCode() != null) { + return caseCode(type.getCode()); } - - @Override - public String caseHost(Host host) { - return ToLf.instance.caseHost(host).toString(); - } - - @Override - public String caseLiteral(Literal l) { - return ToLf.instance.caseLiteral(l).toString(); + return ToLf.instance.caseType(type).toString(); + } + + @Override + public String caseTypeParm(TypeParm t) { + if (t.getCode() != null) return doSwitch(t.getCode()); + return ToLf.instance.caseTypeParm(t).toString(); + } + + @Override + public String caseVarRef(VarRef v) { + if (v.getContainer() != null) { + return String.format("%s.%s", v.getContainer().getName(), v.getVariable().getName()); + } else { + return v.getVariable().getName(); } + } - @Override - public String caseParameterReference(ParameterReference p) { - return ToLf.instance.caseParameterReference(p).toString(); - } - - @Override - public String caseTime(Time t) { - return ToLf.instance.caseTime(t).toString(); - } - - @Override - public String caseType(Type type) { - if (type.getCode() != null) { - return caseCode(type.getCode()); - } - return ToLf.instance.caseType(type).toString(); - } - - @Override - public String caseTypeParm(TypeParm t) { - if (t.getCode() != null) return doSwitch(t.getCode()); - return ToLf.instance.caseTypeParm(t).toString(); - } - - @Override - public String caseVarRef(VarRef v) { - if (v.getContainer() != null) { - return String.format("%s.%s", v.getContainer().getName(), v.getVariable().getName()); - } else { - return v.getVariable().getName(); - } - } - - @Override - public String defaultCase(EObject object) { - throw new UnsupportedOperationException("ToText has no case for " + object.getClass().getName()); - } + @Override + public String defaultCase(EObject object) { + throw new UnsupportedOperationException( + "ToText has no case for " + object.getClass().getName()); + } } diff --git a/org.lflang/src/org/lflang/cli/CliBase.java b/org.lflang/src/org/lflang/cli/CliBase.java index 2f4a9d849c..a6c1c805ed 100644 --- a/org.lflang/src/org/lflang/cli/CliBase.java +++ b/org.lflang/src/org/lflang/cli/CliBase.java @@ -1,26 +1,21 @@ package org.lflang.cli; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Provider; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map.Entry; -import java.util.Properties; import java.util.Set; import java.util.stream.Collectors; - -import picocli.CommandLine; -import picocli.CommandLine.ArgGroup; -import picocli.CommandLine.Command; -import picocli.CommandLine.Model.CommandSpec; -import picocli.CommandLine.Option; -import picocli.CommandLine.Parameters; -import picocli.CommandLine.Spec; - import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; @@ -28,18 +23,16 @@ import org.eclipse.xtext.validation.CheckMode; import org.eclipse.xtext.validation.IResourceValidator; import org.eclipse.xtext.validation.Issue; - import org.lflang.ErrorReporter; import org.lflang.LFRuntimeModule; import org.lflang.LFStandaloneSetup; import org.lflang.util.FileUtil; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonParseException; -import com.google.inject.Inject; -import com.google.inject.Injector; -import com.google.inject.Provider; +import picocli.CommandLine; +import picocli.CommandLine.ArgGroup; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; +import picocli.CommandLine.Spec; /** * Base class for standalone CLI applications. @@ -50,318 +43,277 @@ * @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. - */ - static class MutuallyExclusive { - @Parameters( - arity = "1..", - paramLabel = "FILES", - description = "Paths to one or more Lingua Franca programs.") - protected List files; - - @Option( - names="--json", - description="JSON object containing CLI arguments.") - private String jsonString; - - @Option( - names="--json-file", - description="JSON file containing CLI arguments.") - private Path jsonFile; - } - - @ArgGroup(exclusive = true, multiplicity = "1") - MutuallyExclusive topLevelArg; - - @Option( - names = {"-o", "--output-path"}, - defaultValue = "", - fallbackValue = "", - description = "Specify the root output directory.") - private Path outputPath; - - /** - * Used to collect all errors that happen during validation/generation. - */ - @Inject - protected IssueCollector issueCollector; - - /** - * Used to report error messages at the end. - */ - @Inject - protected ReportingBackend reporter; - - /** - * Used to report error messages at the end. - */ - @Inject - protected ErrorReporter errorReporter; - - /** - * IO context of this run. - */ - @Inject - protected Io io; - - /** - * Injected resource provider. - */ - @Inject - private Provider resourceSetProvider; - - /** - * Injected resource validator. - */ - @Inject - private IResourceValidator validator; - - protected static void cliMain( - String toolName, Class toolClass, - Io io, String[] args) { - // Injector used to obtain Main instance. - final Injector injector = getInjector(toolName, io); - // Main instance. - final CliBase main = injector.getInstance(toolClass); - // Parse arguments and execute main logic. - main.doExecute(io, args); - } - - public void doExecute(Io io, String[] args) { - CommandLine cmd = new CommandLine(this) + /** + * 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. */ + static class MutuallyExclusive { + @Parameters( + arity = "1..", + paramLabel = "FILES", + description = "Paths to one or more Lingua Franca programs.") + protected List files; + + @Option(names = "--json", description = "JSON object containing CLI arguments.") + private String jsonString; + + @Option(names = "--json-file", description = "JSON file containing CLI arguments.") + private Path jsonFile; + } + + @ArgGroup(exclusive = true, multiplicity = "1") + MutuallyExclusive topLevelArg; + + @Option( + names = {"-o", "--output-path"}, + defaultValue = "", + fallbackValue = "", + description = "Specify the root output directory.") + private Path outputPath; + + /** Used to collect all errors that happen during validation/generation. */ + @Inject protected IssueCollector issueCollector; + + /** Used to report error messages at the end. */ + @Inject protected ReportingBackend reporter; + + /** Used to report error messages at the end. */ + @Inject protected ErrorReporter errorReporter; + + /** IO context of this run. */ + @Inject protected Io io; + + /** Injected resource provider. */ + @Inject private Provider resourceSetProvider; + + /** Injected resource validator. */ + @Inject private IResourceValidator validator; + + protected static void cliMain( + String toolName, Class toolClass, Io io, String[] args) { + // Injector used to obtain Main instance. + final Injector injector = getInjector(toolName, io); + // Main instance. + final CliBase main = injector.getInstance(toolClass); + // Parse arguments and execute main logic. + main.doExecute(io, args); + } + + public void doExecute(Io io, String[] args) { + CommandLine cmd = + new CommandLine(this) .setOut(new PrintWriter(io.getOut())) .setErr(new PrintWriter(io.getErr())); - int exitCode = cmd.execute(args); - io.callSystemExit(exitCode); + int exitCode = cmd.execute(args); + io.callSystemExit(exitCode); + } + + /** + * The entrypoint of Picocli applications - the first method called when CliBase, which implements + * the Runnable interface, is instantiated. + */ + public void run() { + // If args are given in a json file, store its contents in jsonString. + if (topLevelArg.jsonFile != null) { + try { + topLevelArg.jsonString = + new String(Files.readAllBytes(io.getWd().resolve(topLevelArg.jsonFile))); + } catch (IOException e) { + reporter.printFatalErrorAndExit("No such file: " + topLevelArg.jsonFile); + } } - - /** - * The entrypoint of Picocli applications - the first method called when - * CliBase, which implements the Runnable interface, is instantiated. - */ - public void run() { - // If args are given in a json file, store its contents in jsonString. - if (topLevelArg.jsonFile != null) { - try { - topLevelArg.jsonString = new String(Files.readAllBytes( - io.getWd().resolve(topLevelArg.jsonFile))); - } catch (IOException e) { - reporter.printFatalErrorAndExit( - "No such file: " + topLevelArg.jsonFile); - } - } - // 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(); - cmd.execute(args); - // If args are already unpacked, invoke tool-specific logic. - } else { - doRun(); - } + // 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(); + cmd.execute(args); + // If args are already unpacked, invoke tool-specific logic. + } else { + doRun(); } - - /* - * The entrypoint of tool-specific logic. - * Lfc and Lff have their own specific implementations for this method. - */ - public abstract void doRun(); - - public static Injector getInjector(String toolName, Io io) { - final ReportingBackend reporter - = new ReportingBackend(io, toolName + ": "); - - // Injector used to obtain Main instance. - return new LFStandaloneSetup( - new LFRuntimeModule(), - new LFStandaloneModule(reporter, io) - ).createInjectorAndDoEMFRegistration(); + } + + /* + * The entrypoint of tool-specific logic. + * Lfc and Lff have their own specific implementations for this method. + */ + public abstract void doRun(); + + public static Injector getInjector(String toolName, Io io) { + final ReportingBackend reporter = new ReportingBackend(io, toolName + ": "); + + // Injector used to obtain Main instance. + return new LFStandaloneSetup(new LFRuntimeModule(), new LFStandaloneModule(reporter, io)) + .createInjectorAndDoEMFRegistration(); + } + + /** Resolve to an absolute path, in the given {@link #io} context. */ + protected Path toAbsolutePath(Path other) { + return io.getWd().resolve(other).toAbsolutePath(); + } + + /** + * Returns the validated input paths. + * + * @return Validated input paths. + */ + protected List getInputPaths() { + List paths = + topLevelArg.files.stream().map(io.getWd()::resolve).collect(Collectors.toList()); + + for (Path path : paths) { + if (!Files.exists(path)) { + reporter.printFatalErrorAndExit(path + ": No such file or directory."); + } } - /** - * Resolve to an absolute path, in the given {@link #io} context. - */ - protected Path toAbsolutePath(Path other) { - return io.getWd().resolve(other).toAbsolutePath(); + return paths; + } + + /** + * Returns the validated, normalized output path. + * + * @return Validated, normalized output path. + */ + protected Path getOutputRoot() { + Path root = null; + if (!outputPath.toString().isEmpty()) { + root = io.getWd().resolve(outputPath).normalize(); + if (!Files.exists(root)) { // FIXME: Create it instead? + reporter.printFatalErrorAndExit(root + ": Output location does not exist."); + } + if (!Files.isDirectory(root)) { + reporter.printFatalErrorAndExit(root + ": Output location is not a directory."); + } } - /** - * Returns the validated input paths. - * - * @return Validated input paths. - */ - protected List getInputPaths() { - List paths = topLevelArg.files.stream() - .map(io.getWd()::resolve) - .collect(Collectors.toList()); - - for (Path path : paths) { - if (!Files.exists(path)) { - reporter.printFatalErrorAndExit( - path + ": No such file or directory."); - } - } - - return paths; + return root; + } + + /** If some errors were collected, print them and abort execution. Otherwise, return. */ + protected void exitIfCollectedErrors() { + if (issueCollector.getErrorsOccurred()) { + // if there are errors, don't print warnings. + List errors = printErrorsIfAny(); + String cause = errors.size() + " previous error"; + if (errors.size() > 1) { + cause += 's'; + } + reporter.printFatalErrorAndExit("Aborting due to " + cause + '.'); } - - /** - * Returns the validated, normalized output path. - * - * @return Validated, normalized output path. - */ - protected Path getOutputRoot() { - Path root = null; - if (!outputPath.toString().isEmpty()) { - root = io.getWd().resolve(outputPath).normalize(); - if (!Files.exists(root)) { // FIXME: Create it instead? - reporter.printFatalErrorAndExit( - root + ": Output location does not exist."); - } - if (!Files.isDirectory(root)) { - reporter.printFatalErrorAndExit( - root + ": Output location is not a directory."); - } + } + + /** + * If any errors were collected, print them, then return them. + * + * @return A list of collected errors. + */ + public List printErrorsIfAny() { + List errors = issueCollector.getErrors(); + errors.forEach(reporter::printIssue); + return errors; + } + + /** + * Validates a given resource. If issues arise during validation, these are recorded using the + * issue collector. + * + * @param resource The resource to validate. + */ + public void validateResource(Resource resource) { + assert resource != null; + + List issues = this.validator.validate(resource, CheckMode.ALL, CancelIndicator.NullImpl); + + for (Issue issue : issues) { + // Issues may also relate to imported resources. + URI uri = issue.getUriToProblem(); + Path path = null; + if (uri != null) { + try { + path = FileUtil.toPath(uri); + } catch (IOException e) { + reporter.printError("Unable to convert '" + uri + "' to path." + e); } - - return root; + } + issueCollector.accept( + new LfIssue( + issue.getMessage(), + issue.getSeverity(), + issue.getLineNumber(), + issue.getColumn(), + issue.getLineNumberEnd(), + issue.getColumnEnd(), + issue.getLength(), + path)); } - - /** - * If some errors were collected, print them and abort execution. - * Otherwise, return. - */ - protected void exitIfCollectedErrors() { - if (issueCollector.getErrorsOccurred()) { - // if there are errors, don't print warnings. - List errors = printErrorsIfAny(); - String cause = errors.size() + " previous error"; - if (errors.size() > 1) { - cause += 's'; - } - reporter.printFatalErrorAndExit("Aborting due to " + cause + '.'); - } + } + + /** + * Obtains a resource from a path. Returns null if path is not an LF file. + * + * @param path The path to obtain the resource from. + * @return The obtained resource. Set to null if path is not an LF file. + */ + public Resource getResource(Path path) { + final ResourceSet set = this.resourceSetProvider.get(); + try { + return set.getResource(URI.createFileURI(path.toString()), true); + } catch (RuntimeException e) { + return null; } + } - /** - * If any errors were collected, print them, then return them. - * @return A list of collected errors. - */ - public List printErrorsIfAny() { - List errors = issueCollector.getErrors(); - errors.forEach(reporter::printIssue); - return errors; - } + private String[] jsonStringToArgs(String jsonString) { + ArrayList argsList = new ArrayList<>(); + JsonObject jsonObject = new JsonObject(); - /** - * Validates a given resource. If issues arise during validation, - * these are recorded using the issue collector. - * - * @param resource The resource to validate. - */ - public void validateResource(Resource resource) { - assert resource != null; - - List issues = this.validator.validate( - resource, CheckMode.ALL, CancelIndicator.NullImpl); - - for (Issue issue : issues) { - // Issues may also relate to imported resources. - URI uri = issue.getUriToProblem(); - Path path = null; - if (uri != null) { - try { - path = FileUtil.toPath(uri); - } catch (IOException e) { - reporter.printError("Unable to convert '" + uri + "' to path." + e); - } - } - issueCollector.accept( - new LfIssue( - issue.getMessage(), - issue.getSeverity(), - issue.getLineNumber(), - issue.getColumn(), - issue.getLineNumberEnd(), - issue.getColumnEnd(), - issue.getLength(), - path)); - } + // Parse JSON string and get top-level JSON object. + try { + jsonObject = JsonParser.parseString(jsonString).getAsJsonObject(); + } catch (JsonParseException e) { + reporter.printFatalErrorAndExit(String.format("Invalid JSON string:%n %s", jsonString)); } - - /** - * Obtains a resource from a path. Returns null if path is not an LF file. - * - * @param path The path to obtain the resource from. - * @return The obtained resource. Set to null if path is not an LF file. - */ - public Resource getResource(Path path) { - final ResourceSet set = this.resourceSetProvider.get(); - try { - return set.getResource(URI.createFileURI(path.toString()), true); - } catch (RuntimeException e) { - return null; - } + // Append input paths. + JsonElement src = jsonObject.get("src"); + if (src == null) { + reporter.printFatalErrorAndExit("JSON Parse Exception: field \"src\" not found."); + } + argsList.add(src.getAsString()); + // Append output path if given. + JsonElement out = jsonObject.get("out"); + if (out != null) { + argsList.add("--output-path"); + argsList.add(out.getAsString()); } - private String[] jsonStringToArgs(String jsonString) { - ArrayList argsList = new ArrayList<>(); - JsonObject jsonObject = new JsonObject(); - - // Parse JSON string and get top-level JSON object. - try { - jsonObject = JsonParser.parseString(jsonString).getAsJsonObject(); - } catch (JsonParseException e) { - reporter.printFatalErrorAndExit( - String.format("Invalid JSON string:%n %s", jsonString)); - } - // Append input paths. - JsonElement src = jsonObject.get("src"); - if (src == null) { - reporter.printFatalErrorAndExit( - "JSON Parse Exception: field \"src\" not found."); - } - argsList.add(src.getAsString()); - // Append output path if given. - JsonElement out = jsonObject.get("out"); - if (out != null) { - argsList.add("--output-path"); - argsList.add(out.getAsString()); - } - - // If there are no other properties, return args array. - JsonElement properties = jsonObject.get("properties"); - if (properties != null) { - // Get the remaining properties. - Set> entrySet = properties - .getAsJsonObject() - .entrySet(); - // Append the remaining properties to the args array. - for(Entry entry : entrySet) { - String property = entry.getKey(); - String value = entry.getValue().getAsString(); - - // Append option. - argsList.add("--" + property); - // Append argument for non-boolean options. - if (value != "true" || property == "threading") { - argsList.add(value); - } - } + // If there are no other properties, return args array. + JsonElement properties = jsonObject.get("properties"); + if (properties != null) { + // Get the remaining properties. + Set> entrySet = properties.getAsJsonObject().entrySet(); + // Append the remaining properties to the args array. + for (Entry entry : entrySet) { + String property = entry.getKey(); + String value = entry.getValue().getAsString(); + + // Append option. + argsList.add("--" + property); + // Append argument for non-boolean options. + if (value != "true" || property == "threading") { + argsList.add(value); } - - // Return as String[]. - String[] args = argsList.toArray(new String[argsList.size()]); - return args; + } } + + // Return as String[]. + String[] args = argsList.toArray(new String[argsList.size()]); + return args; + } } diff --git a/org.lflang/src/org/lflang/cli/LFStandaloneModule.java b/org.lflang/src/org/lflang/cli/LFStandaloneModule.java index 8b85e49282..abf01bfcd3 100644 --- a/org.lflang/src/org/lflang/cli/LFStandaloneModule.java +++ b/org.lflang/src/org/lflang/cli/LFStandaloneModule.java @@ -28,49 +28,45 @@ package org.lflang.cli; +import com.google.inject.Binder; +import com.google.inject.Module; import java.util.Objects; - import org.eclipse.emf.ecore.EValidator; import org.eclipse.emf.ecore.impl.EValidatorRegistryImpl; import org.eclipse.xtext.validation.ValidationMessageAcceptor; - import org.lflang.ErrorReporter; import org.lflang.LFRuntimeModule; -import com.google.inject.Binder; -import com.google.inject.Module; - /** - * Module that is only available when running LFC as a - * standalone program. + * Module that is only available when running LFC as a standalone program. * * @see LFRuntimeModule */ public class LFStandaloneModule implements Module { - // Note that xtext's base module classes has broken support - // for @Provides, which would allow us to bind this field. - // So we directly implement Module, instead of extending eg LFRuntimeModule. - private final ReportingBackend helper; - private final Io io; + // Note that xtext's base module classes has broken support + // for @Provides, which would allow us to bind this field. + // So we directly implement Module, instead of extending eg LFRuntimeModule. + private final ReportingBackend helper; + private final Io io; - public LFStandaloneModule(ReportingBackend helper, Io io) { - this.helper = Objects.requireNonNull(helper); - this.io = Objects.requireNonNull(io); - } + public LFStandaloneModule(ReportingBackend helper, Io io) { + this.helper = Objects.requireNonNull(helper); + this.io = Objects.requireNonNull(io); + } - @Override - public void configure(Binder binder) { - binder.bind(ErrorReporter.class).to(StandaloneErrorReporter.class); - binder.bind(ReportingBackend.class).toInstance(helper); - binder.bind(Io.class).toInstance(io); - binder.bind(ValidationMessageAcceptor.class).to(StandaloneIssueAcceptor.class); - // This is required to force the ResourceValidator to - // use a new registry instance (which is reused by the injector as a singleton). - // Otherwise, it uses the static EValidator.Registry.INSTANCE which is bad - // as the first validator to be created would persist in that static instance. - // New injectors would reuse the existing instance, but - // its fields would have been injected by an older injector - // and be out of sync with the rest of the application. - binder.bind(EValidator.Registry.class).to(EValidatorRegistryImpl.class).asEagerSingleton(); - } + @Override + public void configure(Binder binder) { + binder.bind(ErrorReporter.class).to(StandaloneErrorReporter.class); + binder.bind(ReportingBackend.class).toInstance(helper); + binder.bind(Io.class).toInstance(io); + binder.bind(ValidationMessageAcceptor.class).to(StandaloneIssueAcceptor.class); + // This is required to force the ResourceValidator to + // use a new registry instance (which is reused by the injector as a singleton). + // Otherwise, it uses the static EValidator.Registry.INSTANCE which is bad + // as the first validator to be created would persist in that static instance. + // New injectors would reuse the existing instance, but + // its fields would have been injected by an older injector + // and be out of sync with the rest of the application. + binder.bind(EValidator.Registry.class).to(EValidatorRegistryImpl.class).asEagerSingleton(); + } } diff --git a/org.lflang/src/org/lflang/cli/Lfc.java b/org.lflang/src/org/lflang/cli/Lfc.java index 3ef52e54a0..19869f9394 100644 --- a/org.lflang/src/org/lflang/cli/Lfc.java +++ b/org.lflang/src/org/lflang/cli/Lfc.java @@ -1,27 +1,22 @@ package org.lflang.cli; - +import com.google.inject.Inject; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Properties; - -import picocli.CommandLine.Command; -import picocli.CommandLine.Option; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.generator.GeneratorDelegate; import org.eclipse.xtext.generator.JavaIoFileSystemAccess; import org.eclipse.xtext.util.CancelIndicator; - -import org.lflang.ast.ASTUtils; import org.lflang.FileConfig; import org.lflang.TargetProperty.UnionType; - +import org.lflang.ast.ASTUtils; import org.lflang.generator.LFGeneratorContext; import org.lflang.generator.LFGeneratorContext.BuildParm; import org.lflang.generator.MainContext; - -import com.google.inject.Inject; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; /** * Standalone version of the Lingua Franca compiler (lfc). @@ -36,287 +31,261 @@ mixinStandardHelpOptions = true, versionProvider = VersionProvider.class) public class Lfc extends CliBase { - /** - * Injected code generator. - */ - @Inject - private GeneratorDelegate generator; - - /** - * Injected file access object. - */ - @Inject - private JavaIoFileSystemAccess fileAccess; - - /* - * Supported CLI options. - */ - - @Option( - names = "--build-type", - description = "The build type to use.") - private String buildType; - - @Option( - names = {"-c", "--clean"}, - arity = "0", - description = "Clean before building.") - private boolean clean; - - @Option( - names = "--target-compiler", - description = "Target compiler to invoke.") - private String targetCompiler; - - @Option( - names = "--external-runtime-path", - description = "Specify an external runtime library to be used by the" - + " compiled binary.") - private Path externalRuntimePath; - - @Option( - names = {"-f", "--federated"}, - arity = "0", - description = "Treat main reactor as federated.") - private boolean federated; - - @Option( - names = "--logging", - description = "The logging level to use by the generated binary") - private String logging; - - @Option( - names = {"-l", "--lint"}, - arity = "0", - description = "Enable linting of generated code.") - private boolean lint; - - @Option( - names = {"-n", "--no-compile"}, - arity = "0", - description = "Do not invoke target compiler.") - private boolean noCompile; - - @Option( - names = {"--print-statistics"}, - arity = "0", - description = "Instruct the runtime to collect and print statistics.") - private boolean printStatistics; - - @Option( - names = {"-q", "--quiet"}, - arity = "0", - description = - "Suppress output of the target compiler and other commands") - private boolean quiet; - - @Option( - names = {"-r", "--rti"}, - description = "Specify the location of the RTI.") - private Path rti; - - @Option( - names = "--runtime-version", - description = "Specify the version of the runtime library used for" - + " compiling LF programs.") - private String runtimeVersion; - - @Option( - names = {"-s", "--scheduler"}, - description = "Specify the runtime scheduler (if supported).") - private String scheduler; - - @Option( - names = {"-t", "--threading"}, - paramLabel = "", - description = "Specify whether the runtime should use multi-threading" - + " (true/false).") - private String threading; - - @Option( - names = {"-w", "--workers"}, - description = "Specify the default number of worker threads.") - private Integer workers; - - /** - * Main function of the stand-alone compiler. - * Caution: this will invoke System.exit. - * - * @param args CLI arguments - */ - public static void main(final String[] args) { - main(Io.SYSTEM, args); + /** Injected code generator. */ + @Inject private GeneratorDelegate generator; + + /** Injected file access object. */ + @Inject private JavaIoFileSystemAccess fileAccess; + + /* + * Supported CLI options. + */ + + @Option(names = "--build-type", description = "The build type to use.") + private String buildType; + + @Option( + names = {"-c", "--clean"}, + arity = "0", + description = "Clean before building.") + private boolean clean; + + @Option(names = "--target-compiler", description = "Target compiler to invoke.") + private String targetCompiler; + + @Option( + names = "--external-runtime-path", + description = "Specify an external runtime library to be used by the" + " compiled binary.") + private Path externalRuntimePath; + + @Option( + names = {"-f", "--federated"}, + arity = "0", + description = "Treat main reactor as federated.") + private boolean federated; + + @Option(names = "--logging", description = "The logging level to use by the generated binary") + private String logging; + + @Option( + names = {"-l", "--lint"}, + arity = "0", + description = "Enable linting of generated code.") + private boolean lint; + + @Option( + names = {"-n", "--no-compile"}, + arity = "0", + description = "Do not invoke target compiler.") + private boolean noCompile; + + @Option( + names = {"--print-statistics"}, + arity = "0", + description = "Instruct the runtime to collect and print statistics.") + private boolean printStatistics; + + @Option( + names = {"-q", "--quiet"}, + arity = "0", + description = "Suppress output of the target compiler and other commands") + private boolean quiet; + + @Option( + names = {"-r", "--rti"}, + description = "Specify the location of the RTI.") + private Path rti; + + @Option( + names = "--runtime-version", + description = + "Specify the version of the runtime library used for" + " compiling LF programs.") + private String runtimeVersion; + + @Option( + names = {"-s", "--scheduler"}, + description = "Specify the runtime scheduler (if supported).") + private String scheduler; + + @Option( + names = {"-t", "--threading"}, + paramLabel = "", + description = "Specify whether the runtime should use multi-threading" + " (true/false).") + private String threading; + + @Option( + names = {"-w", "--workers"}, + description = "Specify the default number of worker threads.") + private Integer workers; + + /** + * Main function of the stand-alone compiler. Caution: this will invoke System.exit. + * + * @param args CLI arguments + */ + public static void main(final String[] args) { + main(Io.SYSTEM, args); + } + + /** + * Main function of the standalone compiler, with a custom IO. + * + * @param io IO streams. + * @param args Command-line arguments. + */ + public static void main(Io io, final String... args) { + cliMain("lfc", Lfc.class, io, args); + } + + /** Load the resource, validate it, and, invoke the code generator. */ + @Override + public void doRun() { + List paths = getInputPaths(); + final Path outputRoot = getOutputRoot(); + // Hard code the props based on the options we want. + Properties properties = this.getGeneratorArgs(); + + try { + // Invoke the generator on all input file paths. + invokeGenerator(paths, outputRoot, properties); + } catch (RuntimeException e) { + reporter.printFatalErrorAndExit("An unexpected error occurred:", e); } - - /** - * Main function of the standalone compiler, with a custom IO. - * - * @param io IO streams. - * @param args Command-line arguments. - */ - public static void main(Io io, final String... args) { - cliMain("lfc", Lfc.class, io, args); - } - - /** - * Load the resource, validate it, and, invoke the code generator. - */ - @Override - public void doRun() { - List paths = getInputPaths(); - final Path outputRoot = getOutputRoot(); - // Hard code the props based on the options we want. - Properties properties = this.getGeneratorArgs(); - - try { - // Invoke the generator on all input file paths. - invokeGenerator(paths, outputRoot, properties); - } catch (RuntimeException e) { - reporter.printFatalErrorAndExit("An unexpected error occurred:", e); + } + + /** Invoke the code generator on the given validated file paths. */ + private void invokeGenerator(List files, Path root, Properties properties) { + for (Path path : files) { + path = toAbsolutePath(path); + String outputPath = getActualOutputPath(root, path).toString(); + this.fileAccess.setOutputPath(outputPath); + + final Resource resource = getResource(path); + if (resource == null) { + reporter.printFatalErrorAndExit( + path + " is not an LF file. Use the .lf file extension to" + " denote LF files."); + } else if (federated) { + if (!ASTUtils.makeFederated(resource)) { + reporter.printError("Unable to change main reactor to federated reactor."); } + } + + validateResource(resource); + exitIfCollectedErrors(); + + LFGeneratorContext context = + new MainContext( + LFGeneratorContext.Mode.STANDALONE, + CancelIndicator.NullImpl, + (m, p) -> {}, + properties, + resource, + this.fileAccess, + fileConfig -> errorReporter); + + try { + this.generator.generate(resource, this.fileAccess, context); + } catch (Exception e) { + reporter.printFatalErrorAndExit("Error running generator", e); + } + + exitIfCollectedErrors(); + // Print all other issues (not errors). + issueCollector.getAllIssues().forEach(reporter::printIssue); + + this.io.getOut().println("Code generation finished."); } - - /** - * Invoke the code generator on the given validated file paths. - */ - private void invokeGenerator( - List files, Path root, Properties properties) { - for (Path path : files) { - path = toAbsolutePath(path); - String outputPath = getActualOutputPath(root, path).toString(); - this.fileAccess.setOutputPath(outputPath); - - final Resource resource = getResource(path); - if (resource == null) { - reporter.printFatalErrorAndExit(path - + " is not an LF file. Use the .lf file extension to" - + " denote LF files."); - } else if (federated) { - if (!ASTUtils.makeFederated(resource)) { - reporter.printError( - "Unable to change main reactor to federated reactor."); - } - } - - validateResource(resource); - exitIfCollectedErrors(); - - LFGeneratorContext context = new MainContext( - LFGeneratorContext.Mode.STANDALONE, CancelIndicator.NullImpl, - (m, p) -> {}, properties, resource, this.fileAccess, - fileConfig -> errorReporter - ); - - try { - this.generator.generate(resource, this.fileAccess, context); - } catch (Exception e) { - reporter.printFatalErrorAndExit("Error running generator", e); - } - - exitIfCollectedErrors(); - // Print all other issues (not errors). - issueCollector.getAllIssues().forEach(reporter::printIssue); - - this.io.getOut().println("Code generation finished."); - } + } + + private Path getActualOutputPath(Path root, Path path) { + if (root != null) { + return root.resolve("src-gen"); + } else { + Path pkgRoot = FileConfig.findPackageRoot(path, reporter::printWarning); + return pkgRoot.resolve("src-gen"); } - - private Path getActualOutputPath(Path root, Path path) { - if (root != null) { - return root.resolve("src-gen"); - } else { - Path pkgRoot = FileConfig.findPackageRoot( - path, reporter::printWarning); - return pkgRoot.resolve("src-gen"); - } + } + + /** + * Filter the command-line arguments needed by the code generator, and return them as properties. + * + * @return Properties for the code generator. + */ + public Properties getGeneratorArgs() { + Properties props = new Properties(); + + if (buildType != null) { + // Validate build type. + if (UnionType.BUILD_TYPE_UNION.forName(buildType) == null) { + reporter.printFatalErrorAndExit(buildType + ": Invalid build type."); + } + props.setProperty(BuildParm.BUILD_TYPE.getKey(), buildType); } - /** - * Filter the command-line arguments needed by the code generator, and - * return them as properties. - * - * @return Properties for the code generator. - */ - public Properties getGeneratorArgs() { - Properties props = new Properties(); - - if (buildType != null) { - // Validate build type. - if (UnionType.BUILD_TYPE_UNION.forName(buildType) == null) { - reporter.printFatalErrorAndExit( - buildType + ": Invalid build type."); - } - 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 (clean) { + props.setProperty(BuildParm.CLEAN.getKey(), "true"); + } - if (lint) { - props.setProperty(BuildParm.LINT.getKey(), "true"); - } + if (externalRuntimePath != null) { + props.setProperty(BuildParm.EXTERNAL_RUNTIME_PATH.getKey(), externalRuntimePath.toString()); + } - if (logging != null) { - // Validate log level. - if (UnionType.LOGGING_UNION.forName(logging) == null) { - reporter.printFatalErrorAndExit( - logging + ": Invalid log level."); - } - props.setProperty(BuildParm.LOGGING.getKey(), logging); - } + if (lint) { + props.setProperty(BuildParm.LINT.getKey(), "true"); + } - if(printStatistics) { - props.setProperty(BuildParm.PRINT_STATISTICS.getKey(), "true"); - } + if (logging != null) { + // Validate log level. + if (UnionType.LOGGING_UNION.forName(logging) == null) { + reporter.printFatalErrorAndExit(logging + ": Invalid log level."); + } + props.setProperty(BuildParm.LOGGING.getKey(), logging); + } - if (noCompile) { - props.setProperty(BuildParm.NO_COMPILE.getKey(), "true"); - } + if (printStatistics) { + props.setProperty(BuildParm.PRINT_STATISTICS.getKey(), "true"); + } - if (targetCompiler != null) { - props.setProperty(BuildParm.TARGET_COMPILER.getKey(), targetCompiler); - } + if (noCompile) { + props.setProperty(BuildParm.NO_COMPILE.getKey(), "true"); + } - if (quiet) { - props.setProperty(BuildParm.QUIET.getKey(), "true"); - } + if (targetCompiler != null) { + props.setProperty(BuildParm.TARGET_COMPILER.getKey(), targetCompiler); + } - if (rti != null) { - // Validate RTI path. - if (!Files.exists(io.getWd().resolve(rti))) { - reporter.printFatalErrorAndExit( - rti + ": Invalid RTI path."); - } - props.setProperty(BuildParm.RTI.getKey(), rti.toString()); - } + if (quiet) { + props.setProperty(BuildParm.QUIET.getKey(), "true"); + } - if (runtimeVersion != null) { - props.setProperty(BuildParm.RUNTIME_VERSION.getKey(), runtimeVersion); - } + if (rti != null) { + // Validate RTI path. + if (!Files.exists(io.getWd().resolve(rti))) { + reporter.printFatalErrorAndExit(rti + ": Invalid RTI path."); + } + props.setProperty(BuildParm.RTI.getKey(), rti.toString()); + } - if (scheduler != null) { - // Validate scheduler. - if (UnionType.SCHEDULER_UNION.forName(scheduler) == null) { - reporter.printFatalErrorAndExit( - scheduler + ": Invalid scheduler."); - } - props.setProperty(BuildParm.SCHEDULER.getKey(), scheduler); - } + if (runtimeVersion != null) { + props.setProperty(BuildParm.RUNTIME_VERSION.getKey(), runtimeVersion); + } - if (threading != null) { - props.setProperty(BuildParm.THREADING.getKey(), threading); - } + if (scheduler != null) { + // Validate scheduler. + if (UnionType.SCHEDULER_UNION.forName(scheduler) == null) { + reporter.printFatalErrorAndExit(scheduler + ": Invalid scheduler."); + } + props.setProperty(BuildParm.SCHEDULER.getKey(), scheduler); + } - if (workers != null) { - props.setProperty(BuildParm.WORKERS.getKey(), workers.toString()); - } + if (threading != null) { + props.setProperty(BuildParm.THREADING.getKey(), threading); + } - return props; + 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 297bc5ad84..f4f7714119 100644 --- a/org.lflang/src/org/lflang/cli/Lff.java +++ b/org.lflang/src/org/lflang/cli/Lff.java @@ -8,13 +8,11 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.List; - -import picocli.CommandLine.Command; -import picocli.CommandLine.Option; import org.eclipse.emf.ecore.resource.Resource; - import org.lflang.ast.FormattingUtils; import org.lflang.util.FileUtil; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; /** * Standalone version of the Lingua Franca formatter (lff). Based on lfc. @@ -31,169 +29,163 @@ versionProvider = VersionProvider.class) public class Lff extends CliBase { - /** - * Supported CLI options for Lff. - */ - @Option( - names = {"-d", "--dry-run"}, - description = "Send the formatted file contents to stdout" - + " without writing to the file system.") - private boolean dryRun = false; - - @Option( - names = {"-w", "--wrap"}, - description = "Causes the formatter to line wrap the files to a" - + " specified length.", - defaultValue = "" + FormattingUtils.DEFAULT_LINE_LENGTH, - fallbackValue = "" + FormattingUtils.DEFAULT_LINE_LENGTH) - private int lineLength; - - @Option( - names = "--no-recurse", - description = "Do not format files in subdirectories of the" - + " specified paths.") - private boolean noRecurse = false; - - @Option( - names = {"-v", "--verbose"}, - 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. - * - * @param args CLI arguments - */ - public static void main(String[] args) { - main(Io.SYSTEM, args); + /** Supported CLI options for Lff. */ + @Option( + names = {"-d", "--dry-run"}, + description = + "Send the formatted file contents to stdout" + " without writing to the file system.") + private boolean dryRun = false; + + @Option( + names = {"-w", "--wrap"}, + description = "Causes the formatter to line wrap the files to a" + " specified length.", + defaultValue = "" + FormattingUtils.DEFAULT_LINE_LENGTH, + fallbackValue = "" + FormattingUtils.DEFAULT_LINE_LENGTH) + private int lineLength; + + @Option( + names = "--no-recurse", + description = "Do not format files in subdirectories of the" + " specified paths.") + private boolean noRecurse = false; + + @Option( + names = {"-v", "--verbose"}, + 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. + * + * @param args CLI arguments + */ + public static void main(String[] args) { + main(Io.SYSTEM, args); + } + + /** + * Programmatic entry point, with a custom IO. + * + * @param io IO streams. + * @param args Command-line arguments. + */ + public static void main(Io io, final String... args) { + cliMain("lff", Lff.class, io, args); + } + + /** Validates all paths and invokes the formatter on the input paths. */ + @Override + public void doRun() { + List paths = getInputPaths(); + final Path outputRoot = getOutputRoot(); + + try { + // Format all files defined by the list of paths. + formatAllFiles(paths, outputRoot); + + exitIfCollectedErrors(); + if (!dryRun || verbose) { + reporter.printInfo("Done formatting."); + } + } catch (RuntimeException e) { + reporter.printFatalErrorAndExit("An unexpected error occurred:", e); } - - /** - * Programmatic entry point, with a custom IO. - * - * @param io IO streams. - * @param args Command-line arguments. - */ - public static void main(Io io, final String... args) { - cliMain("lff", Lff.class, io, args); - } - - /** - * Validates all paths and invokes the formatter on the input paths. - */ - @Override - public void doRun() { - List paths = getInputPaths(); - final Path outputRoot = getOutputRoot(); - + } + + /* + * Invokes the formatter on all files defined by the list of paths. + */ + private void formatAllFiles(List paths, Path outputRoot) { + for (Path relativePath : paths) { + if (verbose) { + reporter.printInfo("Formatting " + io.getWd().relativize(relativePath) + ":"); + } + + Path path = toAbsolutePath(relativePath); + if (Files.isDirectory(path) && !noRecurse) { + // Walk the contents of this directory. try { - // Format all files defined by the list of paths. - formatAllFiles(paths, outputRoot); - - exitIfCollectedErrors(); - if (!dryRun || verbose) { - reporter.printInfo("Done formatting."); - } - } catch (RuntimeException e) { - reporter.printFatalErrorAndExit("An unexpected error occurred:", e); - } - } - - /* - * Invokes the formatter on all files defined by the list of paths. - */ - private void formatAllFiles(List paths, Path outputRoot) { - for (Path relativePath : paths) { - if (verbose) { - reporter.printInfo("Formatting " - + io.getWd().relativize(relativePath) + ":"); - } - - Path path = toAbsolutePath(relativePath); - if (Files.isDirectory(path) && !noRecurse) { - // Walk the contents of this directory. - try { - Files.walkFileTree(path, new SimpleFileVisitor<>() { - @Override - public FileVisitResult visitFile( - Path file, BasicFileAttributes attrs) { - formatSingleFile(file, path, outputRoot); - return FileVisitResult.CONTINUE; - } - }); - } catch (IOException e) { - reporter.printError("IO error: " + e); + Files.walkFileTree( + path, + new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + formatSingleFile(file, path, outputRoot); + return FileVisitResult.CONTINUE; } - } else { - // Simple file. - formatSingleFile(path, path.getParent(), outputRoot); - } + }); + } catch (IOException e) { + reporter.printError("IO error: " + e); } + } else { + // Simple file. + formatSingleFile(path, path.getParent(), outputRoot); + } } - - /* - * Invokes the formatter on a single file defined by the given path. - */ - private void formatSingleFile(Path path, Path inputRoot, Path outputRoot) { - path = path.normalize(); - Path outputPath = outputRoot == null + } + + /* + * Invokes the formatter on a single file defined by the given path. + */ + private void formatSingleFile(Path path, Path inputRoot, Path outputRoot) { + path = path.normalize(); + Path outputPath = + outputRoot == null ? path // Format in place. : outputRoot.resolve(inputRoot.relativize(path)).normalize(); - final Resource resource = getResource(path); - // Skip file if not an LF file. - if (resource == null) { - if (verbose) { - reporter.printInfo("Skipped " + path + ": not an LF file"); - } - return; - } - validateResource(resource); + final Resource resource = getResource(path); + // Skip file if not an LF file. + if (resource == null) { + if (verbose) { + reporter.printInfo("Skipped " + path + ": not an LF file"); + } + return; + } + validateResource(resource); - if (!ignoreErrors) { - exitIfCollectedErrors(); - } - final String formattedFileContents = - FormattingUtils.render(resource.getContents().get(0), lineLength); - - if (dryRun) { - io.getOut().print(formattedFileContents); - } else { - try { - FileUtil.writeToFile(formattedFileContents, outputPath, true); - } catch (IOException e) { - if (e instanceof FileAlreadyExistsException) { - // 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" - + " directory within provided input paths have the" - + " same relative paths."); - } - } + if (!ignoreErrors) { + exitIfCollectedErrors(); + } + final String formattedFileContents = + FormattingUtils.render(resource.getContents().get(0), lineLength); + + if (dryRun) { + io.getOut().print(formattedFileContents); + } else { + try { + FileUtil.writeToFile(formattedFileContents, outputPath, true); + } catch (IOException e) { + if (e instanceof FileAlreadyExistsException) { + // 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" + + " directory within provided input paths have the" + + " same relative paths."); } + } + } - 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); - } - reporter.printInfo(msg); - } + 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); + } + reporter.printInfo(msg); } + } } diff --git a/org.lflang/src/org/lflang/cli/StandaloneErrorReporter.java b/org.lflang/src/org/lflang/cli/StandaloneErrorReporter.java index 2b4ae6fe46..83e58eb15c 100644 --- a/org.lflang/src/org/lflang/cli/StandaloneErrorReporter.java +++ b/org.lflang/src/org/lflang/cli/StandaloneErrorReporter.java @@ -27,90 +27,79 @@ package org.lflang.cli; +import com.google.inject.Inject; import java.nio.file.Path; - import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.diagnostics.Severity; - import org.lflang.ErrorReporter; -import com.google.inject.Inject; - /** - * An error reporter that forwards all messages to an {@link IssueCollector}. - * They'll be sorted out later. + * An error reporter that forwards all messages to an {@link IssueCollector}. They'll be sorted out + * later. */ public class StandaloneErrorReporter implements ErrorReporter { - @Inject - private StandaloneIssueAcceptor issueAcceptor; - - private String reportWithNode(String message, Severity severity, EObject obj) { - issueAcceptor.accept(severity, message, obj, null, 0, null); - return message; - } - - private String reportSimpleFileCtx(String message, Severity severity, Integer line, Path path) { - LfIssue issue = new LfIssue(message, severity, line, 1, line, 1, 0, path); - issueAcceptor.accept(issue); - // Return a string that can be inserted into the generated code. - return message; - } - - - @Override - public String reportError(String message) { - return reportSimpleFileCtx(message, Severity.ERROR, null, null); - } - - - @Override - public String reportWarning(String message) { - return reportSimpleFileCtx(message, Severity.WARNING, null, null); - } - - @Override - public String reportInfo(String message) { - return reportSimpleFileCtx(message, Severity.INFO, null, null); - } - - - @Override - public String reportError(EObject obj, String message) { - return reportWithNode(message, Severity.ERROR, obj); - } - - - @Override - public String reportWarning(EObject obj, String message) { - return reportWithNode(message, Severity.WARNING, obj); - } - - @Override - public String reportInfo(EObject obj, String message) { - return reportWithNode(message, Severity.INFO, obj); - } - - - @Override - public String reportError(Path file, Integer line, String message) { - return reportSimpleFileCtx(message, Severity.ERROR, line, file); - } - - - @Override - public String reportWarning(Path file, Integer line, String message) { - return reportSimpleFileCtx(message, Severity.WARNING, line, file); - } - - @Override - public String reportInfo(Path file, Integer line, String message) { - return reportSimpleFileCtx(message, Severity.INFO, line, file); - } - - - @Override - public boolean getErrorsOccurred() { - return issueAcceptor.getErrorsOccurred(); - } + @Inject private StandaloneIssueAcceptor issueAcceptor; + + private String reportWithNode(String message, Severity severity, EObject obj) { + issueAcceptor.accept(severity, message, obj, null, 0, null); + return message; + } + + private String reportSimpleFileCtx(String message, Severity severity, Integer line, Path path) { + LfIssue issue = new LfIssue(message, severity, line, 1, line, 1, 0, path); + issueAcceptor.accept(issue); + // Return a string that can be inserted into the generated code. + return message; + } + + @Override + public String reportError(String message) { + return reportSimpleFileCtx(message, Severity.ERROR, null, null); + } + + @Override + public String reportWarning(String message) { + return reportSimpleFileCtx(message, Severity.WARNING, null, null); + } + + @Override + public String reportInfo(String message) { + return reportSimpleFileCtx(message, Severity.INFO, null, null); + } + + @Override + public String reportError(EObject obj, String message) { + return reportWithNode(message, Severity.ERROR, obj); + } + + @Override + public String reportWarning(EObject obj, String message) { + return reportWithNode(message, Severity.WARNING, obj); + } + + @Override + public String reportInfo(EObject obj, String message) { + return reportWithNode(message, Severity.INFO, obj); + } + + @Override + public String reportError(Path file, Integer line, String message) { + return reportSimpleFileCtx(message, Severity.ERROR, line, file); + } + + @Override + public String reportWarning(Path file, Integer line, String message) { + return reportSimpleFileCtx(message, Severity.WARNING, line, file); + } + + @Override + public String reportInfo(Path file, Integer line, String message) { + return reportSimpleFileCtx(message, Severity.INFO, line, file); + } + + @Override + public boolean getErrorsOccurred() { + return issueAcceptor.getErrorsOccurred(); + } } diff --git a/org.lflang/src/org/lflang/cli/StandaloneIssueAcceptor.java b/org.lflang/src/org/lflang/cli/StandaloneIssueAcceptor.java index 73ca4bea9e..cb9712a7cb 100644 --- a/org.lflang/src/org/lflang/cli/StandaloneIssueAcceptor.java +++ b/org.lflang/src/org/lflang/cli/StandaloneIssueAcceptor.java @@ -1,42 +1,41 @@ package org.lflang.cli; +import com.google.inject.Inject; import java.io.IOException; import java.nio.file.Path; - import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.xtext.diagnostics.Severity; import org.eclipse.xtext.validation.EObjectDiagnosticImpl; import org.eclipse.xtext.validation.ValidationMessageAcceptor; - import org.lflang.util.FileUtil; -import com.google.inject.Inject; - -/** - * - */ +/** */ public class StandaloneIssueAcceptor implements ValidationMessageAcceptor { - @Inject - private IssueCollector collector; - - - boolean getErrorsOccurred() { - return collector.getErrorsOccurred(); - } - - - void accept(LfIssue lfIssue) { - collector.accept(lfIssue); - } - - - void accept(Severity severity, String message, EObject object, EStructuralFeature feature, int index, String code, String... issueData) { - EObjectDiagnosticImpl diagnostic = - new EObjectDiagnosticImpl(severity, code, message, object, feature, index, issueData); - - LfIssue lfIssue = new LfIssue( + @Inject private IssueCollector collector; + + boolean getErrorsOccurred() { + return collector.getErrorsOccurred(); + } + + void accept(LfIssue lfIssue) { + collector.accept(lfIssue); + } + + void accept( + Severity severity, + String message, + EObject object, + EStructuralFeature feature, + int index, + String code, + String... issueData) { + EObjectDiagnosticImpl diagnostic = + new EObjectDiagnosticImpl(severity, code, message, object, feature, index, issueData); + + LfIssue lfIssue = + new LfIssue( message, severity, diagnostic.getLine(), @@ -44,64 +43,81 @@ void accept(Severity severity, String message, EObject object, EStructuralFeatur diagnostic.getLineEnd(), diagnostic.getColumnEnd(), diagnostic.getLength(), - getPath(diagnostic) - ); - - accept(lfIssue); - } - - - /** - * Best effort to get a fileName. May return null. - */ - private Path getPath(EObjectDiagnosticImpl diagnostic) { - Path file = null; - try { - file = FileUtil.toPath(diagnostic.getUriToProblem()); - } catch (IOException e) { - // just continue with null - } - return file; - } - - - private void accept(Severity severity, String message, EObject object, int offset, int length, String code, String... issueData) { - throw new UnsupportedOperationException("not implemented: range based diagnostics"); - } - - - @Override - public void acceptError(String message, EObject object, EStructuralFeature feature, int index, String code, String... issueData) { - accept(Severity.ERROR, message, object, feature, index, code, issueData); - } - - - @Override - public void acceptError(String message, EObject object, int offset, int length, String code, String... issueData) { - accept(Severity.ERROR, message, object, offset, length, code, issueData); - } - - - @Override - public void acceptWarning(String message, EObject object, EStructuralFeature feature, int index, String code, String... issueData) { - accept(Severity.WARNING, message, object, feature, index, code, issueData); - } - - - @Override - public void acceptWarning(String message, EObject object, int offset, int length, String code, String... issueData) { - accept(Severity.WARNING, message, object, offset, length, code, issueData); - } - - - @Override - public void acceptInfo(String message, EObject object, EStructuralFeature feature, int index, String code, String... issueData) { - accept(Severity.INFO, message, object, feature, index, code, issueData); - } - - - @Override - public void acceptInfo(String message, EObject object, int offset, int length, String code, String... issueData) { - accept(Severity.INFO, message, object, offset, length, code, issueData); + getPath(diagnostic)); + + accept(lfIssue); + } + + /** Best effort to get a fileName. May return null. */ + private Path getPath(EObjectDiagnosticImpl diagnostic) { + Path file = null; + try { + file = FileUtil.toPath(diagnostic.getUriToProblem()); + } catch (IOException e) { + // just continue with null } + return file; + } + + private void accept( + Severity severity, + String message, + EObject object, + int offset, + int length, + String code, + String... issueData) { + throw new UnsupportedOperationException("not implemented: range based diagnostics"); + } + + @Override + public void acceptError( + String message, + EObject object, + EStructuralFeature feature, + int index, + String code, + String... issueData) { + accept(Severity.ERROR, message, object, feature, index, code, issueData); + } + + @Override + public void acceptError( + String message, EObject object, int offset, int length, String code, String... issueData) { + accept(Severity.ERROR, message, object, offset, length, code, issueData); + } + + @Override + public void acceptWarning( + String message, + EObject object, + EStructuralFeature feature, + int index, + String code, + String... issueData) { + accept(Severity.WARNING, message, object, feature, index, code, issueData); + } + + @Override + public void acceptWarning( + String message, EObject object, int offset, int length, String code, String... issueData) { + accept(Severity.WARNING, message, object, offset, length, code, issueData); + } + + @Override + public void acceptInfo( + String message, + EObject object, + EStructuralFeature feature, + int index, + String code, + String... issueData) { + accept(Severity.INFO, message, object, feature, index, code, issueData); + } + + @Override + public void acceptInfo( + String message, EObject object, int offset, int length, String code, String... issueData) { + accept(Severity.INFO, message, object, offset, length, code, issueData); + } } diff --git a/org.lflang/src/org/lflang/cli/VersionProvider.java b/org.lflang/src/org/lflang/cli/VersionProvider.java index ce9c8f6252..ca9c3d6bbb 100644 --- a/org.lflang/src/org/lflang/cli/VersionProvider.java +++ b/org.lflang/src/org/lflang/cli/VersionProvider.java @@ -1,11 +1,10 @@ package org.lflang.cli; +import org.lflang.LocalStrings; import picocli.CommandLine.IVersionProvider; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Spec; -import org.lflang.LocalStrings; - /* * Dynamically provides version information to the Lingua Franca CLI. * Picocli will instantiate this class and invoke it to collect version @@ -14,19 +13,18 @@ * @author Atharva Patil */ class VersionProvider implements IVersionProvider { - /* - * Here, picocli will inject the CommandSpec (full command hierarchy) of the - * command that uses this version provider. This allows this version - * provider to be reused among multiple commands. - */ - @Spec CommandSpec spec; + /* + * Here, picocli will inject the CommandSpec (full command hierarchy) of the + * command that uses this version provider. This allows this version + * provider to be reused among multiple commands. + */ + @Spec CommandSpec spec; - // Method invoked by picocli to get the version info. - public String[] getVersion() { - return new String[] { - // "lfc", "lff", etc. - spec.qualifiedName() - + " " + LocalStrings.VERSION - }; - } + // Method invoked by picocli to get the version info. + public String[] getVersion() { + return new String[] { + // "lfc", "lff", etc. + spec.qualifiedName() + " " + LocalStrings.VERSION + }; + } } diff --git a/org.lflang/src/org/lflang/diagram/lsp/LFLanguageServer.java b/org.lflang/src/org/lflang/diagram/lsp/LFLanguageServer.java index 1971fbc081..405e322a88 100644 --- a/org.lflang/src/org/lflang/diagram/lsp/LFLanguageServer.java +++ b/org.lflang/src/org/lflang/diagram/lsp/LFLanguageServer.java @@ -1,10 +1,9 @@ package org.lflang.diagram.lsp; -import org.eclipse.lsp4j.WorkDoneProgressCancelParams; import de.cau.cs.kieler.klighd.lsp.KGraphLanguageServerExtension; - import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.HoverParams; +import org.eclipse.lsp4j.WorkDoneProgressCancelParams; import org.eclipse.xtext.ide.server.hover.IHoverService; import org.eclipse.xtext.util.CancelIndicator; @@ -14,21 +13,24 @@ * @author Peter Donovan */ public class LFLanguageServer extends KGraphLanguageServerExtension { - @Override - public void cancelProgress(WorkDoneProgressCancelParams params) { - Progress.cancel(params.getToken().getRight().intValue()); - } + @Override + public void cancelProgress(WorkDoneProgressCancelParams params) { + Progress.cancel(params.getToken().getRight().intValue()); + } - @Override - protected Hover hover(HoverParams params, CancelIndicator cancelIndicator) { - // This override is just a hacky little patch that is being applied downstream of the original mistake and - // upstream of the ungraceful handling (IndexOutOfBoundsException) of said mistake. This patch is applied here - // simply because it is easy. This would be done differently were it not for the fact that we plan to rebuild - // this infrastructure from scratch anyway. - try { - return super.hover(params, cancelIndicator); - } catch (IndexOutOfBoundsException e) { - return IHoverService.EMPTY_HOVER; // Fail silently - } + @Override + protected Hover hover(HoverParams params, CancelIndicator cancelIndicator) { + // This override is just a hacky little patch that is being applied downstream of the original + // mistake and + // upstream of the ungraceful handling (IndexOutOfBoundsException) of said mistake. This patch + // is applied here + // simply because it is easy. This would be done differently were it not for the fact that we + // plan to rebuild + // this infrastructure from scratch anyway. + try { + return super.hover(params, cancelIndicator); + } catch (IndexOutOfBoundsException e) { + return IHoverService.EMPTY_HOVER; // Fail silently } + } } diff --git a/org.lflang/src/org/lflang/diagram/lsp/LFLanguageServerExtension.java b/org.lflang/src/org/lflang/diagram/lsp/LFLanguageServerExtension.java index fd25b0fc0a..18de51f482 100644 --- a/org.lflang/src/org/lflang/diagram/lsp/LFLanguageServerExtension.java +++ b/org.lflang/src/org/lflang/diagram/lsp/LFLanguageServerExtension.java @@ -1,125 +1,125 @@ package org.lflang.diagram.lsp; -import java.util.concurrent.CompletableFuture; import java.util.ArrayList; - +import java.util.concurrent.CompletableFuture; import org.eclipse.emf.common.util.URI; import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; import org.eclipse.lsp4j.services.LanguageClient; -import org.eclipse.xtext.ide.server.ILanguageServerExtension; import org.eclipse.xtext.ide.server.ILanguageServerAccess; - +import org.eclipse.xtext.ide.server.ILanguageServerExtension; +import org.lflang.LFRuntimeModule; +import org.lflang.LFStandaloneSetup; +import org.lflang.generator.GeneratorResult; import org.lflang.generator.GeneratorResult.Status; import org.lflang.generator.IntegratedBuilder; -import org.lflang.generator.GeneratorResult; -import org.lflang.LFStandaloneSetup; -import org.lflang.LFRuntimeModule; import org.lflang.util.LFCommand; /** - * Provide Lingua-Franca-specific extensions to the - * language server's behavior. + * Provide Lingua-Franca-specific extensions to the language server's behavior. * * @author Peter Donovan */ class LFLanguageServerExtension implements ILanguageServerExtension { - /** The IntegratedBuilder instance that handles all build requests for the current session. */ - private static final IntegratedBuilder builder = new LFStandaloneSetup(new LFRuntimeModule()) - .createInjectorAndDoEMFRegistration().getInstance(IntegratedBuilder.class); + /** The IntegratedBuilder instance that handles all build requests for the current session. */ + private static final IntegratedBuilder builder = + new LFStandaloneSetup(new LFRuntimeModule()) + .createInjectorAndDoEMFRegistration() + .getInstance(IntegratedBuilder.class); - /** The access point for reading documents, communicating with the language client, etc. */ - private LanguageClient client; + /** The access point for reading documents, communicating with the language client, etc. */ + private LanguageClient client; - @Override - public void initialize(ILanguageServerAccess access) { - // This method is never invoked. - } + @Override + public void initialize(ILanguageServerAccess access) { + // This method is never invoked. + } - public void setClient(LanguageClient client) { - this.client = client; - } + public void setClient(LanguageClient client) { + this.client = client; + } - /** - * Handle a request for a complete build of the Lingua - * Franca file specified by {@code uri}. - * @param uri the URI of the LF file of interest - * @return A message describing the outcome of the build - * process. - */ - @JsonRequest("generator/build") - public CompletableFuture build(String uri) { - if (client == null) return CompletableFuture.completedFuture( - "Please wait for the Lingua Franca language server to be fully initialized." - ); - return CompletableFuture.supplyAsync( - () -> { - try { - return buildWithProgress(client, uri, true).getUserMessage(); - } catch (Exception e) { - return "An internal error occurred:\n" + e; - } - } - ); - } + /** + * Handle a request for a complete build of the Lingua Franca file specified by {@code uri}. + * + * @param uri the URI of the LF file of interest + * @return A message describing the outcome of the build process. + */ + @JsonRequest("generator/build") + public CompletableFuture build(String uri) { + if (client == null) + return CompletableFuture.completedFuture( + "Please wait for the Lingua Franca language server to be fully initialized."); + return CompletableFuture.supplyAsync( + () -> { + try { + return buildWithProgress(client, uri, true).getUserMessage(); + } catch (Exception e) { + return "An internal error occurred:\n" + e; + } + }); + } - /** - * Handles a request for the most complete build of the - * specified Lingua Franca file that can be done in a - * limited amount of time. - * @param uri the URI of the LF file of interest - */ - @JsonNotification("generator/partialBuild") - public void partialBuild(String uri) { - if (client == null) return; - buildWithProgress(client, uri, false); - } + /** + * Handles a request for the most complete build of the specified Lingua Franca file that can be + * done in a limited amount of time. + * + * @param uri the URI of the LF file of interest + */ + @JsonNotification("generator/partialBuild") + public void partialBuild(String uri) { + if (client == null) return; + buildWithProgress(client, uri, false); + } - /** - * Completely build the specified LF program and provide information that is sufficient to - * run it. - * @param uri The URI of the LF program to be built. - * @return An array consisting of the directory in which the execute command should be - * executed, the program of the execute command, and the arguments of the execute command. - */ - @JsonNotification("generator/buildAndRun") - public CompletableFuture buildAndRun(String uri) { - return new CompletableFuture().completeAsync(() -> { - var result = buildWithProgress(client, uri, true); - if (!result.getStatus().equals(Status.COMPILED)) return null; - LFCommand cmd = result.getContext().getFileConfig().getCommand(); - ArrayList ret = new ArrayList<>(); - ret.add(cmd.directory().toString()); - ret.addAll(cmd.command()); - return ret.toArray(new String[0]); - }); - } + /** + * Completely build the specified LF program and provide information that is sufficient to run it. + * + * @param uri The URI of the LF program to be built. + * @return An array consisting of the directory in which the execute command should be executed, + * the program of the execute command, and the arguments of the execute command. + */ + @JsonNotification("generator/buildAndRun") + public CompletableFuture buildAndRun(String uri) { + return new CompletableFuture() + .completeAsync( + () -> { + var result = buildWithProgress(client, uri, true); + if (!result.getStatus().equals(Status.COMPILED)) return null; + LFCommand cmd = result.getContext().getFileConfig().getCommand(); + ArrayList ret = new ArrayList<>(); + ret.add(cmd.directory().toString()); + ret.addAll(cmd.command()); + return ret.toArray(new String[0]); + }); + } - /** - * Describes a build process that has a progress. - */ - private GeneratorResult buildWithProgress(LanguageClient client, String uri, boolean mustComplete) { - URI parsedUri; - try { - parsedUri = URI.createFileURI(new java.net.URI(uri).getPath()); - } catch (java.net.URISyntaxException e) { - // This error will appear as a silent failure to most users, but that is acceptable because this error - // should be impossible. The URI is not the result of user input -- the language client provides it -- - // so it should be valid. - System.err.println(e); - return GeneratorResult.NOTHING; - } - Progress progress = new Progress(client, "Build \"" + parsedUri.lastSegment() + "\"", mustComplete); - progress.begin(); - GeneratorResult result = null; - try { - result = builder.run( - parsedUri, mustComplete, progress::report, progress.getCancelIndicator() - ); - } finally { - progress.end(result == null ? "An internal error occurred." : result.getUserMessage()); - } - return result; + /** Describes a build process that has a progress. */ + private GeneratorResult buildWithProgress( + LanguageClient client, String uri, boolean mustComplete) { + URI parsedUri; + try { + parsedUri = URI.createFileURI(new java.net.URI(uri).getPath()); + } catch (java.net.URISyntaxException e) { + // This error will appear as a silent failure to most users, but that is acceptable because + // this error + // should be impossible. The URI is not the result of user input -- the language client + // provides it -- + // so it should be valid. + System.err.println(e); + return GeneratorResult.NOTHING; + } + Progress progress = + new Progress(client, "Build \"" + parsedUri.lastSegment() + "\"", mustComplete); + progress.begin(); + GeneratorResult result = null; + try { + result = + builder.run(parsedUri, mustComplete, progress::report, progress.getCancelIndicator()); + } finally { + progress.end(result == null ? "An internal error occurred." : result.getUserMessage()); } + return result; + } } diff --git a/org.lflang/src/org/lflang/diagram/lsp/LanguageDiagramServer.java b/org.lflang/src/org/lflang/diagram/lsp/LanguageDiagramServer.java index c4d7804453..96e887d242 100644 --- a/org.lflang/src/org/lflang/diagram/lsp/LanguageDiagramServer.java +++ b/org.lflang/src/org/lflang/diagram/lsp/LanguageDiagramServer.java @@ -1,20 +1,8 @@ package org.lflang.diagram.lsp; -import java.util.List; - -import org.eclipse.xtext.Constants; -import org.eclipse.xtext.IGrammarAccess; -import org.eclipse.xtext.ide.server.ILanguageServerExtension; -import org.eclipse.xtext.ide.server.LanguageServerImpl; -import org.eclipse.xtext.service.AbstractGenericModule; -import org.eclipse.xtext.util.Modules2; -import org.lflang.generator.LanguageServerErrorReporter; -import org.lflang.ide.LFIdeSetup; - import com.google.inject.Module; import com.google.inject.name.Names; import com.google.inject.util.Modules; - import de.cau.cs.kieler.kgraph.text.services.KGraphGrammarAccess; import de.cau.cs.kieler.klighd.lsp.KGraphLanguageClient; import de.cau.cs.kieler.klighd.lsp.interactive.layered.LayeredInteractiveLanguageServerExtension; @@ -24,82 +12,102 @@ import de.cau.cs.kieler.klighd.lsp.launch.AbstractRegistrationLanguageServerExtension; import de.cau.cs.kieler.klighd.lsp.launch.ILanguageRegistration; import de.cau.cs.kieler.klighd.lsp.launch.Language; +import java.util.List; +import org.eclipse.xtext.Constants; +import org.eclipse.xtext.IGrammarAccess; +import org.eclipse.xtext.ide.server.ILanguageServerExtension; +import org.eclipse.xtext.ide.server.LanguageServerImpl; +import org.eclipse.xtext.service.AbstractGenericModule; +import org.eclipse.xtext.util.Modules2; +import org.lflang.generator.LanguageServerErrorReporter; +import org.lflang.ide.LFIdeSetup; /** * Language server with extended diagram communication. - * + * * @author Alexander Schulz-Rosengarten */ public class LanguageDiagramServer extends AbstractLanguageServer { - - private static class LFLsCreator extends AbstractLsCreator { - @Override - public Module createLSModules(boolean socket) { - return Modules2.mixin(Modules.override(super.createLSModules(socket)).with(new AbstractGenericModule() { - public Class bindLanguageServerImpl() { - return LFLanguageServer.class; - } - }), it -> { - // Temporary fix for an issue of Klighd with Xtext 2.28 (https://github.com/kieler/KLighD/issues/144) - it.bind(IGrammarAccess.class).to(KGraphGrammarAccess.class); - it.bind(String.class).annotatedWith(Names.named(Constants.LANGUAGE_NAME)).toInstance("de.cau.cs.kieler.kgraph.text.KGraph"); - }); - } - - LayeredInteractiveLanguageServerExtension constraints; - RectpackingInteractiveLanguageServerExtension rectPack; - LFLanguageServerExtension lfExtension; - - @Override - public List getLanguageServerExtensions() { - constraints = injector.getInstance(LayeredInteractiveLanguageServerExtension.class); - rectPack = injector.getInstance(RectpackingInteractiveLanguageServerExtension.class); - lfExtension = injector.getInstance(LFLanguageServerExtension.class); - return List.of( - injector.getInstance(LFRegistrationLanguageServerExtension.class), constraints, rectPack, lfExtension - ); - } - - @Override - public Class getRemoteInterface() { - return KGraphLanguageClient.class; - } - - @Override - public void onConnect() { - super.onConnect(); - constraints.setClient((KGraphLanguageClient) languageClient); - rectPack.setClient((KGraphLanguageClient) languageClient); - LanguageServerErrorReporter.setClient(languageClient); - lfExtension.setClient(languageClient); - // The following is needed because VS Code treats System.err like System.out and System.out like a shout - // into the void. - System.setOut(System.err); - } + private static class LFLsCreator extends AbstractLsCreator { + + @Override + public Module createLSModules(boolean socket) { + return Modules2.mixin( + Modules.override(super.createLSModules(socket)) + .with( + new AbstractGenericModule() { + public Class bindLanguageServerImpl() { + return LFLanguageServer.class; + } + }), + it -> { + // Temporary fix for an issue of Klighd with Xtext 2.28 + // (https://github.com/kieler/KLighD/issues/144) + it.bind(IGrammarAccess.class).to(KGraphGrammarAccess.class); + it.bind(String.class) + .annotatedWith(Names.named(Constants.LANGUAGE_NAME)) + .toInstance("de.cau.cs.kieler.kgraph.text.KGraph"); + }); + } + + LayeredInteractiveLanguageServerExtension constraints; + RectpackingInteractiveLanguageServerExtension rectPack; + LFLanguageServerExtension lfExtension; + + @Override + public List getLanguageServerExtensions() { + constraints = injector.getInstance(LayeredInteractiveLanguageServerExtension.class); + rectPack = injector.getInstance(RectpackingInteractiveLanguageServerExtension.class); + lfExtension = injector.getInstance(LFLanguageServerExtension.class); + return List.of( + injector.getInstance(LFRegistrationLanguageServerExtension.class), + constraints, + rectPack, + lfExtension); } - - private static class LFLanguageRegistration implements ILanguageRegistration { - - @Override - public void bindAndRegisterLanguages() { - LFIdeSetup.doSetup(); - } + + @Override + public Class getRemoteInterface() { + return KGraphLanguageClient.class; } - private static class LFRegistrationLanguageServerExtension extends AbstractRegistrationLanguageServerExtension { - - @Override - public List getLanguageExtensions() { - return List.of(new Language("lf", "Lingua Franca", List.of())); - } + @Override + public void onConnect() { + super.onConnect(); + constraints.setClient((KGraphLanguageClient) languageClient); + rectPack.setClient((KGraphLanguageClient) languageClient); + LanguageServerErrorReporter.setClient(languageClient); + lfExtension.setClient(languageClient); + // The following is needed because VS Code treats System.err like System.out and System.out + // like a shout + // into the void. + System.setOut(System.err); } - - public static void main(String[] args) { - new LanguageDiagramServer().start(); + } + + private static class LFLanguageRegistration implements ILanguageRegistration { + + @Override + public void bindAndRegisterLanguages() { + LFIdeSetup.doSetup(); } - - public void start() { - configureAndRun(new LFLanguageRegistration(), new LFLsCreator()); + } + + private static class LFRegistrationLanguageServerExtension + extends AbstractRegistrationLanguageServerExtension { + + @Override + public List getLanguageExtensions() { + return List.of(new Language("lf", "Lingua Franca", List.of())); } + } + + public static void main(String[] args) { + new LanguageDiagramServer().start(); + } + + public void start() { + configureAndRun(new LFLanguageRegistration(), new LFLsCreator()); + } } diff --git a/org.lflang/src/org/lflang/diagram/lsp/Progress.java b/org.lflang/src/org/lflang/diagram/lsp/Progress.java index 2c1bb2f7fe..56c6788ea8 100644 --- a/org.lflang/src/org/lflang/diagram/lsp/Progress.java +++ b/org.lflang/src/org/lflang/diagram/lsp/Progress.java @@ -2,15 +2,14 @@ import java.util.HashMap; import java.util.Map; - -import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.ProgressParams; -import org.eclipse.lsp4j.services.LanguageClient; -import org.eclipse.lsp4j.WorkDoneProgressCreateParams; import org.eclipse.lsp4j.WorkDoneProgressBegin; +import org.eclipse.lsp4j.WorkDoneProgressCreateParams; import org.eclipse.lsp4j.WorkDoneProgressEnd; import org.eclipse.lsp4j.WorkDoneProgressNotification; import org.eclipse.lsp4j.WorkDoneProgressReport; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.xtext.util.CancelIndicator; /** @@ -19,88 +18,85 @@ * @author Peter Donovan */ public class Progress { - private static int nextToken = 0; - private static final Map cancellations = new HashMap<>(); + private static int nextToken = 0; + private static final Map cancellations = new HashMap<>(); - private final LanguageClient client; - private final String title; - private final int token; - private final boolean cancellable; + private final LanguageClient client; + private final String title; + private final int token; + private final boolean cancellable; - /** - * Initialize the {@code Progress} of a task titled {@code title} that is - * triggered via {@code client}. - * @param client A language client through which a task was triggered. - * @param title The title of the task. - * @param cancellable Whether the task tracked by {@code this} can be - * cancelled. - */ - public Progress(LanguageClient client, String title, boolean cancellable) { - this.client = client; - this.title = title; - this.token = nextToken++; - this.cancellable = cancellable; - if (cancellable) cancellations.put(token, false); - client.createProgress(new WorkDoneProgressCreateParams(Either.forRight(token))); - } + /** + * Initialize the {@code Progress} of a task titled {@code title} that is triggered via {@code + * client}. + * + * @param client A language client through which a task was triggered. + * @param title The title of the task. + * @param cancellable Whether the task tracked by {@code this} can be cancelled. + */ + public Progress(LanguageClient client, String title, boolean cancellable) { + this.client = client; + this.title = title; + this.token = nextToken++; + this.cancellable = cancellable; + if (cancellable) cancellations.put(token, false); + client.createProgress(new WorkDoneProgressCreateParams(Either.forRight(token))); + } - /** - * Cancel the task tracked by the {@code Progress} that has token - * {@code token}. - */ - public static void cancel(int token) { - if (cancellations.containsKey(token)) cancellations.put(token, true); - } + /** Cancel the task tracked by the {@code Progress} that has token {@code token}. */ + public static void cancel(int token) { + if (cancellations.containsKey(token)) cancellations.put(token, true); + } - /** - * Returns the cancel indicator for the task tracked by this - * {@code Progress}. - * @return the cancel indicator for the task tracked by this - * {@code Progress} - */ - public CancelIndicator getCancelIndicator() { - if (cancellable) return () -> cancellations.get(token); - return () -> false; - } + /** + * Returns the cancel indicator for the task tracked by this {@code Progress}. + * + * @return the cancel indicator for the task tracked by this {@code Progress} + */ + public CancelIndicator getCancelIndicator() { + if (cancellable) return () -> cancellations.get(token); + return () -> false; + } - /** - * Report that the task tracked by {@code this} is done. - */ - public void begin() { - WorkDoneProgressBegin begin = new WorkDoneProgressBegin(); - begin.setTitle(title); - begin.setCancellable(cancellable); - begin.setPercentage(0); - notifyProgress(begin); - } + /** Report that the task tracked by {@code this} is done. */ + public void begin() { + WorkDoneProgressBegin begin = new WorkDoneProgressBegin(); + begin.setTitle(title); + begin.setCancellable(cancellable); + begin.setPercentage(0); + notifyProgress(begin); + } - /** - * Report the progress of the task tracked by {@code this}. - * @param message A message describing the progress of the task. - */ - public void report(String message, Integer percentage) { - WorkDoneProgressReport report = new WorkDoneProgressReport(); - report.setMessage(message); - report.setCancellable(cancellable); - report.setPercentage(percentage); - notifyProgress(report); - } + /** + * Report the progress of the task tracked by {@code this}. + * + * @param message A message describing the progress of the task. + */ + public void report(String message, Integer percentage) { + WorkDoneProgressReport report = new WorkDoneProgressReport(); + report.setMessage(message); + report.setCancellable(cancellable); + report.setPercentage(percentage); + notifyProgress(report); + } - /** - * Marks the task tracked by {@code this} as terminated. - * @param message A message describing the outcome of the task. - */ - public void end(String message) { - WorkDoneProgressEnd end = new WorkDoneProgressEnd(); - end.setMessage(message); - notifyProgress(end); - } + /** + * Marks the task tracked by {@code this} as terminated. + * + * @param message A message describing the outcome of the task. + */ + public void end(String message) { + WorkDoneProgressEnd end = new WorkDoneProgressEnd(); + end.setMessage(message); + notifyProgress(end); + } - /** - * Send the given progress notification to the client. - * @param notification - */ - private void notifyProgress(WorkDoneProgressNotification notification) { - client.notifyProgress(new ProgressParams(Either.forRight(token), Either.forLeft(notification))); - } + /** + * Send the given progress notification to the client. + * + * @param notification + */ + private void notifyProgress(WorkDoneProgressNotification notification) { + client.notifyProgress(new ProgressParams(Either.forRight(token), Either.forLeft(notification))); + } } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/AbstractSynthesisExtensions.java b/org.lflang/src/org/lflang/diagram/synthesis/AbstractSynthesisExtensions.java index 1041af3ce7..4d32af61ef 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/AbstractSynthesisExtensions.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/AbstractSynthesisExtensions.java @@ -1,27 +1,27 @@ /************* -* Copyright (c) 2020, Kiel University. -* -* 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, Kiel University. + * + * 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.diagram.synthesis; import de.cau.cs.kieler.klighd.SynthesisOption; @@ -30,33 +30,33 @@ import org.eclipse.emf.ecore.EObject; /** - * Abstract super class for extension classes used in for the diagram synthesis that provides some convince methods. - * + * Abstract super class for extension classes used in for the diagram synthesis that provides some + * convince methods. + * * @author Alexander Schulz-Rosengarten */ public abstract class AbstractSynthesisExtensions { - - @Inject - private AbstractDiagramSynthesis delegate; - - public boolean getBooleanValue(SynthesisOption option) { - return delegate.getBooleanValue(option); - } - - public float getFloatValue(SynthesisOption option) { - return delegate.getFloatValue(option); - } - - public Object getObjectValue(final SynthesisOption option) { - return delegate.getObjectValue(option); - } - - public T associateWith(T derived, Object source) { - return delegate.associateWith(derived, source); - } - - @SuppressWarnings("unchecked") - public > T getRootSynthesis() { - return (T) delegate; - } + + @Inject private AbstractDiagramSynthesis delegate; + + public boolean getBooleanValue(SynthesisOption option) { + return delegate.getBooleanValue(option); + } + + public float getFloatValue(SynthesisOption option) { + return delegate.getFloatValue(option); + } + + public Object getObjectValue(final SynthesisOption option) { + return delegate.getObjectValue(option); + } + + public T associateWith(T derived, Object source) { + return delegate.associateWith(derived, source); + } + + @SuppressWarnings("unchecked") + public > T getRootSynthesis() { + return (T) delegate; + } } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java b/org.lflang/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java index 2b1a2a1bf6..45f3342a0a 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java @@ -1,29 +1,61 @@ /************* -* Copyright (c) 2020, Kiel University. -* -* 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, Kiel University. + * + * 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.diagram.synthesis; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; +import com.google.common.collect.Table; +import de.cau.cs.kieler.klighd.DisplayedActionData; +import de.cau.cs.kieler.klighd.SynthesisOption; +import de.cau.cs.kieler.klighd.kgraph.KEdge; +import de.cau.cs.kieler.klighd.kgraph.KLabel; +import de.cau.cs.kieler.klighd.kgraph.KNode; +import de.cau.cs.kieler.klighd.kgraph.KPort; +import de.cau.cs.kieler.klighd.krendering.Colors; +import de.cau.cs.kieler.klighd.krendering.HorizontalAlignment; +import de.cau.cs.kieler.klighd.krendering.KContainerRendering; +import de.cau.cs.kieler.klighd.krendering.KPolyline; +import de.cau.cs.kieler.klighd.krendering.KRectangle; +import de.cau.cs.kieler.klighd.krendering.KRendering; +import de.cau.cs.kieler.klighd.krendering.KRoundedRectangle; +import de.cau.cs.kieler.klighd.krendering.KStyle; +import de.cau.cs.kieler.klighd.krendering.KText; +import de.cau.cs.kieler.klighd.krendering.LineCap; +import de.cau.cs.kieler.klighd.krendering.LineStyle; +import de.cau.cs.kieler.klighd.krendering.ViewSynthesisShared; +import de.cau.cs.kieler.klighd.krendering.extensions.KContainerRenderingExtensions; +import de.cau.cs.kieler.klighd.krendering.extensions.KEdgeExtensions; +import de.cau.cs.kieler.klighd.krendering.extensions.KLabelExtensions; +import de.cau.cs.kieler.klighd.krendering.extensions.KNodeExtensions; +import de.cau.cs.kieler.klighd.krendering.extensions.KPolylineExtensions; +import de.cau.cs.kieler.klighd.krendering.extensions.KPortExtensions; +import de.cau.cs.kieler.klighd.krendering.extensions.KRenderingExtensions; +import de.cau.cs.kieler.klighd.syntheses.AbstractDiagramSynthesis; +import de.cau.cs.kieler.klighd.util.KlighdProperties; import java.util.ArrayList; import java.util.Collection; import java.util.EnumSet; @@ -35,9 +67,7 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; - import javax.inject.Inject; - import org.eclipse.elk.alg.layered.options.LayerConstraint; import org.eclipse.elk.alg.layered.options.LayeredOptions; import org.eclipse.elk.alg.layered.options.NodePlacementStrategy; @@ -62,9 +92,9 @@ import org.eclipse.xtext.xbase.lib.ListExtensions; import org.eclipse.xtext.xbase.lib.Pair; import org.eclipse.xtext.xbase.lib.StringExtensions; -import org.lflang.ast.ASTUtils; import org.lflang.AttributeUtils; import org.lflang.InferredType; +import org.lflang.ast.ASTUtils; import org.lflang.ast.FormattingUtils; import org.lflang.diagram.synthesis.action.CollapseAllReactorsAction; import org.lflang.diagram.synthesis.action.ExpandAllReactorsAction; @@ -99,40 +129,6 @@ import org.lflang.lf.StateVar; import org.lflang.util.FileUtil; -import com.google.common.collect.HashBasedTable; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Iterables; -import com.google.common.collect.Multimap; -import com.google.common.collect.Table; - -import de.cau.cs.kieler.klighd.DisplayedActionData; -import de.cau.cs.kieler.klighd.SynthesisOption; -import de.cau.cs.kieler.klighd.kgraph.KEdge; -import de.cau.cs.kieler.klighd.kgraph.KLabel; -import de.cau.cs.kieler.klighd.kgraph.KNode; -import de.cau.cs.kieler.klighd.kgraph.KPort; -import de.cau.cs.kieler.klighd.krendering.Colors; -import de.cau.cs.kieler.klighd.krendering.HorizontalAlignment; -import de.cau.cs.kieler.klighd.krendering.KContainerRendering; -import de.cau.cs.kieler.klighd.krendering.KPolyline; -import de.cau.cs.kieler.klighd.krendering.KRectangle; -import de.cau.cs.kieler.klighd.krendering.KRendering; -import de.cau.cs.kieler.klighd.krendering.KRoundedRectangle; -import de.cau.cs.kieler.klighd.krendering.KStyle; -import de.cau.cs.kieler.klighd.krendering.KText; -import de.cau.cs.kieler.klighd.krendering.LineCap; -import de.cau.cs.kieler.klighd.krendering.LineStyle; -import de.cau.cs.kieler.klighd.krendering.ViewSynthesisShared; -import de.cau.cs.kieler.klighd.krendering.extensions.KContainerRenderingExtensions; -import de.cau.cs.kieler.klighd.krendering.extensions.KEdgeExtensions; -import de.cau.cs.kieler.klighd.krendering.extensions.KLabelExtensions; -import de.cau.cs.kieler.klighd.krendering.extensions.KNodeExtensions; -import de.cau.cs.kieler.klighd.krendering.extensions.KPolylineExtensions; -import de.cau.cs.kieler.klighd.krendering.extensions.KPortExtensions; -import de.cau.cs.kieler.klighd.krendering.extensions.KRenderingExtensions; -import de.cau.cs.kieler.klighd.syntheses.AbstractDiagramSynthesis; -import de.cau.cs.kieler.klighd.util.KlighdProperties; - /** * Diagram synthesis for Lingua Franca programs. * @@ -140,1270 +136,1537 @@ */ @ViewSynthesisShared public class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { - @Inject @Extension private KNodeExtensions _kNodeExtensions; - @Inject @Extension private KEdgeExtensions _kEdgeExtensions; - @Inject @Extension private KPortExtensions _kPortExtensions; - @Inject @Extension private KLabelExtensions _kLabelExtensions; - @Inject @Extension private KRenderingExtensions _kRenderingExtensions; - @Inject @Extension private KContainerRenderingExtensions _kContainerRenderingExtensions; - @Inject @Extension private KPolylineExtensions _kPolylineExtensions; - @Inject @Extension private LinguaFrancaStyleExtensions _linguaFrancaStyleExtensions; - @Inject @Extension private LinguaFrancaShapeExtensions _linguaFrancaShapeExtensions; - @Inject @Extension private UtilityExtensions _utilityExtensions; - @Inject @Extension private CycleVisualization _cycleVisualization; - @Inject @Extension private InterfaceDependenciesVisualization _interfaceDependenciesVisualization; - @Inject @Extension private FilterCycleAction _filterCycleAction; - @Inject @Extension private ReactorIcons _reactorIcons; - @Inject @Extension private ModeDiagrams _modeDiagrams; - @Inject @Extension private LayoutPostProcessing _layoutPostProcessing; - - // ------------------------------------------------------------------------- - - public static final String ID = "org.lflang.diagram.synthesis.LinguaFrancaSynthesis"; - - // -- INTERNAL -- - public static final Property REACTOR_RECURSIVE_INSTANTIATION = new Property<>("org.lflang.linguafranca.diagram.synthesis.reactor.recursive.instantiation", false); - public static final Property REACTOR_HAS_BANK_PORT_OFFSET = new Property<>("org.lflang.linguafranca.diagram.synthesis.reactor.bank.offset", false); - public static final Property REACTOR_INPUT = new Property<>("org.lflang.linguafranca.diagram.synthesis.reactor.input", false); - public static final Property REACTOR_OUTPUT = new Property<>("org.lflang.linguafranca.diagram.synthesis.reactor.output", false); - public static final Property REACTION_SPECIAL_TRIGGER = new Property<>("org.lflang.linguafranca.diagram.synthesis.reaction.special.trigger", false); - - // -- STYLE -- - public static final List ALTERNATIVE_DASH_PATTERN = List.of(3.0f); - - // -- TEXT -- - public static final String TEXT_ERROR_RECURSIVE = "Recursive reactor instantiation!"; - public static final String TEXT_ERROR_CONTAINS_RECURSION = "Reactor contains recursive instantiation!"; - public static final String TEXT_ERROR_CONTAINS_CYCLE = "Reactor contains cyclic dependencies!"; - public static final String TEXT_ERROR_CYCLE_DETECTION = "Dependency cycle detection failed.\nCould not detect dependency cycles due to unexpected graph structure."; - public static final String TEXT_ERROR_CYCLE_BTN_SHOW = "Show Cycle"; - public static final String TEXT_ERROR_CYCLE_BTN_FILTER = "Filter Cycle"; - public static final String TEXT_ERROR_CYCLE_BTN_UNFILTER = "Remove Cycle Filter"; - public static final String TEXT_NO_MAIN_REACTOR = "No Main Reactor"; - public static final String TEXT_REACTOR_NULL = "Reactor is null"; - public static final String TEXT_HIDE_ACTION = "[Hide]"; - public static final String TEXT_SHOW_ACTION = "[Details]"; - - // ------------------------------------------------------------------------- - - /** Synthesis category */ - public static final SynthesisOption APPEARANCE = SynthesisOption.createCategory("Appearance", true); - public static final SynthesisOption EXPERIMENTAL = SynthesisOption.createCategory("Experimental", true); - public static final SynthesisOption LAYOUT = SynthesisOption.createCategory("Layout", false).setCategory(LinguaFrancaSynthesis.APPEARANCE); - - /** Synthesis options */ - public static final SynthesisOption SHOW_ALL_REACTORS = SynthesisOption.createCheckOption("All Reactors", false); - public static final SynthesisOption CYCLE_DETECTION = SynthesisOption.createCheckOption("Dependency Cycle Detection", true); - - public static final SynthesisOption SHOW_USER_LABELS = SynthesisOption.createCheckOption("User Labels (@label in JavaDoc)", true).setCategory(APPEARANCE); - public static final SynthesisOption SHOW_HYPERLINKS = SynthesisOption.createCheckOption("Expand/Collapse Hyperlinks", false).setCategory(APPEARANCE); - public static final SynthesisOption REACTIONS_USE_HYPEREDGES = SynthesisOption.createCheckOption("Bundled Dependencies", false).setCategory(APPEARANCE); - public static final SynthesisOption USE_ALTERNATIVE_DASH_PATTERN = SynthesisOption.createCheckOption("Alternative Dependency Line Style", false).setCategory(APPEARANCE); - public static final SynthesisOption SHOW_PORT_NAMES = SynthesisOption.createCheckOption("Port names", true).setCategory(APPEARANCE); - public static final SynthesisOption SHOW_MULTIPORT_WIDTH = SynthesisOption.createCheckOption("Multiport Widths", false).setCategory(APPEARANCE); - public static final SynthesisOption SHOW_REACTION_CODE = SynthesisOption.createCheckOption("Reaction Code", false).setCategory(APPEARANCE); - public static final SynthesisOption SHOW_REACTION_LEVEL = SynthesisOption.createCheckOption("Reaction Level", false).setCategory(APPEARANCE); - public static final SynthesisOption SHOW_REACTION_ORDER_EDGES = SynthesisOption.createCheckOption("Reaction Order Edges", false).setCategory(APPEARANCE); - public static final SynthesisOption SHOW_REACTOR_HOST = SynthesisOption.createCheckOption("Reactor Host Addresses", true).setCategory(APPEARANCE); - public static final SynthesisOption SHOW_INSTANCE_NAMES = SynthesisOption.createCheckOption("Reactor Instance Names", false).setCategory(APPEARANCE); - public static final SynthesisOption REACTOR_PARAMETER_MODE = SynthesisOption.createChoiceOption("Reactor Parameters", ((List)Conversions.doWrapArray(ReactorParameterDisplayModes.values())), ReactorParameterDisplayModes.NONE).setCategory(APPEARANCE); - public static final SynthesisOption SHOW_STATE_VARIABLES = SynthesisOption.createCheckOption("Reactor State Variables", false).setCategory(APPEARANCE); - public static final SynthesisOption REACTOR_BODY_TABLE_COLS = SynthesisOption.createRangeOption("Reactor Parameter/Variable Columns", 1, 10, 1).setCategory(APPEARANCE); - - public static final SynthesisOption SPACING = SynthesisOption.createRangeOption("Spacing (%)", 0, 150, 5, 75).setCategory(LAYOUT); - - /** Synthesis actions */ - public static final DisplayedActionData COLLAPSE_ALL = DisplayedActionData.create(CollapseAllReactorsAction.ID, "Hide all Details"); - public static final DisplayedActionData EXPAND_ALL = DisplayedActionData.create(ExpandAllReactorsAction.ID, "Show all Details"); - - @Override - public List getDisplayedSynthesisOptions() { - return List.of( - SHOW_ALL_REACTORS, - MemorizingExpandCollapseAction.MEMORIZE_EXPANSION_STATES, - CYCLE_DETECTION, - APPEARANCE, - ModeDiagrams.MODES_CATEGORY, - ModeDiagrams.SHOW_TRANSITION_LABELS, - ModeDiagrams.INITIALLY_COLLAPSE_MODES, - SHOW_USER_LABELS, - SHOW_HYPERLINKS, - //LinguaFrancaSynthesisInterfaceDependencies.SHOW_INTERFACE_DEPENDENCIES, - REACTIONS_USE_HYPEREDGES, - USE_ALTERNATIVE_DASH_PATTERN, - SHOW_PORT_NAMES, - SHOW_MULTIPORT_WIDTH, - SHOW_REACTION_CODE, - SHOW_REACTION_LEVEL, - SHOW_REACTION_ORDER_EDGES, - SHOW_REACTOR_HOST, - SHOW_INSTANCE_NAMES, - REACTOR_PARAMETER_MODE, - SHOW_STATE_VARIABLES, - REACTOR_BODY_TABLE_COLS, - LAYOUT, - LayoutPostProcessing.MODEL_ORDER, - SPACING - ); - } - - @Override - public List getDisplayedActions() { - return List.of(COLLAPSE_ALL, EXPAND_ALL); - } - - // ------------------------------------------------------------------------- - - @Override - public KNode transform(final Model model) { - KNode rootNode = _kNodeExtensions.createNode(); - setLayoutOption(rootNode, CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); - setLayoutOption(rootNode, CoreOptions.DIRECTION, Direction.RIGHT); - setLayoutOption(rootNode, CoreOptions.PADDING, new ElkPadding(0)); - - try { - // Find main - Reactor main = IterableExtensions.findFirst(model.getReactors(), _utilityExtensions::isMainOrFederated); - if (main != null) { - ReactorInstance reactorInstance = new ReactorInstance(main, new SynthesisErrorReporter()); - rootNode.getChildren().addAll(createReactorNode(reactorInstance, true, null, null, new HashMap<>())); - } else if (!getBooleanValue(SHOW_ALL_REACTORS)) { - KNode messageNode = _kNodeExtensions.createNode(); - _linguaFrancaShapeExtensions.addErrorMessage(messageNode, TEXT_NO_MAIN_REACTOR, null); - rootNode.getChildren().add(messageNode); - } - - // Show all reactors - if (main == null || getBooleanValue(SHOW_ALL_REACTORS)) { - List reactorNodes = new ArrayList<>(); - for (Reactor reactor : model.getReactors()) { - if (reactor == main) continue; - ReactorInstance reactorInstance = new ReactorInstance(reactor, new SynthesisErrorReporter()); - reactorNodes.addAll(createReactorNode(reactorInstance, main == null, - HashBasedTable.create(), - HashBasedTable.create(), - new HashMap<>())); - } - if (!reactorNodes.isEmpty()) { - // To allow ordering, we need box layout but we also need layered layout for ports thus wrap all node - reactorNodes.add(0, IterableExtensions.head(rootNode.getChildren())); - - int index = 0; - for (KNode node : reactorNodes) { - // Element could be null if there is no main reactor and Show All Reactors is checked. - if (node == null) continue; - if (node.getProperty(CoreOptions.COMMENT_BOX)) continue; - KNode child = _kNodeExtensions.createNode(); - child.getChildren().add(node); - // Add comment nodes - for (KEdge edge : node.getIncomingEdges()) { - if (!edge.getSource().getProperty(CoreOptions.COMMENT_BOX)) continue; - child.getChildren().add(edge.getSource()); - } - _kRenderingExtensions.addInvisibleContainerRendering(child); - setLayoutOption(child, CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); - setLayoutOption(child, CoreOptions.DIRECTION, Direction.RIGHT); - setLayoutOption(child, CoreOptions.PADDING, new ElkPadding(0)); - // Legacy ordering option. - setLayoutOption(child, CoreOptions.PRIORITY, reactorNodes.size() - index); // Order! - rootNode.getChildren().add(child); - index++; - } - - setLayoutOption(rootNode, CoreOptions.ALGORITHM, BoxLayouterOptions.ALGORITHM_ID); - setLayoutOption(rootNode, CoreOptions.SPACING_NODE_NODE, 25.0); - } + @Inject @Extension private KNodeExtensions _kNodeExtensions; + @Inject @Extension private KEdgeExtensions _kEdgeExtensions; + @Inject @Extension private KPortExtensions _kPortExtensions; + @Inject @Extension private KLabelExtensions _kLabelExtensions; + @Inject @Extension private KRenderingExtensions _kRenderingExtensions; + @Inject @Extension private KContainerRenderingExtensions _kContainerRenderingExtensions; + @Inject @Extension private KPolylineExtensions _kPolylineExtensions; + @Inject @Extension private LinguaFrancaStyleExtensions _linguaFrancaStyleExtensions; + @Inject @Extension private LinguaFrancaShapeExtensions _linguaFrancaShapeExtensions; + @Inject @Extension private UtilityExtensions _utilityExtensions; + @Inject @Extension private CycleVisualization _cycleVisualization; + @Inject @Extension private InterfaceDependenciesVisualization _interfaceDependenciesVisualization; + @Inject @Extension private FilterCycleAction _filterCycleAction; + @Inject @Extension private ReactorIcons _reactorIcons; + @Inject @Extension private ModeDiagrams _modeDiagrams; + @Inject @Extension private LayoutPostProcessing _layoutPostProcessing; + + // ------------------------------------------------------------------------- + + public static final String ID = "org.lflang.diagram.synthesis.LinguaFrancaSynthesis"; + + // -- INTERNAL -- + public static final Property REACTOR_RECURSIVE_INSTANTIATION = + new Property<>( + "org.lflang.linguafranca.diagram.synthesis.reactor.recursive.instantiation", false); + public static final Property REACTOR_HAS_BANK_PORT_OFFSET = + new Property<>("org.lflang.linguafranca.diagram.synthesis.reactor.bank.offset", false); + public static final Property REACTOR_INPUT = + new Property<>("org.lflang.linguafranca.diagram.synthesis.reactor.input", false); + public static final Property REACTOR_OUTPUT = + new Property<>("org.lflang.linguafranca.diagram.synthesis.reactor.output", false); + public static final Property REACTION_SPECIAL_TRIGGER = + new Property<>("org.lflang.linguafranca.diagram.synthesis.reaction.special.trigger", false); + + // -- STYLE -- + public static final List ALTERNATIVE_DASH_PATTERN = List.of(3.0f); + + // -- TEXT -- + public static final String TEXT_ERROR_RECURSIVE = "Recursive reactor instantiation!"; + public static final String TEXT_ERROR_CONTAINS_RECURSION = + "Reactor contains recursive instantiation!"; + public static final String TEXT_ERROR_CONTAINS_CYCLE = "Reactor contains cyclic dependencies!"; + public static final String TEXT_ERROR_CYCLE_DETECTION = + "Dependency cycle detection failed.\n" + + "Could not detect dependency cycles due to unexpected graph structure."; + public static final String TEXT_ERROR_CYCLE_BTN_SHOW = "Show Cycle"; + public static final String TEXT_ERROR_CYCLE_BTN_FILTER = "Filter Cycle"; + public static final String TEXT_ERROR_CYCLE_BTN_UNFILTER = "Remove Cycle Filter"; + public static final String TEXT_NO_MAIN_REACTOR = "No Main Reactor"; + public static final String TEXT_REACTOR_NULL = "Reactor is null"; + public static final String TEXT_HIDE_ACTION = "[Hide]"; + public static final String TEXT_SHOW_ACTION = "[Details]"; + + // ------------------------------------------------------------------------- + + /** Synthesis category */ + public static final SynthesisOption APPEARANCE = + SynthesisOption.createCategory("Appearance", true); + + public static final SynthesisOption EXPERIMENTAL = + SynthesisOption.createCategory("Experimental", true); + public static final SynthesisOption LAYOUT = + SynthesisOption.createCategory("Layout", false).setCategory(LinguaFrancaSynthesis.APPEARANCE); + + /** Synthesis options */ + public static final SynthesisOption SHOW_ALL_REACTORS = + SynthesisOption.createCheckOption("All Reactors", false); + + public static final SynthesisOption CYCLE_DETECTION = + SynthesisOption.createCheckOption("Dependency Cycle Detection", true); + + public static final SynthesisOption SHOW_USER_LABELS = + SynthesisOption.createCheckOption("User Labels (@label in JavaDoc)", true) + .setCategory(APPEARANCE); + public static final SynthesisOption SHOW_HYPERLINKS = + SynthesisOption.createCheckOption("Expand/Collapse Hyperlinks", false) + .setCategory(APPEARANCE); + public static final SynthesisOption REACTIONS_USE_HYPEREDGES = + SynthesisOption.createCheckOption("Bundled Dependencies", false).setCategory(APPEARANCE); + public static final SynthesisOption USE_ALTERNATIVE_DASH_PATTERN = + SynthesisOption.createCheckOption("Alternative Dependency Line Style", false) + .setCategory(APPEARANCE); + public static final SynthesisOption SHOW_PORT_NAMES = + SynthesisOption.createCheckOption("Port names", true).setCategory(APPEARANCE); + public static final SynthesisOption SHOW_MULTIPORT_WIDTH = + SynthesisOption.createCheckOption("Multiport Widths", false).setCategory(APPEARANCE); + public static final SynthesisOption SHOW_REACTION_CODE = + SynthesisOption.createCheckOption("Reaction Code", false).setCategory(APPEARANCE); + public static final SynthesisOption SHOW_REACTION_LEVEL = + SynthesisOption.createCheckOption("Reaction Level", false).setCategory(APPEARANCE); + public static final SynthesisOption SHOW_REACTION_ORDER_EDGES = + SynthesisOption.createCheckOption("Reaction Order Edges", false).setCategory(APPEARANCE); + public static final SynthesisOption SHOW_REACTOR_HOST = + SynthesisOption.createCheckOption("Reactor Host Addresses", true).setCategory(APPEARANCE); + public static final SynthesisOption SHOW_INSTANCE_NAMES = + SynthesisOption.createCheckOption("Reactor Instance Names", false).setCategory(APPEARANCE); + public static final SynthesisOption REACTOR_PARAMETER_MODE = + SynthesisOption.createChoiceOption( + "Reactor Parameters", + ((List) Conversions.doWrapArray(ReactorParameterDisplayModes.values())), + ReactorParameterDisplayModes.NONE) + .setCategory(APPEARANCE); + public static final SynthesisOption SHOW_STATE_VARIABLES = + SynthesisOption.createCheckOption("Reactor State Variables", false).setCategory(APPEARANCE); + public static final SynthesisOption REACTOR_BODY_TABLE_COLS = + SynthesisOption.createRangeOption("Reactor Parameter/Variable Columns", 1, 10, 1) + .setCategory(APPEARANCE); + + public static final SynthesisOption SPACING = + SynthesisOption.createRangeOption("Spacing (%)", 0, 150, 5, 75).setCategory(LAYOUT); + + /** Synthesis actions */ + public static final DisplayedActionData COLLAPSE_ALL = + DisplayedActionData.create(CollapseAllReactorsAction.ID, "Hide all Details"); + + public static final DisplayedActionData EXPAND_ALL = + DisplayedActionData.create(ExpandAllReactorsAction.ID, "Show all Details"); + + @Override + public List getDisplayedSynthesisOptions() { + return List.of( + SHOW_ALL_REACTORS, + MemorizingExpandCollapseAction.MEMORIZE_EXPANSION_STATES, + CYCLE_DETECTION, + APPEARANCE, + ModeDiagrams.MODES_CATEGORY, + ModeDiagrams.SHOW_TRANSITION_LABELS, + ModeDiagrams.INITIALLY_COLLAPSE_MODES, + SHOW_USER_LABELS, + SHOW_HYPERLINKS, + // LinguaFrancaSynthesisInterfaceDependencies.SHOW_INTERFACE_DEPENDENCIES, + REACTIONS_USE_HYPEREDGES, + USE_ALTERNATIVE_DASH_PATTERN, + SHOW_PORT_NAMES, + SHOW_MULTIPORT_WIDTH, + SHOW_REACTION_CODE, + SHOW_REACTION_LEVEL, + SHOW_REACTION_ORDER_EDGES, + SHOW_REACTOR_HOST, + SHOW_INSTANCE_NAMES, + REACTOR_PARAMETER_MODE, + SHOW_STATE_VARIABLES, + REACTOR_BODY_TABLE_COLS, + LAYOUT, + LayoutPostProcessing.MODEL_ORDER, + SPACING); + } + + @Override + public List getDisplayedActions() { + return List.of(COLLAPSE_ALL, EXPAND_ALL); + } + + // ------------------------------------------------------------------------- + + @Override + public KNode transform(final Model model) { + KNode rootNode = _kNodeExtensions.createNode(); + setLayoutOption(rootNode, CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + setLayoutOption(rootNode, CoreOptions.DIRECTION, Direction.RIGHT); + setLayoutOption(rootNode, CoreOptions.PADDING, new ElkPadding(0)); + + try { + // Find main + Reactor main = + IterableExtensions.findFirst(model.getReactors(), _utilityExtensions::isMainOrFederated); + if (main != null) { + ReactorInstance reactorInstance = new ReactorInstance(main, new SynthesisErrorReporter()); + rootNode + .getChildren() + .addAll(createReactorNode(reactorInstance, true, null, null, new HashMap<>())); + } else if (!getBooleanValue(SHOW_ALL_REACTORS)) { + KNode messageNode = _kNodeExtensions.createNode(); + _linguaFrancaShapeExtensions.addErrorMessage(messageNode, TEXT_NO_MAIN_REACTOR, null); + rootNode.getChildren().add(messageNode); + } + + // Show all reactors + if (main == null || getBooleanValue(SHOW_ALL_REACTORS)) { + List reactorNodes = new ArrayList<>(); + for (Reactor reactor : model.getReactors()) { + if (reactor == main) continue; + ReactorInstance reactorInstance = + new ReactorInstance(reactor, new SynthesisErrorReporter()); + reactorNodes.addAll( + createReactorNode( + reactorInstance, + main == null, + HashBasedTable.create(), + HashBasedTable.create(), + new HashMap<>())); + } + if (!reactorNodes.isEmpty()) { + // To allow ordering, we need box layout but we also need layered layout for ports thus + // wrap all node + reactorNodes.add(0, IterableExtensions.head(rootNode.getChildren())); + + int index = 0; + for (KNode node : reactorNodes) { + // Element could be null if there is no main reactor and Show All Reactors is checked. + if (node == null) continue; + if (node.getProperty(CoreOptions.COMMENT_BOX)) continue; + KNode child = _kNodeExtensions.createNode(); + child.getChildren().add(node); + // Add comment nodes + for (KEdge edge : node.getIncomingEdges()) { + if (!edge.getSource().getProperty(CoreOptions.COMMENT_BOX)) continue; + child.getChildren().add(edge.getSource()); } - } catch (Exception e) { - e.printStackTrace(); + _kRenderingExtensions.addInvisibleContainerRendering(child); + setLayoutOption(child, CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + setLayoutOption(child, CoreOptions.DIRECTION, Direction.RIGHT); + setLayoutOption(child, CoreOptions.PADDING, new ElkPadding(0)); + // Legacy ordering option. + setLayoutOption(child, CoreOptions.PRIORITY, reactorNodes.size() - index); // Order! + rootNode.getChildren().add(child); + index++; + } - KNode messageNode = _kNodeExtensions.createNode(); - _linguaFrancaShapeExtensions.addErrorMessage(messageNode, "Error in Diagram Synthesis", - e.getClass().getSimpleName() + " occurred. Could not create diagram."); - rootNode.getChildren().add(messageNode); + setLayoutOption(rootNode, CoreOptions.ALGORITHM, BoxLayouterOptions.ALGORITHM_ID); + setLayoutOption(rootNode, CoreOptions.SPACING_NODE_NODE, 25.0); } + } + } catch (Exception e) { + e.printStackTrace(); + + KNode messageNode = _kNodeExtensions.createNode(); + _linguaFrancaShapeExtensions.addErrorMessage( + messageNode, + "Error in Diagram Synthesis", + e.getClass().getSimpleName() + " occurred. Could not create diagram."); + rootNode.getChildren().add(messageNode); + } - return rootNode; + return rootNode; + } + + private Collection createReactorNode( + ReactorInstance reactorInstance, + boolean expandDefault, + Table inputPortsReg, + Table outputPortsReg, + Map allReactorNodes) { + Reactor reactor = reactorInstance.reactorDefinition; + KNode node = _kNodeExtensions.createNode(); + allReactorNodes.put(reactorInstance, node); + associateWith(node, reactor); + _utilityExtensions.setID(node, reactorInstance.uniqueID()); + // save to distinguish nodes associated with the same reactor + NamedInstanceUtil.linkInstance(node, reactorInstance); + + List nodes = new ArrayList<>(); + nodes.add(node); + String label = createReactorLabel(reactorInstance); + + if (reactorInstance.recursive) { + // Mark this node + node.setProperty(REACTOR_RECURSIVE_INSTANTIATION, true); + // Mark root + allReactorNodes + .get(reactorInstance.root()) + .setProperty(REACTOR_RECURSIVE_INSTANTIATION, true); } - private Collection createReactorNode( - ReactorInstance reactorInstance, - boolean expandDefault, - Table inputPortsReg, - Table outputPortsReg, - Map allReactorNodes - ) { - Reactor reactor = reactorInstance.reactorDefinition; - KNode node = _kNodeExtensions.createNode(); - allReactorNodes.put(reactorInstance, node); - associateWith(node, reactor); - _utilityExtensions.setID(node, reactorInstance.uniqueID()); - // save to distinguish nodes associated with the same reactor - NamedInstanceUtil.linkInstance(node, reactorInstance); - - List nodes = new ArrayList<>(); - nodes.add(node); - String label = createReactorLabel(reactorInstance); - - if (reactorInstance.recursive) { - // Mark this node - node.setProperty(REACTOR_RECURSIVE_INSTANTIATION, true); - // Mark root - allReactorNodes.get(reactorInstance.root()).setProperty(REACTOR_RECURSIVE_INSTANTIATION, true); + if (reactor == null) { + _linguaFrancaShapeExtensions.addErrorMessage(node, TEXT_REACTOR_NULL, null); + } else if (reactorInstance.isMainOrFederated()) { + KRoundedRectangle figure = + _linguaFrancaShapeExtensions.addMainReactorFigure(node, reactorInstance, label); + + if (getObjectValue(REACTOR_PARAMETER_MODE) == ReactorParameterDisplayModes.TABLE + && !reactorInstance.parameters.isEmpty()) { + KRectangle rectangle = _kContainerRenderingExtensions.addRectangle(figure); + _kRenderingExtensions.setInvisible(rectangle, true); + _kRenderingExtensions.to( + _kRenderingExtensions.from( + _kRenderingExtensions.setGridPlacementData(rectangle), + _kRenderingExtensions.LEFT, + 6, + 0, + _kRenderingExtensions.TOP, + 0, + 0), + _kRenderingExtensions.RIGHT, + 6, + 0, + _kRenderingExtensions.BOTTOM, + 4, + 0); + _kRenderingExtensions.setHorizontalAlignment(rectangle, HorizontalAlignment.LEFT); + addParameterList(rectangle, reactorInstance.parameters); + } + + if (getBooleanValue(SHOW_STATE_VARIABLES)) { + var variables = + ASTUtils.collectElements( + reactor, LfPackage.eINSTANCE.getReactor_StateVars(), true, false); + if (!variables.isEmpty()) { + KRectangle rectangle = _kContainerRenderingExtensions.addRectangle(figure); + _kRenderingExtensions.setInvisible(rectangle, true); + _kRenderingExtensions.to( + _kRenderingExtensions.from( + _kRenderingExtensions.setGridPlacementData(rectangle), + _kRenderingExtensions.LEFT, + 6, + 0, + _kRenderingExtensions.TOP, + 0, + 0), + _kRenderingExtensions.RIGHT, + 6, + 0, + _kRenderingExtensions.BOTTOM, + 4, + 0); + _kRenderingExtensions.setHorizontalAlignment(rectangle, HorizontalAlignment.LEFT); + addStateVariableList(rectangle, variables); } - - if (reactor == null) { - _linguaFrancaShapeExtensions.addErrorMessage(node, TEXT_REACTOR_NULL, null); - } else if (reactorInstance.isMainOrFederated()) { - KRoundedRectangle figure = _linguaFrancaShapeExtensions.addMainReactorFigure(node, reactorInstance, label); - - if (getObjectValue(REACTOR_PARAMETER_MODE) == ReactorParameterDisplayModes.TABLE - && !reactorInstance.parameters.isEmpty() - ) { - KRectangle rectangle = _kContainerRenderingExtensions.addRectangle(figure); - _kRenderingExtensions.setInvisible(rectangle, true); - _kRenderingExtensions.to( - _kRenderingExtensions.from( - _kRenderingExtensions.setGridPlacementData(rectangle), - _kRenderingExtensions.LEFT, 6, 0, - _kRenderingExtensions.TOP, 0, 0), - _kRenderingExtensions.RIGHT, 6, 0, - _kRenderingExtensions.BOTTOM, 4, 0); - _kRenderingExtensions.setHorizontalAlignment(rectangle, HorizontalAlignment.LEFT); - addParameterList(rectangle, reactorInstance.parameters); - } - - if (getBooleanValue(SHOW_STATE_VARIABLES)) { - var variables = ASTUtils.collectElements(reactor, LfPackage.eINSTANCE.getReactor_StateVars(), true, false); - if (!variables.isEmpty()) { - KRectangle rectangle = _kContainerRenderingExtensions.addRectangle(figure); - _kRenderingExtensions.setInvisible(rectangle, true); - _kRenderingExtensions.to( - _kRenderingExtensions.from( - _kRenderingExtensions.setGridPlacementData(rectangle), - _kRenderingExtensions.LEFT, 6, 0, - _kRenderingExtensions.TOP, 0, 0), - _kRenderingExtensions.RIGHT, 6, 0, - _kRenderingExtensions.BOTTOM, 4, 0); - _kRenderingExtensions.setHorizontalAlignment(rectangle, HorizontalAlignment.LEFT); - addStateVariableList(rectangle, variables); - } - } - - if (reactorInstance.recursive) { - nodes.add(addErrorComment(node, TEXT_ERROR_RECURSIVE)); - _linguaFrancaStyleExtensions.errorStyle(figure); - } else { - _kContainerRenderingExtensions.addChildArea(figure); - node.getChildren().addAll(transformReactorNetwork(reactorInstance, - new HashMap<>(), - new HashMap<>(), - allReactorNodes)); - } - Iterables.addAll(nodes, createUserComments(reactor, node)); - configureReactorNodeLayout(node, true); - _layoutPostProcessing.configureMainReactor(node); + } + + if (reactorInstance.recursive) { + nodes.add(addErrorComment(node, TEXT_ERROR_RECURSIVE)); + _linguaFrancaStyleExtensions.errorStyle(figure); + } else { + _kContainerRenderingExtensions.addChildArea(figure); + node.getChildren() + .addAll( + transformReactorNetwork( + reactorInstance, new HashMap<>(), new HashMap<>(), allReactorNodes)); + } + Iterables.addAll(nodes, createUserComments(reactor, node)); + configureReactorNodeLayout(node, true); + _layoutPostProcessing.configureMainReactor(node); + } else { + ReactorInstance instance = reactorInstance; + + // Expanded Rectangle + ReactorFigureComponents comps = + _linguaFrancaShapeExtensions.addReactorFigure(node, reactorInstance, label); + comps.getOuter().setProperty(KlighdProperties.EXPANDED_RENDERING, true); + for (KRendering figure : comps.getFigures()) { + associateWith(figure, reactor); + _kRenderingExtensions.addDoubleClickAction(figure, MemorizingExpandCollapseAction.ID); + } + _reactorIcons.handleIcon(comps.getReactor(), reactor, false); + + if (getBooleanValue(SHOW_HYPERLINKS)) { + // Collapse button + KText button = + _linguaFrancaShapeExtensions.addTextButton(comps.getReactor(), TEXT_HIDE_ACTION); + _kRenderingExtensions.to( + _kRenderingExtensions.from( + _kRenderingExtensions.setGridPlacementData(button), + _kRenderingExtensions.LEFT, + 8, + 0, + _kRenderingExtensions.TOP, + 0, + 0), + _kRenderingExtensions.RIGHT, + 8, + 0, + _kRenderingExtensions.BOTTOM, + 0, + 0); + _kRenderingExtensions.addSingleClickAction(button, MemorizingExpandCollapseAction.ID); + _kRenderingExtensions.addDoubleClickAction(button, MemorizingExpandCollapseAction.ID); + } + + if (getObjectValue(REACTOR_PARAMETER_MODE) == ReactorParameterDisplayModes.TABLE + && !instance.parameters.isEmpty()) { + KRectangle rectangle = _kContainerRenderingExtensions.addRectangle(comps.getReactor()); + _kRenderingExtensions.setInvisible(rectangle, true); + if (!getBooleanValue(SHOW_HYPERLINKS)) { + _kRenderingExtensions.to( + _kRenderingExtensions.from( + _kRenderingExtensions.setGridPlacementData(rectangle), + _kRenderingExtensions.LEFT, + 6, + 0, + _kRenderingExtensions.TOP, + 0, + 0), + _kRenderingExtensions.RIGHT, + 6, + 0, + _kRenderingExtensions.BOTTOM, + 4, + 0); } else { - ReactorInstance instance = reactorInstance; - - // Expanded Rectangle - ReactorFigureComponents comps = _linguaFrancaShapeExtensions.addReactorFigure(node, reactorInstance, label); - comps.getOuter().setProperty(KlighdProperties.EXPANDED_RENDERING, true); - for (KRendering figure : comps.getFigures()) { - associateWith(figure, reactor); - _kRenderingExtensions.addDoubleClickAction(figure, MemorizingExpandCollapseAction.ID); - } - _reactorIcons.handleIcon(comps.getReactor(), reactor, false); - - if (getBooleanValue(SHOW_HYPERLINKS)) { - // Collapse button - KText button = _linguaFrancaShapeExtensions.addTextButton(comps.getReactor(), TEXT_HIDE_ACTION); - _kRenderingExtensions.to( - _kRenderingExtensions.from( - _kRenderingExtensions.setGridPlacementData(button), - _kRenderingExtensions.LEFT, 8, 0, - _kRenderingExtensions.TOP, 0, 0), - _kRenderingExtensions.RIGHT, 8, 0, - _kRenderingExtensions.BOTTOM, 0, 0); - _kRenderingExtensions.addSingleClickAction(button, MemorizingExpandCollapseAction.ID); - _kRenderingExtensions.addDoubleClickAction(button, MemorizingExpandCollapseAction.ID); - } - - if (getObjectValue(REACTOR_PARAMETER_MODE) == ReactorParameterDisplayModes.TABLE - && !instance.parameters.isEmpty()) { - KRectangle rectangle = _kContainerRenderingExtensions.addRectangle(comps.getReactor()); - _kRenderingExtensions.setInvisible(rectangle, true); - if (!getBooleanValue(SHOW_HYPERLINKS)) { - _kRenderingExtensions.to( - _kRenderingExtensions.from( - _kRenderingExtensions.setGridPlacementData(rectangle), - _kRenderingExtensions.LEFT, 6, 0, - _kRenderingExtensions.TOP, 0, 0), - _kRenderingExtensions.RIGHT, 6, 0, - _kRenderingExtensions.BOTTOM, 4, 0); - } else { - _kRenderingExtensions.to( - _kRenderingExtensions.from( - _kRenderingExtensions.setGridPlacementData(rectangle), - _kRenderingExtensions.LEFT, 6, 0, - _kRenderingExtensions.TOP, 4, 0), - _kRenderingExtensions.RIGHT, 6, 0, - _kRenderingExtensions.BOTTOM, 0, 0); - } - _kRenderingExtensions.setHorizontalAlignment(rectangle, HorizontalAlignment.LEFT); - addParameterList(rectangle, instance.parameters); - } - - if (getBooleanValue(SHOW_STATE_VARIABLES)) { - var variables = ASTUtils.collectElements(reactor, LfPackage.eINSTANCE.getReactor_StateVars(), true, false); - if (!variables.isEmpty()) { - KRectangle rectangle = _kContainerRenderingExtensions.addRectangle(comps.getReactor()); - _kRenderingExtensions.setInvisible(rectangle, true); - if (!getBooleanValue(SHOW_HYPERLINKS)) { - _kRenderingExtensions.to( - _kRenderingExtensions.from( - _kRenderingExtensions.setGridPlacementData(rectangle), - _kRenderingExtensions.LEFT, 6, 0, - _kRenderingExtensions.TOP, 0, 0), - _kRenderingExtensions.RIGHT, 6, 0, - _kRenderingExtensions.BOTTOM, 4, 0); - } else { - _kRenderingExtensions.to( - _kRenderingExtensions.from( - _kRenderingExtensions.setGridPlacementData(rectangle), - _kRenderingExtensions.LEFT, 6, 0, - _kRenderingExtensions.TOP, 4, 0), - _kRenderingExtensions.RIGHT, 6, 0, - _kRenderingExtensions.BOTTOM, 0, 0); - } - _kRenderingExtensions.setHorizontalAlignment(rectangle, HorizontalAlignment.LEFT); - addStateVariableList(rectangle, variables); - } - } - - if (instance.recursive) { - comps.getFigures().forEach(_linguaFrancaStyleExtensions::errorStyle); - } else { - _kContainerRenderingExtensions.addChildArea(comps.getReactor()); - } - - // Collapse Rectangle - comps = _linguaFrancaShapeExtensions.addReactorFigure(node, reactorInstance, label); - comps.getOuter().setProperty(KlighdProperties.COLLAPSED_RENDERING, true); - for (KRendering figure : comps.getFigures()) { - associateWith(figure, reactor); - if (_utilityExtensions.hasContent(instance) && !instance.recursive) { - _kRenderingExtensions.addDoubleClickAction(figure, MemorizingExpandCollapseAction.ID); - } - } - _reactorIcons.handleIcon(comps.getReactor(), reactor, true); - - if (getBooleanValue(SHOW_HYPERLINKS)) { - // Expand button - if (_utilityExtensions.hasContent(instance) && !instance.recursive) { - KText button = _linguaFrancaShapeExtensions.addTextButton(comps.getReactor(), TEXT_SHOW_ACTION); - _kRenderingExtensions.to( - _kRenderingExtensions.from( - _kRenderingExtensions.setGridPlacementData(button), - _kRenderingExtensions.LEFT, 8, 0, - _kRenderingExtensions.TOP, 0, 0), - _kRenderingExtensions.RIGHT, 8, 0, - _kRenderingExtensions.BOTTOM, 8, 0); - _kRenderingExtensions.addSingleClickAction(button, MemorizingExpandCollapseAction.ID); - _kRenderingExtensions.addDoubleClickAction(button, MemorizingExpandCollapseAction.ID); - } - } - - if (instance.recursive) { - comps.getFigures().forEach(_linguaFrancaStyleExtensions::errorStyle); - } - - - // Create ports - Map inputPorts = new HashMap<>(); - Map outputPorts = new HashMap<>(); - List inputs = instance.inputs; - if (LayoutPostProcessing.LEGACY.equals((String) getObjectValue(LayoutPostProcessing.MODEL_ORDER))) { - inputs = ListExtensions.reverseView(instance.inputs); - } - for (PortInstance input : inputs) { - inputPorts.put(input, addIOPort(node, input, true, input.isMultiport(), reactorInstance.isBank())); - } - for (PortInstance output : instance.outputs) { - outputPorts.put(output, addIOPort(node, output, false, output.isMultiport(), reactorInstance.isBank())); - } - // Mark ports - inputPorts.values().forEach(it -> it.setProperty(REACTOR_INPUT, true)); - outputPorts.values().forEach(it -> it.setProperty(REACTOR_OUTPUT, true)); - - // Add content - if (_utilityExtensions.hasContent(instance) && !instance.recursive) { - node.getChildren().addAll(transformReactorNetwork(instance, inputPorts, outputPorts, allReactorNodes)); - } - - // Pass port to given tables - if (!_utilityExtensions.isRoot(instance)) { - if (inputPortsReg != null) { - for (Map.Entry entry : inputPorts.entrySet()) { - inputPortsReg.put(instance, entry.getKey(), entry.getValue()); - } - } - if (outputPortsReg != null) { - for (Map.Entry entry : outputPorts.entrySet()) { - outputPortsReg.put(instance, entry.getKey(), entry.getValue()); - } - } - } - - if (instance.recursive) { - setLayoutOption(node, KlighdProperties.EXPAND, false); - nodes.add(addErrorComment(node, TEXT_ERROR_RECURSIVE)); - } else { - setLayoutOption(node, KlighdProperties.EXPAND, expandDefault); - - // Interface Dependencies - _interfaceDependenciesVisualization.addInterfaceDependencies(node, expandDefault); - } - - if (!_utilityExtensions.isRoot(instance)) { - // If all reactors are being shown, then only put the label on - // the reactor definition, not on its instances. Otherwise, - // add the annotation now. - if (!getBooleanValue(SHOW_ALL_REACTORS)) { - Iterables.addAll(nodes, createUserComments(reactor, node)); - } - } else { - Iterables.addAll(nodes, createUserComments(reactor, node)); - } - configureReactorNodeLayout(node, false); - _layoutPostProcessing.configureReactor(node); + _kRenderingExtensions.to( + _kRenderingExtensions.from( + _kRenderingExtensions.setGridPlacementData(rectangle), + _kRenderingExtensions.LEFT, + 6, + 0, + _kRenderingExtensions.TOP, + 4, + 0), + _kRenderingExtensions.RIGHT, + 6, + 0, + _kRenderingExtensions.BOTTOM, + 0, + 0); } - - // Find and annotate cycles - if (getBooleanValue(CYCLE_DETECTION) && - _utilityExtensions.isRoot(reactorInstance)) { - KNode errNode = detectAndAnnotateCycles(node, reactorInstance, allReactorNodes); - if (errNode != null) { - nodes.add(errNode); - } + _kRenderingExtensions.setHorizontalAlignment(rectangle, HorizontalAlignment.LEFT); + addParameterList(rectangle, instance.parameters); + } + + if (getBooleanValue(SHOW_STATE_VARIABLES)) { + var variables = + ASTUtils.collectElements( + reactor, LfPackage.eINSTANCE.getReactor_StateVars(), true, false); + if (!variables.isEmpty()) { + KRectangle rectangle = _kContainerRenderingExtensions.addRectangle(comps.getReactor()); + _kRenderingExtensions.setInvisible(rectangle, true); + if (!getBooleanValue(SHOW_HYPERLINKS)) { + _kRenderingExtensions.to( + _kRenderingExtensions.from( + _kRenderingExtensions.setGridPlacementData(rectangle), + _kRenderingExtensions.LEFT, + 6, + 0, + _kRenderingExtensions.TOP, + 0, + 0), + _kRenderingExtensions.RIGHT, + 6, + 0, + _kRenderingExtensions.BOTTOM, + 4, + 0); + } else { + _kRenderingExtensions.to( + _kRenderingExtensions.from( + _kRenderingExtensions.setGridPlacementData(rectangle), + _kRenderingExtensions.LEFT, + 6, + 0, + _kRenderingExtensions.TOP, + 4, + 0), + _kRenderingExtensions.RIGHT, + 6, + 0, + _kRenderingExtensions.BOTTOM, + 0, + 0); + } + _kRenderingExtensions.setHorizontalAlignment(rectangle, HorizontalAlignment.LEFT); + addStateVariableList(rectangle, variables); } - - return nodes; - } - - public KNode configureReactorNodeLayout(KNode node, boolean main) { - // Set layered algorithm - setLayoutOption(node, CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); - // Left to right layout - setLayoutOption(node, CoreOptions.DIRECTION, Direction.RIGHT); - // Center free floating children - setLayoutOption(node, CoreOptions.CONTENT_ALIGNMENT, ContentAlignment.centerCenter()); - - // Balanced placement with straight long edges. - setLayoutOption(node, LayeredOptions.NODE_PLACEMENT_STRATEGY, NodePlacementStrategy.NETWORK_SIMPLEX); - // Do not shrink nodes below content - setLayoutOption(node, CoreOptions.NODE_SIZE_CONSTRAINTS, EnumSet.of(SizeConstraint.MINIMUM_SIZE, SizeConstraint.PORTS)); - - // Allows to freely shuffle ports on each side - setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); - // Adjust port label spacing to be closer to edge but not overlap with port figure - // TODO: Add PortLabelPlacement.NEXT_TO_PORT_IF_POSSIBLE back into the configuration, as soon as ELK provides a fix for LF issue #1273 - setLayoutOption(node, CoreOptions.PORT_LABELS_PLACEMENT, EnumSet.of(PortLabelPlacement.ALWAYS_OTHER_SAME_SIDE, PortLabelPlacement.OUTSIDE)); - setLayoutOption(node, CoreOptions.SPACING_LABEL_PORT_HORIZONTAL, 2.0); - setLayoutOption(node, CoreOptions.SPACING_LABEL_PORT_VERTICAL, -3.0); - - // Configure spacing - if (!getBooleanValue(SHOW_HYPERLINKS)) { // Hyperlink version is more relaxed in terms of space - var factor = (double) getIntValue(SPACING) / 100; - - setLayoutOption(node, LayeredOptions.SPACING_COMPONENT_COMPONENT, LayeredOptions.SPACING_COMPONENT_COMPONENT.getDefault() * factor); - - setLayoutOption(node, LayeredOptions.SPACING_NODE_NODE, LayeredOptions.SPACING_NODE_NODE.getDefault() * factor); - setLayoutOption(node, LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS.getDefault() * factor); - - setLayoutOption(node, LayeredOptions.SPACING_PORT_PORT, LayeredOptions.SPACING_PORT_PORT.getDefault() * factor); - - setLayoutOption(node, LayeredOptions.SPACING_EDGE_NODE, LayeredOptions.SPACING_EDGE_NODE.getDefault() * factor); - setLayoutOption(node, LayeredOptions.SPACING_EDGE_NODE_BETWEEN_LAYERS, LayeredOptions.SPACING_EDGE_NODE_BETWEEN_LAYERS.getDefault() * factor); - setLayoutOption(node, LayeredOptions.SPACING_EDGE_EDGE, LayeredOptions.SPACING_EDGE_EDGE.getDefault() * factor); - setLayoutOption(node, LayeredOptions.SPACING_EDGE_EDGE_BETWEEN_LAYERS, LayeredOptions.SPACING_EDGE_EDGE_BETWEEN_LAYERS.getDefault() * factor); - setLayoutOption(node, LayeredOptions.SPACING_EDGE_EDGE, LayeredOptions.SPACING_EDGE_EDGE.getDefault() * factor); - setLayoutOption(node, LayeredOptions.SPACING_EDGE_EDGE_BETWEEN_LAYERS, LayeredOptions.SPACING_EDGE_EDGE_BETWEEN_LAYERS.getDefault() * factor); - setLayoutOption(node, LayeredOptions.SPACING_EDGE_LABEL, LayeredOptions.SPACING_EDGE_LABEL.getDefault() * factor); - - // Padding for sub graph - if (main) { // Special handing for main reactors - setLayoutOption(node, CoreOptions.PADDING, new ElkPadding(-1, 6, 6, 6)); - } else { - setLayoutOption(node, CoreOptions.PADDING, new ElkPadding(2, 6, 6, 6)); - } + } + + if (instance.recursive) { + comps.getFigures().forEach(_linguaFrancaStyleExtensions::errorStyle); + } else { + _kContainerRenderingExtensions.addChildArea(comps.getReactor()); + } + + // Collapse Rectangle + comps = _linguaFrancaShapeExtensions.addReactorFigure(node, reactorInstance, label); + comps.getOuter().setProperty(KlighdProperties.COLLAPSED_RENDERING, true); + for (KRendering figure : comps.getFigures()) { + associateWith(figure, reactor); + if (_utilityExtensions.hasContent(instance) && !instance.recursive) { + _kRenderingExtensions.addDoubleClickAction(figure, MemorizingExpandCollapseAction.ID); } - - return node; - } - - private KNode detectAndAnnotateCycles(KNode node, ReactorInstance reactorInstance, Map allReactorNodes) { - if (node.getProperty(REACTOR_RECURSIVE_INSTANTIATION)) { - _filterCycleAction.resetCycleFiltering(node); - return addErrorComment(node, TEXT_ERROR_CONTAINS_RECURSION); - } else { // only detect dependency cycles if not recursive - try { - boolean hasCycle = _cycleVisualization.detectAndHighlightCycles(reactorInstance, - allReactorNodes, it -> { - if (it instanceof KNode) { - List renderings = IterableExtensions.toList( - Iterables.filter(((KNode) it).getData(), KRendering.class)); - if (renderings.size() == 1) { - _linguaFrancaStyleExtensions.errorStyle(IterableExtensions.head(renderings)); - } else { - IterableExtensions.filter(renderings, rendering -> { - return rendering.getProperty(KlighdProperties.COLLAPSED_RENDERING); - }).forEach(_linguaFrancaStyleExtensions::errorStyle); - } - } else if (it instanceof KEdge) { - Iterables.filter(((KEdge) it).getData(), - KRendering.class).forEach(_linguaFrancaStyleExtensions::errorStyle); - // TODO initiallyHide does not work with incremental (https://github.com/kieler/KLighD/issues/37) - // cycleEgde.initiallyShow() // Show hidden order dependencies - _kRenderingExtensions.setInvisible(_kRenderingExtensions.getKRendering(it), false); - } else if (it instanceof KPort) { - Iterables.filter(((KPort) it).getData(), - KRendering.class).forEach(_linguaFrancaStyleExtensions::errorStyle); - //it.reverseTrianglePort() - } - }); - - if (hasCycle) { - KNode err = addErrorComment(node, TEXT_ERROR_CONTAINS_CYCLE); - - // Add to existing figure - KRectangle rectangle = _kContainerRenderingExtensions.addRectangle(_kRenderingExtensions.getKContainerRendering(err)); - _kRenderingExtensions.to( - _kRenderingExtensions.from( - _kRenderingExtensions.setGridPlacementData(rectangle), - _kRenderingExtensions.LEFT, 3, 0, - _kRenderingExtensions.TOP, (-1), 0), - _kRenderingExtensions.RIGHT, 3, 0, - _kRenderingExtensions.BOTTOM, 3, 0); - _linguaFrancaStyleExtensions.noSelectionStyle(rectangle); - _kRenderingExtensions.setInvisible(rectangle, true); - _kContainerRenderingExtensions.setGridPlacement(rectangle, 2); - - KRectangle subrectangle = _kContainerRenderingExtensions.addRectangle(rectangle); - _kRenderingExtensions.to( - _kRenderingExtensions.from( - _kRenderingExtensions.setGridPlacementData(subrectangle), - _kRenderingExtensions.LEFT, 0, 0, - _kRenderingExtensions.TOP, 0, 0), - _kRenderingExtensions.RIGHT, 2, 0, - _kRenderingExtensions.BOTTOM, 0, 0); - _linguaFrancaStyleExtensions.noSelectionStyle(subrectangle); - _kRenderingExtensions.addSingleClickAction(subrectangle, ShowCycleAction.ID); - - KText subrectangleText = _kContainerRenderingExtensions.addText(subrectangle, TEXT_ERROR_CYCLE_BTN_SHOW); - // Copy text style - List styles = ListExtensions.map( - IterableExtensions.head( - _kRenderingExtensions.getKContainerRendering(err).getChildren()).getStyles(), - EcoreUtil::copy); - subrectangleText.getStyles().addAll(styles); - _kRenderingExtensions.setFontSize(subrectangleText, 5); - _kRenderingExtensions.setSurroundingSpace(subrectangleText, 1, 0); - _linguaFrancaStyleExtensions.noSelectionStyle(subrectangleText); - _kRenderingExtensions.addSingleClickAction(subrectangleText, ShowCycleAction.ID); - - subrectangle = _kContainerRenderingExtensions.addRectangle(rectangle); - _kRenderingExtensions.to( - _kRenderingExtensions.from( - _kRenderingExtensions.setGridPlacementData(subrectangle), - _kRenderingExtensions.LEFT, 0, 0, - _kRenderingExtensions.TOP, 0, 0), - _kRenderingExtensions.RIGHT, 0, 0, - _kRenderingExtensions.BOTTOM, 0, 0); - _linguaFrancaStyleExtensions.noSelectionStyle(subrectangle); - _kRenderingExtensions.addSingleClickAction(subrectangle, FilterCycleAction.ID); - - subrectangleText = _kContainerRenderingExtensions.addText(subrectangle, - _filterCycleAction.isCycleFiltered(node) ? - TEXT_ERROR_CYCLE_BTN_UNFILTER : TEXT_ERROR_CYCLE_BTN_FILTER); - // Copy text style - styles = ListExtensions.map( - IterableExtensions.head( - _kRenderingExtensions.getKContainerRendering(err).getChildren()).getStyles(), - EcoreUtil::copy); - subrectangleText.getStyles().addAll(styles); - _kRenderingExtensions.setFontSize(subrectangleText, 5); - _kRenderingExtensions.setSurroundingSpace(subrectangleText, 1, 0); - _linguaFrancaStyleExtensions.noSelectionStyle(subrectangleText); - _kRenderingExtensions.addSingleClickAction(subrectangleText, FilterCycleAction.ID); - _filterCycleAction.markCycleFilterText(subrectangleText, err); - - // if user interactively requested a filtered diagram keep it filtered during updates - if (_filterCycleAction.isCycleFiltered(node)) { - _filterCycleAction.filterCycle(node); - } - return err; - } - } catch(Exception e) { - _filterCycleAction.resetCycleFiltering(node); - e.printStackTrace(); - return addErrorComment(node, TEXT_ERROR_CYCLE_DETECTION); - } + } + _reactorIcons.handleIcon(comps.getReactor(), reactor, true); + + if (getBooleanValue(SHOW_HYPERLINKS)) { + // Expand button + if (_utilityExtensions.hasContent(instance) && !instance.recursive) { + KText button = + _linguaFrancaShapeExtensions.addTextButton(comps.getReactor(), TEXT_SHOW_ACTION); + _kRenderingExtensions.to( + _kRenderingExtensions.from( + _kRenderingExtensions.setGridPlacementData(button), + _kRenderingExtensions.LEFT, + 8, + 0, + _kRenderingExtensions.TOP, + 0, + 0), + _kRenderingExtensions.RIGHT, + 8, + 0, + _kRenderingExtensions.BOTTOM, + 8, + 0); + _kRenderingExtensions.addSingleClickAction(button, MemorizingExpandCollapseAction.ID); + _kRenderingExtensions.addDoubleClickAction(button, MemorizingExpandCollapseAction.ID); } - return null; - } - - private Collection transformReactorNetwork( - ReactorInstance reactorInstance, - Map parentInputPorts, - Map parentOutputPorts, - Map allReactorNodes - ) { - List nodes = new ArrayList<>(); - Table inputPorts = HashBasedTable.create(); - Table outputPorts = HashBasedTable.create(); - Map reactionNodes = new HashMap<>(); - Map directConnectionDummyNodes = new HashMap<>(); - Multimap actionDestinations = HashMultimap.create(); - Multimap actionSources = HashMultimap.create(); - Map timerNodes = new HashMap<>(); - KNode startupNode = _kNodeExtensions.createNode(); - TriggerInstance startup = null; - KNode shutdownNode = _kNodeExtensions.createNode(); - TriggerInstance shutdown = null; - KNode resetNode = _kNodeExtensions.createNode(); - TriggerInstance reset = null; - - // Transform instances - int index = 0; - for (ReactorInstance child : reactorInstance.children) { - Boolean expansionState = MemorizingExpandCollapseAction.getExpansionState(child); - Collection rNodes = createReactorNode( - child, - expansionState != null ? expansionState : false, - inputPorts, - outputPorts, - allReactorNodes); - nodes.addAll(rNodes); - index++; + } + + if (instance.recursive) { + comps.getFigures().forEach(_linguaFrancaStyleExtensions::errorStyle); + } + + // Create ports + Map inputPorts = new HashMap<>(); + Map outputPorts = new HashMap<>(); + List inputs = instance.inputs; + if (LayoutPostProcessing.LEGACY.equals( + (String) getObjectValue(LayoutPostProcessing.MODEL_ORDER))) { + inputs = ListExtensions.reverseView(instance.inputs); + } + for (PortInstance input : inputs) { + inputPorts.put( + input, addIOPort(node, input, true, input.isMultiport(), reactorInstance.isBank())); + } + for (PortInstance output : instance.outputs) { + outputPorts.put( + output, addIOPort(node, output, false, output.isMultiport(), reactorInstance.isBank())); + } + // Mark ports + inputPorts.values().forEach(it -> it.setProperty(REACTOR_INPUT, true)); + outputPorts.values().forEach(it -> it.setProperty(REACTOR_OUTPUT, true)); + + // Add content + if (_utilityExtensions.hasContent(instance) && !instance.recursive) { + node.getChildren() + .addAll(transformReactorNetwork(instance, inputPorts, outputPorts, allReactorNodes)); + } + + // Pass port to given tables + if (!_utilityExtensions.isRoot(instance)) { + if (inputPortsReg != null) { + for (Map.Entry entry : inputPorts.entrySet()) { + inputPortsReg.put(instance, entry.getKey(), entry.getValue()); + } } - - // Create timers - for (TimerInstance timer : reactorInstance.timers) { - KNode node = associateWith(_kNodeExtensions.createNode(), timer.getDefinition()); - NamedInstanceUtil.linkInstance(node, timer); - _utilityExtensions.setID(node, timer.uniqueID()); - nodes.add(node); - Iterables.addAll(nodes, createUserComments(timer.getDefinition(), node)); - timerNodes.put(timer, node); - _linguaFrancaShapeExtensions.addTimerFigure(node, timer); - _layoutPostProcessing.configureTimer(node); + if (outputPortsReg != null) { + for (Map.Entry entry : outputPorts.entrySet()) { + outputPortsReg.put(instance, entry.getKey(), entry.getValue()); + } } + } + + if (instance.recursive) { + setLayoutOption(node, KlighdProperties.EXPAND, false); + nodes.add(addErrorComment(node, TEXT_ERROR_RECURSIVE)); + } else { + setLayoutOption(node, KlighdProperties.EXPAND, expandDefault); + + // Interface Dependencies + _interfaceDependenciesVisualization.addInterfaceDependencies(node, expandDefault); + } + + if (!_utilityExtensions.isRoot(instance)) { + // If all reactors are being shown, then only put the label on + // the reactor definition, not on its instances. Otherwise, + // add the annotation now. + if (!getBooleanValue(SHOW_ALL_REACTORS)) { + Iterables.addAll(nodes, createUserComments(reactor, node)); + } + } else { + Iterables.addAll(nodes, createUserComments(reactor, node)); + } + configureReactorNodeLayout(node, false); + _layoutPostProcessing.configureReactor(node); + } - // Create reactions - for (ReactionInstance reaction : reactorInstance.reactions) { - int idx = reactorInstance.reactions.indexOf(reaction); - KNode node = associateWith(_kNodeExtensions.createNode(), reaction.getDefinition()); - NamedInstanceUtil.linkInstance(node, reaction); - _utilityExtensions.setID(node, reaction.uniqueID()); - nodes.add(node); - Iterables.addAll(nodes, createUserComments(reaction.getDefinition(), node)); - reactionNodes.put(reaction, node); - - setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); - _layoutPostProcessing.configureReaction(node); - setLayoutOption(node, LayeredOptions.POSITION, new KVector(0, idx + 1)); // try order reactions vertically if in one layer (+1 to account for startup) - - var figure = _linguaFrancaShapeExtensions.addReactionFigure(node, reaction); - - int inputSize = Stream.concat(reaction.triggers.stream(), reaction.sources.stream()).collect(Collectors.toSet()).size(); - int outputSize = reaction.effects.size(); - if (!getBooleanValue(REACTIONS_USE_HYPEREDGES) && (inputSize > 1 || outputSize > 1)) { - // If this node will have more than one input/output port, the port positions must be adjusted to the - // pointy shape. However, this is only possible after the layout. - ReactionPortAdjustment.apply(node, figure); - } - - // connect input - KPort port = null; - for (TriggerInstance trigger : reaction.triggers) { - // Create new port if there is no previous one or each dependency should have its own one - if (port == null || !getBooleanValue(REACTIONS_USE_HYPEREDGES)) { - port = addInvisiblePort(node); - setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.WEST); - - if (getBooleanValue(REACTIONS_USE_HYPEREDGES) || inputSize == 1) { - // manual adjustment disabling automatic one - setLayoutOption(port, CoreOptions.PORT_BORDER_OFFSET, - (double) -LinguaFrancaShapeExtensions.REACTION_POINTINESS); - } - } - - if (trigger.isStartup()) { - connect(createDependencyEdge(((TriggerInstance.BuiltinTriggerVariable) trigger.getDefinition()).definition), - startupNode, - port); - startup = trigger; - } else if (trigger.isShutdown()) { - connect(createDelayEdge(((TriggerInstance.BuiltinTriggerVariable) trigger.getDefinition()).definition), - shutdownNode, - port); - shutdown = trigger; - } else if (trigger.isReset()) { - connect(createDependencyEdge(((TriggerInstance.BuiltinTriggerVariable) trigger.getDefinition()).definition), - resetNode, - port); - reset = trigger; - } else if (trigger instanceof ActionInstance) { - actionDestinations.put(((ActionInstance) trigger), port); - } else if (trigger instanceof PortInstance) { - KPort src = null; - PortInstance triggerAsPort = (PortInstance) trigger; - if (triggerAsPort.getParent() == reactorInstance) { - src = parentInputPorts.get(trigger); - } else { - src = outputPorts.get(triggerAsPort.getParent(), trigger); - } - if (src != null) { - connect(createDependencyEdge(triggerAsPort.getDefinition()), src, port); - } - } else if (trigger instanceof TimerInstance) { - KNode src = timerNodes.get(trigger); - if (src != null) { - connect(createDependencyEdge(trigger.getDefinition()), src, port); - } - } - } - - // connect dependencies - for (TriggerInstance dep : reaction.sources) { - if (reaction.triggers.contains(dep)) continue; // skip - - // Create new port if there is no previous one or each dependency should have its own one - if (port == null || !getBooleanValue(REACTIONS_USE_HYPEREDGES)) { - port = addInvisiblePort(node); - setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.WEST); + // Find and annotate cycles + if (getBooleanValue(CYCLE_DETECTION) && _utilityExtensions.isRoot(reactorInstance)) { + KNode errNode = detectAndAnnotateCycles(node, reactorInstance, allReactorNodes); + if (errNode != null) { + nodes.add(errNode); + } + } - if (getBooleanValue(REACTIONS_USE_HYPEREDGES) || inputSize == 1) { - // manual adjustment disabling automatic one - setLayoutOption(port, CoreOptions.PORT_BORDER_OFFSET, - (double) -LinguaFrancaShapeExtensions.REACTION_POINTINESS); - } - } + return nodes; + } + + public KNode configureReactorNodeLayout(KNode node, boolean main) { + // Set layered algorithm + setLayoutOption(node, CoreOptions.ALGORITHM, LayeredOptions.ALGORITHM_ID); + // Left to right layout + setLayoutOption(node, CoreOptions.DIRECTION, Direction.RIGHT); + // Center free floating children + setLayoutOption(node, CoreOptions.CONTENT_ALIGNMENT, ContentAlignment.centerCenter()); + + // Balanced placement with straight long edges. + setLayoutOption( + node, LayeredOptions.NODE_PLACEMENT_STRATEGY, NodePlacementStrategy.NETWORK_SIMPLEX); + // Do not shrink nodes below content + setLayoutOption( + node, + CoreOptions.NODE_SIZE_CONSTRAINTS, + EnumSet.of(SizeConstraint.MINIMUM_SIZE, SizeConstraint.PORTS)); + + // Allows to freely shuffle ports on each side + setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + // Adjust port label spacing to be closer to edge but not overlap with port figure + // TODO: Add PortLabelPlacement.NEXT_TO_PORT_IF_POSSIBLE back into the configuration, as soon as + // ELK provides a fix for LF issue #1273 + setLayoutOption( + node, + CoreOptions.PORT_LABELS_PLACEMENT, + EnumSet.of(PortLabelPlacement.ALWAYS_OTHER_SAME_SIDE, PortLabelPlacement.OUTSIDE)); + setLayoutOption(node, CoreOptions.SPACING_LABEL_PORT_HORIZONTAL, 2.0); + setLayoutOption(node, CoreOptions.SPACING_LABEL_PORT_VERTICAL, -3.0); + + // Configure spacing + if (!getBooleanValue(SHOW_HYPERLINKS)) { // Hyperlink version is more relaxed in terms of space + var factor = (double) getIntValue(SPACING) / 100; + + setLayoutOption( + node, + LayeredOptions.SPACING_COMPONENT_COMPONENT, + LayeredOptions.SPACING_COMPONENT_COMPONENT.getDefault() * factor); + + setLayoutOption( + node, + LayeredOptions.SPACING_NODE_NODE, + LayeredOptions.SPACING_NODE_NODE.getDefault() * factor); + setLayoutOption( + node, + LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, + LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS.getDefault() * factor); + + setLayoutOption( + node, + LayeredOptions.SPACING_PORT_PORT, + LayeredOptions.SPACING_PORT_PORT.getDefault() * factor); + + setLayoutOption( + node, + LayeredOptions.SPACING_EDGE_NODE, + LayeredOptions.SPACING_EDGE_NODE.getDefault() * factor); + setLayoutOption( + node, + LayeredOptions.SPACING_EDGE_NODE_BETWEEN_LAYERS, + LayeredOptions.SPACING_EDGE_NODE_BETWEEN_LAYERS.getDefault() * factor); + setLayoutOption( + node, + LayeredOptions.SPACING_EDGE_EDGE, + LayeredOptions.SPACING_EDGE_EDGE.getDefault() * factor); + setLayoutOption( + node, + LayeredOptions.SPACING_EDGE_EDGE_BETWEEN_LAYERS, + LayeredOptions.SPACING_EDGE_EDGE_BETWEEN_LAYERS.getDefault() * factor); + setLayoutOption( + node, + LayeredOptions.SPACING_EDGE_EDGE, + LayeredOptions.SPACING_EDGE_EDGE.getDefault() * factor); + setLayoutOption( + node, + LayeredOptions.SPACING_EDGE_EDGE_BETWEEN_LAYERS, + LayeredOptions.SPACING_EDGE_EDGE_BETWEEN_LAYERS.getDefault() * factor); + setLayoutOption( + node, + LayeredOptions.SPACING_EDGE_LABEL, + LayeredOptions.SPACING_EDGE_LABEL.getDefault() * factor); + + // Padding for sub graph + if (main) { // Special handing for main reactors + setLayoutOption(node, CoreOptions.PADDING, new ElkPadding(-1, 6, 6, 6)); + } else { + setLayoutOption(node, CoreOptions.PADDING, new ElkPadding(2, 6, 6, 6)); + } + } - if (dep instanceof PortInstance) { - KPort src = null; - PortInstance depAsPort = (PortInstance) dep; - if (dep.getParent() == reactorInstance) { - src = parentInputPorts.get(dep); + return node; + } + + private KNode detectAndAnnotateCycles( + KNode node, ReactorInstance reactorInstance, Map allReactorNodes) { + if (node.getProperty(REACTOR_RECURSIVE_INSTANTIATION)) { + _filterCycleAction.resetCycleFiltering(node); + return addErrorComment(node, TEXT_ERROR_CONTAINS_RECURSION); + } else { // only detect dependency cycles if not recursive + try { + boolean hasCycle = + _cycleVisualization.detectAndHighlightCycles( + reactorInstance, + allReactorNodes, + it -> { + if (it instanceof KNode) { + List renderings = + IterableExtensions.toList( + Iterables.filter(((KNode) it).getData(), KRendering.class)); + if (renderings.size() == 1) { + _linguaFrancaStyleExtensions.errorStyle(IterableExtensions.head(renderings)); } else { - src = outputPorts.get(depAsPort.getParent(), dep); + IterableExtensions.filter( + renderings, + rendering -> { + return rendering.getProperty(KlighdProperties.COLLAPSED_RENDERING); + }) + .forEach(_linguaFrancaStyleExtensions::errorStyle); } - if (src != null) { - connect(createDependencyEdge(dep.getDefinition()), src, port); - } - } - } - - // connect outputs - port = null; // enforce new ports for outputs - Set> iterSet = reaction.effects != null ? reaction.effects : new HashSet<>(); - for (TriggerInstance effect : iterSet) { - // Create new port if there is no previous one or each dependency should have its own one - if (port == null || !getBooleanValue(REACTIONS_USE_HYPEREDGES)) { - port = addInvisiblePort(node); - setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.EAST); - } + } else if (it instanceof KEdge) { + Iterables.filter(((KEdge) it).getData(), KRendering.class) + .forEach(_linguaFrancaStyleExtensions::errorStyle); + // TODO initiallyHide does not work with incremental + // (https://github.com/kieler/KLighD/issues/37) + // cycleEgde.initiallyShow() // Show hidden order dependencies + _kRenderingExtensions.setInvisible( + _kRenderingExtensions.getKRendering(it), false); + } else if (it instanceof KPort) { + Iterables.filter(((KPort) it).getData(), KRendering.class) + .forEach(_linguaFrancaStyleExtensions::errorStyle); + // it.reverseTrianglePort() + } + }); - if (effect instanceof ActionInstance) { - actionSources.put((ActionInstance) effect, port); - } else if (effect instanceof PortInstance) { - KPort dst = null; - PortInstance effectAsPort = (PortInstance) effect; - if (effectAsPort.isOutput()) { - dst = parentOutputPorts.get(effect); - } else { - dst = inputPorts.get(effectAsPort.getParent(), effect); - } - if (dst != null) { - connect(createDependencyEdge(effect), port, dst); - } - } - } + if (hasCycle) { + KNode err = addErrorComment(node, TEXT_ERROR_CONTAINS_CYCLE); + + // Add to existing figure + KRectangle rectangle = + _kContainerRenderingExtensions.addRectangle( + _kRenderingExtensions.getKContainerRendering(err)); + _kRenderingExtensions.to( + _kRenderingExtensions.from( + _kRenderingExtensions.setGridPlacementData(rectangle), + _kRenderingExtensions.LEFT, + 3, + 0, + _kRenderingExtensions.TOP, + (-1), + 0), + _kRenderingExtensions.RIGHT, + 3, + 0, + _kRenderingExtensions.BOTTOM, + 3, + 0); + _linguaFrancaStyleExtensions.noSelectionStyle(rectangle); + _kRenderingExtensions.setInvisible(rectangle, true); + _kContainerRenderingExtensions.setGridPlacement(rectangle, 2); + + KRectangle subrectangle = _kContainerRenderingExtensions.addRectangle(rectangle); + _kRenderingExtensions.to( + _kRenderingExtensions.from( + _kRenderingExtensions.setGridPlacementData(subrectangle), + _kRenderingExtensions.LEFT, + 0, + 0, + _kRenderingExtensions.TOP, + 0, + 0), + _kRenderingExtensions.RIGHT, + 2, + 0, + _kRenderingExtensions.BOTTOM, + 0, + 0); + _linguaFrancaStyleExtensions.noSelectionStyle(subrectangle); + _kRenderingExtensions.addSingleClickAction(subrectangle, ShowCycleAction.ID); + + KText subrectangleText = + _kContainerRenderingExtensions.addText(subrectangle, TEXT_ERROR_CYCLE_BTN_SHOW); + // Copy text style + List styles = + ListExtensions.map( + IterableExtensions.head( + _kRenderingExtensions.getKContainerRendering(err).getChildren()) + .getStyles(), + EcoreUtil::copy); + subrectangleText.getStyles().addAll(styles); + _kRenderingExtensions.setFontSize(subrectangleText, 5); + _kRenderingExtensions.setSurroundingSpace(subrectangleText, 1, 0); + _linguaFrancaStyleExtensions.noSelectionStyle(subrectangleText); + _kRenderingExtensions.addSingleClickAction(subrectangleText, ShowCycleAction.ID); + + subrectangle = _kContainerRenderingExtensions.addRectangle(rectangle); + _kRenderingExtensions.to( + _kRenderingExtensions.from( + _kRenderingExtensions.setGridPlacementData(subrectangle), + _kRenderingExtensions.LEFT, + 0, + 0, + _kRenderingExtensions.TOP, + 0, + 0), + _kRenderingExtensions.RIGHT, + 0, + 0, + _kRenderingExtensions.BOTTOM, + 0, + 0); + _linguaFrancaStyleExtensions.noSelectionStyle(subrectangle); + _kRenderingExtensions.addSingleClickAction(subrectangle, FilterCycleAction.ID); + + subrectangleText = + _kContainerRenderingExtensions.addText( + subrectangle, + _filterCycleAction.isCycleFiltered(node) + ? TEXT_ERROR_CYCLE_BTN_UNFILTER + : TEXT_ERROR_CYCLE_BTN_FILTER); + // Copy text style + styles = + ListExtensions.map( + IterableExtensions.head( + _kRenderingExtensions.getKContainerRendering(err).getChildren()) + .getStyles(), + EcoreUtil::copy); + subrectangleText.getStyles().addAll(styles); + _kRenderingExtensions.setFontSize(subrectangleText, 5); + _kRenderingExtensions.setSurroundingSpace(subrectangleText, 1, 0); + _linguaFrancaStyleExtensions.noSelectionStyle(subrectangleText); + _kRenderingExtensions.addSingleClickAction(subrectangleText, FilterCycleAction.ID); + _filterCycleAction.markCycleFilterText(subrectangleText, err); + + // if user interactively requested a filtered diagram keep it filtered during updates + if (_filterCycleAction.isCycleFiltered(node)) { + _filterCycleAction.filterCycle(node); + } + return err; } + } catch (Exception e) { + _filterCycleAction.resetCycleFiltering(node); + e.printStackTrace(); + return addErrorComment(node, TEXT_ERROR_CYCLE_DETECTION); + } + } + return null; + } + + private Collection transformReactorNetwork( + ReactorInstance reactorInstance, + Map parentInputPorts, + Map parentOutputPorts, + Map allReactorNodes) { + List nodes = new ArrayList<>(); + Table inputPorts = HashBasedTable.create(); + Table outputPorts = HashBasedTable.create(); + Map reactionNodes = new HashMap<>(); + Map directConnectionDummyNodes = new HashMap<>(); + Multimap actionDestinations = HashMultimap.create(); + Multimap actionSources = HashMultimap.create(); + Map timerNodes = new HashMap<>(); + KNode startupNode = _kNodeExtensions.createNode(); + TriggerInstance startup = null; + KNode shutdownNode = _kNodeExtensions.createNode(); + TriggerInstance shutdown = null; + KNode resetNode = _kNodeExtensions.createNode(); + TriggerInstance reset = null; + + // Transform instances + int index = 0; + for (ReactorInstance child : reactorInstance.children) { + Boolean expansionState = MemorizingExpandCollapseAction.getExpansionState(child); + Collection rNodes = + createReactorNode( + child, + expansionState != null ? expansionState : false, + inputPorts, + outputPorts, + allReactorNodes); + nodes.addAll(rNodes); + index++; + } - // Connect actions - Set actions = new HashSet<>(); - actions.addAll(actionSources.keySet()); - actions.addAll(actionDestinations.keySet()); - - for (ActionInstance action : actions) { - KNode node = associateWith(_kNodeExtensions.createNode(), action.getDefinition()); - NamedInstanceUtil.linkInstance(node, action); - _utilityExtensions.setID(node, action.uniqueID()); - nodes.add(node); - Iterables.addAll(nodes, createUserComments(action.getDefinition(), node)); - setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); - _layoutPostProcessing.configureAction(node); - Pair ports = _linguaFrancaShapeExtensions.addActionFigureAndPorts( - node, - action.isPhysical() ? "P" : "L"); - // TODO handle variables? - if (action.getMinDelay() != null && action.getMinDelay() != ActionInstance.DEFAULT_MIN_DELAY) { - _kLabelExtensions.addOutsideBottomCenteredNodeLabel( - node, - String.format("min delay: %s", action.getMinDelay().toString()), - 7); - } - // TODO default value? - if (action.getDefinition().getMinSpacing() != null) { - _kLabelExtensions.addOutsideBottomCenteredNodeLabel(node, - String.format("min spacing: %s", action.getMinSpacing().toString()), - 7); - } - if (!StringExtensions.isNullOrEmpty(action.getDefinition().getPolicy())) { - _kLabelExtensions.addOutsideBottomCenteredNodeLabel(node, - String.format("policy: %s", action.getPolicy().toString()), - 7); - } - // connect source - for (KPort source : actionSources.get(action)) { - connect(createDelayEdge(action), source, ports.getKey()); - } + // Create timers + for (TimerInstance timer : reactorInstance.timers) { + KNode node = associateWith(_kNodeExtensions.createNode(), timer.getDefinition()); + NamedInstanceUtil.linkInstance(node, timer); + _utilityExtensions.setID(node, timer.uniqueID()); + nodes.add(node); + Iterables.addAll(nodes, createUserComments(timer.getDefinition(), node)); + timerNodes.put(timer, node); + _linguaFrancaShapeExtensions.addTimerFigure(node, timer); + _layoutPostProcessing.configureTimer(node); + } - // connect targets - for (KPort target : actionDestinations.get(action)) { - connect(createDelayEdge(action), ports.getValue(), target); - } + // Create reactions + for (ReactionInstance reaction : reactorInstance.reactions) { + int idx = reactorInstance.reactions.indexOf(reaction); + KNode node = associateWith(_kNodeExtensions.createNode(), reaction.getDefinition()); + NamedInstanceUtil.linkInstance(node, reaction); + _utilityExtensions.setID(node, reaction.uniqueID()); + nodes.add(node); + Iterables.addAll(nodes, createUserComments(reaction.getDefinition(), node)); + reactionNodes.put(reaction, node); + + setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + _layoutPostProcessing.configureReaction(node); + setLayoutOption( + node, + LayeredOptions.POSITION, + new KVector( + 0, idx + 1)); // try order reactions vertically if in one layer (+1 to account for + // startup) + + var figure = _linguaFrancaShapeExtensions.addReactionFigure(node, reaction); + + int inputSize = + Stream.concat(reaction.triggers.stream(), reaction.sources.stream()) + .collect(Collectors.toSet()) + .size(); + int outputSize = reaction.effects.size(); + if (!getBooleanValue(REACTIONS_USE_HYPEREDGES) && (inputSize > 1 || outputSize > 1)) { + // If this node will have more than one input/output port, the port positions must be + // adjusted to the + // pointy shape. However, this is only possible after the layout. + ReactionPortAdjustment.apply(node, figure); + } + + // connect input + KPort port = null; + for (TriggerInstance trigger : reaction.triggers) { + // Create new port if there is no previous one or each dependency should have its own one + if (port == null || !getBooleanValue(REACTIONS_USE_HYPEREDGES)) { + port = addInvisiblePort(node); + setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.WEST); + + if (getBooleanValue(REACTIONS_USE_HYPEREDGES) || inputSize == 1) { + // manual adjustment disabling automatic one + setLayoutOption( + port, + CoreOptions.PORT_BORDER_OFFSET, + (double) -LinguaFrancaShapeExtensions.REACTION_POINTINESS); + } } - // Transform connections. - // First, collect all the source ports. - List sourcePorts = new LinkedList<>(reactorInstance.inputs); - for (ReactorInstance child : reactorInstance.children) { - sourcePorts.addAll(child.outputs); + if (trigger.isStartup()) { + connect( + createDependencyEdge( + ((TriggerInstance.BuiltinTriggerVariable) trigger.getDefinition()).definition), + startupNode, + port); + startup = trigger; + } else if (trigger.isShutdown()) { + connect( + createDelayEdge( + ((TriggerInstance.BuiltinTriggerVariable) trigger.getDefinition()).definition), + shutdownNode, + port); + shutdown = trigger; + } else if (trigger.isReset()) { + connect( + createDependencyEdge( + ((TriggerInstance.BuiltinTriggerVariable) trigger.getDefinition()).definition), + resetNode, + port); + reset = trigger; + } else if (trigger instanceof ActionInstance) { + actionDestinations.put(((ActionInstance) trigger), port); + } else if (trigger instanceof PortInstance) { + KPort src = null; + PortInstance triggerAsPort = (PortInstance) trigger; + if (triggerAsPort.getParent() == reactorInstance) { + src = parentInputPorts.get(trigger); + } else { + src = outputPorts.get(triggerAsPort.getParent(), trigger); + } + if (src != null) { + connect(createDependencyEdge(triggerAsPort.getDefinition()), src, port); + } + } else if (trigger instanceof TimerInstance) { + KNode src = timerNodes.get(trigger); + if (src != null) { + connect(createDependencyEdge(trigger.getDefinition()), src, port); + } } - - for (PortInstance leftPort : sourcePorts) { - KPort source = leftPort.getParent() == reactorInstance ? - parentInputPorts.get(leftPort) : - outputPorts.get(leftPort.getParent(), leftPort); - - for (SendRange sendRange : leftPort.getDependentPorts()) { - for (RuntimeRange rightRange : sendRange.destinations) { - PortInstance rightPort = rightRange.instance; - KPort target = rightPort.getParent() == reactorInstance ? - parentOutputPorts.get(rightPort) : - inputPorts.get(rightPort.getParent(), rightPort); - // There should be a connection, but skip if not. - Connection connection = sendRange.connection; - if (connection != null) { - KEdge edge = createIODependencyEdge(connection, (leftPort.isMultiport() || rightPort.isMultiport())); - if (connection.getDelay() != null) { - KLabel delayLabel = _kLabelExtensions.addCenterEdgeLabel(edge, ASTUtils.toOriginalText(connection.getDelay())); - associateWith(delayLabel, connection.getDelay()); - if (connection.isPhysical()) { - _linguaFrancaStyleExtensions.applyOnEdgePysicalDelayStyle(delayLabel, - reactorInstance.isMainOrFederated() ? Colors.WHITE : Colors.GRAY_95); - } else { - _linguaFrancaStyleExtensions.applyOnEdgeDelayStyle(delayLabel); - } - } else if (connection.isPhysical()) { - KLabel physicalConnectionLabel = _kLabelExtensions.addCenterEdgeLabel(edge, "---"); - _linguaFrancaStyleExtensions.applyOnEdgePysicalStyle(physicalConnectionLabel, - reactorInstance.isMainOrFederated() ? Colors.WHITE : Colors.GRAY_95); - } - if (source != null && target != null) { - // check for inside loop (direct in -> out connection with delay) - if (parentInputPorts.values().contains(source) && - parentOutputPorts.values().contains(target)) { - // edge.setLayoutOption(CoreOptions.INSIDE_SELF_LOOPS_YO, true) // Does not work as expected - // Introduce dummy node to enable direct connection (that is also hidden when collapsed) - KNode dummy = _kNodeExtensions.createNode(); - if (directConnectionDummyNodes.containsKey(target)) { - dummy = directConnectionDummyNodes.get(target); - } else { - nodes.add(dummy); - directConnectionDummyNodes.put(target, dummy); - _kRenderingExtensions.addInvisibleContainerRendering(dummy); - _kNodeExtensions.setNodeSize(dummy, 0, 0); - KEdge extraEdge = createIODependencyEdge(null, - (leftPort.isMultiport() || rightPort.isMultiport())); - connect(extraEdge, dummy, target); - } - connect(edge, source, dummy); - } else { - connect(edge, source, target); - } - } - } - } - } + } + + // connect dependencies + for (TriggerInstance dep : reaction.sources) { + if (reaction.triggers.contains(dep)) continue; // skip + + // Create new port if there is no previous one or each dependency should have its own one + if (port == null || !getBooleanValue(REACTIONS_USE_HYPEREDGES)) { + port = addInvisiblePort(node); + setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.WEST); + + if (getBooleanValue(REACTIONS_USE_HYPEREDGES) || inputSize == 1) { + // manual adjustment disabling automatic one + setLayoutOption( + port, + CoreOptions.PORT_BORDER_OFFSET, + (double) -LinguaFrancaShapeExtensions.REACTION_POINTINESS); + } } - // Add startup/shutdown - if (startup != null) { - _linguaFrancaShapeExtensions.addStartupFigure(startupNode); - _utilityExtensions.setID(startupNode, reactorInstance.uniqueID() + "_startup"); - NamedInstanceUtil.linkInstance(startupNode, startup); - startupNode.setProperty(REACTION_SPECIAL_TRIGGER, true); - nodes.add(0, startupNode); // add at the start (ordered first) - // try to order with reactions vertically if in one layer - setLayoutOption(startupNode, LayeredOptions.POSITION, new KVector(0, 0)); - setLayoutOption(startupNode, LayeredOptions.LAYERING_LAYER_CONSTRAINT, LayerConstraint.FIRST); - _layoutPostProcessing.configureAction(startupNode); - - if (getBooleanValue(REACTIONS_USE_HYPEREDGES)) { - KPort port = addInvisiblePort(startupNode); - startupNode.getOutgoingEdges().forEach(it -> { - it.setSourcePort(port); - }); - } + if (dep instanceof PortInstance) { + KPort src = null; + PortInstance depAsPort = (PortInstance) dep; + if (dep.getParent() == reactorInstance) { + src = parentInputPorts.get(dep); + } else { + src = outputPorts.get(depAsPort.getParent(), dep); + } + if (src != null) { + connect(createDependencyEdge(dep.getDefinition()), src, port); + } } - if (shutdown != null) { - _linguaFrancaShapeExtensions.addShutdownFigure(shutdownNode); - _utilityExtensions.setID(shutdownNode, reactorInstance.uniqueID() + "_shutdown"); - NamedInstanceUtil.linkInstance(shutdownNode, shutdown); - shutdownNode.setProperty(REACTION_SPECIAL_TRIGGER, true); - nodes.add(shutdownNode); // add at the end (ordered last) - // try to order with reactions vertically if in one layer - _layoutPostProcessing.configureShutDown(shutdownNode); - setLayoutOption(shutdownNode, LayeredOptions.POSITION, new KVector(0, reactorInstance.reactions.size() + 1)); - - if (getBooleanValue(REACTIONS_USE_HYPEREDGES)) { // connect all edges to one port - KPort port = addInvisiblePort(shutdownNode); - shutdownNode.getOutgoingEdges().forEach(it -> { - it.setSourcePort(port); - }); - } - } - if (reset != null) { - _linguaFrancaShapeExtensions.addResetFigure(resetNode); - _utilityExtensions.setID(resetNode, reactorInstance.uniqueID() + "_reset"); - NamedInstanceUtil.linkInstance(resetNode, reset); - resetNode.setProperty(REACTION_SPECIAL_TRIGGER, true); - nodes.add(startup != null ? 1 : 0, resetNode); // after startup - // try to order with reactions vertically if in one layer - setLayoutOption(resetNode, LayeredOptions.POSITION, new KVector(0, 0.5)); - setLayoutOption(resetNode, LayeredOptions.LAYERING_LAYER_CONSTRAINT, LayerConstraint.FIRST); - - if (getBooleanValue(REACTIONS_USE_HYPEREDGES)) { // connect all edges to one port - KPort port = addInvisiblePort(resetNode); - resetNode.getOutgoingEdges().forEach(it -> { - it.setSourcePort(port); - }); - } - } - - // Postprocess timer nodes - if (getBooleanValue(REACTIONS_USE_HYPEREDGES)) { // connect all edges to one port - for (KNode timerNode : timerNodes.values()) { - KPort port = addInvisiblePort(timerNode); - timerNode.getOutgoingEdges().forEach(it -> { - it.setSourcePort(port); - }); - } + } + + // connect outputs + port = null; // enforce new ports for outputs + Set> iterSet = + reaction.effects != null ? reaction.effects : new HashSet<>(); + for (TriggerInstance effect : iterSet) { + // Create new port if there is no previous one or each dependency should have its own one + if (port == null || !getBooleanValue(REACTIONS_USE_HYPEREDGES)) { + port = addInvisiblePort(node); + setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.EAST); } - // Add reaction order edges (add last to have them on top of other edges) - if (reactorInstance.reactions.size() > 1) { - KNode prevNode = reactionNodes.get(IterableExtensions.head(reactorInstance.reactions)); - Iterable iterList = IterableExtensions.map( - IterableExtensions.drop(reactorInstance.reactions, 1), - reactionNodes::get); - for (KNode node : iterList) { - KEdge edge = createOrderEdge(); - edge.setSource(prevNode); - edge.setTarget(node); - edge.setProperty(CoreOptions.NO_LAYOUT, true); - - // Do not remove them, as they are needed for cycle detection - KRendering edgeRendering = _kRenderingExtensions.getKRendering(edge); - _kRenderingExtensions.setInvisible(edgeRendering, !getBooleanValue(SHOW_REACTION_ORDER_EDGES)); - _kRenderingExtensions.getInvisible(edgeRendering).setPropagateToChildren(true); - // TODO this does not work work with incremental update (https://github.com/kieler/KLighD/issues/37) - // if (!getBooleanValue(SHOW_REACTION_ORDER_EDGES)) edge.initiallyHide() - - prevNode = node; - } + if (effect instanceof ActionInstance) { + actionSources.put((ActionInstance) effect, port); + } else if (effect instanceof PortInstance) { + KPort dst = null; + PortInstance effectAsPort = (PortInstance) effect; + if (effectAsPort.isOutput()) { + dst = parentOutputPorts.get(effect); + } else { + dst = inputPorts.get(effectAsPort.getParent(), effect); + } + if (dst != null) { + connect(createDependencyEdge(effect), port, dst); + } } + } + } - _layoutPostProcessing.orderChildren(nodes); - _modeDiagrams.handleModes(nodes, reactorInstance); + // Connect actions + Set actions = new HashSet<>(); + actions.addAll(actionSources.keySet()); + actions.addAll(actionDestinations.keySet()); + + for (ActionInstance action : actions) { + KNode node = associateWith(_kNodeExtensions.createNode(), action.getDefinition()); + NamedInstanceUtil.linkInstance(node, action); + _utilityExtensions.setID(node, action.uniqueID()); + nodes.add(node); + Iterables.addAll(nodes, createUserComments(action.getDefinition(), node)); + setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + _layoutPostProcessing.configureAction(node); + Pair ports = + _linguaFrancaShapeExtensions.addActionFigureAndPorts( + node, action.isPhysical() ? "P" : "L"); + // TODO handle variables? + if (action.getMinDelay() != null + && action.getMinDelay() != ActionInstance.DEFAULT_MIN_DELAY) { + _kLabelExtensions.addOutsideBottomCenteredNodeLabel( + node, String.format("min delay: %s", action.getMinDelay().toString()), 7); + } + // TODO default value? + if (action.getDefinition().getMinSpacing() != null) { + _kLabelExtensions.addOutsideBottomCenteredNodeLabel( + node, String.format("min spacing: %s", action.getMinSpacing().toString()), 7); + } + if (!StringExtensions.isNullOrEmpty(action.getDefinition().getPolicy())) { + _kLabelExtensions.addOutsideBottomCenteredNodeLabel( + node, String.format("policy: %s", action.getPolicy().toString()), 7); + } + // connect source + for (KPort source : actionSources.get(action)) { + connect(createDelayEdge(action), source, ports.getKey()); + } + + // connect targets + for (KPort target : actionDestinations.get(action)) { + connect(createDelayEdge(action), ports.getValue(), target); + } + } - return nodes; + // Transform connections. + // First, collect all the source ports. + List sourcePorts = new LinkedList<>(reactorInstance.inputs); + for (ReactorInstance child : reactorInstance.children) { + sourcePorts.addAll(child.outputs); } - private String createReactorLabel(ReactorInstance reactorInstance) { - StringBuilder b = new StringBuilder(); - if (getBooleanValue(SHOW_INSTANCE_NAMES) && !_utilityExtensions.isRoot(reactorInstance)) { - if (!reactorInstance.isMainOrFederated()) { - b.append(reactorInstance.getName()).append(" : "); - } - } - if (reactorInstance.isMainOrFederated()) { - try { - b.append(FileUtil.nameWithoutExtension(reactorInstance.reactorDeclaration.eResource())); - } catch (Exception e) { - throw Exceptions.sneakyThrow(e); + for (PortInstance leftPort : sourcePorts) { + KPort source = + leftPort.getParent() == reactorInstance + ? parentInputPorts.get(leftPort) + : outputPorts.get(leftPort.getParent(), leftPort); + + for (SendRange sendRange : leftPort.getDependentPorts()) { + for (RuntimeRange rightRange : sendRange.destinations) { + PortInstance rightPort = rightRange.instance; + KPort target = + rightPort.getParent() == reactorInstance + ? parentOutputPorts.get(rightPort) + : inputPorts.get(rightPort.getParent(), rightPort); + // There should be a connection, but skip if not. + Connection connection = sendRange.connection; + if (connection != null) { + KEdge edge = + createIODependencyEdge( + connection, (leftPort.isMultiport() || rightPort.isMultiport())); + if (connection.getDelay() != null) { + KLabel delayLabel = + _kLabelExtensions.addCenterEdgeLabel( + edge, ASTUtils.toOriginalText(connection.getDelay())); + associateWith(delayLabel, connection.getDelay()); + if (connection.isPhysical()) { + _linguaFrancaStyleExtensions.applyOnEdgePysicalDelayStyle( + delayLabel, + reactorInstance.isMainOrFederated() ? Colors.WHITE : Colors.GRAY_95); + } else { + _linguaFrancaStyleExtensions.applyOnEdgeDelayStyle(delayLabel); + } + } else if (connection.isPhysical()) { + KLabel physicalConnectionLabel = _kLabelExtensions.addCenterEdgeLabel(edge, "---"); + _linguaFrancaStyleExtensions.applyOnEdgePysicalStyle( + physicalConnectionLabel, + reactorInstance.isMainOrFederated() ? Colors.WHITE : Colors.GRAY_95); } - } else if (reactorInstance.reactorDeclaration == null) { - // There is an error in the graph. - b.append(""); - } else { - b.append(reactorInstance.reactorDeclaration.getName()); - } - if (getObjectValue(REACTOR_PARAMETER_MODE) == ReactorParameterDisplayModes.TITLE) { - if (reactorInstance.parameters.isEmpty()) { - b.append("()"); - } else { - b.append(IterableExtensions.join(reactorInstance.parameters, "(", ", ", ")", - it -> { - return createParameterLabel(it); - })); + if (source != null && target != null) { + // check for inside loop (direct in -> out connection with delay) + if (parentInputPorts.values().contains(source) + && parentOutputPorts.values().contains(target)) { + // edge.setLayoutOption(CoreOptions.INSIDE_SELF_LOOPS_YO, true) // Does not work as + // expected + // Introduce dummy node to enable direct connection (that is also hidden when + // collapsed) + KNode dummy = _kNodeExtensions.createNode(); + if (directConnectionDummyNodes.containsKey(target)) { + dummy = directConnectionDummyNodes.get(target); + } else { + nodes.add(dummy); + directConnectionDummyNodes.put(target, dummy); + _kRenderingExtensions.addInvisibleContainerRendering(dummy); + _kNodeExtensions.setNodeSize(dummy, 0, 0); + KEdge extraEdge = + createIODependencyEdge( + null, (leftPort.isMultiport() || rightPort.isMultiport())); + connect(extraEdge, dummy, target); + } + connect(edge, source, dummy); + } else { + connect(edge, source, target); + } } + } } - return b.toString(); + } } - private void addParameterList(KContainerRendering container, List parameters) { - int cols = 1; - try { - cols = getIntValue(REACTOR_BODY_TABLE_COLS); - } catch (Exception e) {} // ignore - if (cols > parameters.size()) { - cols = parameters.size(); - } - _kContainerRenderingExtensions.setGridPlacement(container, cols); - for (ParameterInstance param : parameters) { - var entry = _linguaFrancaShapeExtensions.addParameterEntry( - container, param.getDefinition(), createParameterLabel(param)); - _kRenderingExtensions.setHorizontalAlignment(entry, HorizontalAlignment.LEFT); - } + // Add startup/shutdown + if (startup != null) { + _linguaFrancaShapeExtensions.addStartupFigure(startupNode); + _utilityExtensions.setID(startupNode, reactorInstance.uniqueID() + "_startup"); + NamedInstanceUtil.linkInstance(startupNode, startup); + startupNode.setProperty(REACTION_SPECIAL_TRIGGER, true); + nodes.add(0, startupNode); // add at the start (ordered first) + // try to order with reactions vertically if in one layer + setLayoutOption(startupNode, LayeredOptions.POSITION, new KVector(0, 0)); + setLayoutOption(startupNode, LayeredOptions.LAYERING_LAYER_CONSTRAINT, LayerConstraint.FIRST); + _layoutPostProcessing.configureAction(startupNode); + + if (getBooleanValue(REACTIONS_USE_HYPEREDGES)) { + KPort port = addInvisiblePort(startupNode); + startupNode + .getOutgoingEdges() + .forEach( + it -> { + it.setSourcePort(port); + }); + } } - - private String createParameterLabel(ParameterInstance param) { - StringBuilder b = new StringBuilder(); - b.append(param.getName()); - String t = param.type.toOriginalText(); - if (!StringExtensions.isNullOrEmpty(t)) { - b.append(": ").append(t); - } - if (param.getOverride() != null) { - b.append(" = "); - var init = param.getActualValue(); - b.append(FormattingUtils.render(init)); - } - return b.toString(); + if (shutdown != null) { + _linguaFrancaShapeExtensions.addShutdownFigure(shutdownNode); + _utilityExtensions.setID(shutdownNode, reactorInstance.uniqueID() + "_shutdown"); + NamedInstanceUtil.linkInstance(shutdownNode, shutdown); + shutdownNode.setProperty(REACTION_SPECIAL_TRIGGER, true); + nodes.add(shutdownNode); // add at the end (ordered last) + // try to order with reactions vertically if in one layer + _layoutPostProcessing.configureShutDown(shutdownNode); + setLayoutOption( + shutdownNode, + LayeredOptions.POSITION, + new KVector(0, reactorInstance.reactions.size() + 1)); + + if (getBooleanValue(REACTIONS_USE_HYPEREDGES)) { // connect all edges to one port + KPort port = addInvisiblePort(shutdownNode); + shutdownNode + .getOutgoingEdges() + .forEach( + it -> { + it.setSourcePort(port); + }); + } } - - public void addStateVariableList(KContainerRendering container, List variables) { - int cols = 1; - try { - cols = getIntValue(REACTOR_BODY_TABLE_COLS); - } catch (Exception e) {} // ignore - if (cols > variables.size()) { - cols = variables.size(); - } - _kContainerRenderingExtensions.setGridPlacement(container, cols); - for (var variable : variables) { - var entry = _linguaFrancaShapeExtensions.addStateEntry( - container, variable, createStateVariableLabel(variable), variable.isReset()); - _kRenderingExtensions.setHorizontalAlignment(entry, HorizontalAlignment.LEFT); - } + if (reset != null) { + _linguaFrancaShapeExtensions.addResetFigure(resetNode); + _utilityExtensions.setID(resetNode, reactorInstance.uniqueID() + "_reset"); + NamedInstanceUtil.linkInstance(resetNode, reset); + resetNode.setProperty(REACTION_SPECIAL_TRIGGER, true); + nodes.add(startup != null ? 1 : 0, resetNode); // after startup + // try to order with reactions vertically if in one layer + setLayoutOption(resetNode, LayeredOptions.POSITION, new KVector(0, 0.5)); + setLayoutOption(resetNode, LayeredOptions.LAYERING_LAYER_CONSTRAINT, LayerConstraint.FIRST); + + if (getBooleanValue(REACTIONS_USE_HYPEREDGES)) { // connect all edges to one port + KPort port = addInvisiblePort(resetNode); + resetNode + .getOutgoingEdges() + .forEach( + it -> { + it.setSourcePort(port); + }); + } } - private String createStateVariableLabel(StateVar variable) { - StringBuilder b = new StringBuilder(); - b.append(variable.getName()); - if (variable.getType() != null) { - var t = InferredType.fromAST(variable.getType()); - b.append(":").append(t.toOriginalText()); - } - if (variable.getInit() != null) { - b.append(FormattingUtils.render(variable.getInit())); - } - return b.toString(); + // Postprocess timer nodes + if (getBooleanValue(REACTIONS_USE_HYPEREDGES)) { // connect all edges to one port + for (KNode timerNode : timerNodes.values()) { + KPort port = addInvisiblePort(timerNode); + timerNode + .getOutgoingEdges() + .forEach( + it -> { + it.setSourcePort(port); + }); + } } - private KEdge createDelayEdge(Object associate) { - KEdge edge = _kEdgeExtensions.createEdge(); - associateWith(edge, associate); - KPolyline line = _kEdgeExtensions.addPolyline(edge); - _linguaFrancaStyleExtensions.boldLineSelectionStyle(line); - _kPolylineExtensions.addJunctionPointDecorator(line); - if (getBooleanValue(USE_ALTERNATIVE_DASH_PATTERN)) { - _kRenderingExtensions.setLineStyle(line, LineStyle.CUSTOM); - _kRenderingExtensions.getLineStyle(line).getDashPattern().addAll(ALTERNATIVE_DASH_PATTERN); - } else { - _kRenderingExtensions.setLineStyle(line, LineStyle.DASH); - } - return edge; + // Add reaction order edges (add last to have them on top of other edges) + if (reactorInstance.reactions.size() > 1) { + KNode prevNode = reactionNodes.get(IterableExtensions.head(reactorInstance.reactions)); + Iterable iterList = + IterableExtensions.map( + IterableExtensions.drop(reactorInstance.reactions, 1), reactionNodes::get); + for (KNode node : iterList) { + KEdge edge = createOrderEdge(); + edge.setSource(prevNode); + edge.setTarget(node); + edge.setProperty(CoreOptions.NO_LAYOUT, true); + + // Do not remove them, as they are needed for cycle detection + KRendering edgeRendering = _kRenderingExtensions.getKRendering(edge); + _kRenderingExtensions.setInvisible( + edgeRendering, !getBooleanValue(SHOW_REACTION_ORDER_EDGES)); + _kRenderingExtensions.getInvisible(edgeRendering).setPropagateToChildren(true); + // TODO this does not work work with incremental update + // (https://github.com/kieler/KLighD/issues/37) + // if (!getBooleanValue(SHOW_REACTION_ORDER_EDGES)) edge.initiallyHide() + + prevNode = node; + } } - private KEdge createIODependencyEdge(Object associate, boolean multiport) { - KEdge edge = _kEdgeExtensions.createEdge(); - if (associate != null) { - associateWith(edge, associate); - } - KPolyline line = _kEdgeExtensions.addPolyline(edge); - _linguaFrancaStyleExtensions.boldLineSelectionStyle(line); - _kPolylineExtensions.addJunctionPointDecorator(line); - if (multiport) { - // Render multiport connections and bank connections in bold. - _kRenderingExtensions.setLineWidth(line, 2.2f); - _kRenderingExtensions.setLineCap(line, LineCap.CAP_SQUARE); - // Adjust junction point size - _kPolylineExtensions.setJunctionPointDecorator(line, line.getJunctionPointRendering(), 6, 6); - } - return edge; - } + _layoutPostProcessing.orderChildren(nodes); + _modeDiagrams.handleModes(nodes, reactorInstance); - private KEdge createDependencyEdge(Object associate) { - KEdge edge = _kEdgeExtensions.createEdge(); - if (associate != null) { - associateWith(edge, associate); - } - KPolyline line = _kEdgeExtensions.addPolyline(edge); - _linguaFrancaStyleExtensions.boldLineSelectionStyle(line); - _kPolylineExtensions.addJunctionPointDecorator(line); - if (getBooleanValue(USE_ALTERNATIVE_DASH_PATTERN)) { - _kRenderingExtensions.setLineStyle(line, LineStyle.CUSTOM); - _kRenderingExtensions.getLineStyle(line).getDashPattern().addAll(ALTERNATIVE_DASH_PATTERN); - } else { - _kRenderingExtensions.setLineStyle(line, LineStyle.DASH); - } - return edge; - } + return nodes; + } - private KEdge createOrderEdge() { - KEdge edge = _kEdgeExtensions.createEdge(); - KPolyline line = _kEdgeExtensions.addPolyline(edge); - _kRenderingExtensions.setLineWidth(line, 1.5f); - _kRenderingExtensions.setLineStyle(line, LineStyle.DOT); - _kRenderingExtensions.setForeground(line, Colors.CHOCOLATE_1); - _linguaFrancaStyleExtensions.boldLineSelectionStyle(line); - //addFixedTailArrowDecorator() // Fix for bug: https://github.com/kieler/KLighD/issues/38 - _kPolylineExtensions.addHeadArrowDecorator(line); - return edge; + private String createReactorLabel(ReactorInstance reactorInstance) { + StringBuilder b = new StringBuilder(); + if (getBooleanValue(SHOW_INSTANCE_NAMES) && !_utilityExtensions.isRoot(reactorInstance)) { + if (!reactorInstance.isMainOrFederated()) { + b.append(reactorInstance.getName()).append(" : "); + } } - - private KEdge connect(KEdge edge, KNode src, KNode dst) { - edge.setSource(src); - edge.setTarget(dst); - return edge; + if (reactorInstance.isMainOrFederated()) { + try { + b.append(FileUtil.nameWithoutExtension(reactorInstance.reactorDeclaration.eResource())); + } catch (Exception e) { + throw Exceptions.sneakyThrow(e); + } + } else if (reactorInstance.reactorDeclaration == null) { + // There is an error in the graph. + b.append(""); + } else { + b.append(reactorInstance.reactorDeclaration.getName()); } - - private KEdge connect(KEdge edge, KNode src, KPort dst) { - edge.setSource(src); - edge.setTargetPort(dst); - edge.setTarget(dst != null ? dst.getNode() : null); - return edge; + if (getObjectValue(REACTOR_PARAMETER_MODE) == ReactorParameterDisplayModes.TITLE) { + if (reactorInstance.parameters.isEmpty()) { + b.append("()"); + } else { + b.append( + IterableExtensions.join( + reactorInstance.parameters, + "(", + ", ", + ")", + it -> { + return createParameterLabel(it); + })); + } } - - private KEdge connect(KEdge edge, KPort src, KNode dst) { - edge.setSourcePort(src); - edge.setSource(src != null ? src.getNode() : null); - edge.setTarget(dst); - return edge; + return b.toString(); + } + + private void addParameterList(KContainerRendering container, List parameters) { + int cols = 1; + try { + cols = getIntValue(REACTOR_BODY_TABLE_COLS); + } catch (Exception e) { + } // ignore + if (cols > parameters.size()) { + cols = parameters.size(); + } + _kContainerRenderingExtensions.setGridPlacement(container, cols); + for (ParameterInstance param : parameters) { + var entry = + _linguaFrancaShapeExtensions.addParameterEntry( + container, param.getDefinition(), createParameterLabel(param)); + _kRenderingExtensions.setHorizontalAlignment(entry, HorizontalAlignment.LEFT); + } + } + + private String createParameterLabel(ParameterInstance param) { + StringBuilder b = new StringBuilder(); + b.append(param.getName()); + String t = param.type.toOriginalText(); + if (!StringExtensions.isNullOrEmpty(t)) { + b.append(": ").append(t); + } + if (param.getOverride() != null) { + b.append(" = "); + var init = param.getActualValue(); + b.append(FormattingUtils.render(init)); + } + return b.toString(); + } + + public void addStateVariableList(KContainerRendering container, List variables) { + int cols = 1; + try { + cols = getIntValue(REACTOR_BODY_TABLE_COLS); + } catch (Exception e) { + } // ignore + if (cols > variables.size()) { + cols = variables.size(); + } + _kContainerRenderingExtensions.setGridPlacement(container, cols); + for (var variable : variables) { + var entry = + _linguaFrancaShapeExtensions.addStateEntry( + container, variable, createStateVariableLabel(variable), variable.isReset()); + _kRenderingExtensions.setHorizontalAlignment(entry, HorizontalAlignment.LEFT); } + } + + private String createStateVariableLabel(StateVar variable) { + StringBuilder b = new StringBuilder(); + b.append(variable.getName()); + if (variable.getType() != null) { + var t = InferredType.fromAST(variable.getType()); + b.append(":").append(t.toOriginalText()); + } + if (variable.getInit() != null) { + b.append(FormattingUtils.render(variable.getInit())); + } + return b.toString(); + } + + private KEdge createDelayEdge(Object associate) { + KEdge edge = _kEdgeExtensions.createEdge(); + associateWith(edge, associate); + KPolyline line = _kEdgeExtensions.addPolyline(edge); + _linguaFrancaStyleExtensions.boldLineSelectionStyle(line); + _kPolylineExtensions.addJunctionPointDecorator(line); + if (getBooleanValue(USE_ALTERNATIVE_DASH_PATTERN)) { + _kRenderingExtensions.setLineStyle(line, LineStyle.CUSTOM); + _kRenderingExtensions.getLineStyle(line).getDashPattern().addAll(ALTERNATIVE_DASH_PATTERN); + } else { + _kRenderingExtensions.setLineStyle(line, LineStyle.DASH); + } + return edge; + } - private KEdge connect(KEdge edge, KPort src, KPort dst) { - edge.setSourcePort(src); - edge.setSource(src != null ? src.getNode() : null); - edge.setTargetPort(dst); - edge.setTarget(dst != null ? dst.getNode() : null); - return edge; + private KEdge createIODependencyEdge(Object associate, boolean multiport) { + KEdge edge = _kEdgeExtensions.createEdge(); + if (associate != null) { + associateWith(edge, associate); + } + KPolyline line = _kEdgeExtensions.addPolyline(edge); + _linguaFrancaStyleExtensions.boldLineSelectionStyle(line); + _kPolylineExtensions.addJunctionPointDecorator(line); + if (multiport) { + // Render multiport connections and bank connections in bold. + _kRenderingExtensions.setLineWidth(line, 2.2f); + _kRenderingExtensions.setLineCap(line, LineCap.CAP_SQUARE); + // Adjust junction point size + _kPolylineExtensions.setJunctionPointDecorator(line, line.getJunctionPointRendering(), 6, 6); } + return edge; + } - /** - * Translate an input/output into a port. - */ - private KPort addIOPort(KNode node, PortInstance lfPort, boolean input, boolean multiport, boolean bank) { - KPort port = _kPortExtensions.createPort(); - node.getPorts().add(port); - associateWith(port, lfPort.getDefinition()); - NamedInstanceUtil.linkInstance(port, lfPort); - _kPortExtensions.setPortSize(port, 6, 6); - - if (input) { - // multiports are smaller by an offset at the right, hence compensate in inputs - double offset = multiport ? -3.4 : -3.3; - setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.WEST); - setLayoutOption(port, CoreOptions.PORT_BORDER_OFFSET, offset); - } else { - double offset = multiport ? -2.6 : -3.3; // multiports are smaller - offset = bank ? offset - LinguaFrancaShapeExtensions.BANK_FIGURE_X_OFFSET_SUM : offset; // compensate bank figure width - setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.EAST); - setLayoutOption(port, CoreOptions.PORT_BORDER_OFFSET, offset); - } + private KEdge createDependencyEdge(Object associate) { + KEdge edge = _kEdgeExtensions.createEdge(); + if (associate != null) { + associateWith(edge, associate); + } + KPolyline line = _kEdgeExtensions.addPolyline(edge); + _linguaFrancaStyleExtensions.boldLineSelectionStyle(line); + _kPolylineExtensions.addJunctionPointDecorator(line); + if (getBooleanValue(USE_ALTERNATIVE_DASH_PATTERN)) { + _kRenderingExtensions.setLineStyle(line, LineStyle.CUSTOM); + _kRenderingExtensions.getLineStyle(line).getDashPattern().addAll(ALTERNATIVE_DASH_PATTERN); + } else { + _kRenderingExtensions.setLineStyle(line, LineStyle.DASH); + } + return edge; + } + + private KEdge createOrderEdge() { + KEdge edge = _kEdgeExtensions.createEdge(); + KPolyline line = _kEdgeExtensions.addPolyline(edge); + _kRenderingExtensions.setLineWidth(line, 1.5f); + _kRenderingExtensions.setLineStyle(line, LineStyle.DOT); + _kRenderingExtensions.setForeground(line, Colors.CHOCOLATE_1); + _linguaFrancaStyleExtensions.boldLineSelectionStyle(line); + // addFixedTailArrowDecorator() // Fix for bug: https://github.com/kieler/KLighD/issues/38 + _kPolylineExtensions.addHeadArrowDecorator(line); + return edge; + } + + private KEdge connect(KEdge edge, KNode src, KNode dst) { + edge.setSource(src); + edge.setTarget(dst); + return edge; + } + + private KEdge connect(KEdge edge, KNode src, KPort dst) { + edge.setSource(src); + edge.setTargetPort(dst); + edge.setTarget(dst != null ? dst.getNode() : null); + return edge; + } + + private KEdge connect(KEdge edge, KPort src, KNode dst) { + edge.setSourcePort(src); + edge.setSource(src != null ? src.getNode() : null); + edge.setTarget(dst); + return edge; + } + + private KEdge connect(KEdge edge, KPort src, KPort dst) { + edge.setSourcePort(src); + edge.setSource(src != null ? src.getNode() : null); + edge.setTargetPort(dst); + edge.setTarget(dst != null ? dst.getNode() : null); + return edge; + } + + /** Translate an input/output into a port. */ + private KPort addIOPort( + KNode node, PortInstance lfPort, boolean input, boolean multiport, boolean bank) { + KPort port = _kPortExtensions.createPort(); + node.getPorts().add(port); + associateWith(port, lfPort.getDefinition()); + NamedInstanceUtil.linkInstance(port, lfPort); + _kPortExtensions.setPortSize(port, 6, 6); + + if (input) { + // multiports are smaller by an offset at the right, hence compensate in inputs + double offset = multiport ? -3.4 : -3.3; + setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.WEST); + setLayoutOption(port, CoreOptions.PORT_BORDER_OFFSET, offset); + } else { + double offset = multiport ? -2.6 : -3.3; // multiports are smaller + offset = + bank + ? offset - LinguaFrancaShapeExtensions.BANK_FIGURE_X_OFFSET_SUM + : offset; // compensate bank figure width + setLayoutOption(port, CoreOptions.PORT_SIDE, PortSide.EAST); + setLayoutOption(port, CoreOptions.PORT_BORDER_OFFSET, offset); + } - if (bank && !node.getProperty(REACTOR_HAS_BANK_PORT_OFFSET)) {// compensate bank figure height - // https://github.com/eclipse/elk/issues/693 - _utilityExtensions.getPortMarginsInitIfAbsent(node).add( - new ElkMargin(0, 0, LinguaFrancaShapeExtensions.BANK_FIGURE_Y_OFFSET_SUM, 0)); - node.setProperty(REACTOR_HAS_BANK_PORT_OFFSET, true); // only once - } + if (bank && !node.getProperty(REACTOR_HAS_BANK_PORT_OFFSET)) { // compensate bank figure height + // https://github.com/eclipse/elk/issues/693 + _utilityExtensions + .getPortMarginsInitIfAbsent(node) + .add(new ElkMargin(0, 0, LinguaFrancaShapeExtensions.BANK_FIGURE_Y_OFFSET_SUM, 0)); + node.setProperty(REACTOR_HAS_BANK_PORT_OFFSET, true); // only once + } - _linguaFrancaShapeExtensions.addTrianglePort(port, multiport); + _linguaFrancaShapeExtensions.addTrianglePort(port, multiport); - String label = lfPort.getName(); - if (!getBooleanValue(SHOW_PORT_NAMES)) { - label = ""; - } - if (getBooleanValue(SHOW_MULTIPORT_WIDTH)) { - if (lfPort.isMultiport()) { - label += (lfPort.getWidth() >= 0) ? - "[" + lfPort.getWidth() + "]" : - "[?]"; - } - } - associateWith(_kLabelExtensions.addOutsidePortLabel(port, label, 8), lfPort.getDefinition()); - return port; + String label = lfPort.getName(); + if (!getBooleanValue(SHOW_PORT_NAMES)) { + label = ""; } - - private KPort addInvisiblePort(KNode node) { - KPort port = _kPortExtensions.createPort(); - node.getPorts().add(port); - port.setSize(0, 0); // invisible - return port; + if (getBooleanValue(SHOW_MULTIPORT_WIDTH)) { + if (lfPort.isMultiport()) { + label += (lfPort.getWidth() >= 0) ? "[" + lfPort.getWidth() + "]" : "[?]"; + } } - - private KNode addErrorComment(KNode node, String message) { + associateWith(_kLabelExtensions.addOutsidePortLabel(port, label, 8), lfPort.getDefinition()); + return port; + } + + private KPort addInvisiblePort(KNode node) { + KPort port = _kPortExtensions.createPort(); + node.getPorts().add(port); + port.setSize(0, 0); // invisible + return port; + } + + private KNode addErrorComment(KNode node, String message) { + KNode comment = _kNodeExtensions.createNode(); + setLayoutOption(comment, CoreOptions.COMMENT_BOX, true); + KRoundedRectangle commentFigure = + _linguaFrancaShapeExtensions.addCommentFigure(comment, message); + _linguaFrancaStyleExtensions.errorStyle(commentFigure); + _kRenderingExtensions.setBackground(commentFigure, Colors.PEACH_PUFF_2); + + // connect + KEdge edge = _kEdgeExtensions.createEdge(); + edge.setSource(comment); + edge.setTarget(node); + _linguaFrancaStyleExtensions.errorStyle(_linguaFrancaShapeExtensions.addCommentPolyline(edge)); + return comment; + } + + private Iterable createUserComments(EObject element, KNode targetNode) { + if (getBooleanValue(SHOW_USER_LABELS)) { + String commentText = AttributeUtils.getLabel(element); + + if (!StringExtensions.isNullOrEmpty(commentText)) { KNode comment = _kNodeExtensions.createNode(); setLayoutOption(comment, CoreOptions.COMMENT_BOX, true); - KRoundedRectangle commentFigure = _linguaFrancaShapeExtensions.addCommentFigure(comment, message); - _linguaFrancaStyleExtensions.errorStyle(commentFigure); - _kRenderingExtensions.setBackground(commentFigure, Colors.PEACH_PUFF_2); + KRoundedRectangle commentFigure = + _linguaFrancaShapeExtensions.addCommentFigure(comment, commentText); + _linguaFrancaStyleExtensions.commentStyle(commentFigure); // connect KEdge edge = _kEdgeExtensions.createEdge(); edge.setSource(comment); - edge.setTarget(node); - _linguaFrancaStyleExtensions.errorStyle(_linguaFrancaShapeExtensions.addCommentPolyline(edge)); - return comment; - } - - private Iterable createUserComments(EObject element, KNode targetNode) { - if (getBooleanValue(SHOW_USER_LABELS)) { - String commentText = AttributeUtils.getLabel(element); - - if (!StringExtensions.isNullOrEmpty(commentText)) { - KNode comment = _kNodeExtensions.createNode(); - setLayoutOption(comment, CoreOptions.COMMENT_BOX, true); - KRoundedRectangle commentFigure = _linguaFrancaShapeExtensions.addCommentFigure(comment, commentText); - _linguaFrancaStyleExtensions.commentStyle(commentFigure); + edge.setTarget(targetNode); + _linguaFrancaStyleExtensions.commentStyle( + _linguaFrancaShapeExtensions.addCommentPolyline(edge)); - // connect - KEdge edge = _kEdgeExtensions.createEdge(); - edge.setSource(comment); - edge.setTarget(targetNode); - _linguaFrancaStyleExtensions.commentStyle(_linguaFrancaShapeExtensions.addCommentPolyline(edge)); - - return List.of(comment); - } - } - return List.of(); + return List.of(comment); + } } - + return List.of(); + } } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/ReactorParameterDisplayModes.java b/org.lflang/src/org/lflang/diagram/synthesis/ReactorParameterDisplayModes.java index 23365eeff9..dba7c94dde 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/ReactorParameterDisplayModes.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/ReactorParameterDisplayModes.java @@ -1,47 +1,51 @@ /************* -* Copyright (c) 2020, Kiel University. -* -* 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, Kiel University. + * + * 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.diagram.synthesis; /** * Enumeration of different display options for reactor parameters. - * + * * @author Alexander Schulz-Rosengarten */ public enum ReactorParameterDisplayModes { - NONE, - TITLE, - TABLE; - - @Override - public String toString() { - switch(this) { - case NONE: return "None"; - case TITLE: return "List in Title"; - case TABLE: return "Table in Body"; - default: return ""; - } + NONE, + TITLE, + TABLE; + + @Override + public String toString() { + switch (this) { + case NONE: + return "None"; + case TITLE: + return "List in Title"; + case TABLE: + return "Table in Body"; + default: + return ""; } + } } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/SynthesisRegistration.java b/org.lflang/src/org/lflang/diagram/synthesis/SynthesisRegistration.java index f1e217b9f1..c48d2e59ee 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/SynthesisRegistration.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/SynthesisRegistration.java @@ -1,5 +1,7 @@ package org.lflang.diagram.synthesis; +import de.cau.cs.kieler.klighd.IKlighdStartupHook; +import de.cau.cs.kieler.klighd.KlighdDataManager; import org.lflang.diagram.synthesis.action.CollapseAllReactorsAction; import org.lflang.diagram.synthesis.action.ExpandAllReactorsAction; import org.lflang.diagram.synthesis.action.FilterCycleAction; @@ -10,43 +12,42 @@ import org.lflang.diagram.synthesis.styles.LinguaFrancaStyleExtensions; import org.lflang.diagram.synthesis.util.NamedInstanceUtil; -import de.cau.cs.kieler.klighd.IKlighdStartupHook; -import de.cau.cs.kieler.klighd.KlighdDataManager; - /** * Registration of all diagram synthesis related classes in Klighd. - * + * * @author Alexander Schulz-Rosengarten */ public class SynthesisRegistration implements IKlighdStartupHook { - - @Override - public void execute() { - KlighdDataManager reg = KlighdDataManager.getInstance(); - - // Synthesis - reg.registerDiagramSynthesisClass(LinguaFrancaSynthesis.ID, LinguaFrancaSynthesis.class); - - // Actions - reg.registerAction(MemorizingExpandCollapseAction.ID, new MemorizingExpandCollapseAction()); - reg.registerAction(ExpandAllReactorsAction.ID, new ExpandAllReactorsAction()); - reg.registerAction(CollapseAllReactorsAction.ID, new CollapseAllReactorsAction()); - reg.registerAction(ShowCycleAction.ID, new ShowCycleAction()); - reg.registerAction(FilterCycleAction.ID, new FilterCycleAction()); - - // Style Mod - reg.registerStyleModifier(ReactionPortAdjustment.ID, new ReactionPortAdjustment()); - - // Blacklist LF-specific properties that should be removed when a diagram is sent from the diagram server to a client. - reg.registerBlacklistedProperty(FilterCycleAction.FILTER_BUTTON); - reg.registerBlacklistedProperty(LinguaFrancaSynthesis.REACTOR_HAS_BANK_PORT_OFFSET); - reg.registerBlacklistedProperty(LinguaFrancaSynthesis.REACTOR_INPUT); - reg.registerBlacklistedProperty(LinguaFrancaSynthesis.REACTOR_OUTPUT); - reg.registerBlacklistedProperty(LinguaFrancaSynthesis.REACTION_SPECIAL_TRIGGER); - reg.registerBlacklistedProperty(ReactionPortAdjustment.PROCESSED); - reg.registerBlacklistedProperty(LinguaFrancaShapeExtensions.REACTOR_CONTENT_CONTAINER); - reg.registerBlacklistedProperty(LinguaFrancaStyleExtensions.LABEL_PARENT_BACKGROUND); - reg.registerBlacklistedProperty(NamedInstanceUtil.LINKED_INSTANCE); // Very important since its values can not be synthesized easily! - } - + + @Override + public void execute() { + KlighdDataManager reg = KlighdDataManager.getInstance(); + + // Synthesis + reg.registerDiagramSynthesisClass(LinguaFrancaSynthesis.ID, LinguaFrancaSynthesis.class); + + // Actions + reg.registerAction(MemorizingExpandCollapseAction.ID, new MemorizingExpandCollapseAction()); + reg.registerAction(ExpandAllReactorsAction.ID, new ExpandAllReactorsAction()); + reg.registerAction(CollapseAllReactorsAction.ID, new CollapseAllReactorsAction()); + reg.registerAction(ShowCycleAction.ID, new ShowCycleAction()); + reg.registerAction(FilterCycleAction.ID, new FilterCycleAction()); + + // Style Mod + reg.registerStyleModifier(ReactionPortAdjustment.ID, new ReactionPortAdjustment()); + + // Blacklist LF-specific properties that should be removed when a diagram is sent from the + // diagram server to a client. + reg.registerBlacklistedProperty(FilterCycleAction.FILTER_BUTTON); + reg.registerBlacklistedProperty(LinguaFrancaSynthesis.REACTOR_HAS_BANK_PORT_OFFSET); + reg.registerBlacklistedProperty(LinguaFrancaSynthesis.REACTOR_INPUT); + reg.registerBlacklistedProperty(LinguaFrancaSynthesis.REACTOR_OUTPUT); + reg.registerBlacklistedProperty(LinguaFrancaSynthesis.REACTION_SPECIAL_TRIGGER); + reg.registerBlacklistedProperty(ReactionPortAdjustment.PROCESSED); + reg.registerBlacklistedProperty(LinguaFrancaShapeExtensions.REACTOR_CONTENT_CONTAINER); + reg.registerBlacklistedProperty(LinguaFrancaStyleExtensions.LABEL_PARENT_BACKGROUND); + reg.registerBlacklistedProperty( + NamedInstanceUtil + .LINKED_INSTANCE); // Very important since its values can not be synthesized easily! + } } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/action/AbstractAction.java b/org.lflang/src/org/lflang/diagram/synthesis/action/AbstractAction.java index d665dff433..e840b99077 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/action/AbstractAction.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/action/AbstractAction.java @@ -1,27 +1,27 @@ /************* -* Copyright (c) 2020, Kiel University. -* -* 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, Kiel University. + * + * 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.diagram.synthesis.action; import de.cau.cs.kieler.klighd.IAction; @@ -32,24 +32,23 @@ /** * Abstract super class for diagram actions that provides some convince methods. - * + * * @author Alexander Schulz-Rosengarten */ public abstract class AbstractAction implements IAction { - public Object sourceElement(final KGraphElement elem) { - return elem.getProperty(KlighdInternalProperties.MODEL_ELEMEMT); - } - - public boolean sourceIs(KNode node, Class clazz) { - return clazz.isInstance(sourceElement(node)); - } - - public boolean sourceIsReactor(final KNode node) { - return sourceElement(node) instanceof Reactor; - } - - public Reactor sourceAsReactor(final KNode node) { - return sourceIsReactor(node) ? (Reactor) sourceElement(node) : null; - } + public Object sourceElement(final KGraphElement elem) { + return elem.getProperty(KlighdInternalProperties.MODEL_ELEMEMT); + } + + public boolean sourceIs(KNode node, Class clazz) { + return clazz.isInstance(sourceElement(node)); + } + + public boolean sourceIsReactor(final KNode node) { + return sourceElement(node) instanceof Reactor; + } + + public Reactor sourceAsReactor(final KNode node) { + return sourceIsReactor(node) ? (Reactor) sourceElement(node) : null; + } } - \ No newline at end of file diff --git a/org.lflang/src/org/lflang/diagram/synthesis/action/CollapseAllReactorsAction.java b/org.lflang/src/org/lflang/diagram/synthesis/action/CollapseAllReactorsAction.java index 0927caeb37..712e9d9e66 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/action/CollapseAllReactorsAction.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/action/CollapseAllReactorsAction.java @@ -1,27 +1,27 @@ /************* -* Copyright (c) 2020, Kiel University. -* -* 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, Kiel University. + * + * 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.diagram.synthesis.action; import de.cau.cs.kieler.klighd.IAction; @@ -29,36 +29,32 @@ import de.cau.cs.kieler.klighd.kgraph.KNode; import de.cau.cs.kieler.klighd.util.ModelingUtil; import java.util.Iterator; -import org.eclipse.xtext.xbase.lib.IteratorExtensions; import org.lflang.diagram.synthesis.util.NamedInstanceUtil; import org.lflang.lf.Mode; /** * Action that expands (shows details) of all reactor nodes. - * + * * @author Alexander Schulz-Rosengarten */ public class CollapseAllReactorsAction extends AbstractAction { - - public static final String ID = "org.lflang.diagram.synthesis.action.CollapseAllReactorsAction"; - - @Override - public IAction.ActionResult execute(final IAction.ActionContext context) { - ViewContext vc = context.getViewContext(); - Iterator nodes = ModelingUtil.eAllContentsOfType(vc.getViewModel(), KNode.class); - - while(nodes.hasNext()) { - var node = nodes.next(); - if (sourceIs(node, Mode.class) || (sourceIsReactor(node) && - !(sourceAsReactor(node).isMain() || sourceAsReactor(node).isFederated()))) { - MemorizingExpandCollapseAction.setExpansionState( - node, - NamedInstanceUtil.getLinkedInstance(node), - vc.getViewer(), - false - ); - } - } - return IAction.ActionResult.createResult(true); + + public static final String ID = "org.lflang.diagram.synthesis.action.CollapseAllReactorsAction"; + + @Override + public IAction.ActionResult execute(final IAction.ActionContext context) { + ViewContext vc = context.getViewContext(); + Iterator nodes = ModelingUtil.eAllContentsOfType(vc.getViewModel(), KNode.class); + + while (nodes.hasNext()) { + var node = nodes.next(); + if (sourceIs(node, Mode.class) + || (sourceIsReactor(node) + && !(sourceAsReactor(node).isMain() || sourceAsReactor(node).isFederated()))) { + MemorizingExpandCollapseAction.setExpansionState( + node, NamedInstanceUtil.getLinkedInstance(node), vc.getViewer(), false); + } } + return IAction.ActionResult.createResult(true); + } } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/action/ExpandAllReactorsAction.java b/org.lflang/src/org/lflang/diagram/synthesis/action/ExpandAllReactorsAction.java index 08f8b5a115..bdd7c173d6 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/action/ExpandAllReactorsAction.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/action/ExpandAllReactorsAction.java @@ -1,27 +1,27 @@ /************* -* Copyright (c) 2020, Kiel University. -* -* 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, Kiel University. + * + * 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.diagram.synthesis.action; import de.cau.cs.kieler.klighd.IAction; @@ -29,36 +29,30 @@ import de.cau.cs.kieler.klighd.kgraph.KNode; import de.cau.cs.kieler.klighd.util.ModelingUtil; import java.util.Iterator; -import org.eclipse.xtext.xbase.lib.IteratorExtensions; import org.lflang.diagram.synthesis.util.NamedInstanceUtil; import org.lflang.lf.Mode; /** * Action that collapses (hides details) of all reactor nodes. - * + * * @author Alexander Schulz-Rosengarten */ public class ExpandAllReactorsAction extends AbstractAction { - public static final String ID = "org.lflang.diagram.synthesis.action.ExpandAllReactorsAction"; - - @Override - public IAction.ActionResult execute(final IAction.ActionContext context) { - ViewContext vc = context.getViewContext(); - Iterator nodes = ModelingUtil.eAllContentsOfType(vc.getViewModel(), KNode.class); - - while(nodes.hasNext()) { - var node = nodes.next(); - if (sourceIs(node, Mode.class) || sourceIsReactor(node)) { - MemorizingExpandCollapseAction.setExpansionState( - node, - NamedInstanceUtil.getLinkedInstance(node), - vc.getViewer(), - true - ); - } - } - return IAction.ActionResult.createResult(true); + public static final String ID = "org.lflang.diagram.synthesis.action.ExpandAllReactorsAction"; + + @Override + public IAction.ActionResult execute(final IAction.ActionContext context) { + ViewContext vc = context.getViewContext(); + Iterator nodes = ModelingUtil.eAllContentsOfType(vc.getViewModel(), KNode.class); + + while (nodes.hasNext()) { + var node = nodes.next(); + if (sourceIs(node, Mode.class) || sourceIsReactor(node)) { + MemorizingExpandCollapseAction.setExpansionState( + node, NamedInstanceUtil.getLinkedInstance(node), vc.getViewer(), true); + } } + return IAction.ActionResult.createResult(true); + } } - \ No newline at end of file diff --git a/org.lflang/src/org/lflang/diagram/synthesis/action/FilterCycleAction.java b/org.lflang/src/org/lflang/diagram/synthesis/action/FilterCycleAction.java index 6daf07a1b3..bbe20806a3 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/action/FilterCycleAction.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/action/FilterCycleAction.java @@ -1,27 +1,27 @@ /************* -* Copyright (c) 2020, Kiel University. -* -* 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, Kiel University. + * + * 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.diagram.synthesis.action; import com.google.common.collect.Iterables; @@ -31,7 +31,6 @@ import de.cau.cs.kieler.klighd.kgraph.KEdge; import de.cau.cs.kieler.klighd.kgraph.KNode; import de.cau.cs.kieler.klighd.krendering.KText; - import java.util.Iterator; import java.util.List; import java.util.WeakHashMap; @@ -46,112 +45,115 @@ /** * Action that filters the diagram for only those elements included in a cycle. - * + * * @author Alexander Schulz-Rosengarten */ public class FilterCycleAction extends AbstractAction { - - public static final String ID = "org.lflang.diagram.synthesis.action.FilterCycleAction"; - - /** - * Memory-leak-free cache of filtered states - */ - private static final WeakHashMap FILTERING_STATES = new WeakHashMap<>(); - - /** - * INTERNAL property to mark filter button. - */ - public static final Property FILTER_BUTTON = new Property<>("org.lflang.diagram.synthesis.action.cyclefilter.button", false); - - @Override - public IAction.ActionResult execute(final IAction.ActionContext context) { - ViewContext vc = context.getViewContext(); - Object all = vc.getOptionValue(LinguaFrancaSynthesis.SHOW_ALL_REACTORS); - List nodes = vc.getViewModel().getChildren(); - - if (all instanceof Boolean && (Boolean) all) { - nodes = IterableExtensions.toList( - Iterables.concat( - ListExtensions.map( - nodes, it -> { return it.getChildren(); } - ) - ) - ); - } - if (IterableExtensions.exists(nodes, this::isCycleFiltered)) { - // undo - nodes.forEach(this::resetCycleFiltering); - - // re-synthesize everything - vc.getViewModel().getChildren().clear(); - vc.update(); - } else { - // filter - nodes.forEach(it -> { - this.markCycleFiltered(it); - this.filterCycle(it); - }); - - Function1 knodeFilterButton = it -> { - return it.getProperty(FILTER_BUTTON); - }; - - Function1 ktextFilterButton = it -> { - return it.getProperty(FILTER_BUTTON); - }; - - // switch filter label - for (KNode node : IterableExtensions.filter(nodes, knodeFilterButton)) { - Iterator ktexts = Iterators.filter(node.eAllContents(), KText.class); - KText text = IteratorExtensions.findFirst(ktexts, ktextFilterButton); - if (text != null) { - text.setText(LinguaFrancaSynthesis.TEXT_ERROR_CYCLE_BTN_UNFILTER); - } - } + public static final String ID = "org.lflang.diagram.synthesis.action.FilterCycleAction"; + + /** Memory-leak-free cache of filtered states */ + private static final WeakHashMap FILTERING_STATES = new WeakHashMap<>(); + + /** INTERNAL property to mark filter button. */ + public static final Property FILTER_BUTTON = + new Property<>("org.lflang.diagram.synthesis.action.cyclefilter.button", false); + + @Override + public IAction.ActionResult execute(final IAction.ActionContext context) { + ViewContext vc = context.getViewContext(); + Object all = vc.getOptionValue(LinguaFrancaSynthesis.SHOW_ALL_REACTORS); + List nodes = vc.getViewModel().getChildren(); + + if (all instanceof Boolean && (Boolean) all) { + nodes = + IterableExtensions.toList( + Iterables.concat( + ListExtensions.map( + nodes, + it -> { + return it.getChildren(); + }))); + } + + if (IterableExtensions.exists(nodes, this::isCycleFiltered)) { + // undo + nodes.forEach(this::resetCycleFiltering); + + // re-synthesize everything + vc.getViewModel().getChildren().clear(); + vc.update(); + } else { + // filter + nodes.forEach( + it -> { + this.markCycleFiltered(it); + this.filterCycle(it); + }); + + Function1 knodeFilterButton = + it -> { + return it.getProperty(FILTER_BUTTON); + }; + + Function1 ktextFilterButton = + it -> { + return it.getProperty(FILTER_BUTTON); + }; + + // switch filter label + for (KNode node : IterableExtensions.filter(nodes, knodeFilterButton)) { + Iterator ktexts = Iterators.filter(node.eAllContents(), KText.class); + KText text = IteratorExtensions.findFirst(ktexts, ktextFilterButton); + if (text != null) { + text.setText(LinguaFrancaSynthesis.TEXT_ERROR_CYCLE_BTN_UNFILTER); } - - return IAction.ActionResult.createResult(true); + } } - - public void filterCycle(KNode root) { - Predicate knodeNotInCycle = it -> { - return !it.getProperty(CycleVisualization.DEPENDENCY_CYCLE); + + return IAction.ActionResult.createResult(true); + } + + public void filterCycle(KNode root) { + Predicate knodeNotInCycle = + it -> { + return !it.getProperty(CycleVisualization.DEPENDENCY_CYCLE); }; - - Predicate kedgeNotInCycle = it -> { - return !it.getProperty(CycleVisualization.DEPENDENCY_CYCLE); + + Predicate kedgeNotInCycle = + it -> { + return !it.getProperty(CycleVisualization.DEPENDENCY_CYCLE); }; - - root.getChildren().removeIf(knodeNotInCycle); - for (KNode node : root.getChildren()) { - node.getOutgoingEdges().removeIf(kedgeNotInCycle); - this.filterCycle(node); - } - } - public void markCycleFiltered(KNode node) { - Object source = this.sourceElement(node); - if (source != null) { - FILTERING_STATES.put(source, true); - } - } - - public void resetCycleFiltering(KNode node) { - Object source = this.sourceElement(node); - if (source != null) { - FILTERING_STATES.remove(source); - } + root.getChildren().removeIf(knodeNotInCycle); + for (KNode node : root.getChildren()) { + node.getOutgoingEdges().removeIf(kedgeNotInCycle); + this.filterCycle(node); } + } - public boolean isCycleFiltered(KNode node) { - Object source = this.sourceElement(node); - Boolean result = FILTERING_STATES.get(source); - return result == null ? false : result; + public void markCycleFiltered(KNode node) { + Object source = this.sourceElement(node); + if (source != null) { + FILTERING_STATES.put(source, true); } + } - public void markCycleFilterText(KText text, KNode node) { - text.setProperty(FILTER_BUTTON, true); - node.setProperty(FILTER_BUTTON, true); + public void resetCycleFiltering(KNode node) { + Object source = this.sourceElement(node); + if (source != null) { + FILTERING_STATES.remove(source); } + } + + public boolean isCycleFiltered(KNode node) { + Object source = this.sourceElement(node); + Boolean result = FILTERING_STATES.get(source); + return result == null ? false : result; + } + + public void markCycleFilterText(KText text, KNode node) { + text.setProperty(FILTER_BUTTON, true); + node.setProperty(FILTER_BUTTON, true); + } } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/action/MemorizingExpandCollapseAction.java b/org.lflang/src/org/lflang/diagram/synthesis/action/MemorizingExpandCollapseAction.java index 20347e2c22..b9a91fe08c 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/action/MemorizingExpandCollapseAction.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/action/MemorizingExpandCollapseAction.java @@ -1,123 +1,119 @@ /************* -* Copyright (c) 2020, Kiel University. -* -* 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, Kiel University. + * + * 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.diagram.synthesis.action; -import java.util.WeakHashMap; - -import org.lflang.diagram.synthesis.util.InterfaceDependenciesVisualization; -import org.lflang.diagram.synthesis.util.NamedInstanceUtil; -import org.lflang.generator.NamedInstance; -import org.lflang.generator.ReactorInstance; - import com.google.common.base.Preconditions; - import de.cau.cs.kieler.klighd.IAction; import de.cau.cs.kieler.klighd.IViewer; import de.cau.cs.kieler.klighd.SynthesisOption; import de.cau.cs.kieler.klighd.ViewContext; import de.cau.cs.kieler.klighd.kgraph.KNode; +import java.util.WeakHashMap; +import org.lflang.diagram.synthesis.util.InterfaceDependenciesVisualization; +import org.lflang.diagram.synthesis.util.NamedInstanceUtil; +import org.lflang.generator.NamedInstance; +import org.lflang.generator.ReactorInstance; /** - * Action for toggling collapse/expand state of reactors that memorizes the state and - * allows correct initialization synthesis runs for the same model. - * Prevents automatic collapsing of manually expanded nodes. - * + * Action for toggling collapse/expand state of reactors that memorizes the state and allows correct + * initialization synthesis runs for the same model. Prevents automatic collapsing of manually + * expanded nodes. + * * @author Alexander Schulz-Rosengarten */ public class MemorizingExpandCollapseAction extends AbstractAction { - - public static final String ID = "org.lflang.diagram.synthesis.action.MemorizingExpandCollapseAction"; - - /** - * The related synthesis option - */ - public static final SynthesisOption MEMORIZE_EXPANSION_STATES = SynthesisOption.createCheckOption("Remember Collapsed/Expanded Reactors", true); - - /** - * Memory-leak-free cache of expansion states - */ - private static final WeakHashMap EXPANSION_STATES = new WeakHashMap<>(); - - /** - * Sets the expansion state of a node and saves it for future synthesis. - */ - public static void setExpansionState(final KNode node, final Object memorizableObj, final IViewer viewer, final boolean expand) { - Preconditions.checkNotNull(node); - - // Store new state if activated - if (((Boolean) viewer.getViewContext().getOptionValue(MEMORIZE_EXPANSION_STATES)) && memorizableObj != null) { - if (memorizableObj instanceof NamedInstance) { - EXPANSION_STATES.put(((NamedInstance) memorizableObj).uniqueID(), expand); - } else { - EXPANSION_STATES.put(memorizableObj, expand); - } - } - - // Apply state - if (expand) { - viewer.expand(node); - } else { - viewer.collapse(node); - } - - // Handle edges that should only appear for one of the renderings - InterfaceDependenciesVisualization.updateInterfaceDependencyVisibility(node, expand); + public static final String ID = + "org.lflang.diagram.synthesis.action.MemorizingExpandCollapseAction"; + + /** The related synthesis option */ + public static final SynthesisOption MEMORIZE_EXPANSION_STATES = + SynthesisOption.createCheckOption("Remember Collapsed/Expanded Reactors", true); + + /** Memory-leak-free cache of expansion states */ + private static final WeakHashMap EXPANSION_STATES = new WeakHashMap<>(); + + /** Sets the expansion state of a node and saves it for future synthesis. */ + public static void setExpansionState( + final KNode node, final Object memorizableObj, final IViewer viewer, final boolean expand) { + + Preconditions.checkNotNull(node); + + // Store new state if activated + if (((Boolean) viewer.getViewContext().getOptionValue(MEMORIZE_EXPANSION_STATES)) + && memorizableObj != null) { + if (memorizableObj instanceof NamedInstance) { + EXPANSION_STATES.put(((NamedInstance) memorizableObj).uniqueID(), expand); + } else { + EXPANSION_STATES.put(memorizableObj, expand); + } } - - /** - * @return the memorized expansion state of the given model element or null if not memorized - */ - public static Boolean getExpansionState(final Object obj) { - if (obj instanceof NamedInstance) { - return EXPANSION_STATES.get(((NamedInstance) obj).uniqueID()); - } - return EXPANSION_STATES.get(obj); + + // Apply state + if (expand) { + viewer.expand(node); + } else { + viewer.collapse(node); } - - //----------------------------------------------------------------------------------------------------------------- - - @Override - public IAction.ActionResult execute(final IAction.ActionContext context) { - ViewContext vc = context.getViewContext(); - IViewer v = vc.getViewer(); - KNode node = context.getKNode(); - NamedInstance linkedInstance = NamedInstanceUtil.getLinkedInstance(node); - - // Find node that is properly linked - while(node != null && linkedInstance == null) { - node = node.getParent(); - linkedInstance = NamedInstanceUtil.getLinkedInstance(node); - } - - if (node == null || (linkedInstance instanceof ReactorInstance && ((ReactorInstance) linkedInstance).isMainOrFederated())) { - return IAction.ActionResult.createResult(false); - } else { - setExpansionState(node, linkedInstance, v, !v.isExpanded(node)); // toggle - return IAction.ActionResult.createResult(true); - } + + // Handle edges that should only appear for one of the renderings + InterfaceDependenciesVisualization.updateInterfaceDependencyVisibility(node, expand); + } + + /** + * @return the memorized expansion state of the given model element or null if not memorized + */ + public static Boolean getExpansionState(final Object obj) { + if (obj instanceof NamedInstance) { + return EXPANSION_STATES.get(((NamedInstance) obj).uniqueID()); + } + return EXPANSION_STATES.get(obj); + } + + // ----------------------------------------------------------------------------------------------------------------- + + @Override + public IAction.ActionResult execute(final IAction.ActionContext context) { + ViewContext vc = context.getViewContext(); + IViewer v = vc.getViewer(); + KNode node = context.getKNode(); + NamedInstance linkedInstance = NamedInstanceUtil.getLinkedInstance(node); + + // Find node that is properly linked + while (node != null && linkedInstance == null) { + node = node.getParent(); + linkedInstance = NamedInstanceUtil.getLinkedInstance(node); + } + + if (node == null + || (linkedInstance instanceof ReactorInstance + && ((ReactorInstance) linkedInstance).isMainOrFederated())) { + return IAction.ActionResult.createResult(false); + } else { + setExpansionState(node, linkedInstance, v, !v.isExpanded(node)); // toggle + return IAction.ActionResult.createResult(true); } - + } } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/action/ShowCycleAction.java b/org.lflang/src/org/lflang/diagram/synthesis/action/ShowCycleAction.java index fb947511e4..5640adc424 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/action/ShowCycleAction.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/action/ShowCycleAction.java @@ -1,27 +1,27 @@ /************* -* Copyright (c) 2020, Kiel University. -* -* 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, Kiel University. + * + * 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.diagram.synthesis.action; import de.cau.cs.kieler.klighd.IAction; @@ -31,61 +31,58 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.Set; - import org.eclipse.xtext.xbase.lib.IteratorExtensions; import org.lflang.diagram.synthesis.util.CycleVisualization; import org.lflang.diagram.synthesis.util.NamedInstanceUtil; /** * Action that expands all reactor nodes that are included in a cycle. - * + * * @author Alexander Schulz-Rosengarten */ public class ShowCycleAction extends AbstractAction { - - public static final String ID = "org.lflang.diagram.synthesis.action.ShowCycleAction"; - private static final CollapseAllReactorsAction collapseAll = new CollapseAllReactorsAction(); - - @Override - public IAction.ActionResult execute(final IAction.ActionContext context) { - ViewContext vc = context.getViewContext(); - - // Collapse all - collapseAll.execute(context); - - // Expand only errors - Iterator knodes = ModelingUtil.eAllContentsOfType(vc.getViewModel(), KNode.class); - - // Filter out nodes that are not in cycle or not a reactor - knodes = IteratorExtensions.filter(knodes, it -> { - return it.getProperty(CycleVisualization.DEPENDENCY_CYCLE) && sourceIsReactor(it); - }); - - // Remove duplicates - Set cycleNodes = IteratorExtensions.toSet(knodes); + public static final String ID = "org.lflang.diagram.synthesis.action.ShowCycleAction"; + + private static final CollapseAllReactorsAction collapseAll = new CollapseAllReactorsAction(); + + @Override + public IAction.ActionResult execute(final IAction.ActionContext context) { + ViewContext vc = context.getViewContext(); + + // Collapse all + collapseAll.execute(context); + + // Expand only errors + Iterator knodes = ModelingUtil.eAllContentsOfType(vc.getViewModel(), KNode.class); - // Include parents - LinkedList check = new LinkedList<>(cycleNodes); + // Filter out nodes that are not in cycle or not a reactor + knodes = + IteratorExtensions.filter( + knodes, + it -> { + return it.getProperty(CycleVisualization.DEPENDENCY_CYCLE) && sourceIsReactor(it); + }); + + // Remove duplicates + Set cycleNodes = IteratorExtensions.toSet(knodes); + + // Include parents + LinkedList check = new LinkedList<>(cycleNodes); + + while (!check.isEmpty()) { + KNode parent = check.pop().getParent(); + if (parent != null && !cycleNodes.contains(parent)) { + cycleNodes.add(parent); + check.add(parent); + } + } - while (!check.isEmpty()) { - KNode parent = check.pop().getParent(); - if (parent != null && !cycleNodes.contains(parent)) { - cycleNodes.add(parent); - check.add(parent); - } - } - - // Expand - for (KNode node : cycleNodes) { - MemorizingExpandCollapseAction.setExpansionState( - node, - NamedInstanceUtil.getLinkedInstance(node), - vc.getViewer(), - true - ); - } - return IAction.ActionResult.createResult(true); + // Expand + for (KNode node : cycleNodes) { + MemorizingExpandCollapseAction.setExpansionState( + node, NamedInstanceUtil.getLinkedInstance(node), vc.getViewer(), true); } - + return IAction.ActionResult.createResult(true); + } } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/postprocessor/ReactionPortAdjustment.java b/org.lflang/src/org/lflang/diagram/synthesis/postprocessor/ReactionPortAdjustment.java index db3a42fd7a..4507039b28 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/postprocessor/ReactionPortAdjustment.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/postprocessor/ReactionPortAdjustment.java @@ -1,39 +1,29 @@ /************* -* Copyright (c) 2020, Kiel University. -* -* 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, Kiel University. + * + * 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.diagram.synthesis.postprocessor; -import java.util.stream.Collectors; - -import org.eclipse.elk.core.options.CoreOptions; -import org.eclipse.elk.core.options.PortSide; -import org.eclipse.elk.graph.properties.Property; -import org.eclipse.xtext.xbase.lib.Extension; -import org.eclipse.xtext.xbase.lib.IterableExtensions; -import org.eclipse.xtext.xbase.lib.Pair; -import org.lflang.diagram.synthesis.styles.LinguaFrancaShapeExtensions; - import de.cau.cs.kieler.klighd.IStyleModifier; import de.cau.cs.kieler.klighd.IViewer; import de.cau.cs.kieler.klighd.internal.ILayoutRecorder; @@ -45,142 +35,159 @@ import de.cau.cs.kieler.klighd.kgraph.KPort; import de.cau.cs.kieler.klighd.krendering.KRendering; import de.cau.cs.kieler.klighd.krendering.KRenderingFactory; +import java.util.stream.Collectors; +import org.eclipse.elk.core.options.CoreOptions; +import org.eclipse.elk.core.options.PortSide; +import org.eclipse.elk.graph.properties.Property; +import org.eclipse.xtext.xbase.lib.Extension; +import org.eclipse.xtext.xbase.lib.IterableExtensions; +import org.eclipse.xtext.xbase.lib.Pair; +import org.lflang.diagram.synthesis.styles.LinguaFrancaShapeExtensions; /** - * Adjusts the port position of reactions node AFTER layout, to allow free port order but also adapt (snuggle) to pointy shape of reaction node. - * + * Adjusts the port position of reactions node AFTER layout, to allow free port order but also adapt + * (snuggle) to pointy shape of reaction node. + * * @author Alexander Schulz-Rosengarten */ public class ReactionPortAdjustment implements IStyleModifier { - public static final String ID = "org.lflang.diagram.synthesis.postprocessor.ReactionPortAdjustment"; - - /** - * INTERNAL property to mark node as processed. - */ - public static final Property PROCESSED = new Property<>("org.lflang.diagram.synthesis.postprocessor.reaction.ports.processed", false); - - @Extension - private KGraphFactory _kGraphFactory = KGraphFactory.eINSTANCE; - private static KRenderingFactory _kRenderingFactory = KRenderingFactory.eINSTANCE; - - /** - * Register this modifier on a reaction rendering. - */ - public static void apply(KNode node, KRendering rendering) { - // Add modifier that fixes port positions such that edges are properly attached to the shape - var invisible = _kRenderingFactory.createKInvisibility(); - invisible.setInvisible(false); // make it ineffective (just for purpose of holding modifier) - invisible.setModifierId(ReactionPortAdjustment.ID); // Add modifier to receive callback after layout - rendering.getStyles().add(invisible); - node.setProperty(PROCESSED, false); - } + public static final String ID = + "org.lflang.diagram.synthesis.postprocessor.ReactionPortAdjustment"; - @Override - public boolean modify(IStyleModifier.StyleModificationContext context) { - try { - KGraphElement node = context.getGraphElement(); - if (node instanceof KNode) { - KNode knode = (KNode) node; - - // Find root node - KNode parent = knode; - while (parent.eContainer() != null) { - parent = (KNode) parent.eContainer(); - } - - // Get viewer (this is a bit brittle because it fetches the viewer from some internal property) - Object viewer = - parent.getAllProperties().entrySet().stream().filter(entry -> - entry.getKey().getId().equals("de.cau.cs.kieler.klighd.viewer") - || entry.getKey().getId().equals("klighd.layout.viewer")) - .findAny().map(entry -> entry.getValue()).orElse(null); - - ILayoutRecorder recorder = null; - if (viewer instanceof IViewer) { - recorder = ((IViewer) viewer).getViewContext().getLayoutRecorder(); - } - - if (!knode.getPorts().isEmpty()) { - if (IterableExtensions.head(knode.getPorts()).getYpos() != 0 && - // Only adjust if layout is already applied important for incremental update animation - !knode.getProperty(ReactionPortAdjustment.PROCESSED)) { - if (recorder != null) { - recorder.startRecording(); - } - - var in = knode.getPorts().stream().filter(p -> - p.getProperty(CoreOptions.PORT_SIDE) == PortSide.WEST).sorted((p1, p2) -> - Float.compare(p1.getYpos(), p2.getYpos())).collect(Collectors.toList()); - - var out = knode.getPorts().stream().filter(p -> - p.getProperty(CoreOptions.PORT_SIDE) == PortSide.EAST).sorted((p1, p2) -> - Float.compare(p1.getYpos(), p2.getYpos())).collect(Collectors.toList()); - - // Adjust - if (in.stream().anyMatch(p -> !p.hasProperty(CoreOptions.PORT_BORDER_OFFSET))) { - adjustPositions(IterableExtensions.indexed(in), in.size(), true); - } - if (out.stream().anyMatch(p -> !p.hasProperty(CoreOptions.PORT_BORDER_OFFSET))) { - adjustPositions(IterableExtensions.indexed(out), out.size(), false); - } - knode.setProperty(ReactionPortAdjustment.PROCESSED, true); - - if (recorder!=null) { - recorder.stopRecording(0); - } - - } else if (IterableExtensions.head(knode.getPorts()).getYpos() == 0) { - knode.setProperty(PROCESSED, false); - } - } - } - } catch (Exception e) { - e.printStackTrace(); - // do not disturb rendering process + /** INTERNAL property to mark node as processed. */ + public static final Property PROCESSED = + new Property<>("org.lflang.diagram.synthesis.postprocessor.reaction.ports.processed", false); + + @Extension private KGraphFactory _kGraphFactory = KGraphFactory.eINSTANCE; + private static KRenderingFactory _kRenderingFactory = KRenderingFactory.eINSTANCE; + + /** Register this modifier on a reaction rendering. */ + public static void apply(KNode node, KRendering rendering) { + // Add modifier that fixes port positions such that edges are properly attached to the shape + var invisible = _kRenderingFactory.createKInvisibility(); + invisible.setInvisible(false); // make it ineffective (just for purpose of holding modifier) + invisible.setModifierId( + ReactionPortAdjustment.ID); // Add modifier to receive callback after layout + rendering.getStyles().add(invisible); + node.setProperty(PROCESSED, false); + } + + @Override + public boolean modify(IStyleModifier.StyleModificationContext context) { + try { + KGraphElement node = context.getGraphElement(); + if (node instanceof KNode) { + KNode knode = (KNode) node; + + // Find root node + KNode parent = knode; + while (parent.eContainer() != null) { + parent = (KNode) parent.eContainer(); } - return false; - } - public void adjustPositions(Iterable> indexedPorts, int count, boolean input) { - float segments = LinguaFrancaShapeExtensions.REACTION_POINTINESS * 2 / (count + 1); - for (Pair indexedPort : indexedPorts) { - KPort port = indexedPort.getValue(); - int idx = indexedPort.getKey(); - float offset = 0; - - if (count % 2 != 0 && idx == count / 2) { - offset += LinguaFrancaShapeExtensions.REACTION_POINTINESS; - } else if (idx < count / 2) { - offset += segments * (idx + 1); - } else { - offset += segments * (count - idx); + // Get viewer (this is a bit brittle because it fetches the viewer from some internal + // property) + Object viewer = + parent.getAllProperties().entrySet().stream() + .filter( + entry -> + entry.getKey().getId().equals("de.cau.cs.kieler.klighd.viewer") + || entry.getKey().getId().equals("klighd.layout.viewer")) + .findAny() + .map(entry -> entry.getValue()) + .orElse(null); + + ILayoutRecorder recorder = null; + if (viewer instanceof IViewer) { + recorder = ((IViewer) viewer).getViewContext().getLayoutRecorder(); + } + + if (!knode.getPorts().isEmpty()) { + if (IterableExtensions.head(knode.getPorts()).getYpos() != 0 + && + // Only adjust if layout is already applied important for incremental update animation + !knode.getProperty(ReactionPortAdjustment.PROCESSED)) { + if (recorder != null) { + recorder.startRecording(); } - - if (!input) { // reverse - offset -= LinguaFrancaShapeExtensions.REACTION_POINTINESS; + + var in = + knode.getPorts().stream() + .filter(p -> p.getProperty(CoreOptions.PORT_SIDE) == PortSide.WEST) + .sorted((p1, p2) -> Float.compare(p1.getYpos(), p2.getYpos())) + .collect(Collectors.toList()); + + var out = + knode.getPorts().stream() + .filter(p -> p.getProperty(CoreOptions.PORT_SIDE) == PortSide.EAST) + .sorted((p1, p2) -> Float.compare(p1.getYpos(), p2.getYpos())) + .collect(Collectors.toList()); + + // Adjust + if (in.stream().anyMatch(p -> !p.hasProperty(CoreOptions.PORT_BORDER_OFFSET))) { + adjustPositions(IterableExtensions.indexed(in), in.size(), true); + } + if (out.stream().anyMatch(p -> !p.hasProperty(CoreOptions.PORT_BORDER_OFFSET))) { + adjustPositions(IterableExtensions.indexed(out), out.size(), false); } - - // apply - port.setPos(port.getXpos() + offset, port.getYpos()); - for (KEdge edge : port.getEdges()) { - if (input) { - edge.setTargetPoint(adjustedKPoint(edge.getTargetPoint(), offset)); - } else { - edge.setSourcePoint(adjustedKPoint(edge.getSourcePoint(), offset)); - } + knode.setProperty(ReactionPortAdjustment.PROCESSED, true); + + if (recorder != null) { + recorder.stopRecording(0); } - - // Save for future layout - port.setProperty(CoreOptions.PORT_BORDER_OFFSET, (double) (input ? -offset : offset)); + + } else if (IterableExtensions.head(knode.getPorts()).getYpos() == 0) { + knode.setProperty(PROCESSED, false); + } } + } + } catch (Exception e) { + e.printStackTrace(); + // do not disturb rendering process } + return false; + } + + public void adjustPositions( + Iterable> indexedPorts, int count, boolean input) { + float segments = LinguaFrancaShapeExtensions.REACTION_POINTINESS * 2 / (count + 1); + for (Pair indexedPort : indexedPorts) { + KPort port = indexedPort.getValue(); + int idx = indexedPort.getKey(); + float offset = 0; + + if (count % 2 != 0 && idx == count / 2) { + offset += LinguaFrancaShapeExtensions.REACTION_POINTINESS; + } else if (idx < count / 2) { + offset += segments * (idx + 1); + } else { + offset += segments * (count - idx); + } + + if (!input) { // reverse + offset -= LinguaFrancaShapeExtensions.REACTION_POINTINESS; + } + + // apply + port.setPos(port.getXpos() + offset, port.getYpos()); + for (KEdge edge : port.getEdges()) { + if (input) { + edge.setTargetPoint(adjustedKPoint(edge.getTargetPoint(), offset)); + } else { + edge.setSourcePoint(adjustedKPoint(edge.getSourcePoint(), offset)); + } + } - public KPoint adjustedKPoint(KPoint point, float xOffset) { - KPoint kPoint = _kGraphFactory.createKPoint(); - kPoint.setX(point.getX() + xOffset); - kPoint.setY(point.getY()); - return kPoint; + // Save for future layout + port.setProperty(CoreOptions.PORT_BORDER_OFFSET, (double) (input ? -offset : offset)); } + } + public KPoint adjustedKPoint(KPoint point, float xOffset) { + KPoint kPoint = _kGraphFactory.createKPoint(); + kPoint.setX(point.getX() + xOffset); + kPoint.setY(point.getY()); + return kPoint; + } } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java b/org.lflang/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java index 260e2c98db..efdc2ff4d5 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java @@ -1,27 +1,27 @@ /************* -* Copyright (c) 2020, Kiel University. -* -* 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, Kiel University. + * + * 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.diagram.synthesis.styles; import static de.cau.cs.kieler.klighd.krendering.extensions.PositionReferenceX.LEFT; @@ -29,29 +29,6 @@ import static de.cau.cs.kieler.klighd.krendering.extensions.PositionReferenceY.BOTTOM; import static de.cau.cs.kieler.klighd.krendering.extensions.PositionReferenceY.TOP; -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -import org.eclipse.elk.core.options.CoreOptions; -import org.eclipse.elk.core.options.PortSide; -import org.eclipse.elk.graph.properties.Property; -import org.eclipse.xtext.xbase.lib.Extension; -import org.eclipse.xtext.xbase.lib.Functions.Function1; -import org.eclipse.xtext.xbase.lib.IterableExtensions; -import org.eclipse.xtext.xbase.lib.Pair; -import org.eclipse.xtext.xbase.lib.StringExtensions; -import org.lflang.ast.ASTUtils; -import org.lflang.diagram.synthesis.AbstractSynthesisExtensions; -import org.lflang.diagram.synthesis.LinguaFrancaSynthesis; -import org.lflang.diagram.synthesis.util.UtilityExtensions; -import org.lflang.generator.ReactionInstance; -import org.lflang.generator.ReactorInstance; -import org.lflang.generator.TimerInstance; -import org.lflang.lf.Parameter; -import org.lflang.lf.StateVar; - import de.cau.cs.kieler.klighd.KlighdConstants; import de.cau.cs.kieler.klighd.kgraph.KEdge; import de.cau.cs.kieler.klighd.kgraph.KNode; @@ -87,6 +64,26 @@ import de.cau.cs.kieler.klighd.krendering.extensions.PositionReferenceX; import de.cau.cs.kieler.klighd.krendering.extensions.PositionReferenceY; import de.cau.cs.kieler.klighd.syntheses.DiagramSyntheses; +import java.util.ArrayList; +import java.util.List; +import javax.inject.Inject; +import org.eclipse.elk.core.options.CoreOptions; +import org.eclipse.elk.core.options.PortSide; +import org.eclipse.elk.graph.properties.Property; +import org.eclipse.xtext.xbase.lib.Extension; +import org.eclipse.xtext.xbase.lib.Functions.Function1; +import org.eclipse.xtext.xbase.lib.IterableExtensions; +import org.eclipse.xtext.xbase.lib.Pair; +import org.eclipse.xtext.xbase.lib.StringExtensions; +import org.lflang.ast.ASTUtils; +import org.lflang.diagram.synthesis.AbstractSynthesisExtensions; +import org.lflang.diagram.synthesis.LinguaFrancaSynthesis; +import org.lflang.diagram.synthesis.util.UtilityExtensions; +import org.lflang.generator.ReactionInstance; +import org.lflang.generator.ReactorInstance; +import org.lflang.generator.TimerInstance; +import org.lflang.lf.Parameter; +import org.lflang.lf.StateVar; /** * Extension class that provides shapes and figures for the Lingua France diagram synthesis. @@ -96,873 +93,1207 @@ @ViewSynthesisShared public class LinguaFrancaShapeExtensions extends AbstractSynthesisExtensions { - public static final float REACTION_POINTINESS = 6; // arrow point length - // Property for marking the KContainterRendering in Reactor figures that is supposed to hold the content - public static final Property REACTOR_CONTENT_CONTAINER = new Property<>( - "org.lflang.diagram.synthesis.shapes.reactor.content", false); - - @Inject - @Extension - private KNodeExtensions _kNodeExtensions; - @Inject - @Extension - private KEdgeExtensions _kEdgeExtensions; - @Inject - @Extension - private KPortExtensions _kPortExtensions; - @Inject - @Extension - private KLabelExtensions _kLabelExtensions; - @Inject - @Extension - private KRenderingExtensions _kRenderingExtensions; - @Inject - @Extension - private KContainerRenderingExtensions _kContainerRenderingExtensions; - @Inject - @Extension - private KPolylineExtensions _kPolylineExtensions; - @Inject - @Extension - private KColorExtensions _kColorExtensions; - @Inject - @Extension - private LinguaFrancaStyleExtensions _linguaFrancaStyleExtensions; - @Inject - @Extension - private UtilityExtensions _utilityExtensions; - @Extension - private KRenderingFactory _kRenderingFactory = KRenderingFactory.eINSTANCE; - - public static final float BANK_FIGURE_X_OFFSET_SUM = 6.0f; - public static final float BANK_FIGURE_Y_OFFSET_SUM = 9.0f; - - /** - * Creates the main reactor frame. - */ - public KRoundedRectangle addMainReactorFigure(KNode node, ReactorInstance reactorInstance, String text) { - int padding = getBooleanValue(LinguaFrancaSynthesis.SHOW_HYPERLINKS) ? 8 : 6; - KRoundedRectangle figure = _kRenderingExtensions.addRoundedRectangle(node, 8, 8, 1); - _kContainerRenderingExtensions.setGridPlacement(figure, 1); - _kRenderingExtensions.setLineWidth(figure, 1); - _kRenderingExtensions.setForeground(figure, Colors.GRAY); - _kRenderingExtensions.setBackground(figure, Colors.WHITE); - _linguaFrancaStyleExtensions.boldLineSelectionStyle(figure); - - // Create parent container - KRectangle parentContainer = _kContainerRenderingExtensions.addRectangle(figure); - _kRenderingExtensions.setInvisible(parentContainer, true); - setGridPlacementDataFromPointToPoint(parentContainer, - LEFT, padding, 0, TOP, padding, 0, - RIGHT, padding, 0, BOTTOM, 4, 0 - ); - - // Create child container - KRectangle childContainer = _kContainerRenderingExtensions.addRectangle(parentContainer); - _kRenderingExtensions.setInvisible(childContainer, true); - _kRenderingExtensions.setPointPlacementData(childContainer, - _kRenderingExtensions.LEFT, 0, 0.5f, - _kRenderingExtensions.TOP, 0, 0.5f, - _kRenderingExtensions.H_CENTRAL, _kRenderingExtensions.V_CENTRAL, 0, - 0, 0, 0); - KGridPlacement placement = _kContainerRenderingExtensions.setGridPlacement(childContainer, 1); - - // Add text to the child container - KText childText = _kContainerRenderingExtensions.addText(childContainer, text); - DiagramSyntheses.suppressSelectability(childText); - _linguaFrancaStyleExtensions.underlineSelectionStyle(childText); - - if (reactorInstance.reactorDefinition.isFederated()) { - KContainerRendering cloudIcon = _linguaFrancaStyleExtensions.addCloudIcon(childContainer); - setGridPlacementDataFromPointToPoint(cloudIcon, - LEFT, 3, 0, TOP, 0, 0, - RIGHT, 0, 0, BOTTOM, 0, 0 - ); - placement.setNumColumns(2); - - if (reactorInstance.reactorDefinition.getHost() != null && - getBooleanValue(LinguaFrancaSynthesis.SHOW_REACTOR_HOST)) { - KText hostNameText = _kContainerRenderingExtensions.addText(childContainer, - ASTUtils.toOriginalText(reactorInstance.reactorDefinition.getHost())); - DiagramSyntheses.suppressSelectability(hostNameText); - _linguaFrancaStyleExtensions.underlineSelectionStyle(hostNameText); - setGridPlacementDataFromPointToPoint(hostNameText, - LEFT, 3, 0, TOP, 0, 0, - RIGHT, 0, 0, BOTTOM, 0, 0 - ); - placement.setNumColumns(3); - } - } - return figure; + public static final float REACTION_POINTINESS = 6; // arrow point length + // Property for marking the KContainterRendering in Reactor figures that is supposed to hold the + // content + public static final Property REACTOR_CONTENT_CONTAINER = + new Property<>("org.lflang.diagram.synthesis.shapes.reactor.content", false); + + @Inject @Extension private KNodeExtensions _kNodeExtensions; + @Inject @Extension private KEdgeExtensions _kEdgeExtensions; + @Inject @Extension private KPortExtensions _kPortExtensions; + @Inject @Extension private KLabelExtensions _kLabelExtensions; + @Inject @Extension private KRenderingExtensions _kRenderingExtensions; + @Inject @Extension private KContainerRenderingExtensions _kContainerRenderingExtensions; + @Inject @Extension private KPolylineExtensions _kPolylineExtensions; + @Inject @Extension private KColorExtensions _kColorExtensions; + @Inject @Extension private LinguaFrancaStyleExtensions _linguaFrancaStyleExtensions; + @Inject @Extension private UtilityExtensions _utilityExtensions; + @Extension private KRenderingFactory _kRenderingFactory = KRenderingFactory.eINSTANCE; + + public static final float BANK_FIGURE_X_OFFSET_SUM = 6.0f; + public static final float BANK_FIGURE_Y_OFFSET_SUM = 9.0f; + + /** Creates the main reactor frame. */ + public KRoundedRectangle addMainReactorFigure( + KNode node, ReactorInstance reactorInstance, String text) { + int padding = getBooleanValue(LinguaFrancaSynthesis.SHOW_HYPERLINKS) ? 8 : 6; + KRoundedRectangle figure = _kRenderingExtensions.addRoundedRectangle(node, 8, 8, 1); + _kContainerRenderingExtensions.setGridPlacement(figure, 1); + _kRenderingExtensions.setLineWidth(figure, 1); + _kRenderingExtensions.setForeground(figure, Colors.GRAY); + _kRenderingExtensions.setBackground(figure, Colors.WHITE); + _linguaFrancaStyleExtensions.boldLineSelectionStyle(figure); + + // Create parent container + KRectangle parentContainer = _kContainerRenderingExtensions.addRectangle(figure); + _kRenderingExtensions.setInvisible(parentContainer, true); + setGridPlacementDataFromPointToPoint( + parentContainer, LEFT, padding, 0, TOP, padding, 0, RIGHT, padding, 0, BOTTOM, 4, 0); + + // Create child container + KRectangle childContainer = _kContainerRenderingExtensions.addRectangle(parentContainer); + _kRenderingExtensions.setInvisible(childContainer, true); + _kRenderingExtensions.setPointPlacementData( + childContainer, + _kRenderingExtensions.LEFT, + 0, + 0.5f, + _kRenderingExtensions.TOP, + 0, + 0.5f, + _kRenderingExtensions.H_CENTRAL, + _kRenderingExtensions.V_CENTRAL, + 0, + 0, + 0, + 0); + KGridPlacement placement = _kContainerRenderingExtensions.setGridPlacement(childContainer, 1); + + // Add text to the child container + KText childText = _kContainerRenderingExtensions.addText(childContainer, text); + DiagramSyntheses.suppressSelectability(childText); + _linguaFrancaStyleExtensions.underlineSelectionStyle(childText); + + if (reactorInstance.reactorDefinition.isFederated()) { + KContainerRendering cloudIcon = _linguaFrancaStyleExtensions.addCloudIcon(childContainer); + setGridPlacementDataFromPointToPoint( + cloudIcon, LEFT, 3, 0, TOP, 0, 0, RIGHT, 0, 0, BOTTOM, 0, 0); + placement.setNumColumns(2); + + if (reactorInstance.reactorDefinition.getHost() != null + && getBooleanValue(LinguaFrancaSynthesis.SHOW_REACTOR_HOST)) { + KText hostNameText = + _kContainerRenderingExtensions.addText( + childContainer, + ASTUtils.toOriginalText(reactorInstance.reactorDefinition.getHost())); + DiagramSyntheses.suppressSelectability(hostNameText); + _linguaFrancaStyleExtensions.underlineSelectionStyle(hostNameText); + setGridPlacementDataFromPointToPoint( + hostNameText, LEFT, 3, 0, TOP, 0, 0, RIGHT, 0, 0, BOTTOM, 0, 0); + placement.setNumColumns(3); + } } - - /** - * Creates the visual representation of a reactor node - */ - public ReactorFigureComponents addReactorFigure(KNode node, ReactorInstance reactorInstance, String text) { - int padding = getBooleanValue(LinguaFrancaSynthesis.SHOW_HYPERLINKS) ? 8 : 6; - - Function1 style = r -> { - _kRenderingExtensions.setLineWidth(r, 1); - _kRenderingExtensions.setForeground(r, Colors.GRAY); - _kRenderingExtensions.setBackground(r, Colors.GRAY_95); - return _linguaFrancaStyleExtensions.boldLineSelectionStyle(r); + return figure; + } + + /** Creates the visual representation of a reactor node */ + public ReactorFigureComponents addReactorFigure( + KNode node, ReactorInstance reactorInstance, String text) { + int padding = getBooleanValue(LinguaFrancaSynthesis.SHOW_HYPERLINKS) ? 8 : 6; + + Function1 style = + r -> { + _kRenderingExtensions.setLineWidth(r, 1); + _kRenderingExtensions.setForeground(r, Colors.GRAY); + _kRenderingExtensions.setBackground(r, Colors.GRAY_95); + return _linguaFrancaStyleExtensions.boldLineSelectionStyle(r); }; - KRoundedRectangle figure = _kRenderingExtensions.addRoundedRectangle(node, 8, 8, 1); - _kContainerRenderingExtensions.setGridPlacement(figure, 1); - style.apply(figure); - figure.setProperty(REACTOR_CONTENT_CONTAINER, true); - - // minimal node size is necessary if no text will be added - List minSize = List.of(2 * figure.getCornerWidth(), 2 * figure.getCornerHeight()); - _kNodeExtensions.setMinimalNodeSize(node, minSize.get(0), minSize.get(1)); - - // Add parent container - KRectangle parentContainer = _kContainerRenderingExtensions.addRectangle(figure); - _kRenderingExtensions.setInvisible(parentContainer, true); - setGridPlacementDataFromPointToPoint(parentContainer, - LEFT, padding, 0, - TOP, padding, 0, - RIGHT, padding, 0, BOTTOM, - _utilityExtensions.hasContent(reactorInstance) ? 4 : padding, 0 - ); - - // Add centered child container - KRectangle childContainer = _kContainerRenderingExtensions.addRectangle(parentContainer); - _kRenderingExtensions.setInvisible(childContainer, true); - _kRenderingExtensions.setPointPlacementData(childContainer, - _kRenderingExtensions.LEFT, 0, 0.5f, - _kRenderingExtensions.TOP, 0, 0.5f, - _kRenderingExtensions.H_CENTRAL, _kRenderingExtensions.V_CENTRAL, 0, - 0, 0, 0); - KGridPlacement placement = _kContainerRenderingExtensions.setGridPlacement(childContainer, 1); - - KText childText = _kContainerRenderingExtensions.addText(childContainer, text); - DiagramSyntheses.suppressSelectability(childText); - _linguaFrancaStyleExtensions.underlineSelectionStyle(childText); - - if (!_utilityExtensions.isRoot(reactorInstance) && - reactorInstance.getDefinition().getHost() != null) { - KRendering cloudUploadIcon = _linguaFrancaStyleExtensions.addCloudUploadIcon(childContainer); - setGridPlacementDataFromPointToPoint(cloudUploadIcon, - LEFT, 3, 0, TOP, 0, 0, - RIGHT, 0, 0, BOTTOM, 0, 0 - ); - placement.setNumColumns(2); - - if (getBooleanValue(LinguaFrancaSynthesis.SHOW_REACTOR_HOST)) { - KText reactorHostText = _kContainerRenderingExtensions.addText(childContainer, - ASTUtils.toOriginalText(reactorInstance.getDefinition().getHost())); - DiagramSyntheses.suppressSelectability(reactorHostText); - _linguaFrancaStyleExtensions.underlineSelectionStyle(reactorHostText); - setGridPlacementDataFromPointToPoint(reactorHostText, - LEFT, 3, 0, TOP, 0, 0, - RIGHT, 0, 0, BOTTOM, 0, 0 - ); - placement.setNumColumns(3); - } - } - - if (reactorInstance.isBank()) { - List bank = new ArrayList<>(); - KContainerRendering container = _kRenderingExtensions.addInvisibleContainerRendering(node); - // TODO handle unresolved width - KRoundedRectangle banks; - banks = _kContainerRenderingExtensions.addRoundedRectangle(container, 8, 8, 1); - style.apply(banks); - setGridPlacementDataFromPointToPoint(banks, - LEFT, BANK_FIGURE_X_OFFSET_SUM, 0, TOP, BANK_FIGURE_Y_OFFSET_SUM, 0, - RIGHT, 0, 0, BOTTOM, 0, 0 - ); - if (reactorInstance.getWidth() == 3) { - banks = _kContainerRenderingExtensions.addRoundedRectangle(container, 8, 8, 1); - style.apply(banks); - setGridPlacementDataFromPointToPoint(banks, - LEFT, BANK_FIGURE_X_OFFSET_SUM / 2, 0, TOP, BANK_FIGURE_Y_OFFSET_SUM / 2, 0, - RIGHT, BANK_FIGURE_X_OFFSET_SUM / 2, 0, BOTTOM, BANK_FIGURE_Y_OFFSET_SUM / 2, 0 - ); - } else if (reactorInstance.getWidth() != 2 && reactorInstance.getWidth() != 3) { - banks = _kContainerRenderingExtensions.addRoundedRectangle(container, 8, 8, 1); - style.apply(banks); - setGridPlacementDataFromPointToPoint(banks, - LEFT, 2 * BANK_FIGURE_X_OFFSET_SUM / 3, 0, TOP, 2 * BANK_FIGURE_Y_OFFSET_SUM / 3, 0, - RIGHT, BANK_FIGURE_X_OFFSET_SUM / 3, 0, BOTTOM, BANK_FIGURE_Y_OFFSET_SUM / 3, 0 - ); - - banks = _kContainerRenderingExtensions.addRoundedRectangle(container, 8, 8, 1); - style.apply(banks); - setGridPlacementDataFromPointToPoint(banks, - LEFT, BANK_FIGURE_X_OFFSET_SUM / 3, 0, TOP, BANK_FIGURE_Y_OFFSET_SUM / 3, 0, - RIGHT, 2 * BANK_FIGURE_X_OFFSET_SUM / 3, 0, BOTTOM, 2 * BANK_FIGURE_Y_OFFSET_SUM / 3, 0 - ); - } - - container.getChildren().add(figure); - setGridPlacementDataFromPointToPoint(figure, - LEFT, 0, 0, TOP, 0, 0, - RIGHT, BANK_FIGURE_X_OFFSET_SUM, 0, BOTTOM, BANK_FIGURE_Y_OFFSET_SUM, 0 - ); - bank.addAll(container.getChildren()); - - KRectangle widthLabelContainer = _kContainerRenderingExtensions.addRectangle(container); - _kRenderingExtensions.setInvisible(widthLabelContainer, true); - setGridPlacementDataFromPointToPoint(widthLabelContainer, - LEFT, 12, 0, BOTTOM, 9, 0, - RIGHT, 6, 0, BOTTOM, 0.5f, 0 - ); - // Handle unresolved width. - String widthLabel = reactorInstance.getWidth() >= 0 ? Integer.toString(reactorInstance.getWidth()) : "?"; - KText widthLabelText = _kContainerRenderingExtensions.addText(widthLabelContainer, widthLabel); - _kRenderingExtensions.setHorizontalAlignment(widthLabelText, HorizontalAlignment.LEFT); - _kRenderingExtensions.setVerticalAlignment(widthLabelText, VerticalAlignment.BOTTOM); - _kRenderingExtensions.setFontSize(widthLabelText, 6); - _linguaFrancaStyleExtensions.noSelectionStyle(widthLabelText); - associateWith(widthLabelText, reactorInstance.getDefinition().getWidthSpec()); - return new ReactorFigureComponents(container, figure, bank); - } else { - return new ReactorFigureComponents(figure, figure, List.of(figure)); - } + KRoundedRectangle figure = _kRenderingExtensions.addRoundedRectangle(node, 8, 8, 1); + _kContainerRenderingExtensions.setGridPlacement(figure, 1); + style.apply(figure); + figure.setProperty(REACTOR_CONTENT_CONTAINER, true); + + // minimal node size is necessary if no text will be added + List minSize = List.of(2 * figure.getCornerWidth(), 2 * figure.getCornerHeight()); + _kNodeExtensions.setMinimalNodeSize(node, minSize.get(0), minSize.get(1)); + + // Add parent container + KRectangle parentContainer = _kContainerRenderingExtensions.addRectangle(figure); + _kRenderingExtensions.setInvisible(parentContainer, true); + setGridPlacementDataFromPointToPoint( + parentContainer, + LEFT, + padding, + 0, + TOP, + padding, + 0, + RIGHT, + padding, + 0, + BOTTOM, + _utilityExtensions.hasContent(reactorInstance) ? 4 : padding, + 0); + + // Add centered child container + KRectangle childContainer = _kContainerRenderingExtensions.addRectangle(parentContainer); + _kRenderingExtensions.setInvisible(childContainer, true); + _kRenderingExtensions.setPointPlacementData( + childContainer, + _kRenderingExtensions.LEFT, + 0, + 0.5f, + _kRenderingExtensions.TOP, + 0, + 0.5f, + _kRenderingExtensions.H_CENTRAL, + _kRenderingExtensions.V_CENTRAL, + 0, + 0, + 0, + 0); + KGridPlacement placement = _kContainerRenderingExtensions.setGridPlacement(childContainer, 1); + + KText childText = _kContainerRenderingExtensions.addText(childContainer, text); + DiagramSyntheses.suppressSelectability(childText); + _linguaFrancaStyleExtensions.underlineSelectionStyle(childText); + + if (!_utilityExtensions.isRoot(reactorInstance) + && reactorInstance.getDefinition().getHost() != null) { + KRendering cloudUploadIcon = _linguaFrancaStyleExtensions.addCloudUploadIcon(childContainer); + setGridPlacementDataFromPointToPoint( + cloudUploadIcon, LEFT, 3, 0, TOP, 0, 0, RIGHT, 0, 0, BOTTOM, 0, 0); + placement.setNumColumns(2); + + if (getBooleanValue(LinguaFrancaSynthesis.SHOW_REACTOR_HOST)) { + KText reactorHostText = + _kContainerRenderingExtensions.addText( + childContainer, ASTUtils.toOriginalText(reactorInstance.getDefinition().getHost())); + DiagramSyntheses.suppressSelectability(reactorHostText); + _linguaFrancaStyleExtensions.underlineSelectionStyle(reactorHostText); + setGridPlacementDataFromPointToPoint( + reactorHostText, LEFT, 3, 0, TOP, 0, 0, RIGHT, 0, 0, BOTTOM, 0, 0); + placement.setNumColumns(3); + } } - /** - * Creates the visual representation of a reaction node - */ - public KPolygon addReactionFigure(KNode node, ReactionInstance reaction) { - int minHeight = 22; - int minWidth = 45; - ReactorInstance reactor = reaction.getParent(); - _kNodeExtensions.setMinimalNodeSize(node, minWidth, minHeight); - - // Create base shape - KPolygon baseShape = _kRenderingExtensions.addPolygon(node); - associateWith(baseShape, reaction); - _kRenderingExtensions.setLineWidth(baseShape, 1); - _kRenderingExtensions.setForeground(baseShape, Colors.GRAY_45); - _kRenderingExtensions.setBackground(baseShape, Colors.GRAY_65); - _linguaFrancaStyleExtensions.boldLineSelectionStyle(baseShape); - baseShape.getPoints().addAll( + if (reactorInstance.isBank()) { + List bank = new ArrayList<>(); + KContainerRendering container = _kRenderingExtensions.addInvisibleContainerRendering(node); + // TODO handle unresolved width + KRoundedRectangle banks; + banks = _kContainerRenderingExtensions.addRoundedRectangle(container, 8, 8, 1); + style.apply(banks); + setGridPlacementDataFromPointToPoint( + banks, + LEFT, + BANK_FIGURE_X_OFFSET_SUM, + 0, + TOP, + BANK_FIGURE_Y_OFFSET_SUM, + 0, + RIGHT, + 0, + 0, + BOTTOM, + 0, + 0); + if (reactorInstance.getWidth() == 3) { + banks = _kContainerRenderingExtensions.addRoundedRectangle(container, 8, 8, 1); + style.apply(banks); + setGridPlacementDataFromPointToPoint( + banks, + LEFT, + BANK_FIGURE_X_OFFSET_SUM / 2, + 0, + TOP, + BANK_FIGURE_Y_OFFSET_SUM / 2, + 0, + RIGHT, + BANK_FIGURE_X_OFFSET_SUM / 2, + 0, + BOTTOM, + BANK_FIGURE_Y_OFFSET_SUM / 2, + 0); + } else if (reactorInstance.getWidth() != 2 && reactorInstance.getWidth() != 3) { + banks = _kContainerRenderingExtensions.addRoundedRectangle(container, 8, 8, 1); + style.apply(banks); + setGridPlacementDataFromPointToPoint( + banks, + LEFT, + 2 * BANK_FIGURE_X_OFFSET_SUM / 3, + 0, + TOP, + 2 * BANK_FIGURE_Y_OFFSET_SUM / 3, + 0, + RIGHT, + BANK_FIGURE_X_OFFSET_SUM / 3, + 0, + BOTTOM, + BANK_FIGURE_Y_OFFSET_SUM / 3, + 0); + + banks = _kContainerRenderingExtensions.addRoundedRectangle(container, 8, 8, 1); + style.apply(banks); + setGridPlacementDataFromPointToPoint( + banks, + LEFT, + BANK_FIGURE_X_OFFSET_SUM / 3, + 0, + TOP, + BANK_FIGURE_Y_OFFSET_SUM / 3, + 0, + RIGHT, + 2 * BANK_FIGURE_X_OFFSET_SUM / 3, + 0, + BOTTOM, + 2 * BANK_FIGURE_Y_OFFSET_SUM / 3, + 0); + } + + container.getChildren().add(figure); + setGridPlacementDataFromPointToPoint( + figure, + LEFT, + 0, + 0, + TOP, + 0, + 0, + RIGHT, + BANK_FIGURE_X_OFFSET_SUM, + 0, + BOTTOM, + BANK_FIGURE_Y_OFFSET_SUM, + 0); + bank.addAll(container.getChildren()); + + KRectangle widthLabelContainer = _kContainerRenderingExtensions.addRectangle(container); + _kRenderingExtensions.setInvisible(widthLabelContainer, true); + setGridPlacementDataFromPointToPoint( + widthLabelContainer, LEFT, 12, 0, BOTTOM, 9, 0, RIGHT, 6, 0, BOTTOM, 0.5f, 0); + // Handle unresolved width. + String widthLabel = + reactorInstance.getWidth() >= 0 ? Integer.toString(reactorInstance.getWidth()) : "?"; + KText widthLabelText = + _kContainerRenderingExtensions.addText(widthLabelContainer, widthLabel); + _kRenderingExtensions.setHorizontalAlignment(widthLabelText, HorizontalAlignment.LEFT); + _kRenderingExtensions.setVerticalAlignment(widthLabelText, VerticalAlignment.BOTTOM); + _kRenderingExtensions.setFontSize(widthLabelText, 6); + _linguaFrancaStyleExtensions.noSelectionStyle(widthLabelText); + associateWith(widthLabelText, reactorInstance.getDefinition().getWidthSpec()); + return new ReactorFigureComponents(container, figure, bank); + } else { + return new ReactorFigureComponents(figure, figure, List.of(figure)); + } + } + + /** Creates the visual representation of a reaction node */ + public KPolygon addReactionFigure(KNode node, ReactionInstance reaction) { + int minHeight = 22; + int minWidth = 45; + ReactorInstance reactor = reaction.getParent(); + _kNodeExtensions.setMinimalNodeSize(node, minWidth, minHeight); + + // Create base shape + KPolygon baseShape = _kRenderingExtensions.addPolygon(node); + associateWith(baseShape, reaction); + _kRenderingExtensions.setLineWidth(baseShape, 1); + _kRenderingExtensions.setForeground(baseShape, Colors.GRAY_45); + _kRenderingExtensions.setBackground(baseShape, Colors.GRAY_65); + _linguaFrancaStyleExtensions.boldLineSelectionStyle(baseShape); + baseShape + .getPoints() + .addAll( List.of( _kRenderingExtensions.createKPosition(LEFT, 0, 0, TOP, 0, 0), _kRenderingExtensions.createKPosition(RIGHT, REACTION_POINTINESS, 0, TOP, 0, 0), _kRenderingExtensions.createKPosition(RIGHT, 0, 0, TOP, 0, 0.5f), _kRenderingExtensions.createKPosition(RIGHT, REACTION_POINTINESS, 0, BOTTOM, 0, 0), _kRenderingExtensions.createKPosition(LEFT, 0, 0, BOTTOM, 0, 0), - _kRenderingExtensions.createKPosition(LEFT, REACTION_POINTINESS, 0, BOTTOM, 0, 0.5f) - ) - ); - - KRectangle contentContainer = _kContainerRenderingExtensions.addRectangle(baseShape); - associateWith(contentContainer, reaction); - _kRenderingExtensions.setInvisible(contentContainer, true); - _kRenderingExtensions.setPointPlacementData(contentContainer, - _kRenderingExtensions.LEFT, REACTION_POINTINESS, 0, - _kRenderingExtensions.TOP, 0, 0, - _kRenderingExtensions.H_LEFT, _kRenderingExtensions.V_TOP, REACTION_POINTINESS, - 0, minWidth - REACTION_POINTINESS * 2, minHeight); - _kContainerRenderingExtensions.setGridPlacement(contentContainer, 1); - - if (reactor.reactions.size() > 1) { - KText textToAdd = _kContainerRenderingExtensions.addText(contentContainer, - Integer.toString(reactor.reactions.indexOf(reaction) + 1)); - _kRenderingExtensions.setFontBold(textToAdd, true); - _linguaFrancaStyleExtensions.noSelectionStyle(textToAdd); - DiagramSyntheses.suppressSelectability(textToAdd); - } - - // optional reaction level - if (getBooleanValue(LinguaFrancaSynthesis.SHOW_REACTION_LEVEL)) { - // Force calculation of levels for reactions. This calculation - // will only be done once. Note that if this fails due to a causality loop, - // then some reactions will have level -1. - try { - String levels = IterableExtensions.join(reaction.getLevels(), ", "); - KText levelsText = _kContainerRenderingExtensions.addText(contentContainer, ("level: " + levels)); - _kRenderingExtensions.setFontBold(levelsText, false); - _linguaFrancaStyleExtensions.noSelectionStyle(levelsText); - DiagramSyntheses.suppressSelectability(levelsText); - } catch (Exception ex) { - // If the graph has cycles, the above fails. Continue without showing levels. - } - } - - // optional code content - boolean hasCode = getBooleanValue(LinguaFrancaSynthesis.SHOW_REACTION_CODE) && - !StringExtensions.isNullOrEmpty(reaction.getDefinition().getCode().getBody()); - if (hasCode) { - KText hasCodeText = _kContainerRenderingExtensions.addText(contentContainer, - _utilityExtensions.trimCode(reaction.getDefinition().getCode())); - associateWith(hasCodeText, reaction); - _kRenderingExtensions.setFontSize(hasCodeText, 6); - _kRenderingExtensions.setFontName(hasCodeText, KlighdConstants.DEFAULT_MONOSPACE_FONT_NAME); - _linguaFrancaStyleExtensions.noSelectionStyle(hasCodeText); - _kRenderingExtensions.setHorizontalAlignment(hasCodeText, HorizontalAlignment.LEFT); - _kRenderingExtensions.setVerticalAlignment(hasCodeText, VerticalAlignment.TOP); - setGridPlacementDataFromPointToPoint(hasCodeText, - _kRenderingExtensions.LEFT, 5, 0, - _kRenderingExtensions.TOP, 5, 0, - _kRenderingExtensions.RIGHT, 5, 0, - _kRenderingExtensions.BOTTOM, 5, 0 - ); - } - - if (reaction.declaredDeadline != null) { - boolean hasDeadlineCode = getBooleanValue(LinguaFrancaSynthesis.SHOW_REACTION_CODE) && - !StringExtensions.isNullOrEmpty(reaction.getDefinition().getDeadline().getCode().getBody()); - if (hasCode || hasDeadlineCode) { - KPolyline line = _kContainerRenderingExtensions.addHorizontalLine(contentContainer, 0); - setGridPlacementDataFromPointToPoint(line, - _kRenderingExtensions.LEFT, 5, 0, - _kRenderingExtensions.TOP, 3, 0, - _kRenderingExtensions.RIGHT, 5, 0, - _kRenderingExtensions.BOTTOM, 6, 0 - ); - } - - // delay with stopwatch - KRectangle labelContainer = _kContainerRenderingExtensions.addRectangle(contentContainer); - _kRenderingExtensions.setInvisible(labelContainer, true); - KRendering placement = setGridPlacementDataFromPointToPoint(labelContainer, - _kRenderingExtensions.LEFT, hasDeadlineCode ? 0 : -REACTION_POINTINESS * 0.5f, 0, - _kRenderingExtensions.TOP, 0, reactor.reactions.size() > 1 || hasCode || hasDeadlineCode ? 0 : 0.5f, - _kRenderingExtensions.RIGHT, 0, 0, - _kRenderingExtensions.BOTTOM, 0, 0 - ); - _kRenderingExtensions.setHorizontalAlignment(placement, HorizontalAlignment.LEFT); - - KRectangle stopWatchFigure = addStopwatchFigure(labelContainer); - _kRenderingExtensions.setLeftTopAlignedPointPlacementData(stopWatchFigure, 0, 0, 0, 0); - - KText stopWatchText = _kContainerRenderingExtensions.addText(labelContainer, - reaction.declaredDeadline.maxDelay.toString()); - associateWith(stopWatchText, reaction.getDefinition().getDeadline().getDelay()); - _kRenderingExtensions.setForeground(stopWatchText, Colors.BROWN); - _kRenderingExtensions.setFontBold(stopWatchText, true); - _kRenderingExtensions.setFontSize(stopWatchText, 7); - _linguaFrancaStyleExtensions.underlineSelectionStyle(stopWatchText); - _kRenderingExtensions.setLeftTopAlignedPointPlacementData(stopWatchText, 15, 0, 0, 0); - - // optional code content - if (hasDeadlineCode) { - KText contentContainerText = _kContainerRenderingExtensions.addText(contentContainer, - _utilityExtensions.trimCode(reaction.getDefinition().getDeadline().getCode())); - associateWith(contentContainerText, reaction.declaredDeadline); - _kRenderingExtensions.setForeground(contentContainerText, Colors.BROWN); - _kRenderingExtensions.setFontSize(contentContainerText, 6); - _kRenderingExtensions.setFontName(contentContainerText, KlighdConstants.DEFAULT_MONOSPACE_FONT_NAME); - setGridPlacementDataFromPointToPoint(contentContainerText, - _kRenderingExtensions.LEFT, 5, 0, - _kRenderingExtensions.TOP, 0, 0, - _kRenderingExtensions.RIGHT, 5, 0, - _kRenderingExtensions.BOTTOM, 5, 0 - ); - _kRenderingExtensions.setHorizontalAlignment(contentContainerText, HorizontalAlignment.LEFT); - _linguaFrancaStyleExtensions.noSelectionStyle(contentContainerText); - } - } - - return baseShape; + _kRenderingExtensions.createKPosition( + LEFT, REACTION_POINTINESS, 0, BOTTOM, 0, 0.5f))); + + KRectangle contentContainer = _kContainerRenderingExtensions.addRectangle(baseShape); + associateWith(contentContainer, reaction); + _kRenderingExtensions.setInvisible(contentContainer, true); + _kRenderingExtensions.setPointPlacementData( + contentContainer, + _kRenderingExtensions.LEFT, + REACTION_POINTINESS, + 0, + _kRenderingExtensions.TOP, + 0, + 0, + _kRenderingExtensions.H_LEFT, + _kRenderingExtensions.V_TOP, + REACTION_POINTINESS, + 0, + minWidth - REACTION_POINTINESS * 2, + minHeight); + _kContainerRenderingExtensions.setGridPlacement(contentContainer, 1); + + if (reactor.reactions.size() > 1) { + KText textToAdd = + _kContainerRenderingExtensions.addText( + contentContainer, Integer.toString(reactor.reactions.indexOf(reaction) + 1)); + _kRenderingExtensions.setFontBold(textToAdd, true); + _linguaFrancaStyleExtensions.noSelectionStyle(textToAdd); + DiagramSyntheses.suppressSelectability(textToAdd); } - /** - * Stopwatch figure for deadlines. - */ - public KRectangle addStopwatchFigure(KContainerRendering parent) { - final int size = 12; - KRectangle container = _kContainerRenderingExtensions.addRectangle(parent); - _kRenderingExtensions.setInvisible(container, true); - _kRenderingExtensions.setPointPlacementData(container, - _kRenderingExtensions.LEFT, 0, 0, - _kRenderingExtensions.TOP, 0, 0, - _kRenderingExtensions.H_LEFT, _kRenderingExtensions.V_TOP, 0, - 0, size, size); - - KPolyline polyline = _kContainerRenderingExtensions.addPolyline(container, 2, - List.of( - _kRenderingExtensions.createKPosition(LEFT, 3, 0.5f, TOP, (-2), 0), - _kRenderingExtensions.createKPosition(LEFT, (-3), 0.5f, TOP, (-2), 0) - ) - ); - _kRenderingExtensions.setForeground(polyline, Colors.BROWN); + // optional reaction level + if (getBooleanValue(LinguaFrancaSynthesis.SHOW_REACTION_LEVEL)) { + // Force calculation of levels for reactions. This calculation + // will only be done once. Note that if this fails due to a causality loop, + // then some reactions will have level -1. + try { + String levels = IterableExtensions.join(reaction.getLevels(), ", "); + KText levelsText = + _kContainerRenderingExtensions.addText(contentContainer, ("level: " + levels)); + _kRenderingExtensions.setFontBold(levelsText, false); + _linguaFrancaStyleExtensions.noSelectionStyle(levelsText); + DiagramSyntheses.suppressSelectability(levelsText); + } catch (Exception ex) { + // If the graph has cycles, the above fails. Continue without showing levels. + } + } - polyline = _kContainerRenderingExtensions.addPolyline(container, 2, - List.of( - _kRenderingExtensions.createKPosition(LEFT, 0, 0.5f, TOP, (-2), 0), - _kRenderingExtensions.createKPosition(LEFT, 0, 0.5f, TOP, 1, 0) - ) - ); - _kRenderingExtensions.setForeground(polyline, Colors.BROWN); - - KEllipse body = _kContainerRenderingExtensions.addEllipse(container); - _kRenderingExtensions.setLineWidth(body, 1); - _kRenderingExtensions.setForeground(body, Colors.BROWN); - _kRenderingExtensions.setPointPlacementData(body, - _kRenderingExtensions.LEFT, 0, 0, - _kRenderingExtensions.TOP, 0, 0, - _kRenderingExtensions.H_LEFT, _kRenderingExtensions.V_TOP, 0, - 0, size, size); - _linguaFrancaStyleExtensions.noSelectionStyle(body); - - KArc arc = _kContainerRenderingExtensions.addArc(body); - arc.setStartAngle((-20)); - arc.setArcAngle(110); - arc.setArcType(Arc.PIE); - _kRenderingExtensions.setLineWidth(arc, 0); - _kRenderingExtensions.setBackground(arc, Colors.BROWN); - _kRenderingExtensions.setPointPlacementData(arc, - _kRenderingExtensions.LEFT, 2, 0, - _kRenderingExtensions.TOP, 2, 0, - _kRenderingExtensions.H_LEFT, _kRenderingExtensions.V_TOP, 2, - 2, size - 4, size - 4); - _linguaFrancaStyleExtensions.noSelectionStyle(arc); - - return container; + // optional code content + boolean hasCode = + getBooleanValue(LinguaFrancaSynthesis.SHOW_REACTION_CODE) + && !StringExtensions.isNullOrEmpty(reaction.getDefinition().getCode().getBody()); + if (hasCode) { + KText hasCodeText = + _kContainerRenderingExtensions.addText( + contentContainer, _utilityExtensions.trimCode(reaction.getDefinition().getCode())); + associateWith(hasCodeText, reaction); + _kRenderingExtensions.setFontSize(hasCodeText, 6); + _kRenderingExtensions.setFontName(hasCodeText, KlighdConstants.DEFAULT_MONOSPACE_FONT_NAME); + _linguaFrancaStyleExtensions.noSelectionStyle(hasCodeText); + _kRenderingExtensions.setHorizontalAlignment(hasCodeText, HorizontalAlignment.LEFT); + _kRenderingExtensions.setVerticalAlignment(hasCodeText, VerticalAlignment.TOP); + setGridPlacementDataFromPointToPoint( + hasCodeText, + _kRenderingExtensions.LEFT, + 5, + 0, + _kRenderingExtensions.TOP, + 5, + 0, + _kRenderingExtensions.RIGHT, + 5, + 0, + _kRenderingExtensions.BOTTOM, + 5, + 0); } - /** - * Creates the visual representation of a timer node - */ - public KEllipse addTimerFigure(KNode node, TimerInstance timer) { - _kNodeExtensions.setMinimalNodeSize(node, 30, 30); + if (reaction.declaredDeadline != null) { + boolean hasDeadlineCode = + getBooleanValue(LinguaFrancaSynthesis.SHOW_REACTION_CODE) + && !StringExtensions.isNullOrEmpty( + reaction.getDefinition().getDeadline().getCode().getBody()); + if (hasCode || hasDeadlineCode) { + KPolyline line = _kContainerRenderingExtensions.addHorizontalLine(contentContainer, 0); + setGridPlacementDataFromPointToPoint( + line, + _kRenderingExtensions.LEFT, + 5, + 0, + _kRenderingExtensions.TOP, + 3, + 0, + _kRenderingExtensions.RIGHT, + 5, + 0, + _kRenderingExtensions.BOTTOM, + 6, + 0); + } + + // delay with stopwatch + KRectangle labelContainer = _kContainerRenderingExtensions.addRectangle(contentContainer); + _kRenderingExtensions.setInvisible(labelContainer, true); + KRendering placement = + setGridPlacementDataFromPointToPoint( + labelContainer, + _kRenderingExtensions.LEFT, + hasDeadlineCode ? 0 : -REACTION_POINTINESS * 0.5f, + 0, + _kRenderingExtensions.TOP, + 0, + reactor.reactions.size() > 1 || hasCode || hasDeadlineCode ? 0 : 0.5f, + _kRenderingExtensions.RIGHT, + 0, + 0, + _kRenderingExtensions.BOTTOM, + 0, + 0); + _kRenderingExtensions.setHorizontalAlignment(placement, HorizontalAlignment.LEFT); + + KRectangle stopWatchFigure = addStopwatchFigure(labelContainer); + _kRenderingExtensions.setLeftTopAlignedPointPlacementData(stopWatchFigure, 0, 0, 0, 0); + + KText stopWatchText = + _kContainerRenderingExtensions.addText( + labelContainer, reaction.declaredDeadline.maxDelay.toString()); + associateWith(stopWatchText, reaction.getDefinition().getDeadline().getDelay()); + _kRenderingExtensions.setForeground(stopWatchText, Colors.BROWN); + _kRenderingExtensions.setFontBold(stopWatchText, true); + _kRenderingExtensions.setFontSize(stopWatchText, 7); + _linguaFrancaStyleExtensions.underlineSelectionStyle(stopWatchText); + _kRenderingExtensions.setLeftTopAlignedPointPlacementData(stopWatchText, 15, 0, 0, 0); + + // optional code content + if (hasDeadlineCode) { + KText contentContainerText = + _kContainerRenderingExtensions.addText( + contentContainer, + _utilityExtensions.trimCode(reaction.getDefinition().getDeadline().getCode())); + associateWith(contentContainerText, reaction.declaredDeadline); + _kRenderingExtensions.setForeground(contentContainerText, Colors.BROWN); + _kRenderingExtensions.setFontSize(contentContainerText, 6); + _kRenderingExtensions.setFontName( + contentContainerText, KlighdConstants.DEFAULT_MONOSPACE_FONT_NAME); + setGridPlacementDataFromPointToPoint( + contentContainerText, + _kRenderingExtensions.LEFT, + 5, + 0, + _kRenderingExtensions.TOP, + 0, + 0, + _kRenderingExtensions.RIGHT, + 5, + 0, + _kRenderingExtensions.BOTTOM, + 5, + 0); + _kRenderingExtensions.setHorizontalAlignment( + contentContainerText, HorizontalAlignment.LEFT); + _linguaFrancaStyleExtensions.noSelectionStyle(contentContainerText); + } + } - KEllipse figure = _kRenderingExtensions.addEllipse(node); - _kRenderingExtensions.setBackground(figure, Colors.GRAY_95); - _linguaFrancaStyleExtensions.noSelectionStyle(figure); - _kRenderingExtensions.setLineWidth(figure, 1); - _linguaFrancaStyleExtensions.boldLineSelectionStyle(figure); + return baseShape; + } + + /** Stopwatch figure for deadlines. */ + public KRectangle addStopwatchFigure(KContainerRendering parent) { + final int size = 12; + KRectangle container = _kContainerRenderingExtensions.addRectangle(parent); + _kRenderingExtensions.setInvisible(container, true); + _kRenderingExtensions.setPointPlacementData( + container, + _kRenderingExtensions.LEFT, + 0, + 0, + _kRenderingExtensions.TOP, + 0, + 0, + _kRenderingExtensions.H_LEFT, + _kRenderingExtensions.V_TOP, + 0, + 0, + size, + size); + + KPolyline polyline = + _kContainerRenderingExtensions.addPolyline( + container, + 2, + List.of( + _kRenderingExtensions.createKPosition(LEFT, 3, 0.5f, TOP, (-2), 0), + _kRenderingExtensions.createKPosition(LEFT, (-3), 0.5f, TOP, (-2), 0))); + _kRenderingExtensions.setForeground(polyline, Colors.BROWN); - List polylinePoints = List.of( + polyline = + _kContainerRenderingExtensions.addPolyline( + container, + 2, + List.of( + _kRenderingExtensions.createKPosition(LEFT, 0, 0.5f, TOP, (-2), 0), + _kRenderingExtensions.createKPosition(LEFT, 0, 0.5f, TOP, 1, 0))); + _kRenderingExtensions.setForeground(polyline, Colors.BROWN); + + KEllipse body = _kContainerRenderingExtensions.addEllipse(container); + _kRenderingExtensions.setLineWidth(body, 1); + _kRenderingExtensions.setForeground(body, Colors.BROWN); + _kRenderingExtensions.setPointPlacementData( + body, + _kRenderingExtensions.LEFT, + 0, + 0, + _kRenderingExtensions.TOP, + 0, + 0, + _kRenderingExtensions.H_LEFT, + _kRenderingExtensions.V_TOP, + 0, + 0, + size, + size); + _linguaFrancaStyleExtensions.noSelectionStyle(body); + + KArc arc = _kContainerRenderingExtensions.addArc(body); + arc.setStartAngle((-20)); + arc.setArcAngle(110); + arc.setArcType(Arc.PIE); + _kRenderingExtensions.setLineWidth(arc, 0); + _kRenderingExtensions.setBackground(arc, Colors.BROWN); + _kRenderingExtensions.setPointPlacementData( + arc, + _kRenderingExtensions.LEFT, + 2, + 0, + _kRenderingExtensions.TOP, + 2, + 0, + _kRenderingExtensions.H_LEFT, + _kRenderingExtensions.V_TOP, + 2, + 2, + size - 4, + size - 4); + _linguaFrancaStyleExtensions.noSelectionStyle(arc); + + return container; + } + + /** Creates the visual representation of a timer node */ + public KEllipse addTimerFigure(KNode node, TimerInstance timer) { + _kNodeExtensions.setMinimalNodeSize(node, 30, 30); + + KEllipse figure = _kRenderingExtensions.addEllipse(node); + _kRenderingExtensions.setBackground(figure, Colors.GRAY_95); + _linguaFrancaStyleExtensions.noSelectionStyle(figure); + _kRenderingExtensions.setLineWidth(figure, 1); + _linguaFrancaStyleExtensions.boldLineSelectionStyle(figure); + + List polylinePoints = + List.of( _kRenderingExtensions.createKPosition(LEFT, 0, 0.5f, TOP, 0, 0.1f), _kRenderingExtensions.createKPosition(LEFT, 0, 0.5f, TOP, 0, 0.5f), - _kRenderingExtensions.createKPosition(LEFT, 0, 0.7f, TOP, 0, 0.7f) - ); - KPolyline polyline = _kContainerRenderingExtensions.addPolyline(figure, 1, polylinePoints); - _linguaFrancaStyleExtensions.boldLineSelectionStyle(polyline); - - List labelParts = new ArrayList<>(); - if (timer.getOffset() != TimerInstance.DEFAULT_OFFSET && timer.getOffset() != null) { - labelParts.add(timer.getOffset().toString()); - } - if (timer.getPeriod() != TimerInstance.DEFAULT_PERIOD && timer.getPeriod() != null) { - if (timer.getOffset() == TimerInstance.DEFAULT_OFFSET) { - labelParts.add(timer.getOffset().toString()); - } - labelParts.add(timer.getPeriod().toString()); - } - if (!labelParts.isEmpty()) { - _kLabelExtensions.addOutsideBottomCenteredNodeLabel(node, - "(" + String.join(", ", labelParts) + ")", 8); - } - return figure; - } + _kRenderingExtensions.createKPosition(LEFT, 0, 0.7f, TOP, 0, 0.7f)); + KPolyline polyline = _kContainerRenderingExtensions.addPolyline(figure, 1, polylinePoints); + _linguaFrancaStyleExtensions.boldLineSelectionStyle(polyline); - /** - * Creates the visual representation of a startup trigger. - */ - public KEllipse addStartupFigure(KNode node) { - _kNodeExtensions.setMinimalNodeSize(node, 18, 18); - KEllipse figure = _kRenderingExtensions.addEllipse(node); - _kRenderingExtensions.setLineWidth(figure, 1); - _kRenderingExtensions.setBackground(figure, Colors.WHITE); - _linguaFrancaStyleExtensions.noSelectionStyle(figure); - _linguaFrancaStyleExtensions.boldLineSelectionStyle(figure); - return figure; + List labelParts = new ArrayList<>(); + if (timer.getOffset() != TimerInstance.DEFAULT_OFFSET && timer.getOffset() != null) { + labelParts.add(timer.getOffset().toString()); } - - /** - * Creates the visual representation of a shutdown trigger. - */ - public KPolygon addShutdownFigure(KNode node) { - _kNodeExtensions.setMinimalNodeSize(node, 18, 18); - KPolygon figure = _kRenderingExtensions.addPolygon(node); - _kRenderingExtensions.setLineWidth(figure, 1); - _kRenderingExtensions.setBackground(figure, Colors.WHITE); - _linguaFrancaStyleExtensions.noSelectionStyle(figure); - _linguaFrancaStyleExtensions.boldLineSelectionStyle(figure); - - List pointsToAdd = List.of( + if (timer.getPeriod() != TimerInstance.DEFAULT_PERIOD && timer.getPeriod() != null) { + if (timer.getOffset() == TimerInstance.DEFAULT_OFFSET) { + labelParts.add(timer.getOffset().toString()); + } + labelParts.add(timer.getPeriod().toString()); + } + if (!labelParts.isEmpty()) { + _kLabelExtensions.addOutsideBottomCenteredNodeLabel( + node, "(" + String.join(", ", labelParts) + ")", 8); + } + return figure; + } + + /** Creates the visual representation of a startup trigger. */ + public KEllipse addStartupFigure(KNode node) { + _kNodeExtensions.setMinimalNodeSize(node, 18, 18); + KEllipse figure = _kRenderingExtensions.addEllipse(node); + _kRenderingExtensions.setLineWidth(figure, 1); + _kRenderingExtensions.setBackground(figure, Colors.WHITE); + _linguaFrancaStyleExtensions.noSelectionStyle(figure); + _linguaFrancaStyleExtensions.boldLineSelectionStyle(figure); + return figure; + } + + /** Creates the visual representation of a shutdown trigger. */ + public KPolygon addShutdownFigure(KNode node) { + _kNodeExtensions.setMinimalNodeSize(node, 18, 18); + KPolygon figure = _kRenderingExtensions.addPolygon(node); + _kRenderingExtensions.setLineWidth(figure, 1); + _kRenderingExtensions.setBackground(figure, Colors.WHITE); + _linguaFrancaStyleExtensions.noSelectionStyle(figure); + _linguaFrancaStyleExtensions.boldLineSelectionStyle(figure); + + List pointsToAdd = + List.of( _kRenderingExtensions.createKPosition(LEFT, 0, 0.5f, TOP, 0, 0), _kRenderingExtensions.createKPosition(RIGHT, 0, 0, TOP, 0, 0.5f), _kRenderingExtensions.createKPosition(RIGHT, 0, 0.5f, BOTTOM, 0, 0), - _kRenderingExtensions.createKPosition(LEFT, 0, 0, BOTTOM, 0, 0.5f) - ); - - figure.getPoints().addAll(pointsToAdd); - return figure; - } - - /** - * Creates the visual representation of a shutdown trigger. - */ - public KEllipse addResetFigure(KNode node) { - _kNodeExtensions.setMinimalNodeSize(node, 18, 18); - KEllipse figure = _kRenderingExtensions.addEllipse(node); - _kRenderingExtensions.setLineWidth(figure, 1); - _kRenderingExtensions.setBackground(figure, Colors.WHITE); - _linguaFrancaStyleExtensions.noSelectionStyle(figure); - _linguaFrancaStyleExtensions.boldLineSelectionStyle(figure); - - KEllipse resetCircle = _kContainerRenderingExtensions.addEllipse(figure); - _kRenderingExtensions.setSurroundingSpace(resetCircle, 3f, 0); - _kRenderingExtensions.setLineWidth(resetCircle, 1.5f); - _kRenderingExtensions.setBackground(resetCircle, Colors.WHITE); - _linguaFrancaStyleExtensions.noSelectionStyle(resetCircle); - - var resetCycleGap = _kContainerRenderingExtensions.addPolygon(resetCircle); - resetCycleGap.getPoints().add(_kRenderingExtensions.createKPosition(PositionReferenceX.LEFT, 0, 0, PositionReferenceY.TOP, 0.0f, 0)); - resetCycleGap.getPoints().add(_kRenderingExtensions.createKPosition(PositionReferenceX.RIGHT, 0, 0, PositionReferenceY.TOP, 0.0f, 0)); - resetCycleGap.getPoints().add(_kRenderingExtensions.createKPosition(PositionReferenceX.RIGHT, 1.1f, 0, PositionReferenceY.BOTTOM, 0, 0)); - resetCycleGap.getPoints().add(_kRenderingExtensions.createKPosition(PositionReferenceX.LEFT, 1.1f, 0, PositionReferenceY.BOTTOM, 0, 0)); - _kRenderingExtensions.setLineWidth(resetCycleGap, 0.3f); - _kRenderingExtensions.setForeground(resetCycleGap, Colors.WHITE); - _kRenderingExtensions.setBackground(resetCycleGap, Colors.WHITE); - _linguaFrancaStyleExtensions.noSelectionStyle(resetCycleGap); - _kRenderingExtensions.setPointPlacementData(resetCycleGap, - _kRenderingExtensions.LEFT, -2, 0.5f, - _kRenderingExtensions.TOP, 1.5f, 0, - _kRenderingExtensions.H_LEFT, _kRenderingExtensions.V_CENTRAL, - 0, 0, 4.0f, 3.0f); - - var resetArrow = _kContainerRenderingExtensions.addPolygon(resetCircle); - resetArrow.getPoints().add(_kRenderingExtensions.createKPosition(PositionReferenceX.LEFT, 0, 0, PositionReferenceY.TOP, 0.0f, 0.1f)); - resetArrow.getPoints().add(_kRenderingExtensions.createKPosition(PositionReferenceX.LEFT, 0.0f, 0.3f, PositionReferenceY.BOTTOM, 0, 0)); - resetArrow.getPoints().add(_kRenderingExtensions.createKPosition(PositionReferenceX.RIGHT, 0.0f, 0, PositionReferenceY.TOP, 0.0f, 0.0f)); - _kRenderingExtensions.setLineWidth(resetArrow, 0.3f); - _kRenderingExtensions.setBackground(resetArrow, Colors.BLACK); - _linguaFrancaStyleExtensions.noSelectionStyle(resetArrow); - _kRenderingExtensions.setPointPlacementData(resetArrow, - _kRenderingExtensions.LEFT, 1.0f, 0.5f, - _kRenderingExtensions.TOP, 1.8f, 0, - _kRenderingExtensions.H_LEFT, _kRenderingExtensions.V_CENTRAL, - 0, 0, 3.2f, 3.2f); - - return figure; + _kRenderingExtensions.createKPosition(LEFT, 0, 0, BOTTOM, 0, 0.5f)); + + figure.getPoints().addAll(pointsToAdd); + return figure; + } + + /** Creates the visual representation of a shutdown trigger. */ + public KEllipse addResetFigure(KNode node) { + _kNodeExtensions.setMinimalNodeSize(node, 18, 18); + KEllipse figure = _kRenderingExtensions.addEllipse(node); + _kRenderingExtensions.setLineWidth(figure, 1); + _kRenderingExtensions.setBackground(figure, Colors.WHITE); + _linguaFrancaStyleExtensions.noSelectionStyle(figure); + _linguaFrancaStyleExtensions.boldLineSelectionStyle(figure); + + KEllipse resetCircle = _kContainerRenderingExtensions.addEllipse(figure); + _kRenderingExtensions.setSurroundingSpace(resetCircle, 3f, 0); + _kRenderingExtensions.setLineWidth(resetCircle, 1.5f); + _kRenderingExtensions.setBackground(resetCircle, Colors.WHITE); + _linguaFrancaStyleExtensions.noSelectionStyle(resetCircle); + + var resetCycleGap = _kContainerRenderingExtensions.addPolygon(resetCircle); + resetCycleGap + .getPoints() + .add( + _kRenderingExtensions.createKPosition( + PositionReferenceX.LEFT, 0, 0, PositionReferenceY.TOP, 0.0f, 0)); + resetCycleGap + .getPoints() + .add( + _kRenderingExtensions.createKPosition( + PositionReferenceX.RIGHT, 0, 0, PositionReferenceY.TOP, 0.0f, 0)); + resetCycleGap + .getPoints() + .add( + _kRenderingExtensions.createKPosition( + PositionReferenceX.RIGHT, 1.1f, 0, PositionReferenceY.BOTTOM, 0, 0)); + resetCycleGap + .getPoints() + .add( + _kRenderingExtensions.createKPosition( + PositionReferenceX.LEFT, 1.1f, 0, PositionReferenceY.BOTTOM, 0, 0)); + _kRenderingExtensions.setLineWidth(resetCycleGap, 0.3f); + _kRenderingExtensions.setForeground(resetCycleGap, Colors.WHITE); + _kRenderingExtensions.setBackground(resetCycleGap, Colors.WHITE); + _linguaFrancaStyleExtensions.noSelectionStyle(resetCycleGap); + _kRenderingExtensions.setPointPlacementData( + resetCycleGap, + _kRenderingExtensions.LEFT, + -2, + 0.5f, + _kRenderingExtensions.TOP, + 1.5f, + 0, + _kRenderingExtensions.H_LEFT, + _kRenderingExtensions.V_CENTRAL, + 0, + 0, + 4.0f, + 3.0f); + + var resetArrow = _kContainerRenderingExtensions.addPolygon(resetCircle); + resetArrow + .getPoints() + .add( + _kRenderingExtensions.createKPosition( + PositionReferenceX.LEFT, 0, 0, PositionReferenceY.TOP, 0.0f, 0.1f)); + resetArrow + .getPoints() + .add( + _kRenderingExtensions.createKPosition( + PositionReferenceX.LEFT, 0.0f, 0.3f, PositionReferenceY.BOTTOM, 0, 0)); + resetArrow + .getPoints() + .add( + _kRenderingExtensions.createKPosition( + PositionReferenceX.RIGHT, 0.0f, 0, PositionReferenceY.TOP, 0.0f, 0.0f)); + _kRenderingExtensions.setLineWidth(resetArrow, 0.3f); + _kRenderingExtensions.setBackground(resetArrow, Colors.BLACK); + _linguaFrancaStyleExtensions.noSelectionStyle(resetArrow); + _kRenderingExtensions.setPointPlacementData( + resetArrow, + _kRenderingExtensions.LEFT, + 1.0f, + 0.5f, + _kRenderingExtensions.TOP, + 1.8f, + 0, + _kRenderingExtensions.H_LEFT, + _kRenderingExtensions.V_CENTRAL, + 0, + 0, + 3.2f, + 3.2f); + + return figure; + } + + /** Creates the visual representation of a reactor port. */ + public KPolygon addTrianglePort(KPort port, boolean multiport) { + port.setSize(8, 8); + + // Create triangle port + KPolygon trianglePort = _kRenderingExtensions.addPolygon(port); + + // Set line width and background color according to multiport or not + float lineWidth = multiport ? 2.2f : 1; + _kRenderingExtensions.setLineWidth(trianglePort, lineWidth); + _linguaFrancaStyleExtensions.boldLineSelectionStyle(trianglePort); + Colors background = multiport ? Colors.WHITE : Colors.BLACK; + _kRenderingExtensions.setBackground(trianglePort, background); + + List pointsToAdd; + if (multiport) { + // Compensate for line width by making triangle smaller + // Do not adjust by port size because this will affect port distribution and cause offsets + // between parallel connections + pointsToAdd = + List.of( + _kRenderingExtensions.createKPosition(LEFT, 0, 0, TOP, 0.6f, 0), + _kRenderingExtensions.createKPosition(RIGHT, 1.2f, 0, TOP, 0, 0.5f), + _kRenderingExtensions.createKPosition(LEFT, 0, 0, BOTTOM, 0.6f, 0)); + } else { + pointsToAdd = + List.of( + _kRenderingExtensions.createKPosition(LEFT, 0, 0, TOP, 0, 0), + _kRenderingExtensions.createKPosition(RIGHT, 0, 0, TOP, 0, 0.5f), + _kRenderingExtensions.createKPosition(LEFT, 0, 0, BOTTOM, 0, 0)); } - - /** - * Creates the visual representation of a reactor port. - */ - public KPolygon addTrianglePort(KPort port, boolean multiport) { - port.setSize(8, 8); - - // Create triangle port - KPolygon trianglePort = _kRenderingExtensions.addPolygon(port); - - // Set line width and background color according to multiport or not - float lineWidth = multiport ? 2.2f : 1; - _kRenderingExtensions.setLineWidth(trianglePort, lineWidth); - _linguaFrancaStyleExtensions.boldLineSelectionStyle(trianglePort); - Colors background = multiport ? Colors.WHITE : Colors.BLACK; - _kRenderingExtensions.setBackground(trianglePort, background); - - List pointsToAdd; - if (multiport) { - // Compensate for line width by making triangle smaller - // Do not adjust by port size because this will affect port distribution and cause offsets between parallel connections - pointsToAdd = List.of( - _kRenderingExtensions.createKPosition(LEFT, 0, 0, TOP, 0.6f, 0), - _kRenderingExtensions.createKPosition(RIGHT, 1.2f, 0, TOP, 0, 0.5f), - _kRenderingExtensions.createKPosition(LEFT, 0, 0, BOTTOM, 0.6f, 0) - ); - } else { - pointsToAdd = List.of( - _kRenderingExtensions.createKPosition(LEFT, 0, 0, TOP, 0, 0), - _kRenderingExtensions.createKPosition(RIGHT, 0, 0, TOP, 0, 0.5f), - _kRenderingExtensions.createKPosition(LEFT, 0, 0, BOTTOM, 0, 0) - ); - } - trianglePort.getPoints().addAll(pointsToAdd); - return trianglePort; - } - - /** - * Added a text as collapse expand button. - */ - public KText addTextButton(KContainerRendering container, String text) { - KText textToAdd = _kContainerRenderingExtensions.addText(container, text); - _kRenderingExtensions.setForeground(textToAdd, Colors.BLUE); - _kRenderingExtensions.setFontSize(textToAdd, 8); - _linguaFrancaStyleExtensions.noSelectionStyle(textToAdd); - return textToAdd; - } - - /** - * Creates the triangular line decorator with text. - */ - public KPolygon addActionDecorator(KPolyline line, String text) { - final float size = 18; - - // Create action decorator - KPolygon actionDecorator = _kContainerRenderingExtensions.addPolygon(line); - _kRenderingExtensions.setBackground(actionDecorator, Colors.WHITE); - List pointsToAdd = List.of( + trianglePort.getPoints().addAll(pointsToAdd); + return trianglePort; + } + + /** Added a text as collapse expand button. */ + public KText addTextButton(KContainerRendering container, String text) { + KText textToAdd = _kContainerRenderingExtensions.addText(container, text); + _kRenderingExtensions.setForeground(textToAdd, Colors.BLUE); + _kRenderingExtensions.setFontSize(textToAdd, 8); + _linguaFrancaStyleExtensions.noSelectionStyle(textToAdd); + return textToAdd; + } + + /** Creates the triangular line decorator with text. */ + public KPolygon addActionDecorator(KPolyline line, String text) { + final float size = 18; + + // Create action decorator + KPolygon actionDecorator = _kContainerRenderingExtensions.addPolygon(line); + _kRenderingExtensions.setBackground(actionDecorator, Colors.WHITE); + List pointsToAdd = + List.of( _kRenderingExtensions.createKPosition(LEFT, 0, 0.5f, TOP, 0, 0), _kRenderingExtensions.createKPosition(RIGHT, 0, 0, BOTTOM, 0, 0), - _kRenderingExtensions.createKPosition(LEFT, 0, 0, BOTTOM, 0, 0) - ); - actionDecorator.getPoints().addAll(pointsToAdd); - - // Set placement data of the action decorator - KDecoratorPlacementData placementData = _kRenderingFactory.createKDecoratorPlacementData(); - placementData.setRelative(0.5f); - placementData.setAbsolute(-size / 2); - placementData.setWidth(size); - placementData.setHeight(size); - placementData.setYOffset(-size * 0.66f); - placementData.setRotateWithLine(true); - actionDecorator.setPlacementData(placementData); - - // Add text to the action decorator - KText textToAdd = _kContainerRenderingExtensions.addText(actionDecorator, text); - _kRenderingExtensions.setFontSize(textToAdd, 8); - _linguaFrancaStyleExtensions.noSelectionStyle(textToAdd); - DiagramSyntheses.suppressSelectability(textToAdd); - _kRenderingExtensions.setPointPlacementData(textToAdd, - _kRenderingExtensions.LEFT, 0, 0.5f, - _kRenderingExtensions.TOP, size * 0.15f, 0.5f, - _kRenderingExtensions.H_CENTRAL, _kRenderingExtensions.V_CENTRAL, 0, - 0, size, size); - - return actionDecorator; - } - - /** - * Creates the triangular action node with text and ports. - */ - public Pair addActionFigureAndPorts(KNode node, String text) { - final float size = 18; - _kNodeExtensions.setMinimalNodeSize(node, size, size); - KPolygon figure = _kRenderingExtensions.addPolygon(node); - _kRenderingExtensions.setBackground(figure, Colors.WHITE); - _linguaFrancaStyleExtensions.boldLineSelectionStyle(figure); - - List pointsToAdd = List.of( + _kRenderingExtensions.createKPosition(LEFT, 0, 0, BOTTOM, 0, 0)); + actionDecorator.getPoints().addAll(pointsToAdd); + + // Set placement data of the action decorator + KDecoratorPlacementData placementData = _kRenderingFactory.createKDecoratorPlacementData(); + placementData.setRelative(0.5f); + placementData.setAbsolute(-size / 2); + placementData.setWidth(size); + placementData.setHeight(size); + placementData.setYOffset(-size * 0.66f); + placementData.setRotateWithLine(true); + actionDecorator.setPlacementData(placementData); + + // Add text to the action decorator + KText textToAdd = _kContainerRenderingExtensions.addText(actionDecorator, text); + _kRenderingExtensions.setFontSize(textToAdd, 8); + _linguaFrancaStyleExtensions.noSelectionStyle(textToAdd); + DiagramSyntheses.suppressSelectability(textToAdd); + _kRenderingExtensions.setPointPlacementData( + textToAdd, + _kRenderingExtensions.LEFT, + 0, + 0.5f, + _kRenderingExtensions.TOP, + size * 0.15f, + 0.5f, + _kRenderingExtensions.H_CENTRAL, + _kRenderingExtensions.V_CENTRAL, + 0, + 0, + size, + size); + + return actionDecorator; + } + + /** Creates the triangular action node with text and ports. */ + public Pair addActionFigureAndPorts(KNode node, String text) { + final float size = 18; + _kNodeExtensions.setMinimalNodeSize(node, size, size); + KPolygon figure = _kRenderingExtensions.addPolygon(node); + _kRenderingExtensions.setBackground(figure, Colors.WHITE); + _linguaFrancaStyleExtensions.boldLineSelectionStyle(figure); + + List pointsToAdd = + List.of( _kRenderingExtensions.createKPosition(LEFT, 0, 0.5f, TOP, 0, 0), _kRenderingExtensions.createKPosition(RIGHT, 0, 0, BOTTOM, 0, 0), - _kRenderingExtensions.createKPosition(LEFT, 0, 0, BOTTOM, 0, 0) - ); - figure.getPoints().addAll(pointsToAdd); - - // Add text to the action figure - KText textToAdd = _kContainerRenderingExtensions.addText(figure, text); - _kRenderingExtensions.setFontSize(textToAdd, 8); - _linguaFrancaStyleExtensions.noSelectionStyle(textToAdd); - DiagramSyntheses.suppressSelectability(textToAdd); - _kRenderingExtensions.setPointPlacementData(textToAdd, - _kRenderingExtensions.LEFT, 0, 0.5f, - _kRenderingExtensions.TOP, (size * 0.15f), 0.5f, - _kRenderingExtensions.H_CENTRAL, - _kRenderingExtensions.V_CENTRAL, 0, 0, size, size); - - // Add input port - KPort in = _kPortExtensions.createPort(); - node.getPorts().add(in); - in.setSize(0, 0); - DiagramSyntheses.setLayoutOption(in, CoreOptions.PORT_SIDE, PortSide.WEST); - DiagramSyntheses.setLayoutOption(in, CoreOptions.PORT_BORDER_OFFSET, -size / ((double) 4)); - - // Add output port - KPort out = _kPortExtensions.createPort(); - node.getPorts().add(out); - DiagramSyntheses.setLayoutOption(out, CoreOptions.PORT_SIDE, PortSide.EAST); - DiagramSyntheses.setLayoutOption(out, CoreOptions.PORT_BORDER_OFFSET, -size / ((double) 4)); - return new Pair(in, out); + _kRenderingExtensions.createKPosition(LEFT, 0, 0, BOTTOM, 0, 0)); + figure.getPoints().addAll(pointsToAdd); + + // Add text to the action figure + KText textToAdd = _kContainerRenderingExtensions.addText(figure, text); + _kRenderingExtensions.setFontSize(textToAdd, 8); + _linguaFrancaStyleExtensions.noSelectionStyle(textToAdd); + DiagramSyntheses.suppressSelectability(textToAdd); + _kRenderingExtensions.setPointPlacementData( + textToAdd, + _kRenderingExtensions.LEFT, + 0, + 0.5f, + _kRenderingExtensions.TOP, + (size * 0.15f), + 0.5f, + _kRenderingExtensions.H_CENTRAL, + _kRenderingExtensions.V_CENTRAL, + 0, + 0, + size, + size); + + // Add input port + KPort in = _kPortExtensions.createPort(); + node.getPorts().add(in); + in.setSize(0, 0); + DiagramSyntheses.setLayoutOption(in, CoreOptions.PORT_SIDE, PortSide.WEST); + DiagramSyntheses.setLayoutOption(in, CoreOptions.PORT_BORDER_OFFSET, -size / ((double) 4)); + + // Add output port + KPort out = _kPortExtensions.createPort(); + node.getPorts().add(out); + DiagramSyntheses.setLayoutOption(out, CoreOptions.PORT_SIDE, PortSide.EAST); + DiagramSyntheses.setLayoutOption(out, CoreOptions.PORT_BORDER_OFFSET, -size / ((double) 4)); + return new Pair(in, out); + } + + /** Creates and adds an error message figure */ + public KRectangle addErrorMessage(KNode node, String title, String message) { + // Create figure for error message + KRectangle figure = _kRenderingExtensions.addRectangle(node); + _kRenderingExtensions.setInvisible(figure, true); + + // Add error message box + KRoundedRectangle errMsgBox = _kContainerRenderingExtensions.addRoundedRectangle(figure, 7, 7); + _kContainerRenderingExtensions.setGridPlacement(errMsgBox, 1); + _kRenderingExtensions.setLineWidth(errMsgBox, 2); + _linguaFrancaStyleExtensions.noSelectionStyle(errMsgBox); + + if (title != null) { + // Add title to error message box + KText titleText = _kContainerRenderingExtensions.addText(errMsgBox, title); + _kRenderingExtensions.setFontSize(titleText, 12); + _kRenderingExtensions.setFontBold(titleText, true); + _kRenderingExtensions.setForeground(titleText, Colors.RED); + setGridPlacementDataFromPointToPoint( + titleText, + _kRenderingExtensions.LEFT, + 8, + 0, + _kRenderingExtensions.TOP, + 8, + 0, + _kRenderingExtensions.RIGHT, + 8, + 0, + _kRenderingExtensions.BOTTOM, + 4, + 0); + DiagramSyntheses.suppressSelectability(titleText); + _linguaFrancaStyleExtensions.noSelectionStyle(titleText); } - /** - * Creates and adds an error message figure - */ - public KRectangle addErrorMessage(KNode node, String title, String message) { - // Create figure for error message - KRectangle figure = _kRenderingExtensions.addRectangle(node); - _kRenderingExtensions.setInvisible(figure, true); - - // Add error message box - KRoundedRectangle errMsgBox = _kContainerRenderingExtensions.addRoundedRectangle(figure, 7, 7); - _kContainerRenderingExtensions.setGridPlacement(errMsgBox, 1); - _kRenderingExtensions.setLineWidth(errMsgBox, 2); - _linguaFrancaStyleExtensions.noSelectionStyle(errMsgBox); - - if (title != null) { - // Add title to error message box - KText titleText = _kContainerRenderingExtensions.addText(errMsgBox, title); - _kRenderingExtensions.setFontSize(titleText, 12); - _kRenderingExtensions.setFontBold(titleText, true); - _kRenderingExtensions.setForeground(titleText, Colors.RED); - setGridPlacementDataFromPointToPoint(titleText, - _kRenderingExtensions.LEFT, 8, 0, - _kRenderingExtensions.TOP, 8, 0, - _kRenderingExtensions.RIGHT, 8, 0, - _kRenderingExtensions.BOTTOM, 4, 0); - DiagramSyntheses.suppressSelectability(titleText); - _linguaFrancaStyleExtensions.noSelectionStyle(titleText); - } - - if (message != null) { - // Add message to error message box - KText msgText = _kContainerRenderingExtensions.addText(errMsgBox, message); - if (title != null) { - setGridPlacementDataFromPointToPoint(msgText, - _kRenderingExtensions.LEFT, 8, 0, - _kRenderingExtensions.TOP, 0, 0, - _kRenderingExtensions.RIGHT, 8, 0, - _kRenderingExtensions.BOTTOM, 4, 0); - } else { - setGridPlacementDataFromPointToPoint(msgText, - _kRenderingExtensions.LEFT, 8, 0, - _kRenderingExtensions.TOP, 8, 0, - _kRenderingExtensions.RIGHT, 8, 0, - _kRenderingExtensions.BOTTOM, 8, 0); - } - _linguaFrancaStyleExtensions.noSelectionStyle(msgText); - } - return figure; + if (message != null) { + // Add message to error message box + KText msgText = _kContainerRenderingExtensions.addText(errMsgBox, message); + if (title != null) { + setGridPlacementDataFromPointToPoint( + msgText, + _kRenderingExtensions.LEFT, + 8, + 0, + _kRenderingExtensions.TOP, + 0, + 0, + _kRenderingExtensions.RIGHT, + 8, + 0, + _kRenderingExtensions.BOTTOM, + 4, + 0); + } else { + setGridPlacementDataFromPointToPoint( + msgText, + _kRenderingExtensions.LEFT, + 8, + 0, + _kRenderingExtensions.TOP, + 8, + 0, + _kRenderingExtensions.RIGHT, + 8, + 0, + _kRenderingExtensions.BOTTOM, + 8, + 0); + } + _linguaFrancaStyleExtensions.noSelectionStyle(msgText); } - - public KRoundedRectangle addCommentFigure(KNode node, String message) { - // Create rectangle for comment figure - KRoundedRectangle commentFigure = _kRenderingExtensions.addRoundedRectangle(node, 1, 1, 1); - _kContainerRenderingExtensions.setGridPlacement(commentFigure, 1); - - // Add message - KText text = _kContainerRenderingExtensions.addText(commentFigure, message); - _kRenderingExtensions.setFontSize(text, 6); - setGridPlacementDataFromPointToPoint(text, - _kRenderingExtensions.LEFT, 3, 0, - _kRenderingExtensions.TOP, 3, 0, - _kRenderingExtensions.RIGHT, 3, 0, - _kRenderingExtensions.BOTTOM, 3, 0); - _linguaFrancaStyleExtensions.noSelectionStyle(text); - return commentFigure; - } - - private KRendering setGridPlacementDataFromPointToPoint(KRendering rendering, - PositionReferenceX fPx, float fAbsoluteLR, float fRelativeLR, - PositionReferenceY fPy, float fAbsoluteTB, float fRelativeTB, - PositionReferenceX tPx, float tAbsoluteLR, float tRelativeLR, - PositionReferenceY tPy, float tAbsoluteTB, float tRelativeTB) { - KAreaPlacementData fromPoint = _kRenderingExtensions.from( - _kRenderingExtensions.setGridPlacementData(rendering), - fPx, fAbsoluteLR, fRelativeLR, - fPy, fAbsoluteTB, fRelativeTB); - return _kRenderingExtensions.to(fromPoint, - tPx, tAbsoluteLR, tRelativeLR, - tPy, tAbsoluteTB, tRelativeTB); - } - - - public KPolyline addCommentPolyline(KEdge edge) { - KPolyline polyline = _kEdgeExtensions.addPolyline(edge); - _kRenderingExtensions.setLineWidth(polyline, 1); - _kRenderingExtensions.setLineStyle(polyline, LineStyle.DOT); - return polyline; - } - - public KContainerRendering addParameterEntry(KContainerRendering parent, Parameter associate, String text) { - KRectangle container = _kContainerRenderingExtensions.addRectangle(parent); - _kRenderingExtensions.setInvisible(container, true); - - var ktext = _kContainerRenderingExtensions.addText(container, text); - _kRenderingExtensions.setFontSize(ktext, 8); - _kRenderingExtensions.setPointPlacementData(ktext, - _kRenderingExtensions.LEFT, 10, 0, - _kRenderingExtensions.TOP, 0, 0.5f, - _kRenderingExtensions.H_LEFT, _kRenderingExtensions.V_CENTRAL, - 0, 0, 0, 0); - associateWith(ktext, associate); - - var dot = _kContainerRenderingExtensions.addEllipse(container); - _kRenderingExtensions.setLineWidth(dot, 1); - _linguaFrancaStyleExtensions.noSelectionStyle(dot); - _kRenderingExtensions.setPointPlacementData(dot, - _kRenderingExtensions.LEFT, 2, 0, - _kRenderingExtensions.TOP, 0, 0.5f, - _kRenderingExtensions.H_LEFT, _kRenderingExtensions.V_CENTRAL, - 0, 0, 5, 5); - - return container; - } - - - public KContainerRendering addStateEntry(KContainerRendering parent, StateVar associate, String text, boolean reset) { - KRectangle container = _kContainerRenderingExtensions.addRectangle(parent); - _kRenderingExtensions.setInvisible(container, true); - - var ktext = _kContainerRenderingExtensions.addText(container, text); - _kRenderingExtensions.setFontSize(ktext, 8); - _kRenderingExtensions.setPointPlacementData(ktext, - _kRenderingExtensions.LEFT, 10, 0, - _kRenderingExtensions.TOP, 0, 0.5f, - _kRenderingExtensions.H_LEFT, _kRenderingExtensions.V_CENTRAL, - 0, 0, 0, 0); - associateWith(ktext, associate); - - KEllipse outerCircle; - - if (reset) { - outerCircle = _kContainerRenderingExtensions.addEllipse(container); - _kRenderingExtensions.setLineWidth(outerCircle, 0.9f); - _kRenderingExtensions.setBackground(outerCircle, Colors.WHITE); - _linguaFrancaStyleExtensions.noSelectionStyle(outerCircle); - _kRenderingExtensions.setPointPlacementData(outerCircle, - _kRenderingExtensions.LEFT, 1.5f, 0, - _kRenderingExtensions.TOP, 0, 0.5f, - _kRenderingExtensions.H_LEFT, _kRenderingExtensions.V_CENTRAL, - 0, 0, 6.3f, 6.3f); - - var resetCycleGap = _kContainerRenderingExtensions.addPolygon(outerCircle); - resetCycleGap.getPoints().add(_kRenderingExtensions.createKPosition(PositionReferenceX.LEFT, 0, 0, PositionReferenceY.TOP, 0.26f, 0)); - resetCycleGap.getPoints().add(_kRenderingExtensions.createKPosition(PositionReferenceX.LEFT, 0, 0.2f, PositionReferenceY.TOP, 0.1f, 0)); - resetCycleGap.getPoints().add(_kRenderingExtensions.createKPosition(PositionReferenceX.LEFT, 0, 0.5f, PositionReferenceY.TOP, 0.0f, 0)); - resetCycleGap.getPoints().add(_kRenderingExtensions.createKPosition(PositionReferenceX.RIGHT, 0, 0.2f, PositionReferenceY.TOP, 0.1f, 0)); - resetCycleGap.getPoints().add(_kRenderingExtensions.createKPosition(PositionReferenceX.RIGHT, 0, 0, PositionReferenceY.TOP, 0.26f, 0)); - resetCycleGap.getPoints().add(_kRenderingExtensions.createKPosition(PositionReferenceX.RIGHT, 0.5f, 0, PositionReferenceY.BOTTOM, 0, 0)); - resetCycleGap.getPoints().add(_kRenderingExtensions.createKPosition(PositionReferenceX.LEFT, 0.5f, 0, PositionReferenceY.BOTTOM, 0, 0)); - _kRenderingExtensions.setLineWidth(resetCycleGap, 0.3f); - _kRenderingExtensions.setForeground(resetCycleGap, Colors.WHITE); - _kRenderingExtensions.setBackground(resetCycleGap, Colors.WHITE); - _linguaFrancaStyleExtensions.noSelectionStyle(resetCycleGap); - _kRenderingExtensions.setPointPlacementData(resetCycleGap, - _kRenderingExtensions.LEFT, -1.2f, 0.5f, - _kRenderingExtensions.TOP, 0.75f, 0, - _kRenderingExtensions.H_LEFT, _kRenderingExtensions.V_CENTRAL, - 0, 0, 2.5f, 1.3f); - - var resetArrow = _kContainerRenderingExtensions.addPolygon(outerCircle); - resetArrow.getPoints().add(_kRenderingExtensions.createKPosition(PositionReferenceX.LEFT, 0, 0, PositionReferenceY.TOP, 0.0f, 0.1f)); - resetArrow.getPoints().add(_kRenderingExtensions.createKPosition(PositionReferenceX.LEFT, 0.0f, 0.3f, PositionReferenceY.BOTTOM, 0, 0)); - resetArrow.getPoints().add(_kRenderingExtensions.createKPosition(PositionReferenceX.RIGHT, 0.0f, 0, PositionReferenceY.TOP, 0.0f, 0.0f)); - _kRenderingExtensions.setLineWidth(resetArrow, 0.3f); - _kRenderingExtensions.setBackground(resetArrow, Colors.BLACK); - _linguaFrancaStyleExtensions.noSelectionStyle(resetArrow); - _kRenderingExtensions.setPointPlacementData(resetArrow, - _kRenderingExtensions.LEFT, 0.8f, 0.5f, - _kRenderingExtensions.TOP, 1.1f, 0, - _kRenderingExtensions.H_LEFT, _kRenderingExtensions.V_CENTRAL, - 0, 0, 1.5f, 1.5f); - } else { - outerCircle = _kContainerRenderingExtensions.addEllipse(container); - _kRenderingExtensions.setLineWidth(outerCircle, 1); - _kRenderingExtensions.setBackground(outerCircle, Colors.WHITE); - _linguaFrancaStyleExtensions.noSelectionStyle(outerCircle); - _kRenderingExtensions.setPointPlacementData(outerCircle, - _kRenderingExtensions.LEFT, 1.5f, 0, - _kRenderingExtensions.TOP, 0, 0.5f, - _kRenderingExtensions.H_LEFT, _kRenderingExtensions.V_CENTRAL, - 0, 0, 6, 6); - } - - var innerDot = _kContainerRenderingExtensions.addEllipse(outerCircle); - _kRenderingExtensions.setLineWidth(innerDot, 0.5f); - _kRenderingExtensions.setBackground(innerDot, Colors.BLACK); - _linguaFrancaStyleExtensions.noSelectionStyle(innerDot); - _kRenderingExtensions.setPointPlacementData(innerDot, - _kRenderingExtensions.LEFT, 0, 0.5f, - _kRenderingExtensions.TOP, 0, 0.5f, - _kRenderingExtensions.H_CENTRAL, _kRenderingExtensions.V_CENTRAL, - 0, 0, 2.5f, 2.5f); - - return container; + return figure; + } + + public KRoundedRectangle addCommentFigure(KNode node, String message) { + // Create rectangle for comment figure + KRoundedRectangle commentFigure = _kRenderingExtensions.addRoundedRectangle(node, 1, 1, 1); + _kContainerRenderingExtensions.setGridPlacement(commentFigure, 1); + + // Add message + KText text = _kContainerRenderingExtensions.addText(commentFigure, message); + _kRenderingExtensions.setFontSize(text, 6); + setGridPlacementDataFromPointToPoint( + text, + _kRenderingExtensions.LEFT, + 3, + 0, + _kRenderingExtensions.TOP, + 3, + 0, + _kRenderingExtensions.RIGHT, + 3, + 0, + _kRenderingExtensions.BOTTOM, + 3, + 0); + _linguaFrancaStyleExtensions.noSelectionStyle(text); + return commentFigure; + } + + private KRendering setGridPlacementDataFromPointToPoint( + KRendering rendering, + PositionReferenceX fPx, + float fAbsoluteLR, + float fRelativeLR, + PositionReferenceY fPy, + float fAbsoluteTB, + float fRelativeTB, + PositionReferenceX tPx, + float tAbsoluteLR, + float tRelativeLR, + PositionReferenceY tPy, + float tAbsoluteTB, + float tRelativeTB) { + KAreaPlacementData fromPoint = + _kRenderingExtensions.from( + _kRenderingExtensions.setGridPlacementData(rendering), + fPx, + fAbsoluteLR, + fRelativeLR, + fPy, + fAbsoluteTB, + fRelativeTB); + return _kRenderingExtensions.to( + fromPoint, tPx, tAbsoluteLR, tRelativeLR, tPy, tAbsoluteTB, tRelativeTB); + } + + public KPolyline addCommentPolyline(KEdge edge) { + KPolyline polyline = _kEdgeExtensions.addPolyline(edge); + _kRenderingExtensions.setLineWidth(polyline, 1); + _kRenderingExtensions.setLineStyle(polyline, LineStyle.DOT); + return polyline; + } + + public KContainerRendering addParameterEntry( + KContainerRendering parent, Parameter associate, String text) { + KRectangle container = _kContainerRenderingExtensions.addRectangle(parent); + _kRenderingExtensions.setInvisible(container, true); + + var ktext = _kContainerRenderingExtensions.addText(container, text); + _kRenderingExtensions.setFontSize(ktext, 8); + _kRenderingExtensions.setPointPlacementData( + ktext, + _kRenderingExtensions.LEFT, + 10, + 0, + _kRenderingExtensions.TOP, + 0, + 0.5f, + _kRenderingExtensions.H_LEFT, + _kRenderingExtensions.V_CENTRAL, + 0, + 0, + 0, + 0); + associateWith(ktext, associate); + + var dot = _kContainerRenderingExtensions.addEllipse(container); + _kRenderingExtensions.setLineWidth(dot, 1); + _linguaFrancaStyleExtensions.noSelectionStyle(dot); + _kRenderingExtensions.setPointPlacementData( + dot, + _kRenderingExtensions.LEFT, + 2, + 0, + _kRenderingExtensions.TOP, + 0, + 0.5f, + _kRenderingExtensions.H_LEFT, + _kRenderingExtensions.V_CENTRAL, + 0, + 0, + 5, + 5); + + return container; + } + + public KContainerRendering addStateEntry( + KContainerRendering parent, StateVar associate, String text, boolean reset) { + KRectangle container = _kContainerRenderingExtensions.addRectangle(parent); + _kRenderingExtensions.setInvisible(container, true); + + var ktext = _kContainerRenderingExtensions.addText(container, text); + _kRenderingExtensions.setFontSize(ktext, 8); + _kRenderingExtensions.setPointPlacementData( + ktext, + _kRenderingExtensions.LEFT, + 10, + 0, + _kRenderingExtensions.TOP, + 0, + 0.5f, + _kRenderingExtensions.H_LEFT, + _kRenderingExtensions.V_CENTRAL, + 0, + 0, + 0, + 0); + associateWith(ktext, associate); + + KEllipse outerCircle; + + if (reset) { + outerCircle = _kContainerRenderingExtensions.addEllipse(container); + _kRenderingExtensions.setLineWidth(outerCircle, 0.9f); + _kRenderingExtensions.setBackground(outerCircle, Colors.WHITE); + _linguaFrancaStyleExtensions.noSelectionStyle(outerCircle); + _kRenderingExtensions.setPointPlacementData( + outerCircle, + _kRenderingExtensions.LEFT, + 1.5f, + 0, + _kRenderingExtensions.TOP, + 0, + 0.5f, + _kRenderingExtensions.H_LEFT, + _kRenderingExtensions.V_CENTRAL, + 0, + 0, + 6.3f, + 6.3f); + + var resetCycleGap = _kContainerRenderingExtensions.addPolygon(outerCircle); + resetCycleGap + .getPoints() + .add( + _kRenderingExtensions.createKPosition( + PositionReferenceX.LEFT, 0, 0, PositionReferenceY.TOP, 0.26f, 0)); + resetCycleGap + .getPoints() + .add( + _kRenderingExtensions.createKPosition( + PositionReferenceX.LEFT, 0, 0.2f, PositionReferenceY.TOP, 0.1f, 0)); + resetCycleGap + .getPoints() + .add( + _kRenderingExtensions.createKPosition( + PositionReferenceX.LEFT, 0, 0.5f, PositionReferenceY.TOP, 0.0f, 0)); + resetCycleGap + .getPoints() + .add( + _kRenderingExtensions.createKPosition( + PositionReferenceX.RIGHT, 0, 0.2f, PositionReferenceY.TOP, 0.1f, 0)); + resetCycleGap + .getPoints() + .add( + _kRenderingExtensions.createKPosition( + PositionReferenceX.RIGHT, 0, 0, PositionReferenceY.TOP, 0.26f, 0)); + resetCycleGap + .getPoints() + .add( + _kRenderingExtensions.createKPosition( + PositionReferenceX.RIGHT, 0.5f, 0, PositionReferenceY.BOTTOM, 0, 0)); + resetCycleGap + .getPoints() + .add( + _kRenderingExtensions.createKPosition( + PositionReferenceX.LEFT, 0.5f, 0, PositionReferenceY.BOTTOM, 0, 0)); + _kRenderingExtensions.setLineWidth(resetCycleGap, 0.3f); + _kRenderingExtensions.setForeground(resetCycleGap, Colors.WHITE); + _kRenderingExtensions.setBackground(resetCycleGap, Colors.WHITE); + _linguaFrancaStyleExtensions.noSelectionStyle(resetCycleGap); + _kRenderingExtensions.setPointPlacementData( + resetCycleGap, + _kRenderingExtensions.LEFT, + -1.2f, + 0.5f, + _kRenderingExtensions.TOP, + 0.75f, + 0, + _kRenderingExtensions.H_LEFT, + _kRenderingExtensions.V_CENTRAL, + 0, + 0, + 2.5f, + 1.3f); + + var resetArrow = _kContainerRenderingExtensions.addPolygon(outerCircle); + resetArrow + .getPoints() + .add( + _kRenderingExtensions.createKPosition( + PositionReferenceX.LEFT, 0, 0, PositionReferenceY.TOP, 0.0f, 0.1f)); + resetArrow + .getPoints() + .add( + _kRenderingExtensions.createKPosition( + PositionReferenceX.LEFT, 0.0f, 0.3f, PositionReferenceY.BOTTOM, 0, 0)); + resetArrow + .getPoints() + .add( + _kRenderingExtensions.createKPosition( + PositionReferenceX.RIGHT, 0.0f, 0, PositionReferenceY.TOP, 0.0f, 0.0f)); + _kRenderingExtensions.setLineWidth(resetArrow, 0.3f); + _kRenderingExtensions.setBackground(resetArrow, Colors.BLACK); + _linguaFrancaStyleExtensions.noSelectionStyle(resetArrow); + _kRenderingExtensions.setPointPlacementData( + resetArrow, + _kRenderingExtensions.LEFT, + 0.8f, + 0.5f, + _kRenderingExtensions.TOP, + 1.1f, + 0, + _kRenderingExtensions.H_LEFT, + _kRenderingExtensions.V_CENTRAL, + 0, + 0, + 1.5f, + 1.5f); + } else { + outerCircle = _kContainerRenderingExtensions.addEllipse(container); + _kRenderingExtensions.setLineWidth(outerCircle, 1); + _kRenderingExtensions.setBackground(outerCircle, Colors.WHITE); + _linguaFrancaStyleExtensions.noSelectionStyle(outerCircle); + _kRenderingExtensions.setPointPlacementData( + outerCircle, + _kRenderingExtensions.LEFT, + 1.5f, + 0, + _kRenderingExtensions.TOP, + 0, + 0.5f, + _kRenderingExtensions.H_LEFT, + _kRenderingExtensions.V_CENTRAL, + 0, + 0, + 6, + 6); } + var innerDot = _kContainerRenderingExtensions.addEllipse(outerCircle); + _kRenderingExtensions.setLineWidth(innerDot, 0.5f); + _kRenderingExtensions.setBackground(innerDot, Colors.BLACK); + _linguaFrancaStyleExtensions.noSelectionStyle(innerDot); + _kRenderingExtensions.setPointPlacementData( + innerDot, + _kRenderingExtensions.LEFT, + 0, + 0.5f, + _kRenderingExtensions.TOP, + 0, + 0.5f, + _kRenderingExtensions.H_CENTRAL, + _kRenderingExtensions.V_CENTRAL, + 0, + 0, + 2.5f, + 2.5f); + + return container; + } } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/styles/LinguaFrancaStyleExtensions.java b/org.lflang/src/org/lflang/diagram/synthesis/styles/LinguaFrancaStyleExtensions.java index f4c0aefd98..6eb2cfd7f0 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/styles/LinguaFrancaStyleExtensions.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/styles/LinguaFrancaStyleExtensions.java @@ -1,29 +1,32 @@ /************* -* Copyright (c) 2020, Kiel University. -* -* 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, Kiel University. + * + * 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.diagram.synthesis.styles; +import static de.cau.cs.kieler.klighd.krendering.extensions.PositionReferenceX.*; +import static de.cau.cs.kieler.klighd.krendering.extensions.PositionReferenceY.*; + import de.cau.cs.kieler.klighd.internal.util.KlighdInternalProperties; import de.cau.cs.kieler.klighd.kgraph.KEdge; import de.cau.cs.kieler.klighd.kgraph.KLabel; @@ -48,7 +51,6 @@ import de.cau.cs.kieler.klighd.labels.decoration.IDecoratorRenderingProvider; import de.cau.cs.kieler.klighd.labels.decoration.LabelDecorationConfigurator; import java.util.List; - import javax.inject.Inject; import org.eclipse.elk.core.math.ElkPadding; import org.eclipse.elk.graph.properties.Property; @@ -56,415 +58,469 @@ import org.eclipse.xtext.xbase.lib.Extension; import org.lflang.diagram.synthesis.AbstractSynthesisExtensions; -import static de.cau.cs.kieler.klighd.krendering.extensions.PositionReferenceX.*; -import static de.cau.cs.kieler.klighd.krendering.extensions.PositionReferenceY.*; - /** * Extension class that provides styles and coloring for the Lingua France diagram synthesis. - * + * * @author Alexander Schulz-Rosengarten */ @ViewSynthesisShared public class LinguaFrancaStyleExtensions extends AbstractSynthesisExtensions { - - /** - * INTERNAL property to communicate a node's background color. - */ - public static final Property LABEL_PARENT_BACKGROUND = new Property<>( - "org.lflang.linguafranca.diagram.synthesis.styles.label.parent.background", Colors.WHITE); - - @Inject - @Extension - private KRenderingExtensions _kRenderingExtensions; - @Inject - @Extension - private KContainerRenderingExtensions _kContainerRenderingExtensions; - @Inject - @Extension - private KPolylineExtensions _kPolylineExtensions; - @Extension - private KRenderingFactory _kRenderingFactory = KRenderingFactory.eINSTANCE; - - public KRendering noSelectionStyle(KRendering r) { - return _kRenderingExtensions.setSelectionTextStrikeout(r, false); - } - - public KRendering underlineSelectionStyle(KRendering r) { - return _kRenderingExtensions.setSelectionTextUnderline(r, Underline.SINGLE); - } - - public KRendering boldLineSelectionStyle(KRendering r) { - float lineWidthValue = _kRenderingExtensions.getLineWidthValue(r); - return _kRenderingExtensions.setSelectionLineWidth(r, lineWidthValue * 2); - } - - public KText boldTextSelectionStyle(KText t) { - return _kRenderingExtensions.setSelectionFontBold(t, true); - } - - public void errorStyle(KRendering r) { - _kRenderingExtensions.setForeground(r, Colors.RED); - _kRenderingExtensions.setLineWidth(r, 2); - _kRenderingExtensions.setSelectionLineWidth(r, 3); - - // Set background color the body if its a port or an line decorator - if (r.eContainer() instanceof KPort || r.eContainer() instanceof KPolyline) { - _kRenderingExtensions.setBackground(r, Colors.RED); - _kRenderingExtensions.getBackground(r).setPropagateToChildren(true); - _kRenderingExtensions.getForeground(r).setPropagateToChildren(true); - _kRenderingExtensions.getLineWidth(r).setPropagateToChildren(true); - } else if (r.eContainer() instanceof KEdge && r instanceof KPolyline) { - // As a workaround for a rendering issue in Klighd VSCode, the style is applied to polyline - // children directly because a propagated background would lead to a filled edge area. - // See https://github.com/kieler/klighd-vscode/issues/67 - // If fixed this commit can be reverted - ((KPolyline) r).getChildren().stream().forEach(c -> errorStyle(c)); - } - } - - public void commentStyle(KRendering r) { - _kRenderingExtensions.setForeground(r, Colors.LIGHT_GOLDENROD); - _kRenderingExtensions.setBackground(r, Colors.PALE_GOLDENROD); - _kRenderingExtensions.setLineWidth(r, 1); - _kRenderingExtensions.setSelectionLineWidth(r, 2); - - if (r.eContainer() instanceof KEdge) { // also color potential arrow heads - _kRenderingExtensions.setBackground(r, Colors.LIGHT_GOLDENROD); - _kRenderingExtensions.getBackground(r).setPropagateToChildren(true); - _kRenderingExtensions.getForeground(r).setPropagateToChildren(true); - _kRenderingExtensions.getLineWidth(r).setPropagateToChildren(true); - } + + /** INTERNAL property to communicate a node's background color. */ + public static final Property LABEL_PARENT_BACKGROUND = + new Property<>( + "org.lflang.linguafranca.diagram.synthesis.styles.label.parent.background", Colors.WHITE); + + @Inject @Extension private KRenderingExtensions _kRenderingExtensions; + @Inject @Extension private KContainerRenderingExtensions _kContainerRenderingExtensions; + @Inject @Extension private KPolylineExtensions _kPolylineExtensions; + @Extension private KRenderingFactory _kRenderingFactory = KRenderingFactory.eINSTANCE; + + public KRendering noSelectionStyle(KRendering r) { + return _kRenderingExtensions.setSelectionTextStrikeout(r, false); + } + + public KRendering underlineSelectionStyle(KRendering r) { + return _kRenderingExtensions.setSelectionTextUnderline(r, Underline.SINGLE); + } + + public KRendering boldLineSelectionStyle(KRendering r) { + float lineWidthValue = _kRenderingExtensions.getLineWidthValue(r); + return _kRenderingExtensions.setSelectionLineWidth(r, lineWidthValue * 2); + } + + public KText boldTextSelectionStyle(KText t) { + return _kRenderingExtensions.setSelectionFontBold(t, true); + } + + public void errorStyle(KRendering r) { + _kRenderingExtensions.setForeground(r, Colors.RED); + _kRenderingExtensions.setLineWidth(r, 2); + _kRenderingExtensions.setSelectionLineWidth(r, 3); + + // Set background color the body if its a port or an line decorator + if (r.eContainer() instanceof KPort || r.eContainer() instanceof KPolyline) { + _kRenderingExtensions.setBackground(r, Colors.RED); + _kRenderingExtensions.getBackground(r).setPropagateToChildren(true); + _kRenderingExtensions.getForeground(r).setPropagateToChildren(true); + _kRenderingExtensions.getLineWidth(r).setPropagateToChildren(true); + } else if (r.eContainer() instanceof KEdge && r instanceof KPolyline) { + // As a workaround for a rendering issue in Klighd VSCode, the style is applied to polyline + // children directly because a propagated background would lead to a filled edge area. + // See https://github.com/kieler/klighd-vscode/issues/67 + // If fixed this commit can be reverted + ((KPolyline) r).getChildren().stream().forEach(c -> errorStyle(c)); } - - private static final int CLOUD_WIDTH = 20; - public KContainerRendering addCloudIcon(final KContainerRendering parent) { - KRectangle figure = _kContainerRenderingExtensions.addRectangle(parent); - _kRenderingExtensions.setInvisible(figure, true); - - KRoundedRectangle roundRectangle = _kContainerRenderingExtensions.addRoundedRectangle( - figure, - CLOUD_WIDTH / 7, - CLOUD_WIDTH / 7 - ); - _kRenderingExtensions.setBackground(roundRectangle, Colors.GRAY); - _kRenderingExtensions.setForeground(roundRectangle, Colors.GRAY); - _kRenderingExtensions.setPointPlacementData(roundRectangle, - _kRenderingExtensions.LEFT, 2, 0, - _kRenderingExtensions.TOP, 0, 0.5f, - _kRenderingExtensions.H_LEFT, _kRenderingExtensions.V_TOP, 0, - 0, CLOUD_WIDTH, CLOUD_WIDTH / 3); - - KEllipse childEllipse = _kContainerRenderingExtensions.addEllipse(figure); - _kRenderingExtensions.setBackground(childEllipse, Colors.GRAY); - _kRenderingExtensions.setForeground(childEllipse, Colors.GRAY); - _kRenderingExtensions.setPointPlacementData(childEllipse, - _kRenderingExtensions.LEFT, 0, 0f, - _kRenderingExtensions.TOP, 0, 0.38f, - _kRenderingExtensions.H_LEFT, _kRenderingExtensions.V_TOP, 0, - 0, CLOUD_WIDTH / 2.5f, CLOUD_WIDTH / 2.5f); - - childEllipse = _kContainerRenderingExtensions.addEllipse(figure); - _kRenderingExtensions.setBackground(childEllipse, Colors.GRAY); - _kRenderingExtensions.setForeground(childEllipse, Colors.GRAY); - _kRenderingExtensions.setPointPlacementData(childEllipse, - _kRenderingExtensions.LEFT, 0, 0.5f, - _kRenderingExtensions.TOP, 0, 0.25f, - _kRenderingExtensions.H_RIGHT, _kRenderingExtensions.V_TOP, 0, - 0, CLOUD_WIDTH / 3f, CLOUD_WIDTH / 3f); - - childEllipse = _kContainerRenderingExtensions.addEllipse(figure); - _kRenderingExtensions.setBackground(childEllipse, Colors.GRAY); - _kRenderingExtensions.setForeground(childEllipse, Colors.GRAY); - _kRenderingExtensions.setPointPlacementData(childEllipse, - _kRenderingExtensions.LEFT, 0, 0.4f, - _kRenderingExtensions.TOP, CLOUD_WIDTH / 10, 0, - _kRenderingExtensions.H_LEFT, _kRenderingExtensions.V_TOP, 0, - 0, CLOUD_WIDTH / 2, CLOUD_WIDTH / 2); - - return figure; + } + + public void commentStyle(KRendering r) { + _kRenderingExtensions.setForeground(r, Colors.LIGHT_GOLDENROD); + _kRenderingExtensions.setBackground(r, Colors.PALE_GOLDENROD); + _kRenderingExtensions.setLineWidth(r, 1); + _kRenderingExtensions.setSelectionLineWidth(r, 2); + + if (r.eContainer() instanceof KEdge) { // also color potential arrow heads + _kRenderingExtensions.setBackground(r, Colors.LIGHT_GOLDENROD); + _kRenderingExtensions.getBackground(r).setPropagateToChildren(true); + _kRenderingExtensions.getForeground(r).setPropagateToChildren(true); + _kRenderingExtensions.getLineWidth(r).setPropagateToChildren(true); } - - public KRendering addCloudUploadIcon(KContainerRendering parent) { - KContainerRendering cloudIcon = addCloudIcon(parent); - KPolygon cloudPolygon = _kContainerRenderingExtensions.addPolygon(cloudIcon); - _kRenderingExtensions.setBackground(cloudPolygon, Colors.WHITE); - _kRenderingExtensions.setForeground(cloudPolygon, Colors.WHITE); - cloudPolygon.getPoints().addAll( + } + + private static final int CLOUD_WIDTH = 20; + + public KContainerRendering addCloudIcon(final KContainerRendering parent) { + KRectangle figure = _kContainerRenderingExtensions.addRectangle(parent); + _kRenderingExtensions.setInvisible(figure, true); + + KRoundedRectangle roundRectangle = + _kContainerRenderingExtensions.addRoundedRectangle( + figure, CLOUD_WIDTH / 7, CLOUD_WIDTH / 7); + _kRenderingExtensions.setBackground(roundRectangle, Colors.GRAY); + _kRenderingExtensions.setForeground(roundRectangle, Colors.GRAY); + _kRenderingExtensions.setPointPlacementData( + roundRectangle, + _kRenderingExtensions.LEFT, + 2, + 0, + _kRenderingExtensions.TOP, + 0, + 0.5f, + _kRenderingExtensions.H_LEFT, + _kRenderingExtensions.V_TOP, + 0, + 0, + CLOUD_WIDTH, + CLOUD_WIDTH / 3); + + KEllipse childEllipse = _kContainerRenderingExtensions.addEllipse(figure); + _kRenderingExtensions.setBackground(childEllipse, Colors.GRAY); + _kRenderingExtensions.setForeground(childEllipse, Colors.GRAY); + _kRenderingExtensions.setPointPlacementData( + childEllipse, + _kRenderingExtensions.LEFT, + 0, + 0f, + _kRenderingExtensions.TOP, + 0, + 0.38f, + _kRenderingExtensions.H_LEFT, + _kRenderingExtensions.V_TOP, + 0, + 0, + CLOUD_WIDTH / 2.5f, + CLOUD_WIDTH / 2.5f); + + childEllipse = _kContainerRenderingExtensions.addEllipse(figure); + _kRenderingExtensions.setBackground(childEllipse, Colors.GRAY); + _kRenderingExtensions.setForeground(childEllipse, Colors.GRAY); + _kRenderingExtensions.setPointPlacementData( + childEllipse, + _kRenderingExtensions.LEFT, + 0, + 0.5f, + _kRenderingExtensions.TOP, + 0, + 0.25f, + _kRenderingExtensions.H_RIGHT, + _kRenderingExtensions.V_TOP, + 0, + 0, + CLOUD_WIDTH / 3f, + CLOUD_WIDTH / 3f); + + childEllipse = _kContainerRenderingExtensions.addEllipse(figure); + _kRenderingExtensions.setBackground(childEllipse, Colors.GRAY); + _kRenderingExtensions.setForeground(childEllipse, Colors.GRAY); + _kRenderingExtensions.setPointPlacementData( + childEllipse, + _kRenderingExtensions.LEFT, + 0, + 0.4f, + _kRenderingExtensions.TOP, + CLOUD_WIDTH / 10, + 0, + _kRenderingExtensions.H_LEFT, + _kRenderingExtensions.V_TOP, + 0, + 0, + CLOUD_WIDTH / 2, + CLOUD_WIDTH / 2); + + return figure; + } + + public KRendering addCloudUploadIcon(KContainerRendering parent) { + KContainerRendering cloudIcon = addCloudIcon(parent); + KPolygon cloudPolygon = _kContainerRenderingExtensions.addPolygon(cloudIcon); + _kRenderingExtensions.setBackground(cloudPolygon, Colors.WHITE); + _kRenderingExtensions.setForeground(cloudPolygon, Colors.WHITE); + cloudPolygon + .getPoints() + .addAll( List.of( - _kRenderingExtensions.createKPosition(LEFT, -1.5f, 0.5f, TOP, CLOUD_WIDTH / 3, 0.5f), + _kRenderingExtensions.createKPosition( + LEFT, -1.5f, 0.5f, TOP, CLOUD_WIDTH / 3, 0.5f), _kRenderingExtensions.createKPosition(LEFT, -1.5f, 0.5f, TOP, 0, 0.58f), _kRenderingExtensions.createKPosition(LEFT, (-4), 0.5f, TOP, 0, 0.58f), _kRenderingExtensions.createKPosition(LEFT, 0, 0.5f, TOP, 0, 0.35f), _kRenderingExtensions.createKPosition(LEFT, 4, 0.5f, TOP, 0, 0.58f), _kRenderingExtensions.createKPosition(LEFT, 1.5f, 0.5f, TOP, 0, 0.58f), - _kRenderingExtensions.createKPosition(LEFT, 1.5f, 0.5f, TOP, CLOUD_WIDTH / 3, 0.5f) - ) - ); - return cloudIcon; - } - - private static LabelDecorationConfigurator _onEdgeLabelConfigurator; // ONLY for use in applyOnEdgeStyle - public void applyOnEdgeStyle(KLabel label) { - if (_onEdgeLabelConfigurator == null) { - LabelDecorationConfigurator configurator = LabelDecorationConfigurator.create().withInlineLabels(true); - _onEdgeLabelConfigurator = configurator.withLabelTextRenderingProvider( - (KContainerRendering container, KLabel klabel) -> { + _kRenderingExtensions.createKPosition( + LEFT, 1.5f, 0.5f, TOP, CLOUD_WIDTH / 3, 0.5f))); + return cloudIcon; + } + + private static LabelDecorationConfigurator + _onEdgeLabelConfigurator; // ONLY for use in applyOnEdgeStyle + + public void applyOnEdgeStyle(KLabel label) { + if (_onEdgeLabelConfigurator == null) { + LabelDecorationConfigurator configurator = + LabelDecorationConfigurator.create().withInlineLabels(true); + _onEdgeLabelConfigurator = + configurator.withLabelTextRenderingProvider( + (KContainerRendering container, KLabel klabel) -> { KText kText = _kRenderingFactory.createKText(); _kRenderingExtensions.setFontSize(kText, 9); container.getChildren().add(kText); return kText; - }); - } - _onEdgeLabelConfigurator.applyTo(label); + }); } - - private static LabelDecorationConfigurator _onEdgeDelayLabelConfigurator; // ONLY for use in applyOnEdgeDelayStyle - public void applyOnEdgeDelayStyle(KLabel label) { - if (_onEdgeDelayLabelConfigurator == null) { - LabelDecorationConfigurator configurator = LabelDecorationConfigurator.create().withInlineLabels(true); - configurator = configurator.withLabelTextRenderingProvider( - (KContainerRendering container, KLabel klabel) -> { - KText kText = _kRenderingFactory.createKText(); - _kRenderingExtensions.setFontSize(kText, 8); - boldTextSelectionStyle(kText); - kText.setProperty(KlighdInternalProperties.MODEL_ELEMEMT, - klabel.getProperty(KlighdInternalProperties.MODEL_ELEMEMT)); - container.getChildren().add(kText); - return kText; - } - ); - configurator = configurator.addDecoratorRenderingProvider(new IDecoratorRenderingProvider() { + _onEdgeLabelConfigurator.applyTo(label); + } + + private static LabelDecorationConfigurator + _onEdgeDelayLabelConfigurator; // ONLY for use in applyOnEdgeDelayStyle + + public void applyOnEdgeDelayStyle(KLabel label) { + if (_onEdgeDelayLabelConfigurator == null) { + LabelDecorationConfigurator configurator = + LabelDecorationConfigurator.create().withInlineLabels(true); + configurator = + configurator.withLabelTextRenderingProvider( + (KContainerRendering container, KLabel klabel) -> { + KText kText = _kRenderingFactory.createKText(); + _kRenderingExtensions.setFontSize(kText, 8); + boldTextSelectionStyle(kText); + kText.setProperty( + KlighdInternalProperties.MODEL_ELEMEMT, + klabel.getProperty(KlighdInternalProperties.MODEL_ELEMEMT)); + container.getChildren().add(kText); + return kText; + }); + configurator = + configurator.addDecoratorRenderingProvider( + new IDecoratorRenderingProvider() { @Override public ElkPadding createDecoratorRendering( - KContainerRendering container, KLabel label, - LabelDecorationConfigurator.LayoutMode layoutMode) { - ElkPadding padding = new ElkPadding(); - padding.top = 1; - padding.bottom = 1; - padding.left = 2; - padding.right = 2; - - KPolygon polygon = _kRenderingFactory.createKPolygon(); - _kRenderingExtensions.from(polygon, LEFT, (-2), 0, BOTTOM, 0, 0); - _kRenderingExtensions.to(polygon, LEFT, 2, 0, TOP, 0, 0); - _kRenderingExtensions.to(polygon, RIGHT, (-2), 0, TOP, 0, 0); - _kRenderingExtensions.to(polygon, RIGHT, 2, 0, BOTTOM, 0, 0); - _kRenderingExtensions.setBackground(polygon, Colors.WHITE); - _kRenderingExtensions.setForeground(polygon, Colors.WHITE); - container.getChildren().add(polygon); - - KPolyline polyline = _kRenderingFactory.createKPolyline(); - _kRenderingExtensions.from(polyline, LEFT, (-2), 0, BOTTOM, 0, 0); - _kRenderingExtensions.to(polyline, LEFT, 2, 0, TOP, 0, 0); - container.getChildren().add(polyline); - - polyline = _kRenderingFactory.createKPolyline(); - _kRenderingExtensions.from(polyline, RIGHT, 2, 0, BOTTOM, 0, 0); - _kRenderingExtensions.to(polyline, RIGHT, (-2), 0, TOP, 0, 0); - container.getChildren().add(polyline); - - return padding; + KContainerRendering container, + KLabel label, + LabelDecorationConfigurator.LayoutMode layoutMode) { + ElkPadding padding = new ElkPadding(); + padding.top = 1; + padding.bottom = 1; + padding.left = 2; + padding.right = 2; + + KPolygon polygon = _kRenderingFactory.createKPolygon(); + _kRenderingExtensions.from(polygon, LEFT, (-2), 0, BOTTOM, 0, 0); + _kRenderingExtensions.to(polygon, LEFT, 2, 0, TOP, 0, 0); + _kRenderingExtensions.to(polygon, RIGHT, (-2), 0, TOP, 0, 0); + _kRenderingExtensions.to(polygon, RIGHT, 2, 0, BOTTOM, 0, 0); + _kRenderingExtensions.setBackground(polygon, Colors.WHITE); + _kRenderingExtensions.setForeground(polygon, Colors.WHITE); + container.getChildren().add(polygon); + + KPolyline polyline = _kRenderingFactory.createKPolyline(); + _kRenderingExtensions.from(polyline, LEFT, (-2), 0, BOTTOM, 0, 0); + _kRenderingExtensions.to(polyline, LEFT, 2, 0, TOP, 0, 0); + container.getChildren().add(polyline); + + polyline = _kRenderingFactory.createKPolyline(); + _kRenderingExtensions.from(polyline, RIGHT, 2, 0, BOTTOM, 0, 0); + _kRenderingExtensions.to(polyline, RIGHT, (-2), 0, TOP, 0, 0); + container.getChildren().add(polyline); + + return padding; } - }); - _onEdgeDelayLabelConfigurator = configurator; - } - _onEdgeDelayLabelConfigurator.applyTo(label); + }); + _onEdgeDelayLabelConfigurator = configurator; } + _onEdgeDelayLabelConfigurator.applyTo(label); + } - private static LabelDecorationConfigurator _onEdgePysicalDelayLabelConfigurator; // ONLY for use in applyOnEdgePysicalDelayStyle - public void applyOnEdgePysicalDelayStyle(KLabel label, Colors parentBackgroundColor) { - if (_onEdgePysicalDelayLabelConfigurator == null) { - LabelDecorationConfigurator configurator = LabelDecorationConfigurator.create().withInlineLabels(true); - configurator = configurator.withLabelTextRenderingProvider( - (KContainerRendering container, KLabel klabel) -> { - KText kText = _kRenderingFactory.createKText(); - _kRenderingExtensions.setFontSize(kText, 8); - boldTextSelectionStyle(kText); - kText.setProperty(KlighdInternalProperties.MODEL_ELEMEMT, - klabel.getProperty(KlighdInternalProperties.MODEL_ELEMEMT)); - container.getChildren().add(kText); - return kText; - } - ); - configurator = configurator.addDecoratorRenderingProvider(new IDecoratorRenderingProvider() { + private static LabelDecorationConfigurator + _onEdgePysicalDelayLabelConfigurator; // ONLY for use in applyOnEdgePysicalDelayStyle + + public void applyOnEdgePysicalDelayStyle(KLabel label, Colors parentBackgroundColor) { + if (_onEdgePysicalDelayLabelConfigurator == null) { + LabelDecorationConfigurator configurator = + LabelDecorationConfigurator.create().withInlineLabels(true); + configurator = + configurator.withLabelTextRenderingProvider( + (KContainerRendering container, KLabel klabel) -> { + KText kText = _kRenderingFactory.createKText(); + _kRenderingExtensions.setFontSize(kText, 8); + boldTextSelectionStyle(kText); + kText.setProperty( + KlighdInternalProperties.MODEL_ELEMEMT, + klabel.getProperty(KlighdInternalProperties.MODEL_ELEMEMT)); + container.getChildren().add(kText); + return kText; + }); + configurator = + configurator.addDecoratorRenderingProvider( + new IDecoratorRenderingProvider() { @Override public ElkPadding createDecoratorRendering( - KContainerRendering container, - KLabel label, - LabelDecorationConfigurator.LayoutMode layoutMode) { - ElkPadding padding = new ElkPadding(); - padding.top = 1; - padding.bottom = 1; - padding.left = 8; - padding.right = 16; - - KPolygon polygon = _kRenderingFactory.createKPolygon(); - _kRenderingExtensions.from(polygon, LEFT, 0, 0, BOTTOM, 0, 0.5f); - _kRenderingExtensions.to(polygon, LEFT, 0, 0, TOP, 1, 0.5f); - _kRenderingExtensions.to(polygon, RIGHT, 0, 0, TOP, 1, 0.5f); - _kRenderingExtensions.to(polygon, RIGHT, 0, 0, BOTTOM, 0, 0.5f); - _kRenderingExtensions.setBackground(polygon, label.getProperty(LABEL_PARENT_BACKGROUND)); - _kRenderingExtensions.setForeground(polygon, label.getProperty(LABEL_PARENT_BACKGROUND)); - container.getChildren().add(polygon); - - KSpline kSpline = _kRenderingFactory.createKSpline(); - _kRenderingExtensions.from(kSpline, LEFT, -0.66f, 0, BOTTOM, -0.5f, 0.5f); - _kRenderingExtensions.to(kSpline, LEFT, 1, 0, BOTTOM, -0.5f, 0.5f); - _kRenderingExtensions.to(kSpline, LEFT, 3, 0, BOTTOM, 8, 0.5f); - _kRenderingExtensions.to(kSpline, LEFT, 5, 0, BOTTOM, 0, 0.5f); - _kRenderingExtensions.to(kSpline, LEFT, 5.5f, 0, BOTTOM, -1.5f, 0.5f); - container.getChildren().add(kSpline); - - kSpline = _kRenderingFactory.createKSpline(); - _kRenderingExtensions.from(kSpline, RIGHT, 15f, 0, BOTTOM, 3.5f, 0.5f); - _kRenderingExtensions.to(kSpline, RIGHT, 14f, 0, BOTTOM, 0, 0.5f); - _kRenderingExtensions.to(kSpline, RIGHT, 11, 0, BOTTOM, -8, 0.5f); - _kRenderingExtensions.to(kSpline, RIGHT, 9, 0, BOTTOM, 0, 0.5f); - _kRenderingExtensions.to(kSpline, RIGHT, 7, 0, BOTTOM, 8, 0.5f); - _kRenderingExtensions.to(kSpline, RIGHT, 4f, 0, BOTTOM, 2, 0.5f); - _kRenderingExtensions.to(kSpline, RIGHT, 1.5f, 0, BOTTOM, 0.5f, 0.5f); - _kRenderingExtensions.to(kSpline, RIGHT, 0.2f, 0, BOTTOM, -0.5f, 0.5f); - _kRenderingExtensions.to(kSpline, RIGHT, -0.7f, 0, BOTTOM, -0.5f, 0.5f); - container.getChildren().add(kSpline); - - polygon = _kRenderingFactory.createKPolygon(); - _kRenderingExtensions.from(polygon, LEFT, 4, 0, BOTTOM, 0, 0); - _kRenderingExtensions.to(polygon, LEFT, 8, 0, TOP, 0, 0); - _kRenderingExtensions.to(polygon, RIGHT, 12, 0, TOP, 0, 0); - _kRenderingExtensions.to(polygon, RIGHT, 16, 0, BOTTOM, 0, 0); - _kRenderingExtensions.setBackground(polygon, Colors.WHITE); - _kRenderingExtensions.setForeground(polygon, Colors.WHITE); - container.getChildren().add(polygon); - - KPolyline polyline = _kRenderingFactory.createKPolyline(); - _kRenderingExtensions.from(polyline, LEFT, 4, 0, BOTTOM, 0, 0); - _kRenderingExtensions.to(polyline, LEFT, 8, 0, TOP, 0, 0); - container.getChildren().add(polyline); - - polyline = _kRenderingFactory.createKPolyline(); - _kRenderingExtensions.from(polyline, RIGHT, 16, 0, BOTTOM, 0, 0); - _kRenderingExtensions.to(polyline, RIGHT, 12, 0, TOP, 0, 0); - container.getChildren().add(polyline); - - return padding; + KContainerRendering container, + KLabel label, + LabelDecorationConfigurator.LayoutMode layoutMode) { + ElkPadding padding = new ElkPadding(); + padding.top = 1; + padding.bottom = 1; + padding.left = 8; + padding.right = 16; + + KPolygon polygon = _kRenderingFactory.createKPolygon(); + _kRenderingExtensions.from(polygon, LEFT, 0, 0, BOTTOM, 0, 0.5f); + _kRenderingExtensions.to(polygon, LEFT, 0, 0, TOP, 1, 0.5f); + _kRenderingExtensions.to(polygon, RIGHT, 0, 0, TOP, 1, 0.5f); + _kRenderingExtensions.to(polygon, RIGHT, 0, 0, BOTTOM, 0, 0.5f); + _kRenderingExtensions.setBackground( + polygon, label.getProperty(LABEL_PARENT_BACKGROUND)); + _kRenderingExtensions.setForeground( + polygon, label.getProperty(LABEL_PARENT_BACKGROUND)); + container.getChildren().add(polygon); + + KSpline kSpline = _kRenderingFactory.createKSpline(); + _kRenderingExtensions.from(kSpline, LEFT, -0.66f, 0, BOTTOM, -0.5f, 0.5f); + _kRenderingExtensions.to(kSpline, LEFT, 1, 0, BOTTOM, -0.5f, 0.5f); + _kRenderingExtensions.to(kSpline, LEFT, 3, 0, BOTTOM, 8, 0.5f); + _kRenderingExtensions.to(kSpline, LEFT, 5, 0, BOTTOM, 0, 0.5f); + _kRenderingExtensions.to(kSpline, LEFT, 5.5f, 0, BOTTOM, -1.5f, 0.5f); + container.getChildren().add(kSpline); + + kSpline = _kRenderingFactory.createKSpline(); + _kRenderingExtensions.from(kSpline, RIGHT, 15f, 0, BOTTOM, 3.5f, 0.5f); + _kRenderingExtensions.to(kSpline, RIGHT, 14f, 0, BOTTOM, 0, 0.5f); + _kRenderingExtensions.to(kSpline, RIGHT, 11, 0, BOTTOM, -8, 0.5f); + _kRenderingExtensions.to(kSpline, RIGHT, 9, 0, BOTTOM, 0, 0.5f); + _kRenderingExtensions.to(kSpline, RIGHT, 7, 0, BOTTOM, 8, 0.5f); + _kRenderingExtensions.to(kSpline, RIGHT, 4f, 0, BOTTOM, 2, 0.5f); + _kRenderingExtensions.to(kSpline, RIGHT, 1.5f, 0, BOTTOM, 0.5f, 0.5f); + _kRenderingExtensions.to(kSpline, RIGHT, 0.2f, 0, BOTTOM, -0.5f, 0.5f); + _kRenderingExtensions.to(kSpline, RIGHT, -0.7f, 0, BOTTOM, -0.5f, 0.5f); + container.getChildren().add(kSpline); + + polygon = _kRenderingFactory.createKPolygon(); + _kRenderingExtensions.from(polygon, LEFT, 4, 0, BOTTOM, 0, 0); + _kRenderingExtensions.to(polygon, LEFT, 8, 0, TOP, 0, 0); + _kRenderingExtensions.to(polygon, RIGHT, 12, 0, TOP, 0, 0); + _kRenderingExtensions.to(polygon, RIGHT, 16, 0, BOTTOM, 0, 0); + _kRenderingExtensions.setBackground(polygon, Colors.WHITE); + _kRenderingExtensions.setForeground(polygon, Colors.WHITE); + container.getChildren().add(polygon); + + KPolyline polyline = _kRenderingFactory.createKPolyline(); + _kRenderingExtensions.from(polyline, LEFT, 4, 0, BOTTOM, 0, 0); + _kRenderingExtensions.to(polyline, LEFT, 8, 0, TOP, 0, 0); + container.getChildren().add(polyline); + + polyline = _kRenderingFactory.createKPolyline(); + _kRenderingExtensions.from(polyline, RIGHT, 16, 0, BOTTOM, 0, 0); + _kRenderingExtensions.to(polyline, RIGHT, 12, 0, TOP, 0, 0); + container.getChildren().add(polyline); + + return padding; } - }); - _onEdgePysicalDelayLabelConfigurator = configurator; - } - label.setProperty(LABEL_PARENT_BACKGROUND, parentBackgroundColor); - _onEdgePysicalDelayLabelConfigurator.applyTo(label); + }); + _onEdgePysicalDelayLabelConfigurator = configurator; } - - private static LabelDecorationConfigurator _onEdgePysicalLabelConfigurator; // ONLY for use in applyOnEdgePysicalStyle - public void applyOnEdgePysicalStyle(KLabel label, Colors parentBackgroundColor) { - if (_onEdgePysicalLabelConfigurator == null) { - LabelDecorationConfigurator configurator = LabelDecorationConfigurator.create().withInlineLabels(true); - configurator = configurator.withLabelTextRenderingProvider( - (KContainerRendering container, KLabel klabel) -> { - KText kText = _kRenderingFactory.createKText(); - _kRenderingExtensions.setInvisible(kText, true); - container.getChildren().add(kText); - return kText; - } - ); - configurator = configurator.addDecoratorRenderingProvider(new IDecoratorRenderingProvider() { + label.setProperty(LABEL_PARENT_BACKGROUND, parentBackgroundColor); + _onEdgePysicalDelayLabelConfigurator.applyTo(label); + } + + private static LabelDecorationConfigurator + _onEdgePysicalLabelConfigurator; // ONLY for use in applyOnEdgePysicalStyle + + public void applyOnEdgePysicalStyle(KLabel label, Colors parentBackgroundColor) { + if (_onEdgePysicalLabelConfigurator == null) { + LabelDecorationConfigurator configurator = + LabelDecorationConfigurator.create().withInlineLabels(true); + configurator = + configurator.withLabelTextRenderingProvider( + (KContainerRendering container, KLabel klabel) -> { + KText kText = _kRenderingFactory.createKText(); + _kRenderingExtensions.setInvisible(kText, true); + container.getChildren().add(kText); + return kText; + }); + configurator = + configurator.addDecoratorRenderingProvider( + new IDecoratorRenderingProvider() { @Override - public ElkPadding createDecoratorRendering(final KContainerRendering container, final KLabel label, final LabelDecorationConfigurator.LayoutMode layoutMode) { - ElkPadding padding = new ElkPadding(); - padding.top = 1; - padding.bottom = 1; - padding.left = 3; - padding.right = 3; - - KPolygon polygon = _kRenderingFactory.createKPolygon(); - _kRenderingExtensions.from(polygon, LEFT, 0, 0, BOTTOM, 0, 0.5f); - _kRenderingExtensions.to(polygon, LEFT, 0, 0, TOP, 1, 0.5f); - _kRenderingExtensions.to(polygon, RIGHT, 0, 0, TOP, 1, 0.5f); - _kRenderingExtensions.to(polygon, RIGHT, 0, 0, BOTTOM, 0, 0.5f); - _kRenderingExtensions.setBackground(polygon, label.getProperty(LABEL_PARENT_BACKGROUND)); - _kRenderingExtensions.setForeground(polygon, label.getProperty(LABEL_PARENT_BACKGROUND)); - container.getChildren().add(polygon); - - KSpline kSpline = _kRenderingFactory.createKSpline(); - _kRenderingExtensions.from(kSpline, LEFT, (-0.66f), 0, BOTTOM, (-0.5f), 0.5f); - _kRenderingExtensions.to(kSpline, LEFT, 1, 0, BOTTOM, (-0.5f), 0.5f); - _kRenderingExtensions.to(kSpline, LEFT, 0, 0.1f, BOTTOM, 8, 0.5f); - _kRenderingExtensions.to(kSpline, LEFT, 0, 0.2f, BOTTOM, 0, 0.5f); - _kRenderingExtensions.to(kSpline, LEFT, 0, 0.3f, BOTTOM, (-8), 0.5f); - _kRenderingExtensions.to(kSpline, LEFT, 0, 0.4f, BOTTOM, 0, 0.5f); - _kRenderingExtensions.to(kSpline, LEFT, 0, 0.45f, BOTTOM, 4f, 0.5f); - _kRenderingExtensions.to(kSpline, LEFT, 0, 0.5f, BOTTOM, 8, 0.5f); - _kRenderingExtensions.to(kSpline, LEFT, 0, 0.55f, BOTTOM, 4f, 0.5f); - _kRenderingExtensions.to(kSpline, LEFT, 0, 0.6f, BOTTOM, 0, 0.5f); - _kRenderingExtensions.to(kSpline, LEFT, 0, 0.65f, BOTTOM, (-4), 0.5f); - _kRenderingExtensions.to(kSpline, LEFT, 0, 0.7f, BOTTOM, (-8), 0.5f); - _kRenderingExtensions.to(kSpline, LEFT, 0, 0.8f, BOTTOM, (-4), 0.5f); - _kRenderingExtensions.to(kSpline, LEFT, 0, 0.9f, BOTTOM, 0, 0.5f); - _kRenderingExtensions.to(kSpline, LEFT, (-1), 1, BOTTOM, (-0.5f), 0.5f); - _kRenderingExtensions.to(kSpline, LEFT, 0.66f, 1, BOTTOM, (-0.5f), 0.5f); - container.getChildren().add(kSpline); - return padding; + public ElkPadding createDecoratorRendering( + final KContainerRendering container, + final KLabel label, + final LabelDecorationConfigurator.LayoutMode layoutMode) { + ElkPadding padding = new ElkPadding(); + padding.top = 1; + padding.bottom = 1; + padding.left = 3; + padding.right = 3; + + KPolygon polygon = _kRenderingFactory.createKPolygon(); + _kRenderingExtensions.from(polygon, LEFT, 0, 0, BOTTOM, 0, 0.5f); + _kRenderingExtensions.to(polygon, LEFT, 0, 0, TOP, 1, 0.5f); + _kRenderingExtensions.to(polygon, RIGHT, 0, 0, TOP, 1, 0.5f); + _kRenderingExtensions.to(polygon, RIGHT, 0, 0, BOTTOM, 0, 0.5f); + _kRenderingExtensions.setBackground( + polygon, label.getProperty(LABEL_PARENT_BACKGROUND)); + _kRenderingExtensions.setForeground( + polygon, label.getProperty(LABEL_PARENT_BACKGROUND)); + container.getChildren().add(polygon); + + KSpline kSpline = _kRenderingFactory.createKSpline(); + _kRenderingExtensions.from(kSpline, LEFT, (-0.66f), 0, BOTTOM, (-0.5f), 0.5f); + _kRenderingExtensions.to(kSpline, LEFT, 1, 0, BOTTOM, (-0.5f), 0.5f); + _kRenderingExtensions.to(kSpline, LEFT, 0, 0.1f, BOTTOM, 8, 0.5f); + _kRenderingExtensions.to(kSpline, LEFT, 0, 0.2f, BOTTOM, 0, 0.5f); + _kRenderingExtensions.to(kSpline, LEFT, 0, 0.3f, BOTTOM, (-8), 0.5f); + _kRenderingExtensions.to(kSpline, LEFT, 0, 0.4f, BOTTOM, 0, 0.5f); + _kRenderingExtensions.to(kSpline, LEFT, 0, 0.45f, BOTTOM, 4f, 0.5f); + _kRenderingExtensions.to(kSpline, LEFT, 0, 0.5f, BOTTOM, 8, 0.5f); + _kRenderingExtensions.to(kSpline, LEFT, 0, 0.55f, BOTTOM, 4f, 0.5f); + _kRenderingExtensions.to(kSpline, LEFT, 0, 0.6f, BOTTOM, 0, 0.5f); + _kRenderingExtensions.to(kSpline, LEFT, 0, 0.65f, BOTTOM, (-4), 0.5f); + _kRenderingExtensions.to(kSpline, LEFT, 0, 0.7f, BOTTOM, (-8), 0.5f); + _kRenderingExtensions.to(kSpline, LEFT, 0, 0.8f, BOTTOM, (-4), 0.5f); + _kRenderingExtensions.to(kSpline, LEFT, 0, 0.9f, BOTTOM, 0, 0.5f); + _kRenderingExtensions.to(kSpline, LEFT, (-1), 1, BOTTOM, (-0.5f), 0.5f); + _kRenderingExtensions.to(kSpline, LEFT, 0.66f, 1, BOTTOM, (-0.5f), 0.5f); + container.getChildren().add(kSpline); + return padding; } - }); - _onEdgePysicalLabelConfigurator = configurator; - } - label.setProperty(LABEL_PARENT_BACKGROUND, parentBackgroundColor); - _onEdgePysicalLabelConfigurator.applyTo(label); + }); + _onEdgePysicalLabelConfigurator = configurator; } + label.setProperty(LABEL_PARENT_BACKGROUND, parentBackgroundColor); + _onEdgePysicalLabelConfigurator.applyTo(label); + } - public KRendering addFixedTailArrowDecorator(KPolyline pl) { - KRendering head = _kPolylineExtensions.addTailArrowDecorator(pl); - KDecoratorPlacementData placement = _kRenderingFactory.createKDecoratorPlacementData(); - placement.setRotateWithLine(true); - placement.setRelative(0f); - placement.setAbsolute(2f); - placement.setWidth(8); - placement.setHeight(6); - placement.setXOffset(-3f); - placement.setYOffset(-4f); - head.setPlacementData(placement); - return head; - } - - public void addArrayDecorator(KEdge edge, Integer size) { - final KRendering line = _kRenderingExtensions.getKRendering(edge); - if (line instanceof KPolyline) { - KDecoratorPlacementData placement = _kRenderingFactory.createKDecoratorPlacementData(); - placement.setRotateWithLine(true); - placement.setRelative(0f); - placement.setAbsolute(6f); - - KPolyline slash = _kContainerRenderingExtensions.addChild( - (KContainerRendering) line, - _kRenderingFactory.createKPolyline()); - slash.getPoints().add( - _kRenderingExtensions.createKPosition( - _kRenderingExtensions.RIGHT, 0, 0, - _kRenderingExtensions.TOP, 0, 0) - ); - slash.getPoints().add( - _kRenderingExtensions.createKPosition( - _kRenderingExtensions.LEFT, 0, 0, - _kRenderingExtensions.BOTTOM, 0, 0) - ); - KDecoratorPlacementData slashPlacement = EcoreUtil.copy(placement); - slashPlacement.setWidth(5); - slashPlacement.setHeight(10); - slashPlacement.setYOffset(-5f); - slash.setPlacementData(slashPlacement); - - if (size != null) { - KText num = _kContainerRenderingExtensions.addChild( - (KContainerRendering) line, - _kRenderingFactory.createKText() - ); - num.setText(size.toString()); - _kRenderingExtensions.setFontSize(num, 5); - noSelectionStyle(num); - KDecoratorPlacementData numPlacement = EcoreUtil.copy(placement); - numPlacement.setXOffset(2f); - num.setPlacementData(numPlacement); - } - } + public KRendering addFixedTailArrowDecorator(KPolyline pl) { + KRendering head = _kPolylineExtensions.addTailArrowDecorator(pl); + KDecoratorPlacementData placement = _kRenderingFactory.createKDecoratorPlacementData(); + placement.setRotateWithLine(true); + placement.setRelative(0f); + placement.setAbsolute(2f); + placement.setWidth(8); + placement.setHeight(6); + placement.setXOffset(-3f); + placement.setYOffset(-4f); + head.setPlacementData(placement); + return head; + } + + public void addArrayDecorator(KEdge edge, Integer size) { + final KRendering line = _kRenderingExtensions.getKRendering(edge); + if (line instanceof KPolyline) { + KDecoratorPlacementData placement = _kRenderingFactory.createKDecoratorPlacementData(); + placement.setRotateWithLine(true); + placement.setRelative(0f); + placement.setAbsolute(6f); + + KPolyline slash = + _kContainerRenderingExtensions.addChild( + (KContainerRendering) line, _kRenderingFactory.createKPolyline()); + slash + .getPoints() + .add( + _kRenderingExtensions.createKPosition( + _kRenderingExtensions.RIGHT, 0, 0, _kRenderingExtensions.TOP, 0, 0)); + slash + .getPoints() + .add( + _kRenderingExtensions.createKPosition( + _kRenderingExtensions.LEFT, 0, 0, _kRenderingExtensions.BOTTOM, 0, 0)); + KDecoratorPlacementData slashPlacement = EcoreUtil.copy(placement); + slashPlacement.setWidth(5); + slashPlacement.setHeight(10); + slashPlacement.setYOffset(-5f); + slash.setPlacementData(slashPlacement); + + if (size != null) { + KText num = + _kContainerRenderingExtensions.addChild( + (KContainerRendering) line, _kRenderingFactory.createKText()); + num.setText(size.toString()); + _kRenderingExtensions.setFontSize(num, 5); + noSelectionStyle(num); + KDecoratorPlacementData numPlacement = EcoreUtil.copy(placement); + numPlacement.setXOffset(2f); + num.setPlacementData(numPlacement); + } } -} \ No newline at end of file + } +} diff --git a/org.lflang/src/org/lflang/diagram/synthesis/styles/ReactorFigureComponents.java b/org.lflang/src/org/lflang/diagram/synthesis/styles/ReactorFigureComponents.java index a7ca735f29..aab539926b 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/styles/ReactorFigureComponents.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/styles/ReactorFigureComponents.java @@ -1,26 +1,24 @@ /** * Copyright (c) 2020, Kiel University. - * - * 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. + * + *

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.diagram.synthesis.styles; @@ -31,81 +29,77 @@ import org.eclipse.xtext.xbase.lib.util.ToStringBuilder; public class ReactorFigureComponents { - private final KContainerRendering outer; + private final KContainerRendering outer; - private final KContainerRendering reactor; + private final KContainerRendering reactor; - private final List figures; + private final List figures; - public ReactorFigureComponents(KContainerRendering outer, - KContainerRendering reactor, - List figures) { - super(); - this.outer = outer; - this.reactor = reactor; - this.figures = figures; - } + public ReactorFigureComponents( + KContainerRendering outer, KContainerRendering reactor, List figures) { + super(); + this.outer = outer; + this.reactor = reactor; + this.figures = figures; + } - @Override - @Pure - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((this.outer== null) ? 0 : this.outer.hashCode()); - result = prime * result + ((this.reactor== null) ? 0 : this.reactor.hashCode()); - return prime * result + ((this.figures== null) ? 0 : this.figures.hashCode()); - } + @Override + @Pure + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.outer == null) ? 0 : this.outer.hashCode()); + result = prime * result + ((this.reactor == null) ? 0 : this.reactor.hashCode()); + return prime * result + ((this.figures == null) ? 0 : this.figures.hashCode()); + } - @Override - @Pure - public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - ReactorFigureComponents other = (ReactorFigureComponents) obj; - if (this.outer == null && other.outer != null) { - return false; - } else if (!this.outer.equals(other.outer)) { - return false; - } - if (this.reactor == null && other.reactor != null) { - return false; - } else if (!this.reactor.equals(other.reactor)) { - return false; - } - if (this.figures == null && other.figures != null) { - return false; - } else if (!this.figures.equals(other.figures)) { - return false; - } - return true; + @Override + @Pure + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + ReactorFigureComponents other = (ReactorFigureComponents) obj; + if (this.outer == null && other.outer != null) { + return false; + } else if (!this.outer.equals(other.outer)) { + return false; } - - @Override - @Pure - public String toString() { - ToStringBuilder b = new ToStringBuilder(this); - b.add("outer", this.outer); - b.add("reactor", this.reactor); - b.add("figures", this.figures); - return b.toString(); + if (this.reactor == null && other.reactor != null) { + return false; + } else if (!this.reactor.equals(other.reactor)) { + return false; } - - @Pure - public KContainerRendering getOuter() { - return this.outer; + if (this.figures == null && other.figures != null) { + return false; + } else if (!this.figures.equals(other.figures)) { + return false; } + return true; + } - @Pure - public KContainerRendering getReactor() { - return this.reactor; - } + @Override + @Pure + public String toString() { + ToStringBuilder b = new ToStringBuilder(this); + b.add("outer", this.outer); + b.add("reactor", this.reactor); + b.add("figures", this.figures); + return b.toString(); + } - @Pure - public List getFigures() { - return this.figures; - } + @Pure + public KContainerRendering getOuter() { + return this.outer; + } + + @Pure + public KContainerRendering getReactor() { + return this.reactor; + } + + @Pure + public List getFigures() { + return this.figures; + } } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/util/CycleVisualization.java b/org.lflang/src/org/lflang/diagram/synthesis/util/CycleVisualization.java index 0fc52b0665..4fb313fe4b 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/util/CycleVisualization.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/util/CycleVisualization.java @@ -1,27 +1,27 @@ /************* -* Copyright (c) 2020, Kiel University. -* -* 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, Kiel University. + * + * 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.diagram.synthesis.util; import com.google.common.collect.HashMultimap; @@ -43,115 +43,114 @@ /** * Dependency cycle detection for Lingua Franca diagrams. - * + * * @author Alexander Schulz-Rosengarten */ @ViewSynthesisShared public class CycleVisualization extends AbstractSynthesisExtensions { - - // Properties for marking diagram elements - public static final Property DEPENDENCY_CYCLE = new Property<>("org.lflang.diagram.synthesis.dependency.cycle", false); - @Inject @Extension private UtilityExtensions _utilityExtensions; - - /** - * Performs cycle detection based on the diagram's graph structure and applies given highlighting to the included elements - */ - public boolean detectAndHighlightCycles(ReactorInstance rootReactorInstance, - Map allReactorNodes, - Consumer highlighter) { - - if (rootReactorInstance.hasCycles() && highlighter != null) { - // Highlight cycles - // A cycle consists of reactions and ports. - HashMultimap> cycleElementsByReactor = HashMultimap.create(); - Set> cycles = rootReactorInstance.getCycles(); - for (NamedInstance element : cycles) { - // First find the involved reactor instances - if (element instanceof ReactorInstance) { - cycleElementsByReactor.put((ReactorInstance) element, element); - } else { - cycleElementsByReactor.put(element.getParent(), element); - } - } - - for (ReactorInstance reactor : cycleElementsByReactor.keySet()) { - KNode node = allReactorNodes.get(reactor); - if (node != null) { - node.setProperty(DEPENDENCY_CYCLE, true); - highlighter.accept(node); + // Properties for marking diagram elements + public static final Property DEPENDENCY_CYCLE = + new Property<>("org.lflang.diagram.synthesis.dependency.cycle", false); + + @Inject @Extension private UtilityExtensions _utilityExtensions; - Set> reactorContentInCycle = cycleElementsByReactor.get(reactor); - - // Reactor edges - for (KEdge edge : node.getOutgoingEdges()) { - if (connectsCycleElements(edge, cycles)) { - edge.setProperty(DEPENDENCY_CYCLE, true); - highlighter.accept(edge); - } - } + /** + * Performs cycle detection based on the diagram's graph structure and applies given highlighting + * to the included elements + */ + public boolean detectAndHighlightCycles( + ReactorInstance rootReactorInstance, + Map allReactorNodes, + Consumer highlighter) { - // Reactor ports - for (KPort port : node.getPorts()) { - if (reactorContentInCycle.contains(NamedInstanceUtil.getLinkedInstance(port))) { - port.setProperty(DEPENDENCY_CYCLE, true); - highlighter.accept(port); - } - } + if (rootReactorInstance.hasCycles() && highlighter != null) { + // Highlight cycles + // A cycle consists of reactions and ports. + HashMultimap> cycleElementsByReactor = + HashMultimap.create(); + Set> cycles = rootReactorInstance.getCycles(); + for (NamedInstance element : cycles) { + // First find the involved reactor instances + if (element instanceof ReactorInstance) { + cycleElementsByReactor.put((ReactorInstance) element, element); + } else { + cycleElementsByReactor.put(element.getParent(), element); + } + } - // Child Nodes - for (KNode childNode : node.getChildren()) { - if (reactorContentInCycle.contains(NamedInstanceUtil.getLinkedInstance(childNode)) && - !_utilityExtensions.sourceIsReactor(childNode)) { - childNode.setProperty(DEPENDENCY_CYCLE, true); - highlighter.accept(childNode); + for (ReactorInstance reactor : cycleElementsByReactor.keySet()) { + KNode node = allReactorNodes.get(reactor); + if (node != null) { + node.setProperty(DEPENDENCY_CYCLE, true); + highlighter.accept(node); + + Set> reactorContentInCycle = cycleElementsByReactor.get(reactor); + + // Reactor edges + for (KEdge edge : node.getOutgoingEdges()) { + if (connectsCycleElements(edge, cycles)) { + edge.setProperty(DEPENDENCY_CYCLE, true); + highlighter.accept(edge); + } + } - for (KEdge edge : childNode.getOutgoingEdges()) { - if (connectsCycleElements(edge, cycles)) { - edge.setProperty(DEPENDENCY_CYCLE, true); - highlighter.accept(edge); - } - } - } - } + // Reactor ports + for (KPort port : node.getPorts()) { + if (reactorContentInCycle.contains(NamedInstanceUtil.getLinkedInstance(port))) { + port.setProperty(DEPENDENCY_CYCLE, true); + highlighter.accept(port); + } + } + + // Child Nodes + for (KNode childNode : node.getChildren()) { + if (reactorContentInCycle.contains(NamedInstanceUtil.getLinkedInstance(childNode)) + && !_utilityExtensions.sourceIsReactor(childNode)) { + childNode.setProperty(DEPENDENCY_CYCLE, true); + highlighter.accept(childNode); + + for (KEdge edge : childNode.getOutgoingEdges()) { + if (connectsCycleElements(edge, cycles)) { + edge.setProperty(DEPENDENCY_CYCLE, true); + highlighter.accept(edge); } + } } - return true; - } - return false; - } - - /** - * Checks whether an edge connects two elements that are part of the cycle. - * Assumes that the source node is always part of the cycle! - */ - private boolean connectsCycleElements(KEdge edge, Set> cycle) { - return ( - // if source is not a reactor, there is nothing to check - !_utilityExtensions.sourceIsReactor(edge.getSource()) - || - // otherwise, the source port must be on the cycle - cycle.contains(NamedInstanceUtil.getLinkedInstance(edge.getSourcePort())) - ) && ( - // leads to reactor port in cycle - _utilityExtensions.sourceIsReactor(edge.getTarget()) - && - cycle.contains(NamedInstanceUtil.getLinkedInstance(edge.getTargetPort())) - || - // leads to reaction in cycle - !_utilityExtensions.sourceIsReactor(edge.getTarget()) - && - cycle.contains(NamedInstanceUtil.getLinkedInstance(edge.getTarget())) - ) && ( - // Special case only for connections - !(_utilityExtensions.sourceElement(edge) instanceof Connection) - || ( - // If the edge represents a connections between two ports in the cycle (checked before), - // then it is only included in the actual cycle, if it is neither delayed nor physical. - ((Connection) _utilityExtensions.sourceElement(edge)).getDelay() == null - && - !((Connection) _utilityExtensions.sourceElement(edge)).isPhysical() - ) - ); + } + } + } + return true; } + return false; + } + + /** + * Checks whether an edge connects two elements that are part of the cycle. Assumes that the + * source node is always part of the cycle! + */ + private boolean connectsCycleElements(KEdge edge, Set> cycle) { + return ( + // if source is not a reactor, there is nothing to check + !_utilityExtensions.sourceIsReactor(edge.getSource()) + || + // otherwise, the source port must be on the cycle + cycle.contains(NamedInstanceUtil.getLinkedInstance(edge.getSourcePort()))) + && ( + // leads to reactor port in cycle + _utilityExtensions.sourceIsReactor(edge.getTarget()) + && cycle.contains(NamedInstanceUtil.getLinkedInstance(edge.getTargetPort())) + || + // leads to reaction in cycle + !_utilityExtensions.sourceIsReactor(edge.getTarget()) + && cycle.contains(NamedInstanceUtil.getLinkedInstance(edge.getTarget()))) + && ( + // Special case only for connections + !(_utilityExtensions.sourceElement(edge) instanceof Connection) + || ( + // If the edge represents a connections between two ports in the cycle (checked before), + // then it is only included in the actual cycle, if it is neither delayed nor physical. + ((Connection) _utilityExtensions.sourceElement(edge)).getDelay() == null + && !((Connection) _utilityExtensions.sourceElement(edge)).isPhysical())); + } } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/util/InterfaceDependenciesVisualization.java b/org.lflang/src/org/lflang/diagram/synthesis/util/InterfaceDependenciesVisualization.java index 41635fb7c7..e3de923847 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/util/InterfaceDependenciesVisualization.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/util/InterfaceDependenciesVisualization.java @@ -1,27 +1,27 @@ /************* -* Copyright (c) 2020, Kiel University. -* -* 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, Kiel University. + * + * 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.diagram.synthesis.util; import com.google.common.collect.ImmutableList; @@ -64,143 +64,177 @@ /** * Utility class to handle dependency edges for collapsed reactors in Lingua Franca diagrams. - * + * * @author Alexander Schulz-Rosengarten */ @ViewSynthesisShared public class InterfaceDependenciesVisualization extends AbstractSynthesisExtensions { - - // Related synthesis option - public static final SynthesisOption SHOW_INTERFACE_DEPENDENCIES = SynthesisOption.createCheckOption("Port Dependencies in Collapsed Reactors", false).setCategory(LinguaFrancaSynthesis.APPEARANCE); - - // Properties for marking diagram elements - public static final Property INTERFACE_DEPENDENCY = new Property<>("org.lflang.linguafranca.diagram.synthesis.dependency.interface", false); - - @Inject @Extension private KEdgeExtensions _kEdgeExtensions; - @Inject @Extension private KRenderingExtensions _kRenderingExtensions; - @Inject @Extension private KContainerRenderingExtensions _kContainerRenderingExtensions; - @Inject @Extension private LinguaFrancaStyleExtensions _linguaFrancaStyleExtensions; - @Inject @Extension private UtilityExtensions _utilityExtensions; - - /** - * Updates the visibility of interface dependencies edges based on the expansion state. - */ - public static void updateInterfaceDependencyVisibility(KNode node, boolean expanded) { - Iterable edges = IterableExtensions.filter(node.getOutgoingEdges(), it -> { - return it.getProperty(INTERFACE_DEPENDENCY); - }); - - Iterable> renders = IterableExtensions.map(edges, (KEdge it) -> { - return Iterables.filter(it.getData(), KRendering.class); - }); - - Iterables.concat(renders).forEach( + + // Related synthesis option + public static final SynthesisOption SHOW_INTERFACE_DEPENDENCIES = + SynthesisOption.createCheckOption("Port Dependencies in Collapsed Reactors", false) + .setCategory(LinguaFrancaSynthesis.APPEARANCE); + + // Properties for marking diagram elements + public static final Property INTERFACE_DEPENDENCY = + new Property<>("org.lflang.linguafranca.diagram.synthesis.dependency.interface", false); + + @Inject @Extension private KEdgeExtensions _kEdgeExtensions; + @Inject @Extension private KRenderingExtensions _kRenderingExtensions; + @Inject @Extension private KContainerRenderingExtensions _kContainerRenderingExtensions; + @Inject @Extension private LinguaFrancaStyleExtensions _linguaFrancaStyleExtensions; + @Inject @Extension private UtilityExtensions _utilityExtensions; + + /** Updates the visibility of interface dependencies edges based on the expansion state. */ + public static void updateInterfaceDependencyVisibility(KNode node, boolean expanded) { + Iterable edges = + IterableExtensions.filter( + node.getOutgoingEdges(), it -> { - KInvisibility inv = IterableExtensions.last(Iterables.filter(it.getStyles(), KInvisibility.class)); - if (inv == null) { - inv = KRenderingFactory.eINSTANCE.createKInvisibility(); - it.getStyles().add(inv); - } - inv.setInvisible(expanded); - } - ); - } + return it.getProperty(INTERFACE_DEPENDENCY); + }); + + Iterable> renders = + IterableExtensions.map( + edges, + (KEdge it) -> { + return Iterables.filter(it.getData(), KRendering.class); + }); + + Iterables.concat(renders) + .forEach( + it -> { + KInvisibility inv = + IterableExtensions.last(Iterables.filter(it.getStyles(), KInvisibility.class)); + if (inv == null) { + inv = KRenderingFactory.eINSTANCE.createKInvisibility(); + it.getStyles().add(inv); + } + inv.setInvisible(expanded); + }); + } + + /** + * Adds interface dependencies to the node if this option is active. Visibility will be adjusted + * based on expansion state. + */ + public Spacing addInterfaceDependencies(KNode node, boolean expanded) { + Spacing marginInit = null; + if (getBooleanValue(SHOW_INTERFACE_DEPENDENCIES)) { + List> deps = getPortDependencies(node); + if (!deps.isEmpty()) { + for (Pair pair : deps) { + createDependencyEdge(pair, expanded); + } - /** - * Adds interface dependencies to the node if this option is active. - * Visibility will be adjusted based on expansion state. - */ - public Spacing addInterfaceDependencies(KNode node, boolean expanded) { - Spacing marginInit = null; - if (getBooleanValue(SHOW_INTERFACE_DEPENDENCIES)) { - List> deps = getPortDependencies(node); - if (!deps.isEmpty()) { - for (Pair pair : deps) { - createDependencyEdge(pair, expanded); - } - - // Fix content (label) of collapsed rendering - KContainerRendering contentContainer = IterableExtensions.findFirst( - Iterables.filter(node.getData(), KContainerRendering.class), - it -> { return it.getProperty(KlighdProperties.COLLAPSED_RENDERING); } - ); - if (contentContainer != null) { - if (!contentContainer.getProperty(LinguaFrancaShapeExtensions.REACTOR_CONTENT_CONTAINER)) { - contentContainer = IteratorExtensions.findFirst( - Iterators.filter(contentContainer.eAllContents(), KContainerRendering.class), - it -> { return it.getProperty(LinguaFrancaShapeExtensions.REACTOR_CONTENT_CONTAINER); } - ); - } - if (contentContainer != null) { - List content = ImmutableList.copyOf(contentContainer.getChildren()); - // Put into two new containers such that they are not centered/maximized - KRectangle firstContainer = _kContainerRenderingExtensions.addRectangle(contentContainer); - _kRenderingExtensions.setInvisible(firstContainer, true); - - KRectangle secondContainer = _kContainerRenderingExtensions.addRectangle(firstContainer); - _kRenderingExtensions.setInvisible(secondContainer, true); - _kContainerRenderingExtensions.setGridPlacement(secondContainer, 1); - Iterables.addAll(secondContainer.getChildren(), content); - _kRenderingExtensions.setPointPlacementData(secondContainer, - _kRenderingExtensions.LEFT, 0, 0.5f, - _kRenderingExtensions.TOP, 0, 0, - _kRenderingExtensions.H_CENTRAL, _kRenderingExtensions.V_TOP, 0, - 0, 0, 0); - - - // Adjust ports separate dependency edges from label/content - if (content.size() > 0) { - marginInit = _utilityExtensions.getPortMarginsInitIfAbsent(node).add( - new ElkMargin((content.size() * 20) - 8, 0, 0, 0) - ); - } - } - } + // Fix content (label) of collapsed rendering + KContainerRendering contentContainer = + IterableExtensions.findFirst( + Iterables.filter(node.getData(), KContainerRendering.class), + it -> { + return it.getProperty(KlighdProperties.COLLAPSED_RENDERING); + }); + if (contentContainer != null) { + if (!contentContainer.getProperty( + LinguaFrancaShapeExtensions.REACTOR_CONTENT_CONTAINER)) { + contentContainer = + IteratorExtensions.findFirst( + Iterators.filter(contentContainer.eAllContents(), KContainerRendering.class), + it -> { + return it.getProperty(LinguaFrancaShapeExtensions.REACTOR_CONTENT_CONTAINER); + }); + } + if (contentContainer != null) { + List content = ImmutableList.copyOf(contentContainer.getChildren()); + // Put into two new containers such that they are not centered/maximized + KRectangle firstContainer = + _kContainerRenderingExtensions.addRectangle(contentContainer); + _kRenderingExtensions.setInvisible(firstContainer, true); + + KRectangle secondContainer = + _kContainerRenderingExtensions.addRectangle(firstContainer); + _kRenderingExtensions.setInvisible(secondContainer, true); + _kContainerRenderingExtensions.setGridPlacement(secondContainer, 1); + Iterables.addAll(secondContainer.getChildren(), content); + _kRenderingExtensions.setPointPlacementData( + secondContainer, + _kRenderingExtensions.LEFT, + 0, + 0.5f, + _kRenderingExtensions.TOP, + 0, + 0, + _kRenderingExtensions.H_CENTRAL, + _kRenderingExtensions.V_TOP, + 0, + 0, + 0, + 0); + + // Adjust ports separate dependency edges from label/content + if (content.size() > 0) { + marginInit = + _utilityExtensions + .getPortMarginsInitIfAbsent(node) + .add(new ElkMargin((content.size() * 20) - 8, 0, 0, 0)); } + } } - return marginInit; - } - - /** - * Find dependencies between ports. - */ - private List> getPortDependencies(KNode node) { - Set inputPorts = IterableExtensions.toSet(IterableExtensions.filter( - node.getPorts(), - it -> { return it.getProperty(LinguaFrancaSynthesis.REACTOR_INPUT); }) - ); - Set outputPorts = IterableExtensions.toSet(IterableExtensions.filter( - node.getPorts(), - it -> { return it.getProperty(LinguaFrancaSynthesis.REACTOR_OUTPUT); }) - ); - - // FIXME Replace with real logic - Random rand = new Random(); - return IterableExtensions.toList( - IterableExtensions.map(IterableExtensions.filter( - Sets.cartesianProduct(inputPorts, outputPorts), - it -> { return rand.nextBoolean(); }), - it -> { return new Pair(it.get(0), it.get(1)); })); - } - - /** - * Create an edges for interface dependencies and adjust visibility based on the expansion state of the node. - */ - private KEdge createDependencyEdge(final Pair connection, final boolean expanded) { - KEdge depEdge = _kEdgeExtensions.createEdge(); - depEdge.setSource(connection.getKey().getNode()); - depEdge.setSourcePort(connection.getKey()); - depEdge.setTarget(connection.getValue().getNode()); - depEdge.setTargetPort(connection.getValue()); - depEdge.setProperty(InterfaceDependenciesVisualization.INTERFACE_DEPENDENCY, true); - depEdge.setProperty(CoreOptions.NO_LAYOUT, true); // no routing! - DiagramSyntheses.suppressSelectability(depEdge); - - KPolyline polyline = _kEdgeExtensions.addPolyline(depEdge); - _kRenderingExtensions.setLineStyle(polyline, LineStyle.DOT); - _linguaFrancaStyleExtensions.noSelectionStyle(polyline); - _kRenderingExtensions.setInvisible(polyline, expanded); // make sure there is a style to toggle! - - return depEdge; + } } + return marginInit; + } + + /** Find dependencies between ports. */ + private List> getPortDependencies(KNode node) { + Set inputPorts = + IterableExtensions.toSet( + IterableExtensions.filter( + node.getPorts(), + it -> { + return it.getProperty(LinguaFrancaSynthesis.REACTOR_INPUT); + })); + Set outputPorts = + IterableExtensions.toSet( + IterableExtensions.filter( + node.getPorts(), + it -> { + return it.getProperty(LinguaFrancaSynthesis.REACTOR_OUTPUT); + })); + + // FIXME Replace with real logic + Random rand = new Random(); + return IterableExtensions.toList( + IterableExtensions.map( + IterableExtensions.filter( + Sets.cartesianProduct(inputPorts, outputPorts), + it -> { + return rand.nextBoolean(); + }), + it -> { + return new Pair(it.get(0), it.get(1)); + })); + } + + /** + * Create an edges for interface dependencies and adjust visibility based on the expansion state + * of the node. + */ + private KEdge createDependencyEdge(final Pair connection, final boolean expanded) { + KEdge depEdge = _kEdgeExtensions.createEdge(); + depEdge.setSource(connection.getKey().getNode()); + depEdge.setSourcePort(connection.getKey()); + depEdge.setTarget(connection.getValue().getNode()); + depEdge.setTargetPort(connection.getValue()); + depEdge.setProperty(InterfaceDependenciesVisualization.INTERFACE_DEPENDENCY, true); + depEdge.setProperty(CoreOptions.NO_LAYOUT, true); // no routing! + DiagramSyntheses.suppressSelectability(depEdge); + + KPolyline polyline = _kEdgeExtensions.addPolyline(depEdge); + _kRenderingExtensions.setLineStyle(polyline, LineStyle.DOT); + _linguaFrancaStyleExtensions.noSelectionStyle(polyline); + _kRenderingExtensions.setInvisible(polyline, expanded); // make sure there is a style to toggle! + + return depEdge; + } } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/util/LayoutPostProcessing.java b/org.lflang/src/org/lflang/diagram/synthesis/util/LayoutPostProcessing.java index e3264c4b75..33a9b02167 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/util/LayoutPostProcessing.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/util/LayoutPostProcessing.java @@ -1,33 +1,36 @@ /************* -* Copyright (c) 2022, Kiel University. -* -* 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) 2022, Kiel University. + * + * 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.diagram.synthesis.util; +import de.cau.cs.kieler.klighd.SynthesisOption; +import de.cau.cs.kieler.klighd.kgraph.KNode; +import de.cau.cs.kieler.klighd.krendering.ViewSynthesisShared; +import de.cau.cs.kieler.klighd.syntheses.DiagramSyntheses; import java.util.Arrays; import java.util.Comparator; import java.util.List; - import org.eclipse.elk.alg.layered.components.ComponentOrderingStrategy; import org.eclipse.elk.alg.layered.options.CrossingMinimizationStrategy; import org.eclipse.elk.alg.layered.options.CycleBreakingStrategy; @@ -41,326 +44,392 @@ import org.lflang.diagram.synthesis.LinguaFrancaSynthesis; import org.lflang.generator.TriggerInstance.BuiltinTriggerVariable; -import de.cau.cs.kieler.klighd.SynthesisOption; -import de.cau.cs.kieler.klighd.kgraph.KNode; -import de.cau.cs.kieler.klighd.krendering.ViewSynthesisShared; -import de.cau.cs.kieler.klighd.syntheses.DiagramSyntheses; - /** * Set layout configuration options for the Lingua Franca diagram synthesis. - * + * * @author Sören Domrös */ @ViewSynthesisShared public class LayoutPostProcessing extends AbstractSynthesisExtensions { - /** Synthesis option to control the order of nodes and edges by model order. */ - public static final String MODEL_ORDER_OPTION = "Model Order"; - /** Uses semi-automatic layout. */ - public static final String LEGACY = "Legacy"; - /** Only reactions are strictly ordered by their model order. */ - public static final String STRICT_REACTION_ONLY = "Reactions Only"; - /** Reactions and reactor are strictly ordered by their model order. */ - public static final String STRICT = "Reactions and Reactors"; - /** Reactions and reactors are ordered by their model order if no additional crossing are created. */ - public static final String TIE_BREAKER = "Optimize Crossings"; - /** - * No crossing minimization is done at all. This requires that actions and timers are sorted based on their model - * order. - */ - public static final String FULL_CONTROL = "Full Control"; - - - public static final SynthesisOption MODEL_ORDER = - SynthesisOption.createChoiceOption( - MODEL_ORDER_OPTION, - Arrays.asList(TIE_BREAKER, STRICT_REACTION_ONLY, STRICT, FULL_CONTROL), - STRICT_REACTION_ONLY).setCategory(LinguaFrancaSynthesis.LAYOUT); + /** Synthesis option to control the order of nodes and edges by model order. */ + public static final String MODEL_ORDER_OPTION = "Model Order"; + /** Uses semi-automatic layout. */ + public static final String LEGACY = "Legacy"; + /** Only reactions are strictly ordered by their model order. */ + public static final String STRICT_REACTION_ONLY = "Reactions Only"; + /** Reactions and reactor are strictly ordered by their model order. */ + public static final String STRICT = "Reactions and Reactors"; + /** + * Reactions and reactors are ordered by their model order if no additional crossing are created. + */ + public static final String TIE_BREAKER = "Optimize Crossings"; + /** + * No crossing minimization is done at all. This requires that actions and timers are sorted based + * on their model order. + */ + public static final String FULL_CONTROL = "Full Control"; - /** - * Comparator to sort KNodes based on the textual order of their linked instances. - * - * Startup, reset and shutdown actions are not in the model and are handled separately: - * Startup actions will always be first. - * Reset actions follow after the startup action. - * Shutdown is always sorted last. However, shutdown actions will not have a model order set and are, therefore, - * implicitly ordered by their connection. - */ - public static final Comparator TEXTUAL_ORDER = new Comparator() { + public static final SynthesisOption MODEL_ORDER = + SynthesisOption.createChoiceOption( + MODEL_ORDER_OPTION, + Arrays.asList(TIE_BREAKER, STRICT_REACTION_ONLY, STRICT, FULL_CONTROL), + STRICT_REACTION_ONLY) + .setCategory(LinguaFrancaSynthesis.LAYOUT); + + /** + * Comparator to sort KNodes based on the textual order of their linked instances. + * + *

Startup, reset and shutdown actions are not in the model and are handled separately: Startup + * actions will always be first. Reset actions follow after the startup action. Shutdown is always + * sorted last. However, shutdown actions will not have a model order set and are, therefore, + * implicitly ordered by their connection. + */ + public static final Comparator TEXTUAL_ORDER = + new Comparator() { @Override public int compare(KNode node1, KNode node2) { - var pos1 = getTextPosition(node1); - var pos2 = getTextPosition(node2); - if (pos1 >= 0 && pos1 >= 0) { - return Integer.compare(pos1, pos2); // textual order - } else if (pos1 >= 0) { - return -1; // unassociated elements last - } else if (pos2 >= 0) { - return 1; // unassociated elements last - } - return Integer.compare(node1.hashCode(), node2.hashCode()); // any stable order between unassociated elements + var pos1 = getTextPosition(node1); + var pos2 = getTextPosition(node2); + if (pos1 >= 0 && pos1 >= 0) { + return Integer.compare(pos1, pos2); // textual order + } else if (pos1 >= 0) { + return -1; // unassociated elements last + } else if (pos2 >= 0) { + return 1; // unassociated elements last + } + return Integer.compare( + node1.hashCode(), node2.hashCode()); // any stable order between unassociated elements } - + private int getTextPosition(KNode node) { - var instance = NamedInstanceUtil.getLinkedInstance(node); - if (instance != null) { - var definition = instance.getDefinition(); - if (definition instanceof BuiltinTriggerVariable) { - // special handling for built-in triggers - switch(((BuiltinTriggerVariable)definition).type) { - case STARTUP: return 0; // first - case RESET: return 1; // second - case SHUTDOWN: return Integer.MAX_VALUE; // last - } - } else if (definition instanceof EObject) { - var ast = NodeModelUtils.getNode((EObject) definition); - if (ast != null) { - return ast.getOffset(); - } - } + var instance = NamedInstanceUtil.getLinkedInstance(node); + if (instance != null) { + var definition = instance.getDefinition(); + if (definition instanceof BuiltinTriggerVariable) { + // special handling for built-in triggers + switch (((BuiltinTriggerVariable) definition).type) { + case STARTUP: + return 0; // first + case RESET: + return 1; // second + case SHUTDOWN: + return Integer.MAX_VALUE; // last + } + } else if (definition instanceof EObject) { + var ast = NodeModelUtils.getNode((EObject) definition); + if (ast != null) { + return ast.getOffset(); + } } - return -1; + } + return -1; } - }; + }; - /** - * Configures layout options for main reactor. - * - * @param node The KNode of the main reactor. - */ - public void configureMainReactor(KNode node) { - configureReactor(node); - } + /** + * Configures layout options for main reactor. + * + * @param node The KNode of the main reactor. + */ + public void configureMainReactor(KNode node) { + configureReactor(node); + } - /** - * Configures layout options for a reactor. - * - * @param node The KNode of a reactor. - */ - public void configureReactor(KNode node) { - String modelOrderStrategy = (String) getObjectValue(MODEL_ORDER); - - switch (modelOrderStrategy) { - case LEGACY: - // Otherwise nodes are not sorted if they are not connected - DiagramSyntheses.setLayoutOption(node, CoreOptions.SEPARATE_CONNECTED_COMPONENTS, false); - // Needed to enforce node positions. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CROSSING_MINIMIZATION_SEMI_INTERACTIVE, true); - // Costs a little more time but layout is quick, therefore, we can do that. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.THOROUGHNESS, 100); - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.TWO_SIDED); - break; - case STRICT_REACTION_ONLY: - // Only set model order for reactions. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, true); - // Do tie-breaking model order cycle breaking. - // Minimize number of backward edges but make decisions based on the model order if no greedy best alternative exists. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.GREEDY_MODEL_ORDER); - // Before crossing minimization sort all nodes and edges/ports but also consider the node model order. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.NODES_AND_EDGES); - // Separate connected components should be drawn separately. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.SEPARATE_CONNECTED_COMPONENTS, true); - // Component order is enforced by looking at the minimum element with respect to model order of each component. - // Remember that the startUp action is always the first node. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS, ComponentOrderingStrategy.FORCE_MODEL_ORDER); - DiagramSyntheses.setLayoutOption(node, LayeredOptions.COMPACTION_CONNECTED_COMPONENTS, true); + /** + * Configures layout options for a reactor. + * + * @param node The KNode of a reactor. + */ + public void configureReactor(KNode node) { + String modelOrderStrategy = (String) getObjectValue(MODEL_ORDER); - // Node order should not change during crossing minimization. - // Since only reactions will have a model order set in this approach the order of reactions in their respective - // separate connected components always respects the model order. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CROSSING_MINIMIZATION_FORCE_NODE_MODEL_ORDER, true); - // Disable greedy switch since this does otherwise change the node order after crossing minimization. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); - break; - case STRICT: - // Do tie-breaking model order cycle breaking. - // Minimize number of backward edges but make decisions based on the model order if no greedy best alternative exists. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.GREEDY_MODEL_ORDER); - // Before crossing minimization sort all nodes and edges/ports but also consider the node model order. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.NODES_AND_EDGES); - // Separate connected components should be drawn separately. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.SEPARATE_CONNECTED_COMPONENTS, true); - // Component order is enforced by looking at the minimum element with respect to model order of each component. - // Remember that the startUp action is always the first node. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS, ComponentOrderingStrategy.FORCE_MODEL_ORDER); - DiagramSyntheses.setLayoutOption(node, LayeredOptions.COMPACTION_CONNECTED_COMPONENTS, true); - - // Node order should not change during crossing minimization. - // Since only reactions and reactors will have a model order set in this approach the order of reactions and reactors in their respective - // separate connected components always respects the model order. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CROSSING_MINIMIZATION_FORCE_NODE_MODEL_ORDER, true); - // Disable greedy switch since this does otherwise change the node order after crossing minimization. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); - break; - case TIE_BREAKER: - // Do tie-breaking model order cycle breaking. - // Minimize number of backward edges but make decisions based on the model order if no greedy best alternative exists. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.GREEDY_MODEL_ORDER); - // Before crossing minimization sort all nodes and edges/ports but also consider the node model order. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.NODES_AND_EDGES); - // Separate connected components should be drawn separately. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.SEPARATE_CONNECTED_COMPONENTS, true); - // Component order is enforced by looking at the minimum element with respect to model order of each component. - // Remember that the startUp action is always the first node. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS, ComponentOrderingStrategy.FORCE_MODEL_ORDER); - DiagramSyntheses.setLayoutOption(node, LayeredOptions.COMPACTION_CONNECTED_COMPONENTS, true); - // During crossing minimization 10 node order violations are regarded as important as 1 edge crossing. - // In reality this chooses the best node order from all tries. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_CROSSING_COUNTER_NODE_INFLUENCE, 0.1); - // Increase the number of tries with different starting configurations. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.THOROUGHNESS, 100); + switch (modelOrderStrategy) { + case LEGACY: + // Otherwise nodes are not sorted if they are not connected + DiagramSyntheses.setLayoutOption(node, CoreOptions.SEPARATE_CONNECTED_COMPONENTS, false); + // Needed to enforce node positions. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CROSSING_MINIMIZATION_SEMI_INTERACTIVE, true); + // Costs a little more time but layout is quick, therefore, we can do that. + DiagramSyntheses.setLayoutOption(node, LayeredOptions.THOROUGHNESS, 100); + DiagramSyntheses.setLayoutOption( + node, + LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, + GreedySwitchType.TWO_SIDED); + break; + case STRICT_REACTION_ONLY: + // Only set model order for reactions. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, true); + // Do tie-breaking model order cycle breaking. + // Minimize number of backward edges but make decisions based on the model order if no + // greedy best alternative exists. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.GREEDY_MODEL_ORDER); + // Before crossing minimization sort all nodes and edges/ports but also consider the node + // model order. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.NODES_AND_EDGES); + // Separate connected components should be drawn separately. + DiagramSyntheses.setLayoutOption(node, LayeredOptions.SEPARATE_CONNECTED_COMPONENTS, true); + // Component order is enforced by looking at the minimum element with respect to model order + // of each component. + // Remember that the startUp action is always the first node. + DiagramSyntheses.setLayoutOption( + node, + LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS, + ComponentOrderingStrategy.FORCE_MODEL_ORDER); + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.COMPACTION_CONNECTED_COMPONENTS, true); - break; - case FULL_CONTROL: - // Do strict model order cycle breaking. This may introduce unnecessary backward edges. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); - // Before crossing minimization sort all nodes and edges/ports but also consider the node model order. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.NODES_AND_EDGES); - // Separate connected components should be drawn separately. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.SEPARATE_CONNECTED_COMPONENTS, true); - // Component order is enforced by looking at the minimum element with respect to model order of each component. - // Remember that the startUp action is always the first node. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS, ComponentOrderingStrategy.FORCE_MODEL_ORDER); - DiagramSyntheses.setLayoutOption(node, LayeredOptions.COMPACTION_CONNECTED_COMPONENTS, true); - // Disable all kinds of crossing minimization entirely. Just take what is in the model and just do it. - // This requires that the list of nodes is not ordered by type, e.g. first all reactions, then all reactors, then all actions, ... - // but by their model order. In other approaches ordering actions between the reactions has no effect. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); - - break; - default: - // Do nothing. - } - } + // Node order should not change during crossing minimization. + // Since only reactions will have a model order set in this approach the order of reactions + // in their respective + // separate connected components always respects the model order. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CROSSING_MINIMIZATION_FORCE_NODE_MODEL_ORDER, true); + // Disable greedy switch since this does otherwise change the node order after crossing + // minimization. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + break; + case STRICT: + // Do tie-breaking model order cycle breaking. + // Minimize number of backward edges but make decisions based on the model order if no + // greedy best alternative exists. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.GREEDY_MODEL_ORDER); + // Before crossing minimization sort all nodes and edges/ports but also consider the node + // model order. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.NODES_AND_EDGES); + // Separate connected components should be drawn separately. + DiagramSyntheses.setLayoutOption(node, LayeredOptions.SEPARATE_CONNECTED_COMPONENTS, true); + // Component order is enforced by looking at the minimum element with respect to model order + // of each component. + // Remember that the startUp action is always the first node. + DiagramSyntheses.setLayoutOption( + node, + LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS, + ComponentOrderingStrategy.FORCE_MODEL_ORDER); + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.COMPACTION_CONNECTED_COMPONENTS, true); - /** - * Configures layout options for an action. - * - * @param node The KNode of an action. - */ - public void configureAction(KNode node) { - String modelOrderStrategy = (String) getObjectValue(MODEL_ORDER); - - switch (modelOrderStrategy) { - case STRICT_REACTION_ONLY: - case STRICT: - case TIE_BREAKER: - // Actions have no model order since their ordering in the model cannot be compared to the order of - // for example reactions since they are generally defined below in inputs/outputs and above the reactions. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, true); - break; - case FULL_CONTROL: - // Give actions a model order since they should be controllable too. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, false); - break; - default: - // Do nothing. - } - } + // Node order should not change during crossing minimization. + // Since only reactions and reactors will have a model order set in this approach the order + // of reactions and reactors in their respective + // separate connected components always respects the model order. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CROSSING_MINIMIZATION_FORCE_NODE_MODEL_ORDER, true); + // Disable greedy switch since this does otherwise change the node order after crossing + // minimization. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + break; + case TIE_BREAKER: + // Do tie-breaking model order cycle breaking. + // Minimize number of backward edges but make decisions based on the model order if no + // greedy best alternative exists. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.GREEDY_MODEL_ORDER); + // Before crossing minimization sort all nodes and edges/ports but also consider the node + // model order. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.NODES_AND_EDGES); + // Separate connected components should be drawn separately. + DiagramSyntheses.setLayoutOption(node, LayeredOptions.SEPARATE_CONNECTED_COMPONENTS, true); + // Component order is enforced by looking at the minimum element with respect to model order + // of each component. + // Remember that the startUp action is always the first node. + DiagramSyntheses.setLayoutOption( + node, + LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS, + ComponentOrderingStrategy.FORCE_MODEL_ORDER); + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.COMPACTION_CONNECTED_COMPONENTS, true); + // During crossing minimization 10 node order violations are regarded as important as 1 edge + // crossing. + // In reality this chooses the best node order from all tries. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CONSIDER_MODEL_ORDER_CROSSING_COUNTER_NODE_INFLUENCE, 0.1); + // Increase the number of tries with different starting configurations. + DiagramSyntheses.setLayoutOption(node, LayeredOptions.THOROUGHNESS, 100); - /** - * Configures layout options for a timer. - * - * @param node The KNode of a timer. - */ - public void configureTimer(KNode node) { - String modelOrderStrategy = (String) getObjectValue(MODEL_ORDER); - - switch (modelOrderStrategy) { - case STRICT_REACTION_ONLY: - case STRICT: - case TIE_BREAKER: - // Timers have no model order since their ordering in the model cannot be compared to the order of - // for example reactions since they are generally defined below in inputs/outputs and above the reactions. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, true); - break; - case FULL_CONTROL: - // Give timers a model order since they should be controllable too. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, false); - break; - default: - // Do nothing. - } + break; + case FULL_CONTROL: + // Do strict model order cycle breaking. This may introduce unnecessary backward edges. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CYCLE_BREAKING_STRATEGY, CycleBreakingStrategy.MODEL_ORDER); + // Before crossing minimization sort all nodes and edges/ports but also consider the node + // model order. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY, OrderingStrategy.NODES_AND_EDGES); + // Separate connected components should be drawn separately. + DiagramSyntheses.setLayoutOption(node, LayeredOptions.SEPARATE_CONNECTED_COMPONENTS, true); + // Component order is enforced by looking at the minimum element with respect to model order + // of each component. + // Remember that the startUp action is always the first node. + DiagramSyntheses.setLayoutOption( + node, + LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS, + ComponentOrderingStrategy.FORCE_MODEL_ORDER); + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.COMPACTION_CONNECTED_COMPONENTS, true); + // Disable all kinds of crossing minimization entirely. Just take what is in the model and + // just do it. + // This requires that the list of nodes is not ordered by type, e.g. first all reactions, + // then all reactors, then all actions, ... + // but by their model order. In other approaches ordering actions between the reactions has + // no effect. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CROSSING_MINIMIZATION_STRATEGY, CrossingMinimizationStrategy.NONE); + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CROSSING_MINIMIZATION_GREEDY_SWITCH_TYPE, GreedySwitchType.OFF); + + break; + default: + // Do nothing. } + } + + /** + * Configures layout options for an action. + * + * @param node The KNode of an action. + */ + public void configureAction(KNode node) { + String modelOrderStrategy = (String) getObjectValue(MODEL_ORDER); - /** - * Configures layout options for a startup action. - * - * @param node The KNode of a startup action. - */ - public void configureStartUp(KNode node) { - // Nothing should be done. Model order is considered per default value. - // The actual ordering of this node has to be done in the synthesis. + switch (modelOrderStrategy) { + case STRICT_REACTION_ONLY: + case STRICT: + case TIE_BREAKER: + // Actions have no model order since their ordering in the model cannot be compared to the + // order of + // for example reactions since they are generally defined below in inputs/outputs and above + // the reactions. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, true); + break; + case FULL_CONTROL: + // Give actions a model order since they should be controllable too. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, false); + break; + default: + // Do nothing. } + } - /** - * Configures layout options for a shutdown action. - * - * @param node The KNode of a shutdown action. - */ - public void configureShutDown(KNode node) { - String modelOrderStrategy = (String) getObjectValue(MODEL_ORDER); - - switch (modelOrderStrategy) { - case STRICT_REACTION_ONLY: - case STRICT: - case TIE_BREAKER: - case FULL_CONTROL: - // The shutdown node cannot have a high model order, since this would confuse cycle breaking. - // It also cannot have a low model order. - // It should have none at all and the other nodes should define its position. - // This is no problem since the shutdown node has only outgoing edges. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, true); - break; - default: - // Do nothing. - } + /** + * Configures layout options for a timer. + * + * @param node The KNode of a timer. + */ + public void configureTimer(KNode node) { + String modelOrderStrategy = (String) getObjectValue(MODEL_ORDER); + + switch (modelOrderStrategy) { + case STRICT_REACTION_ONLY: + case STRICT: + case TIE_BREAKER: + // Timers have no model order since their ordering in the model cannot be compared to the + // order of + // for example reactions since they are generally defined below in inputs/outputs and above + // the reactions. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, true); + break; + case FULL_CONTROL: + // Give timers a model order since they should be controllable too. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, false); + break; + default: + // Do nothing. } + } + + /** + * Configures layout options for a startup action. + * + * @param node The KNode of a startup action. + */ + public void configureStartUp(KNode node) { + // Nothing should be done. Model order is considered per default value. + // The actual ordering of this node has to be done in the synthesis. + } - /** - * Configures layout options for a reaction. - * Currently a reaction does not have internal behavior that is visualized and its order is always considered, - * therefore, nothing needs to be done. - * - * @param node The KNode of a reaction. - */ - public void configureReaction(KNode node) { - // Has no internal behavior and model order is set by default. + /** + * Configures layout options for a shutdown action. + * + * @param node The KNode of a shutdown action. + */ + public void configureShutDown(KNode node) { + String modelOrderStrategy = (String) getObjectValue(MODEL_ORDER); + + switch (modelOrderStrategy) { + case STRICT_REACTION_ONLY: + case STRICT: + case TIE_BREAKER: + case FULL_CONTROL: + // The shutdown node cannot have a high model order, since this would confuse cycle + // breaking. + // It also cannot have a low model order. + // It should have none at all and the other nodes should define its position. + // This is no problem since the shutdown node has only outgoing edges. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, true); + break; + default: + // Do nothing. } + } - /** - * Configures layout options for a dummy node. - * - * @param node The KNode of a dummy node. - */ - public void configureDummy(KNode node) { - String modelOrderStrategy = (String) getObjectValue(MODEL_ORDER); - - switch (modelOrderStrategy) { - case STRICT_REACTION_ONLY: - case STRICT: - case TIE_BREAKER: - case FULL_CONTROL: - // A dummy node has no model order. - DiagramSyntheses.setLayoutOption(node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, true); - break; - default: - // Do nothing. - } + /** + * Configures layout options for a reaction. Currently a reaction does not have internal behavior + * that is visualized and its order is always considered, therefore, nothing needs to be done. + * + * @param node The KNode of a reaction. + */ + public void configureReaction(KNode node) { + // Has no internal behavior and model order is set by default. + } + + /** + * Configures layout options for a dummy node. + * + * @param node The KNode of a dummy node. + */ + public void configureDummy(KNode node) { + String modelOrderStrategy = (String) getObjectValue(MODEL_ORDER); + + switch (modelOrderStrategy) { + case STRICT_REACTION_ONLY: + case STRICT: + case TIE_BREAKER: + case FULL_CONTROL: + // A dummy node has no model order. + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, true); + break; + default: + // Do nothing. } + } - /** - * Orders a list of nodes by their corresponding linked instance if synthesis option for full control is enabled. - * Ordering is done by the {@link #TEXTUAL_ORDER} comparator. - * - * @param nodes List of KNodes to be ordered. - */ - public void orderChildren(List nodes) { - String modelOrderStrategy = (String) getObjectValue(MODEL_ORDER); - if (FULL_CONTROL.equals(modelOrderStrategy)) { - nodes.sort(TEXTUAL_ORDER); - } + /** + * Orders a list of nodes by their corresponding linked instance if synthesis option for full + * control is enabled. Ordering is done by the {@link #TEXTUAL_ORDER} comparator. + * + * @param nodes List of KNodes to be ordered. + */ + public void orderChildren(List nodes) { + String modelOrderStrategy = (String) getObjectValue(MODEL_ORDER); + if (FULL_CONTROL.equals(modelOrderStrategy)) { + nodes.sort(TEXTUAL_ORDER); } + } } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/util/ModeDiagrams.java b/org.lflang/src/org/lflang/diagram/synthesis/util/ModeDiagrams.java index 7222df3cf1..18c13e2b1f 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/util/ModeDiagrams.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/util/ModeDiagrams.java @@ -1,30 +1,62 @@ /************* -* Copyright (c) 2021, Kiel University. -* -* 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) 2021, Kiel University. + * + * 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.diagram.synthesis.util; - +import com.google.common.collect.LinkedHashMultimap; +import com.google.inject.Inject; +import de.cau.cs.kieler.klighd.SynthesisOption; +import de.cau.cs.kieler.klighd.kgraph.KEdge; +import de.cau.cs.kieler.klighd.kgraph.KIdentifier; +import de.cau.cs.kieler.klighd.kgraph.KLabel; +import de.cau.cs.kieler.klighd.kgraph.KNode; +import de.cau.cs.kieler.klighd.kgraph.KPort; +import de.cau.cs.kieler.klighd.krendering.Colors; +import de.cau.cs.kieler.klighd.krendering.HorizontalAlignment; +import de.cau.cs.kieler.klighd.krendering.KContainerRendering; +import de.cau.cs.kieler.klighd.krendering.KDecoratorPlacementData; +import de.cau.cs.kieler.klighd.krendering.KEllipse; +import de.cau.cs.kieler.klighd.krendering.KPolyline; +import de.cau.cs.kieler.klighd.krendering.KRectangle; +import de.cau.cs.kieler.klighd.krendering.KRendering; +import de.cau.cs.kieler.klighd.krendering.KRenderingFactory; +import de.cau.cs.kieler.klighd.krendering.KText; +import de.cau.cs.kieler.klighd.krendering.LineStyle; +import de.cau.cs.kieler.klighd.krendering.ViewSynthesisShared; +import de.cau.cs.kieler.klighd.krendering.extensions.KContainerRenderingExtensions; +import de.cau.cs.kieler.klighd.krendering.extensions.KEdgeExtensions; +import de.cau.cs.kieler.klighd.krendering.extensions.KLabelExtensions; +import de.cau.cs.kieler.klighd.krendering.extensions.KNodeExtensions; +import de.cau.cs.kieler.klighd.krendering.extensions.KPolylineExtensions; +import de.cau.cs.kieler.klighd.krendering.extensions.KPortExtensions; +import de.cau.cs.kieler.klighd.krendering.extensions.KRenderingExtensions; +import de.cau.cs.kieler.klighd.labels.decoration.ITextRenderingProvider; +import de.cau.cs.kieler.klighd.labels.decoration.LabelDecorationConfigurator; +import de.cau.cs.kieler.klighd.labels.decoration.LinesDecorator; +import de.cau.cs.kieler.klighd.labels.decoration.RectangleDecorator; +import de.cau.cs.kieler.klighd.syntheses.DiagramSyntheses; +import de.cau.cs.kieler.klighd.util.KlighdProperties; import java.awt.Color; import java.util.EnumSet; import java.util.HashMap; @@ -33,7 +65,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.stream.Collectors; - import org.eclipse.elk.alg.layered.options.CenterEdgeLabelPlacementStrategy; import org.eclipse.elk.alg.layered.options.EdgeStraighteningStrategy; import org.eclipse.elk.alg.layered.options.FixedAlignment; @@ -57,8 +88,8 @@ import org.lflang.diagram.synthesis.styles.LinguaFrancaShapeExtensions; import org.lflang.diagram.synthesis.styles.LinguaFrancaStyleExtensions; import org.lflang.generator.ModeInstance; -import org.lflang.generator.NamedInstance; import org.lflang.generator.ModeInstance.Transition; +import org.lflang.generator.NamedInstance; import org.lflang.generator.ReactorInstance; import org.lflang.lf.Action; import org.lflang.lf.Mode; @@ -66,531 +97,648 @@ import org.lflang.lf.Reactor; import org.lflang.lf.Timer; -import com.google.common.collect.LinkedHashMultimap; -import com.google.inject.Inject; - -import de.cau.cs.kieler.klighd.SynthesisOption; -import de.cau.cs.kieler.klighd.kgraph.KEdge; -import de.cau.cs.kieler.klighd.kgraph.KIdentifier; -import de.cau.cs.kieler.klighd.kgraph.KLabel; -import de.cau.cs.kieler.klighd.kgraph.KNode; -import de.cau.cs.kieler.klighd.kgraph.KPort; -import de.cau.cs.kieler.klighd.krendering.Colors; -import de.cau.cs.kieler.klighd.krendering.HorizontalAlignment; -import de.cau.cs.kieler.klighd.krendering.KContainerRendering; -import de.cau.cs.kieler.klighd.krendering.KDecoratorPlacementData; -import de.cau.cs.kieler.klighd.krendering.KEllipse; -import de.cau.cs.kieler.klighd.krendering.KPolyline; -import de.cau.cs.kieler.klighd.krendering.KRectangle; -import de.cau.cs.kieler.klighd.krendering.KRendering; -import de.cau.cs.kieler.klighd.krendering.KRenderingFactory; -import de.cau.cs.kieler.klighd.krendering.KText; -import de.cau.cs.kieler.klighd.krendering.LineStyle; -import de.cau.cs.kieler.klighd.krendering.ViewSynthesisShared; -import de.cau.cs.kieler.klighd.krendering.extensions.KContainerRenderingExtensions; -import de.cau.cs.kieler.klighd.krendering.extensions.KEdgeExtensions; -import de.cau.cs.kieler.klighd.krendering.extensions.KLabelExtensions; -import de.cau.cs.kieler.klighd.krendering.extensions.KNodeExtensions; -import de.cau.cs.kieler.klighd.krendering.extensions.KPolylineExtensions; -import de.cau.cs.kieler.klighd.krendering.extensions.KPortExtensions; -import de.cau.cs.kieler.klighd.krendering.extensions.KRenderingExtensions; -import de.cau.cs.kieler.klighd.labels.decoration.ITextRenderingProvider; -import de.cau.cs.kieler.klighd.labels.decoration.LabelDecorationConfigurator; -import de.cau.cs.kieler.klighd.labels.decoration.LinesDecorator; -import de.cau.cs.kieler.klighd.labels.decoration.RectangleDecorator; -import de.cau.cs.kieler.klighd.syntheses.DiagramSyntheses; -import de.cau.cs.kieler.klighd.util.KlighdProperties; - /** * Transformations to support modes in the Lingua Franca diagram synthesis. - * + * * @author Alexander Schulz-Rosengarten */ @ViewSynthesisShared public class ModeDiagrams extends AbstractSynthesisExtensions { - - // Related synthesis option - public static final SynthesisOption MODES_CATEGORY = - SynthesisOption.createCategory("Modes", false).setCategory(LinguaFrancaSynthesis.APPEARANCE); - public static final SynthesisOption SHOW_TRANSITION_LABELS = - SynthesisOption.createCheckOption("Transition Labels", true).setCategory(MODES_CATEGORY); - public static final SynthesisOption INITIALLY_COLLAPSE_MODES = - SynthesisOption.createCheckOption("Initially Collapse Modes", true).setCategory(MODES_CATEGORY); - - private static final Colors MODE_FG = Colors.SLATE_GRAY; - private static final Colors MODE_BG = Colors.SLATE_GRAY_3; - private static final int MODE_BG_ALPHA = 50; - - @Inject @Extension private KNodeExtensions _kNodeExtensions; - @Inject @Extension private KEdgeExtensions _kEdgeExtensions; - @Inject @Extension private KPortExtensions _kPortExtensions; - @Inject @Extension private KLabelExtensions _kLabelExtensions; - @Inject @Extension private KPolylineExtensions _kPolylineExtensions; - @Inject @Extension private KRenderingExtensions _kRenderingExtensions; - @Inject @Extension private KContainerRenderingExtensions _kContainerRenderingExtensions; - @Inject @Extension private LinguaFrancaShapeExtensions _linguaFrancaShapeExtensions; - @Inject @Extension private LinguaFrancaStyleExtensions _linguaFrancaStyleExtensions; - @Inject @Extension private UtilityExtensions _utilityExtensions; - @Inject @Extension private LayoutPostProcessing _layoutPostProcessing; - - @Extension private KRenderingFactory _kRenderingFactory = KRenderingFactory.eINSTANCE; - - public void handleModes(List nodes, ReactorInstance reactor) { - if (!reactor.modes.isEmpty()) { - var modeNodes = new LinkedHashMap(); - var modeDefinitionMap = new LinkedHashMap(); - for (ModeInstance mode : reactor.modes) { - var node = _kNodeExtensions.createNode(); - associateWith(node, mode.getDefinition()); - NamedInstanceUtil.linkInstance(node, mode); - _utilityExtensions.setID(node, mode.uniqueID()); - - modeNodes.put(mode, node); - modeDefinitionMap.put(mode.getDefinition(), mode); - - // Layout - if (mode.isInitial()) { - DiagramSyntheses.setLayoutOption(node, LayeredOptions.LAYERING_LAYER_CONSTRAINT, LayerConstraint.FIRST); - } - // Use general layout configuration of reactors - this.getRootSynthesis().configureReactorNodeLayout(node, false); - _layoutPostProcessing.configureReactor(node); - // Adjust for modes - DiagramSyntheses.setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FREE); - - var expansionState = MemorizingExpandCollapseAction.getExpansionState(mode); - DiagramSyntheses.setLayoutOption(node, KlighdProperties.EXPAND, - expansionState != null ? expansionState : !this.getBooleanValue(INITIALLY_COLLAPSE_MODES)); - - // Expanded Rectangle - var expandFigure = addModeFigure(node, mode, true); - expandFigure.setProperty(KlighdProperties.EXPANDED_RENDERING, true); - _kRenderingExtensions.addDoubleClickAction(expandFigure, MemorizingExpandCollapseAction.ID); - - if (getBooleanValue(LinguaFrancaSynthesis.SHOW_HYPERLINKS)) { - // Collapse button - KText textButton = _linguaFrancaShapeExtensions.addTextButton(expandFigure, LinguaFrancaSynthesis.TEXT_HIDE_ACTION); - _kRenderingExtensions.to( - _kRenderingExtensions.from(_kRenderingExtensions.setGridPlacementData(textButton), - _kRenderingExtensions.LEFT, 8, 0, _kRenderingExtensions.TOP, 0, 0), - _kRenderingExtensions.RIGHT, 8, 0, _kRenderingExtensions.BOTTOM, 0, 0); - _kRenderingExtensions.addSingleClickAction(textButton, MemorizingExpandCollapseAction.ID); - _kRenderingExtensions.addDoubleClickAction(textButton, MemorizingExpandCollapseAction.ID); - } - - if (getBooleanValue(LinguaFrancaSynthesis.SHOW_STATE_VARIABLES)) { - // Add mode-local state variables - var variables = mode.getDefinition().getStateVars(); - if (!variables.isEmpty()) { - KRectangle rectangle = _kContainerRenderingExtensions.addRectangle(expandFigure); - _kRenderingExtensions.setInvisible(rectangle, true); - if (!getBooleanValue(LinguaFrancaSynthesis.SHOW_HYPERLINKS)) { - _kRenderingExtensions.to( - _kRenderingExtensions.from( - _kRenderingExtensions.setGridPlacementData(rectangle), - _kRenderingExtensions.LEFT, 6, 0, - _kRenderingExtensions.TOP, 0, 0), - _kRenderingExtensions.RIGHT, 6, 0, - _kRenderingExtensions.BOTTOM, 4, 0); - } else { - _kRenderingExtensions.to( - _kRenderingExtensions.from( - _kRenderingExtensions.setGridPlacementData(rectangle), - _kRenderingExtensions.LEFT, 6, 0, - _kRenderingExtensions.TOP, 4, 0), - _kRenderingExtensions.RIGHT, 6, 0, - _kRenderingExtensions.BOTTOM, 0, 0); - } - _kRenderingExtensions.setHorizontalAlignment(rectangle, HorizontalAlignment.LEFT); - this.getRootSynthesis().addStateVariableList(rectangle, variables); - } - } - - _kContainerRenderingExtensions.addChildArea(expandFigure); - - // Collapse Rectangle - var collapseFigure = addModeFigure(node, mode, false); - collapseFigure.setProperty(KlighdProperties.COLLAPSED_RENDERING, true); - if (this.hasContent(mode)) { - _kRenderingExtensions.addDoubleClickAction(collapseFigure, MemorizingExpandCollapseAction.ID); - - if (getBooleanValue(LinguaFrancaSynthesis.SHOW_HYPERLINKS)) { - // Expand button - KText textButton = _linguaFrancaShapeExtensions.addTextButton(collapseFigure, LinguaFrancaSynthesis.TEXT_SHOW_ACTION); - _kRenderingExtensions.to(_kRenderingExtensions.from( - _kRenderingExtensions.setGridPlacementData(textButton), - _kRenderingExtensions.LEFT, 8, 0, _kRenderingExtensions.TOP, 0, 0), - _kRenderingExtensions.RIGHT, 8, 0, _kRenderingExtensions.BOTTOM, 8, 0); - _kRenderingExtensions.addSingleClickAction(textButton, MemorizingExpandCollapseAction.ID); - _kRenderingExtensions.addDoubleClickAction(textButton, MemorizingExpandCollapseAction.ID); - } - } + + // Related synthesis option + public static final SynthesisOption MODES_CATEGORY = + SynthesisOption.createCategory("Modes", false).setCategory(LinguaFrancaSynthesis.APPEARANCE); + public static final SynthesisOption SHOW_TRANSITION_LABELS = + SynthesisOption.createCheckOption("Transition Labels", true).setCategory(MODES_CATEGORY); + public static final SynthesisOption INITIALLY_COLLAPSE_MODES = + SynthesisOption.createCheckOption("Initially Collapse Modes", true) + .setCategory(MODES_CATEGORY); + + private static final Colors MODE_FG = Colors.SLATE_GRAY; + private static final Colors MODE_BG = Colors.SLATE_GRAY_3; + private static final int MODE_BG_ALPHA = 50; + + @Inject @Extension private KNodeExtensions _kNodeExtensions; + @Inject @Extension private KEdgeExtensions _kEdgeExtensions; + @Inject @Extension private KPortExtensions _kPortExtensions; + @Inject @Extension private KLabelExtensions _kLabelExtensions; + @Inject @Extension private KPolylineExtensions _kPolylineExtensions; + @Inject @Extension private KRenderingExtensions _kRenderingExtensions; + @Inject @Extension private KContainerRenderingExtensions _kContainerRenderingExtensions; + @Inject @Extension private LinguaFrancaShapeExtensions _linguaFrancaShapeExtensions; + @Inject @Extension private LinguaFrancaStyleExtensions _linguaFrancaStyleExtensions; + @Inject @Extension private UtilityExtensions _utilityExtensions; + @Inject @Extension private LayoutPostProcessing _layoutPostProcessing; + + @Extension private KRenderingFactory _kRenderingFactory = KRenderingFactory.eINSTANCE; + + public void handleModes(List nodes, ReactorInstance reactor) { + if (!reactor.modes.isEmpty()) { + var modeNodes = new LinkedHashMap(); + var modeDefinitionMap = new LinkedHashMap(); + for (ModeInstance mode : reactor.modes) { + var node = _kNodeExtensions.createNode(); + associateWith(node, mode.getDefinition()); + NamedInstanceUtil.linkInstance(node, mode); + _utilityExtensions.setID(node, mode.uniqueID()); + + modeNodes.put(mode, node); + modeDefinitionMap.put(mode.getDefinition(), mode); + + // Layout + if (mode.isInitial()) { + DiagramSyntheses.setLayoutOption( + node, LayeredOptions.LAYERING_LAYER_CONSTRAINT, LayerConstraint.FIRST); + } + // Use general layout configuration of reactors + this.getRootSynthesis().configureReactorNodeLayout(node, false); + _layoutPostProcessing.configureReactor(node); + // Adjust for modes + DiagramSyntheses.setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FREE); + + var expansionState = MemorizingExpandCollapseAction.getExpansionState(mode); + DiagramSyntheses.setLayoutOption( + node, + KlighdProperties.EXPAND, + expansionState != null + ? expansionState + : !this.getBooleanValue(INITIALLY_COLLAPSE_MODES)); + + // Expanded Rectangle + var expandFigure = addModeFigure(node, mode, true); + expandFigure.setProperty(KlighdProperties.EXPANDED_RENDERING, true); + _kRenderingExtensions.addDoubleClickAction(expandFigure, MemorizingExpandCollapseAction.ID); + + if (getBooleanValue(LinguaFrancaSynthesis.SHOW_HYPERLINKS)) { + // Collapse button + KText textButton = + _linguaFrancaShapeExtensions.addTextButton( + expandFigure, LinguaFrancaSynthesis.TEXT_HIDE_ACTION); + _kRenderingExtensions.to( + _kRenderingExtensions.from( + _kRenderingExtensions.setGridPlacementData(textButton), + _kRenderingExtensions.LEFT, + 8, + 0, + _kRenderingExtensions.TOP, + 0, + 0), + _kRenderingExtensions.RIGHT, + 8, + 0, + _kRenderingExtensions.BOTTOM, + 0, + 0); + _kRenderingExtensions.addSingleClickAction(textButton, MemorizingExpandCollapseAction.ID); + _kRenderingExtensions.addDoubleClickAction(textButton, MemorizingExpandCollapseAction.ID); + } + + if (getBooleanValue(LinguaFrancaSynthesis.SHOW_STATE_VARIABLES)) { + // Add mode-local state variables + var variables = mode.getDefinition().getStateVars(); + if (!variables.isEmpty()) { + KRectangle rectangle = _kContainerRenderingExtensions.addRectangle(expandFigure); + _kRenderingExtensions.setInvisible(rectangle, true); + if (!getBooleanValue(LinguaFrancaSynthesis.SHOW_HYPERLINKS)) { + _kRenderingExtensions.to( + _kRenderingExtensions.from( + _kRenderingExtensions.setGridPlacementData(rectangle), + _kRenderingExtensions.LEFT, + 6, + 0, + _kRenderingExtensions.TOP, + 0, + 0), + _kRenderingExtensions.RIGHT, + 6, + 0, + _kRenderingExtensions.BOTTOM, + 4, + 0); + } else { + _kRenderingExtensions.to( + _kRenderingExtensions.from( + _kRenderingExtensions.setGridPlacementData(rectangle), + _kRenderingExtensions.LEFT, + 6, + 0, + _kRenderingExtensions.TOP, + 4, + 0), + _kRenderingExtensions.RIGHT, + 6, + 0, + _kRenderingExtensions.BOTTOM, + 0, + 0); } - - var modeChildren = LinkedHashMultimap.create(); - var nodeModes = new HashMap(); - for (var node : nodes) { - var instance = NamedInstanceUtil.getLinkedInstance(node); - if (instance == null && node.getProperty(CoreOptions.COMMENT_BOX)) { - var firstEdge = IterableExtensions.head(node.getOutgoingEdges()); - if (firstEdge != null && firstEdge.getTarget() != null) { - instance = NamedInstanceUtil.getLinkedInstance(firstEdge.getTarget()); - } - } - if (instance != null) { - var mode = instance.getMode(true); - modeChildren.put(mode, node); - nodeModes.put(node, mode); - } else { - modeChildren.put(null, node); - } + _kRenderingExtensions.setHorizontalAlignment(rectangle, HorizontalAlignment.LEFT); + this.getRootSynthesis() + .addStateVariableList(rectangle, variables); + } + } + + _kContainerRenderingExtensions.addChildArea(expandFigure); + + // Collapse Rectangle + var collapseFigure = addModeFigure(node, mode, false); + collapseFigure.setProperty(KlighdProperties.COLLAPSED_RENDERING, true); + if (this.hasContent(mode)) { + _kRenderingExtensions.addDoubleClickAction( + collapseFigure, MemorizingExpandCollapseAction.ID); + + if (getBooleanValue(LinguaFrancaSynthesis.SHOW_HYPERLINKS)) { + // Expand button + KText textButton = + _linguaFrancaShapeExtensions.addTextButton( + collapseFigure, LinguaFrancaSynthesis.TEXT_SHOW_ACTION); + _kRenderingExtensions.to( + _kRenderingExtensions.from( + _kRenderingExtensions.setGridPlacementData(textButton), + _kRenderingExtensions.LEFT, + 8, + 0, + _kRenderingExtensions.TOP, + 0, + 0), + _kRenderingExtensions.RIGHT, + 8, + 0, + _kRenderingExtensions.BOTTOM, + 8, + 0); + _kRenderingExtensions.addSingleClickAction( + textButton, MemorizingExpandCollapseAction.ID); + _kRenderingExtensions.addDoubleClickAction( + textButton, MemorizingExpandCollapseAction.ID); + } + } + } + + var modeChildren = LinkedHashMultimap.create(); + var nodeModes = new HashMap(); + for (var node : nodes) { + var instance = NamedInstanceUtil.getLinkedInstance(node); + if (instance == null && node.getProperty(CoreOptions.COMMENT_BOX)) { + var firstEdge = IterableExtensions.head(node.getOutgoingEdges()); + if (firstEdge != null && firstEdge.getTarget() != null) { + instance = NamedInstanceUtil.getLinkedInstance(firstEdge.getTarget()); + } + } + if (instance != null) { + var mode = instance.getMode(true); + modeChildren.put(mode, node); + nodeModes.put(node, mode); + } else { + modeChildren.put(null, node); + } + } + + var modeContainer = _kNodeExtensions.createNode(); + modeContainer.getChildren().addAll(modeNodes.values()); + var modeContainerFigure = addModeContainerFigure(modeContainer); + _kRenderingExtensions.addDoubleClickAction( + modeContainerFigure, MemorizingExpandCollapseAction.ID); + + // Use general layout configuration of reactors + this.getRootSynthesis() + .configureReactorNodeLayout(modeContainer, false); + _layoutPostProcessing.configureReactor(modeContainer); + // Adjust for state machine style + // Create alternating directions to make the model more compact + DiagramSyntheses.setLayoutOption(modeContainer, CoreOptions.DIRECTION, Direction.DOWN); + // More state machine like node placement + DiagramSyntheses.setLayoutOption( + modeContainer, + LayeredOptions.NODE_PLACEMENT_STRATEGY, + NodePlacementStrategy.BRANDES_KOEPF); + DiagramSyntheses.setLayoutOption( + modeContainer, LayeredOptions.NODE_PLACEMENT_BK_FIXED_ALIGNMENT, FixedAlignment.BALANCED); + DiagramSyntheses.setLayoutOption( + modeContainer, + LayeredOptions.NODE_PLACEMENT_BK_EDGE_STRAIGHTENING, + EdgeStraighteningStrategy.IMPROVE_STRAIGHTNESS); + // Splines + DiagramSyntheses.setLayoutOption( + modeContainer, CoreOptions.EDGE_ROUTING, EdgeRouting.SPLINES); + DiagramSyntheses.setLayoutOption( + modeContainer, + LayeredOptions.EDGE_LABELS_CENTER_LABEL_PLACEMENT_STRATEGY, + CenterEdgeLabelPlacementStrategy.TAIL_LAYER); + DiagramSyntheses.setLayoutOption(modeContainer, CoreOptions.SPACING_NODE_SELF_LOOP, 18.0); + // Unreachable states are unlikely + DiagramSyntheses.setLayoutOption( + modeContainer, CoreOptions.SEPARATE_CONNECTED_COMPONENTS, false); + // Equal padding + DiagramSyntheses.setLayoutOption(modeContainer, CoreOptions.PADDING, new ElkPadding(6)); + if (reactor.modes.stream() + .anyMatch(m -> m.transitions.stream().anyMatch(t -> t.type == ModeTransition.HISTORY))) { + // Make additional space for history indicator + DiagramSyntheses.setLayoutOption( + modeContainer, + LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, + modeContainer.getProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS) + + (getBooleanValue(SHOW_TRANSITION_LABELS) ? 6.0 : 10.0)); + } + + var modeContainerPorts = new HashMap(); + for (var mode : reactor.modes) { + var modeNode = modeNodes.get(mode); + var edges = new LinkedHashSet(); + // add children + for (var child : modeChildren.get(mode)) { + nodes.remove(child); + modeNode.getChildren().add(child); + + edges.addAll(child.getIncomingEdges()); + edges.addAll(child.getOutgoingEdges()); + } + + // add transitions + var representedTargets = new HashSet>(); + for (var transition : mode.transitions) { + if (!representedTargets.contains( + new Pair(transition.target, transition.type))) { + var edge = _kEdgeExtensions.createEdge(); + edge.setSource(modeNode); + edge.setTarget(modeNodes.get(transition.target)); + addTransitionFigure(edge, transition); + + if (getBooleanValue(SHOW_TRANSITION_LABELS)) { + associateWith(edge, transition.getDefinition()); + } else { + // Bundle similar transitions + representedTargets.add( + new Pair(transition.target, transition.type)); + } + } + } + + // handle cross hierarchy edges + var portCopies = new HashMap(); + var triggerCopies = new HashMap(); + for (var edge : edges) { + if (!edge.getProperty(CoreOptions.NO_LAYOUT)) { + var sourceNodeMode = nodeModes.get(edge.getSource()); + if (sourceNodeMode == null) { + sourceNodeMode = nodeModes.get(edge.getSource().getParent()); } - - var modeContainer = _kNodeExtensions.createNode(); - modeContainer.getChildren().addAll(modeNodes.values()); - var modeContainerFigure = addModeContainerFigure(modeContainer); - _kRenderingExtensions.addDoubleClickAction(modeContainerFigure, MemorizingExpandCollapseAction.ID); - - // Use general layout configuration of reactors - this.getRootSynthesis().configureReactorNodeLayout(modeContainer, false); - _layoutPostProcessing.configureReactor(modeContainer); - // Adjust for state machine style - // Create alternating directions to make the model more compact - DiagramSyntheses.setLayoutOption(modeContainer, CoreOptions.DIRECTION, Direction.DOWN); - // More state machine like node placement - DiagramSyntheses.setLayoutOption(modeContainer, LayeredOptions.NODE_PLACEMENT_STRATEGY, NodePlacementStrategy.BRANDES_KOEPF); - DiagramSyntheses.setLayoutOption(modeContainer, LayeredOptions.NODE_PLACEMENT_BK_FIXED_ALIGNMENT, FixedAlignment.BALANCED); - DiagramSyntheses.setLayoutOption(modeContainer, LayeredOptions.NODE_PLACEMENT_BK_EDGE_STRAIGHTENING, EdgeStraighteningStrategy.IMPROVE_STRAIGHTNESS); - // Splines - DiagramSyntheses.setLayoutOption(modeContainer, CoreOptions.EDGE_ROUTING, EdgeRouting.SPLINES); - DiagramSyntheses.setLayoutOption(modeContainer, LayeredOptions.EDGE_LABELS_CENTER_LABEL_PLACEMENT_STRATEGY, CenterEdgeLabelPlacementStrategy.TAIL_LAYER); - DiagramSyntheses.setLayoutOption(modeContainer, CoreOptions.SPACING_NODE_SELF_LOOP, 18.0); - // Unreachable states are unlikely - DiagramSyntheses.setLayoutOption(modeContainer, CoreOptions.SEPARATE_CONNECTED_COMPONENTS, false); - // Equal padding - DiagramSyntheses.setLayoutOption(modeContainer, CoreOptions.PADDING, new ElkPadding(6)); - if (reactor.modes.stream().anyMatch(m -> m.transitions.stream().anyMatch(t -> t.type == ModeTransition.HISTORY))) { - // Make additional space for history indicator - DiagramSyntheses.setLayoutOption(modeContainer, LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, - modeContainer.getProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS) - + (getBooleanValue(SHOW_TRANSITION_LABELS) ? 6.0 : 10.0)); + var sourceIsInMode = sourceNodeMode != null; + var targetNodeMode = nodeModes.get(edge.getTarget()); + if (targetNodeMode == null) { + targetNodeMode = nodeModes.get(edge.getTarget().getParent()); } + var targetIsInMode = targetNodeMode != null; + + if (!sourceIsInMode || !targetIsInMode) { + var node = sourceIsInMode ? edge.getTarget() : edge.getSource(); + + if (node.getProperty(LinguaFrancaSynthesis.REACTION_SPECIAL_TRIGGER)) { + // Duplicate trigger node + if (!triggerCopies.containsKey(node)) { + var copy = EcoreUtil.copy(node); + modeNode + .getChildren() + .add(modeNode.getChildren().indexOf(edge.getTarget()), copy); + triggerCopies.put(node, copy); - var modeContainerPorts = new HashMap(); - for (var mode : reactor.modes) { - var modeNode = modeNodes.get(mode); - var edges = new LinkedHashSet(); - // add children - for (var child : modeChildren.get(mode)) { - nodes.remove(child); - modeNode.getChildren().add(child); - - edges.addAll(child.getIncomingEdges()); - edges.addAll(child.getOutgoingEdges()); + // Adjust copy + copy.getOutgoingEdges() + .forEach( + e -> { + e.setTarget(null); + e.setTargetPort(null); + }); + copy.getOutgoingEdges().clear(); + copy.getData().stream() + .filter(d -> d instanceof KIdentifier) + .forEach( + d -> { + var kid = (KIdentifier) d; + kid.setId(kid.getId() + "_" + mode.getName()); + }); } - - // add transitions - var representedTargets = new HashSet>(); - for (var transition : mode.transitions) { - if (!representedTargets.contains(new Pair(transition.target, transition.type))) { - var edge = _kEdgeExtensions.createEdge(); - edge.setSource(modeNode); - edge.setTarget(modeNodes.get(transition.target)); - addTransitionFigure(edge, transition); - - if (getBooleanValue(SHOW_TRANSITION_LABELS)) { - associateWith(edge, transition.getDefinition()); - } else { - // Bundle similar transitions - representedTargets.add(new Pair(transition.target, transition.type)); - } - } + + var newNode = triggerCopies.get(node); + edge.setSource(newNode); + + // Remove trigger on top level if only used in modes + if (node.getOutgoingEdges().isEmpty()) { + nodes.remove(node); } - - // handle cross hierarchy edges - var portCopies = new HashMap(); - var triggerCopies = new HashMap(); - for (var edge : edges) { - if (!edge.getProperty(CoreOptions.NO_LAYOUT)) { - var sourceNodeMode = nodeModes.get(edge.getSource()); - if (sourceNodeMode == null) { - sourceNodeMode = nodeModes.get(edge.getSource().getParent()); - } - var sourceIsInMode = sourceNodeMode != null; - var targetNodeMode = nodeModes.get(edge.getTarget()); - if (targetNodeMode == null) { - targetNodeMode = nodeModes.get(edge.getTarget().getParent()); - } - var targetIsInMode = targetNodeMode != null; - - if (!sourceIsInMode || !targetIsInMode) { - var node = sourceIsInMode ? edge.getTarget() : edge.getSource(); - - if (node.getProperty(LinguaFrancaSynthesis.REACTION_SPECIAL_TRIGGER)) { - // Duplicate trigger node - if (!triggerCopies.containsKey(node)) { - var copy = EcoreUtil.copy(node); - modeNode.getChildren().add(modeNode.getChildren().indexOf(edge.getTarget()), copy); - triggerCopies.put(node, copy); - - // Adjust copy - copy.getOutgoingEdges().forEach(e -> {e.setTarget(null);e.setTargetPort(null);}); - copy.getOutgoingEdges().clear(); - copy.getData().stream().filter(d -> d instanceof KIdentifier).forEach(d -> { - var kid = (KIdentifier) d; - kid.setId(kid.getId() + "_" + mode.getName()); - }); - } - - var newNode = triggerCopies.get(node); - edge.setSource(newNode); - - // Remove trigger on top level if only used in modes - if (node.getOutgoingEdges().isEmpty()) { - nodes.remove(node); - } - } else { - var port = sourceIsInMode ? edge.getTargetPort() : edge.getSourcePort(); - var isLocal = modeChildren.get(null).contains(node); - if (isLocal) { - // Add port to mode container - if (modeContainerPorts.containsKey(port)) { - node = modeContainer; - port = modeContainerPorts.get(port); - } else { - var containerPort = _kPortExtensions.createPort(); - modeContainerPorts.put(port, containerPort); - modeContainer.getPorts().add(containerPort); - - _kPortExtensions.setPortSize(containerPort, 8, 8); - KRectangle rect = _kRenderingExtensions.addRectangle(containerPort); - _kRenderingExtensions.setPointPlacementData(rect, - _kRenderingExtensions.LEFT, 0, 0.5f, - _kRenderingExtensions.BOTTOM, 0, 0.5f, - _kRenderingExtensions.H_CENTRAL, _kRenderingExtensions.V_CENTRAL, - 0, 0, 8, 4); - _kRenderingExtensions.setBackground(rect, Colors.BLACK); - _linguaFrancaStyleExtensions.boldLineSelectionStyle(rect); - - DiagramSyntheses.setLayoutOption(containerPort, CoreOptions.PORT_BORDER_OFFSET, -4.0); - DiagramSyntheses.setLayoutOption(containerPort, CoreOptions.PORT_SIDE, sourceIsInMode ? PortSide.EAST : PortSide.WEST); - - var source = _utilityExtensions.sourceElement(node); - var label = ""; - if (source instanceof Action) { - label = ((Action) source).getName(); - } else if (source instanceof Timer) { - label = ((Timer) source).getName(); - } else if (!port.getLabels().isEmpty()) { - label = port.getLabels().get(0).getText(); - if (source instanceof Reactor && getBooleanValue(LinguaFrancaSynthesis.SHOW_INSTANCE_NAMES)) { - NamedInstance linkedInstance = NamedInstanceUtil.getLinkedInstance(node); - if (linkedInstance instanceof ReactorInstance) { - label = ((ReactorInstance) linkedInstance).getName() + "." + label; - } - } - } - var portLabel = _kLabelExtensions.createLabel(containerPort); - portLabel.setText(label); - var portLabelKText = _kRenderingFactory.createKText(); - _kRenderingExtensions.setFontSize(portLabelKText, 8); - portLabel.getData().add(portLabelKText); - - // new connection - var copy = EcoreUtil.copy(edge); - if (sourceIsInMode) { - copy.setSource(modeContainer); - copy.setSourcePort(containerPort); - copy.setTarget(edge.getTarget()); - } else { - copy.setTarget(modeContainer); - copy.setTargetPort(containerPort); - copy.setSource(edge.getSource()); - } - - node = modeContainer; - port = containerPort; - } - } - - // Duplicate port - if (!portCopies.containsKey(port)) { - var copy = EcoreUtil.copy(port); - portCopies.put(port, copy); - - var dummyNode = _kNodeExtensions.createNode(); - var newID = mode.uniqueID() + "_"; - if (!port.getLabels().isEmpty()) { - newID += IterableExtensions.head(port.getLabels()).getText(); - } - _utilityExtensions.setID(dummyNode, newID); - _kRenderingExtensions.addInvisibleContainerRendering(dummyNode); - dummyNode.getPorts().add(copy); - // Assign layer - DiagramSyntheses.setLayoutOption(dummyNode, LayeredOptions.LAYERING_LAYER_CONSTRAINT, - port.getProperty(CoreOptions.PORT_SIDE) == PortSide.WEST ? LayerConstraint.FIRST : LayerConstraint.LAST); - // Configure port spacing - DiagramSyntheses.setLayoutOption(dummyNode, CoreOptions.PORT_LABELS_PLACEMENT, EnumSet.of(PortLabelPlacement.ALWAYS_OTHER_SAME_SIDE, PortLabelPlacement.OUTSIDE)); - // Place freely - DiagramSyntheses.setLayoutOption(dummyNode, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, true); - // Switch port side - DiagramSyntheses.setLayoutOption(copy, CoreOptions.PORT_SIDE, - port.getProperty(CoreOptions.PORT_SIDE) == PortSide.WEST ? PortSide.EAST : PortSide.WEST); - - modeNode.getChildren().add(dummyNode); - } - var newPort = portCopies.get(port); - if (sourceIsInMode) { - edge.setTarget(newPort.getNode()); - edge.setTargetPort(newPort); - } else { - edge.setSource(newPort.getNode()); - edge.setSourcePort(newPort); - } - } + } else { + var port = sourceIsInMode ? edge.getTargetPort() : edge.getSourcePort(); + var isLocal = modeChildren.get(null).contains(node); + if (isLocal) { + // Add port to mode container + if (modeContainerPorts.containsKey(port)) { + node = modeContainer; + port = modeContainerPorts.get(port); + } else { + var containerPort = _kPortExtensions.createPort(); + modeContainerPorts.put(port, containerPort); + modeContainer.getPorts().add(containerPort); + + _kPortExtensions.setPortSize(containerPort, 8, 8); + KRectangle rect = _kRenderingExtensions.addRectangle(containerPort); + _kRenderingExtensions.setPointPlacementData( + rect, + _kRenderingExtensions.LEFT, + 0, + 0.5f, + _kRenderingExtensions.BOTTOM, + 0, + 0.5f, + _kRenderingExtensions.H_CENTRAL, + _kRenderingExtensions.V_CENTRAL, + 0, + 0, + 8, + 4); + _kRenderingExtensions.setBackground(rect, Colors.BLACK); + _linguaFrancaStyleExtensions.boldLineSelectionStyle(rect); + + DiagramSyntheses.setLayoutOption( + containerPort, CoreOptions.PORT_BORDER_OFFSET, -4.0); + DiagramSyntheses.setLayoutOption( + containerPort, + CoreOptions.PORT_SIDE, + sourceIsInMode ? PortSide.EAST : PortSide.WEST); + + var source = _utilityExtensions.sourceElement(node); + var label = ""; + if (source instanceof Action) { + label = ((Action) source).getName(); + } else if (source instanceof Timer) { + label = ((Timer) source).getName(); + } else if (!port.getLabels().isEmpty()) { + label = port.getLabels().get(0).getText(); + if (source instanceof Reactor + && getBooleanValue(LinguaFrancaSynthesis.SHOW_INSTANCE_NAMES)) { + NamedInstance linkedInstance = NamedInstanceUtil.getLinkedInstance(node); + if (linkedInstance instanceof ReactorInstance) { + label = ((ReactorInstance) linkedInstance).getName() + "." + label; } + } + } + var portLabel = _kLabelExtensions.createLabel(containerPort); + portLabel.setText(label); + var portLabelKText = _kRenderingFactory.createKText(); + _kRenderingExtensions.setFontSize(portLabelKText, 8); + portLabel.getData().add(portLabelKText); + + // new connection + var copy = EcoreUtil.copy(edge); + if (sourceIsInMode) { + copy.setSource(modeContainer); + copy.setSourcePort(containerPort); + copy.setTarget(edge.getTarget()); + } else { + copy.setTarget(modeContainer); + copy.setTargetPort(containerPort); + copy.setSource(edge.getSource()); } + + node = modeContainer; + port = containerPort; + } } - } - - // If mode container is unused (no ports for local connections) -> hide it - if (modeContainer.getPorts().isEmpty()) { - _kRenderingExtensions.setInvisible(modeContainerFigure, true); - DiagramSyntheses.setLayoutOption(modeContainer, CoreOptions.PADDING, new ElkPadding(2)); - } else if (getBooleanValue(LinguaFrancaSynthesis.SHOW_INSTANCE_NAMES)) { - // Remove mode container port labels of ports representing internal connections - // because their association to reactor instances is unambiguous due to instance names - for (var p : modeContainer.getPorts()) { - p.getLabels().removeIf(l -> l.getText().contains(".")); + + // Duplicate port + if (!portCopies.containsKey(port)) { + var copy = EcoreUtil.copy(port); + portCopies.put(port, copy); + + var dummyNode = _kNodeExtensions.createNode(); + var newID = mode.uniqueID() + "_"; + if (!port.getLabels().isEmpty()) { + newID += IterableExtensions.head(port.getLabels()).getText(); + } + _utilityExtensions.setID(dummyNode, newID); + _kRenderingExtensions.addInvisibleContainerRendering(dummyNode); + dummyNode.getPorts().add(copy); + // Assign layer + DiagramSyntheses.setLayoutOption( + dummyNode, + LayeredOptions.LAYERING_LAYER_CONSTRAINT, + port.getProperty(CoreOptions.PORT_SIDE) == PortSide.WEST + ? LayerConstraint.FIRST + : LayerConstraint.LAST); + // Configure port spacing + DiagramSyntheses.setLayoutOption( + dummyNode, + CoreOptions.PORT_LABELS_PLACEMENT, + EnumSet.of( + PortLabelPlacement.ALWAYS_OTHER_SAME_SIDE, PortLabelPlacement.OUTSIDE)); + // Place freely + DiagramSyntheses.setLayoutOption( + dummyNode, LayeredOptions.CONSIDER_MODEL_ORDER_NO_MODEL_ORDER, true); + // Switch port side + DiagramSyntheses.setLayoutOption( + copy, + CoreOptions.PORT_SIDE, + port.getProperty(CoreOptions.PORT_SIDE) == PortSide.WEST + ? PortSide.EAST + : PortSide.WEST); + + modeNode.getChildren().add(dummyNode); + } + var newPort = portCopies.get(port); + if (sourceIsInMode) { + edge.setTarget(newPort.getNode()); + edge.setTargetPort(newPort); + } else { + edge.setSource(newPort.getNode()); + edge.setSourcePort(newPort); } + } } - - nodes.add(modeContainer); + } } - } - - private boolean hasContent(ModeInstance mode) { - return !mode.reactions.isEmpty() || !mode.instantiations.isEmpty(); - } - - private KContainerRendering addModeFigure(KNode node, ModeInstance mode, boolean expanded) { - int padding = getBooleanValue(LinguaFrancaSynthesis.SHOW_HYPERLINKS) ? 8 : 6; - - var figure = _kRenderingExtensions.addRoundedRectangle(node, 13, 13, 1); - _kContainerRenderingExtensions.setGridPlacement(figure, 1); - _kRenderingExtensions.setLineWidth(figure, mode.isInitial() ? 3f : 1.5f); - _kRenderingExtensions.setForeground(figure, MODE_FG); - _kRenderingExtensions.setBackground(figure, MODE_BG); - var background = _kRenderingExtensions.getBackground(figure); - background.setAlpha(MODE_BG_ALPHA); - _linguaFrancaStyleExtensions.boldLineSelectionStyle(figure); - - // Invisible container - KRectangle container = _kContainerRenderingExtensions.addRectangle(figure); - _kRenderingExtensions.setInvisible(container, true); - int bottomPadding = this.hasContent(mode) && expanded ? 4 : padding; - var from = _kRenderingExtensions.from( - _kRenderingExtensions.setGridPlacementData(container), - _kRenderingExtensions.LEFT, padding, 0, _kRenderingExtensions.TOP, padding, 0); - _kRenderingExtensions.to(from, _kRenderingExtensions.RIGHT, padding, 0, _kRenderingExtensions.BOTTOM, bottomPadding, 0); - - // Centered child container - KRectangle childContainer = _kContainerRenderingExtensions.addRectangle(container); - this._kRenderingExtensions.setInvisible(childContainer, true); - this._kRenderingExtensions.setPointPlacementData(childContainer, - _kRenderingExtensions.LEFT, 0, 0.5f, _kRenderingExtensions.TOP, 0, 0.5f, - _kRenderingExtensions.H_CENTRAL, _kRenderingExtensions.V_CENTRAL, 0, 0, 0, 0); - this._kContainerRenderingExtensions.setGridPlacement(childContainer, 1); - - KText text = _kContainerRenderingExtensions.addText(childContainer, mode.getName()); - DiagramSyntheses.suppressSelectability(text); - _linguaFrancaStyleExtensions.underlineSelectionStyle(text); - - return figure; - } - - private KContainerRendering addModeContainerFigure(KNode node) { - var rect = _kRenderingExtensions.addRectangle(node); - _kRenderingExtensions.setLineWidth(rect, 1); - _kRenderingExtensions.setLineStyle(rect, LineStyle.DOT); - _kRenderingExtensions.setForeground(rect, MODE_FG); - _linguaFrancaStyleExtensions.boldLineSelectionStyle(rect); - return rect; - } - - private void addTransitionFigure(KEdge edge, Transition transition) { - var spline = _kEdgeExtensions.addSpline(edge); - _kRenderingExtensions.setLineWidth(spline, 1.5f); - _kRenderingExtensions.setForeground(spline, MODE_FG); - _linguaFrancaStyleExtensions.boldLineSelectionStyle(spline); - - if (transition.type == ModeTransition.HISTORY) { - addHistoryDecorator(spline); - } else { - KRendering arrowDecorator = _kPolylineExtensions.addHeadArrowDecorator(spline); - this._kRenderingExtensions.setForeground(arrowDecorator, MODE_FG); - this._kRenderingExtensions.setBackground(arrowDecorator, MODE_FG); - } - - if (getBooleanValue(SHOW_TRANSITION_LABELS)) { - associateWith(spline, transition.getDefinition()); - - KLabel centerEdgeLabel = _kLabelExtensions.addCenterEdgeLabel(edge, this.toTransitionLabel(transition)); - associateWith(centerEdgeLabel, transition.getDefinition()); - applyTransitionOnEdgeStyle(centerEdgeLabel); + } + + // If mode container is unused (no ports for local connections) -> hide it + if (modeContainer.getPorts().isEmpty()) { + _kRenderingExtensions.setInvisible(modeContainerFigure, true); + DiagramSyntheses.setLayoutOption(modeContainer, CoreOptions.PADDING, new ElkPadding(2)); + } else if (getBooleanValue(LinguaFrancaSynthesis.SHOW_INSTANCE_NAMES)) { + // Remove mode container port labels of ports representing internal connections + // because their association to reactor instances is unambiguous due to instance names + for (var p : modeContainer.getPorts()) { + p.getLabels().removeIf(l -> l.getText().contains(".")); } + } + + nodes.add(modeContainer); } - - private String toTransitionLabel(Transition transition) { - var text = new StringBuilder(); - - text.append(transition.reaction.triggers.stream().map(t -> t.getDefinition().getName()).collect(Collectors.joining(", "))); - return text.toString(); + } + + private boolean hasContent(ModeInstance mode) { + return !mode.reactions.isEmpty() || !mode.instantiations.isEmpty(); + } + + private KContainerRendering addModeFigure(KNode node, ModeInstance mode, boolean expanded) { + int padding = getBooleanValue(LinguaFrancaSynthesis.SHOW_HYPERLINKS) ? 8 : 6; + + var figure = _kRenderingExtensions.addRoundedRectangle(node, 13, 13, 1); + _kContainerRenderingExtensions.setGridPlacement(figure, 1); + _kRenderingExtensions.setLineWidth(figure, mode.isInitial() ? 3f : 1.5f); + _kRenderingExtensions.setForeground(figure, MODE_FG); + _kRenderingExtensions.setBackground(figure, MODE_BG); + var background = _kRenderingExtensions.getBackground(figure); + background.setAlpha(MODE_BG_ALPHA); + _linguaFrancaStyleExtensions.boldLineSelectionStyle(figure); + + // Invisible container + KRectangle container = _kContainerRenderingExtensions.addRectangle(figure); + _kRenderingExtensions.setInvisible(container, true); + int bottomPadding = this.hasContent(mode) && expanded ? 4 : padding; + var from = + _kRenderingExtensions.from( + _kRenderingExtensions.setGridPlacementData(container), + _kRenderingExtensions.LEFT, + padding, + 0, + _kRenderingExtensions.TOP, + padding, + 0); + _kRenderingExtensions.to( + from, + _kRenderingExtensions.RIGHT, + padding, + 0, + _kRenderingExtensions.BOTTOM, + bottomPadding, + 0); + + // Centered child container + KRectangle childContainer = _kContainerRenderingExtensions.addRectangle(container); + this._kRenderingExtensions.setInvisible(childContainer, true); + this._kRenderingExtensions.setPointPlacementData( + childContainer, + _kRenderingExtensions.LEFT, + 0, + 0.5f, + _kRenderingExtensions.TOP, + 0, + 0.5f, + _kRenderingExtensions.H_CENTRAL, + _kRenderingExtensions.V_CENTRAL, + 0, + 0, + 0, + 0); + this._kContainerRenderingExtensions.setGridPlacement(childContainer, 1); + + KText text = _kContainerRenderingExtensions.addText(childContainer, mode.getName()); + DiagramSyntheses.suppressSelectability(text); + _linguaFrancaStyleExtensions.underlineSelectionStyle(text); + + return figure; + } + + private KContainerRendering addModeContainerFigure(KNode node) { + var rect = _kRenderingExtensions.addRectangle(node); + _kRenderingExtensions.setLineWidth(rect, 1); + _kRenderingExtensions.setLineStyle(rect, LineStyle.DOT); + _kRenderingExtensions.setForeground(rect, MODE_FG); + _linguaFrancaStyleExtensions.boldLineSelectionStyle(rect); + return rect; + } + + private void addTransitionFigure(KEdge edge, Transition transition) { + var spline = _kEdgeExtensions.addSpline(edge); + _kRenderingExtensions.setLineWidth(spline, 1.5f); + _kRenderingExtensions.setForeground(spline, MODE_FG); + _linguaFrancaStyleExtensions.boldLineSelectionStyle(spline); + + if (transition.type == ModeTransition.HISTORY) { + addHistoryDecorator(spline); + } else { + KRendering arrowDecorator = _kPolylineExtensions.addHeadArrowDecorator(spline); + this._kRenderingExtensions.setForeground(arrowDecorator, MODE_FG); + this._kRenderingExtensions.setBackground(arrowDecorator, MODE_FG); + } + + if (getBooleanValue(SHOW_TRANSITION_LABELS)) { + associateWith(spline, transition.getDefinition()); + + KLabel centerEdgeLabel = + _kLabelExtensions.addCenterEdgeLabel(edge, this.toTransitionLabel(transition)); + associateWith(centerEdgeLabel, transition.getDefinition()); + applyTransitionOnEdgeStyle(centerEdgeLabel); } - - private static LabelDecorationConfigurator _onEdgeTransitionLabelConfigurator; // ONLY for use in applyTransitionOnEdgeStyle - private void applyTransitionOnEdgeStyle(KLabel label) { - if (_onEdgeTransitionLabelConfigurator == null) { - var foreground = new Color(MODE_FG.getRed(), MODE_FG.getGreen(), MODE_FG.getBlue()); - var background = new Color(Colors.GRAY_95.getRed(), Colors.GRAY_95.getGreen(), Colors.GRAY_95.getBlue()); - _onEdgeTransitionLabelConfigurator = LabelDecorationConfigurator.create() - .withInlineLabels(true) - .withLabelTextRenderingProvider(new ITextRenderingProvider() { + } + + private String toTransitionLabel(Transition transition) { + var text = new StringBuilder(); + + text.append( + transition.reaction.triggers.stream() + .map(t -> t.getDefinition().getName()) + .collect(Collectors.joining(", "))); + return text.toString(); + } + + private static LabelDecorationConfigurator + _onEdgeTransitionLabelConfigurator; // ONLY for use in applyTransitionOnEdgeStyle + + private void applyTransitionOnEdgeStyle(KLabel label) { + if (_onEdgeTransitionLabelConfigurator == null) { + var foreground = new Color(MODE_FG.getRed(), MODE_FG.getGreen(), MODE_FG.getBlue()); + var background = + new Color(Colors.GRAY_95.getRed(), Colors.GRAY_95.getGreen(), Colors.GRAY_95.getBlue()); + _onEdgeTransitionLabelConfigurator = + LabelDecorationConfigurator.create() + .withInlineLabels(true) + .withLabelTextRenderingProvider( + new ITextRenderingProvider() { @Override public KRendering createTextRendering( - KContainerRendering container, KLabel llabel) { - var kText = _kRenderingFactory.createKText(); - _kRenderingExtensions.setFontSize(kText, 8); - container.getChildren().add(kText); - return kText; + KContainerRendering container, KLabel llabel) { + var kText = _kRenderingFactory.createKText(); + _kRenderingExtensions.setFontSize(kText, 8); + container.getChildren().add(kText); + return kText; } - }) - .addDecoratorRenderingProvider(RectangleDecorator.create().withBackground(background)) - .addDecoratorRenderingProvider(LinesDecorator.create().withColor(foreground)); - } - _onEdgeTransitionLabelConfigurator.applyTo(label); - } - - private void addHistoryDecorator(KPolyline line) { - var decorator = _kPolylineExtensions.addHeadArrowDecorator(line); - ((KDecoratorPlacementData) decorator.getPlacementData()).setAbsolute((-15.0f)); - - var ellipse = _kContainerRenderingExtensions.addEllipse(line); - _kRenderingExtensions.setDecoratorPlacementData(ellipse, 16, 16, (-6), 1, false); - _kRenderingExtensions.setLineWidth(ellipse, 0.8f); - _kRenderingExtensions.setForeground(ellipse, MODE_FG); - _kRenderingExtensions.setBackground(ellipse, Colors.WHITE); - - var innerLine = _kContainerRenderingExtensions.addPolyline(ellipse); - _kRenderingExtensions.setLineWidth(innerLine, 2); - var points = innerLine.getPoints(); - points.add(_kRenderingExtensions.createKPosition(_kRenderingExtensions.LEFT, 5, 0, _kRenderingExtensions.TOP, 4, 0)); - points.add(_kRenderingExtensions.createKPosition(_kRenderingExtensions.LEFT, 5, 0, _kRenderingExtensions.BOTTOM, 4, 0)); - points.add(_kRenderingExtensions.createKPosition(_kRenderingExtensions.LEFT, 5, 0, _kRenderingExtensions.TOP, 0, 0.5f)); - points.add(_kRenderingExtensions.createKPosition(_kRenderingExtensions.RIGHT, 5, 0, _kRenderingExtensions.TOP, 0, 0.5f)); - points.add(_kRenderingExtensions.createKPosition(_kRenderingExtensions.RIGHT, 5, 0, _kRenderingExtensions.BOTTOM, 4, 0)); - points.add(_kRenderingExtensions.createKPosition(_kRenderingExtensions.RIGHT, 5, 0, _kRenderingExtensions.TOP, 4, 0)); - _kRenderingExtensions.setForeground(innerLine, MODE_FG); + }) + .addDecoratorRenderingProvider(RectangleDecorator.create().withBackground(background)) + .addDecoratorRenderingProvider(LinesDecorator.create().withColor(foreground)); } - -} \ No newline at end of file + _onEdgeTransitionLabelConfigurator.applyTo(label); + } + + private void addHistoryDecorator(KPolyline line) { + var decorator = _kPolylineExtensions.addHeadArrowDecorator(line); + ((KDecoratorPlacementData) decorator.getPlacementData()).setAbsolute((-15.0f)); + + var ellipse = _kContainerRenderingExtensions.addEllipse(line); + _kRenderingExtensions.setDecoratorPlacementData(ellipse, 16, 16, (-6), 1, false); + _kRenderingExtensions.setLineWidth(ellipse, 0.8f); + _kRenderingExtensions.setForeground(ellipse, MODE_FG); + _kRenderingExtensions.setBackground(ellipse, Colors.WHITE); + + var innerLine = _kContainerRenderingExtensions.addPolyline(ellipse); + _kRenderingExtensions.setLineWidth(innerLine, 2); + var points = innerLine.getPoints(); + points.add( + _kRenderingExtensions.createKPosition( + _kRenderingExtensions.LEFT, 5, 0, _kRenderingExtensions.TOP, 4, 0)); + points.add( + _kRenderingExtensions.createKPosition( + _kRenderingExtensions.LEFT, 5, 0, _kRenderingExtensions.BOTTOM, 4, 0)); + points.add( + _kRenderingExtensions.createKPosition( + _kRenderingExtensions.LEFT, 5, 0, _kRenderingExtensions.TOP, 0, 0.5f)); + points.add( + _kRenderingExtensions.createKPosition( + _kRenderingExtensions.RIGHT, 5, 0, _kRenderingExtensions.TOP, 0, 0.5f)); + points.add( + _kRenderingExtensions.createKPosition( + _kRenderingExtensions.RIGHT, 5, 0, _kRenderingExtensions.BOTTOM, 4, 0)); + points.add( + _kRenderingExtensions.createKPosition( + _kRenderingExtensions.RIGHT, 5, 0, _kRenderingExtensions.TOP, 4, 0)); + _kRenderingExtensions.setForeground(innerLine, MODE_FG); + } +} diff --git a/org.lflang/src/org/lflang/diagram/synthesis/util/NamedInstanceUtil.java b/org.lflang/src/org/lflang/diagram/synthesis/util/NamedInstanceUtil.java index e7143b44fc..ab2edc658a 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/util/NamedInstanceUtil.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/util/NamedInstanceUtil.java @@ -1,27 +1,27 @@ /************* -* Copyright (c) 2020, Kiel University. -* -* 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, Kiel University. + * + * 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.diagram.synthesis.util; import de.cau.cs.kieler.klighd.kgraph.KGraphElement; @@ -31,28 +31,24 @@ /** * Utility class to link KGraphElements to NamedInstances. - * + * * @author Alexander Schulz-Rosengarten */ public class NamedInstanceUtil { - public static final Property> LINKED_INSTANCE = new Property<>( - "org.lflang.linguafranca.diagram.synthesis.graph.instance"); + public static final Property> LINKED_INSTANCE = + new Property<>("org.lflang.linguafranca.diagram.synthesis.graph.instance"); - /** - * Establishes a link between KGraphElement and NamedInstance. - */ - public static IPropertyHolder linkInstance(KGraphElement elem, NamedInstance instance) { - return elem.setProperty(LINKED_INSTANCE, instance); - } + /** Establishes a link between KGraphElement and NamedInstance. */ + public static IPropertyHolder linkInstance(KGraphElement elem, NamedInstance instance) { + return elem.setProperty(LINKED_INSTANCE, instance); + } - /** - * Returns the linked NamedInstance for the given KGraphElement. - */ - public static NamedInstance getLinkedInstance(KGraphElement elem) { - var instance = elem.getProperty(LINKED_INSTANCE); - if (instance != null) { - return instance; - } - return null; + /** Returns the linked NamedInstance for the given KGraphElement. */ + public static NamedInstance getLinkedInstance(KGraphElement elem) { + var instance = elem.getProperty(LINKED_INSTANCE); + if (instance != null) { + return instance; } + return null; + } } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/util/ReactorIcons.java b/org.lflang/src/org/lflang/diagram/synthesis/util/ReactorIcons.java index e740ebf653..a69b7d3e4f 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/util/ReactorIcons.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/util/ReactorIcons.java @@ -1,146 +1,148 @@ /************* -* Copyright (c) 2020, Kiel University. -* -* 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, Kiel University. + * + * 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.diagram.synthesis.util; -//import org.eclipse.swt.graphics.ImageData; -//import org.eclipse.swt.graphics.ImageLoader; -import org.eclipse.xtext.xbase.lib.Extension; -import org.lflang.AttributeUtils; -import org.lflang.diagram.synthesis.AbstractSynthesisExtensions; -import org.lflang.lf.ReactorDecl; -import org.lflang.util.FileUtil; - +// import org.eclipse.swt.graphics.ImageData; +// import org.eclipse.swt.graphics.ImageLoader; import com.google.inject.Inject; - import de.cau.cs.kieler.klighd.krendering.KContainerRendering; import de.cau.cs.kieler.klighd.krendering.ViewSynthesisShared; import de.cau.cs.kieler.klighd.krendering.extensions.KContainerRenderingExtensions; import de.cau.cs.kieler.klighd.krendering.extensions.KRenderingExtensions; +import org.eclipse.xtext.xbase.lib.Extension; +import org.lflang.AttributeUtils; +import org.lflang.diagram.synthesis.AbstractSynthesisExtensions; +import org.lflang.lf.ReactorDecl; +import org.lflang.util.FileUtil; /** * Utility class to handle icons for reactors in Lingua Franca diagrams. - * + * * @author Alexander Schulz-Rosengarten */ @ViewSynthesisShared public class ReactorIcons extends AbstractSynthesisExtensions { - @Inject @Extension private KRenderingExtensions _kRenderingExtensions; - @Inject @Extension private KContainerRenderingExtensions _kContainerRenderingExtensions; - -// private static final ImageLoader LOADER = new ImageLoader(); - - // Image cache during synthesis -// private final HashMap cache = new HashMap<>(); - - // Error message - private String error = null; + @Inject @Extension private KRenderingExtensions _kRenderingExtensions; + @Inject @Extension private KContainerRenderingExtensions _kContainerRenderingExtensions; + + // private static final ImageLoader LOADER = new ImageLoader(); + + // Image cache during synthesis + // private final HashMap cache = new HashMap<>(); + + // Error message + private String error = null; - public void handleIcon(KContainerRendering rendering, ReactorDecl reactor, boolean collapsed) { - if (!collapsed) { - return; - } - - // Reset error - error = null; - - // Get annotation - var iconPath = AttributeUtils.getIconPath(reactor); - if (iconPath != null && !iconPath.isEmpty()) { - var iconLocation = FileUtil.locateFile(iconPath, reactor.eResource()); - if (iconLocation == null) { - error = "Cannot find given icon file."; - } else { - /* - * This code was disabled because it cannot be compiled for the language server with Gradle. - * As soon as the Klighd API is extended to support URI-based images in both Eclipse and VSCode, - * this code should be reactivated and adapted. - * See: https://github.com/kieler/KLighD/issues/146 - */ -// ImageData data = loadImage(iconLocation); -// if (data != null) { -// KRectangle figure = _kContainerRenderingExtensions.addRectangle(rendering); -// _kRenderingExtensions.setInvisible(figure, true); -// KGridPlacementData figurePlacement = _kRenderingExtensions.setGridPlacementData(figure, data.width, data.height); -// _kRenderingExtensions.to( -// _kRenderingExtensions.from( -// figurePlacement, -// _kRenderingExtensions.LEFT, 3, 0, -// _kRenderingExtensions.TOP, 0, 0), -// _kRenderingExtensions.RIGHT, 3, 0, -// _kRenderingExtensions.BOTTOM, 3, 0); -// -// KRectangle icon = _kContainerRenderingExtensions.addRectangle(figure); -// _kRenderingExtensions.setInvisible(icon, true); -// _kContainerRenderingExtensions.addImage(icon, data); -// _kRenderingExtensions.setPointPlacementData(icon, -// _kRenderingExtensions.createKPosition( -// _kRenderingExtensions.LEFT, 0, 0.5f, -// _kRenderingExtensions.TOP, 0, 0.5f), -// _kRenderingExtensions.H_CENTRAL, _kRenderingExtensions.V_CENTRAL, 0, -// 0, data.width, data.height); -// } -// if (error != null) { -// var errorText = _kContainerRenderingExtensions.addText(rendering, "Icon not found!\n"+error); -// _kRenderingExtensions.setForeground(errorText, Colors.RED); -// _kRenderingExtensions.setFontBold(errorText, true); -// _kRenderingExtensions.setSurroundingSpaceGrid(errorText, 8, 0); -// } - } - } + public void handleIcon(KContainerRendering rendering, ReactorDecl reactor, boolean collapsed) { + if (!collapsed) { + return; } -// private ImageData loadImage(final java.net.URI uri) { -// try { -// if (cache.containsKey(uri)) { -// return cache.get(uri); -// } -// synchronized (LOADER) { -// InputStream inStream = null; -// try { -// inStream = uri.toURL().openStream(); -// ImageData[] data = LOADER.load(inStream); -// if (data != null && data.length > 0) { -// ImageData img = data[0]; -// cache.put(uri, img); -// return img; -// } else { -// error = "Could not load icon image."; -// return null; -// } -// } finally { -// if (inStream != null) { -// inStream.close(); -// } -// } -// } -// } catch (Exception ex) { -// ex.printStackTrace(); -// error = "Could not load icon image."; -// return null; -// } -// } - + // Reset error + error = null; + + // Get annotation + var iconPath = AttributeUtils.getIconPath(reactor); + if (iconPath != null && !iconPath.isEmpty()) { + var iconLocation = FileUtil.locateFile(iconPath, reactor.eResource()); + if (iconLocation == null) { + error = "Cannot find given icon file."; + } else { + /* + * This code was disabled because it cannot be compiled for the language server with Gradle. + * As soon as the Klighd API is extended to support URI-based images in both Eclipse and VSCode, + * this code should be reactivated and adapted. + * See: https://github.com/kieler/KLighD/issues/146 + */ + // ImageData data = loadImage(iconLocation); + // if (data != null) { + // KRectangle figure = + // _kContainerRenderingExtensions.addRectangle(rendering); + // _kRenderingExtensions.setInvisible(figure, true); + // KGridPlacementData figurePlacement = + // _kRenderingExtensions.setGridPlacementData(figure, data.width, data.height); + // _kRenderingExtensions.to( + // _kRenderingExtensions.from( + // figurePlacement, + // _kRenderingExtensions.LEFT, 3, 0, + // _kRenderingExtensions.TOP, 0, 0), + // _kRenderingExtensions.RIGHT, 3, 0, + // _kRenderingExtensions.BOTTOM, 3, 0); + // + // KRectangle icon = _kContainerRenderingExtensions.addRectangle(figure); + // _kRenderingExtensions.setInvisible(icon, true); + // _kContainerRenderingExtensions.addImage(icon, data); + // _kRenderingExtensions.setPointPlacementData(icon, + // _kRenderingExtensions.createKPosition( + // _kRenderingExtensions.LEFT, 0, 0.5f, + // _kRenderingExtensions.TOP, 0, 0.5f), + // _kRenderingExtensions.H_CENTRAL, + // _kRenderingExtensions.V_CENTRAL, 0, + // 0, data.width, data.height); + // } + // if (error != null) { + // var errorText = _kContainerRenderingExtensions.addText(rendering, + // "Icon not found!\n"+error); + // _kRenderingExtensions.setForeground(errorText, Colors.RED); + // _kRenderingExtensions.setFontBold(errorText, true); + // _kRenderingExtensions.setSurroundingSpaceGrid(errorText, 8, 0); + // } + } + } + } + + // private ImageData loadImage(final java.net.URI uri) { + // try { + // if (cache.containsKey(uri)) { + // return cache.get(uri); + // } + // synchronized (LOADER) { + // InputStream inStream = null; + // try { + // inStream = uri.toURL().openStream(); + // ImageData[] data = LOADER.load(inStream); + // if (data != null && data.length > 0) { + // ImageData img = data[0]; + // cache.put(uri, img); + // return img; + // } else { + // error = "Could not load icon image."; + // return null; + // } + // } finally { + // if (inStream != null) { + // inStream.close(); + // } + // } + // } + // } catch (Exception ex) { + // ex.printStackTrace(); + // error = "Could not load icon image."; + // return null; + // } + // } + } diff --git a/org.lflang/src/org/lflang/diagram/synthesis/util/SynthesisErrorReporter.java b/org.lflang/src/org/lflang/diagram/synthesis/util/SynthesisErrorReporter.java index fdf5070713..a249c009fd 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/util/SynthesisErrorReporter.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/util/SynthesisErrorReporter.java @@ -1,27 +1,27 @@ /************* -* Copyright (c) 2020, Kiel University. -* -* 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, Kiel University. + * + * 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.diagram.synthesis.util; import java.nio.file.Path; @@ -32,53 +32,53 @@ * @author Alexander Schulz-Rosengarten */ public class SynthesisErrorReporter implements ErrorReporter { - @Override - public String reportError(String message) { - return null; - } - - @Override - public String reportError(EObject object, String message) { - return null; - } - - @Override - public String reportError(Path file, Integer line, String message) { - return null; - } - - @Override - public String reportWarning(String message) { - return null; - } - - @Override - public String reportWarning(EObject object, String message) { - return null; - } - - @Override - public String reportWarning(Path file, Integer line, String message) { - return null; - } + @Override + public String reportError(String message) { + return null; + } - @Override - public String reportInfo(String message) { - return null; - } + @Override + public String reportError(EObject object, String message) { + return null; + } - @Override - public String reportInfo(EObject object, String message) { - return null; - } + @Override + public String reportError(Path file, Integer line, String message) { + return null; + } - @Override - public String reportInfo(Path file, Integer line, String message) { - return null; - } + @Override + public String reportWarning(String message) { + return null; + } - @Override - public boolean getErrorsOccurred() { - return false; - } -} \ No newline at end of file + @Override + public String reportWarning(EObject object, String message) { + return null; + } + + @Override + public String reportWarning(Path file, Integer line, String message) { + return null; + } + + @Override + public String reportInfo(String message) { + return null; + } + + @Override + public String reportInfo(EObject object, String message) { + return null; + } + + @Override + public String reportInfo(Path file, Integer line, String message) { + return null; + } + + @Override + public boolean getErrorsOccurred() { + return false; + } +} diff --git a/org.lflang/src/org/lflang/diagram/synthesis/util/UtilityExtensions.java b/org.lflang/src/org/lflang/diagram/synthesis/util/UtilityExtensions.java index fb97bd1449..ca83da41a8 100644 --- a/org.lflang/src/org/lflang/diagram/synthesis/util/UtilityExtensions.java +++ b/org.lflang/src/org/lflang/diagram/synthesis/util/UtilityExtensions.java @@ -1,33 +1,38 @@ /************* -* Copyright (c) 2020, Kiel University. -* -* 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, Kiel University. + * + * 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.diagram.synthesis.util; +import de.cau.cs.kieler.klighd.internal.util.KlighdInternalProperties; +import de.cau.cs.kieler.klighd.kgraph.KGraphElement; +import de.cau.cs.kieler.klighd.kgraph.KGraphFactory; +import de.cau.cs.kieler.klighd.kgraph.KIdentifier; +import de.cau.cs.kieler.klighd.kgraph.KNode; +import de.cau.cs.kieler.klighd.krendering.ViewSynthesisShared; import java.util.ArrayList; import java.util.Arrays; import java.util.List; - import org.eclipse.elk.core.math.ElkMargin; import org.eclipse.elk.core.options.CoreOptions; import org.eclipse.elk.core.util.IndividualSpacings; @@ -39,163 +44,133 @@ import org.lflang.diagram.synthesis.AbstractSynthesisExtensions; import org.lflang.generator.ReactorInstance; import org.lflang.lf.Code; -import org.lflang.lf.Expression; -import org.lflang.lf.Host; -import org.lflang.lf.Literal; -import org.lflang.lf.ParameterReference; import org.lflang.lf.Reactor; -import org.lflang.lf.Time; -import org.lflang.util.StringUtil; - -import de.cau.cs.kieler.klighd.internal.util.KlighdInternalProperties; -import de.cau.cs.kieler.klighd.kgraph.KGraphElement; -import de.cau.cs.kieler.klighd.kgraph.KGraphFactory; -import de.cau.cs.kieler.klighd.kgraph.KIdentifier; -import de.cau.cs.kieler.klighd.kgraph.KNode; -import de.cau.cs.kieler.klighd.krendering.ViewSynthesisShared; /** * Extension class that provides various utility methods for the synthesis. - * + * * @author Alexander Schulz-Rosengarten */ @ViewSynthesisShared public class UtilityExtensions extends AbstractSynthesisExtensions { - - @Extension - private KGraphFactory _kGraphFactory = KGraphFactory.eINSTANCE; - - - /** - * Returns true if the reactor is the primary reactor - */ - public boolean isMainOrFederated(Reactor reactor) { - return reactor.isMain() || reactor.isFederated(); - } - - /** - * Returns true if the instance is a bank of reactors - */ -// def boolean isBank(Instantiation ins) { -// return ins?.widthSpec !== null ? ins.widthSpec.width !== 1 : false -// } - - /** - * Returns true if the reactor as has inner reactions or instances - */ - public boolean hasContent(final ReactorInstance reactor) { - return !reactor.reactions.isEmpty() || !reactor.instantiations().isEmpty(); - } - - /** - * - */ - public boolean isRoot(final ReactorInstance ri) { - return ri.getParent() == null; + + @Extension private KGraphFactory _kGraphFactory = KGraphFactory.eINSTANCE; + + /** Returns true if the reactor is the primary reactor */ + public boolean isMainOrFederated(Reactor reactor) { + return reactor.isMain() || reactor.isFederated(); + } + + /** Returns true if the instance is a bank of reactors */ + // def boolean isBank(Instantiation ins) { + // return ins?.widthSpec !== null ? ins.widthSpec.width !== 1 : false + // } + + /** Returns true if the reactor as has inner reactions or instances */ + public boolean hasContent(final ReactorInstance reactor) { + return !reactor.reactions.isEmpty() || !reactor.instantiations().isEmpty(); + } + + /** */ + public boolean isRoot(final ReactorInstance ri) { + return ri.getParent() == null; + } + + /** Trims the hostcode of reactions. */ + public String trimCode(final Code tokenizedCode) { + if (tokenizedCode == null || StringExtensions.isNullOrEmpty(tokenizedCode.getBody())) { + return ""; } - - /** - * Trims the hostcode of reactions. - */ - public String trimCode(final Code tokenizedCode) { - if (tokenizedCode == null || StringExtensions.isNullOrEmpty(tokenizedCode.getBody())) { - return ""; + try { + ICompositeNode node = NodeModelUtils.findActualNodeFor(tokenizedCode); + String code = node != null ? node.getText() : null; + int contentStart = 0; + List lines = new ArrayList<>(); + Arrays.stream(code.split("\n")) + .dropWhile(line -> !line.contains("{=")) + .forEachOrdered(lines::add); + + // Remove start pattern + if (!lines.isEmpty()) { + if (IterableExtensions.head(lines).trim().equals("{=")) { + lines.remove(0); // skip + } else { + lines.set(0, IterableExtensions.head(lines).replace("{=", "").trim()); + contentStart = 1; } - try { - ICompositeNode node = NodeModelUtils.findActualNodeFor(tokenizedCode); - String code = node != null ? node.getText() : null; - int contentStart = 0; - List lines = new ArrayList<>(); - Arrays.stream(code.split("\n")).dropWhile(line -> !line.contains("{=")).forEachOrdered(lines::add); - - // Remove start pattern - if (!lines.isEmpty()) { - if (IterableExtensions.head(lines).trim().equals("{=")) { - lines.remove(0); // skip - } else { - lines.set(0, IterableExtensions.head(lines).replace("{=", "").trim()); - contentStart = 1; - } - } - - // Remove end pattern - if (!lines.isEmpty()) { - if (IterableExtensions.last(lines).trim().equals("=}")) { - lines.remove(lines.size() - 1); // skip - } else { - lines.set(lines.size() - 1, IterableExtensions.last(lines).replace("=}", "")); - } - } - - // Find indentation - String indentation = null; - while (indentation == null && lines.size() > contentStart) { - String firstLine = lines.get(contentStart); - String trimmed = firstLine.trim(); - if (trimmed.isEmpty()) { - lines.set(contentStart, ""); - contentStart++; - } else { - int firstCharIdx = firstLine.indexOf(trimmed.charAt(0)); - indentation = firstLine.substring(0, firstCharIdx); - } - } - - // Remove root indentation - if (!lines.isEmpty()) { - for (int i = 0; i < lines.size(); i++) { - if (lines.get(i).startsWith(indentation)) { - lines.set(i, lines.get(i).substring(indentation.length())); - } - } - } - - return String.join("\n", lines); - } catch(Exception e) { - e.printStackTrace(); - return tokenizedCode.getBody(); + } + + // Remove end pattern + if (!lines.isEmpty()) { + if (IterableExtensions.last(lines).trim().equals("=}")) { + lines.remove(lines.size() - 1); // skip + } else { + lines.set(lines.size() - 1, IterableExtensions.last(lines).replace("=}", "")); } - } - - /** - * Sets KGE ID. - */ - public boolean setID(KGraphElement kge, String id) { - KIdentifier identifier = _kGraphFactory.createKIdentifier(); - identifier.setId(id); - return kge.getData().add(identifier); - } - - /** - * Retrieves the source element of the given diagram element - */ - public Object sourceElement(KGraphElement elem) { - return elem.getProperty(KlighdInternalProperties.MODEL_ELEMEMT); - } - - /** - * Checks if the source element of the given diagram element is a reactor - */ - public boolean sourceIsReactor(KNode node) { - return sourceElement(node) instanceof Reactor; - } + } - /** - * Returns the port placement margins for the node. - * If this spacing does not yet exist, the properties are initialized. - */ - public ElkMargin getPortMarginsInitIfAbsent(KNode node) { - IndividualSpacings spacing = node.getProperty(CoreOptions.SPACING_INDIVIDUAL); - if (spacing == null) { - spacing = new IndividualSpacings(); - node.setProperty(CoreOptions.SPACING_INDIVIDUAL, spacing); + // Find indentation + String indentation = null; + while (indentation == null && lines.size() > contentStart) { + String firstLine = lines.get(contentStart); + String trimmed = firstLine.trim(); + if (trimmed.isEmpty()) { + lines.set(contentStart, ""); + contentStart++; + } else { + int firstCharIdx = firstLine.indexOf(trimmed.charAt(0)); + indentation = firstLine.substring(0, firstCharIdx); } - ElkMargin margin = spacing.getProperty(CoreOptions.SPACING_PORTS_SURROUNDING); - if (margin == null) { - margin = new ElkMargin(); - node.setProperty(CoreOptions.SPACING_PORTS_SURROUNDING, margin); + } + + // Remove root indentation + if (!lines.isEmpty()) { + for (int i = 0; i < lines.size(); i++) { + if (lines.get(i).startsWith(indentation)) { + lines.set(i, lines.get(i).substring(indentation.length())); + } } - return margin; - } + } + + return String.join("\n", lines); + } catch (Exception e) { + e.printStackTrace(); + return tokenizedCode.getBody(); + } + } + + /** Sets KGE ID. */ + public boolean setID(KGraphElement kge, String id) { + KIdentifier identifier = _kGraphFactory.createKIdentifier(); + identifier.setId(id); + return kge.getData().add(identifier); + } + /** Retrieves the source element of the given diagram element */ + public Object sourceElement(KGraphElement elem) { + return elem.getProperty(KlighdInternalProperties.MODEL_ELEMEMT); + } + + /** Checks if the source element of the given diagram element is a reactor */ + public boolean sourceIsReactor(KNode node) { + return sourceElement(node) instanceof Reactor; + } + + /** + * Returns the port placement margins for the node. If this spacing does not yet exist, the + * properties are initialized. + */ + public ElkMargin getPortMarginsInitIfAbsent(KNode node) { + IndividualSpacings spacing = node.getProperty(CoreOptions.SPACING_INDIVIDUAL); + if (spacing == null) { + spacing = new IndividualSpacings(); + node.setProperty(CoreOptions.SPACING_INDIVIDUAL, spacing); + } + ElkMargin margin = spacing.getProperty(CoreOptions.SPACING_PORTS_SURROUNDING); + if (margin == null) { + margin = new ElkMargin(); + node.setProperty(CoreOptions.SPACING_PORTS_SURROUNDING, margin); + } + return margin; + } } diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtension.java b/org.lflang/src/org/lflang/federated/extensions/CExtension.java index a38b557ed5..5a41efbbd0 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtension.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtension.java @@ -33,14 +33,13 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; - -import org.lflang.ast.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; +import org.lflang.ast.ASTUtils; import org.lflang.federated.generator.FedASTUtils; import org.lflang.federated.generator.FedConnectionInstance; import org.lflang.federated.generator.FedFileConfig; @@ -49,7 +48,6 @@ import org.lflang.federated.serialization.FedROS2CPPSerialization; import org.lflang.generator.CodeBuilder; import org.lflang.generator.LFGeneratorContext; -import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactorInstance; import org.lflang.generator.c.CTypes; import org.lflang.generator.c.CUtil; @@ -59,735 +57,799 @@ import org.lflang.lf.VarRef; /** - * An extension class to the CGenerator that enables certain federated - * functionalities. Currently, this class offers the following features: + * An extension class to the CGenerator that enables certain federated functionalities. Currently, + * this class offers the following features: * *

    - *
  • Allocating and initializing C structures for federated communication
  • - *
  • Creating status field for network input ports that help the receiver logic in - * federate.c communicate the status of a network input port with network input - * control reactions.
  • + *
  • Allocating and initializing C structures for federated communication + *
  • Creating status field for network input ports that help the receiver logic in federate.c + * communicate the status of a network input port with network input control reactions. *
* * @author {Soroush Bateni } * @author {Hou Seng Wong } * @author {Billy Bao } - * */ public class CExtension implements FedTargetExtension { - @Override - public void initializeTargetConfig( - LFGeneratorContext context, - int numOfFederates, FederateInstance federate, - FedFileConfig fileConfig, - ErrorReporter errorReporter, - RtiConfig rtiConfig - ) throws IOException { - - CExtensionUtils.handleCompileDefinitions(federate, numOfFederates, rtiConfig); - - generateCMakeInclude(federate, fileConfig); - - federate.targetConfig.keepalive = true; - federate.targetConfig.setByUser.add(TargetProperty.KEEPALIVE); - - // If there are federates, copy the required files for that. - // Also, create the RTI C file and the launcher script. - // Handle target parameters. - // If the program is federated, then ensure that threading is enabled. - federate.targetConfig.threading = true; - federate.targetConfig.setByUser.add(TargetProperty.THREADING); - - // Include the fed setup file for this federate in the target property - String relPath = getPreamblePath(federate); - federate.targetConfig.fedSetupPreamble = relPath; - federate.targetConfig.setByUser.add(TargetProperty.FED_SETUP); + @Override + public void initializeTargetConfig( + LFGeneratorContext context, + int numOfFederates, + FederateInstance federate, + FedFileConfig fileConfig, + ErrorReporter errorReporter, + RtiConfig rtiConfig) + throws IOException { + + CExtensionUtils.handleCompileDefinitions(federate, numOfFederates, rtiConfig); + + generateCMakeInclude(federate, fileConfig); + + federate.targetConfig.keepalive = true; + federate.targetConfig.setByUser.add(TargetProperty.KEEPALIVE); + + // If there are federates, copy the required files for that. + // Also, create the RTI C file and the launcher script. + // Handle target parameters. + // If the program is federated, then ensure that threading is enabled. + federate.targetConfig.threading = true; + federate.targetConfig.setByUser.add(TargetProperty.THREADING); + + // Include the fed setup file for this federate in the target property + String relPath = getPreamblePath(federate); + federate.targetConfig.fedSetupPreamble = relPath; + federate.targetConfig.setByUser.add(TargetProperty.FED_SETUP); + } + + /** Generate a cmake-include file for {@code federate} if needed. */ + protected void generateCMakeInclude(FederateInstance federate, FedFileConfig fileConfig) + throws IOException { + CExtensionUtils.generateCMakeInclude(federate, fileConfig); + } + + /** + * Generate code for the body of a reaction that handles the action that is triggered by receiving + * a message from a remote federate. + * + * @param action The action. + * @param sendingPort The output port providing the data to send. + * @param receivingPort The ID of the destination port. + * @param connection FIXME + * @param type FIXME + * @param coordinationType The coordination type + * @param errorReporter + */ + public String generateNetworkReceiverBody( + Action action, + VarRef sendingPort, + VarRef receivingPort, + FedConnectionInstance connection, + InferredType type, + CoordinationType coordinationType, + ErrorReporter errorReporter) { + var receiveRef = + CUtil.portRefInReaction(receivingPort, connection.getDstBank(), connection.getDstChannel()); + var result = new CodeBuilder(); + // Transfer the physical time of arrival from the action to the port + result.pr( + receiveRef + + "->physical_time_of_arrival = self->_lf__" + + action.getName() + + ".physical_time_of_arrival;"); + if (coordinationType == CoordinationType.DECENTRALIZED + && !connection.getDefinition().isPhysical()) { + // Transfer the intended tag. + result.pr( + receiveRef + "->intended_tag = self->_lf__" + action.getName() + ".intended_tag;\n"); } - /** - * Generate a cmake-include file for {@code federate} if needed. - */ - protected void generateCMakeInclude(FederateInstance federate, FedFileConfig fileConfig) throws IOException { - CExtensionUtils.generateCMakeInclude(federate, fileConfig); + deserialize(action, receivingPort, connection, type, receiveRef, result, errorReporter); + return result.toString(); + } + + /** + * Generate code to deserialize a message received over the network. + * + * @param action The network action that is mapped to the {@code receivingPort} + * @param receivingPort The receiving port + * @param connection The connection used to receive the message + * @param type Type for the port + * @param receiveRef A target language reference to the receiving port + * @param result Used to put generated code in + * @param errorReporter Used to report errors, if any + */ + protected void deserialize( + Action action, + VarRef receivingPort, + FedConnectionInstance connection, + InferredType type, + String receiveRef, + CodeBuilder result, + ErrorReporter 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 + // used only for statically allocated strings. This would force the + // CGenerator to treat the port and action as token types. + if (types.getTargetType(action).equals("string")) { + action.getType().setCode(null); + action.getType().setId("char*"); } - - /** - * Generate code for the body of a reaction that handles the - * action that is triggered by receiving a message from a remote - * federate. - * @param action The action. - * @param sendingPort The output port providing the data to send. - * @param receivingPort The ID of the destination port. - * @param connection FIXME - * @param type FIXME - * @param coordinationType The coordination type - * @param errorReporter - */ - public String generateNetworkReceiverBody( - Action action, - VarRef sendingPort, - VarRef receivingPort, - FedConnectionInstance connection, - InferredType type, - CoordinationType coordinationType, - ErrorReporter errorReporter - ) { - var receiveRef = CUtil.portRefInReaction(receivingPort, connection.getDstBank(), connection.getDstChannel()); - var result = new CodeBuilder(); - // Transfer the physical time of arrival from the action to the port - result.pr(receiveRef+"->physical_time_of_arrival = self->_lf__"+action.getName()+".physical_time_of_arrival;"); - if (coordinationType == CoordinationType.DECENTRALIZED && !connection.getDefinition().isPhysical()) { - // Transfer the intended tag. - result.pr(receiveRef+"->intended_tag = self->_lf__"+action.getName()+".intended_tag;\n"); - } - - deserialize(action, receivingPort, connection, type, receiveRef, result, errorReporter); - return result.toString(); + if (types.getTargetType((Port) receivingPort.getVariable()).equals("string")) { + ((Port) receivingPort.getVariable()).getType().setCode(null); + ((Port) receivingPort.getVariable()).getType().setId("char*"); } - - /** - * Generate code to deserialize a message received over the network. - * @param action The network action that is mapped to the {@code receivingPort} - * @param receivingPort The receiving port - * @param connection The connection used to receive the message - * @param type Type for the port - * @param receiveRef A target language reference to the receiving port - * @param result Used to put generated code in - * @param errorReporter Used to report errors, if any - */ - protected void deserialize( - Action action, - VarRef receivingPort, - FedConnectionInstance connection, - InferredType type, - String receiveRef, - CodeBuilder result, - ErrorReporter 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 - // used only for statically allocated strings. This would force the - // CGenerator to treat the port and action as token types. - if (types.getTargetType(action).equals("string")) { - action.getType().setCode(null); - action.getType().setId("char*"); - } - if (types.getTargetType((Port) receivingPort.getVariable()).equals("string")) { - ((Port) receivingPort.getVariable()).getType().setCode(null); - ((Port) receivingPort.getVariable()).getType().setId("char*"); + var value = ""; + switch (connection.getSerializer()) { + case NATIVE: + { + // NOTE: Docs say that malloc'd char* is freed on conclusion of the time step. + // So passing it downstream should be OK. + value = action.getName() + "->value"; + if (CUtil.isTokenType(type, types)) { + result.pr("lf_set_token(" + receiveRef + ", " + action.getName() + "->token);"); + } else { + result.pr("lf_set(" + receiveRef + ", " + value + ");"); + } + break; } - var value = ""; - switch (connection.getSerializer()) { - case NATIVE: { - // NOTE: Docs say that malloc'd char* is freed on conclusion of the time step. - // So passing it downstream should be OK. - value = action.getName()+"->value"; - if (CUtil.isTokenType(type, types)) { - result.pr("lf_set_token("+ receiveRef +", "+ action.getName()+"->token);"); - } else { - result.pr("lf_set("+ receiveRef +", "+value+");"); - } - break; - } - case PROTO: { - throw new UnsupportedOperationException("Protobuf serialization is not supported yet."); + case PROTO: + { + throw new UnsupportedOperationException("Protobuf serialization is not supported yet."); } - case ROS2: { - var portType = ASTUtils.getInferredType(((Port) receivingPort.getVariable())); - var portTypeStr = types.getTargetType(portType); - if (CUtil.isTokenType(portType, types)) { - throw new UnsupportedOperationException("Cannot handle ROS serialization when ports are pointers."); - } else if (CExtensionUtils.isSharedPtrType(portType, types)) { - var matcher = CExtensionUtils.sharedPointerVariable.matcher(portTypeStr); - if (matcher.find()) { - portTypeStr = matcher.group("type"); - } + case ROS2: + { + var portType = ASTUtils.getInferredType(((Port) receivingPort.getVariable())); + var portTypeStr = types.getTargetType(portType); + if (CUtil.isTokenType(portType, types)) { + throw new UnsupportedOperationException( + "Cannot handle ROS serialization when ports are pointers."); + } else if (CExtensionUtils.isSharedPtrType(portType, types)) { + var matcher = CExtensionUtils.sharedPointerVariable.matcher(portTypeStr); + if (matcher.find()) { + portTypeStr = matcher.group("type"); } - var ROSDeserializer = new FedROS2CPPSerialization(); - value = FedROS2CPPSerialization.deserializedVarName; + } + var ROSDeserializer = new FedROS2CPPSerialization(); + value = FedROS2CPPSerialization.deserializedVarName; + result.pr( + ROSDeserializer.generateNetworkDeserializerCode( + "self->_lf__" + action.getName(), portTypeStr)); + if (CExtensionUtils.isSharedPtrType(portType, types)) { result.pr( - ROSDeserializer.generateNetworkDeserializerCode( - "self->_lf__"+ action.getName(), - portTypeStr - ) - ); - if (CExtensionUtils.isSharedPtrType(portType, types)) { - result.pr("auto msg_shared_ptr = std::make_shared<"+portTypeStr+">("+value+");"); - result.pr("lf_set("+ receiveRef +", msg_shared_ptr);"); - } else { - result.pr("lf_set("+ receiveRef +", std::move("+value+"));"); - } - break; - } + "auto msg_shared_ptr = std::make_shared<" + portTypeStr + ">(" + value + ");"); + result.pr("lf_set(" + receiveRef + ", msg_shared_ptr);"); + } else { + result.pr("lf_set(" + receiveRef + ", std::move(" + value + "));"); + } + break; } } + } + + /** + * Generate code for the body of a reaction that handles an output that is to be sent over the + * network. + * + * @param sendingPort The output port providing the data to send. + * @param receivingPort The variable reference to the destination port. + * @param connection + * @param type + * @param coordinationType + * @param errorReporter FIXME + */ + public String generateNetworkSenderBody( + VarRef sendingPort, + VarRef receivingPort, + FedConnectionInstance connection, + InferredType type, + CoordinationType coordinationType, + ErrorReporter errorReporter) { + var sendRef = + CUtil.portRefInReaction(sendingPort, connection.getSrcBank(), connection.getSrcChannel()); + var receiveRef = + ASTUtils.generateVarRef( + receivingPort); // Used for comments only, so no need for bank/multiport index. + var result = new CodeBuilder(); + // The ID of the receiving port (rightPort) is the position + // of the action in this list. + int receivingPortID = connection.getDstFederate().networkMessageActions.size(); + + result.pr( + "// Sending from " + + sendRef + + " in federate " + + connection.getSrcFederate().name + + " to " + + receiveRef + + " in federate " + + connection.getDstFederate().name); + + // In case sendRef is a multiport or is in a bank, this reaction will be triggered when any + // channel or bank index of sendRef is present + // ex. if a.out[i] is present, the entire output a.out is triggered. + if (connection.getSrcBank() != -1 || connection.getSrcChannel() != -1) { + result.pr("if (!" + sendRef + "->is_present) return;"); + } - /** - * Generate code for the body of a reaction that handles an output - * that is to be sent over the network. - * @param sendingPort The output port providing the data to send. - * @param receivingPort The variable reference to the destination port. - * @param connection - * @param type - * @param coordinationType - * @param errorReporter FIXME - */ - public String generateNetworkSenderBody( - VarRef sendingPort, - VarRef receivingPort, - FedConnectionInstance connection, - InferredType type, - CoordinationType coordinationType, - ErrorReporter errorReporter - ) { - var sendRef = CUtil.portRefInReaction(sendingPort, connection.getSrcBank(), connection.getSrcChannel()); - var receiveRef = ASTUtils.generateVarRef(receivingPort); // Used for comments only, so no need for bank/multiport index. - var result = new CodeBuilder(); - // The ID of the receiving port (rightPort) is the position - // of the action in this list. - int receivingPortID = connection.getDstFederate().networkMessageActions.size(); - - result.pr("// Sending from " + sendRef + " in federate " - + connection.getSrcFederate().name + " to " + receiveRef - + " in federate " + connection.getDstFederate().name); - - // In case sendRef is a multiport or is in a bank, this reaction will be triggered when any channel or bank index of sendRef is present - // ex. if a.out[i] is present, the entire output a.out is triggered. - if (connection.getSrcBank() != -1 || connection.getSrcChannel() != -1) { - result.pr("if (!"+sendRef+"->is_present) return;"); - } - - // If the connection is physical and the receiving federate is remote, send it directly on a socket. - // If the connection is logical and the coordination mode is centralized, send via RTI. - // If the connection is logical and the coordination mode is decentralized, send directly - String messageType; - // Name of the next immediate destination of this message - var next_destination_name = "\"federate "+connection.getDstFederate().id+"\""; - - // Get the delay literal - String additionalDelayString = CExtensionUtils.getNetworkDelayLiteral(connection.getDefinition().getDelay()); - - if (connection.getDefinition().isPhysical()) { - messageType = "MSG_TYPE_P2P_MESSAGE"; - } else if (coordinationType == CoordinationType.DECENTRALIZED) { - messageType = "MSG_TYPE_P2P_TAGGED_MESSAGE"; - } else { - // Logical connection - // Send the message via rti - messageType = "MSG_TYPE_TAGGED_MESSAGE"; - next_destination_name = "\"federate "+connection.getDstFederate().id+" via the RTI\""; - } - - - String sendingFunction = "send_timed_message"; - String commonArgs = String.join(", ", - additionalDelayString, - messageType, - receivingPortID + "", - connection.getDstFederate().id + "", - next_destination_name, - "message_length" - ); - if (connection.getDefinition().isPhysical()) { - // Messages going on a physical connection do not - // carry a timestamp or require the delay; - sendingFunction = "send_message"; - commonArgs = messageType+", "+receivingPortID+", "+connection.getDstFederate().id+", "+next_destination_name+", message_length"; - } + // If the connection is physical and the receiving federate is remote, send it directly on a + // socket. + // If the connection is logical and the coordination mode is centralized, send via RTI. + // If the connection is logical and the coordination mode is decentralized, send directly + String messageType; + // Name of the next immediate destination of this message + var next_destination_name = "\"federate " + connection.getDstFederate().id + "\""; + + // Get the delay literal + String additionalDelayString = + CExtensionUtils.getNetworkDelayLiteral(connection.getDefinition().getDelay()); + + if (connection.getDefinition().isPhysical()) { + messageType = "MSG_TYPE_P2P_MESSAGE"; + } else if (coordinationType == CoordinationType.DECENTRALIZED) { + messageType = "MSG_TYPE_P2P_TAGGED_MESSAGE"; + } else { + // Logical connection + // Send the message via rti + messageType = "MSG_TYPE_TAGGED_MESSAGE"; + next_destination_name = "\"federate " + connection.getDstFederate().id + " via the RTI\""; + } - serializeAndSend( - connection, - type, - sendRef, - result, - sendingFunction, - commonArgs, - errorReporter - ); - return result.toString(); + String sendingFunction = "send_timed_message"; + String commonArgs = + String.join( + ", ", + additionalDelayString, + messageType, + receivingPortID + "", + connection.getDstFederate().id + "", + next_destination_name, + "message_length"); + if (connection.getDefinition().isPhysical()) { + // Messages going on a physical connection do not + // carry a timestamp or require the delay; + sendingFunction = "send_message"; + commonArgs = + messageType + + ", " + + receivingPortID + + ", " + + connection.getDstFederate().id + + ", " + + next_destination_name + + ", message_length"; } - /** - * FIXME - * @param connection - * @param type - * @param sendRef - * @param result - * @param sendingFunction - * @param commonArgs - * @param errorReporter - */ - protected void serializeAndSend( - FedConnectionInstance connection, - InferredType type, - String sendRef, - CodeBuilder result, - String sendingFunction, - String commonArgs, - ErrorReporter errorReporter - ) { - CTypes types = new CTypes(); - var lengthExpression = ""; - var pointerExpression = ""; - switch (connection.getSerializer()) { - case NATIVE: { - // Handle native types. - if (CUtil.isTokenType(type, types)) { - // NOTE: Transporting token types this way is likely to only work if the sender and receiver - // both have the same endianness. Otherwise, you have to use protobufs or some other serialization scheme. - result.pr("size_t message_length = "+ sendRef +"->token->length * "+ sendRef - +"->token->type->element_size;"); - result.pr(sendingFunction +"("+ commonArgs +", (unsigned char*) "+ sendRef - +"->value);"); - } else { - // string types need to be dealt with specially because they are hidden pointers. - // void type is odd, but it avoids generating non-standard expression sizeof(void), - // which some compilers reject. - lengthExpression = "sizeof("+ types.getTargetType(type)+")"; - pointerExpression = "(unsigned char*)&"+ sendRef +"->value"; - var targetType = types.getTargetType(type); - if (targetType.equals("string")) { - lengthExpression = "strlen("+ sendRef +"->value) + 1"; - pointerExpression = "(unsigned char*) "+ sendRef +"->value"; - } else if (targetType.equals("void")) { - lengthExpression = "0"; - } - result.pr("size_t message_length = "+lengthExpression+";"); - result.pr( - sendingFunction +"("+ commonArgs +", "+pointerExpression+");"); + serializeAndSend(connection, type, sendRef, result, sendingFunction, commonArgs, errorReporter); + return result.toString(); + } + + /** + * FIXME + * + * @param connection + * @param type + * @param sendRef + * @param result + * @param sendingFunction + * @param commonArgs + * @param errorReporter + */ + protected void serializeAndSend( + FedConnectionInstance connection, + InferredType type, + String sendRef, + CodeBuilder result, + String sendingFunction, + String commonArgs, + ErrorReporter errorReporter) { + CTypes types = new CTypes(); + var lengthExpression = ""; + var pointerExpression = ""; + switch (connection.getSerializer()) { + case NATIVE: + { + // Handle native types. + if (CUtil.isTokenType(type, types)) { + // NOTE: Transporting token types this way is likely to only work if the sender and + // receiver + // both have the same endianness. Otherwise, you have to use protobufs or some other + // serialization scheme. + result.pr( + "size_t message_length = " + + sendRef + + "->token->length * " + + sendRef + + "->token->type->element_size;"); + result.pr( + sendingFunction + "(" + commonArgs + ", (unsigned char*) " + sendRef + "->value);"); + } else { + // string types need to be dealt with specially because they are hidden pointers. + // void type is odd, but it avoids generating non-standard expression sizeof(void), + // which some compilers reject. + lengthExpression = "sizeof(" + types.getTargetType(type) + ")"; + pointerExpression = "(unsigned char*)&" + sendRef + "->value"; + var targetType = types.getTargetType(type); + if (targetType.equals("string")) { + lengthExpression = "strlen(" + sendRef + "->value) + 1"; + pointerExpression = "(unsigned char*) " + sendRef + "->value"; + } else if (targetType.equals("void")) { + lengthExpression = "0"; } - break; + result.pr("size_t message_length = " + lengthExpression + ";"); + result.pr(sendingFunction + "(" + commonArgs + ", " + pointerExpression + ");"); + } + break; } - case PROTO: { - throw new UnsupportedOperationException("Protobuf serialization is not supported yet."); + case PROTO: + { + throw new UnsupportedOperationException("Protobuf serialization is not supported yet."); } - case ROS2: { - var variableToSerialize = sendRef; - var typeStr = types.getTargetType(type); - if (CUtil.isTokenType(type, types)) { - throw new UnsupportedOperationException("Cannot handle ROS serialization when ports are pointers."); - } else if (CExtensionUtils.isSharedPtrType(type, types)) { - var matcher = CExtensionUtils.sharedPointerVariable.matcher(typeStr); - if (matcher.find()) { - typeStr = matcher.group("type"); - } + case ROS2: + { + var variableToSerialize = sendRef; + var typeStr = types.getTargetType(type); + if (CUtil.isTokenType(type, types)) { + throw new UnsupportedOperationException( + "Cannot handle ROS serialization when ports are pointers."); + } else if (CExtensionUtils.isSharedPtrType(type, types)) { + var matcher = CExtensionUtils.sharedPointerVariable.matcher(typeStr); + if (matcher.find()) { + typeStr = matcher.group("type"); } - var ROSSerializer = new FedROS2CPPSerialization(); - lengthExpression = ROSSerializer.serializedBufferLength(); - pointerExpression = ROSSerializer.seializedBufferVar(); - result.pr( - ROSSerializer.generateNetworkSerializerCode(variableToSerialize, typeStr, CExtensionUtils.isSharedPtrType(type, types)) - ); - result.pr("size_t message_length = "+lengthExpression+";"); - result.pr(sendingFunction +"("+ commonArgs +", "+pointerExpression+");"); - break; - } - - } - } - - /** - * Generate code for the body of a reaction that decides whether the trigger for the given - * port is going to be present or absent for the current logical time. - * This reaction is put just before the first reaction that is triggered by the network - * input port "port" or has it in its sources. If there are only connections to contained - * reactors, in the top-level reactor. - * - * @param receivingPortID The port to generate the control reaction for - * @param maxSTP The maximum value of STP is assigned to reactions (if any) - * that have port as their trigger or source - */ - public String generateNetworkInputControlReactionBody( - int receivingPortID, - TimeValue maxSTP, - CoordinationType coordination - ) { - // Store the code - var result = new CodeBuilder(); - result.pr("interval_t max_STP = 0LL;"); - - // Find the maximum STP for decentralized coordination - if(coordination == CoordinationType.DECENTRALIZED) { - result.pr("max_STP = "+ CTypes.getInstance().getTargetTimeExpr(maxSTP) +";"); + } + var ROSSerializer = new FedROS2CPPSerialization(); + lengthExpression = ROSSerializer.serializedBufferLength(); + pointerExpression = ROSSerializer.seializedBufferVar(); + result.pr( + ROSSerializer.generateNetworkSerializerCode( + variableToSerialize, typeStr, CExtensionUtils.isSharedPtrType(type, types))); + result.pr("size_t message_length = " + lengthExpression + ";"); + result.pr(sendingFunction + "(" + commonArgs + ", " + pointerExpression + ");"); + break; } - result.pr("// Wait until the port status is known"); - result.pr("wait_until_port_status_known("+receivingPortID+", max_STP);"); - return result.toString(); } - - /** - * Generate code for the body of a reaction that sends a port status message for the given - * port if it is absent. - * - * @oaram srcOutputPort FIXME - * @param connection FIXME - */ - public String generateNetworkOutputControlReactionBody( - VarRef srcOutputPort, - FedConnectionInstance connection - ) { - // Store the code - var result = new CodeBuilder(); - // The ID of the receiving port (rightPort) is the position - // of the networkAction (see below) in this list. - int receivingPortID = connection.getDstFederate().networkMessageActions.size(); - var sendRef = CUtil.portRefInReaction(srcOutputPort, connection.getSrcBank(), connection.getSrcChannel()); - // Get the delay literal - var additionalDelayString = CExtensionUtils.getNetworkDelayLiteral(connection.getDefinition().getDelay()); - result.pr(String.join("\n", - "// If the output port has not been lf_set for the current logical time,", - "// send an ABSENT message to the receiving federate ", - "LF_PRINT_LOG(\"Contemplating whether to send port \"", - " \"absent for port %d to federate %d.\", ", - " "+receivingPortID+", "+connection.getDstFederate().id+");", - "if ("+sendRef+" == NULL || !"+sendRef+"->is_present) {", - " // The output port is NULL or it is not present.", - " send_port_absent_to_federate("+additionalDelayString+", "+receivingPortID+", "+connection.getDstFederate().id+");", - "}" - )); - return result.toString(); + } + + /** + * Generate code for the body of a reaction that decides whether the trigger for the given port is + * going to be present or absent for the current logical time. This reaction is put just before + * the first reaction that is triggered by the network input port "port" or has it in its sources. + * If there are only connections to contained reactors, in the top-level reactor. + * + * @param receivingPortID The port to generate the control reaction for + * @param maxSTP The maximum value of STP is assigned to reactions (if any) that have port as + * their trigger or source + */ + public String generateNetworkInputControlReactionBody( + int receivingPortID, TimeValue maxSTP, CoordinationType coordination) { + // Store the code + var result = new CodeBuilder(); + result.pr("interval_t max_STP = 0LL;"); + + // Find the maximum STP for decentralized coordination + if (coordination == CoordinationType.DECENTRALIZED) { + result.pr("max_STP = " + CTypes.getInstance().getTargetTimeExpr(maxSTP) + ";"); } - - - public String getNetworkBufferType() { - return "uint8_t*"; + result.pr("// Wait until the port status is known"); + result.pr("wait_until_port_status_known(" + receivingPortID + ", max_STP);"); + return result.toString(); + } + + /** + * Generate code for the body of a reaction that sends a port status message for the given port if + * it is absent. + * + * @oaram srcOutputPort FIXME + * @param connection FIXME + */ + public String generateNetworkOutputControlReactionBody( + VarRef srcOutputPort, FedConnectionInstance connection) { + // Store the code + var result = new CodeBuilder(); + // The ID of the receiving port (rightPort) is the position + // of the networkAction (see below) in this list. + int receivingPortID = connection.getDstFederate().networkMessageActions.size(); + var sendRef = + CUtil.portRefInReaction(srcOutputPort, connection.getSrcBank(), connection.getSrcChannel()); + // Get the delay literal + var additionalDelayString = + CExtensionUtils.getNetworkDelayLiteral(connection.getDefinition().getDelay()); + result.pr( + String.join( + "\n", + "// If the output port has not been lf_set for the current logical time,", + "// send an ABSENT message to the receiving federate ", + "LF_PRINT_LOG(\"Contemplating whether to send port \"", + " \"absent for port %d to federate %d.\", ", + " " + receivingPortID + ", " + connection.getDstFederate().id + ");", + "if (" + sendRef + " == NULL || !" + sendRef + "->is_present) {", + " // The output port is NULL or it is not present.", + " send_port_absent_to_federate(" + + additionalDelayString + + ", " + + receivingPortID + + ", " + + connection.getDstFederate().id + + ");", + "}")); + return result.toString(); + } + + public String getNetworkBufferType() { + return "uint8_t*"; + } + + /** + * 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 + public String generatePreamble( + FederateInstance federate, + FedFileConfig fileConfig, + RtiConfig rtiConfig, + ErrorReporter errorReporter) + throws IOException { + // Put the C preamble in a {@code include/_federate.name + _preamble.h} file + String cPreamble = makePreamble(federate, fileConfig, rtiConfig, errorReporter); + String relPath = getPreamblePath(federate); + Path fedPreamblePath = fileConfig.getSrcPath().resolve(relPath); + Files.createDirectories(fedPreamblePath.getParent()); + try (var writer = Files.newBufferedWriter(fedPreamblePath)) { + writer.write(cPreamble); } - - /** - * 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 - public String generatePreamble( - FederateInstance federate, - FedFileConfig fileConfig, - RtiConfig rtiConfig, - ErrorReporter errorReporter - ) throws IOException { - // Put the C preamble in a {@code include/_federate.name + _preamble.h} file - String cPreamble = makePreamble(federate, fileConfig, rtiConfig, errorReporter); - 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/federated/clock-sync.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"); - includes.pr(generateSerializationIncludes(federate, fileConfig)); - } - - return includes.toString(); + 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/federated/clock-sync.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"); + includes.pr(generateSerializationIncludes(federate, fileConfig)); } - /** - * Generate the preamble to setup federated execution in C. - */ - protected String makePreamble( - FederateInstance federate, - FedFileConfig fileConfig, - RtiConfig rtiConfig, - ErrorReporter errorReporter) { - - var code = new CodeBuilder(); - - code.pr("#include \"core/federated/federate.h\""); - code.pr("#include \"core/federated/net_common.h\""); - code.pr("#include \"core/federated/net_util.h\""); - code.pr("#include \"core/threaded/reactor_threaded.h\""); - code.pr("#include \"core/utils/util.h\""); - code.pr("extern federate_instance_t _fed;"); - - // Generate function to return a pointer to the action trigger_t - // that handles incoming network messages destined to the specified - // port. This will only be used if there are federates. - int numOfNetworkActions = federate.networkMessageActions.size(); - code.pr(""" + return includes.toString(); + } + + /** Generate the preamble to setup federated execution in C. */ + protected String makePreamble( + FederateInstance federate, + FedFileConfig fileConfig, + RtiConfig rtiConfig, + ErrorReporter errorReporter) { + + var code = new CodeBuilder(); + + code.pr("#include \"core/federated/federate.h\""); + code.pr("#include \"core/federated/net_common.h\""); + code.pr("#include \"core/federated/net_util.h\""); + code.pr("#include \"core/threaded/reactor_threaded.h\""); + code.pr("#include \"core/utils/util.h\""); + code.pr("extern federate_instance_t _fed;"); + + // Generate function to return a pointer to the action trigger_t + // that handles incoming network messages destined to the specified + // port. This will only be used if there are federates. + int numOfNetworkActions = federate.networkMessageActions.size(); + code.pr( + """ lf_action_base_t* _lf_action_table[%1$s]; size_t _lf_action_table_size = %1$s; - """.formatted(numOfNetworkActions)); + """ + .formatted(numOfNetworkActions)); - int numOfNetworkReactions = federate.networkReceiverReactions.size(); - code.pr(""" + int numOfNetworkReactions = federate.networkReceiverReactions.size(); + code.pr( + """ reaction_t* networkInputReactions[%1$s]; size_t numNetworkInputReactions = %1$s; - """.formatted(numOfNetworkReactions)); + """ + .formatted(numOfNetworkReactions)); - int numOfNetworkSenderControlReactions = federate.networkSenderControlReactions.size(); - code.pr(""" + int numOfNetworkSenderControlReactions = federate.networkSenderControlReactions.size(); + code.pr( + """ reaction_t* portAbsentReaction[%1$s]; size_t numSenderReactions = %1$s; - """.formatted(numOfNetworkSenderControlReactions)); + """ + .formatted(numOfNetworkSenderControlReactions)); - - int numOfSTAAOffsets = federate.stpOffsets.size(); - code.pr(CExtensionUtils.surroundWithIfFederatedDecentralized(""" + int numOfSTAAOffsets = federate.stpOffsets.size(); + code.pr( + CExtensionUtils.surroundWithIfFederatedDecentralized( + """ staa_t* staa_lst[%1$s]; size_t staa_lst_size = %1$s; - """.formatted(numOfSTAAOffsets))); - - code.pr(generateExecutablePreamble(federate, rtiConfig, errorReporter)); - - code.pr(generateSTAAInitialization(federate, rtiConfig, errorReporter)); - - code.pr(generateInitializeTriggers(federate, errorReporter)); - - code.pr(CExtensionUtils.generateFederateNeighborStructure(federate)); - - return code.getCode(); - } - - /** - * Generate preamble code needed for enabled serializers of the federate. - */ - protected String generateSerializationIncludes(FederateInstance federate, FedFileConfig fileConfig) { - return CExtensionUtils.generateSerializationIncludes(federate, fileConfig); - } - - /** - * Create a function that initializes necessary triggers for federated execution, - * which are the triggers for control reactions and references to all network - * actions (which are triggered upon receiving network messages). - * - * @param federate The federate to initialize triggers for. - * @param errorReporter Used to report errors. - * @return The generated code for the macro. - */ - private String generateInitializeTriggers(FederateInstance federate, ErrorReporter errorReporter) { - CodeBuilder code = new CodeBuilder(); - // Temporarily change the original federate reactor's name in the AST to - // the federate's name so that trigger references are accurate. - var federatedReactor = FedASTUtils.findFederatedReactor(federate.instantiation.eResource()); - var oldFederatedReactorName = federatedReactor.getName(); - federatedReactor.setName(federate.name); - var main = new ReactorInstance(federatedReactor, errorReporter, -1); - code.pr(CExtensionUtils.initializeTriggersForNetworkActions(federate, main, errorReporter)); - code.pr("staa_initialization(); \\"); - //code.pr(CExtensionUtils.initializeTriggersForControlReactions(federate, main, errorReporter)); - //code.pr(CExtensionUtils.networkInputReactions(federate, main)); - //code.pr(CExtensionUtils.portAbsentReaction(federate, main)); - federatedReactor.setName(oldFederatedReactorName); - - return """ + """ + .formatted(numOfSTAAOffsets))); + + code.pr(generateExecutablePreamble(federate, rtiConfig, errorReporter)); + + code.pr(generateSTAAInitialization(federate, rtiConfig, errorReporter)); + + code.pr(generateInitializeTriggers(federate, errorReporter)); + + code.pr(CExtensionUtils.generateFederateNeighborStructure(federate)); + + return code.getCode(); + } + + /** Generate preamble code needed for enabled serializers of the federate. */ + protected String generateSerializationIncludes( + FederateInstance federate, FedFileConfig fileConfig) { + return CExtensionUtils.generateSerializationIncludes(federate, fileConfig); + } + + /** + * Create a function that initializes necessary triggers for federated execution, which are the + * triggers for control reactions and references to all network actions (which are triggered upon + * receiving network messages). + * + * @param federate The federate to initialize triggers for. + * @param errorReporter Used to report errors. + * @return The generated code for the macro. + */ + private String generateInitializeTriggers( + FederateInstance federate, ErrorReporter errorReporter) { + CodeBuilder code = new CodeBuilder(); + // Temporarily change the original federate reactor's name in the AST to + // the federate's name so that trigger references are accurate. + var federatedReactor = FedASTUtils.findFederatedReactor(federate.instantiation.eResource()); + var oldFederatedReactorName = federatedReactor.getName(); + federatedReactor.setName(federate.name); + var main = new ReactorInstance(federatedReactor, errorReporter, -1); + code.pr(CExtensionUtils.initializeTriggersForNetworkActions(federate, main, errorReporter)); + code.pr("staa_initialization(); \\"); + // code.pr(CExtensionUtils.initializeTriggersForControlReactions(federate, main, + // errorReporter)); + // code.pr(CExtensionUtils.networkInputReactions(federate, main)); + // code.pr(CExtensionUtils.portAbsentReaction(federate, main)); + federatedReactor.setName(oldFederatedReactorName); + + return """ #define initialize_triggers_for_federate() \\ do { \\ %s } \\ while (0) - """.formatted((code.getCode().isBlank() ? "\\" : code.getCode()).indent(4).stripTrailing()); - } + """ + .formatted((code.getCode().isBlank() ? "\\" : code.getCode()).indent(4).stripTrailing()); + } - /** - * Generate code for an executed preamble. - * - */ - private String generateExecutablePreamble(FederateInstance federate, RtiConfig rtiConfig, ErrorReporter errorReporter) { - CodeBuilder code = new CodeBuilder(); + /** Generate code for an executed preamble. */ + private String generateExecutablePreamble( + FederateInstance federate, RtiConfig rtiConfig, ErrorReporter errorReporter) { + CodeBuilder code = new CodeBuilder(); - code.pr(generateCodeForPhysicalActions(federate, errorReporter)); + code.pr(generateCodeForPhysicalActions(federate, errorReporter)); - code.pr(generateCodeToInitializeFederate(federate, rtiConfig)); + code.pr(generateCodeToInitializeFederate(federate, rtiConfig)); - //code.pr(CExtensionUtils.allocateTriggersForFederate(federate)); + // code.pr(CExtensionUtils.allocateTriggersForFederate(federate)); - return """ + return """ void _lf_executable_preamble() { %s } - """.formatted(code.toString().indent(4).stripTrailing()); - } + """ + .formatted(code.toString().indent(4).stripTrailing()); + } - /** - * Generate code for an executed preamble. - * - */ - private String generateSTAAInitialization(FederateInstance federate, RtiConfig rtiConfig, ErrorReporter errorReporter) { - CodeBuilder code = new CodeBuilder(); - code.pr(CExtensionUtils.surroundWithIfFederatedDecentralized(CExtensionUtils.stpStructs(federate, errorReporter))); + /** Generate code for an executed preamble. */ + private String generateSTAAInitialization( + FederateInstance federate, RtiConfig rtiConfig, ErrorReporter errorReporter) { + CodeBuilder code = new CodeBuilder(); + code.pr( + CExtensionUtils.surroundWithIfFederatedDecentralized( + CExtensionUtils.stpStructs(federate, errorReporter))); - //code.pr(CExtensionUtils.allocateTriggersForFederate(federate)); + // code.pr(CExtensionUtils.allocateTriggersForFederate(federate)); - return """ + return """ void staa_initialization() { %s } - """.formatted(code.toString().indent(4).stripTrailing()); + """ + .formatted(code.toString().indent(4).stripTrailing()); + } + + /** + * Generate code to initialize the {@code federate}. + * + * @param rtiConfig + * @return The generated code + */ + private String generateCodeToInitializeFederate(FederateInstance federate, RtiConfig rtiConfig) { + CodeBuilder code = new CodeBuilder(); + code.pr("// ***** Start initializing the federated execution. */"); + code.pr( + String.join( + "\n", + "// Initialize the socket mutex", + "lf_mutex_init(&outbound_socket_mutex);", + "lf_cond_init(&port_status_changed, &mutex);", + CExtensionUtils.surroundWithIfFederatedDecentralized( + "lf_cond_init(&logical_time_changed, &mutex);"))); + + // Find the STA (A.K.A. the global STP offset) for this federate. + if (federate.targetConfig.coordination == CoordinationType.DECENTRALIZED) { + var reactor = ASTUtils.toDefinition(federate.instantiation.getReactorClass()); + var stpParam = + reactor.getParameters().stream() + .filter( + param -> + param.getName().equalsIgnoreCase("STP_offset") + && (param.getType() == null || param.getType().isTime())) + .findFirst(); + + 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(" + CTypes.getInstance().getTargetTimeExpr(globalSTPTV) + ");"); + } } - /** - * Generate code to initialize the {@code federate}. - * @param rtiConfig - * @return The generated code - */ - private String generateCodeToInitializeFederate(FederateInstance federate, RtiConfig rtiConfig) { - CodeBuilder code = new CodeBuilder(); - code.pr("// ***** Start initializing the federated execution. */"); - code.pr(String.join("\n", - "// Initialize the socket mutex", - "lf_mutex_init(&outbound_socket_mutex);", - "lf_cond_init(&port_status_changed, &mutex);", - CExtensionUtils.surroundWithIfFederatedDecentralized("lf_cond_init(&logical_time_changed, &mutex);") - )); - - // Find the STA (A.K.A. the global STP offset) for this federate. - if (federate.targetConfig.coordination == CoordinationType.DECENTRALIZED) { - var reactor = ASTUtils.toDefinition(federate.instantiation.getReactorClass()); - var stpParam = reactor.getParameters().stream().filter( - param -> - param.getName().equalsIgnoreCase("STP_offset") - && (param.getType() == null || param.getType().isTime()) - ).findFirst(); - - 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("+ CTypes.getInstance().getTargetTimeExpr(globalSTPTV) +");"); - } - } - - // Set indicator variables that specify whether the federate has - // upstream logical connections. - if (federate.dependsOn.size() > 0) { - code.pr("_fed.has_upstream = true;"); - } - if (federate.sendsTo.size() > 0) { - code.pr("_fed.has_downstream = true;"); - } - // Set global variable identifying the federate. - code.pr("_lf_my_fed_id = "+ federate.id+";"); - - // We keep separate record for incoming and outgoing p2p connections to allow incoming traffic to be processed in a separate - // thread without requiring a mutex lock. - var numberOfInboundConnections = federate.inboundP2PConnections.size(); - var numberOfOutboundConnections = federate.outboundP2PConnections.size(); - - code.pr(String.join("\n", - "_fed.number_of_inbound_p2p_connections = "+numberOfInboundConnections+";", - "_fed.number_of_outbound_p2p_connections = "+numberOfOutboundConnections+";" - )); - if (numberOfInboundConnections > 0) { - code.pr(String.join("\n", - "// Initialize the array of socket for incoming connections to -1.", - "for (int i = 0; i < NUMBER_OF_FEDERATES; i++) {", - " _fed.sockets_for_inbound_p2p_connections[i] = -1;", - "}" - )); - } - if (numberOfOutboundConnections > 0) { - code.pr(String.join("\n", - "// Initialize the array of socket for outgoing connections to -1.", - "for (int i = 0; i < NUMBER_OF_FEDERATES; i++) {", - " _fed.sockets_for_outbound_p2p_connections[i] = -1;", - "}" - )); - } + // Set indicator variables that specify whether the federate has + // upstream logical connections. + if (federate.dependsOn.size() > 0) { + code.pr("_fed.has_upstream = true;"); + } + if (federate.sendsTo.size() > 0) { + code.pr("_fed.has_downstream = true;"); + } + // Set global variable identifying the federate. + code.pr("_lf_my_fed_id = " + federate.id + ";"); + + // We keep separate record for incoming and outgoing p2p connections to allow incoming traffic + // to be processed in a separate + // thread without requiring a mutex lock. + var numberOfInboundConnections = federate.inboundP2PConnections.size(); + var numberOfOutboundConnections = federate.outboundP2PConnections.size(); + + code.pr( + String.join( + "\n", + "_fed.number_of_inbound_p2p_connections = " + numberOfInboundConnections + ";", + "_fed.number_of_outbound_p2p_connections = " + numberOfOutboundConnections + ";")); + if (numberOfInboundConnections > 0) { + code.pr( + String.join( + "\n", + "// Initialize the array of socket for incoming connections to -1.", + "for (int i = 0; i < NUMBER_OF_FEDERATES; i++) {", + " _fed.sockets_for_inbound_p2p_connections[i] = -1;", + "}")); + } + if (numberOfOutboundConnections > 0) { + code.pr( + String.join( + "\n", + "// Initialize the array of socket for outgoing connections to -1.", + "for (int i = 0; i < NUMBER_OF_FEDERATES; i++) {", + " _fed.sockets_for_outbound_p2p_connections[i] = -1;", + "}")); + } - // If a test clock offset has been specified, insert code to set it here. - if (federate.targetConfig.clockSyncOptions.testOffset != null) { - code.pr("lf_set_physical_clock_offset((1 + "+ federate.id+") * "+ federate.targetConfig.clockSyncOptions.testOffset.toNanoSeconds()+"LL);"); - } + // If a test clock offset has been specified, insert code to set it here. + if (federate.targetConfig.clockSyncOptions.testOffset != null) { + code.pr( + "lf_set_physical_clock_offset((1 + " + + federate.id + + ") * " + + federate.targetConfig.clockSyncOptions.testOffset.toNanoSeconds() + + "LL);"); + } - code.pr(String.join("\n", - "// Connect to the RTI. This sets _fed.socket_TCP_RTI and _lf_rti_socket_UDP.", - "connect_to_rti("+addDoubleQuotes(rtiConfig.getHost())+", "+ rtiConfig.getPort()+");" - )); + code.pr( + String.join( + "\n", + "// Connect to the RTI. This sets _fed.socket_TCP_RTI and _lf_rti_socket_UDP.", + "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, rtiConfig)) { + code.pr("synchronize_initial_physical_clock_with_rti(_fed.socket_TCP_RTI);"); + } - // 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, rtiConfig)) { - code.pr("synchronize_initial_physical_clock_with_rti(_fed.socket_TCP_RTI);"); - } + if (numberOfInboundConnections > 0) { + code.pr( + String.join( + "\n", + "// Create a socket server to listen to other federates.", + "// If a port is specified by the user, that will be used", + "// as the only possibility for the server. If not, the port", + "// will start from STARTING_PORT. The function will", + "// keep incrementing the port until the number of tries reaches PORT_RANGE_LIMIT.", + "create_server(" + federate.port + ");", + "// Connect to remote federates for each physical connection.", + "// This is done in a separate thread because this thread will call", + "// connect_to_federate for each outbound physical connection at the same", + "// time that the new thread is listening for such connections for inbound", + "// physical connections. The thread will live until all connections", + "// have been established.", + "lf_thread_create(&_fed.inbound_p2p_handling_thread_id," + + " handle_p2p_connections_from_federates, NULL);")); + } - if (numberOfInboundConnections > 0) { - code.pr(String.join("\n", - "// Create a socket server to listen to other federates.", - "// If a port is specified by the user, that will be used", - "// as the only possibility for the server. If not, the port", - "// will start from STARTING_PORT. The function will", - "// keep incrementing the port until the number of tries reaches PORT_RANGE_LIMIT.", - "create_server("+ federate.port+");", - "// Connect to remote federates for each physical connection.", - "// This is done in a separate thread because this thread will call", - "// connect_to_federate for each outbound physical connection at the same", - "// time that the new thread is listening for such connections for inbound", - "// physical connections. The thread will live until all connections", - "// have been established.", - "lf_thread_create(&_fed.inbound_p2p_handling_thread_id, handle_p2p_connections_from_federates, NULL);" - )); + for (FederateInstance remoteFederate : federate.outboundP2PConnections) { + code.pr("connect_to_federate(" + remoteFederate.id + ");"); + } + return code.getCode(); + } + + /** + * Generate code to handle physical actions in the {@code federate}. + * + * @param errorReporter Used to report errors. + * @return Generated code. + */ + private String generateCodeForPhysicalActions( + FederateInstance federate, ErrorReporter errorReporter) { + CodeBuilder code = new CodeBuilder(); + if (federate.targetConfig.coordination.equals(CoordinationType.CENTRALIZED)) { + // If this program uses centralized coordination then check + // for outputs that depend on physical actions so that null messages can be + // sent to the RTI. + var federateClass = ASTUtils.toDefinition(federate.instantiation.getReactorClass()); + var main = + new ReactorInstance( + FedASTUtils.findFederatedReactor(federate.instantiation.eResource()), + errorReporter, + 1); + var instance = new ReactorInstance(federateClass, main, errorReporter); + var outputDelayMap = federate.findOutputsConnectedToPhysicalActions(instance); + var minDelay = TimeValue.MAX_VALUE; + Output outputFound = null; + for (Output output : outputDelayMap.keySet()) { + var outputDelay = outputDelayMap.get(output); + if (outputDelay.isEarlierThan(minDelay)) { + minDelay = outputDelay; + outputFound = output; } - - for (FederateInstance remoteFederate : federate.outboundP2PConnections) { - code.pr("connect_to_federate("+remoteFederate.id+");"); + } + if (minDelay != TimeValue.MAX_VALUE) { + // Unless silenced, issue a warning. + if (federate.targetConfig.coordinationOptions.advance_message_interval == null) { + errorReporter.reportWarning( + outputFound, + String.join( + "\n", + "Found a path from a physical action to output for reactor " + + addDoubleQuotes(instance.getName()) + + ". ", + "The amount of delay is " + minDelay + ".", + "With centralized coordination, this can result in a large number of messages to" + + " the RTI.", + "Consider refactoring the code so that the output does not depend on the physical" + + " action,", + "or consider using decentralized coordination. To silence this warning, set the" + + " target", + "parameter coordination-options with a value like {advance-message-interval: 10" + + " msec}")); } - return code.getCode(); + code.pr( + "_fed.min_delay_from_physical_action_to_federate_output = " + + CTypes.getInstance().getTargetTimeExpr(minDelay) + + ";"); + } } + return code.getCode(); + } - /** - * Generate code to handle physical actions in the {@code federate}. - * @param errorReporter Used to report errors. - * @return Generated code. - */ - private String generateCodeForPhysicalActions(FederateInstance federate, ErrorReporter errorReporter) { - CodeBuilder code = new CodeBuilder(); - if (federate.targetConfig.coordination.equals(CoordinationType.CENTRALIZED)) { - // If this program uses centralized coordination then check - // for outputs that depend on physical actions so that null messages can be - // sent to the RTI. - var federateClass = ASTUtils.toDefinition(federate.instantiation.getReactorClass()); - var main = new ReactorInstance(FedASTUtils.findFederatedReactor(federate.instantiation.eResource()), errorReporter, 1); - var instance = new ReactorInstance(federateClass, main, errorReporter); - var outputDelayMap = federate - .findOutputsConnectedToPhysicalActions(instance); - var minDelay = TimeValue.MAX_VALUE; - Output outputFound = null; - for (Output output : outputDelayMap.keySet()) { - var outputDelay = outputDelayMap.get(output); - if (outputDelay.isEarlierThan(minDelay)) { - minDelay = outputDelay; - outputFound = output; - } - } - if (minDelay != TimeValue.MAX_VALUE) { - // Unless silenced, issue a warning. - if (federate.targetConfig.coordinationOptions.advance_message_interval - == null) { - errorReporter.reportWarning(outputFound, String.join("\n", - "Found a path from a physical action to output for reactor " - + addDoubleQuotes(instance.getName()) - + ". ", - "The amount of delay is " - + minDelay - + ".", - "With centralized coordination, this can result in a large number of messages to the RTI.", - "Consider refactoring the code so that the output does not depend on the physical action,", - "or consider using decentralized coordination. To silence this warning, set the target", - "parameter coordination-options with a value like {advance-message-interval: 10 msec}" - )); - } - code.pr( - "_fed.min_delay_from_physical_action_to_federate_output = " - + CTypes.getInstance().getTargetTimeExpr(minDelay) + ";"); - } - } - return code.getCode(); - } - private String getPreamblePath(FederateInstance f) { - return "include" + File.separator + "_" + f.name + "_preamble.h"; - } + private String getPreamblePath(FederateInstance f) { + return "include" + File.separator + "_" + f.name + "_preamble.h"; + } } diff --git a/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java b/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java index b8f3e2cd11..1f9fbc2454 100644 --- a/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java +++ b/org.lflang/src/org/lflang/federated/extensions/CExtensionUtils.java @@ -8,14 +8,13 @@ import java.util.LinkedList; import java.util.List; import java.util.regex.Pattern; - -import org.lflang.ast.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.InferredType; import org.lflang.TargetConfig.ClockSyncOptions; import org.lflang.TargetProperty; import org.lflang.TargetProperty.ClockSyncMode; import org.lflang.TimeValue; +import org.lflang.ast.ASTUtils; import org.lflang.federated.generator.FedFileConfig; import org.lflang.federated.generator.FederateInstance; import org.lflang.federated.launcher.RtiConfig; @@ -29,675 +28,704 @@ import org.lflang.lf.Expression; import org.lflang.lf.Input; import org.lflang.lf.ParameterReference; -import org.lflang.lf.Reactor; -import org.lflang.lf.ReactorDecl; -import org.lflang.lf.VarRef; public class CExtensionUtils { - // Regular expression pattern for shared_ptr types. - static final Pattern sharedPointerVariable = Pattern.compile("^(/\\*.*?\\*/)?std::shared_ptr<(?((/\\*.*?\\*/)?(\\S+))+)>$"); - - /** - * Generate C code that allocates sufficient memory for the following two - * critical data structures that support network control reactions: - *
    - *
  • {@code triggers_for_network_input_control_reactions}: These are triggers that - * are used at runtime to insert network input control reactions into the - * reaction queue.
  • - *
  • {@code trigger_for_network_output_control_reactions}: Triggers for - * network output control reactions, which are unique per each output port. - * There could be multiple network output control reactions for each - * network - * output port if it is connected to multiple downstream federates.
  • - *
- * - * @param federate The top-level federate instance - * @return A string that allocates memory for the aforementioned three - * structures. - */ - public static String allocateTriggersForFederate( - FederateInstance federate - ) { - - CodeBuilder builder = new CodeBuilder(); - if (federate.networkInputControlReactionsTriggers.size() > 0) { - // Proliferate the network input control reaction trigger array - builder.pr( - """ + // Regular expression pattern for shared_ptr types. + static final Pattern sharedPointerVariable = + Pattern.compile("^(/\\*.*?\\*/)?std::shared_ptr<(?((/\\*.*?\\*/)?(\\S+))+)>$"); + + /** + * Generate C code that allocates sufficient memory for the following two critical data structures + * that support network control reactions: + * + *
    + *
  • {@code triggers_for_network_input_control_reactions}: These are triggers that are used at + * runtime to insert network input control reactions into the reaction queue. + *
  • {@code trigger_for_network_output_control_reactions}: Triggers for network output control + * reactions, which are unique per each output port. There could be multiple network output + * control reactions for each network output port if it is connected to multiple downstream + * federates. + *
+ * + * @param federate The top-level federate instance + * @return A string that allocates memory for the aforementioned three structures. + */ + public static String allocateTriggersForFederate(FederateInstance federate) { + + CodeBuilder builder = new CodeBuilder(); + if (federate.networkInputControlReactionsTriggers.size() > 0) { + // Proliferate the network input control reaction trigger array + builder.pr( + """ // Initialize the array of pointers to network input port triggers _fed.triggers_for_network_input_control_reactions_size = %s; _fed.triggers_for_network_input_control_reactions = (trigger_t**)malloc( _fed.triggers_for_network_input_control_reactions_size * sizeof(trigger_t*)); - """.formatted(federate.networkInputControlReactionsTriggers.size())); - - } - return builder.getCode(); + """ + .formatted(federate.networkInputControlReactionsTriggers.size())); } - - /** - * Generate C code that initializes network actions. - * - * These network actions will be triggered by federate.c whenever a message - * is received from the network. - * @param federate The federate. - * @param main The main reactor that contains the federate (used to lookup references). - * @return - */ - public static String initializeTriggersForNetworkActions(FederateInstance federate, ReactorInstance main, ErrorReporter errorReporter) { - CodeBuilder code = new CodeBuilder(); - if (federate.networkMessageActions.size() > 0) { - // Create a static array of trigger_t pointers. - // networkMessageActions is a list of Actions, but we - // need a list of trigger struct names for ActionInstances. - // There should be exactly one ActionInstance in the - // main reactor for each Action. - var triggers = new LinkedList(); - for (int i = 0; i < federate.networkMessageActions.size(); ++i) { - // Find the corresponding ActionInstance. - Action action = federate.networkMessageActions.get(i); - var reactor = main.lookupReactorInstance(federate.networkReceiverInstantiations.get(i)); - var actionInstance = reactor.lookupActionInstance(action); - triggers.add(CUtil.actionRef(actionInstance, null)); - } - var actionTableCount = 0; - for (String trigger : triggers) { - code.pr("_lf_action_table[" + (actionTableCount++) + "] = (lf_action_base_t*)&" - + trigger + "; \\"); - } - } - return code.getCode(); + return builder.getCode(); + } + + /** + * Generate C code that initializes network actions. + * + *

These network actions will be triggered by federate.c whenever a message is received from + * the network. + * + * @param federate The federate. + * @param main The main reactor that contains the federate (used to lookup references). + * @return + */ + public static String initializeTriggersForNetworkActions( + FederateInstance federate, ReactorInstance main, ErrorReporter errorReporter) { + CodeBuilder code = new CodeBuilder(); + if (federate.networkMessageActions.size() > 0) { + // Create a static array of trigger_t pointers. + // networkMessageActions is a list of Actions, but we + // need a list of trigger struct names for ActionInstances. + // There should be exactly one ActionInstance in the + // main reactor for each Action. + var triggers = new LinkedList(); + for (int i = 0; i < federate.networkMessageActions.size(); ++i) { + // Find the corresponding ActionInstance. + Action action = federate.networkMessageActions.get(i); + var reactor = main.lookupReactorInstance(federate.networkReceiverInstantiations.get(i)); + var actionInstance = reactor.lookupActionInstance(action); + triggers.add(CUtil.actionRef(actionInstance, null)); + } + var actionTableCount = 0; + for (String trigger : triggers) { + code.pr( + "_lf_action_table[" + + (actionTableCount++) + + "] = (lf_action_base_t*)&" + + trigger + + "; \\"); + } } - - /** - * Generate C code that holds a sorted list of STP structs by time. - * - * For decentralized execution, on every logical timestep, a thread will iterate through - * each staa struct, wait for the designated offset time, and set the associated port status to absent - * if it isn't known. - * @param federate The federate. - * @param main The main reactor that contains the federate (used to lookup references). - * @return - */ - public static String stpStructs(FederateInstance federate, ErrorReporter errorReporter) { - CodeBuilder code = new CodeBuilder(); - Collections.sort(federate.stpOffsets, (d1, d2) -> { - return (int) (d1.time - d2.time); + return code.getCode(); + } + + /** + * Generate C code that holds a sorted list of STP structs by time. + * + *

For decentralized execution, on every logical timestep, a thread will iterate through each + * staa struct, wait for the designated offset time, and set the associated port status to absent + * if it isn't known. + * + * @param federate The federate. + * @param main The main reactor that contains the federate (used to lookup references). + * @return + */ + public static String stpStructs(FederateInstance federate, ErrorReporter errorReporter) { + CodeBuilder code = new CodeBuilder(); + Collections.sort( + federate.stpOffsets, + (d1, d2) -> { + return (int) (d1.time - d2.time); }); - if (!federate.stpOffsets.isEmpty()) { - // Create a static array of trigger_t pointers. - // networkMessageActions is a list of Actions, but we - // need a list of trigger struct names for ActionInstances. - // There should be exactly one ActionInstance in the - // main reactor for each Action. - for (int i = 0; i < federate.stpOffsets.size(); ++i) { - // Find the corresponding ActionInstance. - List networkActions = federate.stpToNetworkActionMap.get(federate.stpOffsets.get(i)); - - code.pr("staa_lst[" + i + "] = (staa_t*) malloc(sizeof(staa_t));"); - code.pr("staa_lst[" + i + "]->STAA = " + CTypes.getInstance().getTargetTimeExpr(federate.stpOffsets.get(i)) + ";"); - code.pr("staa_lst[" + i + "]->numActions = " + networkActions.size() + ";"); - code.pr("staa_lst[" + i + "]->actions = (lf_action_base_t**) malloc(sizeof(lf_action_base_t*) * " + networkActions.size() + ");"); - var tableCount = 0; - for(Action action: networkActions){ - code.pr("staa_lst[" + i + "]->actions[" + (tableCount++) + "] = _lf_action_table[" - + federate.networkMessageActions.indexOf(action) + "];"); - } - } + if (!federate.stpOffsets.isEmpty()) { + // Create a static array of trigger_t pointers. + // networkMessageActions is a list of Actions, but we + // need a list of trigger struct names for ActionInstances. + // There should be exactly one ActionInstance in the + // main reactor for each Action. + for (int i = 0; i < federate.stpOffsets.size(); ++i) { + // Find the corresponding ActionInstance. + List networkActions = + federate.stpToNetworkActionMap.get(federate.stpOffsets.get(i)); + + code.pr("staa_lst[" + i + "] = (staa_t*) malloc(sizeof(staa_t));"); + code.pr( + "staa_lst[" + + i + + "]->STAA = " + + CTypes.getInstance().getTargetTimeExpr(federate.stpOffsets.get(i)) + + ";"); + code.pr("staa_lst[" + i + "]->numActions = " + networkActions.size() + ";"); + code.pr( + "staa_lst[" + + i + + "]->actions = (lf_action_base_t**) malloc(sizeof(lf_action_base_t*) * " + + networkActions.size() + + ");"); + var tableCount = 0; + for (Action action : networkActions) { + code.pr( + "staa_lst[" + + i + + "]->actions[" + + (tableCount++) + + "] = _lf_action_table[" + + federate.networkMessageActions.indexOf(action) + + "];"); } - return code.getCode(); + } } - - /** - * Generate C code that initializes three critical structures that support - * network control reactions: - * - triggers_for_network_input_control_reactions: These are triggers that are - * used at runtime to insert network input control reactions into the - * reaction queue. There could be multiple network input control reactions - * for one network input at multiple levels in the hierarchy. - * - trigger_for_network_output_control_reactions: Triggers for - * network output control reactions, which are unique per each output port. - * There could be multiple network output control reactions for each network - * output port if it is connected to multiple downstream federates. - * - * @param instance The reactor instance that is at any level of the - * hierarchy within the federate. - * @param errorReporter The top-level federate - * @return A string that initializes the aforementioned three structures. - */ - // public static String initializeTriggersForControlReactions( - // FederateInstance instance, - // ReactorInstance main, - // ErrorReporter errorReporter - // ) { - // CodeBuilder builder = new CodeBuilder(); - - // if (federate.networkSenderControlReactions.size() > 0) { - // // Create a static array of trigger_t pointers. - // // networkMessageActions is a list of Actions, but we - // // need a list of trigger struct names for ActionInstances. - // // There should be exactly one ActionInstance in the - // // main reactor for each Action. - // var triggers = new LinkedList(); - // for (int i = 0; i < federate.networkSenderControlReactions.size(); ++i) { - // // Find the corresponding ActionInstance. - // Action action = federate.networkMessageActions.get(i); - // var reactor = main.lookupReactorInstance(federate.networkReceiverInstantiations.get(i)); - // var actionInstance = reactor.lookupActionInstance(action); - // triggers.add(CUtil.actionRef(actionInstance, null)); - // } - // var actionTableCount = 0; - // for (String trigger : triggers) { - // code.pr("_lf_action_table[" + (actionTableCount++) + "] = (lf_action_base_t*)&" - // + trigger + "; \\"); - // } - // } - - // ReactorDecl reactorClass = instance.getDefinition().getReactorClass(); - // Reactor reactor = ASTUtils.toDefinition(reactorClass); - // String nameOfSelfStruct = CUtil.reactorRef(instance); - - // // Initialize triggers for network input control reactions - // for (Action trigger : errorReporter.networkInputControlReactionsTriggers) { - // // Check if the trigger belongs to this reactor instance - // if (ASTUtils.allReactions(reactor).stream().anyMatch(r -> { - // return r.getTriggers().stream().anyMatch(t -> { - // if (t instanceof VarRef) { - // return ((VarRef) t).getVariable().equals(trigger); - // } else { - // return false; - // } - // }); - // })) { - // // Initialize the triggers_for_network_input_control_reactions for the input - // builder.pr( - // String.join("\n", - // "/* Add trigger " + nameOfSelfStruct + "->_lf__"+trigger.getName()+" to the global list of network input ports. */ \\", - // "_fed.triggers_for_network_input_control_reactions["+errorReporter.networkInputControlReactionsTriggers.indexOf(trigger)+"]= \\", - // " &"+nameOfSelfStruct+"->_lf__"+trigger.getName()+"; \\" - // ) - // ); - // } - // } - - // nameOfSelfStruct = CUtil.reactorRef(instance); - - // // Initialize the trigger for network output control reactions if it doesn't exist. - // if (errorReporter.networkOutputControlReactionsTrigger != null) { - // builder.pr("_fed.trigger_for_network_output_control_reactions=&" - // + nameOfSelfStruct - // + "->_lf__outputControlReactionTrigger; \\"); - // } - - // return builder.getCode(); - // } - - /** - * Create a port status field variable for a network input port "input" in - * the self struct of a reactor. - * - * @param input The network input port - * @return A string containing the appropriate variable - */ - public static String createPortStatusFieldForInput(Input input) { - StringBuilder builder = new StringBuilder(); - // Check if the port is a multiport - if (ASTUtils.isMultiport(input)) { - // If it is a multiport, then create an auxiliary list of port - // triggers for each channel of - // the multiport to keep track of the status of each channel - // individually - builder.append("trigger_t* _lf__" + input.getName() - + "_network_port_status;\n"); - } else { - // If it is not a multiport, then we could re-use the port trigger, - // and nothing needs to be - // done - } - return builder.toString(); + return code.getCode(); + } + + /** + * Generate C code that initializes three critical structures that support network control + * reactions: - triggers_for_network_input_control_reactions: These are triggers that are used at + * runtime to insert network input control reactions into the reaction queue. There could be + * multiple network input control reactions for one network input at multiple levels in the + * hierarchy. - trigger_for_network_output_control_reactions: Triggers for network output control + * reactions, which are unique per each output port. There could be multiple network output + * control reactions for each network output port if it is connected to multiple downstream + * federates. + * + * @param instance The reactor instance that is at any level of the hierarchy within the federate. + * @param errorReporter The top-level federate + * @return A string that initializes the aforementioned three structures. + */ + // public static String initializeTriggersForControlReactions( + // FederateInstance instance, + // ReactorInstance main, + // ErrorReporter errorReporter + // ) { + // CodeBuilder builder = new CodeBuilder(); + + // if (federate.networkSenderControlReactions.size() > 0) { + // // Create a static array of trigger_t pointers. + // // networkMessageActions is a list of Actions, but we + // // need a list of trigger struct names for ActionInstances. + // // There should be exactly one ActionInstance in the + // // main reactor for each Action. + // var triggers = new LinkedList(); + // for (int i = 0; i < federate.networkSenderControlReactions.size(); ++i) { + // // Find the corresponding ActionInstance. + // Action action = federate.networkMessageActions.get(i); + // var reactor = + // main.lookupReactorInstance(federate.networkReceiverInstantiations.get(i)); + // var actionInstance = reactor.lookupActionInstance(action); + // triggers.add(CUtil.actionRef(actionInstance, null)); + // } + // var actionTableCount = 0; + // for (String trigger : triggers) { + // code.pr("_lf_action_table[" + (actionTableCount++) + "] = (lf_action_base_t*)&" + // + trigger + "; \\"); + // } + // } + + // ReactorDecl reactorClass = instance.getDefinition().getReactorClass(); + // Reactor reactor = ASTUtils.toDefinition(reactorClass); + // String nameOfSelfStruct = CUtil.reactorRef(instance); + + // // Initialize triggers for network input control reactions + // for (Action trigger : errorReporter.networkInputControlReactionsTriggers) { + // // Check if the trigger belongs to this reactor instance + // if (ASTUtils.allReactions(reactor).stream().anyMatch(r -> { + // return r.getTriggers().stream().anyMatch(t -> { + // if (t instanceof VarRef) { + // return ((VarRef) t).getVariable().equals(trigger); + // } else { + // return false; + // } + // }); + // })) { + // // Initialize the triggers_for_network_input_control_reactions for the input + // builder.pr( + // String.join("\n", + // "/* Add trigger " + nameOfSelfStruct + "->_lf__"+trigger.getName()+" to the + // global list of network input ports. */ \\", + // + // "_fed.triggers_for_network_input_control_reactions["+errorReporter.networkInputControlReactionsTriggers.indexOf(trigger)+"]= \\", + // " &"+nameOfSelfStruct+"->_lf__"+trigger.getName()+"; \\" + // ) + // ); + // } + // } + + // nameOfSelfStruct = CUtil.reactorRef(instance); + + // // Initialize the trigger for network output control reactions if it doesn't exist. + // if (errorReporter.networkOutputControlReactionsTrigger != null) { + // builder.pr("_fed.trigger_for_network_output_control_reactions=&" + // + nameOfSelfStruct + // + "->_lf__outputControlReactionTrigger; \\"); + // } + + // return builder.getCode(); + // } + + /** + * Create a port status field variable for a network input port "input" in the self struct of a + * reactor. + * + * @param input The network input port + * @return A string containing the appropriate variable + */ + public static String createPortStatusFieldForInput(Input input) { + StringBuilder builder = new StringBuilder(); + // Check if the port is a multiport + if (ASTUtils.isMultiport(input)) { + // If it is a multiport, then create an auxiliary list of port + // triggers for each channel of + // the multiport to keep track of the status of each channel + // individually + builder.append("trigger_t* _lf__" + input.getName() + "_network_port_status;\n"); + } else { + // If it is not a multiport, then we could re-use the port trigger, + // and nothing needs to be + // done } - - /** - * Given a connection 'delay' predicate, return a string that represents the - * interval_t value of the additional delay that needs to be applied to the - * outgoing message. - * - * The returned additional delay in absence of after on network connection - * (i.e., if delay is passed as a null) is NEVER. This has a special - * meaning in C library functions that send network messages that carry - * timestamps (@see send_timed_message and send_port_absent_to_federate - * in lib/core/federate.c). In this case, the sender will send its current - * tag as the timestamp of the outgoing message without adding a microstep delay. - * If the user has assigned an after delay to the network connection (that - * can be zero) either as a time value (e.g., 200 msec) or as a literal - * (e.g., a parameter), that delay in nsec will be returned. - * - * @param delay - * @return - */ - public static String getNetworkDelayLiteral(Expression delay) { - String additionalDelayString = "NEVER"; - if (delay != null) { - TimeValue tv; - if (delay instanceof ParameterReference) { - // The parameter has to be parameter of the main reactor. - // And that value has to be a Time. - tv = ASTUtils.getDefaultAsTimeValue(((ParameterReference)delay).getParameter()); - } else { - tv = ASTUtils.getLiteralTimeValue(delay); - } - additionalDelayString = Long.toString(tv.toNanoSeconds()); - } - return additionalDelayString; + return builder.toString(); + } + + /** + * Given a connection 'delay' predicate, return a string that represents the interval_t value of + * the additional delay that needs to be applied to the outgoing message. + * + *

The returned additional delay in absence of after on network connection (i.e., if delay is + * passed as a null) is NEVER. This has a special meaning in C library functions that send network + * messages that carry timestamps (@see send_timed_message and send_port_absent_to_federate in + * lib/core/federate.c). In this case, the sender will send its current tag as the timestamp of + * the outgoing message without adding a microstep delay. If the user has assigned an after delay + * to the network connection (that can be zero) either as a time value (e.g., 200 msec) or as a + * literal (e.g., a parameter), that delay in nsec will be returned. + * + * @param delay + * @return + */ + public static String getNetworkDelayLiteral(Expression delay) { + String additionalDelayString = "NEVER"; + if (delay != null) { + TimeValue tv; + if (delay instanceof ParameterReference) { + // The parameter has to be parameter of the main reactor. + // And that value has to be a Time. + tv = ASTUtils.getDefaultAsTimeValue(((ParameterReference) delay).getParameter()); + } else { + tv = ASTUtils.getLiteralTimeValue(delay); + } + additionalDelayString = Long.toString(tv.toNanoSeconds()); } - - static boolean isSharedPtrType(InferredType type, CTypes types) { - return !type.isUndefined() && sharedPointerVariable.matcher(types.getTargetType(type)).find(); + return additionalDelayString; + } + + static boolean isSharedPtrType(InferredType type, CTypes types) { + return !type.isUndefined() && sharedPointerVariable.matcher(types.getTargetType(type)).find(); + } + + public static void handleCompileDefinitions( + FederateInstance federate, int numOfFederates, RtiConfig rtiConfig) { + federate.targetConfig.setByUser.add(TargetProperty.COMPILE_DEFINITIONS); + federate.targetConfig.compileDefinitions.put("FEDERATED", ""); + federate.targetConfig.compileDefinitions.put( + "FEDERATED_" + federate.targetConfig.coordination.toString().toUpperCase(), ""); + if (federate.targetConfig.auth) { + federate.targetConfig.compileDefinitions.put("FEDERATED_AUTHENTICATED", ""); } - - public static void handleCompileDefinitions( - FederateInstance federate, - int numOfFederates, - RtiConfig rtiConfig - ) { - federate.targetConfig.setByUser.add(TargetProperty.COMPILE_DEFINITIONS); - federate.targetConfig.compileDefinitions.put("FEDERATED", ""); - federate.targetConfig.compileDefinitions.put("FEDERATED_"+federate.targetConfig.coordination.toString().toUpperCase(), ""); - if (federate.targetConfig.auth) { - federate.targetConfig.compileDefinitions.put("FEDERATED_AUTHENTICATED", ""); - } - federate.targetConfig.compileDefinitions.put("NUMBER_OF_FEDERATES", String.valueOf(numOfFederates)); - federate.targetConfig.compileDefinitions.put("EXECUTABLE_PREAMBLE", ""); - federate.targetConfig.compileDefinitions.put("WORKERS_NEEDED_FOR_FEDERATE", String.valueOf(minThreadsToHandleInputPorts(federate))); - - handleAdvanceMessageInterval(federate); - - initializeClockSynchronization(federate, rtiConfig); - } - - /** - * The number of threads needs to be at least one larger than the input ports - * to allow the federate to wait on all input ports while allowing an additional - * worker thread to process incoming messages. - * @return The minimum number of threads needed. - */ - public static int minThreadsToHandleInputPorts(FederateInstance federate) { - int nthreads = 1; - nthreads = Math.max(nthreads, federate.networkMessageActions.size() + 1); - return nthreads; + federate.targetConfig.compileDefinitions.put( + "NUMBER_OF_FEDERATES", String.valueOf(numOfFederates)); + federate.targetConfig.compileDefinitions.put("EXECUTABLE_PREAMBLE", ""); + federate.targetConfig.compileDefinitions.put( + "WORKERS_NEEDED_FOR_FEDERATE", String.valueOf(minThreadsToHandleInputPorts(federate))); + + handleAdvanceMessageInterval(federate); + + initializeClockSynchronization(federate, rtiConfig); + } + + /** + * The number of threads needs to be at least one larger than the input ports to allow the + * federate to wait on all input ports while allowing an additional worker thread to process + * incoming messages. + * + * @return The minimum number of threads needed. + */ + public static int minThreadsToHandleInputPorts(FederateInstance federate) { + int nthreads = 1; + nthreads = Math.max(nthreads, federate.networkMessageActions.size() + 1); + return nthreads; + } + + private static void handleAdvanceMessageInterval(FederateInstance federate) { + var advanceMessageInterval = federate.targetConfig.coordinationOptions.advance_message_interval; + federate.targetConfig.setByUser.remove(TargetProperty.COORDINATION_OPTIONS); + if (advanceMessageInterval != null) { + federate.targetConfig.compileDefinitions.put( + "ADVANCE_MESSAGE_INTERVAL", String.valueOf(advanceMessageInterval.toNanoSeconds())); } + } - private static void handleAdvanceMessageInterval(FederateInstance federate) { - var advanceMessageInterval = federate.targetConfig.coordinationOptions.advance_message_interval; - federate.targetConfig.setByUser.remove(TargetProperty.COORDINATION_OPTIONS); - if (advanceMessageInterval != null) { - federate.targetConfig.compileDefinitions.put( - "ADVANCE_MESSAGE_INTERVAL", - String.valueOf(advanceMessageInterval.toNanoSeconds()) - ); + static boolean clockSyncIsOn(FederateInstance federate, RtiConfig rtiConfig) { + return federate.targetConfig.clockSync != ClockSyncMode.OFF + && (!rtiConfig.getHost().equals(federate.host) + || federate.targetConfig.clockSyncOptions.localFederatesOn); + } + + /** + * Initialize clock synchronization (if enabled) and its related options for a given federate. + * + *

Clock synchronization can be enabled using the clock-sync target property. + * + * @see Documentation + */ + public static void initializeClockSynchronization( + FederateInstance federate, RtiConfig rtiConfig) { + // Check if clock synchronization should be enabled for this federate in the first place + if (clockSyncIsOn(federate, rtiConfig)) { + System.out.println("Initial clock synchronization is enabled for federate " + federate.id); + if (federate.targetConfig.clockSync == ClockSyncMode.ON) { + if (federate.targetConfig.clockSyncOptions.collectStats) { + System.out.println("Will collect clock sync statistics for federate " + federate.id); + // Add libm to the compiler flags + // FIXME: This is a linker flag not compile flag but we don't have a way to add linker + // flags + // FIXME: This is probably going to fail on MacOS (especially using clang) + // because libm functions are builtin + federate.targetConfig.compilerFlags.add("-lm"); + federate.targetConfig.setByUser.add(TargetProperty.FLAGS); } - } + System.out.println("Runtime clock synchronization is enabled for federate " + federate.id); + } - static boolean clockSyncIsOn(FederateInstance federate, RtiConfig rtiConfig) { - return federate.targetConfig.clockSync != ClockSyncMode.OFF - && (!rtiConfig.getHost().equals(federate.host) - || federate.targetConfig.clockSyncOptions.localFederatesOn); + addClockSyncCompileDefinitions(federate); } - - /** - * Initialize clock synchronization (if enabled) and its related options for a given federate. - * - * Clock synchronization can be enabled using the clock-sync target property. - * @see Documentation - */ - public static void initializeClockSynchronization( - FederateInstance federate, - RtiConfig rtiConfig - ) { - // Check if clock synchronization should be enabled for this federate in the first place - if (clockSyncIsOn(federate, rtiConfig)) { - System.out.println("Initial clock synchronization is enabled for federate " - + federate.id - ); - if (federate.targetConfig.clockSync == ClockSyncMode.ON) { - if (federate.targetConfig.clockSyncOptions.collectStats) { - System.out.println("Will collect clock sync statistics for federate " + federate.id); - // Add libm to the compiler flags - // FIXME: This is a linker flag not compile flag but we don't have a way to add linker flags - // FIXME: This is probably going to fail on MacOS (especially using clang) - // because libm functions are builtin - federate.targetConfig.compilerFlags.add("-lm"); - federate.targetConfig.setByUser.add(TargetProperty.FLAGS); - } - System.out.println("Runtime clock synchronization is enabled for federate " - + federate.id - ); - } - - addClockSyncCompileDefinitions(federate); - } + } + + /** + * Initialize clock synchronization (if enabled) and its related options for a given federate. + * + *

Clock synchronization can be enabled using the clock-sync target property. + * + * @see Documentation + */ + public static void addClockSyncCompileDefinitions(FederateInstance federate) { + + ClockSyncMode mode = federate.targetConfig.clockSync; + ClockSyncOptions options = federate.targetConfig.clockSyncOptions; + + federate.targetConfig.compileDefinitions.put("_LF_CLOCK_SYNC_INITIAL", ""); + federate.targetConfig.compileDefinitions.put( + "_LF_CLOCK_SYNC_PERIOD_NS", String.valueOf(options.period.toNanoSeconds())); + federate.targetConfig.compileDefinitions.put( + "_LF_CLOCK_SYNC_EXCHANGES_PER_INTERVAL", String.valueOf(options.trials)); + federate.targetConfig.compileDefinitions.put( + "_LF_CLOCK_SYNC_ATTENUATION", String.valueOf(options.attenuation)); + + if (mode == ClockSyncMode.ON) { + federate.targetConfig.compileDefinitions.put("_LF_CLOCK_SYNC_ON", ""); + if (options.collectStats) { + federate.targetConfig.compileDefinitions.put("_LF_CLOCK_SYNC_COLLECT_STATS", ""); + } } + } + /** Generate a file to be included by CMake. */ + public static void generateCMakeInclude(FederateInstance federate, FedFileConfig fileConfig) + throws IOException { + Files.createDirectories(fileConfig.getSrcPath().resolve("include")); - /** - * Initialize clock synchronization (if enabled) and its related options for a given federate. - * - * Clock synchronization can be enabled using the clock-sync target property. - * @see Documentation - */ - public static void addClockSyncCompileDefinitions(FederateInstance federate) { - - ClockSyncMode mode = federate.targetConfig.clockSync; - ClockSyncOptions options = federate.targetConfig.clockSyncOptions; - - - federate.targetConfig.compileDefinitions.put("_LF_CLOCK_SYNC_INITIAL", ""); - federate.targetConfig.compileDefinitions.put("_LF_CLOCK_SYNC_PERIOD_NS", String.valueOf(options.period.toNanoSeconds())); - federate.targetConfig.compileDefinitions.put("_LF_CLOCK_SYNC_EXCHANGES_PER_INTERVAL", String.valueOf(options.trials)); - federate.targetConfig.compileDefinitions.put("_LF_CLOCK_SYNC_ATTENUATION", String.valueOf(options.attenuation)); + Path cmakeIncludePath = + fileConfig + .getSrcPath() + .resolve("include" + File.separator + federate.name + "_extension.cmake"); - if (mode == ClockSyncMode.ON) { - federate.targetConfig.compileDefinitions.put("_LF_CLOCK_SYNC_ON", ""); - if (options.collectStats) { - federate.targetConfig.compileDefinitions.put("_LF_CLOCK_SYNC_COLLECT_STATS", ""); - } - } - } + CodeBuilder cmakeIncludeCode = new CodeBuilder(); + cmakeIncludeCode.pr(generateSerializationCMakeExtension(federate)); + cmakeIncludeCode.pr( + "add_compile_definitions(LF_SOURCE_DIRECTORY=\"" + fileConfig.srcPath + "\")"); + cmakeIncludeCode.pr( + "add_compile_definitions(LF_PACKAGE_DIRECTORY=\"" + fileConfig.srcPkgPath + "\")"); - /** - * Generate a file to be included by CMake. - */ - public static void generateCMakeInclude( - FederateInstance federate, - FedFileConfig fileConfig - ) throws IOException { - Files.createDirectories(fileConfig.getSrcPath().resolve("include")); - - Path cmakeIncludePath = fileConfig.getSrcPath() - .resolve("include" + File.separator + federate.name + "_extension.cmake"); - - CodeBuilder cmakeIncludeCode = new CodeBuilder(); - - cmakeIncludeCode.pr(generateSerializationCMakeExtension(federate)); - cmakeIncludeCode.pr( - "add_compile_definitions(LF_SOURCE_DIRECTORY=\"" - + fileConfig.srcPath - + "\")" - ); - cmakeIncludeCode.pr( - "add_compile_definitions(LF_PACKAGE_DIRECTORY=\"" - + fileConfig.srcPkgPath - + "\")" - ); - - try (var srcWriter = Files.newBufferedWriter(cmakeIncludePath)) { - srcWriter.write(cmakeIncludeCode.getCode()); - } - - federate.targetConfig.cmakeIncludes.add(fileConfig.getSrcPath().relativize(cmakeIncludePath).toString()); - federate.targetConfig.setByUser.add(TargetProperty.CMAKE_INCLUDE); + try (var srcWriter = Files.newBufferedWriter(cmakeIncludePath)) { + srcWriter.write(cmakeIncludeCode.getCode()); } - - /** - * Generate code that sends the neighbor structure message to the RTI. - * See {@code MSG_TYPE_NEIGHBOR_STRUCTURE} in {@code federated/net_common.h}. - * - * @param federate The federate that is sending its neighbor structure - */ - public static String generateFederateNeighborStructure(FederateInstance federate) { - var code = new CodeBuilder(); - code.pr(String.join("\n", - "/**", - "* Generated function that sends information about connections between this federate and", - "* other federates where messages are routed through the RTI. Currently, this", - "* only includes logical connections when the coordination is centralized. This", - "* information is needed for the RTI to perform the centralized coordination.", - "* @see MSG_TYPE_NEIGHBOR_STRUCTURE in net_common.h", - "*/", - "void send_neighbor_structure_to_RTI(int rti_socket) {" - )); - code.indent(); - // Initialize the array of information about the federate's immediate upstream - // and downstream relayed (through the RTI) logical connections, to send to the - // RTI. - code.pr(String.join("\n", - "interval_t candidate_tmp;", - "size_t buffer_size = 1 + 8 + ", - " "+federate.dependsOn.keySet().size()+" * ( sizeof(uint16_t) + sizeof(int64_t) ) +", - " "+federate.sendsTo.keySet().size()+" * sizeof(uint16_t);", - "unsigned char buffer_to_send[buffer_size];", - "", - "size_t message_head = 0;", - "buffer_to_send[message_head] = MSG_TYPE_NEIGHBOR_STRUCTURE;", - "message_head++;", - "encode_int32((int32_t)"+federate.dependsOn.keySet().size()+", &(buffer_to_send[message_head]));", - "message_head+=sizeof(int32_t);", - "encode_int32((int32_t)"+federate.sendsTo.keySet().size()+", &(buffer_to_send[message_head]));", - "message_head+=sizeof(int32_t);" - )); - - if (!federate.dependsOn.keySet().isEmpty()) { - // Next, populate these arrays. - // Find the minimum delay in the process. - // FIXME: Zero delay is not really the same as a microstep delay. - for (FederateInstance upstreamFederate : federate.dependsOn.keySet()) { - code.pr(String.join("\n", - "encode_uint16((uint16_t)"+upstreamFederate.id+", &(buffer_to_send[message_head]));", - "message_head += sizeof(uint16_t);" - )); - // The minimum delay calculation needs to be made in the C code because it - // may depend on parameter values. - // FIXME: These would have to be top-level parameters, which don't really - // have any support yet. Ideally, they could be overridden on the command line. - // When that is done, they will need to be in scope here. - var delays = federate.dependsOn.get(upstreamFederate); - if (delays != null) { - // There is at least one delay, so find the minimum. - // If there is no delay at all, this is encoded as NEVER. - code.pr("candidate_tmp = FOREVER;"); - for (Expression delay : delays) { - if (delay == null) { - // Use NEVER to encode no delay at all. - code.pr("candidate_tmp = NEVER;"); - } else { - 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 + ";", - "}" - )); - } - } - code.pr(String.join("\n", - "encode_int64((int64_t)candidate_tmp, &(buffer_to_send[message_head]));", - "message_head += sizeof(int64_t);" - )); - } else { - // Use NEVER to encode no delay at all. - code.pr(String.join("\n", - "encode_int64(NEVER, &(buffer_to_send[message_head]));", - "message_head += sizeof(int64_t);" - )); - } - } - } - - // Next, set up the downstream array. - if (!federate.sendsTo.keySet().isEmpty()) { - // Next, populate the array. - // Find the minimum delay in the process. - // FIXME: Zero delay is not really the same as a microstep delay. - for (FederateInstance downstreamFederate : federate.sendsTo.keySet()) { - code.pr(String.join("\n", - "encode_uint16("+downstreamFederate.id+", &(buffer_to_send[message_head]));", - "message_head += sizeof(uint16_t);" - )); + federate.targetConfig.cmakeIncludes.add( + fileConfig.getSrcPath().relativize(cmakeIncludePath).toString()); + federate.targetConfig.setByUser.add(TargetProperty.CMAKE_INCLUDE); + } + + /** + * Generate code that sends the neighbor structure message to the RTI. See {@code + * MSG_TYPE_NEIGHBOR_STRUCTURE} in {@code federated/net_common.h}. + * + * @param federate The federate that is sending its neighbor structure + */ + public static String generateFederateNeighborStructure(FederateInstance federate) { + var code = new CodeBuilder(); + code.pr( + String.join( + "\n", + "/**", + "* Generated function that sends information about connections between this federate" + + " and", + "* other federates where messages are routed through the RTI. Currently, this", + "* only includes logical connections when the coordination is centralized. This", + "* information is needed for the RTI to perform the centralized coordination.", + "* @see MSG_TYPE_NEIGHBOR_STRUCTURE in net_common.h", + "*/", + "void send_neighbor_structure_to_RTI(int rti_socket) {")); + code.indent(); + // Initialize the array of information about the federate's immediate upstream + // and downstream relayed (through the RTI) logical connections, to send to the + // RTI. + code.pr( + String.join( + "\n", + "interval_t candidate_tmp;", + "size_t buffer_size = 1 + 8 + ", + " " + + federate.dependsOn.keySet().size() + + " * ( sizeof(uint16_t) + sizeof(int64_t) ) +", + " " + federate.sendsTo.keySet().size() + " * sizeof(uint16_t);", + "unsigned char buffer_to_send[buffer_size];", + "", + "size_t message_head = 0;", + "buffer_to_send[message_head] = MSG_TYPE_NEIGHBOR_STRUCTURE;", + "message_head++;", + "encode_int32((int32_t)" + + federate.dependsOn.keySet().size() + + ", &(buffer_to_send[message_head]));", + "message_head+=sizeof(int32_t);", + "encode_int32((int32_t)" + + federate.sendsTo.keySet().size() + + ", &(buffer_to_send[message_head]));", + "message_head+=sizeof(int32_t);")); + + if (!federate.dependsOn.keySet().isEmpty()) { + // Next, populate these arrays. + // Find the minimum delay in the process. + // FIXME: Zero delay is not really the same as a microstep delay. + for (FederateInstance upstreamFederate : federate.dependsOn.keySet()) { + code.pr( + String.join( + "\n", + "encode_uint16((uint16_t)" + + upstreamFederate.id + + ", &(buffer_to_send[message_head]));", + "message_head += sizeof(uint16_t);")); + // The minimum delay calculation needs to be made in the C code because it + // may depend on parameter values. + // FIXME: These would have to be top-level parameters, which don't really + // have any support yet. Ideally, they could be overridden on the command line. + // When that is done, they will need to be in scope here. + var delays = federate.dependsOn.get(upstreamFederate); + if (delays != null) { + // There is at least one delay, so find the minimum. + // If there is no delay at all, this is encoded as NEVER. + code.pr("candidate_tmp = FOREVER;"); + for (Expression delay : delays) { + if (delay == null) { + // Use NEVER to encode no delay at all. + code.pr("candidate_tmp = NEVER;"); + } else { + 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 + ";", + "}")); } + } + code.pr( + String.join( + "\n", + "encode_int64((int64_t)candidate_tmp, &(buffer_to_send[message_head]));", + "message_head += sizeof(int64_t);")); + } else { + // Use NEVER to encode no delay at all. + code.pr( + String.join( + "\n", + "encode_int64(NEVER, &(buffer_to_send[message_head]));", + "message_head += sizeof(int64_t);")); } - code.pr(String.join("\n", - "write_to_socket_errexit(", - " rti_socket, ", - " buffer_size,", - " buffer_to_send,", - " \"Failed to send the neighbor structure message to the RTI.\"", - ");" - )); - code.unindent(); - code.pr("}"); - return code.toString(); + } } - - public static List getFederatedFiles() { - return List.of( - "federated/net_util.c", - "federated/net_util.h", - "federated/net_common.h", - "federated/federate.c", - "federated/federate.h", - "federated/clock-sync.h", - "federated/clock-sync.c" - ); + // Next, set up the downstream array. + if (!federate.sendsTo.keySet().isEmpty()) { + // Next, populate the array. + // Find the minimum delay in the process. + // FIXME: Zero delay is not really the same as a microstep delay. + for (FederateInstance downstreamFederate : federate.sendsTo.keySet()) { + code.pr( + String.join( + "\n", + "encode_uint16(" + downstreamFederate.id + ", &(buffer_to_send[message_head]));", + "message_head += sizeof(uint16_t);")); + } } - - /** - * Surround {@code code} with blocks to ensure that code only executes - * if the program is federated. - */ - public static String surroundWithIfFederated(String code) { - return """ + code.pr( + String.join( + "\n", + "write_to_socket_errexit(", + " rti_socket, ", + " buffer_size,", + " buffer_to_send,", + " \"Failed to send the neighbor structure message to the RTI.\"", + ");")); + code.unindent(); + code.pr("}"); + return code.toString(); + } + + public static List getFederatedFiles() { + return List.of( + "federated/net_util.c", + "federated/net_util.h", + "federated/net_common.h", + "federated/federate.c", + "federated/federate.h", + "federated/clock-sync.h", + "federated/clock-sync.c"); + } + + /** + * Surround {@code code} with blocks to ensure that code only executes if the program is + * federated. + */ + public static String surroundWithIfFederated(String code) { + return """ #ifdef FEDERATED %s #endif // FEDERATED - """.formatted(code); - } - - /** - * Surround {@code code} with blocks to ensure that code only executes - * if the program is federated and has a centralized coordination. - */ - public static String surroundWithIfFederatedCentralized(String code) { - return """ + """ + .formatted(code); + } + + /** + * Surround {@code code} with blocks to ensure that code only executes if the program is federated + * and has a centralized coordination. + */ + public static String surroundWithIfFederatedCentralized(String code) { + return """ #ifdef FEDERATED_CENTRALIZED %s #endif // FEDERATED_CENTRALIZED - """.formatted(code); - } - - /** - * Surround {@code code} with blocks to ensure that code only executes - * if the program is federated and has a decentralized coordination. - */ - public static String surroundWithIfFederatedDecentralized(String code) { - return """ + """ + .formatted(code); + } + + /** + * Surround {@code code} with blocks to ensure that code only executes if the program is federated + * and has a decentralized coordination. + */ + public static String surroundWithIfFederatedDecentralized(String code) { + return """ #ifdef FEDERATED_DECENTRALIZED %s #endif // FEDERATED_DECENTRALIZED - """.formatted(code); + """ + .formatted(code); + } + + /** Generate preamble code needed for enabled serializers of the federate. */ + public static String generateSerializationIncludes( + FederateInstance federate, FedFileConfig fileConfig) { + CodeBuilder code = new CodeBuilder(); + for (SupportedSerializers serializer : federate.enabledSerializers) { + switch (serializer) { + case NATIVE: + case PROTO: + { + // No need to do anything at this point. + break; + } + case ROS2: + { + var ROSSerializer = new FedROS2CPPSerialization(); + code.pr(ROSSerializer.generatePreambleForSupport().toString()); + break; + } + } } - - /** - * Generate preamble code needed for enabled serializers of the federate. - */ - public static String generateSerializationIncludes( - FederateInstance federate, - FedFileConfig fileConfig - ) { - CodeBuilder code = new CodeBuilder(); - for (SupportedSerializers serializer : federate.enabledSerializers) { - switch (serializer) { - case NATIVE: - case PROTO: { - // No need to do anything at this point. - break; - } - case ROS2: { - var ROSSerializer = new FedROS2CPPSerialization(); - code.pr(ROSSerializer.generatePreambleForSupport().toString()); - break; - } - } - } - return code.getCode(); - } - - /** - * Generate cmake-include code needed for enabled serializers of the federate. - */ - public static String generateSerializationCMakeExtension( - FederateInstance federate - ) { - CodeBuilder code = new CodeBuilder(); - for (SupportedSerializers serializer : federate.enabledSerializers) { - switch (serializer) { - case NATIVE: - case PROTO: { - // No CMake code is needed for now - break; - } - case ROS2: { - var ROSSerializer = new FedROS2CPPSerialization(); - code.pr(ROSSerializer.generateCompilerExtensionForSupport()); - break; - } - } - } - return code.getCode(); + return code.getCode(); + } + + /** Generate cmake-include code needed for enabled serializers of the federate. */ + public static String generateSerializationCMakeExtension(FederateInstance federate) { + CodeBuilder code = new CodeBuilder(); + for (SupportedSerializers serializer : federate.enabledSerializers) { + switch (serializer) { + case NATIVE: + case PROTO: + { + // No CMake code is needed for now + break; + } + case ROS2: + { + var ROSSerializer = new FedROS2CPPSerialization(); + code.pr(ROSSerializer.generateCompilerExtensionForSupport()); + break; + } + } } - - public static CharSequence upstreamPortReactions(FederateInstance federate, ReactorInstance main) { - CodeBuilder code = new CodeBuilder(); - if (!federate.networkMessageActions.isEmpty()) { - // Create a static array of trigger_t pointers. - // networkMessageActions is a list of Actions, but we - // need a list of trigger struct names for ActionInstances. - // There should be exactly one ActionInstance in the - // main reactor for each Action. - var reactions = new LinkedList(); - for (int i = 0; i < federate.networkMessageActions.size(); ++i) { - // Find the corresponding ActionInstance. - var reaction = federate.networkReceiverReactions.get(i); - var reactor = main.lookupReactorInstance(federate.networkReceiverInstantiations.get(i)); - var reactionInstance = reactor.lookupReactionInstance(reaction); - reactions.add(CUtil.reactionRef(reactionInstance)); - } - var tableCount = 0; - for (String react: reactions) { - code.pr("upstreamPortReactions[" + (tableCount++) + "] = (reaction_t*)&" + react + "; \\"); - } - } - return code.getCode(); + return code.getCode(); + } + + public static CharSequence upstreamPortReactions( + FederateInstance federate, ReactorInstance main) { + CodeBuilder code = new CodeBuilder(); + if (!federate.networkMessageActions.isEmpty()) { + // Create a static array of trigger_t pointers. + // networkMessageActions is a list of Actions, but we + // need a list of trigger struct names for ActionInstances. + // There should be exactly one ActionInstance in the + // main reactor for each Action. + var reactions = new LinkedList(); + for (int i = 0; i < federate.networkMessageActions.size(); ++i) { + // Find the corresponding ActionInstance. + var reaction = federate.networkReceiverReactions.get(i); + var reactor = main.lookupReactorInstance(federate.networkReceiverInstantiations.get(i)); + var reactionInstance = reactor.lookupReactionInstance(reaction); + reactions.add(CUtil.reactionRef(reactionInstance)); + } + var tableCount = 0; + for (String react : reactions) { + code.pr("upstreamPortReactions[" + (tableCount++) + "] = (reaction_t*)&" + react + "; \\"); + } } - - public static CharSequence downstreamControlPortReactions(FederateInstance federate, ReactorInstance main) { - CodeBuilder code = new CodeBuilder(); - if (!federate.networkSenderControlReactions.isEmpty()) { - // Create a static array of trigger_t pointers. - // networkMessageActions is a list of Actions, but we - // need a list of trigger struct names for ActionInstances. - // There should be exactly one ActionInstance in the - // main reactor for each Action. - var reactions = new LinkedList(); - for (int i = 0; i < federate.networkSenderControlReactions.size(); ++i) { - // Find the corresponding ActionInstance. - var reaction = federate.networkSenderControlReactions.get(i); - var reactor = main.lookupReactorInstance(federate.networkSenderInstantiations.get(i)); - var reactionInstance = reactor.lookupReactionInstance(reaction); - reactions.add(CUtil.reactionRef(reactionInstance)); - } - var tableCount = 0; - for (String react: reactions) { - code.pr("downstreamControlPortReactions[" + (tableCount++) + "] = (reaction_t*)&" + react + "; \\"); - } - } - return code.getCode(); + return code.getCode(); + } + + public static CharSequence downstreamControlPortReactions( + FederateInstance federate, ReactorInstance main) { + CodeBuilder code = new CodeBuilder(); + if (!federate.networkSenderControlReactions.isEmpty()) { + // Create a static array of trigger_t pointers. + // networkMessageActions is a list of Actions, but we + // need a list of trigger struct names for ActionInstances. + // There should be exactly one ActionInstance in the + // main reactor for each Action. + var reactions = new LinkedList(); + for (int i = 0; i < federate.networkSenderControlReactions.size(); ++i) { + // Find the corresponding ActionInstance. + var reaction = federate.networkSenderControlReactions.get(i); + var reactor = main.lookupReactorInstance(federate.networkSenderInstantiations.get(i)); + var reactionInstance = reactor.lookupReactionInstance(reaction); + reactions.add(CUtil.reactionRef(reactionInstance)); + } + var tableCount = 0; + for (String react : reactions) { + code.pr( + "downstreamControlPortReactions[" + + (tableCount++) + + "] = (reaction_t*)&" + + react + + "; \\"); + } } + return code.getCode(); + } } - diff --git a/org.lflang/src/org/lflang/federated/extensions/FedTargetExtension.java b/org.lflang/src/org/lflang/federated/extensions/FedTargetExtension.java index d50a801617..17ac198781 100644 --- a/org.lflang/src/org/lflang/federated/extensions/FedTargetExtension.java +++ b/org.lflang/src/org/lflang/federated/extensions/FedTargetExtension.java @@ -1,7 +1,6 @@ package org.lflang.federated.extensions; import java.io.IOException; - import org.lflang.ErrorReporter; import org.lflang.InferredType; import org.lflang.TargetProperty.CoordinationType; @@ -17,110 +16,110 @@ public interface FedTargetExtension { - /** - * Perform necessary actions to initialize the target config. - * - * @param context - * @param numOfFederates - * @param federate The federate instance. - * @param fileConfig An instance of {@code FedFileConfig}. - * @param errorReporter Used to report errors. - */ - void initializeTargetConfig(LFGeneratorContext context, int numOfFederates, FederateInstance federate, FedFileConfig fileConfig, - ErrorReporter errorReporter, RtiConfig rtiConfig) throws IOException; + /** + * Perform necessary actions to initialize the target config. + * + * @param context + * @param numOfFederates + * @param federate The federate instance. + * @param fileConfig An instance of {@code FedFileConfig}. + * @param errorReporter Used to report errors. + */ + void initializeTargetConfig( + LFGeneratorContext context, + int numOfFederates, + FederateInstance federate, + FedFileConfig fileConfig, + ErrorReporter errorReporter, + RtiConfig rtiConfig) + throws IOException; - /** - * Generate code for the body of a reaction that handles the - * action that is triggered by receiving a message from a remote - * federate. - * @param action The action. - * @param sendingPort The output port providing the data to send. - * @param receivingPort The ID of the destination port. - * @param connection FIXME - * @param type FIXME - * @param coordinationType The coordination type - * @param errorReporter - */ - String generateNetworkReceiverBody( - Action action, - VarRef sendingPort, - VarRef receivingPort, - FedConnectionInstance connection, - InferredType type, - CoordinationType coordinationType, - ErrorReporter errorReporter - ); + /** + * Generate code for the body of a reaction that handles the action that is triggered by receiving + * a message from a remote federate. + * + * @param action The action. + * @param sendingPort The output port providing the data to send. + * @param receivingPort The ID of the destination port. + * @param connection FIXME + * @param type FIXME + * @param coordinationType The coordination type + * @param errorReporter + */ + String generateNetworkReceiverBody( + Action action, + VarRef sendingPort, + VarRef receivingPort, + FedConnectionInstance connection, + InferredType type, + CoordinationType coordinationType, + ErrorReporter errorReporter); - /** - * Generate code for the body of a reaction that handles an output - * that is to be sent over the network. - * @param sendingPort The output port providing the data to send. - * @param receivingPort The variable reference to the destination port. - * @param connection - * @param type - * @param coordinationType - * @param errorReporter FIXME - */ - String generateNetworkSenderBody( - VarRef sendingPort, - VarRef receivingPort, - FedConnectionInstance connection, - InferredType type, - CoordinationType coordinationType, - ErrorReporter errorReporter - ); + /** + * Generate code for the body of a reaction that handles an output that is to be sent over the + * network. + * + * @param sendingPort The output port providing the data to send. + * @param receivingPort The variable reference to the destination port. + * @param connection + * @param type + * @param coordinationType + * @param errorReporter FIXME + */ + String generateNetworkSenderBody( + VarRef sendingPort, + VarRef receivingPort, + FedConnectionInstance connection, + InferredType type, + CoordinationType coordinationType, + ErrorReporter errorReporter); - /** - * Generate code for the body of a reaction that decides whether the trigger for the given - * port is going to be present or absent for the current logical time. - * This reaction is put just before the first reaction that is triggered by the network - * input port "port" or has it in its sources. If there are only connections to contained - * reactors, in the top-level reactor. - * - * @param receivingPortID The port to generate the control reaction for - * @param maxSTP The maximum value of STP is assigned to reactions (if any) - * that have port as their trigger or source - * @param coordination FIXME - */ - String generateNetworkInputControlReactionBody( - int receivingPortID, - TimeValue maxSTP, - CoordinationType coordination - ); + /** + * Generate code for the body of a reaction that decides whether the trigger for the given port is + * going to be present or absent for the current logical time. This reaction is put just before + * the first reaction that is triggered by the network input port "port" or has it in its sources. + * If there are only connections to contained reactors, in the top-level reactor. + * + * @param receivingPortID The port to generate the control reaction for + * @param maxSTP The maximum value of STP is assigned to reactions (if any) that have port as + * their trigger or source + * @param coordination FIXME + */ + String generateNetworkInputControlReactionBody( + int receivingPortID, TimeValue maxSTP, CoordinationType coordination); - /** - * Generate code for the body of a reaction that sends a port status message for the given - * port if it is absent. - * - * @oaram srcOutputPort FIXME - * @param connection FIXME - */ - String generateNetworkOutputControlReactionBody( - VarRef srcOutputPort, - FedConnectionInstance connection - ); + /** + * Generate code for the body of a reaction that sends a port status message for the given port if + * it is absent. + * + * @oaram srcOutputPort FIXME + * @param connection FIXME + */ + String generateNetworkOutputControlReactionBody( + VarRef srcOutputPort, FedConnectionInstance connection); - /** Optionally apply additional annotations to the reaction. */ - default void annotateReaction(Reaction reaction) {} + /** Optionally apply additional annotations to the reaction. */ + default void annotateReaction(Reaction reaction) {} - /** - * Return the type for the raw network buffer in the target language (e.g., {@code uint_8} in C). This would be the type of the - * network messages after serialization and before deserialization. It is primarily used to determine the type for the - * network action at the receiver. - */ - String getNetworkBufferType(); + /** + * Return the type for the raw network buffer in the target language (e.g., {@code uint_8} in C). + * This would be the type of the network messages after serialization and before deserialization. + * It is primarily used to determine the type for the network action at the receiver. + */ + String getNetworkBufferType(); - /** - * Add necessary preamble to the source to set up federated execution. - * - * @param federate - * @param rtiConfig - * @param errorReporter - * @return - */ - String generatePreamble(FederateInstance federate, - FedFileConfig fileConfig, - RtiConfig rtiConfig, - ErrorReporter errorReporter) - throws IOException; + /** + * Add necessary preamble to the source to set up federated execution. + * + * @param federate + * @param rtiConfig + * @param errorReporter + * @return + */ + String generatePreamble( + FederateInstance federate, + FedFileConfig fileConfig, + 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 5caafc1c0c..ca16d1d445 100644 --- a/org.lflang/src/org/lflang/federated/extensions/FedTargetExtensionFactory.java +++ b/org.lflang/src/org/lflang/federated/extensions/FedTargetExtensionFactory.java @@ -1,24 +1,26 @@ package org.lflang.federated.extensions; import org.lflang.Target; -import org.lflang.lf.TargetDecl; /** * Class for instantiating target extensions. + * * @author Soroush Bateni */ public class FedTargetExtensionFactory { - /** - * Given a target, return the appropriate extension. - */ - public static FedTargetExtension getExtension(Target target) { - switch (target) { - case CCPP: - case C: return new CExtension(); - case Python: return new PythonExtension(); - case TS: return new TSExtension(); - default: throw new RuntimeException("Target not supported"); - } + /** Given a target, return the appropriate extension. */ + public static FedTargetExtension getExtension(Target target) { + switch (target) { + case CCPP: + case C: + return new CExtension(); + case Python: + return new PythonExtension(); + case TS: + return new TSExtension(); + default: + throw new RuntimeException("Target not supported"); } + } } diff --git a/org.lflang/src/org/lflang/federated/extensions/PythonExtension.java b/org.lflang/src/org/lflang/federated/extensions/PythonExtension.java index 0e6be72d31..ee598d8e26 100644 --- a/org.lflang/src/org/lflang/federated/extensions/PythonExtension.java +++ b/org.lflang/src/org/lflang/federated/extensions/PythonExtension.java @@ -27,11 +27,10 @@ package org.lflang.federated.extensions; import java.io.IOException; - -import org.lflang.ast.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.InferredType; import org.lflang.TargetProperty.CoordinationType; +import org.lflang.ast.ASTUtils; import org.lflang.federated.generator.FedConnectionInstance; import org.lflang.federated.generator.FedFileConfig; import org.lflang.federated.generator.FederateInstance; @@ -39,164 +38,147 @@ import org.lflang.federated.serialization.FedSerialization; import org.lflang.federated.serialization.SupportedSerializers; import org.lflang.generator.CodeBuilder; -import org.lflang.generator.ReactionInstance; import org.lflang.generator.python.PyUtil; import org.lflang.lf.Action; import org.lflang.lf.Reaction; import org.lflang.lf.VarRef; /** - * An extension class to the PythonGenerator that enables certain federated - * functionalities. + * An extension class to the PythonGenerator that enables certain federated functionalities. * * @author Soroush Bateni */ public class PythonExtension extends CExtension { - @Override - protected void generateCMakeInclude(FederateInstance federate, FedFileConfig fileConfig) throws IOException {} - - @Override - protected String generateSerializationIncludes(FederateInstance federate, FedFileConfig fileConfig) { - CodeBuilder code = new CodeBuilder(); - for (SupportedSerializers serialization : federate.enabledSerializers) { - switch (serialization) { - case NATIVE: { - FedNativePythonSerialization pickler = new FedNativePythonSerialization(); - code.pr(pickler.generatePreambleForSupport().toString()); - } - case PROTO: { - // Nothing needs to be done - } - case ROS2: { - // FIXME: Not supported yet - } - } - } - return code.getCode(); - } + @Override + protected void generateCMakeInclude(FederateInstance federate, FedFileConfig fileConfig) + throws IOException {} - @Override - public String generateNetworkSenderBody( - VarRef sendingPort, - VarRef receivingPort, - FedConnectionInstance connection, - InferredType type, - CoordinationType coordinationType, - ErrorReporter errorReporter - ) { - var result = new CodeBuilder(); - result.pr(PyUtil.generateGILAcquireCode() + "\n"); - result.pr( - super.generateNetworkSenderBody( - sendingPort, - receivingPort, - connection, - type, - coordinationType, - errorReporter - ) - ); - result.pr(PyUtil.generateGILReleaseCode() + "\n"); - return result.getCode(); + @Override + protected String generateSerializationIncludes( + FederateInstance federate, FedFileConfig fileConfig) { + CodeBuilder code = new CodeBuilder(); + for (SupportedSerializers serialization : federate.enabledSerializers) { + switch (serialization) { + case NATIVE: + { + FedNativePythonSerialization pickler = new FedNativePythonSerialization(); + code.pr(pickler.generatePreambleForSupport().toString()); + } + case PROTO: + { + // Nothing needs to be done + } + case ROS2: + { + // FIXME: Not supported yet + } + } } + return code.getCode(); + } - @Override - public String generateNetworkReceiverBody( - Action action, - VarRef sendingPort, - VarRef receivingPort, - FedConnectionInstance connection, - InferredType type, - CoordinationType coordinationType, - ErrorReporter errorReporter - ) { - var result = new CodeBuilder(); - result.pr(PyUtil.generateGILAcquireCode() + "\n"); - result.pr( - super.generateNetworkReceiverBody( - action, - sendingPort, - receivingPort, - connection, - type, - coordinationType, - errorReporter - ) - ); - result.pr(PyUtil.generateGILReleaseCode() + "\n"); - return result.getCode(); + @Override + public String generateNetworkSenderBody( + VarRef sendingPort, + VarRef receivingPort, + FedConnectionInstance connection, + InferredType type, + CoordinationType coordinationType, + ErrorReporter errorReporter) { + var result = new CodeBuilder(); + result.pr(PyUtil.generateGILAcquireCode() + "\n"); + result.pr( + super.generateNetworkSenderBody( + sendingPort, receivingPort, connection, type, coordinationType, errorReporter)); + result.pr(PyUtil.generateGILReleaseCode() + "\n"); + return result.getCode(); + } - } + @Override + public String generateNetworkReceiverBody( + Action action, + VarRef sendingPort, + VarRef receivingPort, + FedConnectionInstance connection, + InferredType type, + CoordinationType coordinationType, + ErrorReporter errorReporter) { + var result = new CodeBuilder(); + result.pr(PyUtil.generateGILAcquireCode() + "\n"); + result.pr( + super.generateNetworkReceiverBody( + action, sendingPort, receivingPort, connection, type, coordinationType, errorReporter)); + result.pr(PyUtil.generateGILReleaseCode() + "\n"); + return result.getCode(); + } - @Override - protected void deserialize( - Action action, - VarRef receivingPort, - FedConnectionInstance connection, - InferredType type, - String receiveRef, - CodeBuilder result, - ErrorReporter errorReporter - ) { - String value = ""; - switch (connection.getSerializer()) { - case NATIVE: { - value = action.getName(); - FedNativePythonSerialization pickler = new FedNativePythonSerialization(); - result.pr(pickler.generateNetworkDeserializerCode(value, null)); - result.pr("lf_set(" + receiveRef + ", " - + FedSerialization.deserializedVarName + ");\n"); - break; - } - case PROTO: { - throw new UnsupportedOperationException("Protobuf serialization is not supported yet."); + @Override + protected void deserialize( + Action action, + VarRef receivingPort, + FedConnectionInstance connection, + InferredType type, + String receiveRef, + CodeBuilder result, + ErrorReporter errorReporter) { + String value = ""; + switch (connection.getSerializer()) { + case NATIVE: + { + value = action.getName(); + FedNativePythonSerialization pickler = new FedNativePythonSerialization(); + result.pr(pickler.generateNetworkDeserializerCode(value, null)); + result.pr("lf_set(" + receiveRef + ", " + FedSerialization.deserializedVarName + ");\n"); + break; } - case ROS2: { - throw new UnsupportedOperationException("ROS2 serialization is not supported yet."); + case PROTO: + { + throw new UnsupportedOperationException("Protobuf serialization is not supported yet."); } - + case ROS2: + { + throw new UnsupportedOperationException("ROS2 serialization is not supported yet."); } } + } - @Override - protected void serializeAndSend( - FedConnectionInstance connection, - InferredType type, - String sendRef, - CodeBuilder result, - String sendingFunction, - String commonArgs, - ErrorReporter errorReporter - ) { - String lengthExpression = ""; - String pointerExpression = ""; - switch (connection.getSerializer()) { - case NATIVE: { - var variableToSerialize = sendRef + "->value"; - FedNativePythonSerialization pickler = new FedNativePythonSerialization(); - lengthExpression = pickler.serializedBufferLength(); - pointerExpression = pickler.seializedBufferVar(); - result.pr(pickler.generateNetworkSerializerCode(variableToSerialize, null)); - result.pr( - "size_t message_length = " + lengthExpression + ";"); - result.pr( - sendingFunction + "(" + commonArgs + ", " + pointerExpression - + ");\n"); - break; + @Override + protected void serializeAndSend( + FedConnectionInstance connection, + InferredType type, + String sendRef, + CodeBuilder result, + String sendingFunction, + String commonArgs, + ErrorReporter errorReporter) { + String lengthExpression = ""; + String pointerExpression = ""; + switch (connection.getSerializer()) { + case NATIVE: + { + var variableToSerialize = sendRef + "->value"; + FedNativePythonSerialization pickler = new FedNativePythonSerialization(); + lengthExpression = pickler.serializedBufferLength(); + pointerExpression = pickler.seializedBufferVar(); + result.pr(pickler.generateNetworkSerializerCode(variableToSerialize, null)); + result.pr("size_t message_length = " + lengthExpression + ";"); + result.pr(sendingFunction + "(" + commonArgs + ", " + pointerExpression + ");\n"); + break; } - case PROTO: { - throw new UnsupportedOperationException("Protbuf serialization is not supported yet."); + case PROTO: + { + throw new UnsupportedOperationException("Protbuf serialization is not supported yet."); } - case ROS2: { - throw new UnsupportedOperationException("ROS2 serialization is not supported yet."); - } - + case ROS2: + { + throw new UnsupportedOperationException("ROS2 serialization is not supported yet."); } } + } - @Override - public void annotateReaction(Reaction reaction) { - ASTUtils.addReactionAttribute(reaction, "_c_body"); - } + @Override + public void annotateReaction(Reaction reaction) { + ASTUtils.addReactionAttribute(reaction, "_c_body"); + } } diff --git a/org.lflang/src/org/lflang/federated/extensions/TSExtension.java b/org.lflang/src/org/lflang/federated/extensions/TSExtension.java index 4a70f4cc6b..b52b739803 100644 --- a/org.lflang/src/org/lflang/federated/extensions/TSExtension.java +++ b/org.lflang/src/org/lflang/federated/extensions/TSExtension.java @@ -6,12 +6,11 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; - -import org.lflang.ast.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.InferredType; import org.lflang.TargetProperty.CoordinationType; import org.lflang.TimeValue; +import org.lflang.ast.ASTUtils; import org.lflang.federated.generator.FedASTUtils; import org.lflang.federated.generator.FedConnectionInstance; import org.lflang.federated.generator.FedFileConfig; @@ -27,73 +26,94 @@ import org.lflang.lf.Variable; public class TSExtension implements FedTargetExtension { - @Override - public void initializeTargetConfig(LFGeneratorContext context, int numOfFederates, FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter, RtiConfig rtiConfig) throws IOException { - - } + @Override + public void initializeTargetConfig( + LFGeneratorContext context, + int numOfFederates, + FederateInstance federate, + FedFileConfig fileConfig, + ErrorReporter errorReporter, + RtiConfig rtiConfig) + throws IOException {} - @Override - public String generateNetworkReceiverBody(Action action, VarRef sendingPort, VarRef receivingPort, FedConnectionInstance connection, InferredType type, CoordinationType coordinationType, ErrorReporter errorReporter) { - return """ + @Override + public String generateNetworkReceiverBody( + Action action, + VarRef sendingPort, + VarRef receivingPort, + FedConnectionInstance connection, + InferredType type, + CoordinationType coordinationType, + ErrorReporter errorReporter) { + return """ // generateNetworkReceiverBody if (%1$s !== undefined) { %2$s.%3$s = %1$s; } - """.formatted( + """ + .formatted( action.getName(), receivingPort.getContainer().getName(), - receivingPort.getVariable().getName() - ); - } + receivingPort.getVariable().getName()); + } - @Override - public String generateNetworkSenderBody(VarRef sendingPort, VarRef receivingPort, FedConnectionInstance connection, InferredType type, CoordinationType coordinationType, ErrorReporter errorReporter) { - return""" + @Override + public String generateNetworkSenderBody( + VarRef sendingPort, + VarRef receivingPort, + FedConnectionInstance connection, + InferredType type, + CoordinationType coordinationType, + ErrorReporter errorReporter) { + return """ if (%1$s.%2$s !== undefined) { this.util.sendRTITimedMessage(%1$s.%2$s, %3$s, %4$s, %5$s); } - """.formatted( + """ + .formatted( sendingPort.getContainer().getName(), sendingPort.getVariable().getName(), connection.getDstFederate().id, connection.getDstFederate().networkMessageActions.size(), - getNetworkDelayLiteral(connection.getDefinition().getDelay()) - ); - } + getNetworkDelayLiteral(connection.getDefinition().getDelay())); + } - private String getNetworkDelayLiteral(Expression e) { - var cLiteral = CExtensionUtils.getNetworkDelayLiteral(e); - return cLiteral.equals("NEVER") ? "0" : cLiteral; - } + private String getNetworkDelayLiteral(Expression e) { + var cLiteral = CExtensionUtils.getNetworkDelayLiteral(e); + return cLiteral.equals("NEVER") ? "0" : cLiteral; + } - @Override - public String generateNetworkInputControlReactionBody(int receivingPortID, TimeValue maxSTP, CoordinationType coordination) { - return "// TODO(hokeun): Figure out what to do for generateNetworkInputControlReactionBody"; - } + @Override + public String generateNetworkInputControlReactionBody( + int receivingPortID, TimeValue maxSTP, CoordinationType coordination) { + return "// TODO(hokeun): Figure out what to do for generateNetworkInputControlReactionBody"; + } - @Override - public String generateNetworkOutputControlReactionBody(VarRef srcOutputPort, FedConnectionInstance connection) { - return "// TODO(hokeun): Figure out what to do for generateNetworkOutputControlReactionBody"; - } + @Override + public String generateNetworkOutputControlReactionBody( + VarRef srcOutputPort, FedConnectionInstance connection) { + return "// TODO(hokeun): Figure out what to do for generateNetworkOutputControlReactionBody"; + } - @Override - public String getNetworkBufferType() { - return ""; - } + @Override + public String getNetworkBufferType() { + return ""; + } - /** - * Add necessary preamble to the source to set up federated execution. - * - * @return - */ - @Override - public String generatePreamble(FederateInstance federate, FedFileConfig fileConfig, - RtiConfig rtiConfig, - ErrorReporter errorReporter) { - var minOutputDelay = getMinOutputDelay(federate, fileConfig, errorReporter); - var upstreamConnectionDelays = getUpstreamConnectionDelays(federate); - return - """ + /** + * Add necessary preamble to the source to set up federated execution. + * + * @return + */ + @Override + public String generatePreamble( + FederateInstance federate, + FedFileConfig fileConfig, + RtiConfig rtiConfig, + ErrorReporter errorReporter) { + var minOutputDelay = getMinOutputDelay(federate, fileConfig, errorReporter); + var upstreamConnectionDelays = getUpstreamConnectionDelays(federate); + return """ const defaultFederateConfig: __FederateConfig = { dependsOn: [%s], executionTimeout: undefined, @@ -108,94 +128,101 @@ public String generatePreamble(FederateInstance federate, FedFileConfig fileConf sendsTo: [%s], upstreamConnectionDelays: [%s] } - """.formatted( + """ + .formatted( federate.dependsOn.keySet().stream() - .map(e->String.valueOf(e.id)) - .collect(Collectors.joining(",")), + .map(e -> String.valueOf(e.id)) + .collect(Collectors.joining(",")), federate.id, - minOutputDelay == null ? "undefined" - : "%s".formatted(TSTypes.getInstance().getTargetTimeExpr(minOutputDelay)), - federate.networkMessageActions - .stream() + minOutputDelay == null + ? "undefined" + : "%s".formatted(TSTypes.getInstance().getTargetTimeExpr(minOutputDelay)), + federate.networkMessageActions.stream() .map(Variable::getName) .collect(Collectors.joining(",", "\"", "\"")), rtiConfig.getHost(), rtiConfig.getPort(), federate.sendsTo.keySet().stream() - .map(e->String.valueOf(e.id)) - .collect(Collectors.joining(",")), - upstreamConnectionDelays.stream().collect(Collectors.joining(",")) - ); - } + .map(e -> String.valueOf(e.id)) + .collect(Collectors.joining(",")), + upstreamConnectionDelays.stream().collect(Collectors.joining(","))); + } - private TimeValue getMinOutputDelay(FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter) { - if (federate.targetConfig.coordination.equals(CoordinationType.CENTRALIZED)) { - // If this program uses centralized coordination then check - // for outputs that depend on physical actions so that null messages can be - // sent to the RTI. - var federateClass = ASTUtils.toDefinition(federate.instantiation.getReactorClass()); - var main = new ReactorInstance(FedASTUtils.findFederatedReactor(federate.instantiation.eResource()), errorReporter, 1); - var instance = new ReactorInstance(federateClass, main, errorReporter); - var outputDelayMap = federate - .findOutputsConnectedToPhysicalActions(instance); - var minOutputDelay = TimeValue.MAX_VALUE; - Output outputFound = null; - for (Output output : outputDelayMap.keySet()) { - var outputDelay = outputDelayMap.get(output); - if (outputDelay.isEarlierThan(minOutputDelay)) { - minOutputDelay = outputDelay; - outputFound = output; - } - } - if (minOutputDelay != TimeValue.MAX_VALUE) { - // Unless silenced, issue a warning. - if (federate.targetConfig.coordinationOptions.advance_message_interval - == null) { - errorReporter.reportWarning(outputFound, String.join("\n", - "Found a path from a physical action to output for reactor " - + addDoubleQuotes(instance.getName()) - + ". ", - "The amount of delay is " - + minOutputDelay - + ".", - "With centralized coordination, this can result in a large number of messages to the RTI.", - "Consider refactoring the code so that the output does not depend on the physical action,", - "or consider using decentralized coordination. To silence this warning, set the target", - "parameter coordination-options with a value like {advance-message-interval: 10 msec}" - )); - } - return minOutputDelay; - } + private TimeValue getMinOutputDelay( + FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter) { + if (federate.targetConfig.coordination.equals(CoordinationType.CENTRALIZED)) { + // If this program uses centralized coordination then check + // for outputs that depend on physical actions so that null messages can be + // sent to the RTI. + var federateClass = ASTUtils.toDefinition(federate.instantiation.getReactorClass()); + var main = + new ReactorInstance( + FedASTUtils.findFederatedReactor(federate.instantiation.eResource()), + errorReporter, + 1); + var instance = new ReactorInstance(federateClass, main, errorReporter); + var outputDelayMap = federate.findOutputsConnectedToPhysicalActions(instance); + var minOutputDelay = TimeValue.MAX_VALUE; + Output outputFound = null; + for (Output output : outputDelayMap.keySet()) { + var outputDelay = outputDelayMap.get(output); + if (outputDelay.isEarlierThan(minOutputDelay)) { + minOutputDelay = outputDelay; + outputFound = output; } - return null; + } + if (minOutputDelay != TimeValue.MAX_VALUE) { + // Unless silenced, issue a warning. + if (federate.targetConfig.coordinationOptions.advance_message_interval == null) { + errorReporter.reportWarning( + outputFound, + String.join( + "\n", + "Found a path from a physical action to output for reactor " + + addDoubleQuotes(instance.getName()) + + ". ", + "The amount of delay is " + minOutputDelay + ".", + "With centralized coordination, this can result in a large number of messages to" + + " the RTI.", + "Consider refactoring the code so that the output does not depend on the physical" + + " action,", + "or consider using decentralized coordination. To silence this warning, set the" + + " target", + "parameter coordination-options with a value like {advance-message-interval: 10" + + " msec}")); + } + return minOutputDelay; + } } + 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); + 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()"; } - return candidates; + 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 74390c936b..b191a07427 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java +++ b/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java @@ -27,6 +27,7 @@ package org.lflang.federated.generator; +import com.google.common.collect.Iterators; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -36,21 +37,18 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; - 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.IteratorExtensions; - -import org.lflang.ast.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.InferredType; import org.lflang.ModelInfo; import org.lflang.TargetProperty.CoordinationType; import org.lflang.TimeValue; +import org.lflang.ast.ASTUtils; import org.lflang.federated.extensions.FedTargetExtensionFactory; import org.lflang.federated.serialization.SupportedSerializers; import org.lflang.generator.PortInstance; @@ -73,926 +71,881 @@ import org.lflang.lf.Variable; import org.lflang.util.Pair; -import com.google.common.collect.Iterators; - - /** - * A helper class for AST transformations needed for federated - * execution. + * A helper class for AST transformations needed for federated execution. * * @author Soroush Bateni * @author Edward A. Lee */ public class FedASTUtils { - /** - * Map from reactions to bank indices - */ - private static Map reactionBankIndices = null; - - /** - * 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 static void setReactionBankIndex(Reaction reaction, int bankIndex) { - if (bankIndex < 0) { - return; - } - if (reactionBankIndices == null) { - reactionBankIndices = new LinkedHashMap<>(); - } - reactionBankIndices.put(reaction, bankIndex); + /** Map from reactions to bank indices */ + private static Map reactionBankIndices = null; + + /** + * 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 static void setReactionBankIndex(Reaction reaction, int bankIndex) { + if (bankIndex < 0) { + return; } - - /** - * 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 static int getReactionBankIndex(Reaction reaction) { - if (reactionBankIndices == null) return -1; - if (reactionBankIndices.get(reaction) == null) return -1; - return reactionBankIndices.get(reaction); + if (reactionBankIndices == null) { + reactionBankIndices = new LinkedHashMap<>(); } - - /** - * Find the federated reactor in a .lf file. - * - * @param resource Resource representing a .lf file. - * @return The federated reactor if found. - */ - public static Reactor findFederatedReactor(Resource resource) { - return IteratorExtensions.findFirst( - Iterators.filter(resource.getAllContents(), Reactor.class), - Reactor::isFederated - ); + 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 static int getReactionBankIndex(Reaction reaction) { + if (reactionBankIndices == null) return -1; + if (reactionBankIndices.get(reaction) == null) return -1; + return reactionBankIndices.get(reaction); + } + + /** + * Find the federated reactor in a .lf file. + * + * @param resource Resource representing a .lf file. + * @return The federated reactor if found. + */ + public static Reactor findFederatedReactor(Resource resource) { + return IteratorExtensions.findFirst( + Iterators.filter(resource.getAllContents(), Reactor.class), Reactor::isFederated); + } + + /** + * Replace the specified connection with communication between federates. + * + * @param connection Network connection between two federates. + * @param resource + * @param mainDef + * @param coordination One of CoordinationType.DECENTRALIZED or CoordinationType.CENTRALIZED. + * @param errorReporter Used to report errors encountered. + */ + public static void makeCommunication( + FedConnectionInstance connection, + Resource resource, + CoordinationType coordination, + ErrorReporter errorReporter) { + + // Add the sender reactor. + addNetworkSenderReactor(connection, coordination, resource, errorReporter); + + // Next, generate control reactions + // if ( + // !connection.getDefinition().isPhysical() && + // // Connections that are physical don't need control reactions + // connection.getDefinition().getDelay() + // == null // Connections that have delays don't need control reactions + // ) { + // Add the network output control reaction to the parent + FedASTUtils.addNetworkOutputControlReaction(connection); + + // Add the network input control reaction to the parent + // FedASTUtils.addNetworkInputControlReaction(connection, coordination, errorReporter); + // } + + // Add the network receiver reactor in the destinationFederate + addNetworkReceiverReactor(connection, coordination, resource, errorReporter); + } + + public static int networkMessageActionID = 0; + + /** + * Create a "network action" in the reactor that contains the given connection and return it. + * + *

The purpose of this action is to serve as a trigger for a "network input reaction" that is + * responsible for relaying messages to the port that is on the receiving side of the given + * connection. The connection is assumed to be between two reactors that reside in distinct + * federates. Hence, the container of the connection is assumed to be top-level. + * + * @param connection A connection between two federates + * @return The newly created action. + */ + private static Action createNetworkAction(FedConnectionInstance connection) { + // Reactor top = (Reactor) connection.getDefinition().eContainer(); + LfFactory factory = LfFactory.eINSTANCE; + + Action action = factory.createAction(); + // Name the newly created action; set its delay and type. + action.setName("networkMessage_" + networkMessageActionID++); + if (connection.serializer == SupportedSerializers.NATIVE) { + action.setType(EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getType())); + } else { + Type action_type = factory.createType(); + action_type.setId( + FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) + .getNetworkBufferType()); + action.setType(action_type); } - /** - * Replace the specified connection with communication between federates. - * - * @param connection Network connection between two federates. - * @param resource - * @param mainDef - * @param coordination One of CoordinationType.DECENTRALIZED or - * CoordinationType.CENTRALIZED. - * @param errorReporter Used to report errors encountered. - */ - public static void makeCommunication( - FedConnectionInstance connection, - Resource resource, CoordinationType coordination, - ErrorReporter errorReporter - ) { - - // Add the sender reactor. - addNetworkSenderReactor( - connection, - coordination, - resource, - errorReporter - ); - - // Next, generate control reactions - // if ( - // !connection.getDefinition().isPhysical() && - // // Connections that are physical don't need control reactions - // connection.getDefinition().getDelay() - // == null // Connections that have delays don't need control reactions - // ) { - // Add the network output control reaction to the parent - FedASTUtils.addNetworkOutputControlReaction(connection); - - // Add the network input control reaction to the parent - //FedASTUtils.addNetworkInputControlReaction(connection, coordination, errorReporter); - //} - - // Add the network receiver reactor in the destinationFederate - addNetworkReceiverReactor( - connection, - coordination, - resource, - errorReporter - ); - + // The connection is 'physical' if it uses the ~> notation. + if (connection.getDefinition().isPhysical()) { + action.setOrigin(ActionOrigin.PHYSICAL); + // Messages sent on physical connections do not + // carry a timestamp, or a delay. The delay + // provided using after is enforced by setting + // the minDelay. + if (connection.getDefinition().getDelay() != null) { + action.setMinDelay(connection.getDefinition().getDelay()); + } + } else { + action.setOrigin(ActionOrigin.LOGICAL); } - public static int networkMessageActionID = 0; - - /** - * Create a "network action" in the reactor that contains the given - * connection and return it. - * - * The purpose of this action is to serve as a trigger for a "network - * input reaction" that is responsible for relaying messages to the - * port that is on the receiving side of the given connection. The - * connection is assumed to be between two reactors that reside in - * distinct federates. Hence, the container of the connection is - * assumed to be top-level. - * - * @param connection A connection between two federates - * @return The newly created action. - */ - private static Action createNetworkAction(FedConnectionInstance connection) { - //Reactor top = (Reactor) connection.getDefinition().eContainer(); - LfFactory factory = LfFactory.eINSTANCE; - - Action action = factory.createAction(); - // Name the newly created action; set its delay and type. - action.setName("networkMessage_" + networkMessageActionID++); - if (connection.serializer == SupportedSerializers.NATIVE) { - action.setType(EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getType())); - } else { - Type action_type = factory.createType(); - action_type.setId( - FedTargetExtensionFactory.getExtension( - connection.srcFederate.targetConfig.target - ).getNetworkBufferType() - ); - action.setType(action_type); - } - - // The connection is 'physical' if it uses the ~> notation. - if (connection.getDefinition().isPhysical()) { - action.setOrigin(ActionOrigin.PHYSICAL); - // Messages sent on physical connections do not - // carry a timestamp, or a delay. The delay - // provided using after is enforced by setting - // the minDelay. - if (connection.getDefinition().getDelay() != null) { - action.setMinDelay(connection.getDefinition().getDelay()); - } - } else { - action.setOrigin(ActionOrigin.LOGICAL); + return action; + } + + /** + * Add a network receiver reactor for a given input port 'destination' to destination's parent + * reactor. This reaction will react to a generated 'networkAction' (triggered asynchronously, + * e.g., by federate.c). This 'networkAction' will contain the actual message that is sent by the + * sender in 'action->value'. This value is forwarded to 'destination' in the network receiver + * reaction. + * + * @param connection FIXME + * @param coordination One of CoordinationType.DECENTRALIZED or CoordinationType.CENTRALIZED. + * @param resource + * @note: Used in federated execution + */ + private static void addNetworkReceiverReactor( + FedConnectionInstance connection, + CoordinationType coordination, + Resource resource, + ErrorReporter errorReporter) { + LfFactory factory = LfFactory.eINSTANCE; + Type type = EcoreUtil.copy(connection.getDestinationPortInstance().getDefinition().getType()); + + VarRef sourceRef = factory.createVarRef(); // source fed + VarRef instRef = factory.createVarRef(); // instantiation connection + VarRef destRef = factory.createVarRef(); // destination connection + + Reactor receiver = factory.createReactor(); + Reaction networkReceiverReaction = factory.createReaction(); + + Output out = factory.createOutput(); + VarRef outRef = factory.createVarRef(); // out port + Connection receiverFromReaction = factory.createConnection(); + Instantiation networkInstance = factory.createInstantiation(); + + Reactor top = + connection + .getSourcePortInstance() + .getParent() + .getParent() + .reactorDefinition; // Top-level reactor. + + receiver.getReactions().add(networkReceiverReaction); + receiver.getOutputs().add(out); + EObject node = IteratorExtensions.findFirst(resource.getAllContents(), Model.class::isInstance); + ((Model) node).getReactors().add(receiver); + receiver.setName("NetworkReceiver_" + networkIDReceiver++); + // networkReceiverReaction.setName("NetworkReceiverReaction_" + networkIDReceiver++); + + networkInstance.setReactorClass(receiver); + networkInstance.setName( + ASTUtils.getUniqueIdentifier(top, "nr_" + connection.getDstFederate().name)); + top.getInstantiations().add(networkInstance); + + receiverFromReaction.getLeftPorts().add(instRef); + receiverFromReaction.getRightPorts().add(destRef); + + // Create the network action (@see createNetworkAction) + Action networkAction = createNetworkAction(connection); + + // Keep track of this action in the destination federate. + connection.dstFederate.networkMessageActions.add(networkAction); + + TimeValue maxSTP = findMaxSTP(connection, coordination); + + if (!connection.dstFederate.currentSTPOffsets.contains(maxSTP.time)) { + connection.dstFederate.currentSTPOffsets.add(maxSTP.time); + connection.dstFederate.stpOffsets.add(maxSTP); + connection.dstFederate.stpToNetworkActionMap.put(maxSTP, new ArrayList<>()); + } else { + // TODO: Find more efficient way to reuse timevalues + for (var offset : connection.dstFederate.stpOffsets) { + if (maxSTP.time == offset.time) { + maxSTP = offset; + break; } - - return action; + } } - /** - * Add a network receiver reactor for a given input port 'destination' to - * destination's parent reactor. This reaction will react to a generated - * 'networkAction' (triggered asynchronously, e.g., by federate.c). This - * 'networkAction' will contain the actual message that is sent by the - * sender - * in 'action->value'. This value is forwarded to 'destination' in the - * network - * receiver reaction. - * - * @param connection FIXME - * @param coordination One of CoordinationType.DECENTRALIZED or - * CoordinationType.CENTRALIZED. - * @param resource - * @note: Used in federated execution - */ - private static void addNetworkReceiverReactor( - FedConnectionInstance connection, - - CoordinationType coordination, - Resource resource, ErrorReporter errorReporter - ) { - LfFactory factory = LfFactory.eINSTANCE; - Type type = EcoreUtil.copy(connection.getDestinationPortInstance().getDefinition().getType()); - - VarRef sourceRef = factory.createVarRef(); //source fed - VarRef instRef = factory.createVarRef(); //instantiation connection - VarRef destRef = factory.createVarRef(); //destination connection - - Reactor receiver = factory.createReactor(); - Reaction networkReceiverReaction = factory.createReaction(); - - - Output out = factory.createOutput(); - VarRef outRef = factory.createVarRef(); //out port - Connection receiverFromReaction = factory.createConnection(); - Instantiation networkInstance = factory.createInstantiation(); - - Reactor top = connection.getSourcePortInstance() - .getParent() - .getParent().reactorDefinition; // Top-level reactor. - - receiver.getReactions().add(networkReceiverReaction); - receiver.getOutputs().add(out); - EObject node = IteratorExtensions.findFirst(resource.getAllContents(), Model.class::isInstance); - ((Model) node).getReactors().add(receiver); - receiver.setName("NetworkReceiver_" + networkIDReceiver++); - //networkReceiverReaction.setName("NetworkReceiverReaction_" + networkIDReceiver++); - - - networkInstance.setReactorClass(receiver); - networkInstance.setName(ASTUtils.getUniqueIdentifier(top, "nr_" + connection.getDstFederate().name)); - top.getInstantiations().add(networkInstance); - - receiverFromReaction.getLeftPorts().add(instRef); - receiverFromReaction.getRightPorts().add(destRef); - - // Create the network action (@see createNetworkAction) - Action networkAction = createNetworkAction(connection); - - // Keep track of this action in the destination federate. - connection.dstFederate.networkMessageActions.add(networkAction); - - - TimeValue maxSTP = findMaxSTP(connection, coordination); - - if(!connection.dstFederate.currentSTPOffsets.contains(maxSTP.time)) { - connection.dstFederate.currentSTPOffsets.add(maxSTP.time); - connection.dstFederate.stpOffsets.add(maxSTP); - connection.dstFederate.stpToNetworkActionMap.put(maxSTP, new ArrayList<>()); - } else { - // TODO: Find more efficient way to reuse timevalues - for(var offset: connection.dstFederate.stpOffsets){ - if(maxSTP.time == offset.time) { - maxSTP = offset; - break; - } - } - } - - connection.dstFederate.stpToNetworkActionMap.get(maxSTP).add(networkAction); - - // Add the action definition to the parent reactor. - receiver.getActions().add(networkAction); - - // If the sender or receiver is in a bank of reactors, then we want - // these reactions to appear only in the federate whose bank ID matches. - setReactionBankIndex(networkReceiverReaction, connection.getDstBank()); - - // FIXME: do not create a new extension every time it is used - FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) - .annotateReaction(networkReceiverReaction); - - // The connection is 'physical' if it uses the ~> notation. - if (connection.getDefinition().isPhysical()) { - connection.dstFederate.inboundP2PConnections.add(connection.srcFederate); - } else { - // If the connection is logical but coordination - // is decentralized, we would need - // to make P2P connections - if (coordination == CoordinationType.DECENTRALIZED) { - connection.dstFederate.inboundP2PConnections.add(connection.srcFederate); - } - } + connection.dstFederate.stpToNetworkActionMap.get(maxSTP).add(networkAction); + + // Add the action definition to the parent reactor. + receiver.getActions().add(networkAction); + + // If the sender or receiver is in a bank of reactors, then we want + // these reactions to appear only in the federate whose bank ID matches. + setReactionBankIndex(networkReceiverReaction, connection.getDstBank()); + + // FIXME: do not create a new extension every time it is used + FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) + .annotateReaction(networkReceiverReaction); + + // The connection is 'physical' if it uses the ~> notation. + if (connection.getDefinition().isPhysical()) { + connection.dstFederate.inboundP2PConnections.add(connection.srcFederate); + } else { + // If the connection is logical but coordination + // is decentralized, we would need + // to make P2P connections + if (coordination == CoordinationType.DECENTRALIZED) { + connection.dstFederate.inboundP2PConnections.add(connection.srcFederate); + } + } - // Establish references to the involved ports. - sourceRef.setContainer(connection.getSourcePortInstance().getParent().getDefinition()); - sourceRef.setVariable(connection.getSourcePortInstance().getDefinition()); - destRef.setContainer(connection.getDestinationPortInstance().getParent().getDefinition()); - destRef.setVariable(connection.getDestinationPortInstance().getDefinition()); - instRef.setContainer(networkInstance); - instRef.setVariable(out); - - out.setName("msg"); - out.setType(type); - out.setWidthSpec(EcoreUtil.copy(connection.getDestinationPortInstance().getDefinition().getWidthSpec())); - outRef.setVariable(out); - - // Add the output port at the receiver reactor as an effect - //networkReceiverReaction.getEffects().add(outRef); - - VarRef triggerRef = factory.createVarRef(); - // Establish references to the action. - triggerRef.setVariable(networkAction); - // Add the action as a trigger to the receiver reaction - networkReceiverReaction.getTriggers().add(triggerRef); - networkReceiverReaction.getEffects().add(outRef); - - // Generate code for the network receiver reaction - networkReceiverReaction.setCode(factory.createCode()); - networkReceiverReaction.getCode().setBody( - FedTargetExtensionFactory.getExtension( - connection.dstFederate.targetConfig.target).generateNetworkReceiverBody( + // Establish references to the involved ports. + sourceRef.setContainer(connection.getSourcePortInstance().getParent().getDefinition()); + sourceRef.setVariable(connection.getSourcePortInstance().getDefinition()); + destRef.setContainer(connection.getDestinationPortInstance().getParent().getDefinition()); + destRef.setVariable(connection.getDestinationPortInstance().getDefinition()); + instRef.setContainer(networkInstance); + instRef.setVariable(out); + + out.setName("msg"); + out.setType(type); + out.setWidthSpec( + EcoreUtil.copy(connection.getDestinationPortInstance().getDefinition().getWidthSpec())); + outRef.setVariable(out); + + // Add the output port at the receiver reactor as an effect + // networkReceiverReaction.getEffects().add(outRef); + + VarRef triggerRef = factory.createVarRef(); + // Establish references to the action. + triggerRef.setVariable(networkAction); + // Add the action as a trigger to the receiver reaction + networkReceiverReaction.getTriggers().add(triggerRef); + networkReceiverReaction.getEffects().add(outRef); + + // Generate code for the network receiver reaction + networkReceiverReaction.setCode(factory.createCode()); + networkReceiverReaction + .getCode() + .setBody( + FedTargetExtensionFactory.getExtension(connection.dstFederate.targetConfig.target) + .generateNetworkReceiverBody( networkAction, sourceRef, outRef, connection, ASTUtils.getInferredType(networkAction), coordination, - errorReporter - )); - - // Add the receiver reaction to the parent - // parent.getReactions().add(networkReceiverReaction); - - // Add the network receiver reaction to the federate instance's list - // of network reactions - connection.dstFederate.networkReceiverReactions.add(networkReceiverReaction); - connection.dstFederate.networkReactors.add(receiver); - connection.dstFederate.networkConnections.add(receiverFromReaction); - connection.dstFederate.networkReceiverInstantiations.add(networkInstance); - connection.dstFederate.networkPortToInstantiation.put(connection.getDestinationPortInstance(), networkInstance); - connection.dstFederate.networkActionToInstantiation.put(networkAction, networkInstance); - //System.out.println(connection.getSourcePortInstance()); - - if ( - !connection.getDefinition().isPhysical() && - // Connections that are physical don't need control reactions - connection.getDefinition().getDelay() - == null // Connections that have delays don't need control reactions - ) { - // Add necessary dependency annotations to federate to ensure the level - // assigner has enough information to correctly assign levels without introducing deadlock - addRelativeDependencyAnnotation(connection, networkReceiverReaction, errorReporter); - } - } - - /** - * Add a network control reaction for a given input port 'destination' to - * destination's parent reactor. This reaction will block for - * any valid logical time until it is known whether the trigger for the - * action corresponding to the given port is present or absent. - * - * @param connection FIXME - * @param coordination FIXME - * @param errorReporter - * @note Used in federated execution - */ - private static void addNetworkInputControlReaction( - FedConnectionInstance connection, - CoordinationType coordination, - ErrorReporter errorReporter) { - - LfFactory factory = LfFactory.eINSTANCE; - Reaction reaction = factory.createReaction(); - VarRef destRef = factory.createVarRef(); - int receivingPortID = connection.dstFederate.networkMessageActions.size(); - - // If the sender or receiver is in a bank of reactors, then we want - // these reactions to appear only in the federate whose bank ID matches. - setReactionBankIndex(reaction, connection.getDstBank()); - - // FIXME: do not create a new extension every time it is used - FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) - .annotateReaction(reaction); - - // Create a new action that will be used to trigger the - // input control reactions. - Action newTriggerForControlReactionInput = factory.createAction(); - newTriggerForControlReactionInput.setOrigin(ActionOrigin.LOGICAL); - - // Set the container and variable according to the network port - destRef.setContainer(connection.getDestinationPortInstance().getParent().getDefinition()); - destRef.setVariable(connection.getDestinationPortInstance().getDefinition()); - - Reactor top = connection.getDestinationPortInstance() - .getParent() - .getParent().reactorDefinition; - - newTriggerForControlReactionInput.setName( - ASTUtils.getUniqueIdentifier(top, "inputControlReactionTrigger")); - - // Add the newly created Action to the action list of the federated reactor. - top.getActions().add(newTriggerForControlReactionInput); - - // Create the trigger for the reaction - VarRef newTriggerForControlReaction = factory.createVarRef(); - newTriggerForControlReaction.setVariable(newTriggerForControlReactionInput); - - // Add the appropriate triggers to the list of triggers of the reaction - reaction.getTriggers().add(newTriggerForControlReaction); - - // Add the destination port as an effect of the reaction - reaction.getEffects().add(destRef); - - // Generate code for the network input control reaction - reaction.setCode(factory.createCode()); - - TimeValue maxSTP = findMaxSTP(connection, coordination); - - reaction.getCode() - .setBody( - FedTargetExtensionFactory - .getExtension(connection.dstFederate.targetConfig.target) - .generateNetworkInputControlReactionBody( - receivingPortID, - maxSTP, - coordination - ) - ); - - // Insert the reaction - top.getReactions().add(reaction); - - // Add the trigger for this reaction to the list of triggers, used to actually - // trigger the reaction at the beginning of each logical time. - connection.dstFederate.networkInputControlReactionsTriggers.add(newTriggerForControlReactionInput); - - // Add the network input control reaction to the federate instance's list - // of network reactions - //connection.dstFederate.networkReactions.add(reaction); - - // Add necessary dependencies to reaction to ensure that it executes correctly - // relative to other network input control reactions in the federate. - // addRelativeDependency(connection, reaction, errorReporter); - } - - /** - * Add necessary dependency information to the signature of {@code networkInputReaction} so that - * it can execute in the correct order relative to other network reactions in the federate. - * - * In particular, we want to avoid a deadlock if multiple network input control reactions in federate - * are in a zero-delay cycle through other federates in the federation. To avoid the deadlock, we encode the - * zero-delay cycle inside the federate by adding an artificial dependency from the output port of this federate - * that is involved in the cycle to the signature of {@code networkInputReaction} as a source. - */ - private static void addRelativeDependencyAnnotation( - FedConnectionInstance connection, - Reaction networkInputReaction, - ErrorReporter errorReporter) { - var upstreamOutputPortsInFederate = - findUpstreamPortsInFederate( - connection.dstFederate, - connection.getSourcePortInstance(), - new HashSet<>(), - new HashSet<>() - ); - - ModelInfo info = new ModelInfo(); - for (var port: upstreamOutputPortsInFederate) { - //VarRef sourceRef = ASTUtils.factory.createVarRef(); - connection.dstFederate.networkReactionDependencyPairs.add( - new Pair(connection.getDestinationPortInstance(), port) - ); - - //sourceRef.setContainer(port.getParent().getDefinition()); - //sourceRef.setVariable(port.getDefinition()); - // networkInputReaction.getSources().add(sourceRef); - - // // Remove the port if it introduces cycles - // info.update( - // (Model)networkInputReaction.eContainer().eContainer(), - // errorReporter - // ); - // if (!info.topologyCycles().isEmpty()) { - // networkInputReaction.getSources().remove(sourceRef); - // } - } - //System.out.println(connection.dstFederate.networkReactionDependencyPairs); - - } - - /** - * Go upstream from input port {@code port} until we reach one or more output - * ports that belong to the same federate. - * - * Along the path, we follow direct connections, as well as reactions, as long - * as there is no logical delay. When following reactions, we also follow - * dependant reactions (because we are traversing a potential cycle backwards). - * - * @return A set of {@link PortInstance}. If no port exist that match the - * criteria, return an empty set. - */ - private static Set findUpstreamPortsInFederate( - FederateInstance federate, - PortInstance port, - Set visitedPorts, - Set reactionVisited + errorReporter)); + + // Add the receiver reaction to the parent + // parent.getReactions().add(networkReceiverReaction); + + // Add the network receiver reaction to the federate instance's list + // of network reactions + connection.dstFederate.networkReceiverReactions.add(networkReceiverReaction); + connection.dstFederate.networkReactors.add(receiver); + connection.dstFederate.networkConnections.add(receiverFromReaction); + connection.dstFederate.networkReceiverInstantiations.add(networkInstance); + connection.dstFederate.networkPortToInstantiation.put( + connection.getDestinationPortInstance(), networkInstance); + connection.dstFederate.networkActionToInstantiation.put(networkAction, networkInstance); + // System.out.println(connection.getSourcePortInstance()); + + if (!connection.getDefinition().isPhysical() + && + // Connections that are physical don't need control reactions + connection.getDefinition().getDelay() + == null // Connections that have delays don't need control reactions ) { - Set toReturn = new HashSet<>(); - if (port == null) return toReturn; - else if (federate.contains(port.getParent())) { - // Reached the requested federate - toReturn.add(port); - visitedPorts.add(port); - } else if (visitedPorts.contains(port)) { - return toReturn; - } else { - visitedPorts.add(port); - // Follow depends on reactions - port.getDependsOnReactions().forEach( - reaction -> { - followReactionUpstream(federate, visitedPorts, toReturn, reaction, reactionVisited); - } - ); - // Follow depends on ports - port.getDependsOnPorts().forEach( - it -> toReturn.addAll( - findUpstreamPortsInFederate(federate, it.instance, visitedPorts, reactionVisited) - ) - ); - } - return toReturn; + // Add necessary dependency annotations to federate to ensure the level + // assigner has enough information to correctly assign levels without introducing deadlock + addRelativeDependencyAnnotation(connection, networkReceiverReaction, errorReporter); } - - /** - * Follow reactions upstream. This is part of the algorithm of - * {@link #findUpstreamPortsInFederate(FederateInstance, PortInstance, Set)}. - */ - private static void followReactionUpstream( - FederateInstance federate, - Set visitedPorts, - Set toReturn, - ReactionInstance reaction, - Set reactionVisited - ) { - if (reactionVisited.contains(reaction)) return; - reactionVisited.add(reaction); - // Add triggers - Set varRefsToFollow = new HashSet<>(); - varRefsToFollow.addAll( - reaction.getDefinition() - .getTriggers() - .stream() - .filter( - trigger -> !(trigger instanceof BuiltinTriggerRef) - ) - .map(VarRef.class::cast).toList()); - // Add sources - varRefsToFollow.addAll(reaction.getDefinition().getSources()); - - // Follow everything upstream - varRefsToFollow.forEach( - varRef -> - toReturn.addAll( - findUpstreamPortsInFederate( - federate, - reaction.getParent() - .lookupPortInstance(varRef), - visitedPorts, - reactionVisited - ) - ) - ); - - reaction.dependsOnReactions() - .stream() - .filter( - // Stay within the reactor - it -> it.getParent().equals(reaction.getParent()) - ) - .forEach( - it -> followReactionUpstream(federate, visitedPorts, toReturn, it, reactionVisited) - ); - - // FIXME: This is most certainly wrong. Please fix it. - reaction.dependentReactions() - .stream() - .filter( - // Stay within the reactor - it -> it.getParent().equals(reaction.getParent()) - ) - .forEach( - it -> followReactionUpstream(federate, visitedPorts, toReturn, it, reactionVisited) - ); + } + + /** + * Add a network control reaction for a given input port 'destination' to destination's parent + * reactor. This reaction will block for any valid logical time until it is known whether the + * trigger for the action corresponding to the given port is present or absent. + * + * @param connection FIXME + * @param coordination FIXME + * @param errorReporter + * @note Used in federated execution + */ + private static void addNetworkInputControlReaction( + FedConnectionInstance connection, + CoordinationType coordination, + ErrorReporter errorReporter) { + + LfFactory factory = LfFactory.eINSTANCE; + Reaction reaction = factory.createReaction(); + VarRef destRef = factory.createVarRef(); + int receivingPortID = connection.dstFederate.networkMessageActions.size(); + + // If the sender or receiver is in a bank of reactors, then we want + // these reactions to appear only in the federate whose bank ID matches. + setReactionBankIndex(reaction, connection.getDstBank()); + + // FIXME: do not create a new extension every time it is used + FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) + .annotateReaction(reaction); + + // Create a new action that will be used to trigger the + // input control reactions. + Action newTriggerForControlReactionInput = factory.createAction(); + newTriggerForControlReactionInput.setOrigin(ActionOrigin.LOGICAL); + + // Set the container and variable according to the network port + destRef.setContainer(connection.getDestinationPortInstance().getParent().getDefinition()); + destRef.setVariable(connection.getDestinationPortInstance().getDefinition()); + + Reactor top = connection.getDestinationPortInstance().getParent().getParent().reactorDefinition; + + newTriggerForControlReactionInput.setName( + ASTUtils.getUniqueIdentifier(top, "inputControlReactionTrigger")); + + // Add the newly created Action to the action list of the federated reactor. + top.getActions().add(newTriggerForControlReactionInput); + + // Create the trigger for the reaction + VarRef newTriggerForControlReaction = factory.createVarRef(); + newTriggerForControlReaction.setVariable(newTriggerForControlReactionInput); + + // Add the appropriate triggers to the list of triggers of the reaction + reaction.getTriggers().add(newTriggerForControlReaction); + + // Add the destination port as an effect of the reaction + reaction.getEffects().add(destRef); + + // Generate code for the network input control reaction + reaction.setCode(factory.createCode()); + + TimeValue maxSTP = findMaxSTP(connection, coordination); + + reaction + .getCode() + .setBody( + FedTargetExtensionFactory.getExtension(connection.dstFederate.targetConfig.target) + .generateNetworkInputControlReactionBody(receivingPortID, maxSTP, coordination)); + + // Insert the reaction + top.getReactions().add(reaction); + + // Add the trigger for this reaction to the list of triggers, used to actually + // trigger the reaction at the beginning of each logical time. + connection.dstFederate.networkInputControlReactionsTriggers.add( + newTriggerForControlReactionInput); + + // Add the network input control reaction to the federate instance's list + // of network reactions + // connection.dstFederate.networkReactions.add(reaction); + + // Add necessary dependencies to reaction to ensure that it executes correctly + // relative to other network input control reactions in the federate. + // addRelativeDependency(connection, reaction, errorReporter); + } + + /** + * Add necessary dependency information to the signature of {@code networkInputReaction} so that + * it can execute in the correct order relative to other network reactions in the federate. + * + *

In particular, we want to avoid a deadlock if multiple network input control reactions in + * federate are in a zero-delay cycle through other federates in the federation. To avoid the + * deadlock, we encode the zero-delay cycle inside the federate by adding an artificial dependency + * from the output port of this federate that is involved in the cycle to the signature of {@code + * networkInputReaction} as a source. + */ + private static void addRelativeDependencyAnnotation( + FedConnectionInstance connection, + Reaction networkInputReaction, + ErrorReporter errorReporter) { + var upstreamOutputPortsInFederate = + findUpstreamPortsInFederate( + connection.dstFederate, + connection.getSourcePortInstance(), + new HashSet<>(), + new HashSet<>()); + + ModelInfo info = new ModelInfo(); + for (var port : upstreamOutputPortsInFederate) { + // VarRef sourceRef = ASTUtils.factory.createVarRef(); + connection.dstFederate.networkReactionDependencyPairs.add( + new Pair(connection.getDestinationPortInstance(), port)); + + // sourceRef.setContainer(port.getParent().getDefinition()); + // sourceRef.setVariable(port.getDefinition()); + // networkInputReaction.getSources().add(sourceRef); + + // // Remove the port if it introduces cycles + // info.update( + // (Model)networkInputReaction.eContainer().eContainer(), + // errorReporter + // ); + // if (!info.topologyCycles().isEmpty()) { + // networkInputReaction.getSources().remove(sourceRef); + // } } - - /** - * Find the maximum STP offset for the given 'port'. - * - * An STP offset predicate can be nested in contained reactors in - * the federate. - * - * @param connection The connection to find the max STP offset for. - * @param coordination The coordination scheme. - * @return The maximum STP as a TimeValue - */ - private static TimeValue findMaxSTP(FedConnectionInstance connection, - CoordinationType coordination) { - Variable port = connection.getDestinationPortInstance().getDefinition(); - FederateInstance instance = connection.dstFederate; - Reactor reactor = connection.getDestinationPortInstance().getParent().reactorDefinition; - - // Find a list of STP offsets (if any exists) - List STPList = new LinkedList<>(); - - // First, check if there are any connections to contained reactors that - // need to be handled - List connectionsWithPort = ASTUtils - .allConnections(reactor).stream().filter(c -> c.getLeftPorts() - .stream() - .anyMatch((VarRef v) -> v - .getVariable().equals(port))) + // System.out.println(connection.dstFederate.networkReactionDependencyPairs); + + } + + /** + * Go upstream from input port {@code port} until we reach one or more output ports that belong to + * the same federate. + * + *

Along the path, we follow direct connections, as well as reactions, as long as there is no + * logical delay. When following reactions, we also follow dependant reactions (because we are + * traversing a potential cycle backwards). + * + * @return A set of {@link PortInstance}. If no port exist that match the criteria, return an + * empty set. + */ + private static Set findUpstreamPortsInFederate( + FederateInstance federate, + PortInstance port, + Set visitedPorts, + Set reactionVisited) { + Set toReturn = new HashSet<>(); + if (port == null) return toReturn; + else if (federate.contains(port.getParent())) { + // Reached the requested federate + toReturn.add(port); + visitedPorts.add(port); + } else if (visitedPorts.contains(port)) { + return toReturn; + } else { + visitedPorts.add(port); + // Follow depends on reactions + port.getDependsOnReactions() + .forEach( + reaction -> { + followReactionUpstream(federate, visitedPorts, toReturn, reaction, reactionVisited); + }); + // Follow depends on ports + port.getDependsOnPorts() + .forEach( + it -> + toReturn.addAll( + findUpstreamPortsInFederate( + federate, it.instance, visitedPorts, reactionVisited))); + } + return toReturn; + } + + /** + * Follow reactions upstream. This is part of the algorithm of {@link + * #findUpstreamPortsInFederate(FederateInstance, PortInstance, Set)}. + */ + private static void followReactionUpstream( + FederateInstance federate, + Set visitedPorts, + Set toReturn, + ReactionInstance reaction, + Set reactionVisited) { + if (reactionVisited.contains(reaction)) return; + reactionVisited.add(reaction); + // Add triggers + Set varRefsToFollow = new HashSet<>(); + varRefsToFollow.addAll( + reaction.getDefinition().getTriggers().stream() + .filter(trigger -> !(trigger instanceof BuiltinTriggerRef)) + .map(VarRef.class::cast) + .toList()); + // Add sources + varRefsToFollow.addAll(reaction.getDefinition().getSources()); + + // Follow everything upstream + varRefsToFollow.forEach( + varRef -> + toReturn.addAll( + findUpstreamPortsInFederate( + federate, + reaction.getParent().lookupPortInstance(varRef), + visitedPorts, + reactionVisited))); + + reaction.dependsOnReactions().stream() + .filter( + // Stay within the reactor + it -> it.getParent().equals(reaction.getParent())) + .forEach( + it -> followReactionUpstream(federate, visitedPorts, toReturn, it, reactionVisited)); + + // FIXME: This is most certainly wrong. Please fix it. + reaction.dependentReactions().stream() + .filter( + // Stay within the reactor + it -> it.getParent().equals(reaction.getParent())) + .forEach( + it -> followReactionUpstream(federate, visitedPorts, toReturn, it, reactionVisited)); + } + + /** + * Find the maximum STP offset for the given 'port'. + * + *

An STP offset predicate can be nested in contained reactors in the federate. + * + * @param connection The connection to find the max STP offset for. + * @param coordination The coordination scheme. + * @return The maximum STP as a TimeValue + */ + private static TimeValue findMaxSTP( + FedConnectionInstance connection, CoordinationType coordination) { + Variable port = connection.getDestinationPortInstance().getDefinition(); + FederateInstance instance = connection.dstFederate; + Reactor reactor = connection.getDestinationPortInstance().getParent().reactorDefinition; + + // Find a list of STP offsets (if any exists) + List STPList = new LinkedList<>(); + + // First, check if there are any connections to contained reactors that + // need to be handled + List connectionsWithPort = + ASTUtils.allConnections(reactor).stream() + .filter( + c -> c.getLeftPorts().stream().anyMatch((VarRef v) -> v.getVariable().equals(port))) .collect(Collectors.toList()); + // Find the list of reactions that have the port as trigger or source + // (could be a variable name) + List reactionsWithPort = + ASTUtils.allReactions(reactor).stream() + .filter( + r -> { + // Check the triggers of reaction r first + return r.getTriggers().stream() + .anyMatch( + t -> { + if (t instanceof VarRef) { + // Check if the variables match + return ((VarRef) t).getVariable() == port; + } else { + // Not a network port (startup or shutdown) + return false; + } + }) + || // Then check the sources of reaction r + r.getSources().stream().anyMatch(s -> s.getVariable() == port); + }) + .collect(Collectors.toList()); - // Find the list of reactions that have the port as trigger or source - // (could be a variable name) - List reactionsWithPort = ASTUtils - .allReactions(reactor).stream().filter(r -> { - // Check the triggers of reaction r first - return r.getTriggers().stream().anyMatch(t -> { - if (t instanceof VarRef) { - // Check if the variables match - return ((VarRef) t).getVariable() == port; - } else { - // Not a network port (startup or shutdown) - return false; - } - }) || // Then check the sources of reaction r - r.getSources().stream().anyMatch(s -> s.getVariable() - == port); - }).collect(Collectors.toList()); - - // Find a list of STP offsets (if any exists) - if (coordination == CoordinationType.DECENTRALIZED) { - for (Reaction r : safe(reactionsWithPort)) { - // If STP offset is determined, add it - // If not, assume it is zero - if (r.getStp() != null) { - if (r.getStp().getValue() instanceof ParameterReference) { - List instantList = new ArrayList<>(); - instantList.add(instance.instantiation); - final var param = ((ParameterReference) r.getStp().getValue()).getParameter(); - STPList.addAll(ASTUtils.initialValue(param, instantList)); - } else { - STPList.add(r.getStp().getValue()); - } - } - } - // Check the children for STPs as well - for (Connection c : safe(connectionsWithPort)) { - VarRef childPort = c.getRightPorts().get(0); - Reactor childReactor = (Reactor) childPort.getVariable() - .eContainer(); - // Find the list of reactions that have the port as trigger or - // source (could be a variable name) - List childReactionsWithPort = - ASTUtils.allReactions(childReactor) - .stream() - .filter(r -> - r.getTriggers().stream().anyMatch(t -> { - if (t instanceof VarRef) { - // Check if the variables match - return - ((VarRef) t).getVariable() - == childPort.getVariable(); - } else { - // Not a network port (startup or shutdown) - return false; - } - }) || - r.getSources() - .stream() - .anyMatch(s -> - s.getVariable() - == childPort.getVariable()) - ).collect(Collectors.toList()); - - for (Reaction r : safe(childReactionsWithPort)) { - // If STP offset is determined, add it - // If not, assume it is zero - if (r.getStp() != null) { - if (r.getStp().getValue() instanceof ParameterReference) { - List instantList = new ArrayList<>(); - instantList.add(childPort.getContainer()); - final var param = ((ParameterReference) r.getStp().getValue()).getParameter(); - STPList.addAll(ASTUtils.initialValue(param, instantList)); - } else { - STPList.add(r.getStp().getValue()); - } - } - } + // Find a list of STP offsets (if any exists) + if (coordination == CoordinationType.DECENTRALIZED) { + for (Reaction r : safe(reactionsWithPort)) { + // If STP offset is determined, add it + // If not, assume it is zero + if (r.getStp() != null) { + if (r.getStp().getValue() instanceof ParameterReference) { + List instantList = new ArrayList<>(); + instantList.add(instance.instantiation); + final var param = ((ParameterReference) r.getStp().getValue()).getParameter(); + STPList.addAll(ASTUtils.initialValue(param, instantList)); + } else { + STPList.add(r.getStp().getValue()); + } + } + } + // Check the children for STPs as well + for (Connection c : safe(connectionsWithPort)) { + VarRef childPort = c.getRightPorts().get(0); + Reactor childReactor = (Reactor) childPort.getVariable().eContainer(); + // Find the list of reactions that have the port as trigger or + // source (could be a variable name) + List childReactionsWithPort = + ASTUtils.allReactions(childReactor).stream() + .filter( + r -> + r.getTriggers().stream() + .anyMatch( + t -> { + if (t instanceof VarRef) { + // Check if the variables match + return ((VarRef) t).getVariable() + == childPort.getVariable(); + } else { + // Not a network port (startup or shutdown) + return false; + } + }) + || r.getSources().stream() + .anyMatch(s -> s.getVariable() == childPort.getVariable())) + .collect(Collectors.toList()); + + for (Reaction r : safe(childReactionsWithPort)) { + // If STP offset is determined, add it + // If not, assume it is zero + if (r.getStp() != null) { + if (r.getStp().getValue() instanceof ParameterReference) { + List instantList = new ArrayList<>(); + instantList.add(childPort.getContainer()); + final var param = ((ParameterReference) r.getStp().getValue()).getParameter(); + STPList.addAll(ASTUtils.initialValue(param, instantList)); + } else { + STPList.add(r.getStp().getValue()); } + } } - - return STPList.stream() - .map(ASTUtils::getLiteralTimeValue) - .filter(Objects::nonNull) - .reduce(TimeValue.ZERO, TimeValue::max); - } - - /** - * Return a null-safe List - * - * @param The type of the list - * @param list The potentially null List - * @return Empty list or the original list - */ - public static List safe(List list) { - return list == null ? Collections.emptyList() : list; + } } - public static int networkIDSender = 0; - public static int networkIDReceiver = 0; - - private static Map networkSenderReactors = new HashMap<>(); - private static Map networkSenderInstantiations = new HashMap<>(); - - - private static Reactor getNetworkSenderReactor(FedConnectionInstance connection, - CoordinationType coordination, Resource resource, ErrorReporter errorReporter) { - LfFactory factory = LfFactory.eINSTANCE; - Type type = EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getType()); - - //Initialize Reactor and Reaction AST Nodes - Reactor sender = factory.createReactor(); - Reaction networkSenderReaction = factory.createReaction(); - - VarRef inRef = factory.createVarRef(); //in port to network reaction - VarRef destRef = factory.createVarRef(); //destination fed - - Input in = factory.createInput(); - - sender.getReactions().add(networkSenderReaction); - sender.getInputs().add(in); - - EObject node = IteratorExtensions.findFirst(resource.getAllContents(), Model.class::isInstance); - ((Model) node).getReactors().add(sender); - sender.setName("NetworkSender_" + networkIDSender++); - //networkSenderReaction.setName("NetworkSenderReaction_" + networkIDSender++); - - // FIXME: do not create a new extension every time it is used - FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) - .annotateReaction(networkSenderReaction); - - // If the sender or receiver is in a bank of reactors, then we want - // these reactions to appear only in the federate whose bank ID matches. - setReactionBankIndex(networkSenderReaction, connection.getSrcBank()); - - - in.setName("msg"); - in.setType(type); - in.setWidthSpec(EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getWidthSpec())); - inRef.setVariable(in); - - destRef.setContainer(connection.getDestinationPortInstance().getParent().getDefinition()); - destRef.setVariable(connection.getDestinationPortInstance().getDefinition()); - - // Configure the sending reaction. - networkSenderReaction.getTriggers().add(inRef); - networkSenderReaction.setCode(factory.createCode()); - networkSenderReaction.getCode().setBody( + return STPList.stream() + .map(ASTUtils::getLiteralTimeValue) + .filter(Objects::nonNull) + .reduce(TimeValue.ZERO, TimeValue::max); + } + + /** + * Return a null-safe List + * + * @param The type of the list + * @param list The potentially null List + * @return Empty list or the original list + */ + public static List safe(List list) { + return list == null ? Collections.emptyList() : list; + } + + public static int networkIDSender = 0; + public static int networkIDReceiver = 0; + + private static Map networkSenderReactors = new HashMap<>(); + private static Map networkSenderInstantiations = new HashMap<>(); + + private static Reactor getNetworkSenderReactor( + FedConnectionInstance connection, + CoordinationType coordination, + Resource resource, + ErrorReporter errorReporter) { + LfFactory factory = LfFactory.eINSTANCE; + Type type = EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getType()); + + // Initialize Reactor and Reaction AST Nodes + Reactor sender = factory.createReactor(); + Reaction networkSenderReaction = factory.createReaction(); + + VarRef inRef = factory.createVarRef(); // in port to network reaction + VarRef destRef = factory.createVarRef(); // destination fed + + Input in = factory.createInput(); + + sender.getReactions().add(networkSenderReaction); + sender.getInputs().add(in); + + EObject node = IteratorExtensions.findFirst(resource.getAllContents(), Model.class::isInstance); + ((Model) node).getReactors().add(sender); + sender.setName("NetworkSender_" + networkIDSender++); + // networkSenderReaction.setName("NetworkSenderReaction_" + networkIDSender++); + + // FIXME: do not create a new extension every time it is used + FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) + .annotateReaction(networkSenderReaction); + + // If the sender or receiver is in a bank of reactors, then we want + // these reactions to appear only in the federate whose bank ID matches. + setReactionBankIndex(networkSenderReaction, connection.getSrcBank()); + + in.setName("msg"); + in.setType(type); + in.setWidthSpec( + EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getWidthSpec())); + inRef.setVariable(in); + + destRef.setContainer(connection.getDestinationPortInstance().getParent().getDefinition()); + destRef.setVariable(connection.getDestinationPortInstance().getDefinition()); + + // Configure the sending reaction. + networkSenderReaction.getTriggers().add(inRef); + networkSenderReaction.setCode(factory.createCode()); + networkSenderReaction + .getCode() + .setBody( FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) - .generateNetworkSenderBody( - inRef, - destRef, - connection, - InferredType.fromAST(type), - coordination, - errorReporter - )); - - // Add the network sender reaction to the federate instance's list - // of network reactions - connection.srcFederate.networkSenderReactions.add(networkSenderReaction); - connection.srcFederate.networkReactors.add(sender); - - networkSenderReactors.put(connection, sender); - return sender; - - } - - /** - * Add a network sender reactor for a given input port 'source' to - * source's parent reactor. This reaction will react to the 'source' - * and then send a message on the network destined for the - * destinationFederate. - * - * @param connection Network connection between two federates. - * @param coordination One of CoordinationType.DECENTRALIZED or - * CoordinationType.CENTRALIZED. - * @param resource - * @param mainDef - * @param errorReporter FIXME - * @note Used in federated execution - */ - private static void addNetworkSenderReactor( - FedConnectionInstance connection, - CoordinationType coordination, - Resource resource, ErrorReporter errorReporter - ) { - LfFactory factory = LfFactory.eINSTANCE; - // Assume all the types are the same, so just use the first on the right. - - Reactor sender = getNetworkSenderReactor(connection, coordination, resource, errorReporter); - - Instantiation networkInstance = factory.createInstantiation(); - - VarRef sourceRef = factory.createVarRef(); //out port from federate - VarRef instRef = factory.createVarRef(); //out port from federate - - Reactor top = connection.getSourcePortInstance() - .getParent() - .getParent().reactorDefinition; // Top-level reactor. - - - networkInstance.setReactorClass(sender); - networkInstance.setName(ASTUtils.getUniqueIdentifier(top, "ns_" + connection.getDstFederate().name)); - top.getInstantiations().add(networkInstance); - - Connection senderToReaction = factory.createConnection(); - - // Establish references to the involved ports. - sourceRef.setContainer(connection.getSourcePortInstance().getParent().getDefinition()); - sourceRef.setVariable(connection.getSourcePortInstance().getDefinition()); - instRef.setContainer(networkInstance); - instRef.setVariable(sender.getInputs().get(0)); - - senderToReaction.getLeftPorts().add(sourceRef); - senderToReaction.getRightPorts().add(instRef); - - // The connection is 'physical' if it uses the ~> notation. - if (connection.getDefinition().isPhysical()) { - connection.srcFederate.outboundP2PConnections.add(connection.dstFederate); - } else { - // If the connection is logical but coordination - // is decentralized, we would need - // to make P2P connections - if (coordination == CoordinationType.DECENTRALIZED) { - connection.srcFederate.outboundP2PConnections.add(connection.dstFederate); - } - } - - connection.srcFederate.networkConnections.add(senderToReaction); - connection.srcFederate.networkSenderInstantiations.add(networkInstance); - connection.srcFederate.networkPortToInstantiation.put(connection.getSourcePortInstance(), networkInstance); + .generateNetworkSenderBody( + inRef, + destRef, + connection, + InferredType.fromAST(type), + coordination, + errorReporter)); + + // Add the network sender reaction to the federate instance's list + // of network reactions + connection.srcFederate.networkSenderReactions.add(networkSenderReaction); + connection.srcFederate.networkReactors.add(sender); + + networkSenderReactors.put(connection, sender); + return sender; + } + + /** + * Add a network sender reactor for a given input port 'source' to source's parent reactor. This + * reaction will react to the 'source' and then send a message on the network destined for the + * destinationFederate. + * + * @param connection Network connection between two federates. + * @param coordination One of CoordinationType.DECENTRALIZED or CoordinationType.CENTRALIZED. + * @param resource + * @param mainDef + * @param errorReporter FIXME + * @note Used in federated execution + */ + private static void addNetworkSenderReactor( + FedConnectionInstance connection, + CoordinationType coordination, + Resource resource, + ErrorReporter errorReporter) { + LfFactory factory = LfFactory.eINSTANCE; + // Assume all the types are the same, so just use the first on the right. + + Reactor sender = getNetworkSenderReactor(connection, coordination, resource, errorReporter); + + Instantiation networkInstance = factory.createInstantiation(); + + VarRef sourceRef = factory.createVarRef(); // out port from federate + VarRef instRef = factory.createVarRef(); // out port from federate + + Reactor top = + connection + .getSourcePortInstance() + .getParent() + .getParent() + .reactorDefinition; // Top-level reactor. + + networkInstance.setReactorClass(sender); + networkInstance.setName( + ASTUtils.getUniqueIdentifier(top, "ns_" + connection.getDstFederate().name)); + top.getInstantiations().add(networkInstance); + + Connection senderToReaction = factory.createConnection(); + + // Establish references to the involved ports. + sourceRef.setContainer(connection.getSourcePortInstance().getParent().getDefinition()); + sourceRef.setVariable(connection.getSourcePortInstance().getDefinition()); + instRef.setContainer(networkInstance); + instRef.setVariable(sender.getInputs().get(0)); + + senderToReaction.getLeftPorts().add(sourceRef); + senderToReaction.getRightPorts().add(instRef); + + // The connection is 'physical' if it uses the ~> notation. + if (connection.getDefinition().isPhysical()) { + connection.srcFederate.outboundP2PConnections.add(connection.dstFederate); + } else { + // If the connection is logical but coordination + // is decentralized, we would need + // to make P2P connections + if (coordination == CoordinationType.DECENTRALIZED) { + connection.srcFederate.outboundP2PConnections.add(connection.dstFederate); + } } - /** - * Add a network control reaction for a given output port 'source' to - * source's parent reactor. This reaction will send a port absent - * message if the status of the output port is absent. - * - * @param connection FIXME - * @note Used in federated execution - */ - private static void addNetworkOutputControlReaction(FedConnectionInstance connection) { - LfFactory factory = LfFactory.eINSTANCE; - Reaction reaction = factory.createReaction(); - Reactor top = networkSenderReactors.getOrDefault(connection, null); - - // Add the output from the contained reactor as a source to - // the reaction to preserve precedence order. - VarRef newPortRef = factory.createVarRef(); - newPortRef.setVariable(top.getInputs().get(0)); - reaction.getSources().add(newPortRef); - - // If the sender or receiver is in a bank of reactors, then we want - // these reactions to appear only in the federate whose bank ID matches. - setReactionBankIndex(reaction, connection.getSrcBank()); - - // FIXME: do not create a new extension every time it is used - FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) - .annotateReaction(reaction); - - // We use an action at the top-level to manually - // trigger output control reactions. That action is created once - // and recorded in the federate instance. - // Check whether the action already has been created. - // if (connection.srcFederate.networkOutputControlReactionsTrigger == null) { - // // The port has not been created. - // String triggerName = "outputControlReactionTrigger"; - - // // Find the trigger definition in the reactor definition, which could have been - // // generated for another federate instance if there are multiple instances - // // of the same reactor that are each distinct federates. - // Optional optTriggerInput - // = top.getActions().stream().filter( - // I -> I.getName().equals(triggerName)).findFirst(); - - // if (optTriggerInput.isEmpty()) { - // If no trigger with the name "outputControlReactionTrigger" is - // already added to the reactor definition, we need to create it - // for the first time. The trigger is a logical action. - Action newTriggerForControlReactionVariable = factory.createAction(); - newTriggerForControlReactionVariable.setName("outputControlReactionTrigger"); - newTriggerForControlReactionVariable.setOrigin(ActionOrigin.LOGICAL); - top.getActions().add(newTriggerForControlReactionVariable); - - // // Now that the variable is created, store it in the federate instance - // connection.srcFederate.networkOutputControlReactionsTrigger - // = newTriggerForControlReactionVariable; - // } else { - // If the "outputControlReactionTrigger" trigger is already - // there, we can re-use it for this new reaction since a single trigger - // will trigger - // // all network output control reactions. - // connection.srcFederate.networkOutputControlReactionsTrigger = optTriggerInput.get(); - // } - //} - - // Add the trigger for all output control reactions to the list of triggers - VarRef triggerRef = factory.createVarRef(); - triggerRef.setVariable(newTriggerForControlReactionVariable); - reaction.getTriggers().add(triggerRef); - //int val = networkIDSender-1; - //reaction.setName("NetworkSenderControlReaction_" + val); - - // Generate the code - reaction.setCode(factory.createCode()); - - reaction.getCode().setBody( + connection.srcFederate.networkConnections.add(senderToReaction); + connection.srcFederate.networkSenderInstantiations.add(networkInstance); + connection.srcFederate.networkPortToInstantiation.put( + connection.getSourcePortInstance(), networkInstance); + } + + /** + * Add a network control reaction for a given output port 'source' to source's parent reactor. + * This reaction will send a port absent message if the status of the output port is absent. + * + * @param connection FIXME + * @note Used in federated execution + */ + private static void addNetworkOutputControlReaction(FedConnectionInstance connection) { + LfFactory factory = LfFactory.eINSTANCE; + Reaction reaction = factory.createReaction(); + Reactor top = networkSenderReactors.getOrDefault(connection, null); + + // Add the output from the contained reactor as a source to + // the reaction to preserve precedence order. + VarRef newPortRef = factory.createVarRef(); + newPortRef.setVariable(top.getInputs().get(0)); + reaction.getSources().add(newPortRef); + + // If the sender or receiver is in a bank of reactors, then we want + // these reactions to appear only in the federate whose bank ID matches. + setReactionBankIndex(reaction, connection.getSrcBank()); + + // FIXME: do not create a new extension every time it is used + FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) + .annotateReaction(reaction); + + // We use an action at the top-level to manually + // trigger output control reactions. That action is created once + // and recorded in the federate instance. + // Check whether the action already has been created. + // if (connection.srcFederate.networkOutputControlReactionsTrigger == null) { + // // The port has not been created. + // String triggerName = "outputControlReactionTrigger"; + + // // Find the trigger definition in the reactor definition, which could have been + // // generated for another federate instance if there are multiple instances + // // of the same reactor that are each distinct federates. + // Optional optTriggerInput + // = top.getActions().stream().filter( + // I -> I.getName().equals(triggerName)).findFirst(); + + // if (optTriggerInput.isEmpty()) { + // If no trigger with the name "outputControlReactionTrigger" is + // already added to the reactor definition, we need to create it + // for the first time. The trigger is a logical action. + Action newTriggerForControlReactionVariable = factory.createAction(); + newTriggerForControlReactionVariable.setName("outputControlReactionTrigger"); + newTriggerForControlReactionVariable.setOrigin(ActionOrigin.LOGICAL); + top.getActions().add(newTriggerForControlReactionVariable); + + // // Now that the variable is created, store it in the federate instance + // connection.srcFederate.networkOutputControlReactionsTrigger + // = newTriggerForControlReactionVariable; + // } else { + // If the "outputControlReactionTrigger" trigger is already + // there, we can re-use it for this new reaction since a single trigger + // will trigger + // // all network output control reactions. + // connection.srcFederate.networkOutputControlReactionsTrigger = optTriggerInput.get(); + // } + // } + + // Add the trigger for all output control reactions to the list of triggers + VarRef triggerRef = factory.createVarRef(); + triggerRef.setVariable(newTriggerForControlReactionVariable); + reaction.getTriggers().add(triggerRef); + // int val = networkIDSender-1; + // reaction.setName("NetworkSenderControlReaction_" + val); + + // Generate the code + reaction.setCode(factory.createCode()); + + reaction + .getCode() + .setBody( FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) - .generateNetworkOutputControlReactionBody(newPortRef, connection)); - + .generateNetworkOutputControlReactionBody(newPortRef, connection)); - // Insert the newly generated reaction after the generated sender and - // receiver top-level reactions. - top.getReactions().add(reaction); + // Insert the newly generated reaction after the generated sender and + // receiver top-level reactions. + top.getReactions().add(reaction); - // Add the network output control reaction to the federate instance's list - // of network reactions - connection.srcFederate.networkSenderReactions.add(reaction); - connection.srcFederate.networkSenderControlReactions.add(reaction); + // Add the network output control reaction to the federate instance's list + // of network reactions + connection.srcFederate.networkSenderReactions.add(reaction); + connection.srcFederate.networkSenderControlReactions.add(reaction); - //connection.srcFederate.networkPortToControlReaction.put(connection.getSourcePortInstance(), reaction); - //connection.srcFederate.networkOutputControlReactionsTriggers.add(newTriggerForControlReactionVariable); + // connection.srcFederate.networkPortToControlReaction.put(connection.getSourcePortInstance(), + // reaction); + // connection.srcFederate.networkOutputControlReactionsTriggers.add(newTriggerForControlReactionVariable); - - } + } } diff --git a/org.lflang/src/org/lflang/federated/generator/FedConnectionInstance.java b/org.lflang/src/org/lflang/federated/generator/FedConnectionInstance.java index a82e1d2dfa..d08aa5f850 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedConnectionInstance.java +++ b/org.lflang/src/org/lflang/federated/generator/FedConnectionInstance.java @@ -9,102 +9,100 @@ /** * Class representing a federated connection. * - * This is an undocumented class written by a previous contributor who is no longer active. - * It merely serves as a record, presumably to make it easier to pass around information - * around. + *

This is an undocumented class written by a previous contributor who is no longer active. It + * merely serves as a record, presumably to make it easier to pass around information around. * * @author Soroush Bateni */ public class FedConnectionInstance { - SendRange srcRange; + SendRange srcRange; - RuntimeRange dstRange; + RuntimeRange dstRange; - int srcChannel; + int srcChannel; - int srcBank; + int srcBank; - int dstChannel; + int dstChannel; - int dstBank; + int dstBank; - FederateInstance srcFederate; + FederateInstance srcFederate; - FederateInstance dstFederate; + FederateInstance dstFederate; - SupportedSerializers serializer; + SupportedSerializers serializer; - public FedConnectionInstance( - SendRange srcRange, - RuntimeRange dstRange, - int srcChannel, - int srcBank, - int dstChannel, - int dstBank, - FederateInstance srcFederate, - FederateInstance dstFederate, - SupportedSerializers serializer - ) { - this.srcRange = srcRange; - this.srcChannel = srcChannel; - this.srcBank = srcBank; - this.dstChannel = dstChannel; - this.dstBank = dstBank; - this.srcFederate = srcFederate; - this.dstFederate = dstFederate; - this.dstRange = dstRange; - this.serializer = serializer; + public FedConnectionInstance( + SendRange srcRange, + RuntimeRange dstRange, + int srcChannel, + int srcBank, + int dstChannel, + int dstBank, + FederateInstance srcFederate, + FederateInstance dstFederate, + SupportedSerializers serializer) { + this.srcRange = srcRange; + this.srcChannel = srcChannel; + this.srcBank = srcBank; + this.dstChannel = dstChannel; + this.dstBank = dstBank; + this.srcFederate = srcFederate; + this.dstFederate = dstFederate; + this.dstRange = dstRange; + this.serializer = serializer; - this.srcFederate.connections.add(this); - this.dstFederate.connections.add(this); - } + this.srcFederate.connections.add(this); + this.dstFederate.connections.add(this); + } - public SendRange getSrcRange() { - return srcRange; - } + public SendRange getSrcRange() { + return srcRange; + } - public RuntimeRange getDstRange() { - return dstRange; - } + public RuntimeRange getDstRange() { + return dstRange; + } - public int getSrcChannel() { - return srcChannel; - } + public int getSrcChannel() { + return srcChannel; + } - public int getSrcBank() { - return srcBank; - } + public int getSrcBank() { + return srcBank; + } - public int getDstChannel() { - return dstChannel; - } + public int getDstChannel() { + return dstChannel; + } - public int getDstBank() { - return dstBank; - } + public int getDstBank() { + return dstBank; + } - public FederateInstance getSrcFederate() { - return srcFederate; - } + public FederateInstance getSrcFederate() { + return srcFederate; + } - public FederateInstance getDstFederate() { - return dstFederate; - } + public FederateInstance getDstFederate() { + return dstFederate; + } - public SupportedSerializers getSerializer() { - return serializer; - } + public SupportedSerializers getSerializer() { + return serializer; + } - public Connection getDefinition() { - return srcRange.connection; - } + public Connection getDefinition() { + return srcRange.connection; + } - public PortInstance getSourcePortInstance() { - return srcRange.instance; - } + public PortInstance getSourcePortInstance() { + return srcRange.instance; + } - public PortInstance getDestinationPortInstance() { - return dstRange.instance; - } + public PortInstance getDestinationPortInstance() { + return dstRange.instance; + } } diff --git a/org.lflang/src/org/lflang/federated/generator/FedEmitter.java b/org.lflang/src/org/lflang/federated/generator/FedEmitter.java index 212ec6a86a..bdf912d0c8 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedEmitter.java +++ b/org.lflang/src/org/lflang/federated/generator/FedEmitter.java @@ -4,77 +4,70 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; -import java.util.LinkedHashMap; 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.Model; import org.lflang.lf.Reactor; -/** - * Helper class to generate code for federates. - */ +/** Helper class to generate code for federates. */ public class FedEmitter { - private final FedFileConfig fileConfig; - private final Reactor originalMainReactor; - private final ErrorReporter errorReporter; - private final RtiConfig rtiConfig; + private final FedFileConfig fileConfig; + private final Reactor originalMainReactor; + private final ErrorReporter errorReporter; + private final RtiConfig rtiConfig; - public FedEmitter( - FedFileConfig fileConfig, - Reactor originalMainReactor, - ErrorReporter errorReporter, - RtiConfig rtiConfig - ) { - this.fileConfig = fileConfig; - this.originalMainReactor = originalMainReactor; - this.errorReporter = errorReporter; - this.rtiConfig = rtiConfig; - } + public FedEmitter( + FedFileConfig fileConfig, + Reactor originalMainReactor, + ErrorReporter errorReporter, + RtiConfig rtiConfig) { + this.fileConfig = fileConfig; + this.originalMainReactor = originalMainReactor; + this.errorReporter = errorReporter; + this.rtiConfig = rtiConfig; + } - /** - * Generate a .lf file for federate {@code federate}. - * - * @throws IOException - */ - Map generateFederate( - LFGeneratorContext context, - FederateInstance federate, - int numOfFederates - ) throws IOException { - String fedName = federate.name; - Files.createDirectories(fileConfig.getSrcPath()); - System.out.println("##### Generating code for federate " + fedName - + " in directory " - + fileConfig.getSrcPath()); + /** + * Generate a .lf file for federate {@code federate}. + * + * @throws IOException + */ + Map generateFederate( + LFGeneratorContext context, FederateInstance federate, int numOfFederates) + throws IOException { + String fedName = federate.name; + Files.createDirectories(fileConfig.getSrcPath()); + System.out.println( + "##### Generating code for federate " + + fedName + + " in directory " + + fileConfig.getSrcPath()); - String federateCode = String.join( + String federateCode = + String.join( "\n", - new FedTargetEmitter().generateTarget(context, numOfFederates, federate, fileConfig, errorReporter, rtiConfig), + new FedTargetEmitter() + .generateTarget( + context, numOfFederates, federate, fileConfig, errorReporter, rtiConfig), new FedImportEmitter().generateImports(federate, fileConfig), - new FedPreambleEmitter().generatePreamble(federate, fileConfig, rtiConfig, errorReporter), + new FedPreambleEmitter() + .generatePreamble(federate, fileConfig, rtiConfig, errorReporter), new FedReactorEmitter().generateReactorDefinitions(federate), - new FedMainEmitter().generateMainReactor( - federate, - originalMainReactor, - errorReporter - ) - ); - Map codeMapMap = new HashMap<>(); - var lfFilePath = lfFilePath(fileConfig, federate); - try (var srcWriter = Files.newBufferedWriter(lfFilePath)) { - var codeMap = CodeMap.fromGeneratedCode(federateCode); - codeMapMap.put(lfFilePath, codeMap); - srcWriter.write(codeMap.getGeneratedCode()); - } - return codeMapMap; + new FedMainEmitter().generateMainReactor(federate, originalMainReactor, errorReporter)); + Map codeMapMap = new HashMap<>(); + var lfFilePath = lfFilePath(fileConfig, federate); + try (var srcWriter = Files.newBufferedWriter(lfFilePath)) { + var codeMap = CodeMap.fromGeneratedCode(federateCode); + codeMapMap.put(lfFilePath, codeMap); + srcWriter.write(codeMap.getGeneratedCode()); } + return codeMapMap; + } - public static Path lfFilePath(FedFileConfig fileConfig, FederateInstance federate) { - return fileConfig.getSrcPath().resolve(federate.name + ".lf"); - } + public static Path lfFilePath(FedFileConfig fileConfig, FederateInstance federate) { + return fileConfig.getSrcPath().resolve(federate.name + ".lf"); + } } diff --git a/org.lflang/src/org/lflang/federated/generator/FedFileConfig.java b/org.lflang/src/org/lflang/federated/generator/FedFileConfig.java index e434501825..cb12c90a7d 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedFileConfig.java +++ b/org.lflang/src/org/lflang/federated/generator/FedFileConfig.java @@ -1,16 +1,16 @@ /************* * 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 @@ -30,113 +30,105 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; - import org.eclipse.emf.ecore.resource.Resource; - import org.lflang.FileConfig; import org.lflang.util.FileUtil; /** - * A subclass of @see FileConfig that extends the base functionality to add support - * for compiling federated LF programs. The code generator should create one instance - * of this class for each federate. + * A subclass of @see FileConfig that extends the base functionality to add support for compiling + * federated LF programs. The code generator should create one instance of this class for each + * federate. * * @author Soroush Bateni - * */ public class FedFileConfig extends FileConfig { - public FedFileConfig( - Resource resource, - Path srcGenBasePath, - boolean useHierarchicalBin - ) throws IOException { - super(resource, srcGenBasePath, useHierarchicalBin); - } - - public FedFileConfig(FileConfig fileConfig) throws IOException { - super(fileConfig.resource, fileConfig.getSrcGenBasePath(), fileConfig.useHierarchicalBin); - } - - /** - * Return the path to the root of a LF project generated on the basis of a - * federated LF program currently under compilation. - */ - public Path getGenPath() { - return srcPkgPath.resolve("fed-gen").resolve(name); - } - - /** - * Return the path for storing generated LF sources that jointly constitute a - * federation. - */ - public Path getSrcPath() { - return getGenPath().resolve("src"); - } - - /** - * The directory in which to put the generated sources. - * This takes into account the location of the source file relative to the - * package root. Specifically, if the source file is x/y/Z.lf relative - * to the package root, then the generated sources will be put in x/y/Z - * relative to srcGenBasePath. - */ - @Override - public Path getSrcGenPath() { - return getGenPath().resolve("src-gen"); - } - - /** - * Return the path to the root of a LF project generated on the basis of a - * federated LF program currently under compilation. - */ - 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.files); - 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(Paths.get(f)))); - paths.clear(); - paths.addAll(tempList); - } - - /** - * 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(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(); - } + public FedFileConfig(Resource resource, Path srcGenBasePath, boolean useHierarchicalBin) + throws IOException { + super(resource, srcGenBasePath, useHierarchicalBin); + } + + public FedFileConfig(FileConfig fileConfig) throws IOException { + super(fileConfig.resource, fileConfig.getSrcGenBasePath(), fileConfig.useHierarchicalBin); + } + + /** + * Return the path to the root of a LF project generated on the basis of a federated LF program + * currently under compilation. + */ + public Path getGenPath() { + return srcPkgPath.resolve("fed-gen").resolve(name); + } + + /** Return the path for storing generated LF sources that jointly constitute a federation. */ + public Path getSrcPath() { + return getGenPath().resolve("src"); + } + + /** + * The directory in which to put the generated sources. This takes into account the location of + * the source file relative to the package root. Specifically, if the source file is x/y/Z.lf + * relative to the package root, then the generated sources will be put in x/y/Z relative to + * srcGenBasePath. + */ + @Override + public Path getSrcGenPath() { + return getGenPath().resolve("src-gen"); + } + + /** + * Return the path to the root of a LF project generated on the basis of a federated LF program + * currently under compilation. + */ + 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.files); + 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(Paths.get(f)))); + paths.clear(); + paths.addAll(tempList); + } + + /** + * 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(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(); } + } } diff --git a/org.lflang/src/org/lflang/federated/generator/FedGenerator.java b/org.lflang/src/org/lflang/federated/generator/FedGenerator.java index 3d2d118067..424705e06b 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedGenerator.java +++ b/org.lflang/src/org/lflang/federated/generator/FedGenerator.java @@ -2,6 +2,7 @@ import static org.lflang.generator.DockerGenerator.dockerGeneratorFactory; +import com.google.inject.Injector; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; @@ -19,7 +20,6 @@ import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; - import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.generator.JavaIoFileSystemAccess; @@ -27,14 +27,13 @@ import org.eclipse.xtext.resource.XtextResourceSet; import org.eclipse.xtext.util.RuntimeIOException; import org.eclipse.xtext.xbase.lib.Exceptions; - -import org.lflang.ast.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.LFStandaloneSetup; import org.lflang.Target; import org.lflang.TargetConfig; import org.lflang.TargetProperty.CoordinationType; +import org.lflang.ast.ASTUtils; import org.lflang.federated.launcher.FedLauncherGenerator; import org.lflang.federated.launcher.RtiConfig; import org.lflang.generator.CodeMap; @@ -58,574 +57,544 @@ import org.lflang.lf.Reactor; import org.lflang.util.Averager; -import com.google.inject.Injector; - public class FedGenerator { - - /** - * - */ - private final ErrorReporter errorReporter; - - /** - * A list of federate instances. - */ - private final List federates = new ArrayList<>(); - - /** - * File configuration to be used during the LF code generation stage (not the target code - * generation stage of individual federates). - */ - private final FedFileConfig fileConfig; - - /** - * Configuration of the RTI. - */ - 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. - * If the instantiation has a width, there may be more than one federate - * instance. - */ - private Map> federatesByInstantiation; - - /** - * Definition of the main (top-level) reactor. - * This is an automatically generated AST node for the top-level - * reactor. - */ - 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(); + /** */ + private final ErrorReporter errorReporter; + + /** A list of federate instances. */ + private final List federates = new ArrayList<>(); + + /** + * File configuration to be used during the LF code generation stage (not the target code + * generation stage of individual federates). + */ + private final FedFileConfig fileConfig; + + /** Configuration of the RTI. */ + 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. If the + * instantiation has a width, there may be more than one federate instance. + */ + private Map> federatesByInstantiation; + + /** + * Definition of the main (top-level) reactor. This is an automatically generated AST node for the + * top-level reactor. + */ + 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); + + // In a federated execution, we need keepalive to be true, + // otherwise a federate could exit simply because it hasn't received + // any messages. + targetConfig.keepalive = true; + + // Process command-line arguments + processCLIArguments(context); + + // Find the federated reactor + Reactor federation = FedASTUtils.findFederatedReactor(resource); + + // Extract some useful information about the federation + 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 three reactions. + // The action will be physical for physical connections and logical + // for logical connections. + replaceFederateConnectionsWithProxies(federation, resource); + + FedEmitter fedEmitter = + new FedEmitter( + fileConfig, ASTUtils.toDefinition(mainDef.getReactorClass()), errorReporter, rtiConfig); + + // Generate LF code for each federate. + Map lf2lfCodeMapMap = new HashMap<>(); + for (FederateInstance federate : federates) { + lf2lfCodeMapMap.putAll(fedEmitter.generateFederate(context, federate, federates.size())); } - /** - * 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); - - // In a federated execution, we need keepalive to be true, - // otherwise a federate could exit simply because it hasn't received - // any messages. - targetConfig.keepalive = true; - - // Process command-line arguments - processCLIArguments(context); - - // Find the federated reactor - Reactor federation = FedASTUtils.findFederatedReactor(resource); - - // Extract some useful information about the federation - 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 three reactions. - // The action will be physical for physical connections and logical - // for logical connections. - replaceFederateConnectionsWithProxies(federation, resource); - - FedEmitter fedEmitter = new FedEmitter( - fileConfig, - ASTUtils.toDefinition(mainDef.getReactorClass()), - errorReporter, - rtiConfig - ); - - // Generate LF code for each federate. - Map lf2lfCodeMapMap = new HashMap<>(); - for (FederateInstance federate : federates) { - lf2lfCodeMapMap.putAll(fedEmitter.generateFederate( - context, federate, federates.size() - )); - } - - // Do not invoke target code generators if --no-compile flag is used. - if (context.getTargetConfig().noCompile) { - context.finish(Status.GENERATED, lf2lfCodeMapMap); - return false; - } + // 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 -> { - createDockerFiles(context, subContexts); - generateLaunchScript(); - }); + Map codeMapMap = + compileFederates( + context, + lf2lfCodeMapMap, + subContexts -> { + createDockerFiles(context, subContexts); + generateLaunchScript(); + }); - context.finish(Status.COMPILED, codeMapMap); - return false; + 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); } - - private void generateLaunchScript() { - new FedLauncherGenerator( - this.targetConfig, - this.fileConfig, - this.errorReporter - ).doGenerate(federates, rtiConfig); + // 2. create a docker-compose.yml for the federation + try { + new FedDockerComposeGenerator(context, rtiConfig.getHost()).writeDockerComposeFile(services); + } catch (IOException e) { + throw new RuntimeIOException(e); } - - /** - * 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 Context in which the generator operates + */ + private void cleanIfNeeded(LFGeneratorContext context) { + if (context.getArgs().containsKey(BuildParm.CLEAN.getKey())) { + try { + fileConfig.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. - * @param context Context in which the generator operates - */ - private void cleanIfNeeded(LFGeneratorContext context) { - if (context.getArgs().containsKey(BuildParm.CLEAN.getKey())) { - try { - fileConfig.doClean(); - } catch (IOException e) { - System.err.println("WARNING: IO Error during clean"); - } - } + } + + /** Return whether federated execution is supported for {@code resource}. */ + private boolean federatedExecutionIsSupported(Resource resource) { + 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 whether federated execution is supported for {@code resource}. */ - private boolean federatedExecutionIsSupported(Resource resource) { - 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 + "." - ); - } - 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; + if (target.equals(Target.C) && GeneratorUtils.isHostWindows()) { + errorReporter.reportError( + "Federated LF programs with a C target are currently not supported on Windows."); + targetOK = false; } - private Map compileFederates( - LFGeneratorContext context, - Map lf2lfCodeMapMap, - Consumer> finalizer) { - - // FIXME: Use the appropriate resource set instead of always using standalone - Injector inj = new LFStandaloneSetup() - .createInjectorAndDoEMFRegistration(); - XtextResourceSet rs = inj.getInstance(XtextResourceSet.class); - rs.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.TRUE); - // define output path here - JavaIoFileSystemAccess fsa = inj.getInstance(JavaIoFileSystemAccess.class); - fsa.setOutputPath("DEFAULT_OUTPUT", fileConfig.getSrcGenPath().toString()); - - var numOfCompileThreads = Math.min(6, - Math.min( - Math.max(federates.size(), 1), - Runtime.getRuntime().availableProcessors() - ) - ); - 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<>()); - Averager averager = new Averager(federates.size()); - final var threadSafeErrorReporter = new SynchronizedErrorReporter(errorReporter); - for (int i = 0; i < federates.size(); i++) { - FederateInstance fed = federates.get(i); - final int id = i; - compileThreadPool.execute(() -> { - Resource res = rs.getResource(URI.createFileURI( - FedEmitter.lfFilePath(fileConfig, fed).toAbsolutePath().toString() - ), true); - FileConfig subFileConfig = LFGenerator.createFileConfig(res, fileConfig.getSrcGenPath(), true); - ErrorReporter subContextErrorReporter = new LineAdjustingErrorReporter(threadSafeErrorReporter, lf2lfCodeMapMap); - - var props = new Properties(); - if (targetConfig.dockerOptions != null && targetConfig.target.buildsUsingDocker()) { - props.put("no-compile", "true"); - } - props.put("docker", "false"); - - TargetConfig subConfig = new TargetConfig( - props, GeneratorUtils.findTargetDecl(subFileConfig.resource), subContextErrorReporter - ); - SubContext subContext = new SubContext(context, IntegratedBuilder.VALIDATED_PERCENT_PROGRESS, 100) { - @Override - public ErrorReporter getErrorReporter() { - return subContextErrorReporter; - } - - @Override - public void reportProgress(String message, int percentage) { - averager.report(id, percentage, meanPercentage -> super.reportProgress(message, meanPercentage)); - } - - @Override - public FileConfig getFileConfig() { - return subFileConfig; - } - - @Override - public TargetConfig getTargetConfig() { - return subConfig; - } + return targetOK; + } + + private Map compileFederates( + LFGeneratorContext context, + Map lf2lfCodeMapMap, + Consumer> finalizer) { + + // FIXME: Use the appropriate resource set instead of always using standalone + Injector inj = new LFStandaloneSetup().createInjectorAndDoEMFRegistration(); + XtextResourceSet rs = inj.getInstance(XtextResourceSet.class); + rs.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.TRUE); + // define output path here + JavaIoFileSystemAccess fsa = inj.getInstance(JavaIoFileSystemAccess.class); + fsa.setOutputPath("DEFAULT_OUTPUT", fileConfig.getSrcGenPath().toString()); + + var numOfCompileThreads = + Math.min( + 6, Math.min(Math.max(federates.size(), 1), Runtime.getRuntime().availableProcessors())); + 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<>()); + Averager averager = new Averager(federates.size()); + final var threadSafeErrorReporter = new SynchronizedErrorReporter(errorReporter); + for (int i = 0; i < federates.size(); i++) { + FederateInstance fed = federates.get(i); + final int id = i; + compileThreadPool.execute( + () -> { + Resource res = + rs.getResource( + URI.createFileURI( + FedEmitter.lfFilePath(fileConfig, fed).toAbsolutePath().toString()), + true); + FileConfig subFileConfig = + LFGenerator.createFileConfig(res, fileConfig.getSrcGenPath(), true); + ErrorReporter subContextErrorReporter = + new LineAdjustingErrorReporter(threadSafeErrorReporter, lf2lfCodeMapMap); + + var props = new Properties(); + if (targetConfig.dockerOptions != null && targetConfig.target.buildsUsingDocker()) { + props.put("no-compile", "true"); + } + props.put("docker", "false"); + + TargetConfig subConfig = + new TargetConfig( + props, + GeneratorUtils.findTargetDecl(subFileConfig.resource), + subContextErrorReporter); + SubContext subContext = + new SubContext(context, IntegratedBuilder.VALIDATED_PERCENT_PROGRESS, 100) { + @Override + public ErrorReporter getErrorReporter() { + return subContextErrorReporter; + } + + @Override + public void reportProgress(String message, int percentage) { + averager.report( + id, + percentage, + meanPercentage -> super.reportProgress(message, meanPercentage)); + } + + @Override + public FileConfig getFileConfig() { + return subFileConfig; + } + + @Override + public TargetConfig getTargetConfig() { + return subConfig; + } }; - inj.getInstance(LFGenerator.class).doGenerate(res, fsa, subContext); - codeMapMap.putAll(subContext.getResult().getCodeMaps()); - subContexts.add(subContext); - }); - } - // Initiate an orderly shutdown in which previously submitted tasks are - // executed, but no new tasks will be accepted. - compileThreadPool.shutdown(); - - // Wait for all compile threads to finish (NOTE: Can block forever) - try { - compileThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); - } catch (Exception e) { - Exceptions.sneakyThrow(e); - } finally { - finalizer.accept(subContexts); - } - return codeMapMap; + inj.getInstance(LFGenerator.class).doGenerate(res, fsa, subContext); + codeMapMap.putAll(subContext.getResult().getCodeMaps()); + subContexts.add(subContext); + }); } - - /** - * Process command-line arguments passed on to the generator. - * - * @param context Context of the build process. - */ - private void processCLIArguments(LFGeneratorContext context) { - if (context.getArgs().containsKey("rti")) { - setFederationRTIProperties(context); - } + // Initiate an orderly shutdown in which previously submitted tasks are + // executed, but no new tasks will be accepted. + compileThreadPool.shutdown(); + + // Wait for all compile threads to finish (NOTE: Can block forever) + try { + compileThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); + } catch (Exception e) { + Exceptions.sneakyThrow(e); + } finally { + finalizer.accept(subContexts); } - - /** - * Set the RTI hostname, port and username if given as compiler arguments - * - * @param context Context of the build process. - */ - private void setFederationRTIProperties(LFGeneratorContext context) { - String rtiAddr = context.getArgs().getProperty("rti"); - Pattern pattern = Pattern.compile("([a-zA-Z0-9]+@)?([a-zA-Z0-9]+\\.?[a-z]{2,}|[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+):?([0-9]+)?"); - Matcher matcher = pattern.matcher(rtiAddr); - - if (!matcher.find()) { - return; - } - - // the user match group contains a trailing "@" which needs to be removed. - String userWithAt = matcher.group(1); - String user = (userWithAt == null) ? null : userWithAt.substring(0, - userWithAt.length() - - 1); - String host = matcher.group(2); - String port = matcher.group(3); - - if (host != null) { - rtiConfig.setHost(host); - } - if (port != null) { - rtiConfig.setPort(Integer.parseInt(port)); - } - if (user != null) { - rtiConfig.setUser(user); - } + return codeMapMap; + } + + /** + * Process command-line arguments passed on to the generator. + * + * @param context Context of the build process. + */ + private void processCLIArguments(LFGeneratorContext context) { + if (context.getArgs().containsKey("rti")) { + setFederationRTIProperties(context); + } + } + + /** + * Set the RTI hostname, port and username if given as compiler arguments + * + * @param context Context of the build process. + */ + private void setFederationRTIProperties(LFGeneratorContext context) { + String rtiAddr = context.getArgs().getProperty("rti"); + Pattern pattern = + Pattern.compile( + "([a-zA-Z0-9]+@)?([a-zA-Z0-9]+\\.?[a-z]{2,}|[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+):?([0-9]+)?"); + Matcher matcher = pattern.matcher(rtiAddr); + + if (!matcher.find()) { + return; } - /** - * Analyze the federation and record various properties of it. - * - * @param federation The federated reactor that contains all federates' instances. - */ - 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(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 (rtiConfig.getHost().equals("localhost") && - federation.getHost() != null && - !federation.getHost().getAddr().equals("localhost")) { - rtiConfig.setHost(federation.getHost().getAddr()); - } + // the user match group contains a trailing "@" which needs to be removed. + String userWithAt = matcher.group(1); + String user = (userWithAt == null) ? null : userWithAt.substring(0, userWithAt.length() - 1); + String host = matcher.group(2); + String port = matcher.group(3); - // Since federates are always within the main (federated) reactor, - // create a list containing just that one containing instantiation. - // This will be used to look up parameter values. - List mainReactorContext = new ArrayList<>(); - mainReactorContext.add(mainDef); - - // 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, context); - if (federatesByInstantiation == null) { - federatesByInstantiation = new LinkedHashMap<>(); - } - federatesByInstantiation.put(instantiation, federateInstances); - } + if (host != null) { + rtiConfig.setHost(host); } - - /** - * Get federate instances for a given {@code instantiation}. A bank will - * result in the creation of multiple federate instances (one for each - * member of the bank). - * - * @param instantiation An instantiation that corresponds to a federate. - * @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, 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(); - 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); - - if (instantiation.getHost() != null) { - federateInstance.host = instantiation.getHost().getAddr(); - // The following could be 0. - federateInstance.port = instantiation.getHost().getPort(); - // The following could be null. - federateInstance.user = instantiation.getHost().getUser(); - /* FIXME: The at keyword should support a directory component. - * federateInstance.dir = instantiation.getHost().dir - */ - if (federateInstance.host != null - && !federateInstance.host.equals("localhost") - && !federateInstance.host.equals("0.0.0.0")) { - federateInstance.isRemote = true; - } - } - } - return federateInstances; + if (port != null) { + rtiConfig.setPort(Integer.parseInt(port)); } - - /** - * Replace connections between federates in the AST with proxies that - * handle sending and receiving data. - * - * @param federation Reactor class of the federation. - * @param resource - */ - private void replaceFederateConnectionsWithProxies(Reactor federation, Resource resource) { - // Each connection in the AST may represent more than one connection between - // 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(federation, errorReporter); - - for (ReactorInstance child : mainInstance.children) { - for (PortInstance output : child.outputs) { - replaceConnectionFromOutputPort(output, resource); - } - } - - // Remove the connections at the top level - federation.getConnections().clear(); - - // There will be AST transformations that invalidate some info - // cached in ReactorInstance. FIXME: most likely not needed anymore - mainInstance.clearCaches(false); + if (user != null) { + rtiConfig.setUser(user); } - - /** - * Replace the connections from the specified output port. - * - * @param output The output port instance. - * @param resource - */ - private void replaceConnectionFromOutputPort(PortInstance output, Resource resource) { - // Iterate through ranges of the output port - for (SendRange srcRange : output.getDependentPorts()) { - if (srcRange.connection == null) { - // This should not happen. - errorReporter.reportError( - output.getDefinition(), - "Unexpected error. Cannot find output connection for port" - ); - continue; - } - // Iterate through destinations - for (RuntimeRange dstRange : srcRange.destinations) { - replaceOneToManyConnection( - srcRange, - dstRange, - resource - ); - } - } + } + + /** + * Analyze the federation and record various properties of it. + * + * @param federation The federated reactor that contains all federates' instances. + */ + 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(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 (rtiConfig.getHost().equals("localhost") + && federation.getHost() != null + && !federation.getHost().getAddr().equals("localhost")) { + rtiConfig.setHost(federation.getHost().getAddr()); } - /** - * Replace (potentially multiple) connection(s) that originate from an - * output port to multiple destinations. - * - * @param srcRange A range of an output port that sources data for this - * connection. - * @param dstRange A range of input ports that receive the data. - * @param resource - */ - private void replaceOneToManyConnection( - SendRange srcRange, - RuntimeRange dstRange, Resource resource - ) { - MixedRadixInt srcID = srcRange.startMR(); - MixedRadixInt dstID = dstRange.startMR(); - int dstCount = 0; - int srcCount = 0; - - while (dstCount++ < dstRange.width) { - int srcChannel = srcID.getDigits().get(0); - int srcBank = srcID.get(1); - int dstChannel = dstID.getDigits().get(0); - int dstBank = dstID.get(1); - - FederateInstance srcFederate = federatesByInstantiation.get( - srcRange.instance.getParent().getDefinition() - ).get(srcBank); - FederateInstance dstFederate = federatesByInstantiation.get( - dstRange.instance.getParent().getDefinition() - ).get(dstBank); - - // Clear banks - srcFederate.instantiation.setWidthSpec(null); - dstFederate.instantiation.setWidthSpec(null); - - FedConnectionInstance fedConnection = new FedConnectionInstance( - srcRange, - dstRange, - srcChannel, - srcBank, - dstChannel, - dstBank, - srcFederate, - dstFederate, - FedUtils.getSerializer(srcRange.connection, srcFederate, dstFederate) - ); - - replaceFedConnection(fedConnection, resource); - - dstID.increment(); - srcID.increment(); - srcCount++; - if (srcCount == srcRange.width) { - srcID = srcRange.startMR(); // Multicast. Start over. - } - } + // Since federates are always within the main (federated) reactor, + // create a list containing just that one containing instantiation. + // This will be used to look up parameter values. + List mainReactorContext = new ArrayList<>(); + mainReactorContext.add(mainDef); + + // 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, context); + if (federatesByInstantiation == null) { + federatesByInstantiation = new LinkedHashMap<>(); + } + federatesByInstantiation.put(instantiation, federateInstances); } - - /** - * Replace a one-to-one federated connection with proxies. - * - * @param connection A connection between two federates. - * @param resource - */ - private void replaceFedConnection(FedConnectionInstance connection, Resource resource) { - if (!connection.getDefinition().isPhysical() - && targetConfig.coordination != CoordinationType.DECENTRALIZED) { - // Map the delays on connections between federates. - Set dependsOnDelays = - connection.dstFederate.dependsOn.computeIfAbsent( - connection.srcFederate, - k -> new LinkedHashSet<>() - ); - // Put the delay on the cache. - if (connection.getDefinition().getDelay() != null) { - dependsOnDelays.add(connection.getDefinition().getDelay()); - } else { - // To indicate that at least one connection has no delay, add a null entry. - dependsOnDelays.add(null); - } - // Map the connections between federates. - Set sendsToDelays = - connection.srcFederate.sendsTo.computeIfAbsent( - connection.dstFederate, - k -> new LinkedHashSet<>() - ); - if (connection.getDefinition().getDelay() != null) { - sendsToDelays.add(connection.getDefinition().getDelay()); - } else { - // To indicate that at least one connection has no delay, add a null entry. - sendsToDelays.add(null); - } + } + + /** + * Get federate instances for a given {@code instantiation}. A bank will result in the creation of + * multiple federate instances (one for each member of the bank). + * + * @param instantiation An instantiation that corresponds to a federate. + * @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, 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(); + 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); + + if (instantiation.getHost() != null) { + federateInstance.host = instantiation.getHost().getAddr(); + // The following could be 0. + federateInstance.port = instantiation.getHost().getPort(); + // The following could be null. + federateInstance.user = instantiation.getHost().getUser(); + /* FIXME: The at keyword should support a directory component. + * federateInstance.dir = instantiation.getHost().dir + */ + if (federateInstance.host != null + && !federateInstance.host.equals("localhost") + && !federateInstance.host.equals("0.0.0.0")) { + federateInstance.isRemote = true; } + } + } + return federateInstances; + } + + /** + * Replace connections between federates in the AST with proxies that handle sending and receiving + * data. + * + * @param federation Reactor class of the federation. + * @param resource + */ + private void replaceFederateConnectionsWithProxies(Reactor federation, Resource resource) { + // Each connection in the AST may represent more than one connection between + // 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(federation, errorReporter); + + for (ReactorInstance child : mainInstance.children) { + for (PortInstance output : child.outputs) { + replaceConnectionFromOutputPort(output, resource); + } + } - FedASTUtils.makeCommunication(connection, resource, targetConfig.coordination, errorReporter); + // Remove the connections at the top level + federation.getConnections().clear(); + + // There will be AST transformations that invalidate some info + // cached in ReactorInstance. FIXME: most likely not needed anymore + mainInstance.clearCaches(false); + } + + /** + * Replace the connections from the specified output port. + * + * @param output The output port instance. + * @param resource + */ + private void replaceConnectionFromOutputPort(PortInstance output, Resource resource) { + // Iterate through ranges of the output port + for (SendRange srcRange : output.getDependentPorts()) { + if (srcRange.connection == null) { + // This should not happen. + errorReporter.reportError( + output.getDefinition(), "Unexpected error. Cannot find output connection for port"); + continue; + } + // Iterate through destinations + for (RuntimeRange dstRange : srcRange.destinations) { + replaceOneToManyConnection(srcRange, dstRange, resource); + } + } + } + + /** + * Replace (potentially multiple) connection(s) that originate from an output port to multiple + * destinations. + * + * @param srcRange A range of an output port that sources data for this connection. + * @param dstRange A range of input ports that receive the data. + * @param resource + */ + private void replaceOneToManyConnection( + SendRange srcRange, RuntimeRange dstRange, Resource resource) { + MixedRadixInt srcID = srcRange.startMR(); + MixedRadixInt dstID = dstRange.startMR(); + int dstCount = 0; + int srcCount = 0; + + while (dstCount++ < dstRange.width) { + int srcChannel = srcID.getDigits().get(0); + int srcBank = srcID.get(1); + int dstChannel = dstID.getDigits().get(0); + int dstBank = dstID.get(1); + + FederateInstance srcFederate = + federatesByInstantiation.get(srcRange.instance.getParent().getDefinition()).get(srcBank); + FederateInstance dstFederate = + federatesByInstantiation.get(dstRange.instance.getParent().getDefinition()).get(dstBank); + + // Clear banks + srcFederate.instantiation.setWidthSpec(null); + dstFederate.instantiation.setWidthSpec(null); + + FedConnectionInstance fedConnection = + new FedConnectionInstance( + srcRange, + dstRange, + srcChannel, + srcBank, + dstChannel, + dstBank, + srcFederate, + dstFederate, + FedUtils.getSerializer(srcRange.connection, srcFederate, dstFederate)); + + replaceFedConnection(fedConnection, resource); + + dstID.increment(); + srcID.increment(); + srcCount++; + if (srcCount == srcRange.width) { + srcID = srcRange.startMR(); // Multicast. Start over. + } } + } + + /** + * Replace a one-to-one federated connection with proxies. + * + * @param connection A connection between two federates. + * @param resource + */ + private void replaceFedConnection(FedConnectionInstance connection, Resource resource) { + if (!connection.getDefinition().isPhysical() + && targetConfig.coordination != CoordinationType.DECENTRALIZED) { + // Map the delays on connections between federates. + Set dependsOnDelays = + connection.dstFederate.dependsOn.computeIfAbsent( + connection.srcFederate, k -> new LinkedHashSet<>()); + // Put the delay on the cache. + if (connection.getDefinition().getDelay() != null) { + dependsOnDelays.add(connection.getDefinition().getDelay()); + } else { + // To indicate that at least one connection has no delay, add a null entry. + dependsOnDelays.add(null); + } + // Map the connections between federates. + Set sendsToDelays = + connection.srcFederate.sendsTo.computeIfAbsent( + connection.dstFederate, k -> new LinkedHashSet<>()); + if (connection.getDefinition().getDelay() != null) { + sendsToDelays.add(connection.getDefinition().getDelay()); + } else { + // To indicate that at least one connection has no delay, add a null entry. + sendsToDelays.add(null); + } + } + + FedASTUtils.makeCommunication(connection, resource, targetConfig.coordination, errorReporter); + } } diff --git a/org.lflang/src/org/lflang/federated/generator/FedImportEmitter.java b/org.lflang/src/org/lflang/federated/generator/FedImportEmitter.java index 95351237a7..baf13d6a0a 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedImportEmitter.java +++ b/org.lflang/src/org/lflang/federated/generator/FedImportEmitter.java @@ -4,9 +4,7 @@ import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; - import org.eclipse.emf.ecore.util.EcoreUtil; - import org.lflang.ast.FormattingUtils; import org.lflang.generator.CodeBuilder; import org.lflang.lf.Import; @@ -19,44 +17,41 @@ */ public class FedImportEmitter { - private static Set visitedImports = new HashSet<>(); - - /** - * Generate import statements for {@code federate}. - */ - String generateImports(FederateInstance federate, FedFileConfig fileConfig) { - var imports = ((Model) federate.instantiation.eContainer().eContainer()) - .getImports() - .stream() - .filter(federate::contains).toList(); - - // Transform the URIs + private static Set visitedImports = new HashSet<>(); + + /** Generate import statements for {@code federate}. */ + String generateImports(FederateInstance federate, FedFileConfig fileConfig) { + var imports = + ((Model) federate.instantiation.eContainer().eContainer()) + .getImports().stream().filter(federate::contains).toList(); + + // Transform the URIs + imports.stream() + .filter(i -> !visitedImports.contains(i)) + .forEach( + i -> { + visitedImports.add(i); + Path importPath = fileConfig.srcPath.resolve(i.getImportURI()).toAbsolutePath(); + i.setImportURI( + fileConfig.getSrcPath().relativize(importPath).toString().replace('\\', '/')); + }); + + var importStatements = new CodeBuilder(); + + // Add import statements needed for the ordinary functionality of the federate + importStatements.pr( imports.stream() - .filter(i -> !visitedImports.contains(i)) - .forEach(i -> { - visitedImports.add(i); - Path importPath = - fileConfig.srcPath - .resolve(i.getImportURI()).toAbsolutePath(); - i.setImportURI(fileConfig.getSrcPath().relativize(importPath) - .toString().replace('\\', '/') - ); - }); - - var importStatements = new CodeBuilder(); - - // Add import statements needed for the ordinary functionality of the federate - importStatements.pr(imports.stream() - .map(i -> { - var new_import = EcoreUtil.copy(i); - new_import.getReactorClasses().removeIf( - importedReactor -> !federate.contains(importedReactor) - ); - return new_import; - }) - .map(FormattingUtils.renderer(federate.targetConfig.target)) - .collect(Collectors.joining("\n"))); - - return importStatements.getCode(); - } + .map( + i -> { + var new_import = EcoreUtil.copy(i); + new_import + .getReactorClasses() + .removeIf(importedReactor -> !federate.contains(importedReactor)); + return new_import; + }) + .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 d54d7fb068..ecedc3b583 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java +++ b/org.lflang/src/org/lflang/federated/generator/FedMainEmitter.java @@ -4,120 +4,128 @@ import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; - import org.eclipse.emf.ecore.EObject; - -import org.lflang.ast.ASTUtils; import org.lflang.ErrorReporter; +import org.lflang.ast.ASTUtils; import org.lflang.ast.FormattingUtils; -import org.lflang.generator.CodeBuilder; import org.lflang.generator.PortInstance; -import org.lflang.generator.ReactorInstance; -import org.lflang.lf.Instantiation; import org.lflang.lf.Reactor; import org.lflang.lf.Variable; - import org.lflang.util.Pair; - -/** - * Helper class to generate a main reactor - */ +/** Helper class to generate a main reactor */ public class FedMainEmitter { - /** - * Generate a main reactor for {@code federate}. - * - * @param federate - * @param originalMainReactor The original main reactor. - * @param errorReporter Used to report errors. - * @return The main reactor. - */ - String generateMainReactor(FederateInstance federate, Reactor originalMainReactor, ErrorReporter errorReporter) { - // FIXME: Handle modes at the top-level - if (!ASTUtils.allModes(originalMainReactor).isEmpty()) { - errorReporter.reportError( - ASTUtils.allModes(originalMainReactor).stream().findFirst().get(), - "Modes at the top level are not supported under federated execution." - ); - } - var renderer = FormattingUtils.renderer(federate.targetConfig.target); - - return String - .join( - "\n", - generateMainSignature(federate, originalMainReactor, renderer), - String.join( - "\n", - renderer.apply(federate.instantiation), - ASTUtils.allStateVars(originalMainReactor).stream().filter(federate::contains).map(renderer).collect(Collectors.joining("\n")), - ASTUtils.allActions(originalMainReactor).stream().filter(federate::contains).map(renderer).collect(Collectors.joining("\n")), - ASTUtils.allTimers(originalMainReactor).stream().filter(federate::contains).map(renderer).collect(Collectors.joining("\n")), - ASTUtils.allMethods(originalMainReactor).stream().filter(federate::contains).map(renderer).collect(Collectors.joining("\n")), - ASTUtils.allReactions(originalMainReactor).stream().filter(federate::contains).map(renderer).collect(Collectors.joining("\n")), - federate.networkSenderInstantiations.stream().map(renderer).collect(Collectors.joining("\n")), - federate.networkReceiverInstantiations.stream().map(renderer).collect(Collectors.joining("\n")), - federate.networkConnections.stream().map(renderer).collect(Collectors.joining("\n")) - ).indent(4).stripTrailing(), - "}" - ); + /** + * Generate a main reactor for {@code federate}. + * + * @param federate + * @param originalMainReactor The original main reactor. + * @param errorReporter Used to report errors. + * @return The main reactor. + */ + String generateMainReactor( + FederateInstance federate, Reactor originalMainReactor, ErrorReporter errorReporter) { + // FIXME: Handle modes at the top-level + if (!ASTUtils.allModes(originalMainReactor).isEmpty()) { + errorReporter.reportError( + ASTUtils.allModes(originalMainReactor).stream().findFirst().get(), + "Modes at the top level are not supported under federated execution."); } + var renderer = FormattingUtils.renderer(federate.targetConfig.target); - private static String getDependencyList(FederateInstance federate, Pair p){ - //StringBuilder lst = new StringBuilder(); - var inputPort = p.getFirst(); - var outputPort = p.getSecond(); - var inputPortInstance = federate.networkPortToInstantiation.getOrDefault(inputPort, null); - var outputPortInstance = federate.networkPortToInstantiation.getOrDefault(outputPort, null); - //var outputPortControlReaction = federate.networkPortToInstantiation.getOrDefault(outputPort, null); - if(inputPortInstance == null) return ""; - //System.out.println("IP: " + inputPortReaction.getCode()); - if(outputPortInstance != null){ - //System.out.println("OP: " + outputPortReaction.toString()); - return inputPortInstance.getName() + "," + outputPortInstance.getName(); - } - return ""; - - - - + return String.join( + "\n", + generateMainSignature(federate, originalMainReactor, renderer), + String.join( + "\n", + renderer.apply(federate.instantiation), + ASTUtils.allStateVars(originalMainReactor).stream() + .filter(federate::contains) + .map(renderer) + .collect(Collectors.joining("\n")), + ASTUtils.allActions(originalMainReactor).stream() + .filter(federate::contains) + .map(renderer) + .collect(Collectors.joining("\n")), + ASTUtils.allTimers(originalMainReactor).stream() + .filter(federate::contains) + .map(renderer) + .collect(Collectors.joining("\n")), + ASTUtils.allMethods(originalMainReactor).stream() + .filter(federate::contains) + .map(renderer) + .collect(Collectors.joining("\n")), + ASTUtils.allReactions(originalMainReactor).stream() + .filter(federate::contains) + .map(renderer) + .collect(Collectors.joining("\n")), + federate.networkSenderInstantiations.stream() + .map(renderer) + .collect(Collectors.joining("\n")), + federate.networkReceiverInstantiations.stream() + .map(renderer) + .collect(Collectors.joining("\n")), + federate.networkConnections.stream() + .map(renderer) + .collect(Collectors.joining("\n"))) + .indent(4) + .stripTrailing(), + "}"); + } + + private static String getDependencyList( + FederateInstance federate, Pair p) { + // StringBuilder lst = new StringBuilder(); + var inputPort = p.getFirst(); + var outputPort = p.getSecond(); + var inputPortInstance = federate.networkPortToInstantiation.getOrDefault(inputPort, null); + var outputPortInstance = federate.networkPortToInstantiation.getOrDefault(outputPort, null); + // var outputPortControlReaction = federate.networkPortToInstantiation.getOrDefault(outputPort, + // null); + if (inputPortInstance == null) return ""; + // System.out.println("IP: " + inputPortReaction.getCode()); + if (outputPortInstance != null) { + // System.out.println("OP: " + outputPortReaction.toString()); + return inputPortInstance.getName() + "," + outputPortInstance.getName(); } - - /** - * Generate the signature of the main reactor. - * @param federate The federate. - * @param originalMainReactor The original main reactor of the original .lf file. - * @param renderer Used to render EObjects (in String representation). - */ - private CharSequence generateMainSignature(FederateInstance federate, Reactor originalMainReactor, Function renderer) { - var paramList = ASTUtils.allParameters(originalMainReactor) - .stream() - .filter(federate::contains) - .map(renderer) - .collect( - Collectors.joining( - ",", "(", ")" - ) - ); - // Empty "()" is currently not allowed by the syntax - - var networkMessageActionsListString = federate.networkMessageActions - .stream() + return ""; + } + + /** + * Generate the signature of the main reactor. + * + * @param federate The federate. + * @param originalMainReactor The original main reactor of the original .lf file. + * @param renderer Used to render EObjects (in String representation). + */ + private CharSequence generateMainSignature( + FederateInstance federate, Reactor originalMainReactor, Function renderer) { + var paramList = + ASTUtils.allParameters(originalMainReactor).stream() + .filter(federate::contains) + .map(renderer) + .collect(Collectors.joining(",", "(", ")")); + // Empty "()" is currently not allowed by the syntax + + var networkMessageActionsListString = + federate.networkMessageActions.stream() .map(Variable::getName) .collect(Collectors.joining(",")); - List vals = new ArrayList<>(); - for (var pair: federate.networkReactionDependencyPairs){ - vals.add(getDependencyList(federate, pair)); - } + List vals = new ArrayList<>(); + for (var pair : federate.networkReactionDependencyPairs) { + vals.add(getDependencyList(federate, pair)); + } - String intraDependencies = String.join(";", vals); - return - """ + String intraDependencies = String.join(";", vals); + return """ @_fed_config(network_message_actions="%s", dependencyPairs="%s") main reactor %s { - """.formatted(networkMessageActionsListString, - intraDependencies, - paramList.equals("()") ? "" : paramList); - } + """ + .formatted( + networkMessageActionsListString, + intraDependencies, + paramList.equals("()") ? "" : paramList); + } } diff --git a/org.lflang/src/org/lflang/federated/generator/FedPreambleEmitter.java b/org.lflang/src/org/lflang/federated/generator/FedPreambleEmitter.java index 154919c816..9cb35b2949 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedPreambleEmitter.java +++ b/org.lflang/src/org/lflang/federated/generator/FedPreambleEmitter.java @@ -3,9 +3,8 @@ import static org.lflang.ast.ASTUtils.toText; import java.io.IOException; - -import org.lflang.ast.ASTUtils; import org.lflang.ErrorReporter; +import org.lflang.ast.ASTUtils; import org.lflang.federated.extensions.FedTargetExtensionFactory; import org.lflang.federated.launcher.RtiConfig; import org.lflang.generator.CodeBuilder; @@ -14,38 +13,43 @@ public class FedPreambleEmitter { - 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, RtiConfig rtiConfig, ErrorReporter errorReporter) - throws IOException { - CodeBuilder preambleCode = new CodeBuilder(); - - // Transfer top-level preambles - var mainModel = (Model) ASTUtils.toDefinition(federate.instantiation.getReactorClass()).eContainer(); - for (Preamble p : mainModel.getPreambles()) { - preambleCode.pr( - """ + 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, + RtiConfig rtiConfig, + ErrorReporter errorReporter) + throws IOException { + CodeBuilder preambleCode = new CodeBuilder(); + + // Transfer top-level preambles + var mainModel = + (Model) ASTUtils.toDefinition(federate.instantiation.getReactorClass()).eContainer(); + for (Preamble p : mainModel.getPreambles()) { + preambleCode.pr( + """ %spreamble {= %s =} - """.formatted( - p.getVisibility() == null ? "" : p.getVisibility() + " ", - toText(p.getCode()) - )); - } + """ + .formatted( + p.getVisibility() == null ? "" : p.getVisibility() + " ", toText(p.getCode()))); + } - preambleCode.pr(""" + preambleCode.pr( + """ preamble {= %s - =}""".formatted(FedTargetExtensionFactory.getExtension(federate.targetConfig.target).generatePreamble( - federate, fileConfig, rtiConfig, errorReporter - )) - ); + =}""" + .formatted( + FedTargetExtensionFactory.getExtension(federate.targetConfig.target) + .generatePreamble(federate, fileConfig, rtiConfig, errorReporter))); - return preambleCode.getCode(); - } + 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 976049601c..69a6342486 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedReactorEmitter.java +++ b/org.lflang/src/org/lflang/federated/generator/FedReactorEmitter.java @@ -1,24 +1,22 @@ package org.lflang.federated.generator; import java.util.stream.Collectors; - import org.lflang.ast.FormattingUtils; import org.lflang.lf.Model; public class FedReactorEmitter { - public FedReactorEmitter() {} + public FedReactorEmitter() {} - /** - * @param federate - * @return - */ - String generateReactorDefinitions(FederateInstance federate) { - return ((Model) federate.instantiation.eContainer().eContainer()) - .getReactors() - .stream() + /** + * @param federate + * @return + */ + String generateReactorDefinitions(FederateInstance federate) { + return ((Model) federate.instantiation.eContainer().eContainer()) + .getReactors().stream() .filter(federate::contains) .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 index af83d1f919..3252f31a39 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedTargetConfig.java +++ b/org.lflang/src/org/lflang/federated/generator/FedTargetConfig.java @@ -2,77 +2,69 @@ import static org.lflang.ast.ASTUtils.convertToEmptyListIfNull; +import org.eclipse.emf.ecore.resource.Resource; 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. + * 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() - ); + /** + * 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() - ); + mergeImportedConfig( + federateResource, context.getFileConfig().resource, context.getErrorReporter()); - clearPropertiesToIgnore(); + clearPropertiesToIgnore(); - ((FedFileConfig)context.getFileConfig()).relativizePaths(this); + ((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); + } } + } - /** - * 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); - } + /** 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 1616286048..402138f283 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedTargetEmitter.java +++ b/org.lflang/src/org/lflang/federated/generator/FedTargetEmitter.java @@ -1,7 +1,6 @@ package org.lflang.federated.generator; import java.io.IOException; - import org.lflang.ErrorReporter; import org.lflang.TargetProperty; import org.lflang.ast.FormattingUtils; @@ -11,34 +10,25 @@ public class FedTargetEmitter { - String generateTarget( - LFGeneratorContext context, - int numOfFederates, - FederateInstance federate, - FedFileConfig fileConfig, - ErrorReporter errorReporter, - RtiConfig rtiConfig - ) throws IOException { + String generateTarget( + LFGeneratorContext context, + int numOfFederates, + FederateInstance federate, + FedFileConfig fileConfig, + ErrorReporter errorReporter, + RtiConfig rtiConfig) + throws IOException { - // 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, - rtiConfig - ); + // 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, rtiConfig); - return FormattingUtils.renderer(federate.targetConfig.target).apply( - TargetProperty.extractTargetDecl( - federate.targetConfig.target, - federate.targetConfig - ) - ); - } + return FormattingUtils.renderer(federate.targetConfig.target) + .apply( + TargetProperty.extractTargetDecl(federate.targetConfig.target, federate.targetConfig)); + } } diff --git a/org.lflang/src/org/lflang/federated/generator/FedUtils.java b/org.lflang/src/org/lflang/federated/generator/FedUtils.java index dd5ee6a9b3..2970be7c2a 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedUtils.java +++ b/org.lflang/src/org/lflang/federated/generator/FedUtils.java @@ -1,38 +1,24 @@ package org.lflang.federated.generator; -import java.util.List; - import org.lflang.federated.serialization.SupportedSerializers; -import org.lflang.generator.PortInstance; -import org.lflang.generator.ReactionInstance; -import org.lflang.generator.ReactorInstance; import org.lflang.lf.Connection; -import org.lflang.lf.Reaction; -import org.lflang.lf.VarRef; -/** - * A collection of utility methods for the federated generator. - */ +/** A collection of utility methods for the federated generator. */ public class FedUtils { - /** - * Get the serializer for the {@code connection} between {@code srcFederate} and {@code dstFederate}. - */ - public static SupportedSerializers getSerializer( - Connection connection, - FederateInstance srcFederate, - FederateInstance dstFederate - ) { - // Get the serializer - SupportedSerializers serializer = SupportedSerializers.NATIVE; - if (connection.getSerializer() != null) { - serializer = SupportedSerializers.valueOf( - connection.getSerializer().getType().toUpperCase() - ); - } - // Add it to the list of enabled serializers for the source and destination federates - srcFederate.enabledSerializers.add(serializer); - dstFederate.enabledSerializers.add(serializer); - return serializer; + /** + * Get the serializer for the {@code connection} between {@code srcFederate} and {@code + * dstFederate}. + */ + public static SupportedSerializers getSerializer( + Connection connection, FederateInstance srcFederate, FederateInstance dstFederate) { + // Get the serializer + SupportedSerializers serializer = SupportedSerializers.NATIVE; + if (connection.getSerializer() != null) { + serializer = SupportedSerializers.valueOf(connection.getSerializer().getType().toUpperCase()); } - + // Add it to the list of enabled serializers for the source and destination federates + srcFederate.enabledSerializers.add(serializer); + dstFederate.enabledSerializers.add(serializer); + return serializer; + } } diff --git a/org.lflang/src/org/lflang/federated/generator/FederateInstance.java b/org.lflang/src/org/lflang/federated/generator/FederateInstance.java index 12ca3207b2..8929831f40 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.HashMap; import java.util.HashSet; @@ -35,13 +36,11 @@ 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.ast.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.TargetConfig; import org.lflang.TimeValue; +import org.lflang.ast.ASTUtils; import org.lflang.federated.serialization.SupportedSerializers; import org.lflang.generator.ActionInstance; import org.lflang.generator.PortInstance; @@ -69,669 +68,622 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.Variable; import org.lflang.util.Pair; -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) { - // If the instantiation is in a bank, then we have to append - // the bank index to the name. - if (instantiation.getWidthSpec() != null) { - this.name = "federate__" + instantiation.getName() + "__" + bankIndex; - } else { - this.name = "federate__" + instantiation.getName(); - } - } + /** + * 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) { + // If the instantiation is in a bank, then we have to append + // the bank index to the name. + if (instantiation.getWidthSpec() != null) { + this.name = "federate__" + instantiation.getName() + "__" + bankIndex; + } else { + this.name = "federate__" + instantiation.getName(); + } } - - ///////////////////////////////////////////// - //// Public Fields - - /** - * The position within a bank of reactors for this federate. - * This is 0 if the instantiation is not a bank of reactors. - */ - public int bankIndex = 0; - - /** - * A list of outputs that can be triggered directly or indirectly by physical actions. - */ - public Set outputsConnectedToPhysicalActions = new LinkedHashSet<>(); - - /** - * 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 triggers that trigger the output control reactions 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 List networkOutputControlReactionsTriggers = new ArrayList<>(); + + /** Indicates whether the federate is remote or local */ + public boolean isRemote = false; + + /** + * List of generated network reactions (network receivers) that belong to this federate instance. + */ + public List networkReceiverReactions = new ArrayList<>(); + + /** List of generated network reactions (network sender) that belong to this federate instance. */ + public List networkSenderReactions = new ArrayList<>(); + + /** + * List of generated network control reactions (network sender) that belong to this federate + * instance. + */ + public List networkSenderControlReactions = new ArrayList<>(); + + /** + * List of generated network reactors (network input and outputs) that belong to this federate + * instance. + */ + public List networkReactors = new ArrayList<>(); + + /** + * List of relative dependencies between network input and output reactions belonging to the same + * federate that have zero logical delay between them. + */ + public List> networkReactionDependencyPairs = new ArrayList<>(); + + /** + * Mapping from a port instance of a connection to its associated network reaction. We populate + * this map as we process connections as a means of annotating intra-federate dependencies + */ + public Map networkPortToInstantiation = new HashMap<>(); + + /** + * List of generated network connections (network input and outputs) that belong to this federate + * instance. + */ + public List networkConnections = new ArrayList<>(); + + /** + * List of generated network instantiations (network input and outputs) that belong to this + * federate instance. + */ + public List networkSenderInstantiations = new ArrayList<>(); + + /** + * List of generated network instantiations (network input and outputs) that belong to this + * federate instance. + */ + public List networkReceiverInstantiations = new ArrayList<>(); + + /** Parsed target config of the federate. */ + public TargetConfig targetConfig; + + /** Keep a unique list of enabled serializers */ + public HashSet enabledSerializers = new HashSet<>(); + + /** Keep a unique list of enabled serializers */ + public List stpOffsets = new ArrayList<>(); + + public Set currentSTPOffsets = new HashSet<>(); + + /** Keep a map of STP values to a list of network actions */ + public HashMap> stpToNetworkActionMap = new HashMap<>(); + + /** Keep a map of network actions to their associated instantiations */ + public HashMap networkActionToInstantiation = new HashMap<>(); + + /** + * 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? } - - /** - * 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 triggers that trigger the output control reactions 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 List networkOutputControlReactionsTriggers = new ArrayList<>(); - - /** - * Indicates whether the federate is remote or local - */ - public boolean isRemote = false; - - /** - * List of generated network reactions (network receivers) that belong to this federate instance. - */ - public List networkReceiverReactions = new ArrayList<>(); - - /** - * List of generated network reactions (network sender) that belong to this federate instance. - */ - public List networkSenderReactions = new ArrayList<>(); - - /** - * List of generated network control reactions (network sender) that belong to this federate instance. - */ - public List networkSenderControlReactions = new ArrayList<>(); - - - /** - * List of generated network reactors (network input and outputs) that - * belong to this federate instance. - */ - public List networkReactors = new ArrayList<>(); - - - /** - * List of relative dependencies between network input and output reactions belonging to - * the same federate that have zero logical delay between them. - */ - public List> networkReactionDependencyPairs = new ArrayList<>(); - - /** - * Mapping from a port instance of a connection to its associated network reaction. We populate - * this map as we process connections as a means of annotating intra-federate dependencies - */ - public Map networkPortToInstantiation = new HashMap<>(); - - /** - * List of generated network connections (network input and outputs) that - * belong to this federate instance. - */ - public List networkConnections = new ArrayList<>(); - - - /** - * List of generated network instantiations (network input and outputs) that - * belong to this federate instance. - */ - public List networkSenderInstantiations = new ArrayList<>(); - - /** - * List of generated network instantiations (network input and outputs) that - * belong to this federate instance. - */ - public List networkReceiverInstantiations = new ArrayList<>(); - - /** - * Parsed target config of the federate. - */ - public TargetConfig targetConfig; - - /** - * Keep a unique list of enabled serializers - */ - public HashSet enabledSerializers = new HashSet<>(); - - /** - * Keep a unique list of enabled serializers - */ - public List stpOffsets = new ArrayList<>(); - - public Set currentSTPOffsets = new HashSet<>(); - - /** - * Keep a map of STP values to a list of network actions - */ - public HashMap> stpToNetworkActionMap = new HashMap<>(); - - /** - * Keep a map of network actions to their associated instantiations - */ - public HashMap networkActionToInstantiation = new HashMap<>(); - - /** - * 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."); + 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; } - /** - * 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; - } - - boolean instantiationsCheck = false; - if (networkReactors.contains(ASTUtils.toDefinition(reactor))){ - return true; - } - // 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 instantiationsCheck; + boolean instantiationsCheck = false; + if (networkReactors.contains(ASTUtils.toDefinition(reactor))) { + 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; + // 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 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 -> !networkReceiverReactions.contains(r) && !networkSenderReactions.contains(r) && contains(r) - ).collect(Collectors.toCollection(ArrayList::new)); - returnValue |= !topLevelUserDefinedReactions.isEmpty(); - return returnValue; + 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 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 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 -> + !networkReceiverReactions.contains(r) + && !networkSenderReactions.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; } + } } - - 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 (networkReceiverReactions.contains(reaction) || networkSenderReactions.contains(reaction)) { - // Reaction is a network reaction that belongs to this federate + // Look in sources + for (VarRef source : convertToEmptyListIfNull(react.getSources())) { + if (Objects.equal(source.getVariable(), action)) { 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); + // Look in effects + for (VarRef effect : convertToEmptyListIfNull(react.getEffects())) { + if (Objects.equal(effect.getVariable(), action)) { + return true; + } } + } + } - indexExcludedTopLevelReactions(reactor); - - return !excludeReactions.contains(reaction); + 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 (networkReceiverReactions.contains(reaction) || networkSenderReactions.contains(reaction)) { + // Reaction is a network reaction that belongs to this federate + return true; } - /** - * 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; - } - } - } - } - } + int reactionBankIndex = FedASTUtils.getReactionBankIndex(reaction); + if (reactionBankIndex >= 0 && this.bankIndex >= 0 && reactionBankIndex != this.bankIndex) { + return false; + } - 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 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; + 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; } - i = i.getParent(); + } } - 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 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 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."); + } - 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 -> !networkReceiverReactions.contains(it)) - .filter(it -> !networkSenderReactions.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 -> !networkReceiverReactions.contains(it)) + .filter(it -> !networkSenderReactions.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; + } } + return minDelay; + } - // TODO: Put this function into a utils file instead - private List convertToEmptyListIfNull(List list) { - return list == null ? new ArrayList<>() : list; - } + // 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/federated/generator/LineAdjustingErrorReporter.java b/org.lflang/src/org/lflang/federated/generator/LineAdjustingErrorReporter.java index f483c228db..aa2f49b6da 100644 --- a/org.lflang/src/org/lflang/federated/generator/LineAdjustingErrorReporter.java +++ b/org.lflang/src/org/lflang/federated/generator/LineAdjustingErrorReporter.java @@ -2,10 +2,8 @@ import java.nio.file.Path; import java.util.Map; - import org.eclipse.emf.ecore.EObject; import org.eclipse.lsp4j.DiagnosticSeverity; - import org.lflang.ErrorReporter; import org.lflang.generator.CodeMap; import org.lflang.generator.Position; @@ -13,116 +11,102 @@ public class LineAdjustingErrorReporter implements ErrorReporter { - private final ErrorReporter parent; - private final Map codeMapMap; - - public LineAdjustingErrorReporter(ErrorReporter parent, Map codeMapMap) { - this.parent = parent; - this.codeMapMap = codeMapMap; - } - - @Override - public String reportError(String message) { - return parent.reportError(message); - } - - @Override - public String reportWarning(String message) { - return parent.reportWarning(message); - } - - @Override - public String reportInfo(String message) { - return parent.reportInfo(message); - } - - @Override - public String reportError(EObject object, String message) { - return parent.reportError(object, message); - } - - @Override - public String reportWarning(EObject object, String message) { - return parent.reportWarning(object, message); - } - - @Override - public String reportInfo(EObject object, String message) { - return parent.reportInfo(object, message); - } - - @Override - public String reportError(Path file, Integer line, String message) { - return report(file, line, message, DiagnosticSeverity.Error); - } - - @Override - public String reportWarning(Path file, Integer line, String message) { - return report(file, line, message, DiagnosticSeverity.Warning); - } - - @Override - public String reportInfo(Path file, Integer line, String message) { - return report(file, line, message, DiagnosticSeverity.Information); - } - - private String report(Path file, Integer line, String message, DiagnosticSeverity severity) { - if (line == null) return report(file, severity, message); - var position = Position.fromOneBased(line, Integer.MAX_VALUE); - return report( - file, - severity, - message, - Position.fromZeroBased(position.getZeroBasedLine(), 0), - position - ); - } - - @Override - public String report(Path file, DiagnosticSeverity severity, String message) { - return ErrorReporter.super.report(file, severity, message); - } - - @Override - public String report(Path file, DiagnosticSeverity severity, String message, int line) { - return ErrorReporter.super.report(file, severity, message, line); - } - - @Override - public String report( - Path file, - DiagnosticSeverity severity, - String message, - Position startPos, - Position endPos - ) { - String ret = null; - if (codeMapMap.containsKey(file)) { - var relevantMap = codeMapMap.get(file); - for (Path lfSource : relevantMap.lfSourcePaths()) { - var adjustedRange = relevantMap.adjusted( - lfSource, - new Range(startPos, endPos) - ); - ret = parent.report( - lfSource, - severity, - message, - adjustedRange.getStartInclusive().equals(Position.ORIGIN) ? - Position.fromZeroBased( - adjustedRange.getEndExclusive().getZeroBasedLine(), - 0 - ) : adjustedRange.getStartInclusive(), - adjustedRange.getEndExclusive() - ); - } - } - if (ret == null) return severity == DiagnosticSeverity.Error ? reportError(message) : reportWarning(message); - return ret; - } - - @Override - public boolean getErrorsOccurred() { - return parent.getErrorsOccurred(); + private final ErrorReporter parent; + private final Map codeMapMap; + + public LineAdjustingErrorReporter(ErrorReporter parent, Map codeMapMap) { + this.parent = parent; + this.codeMapMap = codeMapMap; + } + + @Override + public String reportError(String message) { + return parent.reportError(message); + } + + @Override + public String reportWarning(String message) { + return parent.reportWarning(message); + } + + @Override + public String reportInfo(String message) { + return parent.reportInfo(message); + } + + @Override + public String reportError(EObject object, String message) { + return parent.reportError(object, message); + } + + @Override + public String reportWarning(EObject object, String message) { + return parent.reportWarning(object, message); + } + + @Override + public String reportInfo(EObject object, String message) { + return parent.reportInfo(object, message); + } + + @Override + public String reportError(Path file, Integer line, String message) { + return report(file, line, message, DiagnosticSeverity.Error); + } + + @Override + public String reportWarning(Path file, Integer line, String message) { + return report(file, line, message, DiagnosticSeverity.Warning); + } + + @Override + public String reportInfo(Path file, Integer line, String message) { + return report(file, line, message, DiagnosticSeverity.Information); + } + + private String report(Path file, Integer line, String message, DiagnosticSeverity severity) { + if (line == null) return report(file, severity, message); + var position = Position.fromOneBased(line, Integer.MAX_VALUE); + return report( + file, severity, message, Position.fromZeroBased(position.getZeroBasedLine(), 0), position); + } + + @Override + public String report(Path file, DiagnosticSeverity severity, String message) { + return ErrorReporter.super.report(file, severity, message); + } + + @Override + public String report(Path file, DiagnosticSeverity severity, String message, int line) { + return ErrorReporter.super.report(file, severity, message, line); + } + + @Override + public String report( + Path file, DiagnosticSeverity severity, String message, Position startPos, Position endPos) { + String ret = null; + if (codeMapMap.containsKey(file)) { + var relevantMap = codeMapMap.get(file); + for (Path lfSource : relevantMap.lfSourcePaths()) { + var adjustedRange = relevantMap.adjusted(lfSource, new Range(startPos, endPos)); + ret = + parent.report( + lfSource, + severity, + message, + adjustedRange.getStartInclusive().equals(Position.ORIGIN) + ? Position.fromZeroBased(adjustedRange.getEndExclusive().getZeroBasedLine(), 0) + : adjustedRange.getStartInclusive(), + adjustedRange.getEndExclusive()); + } } + if (ret == null) + return severity == DiagnosticSeverity.Error ? reportError(message) : reportWarning(message); + return ret; + } + + @Override + public boolean getErrorsOccurred() { + return parent.getErrorsOccurred(); + } } diff --git a/org.lflang/src/org/lflang/federated/generator/SynchronizedErrorReporter.java b/org.lflang/src/org/lflang/federated/generator/SynchronizedErrorReporter.java index 571d2de5c7..08c47c4cd3 100644 --- a/org.lflang/src/org/lflang/federated/generator/SynchronizedErrorReporter.java +++ b/org.lflang/src/org/lflang/federated/generator/SynchronizedErrorReporter.java @@ -1,83 +1,83 @@ package org.lflang.federated.generator; import java.nio.file.Path; - import org.eclipse.emf.ecore.EObject; import org.eclipse.lsp4j.DiagnosticSeverity; - import org.lflang.ErrorReporter; import org.lflang.generator.Position; public class SynchronizedErrorReporter implements ErrorReporter { - private final ErrorReporter parent; - - public SynchronizedErrorReporter(ErrorReporter parent) { - this.parent = parent; - } - - @Override - public synchronized String reportError(String message) { - return parent.reportError(message); - } - - @Override - public synchronized String reportWarning(String message) { - return parent.reportWarning(message); - } - - @Override - public synchronized String reportInfo(String message) { - return parent.reportInfo(message); - } - - @Override - public synchronized String reportError(EObject object, String message) { - return parent.reportError(object, message); - } - - @Override - public synchronized String reportWarning(EObject object, String message) { - return parent.reportWarning(object, message); - } - - @Override - public synchronized String reportInfo(EObject object, String message) { - return parent.reportInfo(object, message); - } - - @Override - public synchronized String reportError(Path file, Integer line, String message) { - return parent.reportError(file, line, message); - } - - @Override - public synchronized String reportWarning(Path file, Integer line, String message) { - return parent.reportWarning(file, line, message); - } - - @Override - public synchronized String reportInfo(Path file, Integer line, String message) { - return parent.reportInfo(file, line, message); - } - - @Override - public synchronized String report(Path file, DiagnosticSeverity severity, String message) { - return parent.report(file, severity, message); - } - - @Override - public synchronized String report(Path file, DiagnosticSeverity severity, String message, int line) { - return parent.report(file, severity, message, line); - } - - @Override - public synchronized String report(Path file, DiagnosticSeverity severity, String message, Position startPos, Position endPos) { - return parent.report(file, severity, message, startPos, endPos); - } - - @Override - public synchronized boolean getErrorsOccurred() { - return parent.getErrorsOccurred(); - } + private final ErrorReporter parent; + + public SynchronizedErrorReporter(ErrorReporter parent) { + this.parent = parent; + } + + @Override + public synchronized String reportError(String message) { + return parent.reportError(message); + } + + @Override + public synchronized String reportWarning(String message) { + return parent.reportWarning(message); + } + + @Override + public synchronized String reportInfo(String message) { + return parent.reportInfo(message); + } + + @Override + public synchronized String reportError(EObject object, String message) { + return parent.reportError(object, message); + } + + @Override + public synchronized String reportWarning(EObject object, String message) { + return parent.reportWarning(object, message); + } + + @Override + public synchronized String reportInfo(EObject object, String message) { + return parent.reportInfo(object, message); + } + + @Override + public synchronized String reportError(Path file, Integer line, String message) { + return parent.reportError(file, line, message); + } + + @Override + public synchronized String reportWarning(Path file, Integer line, String message) { + return parent.reportWarning(file, line, message); + } + + @Override + public synchronized String reportInfo(Path file, Integer line, String message) { + return parent.reportInfo(file, line, message); + } + + @Override + public synchronized String report(Path file, DiagnosticSeverity severity, String message) { + return parent.report(file, severity, message); + } + + @Override + public synchronized String report( + Path file, DiagnosticSeverity severity, String message, int line) { + return parent.report(file, severity, message, line); + } + + @Override + public synchronized String report( + Path file, DiagnosticSeverity severity, String message, Position startPos, Position endPos) { + return parent.report(file, severity, message, startPos, endPos); + } + + @Override + public synchronized boolean getErrorsOccurred() { + return parent.getErrorsOccurred(); + } } diff --git a/org.lflang/src/org/lflang/federated/launcher/BuildConfig.java b/org.lflang/src/org/lflang/federated/launcher/BuildConfig.java index c1a01bfa47..47345af147 100644 --- a/org.lflang/src/org/lflang/federated/launcher/BuildConfig.java +++ b/org.lflang/src/org/lflang/federated/launcher/BuildConfig.java @@ -4,57 +4,48 @@ import org.lflang.federated.generator.FedFileConfig; import org.lflang.federated.generator.FederateInstance; -/** - * A collection of methods used for building target code for federates. - */ +/** 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(); - } + /** 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/CBuildConfig.java b/org.lflang/src/org/lflang/federated/launcher/CBuildConfig.java index bd88a719b4..d77ea3988c 100644 --- a/org.lflang/src/org/lflang/federated/launcher/CBuildConfig.java +++ b/org.lflang/src/org/lflang/federated/launcher/CBuildConfig.java @@ -1,16 +1,16 @@ /************* * 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 @@ -26,51 +26,51 @@ package org.lflang.federated.launcher; import java.io.File; - import org.lflang.ErrorReporter; import org.lflang.federated.generator.FedFileConfig; import org.lflang.federated.generator.FederateInstance; import org.lflang.generator.c.CCompiler; /** - * Utility class that can be used to create a launcher for federated LF programs - * that are written in C. + * Utility class that can be used to create a launcher for federated LF programs that are written in + * C. * * @author Soroush Bateni * @author Marten Lohstroh */ public class CBuildConfig extends BuildConfig { - public CBuildConfig(FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter) { - super(federate, fileConfig, errorReporter); - } + public CBuildConfig( + FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter) { + super(federate, fileConfig, errorReporter); + } - @Override - public String compileCommand() { + @Override + 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 (!federate.targetConfig.compileAdditionalSources.contains(linuxPlatformSupport)) { - federate.targetConfig.compileAdditionalSources.add(linuxPlatformSupport); - } - CCompiler cCompiler= new CCompiler(federate.targetConfig, fileConfig, errorReporter, false); - commandToReturn = String.join(" ", - cCompiler.compileCCommand( - fileConfig.name+"_"+federate.name, - false - ).toString()); - return commandToReturn; + 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 (!federate.targetConfig.compileAdditionalSources.contains(linuxPlatformSupport)) { + federate.targetConfig.compileAdditionalSources.add(linuxPlatformSupport); } + CCompiler cCompiler = new CCompiler(federate.targetConfig, fileConfig, errorReporter, false); + commandToReturn = + String.join( + " ", + cCompiler.compileCCommand(fileConfig.name + "_" + federate.name, false).toString()); + return commandToReturn; + } - @Override - public String remoteExecuteCommand() { - return "bin/"+fileConfig.name+"_"+federate.name+" -i '$FEDERATION_ID'"; - } + @Override + public String remoteExecuteCommand() { + return "bin/" + fileConfig.name + "_" + federate.name + " -i '$FEDERATION_ID'"; + } - @Override - public String localExecuteCommand() { - return fileConfig.getFedBinPath().resolve(federate.name)+" -i $FEDERATION_ID"; - } + @Override + public String localExecuteCommand() { + return fileConfig.getFedBinPath().resolve(federate.name) + " -i $FEDERATION_ID"; + } } diff --git a/org.lflang/src/org/lflang/federated/launcher/FedLauncherGenerator.java b/org.lflang/src/org/lflang/federated/launcher/FedLauncherGenerator.java index 3ad08e2450..23547ca1d0 100644 --- a/org.lflang/src/org/lflang/federated/launcher/FedLauncherGenerator.java +++ b/org.lflang/src/org/lflang/federated/launcher/FedLauncherGenerator.java @@ -1,25 +1,25 @@ /************* * 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 + * + * 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. ***************/ @@ -33,9 +33,7 @@ import java.nio.file.Path; import java.util.ArrayList; 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; @@ -43,453 +41,477 @@ /** * Utility class that can be used to create a launcher for federated LF programs. - * + * * @author Edward A. Lee * @author Soroush Bateni */ public class FedLauncherGenerator { - protected TargetConfig targetConfig; - protected FedFileConfig fileConfig; - protected ErrorReporter errorReporter; - - /** - * @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 FedLauncherGenerator(TargetConfig targetConfig, FedFileConfig fileConfig, ErrorReporter errorReporter) { - this.targetConfig = targetConfig; - this.fileConfig = fileConfig; - this.errorReporter = errorReporter; + protected TargetConfig targetConfig; + protected FedFileConfig fileConfig; + protected ErrorReporter errorReporter; + + /** + * @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 FedLauncherGenerator( + TargetConfig targetConfig, FedFileConfig fileConfig, ErrorReporter errorReporter) { + this.targetConfig = targetConfig; + this.fileConfig = fileConfig; + this.errorReporter = errorReporter; + } + + /** + * Create the launcher shell scripts. This will create one or two files in the output path (bin + * directory). The first has name equal to the filename of the source file without the ".lf" + * extension. This will be a shell script that launches the RTI and the federates. If, in + * addition, either the RTI or any federate is mapped to a particular machine (anything other than + * the default "localhost" or "0.0.0.0"), then this will generate a shell script in the bin + * directory with name filename_distribute.sh that copies the relevant source files to the remote + * host and compiles them so that they are ready to execute using the launcher. + * + *

A precondition for this to work is that the user invoking this code generator can log into + * the remote host without supplying a password. Specifically, you have to have installed your + * public key (typically found in ~/.ssh/id_rsa.pub) in ~/.ssh/authorized_keys on the remote host. + * In addition, the remote host must be running an ssh service. On an Arch Linux system using + * systemd, for example, this means running: + * + *

sudo systemctl ssh.service + * + *

Enable means to always start the service at startup, whereas start means to just start it + * this once. + * + *

On macOS, open System Preferences from the Apple menu and click on the "Sharing" preference + * panel. Select the checkbox next to "Remote Login" to enable it. + * + *

In addition, every host must have OpenSSL installed, with at least version 1.1.1a. You can + * check the version with + * + *

openssl version + * + * @param federates A list of federate instances in the federation + * @param rtiConfig Can have values for 'host', 'dir', and 'user' + */ + public void doGenerate(List federates, 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. + // What this means is that the shell that invokes the launcher + // needs to remain live for the duration of the federation. + // If that shell is killed, the federation will die. + // Hence, it is reasonable to launch the federation on a + // machine that participates in the federation, for example, + // on the machine that runs the RTI. The command I tried + // to get screen to work looks like this: + // ssh -t «target» cd «path»; screen -S «filename»_«federate.name» -L + // bin/«filename»_«federate.name» 2>&1 + // var outPath = binGenPath + StringBuilder shCode = new StringBuilder(); + StringBuilder distCode = new StringBuilder(); + shCode.append(getSetupCode() + "\n"); + String distHeader = getDistHeader(); + String host = rtiConfig.getHost(); + String target = host; + + String user = rtiConfig.getUser(); + if (user != null) { + target = user + "@" + host; } - /** - * Create the launcher shell scripts. This will create one or two files - * in the output path (bin directory). The first has name equal to - * the filename of the source file without the ".lf" extension. - * This will be a shell script that launches the - * RTI and the federates. If, in addition, either the RTI or any - * federate is mapped to a particular machine (anything other than - * the default "localhost" or "0.0.0.0"), then this will generate - * a shell script in the bin directory with name filename_distribute.sh - * that copies the relevant source files to the remote host and compiles - * them so that they are ready to execute using the launcher. - * - * A precondition for this to work is that the user invoking this - * code generator can log into the remote host without supplying - * a password. Specifically, you have to have installed your - * public key (typically found in ~/.ssh/id_rsa.pub) in - * ~/.ssh/authorized_keys on the remote host. In addition, the - * remote host must be running an ssh service. - * On an Arch Linux system using systemd, for example, this means - * running: - * - * sudo systemctl ssh.service - * - * Enable means to always start the service at startup, whereas - * start means to just start it this once. - * - * On macOS, open System Preferences from the Apple menu and - * click on the "Sharing" preference panel. Select the checkbox - * next to "Remote Login" to enable it. - * - * In addition, every host must have OpenSSL installed, with at least - * version 1.1.1a. You can check the version with - * - * openssl version - * - * @param federates A list of federate instances in the federation - * @param rtiConfig - * Can have values for 'host', 'dir', and 'user' - */ - public void doGenerate( - List federates, - 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. - // What this means is that the shell that invokes the launcher - // needs to remain live for the duration of the federation. - // If that shell is killed, the federation will die. - // Hence, it is reasonable to launch the federation on a - // machine that participates in the federation, for example, - // on the machine that runs the RTI. The command I tried - // to get screen to work looks like this: - // ssh -t «target» cd «path»; screen -S «filename»_«federate.name» -L bin/«filename»_«federate.name» 2>&1 - // var outPath = binGenPath - StringBuilder shCode = new StringBuilder(); - StringBuilder distCode = new StringBuilder(); - shCode.append(getSetupCode() + "\n"); - String distHeader = getDistHeader(); - String host = rtiConfig.getHost(); - String target = host; - - String user = rtiConfig.getUser(); - if (user != null) { - target = user + "@" + host; - } - - shCode.append("#### Host is " + host); - - // Launch the RTI in the foreground. - if (host.equals("localhost") || host.equals("0.0.0.0")) { - // FIXME: the paths below will not work on Windows - shCode.append(getLaunchCode(getRtiCommand(federates, false)) + "\n"); - } else { - // Start the RTI on the remote machine. - // FIXME: Should $FEDERATION_ID be used to ensure unique directories, executables, on the remote host? - // Copy the source code onto the remote machine and compile it there. - if(distCode.length() == 0) distCode.append(distHeader + "\n"); - - String logFileName = String.format("log/%s_RTI.log", fileConfig.name); - - // Launch the RTI on the remote machine using ssh and screen. - // The -t argument to ssh creates a virtual terminal, which is needed by screen. - // The -S gives the session a name. - // The -L option turns on logging. Unfortunately, the -Logfile giving the log file name - // is not standardized in screen. Logs go to screenlog.0 (or screenlog.n). - // FIXME: Remote errors are not reported back via ssh from screen. - // How to get them back to the local machine? - // Perhaps use -c and generate a screen command file to control the logfile name, - // but screen apparently doesn't write anything to the log file! - // - // The cryptic 2>&1 reroutes stderr to stdout so that both are returned. - // The sleep at the end prevents screen from exiting before outgoing messages from - // the federate have had time to go out to the RTI through the socket. - - shCode.append(getRemoteLaunchCode(host, target, logFileName, - getRtiCommand(federates, true)) + "\n"); - } - - // 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 = buildConfig.compileCommand(); - // FIXME: Should $FEDERATION_ID be used to ensure unique directories, executables, on the remote host? - 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 = buildConfig.localExecuteCommand(); - shCode.append(getFedLocalLaunchCode(federate, executeCommand, federateIndex++) + "\n"); - } - } - if (host.equals("localhost") || host.equals("0.0.0.0")) { - // Local PID managements - shCode.append("echo \"#### Bringing the RTI back to foreground so it can receive Control-C.\"" + "\n"); - shCode.append("fg %1" + "\n"); - } - // Wait for launched processes to finish - shCode.append(String.join("\n", - "echo \"RTI has exited. Wait for federates to exit.\"", - "# Wait for launched processes to finish.", - "# The errors are handled separately via trap.", - "for pid in \"${pids[@]}\"", - "do", - " wait $pid", - "done", - "echo \"All done.\"" - ) + "\n"); - - // Create bin directory for the script. - if (!Files.exists(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 " - + " in directory " - + fileConfig.binPath); - - // Write the launcher file. - // Delete file previously produced, if any. - File file = fileConfig.binPath.resolve(fileConfig.name).toFile(); - if (file.exists()) { - file.delete(); - } + shCode.append("#### Host is " + host); + + // Launch the RTI in the foreground. + if (host.equals("localhost") || host.equals("0.0.0.0")) { + // FIXME: the paths below will not work on Windows + shCode.append(getLaunchCode(getRtiCommand(federates, false)) + "\n"); + } else { + // Start the RTI on the remote machine. + // FIXME: Should $FEDERATION_ID be used to ensure unique directories, executables, on the + // remote host? + // Copy the source code onto the remote machine and compile it there. + if (distCode.length() == 0) distCode.append(distHeader + "\n"); + + String logFileName = String.format("log/%s_RTI.log", fileConfig.name); + + // Launch the RTI on the remote machine using ssh and screen. + // The -t argument to ssh creates a virtual terminal, which is needed by screen. + // The -S gives the session a name. + // The -L option turns on logging. Unfortunately, the -Logfile giving the log file name + // is not standardized in screen. Logs go to screenlog.0 (or screenlog.n). + // FIXME: Remote errors are not reported back via ssh from screen. + // How to get them back to the local machine? + // Perhaps use -c and generate a screen command file to control the logfile name, + // but screen apparently doesn't write anything to the log file! + // + // The cryptic 2>&1 reroutes stderr to stdout so that both are returned. + // The sleep at the end prevents screen from exiting before outgoing messages from + // the federate have had time to go out to the RTI through the socket. + + shCode.append( + getRemoteLaunchCode(host, target, logFileName, getRtiCommand(federates, true)) + "\n"); + } - 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); - } + // 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 = buildConfig.compileCommand(); + // FIXME: Should $FEDERATION_ID be used to ensure unique directories, executables, on the + // remote host? + 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 = buildConfig.localExecuteCommand(); + shCode.append(getFedLocalLaunchCode(federate, executeCommand, federateIndex++) + "\n"); + } + } + if (host.equals("localhost") || host.equals("0.0.0.0")) { + // Local PID managements + shCode.append( + "echo \"#### Bringing the RTI back to foreground so it can receive Control-C.\"" + "\n"); + shCode.append("fg %1" + "\n"); + } + // Wait for launched processes to finish + shCode.append( + String.join( + "\n", + "echo \"RTI has exited. Wait for federates to exit.\"", + "# Wait for launched processes to finish.", + "# The errors are handled separately via trap.", + "for pid in \"${pids[@]}\"", + "do", + " wait $pid", + "done", + "echo \"All done.\"") + + "\n"); + + // Create bin directory for the script. + if (!Files.exists(fileConfig.binPath)) { + try { + Files.createDirectories(fileConfig.binPath); + } catch (IOException e) { + errorReporter.reportError("Unable to create directory: " + fileConfig.binPath); + } + } - if (!file.setExecutable(true, false)) { - errorReporter.reportWarning("Unable to make launcher script executable."); - } + System.out.println( + "##### Generating launcher for federation " + " in directory " + fileConfig.binPath); - // Write the distributor file. - // Delete the file even if it does not get generated. - file = fileConfig.binPath.resolve(fileConfig.name + "_distribute.sh").toFile(); - if (file.exists()) { - file.delete(); - } - if (distCode.length() > 0) { - 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); - } - } + // Write the launcher file. + // Delete file previously produced, if any. + File file = fileConfig.binPath.resolve(fileConfig.name).toFile(); + if (file.exists()) { + file.delete(); } - private String getSetupCode() { - return String.join("\n", - "#!/bin/bash", - "# Launcher for federated " + fileConfig.name + ".lf Lingua Franca program.", - "# Uncomment to specify to behave as close as possible to the POSIX standard.", - "# set -o posix", - "", - "# Enable job control", - "set -m", - "shopt -s huponexit", - "", - "# Set a trap to kill all background jobs on error or control-C", - "# Use two distinct traps so we can see which signal causes this.", - "cleanup() {", - " printf \"Killing federate %s.\\n\" ${pids[*]}", - " # The || true clause means this is not an error if kill fails.", - " kill ${pids[@]} || true", - " printf \"#### Killing RTI %s.\\n\" ${RTI}", - " kill ${RTI} || true", - " exit 1", - "}", - "cleanup_err() {", - " echo \"#### Received ERR signal on line $1. Invoking cleanup().\"", - " cleanup", - "}", - "cleanup_sigint() {", - " echo \"#### Received SIGINT signal on line $1. Invoking cleanup().\"", - " cleanup", - "}", - "", - "trap 'cleanup_err $LINENO' ERR", - "trap 'cleanup_sigint $LINENO' SIGINT", - "", - "# Create a random 48-byte text ID for this federation.", - "# The likelihood of two federations having the same ID is 1/16,777,216 (1/2^24).", - "FEDERATION_ID=`openssl rand -hex 24`", - "echo \"Federate " + fileConfig.name + " in Federation ID '$FEDERATION_ID'\"", - "# Launch the federates:" - ); + 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); } - private String getDistHeader() { - return String.join("\n", - "#!/bin/bash", - "# Distributor for federated "+ fileConfig.name + ".lf Lingua Franca program.", - "# Uncomment to specify to behave as close as possible to the POSIX standard.", - "# set -o posix" - ); + if (!file.setExecutable(true, false)) { + errorReporter.reportWarning("Unable to make launcher script executable."); } - private String getRtiCommand(List federates, boolean isRemote) { - List commands = new ArrayList<>(); - if (isRemote) { - commands.add("RTI -i '${FEDERATION_ID}' \\"); - } else { - commands.add("RTI -i ${FEDERATION_ID} \\"); - } - if (targetConfig.auth) { - commands.add(" -a \\"); - } - if (targetConfig.tracing != null) { - commands.add(" -t \\"); - } - commands.addAll(List.of( - " -n "+federates.size()+" \\", - " -c "+targetConfig.clockSync.toString()+" \\" - )); - if (targetConfig.clockSync.equals(ClockSyncMode.ON)) { - commands.add("period " + targetConfig.clockSyncOptions.period.toNanoSeconds()+" \\"); - } - if (targetConfig.clockSync.equals(ClockSyncMode.ON) || - targetConfig.clockSync.equals(ClockSyncMode.INIT)) { - commands.add("exchanges-per-interval "+targetConfig.clockSyncOptions.trials+" \\"); + // Write the distributor file. + // Delete the file even if it does not get generated. + file = fileConfig.binPath.resolve(fileConfig.name + "_distribute.sh").toFile(); + if (file.exists()) { + file.delete(); + } + if (distCode.length() > 0) { + 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); } - commands.add("&"); - return String.join("\n", commands); + } catch (FileNotFoundException e) { + errorReporter.reportError("Unable to find file: " + file); + } catch (IOException e) { + errorReporter.reportError("Unable to write to file " + file); + } } - - private String getLaunchCode(String rtiLaunchCode) { - return String.join("\n", - "echo \"#### Launching the runtime infrastructure (RTI).\"", - "# First, check if the RTI is on the PATH", - "if ! command -v RTI &> /dev/null", - "then", - " echo \"RTI could not be found.\"", - " echo \"The source code can be obtained from https://github.com/lf-lang/reactor-c/tree/main/core/federated/RTI\"", - " exit", - "fi ", - "# The RTI is started first to allow proper boot-up", - "# before federates will try to connect.", - "# The RTI will be brought back to foreground", - "# to be responsive to user inputs after all federates", - "# are launched.", - rtiLaunchCode, - "# Store the PID of the RTI", - "RTI=$!", - "# Wait for the RTI to boot up before", - "# starting federates (this could be done by waiting for a specific output", - "# from the RTI, but here we use sleep)", - "sleep 1" - ); + } + + private String getSetupCode() { + return String.join( + "\n", + "#!/bin/bash", + "# Launcher for federated " + fileConfig.name + ".lf Lingua Franca program.", + "# Uncomment to specify to behave as close as possible to the POSIX standard.", + "# set -o posix", + "", + "# Enable job control", + "set -m", + "shopt -s huponexit", + "", + "# Set a trap to kill all background jobs on error or control-C", + "# Use two distinct traps so we can see which signal causes this.", + "cleanup() {", + " printf \"Killing federate %s.\\n\" ${pids[*]}", + " # The || true clause means this is not an error if kill fails.", + " kill ${pids[@]} || true", + " printf \"#### Killing RTI %s.\\n\" ${RTI}", + " kill ${RTI} || true", + " exit 1", + "}", + "cleanup_err() {", + " echo \"#### Received ERR signal on line $1. Invoking cleanup().\"", + " cleanup", + "}", + "cleanup_sigint() {", + " echo \"#### Received SIGINT signal on line $1. Invoking cleanup().\"", + " cleanup", + "}", + "", + "trap 'cleanup_err $LINENO' ERR", + "trap 'cleanup_sigint $LINENO' SIGINT", + "", + "# Create a random 48-byte text ID for this federation.", + "# The likelihood of two federations having the same ID is 1/16,777,216 (1/2^24).", + "FEDERATION_ID=`openssl rand -hex 24`", + "echo \"Federate " + fileConfig.name + " in Federation ID '$FEDERATION_ID'\"", + "# Launch the federates:"); + } + + private String getDistHeader() { + return String.join( + "\n", + "#!/bin/bash", + "# Distributor for federated " + fileConfig.name + ".lf Lingua Franca program.", + "# Uncomment to specify to behave as close as possible to the POSIX standard.", + "# set -o posix"); + } + + private String getRtiCommand(List federates, boolean isRemote) { + List commands = new ArrayList<>(); + if (isRemote) { + commands.add("RTI -i '${FEDERATION_ID}' \\"); + } else { + commands.add("RTI -i ${FEDERATION_ID} \\"); } - - private String getRemoteLaunchCode(Object host, Object target, String logFileName, String rtiLaunchString) { - return String.join("\n", - "echo \"#### Launching the runtime infrastructure (RTI) on remote host " + host + ".\"", - "# FIXME: Killing this ssh does not kill the remote process.", - "# A double -t -t option to ssh forces creation of a virtual terminal, which", - "# fixes the problem, but then the ssh command does not execute. The remote", - "# federate does not start!", - "ssh " + target + " 'mkdir -p log; \\", - " echo \"-------------- Federation ID: \"'$FEDERATION_ID' >> " + logFileName + "; \\", - " date >> " + logFileName + "; \\", - " echo \"Executing RTI: " + rtiLaunchString + "\" 2>&1 | tee -a " + logFileName + "; \\", - " # First, check if the RTI is on the PATH", - " if ! command -v RTI &> /dev/null", - " then", - " echo \"RTI could not be found.\"", - " echo \"The source code can be found in org.lflang/src/lib/core/federated/RTI\"", - " exit", - " fi", - " " + rtiLaunchString + " 2>&1 | tee -a " + logFileName + "' &", - "# Store the PID of the channel to RTI", - "RTI=$!", - "# Wait for the RTI to boot up before", - "# starting federates (this could be done by waiting for a specific output", - "# from the RTI, but here we use sleep)", - "sleep 5" - ); + if (targetConfig.auth) { + commands.add(" -a \\"); } - - private String getDistCode( - Path remoteBase, - FederateInstance federate, - Path remoteRelSrcGenPath, - String logFileName, - Path localAbsSrcGenPath, - String compileCommand) { - return String.join("\n", - "echo \"Making directory " + remoteBase - + " and subdirectories src-gen, bin, and log on host " - + getUserHost(federate.user, federate.host) - + "\"", - "# The >> syntax appends stdout to a file. The 2>&1 appends stderr to the same file.", - "ssh " + getUserHost(federate.user, federate.host) - + " '\\", - " mkdir -p " + remoteBase.resolve(remoteRelSrcGenPath).resolve("core") - + " " + remoteBase.resolve("bin") + " " - + remoteBase + "/log; \\", - " echo \"--------------\" >> " + remoteBase + "/" - + logFileName + "; \\", - " date >> " + remoteBase + "/" + logFileName + ";", - "'", - "pushd " + localAbsSrcGenPath - + " > /dev/null", - "echo \"Copying source files to host " - + getUserHost(federate.user, federate.host) - + "\"", - "scp -r * " - + getUserHost(federate.user, federate.host) + ":" - + remoteBase.resolve(remoteRelSrcGenPath), - "popd > /dev/null", - "echo \"Compiling on host " - + getUserHost(federate.user, federate.host) - + " using: " + compileCommand + "\"", - "ssh " + getUserHost(federate.user, federate.host) - + " 'cd " + remoteBase + "; \\", - " echo \"In " + remoteBase + " compiling with: " - + compileCommand + "\" >> " + logFileName - + " 2>&1; \\", - " # Capture the output in the log file and stdout. \\", - " " + compileCommand + " 2>&1 | tee -a " - + logFileName + ";' " - ); + if (targetConfig.tracing != null) { + commands.add(" -t \\"); } - - private String getUserHost(Object user, Object host) { - if (user == null) { - return host.toString(); - } - return user + "@" + host; + commands.addAll( + List.of( + " -n " + federates.size() + " \\", + " -c " + targetConfig.clockSync.toString() + " \\")); + if (targetConfig.clockSync.equals(ClockSyncMode.ON)) { + commands.add("period " + targetConfig.clockSyncOptions.period.toNanoSeconds() + " \\"); } - - private String getFedRemoteLaunchCode( - FederateInstance federate, - Path path, - String logFileName, - String executeCommand, - int federateIndex - ) { - return String.join("\n", - "echo \"#### Launching the federate "+federate.name+" on host "+getUserHost(federate.user, federate.host)+"\"", - "# FIXME: Killing this ssh does not kill the remote process.", - "# A double -t -t option to ssh forces creation of a virtual terminal, which", - "# fixes the problem, but then the ssh command does not execute. The remote", - "# federate does not start!", - "ssh "+getUserHost(federate.user, federate.host)+" '\\", - " cd "+path+"; \\", - " echo \"-------------- Federation ID: \"'$FEDERATION_ID' >> "+logFileName+"; \\", - " date >> "+logFileName+"; \\", - " echo \"In "+path+", executing: "+executeCommand+"\" 2>&1 | tee -a "+logFileName+"; \\", - " "+executeCommand+" 2>&1 | tee -a "+logFileName+"' &", - "pids["+federateIndex+"]=$!" - ); + if (targetConfig.clockSync.equals(ClockSyncMode.ON) + || targetConfig.clockSync.equals(ClockSyncMode.INIT)) { + commands.add("exchanges-per-interval " + targetConfig.clockSyncOptions.trials + " \\"); } - - private String getFedLocalLaunchCode(FederateInstance federate, String executeCommand, int federateIndex) { - return String.format(String.join("\n", - "echo \"#### Launching the federate %s.\"", - "%s &", - "pids[%s]=$!" - ), + commands.add("&"); + return String.join("\n", commands); + } + + private String getLaunchCode(String rtiLaunchCode) { + return String.join( + "\n", + "echo \"#### Launching the runtime infrastructure (RTI).\"", + "# First, check if the RTI is on the PATH", + "if ! command -v RTI &> /dev/null", + "then", + " echo \"RTI could not be found.\"", + " echo \"The source code can be obtained from" + + " https://github.com/lf-lang/reactor-c/tree/main/core/federated/RTI\"", + " exit", + "fi ", + "# The RTI is started first to allow proper boot-up", + "# before federates will try to connect.", + "# The RTI will be brought back to foreground", + "# to be responsive to user inputs after all federates", + "# are launched.", + rtiLaunchCode, + "# Store the PID of the RTI", + "RTI=$!", + "# Wait for the RTI to boot up before", + "# starting federates (this could be done by waiting for a specific output", + "# from the RTI, but here we use sleep)", + "sleep 1"); + } + + private String getRemoteLaunchCode( + Object host, Object target, String logFileName, String rtiLaunchString) { + return String.join( + "\n", + "echo \"#### Launching the runtime infrastructure (RTI) on remote host " + host + ".\"", + "# FIXME: Killing this ssh does not kill the remote process.", + "# A double -t -t option to ssh forces creation of a virtual terminal, which", + "# fixes the problem, but then the ssh command does not execute. The remote", + "# federate does not start!", + "ssh " + target + " 'mkdir -p log; \\", + " echo \"-------------- Federation ID: \"'$FEDERATION_ID' >> " + logFileName + "; \\", + " date >> " + logFileName + "; \\", + " echo \"Executing RTI: " + rtiLaunchString + "\" 2>&1 | tee -a " + logFileName + "; \\", + " # First, check if the RTI is on the PATH", + " if ! command -v RTI &> /dev/null", + " then", + " echo \"RTI could not be found.\"", + " echo \"The source code can be found in org.lflang/src/lib/core/federated/RTI\"", + " exit", + " fi", + " " + rtiLaunchString + " 2>&1 | tee -a " + logFileName + "' &", + "# Store the PID of the channel to RTI", + "RTI=$!", + "# Wait for the RTI to boot up before", + "# starting federates (this could be done by waiting for a specific output", + "# from the RTI, but here we use sleep)", + "sleep 5"); + } + + private String getDistCode( + Path remoteBase, + FederateInstance federate, + Path remoteRelSrcGenPath, + String logFileName, + Path localAbsSrcGenPath, + String compileCommand) { + return String.join( + "\n", + "echo \"Making directory " + + remoteBase + + " and subdirectories src-gen, bin, and log on host " + + getUserHost(federate.user, federate.host) + + "\"", + "# The >> syntax appends stdout to a file. The 2>&1 appends stderr to the same file.", + "ssh " + getUserHost(federate.user, federate.host) + " '\\", + " mkdir -p " + + remoteBase.resolve(remoteRelSrcGenPath).resolve("core") + + " " + + remoteBase.resolve("bin") + + " " + + remoteBase + + "/log; \\", + " echo \"--------------\" >> " + remoteBase + "/" + logFileName + "; \\", + " date >> " + remoteBase + "/" + logFileName + ";", + "'", + "pushd " + localAbsSrcGenPath + " > /dev/null", + "echo \"Copying source files to host " + getUserHost(federate.user, federate.host) + "\"", + "scp -r * " + + getUserHost(federate.user, federate.host) + + ":" + + remoteBase.resolve(remoteRelSrcGenPath), + "popd > /dev/null", + "echo \"Compiling on host " + + getUserHost(federate.user, federate.host) + + " using: " + + compileCommand + + "\"", + "ssh " + getUserHost(federate.user, federate.host) + " 'cd " + remoteBase + "; \\", + " echo \"In " + + remoteBase + + " compiling with: " + + compileCommand + + "\" >> " + + logFileName + + " 2>&1; \\", + " # Capture the output in the log file and stdout. \\", + " " + compileCommand + " 2>&1 | tee -a " + logFileName + ";' "); + } + + private String getUserHost(Object user, Object host) { + if (user == null) { + return host.toString(); + } + return user + "@" + host; + } + + private String getFedRemoteLaunchCode( + FederateInstance federate, + Path path, + String logFileName, + String executeCommand, + int federateIndex) { + return String.join( + "\n", + "echo \"#### Launching the federate " + + federate.name + + " on host " + + getUserHost(federate.user, federate.host) + + "\"", + "# FIXME: Killing this ssh does not kill the remote process.", + "# A double -t -t option to ssh forces creation of a virtual terminal, which", + "# fixes the problem, but then the ssh command does not execute. The remote", + "# federate does not start!", + "ssh " + getUserHost(federate.user, federate.host) + " '\\", + " cd " + path + "; \\", + " echo \"-------------- Federation ID: \"'$FEDERATION_ID' >> " + logFileName + "; \\", + " date >> " + logFileName + "; \\", + " echo \"In " + + path + + ", executing: " + + executeCommand + + "\" 2>&1 | tee -a " + + logFileName + + "; \\", + " " + executeCommand + " 2>&1 | tee -a " + logFileName + "' &", + "pids[" + federateIndex + "]=$!"); + } + + private String getFedLocalLaunchCode( + FederateInstance federate, String executeCommand, int federateIndex) { + return String.format( + String.join("\n", "echo \"#### Launching the federate %s.\"", "%s &", "pids[%s]=$!"), federate.name, 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(); - }; - } + } + + /** + * 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/PyBuildConfig.java b/org.lflang/src/org/lflang/federated/launcher/PyBuildConfig.java index 85d6dc4a0f..1a8a996349 100644 --- a/org.lflang/src/org/lflang/federated/launcher/PyBuildConfig.java +++ b/org.lflang/src/org/lflang/federated/launcher/PyBuildConfig.java @@ -6,17 +6,32 @@ public class PyBuildConfig extends BuildConfig { - public PyBuildConfig(FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter) { - super(federate, fileConfig, errorReporter); - } + 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 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'"; - } + @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 index 81c608d5b3..60e07c9f53 100644 --- a/org.lflang/src/org/lflang/federated/launcher/RtiConfig.java +++ b/org.lflang/src/org/lflang/federated/launcher/RtiConfig.java @@ -4,89 +4,66 @@ /** * Class for storing configuration settings pertaining to the RTI. + * * @author Marten Lohstroh */ public class RtiConfig { - private Path directory; + private Path directory; - /** - * The host on which the RTI process is to be spawned. - */ - private String host; + /** 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 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; + /** 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; - } + /** 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 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 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 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; - } + /** 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 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 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 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; - } + /** 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/TsBuildConfig.java b/org.lflang/src/org/lflang/federated/launcher/TsBuildConfig.java index 87b7ffaeb1..97f40c767a 100644 --- a/org.lflang/src/org/lflang/federated/launcher/TsBuildConfig.java +++ b/org.lflang/src/org/lflang/federated/launcher/TsBuildConfig.java @@ -1,59 +1,59 @@ /************* * 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 + * + * 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. ***************/ 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 TypeScript. - * + * Utility class that can be used to create a launcher for federated LF programs that are written in + * TypeScript. + * * @author Soroush Bateni * @author Hokeun Kim * @author Marten Lohstroh */ public class TsBuildConfig extends BuildConfig { - - public TsBuildConfig(FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter) { - super(federate, fileConfig, errorReporter); - } - - @Override - public String compileCommand() { - return null; - } - - @Override - public String localExecuteCommand() { - String jsFilename = federate.name + ".js"; - return "node "+fileConfig.getSrcGenPath().resolve(federate.name).resolve("dist").resolve(jsFilename)+" -i $FEDERATION_ID"; - } - + public TsBuildConfig( + FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter) { + super(federate, fileConfig, errorReporter); + } + + @Override + public String compileCommand() { + return null; + } + + @Override + 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/federated/serialization/FedNativePythonSerialization.java b/org.lflang/src/org/lflang/federated/serialization/FedNativePythonSerialization.java index e20588d62d..3947b1bd92 100644 --- a/org.lflang/src/org/lflang/federated/serialization/FedNativePythonSerialization.java +++ b/org.lflang/src/org/lflang/federated/serialization/FedNativePythonSerialization.java @@ -1,17 +1,17 @@ /************* * 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 @@ -32,91 +32,105 @@ /** * Enables support for Python pickle serialization. - * - * @author Soroush Bateni * + * @author Soroush Bateni */ public class FedNativePythonSerialization implements FedSerialization { - @Override - public boolean isCompatible(GeneratorBase generator) { - if (generator.getTarget() != Target.Python ) { - throw new UnsupportedOperationException("This class only support Python serialization."); - } - return true; + @Override + public boolean isCompatible(GeneratorBase generator) { + if (generator.getTarget() != Target.Python) { + throw new UnsupportedOperationException("This class only support Python serialization."); } + return true; + } - @Override - public String serializedBufferLength() { - return serializedVarName+".len"; - } + @Override + public String serializedBufferLength() { + return serializedVarName + ".len"; + } - @Override - public String seializedBufferVar() { - return serializedVarName+".buf"; - } + @Override + public String seializedBufferVar() { + return serializedVarName + ".buf"; + } - @Override - public StringBuilder generateNetworkSerializerCode(String varName, - String originalType) { - StringBuilder serializerCode = new StringBuilder(); - - // Check that global_pickler is not null - serializerCode.append("if (global_pickler == NULL) lf_print_error_and_exit(\"The pickle module is not loaded.\");\n"); - // Define the serialized PyObject - serializerCode.append("PyObject* serialized_pyobject = PyObject_CallMethod(global_pickler, \"dumps\", \"O\", "+varName+");\n"); - - // Error check - serializerCode.append("if (serialized_pyobject == NULL) {\n"); - serializerCode.append(" if (PyErr_Occurred()) PyErr_Print();\n"); - serializerCode.append(" lf_print_error_and_exit(\"Could not serialize serialized_pyobject.\");\n"); - serializerCode.append("}\n"); - - serializerCode.append("Py_buffer "+serializedVarName+";\n"); - serializerCode.append("int returnValue = PyBytes_AsStringAndSize(serialized_pyobject, &"+serializedVarName+".buf, &"+serializedVarName+".len);\n"); - // Error check - serializerCode.append("if (returnValue == -1) {\n"); - serializerCode.append(" if (PyErr_Occurred()) PyErr_Print();\n"); - serializerCode.append(" lf_print_error_and_exit(\"Could not serialize "+serializedVarName+".\");\n"); - serializerCode.append("}\n"); - - - - return serializerCode; - } + @Override + public StringBuilder generateNetworkSerializerCode(String varName, String originalType) { + StringBuilder serializerCode = new StringBuilder(); - @Override - public StringBuilder generateNetworkDeserializerCode(String varName, - String targetType) { - StringBuilder deserializerCode = new StringBuilder(); - - // Convert the network message to a Python ByteArray - deserializerCode.append("PyObject* message_byte_array = "+ - "PyBytes_FromStringAndSize((char*)"+varName+"->token->value, "+varName+"->token->length);\n"); - // Deserialize using Pickle - deserializerCode.append("Py_XINCREF(message_byte_array);\n"); - deserializerCode.append("PyObject* "+deserializedVarName+ - " = PyObject_CallMethod(global_pickler, \"loads\", \"O\", message_byte_array);\n"); - // Error check - deserializerCode.append("if ("+deserializedVarName+" == NULL) {\n"); - deserializerCode.append(" if (PyErr_Occurred()) PyErr_Print();\n"); - deserializerCode.append(" lf_print_error_and_exit(\"Could not deserialize "+deserializedVarName+".\");\n"); - deserializerCode.append("}\n"); - - // Decrment the reference count - deserializerCode.append("Py_XDECREF(message_byte_array);\n"); - - return deserializerCode; - } + // Check that global_pickler is not null + serializerCode.append( + "if (global_pickler == NULL) lf_print_error_and_exit(\"The pickle module is not" + + " loaded.\");\n"); + // Define the serialized PyObject + serializerCode.append( + "PyObject* serialized_pyobject = PyObject_CallMethod(global_pickler, \"dumps\", \"O\", " + + varName + + ");\n"); - @Override - public StringBuilder generatePreambleForSupport() { - return new StringBuilder(""); - } + // Error check + serializerCode.append("if (serialized_pyobject == NULL) {\n"); + serializerCode.append(" if (PyErr_Occurred()) PyErr_Print();\n"); + serializerCode.append( + " lf_print_error_and_exit(\"Could not serialize serialized_pyobject.\");\n"); + serializerCode.append("}\n"); - @Override - public StringBuilder generateCompilerExtensionForSupport() { - return new StringBuilder(""); - } + serializerCode.append("Py_buffer " + serializedVarName + ";\n"); + serializerCode.append( + "int returnValue = PyBytes_AsStringAndSize(serialized_pyobject, &" + + serializedVarName + + ".buf, &" + + serializedVarName + + ".len);\n"); + // Error check + serializerCode.append("if (returnValue == -1) {\n"); + serializerCode.append(" if (PyErr_Occurred()) PyErr_Print();\n"); + serializerCode.append( + " lf_print_error_and_exit(\"Could not serialize " + serializedVarName + ".\");\n"); + serializerCode.append("}\n"); + + return serializerCode; + } + + @Override + public StringBuilder generateNetworkDeserializerCode(String varName, String targetType) { + StringBuilder deserializerCode = new StringBuilder(); + + // Convert the network message to a Python ByteArray + deserializerCode.append( + "PyObject* message_byte_array = " + + "PyBytes_FromStringAndSize((char*)" + + varName + + "->token->value, " + + varName + + "->token->length);\n"); + // Deserialize using Pickle + deserializerCode.append("Py_XINCREF(message_byte_array);\n"); + deserializerCode.append( + "PyObject* " + + deserializedVarName + + " = PyObject_CallMethod(global_pickler, \"loads\", \"O\", message_byte_array);\n"); + // Error check + deserializerCode.append("if (" + deserializedVarName + " == NULL) {\n"); + deserializerCode.append(" if (PyErr_Occurred()) PyErr_Print();\n"); + deserializerCode.append( + " lf_print_error_and_exit(\"Could not deserialize " + deserializedVarName + ".\");\n"); + deserializerCode.append("}\n"); + + // Decrment the reference count + deserializerCode.append("Py_XDECREF(message_byte_array);\n"); + + return deserializerCode; + } + + @Override + public StringBuilder generatePreambleForSupport() { + return new StringBuilder(""); + } + @Override + public StringBuilder generateCompilerExtensionForSupport() { + return new StringBuilder(""); + } } diff --git a/org.lflang/src/org/lflang/federated/serialization/FedROS2CPPSerialization.java b/org.lflang/src/org/lflang/federated/serialization/FedROS2CPPSerialization.java index d116489dca..1384d11517 100644 --- a/org.lflang/src/org/lflang/federated/serialization/FedROS2CPPSerialization.java +++ b/org.lflang/src/org/lflang/federated/serialization/FedROS2CPPSerialization.java @@ -1,17 +1,17 @@ /************* * 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 @@ -32,179 +32,191 @@ /** * Enables support for ROS 2 serialization in C/C++ code. - * - * @author Soroush Bateni * + * @author Soroush Bateni */ public class FedROS2CPPSerialization implements FedSerialization { - /** - * Check whether the current generator is compatible with the given - * serialization technique or not. - * - * @param generator The current generator. - * @return true if compatible, false if not. - */ - @Override - public boolean isCompatible(GeneratorBase generator) { - if (generator.getTarget() != Target.C) { - generator.errorReporter.reportError("ROS serialization is currently only supported for the C target."); - return false; - } else if (!generator.getTargetConfig().compiler.equalsIgnoreCase("g++")) { - generator.errorReporter.reportError( - "Please use the 'compiler: \"g++\"' target property \n"+ - "for ROS serialization" - ); - return false; - } - return true; - } - - /** - * @return Expression in target language that corresponds to the length - * of the serialized buffer. - */ - @Override - public String serializedBufferLength() { - return serializedVarName+".size()"; + /** + * Check whether the current generator is compatible with the given serialization technique or + * not. + * + * @param generator The current generator. + * @return true if compatible, false if not. + */ + @Override + public boolean isCompatible(GeneratorBase generator) { + if (generator.getTarget() != Target.C) { + generator.errorReporter.reportError( + "ROS serialization is currently only supported for the C target."); + return false; + } else if (!generator.getTargetConfig().compiler.equalsIgnoreCase("g++")) { + generator.errorReporter.reportError( + "Please use the 'compiler: \"g++\"' target property \n" + "for ROS serialization"); + return false; } + return true; + } - /** - * @return Expression in target language that is the buffer variable - * itself. - */ - @Override - public String seializedBufferVar() { - return serializedVarName+".get_rcl_serialized_message().buffer"; - } - - /** - * Generate code in C++ that serializes 'varName'. This code - * will convert the data in 'varName' from its 'originalType' into an - * uint8_t. The serialized data will be put in a variable called - * 'serialized_message', defined by @see serializedVarName. - * - * @param varName The variable to be serialized. - * @param originalType The original type of the variable. - * @return Target code that serializes the 'varName' from 'type' - * to an unsigned byte array. - */ - @Override - public StringBuilder generateNetworkSerializerCode(String varName, String originalType) { - StringBuilder serializerCode = new StringBuilder(); - - serializerCode.append("rclcpp::SerializedMessage "+serializedVarName+"(0u);\n"); - // Use the port type verbatim here, which can result - // in compile error if it is not a valid ROS type - serializerCode.append("using MessageT = "+originalType+";\n"); - serializerCode.append("static rclcpp::Serialization _lf_serializer;\n"); - serializerCode.append("_lf_serializer.serialize_message(&"+varName+"->value , &"+serializedVarName+");\n"); - - return serializerCode; - } - - /** - * Variant of @see generateNetworkSerializerCode(String varName, String originalType) - * that also supports shared pointer (i.e., std::shared_ptr<>) definitions of ROS port - * types. - * @param isSharedPtrType Indicates whether the port type is a shared pointer or not. - */ - public StringBuilder generateNetworkSerializerCode(String varName, String originalType, boolean isSharedPtrType) { - StringBuilder serializerCode = new StringBuilder(); - - serializerCode.append("rclcpp::SerializedMessage "+serializedVarName+"(0u);\n"); - // Use the port type verbatim here, which can result - // in compile error if it is not a valid ROS type - serializerCode.append("using MessageT = "+originalType+";\n"); - serializerCode.append("static rclcpp::Serialization _lf_serializer;\n"); - if (isSharedPtrType) { - serializerCode.append("_lf_serializer.serialize_message("+varName+"->value.get() , &"+serializedVarName+");\n"); - } else { - serializerCode.append("_lf_serializer.serialize_message(&"+varName+"->value , &"+serializedVarName+");\n"); - } - - return serializerCode; - } + /** + * @return Expression in target language that corresponds to the length of the serialized buffer. + */ + @Override + public String serializedBufferLength() { + return serializedVarName + ".size()"; + } + /** + * @return Expression in target language that is the buffer variable itself. + */ + @Override + public String seializedBufferVar() { + return serializedVarName + ".get_rcl_serialized_message().buffer"; + } - - /** - * Generate code in C++ that deserializes 'varName'. This code will - * convert the data in 'varName' from a uint8_t into the 'targetType'. - * The deserialized data will be put in a variable called deserialized_message - * defined by @see deserializedVarName. - * - * @param varName The variable to deserialize. - * @param targetType The type to deserialize into. - * @return Target code that deserializes 'varName' from an unsigned byte array - * to 'type'. - */ - @Override - public StringBuilder generateNetworkDeserializerCode(String varName, String targetType) { - StringBuilder deserializerCode = new StringBuilder(); - - deserializerCode.append( - "auto message = std::make_unique( rcl_serialized_message_t{\n" - + " .buffer = (uint8_t*)"+varName+".tmplt.token->value,\n" - + " .buffer_length = "+varName+".tmplt.token->length,\n" - + " .buffer_capacity = "+varName+".tmplt.token->length,\n" - + " .allocator = rcl_get_default_allocator()\n" - + "});\n" - ); - deserializerCode.append("auto msg = std::make_unique(std::move(*message.get()));\n"); - deserializerCode.append(varName+".tmplt.token->value = NULL; // Manually move the data\n"); - // Use the port type verbatim here, which can result - // in compile error if it is not a valid ROS type - deserializerCode.append("using MessageT = "+targetType+";\n"); - deserializerCode.append( - "MessageT "+deserializedVarName+" = MessageT();\n" - + "auto _lf_serializer = rclcpp::Serialization();\n" - + "_lf_serializer.deserialize_message(msg.get(), &"+deserializedVarName+");\n" - ); - - return deserializerCode; - } + /** + * Generate code in C++ that serializes 'varName'. This code will convert the data in 'varName' + * from its 'originalType' into an uint8_t. The serialized data will be put in a variable called + * 'serialized_message', defined by @see serializedVarName. + * + * @param varName The variable to be serialized. + * @param originalType The original type of the variable. + * @return Target code that serializes the 'varName' from 'type' to an unsigned byte array. + */ + @Override + public StringBuilder generateNetworkSerializerCode(String varName, String originalType) { + StringBuilder serializerCode = new StringBuilder(); - /** - * @return Code in C that includes all the necessary preamble to enable - * support for ROS 2 serialization. - */ - @Override - public StringBuilder generatePreambleForSupport() { - StringBuilder preamble = new StringBuilder(); - - preamble.append( - "#include \"rcutils/allocator.h\"\n" - + "#include \"rclcpp/rclcpp.hpp\"\n" - + "#include \"rclcpp/serialization.hpp\"\n" - + "#include \"rclcpp/serialized_message.hpp\"\n" - ); - - return preamble; - } - - /** - * @return Code that should be appended to the CMakeLists.txt to enable - * support for ROS 2 serialization. - */ - @Override - public StringBuilder generateCompilerExtensionForSupport() { - StringBuilder cMakeExtension = new StringBuilder(); - - cMakeExtension.append( - "enable_language(CXX)\n" - + "set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wno-write-strings -O2\")\n" - + "\n" - + "find_package(ament_cmake REQUIRED)\n" - + "find_package(rclcpp REQUIRED)\n" - + "find_package(rclcpp_components REQUIRED)\n" - + "find_package(rcutils)\n" - + "find_package(rmw REQUIRED)\n" - + "\n" - + "ament_target_dependencies( ${LF_MAIN_TARGET} rclcpp rmw)" - ); - - return cMakeExtension; + serializerCode.append("rclcpp::SerializedMessage " + serializedVarName + "(0u);\n"); + // Use the port type verbatim here, which can result + // in compile error if it is not a valid ROS type + serializerCode.append("using MessageT = " + originalType + ";\n"); + serializerCode.append("static rclcpp::Serialization _lf_serializer;\n"); + serializerCode.append( + "_lf_serializer.serialize_message(&" + + varName + + "->value , &" + + serializedVarName + + ");\n"); + + return serializerCode; + } + + /** + * Variant of @see generateNetworkSerializerCode(String varName, String originalType) that also + * supports shared pointer (i.e., std::shared_ptr<>) definitions of ROS port types. + * + * @param isSharedPtrType Indicates whether the port type is a shared pointer or not. + */ + public StringBuilder generateNetworkSerializerCode( + String varName, String originalType, boolean isSharedPtrType) { + StringBuilder serializerCode = new StringBuilder(); + + serializerCode.append("rclcpp::SerializedMessage " + serializedVarName + "(0u);\n"); + // Use the port type verbatim here, which can result + // in compile error if it is not a valid ROS type + serializerCode.append("using MessageT = " + originalType + ";\n"); + serializerCode.append("static rclcpp::Serialization _lf_serializer;\n"); + if (isSharedPtrType) { + serializerCode.append( + "_lf_serializer.serialize_message(" + + varName + + "->value.get() , &" + + serializedVarName + + ");\n"); + } else { + serializerCode.append( + "_lf_serializer.serialize_message(&" + + varName + + "->value , &" + + serializedVarName + + ");\n"); } + return serializerCode; + } + + /** + * Generate code in C++ that deserializes 'varName'. This code will convert the data in 'varName' + * from a uint8_t into the 'targetType'. The deserialized data will be put in a variable called + * deserialized_message defined by @see deserializedVarName. + * + * @param varName The variable to deserialize. + * @param targetType The type to deserialize into. + * @return Target code that deserializes 'varName' from an unsigned byte array to 'type'. + */ + @Override + public StringBuilder generateNetworkDeserializerCode(String varName, String targetType) { + StringBuilder deserializerCode = new StringBuilder(); + + deserializerCode.append( + "auto message = std::make_unique( rcl_serialized_message_t{\n" + + " .buffer = (uint8_t*)" + + varName + + ".tmplt.token->value,\n" + + " .buffer_length = " + + varName + + ".tmplt.token->length,\n" + + " .buffer_capacity = " + + varName + + ".tmplt.token->length,\n" + + " .allocator = rcl_get_default_allocator()\n" + + "});\n"); + deserializerCode.append( + "auto msg = std::make_unique(std::move(*message.get()));\n"); + deserializerCode.append(varName + ".tmplt.token->value = NULL; // Manually move the data\n"); + // Use the port type verbatim here, which can result + // in compile error if it is not a valid ROS type + deserializerCode.append("using MessageT = " + targetType + ";\n"); + deserializerCode.append( + "MessageT " + + deserializedVarName + + " = MessageT();\n" + + "auto _lf_serializer = rclcpp::Serialization();\n" + + "_lf_serializer.deserialize_message(msg.get(), &" + + deserializedVarName + + ");\n"); + + return deserializerCode; + } + + /** + * @return Code in C that includes all the necessary preamble to enable support for ROS 2 + * serialization. + */ + @Override + public StringBuilder generatePreambleForSupport() { + StringBuilder preamble = new StringBuilder(); + + preamble.append( + "#include \"rcutils/allocator.h\"\n" + + "#include \"rclcpp/rclcpp.hpp\"\n" + + "#include \"rclcpp/serialization.hpp\"\n" + + "#include \"rclcpp/serialized_message.hpp\"\n"); + + return preamble; + } + + /** + * @return Code that should be appended to the CMakeLists.txt to enable support for ROS 2 + * serialization. + */ + @Override + public StringBuilder generateCompilerExtensionForSupport() { + StringBuilder cMakeExtension = new StringBuilder(); + + cMakeExtension.append( + "enable_language(CXX)\n" + + "set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wno-write-strings -O2\")\n" + + "\n" + + "find_package(ament_cmake REQUIRED)\n" + + "find_package(rclcpp REQUIRED)\n" + + "find_package(rclcpp_components REQUIRED)\n" + + "find_package(rcutils)\n" + + "find_package(rmw REQUIRED)\n" + + "\n" + + "ament_target_dependencies( ${LF_MAIN_TARGET} rclcpp rmw)"); + + return cMakeExtension; + } } diff --git a/org.lflang/src/org/lflang/federated/serialization/FedSerialization.java b/org.lflang/src/org/lflang/federated/serialization/FedSerialization.java index 9b7ba0b2eb..fab7201c21 100644 --- a/org.lflang/src/org/lflang/federated/serialization/FedSerialization.java +++ b/org.lflang/src/org/lflang/federated/serialization/FedSerialization.java @@ -3,80 +3,69 @@ import org.lflang.generator.GeneratorBase; /** - * Interface to enable support for automatic data serialization - * in target code. - * - * @author Soroush Bateni + * Interface to enable support for automatic data serialization in target code. * + * @author Soroush Bateni */ public interface FedSerialization { - - /** - * Variable name in the target language for the serialized data. - */ - final static String serializedVarName = "serialized_message"; - - /** - * Variable name in the target language for the deserialized data. - */ - final static String deserializedVarName = "deserialized_message"; - - /** - * Check whether the current generator is compatible with the given - * serialization technique or not. - * - * @param generator The current generator. - * @return true if compatible, false if not. - */ - public boolean isCompatible(GeneratorBase generator); - /** - * @return Expression in target language that corresponds to the length - * of the serialized buffer. - */ - public String serializedBufferLength(); - - /** - * @return Expression in target language that is the buffer variable - * itself. - */ - public String seializedBufferVar(); - - /** - * Generate code in target language that serializes 'varName'. This code - * will convert the data in 'varName' from its 'originalType' into an - * unsigned byte array. The serialized data will be put in a variable - * with the name defined by @see serializedVarName. - * - * @param varName The variable to be serialized. - * @param originalType The original type of the variable. - * @return Target code that serializes the 'varName' from 'type' - * to an unsigned byte array. - */ - public StringBuilder generateNetworkSerializerCode(String varName, String originalType); - - /** - * Generate code in target language that deserializes 'varName'. This code will - * convert the data in 'varName' from an unsigned byte array into the 'targetType'. - * The deserialized data will be put in a variable with the name defined by - * @see deserializedVarName. - * - * @param varName The variable to deserialize. - * @param targetType The type to deserialize into. - * @return Target code that deserializes 'varName' from an unsigned byte array - * to 'type'. - */ - public StringBuilder generateNetworkDeserializerCode(String varName, String targetType); - - /** - * @return Code in target language that includes all the necessary preamble to enable - * support for the current serializer. - */ - public StringBuilder generatePreambleForSupport(); - - /** - * @return Code that should be appended to the compiler instructions (e.g., flags to gcc or - * additional lines to a CMakeLists.txt) to enable support for the current serializer. - */ - public StringBuilder generateCompilerExtensionForSupport(); + /** Variable name in the target language for the serialized data. */ + static final String serializedVarName = "serialized_message"; + + /** Variable name in the target language for the deserialized data. */ + static final String deserializedVarName = "deserialized_message"; + + /** + * Check whether the current generator is compatible with the given serialization technique or + * not. + * + * @param generator The current generator. + * @return true if compatible, false if not. + */ + public boolean isCompatible(GeneratorBase generator); + + /** + * @return Expression in target language that corresponds to the length of the serialized buffer. + */ + public String serializedBufferLength(); + + /** + * @return Expression in target language that is the buffer variable itself. + */ + public String seializedBufferVar(); + + /** + * Generate code in target language that serializes 'varName'. This code will convert the data in + * 'varName' from its 'originalType' into an unsigned byte array. The serialized data will be put + * in a variable with the name defined by @see serializedVarName. + * + * @param varName The variable to be serialized. + * @param originalType The original type of the variable. + * @return Target code that serializes the 'varName' from 'type' to an unsigned byte array. + */ + public StringBuilder generateNetworkSerializerCode(String varName, String originalType); + + /** + * Generate code in target language that deserializes 'varName'. This code will convert the data + * in 'varName' from an unsigned byte array into the 'targetType'. The deserialized data will be + * put in a variable with the name defined by + * + * @see deserializedVarName. + * @param varName The variable to deserialize. + * @param targetType The type to deserialize into. + * @return Target code that deserializes 'varName' from an unsigned byte array to 'type'. + */ + public StringBuilder generateNetworkDeserializerCode(String varName, String targetType); + + /** + * @return Code in target language that includes all the necessary preamble to enable support for + * the current serializer. + */ + public StringBuilder generatePreambleForSupport(); + + /** + * @return Code that should be appended to the compiler instructions (e.g., flags to gcc or + * additional lines to a CMakeLists.txt) to enable support for the current serializer. + */ + public StringBuilder generateCompilerExtensionForSupport(); } diff --git a/org.lflang/src/org/lflang/federated/serialization/SupportedSerializers.java b/org.lflang/src/org/lflang/federated/serialization/SupportedSerializers.java index 332ab6152c..4767f3f4d7 100644 --- a/org.lflang/src/org/lflang/federated/serialization/SupportedSerializers.java +++ b/org.lflang/src/org/lflang/federated/serialization/SupportedSerializers.java @@ -2,23 +2,25 @@ /** * The supported serializers. + * * @author Soroush Bateni */ public enum SupportedSerializers { - NATIVE("native"), // Dangerous: just copies the memory layout of the sender - ROS2("ros2"), - PROTO("proto"); + NATIVE("native"), // Dangerous: just copies the memory layout of the sender + ROS2("ros2"), + PROTO("proto"); - private String serializer; - SupportedSerializers(String serializer) { - this.serializer = serializer; - } - - public String getSerializer() { - return serializer; - } - - public void setSerializer(String serializer) { - this.serializer = serializer; - } + private String serializer; + + SupportedSerializers(String serializer) { + this.serializer = serializer; + } + + public String getSerializer() { + return serializer; + } + + public void setSerializer(String serializer) { + this.serializer = serializer; + } } diff --git a/org.lflang/src/org/lflang/federated/validation/FedValidator.java b/org.lflang/src/org/lflang/federated/validation/FedValidator.java index 49add78557..9aeb535d2f 100644 --- a/org.lflang/src/org/lflang/federated/validation/FedValidator.java +++ b/org.lflang/src/org/lflang/federated/validation/FedValidator.java @@ -3,9 +3,8 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; - -import org.lflang.ast.ASTUtils; import org.lflang.ErrorReporter; +import org.lflang.ast.ASTUtils; import org.lflang.lf.Input; import org.lflang.lf.Instantiation; import org.lflang.lf.Output; @@ -13,57 +12,55 @@ import org.lflang.lf.Reactor; import org.lflang.lf.VarRef; -/** - * Helper class that is used to validate a federated reactor. - */ +/** Helper class that is used to validate a federated reactor. */ public class FedValidator { - public static void validateFederatedReactor(Reactor reactor, ErrorReporter errorReporter) { - if (!reactor.isFederated()) return; + public static void validateFederatedReactor(Reactor reactor, ErrorReporter errorReporter) { + if (!reactor.isFederated()) return; - // 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(reactor); - 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() - ); - containsAllVarRefs(allVarRefsReferencingFederates, errorReporter); - } + // 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(reactor); + 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()); + containsAllVarRefs(allVarRefsReferencingFederates, errorReporter); } + } - /** - * Check if this federate contains all the {@code varRefs}. If not, report an error using {@code errorReporter}. - */ - private static void containsAllVarRefs(List varRefs, ErrorReporter errorReporter) { - var referencesFederate = false; - Instantiation instantiation = null; - for (VarRef varRef : varRefs) { - if (instantiation == null) { - instantiation = varRef.getContainer(); - referencesFederate = true; - } else if (!varRef.getContainer().equals(instantiation)) { - errorReporter.reportError(varRef, "Mixed triggers and effects from" + - " different federates. This is not permitted"); - } - } - + /** + * Check if this federate contains all the {@code varRefs}. If not, report an error using {@code + * errorReporter}. + */ + private static void containsAllVarRefs(List varRefs, ErrorReporter errorReporter) { + var referencesFederate = false; + Instantiation instantiation = null; + for (VarRef varRef : varRefs) { + if (instantiation == null) { + instantiation = varRef.getContainer(); + referencesFederate = true; + } else if (!varRef.getContainer().equals(instantiation)) { + errorReporter.reportError( + varRef, + "Mixed triggers and effects from" + " different federates. This is not permitted"); + } } + } } diff --git a/org.lflang/src/org/lflang/formatting2/LFFormatter.java b/org.lflang/src/org/lflang/formatting2/LFFormatter.java index 2a5e60cdc8..7df4053eef 100644 --- a/org.lflang/src/org/lflang/formatting2/LFFormatter.java +++ b/org.lflang/src/org/lflang/formatting2/LFFormatter.java @@ -4,8 +4,8 @@ package org.lflang.formatting2; +import com.google.inject.Inject; import java.util.List; - import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.formatting2.FormatterRequest; import org.eclipse.xtext.formatting2.IFormatter2; @@ -15,38 +15,32 @@ import org.eclipse.xtext.util.CancelIndicator; import org.eclipse.xtext.validation.CheckMode; import org.eclipse.xtext.validation.IResourceValidator; - import org.lflang.ast.FormattingUtils; -import com.google.inject.Inject; - public class LFFormatter implements IFormatter2 { - @Inject - private IResourceValidator validator; + @Inject private IResourceValidator validator; - @Override - public List format(FormatterRequest request) { - // TODO: Use a CancelIndicator that actually cancels? - if (!request.getTextRegionAccess().getResource().getErrors().isEmpty() - || !validator.validate( + @Override + public List format(FormatterRequest request) { + // TODO: Use a CancelIndicator that actually cancels? + if (!request.getTextRegionAccess().getResource().getErrors().isEmpty() + || !validator + .validate( request.getTextRegionAccess().getResource(), CheckMode.ALL, - CancelIndicator.NullImpl - ).isEmpty() - ) { - return List.of(); - } - ITextSegment documentRegion = request.getTextRegionAccess().regionForDocument(); - List documentContents = request.getTextRegionAccess().getResource().getContents(); - if (documentContents.isEmpty()) return List.of(); - return List.of( - new TextReplacement( - request.getTextRegionAccess(), - documentRegion.getOffset(), - documentRegion.getLength(), - FormattingUtils.render(documentContents.get(0)) - ) - ); + CancelIndicator.NullImpl) + .isEmpty()) { + return List.of(); } + ITextSegment documentRegion = request.getTextRegionAccess().regionForDocument(); + List documentContents = request.getTextRegionAccess().getResource().getContents(); + if (documentContents.isEmpty()) return List.of(); + return List.of( + new TextReplacement( + request.getTextRegionAccess(), + documentRegion.getOffset(), + documentRegion.getLength(), + FormattingUtils.render(documentContents.get(0)))); + } } diff --git a/org.lflang/src/org/lflang/generator/ActionInstance.java b/org.lflang/src/org/lflang/generator/ActionInstance.java index bdc8793383..b749a26426 100644 --- a/org.lflang/src/org/lflang/generator/ActionInstance.java +++ b/org.lflang/src/org/lflang/generator/ActionInstance.java @@ -1,28 +1,28 @@ /* Instance of an action. */ /************* -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.generator; @@ -32,69 +32,69 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY /** * Instance of an action. + * * @author Edward A. Lee * @author Marten Lohstroh */ public class ActionInstance extends TriggerInstance { - /** The constant default for a minimum delay. */ - public static final TimeValue DEFAULT_MIN_DELAY = TimeValue.ZERO; + /** The constant default for a minimum delay. */ + public static final TimeValue DEFAULT_MIN_DELAY = TimeValue.ZERO; - /** The minimum delay for this action. */ - private TimeValue minDelay = DEFAULT_MIN_DELAY; + /** The minimum delay for this action. */ + private TimeValue minDelay = DEFAULT_MIN_DELAY; - /** The minimum spacing between action events. */ - private TimeValue minSpacing = null; + /** The minimum spacing between action events. */ + private TimeValue minSpacing = null; - /** The replacement policy for when minimum spacing is violated. */ - private String policy = null; + /** The replacement policy for when minimum spacing is violated. */ + private String policy = null; - /** Indicator of whether the action is physical. */ - private boolean physical; + /** Indicator of whether the action is physical. */ + private boolean physical; - /** - * Create a new action instance. - * If the definition is null, then this is a shutdown action. - * @param definition The AST definition, or null for startup. - * @param parent The parent reactor. - */ - public ActionInstance(Action definition, ReactorInstance parent) { - super(definition, parent); - if (parent == null) { - throw new InvalidSourceException("Cannot create an ActionInstance with no parent."); - } - if (definition != null) { - if (definition.getMinDelay() != null) { - this.minDelay = parent.getTimeValue(definition.getMinDelay()); - } - if (definition.getMinSpacing() != null) { - this.minSpacing = parent.getTimeValue(definition.getMinSpacing()); - } - if (definition.getOrigin() == ActionOrigin.PHYSICAL) { - physical = true; - } - policy = definition.getPolicy(); - } + /** + * Create a new action instance. If the definition is null, then this is a shutdown action. + * + * @param definition The AST definition, or null for startup. + * @param parent The parent reactor. + */ + public ActionInstance(Action definition, ReactorInstance parent) { + super(definition, parent); + if (parent == null) { + throw new InvalidSourceException("Cannot create an ActionInstance with no parent."); } - - /** Return the minimum delay for this action. */ - public TimeValue getMinDelay() { - return minDelay; + if (definition != null) { + if (definition.getMinDelay() != null) { + this.minDelay = parent.getTimeValue(definition.getMinDelay()); + } + if (definition.getMinSpacing() != null) { + this.minSpacing = parent.getTimeValue(definition.getMinSpacing()); + } + if (definition.getOrigin() == ActionOrigin.PHYSICAL) { + physical = true; + } + policy = definition.getPolicy(); } - - /** Return the minimum spacing between action events. */ - public TimeValue getMinSpacing() { - return minSpacing; - } - - /** Return the replacement policy for when minimum spacing is violated. */ - public String getPolicy() { - return policy; - } - - /** Return the indicator of whether the action is physical. */ - public boolean isPhysical() { - return physical; - } - + } + + /** Return the minimum delay for this action. */ + public TimeValue getMinDelay() { + return minDelay; + } + + /** Return the minimum spacing between action events. */ + public TimeValue getMinSpacing() { + return minSpacing; + } + + /** Return the replacement policy for when minimum spacing is violated. */ + public String getPolicy() { + return policy; + } + + /** Return the indicator of whether the action is physical. */ + public boolean isPhysical() { + return physical; + } } diff --git a/org.lflang/src/org/lflang/generator/CodeBuilder.java b/org.lflang/src/org/lflang/generator/CodeBuilder.java index 2b77239680..e4e92294fb 100644 --- a/org.lflang/src/org/lflang/generator/CodeBuilder.java +++ b/org.lflang/src/org/lflang/generator/CodeBuilder.java @@ -1,533 +1,555 @@ package org.lflang.generator; -import java.nio.file.Path; -import java.io.IOException; +import static org.lflang.generator.c.CMixedRadixGenerator.*; +import static org.lflang.util.StringUtil.joinObjects; +import java.io.IOException; +import java.nio.file.Path; import org.eclipse.emf.common.CommonPlugin; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; -import org.lflang.federated.generator.FederateInstance; import org.lflang.generator.c.CUtil; import org.lflang.lf.Code; import org.lflang.util.FileUtil; -import static org.lflang.generator.c.CMixedRadixGenerator.*; -import static org.lflang.util.StringUtil.joinObjects; /** - * Helper class for printing code with indentation. - * This class is backed by a StringBuilder and is used to accumulate - * code to be printed to a file. Its main function is to handle indentation. + * Helper class for printing code with indentation. This class is backed by a StringBuilder and is + * used to accumulate code to be printed to a file. Its main function is to handle indentation. * * @author Edward A. Lee * @author Peter Donovan */ public class CodeBuilder { - /** - * Construct a new empty code emitter. - */ - public CodeBuilder() {} - - /** - * Construct a new code emitter with the text and indentation - * of the specified code emitter. - * @param model The model code emitter. - */ - public CodeBuilder(CodeBuilder model) { - indentation = model.indentation; - code.append(model); - } - - ///////////////////////////////////////////// - ///// Public methods. - - /** - * Get the code produced so far. - * @return The code produced so far as a String. - */ - public String getCode() { - return code.toString(); - } - - /** - * Increase the indentation of the output code produced. - */ - public void indent() { - indentation += " "; - } - - /** - * Insert the specified text at the specified position. - * @param position The position. - * @param text The text. - */ - public void insert(int position, String text) { - code.insert(position, text); - } - - /** - * Return the length of the code in characters. - */ - public int length() { - return code.length(); - } - - /** - * Add a new line. - */ - public void newLine() { - this.pr(""); - } - - /** - * Append the specified text plus a final newline. - * @param format A format string to be used by {@code String.format} or - * the text to append if no further arguments are given. - * @param args Additional arguments to pass to the formatter. - */ - public void pr(String format, Object... args) { - pr( - (args != null && args.length > 0) ? String.format(format, args) : format - ); - } - - /** - * Append the given text to the code buffer at the current indentation level. - */ - public void pr(CharSequence text) { - for (String line : (Iterable) () -> text.toString().lines().iterator()) { - code.append(indentation).append(line).append("\n"); - } + /** Construct a new empty code emitter. */ + public CodeBuilder() {} + + /** + * Construct a new code emitter with the text and indentation of the specified code emitter. + * + * @param model The model code emitter. + */ + public CodeBuilder(CodeBuilder model) { + indentation = model.indentation; + code.append(model); + } + + ///////////////////////////////////////////// + ///// Public methods. + + /** + * Get the code produced so far. + * + * @return The code produced so far as a String. + */ + public String getCode() { + return code.toString(); + } + + /** Increase the indentation of the output code produced. */ + public void indent() { + indentation += " "; + } + + /** + * Insert the specified text at the specified position. + * + * @param position The position. + * @param text The text. + */ + public void insert(int position, String text) { + code.insert(position, text); + } + + /** Return the length of the code in characters. */ + public int length() { + return code.length(); + } + + /** Add a new line. */ + public void newLine() { + this.pr(""); + } + + /** + * Append the specified text plus a final newline. + * + * @param format A format string to be used by {@code String.format} or the text to append if no + * further arguments are given. + * @param args Additional arguments to pass to the formatter. + */ + public void pr(String format, Object... args) { + pr((args != null && args.length > 0) ? String.format(format, args) : format); + } + + /** Append the given text to the code buffer at the current indentation level. */ + public void pr(CharSequence text) { + for (String line : (Iterable) () -> text.toString().lines().iterator()) { + code.append(indentation).append(line).append("\n"); } - - /** - * Version of pr() that prints a source line number using a #line - * prior to each line of the output. Use this when multiple lines of - * output code are all due to the same source line in the .lf file. - * @param eObject The AST node that this source line is based on. - * @param text The text to append. - */ - public void pr(EObject eObject, Object text) { - var split = text.toString().split("\n"); - for (String line : split) { - prSourceLineNumber(eObject); - pr(line); - } + } + + /** + * Version of pr() that prints a source line number using a #line prior to each line of the + * output. Use this when multiple lines of output code are all due to the same source line in the + * .lf file. + * + * @param eObject The AST node that this source line is based on. + * @param text The text to append. + */ + public void pr(EObject eObject, Object text) { + var split = text.toString().split("\n"); + for (String line : split) { + prSourceLineNumber(eObject); + pr(line); } - - /** Print the #line compiler directive with the line number of - * the specified object. - * @param eObject The node. - */ - public void prSourceLineNumber(EObject eObject) { - var node = NodeModelUtils.getNode(eObject); - if (node != null) { - // For code blocks (delimited by {= ... =}, unfortunately, - // we have to adjust the offset by the number of newlines before {=. - // Unfortunately, this is complicated because the code has been - // tokenized. - var offset = 0; - if (eObject instanceof Code) { - offset += 1; - } - // Extract the filename from eResource, an astonishingly difficult thing to do. - String filePath = CommonPlugin.resolve(eObject.eResource().getURI()).path(); - pr("#line " + (node.getStartLine() + offset) + " \"" + filePath + "\""); - } + } + + /** + * Print the #line compiler directive with the line number of the specified object. + * + * @param eObject The node. + */ + public void prSourceLineNumber(EObject eObject) { + var node = NodeModelUtils.getNode(eObject); + if (node != null) { + // For code blocks (delimited by {= ... =}, unfortunately, + // we have to adjust the offset by the number of newlines before {=. + // Unfortunately, this is complicated because the code has been + // tokenized. + var offset = 0; + if (eObject instanceof Code) { + offset += 1; + } + // Extract the filename from eResource, an astonishingly difficult thing to do. + String filePath = CommonPlugin.resolve(eObject.eResource().getURI()).path(); + pr("#line " + (node.getStartLine() + offset) + " \"" + filePath + "\""); } - - /** - * Append a single-line, C-style comment to the code. - * @param comment The comment. - */ - public void prComment(String comment) { - pr("// " + comment); + } + + /** + * Append a single-line, C-style comment to the code. + * + * @param comment The comment. + */ + public void prComment(String comment) { + pr("// " + comment); + } + + /** + * Remove all lines that start with the specified prefix and return a new CodeBuilder with the + * result. + * + * @param prefix The prefix. + */ + public CodeBuilder removeLines(String prefix) { + String separator = "\n"; + String[] lines = toString().split(separator); + + CodeBuilder builder = new CodeBuilder(); + + for (String line : lines) { + String trimmedLine = line.trim(); + if (!trimmedLine.startsWith(prefix)) { + builder.pr(line); + } } - - /** - * Remove all lines that start with the specified prefix - * and return a new CodeBuilder with the result. - * @param prefix The prefix. - */ - public CodeBuilder removeLines(String prefix) { - String separator = "\n"; - String[] lines = toString().split(separator); - - CodeBuilder builder = new CodeBuilder(); - - for(String line : lines) { - String trimmedLine = line.trim(); - if(!trimmedLine.startsWith(prefix)) { - builder.pr(line); - } - } - return builder; + return builder; + } + + /** + * Start a scoped block, which is a section of code surrounded by curley braces and indented. This + * must be followed by an {@link #endScopedBlock()}. + */ + public void startScopedBlock() { + pr("{"); + indent(); + } + + /** + * Start a scoped block for the specified reactor. If the reactor is a bank, then this starts a + * for loop that iterates over the bank members using a standard index variable whose name is that + * returned by {@link CUtil#bankIndex(ReactorInstance)}. If the reactor is null or is not a bank, + * then this simply starts a scoped block by printing an opening curly brace. This also adds a + * declaration of a pointer to the self struct of the reactor or bank member. + * + *

This block is intended to be nested, where each block is put within a similar block for the + * reactor's parent. This ensures that all (possibly nested) bank index variables are defined + * within the block. + * + *

This must be followed by an {@link #endScopedBlock()}. + * + * @param reactor The reactor instance. + */ + public void startScopedBlock(ReactorInstance reactor) { + if (reactor != null && reactor.isBank()) { + var index = CUtil.bankIndexName(reactor); + pr("// Reactor is a bank. Iterate over bank members."); + pr("for (int " + index + " = 0; " + index + " < " + reactor.width + "; " + index + "++) {"); + indent(); + } else { + startScopedBlock(); } - - /** - * Start a scoped block, which is a section of code - * surrounded by curley braces and indented. - * This must be followed by an {@link #endScopedBlock()}. - */ - public void startScopedBlock() { - pr("{"); - indent(); + } + + /** + * If the specified port is a multiport, then start a specified iteration over the channels of the + * multiport using as the channel index the variable name returned by {@link + * CUtil#channelIndex(PortInstance)}. If the port is not a multiport, do nothing. This is required + * to be followed by {@link #endChannelIteration(PortInstance)}. + * + * @param port The port. + */ + public void startChannelIteration(PortInstance port) { + if (port.isMultiport) { + var channel = CUtil.channelIndexName(port); + pr("// Port " + port.getFullName() + " is a multiport. Iterate over its channels."); + pr( + "for (int " + + channel + + " = 0; " + + channel + + " < " + + port.width + + "; " + + channel + + "++) {"); + indent(); } - - /** - * Start a scoped block for the specified reactor. - * If the reactor is a bank, then this starts a for loop - * that iterates over the bank members using a standard index - * variable whose name is that returned by {@link CUtil#bankIndex(ReactorInstance)}. - * If the reactor is null or is not a bank, then this simply - * starts a scoped block by printing an opening curly brace. - * This also adds a declaration of a pointer to the self - * struct of the reactor or bank member. - * - * This block is intended to be nested, where each block is - * put within a similar block for the reactor's parent. - * This ensures that all (possibly nested) bank index variables - * are defined within the block. - * - * This must be followed by an {@link #endScopedBlock()}. - * - * @param reactor The reactor instance. - */ - public void startScopedBlock( - ReactorInstance reactor - ) { - if (reactor != null && reactor.isBank()) { - var index = CUtil.bankIndexName(reactor); - pr("// Reactor is a bank. Iterate over bank members."); - pr("for (int "+index+" = 0; "+index+" < "+reactor.width+"; "+index+"++) {"); - indent(); - } else { - startScopedBlock(); - } + } + + /** + * Start a scoped block to iterate over bank members and channels for the specified port with a + * variable with the name given by count counting the iterations. If this port is a multiport, + * then the channel index variable name is that returned by {@link + * CUtil#channelIndex(PortInstance)}. + * + *

This block is intended to be nested, where each block is put within a similar block for the + * reactor's parent. + * + *

This is required to be followed by a call to {@link + * #endScopedBankChannelIteration(PortInstance, String)}. + * + * @param port The port. + * @param count The variable name to use for the counter, or null to not provide a counter. + */ + public void startScopedBankChannelIteration(PortInstance port, String count) { + if (count != null) { + startScopedBlock(); + pr("int " + count + " = 0;"); } - - /** - * If the specified port is a multiport, then start a specified iteration - * over the channels of the multiport using as the channel index the - * variable name returned by {@link CUtil#channelIndex(PortInstance)}. - * If the port is not a multiport, do nothing. - * This is required to be followed by {@link #endChannelIteration(PortInstance)}. - * @param port The port. - */ - public void startChannelIteration(PortInstance port) { - if (port.isMultiport) { - var channel = CUtil.channelIndexName(port); - pr("// Port "+port.getFullName()+" is a multiport. Iterate over its channels."); - pr("for (int "+channel+" = 0; "+channel+" < "+port.width+"; "+channel+"++) {"); - indent(); - } + startScopedBlock(port.parent); + startChannelIteration(port); + } + + /** + * Start a scoped block that iterates over the specified range of port channels. + * + *

This must be followed by a call to {@link #endScopedRangeBlock(RuntimeRange)}. + * + *

This block should NOT be nested, where each block is put within a similar block for the + * reactor's parent. Within the created block, every use of {@link + * CUtil#reactorRef(ReactorInstance, String)} must provide the second argument, a runtime index + * variable name, that must match the runtimeIndex parameter given here. + * + * @param range The range of port channels. + * @param runtimeIndex A variable name to use to index the runtime instance of either port's + * parent or the port's parent's parent (if nested is true), or null to use the default, + * "runtime_index". + * @param bankIndex A variable name to use to index the bank of the port's parent or null to use + * the default, the string returned by {@link CUtil#bankIndexName(ReactorInstance)}. + * @param channelIndex A variable name to use to index the channel or null to use the default, the + * string returned by {@link CUtil#channelIndexName(PortInstance)}. + * @param nested If true, then the runtimeIndex variable will be set to the bank index of the + * port's parent's parent rather than the port's parent. + */ + public void startScopedRangeBlock( + RuntimeRange range, + String runtimeIndex, + String bankIndex, + String channelIndex, + boolean nested) { + + pr("// Iterate over range " + range.toString() + "."); + var ri = (runtimeIndex == null) ? "runtime_index" : runtimeIndex; + var ci = (channelIndex == null) ? CUtil.channelIndexName(range.instance) : channelIndex; + var bi = (bankIndex == null) ? CUtil.bankIndexName(range.instance.parent) : bankIndex; + var rangeMR = range.startMR(); + var sizeMR = rangeMR.getDigits().size(); + var nestedLevel = (nested) ? 2 : 1; + + startScopedBlock(); + if (range.width > 1) { + pr( + String.join( + "\n", + "int range_start[] = { " + joinObjects(rangeMR.getDigits(), ", ") + " };", + "int range_radixes[] = { " + joinObjects(rangeMR.getRadixes(), ", ") + " };", + "int permutation[] = { " + joinObjects(range.permutation(), ", ") + " };", + "mixed_radix_int_t range_mr = {", + " " + sizeMR + ",", + " range_start,", + " range_radixes,", + " permutation", + "};", + "for (int range_count = " + + range.start + + "; range_count < " + + range.start + + " + " + + range.width + + "; range_count++) {")); + indent(); + pr( + String.join( + "\n", + "int " + + ri + + " = mixed_radix_parent(&range_mr, " + + nestedLevel + + "); // Runtime index.", + "SUPPRESS_UNUSED_WARNING(" + ri + ");", + "int " + ci + " = range_mr.digits[0]; // Channel index.", + "SUPPRESS_UNUSED_WARNING(" + ci + ");", + "int " + bi + " = " + (sizeMR <= 1 ? "0" : "range_mr.digits[1]") + "; // Bank index.", + "SUPPRESS_UNUSED_WARNING(" + bi + ");")); + + } else { + var ciValue = rangeMR.getDigits().get(0); + var riValue = rangeMR.get(nestedLevel); + var biValue = (sizeMR > 1) ? rangeMR.getDigits().get(1) : 0; + pr( + String.join( + "\n", + "int " + + ri + + " = " + + riValue + + "; SUPPRESS_UNUSED_WARNING(" + + ri + + "); // Runtime index.", + "int " + + ci + + " = " + + ciValue + + "; SUPPRESS_UNUSED_WARNING(" + + ci + + "); // Channel index.", + "int " + + bi + + " = " + + biValue + + "; SUPPRESS_UNUSED_WARNING(" + + bi + + "); // Bank index.", + "int range_count = 0; SUPPRESS_UNUSED_WARNING(range_count);")); } - - /** - * Start a scoped block to iterate over bank members and - * channels for the specified port with a variable with - * the name given by count counting the iterations. - * If this port is a multiport, then the channel index - * variable name is that returned by {@link CUtil#channelIndex(PortInstance)}. - * - * This block is intended to be nested, where each block is - * put within a similar block for the reactor's parent. - * - * This is required to be followed by a call to - * {@link #endScopedBankChannelIteration(PortInstance, String)}. - * @param port The port. - * @param count The variable name to use for the counter, or - * null to not provide a counter. - */ - public void startScopedBankChannelIteration(PortInstance port, - String count) { - if (count != null) { - startScopedBlock(); - pr("int "+count+" = 0;"); - } - startScopedBlock(port.parent); - startChannelIteration(port); + } + + /** + * Start a scoped block that iterates over the specified pair of ranges. The destination range can + * be wider than the source range, in which case the source range is reused until the destination + * range is filled. The following integer variables will be defined within the scoped block: + * + *

    + *
  • src_channel: The channel index for the source. + *
  • src_bank: The bank index of the source port's parent. + *
  • src_runtime: The runtime index of the source port's parent or the parent's parent + * (if the source is an input). + *
+ * + *
    + *
  • dst_channel: The channel index for the destination. + *
  • dst_bank: The bank index of the destination port's parent. + *
  • dst_runtime: The runtime index of the destination port's parent or the parent's + * parent (if destination is an output). + *
+ * + *

For convenience, the above variable names are defined in the private class variables sc, sb, + * sr, and dc, db, dr. + * + *

This block should NOT be nested, where each block is put within a similar block for the + * reactor's parent. Within the created block, every use of {@link + * CUtil#reactorRef(ReactorInstance, String)} and related functions must provide the above + * variable names. + * + *

This must be followed by a call to {@link #endScopedRangeBlock(SendRange, RuntimeRange)}.x + * + * @param srcRange The send range. + * @param dstRange The destination range. + */ + public void startScopedRangeBlock(SendRange srcRange, RuntimeRange dstRange) { + var srcRangeMR = srcRange.startMR(); + var srcSizeMR = srcRangeMR.getRadixes().size(); + var srcNestedLevel = (srcRange.instance.isInput()) ? 2 : 1; + var dstNested = dstRange.instance.isOutput(); + + pr("// Iterate over ranges " + srcRange + " and " + dstRange + "."); + startScopedBlock(); + if (srcRange.width > 1) { + pr( + String.join( + "\n", + "int src_start[] = { " + joinObjects(srcRangeMR.getDigits(), ", ") + " };", + "int src_value[] = { " + + joinObjects(srcRangeMR.getDigits(), ", ") + + " }; // Will be incremented.", + "int src_radixes[] = { " + joinObjects(srcRangeMR.getRadixes(), ", ") + " };", + "int src_permutation[] = { " + joinObjects(srcRange.permutation(), ", ") + " };", + "mixed_radix_int_t src_range_mr = {", + " " + srcSizeMR + ",", + " src_value,", + " src_radixes,", + " src_permutation", + "};")); + } else { + var ciValue = srcRangeMR.getDigits().get(0); + var biValue = (srcSizeMR > 1) ? srcRangeMR.getDigits().get(1) : 0; + var riValue = srcRangeMR.get(srcNestedLevel); + pr( + String.join( + "\n", + "int " + sr + " = " + riValue + "; // Runtime index.", + "SUPPRESS_UNUSED_WARNING(" + sr + ");", + "int " + sc + " = " + ciValue + "; // Channel index.", + "SUPPRESS_UNUSED_WARNING(" + sc + ");", + "int " + sb + " = " + biValue + "; // Bank index.", + "SUPPRESS_UNUSED_WARNING(" + sb + ");")); } - /** - * Start a scoped block that iterates over the specified range of port channels. - * - * This must be followed by a call to - * {@link #endScopedRangeBlock(RuntimeRange)}. - * - * This block should NOT be nested, where each block is - * put within a similar block for the reactor's parent. - * Within the created block, every use of - * {@link CUtil#reactorRef(ReactorInstance, String)} - * must provide the second argument, a runtime index variable name, - * that must match the runtimeIndex parameter given here. - * - * @param range The range of port channels. - * @param runtimeIndex A variable name to use to index the runtime instance of - * either port's parent or the port's parent's parent (if nested is true), or - * null to use the default, "runtime_index". - * @param bankIndex A variable name to use to index the bank of the port's parent or null to use the - * default, the string returned by {@link CUtil#bankIndexName(ReactorInstance)}. - * @param channelIndex A variable name to use to index the channel or null to - * use the default, the string returned by {@link CUtil#channelIndexName(PortInstance)}. - * @param nested If true, then the runtimeIndex variable will be set - * to the bank index of the port's parent's parent rather than the - * port's parent. - */ - public void startScopedRangeBlock( - RuntimeRange range, - String runtimeIndex, - String bankIndex, - String channelIndex, - boolean nested - ) { - - pr("// Iterate over range "+range.toString()+"."); - var ri = (runtimeIndex == null)? "runtime_index" : runtimeIndex; - var ci = (channelIndex == null)? CUtil.channelIndexName(range.instance) : channelIndex; - var bi = (bankIndex == null)? CUtil.bankIndexName(range.instance.parent) : bankIndex; - var rangeMR = range.startMR(); - var sizeMR = rangeMR.getDigits().size(); - var nestedLevel = (nested) ? 2 : 1; - - startScopedBlock(); - if (range.width > 1) { - pr(String.join("\n", - "int range_start[] = { "+joinObjects(rangeMR.getDigits(), ", ")+" };", - "int range_radixes[] = { "+joinObjects(rangeMR.getRadixes(), ", ")+" };", - "int permutation[] = { "+joinObjects(range.permutation(), ", ")+" };", - "mixed_radix_int_t range_mr = {", - " "+sizeMR+",", - " range_start,", - " range_radixes,", - " permutation", - "};", - "for (int range_count = "+range.start+"; range_count < "+range.start+" + "+range.width+"; range_count++) {" - )); - indent(); - pr(String.join("\n", - "int "+ri+" = mixed_radix_parent(&range_mr, "+nestedLevel+"); // Runtime index.", - "SUPPRESS_UNUSED_WARNING("+ri+");", - "int "+ci+" = range_mr.digits[0]; // Channel index.", - "SUPPRESS_UNUSED_WARNING("+ci+");", - "int "+bi+" = "+(sizeMR <= 1 ? "0" : "range_mr.digits[1]")+"; // Bank index.", - "SUPPRESS_UNUSED_WARNING("+bi+");" - )); - - } else { - var ciValue = rangeMR.getDigits().get(0); - var riValue = rangeMR.get(nestedLevel); - var biValue = (sizeMR > 1)? rangeMR.getDigits().get(1) : 0; - pr(String.join("\n", - "int "+ri+" = "+riValue+"; SUPPRESS_UNUSED_WARNING("+ri+"); // Runtime index.", - "int "+ci+" = "+ciValue+"; SUPPRESS_UNUSED_WARNING("+ci+"); // Channel index.", - "int "+bi+" = "+biValue+"; SUPPRESS_UNUSED_WARNING("+bi+"); // Bank index.", - "int range_count = 0; SUPPRESS_UNUSED_WARNING(range_count);" - )); - } + startScopedRangeBlock(dstRange, dr, db, dc, dstNested); + + if (srcRange.width > 1) { + pr( + String.join( + "\n", + "int " + + sr + + " = mixed_radix_parent(&src_range_mr, " + + srcNestedLevel + + "); // Runtime index.", + "SUPPRESS_UNUSED_WARNING(" + sr + ");", + "int " + sc + " = src_range_mr.digits[0]; // Channel index.", + "SUPPRESS_UNUSED_WARNING(" + sc + ");", + "int " + + sb + + " = " + + (srcSizeMR <= 1 ? "0" : "src_range_mr.digits[1]") + + "; // Bank index.", + "SUPPRESS_UNUSED_WARNING(" + sb + ");")); } - - /** - * Start a scoped block that iterates over the specified pair of ranges. - * The destination range can be wider than the source range, in which case the - * source range is reused until the destination range is filled. - * The following integer variables will be defined within the scoped block: - * - *

    - *
  • src_channel: The channel index for the source.
  • - *
  • src_bank: The bank index of the source port's parent.
  • - *
  • src_runtime: The runtime index of the source port's parent or - * the parent's parent (if the source is an input).
  • - *
- *
    - *
  • dst_channel: The channel index for the destination.
  • - *
  • dst_bank: The bank index of the destination port's parent.
  • - *
  • dst_runtime: The runtime index of the destination port's parent or - * the parent's parent (if destination is an output).
  • - *
- * - *

For convenience, the above variable names are defined in the private - * class variables sc, sb, sr, and dc, db, dr.

- * - *

This block should NOT be nested, where each block is - * put within a similar block for the reactor's parent. - * Within the created block, every use of - * {@link CUtil#reactorRef(ReactorInstance, String)} - * and related functions must provide the above variable names.

- * - *

This must be followed by a call to - * {@link #endScopedRangeBlock(SendRange, RuntimeRange)}.

x - * - * @param srcRange The send range. - * @param dstRange The destination range. - */ - public void startScopedRangeBlock( - SendRange srcRange, - RuntimeRange dstRange - ) { - var srcRangeMR = srcRange.startMR(); - var srcSizeMR = srcRangeMR.getRadixes().size(); - var srcNestedLevel = (srcRange.instance.isInput()) ? 2 : 1; - var dstNested = dstRange.instance.isOutput(); - - pr("// Iterate over ranges "+srcRange+" and "+dstRange+"."); - startScopedBlock(); - if (srcRange.width > 1) { - pr(String.join("\n", - "int src_start[] = { "+joinObjects(srcRangeMR.getDigits(), ", ")+" };", - "int src_value[] = { "+joinObjects(srcRangeMR.getDigits(), ", ")+" }; // Will be incremented.", - "int src_radixes[] = { "+joinObjects(srcRangeMR.getRadixes(), ", ")+" };", - "int src_permutation[] = { "+joinObjects(srcRange.permutation(), ", ")+" };", - "mixed_radix_int_t src_range_mr = {", - " "+srcSizeMR+",", - " src_value,", - " src_radixes,", - " src_permutation", - "};" - )); - } else { - var ciValue = srcRangeMR.getDigits().get(0); - var biValue = (srcSizeMR > 1)? srcRangeMR.getDigits().get(1) : 0; - var riValue = srcRangeMR.get(srcNestedLevel); - pr(String.join("\n", - "int "+sr+" = "+riValue+"; // Runtime index.", - "SUPPRESS_UNUSED_WARNING("+sr+");", - "int "+sc+" = "+ciValue+"; // Channel index.", - "SUPPRESS_UNUSED_WARNING("+sc+");", - "int "+sb+" = "+biValue+"; // Bank index.", - "SUPPRESS_UNUSED_WARNING("+sb+");" - )); - } - - startScopedRangeBlock(dstRange, dr, db, dc, dstNested); - - if (srcRange.width > 1) { - pr(String.join("\n", - "int "+sr+" = mixed_radix_parent(&src_range_mr, "+srcNestedLevel+"); // Runtime index.", - "SUPPRESS_UNUSED_WARNING("+sr+");", - "int "+sc+" = src_range_mr.digits[0]; // Channel index.", - "SUPPRESS_UNUSED_WARNING("+sc+");", - "int "+sb+" = "+(srcSizeMR <= 1 ? "0" : "src_range_mr.digits[1]")+"; // Bank index.", - "SUPPRESS_UNUSED_WARNING("+sb+");" - )); - } + } + + public void endScopedBlock() { + unindent(); + pr("}"); + } + + /** + * If the specified port is a multiport, then start a specified iteration over the channels of the + * multiport using as the channel index the variable name returned by {@link + * CUtil#channelIndex(PortInstance)}. If the port is not a multiport, do nothing. + * + * @param port The port. + */ + public void endChannelIteration(PortInstance port) { + if (port.isMultiport) { + unindent(); + pr("}"); } - - public void endScopedBlock() { - unindent(); - pr("}"); + } + + /** + * End a scoped block to iterate over bank members and channels for the specified port with a + * variable with the name given by count counting the iterations. + * + * @param port The port. + * @param count The variable name to use for the counter, or null to not provide a counter. + */ + public void endScopedBankChannelIteration(PortInstance port, String count) { + if (count != null) { + pr(count + "++;"); } - - /** - * If the specified port is a multiport, then start a specified iteration - * over the channels of the multiport using as the channel index the - * variable name returned by {@link CUtil#channelIndex(PortInstance)}. - * If the port is not a multiport, do nothing. - * @param port The port. - */ - public void endChannelIteration(PortInstance port) { - if (port.isMultiport) { - unindent(); - pr("}"); - } + endChannelIteration(port); + endScopedBlock(); + if (count != null) { + endScopedBlock(); } - - /** - * End a scoped block to iterate over bank members and - * channels for the specified port with a variable with - * the name given by count counting the iterations. - * @param port The port. - * @param count The variable name to use for the counter, or - * null to not provide a counter. - */ - public void endScopedBankChannelIteration( - PortInstance port, String count - ) { - if (count != null) { - pr(count + "++;"); - } - endChannelIteration(port); - endScopedBlock(); - if (count != null) { - endScopedBlock(); - } + } + + /** + * End a scoped block for the specified range. + * + * @param range The send range. + */ + public void endScopedRangeBlock(RuntimeRange range) { + if (range.width > 1) { + pr("mixed_radix_incr(&range_mr);"); + endScopedBlock(); // Terminate for loop. } - - /** - * End a scoped block for the specified range. - * @param range The send range. - */ - public void endScopedRangeBlock( - RuntimeRange range - ) { - if (range.width > 1) { - pr("mixed_radix_incr(&range_mr);"); - endScopedBlock(); // Terminate for loop. - } - endScopedBlock(); - } - - /** - * End a scoped block that iterates over the specified pair of ranges. - * - * @param srcRange The send range. - * @param dstRange The destination range. - */ - public void endScopedRangeBlock( - SendRange srcRange, - RuntimeRange dstRange - ) { - if (srcRange.width > 1) { - pr(String.join("\n", - "mixed_radix_incr(&src_range_mr);", - "if (mixed_radix_to_int(&src_range_mr) >= "+srcRange.start+" + "+srcRange.width+") {", - " // Start over with the source.", - " for (int i = 0; i < src_range_mr.size; i++) {", - " src_range_mr.digits[i] = src_start[i];", - " }", - "}" - )); - } - if (dstRange.width > 1) { - pr("mixed_radix_incr(&range_mr);"); - endScopedBlock(); // Terminate for loop. - } - // Terminate unconditional scope block in startScopedRangeBlock calls. - endScopedBlock(); - endScopedBlock(); + endScopedBlock(); + } + + /** + * End a scoped block that iterates over the specified pair of ranges. + * + * @param srcRange The send range. + * @param dstRange The destination range. + */ + public void endScopedRangeBlock(SendRange srcRange, RuntimeRange dstRange) { + if (srcRange.width > 1) { + pr( + String.join( + "\n", + "mixed_radix_incr(&src_range_mr);", + "if (mixed_radix_to_int(&src_range_mr) >= " + + srcRange.start + + " + " + + srcRange.width + + ") {", + " // Start over with the source.", + " for (int i = 0; i < src_range_mr.size; i++) {", + " src_range_mr.digits[i] = src_start[i];", + " }", + "}")); } - - /** - * Return the code as a string. - */ - @Override - public String toString() { - return code.toString(); - } - - /** - * Reduce the indentation by one level for generated code/ - */ - public void unindent() { - indentation = indentation.substring(0, Math.max(0, indentation.length() - 4)); + if (dstRange.width > 1) { + pr("mixed_radix_incr(&range_mr);"); + endScopedBlock(); // Terminate for loop. } - - /** - * Write the text to a file. - * @param path The file to write the code to. - */ - public CodeMap writeToFile(String path) throws IOException { - CodeMap ret = CodeMap.fromGeneratedCode(code.toString()); - FileUtil.writeToFile(ret.getGeneratedCode(), Path.of(path), true); - return ret; - } - - //////////////////////////////////////////// - //// Private fields. - - /** Place to store the code. */ - private final StringBuilder code = new StringBuilder(); - - /** Current indentation. */ - private String indentation = ""; + // Terminate unconditional scope block in startScopedRangeBlock calls. + endScopedBlock(); + endScopedBlock(); + } + + /** Return the code as a string. */ + @Override + public String toString() { + return code.toString(); + } + + /** Reduce the indentation by one level for generated code/ */ + public void unindent() { + indentation = indentation.substring(0, Math.max(0, indentation.length() - 4)); + } + + /** + * Write the text to a file. + * + * @param path The file to write the code to. + */ + public CodeMap writeToFile(String path) throws IOException { + CodeMap ret = CodeMap.fromGeneratedCode(code.toString()); + FileUtil.writeToFile(ret.getGeneratedCode(), Path.of(path), true); + return ret; + } + + //////////////////////////////////////////// + //// Private fields. + + /** Place to store the code. */ + private final StringBuilder code = new StringBuilder(); + + /** Current indentation. */ + private String indentation = ""; } diff --git a/org.lflang/src/org/lflang/generator/CodeMap.java b/org.lflang/src/org/lflang/generator/CodeMap.java index d00fe9bc9a..9d196c7daa 100644 --- a/org.lflang/src/org/lflang/generator/CodeMap.java +++ b/org.lflang/src/org/lflang/generator/CodeMap.java @@ -1,348 +1,320 @@ package org.lflang.generator; import java.nio.file.Path; +import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.NavigableMap; import java.util.Set; import java.util.TreeMap; -import java.util.HashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; - import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.nodemodel.INode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import org.eclipse.xtext.util.LineAndColumn; - import org.lflang.lf.ParameterReference; -import org.lflang.lf.impl.ParameterReferenceImpl; /** - * Encapsulates data about the correspondence between - * ranges of generated code and ranges of a Lingua Franca - * file. + * Encapsulates data about the correspondence between ranges of generated code and ranges of a + * Lingua Franca file. */ public class CodeMap { - public static class Correspondence { - // This pattern has the markers "/* */", which some languages use as line comments. This does not - // represent any serious effort to embed the string representation of this object in generated code - // without introducing a syntax error. Instead, it is done simply because it is easy. - private static final Pattern PATTERN = Pattern.compile(String.format( - "/\\*Correspondence: (?%s) \\-> (?%s) \\(verbatim=(?true|false); src=(?%s)\\)\\*/", - Position.removeNamedCapturingGroups(Range.PATTERN), - Position.removeNamedCapturingGroups(Range.PATTERN), - ".*?" - )); - - private final Path path; - private final Range lfRange; - private final Range generatedRange; - private final boolean verbatim; - - /** - * Instantiates a Correspondence between - * {@code lfRange} at {@code path} and - * {@code generatedRange} in the generated file - * associated with this Correspondence. - * @param path a path to an LF source file - * @param lfRange a range in the given LF file - * @param generatedRange the range of generated code - * associated with - * {@code lfRange} - */ - private Correspondence(Path path, Range lfRange, Range generatedRange, boolean verbatim) { - this.path = path; - this.lfRange = lfRange; - this.generatedRange = generatedRange; - this.verbatim = verbatim; - } - - /** - * Returns a path to the LF source file described by - * this Correspondence. - * @return a path to the LF source file described by - * this Correspondence - */ - public Path getPath() { - return path; - } - - /** - * Returns a range in an LF source file. - * @return a range in an LF source file - */ - public Range getLfRange() { - return lfRange; - } + public static class Correspondence { + // This pattern has the markers "/* */", which some languages use as line comments. This does + // not + // represent any serious effort to embed the string representation of this object in generated + // code + // without introducing a syntax error. Instead, it is done simply because it is easy. + private static final Pattern PATTERN = + Pattern.compile( + String.format( + "/\\*Correspondence: (?%s) \\-> (?%s)" + + " \\(verbatim=(?true|false); src=(?%s)\\)\\*/", + Position.removeNamedCapturingGroups(Range.PATTERN), + Position.removeNamedCapturingGroups(Range.PATTERN), + ".*?")); - /** - * Returns a range in a generated file that - * corresponds to a range in an LF file. - * @return a range in a generated file that - * corresponds to a range in an LF file - */ - public Range getGeneratedRange() { - return generatedRange; - } + private final Path path; + private final Range lfRange; + private final Range generatedRange; + private final boolean verbatim; - @Override - public String toString() { - return String.format( - "/*Correspondence: %s -> %s (verbatim=%b; src=%s)*/", - lfRange.toString(), generatedRange.toString(), verbatim, path.toString() - ); - } - - /** - * Returns the Correspondence represented by - * {@code s}. - * @param s a String that represents a - * Correspondence, formatted like the - * output of Correspondence::toString - * @param relativeTo the offset relative to which - * the offsets given are given - * @return the Correspondence represented by - * {@code s} - */ - public static Correspondence fromString(String s, Position relativeTo) { - Matcher matcher = PATTERN.matcher(s); - if (matcher.matches()) { - Range lfRange = Range.fromString(matcher.group("lfRange")); - Range generatedRange = Range.fromString(matcher.group("generatedRange"), relativeTo); - return new Correspondence( - Path.of(matcher.group("path")), - lfRange, - generatedRange, - Boolean.parseBoolean(matcher.group("verbatim")) - ); - } - throw new IllegalArgumentException(String.format("Could not parse %s as a Correspondence.", s)); - } - - /** - * Returns {@code representation}, tagged with - * a Correspondence to the source code associated - * with {@code astNode}. - * @param astNode an arbitrary AST node - * @param representation the code generated from - * that AST node - * @param verbatim whether {@code representation} - * is copied verbatim from the - * part of the source code - * associated with {@code astNode} - * @return {@code representation}, tagged with - * a Correspondence to the source code associated - * with {@code astNode} - */ - public static String tag(EObject astNode, String representation, boolean verbatim) { - final INode node = NodeModelUtils.getNode(astNode); - // If the EObject originates from an AST transformation, then it does not correspond directly - // to any LF code, and it need not be tagged at all. - if (node == null) return representation; - final LineAndColumn oneBasedLfLineAndColumn = NodeModelUtils.getLineAndColumn(node, node.getTotalOffset()); - Position lfStart = Position.fromOneBased( - 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( - lfPath, - new Range(lfStart, lfStart.plus(verbatim ? representation : node.getText())), - new Range(Position.ORIGIN, Position.displacementOf(representation)), - verbatim - ) + representation; - } - - /** - * Return the {@code eResource} associated with the given AST node. - * This is a dangerous operation which can cause an unrecoverable error. - */ - private static Resource bestEffortGetEResource(EObject astNode) { - if (astNode instanceof ParameterReference pri) { - return pri.getParameter().eResource(); - } - return astNode.eResource(); - } - - /** - * Make a best-effort attempt to find the index of - * a near substring whose first line is expected to - * be an exact substring of {@code s}. Return 0 - * upon failure. - * @param s an arbitrary string - * @param imperfectSubstring an approximate - * substring of {@code s} - * @return the index of {@code imperfectSubstring} - * within {@code s} - */ - private static int indexOf(String s, String imperfectSubstring) { - String firstLine = imperfectSubstring.lines().findFirst().orElse(""); - return Math.max(0, s.indexOf(firstLine)); - } + /** + * Instantiates a Correspondence between {@code lfRange} at {@code path} and {@code + * generatedRange} in the generated file associated with this Correspondence. + * + * @param path a path to an LF source file + * @param lfRange a range in the given LF file + * @param generatedRange the range of generated code associated with {@code lfRange} + */ + private Correspondence(Path path, Range lfRange, Range generatedRange, boolean verbatim) { + this.path = path; + this.lfRange = lfRange; + this.generatedRange = generatedRange; + this.verbatim = verbatim; } /** - * The content of the generated file represented by - * this. + * Returns a path to the LF source file described by this Correspondence. + * + * @return a path to the LF source file described by this Correspondence */ - private final String generatedCode; + public Path getPath() { + return path; + } + /** - * A mapping from Lingua Franca source paths to mappings - * from ranges in the generated file represented by this - * to ranges in Lingua Franca files. + * Returns a range in an LF source file. + * + * @return a range in an LF source file */ - private final Map> map; + public Range getLfRange() { + return lfRange; + } + /** - * A mapping from Lingua Franca source paths to mappings - * from ranges in the generated file represented by this - * to whether those ranges are copied verbatim from the - * source. + * Returns a range in a generated file that corresponds to a range in an LF file. + * + * @return a range in a generated file that corresponds to a range in an LF file */ - private final Map> isVerbatimByLfSourceByRange; + public Range getGeneratedRange() { + return generatedRange; + } - /* ------------------------- PUBLIC METHODS -------------------------- */ + @Override + public String toString() { + return String.format( + "/*Correspondence: %s -> %s (verbatim=%b; src=%s)*/", + lfRange.toString(), generatedRange.toString(), verbatim, path.toString()); + } /** - * Instantiates a {@code CodeMap} from - * {@code internalGeneratedCode}. - * {@code internalGeneratedCode} may be invalid - * code that is different from the final generated code - * because it should contain deserializable - * representations of {@code Correspondences}. - * @param internalGeneratedCode code from a code - * generator that contains - * serialized - * Correspondences - * @return a CodeMap documenting the provided code + * Returns the Correspondence represented by {@code s}. + * + * @param s a String that represents a Correspondence, formatted like the output of + * Correspondence::toString + * @param relativeTo the offset relative to which the offsets given are given + * @return the Correspondence represented by {@code s} */ - public static CodeMap fromGeneratedCode(String internalGeneratedCode) { - Map> map = new HashMap<>(); - Map> isVerbatimByLfSourceByRange = new HashMap<>(); - StringBuilder generatedCode = new StringBuilder(); - Iterator it = internalGeneratedCode.lines().iterator(); - int zeroBasedLine = 0; - while (it.hasNext()) { - generatedCode.append(processGeneratedLine(it.next(), zeroBasedLine++, map, isVerbatimByLfSourceByRange)).append('\n'); - } - return new CodeMap(generatedCode.toString(), map, isVerbatimByLfSourceByRange); + public static Correspondence fromString(String s, Position relativeTo) { + Matcher matcher = PATTERN.matcher(s); + if (matcher.matches()) { + Range lfRange = Range.fromString(matcher.group("lfRange")); + Range generatedRange = Range.fromString(matcher.group("generatedRange"), relativeTo); + return new Correspondence( + Path.of(matcher.group("path")), + lfRange, + generatedRange, + Boolean.parseBoolean(matcher.group("verbatim"))); + } + throw new IllegalArgumentException( + String.format("Could not parse %s as a Correspondence.", s)); } /** - * Returns the generated code (without Correspondences). - * @return the generated code (without Correspondences) + * Returns {@code representation}, tagged with a Correspondence to the source code associated + * with {@code astNode}. + * + * @param astNode an arbitrary AST node + * @param representation the code generated from that AST node + * @param verbatim whether {@code representation} is copied verbatim from the part of the source + * code associated with {@code astNode} + * @return {@code representation}, tagged with a Correspondence to the source code associated + * with {@code astNode} */ - public String getGeneratedCode() { - return generatedCode; + public static String tag(EObject astNode, String representation, boolean verbatim) { + final INode node = NodeModelUtils.getNode(astNode); + // If the EObject originates from an AST transformation, then it does not correspond directly + // to any LF code, and it need not be tagged at all. + if (node == null) return representation; + final LineAndColumn oneBasedLfLineAndColumn = + NodeModelUtils.getLineAndColumn(node, node.getTotalOffset()); + Position lfStart = + Position.fromOneBased( + 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( + lfPath, + new Range(lfStart, lfStart.plus(verbatim ? representation : node.getText())), + new Range(Position.ORIGIN, Position.displacementOf(representation)), + verbatim) + + representation; } /** - * Returns the set of all paths to Lingua Franca files - * that are known to contain code that corresponds to - * code in the generated file represented by this. + * Return the {@code eResource} associated with the given AST node. This is a dangerous + * operation which can cause an unrecoverable error. */ - public Set lfSourcePaths() { - return map.keySet(); + private static Resource bestEffortGetEResource(EObject astNode) { + if (astNode instanceof ParameterReference pri) { + return pri.getParameter().eResource(); + } + return astNode.eResource(); } /** - * Returns the position in {@code lfFile} - * corresponding to {@code generatedFilePosition} - * if such a position is known, or the zero Position - * otherwise. - * @param lfFile the path to an arbitrary Lingua Franca - * source file - * @param generatedFilePosition a position in a - * generated file - * @return the position in {@code lfFile} - * corresponding to {@code generatedFilePosition} + * Make a best-effort attempt to find the index of a near substring whose first line is expected + * to be an exact substring of {@code s}. Return 0 upon failure. + * + * @param s an arbitrary string + * @param imperfectSubstring an approximate substring of {@code s} + * @return the index of {@code imperfectSubstring} within {@code s} */ - public Position adjusted(Path lfFile, Position generatedFilePosition) { - NavigableMap mapOfInterest = map.get(lfFile); - Map isVerbatimByRange = isVerbatimByLfSourceByRange.get(lfFile); - Map.Entry nearestEntry = mapOfInterest.floorEntry(Range.degenerateRange(generatedFilePosition)); - if (nearestEntry == null) return Position.ORIGIN; - if (!isVerbatimByRange.get(nearestEntry.getKey())) { - return nearestEntry.getValue().getStartInclusive(); - } - if (nearestEntry.getKey().contains(generatedFilePosition)) { - return nearestEntry.getValue().getStartInclusive().plus( - generatedFilePosition.minus(nearestEntry.getKey().getStartInclusive()) - ); - } - return Position.ORIGIN; + private static int indexOf(String s, String imperfectSubstring) { + String firstLine = imperfectSubstring.lines().findFirst().orElse(""); + return Math.max(0, s.indexOf(firstLine)); } + } - /** - * Returns the range in {@code lfFile} - * corresponding to {@code generatedFileRange} - * if such a range is known, or a degenerate Range - * otherwise. - * @param lfFile the path to an arbitrary Lingua Franca - * source file - * @param generatedFileRange a position in a - * generated file - * @return the range in {@code lfFile} - * corresponding to {@code generatedFileRange} - */ - public Range adjusted(Path lfFile, Range generatedFileRange) { - final Position start = adjusted(lfFile, generatedFileRange.getStartInclusive()); - final Position end = adjusted(lfFile, generatedFileRange.getEndExclusive()); - return start.compareTo(end) <= 0 ? new Range(start, end) : new Range(start, start); + /** The content of the generated file represented by this. */ + private final String generatedCode; + /** + * A mapping from Lingua Franca source paths to mappings from ranges in the generated file + * represented by this to ranges in Lingua Franca files. + */ + private final Map> map; + /** + * A mapping from Lingua Franca source paths to mappings from ranges in the generated file + * represented by this to whether those ranges are copied verbatim from the source. + */ + private final Map> isVerbatimByLfSourceByRange; + + /* ------------------------- PUBLIC METHODS -------------------------- */ + + /** + * Instantiates a {@code CodeMap} from {@code internalGeneratedCode}. {@code + * internalGeneratedCode} may be invalid code that is different from the final generated code + * because it should contain deserializable representations of {@code Correspondences}. + * + * @param internalGeneratedCode code from a code generator that contains serialized + * Correspondences + * @return a CodeMap documenting the provided code + */ + public static CodeMap fromGeneratedCode(String internalGeneratedCode) { + Map> map = new HashMap<>(); + Map> isVerbatimByLfSourceByRange = new HashMap<>(); + StringBuilder generatedCode = new StringBuilder(); + Iterator it = internalGeneratedCode.lines().iterator(); + int zeroBasedLine = 0; + while (it.hasNext()) { + generatedCode + .append( + processGeneratedLine(it.next(), zeroBasedLine++, map, isVerbatimByLfSourceByRange)) + .append('\n'); } + return new CodeMap(generatedCode.toString(), map, isVerbatimByLfSourceByRange); + } - /* ------------------------- PRIVATE METHODS ------------------------- */ + /** + * Returns the generated code (without Correspondences). + * + * @return the generated code (without Correspondences) + */ + public String getGeneratedCode() { + return generatedCode; + } - private CodeMap( - String generatedCode, Map> map, - Map> isVerbatimByLfSourceByRange - ) { - this.generatedCode = generatedCode; - this.map = map; - this.isVerbatimByLfSourceByRange = isVerbatimByLfSourceByRange; + /** + * Returns the set of all paths to Lingua Franca files that are known to contain code that + * corresponds to code in the generated file represented by this. + */ + public Set lfSourcePaths() { + return map.keySet(); + } + + /** + * Returns the position in {@code lfFile} corresponding to {@code generatedFilePosition} if such a + * position is known, or the zero Position otherwise. + * + * @param lfFile the path to an arbitrary Lingua Franca source file + * @param generatedFilePosition a position in a generated file + * @return the position in {@code lfFile} corresponding to {@code generatedFilePosition} + */ + public Position adjusted(Path lfFile, Position generatedFilePosition) { + NavigableMap mapOfInterest = map.get(lfFile); + Map isVerbatimByRange = isVerbatimByLfSourceByRange.get(lfFile); + Map.Entry nearestEntry = + mapOfInterest.floorEntry(Range.degenerateRange(generatedFilePosition)); + if (nearestEntry == null) return Position.ORIGIN; + if (!isVerbatimByRange.get(nearestEntry.getKey())) { + return nearestEntry.getValue().getStartInclusive(); + } + if (nearestEntry.getKey().contains(generatedFilePosition)) { + return nearestEntry + .getValue() + .getStartInclusive() + .plus(generatedFilePosition.minus(nearestEntry.getKey().getStartInclusive())); } + return Position.ORIGIN; + } - /** - * Removes serialized Correspondences from {@code line} - * and updates {@code map} according to those - * Correspondences. - * @param line a line of generated code - * @param zeroBasedLineIndex the index of the given line - * @param map a map that stores Correspondences - * @return the line of generated code with all - * Correspondences removed - */ - private static String processGeneratedLine( - String line, - int zeroBasedLineIndex, - Map> map, - Map> isVerbatimByLfSourceByRange - ) { - Matcher matcher = Correspondence.PATTERN.matcher(line); - StringBuilder cleanedLine = new StringBuilder(); - int lastEnd = 0; - while (matcher.find()) { - cleanedLine.append(line, lastEnd, matcher.start()); - Correspondence c = Correspondence.fromString( - matcher.group(), - Position.fromZeroBased(zeroBasedLineIndex, cleanedLine.length()) - ); - if (!map.containsKey(c.path)) map.put(c.path, new TreeMap<>()); - map.get(c.path).put(c.generatedRange, c.lfRange); - if (!isVerbatimByLfSourceByRange.containsKey(c.path)) isVerbatimByLfSourceByRange.put(c.path, new HashMap<>()); - isVerbatimByLfSourceByRange.get(c.path).put(c.generatedRange, c.verbatim); - lastEnd = matcher.end(); - } - cleanedLine.append(line.substring(lastEnd)); - return cleanedLine.toString(); + /** + * Returns the range in {@code lfFile} corresponding to {@code generatedFileRange} if such a range + * is known, or a degenerate Range otherwise. + * + * @param lfFile the path to an arbitrary Lingua Franca source file + * @param generatedFileRange a position in a generated file + * @return the range in {@code lfFile} corresponding to {@code generatedFileRange} + */ + public Range adjusted(Path lfFile, Range generatedFileRange) { + final Position start = adjusted(lfFile, generatedFileRange.getStartInclusive()); + final Position end = adjusted(lfFile, generatedFileRange.getEndExclusive()); + return start.compareTo(end) <= 0 ? new Range(start, end) : new Range(start, start); + } + + /* ------------------------- PRIVATE METHODS ------------------------- */ + + private CodeMap( + String generatedCode, + Map> map, + Map> isVerbatimByLfSourceByRange) { + this.generatedCode = generatedCode; + this.map = map; + this.isVerbatimByLfSourceByRange = isVerbatimByLfSourceByRange; + } + + /** + * Removes serialized Correspondences from {@code line} and updates {@code map} according to those + * Correspondences. + * + * @param line a line of generated code + * @param zeroBasedLineIndex the index of the given line + * @param map a map that stores Correspondences + * @return the line of generated code with all Correspondences removed + */ + private static String processGeneratedLine( + String line, + int zeroBasedLineIndex, + Map> map, + Map> isVerbatimByLfSourceByRange) { + Matcher matcher = Correspondence.PATTERN.matcher(line); + StringBuilder cleanedLine = new StringBuilder(); + int lastEnd = 0; + while (matcher.find()) { + cleanedLine.append(line, lastEnd, matcher.start()); + Correspondence c = + Correspondence.fromString( + matcher.group(), Position.fromZeroBased(zeroBasedLineIndex, cleanedLine.length())); + if (!map.containsKey(c.path)) map.put(c.path, new TreeMap<>()); + map.get(c.path).put(c.generatedRange, c.lfRange); + if (!isVerbatimByLfSourceByRange.containsKey(c.path)) + isVerbatimByLfSourceByRange.put(c.path, new HashMap<>()); + isVerbatimByLfSourceByRange.get(c.path).put(c.generatedRange, c.verbatim); + lastEnd = matcher.end(); } + cleanedLine.append(line.substring(lastEnd)); + return cleanedLine.toString(); + } } diff --git a/org.lflang/src/org/lflang/generator/DeadlineInstance.java b/org.lflang/src/org/lflang/generator/DeadlineInstance.java index 208c116791..e73f6b8f9b 100644 --- a/org.lflang/src/org/lflang/generator/DeadlineInstance.java +++ b/org.lflang/src/org/lflang/generator/DeadlineInstance.java @@ -1,29 +1,29 @@ /** A data structure for a deadline instance. */ /************* -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.generator; @@ -31,41 +31,37 @@ import org.lflang.lf.Deadline; /** - * Instance of a deadline. 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 deadline. 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 Marten Lohstroh * @author Edward A. Lee */ public class DeadlineInstance { - - /** - * Create a new deadline instance associated with the given reaction - * instance. - */ - public DeadlineInstance(Deadline definition, ReactionInstance reaction) { - if (definition.getDelay() != null) { - this.maxDelay = reaction.parent.getTimeValue(definition.getDelay()); - } else { - this.maxDelay = TimeValue.ZERO; - } + + /** Create a new deadline instance associated with the given reaction instance. */ + public DeadlineInstance(Deadline definition, ReactionInstance reaction) { + if (definition.getDelay() != null) { + this.maxDelay = reaction.parent.getTimeValue(definition.getDelay()); + } else { + this.maxDelay = TimeValue.ZERO; } + } - ////////////////////////////////////////////////////// - //// Public fields. + ////////////////////////////////////////////////////// + //// Public fields. - /** - * The delay D associated with this deadline. If physical time T < logical - * time t + D, the deadline is met, otherwise, it is violated. - */ - public final TimeValue maxDelay; + /** + * The delay D associated with this deadline. If physical time T < logical time t + D, the + * deadline is met, otherwise, it is violated. + */ + public final TimeValue maxDelay; - ////////////////////////////////////////////////////// - //// Public methods. - - @Override - public String toString() { - return "DeadlineInstance " + maxDelay.toString(); - } + ////////////////////////////////////////////////////// + //// Public methods. + + @Override + public String toString() { + return "DeadlineInstance " + maxDelay.toString(); + } } diff --git a/org.lflang/src/org/lflang/generator/DelayBodyGenerator.java b/org.lflang/src/org/lflang/generator/DelayBodyGenerator.java index 39742a3488..240eba7bc1 100644 --- a/org.lflang/src/org/lflang/generator/DelayBodyGenerator.java +++ b/org.lflang/src/org/lflang/generator/DelayBodyGenerator.java @@ -6,54 +6,47 @@ public interface DelayBodyGenerator { - /** - * Constant that specifies how to name generated delay reactors. - */ - String GEN_DELAY_CLASS_NAME = "_lf_GenDelay"; - - /** - * Generate code for the body of a reaction that takes an input and - * schedules an action with the value of that input. - * - * @param action the action to schedule - * @param port the port to read from - */ - String generateDelayBody(Action action, VarRef port); - - /** - * Generate code for the body of a reaction that is triggered by the - * given action and writes its value to the given port. - * - * @param action the action that triggers the reaction - * @param port the port to write to - */ - String generateForwardBody(Action action, VarRef port); - - /** - * Generate code for the generic type to be used in the class definition - * of a generated delay reactor. - */ - String generateDelayGeneric(); - - /** - * Indicates whether delay banks generated from after delays should have a variable length - * width. - *

- * If this is true, any delay reactors that are inserted for after delays on multiport - * connections - * will have an unspecified variable length width. The code generator is then responsible for - * inferring the - * correct width of the delay bank, which is only possible if the precise connection width is - * known at compile time. - *

- * If this is false, the width specification of the generated bank will list all the ports - * listed on the right - * side of the connection. This gives the code generator the information needed to infer the - * correct width at - * runtime. - */ - boolean generateAfterDelaysWithVariableWidth(); - - /** Used to optionally apply additional transformations to the generated reactions */ - default void finalizeReactions(Reaction delayReaction, Reaction forwardReaction) { } + /** Constant that specifies how to name generated delay reactors. */ + String GEN_DELAY_CLASS_NAME = "_lf_GenDelay"; + + /** + * Generate code for the body of a reaction that takes an input and schedules an action with the + * value of that input. + * + * @param action the action to schedule + * @param port the port to read from + */ + String generateDelayBody(Action action, VarRef port); + + /** + * Generate code for the body of a reaction that is triggered by the given action and writes its + * value to the given port. + * + * @param action the action that triggers the reaction + * @param port the port to write to + */ + String generateForwardBody(Action action, VarRef port); + + /** + * Generate code for the generic type to be used in the class definition of a generated delay + * reactor. + */ + String generateDelayGeneric(); + + /** + * Indicates whether delay banks generated from after delays should have a variable length width. + * + *

If this is true, any delay reactors that are inserted for after delays on multiport + * connections will have an unspecified variable length width. The code generator is then + * responsible for inferring the correct width of the delay bank, which is only possible if the + * precise connection width is known at compile time. + * + *

If this is false, the width specification of the generated bank will list all the ports + * listed on the right side of the connection. This gives the code generator the information + * needed to infer the correct width at runtime. + */ + boolean generateAfterDelaysWithVariableWidth(); + + /** Used to optionally apply additional transformations to the generated reactions */ + default void finalizeReactions(Reaction delayReaction, Reaction forwardReaction) {} } diff --git a/org.lflang/src/org/lflang/generator/DiagnosticReporting.java b/org.lflang/src/org/lflang/generator/DiagnosticReporting.java index 82d3535444..871f417a0d 100644 --- a/org.lflang/src/org/lflang/generator/DiagnosticReporting.java +++ b/org.lflang/src/org/lflang/generator/DiagnosticReporting.java @@ -2,59 +2,62 @@ import java.nio.file.Path; import java.util.Map; - import org.eclipse.lsp4j.DiagnosticSeverity; - import org.lflang.ErrorReporter; /** - * {@code DiagnosticReporting} provides utilities for - * reporting validation output. + * {@code DiagnosticReporting} provides utilities for reporting validation output. * * @author Peter Donovan */ public class DiagnosticReporting { - private DiagnosticReporting() { - // utility class - } - - /** - * A means of parsing the output of a validator. - */ - @FunctionalInterface public interface Strategy { - /** - * Parse the validation output and report any errors - * that it contains. - * @param validationOutput any validation output - * @param errorReporter any error reporter - * @param map the map from generated files to CodeMaps - */ - void report(String validationOutput, ErrorReporter errorReporter, Map map); - } - - /** - * Format the given data as a human-readable message. - * @param message An error message. - * @param path The path of the source of the message. - * @param position The position where the message originates. - * @return The given data as a human-readable message. - */ - public static String messageOf(String message, Path path, Position position) { - return String.format("%s [%s:%s:%s]", message, path.getFileName().toString(), position.getOneBasedLine(), position.getOneBasedColumn()); - } + private DiagnosticReporting() { + // utility class + } + /** A means of parsing the output of a validator. */ + @FunctionalInterface + public interface Strategy { /** - * Convert {@code severity} into a {@code DiagnosticSeverity} using a heuristic that should be - * compatible with many tools. - * @param severity The string representation of a diagnostic severity. - * @return The {@code DiagnosticSeverity} representation of {@code severity}. + * Parse the validation output and report any errors that it contains. + * + * @param validationOutput any validation output + * @param errorReporter any error reporter + * @param map the map from generated files to CodeMaps */ - public static DiagnosticSeverity severityOf(String severity) { - severity = severity.toLowerCase(); - if (severity.contains("error")) return DiagnosticSeverity.Error; - else if (severity.contains("warning")) return DiagnosticSeverity.Warning; - else if (severity.contains("hint") || severity.contains("help")) return DiagnosticSeverity.Hint; - else return DiagnosticSeverity.Information; - } + void report(String validationOutput, ErrorReporter errorReporter, Map map); + } + + /** + * Format the given data as a human-readable message. + * + * @param message An error message. + * @param path The path of the source of the message. + * @param position The position where the message originates. + * @return The given data as a human-readable message. + */ + public static String messageOf(String message, Path path, Position position) { + return String.format( + "%s [%s:%s:%s]", + message, + path.getFileName().toString(), + position.getOneBasedLine(), + position.getOneBasedColumn()); + } + + /** + * Convert {@code severity} into a {@code DiagnosticSeverity} using a heuristic that should be + * compatible with many tools. + * + * @param severity The string representation of a diagnostic severity. + * @return The {@code DiagnosticSeverity} representation of {@code severity}. + */ + public static DiagnosticSeverity severityOf(String severity) { + severity = severity.toLowerCase(); + if (severity.contains("error")) return DiagnosticSeverity.Error; + else if (severity.contains("warning")) return DiagnosticSeverity.Warning; + else if (severity.contains("hint") || severity.contains("help")) return DiagnosticSeverity.Hint; + else return DiagnosticSeverity.Information; + } } diff --git a/org.lflang/src/org/lflang/generator/DockerComposeGenerator.java b/org.lflang/src/org/lflang/generator/DockerComposeGenerator.java index 040d925614..a3e5028317 100644 --- a/org.lflang/src/org/lflang/generator/DockerComposeGenerator.java +++ b/org.lflang/src/org/lflang/generator/DockerComposeGenerator.java @@ -4,7 +4,6 @@ import java.nio.file.Path; import java.util.List; import java.util.stream.Collectors; - import org.lflang.util.FileUtil; /** @@ -15,109 +14,104 @@ */ public class DockerComposeGenerator { - /** - * Path to the docker-compose.yml file. - */ - protected final Path path; + /** Path to the docker-compose.yml file. */ + protected final Path path; - public DockerComposeGenerator(LFGeneratorContext context) { - this.path = context.getFileConfig().getSrcGenPath().resolve("docker-compose.yml"); - } + public DockerComposeGenerator(LFGeneratorContext context) { + this.path = context.getFileConfig().getSrcGenPath().resolve("docker-compose.yml"); + } - /** - * Return a string that represents the network portion of the docker-compose configuration. - * @param networkName Name of the default network - */ - protected String generateDockerNetwork(String networkName) { - return """ + /** + * Return a string that represents the network portion of the docker-compose configuration. + * + * @param networkName Name of the default network + */ + protected String generateDockerNetwork(String networkName) { + return """ networks: default: name: "%s" - """.formatted(networkName); - } + """ + .formatted(networkName); + } - /** - * Return a string that represents the services portion of the docker-compose configuration. - * @param services A list of docker data representing the services to render - */ - protected String generateDockerServices(List services) { - return """ - version: "3.9" + /** + * Return a string that represents the services portion of the docker-compose configuration. + * + * @param services A list of docker data representing the services to render + */ + protected String generateDockerServices(List services) { + return """ + version: "3.9" services: %s - """.formatted(services.stream().map( - data -> getServiceDescription(data) - ).collect(Collectors.joining("\n"))); - } + """ + .formatted( + services.stream() + .map(data -> getServiceDescription(data)) + .collect(Collectors.joining("\n"))); + } - /** - * Return the command to build and run using the docker-compose configuration. - */ - public String getUsageInstructions() { - return """ + /** Return the command to build and run using the docker-compose configuration. */ + public String getUsageInstructions() { + return """ ##################################### To build and run: pushd %s && docker compose up --build To return to the current working directory afterwards: popd ##################################### - """.formatted(path.getParent()); - } + """ + .formatted(path.getParent()); + } - /** - * Turn given docker data into a string. - */ - protected String getServiceDescription(DockerData data) { - return """ + /** Turn given docker data into a string. */ + protected String getServiceDescription(DockerData data) { + return """ %s: build: context: "%s" container_name: "%s" - """.formatted(getServiceName(data), getBuildContext(data), getContainerName(data)); - } + """ + .formatted(getServiceName(data), getBuildContext(data), getContainerName(data)); + } - /** - * Return the name of the service represented by the given data. - */ - protected String getServiceName(DockerData data) { - return "main"; - } + /** Return the name of the service represented by the given data. */ + protected String getServiceName(DockerData data) { + return "main"; + } - /** - * Return the name of the service represented by the given data. - */ - protected String getBuildContext(DockerData data) { - return "."; - } + /** Return the name of the service represented by the given data. */ + protected String getBuildContext(DockerData data) { + return "."; + } - /** - * Return the name of the container for the given data. - */ - protected String getContainerName(DockerData data) { - return data.serviceName; - } + /** Return the name of the container for the given data. */ + protected String getContainerName(DockerData data) { + return data.serviceName; + } - /** - * Write the docker-compose.yml file with a default network called "lf". - * @param services A list of all the services. - */ - public void writeDockerComposeFile(List services) throws IOException { - writeDockerComposeFile(services, "lf"); - } + /** + * Write the docker-compose.yml file with a default network called "lf". + * + * @param services A list of all the services. + */ + public void writeDockerComposeFile(List services) throws IOException { + writeDockerComposeFile(services, "lf"); + } - /** - * Write the docker-compose.yml file. - * @param services A list of all the services to include. - * @param networkName The name of the network to which docker will connect the services. - */ - public void writeDockerComposeFile( - List services, - String networkName - ) throws IOException { - var contents = String.join("\n", - this.generateDockerServices(services), - this.generateDockerNetwork(networkName)); - FileUtil.writeToFile(contents, path); - System.out.println(getUsageInstructions()); - } + /** + * Write the docker-compose.yml file. + * + * @param services A list of all the services to include. + * @param networkName The name of the network to which docker will connect the services. + */ + public void writeDockerComposeFile(List services, String networkName) + throws IOException { + var contents = + String.join( + "\n", this.generateDockerServices(services), this.generateDockerNetwork(networkName)); + FileUtil.writeToFile(contents, path); + System.out.println(getUsageInstructions()); + } } diff --git a/org.lflang/src/org/lflang/generator/DockerData.java b/org.lflang/src/org/lflang/generator/DockerData.java index a2b1927125..af29e9e1d5 100644 --- a/org.lflang/src/org/lflang/generator/DockerData.java +++ b/org.lflang/src/org/lflang/generator/DockerData.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.nio.file.Path; - import org.lflang.util.FileUtil; /** @@ -11,43 +10,31 @@ * @author Marten Lohstroh */ public class DockerData { - /** - * The absolute path to the docker file. - */ - private final Path dockerFilePath; + /** The absolute path to the docker file. */ + private final Path dockerFilePath; - /** - * The content of the docker file to be generated. - */ - private final String dockerFileContent; + /** The content of the docker file to be generated. */ + private final String dockerFileContent; - /** - * The name of the service. - */ - public final String serviceName; + /** The name of the service. */ + public final String serviceName; - public DockerData( - String serviceName, - Path dockerFilePath, - String dockerFileContent - ) { + public DockerData(String serviceName, Path dockerFilePath, String dockerFileContent) { - if (!dockerFilePath.toFile().isAbsolute()) { - throw new RuntimeException("Cannot use relative docker file path in DockerData instance"); - } - this.serviceName = serviceName; - this.dockerFilePath = dockerFilePath; - this.dockerFileContent = dockerFileContent; + if (!dockerFilePath.toFile().isAbsolute()) { + throw new RuntimeException("Cannot use relative docker file path in DockerData instance"); } - - /** - * Write a docker file based on this data. - */ - public void writeDockerFile() throws IOException { - if (dockerFilePath.toFile().exists()) { - dockerFilePath.toFile().delete(); - } - FileUtil.writeToFile(dockerFileContent, dockerFilePath); - System.out.println("Dockerfile written to " + dockerFilePath); + this.serviceName = serviceName; + this.dockerFilePath = dockerFilePath; + this.dockerFileContent = dockerFileContent; + } + + /** Write a docker file based on this data. */ + public void writeDockerFile() throws IOException { + if (dockerFilePath.toFile().exists()) { + dockerFilePath.toFile().delete(); } + FileUtil.writeToFile(dockerFileContent, dockerFilePath); + System.out.println("Dockerfile written to " + dockerFilePath); + } } diff --git a/org.lflang/src/org/lflang/generator/DockerGenerator.java b/org.lflang/src/org/lflang/generator/DockerGenerator.java index 851ee6c0be..cf6044f14f 100644 --- a/org.lflang/src/org/lflang/generator/DockerGenerator.java +++ b/org.lflang/src/org/lflang/generator/DockerGenerator.java @@ -4,7 +4,6 @@ import org.lflang.generator.python.PythonDockerGenerator; import org.lflang.generator.ts.TSDockerGenerator; - /** * A class for generating docker files. * @@ -13,46 +12,43 @@ */ public abstract class DockerGenerator { - /** - * Configuration for interactions with the filesystem. - */ - protected final LFGeneratorContext context; - - /** - * The constructor for the base docker file generation class. - * @param context The context of the code generator. - */ - public DockerGenerator(LFGeneratorContext context) { - this.context = context; - - } - - /** - * Generate the contents of a Dockerfile. - */ - protected abstract String generateDockerFileContent(); - - /** - * Produce a DockerData object. - * If the returned object is to be used in a federated context, - * pass in the file configuration of the federated generator, null otherwise. - * @return docker data created based on the context in this instance - */ - public DockerData generateDockerData() { - var name = context.getFileConfig().name; - var dockerFilePath = context.getFileConfig().getSrcGenPath().resolve("Dockerfile"); - var dockerFileContent = generateDockerFileContent(); - - return new DockerData(name.replace("_", ""), dockerFilePath, dockerFileContent); - } - - public static DockerGenerator dockerGeneratorFactory(LFGeneratorContext context) { - var target = context.getTargetConfig().target; - return switch (target) { - case C, CCPP -> new CDockerGenerator(context); - case TS -> new TSDockerGenerator(context); - case Python -> new PythonDockerGenerator(context); - case CPP, Rust -> throw new IllegalArgumentException("No Docker support for " + target + " yet."); - }; - } + /** Configuration for interactions with the filesystem. */ + protected final LFGeneratorContext context; + + /** + * The constructor for the base docker file generation class. + * + * @param context The context of the code generator. + */ + public DockerGenerator(LFGeneratorContext context) { + this.context = context; + } + + /** Generate the contents of a Dockerfile. */ + protected abstract String generateDockerFileContent(); + + /** + * Produce a DockerData object. If the returned object is to be used in a federated context, pass + * in the file configuration of the federated generator, null otherwise. + * + * @return docker data created based on the context in this instance + */ + public DockerData generateDockerData() { + var name = context.getFileConfig().name; + var dockerFilePath = context.getFileConfig().getSrcGenPath().resolve("Dockerfile"); + var dockerFileContent = generateDockerFileContent(); + + return new DockerData(name.replace("_", ""), dockerFilePath, dockerFileContent); + } + + public static DockerGenerator dockerGeneratorFactory(LFGeneratorContext context) { + var target = context.getTargetConfig().target; + return switch (target) { + case C, CCPP -> new CDockerGenerator(context); + case TS -> new TSDockerGenerator(context); + case Python -> new PythonDockerGenerator(context); + case CPP, Rust -> throw new IllegalArgumentException( + "No Docker support for " + target + " yet."); + }; + } } diff --git a/org.lflang/src/org/lflang/generator/FedDockerComposeGenerator.java b/org.lflang/src/org/lflang/generator/FedDockerComposeGenerator.java index d666abba2d..18896f03a0 100644 --- a/org.lflang/src/org/lflang/generator/FedDockerComposeGenerator.java +++ b/org.lflang/src/org/lflang/generator/FedDockerComposeGenerator.java @@ -1,6 +1,5 @@ package org.lflang.generator; - import java.util.List; /** @@ -10,57 +9,55 @@ */ public class FedDockerComposeGenerator extends DockerComposeGenerator { - /** - * The host on which to run the rti. - */ - private String rtiHost; + /** The host on which to run the rti. */ + private String rtiHost; - /** - * The name of this federation. - */ - private String containerName; + /** The name of this federation. */ + private String containerName; - public FedDockerComposeGenerator(LFGeneratorContext context, String rtiHost) { - super(context); - this.rtiHost = rtiHost; - this.containerName = context.getFileConfig().name; - } + public FedDockerComposeGenerator(LFGeneratorContext context, String rtiHost) { + super(context); + this.rtiHost = rtiHost; + this.containerName = context.getFileConfig().name; + } - @Override - protected String generateDockerServices(List services) { - return """ + @Override + protected String generateDockerServices(List services) { + return """ %s\ rti: image: "lflang/rti:rti" hostname: "%s" command: "-i 1 -n %s" container_name: "%s-rti" - """.formatted(super.generateDockerServices(services), - this.rtiHost, services.size(), containerName); - } - - @Override - protected String getServiceDescription(DockerData data) { - return """ + """ + .formatted( + super.generateDockerServices(services), this.rtiHost, services.size(), containerName); + } + + @Override + protected String getServiceDescription(DockerData data) { + return """ %s\ command: "-i 1" depends_on: - rti - """.formatted(super.getServiceDescription(data)); - } - - @Override - protected String getServiceName(DockerData data) { - return data.serviceName; - } - - @Override - protected String getBuildContext(DockerData data) { - return data.serviceName; - } - - @Override - protected String getContainerName(DockerData data) { - return this.containerName + "-" + data.serviceName; - } + """ + .formatted(super.getServiceDescription(data)); + } + + @Override + protected String getServiceName(DockerData data) { + return data.serviceName; + } + + @Override + protected String getBuildContext(DockerData data) { + return data.serviceName; + } + + @Override + protected String getContainerName(DockerData data) { + return this.containerName + "-" + data.serviceName; + } } diff --git a/org.lflang/src/org/lflang/generator/GenerationException.java b/org.lflang/src/org/lflang/generator/GenerationException.java index 1920e9aca8..1bc7985ee7 100644 --- a/org.lflang/src/org/lflang/generator/GenerationException.java +++ b/org.lflang/src/org/lflang/generator/GenerationException.java @@ -27,40 +27,36 @@ import org.eclipse.emf.ecore.EObject; // import org.jetbrains.annotations.Nullable; -/** - * An exception that occurred during code generation. May also - * wrap another exception. - */ -public class GenerationException extends RuntimeException { // note that this is an unchecked exception. - - /* @Nullable */ - private final EObject location; - - public GenerationException(String message) { - this(null, message, null); - } - - public GenerationException(/* @Nullable */ EObject location, String message) { - this(location, message, null); - } - - public GenerationException(String message, Throwable cause) { - this(null, message, cause); - - } - - public GenerationException(/* @Nullable */ EObject location, String message, Throwable cause) { - super(message, cause); - this.location = location; - } - - public GenerationException(Throwable cause) { - this(null, null, cause); - } - - /* @Nullable */ - public EObject getLocation() { - return location; - } +/** An exception that occurred during code generation. May also wrap another exception. */ +public class GenerationException + extends RuntimeException { // note that this is an unchecked exception. + + /* @Nullable */ + private final EObject location; + + public GenerationException(String message) { + this(null, message, null); + } + + public GenerationException(/* @Nullable */ EObject location, String message) { + this(location, message, null); + } + + public GenerationException(String message, Throwable cause) { + this(null, message, cause); + } + + public GenerationException(/* @Nullable */ EObject location, String message, Throwable cause) { + super(message, cause); + this.location = location; + } + + public GenerationException(Throwable cause) { + this(null, null, cause); + } + + /* @Nullable */ + public EObject getLocation() { + return location; + } } - diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.java b/org.lflang/src/org/lflang/generator/GeneratorBase.java index 5ae5045533..942956c2d5 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.java +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.java @@ -24,47 +24,42 @@ ***************/ 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.ast.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.MainConflictChecker; import org.lflang.Target; import org.lflang.TargetConfig; +import org.lflang.ast.ASTUtils; import org.lflang.ast.AstTransformation; import org.lflang.graph.InstantiationGraph; import org.lflang.lf.Connection; 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 @@ -74,566 +69,570 @@ */ 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 {@code 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 {@code 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, {@code 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; - - /** - * 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 {@code 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 {@code 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, {@code 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; + + /** 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 {@code 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 {@code 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 {@code 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, {@code 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 {@code reactor Foo {bar = new Bar()}} then the definition of {@code Bar} has to be generated before - // the definition of {@code 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 {@code 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, {@code 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 {@code reactor Foo {bar = new Bar()}} then the definition of {@code Bar} has + // to be generated before + // the definition of {@code 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 {@code files} from. - * @param fileConfig The fileConfig used to make the copy and resolve paths. - */ - protected void copyUserFiles(TargetConfig targetConfig, FileConfig fileConfig) { - var dst = this.context.getFileConfig().getSrcGenPath(); - FileUtil.copyFilesOrDirectories(targetConfig.files, dst, fileConfig, errorReporter, false); - } - - /** - * 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 {@code files} from. + * @param fileConfig The fileConfig used to make the copy and resolve paths. + */ + protected void copyUserFiles(TargetConfig targetConfig, FileConfig fileConfig) { + var dst = this.context.getFileConfig().getSrcGenPath(); + FileUtil.copyFilesOrDirectories(targetConfig.files, dst, fileConfig, errorReporter, false); + } + + /** + * 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 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 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!"); - } + } + + /** + * 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( + "Watchdogs are currently only supported for threaded programs in the C target."); } - - /** - * 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) { + } + + /** + * 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."); - } - } - - /** - * 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"; - } - - /** - * 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; + connection, + "Cannot transform connection in modal reactor. Connection uses currently not" + + " supported features."); + } else { + var reaction = factory.createReaction(); + ((Mode) connection.eContainer()).getReactions().add(reaction); + + var sourceRef = connection.getLeftPorts().get(0); + var destRef = connection.getRightPorts().get(0); + reaction.getTriggers().add(sourceRef); + reaction.getEffects().add(destRef); + + var code = factory.createCode(); + var source = + (sourceRef.getContainer() != null ? sourceRef.getContainer().getName() + "." : "") + + sourceRef.getVariable().getName(); + var dest = + (destRef.getContainer() != null ? destRef.getContainer().getName() + "." : "") + + destRef.getVariable().getName(); + code.setBody(getConflictingConnectionsInModalReactorsBody(source, dest)); + reaction.setCode(code); + + EcoreUtil.remove(connection); + } } + } } - - /** - * Given a line of text from the output of a compiler, return - * an instance of ErrorFileAndLine if the line is recognized as - * the first line of an error message. Otherwise, return null. - * This base class simply returns null. - * @param line A line of output from a compiler or other external - * tool that might generate errors. - * @return If the line is recognized as the start of an error message, - * then return a class containing the path to the file on which the - * error occurred (or null if there is none), the line number (or the - * string "1" if there is none), the character position (or the string - * "0" if there is none), and the message (or an empty string if there - * is none). - */ - protected ErrorFileAndLine parseCommandOutput(String line) { - return null; + } + + /** + * Return target code for forwarding reactions iff the connections have the same destination as + * other connections or reaction in mutually exclusive modes. + * + *

This method needs to be overridden in target specific code generators that support modal + * reactors. + */ + protected String getConflictingConnectionsInModalReactorsBody(String source, String dest) { + errorReporter.reportError( + "The currently selected code generation " + + "is missing an implementation for conflicting " + + "transforming connections in modal reactors."); + return "MODAL MODELS NOT SUPPORTED"; + } + + /** Hook for additional post-processing of the model. */ + protected void additionalPostProcessingForModes() { + // Do nothing + } + + /** Parsed error message from a compiler is returned here. */ + public static class ErrorFileAndLine { + public String filepath = null; + public String line = "1"; + public String character = "0"; + public String message = ""; + public boolean isError = true; // false for a warning. + + @Override + public String toString() { + return (isError ? "Error" : "Non-error") + + " at " + + line + + ":" + + character + + " of file " + + filepath + + ": " + + message; } - - /** - * Parse the specified string for command errors that can be reported - * using marks in the Eclipse IDE. In this class, we attempt to parse - * the messages to look for file and line information, thereby generating - * marks on the appropriate lines. This should not be called in standalone - * mode. - * - * @param stderr The output on standard error of executing a command. - */ - public void reportCommandErrors(String stderr) { - // NOTE: If the VS Code branch passes code review, then this function, - // parseCommandOutput, and ErrorFileAndLine will be deleted soon after. - // First, split the message into lines. - String[] lines = stderr.split("\\r?\\n"); - StringBuilder message = new StringBuilder(); - Integer lineNumber = null; - Path path = context.getFileConfig().srcFile; - // In case errors occur within an imported file, record the original path. - Path originalPath = path; - - int severity = IMarker.SEVERITY_ERROR; - for (String line : lines) { - ErrorFileAndLine parsed = parseCommandOutput(line); - if (parsed != null) { - // Found a new line number designator. - // If there is a previously accumulated message, report it. - if (message.length() > 0) { - if (severity == IMarker.SEVERITY_ERROR) - errorReporter.reportError(path, lineNumber, message.toString()); - else - errorReporter.reportWarning(path, lineNumber, message.toString()); - - if (!Objects.equal(originalPath.toFile(), path.toFile())) { - // Report an error also in the top-level resource. - // FIXME: It should be possible to descend through the import - // statements to find which one matches and mark all the - // import statements down the chain. But what a pain! - if (severity == IMarker.SEVERITY_ERROR) { - errorReporter.reportError(originalPath, 1, "Error in imported file: " + path); - } else { - errorReporter.reportWarning(originalPath, 1, "Warning in imported file: " + path); - } - } - } - if (parsed.isError) { - severity = IMarker.SEVERITY_ERROR; - } else { - severity = IMarker.SEVERITY_WARNING; - } - - // Start accumulating a new message. - message = new StringBuilder(); - // Append the message on the line number designator line. - message.append(parsed.message); - - // Set the new line number. - try { - lineNumber = Integer.decode(parsed.line); - } catch (Exception ex) { - // Set the line number unknown. - lineNumber = null; - } - // FIXME: Ignoring the position within the line. - // Determine the path within which the error occurred. - path = Paths.get(parsed.filepath); - } else { - // No line designator. - if (message.length() > 0) { - message.append("\n"); - } else { - if (!line.toLowerCase().contains("error:")) { - severity = IMarker.SEVERITY_WARNING; - } - } - message.append(line); - } - } + } + + /** + * Given a line of text from the output of a compiler, return an instance of ErrorFileAndLine if + * the line is recognized as the first line of an error message. Otherwise, return null. This base + * class simply returns null. + * + * @param line A line of output from a compiler or other external tool that might generate errors. + * @return If the line is recognized as the start of an error message, then return a class + * containing the path to the file on which the error occurred (or null if there is none), the + * line number (or the string "1" if there is none), the character position (or the string "0" + * if there is none), and the message (or an empty string if there is none). + */ + protected ErrorFileAndLine parseCommandOutput(String line) { + return null; + } + + /** + * Parse the specified string for command errors that can be reported using marks in the Eclipse + * IDE. In this class, we attempt to parse the messages to look for file and line information, + * thereby generating marks on the appropriate lines. This should not be called in standalone + * mode. + * + * @param stderr The output on standard error of executing a command. + */ + public void reportCommandErrors(String stderr) { + // NOTE: If the VS Code branch passes code review, then this function, + // parseCommandOutput, and ErrorFileAndLine will be deleted soon after. + // First, split the message into lines. + String[] lines = stderr.split("\\r?\\n"); + StringBuilder message = new StringBuilder(); + Integer lineNumber = null; + Path path = context.getFileConfig().srcFile; + // In case errors occur within an imported file, record the original path. + Path originalPath = path; + + int severity = IMarker.SEVERITY_ERROR; + for (String line : lines) { + ErrorFileAndLine parsed = parseCommandOutput(line); + if (parsed != null) { + // Found a new line number designator. + // If there is a previously accumulated message, report it. if (message.length() > 0) { + if (severity == IMarker.SEVERITY_ERROR) + errorReporter.reportError(path, lineNumber, message.toString()); + else errorReporter.reportWarning(path, lineNumber, message.toString()); + + if (!Objects.equal(originalPath.toFile(), path.toFile())) { + // Report an error also in the top-level resource. + // FIXME: It should be possible to descend through the import + // statements to find which one matches and mark all the + // import statements down the chain. But what a pain! if (severity == IMarker.SEVERITY_ERROR) { - errorReporter.reportError(path, lineNumber, message.toString()); + errorReporter.reportError(originalPath, 1, "Error in imported file: " + path); } else { - errorReporter.reportWarning(path, lineNumber, message.toString()); + errorReporter.reportWarning(originalPath, 1, "Warning in imported file: " + path); } + } + } + if (parsed.isError) { + severity = IMarker.SEVERITY_ERROR; + } else { + severity = IMarker.SEVERITY_WARNING; + } - if (originalPath.toFile() != path.toFile()) { - // Report an error also in the top-level resource. - // FIXME: It should be possible to descend through the import - // statements to find which one matches and mark all the - // import statements down the chain. But what a pain! - if (severity == IMarker.SEVERITY_ERROR) { - errorReporter.reportError(originalPath, 1, "Error in imported file: " + path); - } else { - errorReporter.reportWarning(originalPath, 1, "Warning in imported file: " + path); - } - } + // Start accumulating a new message. + message = new StringBuilder(); + // Append the message on the line number designator line. + message.append(parsed.message); + + // Set the new line number. + try { + lineNumber = Integer.decode(parsed.line); + } catch (Exception ex) { + // Set the line number unknown. + lineNumber = null; + } + // FIXME: Ignoring the position within the line. + // Determine the path within which the error occurred. + path = Paths.get(parsed.filepath); + } else { + // No line designator. + if (message.length() > 0) { + message.append("\n"); + } else { + if (!line.toLowerCase().contains("error:")) { + severity = IMarker.SEVERITY_WARNING; + } } + message.append(line); + } } - - // ////////////////////////////////////////////////// - // // Private functions - - /** - * Print to stdout information about what source file is being generated, - * what mode the generator is in, and where the generated sources are to be put. - */ - public void printInfo(LFGeneratorContext.Mode mode) { - System.out.println("Generating code for: " + context.getFileConfig().resource.getURI().toString()); - System.out.println("******** mode: " + mode); - System.out.println("******** generated sources: " + context.getFileConfig().getSrcGenPath()); + if (message.length() > 0) { + if (severity == IMarker.SEVERITY_ERROR) { + errorReporter.reportError(path, lineNumber, message.toString()); + } else { + errorReporter.reportWarning(path, lineNumber, message.toString()); + } + + if (originalPath.toFile() != path.toFile()) { + // Report an error also in the top-level resource. + // FIXME: It should be possible to descend through the import + // statements to find which one matches and mark all the + // import statements down the chain. But what a pain! + if (severity == IMarker.SEVERITY_ERROR) { + errorReporter.reportError(originalPath, 1, "Error in imported file: " + path); + } else { + errorReporter.reportWarning(originalPath, 1, "Warning in imported file: " + path); + } + } } - - /** - * Get the buffer type used for network messages - */ - public String getNetworkBufferType() { return ""; } - - /** - * Return the Targets enum for the current target - */ - public abstract Target getTarget(); + } + + // ////////////////////////////////////////////////// + // // Private functions + + /** + * Print to stdout information about what source file is being generated, what mode the generator + * is in, and where the generated sources are to be put. + */ + public void printInfo(LFGeneratorContext.Mode mode) { + System.out.println( + "Generating code for: " + context.getFileConfig().resource.getURI().toString()); + System.out.println("******** mode: " + mode); + System.out.println("******** generated sources: " + context.getFileConfig().getSrcGenPath()); + } + + /** Get the buffer type used for network messages */ + public String getNetworkBufferType() { + return ""; + } + + /** Return the Targets enum for the current target */ + public abstract Target getTarget(); } diff --git a/org.lflang/src/org/lflang/generator/GeneratorCommandFactory.java b/org.lflang/src/org/lflang/generator/GeneratorCommandFactory.java index 081f07320b..b1dc44e094 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorCommandFactory.java +++ b/org.lflang/src/org/lflang/generator/GeneratorCommandFactory.java @@ -1,26 +1,26 @@ /************* - Copyright (c) 2019-2021 TU Dresden - Copyright (c) 2019-2021 UC 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 TU Dresden + * Copyright (c) 2019-2021 UC 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; @@ -29,120 +29,128 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.nio.file.Paths; import java.util.List; import java.util.Objects; - import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.util.LFCommand; /** * A factory class responsible for creating commands for use by the LF code generators. - *

- * In addition to the basic functionality of LFCommand, this class additionally ensures that error messages (or - * optionally warnings) are shown when a command is not found and that certain environment variables are set (see - * {@link #createCommand(String, List, Path, boolean) createCommand}). + * + *

In addition to the basic functionality of LFCommand, this class additionally ensures that + * error messages (or optionally warnings) are shown when a command is not found and that certain + * environment variables are set (see {@link #createCommand(String, List, Path, boolean) + * createCommand}). */ public class GeneratorCommandFactory { - protected final ErrorReporter errorReporter; - protected final FileConfig fileConfig; - protected boolean quiet = false; - - public GeneratorCommandFactory(ErrorReporter errorReporter, FileConfig fileConfig) { - this.errorReporter = Objects.requireNonNull(errorReporter); - this.fileConfig = Objects.requireNonNull(fileConfig); - } - - /// enable quiet mode (command output is not printed) - public void setQuiet() { quiet = true; } - - /// enable verbose mode (command output is printed) - public void setVerbose() { quiet = false; } - - /** - * Create a LFCommand instance from a given command and an argument list. - *

- * The command will be executed in the CWD and if the command cannot be found an error message is shown. - * - * @param cmd the command to look up - * @param args a list of arguments - * @return an LFCommand object or null if the command could not be found - * @see #createCommand(String, List, Path, boolean) - */ - public LFCommand createCommand(String cmd, List args) { - return createCommand(cmd, args, null, true); + protected final ErrorReporter errorReporter; + protected final FileConfig fileConfig; + protected boolean quiet = false; + + public GeneratorCommandFactory(ErrorReporter errorReporter, FileConfig fileConfig) { + this.errorReporter = Objects.requireNonNull(errorReporter); + this.fileConfig = Objects.requireNonNull(fileConfig); + } + + /// enable quiet mode (command output is not printed) + public void setQuiet() { + quiet = true; + } + + /// enable verbose mode (command output is printed) + public void setVerbose() { + quiet = false; + } + + /** + * Create a LFCommand instance from a given command and an argument list. + * + *

The command will be executed in the CWD and if the command cannot be found an error message + * is shown. + * + * @param cmd the command to look up + * @param args a list of arguments + * @return an LFCommand object or null if the command could not be found + * @see #createCommand(String, List, Path, boolean) + */ + public LFCommand createCommand(String cmd, List args) { + return createCommand(cmd, args, null, true); + } + + /** + * Create a LFCommand instance from a given command, an argument list and a directory. + * + * @param cmd the command to look up + * @param args a list of arguments + * @param failOnError If true, an error is shown if the command cannot be found. Otherwise, only a + * warning is displayed. + * @return an LFCommand object or null if the command could not be found + * @see #createCommand(String, List, Path, boolean) + */ + public LFCommand createCommand(String cmd, List args, boolean failOnError) { + return createCommand(cmd, args, null, failOnError); + } + + /** + * Create a LFCommand instance from a given command, an argument list and a directory. + * + *

The command will be executed in the CWD and if the command cannot be found an error message + * is shown. + * + * @param cmd the command to look up + * @param args a list of arguments + * @param dir the directory to execute the command in. If null, this will default to the CWD + * @return an LFCommand object or null if the command could not be found + * @see #createCommand(String, List, Path, boolean) + */ + public LFCommand createCommand(String cmd, List args, Path dir) { + return createCommand(cmd, args, dir, true); + } + + /** + * Create a LFCommand instance from a given command, an argument list and a directory. + * + *

This will check first if the command can actually be found and executed. If the command is + * not found, null is returned. In addition, an error message will be shown if failOnError is + * true. Otherwise, a warning will be displayed. + * + * @param cmd the command to look up + * @param args a list of arguments + * @param dir the directory to execute the command in. If null, this will default to the CWD + * @param failOnError If true, an error is shown if the command cannot be found. Otherwise, only a + * warning is displayed. + * @return an LFCommand object or null if the command could not be found + * @see LFCommand#get(String, List, boolean, Path) + */ + public LFCommand createCommand(String cmd, List args, Path dir, boolean failOnError) { + assert cmd != null && args != null; + if (dir == null) { + dir = Paths.get(""); } - - - /** - * Create a LFCommand instance from a given command, an argument list and a directory. - * - * @param cmd the command to look up - * @param args a list of arguments - * @param failOnError If true, an error is shown if the command cannot be found. Otherwise, only a warning is - * displayed. - * @return an LFCommand object or null if the command could not be found - * @see #createCommand(String, List, Path, boolean) - */ - public LFCommand createCommand(String cmd, List args, boolean failOnError) { - return createCommand(cmd, args, null, failOnError); + LFCommand command = LFCommand.get(cmd, args, quiet, dir); + if (command != null) { + command.setEnvironmentVariable("LF_CURRENT_WORKING_DIRECTORY", dir.toString()); + command.setEnvironmentVariable("LF_SOURCE_DIRECTORY", fileConfig.srcPath.toString()); + command.setEnvironmentVariable("LF_PACKAGE_DIRECTORY", fileConfig.srcPkgPath.toString()); + command.setEnvironmentVariable( + "LF_SOURCE_GEN_DIRECTORY", fileConfig.getSrcGenPath().toString()); + command.setEnvironmentVariable("LF_BIN_DIRECTORY", fileConfig.binPath.toString()); + } else { + final String message = + "The command " + + cmd + + " could not be found in the current working directory or in your PATH. " + + "Make sure that your PATH variable includes the directory where " + + cmd + + " is installed. " + + "You can set PATH in ~/.bash_profile on Linux or Mac."; + if (failOnError) { + errorReporter.reportError(message); + } else { + errorReporter.reportWarning(message); + } } - - /** - * Create a LFCommand instance from a given command, an argument list and a directory. - *

- * The command will be executed in the CWD and if the command cannot be found an error message is shown. - * - * @param cmd the command to look up - * @param args a list of arguments - * @param dir the directory to execute the command in. If null, this will default to the CWD - * @return an LFCommand object or null if the command could not be found - * @see #createCommand(String, List, Path, boolean) - */ - public LFCommand createCommand(String cmd, List args, Path dir) { - return createCommand(cmd, args, dir, true); - } - - - /** - * Create a LFCommand instance from a given command, an argument list and a directory. - *

- * This will check first if the command can actually be found and executed. If the command is not found, null is - * returned. In addition, an error message will be shown if failOnError is true. Otherwise, a warning will be - * displayed. - * - * @param cmd the command to look up - * @param args a list of arguments - * @param dir the directory to execute the command in. If null, this will default to the CWD - * @param failOnError If true, an error is shown if the command cannot be found. Otherwise, only a warning is - * displayed. - * @return an LFCommand object or null if the command could not be found - * @see LFCommand#get(String, List, boolean, Path) - */ - public LFCommand createCommand(String cmd, List args, Path dir, boolean failOnError) { - assert cmd != null && args != null; - if (dir == null) { - dir = Paths.get(""); - } - LFCommand command = LFCommand.get(cmd, args, quiet, dir); - if (command != null) { - command.setEnvironmentVariable("LF_CURRENT_WORKING_DIRECTORY", dir.toString()); - command.setEnvironmentVariable("LF_SOURCE_DIRECTORY", fileConfig.srcPath.toString()); - command.setEnvironmentVariable("LF_PACKAGE_DIRECTORY", fileConfig.srcPkgPath.toString()); - command.setEnvironmentVariable("LF_SOURCE_GEN_DIRECTORY", fileConfig.getSrcGenPath().toString()); - command.setEnvironmentVariable("LF_BIN_DIRECTORY", fileConfig.binPath.toString()); - } else { - final String message = - "The command " + cmd + " could not be found in the current working directory or in your PATH. " + - "Make sure that your PATH variable includes the directory where " + cmd + " is installed. " + - "You can set PATH in ~/.bash_profile on Linux or Mac."; - if (failOnError) { - errorReporter.reportError(message); - } else { - errorReporter.reportWarning(message); - } - } - - return command; - } + return command; + } } diff --git a/org.lflang/src/org/lflang/generator/GeneratorResult.java b/org.lflang/src/org/lflang/generator/GeneratorResult.java index 2b42e17f34..b882510d50 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorResult.java +++ b/org.lflang/src/org/lflang/generator/GeneratorResult.java @@ -1,19 +1,9 @@ package org.lflang.generator; -import java.io.File; import java.nio.file.Path; import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.function.BiFunction; -import java.util.function.Function; - -import org.eclipse.xtext.generator.GeneratorContext; - -import org.lflang.FileConfig; -import org.lflang.Target; -import org.lflang.TargetConfig; -import org.lflang.util.LFCommand; /** * A {@code GeneratorResult} is the outcome of a code generation task. @@ -21,101 +11,99 @@ * @author Peter Donovan */ public class GeneratorResult { - public static GeneratorResult NOTHING = incompleteGeneratorResult(Status.NOTHING); - public static GeneratorResult CANCELLED = incompleteGeneratorResult(Status.CANCELLED); - public static GeneratorResult FAILED = incompleteGeneratorResult(Status.FAILED); - public static BiFunction, GeneratorResult> GENERATED_NO_EXECUTABLE - = (context, codeMaps) -> new GeneratorResult(Status.GENERATED, context, codeMaps); - - /** - * A {@code Status} is a level of completion of a code generation task. - */ - public enum Status { - NOTHING(result -> ""), // Code generation was not performed. - CANCELLED(result -> "Code generation was cancelled."), - FAILED(result -> ""), // This may be due to a failed validation check, in which case the error should have been - // sent to the error reporter and handled there. This makes a message unnecessary. - GENERATED(GetUserMessage.COMPLETED), - COMPILED(GetUserMessage.COMPLETED); - - /** - * A {@code GetUserMessage} is a function that translates a - * {@code GeneratorResult} into a human-readable report for the end user. - */ - public interface GetUserMessage { - GetUserMessage COMPLETED = result -> { - return String.format( - "Code generation complete. The executable is at \"%s\".", - result.getContext().getFileConfig().getExecutable() - ); - }; - String apply(GeneratorResult result); - } - - /** The {@code GetUserMessage} associated with this {@code Status}. */ - private final GetUserMessage gum; - - /** Initializes a {@code Status} whose {@code GetUserMessage} is {@code gum}. */ - Status(GetUserMessage gum) { - this.gum = gum; - } - } - - private final Status status; - - private final LFGeneratorContext context; - - private final Map codeMaps; + public static GeneratorResult NOTHING = incompleteGeneratorResult(Status.NOTHING); + public static GeneratorResult CANCELLED = incompleteGeneratorResult(Status.CANCELLED); + public static GeneratorResult FAILED = incompleteGeneratorResult(Status.FAILED); + public static BiFunction, GeneratorResult> + GENERATED_NO_EXECUTABLE = + (context, codeMaps) -> new GeneratorResult(Status.GENERATED, context, codeMaps); + + /** A {@code Status} is a level of completion of a code generation task. */ + public enum Status { + NOTHING(result -> ""), // Code generation was not performed. + CANCELLED(result -> "Code generation was cancelled."), + FAILED( + result -> + ""), // This may be due to a failed validation check, in which case the error should + // have been + // sent to the error reporter and handled there. This makes a message unnecessary. + GENERATED(GetUserMessage.COMPLETED), + COMPILED(GetUserMessage.COMPLETED); /** - * Initialize a GeneratorResult. - * @param status The level of completion of a code generation task. - * @param context The context within which the result was produced. - * @param codeMaps A mapping from generated files to their CodeMaps. + * A {@code GetUserMessage} is a function that translates a {@code GeneratorResult} into a + * human-readable report for the end user. */ - public GeneratorResult(Status status, LFGeneratorContext context, Map codeMaps) { - this.status = status != null ? status : Status.NOTHING; - this.context = context; - this.codeMaps = codeMaps != null ? codeMaps : Collections.emptyMap(); + public interface GetUserMessage { + GetUserMessage COMPLETED = + result -> { + return String.format( + "Code generation complete. The executable is at \"%s\".", + result.getContext().getFileConfig().getExecutable()); + }; + + String apply(GeneratorResult result); } - /** - * Return the result of an incomplete generation task that terminated - * with status {@code status}. - * @return the result of an incomplete generation task that terminated - * with status {@code status} - */ - private static GeneratorResult incompleteGeneratorResult(Status status) { - return new GeneratorResult(status, null, Collections.emptyMap()); - } + /** The {@code GetUserMessage} associated with this {@code Status}. */ + private final GetUserMessage gum; - /** Return the status of {@code this}. */ - public Status getStatus() { - return status; + /** Initializes a {@code Status} whose {@code GetUserMessage} is {@code gum}. */ + Status(GetUserMessage gum) { + this.gum = gum; } - - /** - * Return a message that can be relayed to the end user about this - * {@code GeneratorResult}. - */ - public String getUserMessage() { - return status.gum.apply(this); - } - - /** - * Return a map from generated sources to their code maps. The - * completeness of this resulting map is given on a best-effort - * basis, but those mappings that it does contain are guaranteed - * to be correct. - * @return An unmodifiable map from generated sources to their - * code maps. - */ - public Map getCodeMaps() { - return Collections.unmodifiableMap(codeMaps); - } - - public LFGeneratorContext getContext() { - return context; - } - + } + + private final Status status; + + private final LFGeneratorContext context; + + private final Map codeMaps; + + /** + * Initialize a GeneratorResult. + * + * @param status The level of completion of a code generation task. + * @param context The context within which the result was produced. + * @param codeMaps A mapping from generated files to their CodeMaps. + */ + public GeneratorResult(Status status, LFGeneratorContext context, Map codeMaps) { + this.status = status != null ? status : Status.NOTHING; + this.context = context; + this.codeMaps = codeMaps != null ? codeMaps : Collections.emptyMap(); + } + + /** + * Return the result of an incomplete generation task that terminated with status {@code status}. + * + * @return the result of an incomplete generation task that terminated with status {@code status} + */ + private static GeneratorResult incompleteGeneratorResult(Status status) { + return new GeneratorResult(status, null, Collections.emptyMap()); + } + + /** Return the status of {@code this}. */ + public Status getStatus() { + return status; + } + + /** Return a message that can be relayed to the end user about this {@code GeneratorResult}. */ + public String getUserMessage() { + return status.gum.apply(this); + } + + /** + * Return a map from generated sources to their code maps. The completeness of this resulting map + * is given on a best-effort basis, but those mappings that it does contain are guaranteed to be + * correct. + * + * @return An unmodifiable map from generated sources to their code maps. + */ + public Map getCodeMaps() { + return Collections.unmodifiableMap(codeMaps); + } + + public LFGeneratorContext getContext() { + return context; + } } diff --git a/org.lflang/src/org/lflang/generator/GeneratorUtils.java b/org.lflang/src/org/lflang/generator/GeneratorUtils.java index b4563a6165..04ae33ba0c 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorUtils.java +++ b/org.lflang/src/org/lflang/generator/GeneratorUtils.java @@ -4,19 +4,17 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; - 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.resource.Resource; import org.eclipse.xtext.xbase.lib.IteratorExtensions; - -import org.lflang.ast.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.TargetConfig; import org.lflang.TargetProperty; +import org.lflang.ast.ASTUtils; import org.lflang.generator.LFGeneratorContext.Mode; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; @@ -27,177 +25,162 @@ import org.lflang.lf.TargetDecl; /** - * A helper class with functions that may be useful for code - * generators. - * This is created to ease our transition from Xtend and - * possibly Eclipse. All functions in this class should - * instead be in GeneratorUtils.kt, but Eclipse cannot - * handle Kotlin files. + * A helper class with functions that may be useful for code generators. This is created to ease our + * transition from Xtend and possibly Eclipse. All functions in this class should instead be in + * GeneratorUtils.kt, but Eclipse cannot handle Kotlin files. */ public class GeneratorUtils { - private GeneratorUtils() { - // utility class - } + private GeneratorUtils() { + // utility class + } - /** - * Return the target declaration found in the given resource. - */ - public static TargetDecl findTargetDecl(Resource resource) { - return findAll(resource, TargetDecl.class).iterator().next(); - } + /** Return the target declaration found in the given resource. */ + public static TargetDecl findTargetDecl(Resource resource) { + return findAll(resource, TargetDecl.class).iterator().next(); + } - /** - * Look for physical actions in 'resource'. - * If appropriate, set keepalive to true in - * {@code targetConfig}. - * This is a helper function for setTargetConfig. It - * should not be used elsewhere. - */ - public static void accommodatePhysicalActionsIfPresent( - List resources, - boolean setsKeepAliveOptionAutomatically, - TargetConfig targetConfig, - ErrorReporter errorReporter - ) { - if (!setsKeepAliveOptionAutomatically) { - return; - } - for (Resource resource : resources) { - for (Action action : findAll(resource, Action.class)) { - if (action.getOrigin() == ActionOrigin.PHYSICAL && - // Check if the user has explicitly set keepalive to false - !targetConfig.setByUser.contains(TargetProperty.KEEPALIVE) && - !targetConfig.keepalive - ) { - // If not, set it to true - targetConfig.keepalive = true; - errorReporter.reportWarning( - action, - String.format( - "Setting %s to true because of the physical action %s.", - TargetProperty.KEEPALIVE.getDisplayName(), - action.getName() - ) - ); - return; - } - } + /** + * Look for physical actions in 'resource'. If appropriate, set keepalive to true in {@code + * targetConfig}. This is a helper function for setTargetConfig. It should not be used elsewhere. + */ + public static void accommodatePhysicalActionsIfPresent( + List resources, + boolean setsKeepAliveOptionAutomatically, + TargetConfig targetConfig, + ErrorReporter errorReporter) { + if (!setsKeepAliveOptionAutomatically) { + return; + } + for (Resource resource : resources) { + for (Action action : findAll(resource, Action.class)) { + if (action.getOrigin() == ActionOrigin.PHYSICAL + && + // Check if the user has explicitly set keepalive to false + !targetConfig.setByUser.contains(TargetProperty.KEEPALIVE) + && !targetConfig.keepalive) { + // If not, set it to true + targetConfig.keepalive = true; + errorReporter.reportWarning( + action, + String.format( + "Setting %s to true because of the physical action %s.", + TargetProperty.KEEPALIVE.getDisplayName(), action.getName())); + return; } + } } + } - /** - * Return all instances of {@code eObjectType} in - * {@code resource}. - * @param resource A resource to be searched. - * @param nodeType The type of the desired parse tree - * nodes. - * @param The type of the desired parse tree nodes. - * @return all instances of {@code eObjectType} in - * {@code resource} - */ - public static Iterable findAll(Resource resource, Class nodeType) { - return () -> IteratorExtensions.filter(resource.getAllContents(), nodeType); - } + /** + * Return all instances of {@code eObjectType} in {@code resource}. + * + * @param resource A resource to be searched. + * @param nodeType The type of the desired parse tree nodes. + * @param The type of the desired parse tree nodes. + * @return all instances of {@code eObjectType} in {@code resource} + */ + public static Iterable findAll(Resource resource, Class nodeType) { + return () -> IteratorExtensions.filter(resource.getAllContents(), nodeType); + } - /** - * Return the resources that provide the given - * reactors. - * @param reactors The reactors for which to find - * containing resources. - * @return the resources that provide the given - * reactors. - */ - public static List getResources(Iterable reactors) { - HashSet visited = new HashSet<>(); - List resources = new ArrayList<>(); - for (Reactor r : reactors) { - Resource resource = r.eResource(); - if (!visited.contains(resource)) { - visited.add(resource); - resources.add(resource); - } - } - return resources; + /** + * Return the resources that provide the given reactors. + * + * @param reactors The reactors for which to find containing resources. + * @return the resources that provide the given reactors. + */ + public static List getResources(Iterable reactors) { + HashSet visited = new HashSet<>(); + List resources = new ArrayList<>(); + for (Reactor r : reactors) { + Resource resource = r.eResource(); + if (!visited.contains(resource)) { + visited.add(resource); + resources.add(resource); + } } + return resources; + } - /** - * Return the {@code LFResource} representation of the - * given resource. - * @param resource The {@code Resource} to be - * represented as an {@code LFResource} - * @param srcGenBasePath The root directory for any - * generated sources associated with the resource. - * @param context The generator invocation context. - * @param errorReporter An error message acceptor. - * @return the {@code LFResource} representation of the - * given resource. - */ - public static LFResource getLFResource( - Resource resource, - Path srcGenBasePath, - LFGeneratorContext context, - ErrorReporter errorReporter - ) { - var target = ASTUtils.targetDecl(resource); - KeyValuePairs config = target.getConfig(); - var targetConfig = new TargetConfig(target); - if (config != null) { - List pairs = config.getPairs(); - TargetProperty.set(targetConfig, pairs != null ? pairs : List.of(), errorReporter); - } - FileConfig fc = LFGenerator.createFileConfig(resource, srcGenBasePath, context.getFileConfig().useHierarchicalBin); - return new LFResource(resource, fc, targetConfig); + /** + * Return the {@code LFResource} representation of the given resource. + * + * @param resource The {@code Resource} to be represented as an {@code LFResource} + * @param srcGenBasePath The root directory for any generated sources associated with the + * resource. + * @param context The generator invocation context. + * @param errorReporter An error message acceptor. + * @return the {@code LFResource} representation of the given resource. + */ + public static LFResource getLFResource( + Resource resource, + Path srcGenBasePath, + LFGeneratorContext context, + ErrorReporter errorReporter) { + var target = ASTUtils.targetDecl(resource); + KeyValuePairs config = target.getConfig(); + var targetConfig = new TargetConfig(target); + if (config != null) { + List pairs = config.getPairs(); + TargetProperty.set(targetConfig, pairs != null ? pairs : List.of(), errorReporter); } + FileConfig fc = + LFGenerator.createFileConfig( + resource, srcGenBasePath, context.getFileConfig().useHierarchicalBin); + return new LFResource(resource, fc, targetConfig); + } - /** - * If the mode is Mode.EPOCH (the code generator is running in an - * Eclipse IDE), then refresh the project. This will ensure that - * any generated files become visible in the project. - * @param resource The resource. - * @param compilerMode An indicator of whether Epoch is running. - */ - public static void refreshProject(Resource resource, Mode compilerMode) { - if (compilerMode == LFGeneratorContext.Mode.EPOCH) { - URI uri = resource.getURI(); - if (uri.isPlatformResource()) { // This condition should normally be met when running Epoch - IResource member = ResourcesPlugin.getWorkspace().getRoot().findMember(uri.toPlatformString(true)); - try { - member.getProject().refreshLocal(IResource.DEPTH_INFINITE, null); - } catch (CoreException e) { - System.err.println("Unable to refresh workspace: " + e); - } - } + /** + * If the mode is Mode.EPOCH (the code generator is running in an Eclipse IDE), then refresh the + * project. This will ensure that any generated files become visible in the project. + * + * @param resource The resource. + * @param compilerMode An indicator of whether Epoch is running. + */ + public static void refreshProject(Resource resource, Mode compilerMode) { + if (compilerMode == LFGeneratorContext.Mode.EPOCH) { + URI uri = resource.getURI(); + if (uri.isPlatformResource()) { // This condition should normally be met when running Epoch + IResource member = + ResourcesPlugin.getWorkspace().getRoot().findMember(uri.toPlatformString(true)); + try { + member.getProject().refreshLocal(IResource.DEPTH_INFINITE, null); + } catch (CoreException e) { + System.err.println("Unable to refresh workspace: " + e); } + } } + } - /** Return whether the operating system is Windows. */ - public static boolean isHostWindows() { - return System.getProperty("os.name").toLowerCase().contains("win"); - } + /** Return whether the operating system is Windows. */ + public static boolean isHostWindows() { + return System.getProperty("os.name").toLowerCase().contains("win"); + } - /** - * Check whether code can be generated; report any problems - * and inform the context accordingly. - * @return Whether it is possible to generate code. - */ - public static boolean canGenerate( - Boolean errorsOccurred, - Instantiation mainDef, - ErrorReporter errorReporter, - LFGeneratorContext context - ) { - // stop if there are any errors found in the program by doGenerate() in GeneratorBase - if (errorsOccurred) { - context.finish(GeneratorResult.FAILED); - return false; - } - // abort if there is no main reactor - if (mainDef == null) { - errorReporter.reportInfo("INFO: The given Lingua Franca program does not define a main reactor. Therefore, no code was generated."); - context.finish(GeneratorResult.NOTHING); - return false; - } - return true; + /** + * Check whether code can be generated; report any problems and inform the context accordingly. + * + * @return Whether it is possible to generate code. + */ + public static boolean canGenerate( + Boolean errorsOccurred, + Instantiation mainDef, + ErrorReporter errorReporter, + LFGeneratorContext context) { + // stop if there are any errors found in the program by doGenerate() in GeneratorBase + if (errorsOccurred) { + context.finish(GeneratorResult.FAILED); + return false; + } + // abort if there is no main reactor + if (mainDef == null) { + errorReporter.reportInfo( + "INFO: The given Lingua Franca program does not define a main reactor. Therefore, no code" + + " was generated."); + context.finish(GeneratorResult.NOTHING); + return false; } + return true; + } } diff --git a/org.lflang/src/org/lflang/generator/HumanReadableReportingStrategy.java b/org.lflang/src/org/lflang/generator/HumanReadableReportingStrategy.java index f6ecd0a991..8c1dd1e547 100644 --- a/org.lflang/src/org/lflang/generator/HumanReadableReportingStrategy.java +++ b/org.lflang/src/org/lflang/generator/HumanReadableReportingStrategy.java @@ -6,171 +6,163 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; - import org.eclipse.lsp4j.DiagnosticSeverity; import org.eclipse.xtext.xbase.lib.Procedures.Procedure0; import org.eclipse.xtext.xbase.lib.Procedures.Procedure2; - import org.lflang.ErrorReporter; /** - * An error reporting strategy that parses human-readable - * output. + * An error reporting strategy that parses human-readable output. * * @author Peter Donovan */ public class HumanReadableReportingStrategy implements DiagnosticReporting.Strategy { - /** A pattern that matches lines that should be reported via this strategy. */ - private final Pattern diagnosticMessagePattern; - /** A pattern that matches labels that show the exact range to which the diagnostic pertains. */ - private final Pattern labelPattern; - /** The path against which any paths should be resolved. */ - private final Path relativeTo; - /** The next line to be processed, or {@code null}. */ - private String bufferedLine; + /** A pattern that matches lines that should be reported via this strategy. */ + private final Pattern diagnosticMessagePattern; + /** A pattern that matches labels that show the exact range to which the diagnostic pertains. */ + private final Pattern labelPattern; + /** The path against which any paths should be resolved. */ + private final Path relativeTo; + /** The next line to be processed, or {@code null}. */ + private String bufferedLine; - /** - * Instantiate a reporting strategy for lines of - * validator output that match {@code diagnosticMessagePattern}. - * @param diagnosticMessagePattern A pattern that matches lines that should be - * reported via this strategy. This pattern - * must contain named capturing groups called - * "path", "line", "column", "message", and - * "severity". - * @param labelPattern A pattern that matches lines that act as labels, showing - * the location of the relevant piece of text. This pattern - * must contain two groups, the first of which must match - * characters that precede the location given by the "line" - * and "column" groups. - */ - public HumanReadableReportingStrategy(Pattern diagnosticMessagePattern, Pattern labelPattern) { - this(diagnosticMessagePattern, labelPattern, null); - } + /** + * Instantiate a reporting strategy for lines of validator output that match {@code + * diagnosticMessagePattern}. + * + * @param diagnosticMessagePattern A pattern that matches lines that should be reported via this + * strategy. This pattern must contain named capturing groups called "path", "line", "column", + * "message", and "severity". + * @param labelPattern A pattern that matches lines that act as labels, showing the location of + * the relevant piece of text. This pattern must contain two groups, the first of which must + * match characters that precede the location given by the "line" and "column" groups. + */ + public HumanReadableReportingStrategy(Pattern diagnosticMessagePattern, Pattern labelPattern) { + this(diagnosticMessagePattern, labelPattern, null); + } - /** - * Instantiate a reporting strategy for lines of - * validator output that match {@code diagnosticMessagePattern}. - * @param diagnosticMessagePattern a pattern that matches lines that should be - * reported via this strategy. This pattern - * must contain named capturing groups called - * "path", "line", "column", "message", and - * "severity". - * @param labelPattern A pattern that matches lines that act as labels, showing - * the location of the relevant piece of text. This pattern - * must contain two groups, the first of which must match - * characters that precede the location given by the "line" - * and "column" groups. - * @param relativeTo The path against which any paths should be resolved. - */ - public HumanReadableReportingStrategy(Pattern diagnosticMessagePattern, Pattern labelPattern, Path relativeTo) { - for (String groupName : new String[]{"path", "line", "column", "message", "severity"}) { - assert diagnosticMessagePattern.pattern().contains(groupName) : String.format( - "Error line patterns must have a named capturing group called %s", groupName - ); - } - this.diagnosticMessagePattern = diagnosticMessagePattern; - this.labelPattern = labelPattern; - this.relativeTo = relativeTo; - this.bufferedLine = null; + /** + * Instantiate a reporting strategy for lines of validator output that match {@code + * diagnosticMessagePattern}. + * + * @param diagnosticMessagePattern a pattern that matches lines that should be reported via this + * strategy. This pattern must contain named capturing groups called "path", "line", "column", + * "message", and "severity". + * @param labelPattern A pattern that matches lines that act as labels, showing the location of + * the relevant piece of text. This pattern must contain two groups, the first of which must + * match characters that precede the location given by the "line" and "column" groups. + * @param relativeTo The path against which any paths should be resolved. + */ + public HumanReadableReportingStrategy( + Pattern diagnosticMessagePattern, Pattern labelPattern, Path relativeTo) { + for (String groupName : new String[] {"path", "line", "column", "message", "severity"}) { + assert diagnosticMessagePattern.pattern().contains(groupName) + : String.format( + "Error line patterns must have a named capturing group called %s", groupName); } + this.diagnosticMessagePattern = diagnosticMessagePattern; + this.labelPattern = labelPattern; + this.relativeTo = relativeTo; + this.bufferedLine = null; + } - @Override - public void report(String validationOutput, ErrorReporter errorReporter, Map map) { - Iterator it = validationOutput.lines().iterator(); - while (it.hasNext() || bufferedLine != null) { - if (bufferedLine != null) { - reportErrorLine(bufferedLine, it, errorReporter, map); - bufferedLine = null; - } else { - reportErrorLine(it.next(), it, errorReporter, map); - } - } + @Override + public void report(String validationOutput, ErrorReporter errorReporter, Map map) { + Iterator it = validationOutput.lines().iterator(); + while (it.hasNext() || bufferedLine != null) { + if (bufferedLine != null) { + reportErrorLine(bufferedLine, it, errorReporter, map); + bufferedLine = null; + } else { + reportErrorLine(it.next(), it, errorReporter, map); + } } + } - /** - * Report the validation message contained in the given line of text. - * @param line The current line. - * @param it An iterator over the lines that follow the current line. - * @param errorReporter An arbitrary ErrorReporter. - * @param maps A mapping from generated file paths to - * CodeMaps. - */ - private void reportErrorLine(String line, Iterator it, ErrorReporter errorReporter, Map maps) { - Matcher matcher = diagnosticMessagePattern.matcher(stripEscaped(line)); - if (matcher.matches()) { - final Path path = Paths.get(matcher.group("path")); - final Position generatedFilePosition = Position.fromOneBased( - Integer.parseInt(matcher.group("line")), - Integer.parseInt(matcher.group("column") != null ? matcher.group("column") : "0") // FIXME: Unreliable heuristic - ); - final String message = DiagnosticReporting.messageOf( - matcher.group("message"), path, generatedFilePosition - ); - final CodeMap map = maps.get(relativeTo != null ? relativeTo.resolve(path) : path); - final DiagnosticSeverity severity = DiagnosticReporting.severityOf(matcher.group("severity")); - if (map == null) { - errorReporter.report(null, severity, message); - return; - } - for (Path srcFile : map.lfSourcePaths()) { - Position lfFilePosition = map.adjusted(srcFile, generatedFilePosition); - if (matcher.group("column") != null) { - reportAppropriateRange( - (p0, p1) -> errorReporter.report(srcFile, severity, message, p0, p1), lfFilePosition, it - ); - } else { - errorReporter.report(srcFile, severity, message, lfFilePosition.getOneBasedLine()); - } - } + /** + * Report the validation message contained in the given line of text. + * + * @param line The current line. + * @param it An iterator over the lines that follow the current line. + * @param errorReporter An arbitrary ErrorReporter. + * @param maps A mapping from generated file paths to CodeMaps. + */ + private void reportErrorLine( + String line, Iterator it, ErrorReporter errorReporter, Map maps) { + Matcher matcher = diagnosticMessagePattern.matcher(stripEscaped(line)); + if (matcher.matches()) { + final Path path = Paths.get(matcher.group("path")); + final Position generatedFilePosition = + Position.fromOneBased( + Integer.parseInt(matcher.group("line")), + Integer.parseInt( + matcher.group("column") != null + ? matcher.group("column") + : "0") // FIXME: Unreliable heuristic + ); + final String message = + DiagnosticReporting.messageOf(matcher.group("message"), path, generatedFilePosition); + final CodeMap map = maps.get(relativeTo != null ? relativeTo.resolve(path) : path); + final DiagnosticSeverity severity = DiagnosticReporting.severityOf(matcher.group("severity")); + if (map == null) { + errorReporter.report(null, severity, message); + return; + } + for (Path srcFile : map.lfSourcePaths()) { + Position lfFilePosition = map.adjusted(srcFile, generatedFilePosition); + if (matcher.group("column") != null) { + reportAppropriateRange( + (p0, p1) -> errorReporter.report(srcFile, severity, message, p0, p1), + lfFilePosition, + it); + } else { + errorReporter.report(srcFile, severity, message, lfFilePosition.getOneBasedLine()); } + } } + } - /** - * Report the appropriate range to {@code report}. - * @param report A reporting method whose first and - * second parameters are the (included) - * start and (excluded) end of the - * relevant range. - * @param lfFilePosition The point about which the - * relevant range is anchored. - * @param it An iterator over the lines immediately - * following a diagnostic message. - */ - private void reportAppropriateRange( - Procedure2 report, Position lfFilePosition, Iterator it - ) { - Procedure0 failGracefully = () -> report.apply(lfFilePosition, lfFilePosition.plus(" ")); - if (!it.hasNext()) { - failGracefully.apply(); - return; - } - String line = it.next(); - Matcher labelMatcher = labelPattern.matcher(line); - if (labelMatcher.find()) { - report.apply( - Position.fromZeroBased( - lfFilePosition.getZeroBasedLine(), - lfFilePosition.getZeroBasedColumn() - labelMatcher.group(1).length() - ), - lfFilePosition.plus(labelMatcher.group(2)) - ); - return; - } - if (diagnosticMessagePattern.matcher(line).find()) { - failGracefully.apply(); - bufferedLine = line; - return; - } - reportAppropriateRange(report, lfFilePosition, it); + /** + * Report the appropriate range to {@code report}. + * + * @param report A reporting method whose first and second parameters are the (included) start and + * (excluded) end of the relevant range. + * @param lfFilePosition The point about which the relevant range is anchored. + * @param it An iterator over the lines immediately following a diagnostic message. + */ + private void reportAppropriateRange( + Procedure2 report, Position lfFilePosition, Iterator it) { + Procedure0 failGracefully = () -> report.apply(lfFilePosition, lfFilePosition.plus(" ")); + if (!it.hasNext()) { + failGracefully.apply(); + return; } - - /** - * Strip the ANSI escape sequences from {@code s}. - * @param s Any string. - * @return {@code s}, with any escape sequences removed. - */ - private static String stripEscaped(String s) { - return s.replaceAll("\u001B\\[[;\\d]*[ -/]*[@-~]", ""); + String line = it.next(); + Matcher labelMatcher = labelPattern.matcher(line); + if (labelMatcher.find()) { + report.apply( + Position.fromZeroBased( + lfFilePosition.getZeroBasedLine(), + lfFilePosition.getZeroBasedColumn() - labelMatcher.group(1).length()), + lfFilePosition.plus(labelMatcher.group(2))); + return; } + if (diagnosticMessagePattern.matcher(line).find()) { + failGracefully.apply(); + bufferedLine = line; + return; + } + reportAppropriateRange(report, lfFilePosition, it); + } + + /** + * Strip the ANSI escape sequences from {@code s}. + * + * @param s Any string. + * @return {@code s}, with any escape sequences removed. + */ + private static String stripEscaped(String s) { + return s.replaceAll("\u001B\\[[;\\d]*[ -/]*[@-~]", ""); + } } diff --git a/org.lflang/src/org/lflang/generator/IntegratedBuilder.java b/org.lflang/src/org/lflang/generator/IntegratedBuilder.java index d6a2d3d681..f87c81728d 100644 --- a/org.lflang/src/org/lflang/generator/IntegratedBuilder.java +++ b/org.lflang/src/org/lflang/generator/IntegratedBuilder.java @@ -1,9 +1,10 @@ package org.lflang.generator; +import com.google.inject.Inject; +import com.google.inject.Provider; import java.nio.file.Path; import java.util.List; import java.util.Properties; - import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; @@ -15,142 +16,133 @@ import org.eclipse.xtext.validation.CheckMode; import org.eclipse.xtext.validation.IResourceValidator; import org.eclipse.xtext.validation.Issue; - import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.generator.LFGeneratorContext.Mode; -import com.google.inject.Inject; -import com.google.inject.Provider; - /** - * Manages Lingua Franca build processes that are requested - * from the language server. + * Manages Lingua Franca build processes that are requested from the language server. * * @author Peter Donovan */ public class IntegratedBuilder { - public static final int START_PERCENT_PROGRESS = 0; - public static final int VALIDATED_PERCENT_PROGRESS = 33; - public static final int GENERATED_PERCENT_PROGRESS = 67; - public static final int COMPILED_PERCENT_PROGRESS = 100; - - /** - * A {@code ProgressReporter} reports the progress of a build. - */ - public interface ReportProgress { - void apply(String message, Integer percentage); - } - - // Note: This class is not currently used in response to - // document edits, even though the validator and code - // generator are invoked by Xtext in response to - // document edits. - /** - * A {@code ReportMethod} is a way of reporting issues. - */ - private interface ReportMethod { - void apply(Path file, Integer line, String message); - } - - /* ---------------------- INJECTED DEPENDENCIES ---------------------- */ - - @Inject - private IResourceValidator validator; - @Inject - private GeneratorDelegate generator; - @Inject - private JavaIoFileSystemAccess fileAccess; - @Inject - private Provider resourceSetProvider; - - /* ------------------------- PUBLIC METHODS -------------------------- */ - - /** - * Generates code from the Lingua Franca file {@code f}. - * @param uri The URI of a Lingua Franca file. - * @param mustComplete Whether the build must be taken to completion. - * @return The result of the build. - */ - public GeneratorResult run( - URI uri, - boolean mustComplete, - ReportProgress reportProgress, - CancelIndicator cancelIndicator - ) { - fileAccess.setOutputPath( - FileConfig.findPackageRoot(Path.of(uri.path()), s -> {}).resolve(FileConfig.DEFAULT_SRC_GEN_DIR).toString() - ); - List parseRoots = getResource(uri).getContents(); - if (parseRoots.isEmpty()) return GeneratorResult.NOTHING; - ErrorReporter errorReporter = new LanguageServerErrorReporter(parseRoots.get(0)); - reportProgress.apply("Validating...", START_PERCENT_PROGRESS); - validate(uri, errorReporter); - reportProgress.apply("Code validation complete.", VALIDATED_PERCENT_PROGRESS); - if (cancelIndicator.isCanceled()) return GeneratorResult.CANCELLED; - if (errorReporter.getErrorsOccurred()) return GeneratorResult.FAILED; - reportProgress.apply("Generating code...", VALIDATED_PERCENT_PROGRESS); - return doGenerate(uri, mustComplete, reportProgress, cancelIndicator); - } - - /* ------------------------- PRIVATE METHODS ------------------------- */ - - /** - * Validates the Lingua Franca file {@code f}. - * @param uri The URI of a Lingua Franca file. - * @param errorReporter The error reporter. - */ - private void validate(URI uri, ErrorReporter errorReporter) { - for (Issue issue : validator.validate(getResource(uri), CheckMode.ALL, CancelIndicator.NullImpl)) { - getReportMethod(errorReporter, issue.getSeverity()).apply( - Path.of(uri.path()), issue.getLineNumber(), issue.getMessage() - ); - } + public static final int START_PERCENT_PROGRESS = 0; + public static final int VALIDATED_PERCENT_PROGRESS = 33; + public static final int GENERATED_PERCENT_PROGRESS = 67; + public static final int COMPILED_PERCENT_PROGRESS = 100; + + /** A {@code ProgressReporter} reports the progress of a build. */ + public interface ReportProgress { + void apply(String message, Integer percentage); + } + + // Note: This class is not currently used in response to + // document edits, even though the validator and code + // generator are invoked by Xtext in response to + // document edits. + /** A {@code ReportMethod} is a way of reporting issues. */ + private interface ReportMethod { + void apply(Path file, Integer line, String message); + } + + /* ---------------------- INJECTED DEPENDENCIES ---------------------- */ + + @Inject private IResourceValidator validator; + @Inject private GeneratorDelegate generator; + @Inject private JavaIoFileSystemAccess fileAccess; + @Inject private Provider resourceSetProvider; + + /* ------------------------- PUBLIC METHODS -------------------------- */ + + /** + * Generates code from the Lingua Franca file {@code f}. + * + * @param uri The URI of a Lingua Franca file. + * @param mustComplete Whether the build must be taken to completion. + * @return The result of the build. + */ + public GeneratorResult run( + URI uri, + boolean mustComplete, + ReportProgress reportProgress, + CancelIndicator cancelIndicator) { + fileAccess.setOutputPath( + FileConfig.findPackageRoot(Path.of(uri.path()), s -> {}) + .resolve(FileConfig.DEFAULT_SRC_GEN_DIR) + .toString()); + List parseRoots = getResource(uri).getContents(); + if (parseRoots.isEmpty()) return GeneratorResult.NOTHING; + ErrorReporter errorReporter = new LanguageServerErrorReporter(parseRoots.get(0)); + reportProgress.apply("Validating...", START_PERCENT_PROGRESS); + validate(uri, errorReporter); + reportProgress.apply("Code validation complete.", VALIDATED_PERCENT_PROGRESS); + if (cancelIndicator.isCanceled()) return GeneratorResult.CANCELLED; + if (errorReporter.getErrorsOccurred()) return GeneratorResult.FAILED; + reportProgress.apply("Generating code...", VALIDATED_PERCENT_PROGRESS); + return doGenerate(uri, mustComplete, reportProgress, cancelIndicator); + } + + /* ------------------------- PRIVATE METHODS ------------------------- */ + + /** + * Validates the Lingua Franca file {@code f}. + * + * @param uri The URI of a Lingua Franca file. + * @param errorReporter The error reporter. + */ + private void validate(URI uri, ErrorReporter errorReporter) { + for (Issue issue : + validator.validate(getResource(uri), CheckMode.ALL, CancelIndicator.NullImpl)) { + getReportMethod(errorReporter, issue.getSeverity()) + .apply(Path.of(uri.path()), issue.getLineNumber(), issue.getMessage()); } - - /** - * Generates code from the contents of {@code f}. - * @param uri The URI of a Lingua Franca file. - * @param mustComplete Whether the build must be taken to completion. - * @param cancelIndicator An indicator that returns true when the build is - * cancelled. - * @return The result of the build. - */ - private GeneratorResult doGenerate( - URI uri, - boolean mustComplete, - ReportProgress reportProgress, - CancelIndicator cancelIndicator - ) { - var resource = getResource(uri); - LFGeneratorContext context = new MainContext( + } + + /** + * Generates code from the contents of {@code f}. + * + * @param uri The URI of a Lingua Franca file. + * @param mustComplete Whether the build must be taken to completion. + * @param cancelIndicator An indicator that returns true when the build is cancelled. + * @return The result of the build. + */ + private GeneratorResult doGenerate( + URI uri, + boolean mustComplete, + ReportProgress reportProgress, + CancelIndicator cancelIndicator) { + var resource = getResource(uri); + LFGeneratorContext context = + new MainContext( mustComplete ? Mode.LSP_SLOW : LFGeneratorContext.Mode.LSP_MEDIUM, - cancelIndicator, reportProgress, new Properties(), - resource, fileAccess, - fileConfig -> new LanguageServerErrorReporter(resource.getContents().get(0)) - ); - generator.generate(getResource(uri), fileAccess, context); - return context.getResult(); - } - - /** - * Returns the resource corresponding to {@code uri}. - * @param uri The URI of a Lingua Franca file. - * @return The resource corresponding to {@code uri}. - */ - private Resource getResource(URI uri) { - return resourceSetProvider.get().getResource(uri, true); - } - - /** - * Returns the appropriate reporting method for the - * given {@code Severity}. - * @param severity An arbitrary {@code Severity}. - * @return The appropriate reporting method for - * {@code severity}. - */ - private ReportMethod getReportMethod(ErrorReporter errorReporter, Severity severity) { - if (severity == Severity.ERROR) return errorReporter::reportError; - return errorReporter::reportWarning; - } + cancelIndicator, + reportProgress, + new Properties(), + resource, + fileAccess, + fileConfig -> new LanguageServerErrorReporter(resource.getContents().get(0))); + generator.generate(getResource(uri), fileAccess, context); + return context.getResult(); + } + + /** + * Returns the resource corresponding to {@code uri}. + * + * @param uri The URI of a Lingua Franca file. + * @return The resource corresponding to {@code uri}. + */ + private Resource getResource(URI uri) { + return resourceSetProvider.get().getResource(uri, true); + } + + /** + * Returns the appropriate reporting method for the given {@code Severity}. + * + * @param severity An arbitrary {@code Severity}. + * @return The appropriate reporting method for {@code severity}. + */ + private ReportMethod getReportMethod(ErrorReporter errorReporter, Severity severity) { + if (severity == Severity.ERROR) return errorReporter::reportError; + return errorReporter::reportWarning; + } } diff --git a/org.lflang/src/org/lflang/generator/InvalidLfSourceException.java b/org.lflang/src/org/lflang/generator/InvalidLfSourceException.java index 515bdbb8a0..61cf9632f2 100644 --- a/org.lflang/src/org/lflang/generator/InvalidLfSourceException.java +++ b/org.lflang/src/org/lflang/generator/InvalidLfSourceException.java @@ -27,32 +27,31 @@ import org.eclipse.emf.ecore.EObject; /** - * An exception that indicates invalid source, which should - * be reported to the user. This is an error, it should not - * be used for warnings. + * An exception that indicates invalid source, which should be reported to the user. This is an + * error, it should not be used for warnings. * * @author Clément Fournier */ public class InvalidLfSourceException extends RuntimeException { - private final EObject node; - private final String problem; + private final EObject node; + private final String problem; - public InvalidLfSourceException(EObject node, String problem) { - super(problem); - this.node = node; - this.problem = problem; - } + public InvalidLfSourceException(EObject node, String problem) { + super(problem); + this.node = node; + this.problem = problem; + } - public InvalidLfSourceException(String problem, EObject node) { - this(node, problem); - } + public InvalidLfSourceException(String problem, EObject node) { + this(node, problem); + } - public EObject getNode() { - return node; - } + public EObject getNode() { + return node; + } - public String getProblem() { - return problem; - } + public String getProblem() { + return problem; + } } diff --git a/org.lflang/src/org/lflang/generator/InvalidSourceException.java b/org.lflang/src/org/lflang/generator/InvalidSourceException.java index 11bedabce2..88b65d0c24 100644 --- a/org.lflang/src/org/lflang/generator/InvalidSourceException.java +++ b/org.lflang/src/org/lflang/generator/InvalidSourceException.java @@ -25,19 +25,17 @@ package org.lflang.generator; /** - * - * This exception is thrown when a program fails a validity check - * performed by a code generator (and not the validator). This should - * be thrown only when local control flow cannot recover, otherwise - * using {@link GeneratorBase#errorReporter} should be preferred, - * in order to collect more errors before failing. + * This exception is thrown when a program fails a validity check performed by a code generator (and + * not the validator). This should be thrown only when local control flow cannot recover, otherwise + * using {@link GeneratorBase#errorReporter} should be preferred, in order to collect more errors + * before failing. * * @author Clément Fournier */ public class InvalidSourceException extends RuntimeException { - /** Create a new instance of the exception with the given message. */ - public InvalidSourceException(String message) { - super(message); - } + /** Create a new instance of the exception with the given message. */ + public InvalidSourceException(String message) { + super(message); + } } diff --git a/org.lflang/src/org/lflang/generator/LFGenerator.java b/org.lflang/src/org/lflang/generator/LFGenerator.java index c8d985e80c..f2881c9fb8 100644 --- a/org.lflang/src/org/lflang/generator/LFGenerator.java +++ b/org.lflang/src/org/lflang/generator/LFGenerator.java @@ -1,19 +1,18 @@ package org.lflang.generator; +import com.google.inject.Inject; import java.io.IOException; import java.lang.reflect.Constructor; import java.nio.file.Path; - import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.generator.AbstractGenerator; import org.eclipse.xtext.generator.IFileSystemAccess2; import org.eclipse.xtext.generator.IGeneratorContext; import org.eclipse.xtext.util.RuntimeIOException; - -import org.lflang.ast.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.Target; +import org.lflang.ast.ASTUtils; import org.lflang.federated.generator.FedASTUtils; import org.lflang.federated.generator.FedFileConfig; import org.lflang.federated.generator.FedGenerator; @@ -23,170 +22,179 @@ import org.lflang.generator.python.PythonGenerator; import org.lflang.scoping.LFGlobalScopeProvider; -import com.google.inject.Inject; - /** * Generates code from your model files on save. * - * See - * https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#code-generation + *

See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#code-generation */ public class LFGenerator extends AbstractGenerator { - @Inject - private LFGlobalScopeProvider scopeProvider; - - // Indicator of whether generator errors occurred. - protected boolean generatorErrorsOccurred = false; - - /** - * Create a target-specific FileConfig object in Kotlin - *

- * Since the CppFileConfig and TSFileConfig class are implemented in Kotlin, the classes are - * not visible from all contexts. If the RCA is run from within Eclipse via - * "Run as Eclipse Application", the Kotlin classes are unfortunately not - * available at runtime due to bugs in the Eclipse Kotlin plugin. (See - * https://stackoverflow.com/questions/68095816/is-ist-possible-to-build-mixed-kotlin-and-java-applications-with-a-recent-eclips) - *

- * If the FileConfig class is found, this method returns an instance. - * Otherwise, it returns an Instance of FileConfig. - * - * @return A FileConfig object in Kotlin if the class can be found. - * @throws IOException If the file config could not be created properly - */ - public static FileConfig createFileConfig(Resource resource, Path srcGenBasePath, - boolean useHierarchicalBin) { - - final Target target = Target.fromDecl(ASTUtils.targetDecl(resource)); - assert target != null; - - // Since our Eclipse Plugin uses code injection via guice, we need to - // play a few tricks here so that FileConfig does not appear as an - // import. Instead, we look the class up at runtime and instantiate it if - // found. - try { - if (FedASTUtils.findFederatedReactor(resource) != null) { - return new FedFileConfig(resource, srcGenBasePath, useHierarchicalBin); - } - switch (target) { - case CCPP: - case C: return new CFileConfig(resource, srcGenBasePath, useHierarchicalBin); - case Python: return new PyFileConfig(resource, srcGenBasePath, useHierarchicalBin); - case CPP: - case Rust: - case TS: - String className = "org.lflang.generator." + target.packageName + "." + target.classNamePrefix + "FileConfig"; - try { - return (FileConfig) Class.forName(className) - .getDeclaredConstructor(Resource.class, Path.class, boolean.class) - .newInstance(resource, srcGenBasePath, useHierarchicalBin); - } catch (ReflectiveOperationException e) { - throw new RuntimeException( - "Exception instantiating " + className, e.getCause()); - } - default: - throw new RuntimeException("Could not find FileConfig implementation for target " + target); - } - } catch (IOException e) { - throw new RuntimeException("Unable to create FileConfig object for target " + target + ": " + e.getStackTrace()); - } + @Inject private LFGlobalScopeProvider scopeProvider; + + // Indicator of whether generator errors occurred. + protected boolean generatorErrorsOccurred = false; + + /** + * Create a target-specific FileConfig object in Kotlin + * + *

Since the CppFileConfig and TSFileConfig class are implemented in Kotlin, the classes are + * not visible from all contexts. If the RCA is run from within Eclipse via "Run as Eclipse + * Application", the Kotlin classes are unfortunately not available at runtime due to bugs in the + * Eclipse Kotlin plugin. (See + * https://stackoverflow.com/questions/68095816/is-ist-possible-to-build-mixed-kotlin-and-java-applications-with-a-recent-eclips) + * + *

If the FileConfig class is found, this method returns an instance. Otherwise, it returns an + * Instance of FileConfig. + * + * @return A FileConfig object in Kotlin if the class can be found. + * @throws IOException If the file config could not be created properly + */ + public static FileConfig createFileConfig( + Resource resource, Path srcGenBasePath, boolean useHierarchicalBin) { + + final Target target = Target.fromDecl(ASTUtils.targetDecl(resource)); + assert target != null; + + // Since our Eclipse Plugin uses code injection via guice, we need to + // play a few tricks here so that FileConfig does not appear as an + // import. Instead, we look the class up at runtime and instantiate it if + // found. + try { + if (FedASTUtils.findFederatedReactor(resource) != null) { + return new FedFileConfig(resource, srcGenBasePath, useHierarchicalBin); + } + switch (target) { + case CCPP: + case C: + return new CFileConfig(resource, srcGenBasePath, useHierarchicalBin); + case Python: + return new PyFileConfig(resource, srcGenBasePath, useHierarchicalBin); + case CPP: + case Rust: + case TS: + String className = + "org.lflang.generator." + + target.packageName + + "." + + target.classNamePrefix + + "FileConfig"; + try { + return (FileConfig) + Class.forName(className) + .getDeclaredConstructor(Resource.class, Path.class, boolean.class) + .newInstance(resource, srcGenBasePath, useHierarchicalBin); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Exception instantiating " + className, e.getCause()); + } + default: + throw new RuntimeException( + "Could not find FileConfig implementation for target " + target); + } + } catch (IOException e) { + throw new RuntimeException( + "Unable to create FileConfig object for target " + target + ": " + e.getStackTrace()); } - - /** - * Create a generator object for the given target. - * Returns null if the generator could not be created. - */ - private GeneratorBase createGenerator(LFGeneratorContext context) { - final Target target = Target.fromDecl(ASTUtils.targetDecl(context.getFileConfig().resource)); - assert target != null; - return switch (target) { - case C -> new CGenerator(context, false); - case CCPP -> new CGenerator(context, true); - case Python -> new PythonGenerator(context); - case CPP, TS, Rust -> - createKotlinBaseGenerator(target, context); - // If no case matched, then throw a runtime exception. - default -> throw new RuntimeException("Unexpected target!"); - }; + } + + /** + * Create a generator object for the given target. Returns null if the generator could not be + * created. + */ + private GeneratorBase createGenerator(LFGeneratorContext context) { + final Target target = Target.fromDecl(ASTUtils.targetDecl(context.getFileConfig().resource)); + assert target != null; + return switch (target) { + case C -> new CGenerator(context, false); + case CCPP -> new CGenerator(context, true); + case Python -> new PythonGenerator(context); + case CPP, TS, Rust -> createKotlinBaseGenerator(target, context); + // If no case matched, then throw a runtime exception. + default -> throw new RuntimeException("Unexpected target!"); + }; + } + + /** + * Create a code generator in Kotlin. + * + *

Since the CppGenerator and TSGenerator class are implemented in Kotlin, the classes are not + * visible from all contexts. If the RCA is run from within Eclipse via "Run as Eclipse + * Application", the Kotlin classes are unfortunately not available at runtime due to bugs in the + * Eclipse Kotlin plugin. (See + * https://stackoverflow.com/questions/68095816/is-ist-possible-to-build-mixed-kotlin-and-java-applications-with-a-recent-eclips) + * In this case, the method returns null + * + * @return A Kotlin Generator object if the class can be found + */ + private GeneratorBase createKotlinBaseGenerator(Target target, LFGeneratorContext context) { + // Since our Eclipse Plugin uses code injection via guice, we need to + // play a few tricks here so that Kotlin FileConfig and + // Kotlin Generator do not appear as an import. Instead, we look the + // class up at runtime and instantiate it if found. + String classPrefix = + "org.lflang.generator." + target.packageName + "." + target.classNamePrefix; + try { + Class generatorClass = Class.forName(classPrefix + "Generator"); + Constructor ctor = + generatorClass.getDeclaredConstructor( + LFGeneratorContext.class, LFGlobalScopeProvider.class); + + return (GeneratorBase) ctor.newInstance(context, scopeProvider); + } catch (ReflectiveOperationException e) { + generatorErrorsOccurred = true; + context + .getErrorReporter() + .reportError( + "The code generator for the " + + target + + " target could not be found. " + + "This is likely because you built Epoch using " + + "Eclipse. The " + + target + + " code generator is written in Kotlin and, unfortunately, the plugin that" + + " Eclipse uses for compiling Kotlin code is broken. Please consider building" + + " Epoch using Maven.\n" + + "For step-by-step instructions, see: " + + "https://github.com/icyphy/lingua-franca/wiki/Running-Lingua-Franca-IDE-%28Epoch%29-with-Kotlin-based-Code-Generators-Enabled-%28without-Eclipse-Environment%29"); + return null; + } + } + + @Override + public void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) { + final LFGeneratorContext lfContext; + if (context instanceof LFGeneratorContext) { + lfContext = (LFGeneratorContext) context; + } else { + lfContext = LFGeneratorContext.lfGeneratorContextOf(resource, fsa, context); } + // The fastest way to generate code is to not generate any code. + if (lfContext.getMode() == LFGeneratorContext.Mode.LSP_FAST) return; - /** - * Create a code generator in Kotlin. - *

- * Since the CppGenerator and TSGenerator class are implemented in Kotlin, the classes are - * not visible from all contexts. If the RCA is run from within Eclipse via - * "Run as Eclipse Application", the Kotlin classes are unfortunately not - * available at runtime due to bugs in the Eclipse Kotlin plugin. (See - * https://stackoverflow.com/questions/68095816/is-ist-possible-to-build-mixed-kotlin-and-java-applications-with-a-recent-eclips) - * In this case, the method returns null - * - * @return A Kotlin Generator object if the class can be found - */ - private GeneratorBase createKotlinBaseGenerator(Target target, LFGeneratorContext context) { - // Since our Eclipse Plugin uses code injection via guice, we need to - // play a few tricks here so that Kotlin FileConfig and - // Kotlin Generator do not appear as an import. Instead, we look the - // class up at runtime and instantiate it if found. - String classPrefix = "org.lflang.generator." + target.packageName + "." + target.classNamePrefix; - try { - Class generatorClass = Class.forName(classPrefix + "Generator"); - Constructor ctor = generatorClass - .getDeclaredConstructor(LFGeneratorContext.class, LFGlobalScopeProvider.class); - - return (GeneratorBase) ctor.newInstance(context, scopeProvider); - } catch (ReflectiveOperationException e) { - generatorErrorsOccurred = true; - context.getErrorReporter().reportError( - "The code generator for the " + target + " target could not be found. " - + "This is likely because you built Epoch using " - + "Eclipse. The " + target + " code generator is written in Kotlin " - + "and, unfortunately, the plugin that Eclipse uses " - + "for compiling Kotlin code is broken. " - + "Please consider building Epoch using Maven.\n" - + "For step-by-step instructions, see: " - + "https://github.com/icyphy/lingua-franca/wiki/Running-Lingua-Franca-IDE-%28Epoch%29-with-Kotlin-based-Code-Generators-Enabled-%28without-Eclipse-Environment%29"); - return null; - } - } + if (FedASTUtils.findFederatedReactor(resource) != null) { + try { + generatorErrorsOccurred = (new FedGenerator(lfContext)).doGenerate(resource, lfContext); + } catch (IOException e) { + throw new RuntimeIOException("Error during federated code generation", e); + } - @Override - public void doGenerate(Resource resource, IFileSystemAccess2 fsa, - IGeneratorContext context) { - final LFGeneratorContext lfContext; - if (context instanceof LFGeneratorContext) { - lfContext = (LFGeneratorContext)context; - } else { - lfContext = LFGeneratorContext.lfGeneratorContextOf(resource, fsa, context); - } - - // The fastest way to generate code is to not generate any code. - if (lfContext.getMode() == LFGeneratorContext.Mode.LSP_FAST) return; - - if (FedASTUtils.findFederatedReactor(resource) != null) { - try { - generatorErrorsOccurred = (new FedGenerator(lfContext)).doGenerate(resource, lfContext); - } catch (IOException e) { - throw new RuntimeIOException("Error during federated code generation", e); - } - - } else { - - final GeneratorBase generator = createGenerator(lfContext); - - if (generator != null) { - generator.doGenerate(resource, lfContext); - generatorErrorsOccurred = generator.errorsOccurred(); - } - } - final ErrorReporter errorReporter = lfContext.getErrorReporter(); - if (errorReporter instanceof LanguageServerErrorReporter) { - ((LanguageServerErrorReporter) errorReporter).publishDiagnostics(); - } - } + } else { - /** Return true if errors occurred in the last call to doGenerate(). */ - public boolean errorsOccurred() { - return generatorErrorsOccurred; + final GeneratorBase generator = createGenerator(lfContext); + + if (generator != null) { + generator.doGenerate(resource, lfContext); + generatorErrorsOccurred = generator.errorsOccurred(); + } + } + final ErrorReporter errorReporter = lfContext.getErrorReporter(); + if (errorReporter instanceof LanguageServerErrorReporter) { + ((LanguageServerErrorReporter) errorReporter).publishDiagnostics(); } + } + + /** Return true if errors occurred in the last call to doGenerate(). */ + public boolean errorsOccurred() { + return generatorErrorsOccurred; + } } diff --git a/org.lflang/src/org/lflang/generator/LFGeneratorContext.java b/org.lflang/src/org/lflang/generator/LFGeneratorContext.java index 84032ecdc6..727ce229b9 100644 --- a/org.lflang/src/org/lflang/generator/LFGeneratorContext.java +++ b/org.lflang/src/org/lflang/generator/LFGeneratorContext.java @@ -3,160 +3,146 @@ import java.nio.file.Path; import java.util.Map; import java.util.Properties; - import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.generator.IFileSystemAccess2; import org.eclipse.xtext.generator.IGeneratorContext; - import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.TargetConfig; /** - * An {@code LFGeneratorContext} is the context of a Lingua Franca build process. - * It is the point of communication between a build process and the environment - * in which it is executed. + * An {@code LFGeneratorContext} is the context of a Lingua Franca build process. It is the point of + * communication between a build process and the environment in which it is executed. * * @author Peter Donovan */ public interface LFGeneratorContext extends IGeneratorContext { - /** - * Enumeration of keys used to parameterize the build process. - */ - enum BuildParm { - BUILD_TYPE("The build type to use"), - CLEAN("Clean before building."), - EXTERNAL_RUNTIME_PATH("Specify an external runtime library to be used by the compiled binary."), - FEDERATED("Treat main reactor as federated."), - HELP("Display this information."), - LOGGING("The logging level to use by the generated binary"), - LINT("Enable or disable linting of generated code."), - NO_COMPILE("Do not invoke target compiler."), - OUTPUT_PATH("Specify the root output directory."), - PRINT_STATISTICS("Instruct the runtime to collect and print statistics."), - QUIET("Suppress output of the target compiler and other commands"), - RTI("Specify the location of the RTI."), - RUNTIME_VERSION("Specify the version of the runtime library used for compiling LF programs."), - SCHEDULER("Specify the runtime scheduler (if supported)."), - TARGET_COMPILER("Target compiler to invoke."), - THREADING("Specify whether the runtime should use multi-threading (true/false)."), - VERSION("Print version information."), - WORKERS("Specify the default number of worker threads."); - - public final String description; - - BuildParm(String description) { - this.description = description; - } - - /** - * Return the string to use as the key to store a value relating to this parameter. - */ - public String getKey() { - return this.name().toLowerCase().replace('_', '-'); - } - - /** - * Return the value corresponding to this parameter or {@code null} if there is none. - * @param context The context passed to the code generator. - */ - public String getValue(LFGeneratorContext context) { - return context.getArgs().getProperty(this.getKey()); - } + /** Enumeration of keys used to parameterize the build process. */ + enum BuildParm { + BUILD_TYPE("The build type to use"), + CLEAN("Clean before building."), + EXTERNAL_RUNTIME_PATH("Specify an external runtime library to be used by the compiled binary."), + FEDERATED("Treat main reactor as federated."), + HELP("Display this information."), + LOGGING("The logging level to use by the generated binary"), + LINT("Enable or disable linting of generated code."), + NO_COMPILE("Do not invoke target compiler."), + OUTPUT_PATH("Specify the root output directory."), + PRINT_STATISTICS("Instruct the runtime to collect and print statistics."), + QUIET("Suppress output of the target compiler and other commands"), + RTI("Specify the location of the RTI."), + RUNTIME_VERSION("Specify the version of the runtime library used for compiling LF programs."), + SCHEDULER("Specify the runtime scheduler (if supported)."), + TARGET_COMPILER("Target compiler to invoke."), + THREADING("Specify whether the runtime should use multi-threading (true/false)."), + VERSION("Print version information."), + WORKERS("Specify the default number of worker threads."); + + public final String description; + + BuildParm(String description) { + this.description = description; } - - enum Mode { - STANDALONE, - EPOCH, - LSP_FAST, - LSP_MEDIUM, - LSP_SLOW, - UNDEFINED + /** Return the string to use as the key to store a value relating to this parameter. */ + public String getKey() { + return this.name().toLowerCase().replace('_', '-'); } /** - * Return the mode of operation, which indicates how the compiler has been invoked - * (e.g., from within Epoch, from the command line, or via a Language Server). - */ - Mode getMode(); - - /** - * Return any arguments that will override target properties. - */ - Properties getArgs(); - - /** - * Get the error reporter for this context; construct one if it hasn't been - * constructed yet. - */ - ErrorReporter getErrorReporter(); - - /** - * Mark the code generation process performed in this - * context as finished with the result {@code result}. - * @param result The result of the code generation - * process that was performed in this - * context. + * Return the value corresponding to this parameter or {@code null} if there is none. + * + * @param context The context passed to the code generator. */ - void finish(GeneratorResult result); - - /** - * Return the result of the code generation process that was performed in - * this context. - * @return the result of the code generation process that was performed in - * this context - */ - GeneratorResult getResult(); - - FileConfig getFileConfig(); - - TargetConfig getTargetConfig(); - - /** - * Report the progress of a build. - * @param message A message for the LF programmer to read. - * @param percentage The approximate percent completion of the build. - */ - void reportProgress(String message, int percentage); - - /** - * Conclude this build and record the result if necessary. - * @param status The status of the result. - * @param codeMaps The generated files and their corresponding code maps. - */ - default void finish( - GeneratorResult.Status status, - Map codeMaps - ) { - finish(new GeneratorResult(status, this, codeMaps)); - } - - /** - * Conclude this build and record that it was unsuccessful. - */ - default void unsuccessfulFinish() { - finish( - getCancelIndicator() != null && getCancelIndicator().isCanceled() ? - GeneratorResult.CANCELLED : GeneratorResult.FAILED - ); - } - - /** - * Return the {@code LFGeneratorContext} that best describes the given {@code context} when - * building {@code Resource}. - * @param resource - * @param fsa - * @param context The context of a Lingua Franca build process. - * @return The {@code LFGeneratorContext} that best describes the given {@code context} when - * building {@code Resource}. - */ - static LFGeneratorContext lfGeneratorContextOf(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) { - if (context instanceof LFGeneratorContext) return (LFGeneratorContext) context; - - if (resource.getURI().isPlatform()) return new MainContext(Mode.EPOCH, resource, fsa, context.getCancelIndicator()); - - return new MainContext(Mode.LSP_FAST, resource, fsa, context.getCancelIndicator()); + public String getValue(LFGeneratorContext context) { + return context.getArgs().getProperty(this.getKey()); } + } + + enum Mode { + STANDALONE, + EPOCH, + LSP_FAST, + LSP_MEDIUM, + LSP_SLOW, + UNDEFINED + } + + /** + * Return the mode of operation, which indicates how the compiler has been invoked (e.g., from + * within Epoch, from the command line, or via a Language Server). + */ + Mode getMode(); + + /** Return any arguments that will override target properties. */ + Properties getArgs(); + + /** Get the error reporter for this context; construct one if it hasn't been constructed yet. */ + ErrorReporter getErrorReporter(); + + /** + * Mark the code generation process performed in this context as finished with the result {@code + * result}. + * + * @param result The result of the code generation process that was performed in this context. + */ + void finish(GeneratorResult result); + + /** + * Return the result of the code generation process that was performed in this context. + * + * @return the result of the code generation process that was performed in this context + */ + GeneratorResult getResult(); + + FileConfig getFileConfig(); + + TargetConfig getTargetConfig(); + + /** + * Report the progress of a build. + * + * @param message A message for the LF programmer to read. + * @param percentage The approximate percent completion of the build. + */ + void reportProgress(String message, int percentage); + + /** + * Conclude this build and record the result if necessary. + * + * @param status The status of the result. + * @param codeMaps The generated files and their corresponding code maps. + */ + default void finish(GeneratorResult.Status status, Map codeMaps) { + finish(new GeneratorResult(status, this, codeMaps)); + } + + /** Conclude this build and record that it was unsuccessful. */ + default void unsuccessfulFinish() { + finish( + getCancelIndicator() != null && getCancelIndicator().isCanceled() + ? GeneratorResult.CANCELLED + : GeneratorResult.FAILED); + } + + /** + * Return the {@code LFGeneratorContext} that best describes the given {@code context} when + * building {@code Resource}. + * + * @param resource + * @param fsa + * @param context The context of a Lingua Franca build process. + * @return The {@code LFGeneratorContext} that best describes the given {@code context} when + * building {@code Resource}. + */ + static LFGeneratorContext lfGeneratorContextOf( + Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) { + if (context instanceof LFGeneratorContext) return (LFGeneratorContext) context; + + if (resource.getURI().isPlatform()) + return new MainContext(Mode.EPOCH, resource, fsa, context.getCancelIndicator()); + + return new MainContext(Mode.LSP_FAST, resource, fsa, context.getCancelIndicator()); + } } diff --git a/org.lflang/src/org/lflang/generator/LFResource.java b/org.lflang/src/org/lflang/generator/LFResource.java index bc59c418ee..42367dd24f 100644 --- a/org.lflang/src/org/lflang/generator/LFResource.java +++ b/org.lflang/src/org/lflang/generator/LFResource.java @@ -5,36 +5,43 @@ import org.lflang.TargetConfig; /** - * A class that keeps metadata for discovered resources - * during code generation and the supporting structures - * associated with that resource. - * + * A class that keeps metadata for discovered resources during code generation and the supporting + * structures associated with that resource. + * * @author Soroush Bateni */ public class LFResource { - LFResource(Resource resource, FileConfig fileConfig, TargetConfig targetConfig) { - this.eResource = resource; // FIXME: this is redundant because fileConfig already has the resource. - this.fileConfig = fileConfig; - this.targetConfig = targetConfig; - } - - /** - * Resource associated with a file either from the main .lf - * file or one of the imported ones. - */ - Resource eResource; - public Resource getEResource() { return this.eResource; }; - - /** - * The file config associated with 'resource' that can be - * used to discover files relative to that resource. - */ - FileConfig fileConfig; - public FileConfig getFileConfig() { return this.fileConfig; }; - - /** - * The target config read from the resource. - */ - TargetConfig targetConfig; - public TargetConfig getTargetConfig() { return this.targetConfig; }; + LFResource(Resource resource, FileConfig fileConfig, TargetConfig targetConfig) { + this.eResource = + resource; // FIXME: this is redundant because fileConfig already has the resource. + this.fileConfig = fileConfig; + this.targetConfig = targetConfig; + } + + /** Resource associated with a file either from the main .lf file or one of the imported ones. */ + Resource eResource; + + public Resource getEResource() { + return this.eResource; + } + ; + + /** + * The file config associated with 'resource' that can be used to discover files relative to that + * resource. + */ + FileConfig fileConfig; + + public FileConfig getFileConfig() { + return this.fileConfig; + } + ; + + /** The target config read from the resource. */ + TargetConfig targetConfig; + + public TargetConfig getTargetConfig() { + return this.targetConfig; + } + ; } diff --git a/org.lflang/src/org/lflang/generator/LanguageServerErrorReporter.java b/org.lflang/src/org/lflang/generator/LanguageServerErrorReporter.java index 5ce5da28dc..31304f43c9 100644 --- a/org.lflang/src/org/lflang/generator/LanguageServerErrorReporter.java +++ b/org.lflang/src/org/lflang/generator/LanguageServerErrorReporter.java @@ -6,16 +6,14 @@ import java.util.List; import java.util.Map; import java.util.Optional; - import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.DiagnosticSeverity; import org.eclipse.lsp4j.PublishDiagnosticsParams; import org.eclipse.lsp4j.Range; -import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import org.eclipse.lsp4j.services.LanguageClient; - +import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import org.lflang.ErrorReporter; /** @@ -25,179 +23,174 @@ */ public class LanguageServerErrorReporter implements ErrorReporter { - /** - * The language client to which errors should be - * reported, if such a client is available. - * FIXME: This is a de facto global, and it is a hack. - */ - private static LanguageClient client; - - /** The document for which this is a diagnostic acceptor. */ - private final EObject parseRoot; - /** The list of all diagnostics since the last reset. */ - private final Map> diagnostics; - - /* ------------------------ CONSTRUCTORS -------------------------- */ - - /** - * Initialize a {@code DiagnosticAcceptor} for the - * document whose parse tree root node is - * {@code parseRoot}. - * @param parseRoot the root of the AST of the document - * for which this is an error reporter - */ - public LanguageServerErrorReporter(EObject parseRoot) { - this.parseRoot = parseRoot; - this.diagnostics = new HashMap<>(); - } - - /* ----------------------- PUBLIC METHODS ------------------------- */ - - @Override - public String reportError(String message) { - return report(getMainFile(), DiagnosticSeverity.Error, message); - } - - @Override - public String reportWarning(String message) { - return report(getMainFile(), DiagnosticSeverity.Warning, message); - } - - @Override - public String reportInfo(String message) { - return report(getMainFile(), DiagnosticSeverity.Information, message); - } - - @Override - public String reportError(EObject object, String message) { - return reportError(message); - } - - @Override - public String reportWarning(EObject object, String message) { - return reportWarning(message); - } - - @Override - public String reportInfo(EObject object, String message) { - return reportInfo(message); - } - - @Override - public String reportError(Path file, Integer line, String message) { - return report(file, DiagnosticSeverity.Error, message, line != null ? line : 1); - } - - @Override - public String reportWarning(Path file, Integer line, String message) { - return report(file, DiagnosticSeverity.Warning, message, line != null ? line : 1); - } - - @Override - public String reportInfo(Path file, Integer line, String message) { - return report(file, DiagnosticSeverity.Information, message, line != null ? line : 1); - } - - @Override - public boolean getErrorsOccurred() { - return diagnostics.values().stream().anyMatch( - it -> it.stream().anyMatch(diagnostic -> diagnostic.getSeverity() == DiagnosticSeverity.Error) - ); - } - - @Override - public String report(Path file, DiagnosticSeverity severity, String message) { - return report(file, severity, message, 1); - } - - @Override - public String report(Path file, DiagnosticSeverity severity, String message, int line) { - Optional text = getLine(line - 1); - return report( - file, - severity, - message, - Position.fromOneBased(line, 1), - Position.fromOneBased(line, 1 + (text.isEmpty() ? 0 : text.get().length())) - ); - } - - @Override - public String report(Path file, DiagnosticSeverity severity, String message, Position startPos, Position endPos) { - if (file == null) file = getMainFile(); - diagnostics.putIfAbsent(file, new ArrayList<>()); - diagnostics.get(file).add(new Diagnostic( - toRange(startPos, endPos), message, severity, "LF Language Server" - )); - return "" + severity + ": " + message; - } - - /** - * Save a reference to the language client. - * @param client the language client - */ - public static void setClient(LanguageClient client) { - LanguageServerErrorReporter.client = client; - } - - /** - * Publish diagnostics by forwarding them to the - * language client. - */ - public void publishDiagnostics() { - if (client == null) { - System.err.println( - "WARNING: Cannot publish diagnostics because the language client has not yet been found." - ); - return; - } - for (Path file : diagnostics.keySet()) { - PublishDiagnosticsParams publishDiagnosticsParams = new PublishDiagnosticsParams(); - publishDiagnosticsParams.setUri(URI.createFileURI(file.toString()).toString()); - publishDiagnosticsParams.setDiagnostics(diagnostics.get(file)); - client.publishDiagnostics(publishDiagnosticsParams); - } - } - - /* ----------------------- PRIVATE METHODS ------------------------ */ - - /** Return the file on which the current validation process was triggered. */ - private Path getMainFile() { - return Path.of(parseRoot.eResource().getURI().toFileString()); - } - - /** - * Return the text of the document for which this is an - * error reporter. - * @return the text of the document for which this is an - * error reporter - */ - private String getText() { - return NodeModelUtils.getNode(parseRoot).getText(); - } - - /** - * Return the line at index {@code line} in the - * document for which this is an error reporter. - * @param line the zero-based line index - * @return the line located at the given index - */ - private Optional getLine(int line) { - return getText().lines().skip(line).findFirst(); - } - - /** - * Return the Range that starts at {@code p0} and ends - * at {@code p1}. - * @param p0 an arbitrary Position - * @param p1 a Position that is greater than {@code p0} - * @return the Range that starts at {@code p0} and ends - * at {@code p1} - */ - private Range toRange(Position p0, Position p1) { - return new Range( - new org.eclipse.lsp4j.Position(p0.getZeroBasedLine(), p0.getZeroBasedColumn()), - new org.eclipse.lsp4j.Position(p1.getZeroBasedLine(), p1.getZeroBasedColumn()) - ); - } + /** + * The language client to which errors should be reported, if such a client is available. FIXME: + * This is a de facto global, and it is a hack. + */ + private static LanguageClient client; + + /** The document for which this is a diagnostic acceptor. */ + private final EObject parseRoot; + /** The list of all diagnostics since the last reset. */ + private final Map> diagnostics; + + /* ------------------------ CONSTRUCTORS -------------------------- */ + + /** + * Initialize a {@code DiagnosticAcceptor} for the document whose parse tree root node is {@code + * parseRoot}. + * + * @param parseRoot the root of the AST of the document for which this is an error reporter + */ + public LanguageServerErrorReporter(EObject parseRoot) { + this.parseRoot = parseRoot; + this.diagnostics = new HashMap<>(); + } + + /* ----------------------- PUBLIC METHODS ------------------------- */ + + @Override + public String reportError(String message) { + return report(getMainFile(), DiagnosticSeverity.Error, message); + } + + @Override + public String reportWarning(String message) { + return report(getMainFile(), DiagnosticSeverity.Warning, message); + } + + @Override + public String reportInfo(String message) { + return report(getMainFile(), DiagnosticSeverity.Information, message); + } + + @Override + public String reportError(EObject object, String message) { + return reportError(message); + } + + @Override + public String reportWarning(EObject object, String message) { + return reportWarning(message); + } + + @Override + public String reportInfo(EObject object, String message) { + return reportInfo(message); + } + + @Override + public String reportError(Path file, Integer line, String message) { + return report(file, DiagnosticSeverity.Error, message, line != null ? line : 1); + } + + @Override + public String reportWarning(Path file, Integer line, String message) { + return report(file, DiagnosticSeverity.Warning, message, line != null ? line : 1); + } + + @Override + public String reportInfo(Path file, Integer line, String message) { + return report(file, DiagnosticSeverity.Information, message, line != null ? line : 1); + } + + @Override + public boolean getErrorsOccurred() { + return diagnostics.values().stream() + .anyMatch( + it -> + it.stream() + .anyMatch(diagnostic -> diagnostic.getSeverity() == DiagnosticSeverity.Error)); + } + + @Override + public String report(Path file, DiagnosticSeverity severity, String message) { + return report(file, severity, message, 1); + } + + @Override + public String report(Path file, DiagnosticSeverity severity, String message, int line) { + Optional text = getLine(line - 1); + return report( + file, + severity, + message, + Position.fromOneBased(line, 1), + Position.fromOneBased(line, 1 + (text.isEmpty() ? 0 : text.get().length()))); + } + + @Override + public String report( + Path file, DiagnosticSeverity severity, String message, Position startPos, Position endPos) { + if (file == null) file = getMainFile(); + diagnostics.putIfAbsent(file, new ArrayList<>()); + diagnostics + .get(file) + .add(new Diagnostic(toRange(startPos, endPos), message, severity, "LF Language Server")); + return "" + severity + ": " + message; + } + + /** + * Save a reference to the language client. + * + * @param client the language client + */ + public static void setClient(LanguageClient client) { + LanguageServerErrorReporter.client = client; + } + + /** Publish diagnostics by forwarding them to the language client. */ + public void publishDiagnostics() { + if (client == null) { + System.err.println( + "WARNING: Cannot publish diagnostics because the language client has not yet been" + + " found."); + return; + } + for (Path file : diagnostics.keySet()) { + PublishDiagnosticsParams publishDiagnosticsParams = new PublishDiagnosticsParams(); + publishDiagnosticsParams.setUri(URI.createFileURI(file.toString()).toString()); + publishDiagnosticsParams.setDiagnostics(diagnostics.get(file)); + client.publishDiagnostics(publishDiagnosticsParams); + } + } + + /* ----------------------- PRIVATE METHODS ------------------------ */ + + /** Return the file on which the current validation process was triggered. */ + private Path getMainFile() { + return Path.of(parseRoot.eResource().getURI().toFileString()); + } + + /** + * Return the text of the document for which this is an error reporter. + * + * @return the text of the document for which this is an error reporter + */ + private String getText() { + return NodeModelUtils.getNode(parseRoot).getText(); + } + + /** + * Return the line at index {@code line} in the document for which this is an error reporter. + * + * @param line the zero-based line index + * @return the line located at the given index + */ + private Optional getLine(int line) { + return getText().lines().skip(line).findFirst(); + } + + /** + * Return the Range that starts at {@code p0} and ends at {@code p1}. + * + * @param p0 an arbitrary Position + * @param p1 a Position that is greater than {@code p0} + * @return the Range that starts at {@code p0} and ends at {@code p1} + */ + private Range toRange(Position p0, Position p1) { + return new Range( + new org.eclipse.lsp4j.Position(p0.getZeroBasedLine(), p0.getZeroBasedColumn()), + new org.eclipse.lsp4j.Position(p1.getZeroBasedLine(), p1.getZeroBasedColumn())); + } } diff --git a/org.lflang/src/org/lflang/generator/LfExpressionVisitor.java b/org.lflang/src/org/lflang/generator/LfExpressionVisitor.java index 2bb72789ff..15f0e8ddad 100644 --- a/org.lflang/src/org/lflang/generator/LfExpressionVisitor.java +++ b/org.lflang/src/org/lflang/generator/LfExpressionVisitor.java @@ -40,121 +40,120 @@ */ 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"); + 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); } - /** Base visitor class where methods are defaulted to a common one. */ - abstract class DefaultLfVisitor implements LfExpressionVisitor { + 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); - 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 visitLiteral(Literal expr, P param) { - return visitExpression(expr, param); - } + @Override + public R visitTimeLiteral(Time expr, P param) { + return visitExpression(expr, param); + } - @Override - public R visitBracedListExpr(BracedListExpression expr, P param) { - return visitExpression(expr, param); - } + @Override + public R visitCodeExpr(CodeExpr expr, P param) { + return visitExpression(expr, param); + } - @Override - public R visitTimeLiteral(Time 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 R visitCodeExpr(CodeExpr expr, P param) { - return visitExpression(expr, param); - } + @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 R visitParameterRef(ParameterReference expr, P param) { - return visitExpression(expr, param); - } + @Override + public Expression visitTimeLiteral(Time expr, P param) { + Time clone = LfFactory.eINSTANCE.createTime(); + clone.setUnit(expr.getUnit()); + clone.setInterval(expr.getInterval()); + return clone; } - /** - * 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; - } + @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/MainContext.java b/org.lflang/src/org/lflang/generator/MainContext.java index 5ee53e5f83..4a60c4d6a8 100644 --- a/org.lflang/src/org/lflang/generator/MainContext.java +++ b/org.lflang/src/org/lflang/generator/MainContext.java @@ -4,12 +4,10 @@ import java.util.Objects; import java.util.Properties; import java.util.function.Function; - import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.generator.IFileSystemAccess2; import org.eclipse.xtext.util.CancelIndicator; import org.eclipse.xtext.util.RuntimeIOException; - import org.lflang.DefaultErrorReporter; import org.lflang.ErrorReporter; import org.lflang.FileConfig; @@ -17,154 +15,156 @@ import org.lflang.generator.IntegratedBuilder.ReportProgress; /** - * A {@code MainContext} is an {@code LFGeneratorContext} that is - * not nested in any other generator context. There is one - * {@code MainContext} for every build process. + * A {@code MainContext} is an {@code LFGeneratorContext} that is not nested in any other generator + * context. There is one {@code MainContext} for every build process. * * @author Peter Donovan */ public class MainContext implements LFGeneratorContext { - - /** - * This constructor will be set by the LF plugin, if the generator is running in Epoch. - */ - public static Function EPOCH_ERROR_REPORTER_CONSTRUCTOR = null; - - /** - * The indicator that shows whether this build - * process is canceled. - */ - private final CancelIndicator cancelIndicator; - /** The {@code ReportProgress} function of {@code this}. */ - private final ReportProgress reportProgress; - - private final FileConfig fileConfig; - - /** Whether the requested build is required to be complete. */ - private final Mode mode; - - private TargetConfig targetConfig; - - /** The result of the code generation process. */ - private GeneratorResult result = null; - private final Properties args; - private final ErrorReporter errorReporter; - - /** - * Initialize the context of a build process whose cancellation is - * indicated by {@code cancelIndicator} - * @param mode The mode of this build process. - * @param cancelIndicator The cancel indicator of the code generation - * process to which this corresponds. - */ - public MainContext(Mode mode, Resource resource, IFileSystemAccess2 fsa, CancelIndicator cancelIndicator) { - this( - mode, cancelIndicator, (message, completion) -> {}, new Properties(), resource, fsa, - (mode == Mode.EPOCH && EPOCH_ERROR_REPORTER_CONSTRUCTOR != null) ? - EPOCH_ERROR_REPORTER_CONSTRUCTOR : - fileConfig -> new DefaultErrorReporter() - ); - } - - /** - * Initialize the context of a build process whose cancellation is - * indicated by {@code cancelIndicator} - * @param mode The mode of this build process. - * @param cancelIndicator The cancel indicator of the code generation - * process to which this corresponds. - * @param reportProgress The {@code ReportProgress} function of - * {@code this}. - * @param args Any arguments that may be used to affect the product of the - * build. - * @param resource ... - * @param fsa ... - * @param constructErrorReporter A function that constructs the appropriate - * error reporter for the given FileConfig. - */ - public MainContext( - Mode mode, - CancelIndicator cancelIndicator, - ReportProgress reportProgress, - Properties args, - Resource resource, - IFileSystemAccess2 fsa, - Function constructErrorReporter - ) { - this.mode = mode; - this.cancelIndicator = cancelIndicator == null ? () -> false : cancelIndicator; - this.reportProgress = reportProgress; - this.args = args; - - try { - var useHierarchicalBin = args.containsKey("hierarchical-bin") && Boolean.parseBoolean(args.getProperty("hierarchical-bin")); - fileConfig = Objects.requireNonNull(LFGenerator.createFileConfig(resource, FileConfig.getSrcGenRoot(fsa), useHierarchicalBin)); - } catch (IOException e) { - throw new RuntimeIOException("Error during FileConfig instantiation", e); - } - - this.errorReporter = constructErrorReporter.apply(this.fileConfig); - - loadTargetConfig(); - } - - @Override - public CancelIndicator getCancelIndicator() { - return cancelIndicator; - } - - @Override - public Mode getMode() { - return mode; - } - - @Override - public Properties getArgs() { - return args; - } - - @Override - public ErrorReporter getErrorReporter() { - return errorReporter; + /** This constructor will be set by the LF plugin, if the generator is running in Epoch. */ + public static Function EPOCH_ERROR_REPORTER_CONSTRUCTOR = null; + + /** The indicator that shows whether this build process is canceled. */ + private final CancelIndicator cancelIndicator; + /** The {@code ReportProgress} function of {@code this}. */ + private final ReportProgress reportProgress; + + private final FileConfig fileConfig; + + /** Whether the requested build is required to be complete. */ + private final Mode mode; + + private TargetConfig targetConfig; + + /** The result of the code generation process. */ + private GeneratorResult result = null; + + private final Properties args; + private final ErrorReporter errorReporter; + + /** + * Initialize the context of a build process whose cancellation is indicated by {@code + * cancelIndicator} + * + * @param mode The mode of this build process. + * @param cancelIndicator The cancel indicator of the code generation process to which this + * corresponds. + */ + public MainContext( + Mode mode, Resource resource, IFileSystemAccess2 fsa, CancelIndicator cancelIndicator) { + this( + mode, + cancelIndicator, + (message, completion) -> {}, + new Properties(), + resource, + fsa, + (mode == Mode.EPOCH && EPOCH_ERROR_REPORTER_CONSTRUCTOR != null) + ? EPOCH_ERROR_REPORTER_CONSTRUCTOR + : fileConfig -> new DefaultErrorReporter()); + } + + /** + * Initialize the context of a build process whose cancellation is indicated by {@code + * cancelIndicator} + * + * @param mode The mode of this build process. + * @param cancelIndicator The cancel indicator of the code generation process to which this + * corresponds. + * @param reportProgress The {@code ReportProgress} function of {@code this}. + * @param args Any arguments that may be used to affect the product of the build. + * @param resource ... + * @param fsa ... + * @param constructErrorReporter A function that constructs the appropriate error reporter for the + * given FileConfig. + */ + public MainContext( + Mode mode, + CancelIndicator cancelIndicator, + ReportProgress reportProgress, + Properties args, + Resource resource, + IFileSystemAccess2 fsa, + Function constructErrorReporter) { + this.mode = mode; + this.cancelIndicator = cancelIndicator == null ? () -> false : cancelIndicator; + this.reportProgress = reportProgress; + this.args = args; + + try { + var useHierarchicalBin = + args.containsKey("hierarchical-bin") + && Boolean.parseBoolean(args.getProperty("hierarchical-bin")); + fileConfig = + Objects.requireNonNull( + LFGenerator.createFileConfig( + resource, FileConfig.getSrcGenRoot(fsa), useHierarchicalBin)); + } catch (IOException e) { + throw new RuntimeIOException("Error during FileConfig instantiation", e); } - @Override - public void finish(GeneratorResult result) { - if (this.result != null) throw new IllegalStateException("A code generation process can only have one result."); - this.result = result; - reportProgress(result.getUserMessage(), 100); - } - - @Override - public GeneratorResult getResult() { - return result != null ? result : GeneratorResult.NOTHING; - } - - @Override - public FileConfig getFileConfig() { - return this.fileConfig; - } - - @Override - public TargetConfig getTargetConfig() { - return this.targetConfig; - } - - @Override - public void reportProgress(String message, int percentage) { - reportProgress.apply(message, percentage); - } - - /** - * Load the target configuration based on the contents of the resource. - * This is done automatically upon instantiation of the context, but - * in case the resource changes (e.g., due to an AST transformation), - * this method can be called to reload to ensure that the changes are - * reflected in the target configuration. - */ - public void loadTargetConfig() { - this.targetConfig = new TargetConfig( - args, GeneratorUtils.findTargetDecl(fileConfig.resource), errorReporter - ); - } + this.errorReporter = constructErrorReporter.apply(this.fileConfig); + + loadTargetConfig(); + } + + @Override + public CancelIndicator getCancelIndicator() { + return cancelIndicator; + } + + @Override + public Mode getMode() { + return mode; + } + + @Override + public Properties getArgs() { + return args; + } + + @Override + public ErrorReporter getErrorReporter() { + return errorReporter; + } + + @Override + public void finish(GeneratorResult result) { + if (this.result != null) + throw new IllegalStateException("A code generation process can only have one result."); + this.result = result; + reportProgress(result.getUserMessage(), 100); + } + + @Override + public GeneratorResult getResult() { + return result != null ? result : GeneratorResult.NOTHING; + } + + @Override + public FileConfig getFileConfig() { + return this.fileConfig; + } + + @Override + public TargetConfig getTargetConfig() { + return this.targetConfig; + } + + @Override + public void reportProgress(String message, int percentage) { + reportProgress.apply(message, percentage); + } + + /** + * Load the target configuration based on the contents of the resource. This is done automatically + * upon instantiation of the context, but in case the resource changes (e.g., due to an AST + * transformation), this method can be called to reload to ensure that the changes are reflected + * in the target configuration. + */ + public void loadTargetConfig() { + this.targetConfig = + new TargetConfig(args, GeneratorUtils.findTargetDecl(fileConfig.resource), errorReporter); + } } diff --git a/org.lflang/src/org/lflang/generator/MixedRadixInt.java b/org.lflang/src/org/lflang/generator/MixedRadixInt.java index 700012385c..c75ebd5864 100644 --- a/org.lflang/src/org/lflang/generator/MixedRadixInt.java +++ b/org.lflang/src/org/lflang/generator/MixedRadixInt.java @@ -34,241 +34,223 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.Set; /** - * Representation of a permuted mixed radix (PMR) integer. - * A mixed radix number is a number representation where each digit can have - * a distinct radix. The radixes are given by a list of numbers, r0, r1, ... , rn, - * where r0 is the radix of the lowest-order digit and rn is the radix of the - * highest order digit that has a specified radix. + * Representation of a permuted mixed radix (PMR) integer. A mixed radix number is a number + * representation where each digit can have a distinct radix. The radixes are given by a list of + * numbers, r0, r1, ... , rn, where r0 is the radix of the lowest-order digit and rn is the radix of + * the highest order digit that has a specified radix. + * + *

A PMR is a mixed radix number that, when incremented, increments the digits in the order given + * by the permutation matrix. For an ordinary mixed radix number, the permutation matrix is [0, 1, + * ..., n-1]. The permutation matrix may be any permutation of these digits, [d0, d1, ..., dn-1], in + * which case, when incremented, the d0 digit will be incremented first. If it overflows, it will be + * set to 0 and the d1 digit will be incremented. If it overflows, the next digit is incremented. If + * the last digit overflows, then the number wraps around so that all digits become zero. + * + *

This implementation realizes a finite set of numbers, where incrementing past the end of the + * range wraps around to the beginning. As a consequence, the increment() function from any starting + * point is guaranteed to eventually cover all possible values. + * + *

The {@link #toString()} method gives a string representation of the number where each digit is + * represented by the string "d%r", where d is the digit and r is the radix. For example, the number + * "1%2, 2%3, 1%4" has value 11, 1 + (2*2) + (1*2*3). * - * A PMR is a mixed radix number that, when incremented, - * increments the digits in the order given by the permutation matrix. - * For an ordinary mixed radix number, the permutation matrix is - * [0, 1, ..., n-1]. The permutation matrix may be any permutation of - * these digits, [d0, d1, ..., dn-1], in which case, when incremented, - * the d0 digit will be incremented first. If it overflows, it will be - * set to 0 and the d1 digit will be incremented. If it overflows, the - * next digit is incremented. If the last digit overflows, then the - * number wraps around so that all digits become zero. - * - * This implementation realizes a finite set of numbers, where incrementing - * past the end of the range wraps around to the beginning. As a consequence, - * the increment() function from any starting point is guaranteed to eventually - * cover all possible values. - * - * The {@link #toString()} method gives a string representation of the number - * where each digit is represented by the string "d%r", where d is the digit - * and r is the radix. For example, the number "1%2, 2%3, 1%4" has value 11, - * 1 + (2*2) + (1*2*3). - * * @author Edward A. Lee */ public class MixedRadixInt { - - /** - * Create a mixed radix number with the specified digits and radixes, - * which are given low-order digits first. - * If there is one more digit than radixes, throw an exception. - * @param digits The digits, or null to get a zero-valued number. - * @param radixes The radixes. - * @param permutation The permutation matrix, or null for the default permutation. - */ - public MixedRadixInt( - List digits, List radixes, List permutation - ) { - if (radixes == null - || (digits != null && digits.size() > radixes.size()) - || (permutation != null && permutation.size() != radixes.size()) - || radixes.contains(0)) { - throw new IllegalArgumentException("Invalid constructor arguments."); - } - this.radixes = radixes; - if (digits != null) { - this.digits = digits; - } else { - this.digits = new ArrayList(1); - this.digits.add(0); - } - if (permutation != null) { - // Check the permutation matrix. - Set indices = new HashSet(); - for (int p : permutation) { - if (p < 0 || p >= radixes.size() || indices.contains(p)) { - throw new IllegalArgumentException( - "Permutation list is required to be a permutation of [0, 1, ... , n-1]."); - } - indices.add(p); - } - this.permutation = permutation; - } - } - - /** - * A zero-valued mixed radix number with just one digit will radix 1. - */ - public final static MixedRadixInt ZERO = new MixedRadixInt(null, List.of(1), null); - - ////////////////////////////////////////////////////////// - //// Public methods - /** - * Get the value as an integer. - */ - public int get() { - return get(0); + /** + * Create a mixed radix number with the specified digits and radixes, which are given low-order + * digits first. If there is one more digit than radixes, throw an exception. + * + * @param digits The digits, or null to get a zero-valued number. + * @param radixes The radixes. + * @param permutation The permutation matrix, or null for the default permutation. + */ + public MixedRadixInt(List digits, List radixes, List permutation) { + if (radixes == null + || (digits != null && digits.size() > radixes.size()) + || (permutation != null && permutation.size() != radixes.size()) + || radixes.contains(0)) { + throw new IllegalArgumentException("Invalid constructor arguments."); } - - /** - * Get the value as an integer after dropping the first n digits. - * @param n The number of digits to drop. - */ - public int get(int n) { - int result = 0; - int scale = 1; - if (n < 0) n = 0; - for (int i = n; i < radixes.size(); i++) { - if (i >= digits.size()) return result; - result += digits.get(i) * scale; - scale *= radixes.get(i); - } - return result; + this.radixes = radixes; + if (digits != null) { + this.digits = digits; + } else { + this.digits = new ArrayList(1); + this.digits.add(0); } - - /** - * Return the digits. This is assured of returning as many - * digits as there are radixes. - */ - public List getDigits() { - while (digits.size() < radixes.size()) { - digits.add(0); + if (permutation != null) { + // Check the permutation matrix. + Set indices = new HashSet(); + for (int p : permutation) { + if (p < 0 || p >= radixes.size() || indices.contains(p)) { + throw new IllegalArgumentException( + "Permutation list is required to be a permutation of [0, 1, ... , n-1]."); } - return digits; + indices.add(p); + } + this.permutation = permutation; } - - /** - * Return the permutation list. - */ - public List getPermutation() { - if (permutation == null) { - // Construct a default permutation. - permutation = new ArrayList(radixes.size()); - for (int i = 0; i < radixes.size(); i++) { - permutation.add(i); - } - } - return permutation; + } + + /** A zero-valued mixed radix number with just one digit will radix 1. */ + public static final MixedRadixInt ZERO = new MixedRadixInt(null, List.of(1), null); + + ////////////////////////////////////////////////////////// + //// Public methods + + /** Get the value as an integer. */ + public int get() { + return get(0); + } + + /** + * Get the value as an integer after dropping the first n digits. + * + * @param n The number of digits to drop. + */ + public int get(int n) { + int result = 0; + int scale = 1; + if (n < 0) n = 0; + for (int i = n; i < radixes.size(); i++) { + if (i >= digits.size()) return result; + result += digits.get(i) * scale; + scale *= radixes.get(i); } - - /** - * Return the radixes. - */ - public List getRadixes() { - return radixes; + return result; + } + + /** Return the digits. This is assured of returning as many digits as there are radixes. */ + public List getDigits() { + while (digits.size() < radixes.size()) { + digits.add(0); } - - /** - * Increment the number by one, using the permutation vector to - * determine the order in which the digits are incremented. - * If an overflow occurs, then a radix-infinity digit will be added - * to the digits array if there isn't one there already. - */ - public void increment() { - int i = 0; - while (i < radixes.size()) { - int digit_to_increment = getPermutation().get(i); - while (digit_to_increment >= digits.size()) { - digits.add(0); - } - digits.set(digit_to_increment, digits.get(digit_to_increment) + 1); - if (digits.get(digit_to_increment) >= radixes.get(digit_to_increment)) { - digits.set(digit_to_increment, 0); - i++; - } else { - return; // All done. - } - } + return digits; + } + + /** Return the permutation list. */ + public List getPermutation() { + if (permutation == null) { + // Construct a default permutation. + permutation = new ArrayList(radixes.size()); + for (int i = 0; i < radixes.size(); i++) { + permutation.add(i); + } } - - /** - * Return the magnitude of this PMR, which is defined to be the number - * of times that increment() would need to invoked starting with zero - * before the value returned by {@link #get()} would be reached. - */ - public int magnitude() { - int factor = 1; - int result = 0; - List p = getPermutation(); - for (int i = 0; i < radixes.size(); i++) { - if (digits.size() <= i) return result; - result += factor * digits.get(p.get(i)); - factor *= radixes.get(p.get(i)); - } - return result; + return permutation; + } + + /** Return the radixes. */ + public List getRadixes() { + return radixes; + } + + /** + * Increment the number by one, using the permutation vector to determine the order in which the + * digits are incremented. If an overflow occurs, then a radix-infinity digit will be added to the + * digits array if there isn't one there already. + */ + public void increment() { + int i = 0; + while (i < radixes.size()) { + int digit_to_increment = getPermutation().get(i); + while (digit_to_increment >= digits.size()) { + digits.add(0); + } + digits.set(digit_to_increment, digits.get(digit_to_increment) + 1); + if (digits.get(digit_to_increment) >= radixes.get(digit_to_increment)) { + digits.set(digit_to_increment, 0); + i++; + } else { + return; // All done. + } } - - /** - * Return the number of digits in this mixed radix number. - * This is the size of the radixes list. - */ - public int numDigits() { - return radixes.size(); + } + + /** + * Return the magnitude of this PMR, which is defined to be the number of times that increment() + * would need to invoked starting with zero before the value returned by {@link #get()} would be + * reached. + */ + public int magnitude() { + int factor = 1; + int result = 0; + List p = getPermutation(); + for (int i = 0; i < radixes.size(); i++) { + if (digits.size() <= i) return result; + result += factor * digits.get(p.get(i)); + factor *= radixes.get(p.get(i)); } - - /** - * Set the value of this number to equal that of the specified integer. - * @param v The ordinary integer value of this number. - */ - public void set(int v) { - int temp = v; - int count = 0; - for (int radix : radixes) { - if (count >= digits.size()) { - digits.add(temp % radix); - } else { - digits.set(count, temp % radix); - } - count++; - temp = temp / radix; - } + return result; + } + + /** + * Return the number of digits in this mixed radix number. This is the size of the radixes list. + */ + public int numDigits() { + return radixes.size(); + } + + /** + * Set the value of this number to equal that of the specified integer. + * + * @param v The ordinary integer value of this number. + */ + public void set(int v) { + int temp = v; + int count = 0; + for (int radix : radixes) { + if (count >= digits.size()) { + digits.add(temp % radix); + } else { + digits.set(count, temp % radix); + } + count++; + temp = temp / radix; } - - /** - * Set the magnitude of this number to equal that of the specified integer, - * which is the number of times that increment must be invoked from zero - * for the value returned by {@link #get()} to equal v. - * @param v The new magnitude of this number. - */ - public void setMagnitude(int v) { - int temp = v; - for (int i = 0; i < radixes.size(); i++) { - int p = getPermutation().get(i); - while (digits.size() < p + 1) digits.add(0); - digits.set(p, temp % radixes.get(p)); - temp = temp / radixes.get(p); - } + } + + /** + * Set the magnitude of this number to equal that of the specified integer, which is the number of + * times that increment must be invoked from zero for the value returned by {@link #get()} to + * equal v. + * + * @param v The new magnitude of this number. + */ + public void setMagnitude(int v) { + int temp = v; + for (int i = 0; i < radixes.size(); i++) { + int p = getPermutation().get(i); + while (digits.size() < p + 1) digits.add(0); + digits.set(p, temp % radixes.get(p)); + temp = temp / radixes.get(p); } - - /** - * Give a string representation of the number, where each digit is - * represented as n%r, where r is the radix. - */ - @Override - public String toString() { - List pieces = new LinkedList(); - Iterator radixIterator = radixes.iterator(); - for (int digit : digits) { - if (! radixIterator.hasNext()) { - pieces.add(digit + "%infinity"); - } else { - pieces.add(digit + "%" + radixIterator.next()); - } - } - return String.join(", ", pieces); + } + + /** + * Give a string representation of the number, where each digit is represented as n%r, where r is + * the radix. + */ + @Override + public String toString() { + List pieces = new LinkedList(); + Iterator radixIterator = radixes.iterator(); + for (int digit : digits) { + if (!radixIterator.hasNext()) { + pieces.add(digit + "%infinity"); + } else { + pieces.add(digit + "%" + radixIterator.next()); + } } + return String.join(", ", pieces); + } - ////////////////////////////////////////////////////////// - //// Private variables + ////////////////////////////////////////////////////////// + //// Private variables - private List radixes; - private List digits; - private List permutation; + private List radixes; + private List digits; + private List permutation; } diff --git a/org.lflang/src/org/lflang/generator/ModeInstance.java b/org.lflang/src/org/lflang/generator/ModeInstance.java index 1f30f25802..932683f0ed 100644 --- a/org.lflang/src/org/lflang/generator/ModeInstance.java +++ b/org.lflang/src/org/lflang/generator/ModeInstance.java @@ -1,215 +1,206 @@ /************* -Copyright (c) 2021, Kiel University. - -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) 2021, Kiel University. + * + * 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 java.util.LinkedList; import java.util.List; - import org.lflang.lf.Mode; import org.lflang.lf.ModeTransition; import org.lflang.lf.VarRef; /** * Representation of a runtime instance of a mode. - * + * * @author Alexander Schulz-Rosengarten */ public class ModeInstance extends NamedInstance { - /** - * Create a new reaction instance from the specified definition - * within the specified parent. This constructor should be called - * only by the ReactorInstance class after all other contents - * (reactions, etc.) are registered because this constructor call - * will look them up. - * @param definition A mode definition. - * @param parent The parent reactor instance, which cannot be null. - */ - protected ModeInstance(Mode definition, ReactorInstance parent) { - super(definition, parent); - - collectMembers(); + /** + * Create a new reaction instance from the specified definition within the specified parent. This + * constructor should be called only by the ReactorInstance class after all other contents + * (reactions, etc.) are registered because this constructor call will look them up. + * + * @param definition A mode definition. + * @param parent The parent reactor instance, which cannot be null. + */ + protected ModeInstance(Mode definition, ReactorInstance parent) { + super(definition, parent); + + collectMembers(); + } + + //////////////////////////////////////////////////// + // Member fields. + + /** The action instances belonging to this mode instance. */ + public List actions = new LinkedList(); + + /** The reactor instances belonging to this mode instance, in order of declaration. */ + public List instantiations = new LinkedList(); + + /** List of reaction instances for this reactor instance. */ + public List reactions = new LinkedList(); + + /** The timer instances belonging to this reactor instance. */ + public List timers = new LinkedList(); + + /** The outgoing transitions of this mode. */ + public List transitions = new LinkedList(); + + //////////////////////////////////////////////////// + // Public methods. + + /** + * Return the name of this mode. + * + * @return The name of this mode. + */ + @Override + public String getName() { + return this.definition.getName(); + } + + /** {@inheritDoc} */ + @Override + public ReactorInstance root() { + return parent.root(); + } + + /** Return a descriptive string. */ + @Override + public String toString() { + return getName() + " of " + parent.getFullName(); + } + + /** Returns true iff this mode is the initial mode of this reactor instance. */ + public boolean isInitial() { + return definition.isInitial(); + } + + /** + * Sets up all transitions that leave this mode. Requires that all mode instances and other + * contents (reactions, etc.) of the parent reactor are created. + */ + public void setupTranstions() { + transitions.clear(); + for (var reaction : reactions) { + for (var effect : reaction.definition.getEffects()) { + if (effect instanceof VarRef) { + var target = effect.getVariable(); + if (target instanceof Mode) { + transitions.add( + new Transition(this, parent.lookupModeInstance((Mode) target), reaction, effect)); + } + } + } } - - //////////////////////////////////////////////////// - // Member fields. - - /** The action instances belonging to this mode instance. */ - public List actions = new LinkedList(); - - /** The reactor instances belonging to this mode instance, in order of declaration. */ - public List instantiations = new LinkedList(); - - /** List of reaction instances for this reactor instance. */ - public List reactions = new LinkedList(); - - /** The timer instances belonging to this reactor instance. */ - public List timers = new LinkedList(); - - /** The outgoing transitions of this mode. */ - public List transitions = new LinkedList(); - - //////////////////////////////////////////////////// - // Public methods. - - /** - * Return the name of this mode. - * @return The name of this mode. - */ - @Override - public String getName() { - return this.definition.getName(); + } + + /** Returns true iff this mode contains the given instance. */ + public boolean contains(NamedInstance instance) { + if (instance instanceof TimerInstance) { + return timers.contains(instance); + } else if (instance instanceof ActionInstance) { + return actions.contains(instance); + } else if (instance instanceof ReactorInstance) { + return instantiations.contains(instance); + } else if (instance instanceof ReactionInstance) { + return reactions.contains(instance); + } else { + return false; } - - /** - * {@inheritDoc} - */ - @Override - public ReactorInstance root() { - return parent.root(); + } + + //////////////////////////////////////////////////// + // Private methods. + + private void collectMembers() { + // Collect timers + for (var decl : definition.getTimers()) { + var instance = parent.lookupTimerInstance(decl); + if (instance != null) { + this.timers.add(instance); + } } - /** - * Return a descriptive string. - */ - @Override - public String toString() { - return getName() + " of " + parent.getFullName(); + // Collect actions + for (var decl : definition.getActions()) { + var instance = parent.lookupActionInstance(decl); + if (instance != null) { + this.actions.add(instance); + } } - - /** - * Returns true iff this mode is the initial mode of this reactor instance. - */ - public boolean isInitial() { - return definition.isInitial(); + + // Collect reactor instantiation + for (var decl : definition.getInstantiations()) { + var instance = parent.lookupReactorInstance(decl); + if (instance != null) { + this.instantiations.add(instance); + } } - - /** - * Sets up all transitions that leave this mode. - * Requires that all mode instances and other contents - * (reactions, etc.) of the parent reactor are created. - */ - public void setupTranstions() { - transitions.clear(); - for (var reaction : reactions) { - for (var effect : reaction.definition.getEffects()) { - if (effect instanceof VarRef) { - var target = effect.getVariable(); - if (target instanceof Mode) { - transitions.add(new Transition(this, parent.lookupModeInstance((Mode) target), reaction, effect)); - } - } - } - } + + // Collect reactions + for (var decl : definition.getReactions()) { + var instance = parent.lookupReactionInstance(decl); + if (instance != null) { + this.reactions.add(instance); + } } - - /** - * Returns true iff this mode contains the given instance. - */ - public boolean contains(NamedInstance instance) { - if (instance instanceof TimerInstance) { - return timers.contains(instance); - } else if (instance instanceof ActionInstance) { - return actions.contains(instance); - } else if (instance instanceof ReactorInstance) { - return instantiations.contains(instance); - } else if (instance instanceof ReactionInstance) { - return reactions.contains(instance); - } else { - return false; - } + } + + //////////////////////////////////////////////////// + // Data class. + + public static class Transition extends NamedInstance { + public final ModeInstance source; + public final ModeInstance target; + public final ReactionInstance reaction; + public final ModeTransition type; + + Transition( + ModeInstance source, ModeInstance target, ReactionInstance reaction, VarRef definition) { + super(definition, source.parent); + this.source = source; + this.target = target; + this.reaction = reaction; + this.type = + definition.getTransition() == null ? ModeTransition.RESET : definition.getTransition(); } - //////////////////////////////////////////////////// - // Private methods. - - private void collectMembers() { - // Collect timers - for (var decl : definition.getTimers()) { - var instance = parent.lookupTimerInstance(decl); - if (instance != null) { - this.timers.add(instance); - } - } - - // Collect actions - for (var decl : definition.getActions()) { - var instance = parent.lookupActionInstance(decl); - if (instance != null) { - this.actions.add(instance); - } - } - - // Collect reactor instantiation - for (var decl : definition.getInstantiations()) { - var instance = parent.lookupReactorInstance(decl); - if (instance != null) { - this.instantiations.add(instance); - } - } - - // Collect reactions - for (var decl : definition.getReactions()) { - var instance = parent.lookupReactionInstance(decl); - if (instance != null) { - this.reactions.add(instance); - } - } + @Override + public String getName() { + return this.source.getName() + " -> " + this.target + " by " + this.reaction.getName(); } - - //////////////////////////////////////////////////// - // Data class. - - public static class Transition extends NamedInstance { - public final ModeInstance source; - public final ModeInstance target; - public final ReactionInstance reaction; - public final ModeTransition type; - - Transition(ModeInstance source, ModeInstance target, ReactionInstance reaction, VarRef definition) { - super(definition, source.parent); - this.source = source; - this.target = target; - this.reaction = reaction; - this.type = definition.getTransition() == null ? ModeTransition.RESET : definition.getTransition(); - } - - @Override - public String getName() { - return this.source.getName() + " -> " + this.target + " by " + this.reaction.getName(); - } - - @Override - public ReactorInstance root() { - return this.parent.root(); - } - - public ModeTransition getType() { - return type; - } - + + @Override + public ReactorInstance root() { + return this.parent.root(); + } + + public ModeTransition getType() { + return type; } - + } } diff --git a/org.lflang/src/org/lflang/generator/NamedInstance.java b/org.lflang/src/org/lflang/generator/NamedInstance.java index 1d8c37321f..3455bcdf40 100644 --- a/org.lflang/src/org/lflang/generator/NamedInstance.java +++ b/org.lflang/src/org/lflang/generator/NamedInstance.java @@ -1,337 +1,319 @@ /* Base class for instances with names in Lingua Franca. */ /************* -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.generator; import java.util.ArrayList; import java.util.HashMap; import java.util.List; - import org.eclipse.emf.ecore.EObject; -/** - * Base class for compile-time instances with names in Lingua Franca. - * An instance of concrete subclasses of this class represents one or - * more runtime instances of a reactor, port, reaction, etc. There - * will be more than one runtime instance if the object or any of its - * parents is a bank of reactors. - * +/** + * Base class for compile-time instances with names in Lingua Franca. An instance of concrete + * subclasses of this class represents one or more runtime instances of a reactor, port, reaction, + * etc. There will be more than one runtime instance if the object or any of its parents is a bank + * of reactors. + * * @author Marten Lohstroh * @author Edward A. Lee */ public abstract class NamedInstance { - - /** - * Construct a new instance with the specified definition - * and parent. E.g., for a reactor instance, the definition - * is Instantiation, and for a port instance, it is Port. These are - * nodes in the AST. This is protected because only subclasses - * should be constructed. - * @param definition The definition in the AST for this instance. - * @param parent The reactor instance that creates this instance. - */ - protected NamedInstance(T definition, ReactorInstance parent) { - this.definition = definition; - this.parent = parent; - - // Calculate the depth. - this.depth = 0; - ReactorInstance p = parent; - while (p != null) { - p = p.parent; - this.depth++; - } - } - - ////////////////////////////////////////////////////// - //// Public fields. - - /** A limit on the number of characters returned by uniqueID. */ - public static int identifierLengthLimit = 40; - - ////////////////////////////////////////////////////// - //// Public methods. - - /** - * Return the definition, which is the AST node for this object. - */ - public T getDefinition() { - return definition; - } - - /** - * Get the depth of the reactor instance. This is 0 for the main reactor, - * 1 for reactors immediately contained therein, etc. - */ - public int getDepth() { - return depth; - } - - /** - * Return the full name of this instance, which has the form - * "a.b.c", where "c" is the name of this instance, "b" is the name - * of its container, and "a" is the name of its container, stopping - * at the container in main. If any reactor in the hierarchy is - * in a bank of reactors then, it will appear as a[index]. - * Similarly, if c is a port in a multiport, it will appear as - * c[index]. - * @return The full name of this instance. - */ - public String getFullName() { - return getFullNameWithJoiner("."); - } - - /** - * Return the name of this instance as given in its definition. - * Note that this is unique only relative to other instances with - * the same parent. - * @return The name of this instance within its parent. - */ - public abstract String getName(); - - /** - * Return the parent or null if this is a top-level reactor. - */ - public ReactorInstance getParent() { - return parent; - } - - /** - * Return the parent at the given depth or null if there is - * no parent at the given depth. - * @param d The depth. - */ - public ReactorInstance getParent(int d) { - if (d >= depth || d < 0) return null; - ReactorInstance p = parent; - while (p != null) { - if (p.depth == d) return p; - p = p.parent; - } - return null; + + /** + * Construct a new instance with the specified definition and parent. E.g., for a reactor + * instance, the definition is Instantiation, and for a port instance, it is Port. These are nodes + * in the AST. This is protected because only subclasses should be constructed. + * + * @param definition The definition in the AST for this instance. + * @param parent The reactor instance that creates this instance. + */ + protected NamedInstance(T definition, ReactorInstance parent) { + this.definition = definition; + this.parent = parent; + + // Calculate the depth. + this.depth = 0; + ReactorInstance p = parent; + while (p != null) { + p = p.parent; + this.depth++; } + } + + ////////////////////////////////////////////////////// + //// Public fields. + + /** A limit on the number of characters returned by uniqueID. */ + public static int identifierLengthLimit = 40; + + ////////////////////////////////////////////////////// + //// Public methods. + + /** Return the definition, which is the AST node for this object. */ + public T getDefinition() { + return definition; + } - /** - * Return the width of this instance, which in this base class is 1. - * Subclasses PortInstance and ReactorInstance change this to the - * multiport and bank widths respectively. - */ - public int getWidth() { - return width; + /** + * Get the depth of the reactor instance. This is 0 for the main reactor, 1 for reactors + * immediately contained therein, etc. + */ + public int getDepth() { + return depth; + } + + /** + * Return the full name of this instance, which has the form "a.b.c", where "c" is the name of + * this instance, "b" is the name of its container, and "a" is the name of its container, stopping + * at the container in main. If any reactor in the hierarchy is in a bank of reactors then, it + * will appear as a[index]. Similarly, if c is a port in a multiport, it will appear as c[index]. + * + * @return The full name of this instance. + */ + public String getFullName() { + return getFullNameWithJoiner("."); + } + + /** + * Return the name of this instance as given in its definition. Note that this is unique only + * relative to other instances with the same parent. + * + * @return The name of this instance within its parent. + */ + public abstract String getName(); + + /** Return the parent or null if this is a top-level reactor. */ + public ReactorInstance getParent() { + return parent; + } + + /** + * Return the parent at the given depth or null if there is no parent at the given depth. + * + * @param d The depth. + */ + public ReactorInstance getParent(int d) { + if (d >= depth || d < 0) return null; + ReactorInstance p = parent; + while (p != null) { + if (p.depth == d) return p; + p = p.parent; } - - /** - * Return true if this instance has the specified parent - * (possibly indirectly, anywhere up the hierarchy). - */ - public boolean hasParent(ReactorInstance container) { - - ReactorInstance p = parent; - - while (p != null) { - if (p == container) return true; - p = p.parent; - } - return false; + return null; + } + + /** + * Return the width of this instance, which in this base class is 1. Subclasses PortInstance and + * ReactorInstance change this to the multiport and bank widths respectively. + */ + public int getWidth() { + return width; + } + + /** + * Return true if this instance has the specified parent (possibly indirectly, anywhere up the + * hierarchy). + */ + public boolean hasParent(ReactorInstance container) { + + ReactorInstance p = parent; + + while (p != null) { + if (p == container) return true; + p = p.parent; } - - /** - * Return a list of all the parents starting with the root(). - */ - public List parents() { - List result = new ArrayList(depth + 1); - if (this instanceof ReactorInstance && parent == null) { - // This is the top level, so it must be a reactor. - result.add((ReactorInstance) this); - } - ReactorInstance container = parent; - while (container != null) { - result.add(container); - container = container.parent; - } - return result; + return false; + } + + /** Return a list of all the parents starting with the root(). */ + public List parents() { + List result = new ArrayList(depth + 1); + if (this instanceof ReactorInstance && parent == null) { + // This is the top level, so it must be a reactor. + result.add((ReactorInstance) this); } - - /** - * Return the root reactor, which is the top-level parent. - * @return The top-level parent. - */ - public ReactorInstance root() { - if (parent != null) { - return parent.root(); - } else { - return (ReactorInstance)this; - } + ReactorInstance container = parent; + while (container != null) { + result.add(container); + container = container.parent; } - - /** - * Set the width. This method is here for testing only and should - * not be used for any other purpose. - * @param width The new width. - */ - public void setWidth(int width) { - this.width = width; + return result; + } + + /** + * Return the root reactor, which is the top-level parent. + * + * @return The top-level parent. + */ + public ReactorInstance root() { + if (parent != null) { + return parent.root(); + } else { + return (ReactorInstance) this; } + } - /** - * Return an identifier for this instance, which has the form "a_b_c" - * or "a_b_c_n", where "c" is the name of this instance, "b" is the name - * of its container, and "a" is the name of its container, stopping - * at the container in main. All names are converted to lower case. - * The suffix _n is usually omitted, but it is possible to get name - * collisions using the above scheme, in which case _n will be an - * increasing integer until there is no collision. - * If the length of the root of the name as calculated above (the root - * is without the _n suffix) is longer than - * the static variable identifierLengthLimit, then the name will be - * truncated. The returned name will be the tail of the name calculated - * above with the prefix '_'. - * @return An identifier for this instance that is guaranteed to be - * unique within the top-level parent. - */ - public String uniqueID() { - if (_uniqueID == null) { - // Construct the unique ID only if it has not been - // previously constructed. - String prefix = getFullNameWithJoiner("_").toLowerCase(); - - // Replace all non-alphanumeric (Latin) characters with underscore. - prefix = prefix.replaceAll("[^A-Za-z0-9]", "_"); - - // Truncate, if necessary. - if (prefix.length() > identifierLengthLimit) { - prefix = '_' - + prefix.substring(prefix.length() - identifierLengthLimit + 1); - } - - // Ensure uniqueness. - ReactorInstance toplevel = root(); - if (toplevel.uniqueIDCount == null) { - toplevel.uniqueIDCount = new HashMap(); - } - var count = toplevel.uniqueIDCount.get(prefix); - if (count == null) { - toplevel.uniqueIDCount.put(prefix, 1); - _uniqueID = prefix; - } else { - toplevel.uniqueIDCount.put(prefix, count + 1); - // NOTE: The length of this name could exceed - // identifierLengthLimit. Is this OK? - _uniqueID = prefix + '_' + (count + 1); - } - } - return _uniqueID; + /** + * Set the width. This method is here for testing only and should not be used for any other + * purpose. + * + * @param width The new width. + */ + public void setWidth(int width) { + this.width = width; + } + + /** + * Return an identifier for this instance, which has the form "a_b_c" or "a_b_c_n", where "c" is + * the name of this instance, "b" is the name of its container, and "a" is the name of its + * container, stopping at the container in main. All names are converted to lower case. The suffix + * _n is usually omitted, but it is possible to get name collisions using the above scheme, in + * which case _n will be an increasing integer until there is no collision. If the length of the + * root of the name as calculated above (the root is without the _n suffix) is longer than the + * static variable identifierLengthLimit, then the name will be truncated. The returned name will + * be the tail of the name calculated above with the prefix '_'. + * + * @return An identifier for this instance that is guaranteed to be unique within the top-level + * parent. + */ + public String uniqueID() { + if (_uniqueID == null) { + // Construct the unique ID only if it has not been + // previously constructed. + String prefix = getFullNameWithJoiner("_").toLowerCase(); + + // Replace all non-alphanumeric (Latin) characters with underscore. + prefix = prefix.replaceAll("[^A-Za-z0-9]", "_"); + + // Truncate, if necessary. + if (prefix.length() > identifierLengthLimit) { + prefix = '_' + prefix.substring(prefix.length() - identifierLengthLimit + 1); + } + + // Ensure uniqueness. + ReactorInstance toplevel = root(); + if (toplevel.uniqueIDCount == null) { + toplevel.uniqueIDCount = new HashMap(); + } + var count = toplevel.uniqueIDCount.get(prefix); + if (count == null) { + toplevel.uniqueIDCount.put(prefix, 1); + _uniqueID = prefix; + } else { + toplevel.uniqueIDCount.put(prefix, count + 1); + // NOTE: The length of this name could exceed + // identifierLengthLimit. Is this OK? + _uniqueID = prefix + '_' + (count + 1); + } } - - /** - * Returns the directly/indirectly enclosing mode. - * @param direct flag whether to check only for direct enclosing mode - * or also consider modes of parent reactor instances. - * @return The mode, if any, null otherwise. - */ - public ModeInstance getMode(boolean direct) { - ModeInstance mode = null; - if (parent != null) { - if (!parent.modes.isEmpty()) { - mode = parent.modes.stream().filter(it -> it.contains(this)).findFirst().orElse(null); - } - if (mode == null && !direct) { - mode = parent.getMode(false); - } - } - return mode; + return _uniqueID; + } + + /** + * Returns the directly/indirectly enclosing mode. + * + * @param direct flag whether to check only for direct enclosing mode or also consider modes of + * parent reactor instances. + * @return The mode, if any, null otherwise. + */ + public ModeInstance getMode(boolean direct) { + ModeInstance mode = null; + if (parent != null) { + if (!parent.modes.isEmpty()) { + mode = parent.modes.stream().filter(it -> it.contains(this)).findFirst().orElse(null); + } + if (mode == null && !direct) { + mode = parent.getMode(false); + } } + return mode; + } - ////////////////////////////////////////////////////// - //// Protected fields. - - /** The Instantiation AST object from which this was created. */ - T definition; - - /** The reactor instance that creates this instance. */ - ReactorInstance parent; - - /** - * Map from a name of the form a_b_c to the number of - * unique IDs with that prefix that have been already - * assigned. If none have been assigned, then there is - * no entry in this map. This map should be non-null only - * for the main reactor (the top level). - */ - HashMap uniqueIDCount; - - /** - * The width of this instance. This is 1 for everything - * except a PortInstance representing a multiport and a - * ReactorInstance representing a bank. - */ - int width = 1; - - ////////////////////////////////////////////////////// - //// Protected methods. - - /** - * Return a string of the form - * "a.b.c", where "." is replaced by the specified joiner, - * "c" is the name of this instance, "b" is the name - * of its container, and "a" is the name of its container, stopping - * at the container in main. - * @return A string representing this instance. - */ - protected String getFullNameWithJoiner(String joiner) { - // This is not cached because _uniqueID is cached. - if (parent == null) { - return this.getName(); - } else if (getMode(true) != null) { - return parent.getFullNameWithJoiner(joiner) + joiner + getMode(true).getName() + joiner + this.getName(); - } else { - return parent.getFullNameWithJoiner(joiner) + joiner + this.getName(); - } + ////////////////////////////////////////////////////// + //// Protected fields. + + /** The Instantiation AST object from which this was created. */ + T definition; + + /** The reactor instance that creates this instance. */ + ReactorInstance parent; + + /** + * Map from a name of the form a_b_c to the number of unique IDs with that prefix that have been + * already assigned. If none have been assigned, then there is no entry in this map. This map + * should be non-null only for the main reactor (the top level). + */ + HashMap uniqueIDCount; + + /** + * The width of this instance. This is 1 for everything except a PortInstance representing a + * multiport and a ReactorInstance representing a bank. + */ + int width = 1; + + ////////////////////////////////////////////////////// + //// Protected methods. + + /** + * Return a string of the form "a.b.c", where "." is replaced by the specified joiner, "c" is the + * name of this instance, "b" is the name of its container, and "a" is the name of its container, + * stopping at the container in main. + * + * @return A string representing this instance. + */ + protected String getFullNameWithJoiner(String joiner) { + // This is not cached because _uniqueID is cached. + if (parent == null) { + return this.getName(); + } else if (getMode(true) != null) { + return parent.getFullNameWithJoiner(joiner) + + joiner + + getMode(true).getName() + + joiner + + this.getName(); + } else { + return parent.getFullNameWithJoiner(joiner) + joiner + this.getName(); } + } + + ////////////////////////////////////////////////////// + //// Protected fields. + + /** + * The depth in the hierarchy of this instance. This is 0 for main or federated, 1 for the + * reactors immediately contained, etc. + */ + protected int depth = 0; + + ////////////////////////////////////////////////////// + //// Private fields. + + /** The full name of this instance. */ + private String _fullName = null; - ////////////////////////////////////////////////////// - //// Protected fields. - - /** - * The depth in the hierarchy of this instance. - * This is 0 for main or federated, 1 for the reactors immediately contained, etc. - */ - protected int depth = 0; - - ////////////////////////////////////////////////////// - //// Private fields. - - /** - * The full name of this instance. - */ - private String _fullName = null; - - /** - * Unique ID for this instance. This is null until - * uniqueID() is called. - */ - private String _uniqueID = null; -} \ No newline at end of file + /** Unique ID for this instance. This is null until uniqueID() is called. */ + private String _uniqueID = null; +} diff --git a/org.lflang/src/org/lflang/generator/ParameterInstance.java b/org.lflang/src/org/lflang/generator/ParameterInstance.java index 851fcb627e..92a1b03ed4 100644 --- a/org.lflang/src/org/lflang/generator/ParameterInstance.java +++ b/org.lflang/src/org/lflang/generator/ParameterInstance.java @@ -1,121 +1,118 @@ /** A data structure for a parameter instance. */ /************* -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.generator; import java.util.List; import java.util.Optional; - -import org.lflang.ast.ASTUtils; import org.lflang.InferredType; +import org.lflang.ast.ASTUtils; import org.lflang.lf.Assignment; import org.lflang.lf.Initializer; import org.lflang.lf.Parameter; /** - * Representation of a compile-time instance of a parameter. - * Upon creation, it is checked whether this parameter is overridden by an - * assignment in the instantiation that this parameter instance is a result of. - * If it is overridden, the parameter gets initialized using the value looked up - * in the instantiation hierarchy. + * Representation of a compile-time instance of a parameter. Upon creation, it is checked whether + * this parameter is overridden by an assignment in the instantiation that this parameter instance + * is a result of. If it is overridden, the parameter gets initialized using the value looked up in + * the instantiation hierarchy. + * * @author Marten Lohstroh * @author Edward A. Lee */ public class ParameterInstance extends NamedInstance { - /** - * Create a runtime instance from the specified definition - * and with the specified parent that instantiated it. - * @param definition The declaration in the AST. - * @param parent The reactor instance this parameter is a part of. - */ - public ParameterInstance(Parameter definition, ReactorInstance parent) { - super(definition, parent); - if (parent == null) { - throw new InvalidSourceException("Cannot create a ParameterInstance with no parent."); - } - - this.type = ASTUtils.getInferredType(definition); - } - - ///////////////////////////////////////////// - //// Public Fields - - public InferredType type; - - ///////////////////////////////////////////// - //// Public Methods - - /** - * Get the initial value of this parameter. - */ - 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; - } - - /** - * Return the name of this parameter. - * @return The name of this parameter. - */ - public String getName() { - return this.definition.getName(); - } - - /** - * Return the assignment that overrides this parameter in - * the parent's instantiation or null if there is no override. - */ - public Assignment getOverride() { - List assignments = parent.definition.getParameters(); - Optional assignment = assignments.stream().filter( - it -> it.getLhs() == definition - ).findFirst(); - return assignment.orElse(null); + /** + * Create a runtime instance from the specified definition and with the specified parent that + * instantiated it. + * + * @param definition The declaration in the AST. + * @param parent The reactor instance this parameter is a part of. + */ + public ParameterInstance(Parameter definition, ReactorInstance parent) { + super(definition, parent); + if (parent == null) { + throw new InvalidSourceException("Cannot create a ParameterInstance with no parent."); } - /** Return a descriptive string. */ - @Override - public String toString() { - return "ParameterInstance " + getFullName(); + this.type = ASTUtils.getInferredType(definition); + } + + ///////////////////////////////////////////// + //// Public Fields + + public InferredType type; + + ///////////////////////////////////////////// + //// Public Methods + + /** Get the initial value of this parameter. */ + 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; + } + + /** + * Return the name of this parameter. + * + * @return The name of this parameter. + */ + public String getName() { + return this.definition.getName(); + } + + /** + * Return the assignment that overrides this parameter in the parent's instantiation or null if + * there is no override. + */ + public Assignment getOverride() { + List assignments = parent.definition.getParameters(); + Optional assignment = + assignments.stream().filter(it -> it.getLhs() == definition).findFirst(); + return assignment.orElse(null); + } + + /** Return a descriptive string. */ + @Override + public String toString() { + return "ParameterInstance " + getFullName(); + } } diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index 57dbdcf73d..60a904ef5b 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -1,35 +1,34 @@ /** A data structure for a port 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 java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.PriorityQueue; - import org.lflang.ErrorReporter; import org.lflang.lf.Input; import org.lflang.lf.Output; @@ -38,422 +37,404 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.WidthSpec; import org.lflang.lf.WidthTerm; -/** - * Representation of a compile-time instance of a port. - * Like {@link ReactorInstance}, if one or more parents of this port - * is a bank of reactors, then there will be more than one runtime instance +/** + * Representation of a compile-time instance of a port. Like {@link ReactorInstance}, if one or more + * parents of this port is a bank of reactors, then there will be more than one runtime instance * corresponding to this compile-time instance. - * - * This may be a single port or a multiport. If it is a multiport, then - * one instance of this PortInstance class represents all channels. - * If in addition any parent is a bank, then it represents all channels of all - * bank members. The {@link #eventualDestinations()} and {@link #eventualSources()} - * functions report the connectivity of all such channels as lists of - * {@link SendRange} and {@link RuntimeRange} objects. - * + * + *

This may be a single port or a multiport. If it is a multiport, then one instance of this + * PortInstance class represents all channels. If in addition any parent is a bank, then it + * represents all channels of all bank members. The {@link #eventualDestinations()} and {@link + * #eventualSources()} functions report the connectivity of all such channels as lists of {@link + * SendRange} and {@link RuntimeRange} objects. + * * @author Marten Lohstroh * @author Edward A. Lee */ public class PortInstance extends TriggerInstance { - /** - * Create a runtime instance from the specified definition - * and with the specified parent that instantiated it. - * @param definition The declaration in the AST. - * @param parent The parent. - */ - public PortInstance(Port definition, ReactorInstance parent) { - this(definition, parent, null); - } + /** + * Create a runtime instance from the specified definition and with the specified parent that + * instantiated it. + * + * @param definition The declaration in the AST. + * @param parent The parent. + */ + public PortInstance(Port definition, ReactorInstance parent) { + this(definition, parent, null); + } - /** - * Create a port instance from the specified definition - * and with the specified parent that instantiated it. - * @param definition The declaration in the AST. - * @param parent The parent. - * @param errorReporter An error reporter, or null to throw exceptions. - */ - public PortInstance(Port definition, ReactorInstance parent, ErrorReporter errorReporter) { - super(definition, parent); - - if (parent == null) { - throw new NullPointerException("Cannot create a PortInstance with no parent."); - } - - setInitialWidth(errorReporter); - } + /** + * Create a port instance from the specified definition and with the specified parent that + * instantiated it. + * + * @param definition The declaration in the AST. + * @param parent The parent. + * @param errorReporter An error reporter, or null to throw exceptions. + */ + public PortInstance(Port definition, ReactorInstance parent, ErrorReporter errorReporter) { + super(definition, parent); - ////////////////////////////////////////////////////// - //// Public methods - - /** - * Clear cached information about the connectivity of this port. - * In particular, {@link #eventualDestinations()} and {@link #eventualSources()} - * cache the lists they return. To force those methods to recompute - * their lists, call this method. This method also clears the caches - * of any ports that are listed as eventual destinations and sources. - */ - public void clearCaches() { - if (clearingCaches) return; // Prevent stack overflow. - clearingCaches = true; - try { - if (eventualSourceRanges != null) { - for (RuntimeRange sourceRange : eventualSourceRanges) { - sourceRange.instance.clearCaches(); - } - } - if (eventualDestinationRanges != null) { - for (SendRange sendRange : eventualDestinationRanges) { - for (RuntimeRange destinationRange : sendRange.destinations) { - destinationRange.instance.clearCaches(); - } - } - } - eventualDestinationRanges = null; - eventualSourceRanges = null; - } finally { - clearingCaches = false; - } + if (parent == null) { + throw new NullPointerException("Cannot create a PortInstance with no parent."); } - /** - * Return a list of ranges of this port, where each range sends - * to a list of destination ports that receive data from the range of - * this port. Each destination port is annotated with the channel - * range on which it receives data. - * The ports listed are only ports that are sources for reactions, - * not relay ports that the data may go through on the way. - * Also, if there is an "after" delay anywhere along the path, - * then the destination is not in the resulting list. - * - * If this port itself has dependent reactions, - * then this port will be included as a destination in all items - * on the returned list. - * - * Each item in the returned list has the following fields: - *

    - *
  • {@code startRange}: The starting channel index of this port.
  • - *
  • {@code rangeWidth}: The number of channels sent to the destinations.
  • - *
  • {@code destinations}: A list of port ranges for destination ports, each - * of which has the same width as {@code rangeWidth}.
  • - *
- * - * Each item also has a method, {@link SendRange#getNumberOfDestinationReactors()}, - * that returns the total number of unique destination reactors for - * its range. This is not necessarily the same as the number - * of ports in its destinations field because some of the ports may - * share the same container reactor. - */ - public List eventualDestinations() { - if (eventualDestinationRanges != null) { - return eventualDestinationRanges; + setInitialWidth(errorReporter); + } + + ////////////////////////////////////////////////////// + //// Public methods + + /** + * Clear cached information about the connectivity of this port. In particular, {@link + * #eventualDestinations()} and {@link #eventualSources()} cache the lists they return. To force + * those methods to recompute their lists, call this method. This method also clears the caches of + * any ports that are listed as eventual destinations and sources. + */ + public void clearCaches() { + if (clearingCaches) return; // Prevent stack overflow. + clearingCaches = true; + try { + if (eventualSourceRanges != null) { + for (RuntimeRange sourceRange : eventualSourceRanges) { + sourceRange.instance.clearCaches(); } - - // Construct the full range for this port. - RuntimeRange range = new RuntimeRange.Port(this); - eventualDestinationRanges = eventualDestinations(range); - return eventualDestinationRanges; - } - - /** - * Return a list of ranges of ports that send data to this port. - * If this port is directly written to by one more more reactions, - * then it is its own eventual source and only this port - * will be represented in the result. - * - * If this is not a multiport and is not within a bank, then the list will have - * only one item and the range will have a total width of one. Otherwise, it will - * have enough items so that the range widths add up to the width of this - * multiport multiplied by the total number of instances within containing banks. - * - * The ports listed are only ports that are written to by reactions, - * not relay ports that the data may go through on the way. - */ - public List> eventualSources() { - return eventualSources(new RuntimeRange.Port(this)); + } + if (eventualDestinationRanges != null) { + for (SendRange sendRange : eventualDestinationRanges) { + for (RuntimeRange destinationRange : sendRange.destinations) { + destinationRange.instance.clearCaches(); + } + } + } + eventualDestinationRanges = null; + eventualSourceRanges = null; + } finally { + clearingCaches = false; } + } - /** - * Return the list of ranges of this port together with the - * downstream ports that are connected to this port. - * The total with of the ranges in the returned list is a - * multiple N >= 0 of the total width of this port. - */ - public List getDependentPorts() { - return dependentPorts; + /** + * Return a list of ranges of this port, where each range sends to a list of destination ports + * that receive data from the range of this port. Each destination port is annotated with the + * channel range on which it receives data. The ports listed are only ports that are sources for + * reactions, not relay ports that the data may go through on the way. Also, if there is an + * "after" delay anywhere along the path, then the destination is not in the resulting list. + * + *

If this port itself has dependent reactions, then this port will be included as a + * destination in all items on the returned list. + * + *

Each item in the returned list has the following fields: + * + *

    + *
  • {@code startRange}: The starting channel index of this port. + *
  • {@code rangeWidth}: The number of channels sent to the destinations. + *
  • {@code destinations}: A list of port ranges for destination ports, each of which has the + * same width as {@code rangeWidth}. + *
+ * + * Each item also has a method, {@link SendRange#getNumberOfDestinationReactors()}, that returns + * the total number of unique destination reactors for its range. This is not necessarily the same + * as the number of ports in its destinations field because some of the ports may share the same + * container reactor. + */ + public List eventualDestinations() { + if (eventualDestinationRanges != null) { + return eventualDestinationRanges; } - /** - * Return the list of upstream ports that are connected to this port, - * or an empty set if there are none. - * For an ordinary port, this list will have length 0 or 1. - * For a multiport, it can have a larger size. - */ - public List> getDependsOnPorts() { - return dependsOnPorts; - } - - /** - * Return true if the port is an input. - */ - public boolean isInput() { - return (definition instanceof Input); - } - - /** - * Return true if this is a multiport. - */ - public boolean isMultiport() { - return isMultiport; - } + // Construct the full range for this port. + RuntimeRange range = new RuntimeRange.Port(this); + eventualDestinationRanges = eventualDestinations(range); + return eventualDestinationRanges; + } + + /** + * Return a list of ranges of ports that send data to this port. If this port is directly written + * to by one more more reactions, then it is its own eventual source and only this port will be + * represented in the result. + * + *

If this is not a multiport and is not within a bank, then the list will have only one item + * and the range will have a total width of one. Otherwise, it will have enough items so that the + * range widths add up to the width of this multiport multiplied by the total number of instances + * within containing banks. + * + *

The ports listed are only ports that are written to by reactions, not relay ports that the + * data may go through on the way. + */ + public List> eventualSources() { + return eventualSources(new RuntimeRange.Port(this)); + } + + /** + * Return the list of ranges of this port together with the downstream ports that are connected to + * this port. The total with of the ranges in the returned list is a multiple N >= 0 of the total + * width of this port. + */ + public List getDependentPorts() { + return dependentPorts; + } + + /** + * Return the list of upstream ports that are connected to this port, or an empty set if there are + * none. For an ordinary port, this list will have length 0 or 1. For a multiport, it can have a + * larger size. + */ + public List> getDependsOnPorts() { + return dependsOnPorts; + } + + /** Return true if the port is an input. */ + public boolean isInput() { + return (definition instanceof Input); + } + + /** Return true if this is a multiport. */ + public boolean isMultiport() { + return isMultiport; + } + + /** Return true if the port is an output. */ + public boolean isOutput() { + return (definition instanceof Output); + } + + @Override + public String toString() { + return "PortInstance " + getFullName(); + } + + ////////////////////////////////////////////////////// + //// Protected fields. + + /** + * Ranges of this port together with downstream ports that are connected directly to this port. + * When there are multiple destinations, the destinations are listed in the order they appear in + * connections in the parent reactor instance of this port (inside connections), followed by the + * order in which they appear in the parent's parent (outside connections). The total of the + * widths of these SendRanges is an integer multiple N >= 0 of the width of this port (this is + * checked by the validator). Each channel of this port will be broadcast to N recipients (or, if + * there are no connections to zero recipients). + */ + List dependentPorts = new ArrayList(); + + /** + * Upstream ports that are connected directly to this port, if there are any. For an ordinary + * port, this set will have size 0 or 1. For a multiport, it can have a larger size. This + * initially has capacity 1 because that is by far the most common case. + */ + List> dependsOnPorts = new ArrayList>(1); + + /** Indicator of whether this is a multiport. */ + boolean isMultiport = false; - /** - * Return true if the port is an output. - */ - public boolean isOutput() { - return (definition instanceof Output); + ////////////////////////////////////////////////////// + //// Private methods. + + /** + * Given a RuntimeRange, return a list of SendRange that describes the eventual destinations of + * the given range. The sum of the total widths of the send ranges on the returned list will be an + * integer multiple N of the total width of the specified range. Each returned SendRange has a + * list of destination RuntimeRanges, each of which represents a port that has dependent + * reactions. Intermediate ports with no dependent reactions are not listed. + * + * @param srcRange The source range. + */ + private static List eventualDestinations(RuntimeRange srcRange) { + + // Getting the destinations is more complex than getting the sources + // because of multicast, where there is more than one connection statement + // for a source of data. The strategy we follow here is to first get all + // the ports that this port eventually sends to. Then, if needed, split + // the resulting ranges so that the resulting list covers exactly + // srcRange, possibly in pieces. We make two passes. First, we build + // a queue of ranges that may overlap, then we split those ranges + // and consolidate their destinations. + + List result = new ArrayList(); + PriorityQueue queue = new PriorityQueue(); + PortInstance srcPort = srcRange.instance; + + // Start with, if this port has dependent reactions, then add it to + // every range of the result. + if (!srcRange.instance.dependentReactions.isEmpty()) { + // This will be the final result if there are no connections. + SendRange candidate = + new SendRange( + srcRange.instance, + srcRange.start, + srcRange.width, + null, // No interleaving for this range. + null // No connection for this range. + ); + candidate.destinations.add(srcRange); + queue.add(candidate); } - - @Override - public String toString() { - return "PortInstance " + getFullName(); - } - - ////////////////////////////////////////////////////// - //// Protected fields. - - /** - * Ranges of this port together with downstream ports that - * are connected directly to this port. When there are multiple destinations, - * the destinations are listed in the order they appear in connections - * in the parent reactor instance of this port (inside connections), - * followed by the order in which they appear in the parent's parent (outside - * connections). The total of the widths of these SendRanges is an integer - * multiple N >= 0 of the width of this port (this is checked - * by the validator). Each channel of this port will be broadcast - * to N recipients (or, if there are no connections to zero recipients). - */ - List dependentPorts = new ArrayList(); - - /** - * Upstream ports that are connected directly to this port, if there are any. - * For an ordinary port, this set will have size 0 or 1. - * For a multiport, it can have a larger size. - * This initially has capacity 1 because that is by far the most common case. - */ - List> dependsOnPorts = new ArrayList>(1); - - /** Indicator of whether this is a multiport. */ - boolean isMultiport = false; - - ////////////////////////////////////////////////////// - //// Private methods. - - /** - * Given a RuntimeRange, return a list of SendRange that describes - * the eventual destinations of the given range. - * The sum of the total widths of the send ranges on the returned list - * will be an integer multiple N of the total width of the specified range. - * Each returned SendRange has a list - * of destination RuntimeRanges, each of which represents a port that - * has dependent reactions. Intermediate ports with no dependent - * reactions are not listed. - * @param srcRange The source range. - */ - private static List eventualDestinations(RuntimeRange srcRange) { - - // Getting the destinations is more complex than getting the sources - // because of multicast, where there is more than one connection statement - // for a source of data. The strategy we follow here is to first get all - // the ports that this port eventually sends to. Then, if needed, split - // the resulting ranges so that the resulting list covers exactly - // srcRange, possibly in pieces. We make two passes. First, we build - // a queue of ranges that may overlap, then we split those ranges - // and consolidate their destinations. - - List result = new ArrayList(); - PriorityQueue queue = new PriorityQueue(); - PortInstance srcPort = srcRange.instance; - - // Start with, if this port has dependent reactions, then add it to - // every range of the result. - if (!srcRange.instance.dependentReactions.isEmpty()) { - // This will be the final result if there are no connections. - SendRange candidate = new SendRange( - srcRange.instance, - srcRange.start, - srcRange.width, - null, // No interleaving for this range. - null // No connection for this range. - ); - candidate.destinations.add(srcRange); - queue.add(candidate); - } - // Need to find send ranges that overlap with this srcRange. - Iterator sendRanges = srcPort.dependentPorts.iterator(); - while (sendRanges.hasNext()) { - - SendRange wSendRange = sendRanges.next(); - - if (wSendRange.connection != null && wSendRange.connection.getDelay() != null) { - continue; - } - - wSendRange = wSendRange.overlap(srcRange); - if (wSendRange == null) { - // This send range does not overlap with the desired range. Try the next one. - continue; - } - for (RuntimeRange dstRange : wSendRange.destinations) { - // Recursively get the send ranges of that destination port. - List dstSendRanges = eventualDestinations(dstRange); - int sendRangeStart = 0; - for (SendRange dstSend : dstSendRanges) { - queue.add(dstSend.newSendRange(wSendRange, sendRangeStart)); - sendRangeStart += dstSend.width; - } - } + // Need to find send ranges that overlap with this srcRange. + Iterator sendRanges = srcPort.dependentPorts.iterator(); + while (sendRanges.hasNext()) { + + SendRange wSendRange = sendRanges.next(); + + if (wSendRange.connection != null && wSendRange.connection.getDelay() != null) { + continue; + } + + wSendRange = wSendRange.overlap(srcRange); + if (wSendRange == null) { + // This send range does not overlap with the desired range. Try the next one. + continue; + } + for (RuntimeRange dstRange : wSendRange.destinations) { + // Recursively get the send ranges of that destination port. + List dstSendRanges = eventualDestinations(dstRange); + int sendRangeStart = 0; + for (SendRange dstSend : dstSendRanges) { + queue.add(dstSend.newSendRange(wSendRange, sendRangeStart)); + sendRangeStart += dstSend.width; } + } + } - // Now check for overlapping ranges, constructing a new result. - SendRange candidate = queue.poll(); - SendRange next = queue.poll(); - while (candidate != null) { - if (next == null) { - // No more candidates. We are done. - result.add(candidate); - break; - } - if (candidate.start == next.start) { - // Ranges have the same starting point. Need to merge them. - if (candidate.width <= next.width) { - // Can use all of the channels of candidate. - // Import the destinations of next and split it. - for (RuntimeRange destination : next.destinations) { - candidate.destinations.add(destination.head(candidate.width)); - } - if (candidate.width < next.width) { - // The next range has more channels connected to this sender. - // Put it back on the queue an poll for a new next. - queue.add(next.tail(candidate.width)); - next = queue.poll(); - } else { - // We are done with next and can discard it. - next = queue.poll(); - } - } else { - // candidate is wider than next. Switch them and continue. - SendRange temp = candidate; - candidate = next; - next = temp; - } - } else { - // Because the result list is sorted, next starts at - // a higher channel than candidate. - if (candidate.start + candidate.width <= next.start) { - // Can use candidate as is and make next the new candidate. - result.add(candidate); - candidate = next; - next = queue.poll(); - } else { - // Ranges overlap. Can use a truncated candidate and make its - // truncated version the new candidate. - result.add(candidate.head(next.start)); - candidate = (SendRange)candidate.tail(next.start); - } - } + // Now check for overlapping ranges, constructing a new result. + SendRange candidate = queue.poll(); + SendRange next = queue.poll(); + while (candidate != null) { + if (next == null) { + // No more candidates. We are done. + result.add(candidate); + break; + } + if (candidate.start == next.start) { + // Ranges have the same starting point. Need to merge them. + if (candidate.width <= next.width) { + // Can use all of the channels of candidate. + // Import the destinations of next and split it. + for (RuntimeRange destination : next.destinations) { + candidate.destinations.add(destination.head(candidate.width)); + } + if (candidate.width < next.width) { + // The next range has more channels connected to this sender. + // Put it back on the queue an poll for a new next. + queue.add(next.tail(candidate.width)); + next = queue.poll(); + } else { + // We are done with next and can discard it. + next = queue.poll(); + } + } else { + // candidate is wider than next. Switch them and continue. + SendRange temp = candidate; + candidate = next; + next = temp; + } + } else { + // Because the result list is sorted, next starts at + // a higher channel than candidate. + if (candidate.start + candidate.width <= next.start) { + // Can use candidate as is and make next the new candidate. + result.add(candidate); + candidate = next; + next = queue.poll(); + } else { + // Ranges overlap. Can use a truncated candidate and make its + // truncated version the new candidate. + result.add(candidate.head(next.start)); + candidate = (SendRange) candidate.tail(next.start); } - - return result; + } } - /** - * Return a list of ranges of ports that send data to this port within the - * specified range. If this port is directly written to by one more more reactions, - * then it is its own eventual source and only this port - * will be represented in the result. - * - * If this is not a multiport and is not within a bank, then the list will have - * only one item and the range will have a total width of one. Otherwise, it will - * have enough items so that the range widths add up to the width of this - * multiport multiplied by the total number of instances within containing banks. - * - * The ports listed are only ports that are written to by reactions, - * not relay ports that the data may go through on the way. - */ - private List> eventualSources(RuntimeRange range) { - if (eventualSourceRanges == null) { - // Cached result has not been created. - eventualSourceRanges = new ArrayList>(); - - if (!dependsOnReactions.isEmpty()) { - eventualSourceRanges.add(new RuntimeRange.Port(this)); - } else { - var channelsCovered = 0; - for (RuntimeRange sourceRange : dependsOnPorts) { - // Check whether the sourceRange overlaps with the range. - if (channelsCovered + sourceRange.width >= range.start - && channelsCovered < range.start + range.width) { - eventualSourceRanges.addAll(sourceRange.instance.eventualSources(sourceRange)); - } - channelsCovered += sourceRange.width; - } - } + return result; + } + + /** + * Return a list of ranges of ports that send data to this port within the specified range. If + * this port is directly written to by one more more reactions, then it is its own eventual source + * and only this port will be represented in the result. + * + *

If this is not a multiport and is not within a bank, then the list will have only one item + * and the range will have a total width of one. Otherwise, it will have enough items so that the + * range widths add up to the width of this multiport multiplied by the total number of instances + * within containing banks. + * + *

The ports listed are only ports that are written to by reactions, not relay ports that the + * data may go through on the way. + */ + private List> eventualSources(RuntimeRange range) { + if (eventualSourceRanges == null) { + // Cached result has not been created. + eventualSourceRanges = new ArrayList>(); + + if (!dependsOnReactions.isEmpty()) { + eventualSourceRanges.add(new RuntimeRange.Port(this)); + } else { + var channelsCovered = 0; + for (RuntimeRange sourceRange : dependsOnPorts) { + // Check whether the sourceRange overlaps with the range. + if (channelsCovered + sourceRange.width >= range.start + && channelsCovered < range.start + range.width) { + eventualSourceRanges.addAll(sourceRange.instance.eventualSources(sourceRange)); + } + channelsCovered += sourceRange.width; } - return eventualSourceRanges; + } } + return eventualSourceRanges; + } + + /** + * Set the initial multiport width, if this is a multiport, from the widthSpec in the definition. + * This will be set to -1 if the width cannot be determined. + * + * @param errorReporter For reporting errors. + */ + private void setInitialWidth(ErrorReporter errorReporter) { + // If this is a multiport, determine the width. + WidthSpec widthSpec = definition.getWidthSpec(); + + if (widthSpec != null) { + if (widthSpec.isOfVariableLength()) { + errorReporter.reportError( + definition, "Variable-width multiports not supported (yet): " + definition.getName()); + } else { + isMultiport = true; - /** - * Set the initial multiport width, if this is a multiport, from the widthSpec - * in the definition. This will be set to -1 if the width cannot be determined. - * @param errorReporter For reporting errors. - */ - private void setInitialWidth(ErrorReporter errorReporter) { - // If this is a multiport, determine the width. - WidthSpec widthSpec = definition.getWidthSpec(); - - if (widthSpec != null) { - if (widthSpec.isOfVariableLength()) { - errorReporter.reportError(definition, - "Variable-width multiports not supported (yet): " + definition.getName()); + // Determine the initial width, if possible. + // The width may be given by a parameter or even sum of parameters. + width = 0; + for (WidthTerm term : widthSpec.getTerms()) { + Parameter parameter = term.getParameter(); + if (parameter != null) { + Integer parameterValue = parent.initialIntParameterValue(parameter); + // Only a Literal is supported. + if (parameterValue != null) { + width += parameterValue; } else { - isMultiport = true; - - // Determine the initial width, if possible. - // The width may be given by a parameter or even sum of parameters. - width = 0; - for (WidthTerm term : widthSpec.getTerms()) { - Parameter parameter = term.getParameter(); - if (parameter != null) { - Integer parameterValue = parent.initialIntParameterValue(parameter); - // Only a Literal is supported. - if (parameterValue != null) { - width += parameterValue; - } else { - width = -1; - return; - } - } else if (term.getWidth() != 0){ - width += term.getWidth(); - } else { - width = -1; - return; - } - } + width = -1; + return; } + } else if (term.getWidth() != 0) { + width += term.getWidth(); + } else { + width = -1; + return; + } } + } } + } + + ////////////////////////////////////////////////////// + //// Private fields. + + /** Cached list of destination ports with channel ranges. */ + private List eventualDestinationRanges; + + /** Cached list of source ports with channel ranges. */ + private List> eventualSourceRanges; - ////////////////////////////////////////////////////// - //// Private fields. - - /** Cached list of destination ports with channel ranges. */ - private List eventualDestinationRanges; - - /** Cached list of source ports with channel ranges. */ - private List> eventualSourceRanges; - - /** Indicator that we are clearing the caches. */ - private boolean clearingCaches = false; + /** Indicator that we are clearing the caches. */ + private boolean clearingCaches = false; } diff --git a/org.lflang/src/org/lflang/generator/Position.java b/org.lflang/src/org/lflang/generator/Position.java index 872b09a24e..80e3976365 100644 --- a/org.lflang/src/org/lflang/generator/Position.java +++ b/org.lflang/src/org/lflang/generator/Position.java @@ -4,234 +4,219 @@ import java.util.regex.Pattern; /** - * A position in a document, including line and - * column. This position may be relative to some + * A position in a document, including line and column. This position may be relative to some * position other than the origin. * * @author Peter Donovan */ public class Position implements Comparable { - public static final Pattern PATTERN = Pattern.compile("\\((?[0-9]+), (?[0-9]+)\\)"); - - public static final Position ORIGIN = Position.fromZeroBased(0, 0); - - private static final Pattern LINE_SEPARATOR = Pattern.compile("(\n)|(\r)|(\r\n)"); - - private final int line; - private final int column; - - /* ------------------------ CONSTRUCTORS -------------------------- */ - - /** - * Return the Position that describes the given - * zero-based line and column numbers. - * @param line the zero-based line number - * @param column the zero-based column number - * @return a Position describing the position described - * by {@code line} and {@code column}. - */ - public static Position fromZeroBased(int line, int column) { - return new Position(line, column); - } - - /** - * Return the Position that describes the given - * one-based line and column numbers. - * @param line the one-based line number - * @param column the one-based column number - * @return a Position describing the position described - * by {@code line} and {@code column}. - */ - public static Position fromOneBased(int line, int column) { - return new Position(line - 1, column - 1); - } - - /** - * Return the Position that equals the displacement - * caused by {@code text}, assuming that {@code text} - * starts in column 0. - * @param text an arbitrary string - * @return the Position that equals the displacement - * caused by {@code text} - */ - public static Position displacementOf(String text) { - String[] lines = text.lines().toArray(String[]::new); - if (lines.length == 0) return ORIGIN; - return Position.fromZeroBased(lines.length - 1, lines[lines.length - 1].length()); - } - - /** - * Return the Position that describes the same location - * in {@code content} as {@code offset}. - * @param offset a location, expressed as an offset from - * the beginning of {@code content} - * @param content the content of a document - * @return the Position that describes the same location - * in {@code content} as {@code offset} - */ - public static Position fromOffset(int offset, String content) { - int lineNumber = 0; - Matcher matcher = LINE_SEPARATOR.matcher(content); - int start = 0; - while (matcher.find(start)) { - if (matcher.start() > offset) return Position.fromZeroBased(lineNumber, offset - start); - start = matcher.end(); - lineNumber++; - } - return Position.fromZeroBased(lineNumber, offset); - } - - /** - * Create a new Position with the given line and column - * numbers. - * @param line the zero-based line number - * @param column the zero-based column number - */ - private Position(int line, int column) { - // Assertions about whether line and column are - // non-negative are deliberately omitted. Positions - // can be relative. - this.line = line; - this.column = column; - } - - /* ----------------------- PUBLIC METHODS ------------------------- */ - - /** - * Return the one-based line number described by this - * {@code Position}. - * @return the one-based line number described by this - * {@code Position} - */ - public int getOneBasedLine() { - return line + 1; - } - - /** - * Return the one-based column number described by this - * {@code Position}. - * @return the one-based column number described by this - * {@code Position} - */ - public int getOneBasedColumn() { - return column + 1; - } - - /** - * Return the zero-based line number described by this - * {@code Position}. - * @return the zero-based line number described by this - * {@code Position} - */ - public int getZeroBasedLine() { - return line; - } - - /** - * Return the zero-based column number described by this - * {@code Position}. - * @return the zero-based column number described by this - * {@code Position} - */ - public int getZeroBasedColumn() { - return column; - } - - /** - * Return the Position that equals the displacement of - * ((text whose displacement equals {@code this}) - * concatenated with {@code text}). Note that this is - * not necessarily equal to - * ({@code this} + displacementOf(text)). - * @param text an arbitrary string - * @return the Position that equals the displacement - * caused by {@code text} - */ - public Position plus(String text) { - text += "\n"; // Turn line separators into line terminators. - String[] lines = text.lines().toArray(String[]::new); - if (lines.length == 0) return this; // OK not to copy because Positions are immutable - int lastLineLength = lines[lines.length - 1].length(); - return new Position(line + lines.length - 1, lines.length > 1 ? lastLineLength : column + lastLineLength); - } - - /** - * Return the sum of this and another {@code Position}. - * The result has meaning because Positions are - * relative. - * @param other another {@code Position} - * @return the sum of this and {@code other} - */ - public Position plus(Position other) { - return new Position(line + other.line, column + other.column); - } - - /** - * Return the difference of this and another {@code - * Position}. The result has meaning because - * Positions are relative. - * @param other another {@code Position} - * @return the difference of this and {@code other} - */ - public Position minus(Position other) { - return new Position(line - other.line, column - other.column); - } - - /** - * Compare two positions according to their order of - * appearance in a document (first according to line, - * then according to column). - */ - @Override - public int compareTo(Position o) { - if (line != o.line) { - return line - o.line; - } - return column - o.column; - } - - @Override - public boolean equals(Object obj) { - return obj instanceof Position && ((Position) obj).compareTo(this) == 0; - } - - @Override - public String toString() { - return String.format("(%d, %d)", getZeroBasedLine(), getZeroBasedColumn()); - } - - /** - * Return the Position represented by {@code s}. - * @param s a String that represents a Position, - * formatted like the output of - * {@code Position::toString}. - * @return the Position represented by {@code s} - */ - public static Position fromString(String s) { - Matcher matcher = PATTERN.matcher(s); - if (matcher.matches()) { - return Position.fromZeroBased( - Integer.parseInt(matcher.group("line")), - Integer.parseInt(matcher.group("column")) - ); - } - throw new IllegalArgumentException(String.format("Could not parse %s as a Position.", s)); - } - - @Override - public int hashCode() { - return line * 31 + column; - } - - /** - * Remove the names from the named capturing groups - * that appear in {@code regex}. - * @param regex an arbitrary regular expression - * @return a string representation of {@code regex} - * with the names removed from the named capturing - * groups - */ - public static String removeNamedCapturingGroups(Pattern regex) { // FIXME: Does this belong here? - return regex.toString().replaceAll("\\(\\?<\\w+>", "("); - } + public static final Pattern PATTERN = Pattern.compile("\\((?[0-9]+), (?[0-9]+)\\)"); + + public static final Position ORIGIN = Position.fromZeroBased(0, 0); + + private static final Pattern LINE_SEPARATOR = Pattern.compile("(\n)|(\r)|(\r\n)"); + + private final int line; + private final int column; + + /* ------------------------ CONSTRUCTORS -------------------------- */ + + /** + * Return the Position that describes the given zero-based line and column numbers. + * + * @param line the zero-based line number + * @param column the zero-based column number + * @return a Position describing the position described by {@code line} and {@code column}. + */ + public static Position fromZeroBased(int line, int column) { + return new Position(line, column); + } + + /** + * Return the Position that describes the given one-based line and column numbers. + * + * @param line the one-based line number + * @param column the one-based column number + * @return a Position describing the position described by {@code line} and {@code column}. + */ + public static Position fromOneBased(int line, int column) { + return new Position(line - 1, column - 1); + } + + /** + * Return the Position that equals the displacement caused by {@code text}, assuming that {@code + * text} starts in column 0. + * + * @param text an arbitrary string + * @return the Position that equals the displacement caused by {@code text} + */ + public static Position displacementOf(String text) { + String[] lines = text.lines().toArray(String[]::new); + if (lines.length == 0) return ORIGIN; + return Position.fromZeroBased(lines.length - 1, lines[lines.length - 1].length()); + } + + /** + * Return the Position that describes the same location in {@code content} as {@code offset}. + * + * @param offset a location, expressed as an offset from the beginning of {@code content} + * @param content the content of a document + * @return the Position that describes the same location in {@code content} as {@code offset} + */ + public static Position fromOffset(int offset, String content) { + int lineNumber = 0; + Matcher matcher = LINE_SEPARATOR.matcher(content); + int start = 0; + while (matcher.find(start)) { + if (matcher.start() > offset) return Position.fromZeroBased(lineNumber, offset - start); + start = matcher.end(); + lineNumber++; + } + return Position.fromZeroBased(lineNumber, offset); + } + + /** + * Create a new Position with the given line and column numbers. + * + * @param line the zero-based line number + * @param column the zero-based column number + */ + private Position(int line, int column) { + // Assertions about whether line and column are + // non-negative are deliberately omitted. Positions + // can be relative. + this.line = line; + this.column = column; + } + + /* ----------------------- PUBLIC METHODS ------------------------- */ + + /** + * Return the one-based line number described by this {@code Position}. + * + * @return the one-based line number described by this {@code Position} + */ + public int getOneBasedLine() { + return line + 1; + } + + /** + * Return the one-based column number described by this {@code Position}. + * + * @return the one-based column number described by this {@code Position} + */ + public int getOneBasedColumn() { + return column + 1; + } + + /** + * Return the zero-based line number described by this {@code Position}. + * + * @return the zero-based line number described by this {@code Position} + */ + public int getZeroBasedLine() { + return line; + } + + /** + * Return the zero-based column number described by this {@code Position}. + * + * @return the zero-based column number described by this {@code Position} + */ + public int getZeroBasedColumn() { + return column; + } + + /** + * Return the Position that equals the displacement of ((text whose displacement equals {@code + * this}) concatenated with {@code text}). Note that this is not necessarily equal to ({@code + * this} + displacementOf(text)). + * + * @param text an arbitrary string + * @return the Position that equals the displacement caused by {@code text} + */ + public Position plus(String text) { + text += "\n"; // Turn line separators into line terminators. + String[] lines = text.lines().toArray(String[]::new); + if (lines.length == 0) return this; // OK not to copy because Positions are immutable + int lastLineLength = lines[lines.length - 1].length(); + return new Position( + line + lines.length - 1, lines.length > 1 ? lastLineLength : column + lastLineLength); + } + + /** + * Return the sum of this and another {@code Position}. The result has meaning because Positions + * are relative. + * + * @param other another {@code Position} + * @return the sum of this and {@code other} + */ + public Position plus(Position other) { + return new Position(line + other.line, column + other.column); + } + + /** + * Return the difference of this and another {@code Position}. The result has meaning because + * Positions are relative. + * + * @param other another {@code Position} + * @return the difference of this and {@code other} + */ + public Position minus(Position other) { + return new Position(line - other.line, column - other.column); + } + + /** + * Compare two positions according to their order of appearance in a document (first according to + * line, then according to column). + */ + @Override + public int compareTo(Position o) { + if (line != o.line) { + return line - o.line; + } + return column - o.column; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Position && ((Position) obj).compareTo(this) == 0; + } + + @Override + public String toString() { + return String.format("(%d, %d)", getZeroBasedLine(), getZeroBasedColumn()); + } + + /** + * Return the Position represented by {@code s}. + * + * @param s a String that represents a Position, formatted like the output of {@code + * Position::toString}. + * @return the Position represented by {@code s} + */ + public static Position fromString(String s) { + Matcher matcher = PATTERN.matcher(s); + if (matcher.matches()) { + return Position.fromZeroBased( + Integer.parseInt(matcher.group("line")), Integer.parseInt(matcher.group("column"))); + } + throw new IllegalArgumentException(String.format("Could not parse %s as a Position.", s)); + } + + @Override + public int hashCode() { + return line * 31 + column; + } + + /** + * Remove the names from the named capturing groups that appear in {@code regex}. + * + * @param regex an arbitrary regular expression + * @return a string representation of {@code regex} with the names removed from the named + * capturing groups + */ + public static String removeNamedCapturingGroups(Pattern regex) { // FIXME: Does this belong here? + return regex.toString().replaceAll("\\(\\?<\\w+>", "("); + } } diff --git a/org.lflang/src/org/lflang/generator/Range.java b/org.lflang/src/org/lflang/generator/Range.java index ffca1104d9..9300127d36 100644 --- a/org.lflang/src/org/lflang/generator/Range.java +++ b/org.lflang/src/org/lflang/generator/Range.java @@ -4,136 +4,129 @@ import java.util.regex.Pattern; /** - * Represents a range in a document. Ranges have a - * natural ordering that respects their start + * Represents a range in a document. Ranges have a natural ordering that respects their start * position(s) only. */ public class Range implements Comparable { - public static final Pattern PATTERN = Pattern.compile(String.format( - "Range: \\[(?%s), (?%s)\\)", - Position.removeNamedCapturingGroups(Position.PATTERN), - Position.removeNamedCapturingGroups(Position.PATTERN) - )); + public static final Pattern PATTERN = + Pattern.compile( + String.format( + "Range: \\[(?%s), (?%s)\\)", + Position.removeNamedCapturingGroups(Position.PATTERN), + Position.removeNamedCapturingGroups(Position.PATTERN))); - /** The start of the Range (INCLUSIVE). */ - private final Position start; - /** The end of the Range (EXCLUSIVE). */ - private final Position end; + /** The start of the Range (INCLUSIVE). */ + private final Position start; + /** The end of the Range (EXCLUSIVE). */ + private final Position end; - /* ------------------------- PUBLIC METHODS -------------------------- */ + /* ------------------------- PUBLIC METHODS -------------------------- */ - /** - * Initializes a Range that starts at - * {@code startInclusive} and ends at, but does not - * include, {@code endExclusive}. - * @param startInclusive the start of the range - * (inclusive) - * @param endExclusive the end of the range (exclusive) - */ - public Range(Position startInclusive, Position endExclusive) { - assert startInclusive.compareTo(endExclusive) <= 0: "endExclusive cannot precede startInclusive"; - start = startInclusive; - end = endExclusive; - } + /** + * Initializes a Range that starts at {@code startInclusive} and ends at, but does not include, + * {@code endExclusive}. + * + * @param startInclusive the start of the range (inclusive) + * @param endExclusive the end of the range (exclusive) + */ + public Range(Position startInclusive, Position endExclusive) { + assert startInclusive.compareTo(endExclusive) <= 0 + : "endExclusive cannot precede startInclusive"; + start = startInclusive; + end = endExclusive; + } - /** - * Returns the first Position that is included in this - * Range. - * @return the first Position that is included in this - * Range - */ - public Position getStartInclusive() { - return start; - } + /** + * Returns the first Position that is included in this Range. + * + * @return the first Position that is included in this Range + */ + public Position getStartInclusive() { + return start; + } - /** - * Returns the Position that immediately follows the - * last Position in this Range. - * @return the Position that immediately follows the - * last Position in this Range - */ - public Position getEndExclusive() { - return end; - } + /** + * Returns the Position that immediately follows the last Position in this Range. + * + * @return the Position that immediately follows the last Position in this Range + */ + public Position getEndExclusive() { + return end; + } - @Override - public boolean equals(Object o) { - if (!(o instanceof Range r)) return false; - return start.equals(r.start); - } + @Override + public boolean equals(Object o) { + if (!(o instanceof Range r)) return false; + return start.equals(r.start); + } - @Override - public int hashCode() { - return start.hashCode(); - } + @Override + public int hashCode() { + return start.hashCode(); + } - /** - * Compares this to {@code o}. - * @param o another Range - * @return an integer indicating how this compares to - * {@code o} - */ - @Override - public int compareTo(Range o) { - return this.start.compareTo(o.start); - } + /** + * Compares this to {@code o}. + * + * @param o another Range + * @return an integer indicating how this compares to {@code o} + */ + @Override + public int compareTo(Range o) { + return this.start.compareTo(o.start); + } - /** - * Returns whether this contains {@code p}. - * @param p an arbitrary Position - * @return whether this contains {@code p} - */ - public boolean contains(Position p) { - return start.compareTo(p) <= 0 && p.compareTo(end) < 0; - } + /** + * Returns whether this contains {@code p}. + * + * @param p an arbitrary Position + * @return whether this contains {@code p} + */ + public boolean contains(Position p) { + return start.compareTo(p) <= 0 && p.compareTo(end) < 0; + } - @Override - public String toString() { - return String.format("Range: [%s, %s)", start, end); - } + @Override + public String toString() { + return String.format("Range: [%s, %s)", start, end); + } - /** - * Converts {@code s} to a Range. - * @param s a String that represents a Range, formatted - * like the output of {@code Range::toString} - * @return the Range r such that {@code r.toString()} - * equals {@code s} - */ - public static Range fromString(String s) { - return fromString(s, Position.fromZeroBased(0, 0)); - } + /** + * Converts {@code s} to a Range. + * + * @param s a String that represents a Range, formatted like the output of {@code Range::toString} + * @return the Range r such that {@code r.toString()} equals {@code s} + */ + public static Range fromString(String s) { + return fromString(s, Position.fromZeroBased(0, 0)); + } - /** - * Converts {@code s} to a Range, with the - * assumption that the positions expressed in {@code s} - * are given relative to {@code relativeTo}. - * @param s a String that represents a Range, formatted - * like the output of {@code Range::toString} - * @param relativeTo the position relative to which the - * positions in {@code s} are - * represented - * @return the Range represented by {@code s}, - * expressed relative to the Position relative to which - * the Position {@code relativeTo} is expressed - */ - public static Range fromString(String s, Position relativeTo) { - Matcher matcher = PATTERN.matcher(s); - if (matcher.matches()) { - Position start = Position.fromString(matcher.group("start")); - Position end = Position.fromString(matcher.group("end")); - return new Range(start.plus(relativeTo), end.plus(relativeTo)); - } - throw new IllegalArgumentException(String.format("Could not parse %s as a Range.", s)); + /** + * Converts {@code s} to a Range, with the assumption that the positions expressed in {@code s} + * are given relative to {@code relativeTo}. + * + * @param s a String that represents a Range, formatted like the output of {@code Range::toString} + * @param relativeTo the position relative to which the positions in {@code s} are represented + * @return the Range represented by {@code s}, expressed relative to the Position relative to + * which the Position {@code relativeTo} is expressed + */ + public static Range fromString(String s, Position relativeTo) { + Matcher matcher = PATTERN.matcher(s); + if (matcher.matches()) { + Position start = Position.fromString(matcher.group("start")); + Position end = Position.fromString(matcher.group("end")); + return new Range(start.plus(relativeTo), end.plus(relativeTo)); } + throw new IllegalArgumentException(String.format("Could not parse %s as a Range.", s)); + } - /** - * Returns the degenerate range that simply - * describes the exact location specified by {@code p}. - * @param p an arbitrary Position - * @return a Range that starts and ends immediately - * before {@code p} - */ - public static Range degenerateRange(Position p) { - return new Range(p, p); - } + /** + * Returns the degenerate range that simply describes the exact location specified by {@code p}. + * + * @param p an arbitrary Position + * @return a Range that starts and ends immediately before {@code p} + */ + public static Range degenerateRange(Position p) { + return new Range(p, p); + } } diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index e6dcc6d629..5e8069728f 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -1,28 +1,28 @@ /** Representation of a runtime instance of a reaction. */ /************* -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; @@ -31,9 +31,8 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.LinkedList; import java.util.List; import java.util.Set; - -import org.lflang.ast.ASTUtils; import org.lflang.TimeValue; +import org.lflang.ast.ASTUtils; import org.lflang.lf.Action; import org.lflang.lf.BuiltinTriggerRef; import org.lflang.lf.Port; @@ -44,527 +43,499 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.Variable; /** - * Representation of a compile-time instance of a reaction. - * Like {@link ReactorInstance}, if one or more parents of this reaction - * is a bank of reactors, then there will be more than one runtime instance - * corresponding to this compile-time instance. The {@link #getRuntimeInstances()} - * method returns a list of these runtime instances, each an instance of the - * inner class {@link ReactionInstance.Runtime}. Each runtime instance has a "level", which is - * its depth an acyclic precedence graph representing the dependencies between - * reactions at a tag. + * Representation of a compile-time instance of a reaction. Like {@link ReactorInstance}, if one or + * more parents of this reaction is a bank of reactors, then there will be more than one runtime + * instance corresponding to this compile-time instance. The {@link #getRuntimeInstances()} method + * returns a list of these runtime instances, each an instance of the inner class {@link + * ReactionInstance.Runtime}. Each runtime instance has a "level", which is its depth an acyclic + * precedence graph representing the dependencies between reactions at a tag. * * @author Edward A. Lee * @author Marten Lohstroh */ public class ReactionInstance extends NamedInstance { - /** - * Create a new reaction instance from the specified definition - * within the specified parent. This constructor should be called - * only by the ReactorInstance class, but it is public to enable unit tests. - * @param definition A reaction definition. - * @param parent The parent reactor instance, which cannot be null. - * @param index The index of the reaction within the reactor (0 for the - * first reaction, 1 for the second, etc.). - */ - public ReactionInstance( - Reaction definition, - ReactorInstance parent, - int index - ) { - super(definition, parent); - this.index = index; - - String body = ASTUtils.toText(definition.getCode()); - - // Identify the dependencies for this reaction. - // First handle the triggers. - for (TriggerRef trigger : definition.getTriggers()) { - if (trigger instanceof VarRef) { - Variable variable = ((VarRef)trigger).getVariable(); - if (variable instanceof Port) { - PortInstance portInstance = parent.lookupPortInstance((Port)variable); - // If the trigger is the port of a contained bank, then the - // portInstance will be null and we have to instead search for - // each port instance in the bank. - if (portInstance != null) { - this.sources.add(portInstance); - portInstance.dependentReactions.add(this); - this.triggers.add(portInstance); - } else if (((VarRef)trigger).getContainer() != null) { - // Port belongs to a contained reactor or bank. - ReactorInstance containedReactor - = parent.lookupReactorInstance(((VarRef)trigger).getContainer()); - if (containedReactor != null) { - portInstance = containedReactor.lookupPortInstance((Port)variable); - if (portInstance != null) { - this.sources.add(portInstance); - portInstance.dependentReactions.add(this); - this.triggers.add(portInstance); - } - } - } - } else if (variable instanceof Action) { - var actionInstance = parent.lookupActionInstance( - (Action)((VarRef)trigger).getVariable()); - this.triggers.add(actionInstance); - actionInstance.dependentReactions.add(this); - this.sources.add(actionInstance); - } else if (variable instanceof Timer) { - var timerInstance = parent.lookupTimerInstance( - (Timer)((VarRef)trigger).getVariable()); - this.triggers.add(timerInstance); - timerInstance.dependentReactions.add(this); - this.sources.add(timerInstance); - } - } else if (trigger instanceof BuiltinTriggerRef) { - this.triggers.add(parent.getOrCreateBuiltinTrigger((BuiltinTriggerRef) trigger)); + /** + * Create a new reaction instance from the specified definition within the specified parent. This + * constructor should be called only by the ReactorInstance class, but it is public to enable unit + * tests. + * + * @param definition A reaction definition. + * @param parent The parent reactor instance, which cannot be null. + * @param index The index of the reaction within the reactor (0 for the first reaction, 1 for the + * second, etc.). + */ + public ReactionInstance(Reaction definition, ReactorInstance parent, int index) { + super(definition, parent); + this.index = index; + + String body = ASTUtils.toText(definition.getCode()); + + // Identify the dependencies for this reaction. + // First handle the triggers. + for (TriggerRef trigger : definition.getTriggers()) { + if (trigger instanceof VarRef) { + Variable variable = ((VarRef) trigger).getVariable(); + if (variable instanceof Port) { + PortInstance portInstance = parent.lookupPortInstance((Port) variable); + // If the trigger is the port of a contained bank, then the + // portInstance will be null and we have to instead search for + // each port instance in the bank. + if (portInstance != null) { + this.sources.add(portInstance); + portInstance.dependentReactions.add(this); + this.triggers.add(portInstance); + } else if (((VarRef) trigger).getContainer() != null) { + // Port belongs to a contained reactor or bank. + ReactorInstance containedReactor = + parent.lookupReactorInstance(((VarRef) trigger).getContainer()); + if (containedReactor != null) { + portInstance = containedReactor.lookupPortInstance((Port) variable); + if (portInstance != null) { + this.sources.add(portInstance); + portInstance.dependentReactions.add(this); + this.triggers.add(portInstance); + } } + } + } else if (variable instanceof Action) { + var actionInstance = + parent.lookupActionInstance((Action) ((VarRef) trigger).getVariable()); + this.triggers.add(actionInstance); + actionInstance.dependentReactions.add(this); + this.sources.add(actionInstance); + } else if (variable instanceof Timer) { + var timerInstance = parent.lookupTimerInstance((Timer) ((VarRef) trigger).getVariable()); + this.triggers.add(timerInstance); + timerInstance.dependentReactions.add(this); + this.sources.add(timerInstance); } - // Next handle the ports that this reaction reads. - for (VarRef source : definition.getSources()) { - Variable variable = source.getVariable(); - if (variable instanceof Port) { - var portInstance = parent.lookupPortInstance((Port)variable); - - // If the trigger is the port of a contained bank, then the - // portInstance will be null and we have to instead search for - // each port instance in the bank. - if (portInstance != null) { - this.sources.add(portInstance); - this.reads.add(portInstance); - portInstance.dependentReactions.add(this); - } else if (source.getContainer() != null) { - ReactorInstance containedReactor - = parent.lookupReactorInstance(source.getContainer()); - if (containedReactor != null) { - portInstance = containedReactor.lookupPortInstance((Port)variable); - if (portInstance != null) { - this.sources.add(portInstance); - portInstance.dependentReactions.add(this); - this.triggers.add(portInstance); - } - } - } + } else if (trigger instanceof BuiltinTriggerRef) { + this.triggers.add(parent.getOrCreateBuiltinTrigger((BuiltinTriggerRef) trigger)); + } + } + // Next handle the ports that this reaction reads. + for (VarRef source : definition.getSources()) { + Variable variable = source.getVariable(); + if (variable instanceof Port) { + var portInstance = parent.lookupPortInstance((Port) variable); + + // If the trigger is the port of a contained bank, then the + // portInstance will be null and we have to instead search for + // each port instance in the bank. + if (portInstance != null) { + this.sources.add(portInstance); + this.reads.add(portInstance); + portInstance.dependentReactions.add(this); + } else if (source.getContainer() != null) { + ReactorInstance containedReactor = parent.lookupReactorInstance(source.getContainer()); + if (containedReactor != null) { + portInstance = containedReactor.lookupPortInstance((Port) variable); + if (portInstance != null) { + this.sources.add(portInstance); + portInstance.dependentReactions.add(this); + this.triggers.add(portInstance); } + } } + } + } - // Finally, handle the effects. - for (VarRef effect : definition.getEffects()) { - Variable variable = effect.getVariable(); - if (variable instanceof Port) { - var portInstance = parent.lookupPortInstance(effect); - if (portInstance != null) { - this.effects.add(portInstance); - portInstance.dependsOnReactions.add(this); - } else { - throw new InvalidSourceException( - "Unexpected effect. Cannot find port " + variable.getName()); - } - } else if (variable instanceof Action) { - // Effect is an Action. - var actionInstance = parent.lookupActionInstance( - (Action)variable); - this.effects.add(actionInstance); - actionInstance.dependsOnReactions.add(this); - } else { - // Effect is either a mode or an unresolved reference. - // Do nothing, transitions will be set up by the ModeInstance. - } - } - // Create a deadline instance if one has been defined. - if (this.definition.getDeadline() != null) { - this.declaredDeadline = new DeadlineInstance( - this.definition.getDeadline(), this); + // Finally, handle the effects. + for (VarRef effect : definition.getEffects()) { + Variable variable = effect.getVariable(); + if (variable instanceof Port) { + var portInstance = parent.lookupPortInstance(effect); + if (portInstance != null) { + this.effects.add(portInstance); + portInstance.dependsOnReactions.add(this); + } else { + throw new InvalidSourceException( + "Unexpected effect. Cannot find port " + variable.getName()); } + } else if (variable instanceof Action) { + // Effect is an Action. + var actionInstance = parent.lookupActionInstance((Action) variable); + this.effects.add(actionInstance); + actionInstance.dependsOnReactions.add(this); + } else { + // Effect is either a mode or an unresolved reference. + // Do nothing, transitions will be set up by the ModeInstance. + } } - - ////////////////////////////////////////////////////// - //// Public fields. - - /** - * Indicates the chain this reaction is a part of. It is constructed - * through a bit-wise or among all upstream chains. Each fork in the - * dependency graph setting a new, unused bit to true in order to - * disambiguate it from parallel chains. Note that zero results in - * no overlap with any other reaction, which means the reaction can - * execute in parallel with any other reaction. The default is 1L. - * If left at the default, parallel execution will be based purely - * on levels. - */ - public long chainID = 1L; - - /** - * The ports or actions that this reaction may write to. - */ - public Set> effects = new LinkedHashSet<>(); - - /** - * The ports, actions, or timers that this reaction is triggered by or uses. - */ - public Set> sources = new LinkedHashSet<>(); - // FIXME: Above sources is misnamed because in the grammar, - // "sources" are only the inputs a reaction reads without being - // triggered by them. The name "reads" used here would be a better - // choice in the grammar. - - /** - * Deadline for this reaction instance, if declared. - */ - public DeadlineInstance declaredDeadline; - - /** - * Index of order of occurrence within the reactor definition. - * The first reaction has index 0, the second index 1, etc. - */ - public int index; - - /** - * The ports that this reaction reads but that do not trigger it. - */ - public Set> reads = new LinkedHashSet<>(); - - /** - * The trigger instances (input ports, timers, and actions - * that trigger reactions) that trigger this reaction. - */ - public Set> triggers = new LinkedHashSet<>(); - - ////////////////////////////////////////////////////// - //// Public methods. - - /** - * Clear caches used in reporting dependentReactions() and dependsOnReactions(). - * This method should be called if any changes are made to triggers, sources, - * or effects. - * @param includingRuntimes If false, leave the runtime instances 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) { - dependentReactionsCache = null; - dependsOnReactionsCache = null; - if (includingRuntimes) runtimeInstances = null; + // Create a deadline instance if one has been defined. + if (this.definition.getDeadline() != null) { + this.declaredDeadline = new DeadlineInstance(this.definition.getDeadline(), this); } - - /** - * Return the set of immediate downstream reactions, which are reactions - * that receive data produced by this reaction plus - * at most one reaction in the same reactor whose definition - * lexically follows this one. - */ - public Set dependentReactions() { - // Cache the result. - if (dependentReactionsCache != null) return dependentReactionsCache; - dependentReactionsCache = new LinkedHashSet<>(); - - // First, add the next lexical reaction, if appropriate. - if (parent.reactions.size() > index + 1) { - // Find the next reaction in the parent's reaction list. - dependentReactionsCache.add(parent.reactions.get(index + 1)); - } - - // Next, add reactions that get data from this one via a port. - for (TriggerInstance effect : effects) { - if (effect instanceof PortInstance) { - for (SendRange senderRange - : ((PortInstance)effect).eventualDestinations()) { - for (RuntimeRange destinationRange - : senderRange.destinations) { - dependentReactionsCache.addAll( - destinationRange.instance.dependentReactions); - } - } - } - } - return dependentReactionsCache; + } + + ////////////////////////////////////////////////////// + //// Public fields. + + /** + * Indicates the chain this reaction is a part of. It is constructed through a bit-wise or among + * all upstream chains. Each fork in the dependency graph setting a new, unused bit to true in + * order to disambiguate it from parallel chains. Note that zero results in no overlap with any + * other reaction, which means the reaction can execute in parallel with any other reaction. The + * default is 1L. If left at the default, parallel execution will be based purely on levels. + */ + public long chainID = 1L; + + /** The ports or actions that this reaction may write to. */ + public Set> effects = new LinkedHashSet<>(); + + /** The ports, actions, or timers that this reaction is triggered by or uses. */ + public Set> sources = new LinkedHashSet<>(); + // FIXME: Above sources is misnamed because in the grammar, + // "sources" are only the inputs a reaction reads without being + // triggered by them. The name "reads" used here would be a better + // choice in the grammar. + + /** Deadline for this reaction instance, if declared. */ + public DeadlineInstance declaredDeadline; + + /** + * Index of order of occurrence within the reactor definition. The first reaction has index 0, the + * second index 1, etc. + */ + public int index; + + /** The ports that this reaction reads but that do not trigger it. */ + public Set> reads = new LinkedHashSet<>(); + + /** + * The trigger instances (input ports, timers, and actions that trigger reactions) that trigger + * this reaction. + */ + public Set> triggers = new LinkedHashSet<>(); + + ////////////////////////////////////////////////////// + //// Public methods. + + /** + * Clear caches used in reporting dependentReactions() and dependsOnReactions(). This method + * should be called if any changes are made to triggers, sources, or effects. + * + * @param includingRuntimes If false, leave the runtime instances 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) { + dependentReactionsCache = null; + dependsOnReactionsCache = null; + if (includingRuntimes) runtimeInstances = null; + } + + /** + * Return the set of immediate downstream reactions, which are reactions that receive data + * produced by this reaction plus at most one reaction in the same reactor whose definition + * lexically follows this one. + */ + public Set dependentReactions() { + // Cache the result. + if (dependentReactionsCache != null) return dependentReactionsCache; + dependentReactionsCache = new LinkedHashSet<>(); + + // First, add the next lexical reaction, if appropriate. + if (parent.reactions.size() > index + 1) { + // Find the next reaction in the parent's reaction list. + dependentReactionsCache.add(parent.reactions.get(index + 1)); } - /** - * Return the set of immediate upstream reactions, which are reactions - * that send data to this one plus at most one reaction in the same - * reactor whose definition immediately precedes the definition of this one - */ - public Set dependsOnReactions() { - // Cache the result. - if (dependsOnReactionsCache != null) return dependsOnReactionsCache; - dependsOnReactionsCache = new LinkedHashSet<>(); - - // First, add the previous lexical reaction, if appropriate. - if (index > 0) { - // Find the previous ordered reaction in the parent's reaction list. - int earlierIndex = index - 1; - ReactionInstance earlierOrderedReaction = parent.reactions.get(earlierIndex); - while (--earlierIndex >= 0) { - earlierOrderedReaction = parent.reactions.get(earlierIndex); - } - if (earlierIndex >= 0) { - dependsOnReactionsCache.add(parent.reactions.get(index - 1)); - } + // Next, add reactions that get data from this one via a port. + for (TriggerInstance effect : effects) { + if (effect instanceof PortInstance) { + for (SendRange senderRange : ((PortInstance) effect).eventualDestinations()) { + for (RuntimeRange destinationRange : senderRange.destinations) { + dependentReactionsCache.addAll(destinationRange.instance.dependentReactions); + } } - - // Next, add reactions that send data to this one. - for (TriggerInstance source : sources) { - if (source instanceof PortInstance) { - // First, add reactions that send data through an intermediate port. - for (RuntimeRange senderRange - : ((PortInstance)source).eventualSources()) { - dependsOnReactionsCache.addAll(senderRange.instance.dependsOnReactions); - } - // Then, add reactions that send directly to this port. - dependsOnReactionsCache.addAll(source.dependsOnReactions); - } - } - return dependsOnReactionsCache; + } } - - /** - * Return a set of levels that runtime instances of this reaction have. - * A ReactionInstance may have more than one level if it lies within - * a bank and its dependencies on other reactions pass through multiports. - */ - public Set getLevels() { - Set result = new LinkedHashSet<>(); - // Force calculation of levels if it has not been done. - // FIXME: Comment out this as I think it is redundant. - // If it is NOT redundant then deadline propagation is not correct - // parent.assignLevels(); - for (Runtime runtime : runtimeInstances) { - result.add(runtime.level); - } - return result; + return dependentReactionsCache; + } + + /** + * Return the set of immediate upstream reactions, which are reactions that send data to this one + * plus at most one reaction in the same reactor whose definition immediately precedes the + * definition of this one + */ + public Set dependsOnReactions() { + // Cache the result. + if (dependsOnReactionsCache != null) return dependsOnReactionsCache; + dependsOnReactionsCache = new LinkedHashSet<>(); + + // First, add the previous lexical reaction, if appropriate. + if (index > 0) { + // Find the previous ordered reaction in the parent's reaction list. + int earlierIndex = index - 1; + ReactionInstance earlierOrderedReaction = parent.reactions.get(earlierIndex); + while (--earlierIndex >= 0) { + earlierOrderedReaction = parent.reactions.get(earlierIndex); + } + if (earlierIndex >= 0) { + dependsOnReactionsCache.add(parent.reactions.get(index - 1)); + } } - /** - * Return a set of deadlines that runtime instances of this reaction have. - * A ReactionInstance may have more than one deadline if it lies within. - */ - public Set getInferredDeadlines() { - Set result = new LinkedHashSet<>(); - for (Runtime runtime : runtimeInstances) { - result.add(runtime.deadline); + // Next, add reactions that send data to this one. + for (TriggerInstance source : sources) { + if (source instanceof PortInstance) { + // First, add reactions that send data through an intermediate port. + for (RuntimeRange senderRange : ((PortInstance) source).eventualSources()) { + dependsOnReactionsCache.addAll(senderRange.instance.dependsOnReactions); } - return result; + // Then, add reactions that send directly to this port. + dependsOnReactionsCache.addAll(source.dependsOnReactions); + } } - - - /** - * Return a list of levels that runtime instances of this reaction have. - * The size of this list is the total number of runtime instances. - * A ReactionInstance may have more than one level if it lies within - * a bank and its dependencies on other reactions pass through multiports. - */ - public List getLevelsList() { - List result = new LinkedList<>(); - // Force calculation of levels if it has not been done. - // FIXME: Comment out this as I think it is redundant. - // If it is NOT redundant then deadline propagation is not correct - // parent.assignLevels(); - for (Runtime runtime : runtimeInstances) { - result.add(runtime.level); - } - return result; + return dependsOnReactionsCache; + } + + /** + * Return a set of levels that runtime instances of this reaction have. A ReactionInstance may + * have more than one level if it lies within a bank and its dependencies on other reactions pass + * through multiports. + */ + public Set getLevels() { + Set result = new LinkedHashSet<>(); + // Force calculation of levels if it has not been done. + // FIXME: Comment out this as I think it is redundant. + // If it is NOT redundant then deadline propagation is not correct + // parent.assignLevels(); + for (Runtime runtime : runtimeInstances) { + result.add(runtime.level); } - - /** - * Return a list of the deadlines that runtime instances of this reaction have. - * The size of this list is the total number of runtime instances. - * A ReactionInstance may have more than one deadline if it lies within - */ - public List getInferredDeadlinesList() { - List result = new LinkedList<>(); - for (Runtime runtime : runtimeInstances) { - result.add(runtime.deadline); - } - return result; + return result; + } + + /** + * Return a set of deadlines that runtime instances of this reaction have. A ReactionInstance may + * have more than one deadline if it lies within. + */ + public Set getInferredDeadlines() { + Set result = new LinkedHashSet<>(); + for (Runtime runtime : runtimeInstances) { + result.add(runtime.deadline); } - - - /** - * Return the name of this reaction, which is 'reaction_n', - * where n is replaced by the reaction index. - * @return The name of this reaction. - */ - @Override - public String getName() { - return "reaction_" + this.index; + return result; + } + + /** + * Return a list of levels that runtime instances of this reaction have. The size of this list is + * the total number of runtime instances. A ReactionInstance may have more than one level if it + * lies within a bank and its dependencies on other reactions pass through multiports. + */ + public List getLevelsList() { + List result = new LinkedList<>(); + // Force calculation of levels if it has not been done. + // FIXME: Comment out this as I think it is redundant. + // If it is NOT redundant then deadline propagation is not correct + // parent.assignLevels(); + for (Runtime runtime : runtimeInstances) { + result.add(runtime.level); } - - /** - * Return an array of runtime instances of this reaction in a - * natural order, defined as follows. The position within the - * returned list of the runtime instance is given by a mixed-radix - * number where the low-order digit is the bank index within the - * container reactor (or {@code 0} if it is not a bank), the second low order - * digit is the bank index of the container's container (or {@code 0} if - * it is not a bank), etc., until the container that is directly - * contained by the top level (the top-level reactor need not be - * included because its index is always {@code 0}). - * - * The size of the returned array is the product of the widths of all of the - * container {@link ReactorInstance} objects. If none of these is a bank, - * then the size will be {@code 1}. - * - * This method creates this array the first time it is called, but then - * holds on to it. The array is used by {@link ReactionInstanceGraph} - * to determine and record levels and deadline for runtime instances - * of reactors. - */ - public List getRuntimeInstances() { - if (runtimeInstances != null) return runtimeInstances; - int size = parent.getTotalWidth(); - // If the width cannot be determined, assume there is only one instance. - if (size < 0) size = 1; - runtimeInstances = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - Runtime r = new Runtime(); - r.id = i; - if (declaredDeadline != null) { - r.deadline = declaredDeadline.maxDelay; - } - runtimeInstances.add(r); - } - return runtimeInstances; + return result; + } + + /** + * Return a list of the deadlines that runtime instances of this reaction have. The size of this + * list is the total number of runtime instances. A ReactionInstance may have more than one + * deadline if it lies within + */ + public List getInferredDeadlinesList() { + List result = new LinkedList<>(); + for (Runtime runtime : runtimeInstances) { + result.add(runtime.deadline); } - - /** - * Purge 'portInstance' from this reaction, removing it from the list - * of triggers, sources, effects, and reads. Note that this leaves - * the runtime instances intact, including their level information. - */ - public void removePortInstance(PortInstance portInstance) { - this.triggers.remove(portInstance); - this.sources.remove(portInstance); - this.effects.remove(portInstance); - this.reads.remove(portInstance); - clearCaches(false); - portInstance.clearCaches(); + return result; + } + + /** + * Return the name of this reaction, which is 'reaction_n', where n is replaced by the reaction + * index. + * + * @return The name of this reaction. + */ + @Override + public String getName() { + return "reaction_" + this.index; + } + + /** + * Return an array of runtime instances of this reaction in a natural order, defined as + * follows. The position within the returned list of the runtime instance is given by a + * mixed-radix number where the low-order digit is the bank index within the container reactor (or + * {@code 0} if it is not a bank), the second low order digit is the bank index of the container's + * container (or {@code 0} if it is not a bank), etc., until the container that is directly + * contained by the top level (the top-level reactor need not be included because its index is + * always {@code 0}). + * + *

The size of the returned array is the product of the widths of all of the container {@link + * ReactorInstance} objects. If none of these is a bank, then the size will be {@code 1}. + * + *

This method creates this array the first time it is called, but then holds on to it. The + * array is used by {@link ReactionInstanceGraph} to determine and record levels and deadline for + * runtime instances of reactors. + */ + public List getRuntimeInstances() { + if (runtimeInstances != null) return runtimeInstances; + int size = parent.getTotalWidth(); + // If the width cannot be determined, assume there is only one instance. + if (size < 0) size = 1; + runtimeInstances = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + Runtime r = new Runtime(); + r.id = i; + if (declaredDeadline != null) { + r.deadline = declaredDeadline.maxDelay; + } + runtimeInstances.add(r); } - - /** - * Return a descriptive string. - */ - @Override - public String toString() { - return getName() + " of " + parent.getFullName(); + return runtimeInstances; + } + + /** + * Purge 'portInstance' from this reaction, removing it from the list of triggers, sources, + * effects, and reads. Note that this leaves the runtime instances intact, including their level + * information. + */ + public void removePortInstance(PortInstance portInstance) { + this.triggers.remove(portInstance); + this.sources.remove(portInstance); + this.effects.remove(portInstance); + this.reads.remove(portInstance); + clearCaches(false); + portInstance.clearCaches(); + } + + /** Return a descriptive string. */ + @Override + public String toString() { + return getName() + " of " + parent.getFullName(); + } + + /** + * Determine logical execution time for each reaction during compile time based on immediate + * downstream logical delays (after delays and actions) and label each reaction with the minimum + * of all such delays. + */ + public TimeValue assignLogicalExecutionTime() { + if (this.let != null) { + return this.let; } - /** - * Determine logical execution time for each reaction during compile - * time based on immediate downstream logical delays (after delays and actions) - * and label each reaction with the minimum of all such delays. - */ - public TimeValue assignLogicalExecutionTime() { - if (this.let != null) { - return this.let; - } - - if (this.parent.isGeneratedDelay()) { - return this.let = TimeValue.ZERO; - } + if (this.parent.isGeneratedDelay()) { + return this.let = TimeValue.ZERO; + } - TimeValue let = null; - - // Iterate over effect and find minimum delay. - for (TriggerInstance effect : effects) { - if (effect instanceof PortInstance) { - var afters = this.parent.getParent().children.stream().filter(c -> { - if (c.isGeneratedDelay()) { - return c.inputs.get(0).getDependsOnPorts().get(0).instance - .equals((PortInstance) effect); - } - return false; - }).map(c -> c.actions.get(0).getMinDelay()) - .min(TimeValue::compare); - - if (afters.isPresent()) { - if (let == null) { - let = afters.get(); - } else { - let = TimeValue.min(afters.get(), let); - } - } - } else if (effect instanceof ActionInstance) { - var action = ((ActionInstance) effect).getMinDelay(); - if (let == null) { - let = action; - } else { - let = TimeValue.min(action, let); - } - } + TimeValue let = null; + + // Iterate over effect and find minimum delay. + for (TriggerInstance effect : effects) { + if (effect instanceof PortInstance) { + var afters = + this.parent.getParent().children.stream() + .filter( + c -> { + if (c.isGeneratedDelay()) { + return c.inputs + .get(0) + .getDependsOnPorts() + .get(0) + .instance + .equals((PortInstance) effect); + } + return false; + }) + .map(c -> c.actions.get(0).getMinDelay()) + .min(TimeValue::compare); + + if (afters.isPresent()) { + if (let == null) { + let = afters.get(); + } else { + let = TimeValue.min(afters.get(), let); + } } - + } else if (effect instanceof ActionInstance) { + var action = ((ActionInstance) effect).getMinDelay(); if (let == null) { - let = TimeValue.ZERO; + let = action; + } else { + let = TimeValue.min(action, let); } - return this.let = let; + } } - ////////////////////////////////////////////////////// - //// Private variables. - - /** Cache of the set of downstream reactions. */ - private Set dependentReactionsCache; - - /** Cache of the set of upstream reactions. */ - private Set dependsOnReactionsCache; - - /** - * Array of runtime instances of this reaction. - * This has length 1 unless the reaction is contained - * by one or more banks. Suppose that this reaction - * has depth 3, with full name r0.r1.r2.r. The top-level - * reactor is r0, which contains r1, which contains r2, - * which contains this reaction r. Suppose the widths - * of the containing reactors are w0, w1, and w2, and - * we are interested in the instance at bank indexes - * b0, b1, and b2. That instance is in this array at - * location given by the natural ordering, which - * is the mixed radix number b2%w2; b1%w1. - */ - private List runtimeInstances; - - private TimeValue let = null; - - /////////////////////////////////////////////////////////// - //// Inner classes - - /** Inner class representing a runtime instance of a reaction. */ - public class Runtime { - public TimeValue deadline; - // If this reaction instance depends on exactly one upstream - // reaction (via a port), then the "dominating" field will - // point to that upstream reaction. - public Runtime dominating; - /** ID ranging from 0 to parent.getTotalWidth() - 1. */ - public int id; - public int level; - - public ReactionInstance getReaction() { - return ReactionInstance.this; - } - @Override - public String toString() { - String result = ReactionInstance.this + "(level: " + level; - if (deadline != null && deadline != TimeValue.MAX_VALUE) { - result += ", deadline: " + deadline; - } - if (dominating != null) { - result += ", dominating: " + dominating.getReaction(); - } - result += ")"; - return result; - } + if (let == null) { + let = TimeValue.ZERO; + } + return this.let = let; + } + + ////////////////////////////////////////////////////// + //// Private variables. + + /** Cache of the set of downstream reactions. */ + private Set dependentReactionsCache; + + /** Cache of the set of upstream reactions. */ + private Set dependsOnReactionsCache; + + /** + * Array of runtime instances of this reaction. This has length 1 unless the reaction is contained + * by one or more banks. Suppose that this reaction has depth 3, with full name r0.r1.r2.r. The + * top-level reactor is r0, which contains r1, which contains r2, which contains this reaction r. + * Suppose the widths of the containing reactors are w0, w1, and w2, and we are interested in the + * instance at bank indexes b0, b1, and b2. That instance is in this array at location given by + * the natural ordering, which is the mixed radix number b2%w2; b1%w1. + */ + private List runtimeInstances; + + private TimeValue let = null; + + /////////////////////////////////////////////////////////// + //// Inner classes + + /** Inner class representing a runtime instance of a reaction. */ + public class Runtime { + public TimeValue deadline; + // If this reaction instance depends on exactly one upstream + // reaction (via a port), then the "dominating" field will + // point to that upstream reaction. + public Runtime dominating; + /** ID ranging from 0 to parent.getTotalWidth() - 1. */ + public int id; + + public int level; + + public ReactionInstance getReaction() { + return ReactionInstance.this; + } - public Runtime() { - this.dominating = null; - this.id = 0; - this.level = 0; - if (ReactionInstance.this.declaredDeadline != null) { - this.deadline = ReactionInstance.this.declaredDeadline.maxDelay; - } else { - this.deadline = TimeValue.MAX_VALUE; - } - } + @Override + public String toString() { + String result = ReactionInstance.this + "(level: " + level; + if (deadline != null && deadline != TimeValue.MAX_VALUE) { + result += ", deadline: " + deadline; + } + if (dominating != null) { + result += ", dominating: " + dominating.getReaction(); + } + result += ")"; + return result; + } + + public Runtime() { + this.dominating = null; + this.id = 0; + this.level = 0; + if (ReactionInstance.this.declaredDeadline != null) { + this.deadline = ReactionInstance.this.declaredDeadline.maxDelay; + } else { + this.deadline = TimeValue.MAX_VALUE; + } } + } } diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index ccc061b19f..45386733ba 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -1,28 +1,28 @@ /** A graph that represents causality cycles formed by reaction instances. */ /************* -Copyright (c) 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) 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; @@ -33,8 +33,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.Map; import java.util.Set; import java.util.stream.Collectors; - -import org.eclipse.xtend.lib.macro.services.UpstreamTypeLookup; import org.lflang.AttributeUtils; import org.lflang.generator.ReactionInstance.Runtime; import org.lflang.generator.c.CUtil; @@ -44,384 +42,375 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.validation.AttributeSpec; /** - * This class analyzes the dependencies between reaction runtime instances. - * For each ReactionInstance, there may be more than one runtime instance because - * the ReactionInstance may be nested within one or more banks. - * 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. + * This class analyzes the dependencies between reaction runtime instances. For each + * ReactionInstance, there may be more than one runtime instance because the ReactionInstance may be + * nested within one or more banks. 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. + *

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. + *

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. - */ - public ReactionInstanceGraph(ReactorInstance main) { - this.main = main; - rebuild(); + /** + * 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; + rebuild(); + } + + /////////////////////////////////////////////////////////// + //// Public fields + + /** 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 adds all the nodes and edges. + */ + public void rebuild() { + this.clear(); + addNodesAndEdges(main); + + // FIXME: Use {@link TargetProperty#EXPORT_DEPENDENCY_GRAPH}. + // System.out.println(toDOT()); + addDependentNetworkEdges(main); + + // Assign a level to each reaction. + // If there are cycles present in the graph, it will be detected here. + assignLevels(); + if (nodeCount() != 0) { + // The graph has cycles. + // main.reporter.reportError("Reactions form a cycle! " + toString()); + // Do not throw an exception so that cycle visualization can proceed. + // throw new InvalidSourceException("Reactions form a cycle!"); } - - /////////////////////////////////////////////////////////// - //// Public fields - - /** - * 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 - * adds all the nodes and edges. - */ - public void rebuild() { - this.clear(); - addNodesAndEdges(main); - - - // FIXME: Use {@link TargetProperty#EXPORT_DEPENDENCY_GRAPH}. - // System.out.println(toDOT()); - addDependentNetworkEdges(main); - - // Assign a level to each reaction. - // If there are cycles present in the graph, it will be detected here. - assignLevels(); - if (nodeCount() != 0) { - // The graph has cycles. - // main.reporter.reportError("Reactions form a cycle! " + toString()); - // Do not throw an exception so that cycle visualization can proceed. - // throw new InvalidSourceException("Reactions form a cycle!"); - } + } + /** + * Adds manually a set of dependent network edges as needed to nudge the level assignment + * algorithm into creating a correct level assignment. + * + * @param main + */ + private void addDependentNetworkEdges(ReactorInstance main) { + + Attribute attribute = + AttributeUtils.findAttributeByName(main.definition.getReactorClass(), "_fed_config"); + String actionsStr = + AttributeUtils.getAttributeParameter(attribute, AttributeSpec.DEPENDENCY_PAIRS); + if (actionsStr == null) + return; // No dependent network edges, the levels algorithm has enough information + List dependencies = List.of(actionsStr.split(";", -1)); + // Recursively add nodes and edges from contained reactors. + Map m = new HashMap<>(); + for (ReactorInstance child : main.children) { + m.put(child.getName(), child); } - /** - * Adds manually a set of dependent network edges as needed to nudge the level assignment - * algorithm into creating a correct level assignment. - * @param main - */ - private void addDependentNetworkEdges(ReactorInstance main) { - - Attribute attribute = AttributeUtils.findAttributeByName(main.definition.getReactorClass(), "_fed_config"); - String actionsStr = AttributeUtils.getAttributeParameter(attribute, AttributeSpec.DEPENDENCY_PAIRS); - if (actionsStr == null) return; //No dependent network edges, the levels algorithm has enough information - List dependencies = List.of(actionsStr.split(";", -1)); - // Recursively add nodes and edges from contained reactors. - Map m = new HashMap<>(); - for (ReactorInstance child : main.children) { - m.put(child.getName(), child); - } - for(String dependency: dependencies){ - List dep = List.of(dependency.split(",", 2)); - ReactorInstance downStream = m.getOrDefault(dep.get(0), null); - ReactorInstance upStream = m.getOrDefault(dep.get(1), null); - if(downStream == null || upStream == null) { - System.out.println("Downstream or Upstream reaction pair is undefined. Continuing."); - continue; - } - ReactionInstance down = downStream.reactions.get(0); - Runtime downRuntime = down.getRuntimeInstances().get(0); - for(ReactionInstance up: upStream.reactions){ - Runtime upRuntime = up.getRuntimeInstances().get(0); - addEdge(downRuntime, upRuntime); - } - } - - } - /** - * This function rebuilds the graph and propagates and assigns deadlines - * to all reactions. - */ - public void rebuildAndAssignDeadlines() { - this.clear(); - addNodesAndEdges(main); - addDependentNetworkEdges(main); - assignInferredDeadlines(); - this.clear(); + for (String dependency : dependencies) { + List dep = List.of(dependency.split(",", 2)); + ReactorInstance downStream = m.getOrDefault(dep.get(0), null); + ReactorInstance upStream = m.getOrDefault(dep.get(1), null); + if (downStream == null || upStream == null) { + System.out.println("Downstream or Upstream reaction pair is undefined. Continuing."); + continue; + } + ReactionInstance down = downStream.reactions.get(0); + Runtime downRuntime = down.getRuntimeInstances().get(0); + for (ReactionInstance up : upStream.reactions) { + Runtime upRuntime = up.getRuntimeInstances().get(0); + addEdge(downRuntime, upRuntime); + } } - - /* - * 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() { - return numReactionsPerLevel.toArray(new Integer[0]); + } + /** This function rebuilds the graph and propagates and assigns deadlines to all reactions. */ + public void rebuildAndAssignDeadlines() { + this.clear(); + addNodesAndEdges(main); + addDependentNetworkEdges(main); + assignInferredDeadlines(); + this.clear(); + } + + /* + * 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() { + return numReactionsPerLevel.toArray(new Integer[0]); + } + + /** Return the max breadth of the reaction dependency graph */ + public int getBreadth() { + var maxBreadth = 0; + for (Integer breadth : numReactionsPerLevel) { + if (breadth > maxBreadth) { + maxBreadth = breadth; + } } + return maxBreadth; + } + + /////////////////////////////////////////////////////////// + //// Protected methods + + /** + * Add to the graph edges between the given reaction and all the reactions that depend on the + * specified port. + * + * @param port The port that the given reaction has as an effect. + * @param reaction The reaction to relate downstream reactions to. + */ + protected void addDownstreamReactions(PortInstance port, ReactionInstance reaction) { + // 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); + for (ReactionInstance dstReaction : dstRange.instance.dependentReactions) { + List dstRuntimes = dstReaction.getRuntimeInstances(); + Runtime srcRuntime = srcRuntimes.get(srcIndex); + Runtime dstRuntime = dstRuntimes.get(dstIndex); + // Only add this dependency if the reactions are not in modes at all or in the same mode + // or in modes of separate reactors + // This allows modes to break cycles since modes are always mutually exclusive. + if (srcRuntime.getReaction().getMode(true) == null + || dstRuntime.getReaction().getMode(true) == null + || srcRuntime.getReaction().getMode(true) == dstRuntime.getReaction().getMode(true) + || srcRuntime.getReaction().getParent() != dstRuntime.getReaction().getParent()) { + addEdge(dstRuntime, srcRuntime); + } - /** - * Return the max breadth of the reaction dependency graph - */ - public int getBreadth() { - var maxBreadth = 0; - for (Integer breadth: numReactionsPerLevel ) { - if (breadth > maxBreadth) { - maxBreadth = breadth; + // Propagate the deadlines, if any. + if (srcRuntime.deadline.compareTo(dstRuntime.deadline) > 0) { + srcRuntime.deadline = dstRuntime.deadline; } - } - return maxBreadth; - } - /////////////////////////////////////////////////////////// - //// Protected methods - - /** - * Add to the graph edges between the given reaction and all the reactions - * that depend on the specified port. - * @param port The port that the given reaction has as an effect. - * @param reaction The reaction to relate downstream reactions to. - */ - protected void addDownstreamReactions(PortInstance port, ReactionInstance reaction) { - // 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); - for (ReactionInstance dstReaction : dstRange.instance.dependentReactions) { - List dstRuntimes = dstReaction.getRuntimeInstances(); - Runtime srcRuntime = srcRuntimes.get(srcIndex); - Runtime dstRuntime = dstRuntimes.get(dstIndex); - // Only add this dependency if the reactions are not in modes at all or in the same mode or in modes of separate reactors - // This allows modes to break cycles since modes are always mutually exclusive. - if (srcRuntime.getReaction().getMode(true) == null || - dstRuntime.getReaction().getMode(true) == null || - srcRuntime.getReaction().getMode(true) == dstRuntime.getReaction().getMode(true) || - srcRuntime.getReaction().getParent() != dstRuntime.getReaction().getParent()) { - addEdge(dstRuntime, srcRuntime); - } - - // Propagate the deadlines, if any. - 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. - if (this.getUpstreamAdjacentNodes(dstRuntime).size() == 1 - && (dstRuntime.getReaction().index == 0)) { - dstRuntime.dominating = srcRuntime; - } else { - dstRuntime.dominating = null; - } - } - dstRangePosition.increment(); - sendRangePosition.increment(); - sendRangeCount++; - if (sendRangeCount >= sendRange.width) { - // Reset to multicast. - sendRangeCount = 0; - sendRangePosition = sendRange.startMR(); - } - } + // If this seems to be a single dominating reaction, set it. + // If another upstream reaction shows up, then this will be + // reset to null. + if (this.getUpstreamAdjacentNodes(dstRuntime).size() == 1 + && (dstRuntime.getReaction().index == 0)) { + dstRuntime.dominating = srcRuntime; + } else { + dstRuntime.dominating = null; } + } + dstRangePosition.increment(); + sendRangePosition.increment(); + sendRangeCount++; + if (sendRangeCount >= sendRange.width) { + // Reset to multicast. + sendRangeCount = 0; + sendRangePosition = sendRange.startMR(); + } } + } } - - /** - * 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. - */ - 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); - } - - // If there is an earlier reaction in this same reactor, then - // create a link in the reaction graph for all runtime instances. - if (previousReaction != null) { - List previousRuntimes = previousReaction.getRuntimeInstances(); - int count = 0; - for (Runtime runtime : runtimes) { - // Only add the reaction order edge if previous reaction is outside of a mode or both are in the same mode - // This allows modes to break cycles since modes are always mutually exclusive. - if (runtime.getReaction().getMode(true) == null || runtime.getReaction().getMode(true) == reaction.getMode(true)) { - this.addEdge(runtime, previousRuntimes.get(count)); - count++; - } - } - } - previousReaction = reaction; - - - // Add downstream reactions. Note that this is sufficient. - // We don't need to also add upstream reactions because this reaction - // will be downstream of those upstream reactions. - for (TriggerInstance effect : reaction.effects) { - if (effect instanceof PortInstance) { - addDownstreamReactions((PortInstance)effect, reaction); - } - } + } + + /** + * 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. + */ + 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); + } + + // If there is an earlier reaction in this same reactor, then + // create a link in the reaction graph for all runtime instances. + if (previousReaction != null) { + List previousRuntimes = previousReaction.getRuntimeInstances(); + int count = 0; + for (Runtime runtime : runtimes) { + // Only add the reaction order edge if previous reaction is outside of a mode or both are + // in the same mode + // This allows modes to break cycles since modes are always mutually exclusive. + if (runtime.getReaction().getMode(true) == null + || runtime.getReaction().getMode(true) == reaction.getMode(true)) { + this.addEdge(runtime, previousRuntimes.get(count)); + count++; + } } - // Recursively add nodes and edges from contained reactors. - for (ReactorInstance child : reactor.children) { - addNodesAndEdges(child); + } + previousReaction = reaction; + + // Add downstream reactions. Note that this is sufficient. + // We don't need to also add upstream reactions because this reaction + // will be downstream of those upstream reactions. + for (TriggerInstance effect : reaction.effects) { + if (effect instanceof PortInstance) { + addDownstreamReactions((PortInstance) effect, reaction); } + } + } + // Recursively add nodes and edges from contained reactors. + for (ReactorInstance child : reactor.children) { + addNodesAndEdges(child); + } + } + + /////////////////////////////////////////////////////////// + //// Private fields + + /** + * 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)); + + /////////////////////////////////////////////////////////// + //// Private methods + + /** + * 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 the levels of the reactions it depends on. + */ + private void assignLevels() { + List start = new ArrayList<>(rootNodes()); + + // All root nodes start with level 0. + for (Runtime origin : start) { + origin.level = 0; } - /////////////////////////////////////////////////////////// - //// Private fields - - /** - * 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)); - - /////////////////////////////////////////////////////////// - //// Private methods - - /** - * 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 - * the levels of the reactions it depends on. - */ - 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; + // the graph must be cyclic. + while (!start.isEmpty()) { + Runtime origin = start.remove(0); + Set toRemove = new LinkedHashSet<>(); + Set downstreamAdjacentNodes = getDownstreamAdjacentNodes(origin); + + // Visit effect nodes. + 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; + } + // Remove visited edges. + for (Runtime effect : toRemove) { + removeEdge(effect, origin); + // If the effect node has no more incoming edges, + // then move it in the start set. + if (getUpstreamAdjacentNodes(effect).isEmpty()) { + start.add(effect); } + } - // 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); - Set toRemove = new LinkedHashSet<>(); - Set downstreamAdjacentNodes = getDownstreamAdjacentNodes(origin); - - // Visit effect nodes. - for (Runtime effect : downstreamAdjacentNodes) { - // Stage edge between origin and effect for removal. - toRemove.add(effect); + // Remove visited origin. + removeNode(origin); - // Update level of downstream node. - effect.level = origin.level + 1; - } - // Remove visited edges. - for (Runtime effect : toRemove) { - removeEdge(effect, origin); - // If the effect node has no more incoming edges, - // then move it in the start set. - if (getUpstreamAdjacentNodes(effect).isEmpty()) { - start.add(effect); - } - } - - // Remove visited origin. - removeNode(origin); - - // Update numReactionsPerLevel info - adjustNumReactionsPerLevel(origin.level, 1); - } + // Update numReactionsPerLevel info + adjustNumReactionsPerLevel(origin.level, 1); } - - /** - * This function assigns inferred deadlines to all the reactions in the graph. - * It is modeled after {@code 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); - Set toRemove = new LinkedHashSet<>(); - Set upstreamAdjacentNodes = getUpstreamAdjacentNodes(origin); - - // Visit effect nodes. - 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; - } - } - // Remove visited edges. - for (Runtime upstream : toRemove) { - removeEdge(origin, upstream); - // If the upstream node has no more outgoing edges, - // then move it in the start set. - if (getDownstreamAdjacentNodes(upstream).size() == 0) { - start.add(upstream); - } - } - - // Remove visited origin. - removeNode(origin); + } + + /** + * This function assigns inferred deadlines to all the reactions in the graph. It is modeled after + * {@code 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); + Set toRemove = new LinkedHashSet<>(); + Set upstreamAdjacentNodes = getUpstreamAdjacentNodes(origin); + + // Visit effect nodes. + 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; } - } - - /** - * Adjust {@link #numReactionsPerLevel} at index level by - * adding to the previously recorded number valueToAdd. - * If there is no previously recorded number for this level, then - * create one with index level and value valueToAdd. - * @param level The level. - * @param valueToAdd The value to add to the number of levels. - */ - private void adjustNumReactionsPerLevel(int level, int valueToAdd) { - if (numReactionsPerLevel.size() > level) { - numReactionsPerLevel.set(level, numReactionsPerLevel.get(level) + valueToAdd); - } else { - while (numReactionsPerLevel.size() < level) { - numReactionsPerLevel.add(0); - } - numReactionsPerLevel.add(valueToAdd); + } + // Remove visited edges. + for (Runtime upstream : toRemove) { + removeEdge(origin, upstream); + // If the upstream node has no more outgoing edges, + // then move it in the start set. + if (getDownstreamAdjacentNodes(upstream).size() == 0) { + start.add(upstream); } + } + + // Remove visited origin. + removeNode(origin); } + } + + /** + * Adjust {@link #numReactionsPerLevel} at index level by + * adding to the previously recorded number valueToAdd. + * If there is no previously recorded number for this level, then + * create one with index level and value valueToAdd. + * @param level The level. + * @param valueToAdd The value to add to the number of levels. + */ + private void adjustNumReactionsPerLevel(int level, int valueToAdd) { + if (numReactionsPerLevel.size() > level) { + numReactionsPerLevel.set(level, numReactionsPerLevel.get(level) + valueToAdd); + } else { + while (numReactionsPerLevel.size() < level) { + numReactionsPerLevel.add(0); + } + numReactionsPerLevel.add(valueToAdd); + } + } - /** - * Return the DOT (GraphViz) representation of the graph. - */ - @Override - public String toDOT() { - var dotRepresentation = new CodeBuilder(); - var edges = new StringBuilder(); + /** Return the DOT (GraphViz) representation of the graph. */ + @Override + public String toDOT() { + var dotRepresentation = new CodeBuilder(); + var edges = new StringBuilder(); - // Start the digraph with a left-write rank - dotRepresentation.pr( + // Start the digraph with a left-write rank + dotRepresentation.pr( """ digraph { rankdir=LF; @@ -430,49 +419,52 @@ public String toDOT() { edge [fontname=Times]; """); - var nodes = nodes(); - // Group nodes by levels - var groupedNodes = - nodes.stream() - .collect( - Collectors.groupingBy(it -> it.level) - ); - - dotRepresentation.indent(); - // For each level - for (var level : groupedNodes.keySet()) { - // Create a subgraph - dotRepresentation.pr("subgraph cluster_level_" + level + " {"); - dotRepresentation.pr(" graph [compound=True, label = \"level " + level + "\"];"); - - // Get the nodes at the current level - var currentLevelNodes = groupedNodes.get(level); - for (var node: currentLevelNodes) { - // Draw the node - var label = CUtil.getName(node.getReaction().getParent().tpr) + "." + node.getReaction().getName(); - // Need a positive number to name the nodes in GraphViz - var labelHashCode = label.hashCode() & 0xfffffff; - dotRepresentation.pr(" node_" + labelHashCode + " [label=\""+ label +"\"];"); - - // Draw the edges - var downstreamNodes = getDownstreamAdjacentNodes(node); - for (var downstreamNode: downstreamNodes) { - var downstreamLabel = CUtil.getName(downstreamNode.getReaction().getParent().tpr) + "." + downstreamNode.getReaction().getName(); - edges.append(" node_" + labelHashCode + " -> node_" + - (downstreamLabel.hashCode() & 0xfffffff) + ";\n" - ); - } - } - // Close the subgraph - dotRepresentation.pr("}"); + var nodes = nodes(); + // Group nodes by levels + var groupedNodes = nodes.stream().collect(Collectors.groupingBy(it -> it.level)); + + dotRepresentation.indent(); + // For each level + for (var level : groupedNodes.keySet()) { + // Create a subgraph + dotRepresentation.pr("subgraph cluster_level_" + level + " {"); + dotRepresentation.pr(" graph [compound=True, label = \"level " + level + "\"];"); + + // Get the nodes at the current level + var currentLevelNodes = groupedNodes.get(level); + for (var node : currentLevelNodes) { + // Draw the node + var label = + CUtil.getName(node.getReaction().getParent().tpr) + "." + node.getReaction().getName(); + // Need a positive number to name the nodes in GraphViz + var labelHashCode = label.hashCode() & 0xfffffff; + dotRepresentation.pr(" node_" + labelHashCode + " [label=\"" + label + "\"];"); + + // Draw the edges + var downstreamNodes = getDownstreamAdjacentNodes(node); + for (var downstreamNode : downstreamNodes) { + var downstreamLabel = + CUtil.getName(downstreamNode.getReaction().getParent().tpr) + + "." + + downstreamNode.getReaction().getName(); + edges.append( + " node_" + + labelHashCode + + " -> node_" + + (downstreamLabel.hashCode() & 0xfffffff) + + ";\n"); } - dotRepresentation.unindent(); - // Add the edges to the definition of the graph at the bottom - dotRepresentation.pr(edges); - // Close the digraph - dotRepresentation.pr("}"); - - // Return the DOT representation - return dotRepresentation.toString(); + } + // Close the subgraph + dotRepresentation.pr("}"); } + dotRepresentation.unindent(); + // Add the edges to the definition of the graph at the bottom + dotRepresentation.pr(edges); + // Close the digraph + dotRepresentation.pr("}"); + + // Return the DOT representation + return dotRepresentation.toString(); + } } diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 1a62f45a5c..0b54779a36 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -1,28 +1,28 @@ /** 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; @@ -37,11 +37,9 @@ 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.ast.ASTUtils; -import org.lflang.AttributeUtils; import org.lflang.ErrorReporter; import org.lflang.TimeValue; +import org.lflang.ast.ASTUtils; import org.lflang.generator.TriggerInstance.BuiltinTriggerVariable; import org.lflang.generator.c.TypeParameterizedReactor; import org.lflang.lf.Action; @@ -68,1108 +66,1079 @@ 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. + * Representation of a compile-time instance of a reactor. If the reactor is instantiated as a bank + * of reactors, or if any of its parents is instantiated as a bank of reactors, then one instance of + * this ReactorInstance class represents all the runtime instances within these banks. The {@link + * #getTotalWidth()} method returns the number of such runtime instances, which is the product of + * the bank width of this reactor instance and the bank widths of all of its parents. There is + * exactly one instance of this ReactorInstance class for each graphical rendition of a reactor in + * the diagram view. * - * For the main reactor, which has no parent, once constructed, - * this object represents the entire Lingua Franca program. - * If the program has causality loops (a programming error), then - * {@link #hasCycles()} will return true and {@link #getCycles()} will - * return the ports and reaction instances involved in the cycles. + *

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

This method uses a variant of Kahn's algorithm, which is linear in V + E, where V is the + * number of vertices (reactions) and E is the number of edges (dependencies between reactions). + * + * @return An empty graph if successful and otherwise a graph with runtime reaction instances that + * form cycles. + */ + public ReactionInstanceGraph assignLevels() { + if (depth != 0) return root().assignLevels(); + if (cachedReactionLoopGraph == null) { + cachedReactionLoopGraph = new ReactionInstanceGraph(this); } - - /** - * Create a new instantiation hierarchy that starts with the given top-level reactor - * but only creates contained reactors up to the specified depth. - * @param reactor The top-level reactor. - * @param reporter The error reporter. - * @param desiredDepth The depth to which to go, or -1 to construct the full hierarchy. - */ - public ReactorInstance(Reactor reactor, ErrorReporter reporter, int desiredDepth) { - this(ASTUtils.createInstantiation(reactor), null, reporter, desiredDepth); + return cachedReactionLoopGraph; + } + + /** + * This function assigns/propagates deadlines through the Reaction Instance Graph. It performs + * Kahn's algorithm in reverse, starting from the leaf nodes and propagates deadlines upstream. To + * reduce cost, it should only be invoked when there are user-specified deadlines in the program. + * + * @return + */ + public ReactionInstanceGraph assignDeadlines() { + if (depth != 0) return root().assignDeadlines(); + if (cachedReactionLoopGraph == null) { + cachedReactionLoopGraph = new ReactionInstanceGraph(this); } - - /** - * Create a new instantiation with the specified parent. - * This constructor is here to allow for unit tests. - * It should not be used for any other purpose. - * @param reactor The top-level reactor. - * @param parent The parent reactor instance. - * @param reporter The error reporter. - */ - public ReactorInstance(Reactor reactor, ReactorInstance parent, ErrorReporter reporter) { - this(ASTUtils.createInstantiation(reactor), parent, reporter, -1); + cachedReactionLoopGraph.rebuildAndAssignDeadlines(); + return cachedReactionLoopGraph; + } + + /** + * Return the instance of a child rector created by the specified definition or null if there is + * none. + * + * @param definition The definition of the child reactor ("new" statement). + */ + public ReactorInstance getChildReactorInstance(Instantiation definition) { + for (ReactorInstance child : this.children) { + if (child.definition == definition) { + return child; + } } - - ////////////////////////////////////////////////////// - //// Public fields. - - /** The action instances belonging to this reactor instance. */ - public List actions = new ArrayList<>(); - - /** - * The contained reactor instances, in order of declaration. - * For banks of reactors, this includes both the bank definition - * Reactor (which has bankIndex == -2) followed by each of the - * bank members (which have bankIndex >= 0). - */ - public final List children = new ArrayList<>(); - - /** The input port instances belonging to this reactor instance. */ - public final List inputs = new ArrayList<>(); - - /** The output port instances belonging to this reactor instance. */ - public final List outputs = new ArrayList<>(); - - /** The parameters of this instance. */ - public final List parameters = new ArrayList<>(); - - /** List of reaction instances for this reactor instance. */ - public final List reactions = new ArrayList<>(); - - /** 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 TypeParameterizedReactor tpr; - - ////////////////////////////////////////////////////// - //// 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; - } - - /** - * 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 {@code _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()) { - if (reactorDefinition.isFederated() && !super.uniqueID().startsWith("federate__")) return "federate__" + super.uniqueID() + "_main"; - 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 {@code _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()) { + if (reactorDefinition.isFederated() && !super.uniqueID().startsWith("federate__")) + return "federate__" + super.uniqueID() + "_main"; + 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; + 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 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; + } + + /** + * 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; + } } - - /** - * 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 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 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) { + // Create the reaction instance. + var reactionInstance = new ReactionInstance(reaction, this, count++); + // Add the reaction instance to the map of reactions for this + // reactor. + this.reactions.add(reactionInstance); + } } - - /** - * 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; + } + + /** 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)); + } + + /** 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 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; - } + } + + //////////////////////////////////////// + //// 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. + */ + public ReactorInstance( + Instantiation definition, ReactorInstance parent, ErrorReporter reporter, int desiredDepth) { + super(definition, parent); + this.reporter = reporter; + this.reactorDeclaration = definition.getReactorClass(); + this.reactorDefinition = ASTUtils.toDefinition(reactorDeclaration); + this.tpr = new TypeParameterizedReactor(definition); + + // 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 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; + this.recursive = foundSelfAsParent; + if (recursive) { + reporter.reportError(definition, "Recursive reactor instantiation."); } - /** 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; + // If the reactor definition is null, give up here. Otherwise, diagram generation + // will fail an NPE. + if (reactorDefinition == null) { + reporter.reportError(definition, "Reactor instantiation has no matching reactor definition."); + return; } - /** - * Return a descriptive string. - */ - @Override - public String toString() { - return "ReactorInstance " + getFullName(); - } + setInitialWidth(); - /** - * 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); + // Apply overrides and instantiate parameters for this reactor instance. + for (Parameter parameter : ASTUtils.allParameters(reactorDefinition)) { + this.parameters.add(new ParameterInstance(parameter, this)); } - ////////////////////////////////////////////////////// - //// 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 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) { - // Create the reaction instance. - var reactionInstance = new ReactionInstance(reaction, this, count++); - // Add the reaction instance to the map of reactions for this - // reactor. - this.reactions.add(reactionInstance); - } - } + // Instantiate inputs for this reactor instance + for (Input inputDecl : ASTUtils.allInputs(reactorDefinition)) { + this.inputs.add(new PortInstance(inputDecl, this, reporter)); } - /** - * 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 outputs for this reactor instance + for (Output outputDecl : ASTUtils.allOutputs(reactorDefinition)) { + this.outputs.add(new PortInstance(outputDecl, this, reporter)); } - /** 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); - } - } + // 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 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. - */ - public ReactorInstance( - Instantiation definition, - ReactorInstance parent, - ErrorReporter reporter, - int desiredDepth) { - super(definition, parent); - this.reporter = reporter; - this.reactorDeclaration = definition.getReactorClass(); - this.reactorDefinition = ASTUtils.toDefinition(reactorDeclaration); - this.tpr = new TypeParameterizedReactor(definition); - - // 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)); + } + + public TypeParameterizedReactor getTypeParameterizedReactor() { + return this.tpr; + } + + ////////////////////////////////////////////////////// + //// 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."); } - - // 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)); + 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 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)); + 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(); } + } } - - public TypeParameterizedReactor getTypeParameterizedReactor() { - return this.tpr; - } - - ////////////////////////////////////////////////////// - //// 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 {@code interleaved(b.m)}, where {@code b} is a bank + * and {@code m} is a multiport, then the corresponding range in the returned list is marked + * interleaved. + * + *

For example, if {@code b} and {@code m} have width 2, without the interleaved keyword, the + * returned range represents the sequence {@code [b0.m0, b0.m1, b1.m0, b1.m1]}. With the + * interleaved marking, the returned range represents the sequence {@code [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 {@code interleaved(b.m)}, where {@code b} is - * a bank and {@code m} is a multiport, then the corresponding range in the returned - * list is marked interleaved. - * - * For example, if {@code b} and {@code m} have width 2, without the interleaved keyword, - * the returned range represents the sequence {@code [b0.m0, b0.m1, b1.m0, b1.m1]}. - * With the interleaved marking, the returned range represents the sequence - * {@code [b0.m0, b1.m0, b0.m1, b1.m1]}. Both ranges will have width 4. - * - * @param references The variable references on one side of the connection. - * @param connection The connection. - */ - private List> listPortInstances( - List references, Connection connection - ) { - List> result = new ArrayList<>(); - List> tails = new LinkedList<>(); - int count = 0; - for (VarRef portRef : references) { - // Simple error checking first. - if (!(portRef.getVariable() instanceof Port)) { - reporter.reportError(portRef, "Not a port."); - return result; - } - // First, figure out which reactor we are dealing with. - // The reactor we want is the container of the port. - // If the port reference has no container, then the reactor is this one. - var reactor = this; - if (portRef.getContainer() != null) { - reactor = getChildReactorInstance(portRef.getContainer()); - } - // The reactor can be null only if there is an error in the code. - // Skip this portRef so that diagram synthesis can complete. - if (reactor != null) { - PortInstance portInstance = reactor.lookupPortInstance( - (Port) portRef.getVariable()); - - Set interleaved = new LinkedHashSet<>(); - if (portRef.isInterleaved()) { - // NOTE: Here, we are assuming that the interleaved() - // keyword is only allowed on the multiports contained by - // contained reactors. - interleaved.add(portInstance.parent); - } - RuntimeRange range = new RuntimeRange.Port( - portInstance, interleaved); - // If this portRef is not the last one in the references list - // then we have to check whether the range can be incremented at - // the lowest two levels (port and container). If not, - // split the range and add the tail to list to iterate over again. - // The reason for this is that the connection has only local visibility, - // but the range width may be reflective of bank structure higher - // in the hierarchy. - if (count < references.size() - 1) { - int portWidth = portInstance.width; - int portParentWidth = portInstance.parent.width; - // If the port is being connected on the inside and there is - // more than one port in the list, then we can only connect one - // bank member at a time. - if (reactor == this && references.size() > 1) { - portParentWidth = 1; - } - int widthBound = portWidth * portParentWidth; - - // If either of these widths cannot be determined, assume infinite. - if (portWidth < 0) widthBound = Integer.MAX_VALUE; - if (portParentWidth < 0) widthBound = Integer.MAX_VALUE; - - if (widthBound < range.width) { - // Need to split the range. - tails.add(range.tail(widthBound)); - range = range.head(widthBound); - } - } - result.add(range); - } + } + // First, figure out which reactor we are dealing with. + // The reactor we want is the container of the port. + // If the port reference has no container, then the reactor is this one. + var reactor = this; + if (portRef.getContainer() != null) { + reactor = getChildReactorInstance(portRef.getContainer()); + } + // The reactor can be null only if there is an error in the code. + // Skip this portRef so that diagram synthesis can complete. + if (reactor != null) { + PortInstance portInstance = reactor.lookupPortInstance((Port) portRef.getVariable()); + + Set interleaved = new LinkedHashSet<>(); + if (portRef.isInterleaved()) { + // NOTE: Here, we are assuming that the interleaved() + // keyword is only allowed on the multiports contained by + // contained reactors. + interleaved.add(portInstance.parent); } - // Iterate over the tails. - while(tails.size() > 0) { - List> moreTails = new LinkedList<>(); - count = 0; - for (RuntimeRange tail : tails) { - if (count < tails.size() - 1) { - int widthBound = tail.instance.width; - if (tail._interleaved.contains(tail.instance.parent)) { - widthBound = tail.instance.parent.width; - } - // If the width cannot be determined, assume infinite. - if (widthBound < 0) widthBound = Integer.MAX_VALUE; - - if (widthBound < tail.width) { - // Need to split the range again - moreTails.add(tail.tail(widthBound)); - tail = tail.head(widthBound); - } - } - result.add(tail); - } - tails = moreTails; + RuntimeRange range = new RuntimeRange.Port(portInstance, interleaved); + // If this portRef is not the last one in the references list + // then we have to check whether the range can be incremented at + // the lowest two levels (port and container). If not, + // split the range and add the tail to list to iterate over again. + // The reason for this is that the connection has only local visibility, + // but the range width may be reflective of bank structure higher + // in the hierarchy. + if (count < references.size() - 1) { + int portWidth = portInstance.width; + int portParentWidth = portInstance.parent.width; + // If the port is being connected on the inside and there is + // more than one port in the list, then we can only connect one + // bank member at a time. + if (reactor == this && references.size() > 1) { + portParentWidth = 1; + } + int widthBound = portWidth * portParentWidth; + + // If either of these widths cannot be determined, assume infinite. + if (portWidth < 0) widthBound = Integer.MAX_VALUE; + if (portParentWidth < 0) widthBound = Integer.MAX_VALUE; + + if (widthBound < range.width) { + // Need to split the range. + tails.add(range.tail(widthBound)); + range = range.head(widthBound); + } } - return result; + result.add(range); + } } - - /** - * If this is a bank of reactors, set the width. - * It will be set to -1 if it cannot be determined. - */ - private void setInitialWidth() { - WidthSpec widthSpec = definition.getWidthSpec(); - if (widthSpec != null) { - // We need the instantiations list of the containing reactor, - // not this one. - width = ASTUtils.width(widthSpec, parent.instantiations()); + // Iterate over the tails. + while (tails.size() > 0) { + List> moreTails = new LinkedList<>(); + count = 0; + for (RuntimeRange tail : tails) { + if (count < tails.size() - 1) { + int widthBound = tail.instance.width; + if (tail._interleaved.contains(tail.instance.parent)) { + widthBound = tail.instance.parent.width; + } + // If the width cannot be determined, assume infinite. + if (widthBound < 0) widthBound = Integer.MAX_VALUE; + + if (widthBound < tail.width) { + // Need to split the range again + moreTails.add(tail.tail(widthBound)); + tail = tail.head(widthBound); + } } + result.add(tail); + } + tails = moreTails; } - - ////////////////////////////////////////////////////// - //// Private fields. - - /** - * Cached set of reactions and ports that form a causality loop. - */ - private Set> cachedCycles; - - /** - * Cached reaction graph containing reactions that form a causality loop. - */ - private ReactionInstanceGraph cachedReactionLoopGraph = null; - - /** - * Return true if this is a generated delay reactor that originates from - * an "after" delay on a connection. - * - * @return True if this is a generated delay, false otherwise. - */ - public boolean isGeneratedDelay() { - // FIXME: hacky string matching again... - return this.definition.getReactorClass().getName().contains(DelayBodyGenerator.GEN_DELAY_CLASS_NAME); + 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() { + // FIXME: hacky string matching again... + return this.definition + .getReactorClass() + .getName() + .contains(DelayBodyGenerator.GEN_DELAY_CLASS_NAME); + } } diff --git a/org.lflang/src/org/lflang/generator/RuntimeRange.java b/org.lflang/src/org/lflang/generator/RuntimeRange.java index 36f132b27e..d684a41bd9 100644 --- a/org.lflang/src/org/lflang/generator/RuntimeRange.java +++ b/org.lflang/src/org/lflang/generator/RuntimeRange.java @@ -32,478 +32,419 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.Set; /** - * Class representing a range of runtime instance objects - * (port channels, reactors, reactions, etc.). This class and its derived classes - * have the most detailed information about the structure of a Lingua Franca - * program. There are three levels of detail: + * Class representing a range of runtime instance objects (port channels, reactors, reactions, + * etc.). This class and its derived classes have the most detailed information about the structure + * of a Lingua Franca program. There are three levels of detail: + * *

    - *
  • The abstract syntax tree (AST).
  • - *
  • The compile-time instance graph (CIG).
  • - *
  • The runtime instance graph (RIG).
  • + *
  • The abstract syntax tree (AST). + *
  • The compile-time instance graph (CIG). + *
  • The runtime instance graph (RIG). *
- * - * In the AST, each reactor class is represented once. - * In the CIG, each reactor class is represented as many times as it is - * instantiated, except that a bank has only one representation (as - * in the graphical rendition). Equivalently, each CIG node has a unique - * full name, even though it may represent many runtime instances. - * The CIG is represented by - * {@link NamedInstance} and its derived classes. - * In the RIG, each bank is expanded so each bank member and - * each port channel is represented. - * - * In general, determining dependencies between reactions requires analysis - * at the level of the RIG. But a brute-force representation of the RIG - * can get very large, and for most programs it has a great deal of - * redundancy. In a fully detailed representation of the RIG, for example, - * a bank of width N that contains a bank of width M within which there - * is a reactor R with port P will have N*M runtime instances of the port. - * If the port is a multiport with width L, then there are N*M*L - * edges connected to instances of that port, each of which may go - * to a distinct set of other remote runtime port instances. - * - * This class and its subclasses give a more compact representation of the - * RIG in most common cases where collections of runtime instances all have - * the same dependencies or dependencies form a range that can be represented - * compactly. - * - * A RuntimeRange represents an adjacent set of RIG objects (port channels, reactions - * reactors). For example, it can represent port channels 2 through 5 in a multiport - * of width 10. The width in this case is 4. If such a port is - * contained by one or more banks of reactors, then channels 2 through 5 - * of one bank member form a contiguous range. If you want channels 2 through 5 - * of all bank members, then this needs to be represented with multiple ranges. - * - * The maxWidth is the width of the instance multiplied by the widths of - * each of its containers. For example, if a port of width 4 is contained by - * a bank of width 2 that is then contained by a bank of width 3, then - * the maxWidth will be 2*3*4 = 24. - * - * The function iterationOrder returns a list that includes the instance - * of this range and all its containers, except the top-level reactor (main - * or federated). The order of this list is the order in which an - * iteration over the RIG objects represented by this range should be - * iterated. If the instance is a PortInstance, then this order will - * depend on whether connections at any level of the hierarchy are - * interleaved. - * - * The simplest Ranges are those where the corresponding CIG node represents - * only one runtime instance (its instance is not (deeply) within a bank - * and is not a multiport). In this case, the RuntimeRange and all the objects - * returned by iterationOrder will have width 1. - * - * In a more complex instance, consider a bank A of width 2 that contains a - * bank B of width 2 that contains a port instance P with width 2. . - * There are a total of 8 instances of P, which we can name: - * - * A0.B0.P0 - * A0.B0.P1 - * A0.B1.P0 - * A0.B1.P1 - * A1.B0.P0 - * A1.B0.P1 - * A1.B1.P0 - * A1.B1.P1 - * - * If there is no interleaving, iterationOrder() returns [P, B, A], - * indicating that they should be iterated by incrementing the index of P - * first, then the index of B, then the index of A, as done above. - * - * If the connection within B to port P is interleaved, then the order - * of iteration order will be [B, P, A], resulting in the list: - * - * A0.B0.P0 - * A0.B1.P0 - * A0.B0.P1 - * A0.B1.P1 - * A1.B0.P0 - * A1.B1.P0 - * A1.B0.P1 - * A1.B1.P1 - * - * If the connection within A to B is also interleaved, then the order - * will be [A, B, P], resulting in the list: - * - * A0.B0.P0 - * A1.B0.P0 - * A0.B1.P0 - * A1.B1.P0 - * A0.B0.P1 - * A1.B0.P1 - * A0.B1.P1 - * A1.B1.P1 - * - * Finally, if the connection within A to B is interleaved, but not the - * connection within B to P, then the order will be [A, P, B], resulting in - * the list: - * - * A0.B0.P0 - * A1.B0.P0 - * A0.B0.P1 - * A1.B0.P1 - * A0.B1.P0 - * A1.B1.P0 - * A0.B1.P1 - * A1.B1.P1 - * - * A RuntimeRange is a contiguous subset of one of the above lists, given by - * a start offset and a width that is less than or equal to maxWidth. - * - * Each element of the above lists can be represented as a permuted mixed-radix (PMR) number, - * where the low-order digit has radix equal to the width of P, the second digit - * has radix equal to the width of B, and the final digit has radix equal to the - * width of A. Each PMR has a permutation vector that defines how to increment - * PMR number. This permutation vector is derived from the iteration order as - * follows. When there is no interleaving, the iteration order is [P, B, A], - * and the permutation vector is [0, 1, 2]. When there is interleaving, the permutation - * vector simply specifies the iteration order. For example, if the iteration order - * is [A, P, B], then the permutation vector is [2, 0, 1], indicating that digit 2 - * of the PMR (corresponding to A) should be incremented first, then digit 0 (for P), - * then digit 1 (for B). - * - * For a RuntimeRange with width greater than 1, - * the head() and tail() functions split the range. - * - * This class and subclasses are designed to be immutable. - * Modifications always return a new RuntimeRange. + * + * In the AST, each reactor class is represented once. In the CIG, each reactor class is represented + * as many times as it is instantiated, except that a bank has only one representation (as in the + * graphical rendition). Equivalently, each CIG node has a unique full name, even though it may + * represent many runtime instances. The CIG is represented by {@link NamedInstance} and its derived + * classes. In the RIG, each bank is expanded so each bank member and each port channel is + * represented. + * + *

In general, determining dependencies between reactions requires analysis at the level of the + * RIG. But a brute-force representation of the RIG can get very large, and for most programs it has + * a great deal of redundancy. In a fully detailed representation of the RIG, for example, a bank of + * width N that contains a bank of width M within which there is a reactor R with port P will have + * N*M runtime instances of the port. If the port is a multiport with width L, then there are N*M*L + * edges connected to instances of that port, each of which may go to a distinct set of other remote + * runtime port instances. + * + *

This class and its subclasses give a more compact representation of the RIG in most common + * cases where collections of runtime instances all have the same dependencies or dependencies form + * a range that can be represented compactly. + * + *

A RuntimeRange represents an adjacent set of RIG objects (port channels, reactions reactors). + * For example, it can represent port channels 2 through 5 in a multiport of width 10. The width in + * this case is 4. If such a port is contained by one or more banks of reactors, then channels 2 + * through 5 of one bank member form a contiguous range. If you want channels 2 through 5 of all + * bank members, then this needs to be represented with multiple ranges. + * + *

The maxWidth is the width of the instance multiplied by the widths of each of its containers. + * For example, if a port of width 4 is contained by a bank of width 2 that is then contained by a + * bank of width 3, then the maxWidth will be 2*3*4 = 24. + * + *

The function iterationOrder returns a list that includes the instance of this range and all + * its containers, except the top-level reactor (main or federated). The order of this list is the + * order in which an iteration over the RIG objects represented by this range should be iterated. If + * the instance is a PortInstance, then this order will depend on whether connections at any level + * of the hierarchy are interleaved. + * + *

The simplest Ranges are those where the corresponding CIG node represents only one runtime + * instance (its instance is not (deeply) within a bank and is not a multiport). In this case, the + * RuntimeRange and all the objects returned by iterationOrder will have width 1. + * + *

In a more complex instance, consider a bank A of width 2 that contains a bank B of width 2 + * that contains a port instance P with width 2. . There are a total of 8 instances of P, which we + * can name: + * + *

A0.B0.P0 A0.B0.P1 A0.B1.P0 A0.B1.P1 A1.B0.P0 A1.B0.P1 A1.B1.P0 A1.B1.P1 + * + *

If there is no interleaving, iterationOrder() returns [P, B, A], indicating that they should + * be iterated by incrementing the index of P first, then the index of B, then the index of A, as + * done above. + * + *

If the connection within B to port P is interleaved, then the order of iteration order will be + * [B, P, A], resulting in the list: + * + *

A0.B0.P0 A0.B1.P0 A0.B0.P1 A0.B1.P1 A1.B0.P0 A1.B1.P0 A1.B0.P1 A1.B1.P1 + * + *

If the connection within A to B is also interleaved, then the order will be [A, B, P], + * resulting in the list: + * + *

A0.B0.P0 A1.B0.P0 A0.B1.P0 A1.B1.P0 A0.B0.P1 A1.B0.P1 A0.B1.P1 A1.B1.P1 + * + *

Finally, if the connection within A to B is interleaved, but not the connection within B to P, + * then the order will be [A, P, B], resulting in the list: + * + *

A0.B0.P0 A1.B0.P0 A0.B0.P1 A1.B0.P1 A0.B1.P0 A1.B1.P0 A0.B1.P1 A1.B1.P1 + * + *

A RuntimeRange is a contiguous subset of one of the above lists, given by a start offset and a + * width that is less than or equal to maxWidth. + * + *

Each element of the above lists can be represented as a permuted mixed-radix (PMR) number, + * where the low-order digit has radix equal to the width of P, the second digit has radix equal to + * the width of B, and the final digit has radix equal to the width of A. Each PMR has a permutation + * vector that defines how to increment PMR number. This permutation vector is derived from the + * iteration order as follows. When there is no interleaving, the iteration order is [P, B, A], and + * the permutation vector is [0, 1, 2]. When there is interleaving, the permutation vector simply + * specifies the iteration order. For example, if the iteration order is [A, P, B], then the + * permutation vector is [2, 0, 1], indicating that digit 2 of the PMR (corresponding to A) should + * be incremented first, then digit 0 (for P), then digit 1 (for B). + * + *

For a RuntimeRange with width greater than 1, the head() and tail() functions split the range. + * + *

This class and subclasses are designed to be immutable. Modifications always return a new + * RuntimeRange. * * @author Edward A. Lee */ public class RuntimeRange> implements Comparable> { - - /** - * Create a new range representing the full width of the specified instance - * with no interleaving. The instances will be a list with the specified instance - * first, its parent next, and on up the hierarchy until the depth 1 parent (the - * top-level reactor is not included because it can never be a bank). - * @param instance The instance. - * @param interleaved A list of parents that are interleaved or null if none. - */ - public RuntimeRange( - T instance, - Set interleaved - ) { - this(instance, 0, 0, interleaved); - } - /** - * Create a new range representing a range of the specified instance - * with no interleaving. The instances will be a list with the specified instance - * first, its parent next, and on up the hierarchy until the depth 1 parent (the - * top-level reactor is not included because it can never be a bank). - * @param instance The instance over which this is a range (port, reaction, etc.) - * @param start The starting index for the range. - * @param width The width of the range or 0 to specify the maximum possible width. - * @param interleaved A list of parents that are interleaved or null if none. - */ - public RuntimeRange( - T instance, - int start, - int width, - Set interleaved - ) { - this.instance = instance; - this.start = start; - if (interleaved != null) { - this._interleaved.addAll(interleaved); - } - - int maxWidth = instance.width; // Initial value. - NamedInstance parent = instance.parent; - while (parent.depth > 0) { - maxWidth *= parent.width; - parent = parent.parent; - } - this.maxWidth = maxWidth; - - if (width > 0 && width + start < maxWidth) { - this.width = width; - } else { - this.width = maxWidth - start; - } - } - - ////////////////////////////////////////////////////////// - //// Public variables - - /** The instance that this is a range of. */ - public final T instance; - - /** The start offset of this range. */ - public final int start; - - /** The maximum width of any range with this instance. */ - public final int maxWidth; - - /** The width of this range. */ - public final int width; - - ////////////////////////////////////////////////////////// - //// Public methods - - /** - * Compare ranges by first comparing their start offset, and then, - * if these are equal, comparing their widths. This comparison is - * meaningful only for ranges that have the same instances. - * Note that this can return 0 even if equals() does not return true. - */ - @Override - public int compareTo(RuntimeRange o) { - if (start < o.start) { - return -1; - } else if (start == o.start) { - return Integer.compare(width, o.width); - } else { - return 1; - } + /** + * Create a new range representing the full width of the specified instance with no interleaving. + * The instances will be a list with the specified instance first, its parent next, and on up the + * hierarchy until the depth 1 parent (the top-level reactor is not included because it can never + * be a bank). + * + * @param instance The instance. + * @param interleaved A list of parents that are interleaved or null if none. + */ + public RuntimeRange(T instance, Set interleaved) { + this(instance, 0, 0, interleaved); + } + + /** + * Create a new range representing a range of the specified instance with no interleaving. The + * instances will be a list with the specified instance first, its parent next, and on up the + * hierarchy until the depth 1 parent (the top-level reactor is not included because it can never + * be a bank). + * + * @param instance The instance over which this is a range (port, reaction, etc.) + * @param start The starting index for the range. + * @param width The width of the range or 0 to specify the maximum possible width. + * @param interleaved A list of parents that are interleaved or null if none. + */ + public RuntimeRange(T instance, int start, int width, Set interleaved) { + this.instance = instance; + this.start = start; + if (interleaved != null) { + this._interleaved.addAll(interleaved); } - - /** - * Return a new RuntimeRange that is identical to this range but - * with width reduced to the specified width. - * If the new width is greater than or equal to the width - * of this range, then return this range. - * If the newWidth is 0 or negative, return null. - * @param newWidth The new width. - */ - public RuntimeRange head(int newWidth) { - if (newWidth >= width) return this; - if (newWidth <= 0) return null; - return new RuntimeRange<>(instance, start, newWidth, _interleaved); + + int maxWidth = instance.width; // Initial value. + NamedInstance parent = instance.parent; + while (parent.depth > 0) { + maxWidth *= parent.width; + parent = parent.parent; } - - /** - * Return the list of natural identifiers for the runtime instances - * in this range. Each returned identifier is an integer representation - * of the mixed-radix number [d0, ... , dn] with radices [w0, ... , wn], - * where d0 is the bank or channel index of this RuntimeRange's instance, which - * has width w0, and dn is the bank index of its topmost parent below the - * top-level (main) reactor, which has width wn. The depth of this RuntimeRange's - * instance, therefore, is n - 1. The order of the returned list is the order - * in which the runtime instances should be iterated. - */ - public List instances() { - List result = new ArrayList<>(width); - MixedRadixInt mr = startMR(); - int count = 0; - while (count++ < width) { - result.add(mr.get()); - mr.increment(); - } - return result; + this.maxWidth = maxWidth; + + if (width > 0 && width + start < maxWidth) { + this.width = width; + } else { + this.width = maxWidth - start; } - - /** - * Return a list containing the instance for this range and all of its - * parents, not including the top level reactor, in the order in which - * their banks and multiport channels should be iterated. - * For each depth at which the connection is interleaved, that parent - * will appear before this instance in depth order (shallower to deeper). - * For each depth at which the connection is not interleaved, that parent - * will appear after this instance in reverse depth order (deeper to - * shallower). - */ - public List> iterationOrder() { - ArrayList> result = new ArrayList<>(); - result.add(instance); - ReactorInstance parent = instance.parent; - while (parent.depth > 0) { - if (_interleaved.contains(parent)) { - // Put the parent at the head of the list. - result.add(0, parent); - } else { - result.add(parent); - } - parent = parent.parent; - } - return result; + } + + ////////////////////////////////////////////////////////// + //// Public variables + + /** The instance that this is a range of. */ + public final T instance; + + /** The start offset of this range. */ + public final int start; + + /** The maximum width of any range with this instance. */ + public final int maxWidth; + + /** The width of this range. */ + public final int width; + + ////////////////////////////////////////////////////////// + //// Public methods + + /** + * Compare ranges by first comparing their start offset, and then, if these are equal, comparing + * their widths. This comparison is meaningful only for ranges that have the same instances. Note + * that this can return 0 even if equals() does not return true. + */ + @Override + public int compareTo(RuntimeRange o) { + if (start < o.start) { + return -1; + } else if (start == o.start) { + return Integer.compare(width, o.width); + } else { + return 1; } - - /** - * Return a range that is the subset of this range that overlaps with the - * specified range or null if there is no overlap. - */ - public RuntimeRange overlap(RuntimeRange range) { - if (!overlaps(range)) return null; - int newStart = Math.max(start, range.start); - int newEnd = Math.min(start + width, range.start + range.width); - return tail(newStart - start).head(newEnd - newStart); + } + + /** + * Return a new RuntimeRange that is identical to this range but with width reduced to the + * specified width. If the new width is greater than or equal to the width of this range, then + * return this range. If the newWidth is 0 or negative, return null. + * + * @param newWidth The new width. + */ + public RuntimeRange head(int newWidth) { + if (newWidth >= width) return this; + if (newWidth <= 0) return null; + return new RuntimeRange<>(instance, start, newWidth, _interleaved); + } + + /** + * Return the list of natural identifiers for the runtime instances in this + * range. Each returned identifier is an integer representation of the mixed-radix number [d0, ... + * , dn] with radices [w0, ... , wn], where d0 is the bank or channel index of this RuntimeRange's + * instance, which has width w0, and dn is the bank index of its topmost parent below the + * top-level (main) reactor, which has width wn. The depth of this RuntimeRange's instance, + * therefore, is n - 1. The order of the returned list is the order in which the runtime instances + * should be iterated. + */ + public List instances() { + List result = new ArrayList<>(width); + MixedRadixInt mr = startMR(); + int count = 0; + while (count++ < width) { + result.add(mr.get()); + mr.increment(); } - - /** - * Return true if the specified range has the same instance as this range - * and the ranges overlap. - */ - public boolean overlaps(RuntimeRange range) { - if (!instance.equals(range.instance)) return false; - return start < range.start + range.width && start + width > range.start; + return result; + } + + /** + * Return a list containing the instance for this range and all of its parents, not including the + * top level reactor, in the order in which their banks and multiport channels should be iterated. + * For each depth at which the connection is interleaved, that parent will appear before this + * instance in depth order (shallower to deeper). For each depth at which the connection is not + * interleaved, that parent will appear after this instance in reverse depth order (deeper to + * shallower). + */ + public List> iterationOrder() { + ArrayList> result = new ArrayList<>(); + result.add(instance); + ReactorInstance parent = instance.parent; + while (parent.depth > 0) { + if (_interleaved.contains(parent)) { + // Put the parent at the head of the list. + result.add(0, parent); + } else { + result.add(parent); + } + parent = parent.parent; } - - /** - * Return a set of identifiers for runtime instances of a parent of this - * {@link RuntimeRange}'s instance {@code n} levels above this {@link RuntimeRange}'s instance. If {@code n == 1}, for - * example, then this return the identifiers for the parent ReactorInstance. - * - * This returns a list of natural identifiers, - * as defined below, for the instances within the range. - * - * The resulting list can be used to count the number of distinct - * runtime instances of this RuntimeRange's instance (using {@code n == 0}) or any of its parents that - * lie within the range and to provide an index into an array of runtime - * instances. - * - * Each natural identifier is the integer value of a mixed-radix number - * defined as follows: - *

    - *
  • The low-order digit is the index of the runtime instance of {@code i} within its container. - * If the {@link NamedInstance} is a {@code PortInstance}, this will be the multiport channel or {@code 0} if it is not a - * multiport. If the NamedInstance is a ReactorInstance, then it will be the bank - * index or {@code 0} if the reactor is not a bank. The radix for this digit will be - * the multiport width or bank width or 1 if the NamedInstance is neither a - * multiport nor a bank.
  • - *
  • The next digit will be the bank index of the container of the specified - * {@link NamedInstance} or {@code 0} if it is not a bank.
  • - *
  • The remaining digits will be bank indices of containers up to but not - * including the top-level reactor (there is no point in including the top-level - * reactor because it is never a bank).
  • - *
  • Each index that is returned can be used as an index into an array of - * runtime instances that is assumed to be in a natural order.
  • - *
- * - * @param n The number of levels up of the parent. This is required to be - * less than the depth of this RuntimeRange's instance or an exception will be thrown. - */ - public Set parentInstances(int n) { - Set result = new LinkedHashSet<>(width); - MixedRadixInt mr = startMR(); - int count = 0; - while (count++ < width) { - result.add(mr.get(n)); - mr.increment(); - } - return result; + return result; + } + + /** + * Return a range that is the subset of this range that overlaps with the specified range or null + * if there is no overlap. + */ + public RuntimeRange overlap(RuntimeRange range) { + if (!overlaps(range)) return null; + int newStart = Math.max(start, range.start); + int newEnd = Math.min(start + width, range.start + range.width); + return tail(newStart - start).head(newEnd - newStart); + } + + /** + * Return true if the specified range has the same instance as this range and the ranges overlap. + */ + public boolean overlaps(RuntimeRange range) { + if (!instance.equals(range.instance)) return false; + return start < range.start + range.width && start + width > range.start; + } + + /** + * Return a set of identifiers for runtime instances of a parent of this {@link RuntimeRange}'s + * instance {@code n} levels above this {@link RuntimeRange}'s instance. If {@code n == 1}, for + * example, then this return the identifiers for the parent ReactorInstance. + * + *

This returns a list of natural identifiers, as defined below, for the instances + * within the range. + * + *

The resulting list can be used to count the number of distinct runtime instances of this + * RuntimeRange's instance (using {@code n == 0}) or any of its parents that lie within the range + * and to provide an index into an array of runtime instances. + * + *

Each natural identifier is the integer value of a mixed-radix number defined as + * follows: + * + *

    + *
  • The low-order digit is the index of the runtime instance of {@code i} within its + * container. If the {@link NamedInstance} is a {@code PortInstance}, this will be the + * multiport channel or {@code 0} if it is not a multiport. If the NamedInstance is a + * ReactorInstance, then it will be the bank index or {@code 0} if the reactor is not a + * bank. The radix for this digit will be the multiport width or bank width or 1 if the + * NamedInstance is neither a multiport nor a bank. + *
  • The next digit will be the bank index of the container of the specified {@link + * NamedInstance} or {@code 0} if it is not a bank. + *
  • The remaining digits will be bank indices of containers up to but not including the + * top-level reactor (there is no point in including the top-level reactor because it is + * never a bank). + *
  • Each index that is returned can be used as an index into an array of runtime instances + * that is assumed to be in a natural order. + *
+ * + * @param n The number of levels up of the parent. This is required to be less than the depth of + * this RuntimeRange's instance or an exception will be thrown. + */ + public Set parentInstances(int n) { + Set result = new LinkedHashSet<>(width); + MixedRadixInt mr = startMR(); + int count = 0; + while (count++ < width) { + result.add(mr.get(n)); + mr.increment(); } + return result; + } - /** - * Return the nearest containing ReactorInstance for this instance. - * If this instance is a ReactorInstance, then return it. - * Otherwise, return its parent. - */ - public ReactorInstance parentReactor() { - if (instance instanceof ReactorInstance) { - return (ReactorInstance)instance; - } else { - return instance.getParent(); - } + /** + * Return the nearest containing ReactorInstance for this instance. If this instance is a + * ReactorInstance, then return it. Otherwise, return its parent. + */ + public ReactorInstance parentReactor() { + if (instance instanceof ReactorInstance) { + return (ReactorInstance) instance; + } else { + return instance.getParent(); } - - /** - * Return the permutation vector that indicates the order in which the digits - * of the permuted mixed-radix representations of indices in this range should - * be incremented. - */ - public List permutation() { - List result = new ArrayList<>(instance.depth); - // Populate the result with default zeros. - for (int i = 0; i < instance.depth; i++) { - result.add(0); - } - int count = 0; - for (NamedInstance i : iterationOrder()) { - result.set(count++, instance.depth - i.depth); - } - return result; + } + + /** + * Return the permutation vector that indicates the order in which the digits of the permuted + * mixed-radix representations of indices in this range should be incremented. + */ + public List permutation() { + List result = new ArrayList<>(instance.depth); + // Populate the result with default zeros. + for (int i = 0; i < instance.depth; i++) { + result.add(0); } - - /** - * Return the radixes vector containing the width of this instance followed - * by the widths of its containers, not including the top level, which always - * has radix 1 and value 0. - */ - public List radixes() { - List result = new ArrayList<>(instance.depth); - int width = instance.width; - // If the width cannot be determined, assume 1. - if (width < 0) width = 1; - result.add(width); - ReactorInstance p = instance.getParent(); - while (p != null && p.getDepth() > 0) { - width = p.getWidth(); - // If the width cannot be determined, assume 1. - if (width < 0) width = 1; - result.add(width); - p = p.getParent(); - } - return result; + int count = 0; + for (NamedInstance i : iterationOrder()) { + result.set(count++, instance.depth - i.depth); } + return result; + } - /** - * Return the start as a new permuted mixed-radix number. - * For any instance that is neither a multiport nor a bank, the - * corresponding digit will be 0. - */ - public MixedRadixInt startMR() { - MixedRadixInt result = new MixedRadixInt(null, radixes(), permutation()); - result.setMagnitude(start); - return result; + /** + * Return the radixes vector containing the width of this instance followed by the widths of its + * containers, not including the top level, which always has radix 1 and value 0. + */ + public List radixes() { + List result = new ArrayList<>(instance.depth); + int width = instance.width; + // If the width cannot be determined, assume 1. + if (width < 0) width = 1; + result.add(width); + ReactorInstance p = instance.getParent(); + while (p != null && p.getDepth() > 0) { + width = p.getWidth(); + // If the width cannot be determined, assume 1. + if (width < 0) width = 1; + result.add(width); + p = p.getParent(); } + return result; + } - /** - * Return a new range that represents the leftover elements - * starting at the specified offset relative to start. - * If start + offset is greater than or equal to the width, then this returns null. - * If this offset is 0 then this returns this range unmodified. - * @param offset The number of elements to consume. - */ - public RuntimeRange tail(int offset) { - if (offset == 0) return this; - if (offset >= width) return null; - return new RuntimeRange<>(instance, start + offset, width - offset, _interleaved); + /** + * Return the start as a new permuted mixed-radix number. For any instance that is neither a + * multiport nor a bank, the corresponding digit will be 0. + */ + public MixedRadixInt startMR() { + MixedRadixInt result = new MixedRadixInt(null, radixes(), permutation()); + result.setMagnitude(start); + return result; + } + + /** + * Return a new range that represents the leftover elements starting at the specified offset + * relative to start. If start + offset is greater than or equal to the width, then this returns + * null. If this offset is 0 then this returns this range unmodified. + * + * @param offset The number of elements to consume. + */ + public RuntimeRange tail(int offset) { + if (offset == 0) return this; + if (offset >= width) return null; + return new RuntimeRange<>(instance, start + offset, width - offset, _interleaved); + } + + /** + * Toggle the interleaved status of the specified reactor, which is assumed to be a parent of the + * instance of this range. If it was previously interleaved, make it not interleaved and vice + * versa. This returns a new RuntimeRange. + * + * @param reactor The parent reactor at which to toggle interleaving. + */ + public RuntimeRange toggleInterleaved(ReactorInstance reactor) { + Set newInterleaved = new HashSet<>(_interleaved); + if (_interleaved.contains(reactor)) { + newInterleaved.remove(reactor); + } else { + newInterleaved.add(reactor); } - - /** - * Toggle the interleaved status of the specified reactor, which is assumed - * to be a parent of the instance of this range. - * If it was previously interleaved, make it not interleaved - * and vice versa. This returns a new RuntimeRange. - * @param reactor The parent reactor at which to toggle interleaving. - */ - public RuntimeRange toggleInterleaved(ReactorInstance reactor) { - Set newInterleaved = new HashSet<>(_interleaved); - if (_interleaved.contains(reactor)) { - newInterleaved.remove(reactor); - } else { - newInterleaved.add(reactor); - } - return new RuntimeRange<>(instance, start, width, newInterleaved); + return new RuntimeRange<>(instance, start, width, newInterleaved); + } + + @Override + public String toString() { + return instance.getFullName() + "(" + start + "," + width + ")"; + } + + ////////////////////////////////////////////////////////// + //// Protected variables + + /** Record of which levels are interleaved. */ + Set _interleaved = new HashSet<>(); + + ////////////////////////////////////////////////////////// + //// Public inner classes + + /** Special case of RuntimeRange for PortInstance. */ + public static class Port extends RuntimeRange { + public Port(PortInstance instance) { + super(instance, null); } - - @Override - public String toString() { - return instance.getFullName() + "(" + start + "," + width + ")"; + + public Port(PortInstance instance, Set interleaved) { + super(instance, interleaved); } - - ////////////////////////////////////////////////////////// - //// Protected variables - - /** Record of which levels are interleaved. */ - Set _interleaved = new HashSet<>(); - - ////////////////////////////////////////////////////////// - //// Public inner classes - - /** - * Special case of RuntimeRange for PortInstance. - */ - public static class Port extends RuntimeRange { - public Port(PortInstance instance) { - super(instance, null); - } - public Port(PortInstance instance, Set interleaved) { - super(instance, interleaved); - } - public Port(PortInstance instance, int start, int width, Set interleaved) { - super(instance, start, width, interleaved); - } + + public Port(PortInstance instance, int start, int width, Set interleaved) { + super(instance, start, width, interleaved); } + } } diff --git a/org.lflang/src/org/lflang/generator/SendRange.java b/org.lflang/src/org/lflang/generator/SendRange.java index c6f6221b5f..5e7f406f1e 100644 --- a/org.lflang/src/org/lflang/generator/SendRange.java +++ b/org.lflang/src/org/lflang/generator/SendRange.java @@ -32,286 +32,277 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.List; import java.util.Map; import java.util.Set; - import org.lflang.lf.Connection; /** - * Class representing a range of a port that sources data - * together with a list of destination ranges of other ports that should all - * receive the same data sent in this range. - * All ranges in the destinations list have widths that are an integer - * multiple N of this range but not necessarily the same start offsets. - * - * This class and subclasses are designed to be immutable. - * Modifications always return a new RuntimeRange. + * Class representing a range of a port that sources data together with a list of destination ranges + * of other ports that should all receive the same data sent in this range. All ranges in the + * destinations list have widths that are an integer multiple N of this range but not necessarily + * the same start offsets. + * + *

This class and subclasses are designed to be immutable. Modifications always return a new + * RuntimeRange. * * @author Edward A. Lee -*/ + */ public class SendRange extends RuntimeRange.Port { - - /** - * Create a new send range. - * @param instance The instance over which this is a range of. - * @param start The starting index. - * @param width The width. - * @param interleaved A list of parents that are interleaved or null if none. - * @param connection The connection that establishes this send or null if not unique or none. - */ - public SendRange( - PortInstance instance, - int start, - int width, - Set interleaved, - Connection connection - ) { - super(instance, start, width, interleaved); - this.connection = connection; - } - /** - * Create a new send range representing sending from the specified - * src to the specified dst. This preserves the interleaved status - * of both the src and dst. - * @param src The source range. - * @param dst The destination range. - * @param interleaved A list of parents that are interleaved or null if none. - * @param connection The connection that establishes this send or null if not unique or none. - */ - public SendRange( - RuntimeRange src, - RuntimeRange dst, - Set interleaved, - Connection connection - ) { - super(src.instance, src.start, src.width, interleaved); - destinations.add(dst); - _interleaved.addAll(src._interleaved); - this.connection = connection; - } + /** + * Create a new send range. + * + * @param instance The instance over which this is a range of. + * @param start The starting index. + * @param width The width. + * @param interleaved A list of parents that are interleaved or null if none. + * @param connection The connection that establishes this send or null if not unique or none. + */ + public SendRange( + PortInstance instance, + int start, + int width, + Set interleaved, + Connection connection) { + super(instance, start, width, interleaved); + this.connection = connection; + } - ////////////////////////////////////////////////////////// - //// Public variables + /** + * Create a new send range representing sending from the specified src to the specified dst. This + * preserves the interleaved status of both the src and dst. + * + * @param src The source range. + * @param dst The destination range. + * @param interleaved A list of parents that are interleaved or null if none. + * @param connection The connection that establishes this send or null if not unique or none. + */ + public SendRange( + RuntimeRange src, + RuntimeRange dst, + Set interleaved, + Connection connection) { + super(src.instance, src.start, src.width, interleaved); + destinations.add(dst); + _interleaved.addAll(src._interleaved); + this.connection = connection; + } - /** The connection that establishes this relationship or null if not unique or none. */ - public final Connection connection; - - /** The list of destination ranges to which this broadcasts. */ - public final List> destinations = new ArrayList<>(); + ////////////////////////////////////////////////////////// + //// Public variables - ////////////////////////////////////////////////////////// - //// Public methods + /** The connection that establishes this relationship or null if not unique or none. */ + public final Connection connection; - /** - * Add a destination to the list of destinations of this range. - * If the width of the destination is not a multiple of the width - * of this range, throw an exception. - * @throws IllegalArgumentException If the width doesn't match. - */ - public void addDestination(RuntimeRange dst) { - if (dst.width % width != 0) { - throw new IllegalArgumentException( - "Destination range width is not a multiple of sender's width"); - } - destinations.add(dst); - // Void any precomputed number of destinations. - _numberOfDestinationReactors = -1; + /** The list of destination ranges to which this broadcasts. */ + public final List> destinations = new ArrayList<>(); + + ////////////////////////////////////////////////////////// + //// Public methods + + /** + * Add a destination to the list of destinations of this range. If the width of the destination is + * not a multiple of the width of this range, throw an exception. + * + * @throws IllegalArgumentException If the width doesn't match. + */ + public void addDestination(RuntimeRange dst) { + if (dst.width % width != 0) { + throw new IllegalArgumentException( + "Destination range width is not a multiple of sender's width"); } - - /** - * Override the base class to add additional comparisons so that - * ordering is never ambiguous. This means that sorting will be deterministic. - * Note that this can return 0 even if equals() does not return true. - */ - @Override - public int compareTo(RuntimeRange o) { - int result = super.compareTo(o); - if (result == 0) { - // Longer destination lists come first. - if (destinations.size() > ((SendRange)o).destinations.size()) { - return -1; - } else if (destinations.size() == ((SendRange)o).destinations.size()) { - return instance.getFullName().compareTo(o.instance.getFullName()); - } else { - return 1; - } - } - return result; + destinations.add(dst); + // Void any precomputed number of destinations. + _numberOfDestinationReactors = -1; + } + + /** + * Override the base class to add additional comparisons so that ordering is never ambiguous. This + * means that sorting will be deterministic. Note that this can return 0 even if equals() does not + * return true. + */ + @Override + public int compareTo(RuntimeRange o) { + int result = super.compareTo(o); + if (result == 0) { + // Longer destination lists come first. + if (destinations.size() > ((SendRange) o).destinations.size()) { + return -1; + } else if (destinations.size() == ((SendRange) o).destinations.size()) { + return instance.getFullName().compareTo(o.instance.getFullName()); + } else { + return 1; + } } + return result; + } - /** - * Return the total number of destination reactors for this range. - * Specifically, this is the number of distinct runtime reactor instances - * that react to messages from this send range. - */ - public int getNumberOfDestinationReactors() { - if (_numberOfDestinationReactors < 0) { - // Has not been calculated before. Calculate now. - _numberOfDestinationReactors = 0; - Map> result = new HashMap<>(); - for (RuntimeRange destination : this.destinations) { - // The following set contains unique identifiers the parent reactors - // of destination ports. - Set parentIDs = destination.parentInstances(1); - Set previousParentIDs = result.get(destination.instance.parent); - if (previousParentIDs == null) { - result.put(destination.instance.parent, parentIDs); - } else { - previousParentIDs.addAll(parentIDs); - } - } - for (ReactorInstance parent : result.keySet()) { - _numberOfDestinationReactors += result.get(parent).size(); - } + /** + * Return the total number of destination reactors for this range. Specifically, this is the + * number of distinct runtime reactor instances that react to messages from this send range. + */ + public int getNumberOfDestinationReactors() { + if (_numberOfDestinationReactors < 0) { + // Has not been calculated before. Calculate now. + _numberOfDestinationReactors = 0; + Map> result = new HashMap<>(); + for (RuntimeRange destination : this.destinations) { + // The following set contains unique identifiers the parent reactors + // of destination ports. + Set parentIDs = destination.parentInstances(1); + Set previousParentIDs = result.get(destination.instance.parent); + if (previousParentIDs == null) { + result.put(destination.instance.parent, parentIDs); + } else { + previousParentIDs.addAll(parentIDs); } - return _numberOfDestinationReactors; + } + for (ReactorInstance parent : result.keySet()) { + _numberOfDestinationReactors += result.get(parent).size(); + } } + return _numberOfDestinationReactors; + } - /** - * Return a new SendRange that is identical to this range but - * with width reduced to the specified width. - * If the new width is greater than or equal to the width - * of this range, then return this range. - * If the newWidth is 0 or negative, return null. - * This overrides the base class to also apply head() - * to the destination list. - * @param newWidth The new width. - */ - @Override - public SendRange head(int newWidth) { - // NOTE: Cannot use the superclass because it returns a RuntimeRange, not a SendRange. - // Also, cannot return this without applying head() to the destinations. - if (newWidth <= 0) return null; + /** + * Return a new SendRange that is identical to this range but with width reduced to the specified + * width. If the new width is greater than or equal to the width of this range, then return this + * range. If the newWidth is 0 or negative, return null. This overrides the base class to also + * apply head() to the destination list. + * + * @param newWidth The new width. + */ + @Override + public SendRange head(int newWidth) { + // NOTE: Cannot use the superclass because it returns a RuntimeRange, not a SendRange. + // Also, cannot return this without applying head() to the destinations. + if (newWidth <= 0) return null; - SendRange result = new SendRange(instance, start, newWidth, _interleaved, connection); - - for (RuntimeRange destination : destinations) { - result.destinations.add(destination.head(newWidth)); - } - return result; + SendRange result = new SendRange(instance, start, newWidth, _interleaved, connection); + + for (RuntimeRange destination : destinations) { + result.destinations.add(destination.head(newWidth)); } + return result; + } - /** - * Return a range that is the subset of this range that overlaps with the - * specified range or null if there is no overlap. - */ - @Override - public SendRange overlap(RuntimeRange range) { - if (!overlaps(range)) return null; - if (range.start == start && range.width == width) return this; - int newStart = Math.max(start, range.start); - int newEnd = Math.min(start + width, range.start + range.width); - int newWidth = newEnd - newStart; - SendRange result = new SendRange(instance, newStart, newWidth, _interleaved, connection); - result._interleaved.addAll(_interleaved); - for (RuntimeRange destination : destinations) { - // The destination width is a multiple of this range's width. - // If the multiple is greater than 1, then the destination needs to - // split into multiple destinations. - while (destination != null) { - int dstStart = destination.start + (newStart - start); - RuntimeRange.Port dst = new RuntimeRange.Port( - destination.instance, - dstStart, - newWidth, - destination._interleaved - ); - result.addDestination(dst); - destination = destination.tail(width); - } - } - return result; + /** + * Return a range that is the subset of this range that overlaps with the specified range or null + * if there is no overlap. + */ + @Override + public SendRange overlap(RuntimeRange range) { + if (!overlaps(range)) return null; + if (range.start == start && range.width == width) return this; + int newStart = Math.max(start, range.start); + int newEnd = Math.min(start + width, range.start + range.width); + int newWidth = newEnd - newStart; + SendRange result = new SendRange(instance, newStart, newWidth, _interleaved, connection); + result._interleaved.addAll(_interleaved); + for (RuntimeRange destination : destinations) { + // The destination width is a multiple of this range's width. + // If the multiple is greater than 1, then the destination needs to + // split into multiple destinations. + while (destination != null) { + int dstStart = destination.start + (newStart - start); + RuntimeRange.Port dst = + new RuntimeRange.Port( + destination.instance, dstStart, newWidth, destination._interleaved); + result.addDestination(dst); + destination = destination.tail(width); + } } + return result; + } - /** - * Return a new SendRange that represents the leftover elements - * starting at the specified offset. If the offset is greater - * than or equal to the width, then this returns null. - * If this offset is 0 then this returns this range unmodified. - * This overrides the base class to also apply tail() - * to the destination list. - * @param offset The number of elements to consume. - */ - @Override - public SendRange tail(int offset) { - // NOTE: Cannot use the superclass because it returns a RuntimeRange, not a SendRange. - // Also, cannot return this without applying tail() to the destinations. - if (offset >= width) return null; - SendRange result = new SendRange( - instance, start + offset, width - offset, _interleaved, connection); + /** + * Return a new SendRange that represents the leftover elements starting at the specified offset. + * If the offset is greater than or equal to the width, then this returns null. If this offset is + * 0 then this returns this range unmodified. This overrides the base class to also apply tail() + * to the destination list. + * + * @param offset The number of elements to consume. + */ + @Override + public SendRange tail(int offset) { + // NOTE: Cannot use the superclass because it returns a RuntimeRange, not a SendRange. + // Also, cannot return this without applying tail() to the destinations. + if (offset >= width) return null; + SendRange result = + new SendRange(instance, start + offset, width - offset, _interleaved, connection); - for (RuntimeRange destination : destinations) { - result.destinations.add(destination.tail(offset)); - } - return result; + for (RuntimeRange destination : destinations) { + result.destinations.add(destination.tail(offset)); } + return result; + } - @Override - public String toString() { - StringBuilder result = new StringBuilder(); - result.append(super.toString()); - result.append("->["); - List dsts = new LinkedList<>(); - for (RuntimeRange dst : destinations) { - dsts.add(dst.toString()); - } - result.append(String.join(", ", dsts)); - result.append("]"); - return result.toString(); + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(super.toString()); + result.append("->["); + List dsts = new LinkedList<>(); + for (RuntimeRange dst : destinations) { + dsts.add(dst.toString()); } + result.append(String.join(", ", dsts)); + result.append("]"); + return result.toString(); + } + + ////////////////////////////////////////////////////////// + //// Protected methods - ////////////////////////////////////////////////////////// - //// Protected methods + /** + * Assuming that this SendRange is completely contained by one of the destinations of the + * specified srcRange, return a new SendRange where the send range is the subrange of the + * specified srcRange that overlaps with this SendRange and the destinations include all the + * destinations of this SendRange. If the assumption is not satisfied, throw an + * IllegalArgumentException. + * + *

If any parent of this range is marked interleaved and is also a parent of the specified + * srcRange, then that parent will be marked interleaved in the result. + * + * @param srcRange A new source range. + * @param srcRangeOffset An offset into the source range. + */ + protected SendRange newSendRange(SendRange srcRange, int srcRangeOffset) { + // Every destination of srcRange receives all channels of srcRange (multicast). + // Find which multicast destination overlaps with this srcRange. + for (RuntimeRange srcDestination : srcRange.destinations) { + RuntimeRange overlap = srcDestination.overlap(this); + if (overlap == null) continue; // Not this one. - /** - * Assuming that this SendRange is completely contained by one - * of the destinations of the specified srcRange, return a new SendRange - * where the send range is the subrange of the specified srcRange that - * overlaps with this SendRange and the destinations include all the - * destinations of this SendRange. If the assumption is not satisfied, - * throw an IllegalArgumentException. - * - * If any parent of this range is marked interleaved and is also a parent of the - * specified srcRange, then that parent will be marked interleaved - * in the result. - * - * @param srcRange A new source range. - * @param srcRangeOffset An offset into the source range. - */ - protected SendRange newSendRange(SendRange srcRange, int srcRangeOffset) { - // Every destination of srcRange receives all channels of srcRange (multicast). - // Find which multicast destination overlaps with this srcRange. - for (RuntimeRange srcDestination : srcRange.destinations) { - RuntimeRange overlap = srcDestination.overlap(this); - if (overlap == null) continue; // Not this one. - - if (overlap.width == width) { - // Found an overlap that is completely contained. - // If this width is greater than the srcRange width, - // then assume srcRange is multicasting via this. - int newWidth = Math.min(width, srcRange.width); - // The interleaving of the result is the union of the two interleavings. - Set interleaving = new LinkedHashSet<>(); - interleaving.addAll(_interleaved); - interleaving.addAll(srcRange._interleaved); - SendRange result = new SendRange( - srcRange.instance, - srcRange.start + srcRangeOffset, - newWidth, - interleaving, - connection); - for (RuntimeRange dst : destinations) { - result.addDestination(dst); - } - return result; - } + if (overlap.width == width) { + // Found an overlap that is completely contained. + // If this width is greater than the srcRange width, + // then assume srcRange is multicasting via this. + int newWidth = Math.min(width, srcRange.width); + // The interleaving of the result is the union of the two interleavings. + Set interleaving = new LinkedHashSet<>(); + interleaving.addAll(_interleaved); + interleaving.addAll(srcRange._interleaved); + SendRange result = + new SendRange( + srcRange.instance, + srcRange.start + srcRangeOffset, + newWidth, + interleaving, + connection); + for (RuntimeRange dst : destinations) { + result.addDestination(dst); } - throw new IllegalArgumentException( - "Expected this SendRange " + this - + " to be completely contained by a destination of " + srcRange); + return result; + } } + throw new IllegalArgumentException( + "Expected this SendRange " + + this + + " to be completely contained by a destination of " + + srcRange); + } - ////////////////////////////////////////////////////////// - //// Private variables + ////////////////////////////////////////////////////////// + //// Private variables - private int _numberOfDestinationReactors = -1; // Never access this directly. + private int _numberOfDestinationReactors = -1; // Never access this directly. } diff --git a/org.lflang/src/org/lflang/generator/SubContext.java b/org.lflang/src/org/lflang/generator/SubContext.java index 5e7a981ab8..cf90cb1fda 100644 --- a/org.lflang/src/org/lflang/generator/SubContext.java +++ b/org.lflang/src/org/lflang/generator/SubContext.java @@ -1,90 +1,88 @@ package org.lflang.generator; -import java.io.File; import java.util.Properties; - import org.eclipse.xtext.util.CancelIndicator; - import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.TargetConfig; /** - * A {@code SubContext} is the context of a process within a build process. For example, - * compilation of generated code may optionally be given a {@code SubContext} because - * compilation is part of a complete build. + * A {@code SubContext} is the context of a process within a build process. For example, compilation + * of generated code may optionally be given a {@code SubContext} because compilation is part of a + * complete build. * * @author Peter Donovan */ public class SubContext implements LFGeneratorContext { - private final LFGeneratorContext containingContext; - private final int startPercentProgress; - private final int endPercentProgress; - private GeneratorResult result = null; - - protected ErrorReporter errorReporter; - - /** - * Initializes the context within {@code containingContext} of the process that extends from - * {@code startPercentProgress} to {@code endPercentProgress}. - * @param containingContext The context of the containing build process. - * @param startPercentProgress The percent progress of the containing build process when this - * nested process starts. - * @param endPercentProgress The percent progress of the containing build process when this - * nested process ends. - */ - public SubContext(LFGeneratorContext containingContext, int startPercentProgress, int endPercentProgress) { - this.containingContext = containingContext; - this.startPercentProgress = startPercentProgress; - this.endPercentProgress = endPercentProgress; - } - - @Override - public CancelIndicator getCancelIndicator() { - return containingContext.getCancelIndicator(); - } - - @Override - public Mode getMode() { - return containingContext.getMode(); - } - - @Override - public Properties getArgs() { - return containingContext.getArgs(); - } - - @Override - public ErrorReporter getErrorReporter() { - return containingContext.getErrorReporter(); - } - - @Override - public void finish(GeneratorResult result) { - this.result = result; - } - - @Override - public GeneratorResult getResult() { - return result; - } - - @Override - public FileConfig getFileConfig() { - return containingContext.getFileConfig(); - } - - @Override - public TargetConfig getTargetConfig() { - return containingContext.getTargetConfig(); - } - - @Override - public void reportProgress(String message, int percentage) { - containingContext.reportProgress( - message, - startPercentProgress * (100 - percentage) / 100 + endPercentProgress * percentage / 100 - ); - } + private final LFGeneratorContext containingContext; + private final int startPercentProgress; + private final int endPercentProgress; + private GeneratorResult result = null; + + protected ErrorReporter errorReporter; + + /** + * Initializes the context within {@code containingContext} of the process that extends from + * {@code startPercentProgress} to {@code endPercentProgress}. + * + * @param containingContext The context of the containing build process. + * @param startPercentProgress The percent progress of the containing build process when this + * nested process starts. + * @param endPercentProgress The percent progress of the containing build process when this nested + * process ends. + */ + public SubContext( + LFGeneratorContext containingContext, int startPercentProgress, int endPercentProgress) { + this.containingContext = containingContext; + this.startPercentProgress = startPercentProgress; + this.endPercentProgress = endPercentProgress; + } + + @Override + public CancelIndicator getCancelIndicator() { + return containingContext.getCancelIndicator(); + } + + @Override + public Mode getMode() { + return containingContext.getMode(); + } + + @Override + public Properties getArgs() { + return containingContext.getArgs(); + } + + @Override + public ErrorReporter getErrorReporter() { + return containingContext.getErrorReporter(); + } + + @Override + public void finish(GeneratorResult result) { + this.result = result; + } + + @Override + public GeneratorResult getResult() { + return result; + } + + @Override + public FileConfig getFileConfig() { + return containingContext.getFileConfig(); + } + + @Override + public TargetConfig getTargetConfig() { + return containingContext.getTargetConfig(); + } + + @Override + public void reportProgress(String message, int percentage) { + containingContext.reportProgress( + message, + startPercentProgress * (100 - percentage) / 100 + endPercentProgress * percentage / 100); + } } diff --git a/org.lflang/src/org/lflang/generator/TargetTypes.java b/org.lflang/src/org/lflang/generator/TargetTypes.java index 585d65c96a..eda5cffd11 100644 --- a/org.lflang/src/org/lflang/generator/TargetTypes.java +++ b/org.lflang/src/org/lflang/generator/TargetTypes.java @@ -3,10 +3,9 @@ import java.util.List; import java.util.Objects; import java.util.stream.Collectors; - -import org.lflang.ast.ASTUtils; import org.lflang.InferredType; import org.lflang.TimeValue; +import org.lflang.ast.ASTUtils; import org.lflang.lf.Action; import org.lflang.lf.BracedListExpression; import org.lflang.lf.CodeExpr; @@ -21,257 +20,217 @@ import org.lflang.lf.Type; /** - * Information about the types of a target language. Contains - * utilities to convert LF expressions and types to the target - * language. Each code generator is expected to use at least one + * Information about the types of a target language. Contains utilities to convert LF expressions + * and types to the target language. Each code generator is expected to use at least one * language-specific instance of this interface. - *

- * TODO currently, {@link GeneratorBase} implements this interface, - * it should instead contain an instance. + * + *

TODO currently, {@link GeneratorBase} implements this interface, it should instead contain an + * instance. * * @author Clément Fournier - TU Dresden, INSA Rennes */ public interface TargetTypes { - - /** - * Return true if the target supports generics (i.e., parametric - * polymorphism), false otherwise. - */ - boolean supportsGenerics(); - - - /** - * Return the type of time durations. - */ - String getTargetTimeType(); - - - /** - * Return the type of tags. - */ - String getTargetTagType(); - - - /** - * Return the type of fixed sized lists (or arrays). - */ - String getTargetFixedSizeListType(String baseType, int size); - - - /** - * Return the type of variable sized lists (eg {@code std::vector}). - */ - String getTargetVariableSizeListType(String baseType); - - - default String getTargetParamRef(ParameterReference expr, InferredType typeOrNull) { - 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. - */ - String getTargetUndefinedType(); - - /** - * Returns a version of the given LF identifier that is - * escaped properly for insertion into a piece of target - * code. - */ - default String escapeIdentifier(String ident) { - return ident; - } - - /** - * Returns an expression in the target language that corresponds - * to the given time value ({@link #getTargetTimeType()}). - */ - default String getTargetTimeExpr(TimeValue timeValue) { - // todo make non-default when we reuse this for all generators, - // all targets should support this. - Objects.requireNonNull(timeValue); - throw new UnsupportedGeneratorFeatureException("Time expressions"); - } - - /** - * Returns an expression in the target language that corresponds - * to a variable-size list expression. - * - * @throws UnsupportedGeneratorFeatureException If the target does not support this - */ - default String getVariableSizeListInitExpression(List contents, boolean withBraces) { - throw new UnsupportedGeneratorFeatureException("Variable size lists"); - } - - /** - * Returns an expression in the target language that corresponds - * to a fixed-size list expression. - * - * @throws UnsupportedGeneratorFeatureException If the target does not support this - */ - default String getFixedSizeListInitExpression(List contents, int listSize, boolean withBraces) { - throw new UnsupportedGeneratorFeatureException("Fixed size lists"); - } - - - /** - * Returns the expression that is used to replace a - * missing expression in the source language. The expression - * may for instance be a type-agnostic default value - * (e.g. Rust's {@code Default::default()}), or produce - * a compiler error (e.g. Rust's {@code compiler_error!("missing initializer")}). - * - * @throws UnsupportedGeneratorFeatureException If the target does not support this - */ - default String getMissingExpr(InferredType type) { - throw new UnsupportedGeneratorFeatureException("Missing initializers"); - } - - - /** - * Returns a target type inferred from the type node, or the - * initializer list. If both are absent, then the undefined - * type is returned. - */ - default String getTargetType(Type type, Initializer init) { - return getTargetType(ASTUtils.getInferredType(type, init)); - } - - /** - * Returns the target type of the type node. This just provides - * a default parameter for {@link #getTargetType(Type, Initializer)}. - * If the parameter is null, then the undefined type is returned. - */ - default String getTargetType(Type type) { - return getTargetType(type, null); - } - - /** - * Return a string representing the specified type in the - * target language. - */ - default String getTargetType(InferredType type) { - if (type.isUndefined()) { - return getTargetUndefinedType(); - } else if (type.isTime) { - if (type.isFixedSizeList) { - return getTargetFixedSizeListType(getTargetTimeType(), type.listSize); - } else if (type.isVariableSizeList) { - return getTargetVariableSizeListType(getTargetTimeType()); - } else { - return getTargetTimeType(); - } - } else if (type.isFixedSizeList) { - return getTargetFixedSizeListType(type.baseType(), type.listSize); - } else if (type.isVariableSizeList) { - return getTargetVariableSizeListType(type.baseType()); - } else if (!type.astType.getTypeArgs().isEmpty()) { - List args = type.astType.getTypeArgs().stream().map(this::getTargetType).toList(); - return getGenericType(type.baseType(), args); - } - return type.toOriginalText(); - } - - /** Build a generic type. The type args list must not be empty. */ - default String getGenericType(String base, List args) { - assert !args.isEmpty() : "Empty type arguments for " + base; - return base + "<" + String.join(", ", args) + ">"; - } - - /** - * Return a string representing the type of the given - * parameter. - */ - default String getTargetType(Parameter p) { - return getTargetType(ASTUtils.getInferredType(p)); - } - - /** - * Return a string representing the type of the given - * state variable. - */ - default String getTargetType(StateVar s) { - return getTargetType(ASTUtils.getInferredType(s)); - } - - /** - * Return a string representing the type of the given - * action. - */ - default String getTargetType(Action a) { - return getTargetType(ASTUtils.getInferredType(a)); - } - - /** - * Return a string representing the type of the given - * port. - */ - default String getTargetType(Port p) { - return getTargetType(ASTUtils.getInferredType(p)); - } - - /** - * Returns the representation of the given initializer - * expression in target code. The given type, if non-null, - * may inform the code generation. - * - * @param init Initializer node (nullable) - * @param type Declared type of the expression (nullable) - */ - default String getTargetInitializer(Initializer init, Type type) { - var inferredType = ASTUtils.getInferredType(type, init); - if (init == null) { - return getMissingExpr(inferredType); - } - var single = ASTUtils.asSingleExpr(init); - if (single != null) { - return getTargetExpr(single, inferredType); - } - var targetValues = init.getExprs().stream().map(it -> getTargetExpr(it, inferredType)).collect(Collectors.toList()); - if (inferredType.isFixedSizeList) { - return getFixedSizeListInitExpression(targetValues, inferredType.listSize, init.isBraces()); - } else { - return getVariableSizeListInitExpression(targetValues, init.isBraces()); - } - } - - - /** - * Returns the representation of the given expression in target code. - * The given type, if non-null, may inform the code generation. - */ - default String getTargetExpr(Expression expr, InferredType type) { - if (ASTUtils.isZero(expr) && type != null && type.isTime) { - return getTargetTimeExpr(TimeValue.ZERO); - } else if (expr instanceof ParameterReference) { - return getTargetParamRef((ParameterReference) expr, type); - } else if (expr instanceof Time) { - return getTargetTimeExpr((Time) expr); - } else if (expr instanceof Literal) { - 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); - } - } - - /** - * Returns the representation of the given time value in - * target code. - */ - default String getTargetTimeExpr(Time t) { - return getTargetTimeExpr(ASTUtils.toTimeValue(t)); - } + /** + * Return true if the target supports generics (i.e., parametric polymorphism), false otherwise. + */ + boolean supportsGenerics(); + + /** Return the type of time durations. */ + String getTargetTimeType(); + + /** Return the type of tags. */ + String getTargetTagType(); + + /** Return the type of fixed sized lists (or arrays). */ + String getTargetFixedSizeListType(String baseType, int size); + + /** Return the type of variable sized lists (eg {@code std::vector}). */ + String getTargetVariableSizeListType(String baseType); + + default String getTargetParamRef(ParameterReference expr, InferredType typeOrNull) { + 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. */ + String getTargetUndefinedType(); + + /** + * Returns a version of the given LF identifier that is escaped properly for insertion into a + * piece of target code. + */ + default String escapeIdentifier(String ident) { + return ident; + } + + /** + * Returns an expression in the target language that corresponds to the given time value ({@link + * #getTargetTimeType()}). + */ + default String getTargetTimeExpr(TimeValue timeValue) { + // todo make non-default when we reuse this for all generators, + // all targets should support this. + Objects.requireNonNull(timeValue); + throw new UnsupportedGeneratorFeatureException("Time expressions"); + } + + /** + * Returns an expression in the target language that corresponds to a variable-size list + * expression. + * + * @throws UnsupportedGeneratorFeatureException If the target does not support this + */ + default String getVariableSizeListInitExpression(List contents, boolean withBraces) { + throw new UnsupportedGeneratorFeatureException("Variable size lists"); + } + + /** + * Returns an expression in the target language that corresponds to a fixed-size list expression. + * + * @throws UnsupportedGeneratorFeatureException If the target does not support this + */ + default String getFixedSizeListInitExpression( + List contents, int listSize, boolean withBraces) { + throw new UnsupportedGeneratorFeatureException("Fixed size lists"); + } + + /** + * Returns the expression that is used to replace a missing expression in the source language. The + * expression may for instance be a type-agnostic default value (e.g. Rust's {@code + * Default::default()}), or produce a compiler error (e.g. Rust's {@code compiler_error!("missing + * initializer")}). + * + * @throws UnsupportedGeneratorFeatureException If the target does not support this + */ + default String getMissingExpr(InferredType type) { + throw new UnsupportedGeneratorFeatureException("Missing initializers"); + } + + /** + * Returns a target type inferred from the type node, or the initializer list. If both are absent, + * then the undefined type is returned. + */ + default String getTargetType(Type type, Initializer init) { + return getTargetType(ASTUtils.getInferredType(type, init)); + } + + /** + * Returns the target type of the type node. This just provides a default parameter for {@link + * #getTargetType(Type, Initializer)}. If the parameter is null, then the undefined type is + * returned. + */ + default String getTargetType(Type type) { + return getTargetType(type, null); + } + + /** Return a string representing the specified type in the target language. */ + default String getTargetType(InferredType type) { + if (type.isUndefined()) { + return getTargetUndefinedType(); + } else if (type.isTime) { + if (type.isFixedSizeList) { + return getTargetFixedSizeListType(getTargetTimeType(), type.listSize); + } else if (type.isVariableSizeList) { + return getTargetVariableSizeListType(getTargetTimeType()); + } else { + return getTargetTimeType(); + } + } else if (type.isFixedSizeList) { + return getTargetFixedSizeListType(type.baseType(), type.listSize); + } else if (type.isVariableSizeList) { + return getTargetVariableSizeListType(type.baseType()); + } else if (!type.astType.getTypeArgs().isEmpty()) { + List args = type.astType.getTypeArgs().stream().map(this::getTargetType).toList(); + return getGenericType(type.baseType(), args); + } + return type.toOriginalText(); + } + + /** Build a generic type. The type args list must not be empty. */ + default String getGenericType(String base, List args) { + assert !args.isEmpty() : "Empty type arguments for " + base; + return base + "<" + String.join(", ", args) + ">"; + } + + /** Return a string representing the type of the given parameter. */ + default String getTargetType(Parameter p) { + return getTargetType(ASTUtils.getInferredType(p)); + } + + /** Return a string representing the type of the given state variable. */ + default String getTargetType(StateVar s) { + return getTargetType(ASTUtils.getInferredType(s)); + } + + /** Return a string representing the type of the given action. */ + default String getTargetType(Action a) { + return getTargetType(ASTUtils.getInferredType(a)); + } + + /** Return a string representing the type of the given port. */ + default String getTargetType(Port p) { + return getTargetType(ASTUtils.getInferredType(p)); + } + + /** + * Returns the representation of the given initializer expression in target code. The given type, + * if non-null, may inform the code generation. + * + * @param init Initializer node (nullable) + * @param type Declared type of the expression (nullable) + */ + default String getTargetInitializer(Initializer init, Type type) { + var inferredType = ASTUtils.getInferredType(type, init); + if (init == null) { + return getMissingExpr(inferredType); + } + var single = ASTUtils.asSingleExpr(init); + if (single != null) { + return getTargetExpr(single, inferredType); + } + var targetValues = + init.getExprs().stream() + .map(it -> getTargetExpr(it, inferredType)) + .collect(Collectors.toList()); + if (inferredType.isFixedSizeList) { + return getFixedSizeListInitExpression(targetValues, inferredType.listSize, init.isBraces()); + } else { + return getVariableSizeListInitExpression(targetValues, init.isBraces()); + } + } + + /** + * Returns the representation of the given expression in target code. The given type, if non-null, + * may inform the code generation. + */ + default String getTargetExpr(Expression expr, InferredType type) { + if (ASTUtils.isZero(expr) && type != null && type.isTime) { + return getTargetTimeExpr(TimeValue.ZERO); + } else if (expr instanceof ParameterReference) { + return getTargetParamRef((ParameterReference) expr, type); + } else if (expr instanceof Time) { + return getTargetTimeExpr((Time) expr); + } else if (expr instanceof Literal) { + 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); + } + } + + /** Returns the representation of the given time value in target code. */ + default String getTargetTimeExpr(Time t) { + return getTargetTimeExpr(ASTUtils.toTimeValue(t)); + } } diff --git a/org.lflang/src/org/lflang/generator/TimerInstance.java b/org.lflang/src/org/lflang/generator/TimerInstance.java index 492398998c..b25552bb6c 100644 --- a/org.lflang/src/org/lflang/generator/TimerInstance.java +++ b/org.lflang/src/org/lflang/generator/TimerInstance.java @@ -1,28 +1,28 @@ /** Instance of a timer. */ /************* -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.generator; @@ -31,63 +31,58 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY /** * Instance of a timer. - * + * * @author Marten Lohstroh * @author Edward A. Lee */ public class TimerInstance extends TriggerInstance { - /** The global default for offset. */ - public static final TimeValue DEFAULT_OFFSET = TimeValue.ZERO; + /** The global default for offset. */ + public static final TimeValue DEFAULT_OFFSET = TimeValue.ZERO; - /** The global default for period. */ - public static final TimeValue DEFAULT_PERIOD = TimeValue.ZERO; + /** The global default for period. */ + public static final TimeValue DEFAULT_PERIOD = TimeValue.ZERO; - private TimeValue offset = DEFAULT_OFFSET; + private TimeValue offset = DEFAULT_OFFSET; - private TimeValue period = DEFAULT_PERIOD; + private TimeValue period = DEFAULT_PERIOD; - /** - * Create a new timer instance. - * If the definition is null, then this is a startup timer. - * @param definition The AST definition, or null for startup. - * @param parent The parent reactor. - */ - public TimerInstance(Timer definition, ReactorInstance parent) { - super(definition, parent); - if (parent == null) { - throw new InvalidSourceException("Cannot create an TimerInstance with no parent."); + /** + * Create a new timer instance. If the definition is null, then this is a startup timer. + * + * @param definition The AST definition, or null for startup. + * @param parent The parent reactor. + */ + public TimerInstance(Timer definition, ReactorInstance parent) { + super(definition, parent); + if (parent == null) { + throw new InvalidSourceException("Cannot create an TimerInstance with no parent."); + } + if (definition != null) { + if (definition.getOffset() != null) { + try { + this.offset = parent.getTimeValue(definition.getOffset()); + } catch (IllegalArgumentException ex) { + parent.reporter.reportError(definition.getOffset(), "Invalid time."); } - if (definition != null) { - if (definition.getOffset() != null) { - try { - this.offset = parent.getTimeValue(definition.getOffset()); - } catch (IllegalArgumentException ex) { - parent.reporter.reportError(definition.getOffset(), "Invalid time."); - } - } - if (definition.getPeriod() != null) { - try { - this.period = parent.getTimeValue(definition.getPeriod()); - } catch (IllegalArgumentException ex) { - parent.reporter.reportError(definition.getPeriod(), "Invalid time."); - } - } + } + if (definition.getPeriod() != null) { + try { + this.period = parent.getTimeValue(definition.getPeriod()); + } catch (IllegalArgumentException ex) { + parent.reporter.reportError(definition.getPeriod(), "Invalid time."); } + } } + } - /** - * Get the value of the offset parameter. - */ - public TimeValue getOffset() { - return offset; - } - - /** - * Get the value of the offset parameter. - */ - public TimeValue getPeriod() { - return period; - } + /** Get the value of the offset parameter. */ + public TimeValue getOffset() { + return offset; + } + /** Get the value of the offset parameter. */ + public TimeValue getPeriod() { + return period; + } } diff --git a/org.lflang/src/org/lflang/generator/TriggerInstance.java b/org.lflang/src/org/lflang/generator/TriggerInstance.java index 47212ff0e8..b4ddc35a15 100644 --- a/org.lflang/src/org/lflang/generator/TriggerInstance.java +++ b/org.lflang/src/org/lflang/generator/TriggerInstance.java @@ -1,181 +1,170 @@ /** Instance of a trigger (port, action, or timer). */ /************* -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.generator; import java.util.LinkedHashSet; import java.util.Set; - import org.lflang.lf.BuiltinTrigger; import org.lflang.lf.BuiltinTriggerRef; import org.lflang.lf.TriggerRef; import org.lflang.lf.Variable; import org.lflang.lf.impl.VariableImpl; -/** Instance of a trigger (port, action, or timer). +/** + * Instance of a trigger (port, action, or timer). * - * @author Marten Lohstroh - * @author Edward A. Lee - * @author Alexander Schulz-Rosengarten + * @author Marten Lohstroh + * @author Edward A. Lee + * @author Alexander Schulz-Rosengarten */ public class TriggerInstance extends NamedInstance { - /** Construct a new instance with the specified definition - * and parent. E.g., for a action instance, the definition - * is Action, and for a port instance, it is Port. These are - * nodes in the AST. This is protected because only subclasses - * should be constructed. - * @param definition The definition in the AST for this instance. - * @param parent The reactor instance that creates this instance. - */ - protected TriggerInstance(T definition, ReactorInstance parent) { - super(definition, parent); - } - - /** - * Construct a new instance for a special builtin trigger. - * - * @param trigger The actual trigger definition. - * @param parent The reactor instance that creates this instance. - */ - static TriggerInstance builtinTrigger(BuiltinTriggerRef trigger, ReactorInstance parent) { - return new TriggerInstance<>(new BuiltinTriggerVariable(trigger), parent); - } - - ///////////////////////////////////////////// - //// Public Methods - - /** - * Return the reaction instances that are triggered or read by this trigger. - * If this port is an output, then the reaction instances - * belong to the parent of the port's parent. If the port - * is an input, then the reaction instances belong to the - * port's parent. - */ - public Set getDependentReactions() { - return dependentReactions; + /** + * Construct a new instance with the specified definition and parent. E.g., for a action instance, + * the definition is Action, and for a port instance, it is Port. These are nodes in the AST. This + * is protected because only subclasses should be constructed. + * + * @param definition The definition in the AST for this instance. + * @param parent The reactor instance that creates this instance. + */ + protected TriggerInstance(T definition, ReactorInstance parent) { + super(definition, parent); + } + + /** + * Construct a new instance for a special builtin trigger. + * + * @param trigger The actual trigger definition. + * @param parent The reactor instance that creates this instance. + */ + static TriggerInstance builtinTrigger( + BuiltinTriggerRef trigger, ReactorInstance parent) { + return new TriggerInstance<>(new BuiltinTriggerVariable(trigger), parent); + } + + ///////////////////////////////////////////// + //// Public Methods + + /** + * Return the reaction instances that are triggered or read by this trigger. If this port is an + * output, then the reaction instances belong to the parent of the port's parent. If the port is + * an input, then the reaction instances belong to the port's parent. + */ + public Set getDependentReactions() { + return dependentReactions; + } + + /** + * Return the reaction instances that may send data via this port. If this port is an input, then + * the reaction instance belongs to parent of the port's parent. If it is an output, the reaction + * instance belongs to the port's parent. + */ + public Set getDependsOnReactions() { + return dependsOnReactions; + } + + /** + * Return the name of this trigger. + * + * @return The name of this trigger. + */ + @Override + public String getName() { + return definition.getName(); + } + + /** Return true if this trigger is "shutdown". */ + public boolean isShutdown() { + return isBuiltInType(BuiltinTrigger.SHUTDOWN); + } + + /** Return true if this trigger is "startup"./ */ + public boolean isStartup() { + return isBuiltInType(BuiltinTrigger.STARTUP); + } + + /** Return true if this trigger is "startup"./ */ + public boolean isReset() { + return isBuiltInType(BuiltinTrigger.RESET); + } + + ///////////////////////////////////////////// + //// Private Methods + + private boolean isBuiltInType(BuiltinTrigger type) { + return this.definition instanceof BuiltinTriggerVariable + && ((BuiltinTriggerRef) ((BuiltinTriggerVariable) this.definition).definition) + .getType() + .equals(type); + } + + ///////////////////////////////////////////// + //// Protected Fields + + /** + * Reaction instances that are triggered or read by this trigger. If this port is an output, then + * the reaction instances belong to the parent of the port's parent. If the port is an input, then + * the reaction instances belong to the port's parent. + */ + Set dependentReactions = new LinkedHashSet<>(); + + /** + * Reaction instances that may send data via this port. If this port is an input, then the + * reaction instance belongs to parent of the port's parent. If it is an output, the reaction + * instance belongs to the port's parent. + */ + Set dependsOnReactions = new LinkedHashSet<>(); + + ///////////////////////////////////////////// + //// Special class for builtin triggers + + /** This class allows to have BuiltinTriggers represented by a Variable type. */ + public static class BuiltinTriggerVariable extends VariableImpl { + + /** The builtin trigger type represented by this variable. */ + public final BuiltinTrigger type; + + /** The actual TriggerRef definition in the AST. */ + public final TriggerRef definition; + + public BuiltinTriggerVariable(BuiltinTriggerRef trigger) { + this.type = trigger.getType(); + this.definition = trigger; } - /** - * Return the reaction instances that may send data via this port. - * If this port is an input, then the reaction instance - * belongs to parent of the port's parent. If it is an output, - * the reaction instance belongs to the port's parent. - */ - public Set getDependsOnReactions() { - return dependsOnReactions; - } - - /** - * Return the name of this trigger. - * @return The name of this trigger. - */ @Override public String getName() { - return definition.getName(); - } - - /** - * Return true if this trigger is "shutdown". - */ - public boolean isShutdown() { - return isBuiltInType(BuiltinTrigger.SHUTDOWN); - } - - /** - * Return true if this trigger is "startup"./ - */ - public boolean isStartup() { - return isBuiltInType(BuiltinTrigger.STARTUP); + return this.type.name().toLowerCase(); } - /** - * Return true if this trigger is "startup"./ - */ - public boolean isReset() { - return isBuiltInType(BuiltinTrigger.RESET); - } - - ///////////////////////////////////////////// - //// Private Methods - - private boolean isBuiltInType(BuiltinTrigger type) { - return this.definition instanceof BuiltinTriggerVariable - && ((BuiltinTriggerRef) ((BuiltinTriggerVariable) this.definition).definition).getType().equals(type); - } - - ///////////////////////////////////////////// - //// Protected Fields - - - /** - * Reaction instances that are triggered or read by this trigger. - * If this port is an output, then the reaction instances - * belong to the parent of the port's parent. If the port - * is an input, then the reaction instances belong to the - * port's parent. - */ - Set dependentReactions = new LinkedHashSet<>(); - - /** - * Reaction instances that may send data via this port. - * If this port is an input, then the reaction instance - * belongs to parent of the port's parent. If it is an output, - * the reaction instance belongs to the port's parent. - */ - Set dependsOnReactions = new LinkedHashSet<>(); - - ///////////////////////////////////////////// - //// Special class for builtin triggers - - /** - * This class allows to have BuiltinTriggers represented by a Variable type. - */ - static public class BuiltinTriggerVariable extends VariableImpl { - - /** The builtin trigger type represented by this variable. */ - public final BuiltinTrigger type; - - /** The actual TriggerRef definition in the AST. */ - public final TriggerRef definition; - - public BuiltinTriggerVariable(BuiltinTriggerRef trigger) { - this.type = trigger.getType(); - this.definition = trigger; - } - - @Override - public String getName() { - return this.type.name().toLowerCase(); - } - - @Override - public void setName(String newName) { - throw new UnsupportedOperationException( - this.getClass().getName() + " has an immutable name."); - } + @Override + public void setName(String newName) { + throw new UnsupportedOperationException( + this.getClass().getName() + " has an immutable name."); } + } } diff --git a/org.lflang/src/org/lflang/generator/UnsupportedGeneratorFeatureException.java b/org.lflang/src/org/lflang/generator/UnsupportedGeneratorFeatureException.java index 59fe20386c..c1519a161a 100644 --- a/org.lflang/src/org/lflang/generator/UnsupportedGeneratorFeatureException.java +++ b/org.lflang/src/org/lflang/generator/UnsupportedGeneratorFeatureException.java @@ -26,17 +26,14 @@ import org.eclipse.emf.ecore.EObject; -/** - * Signals that the code generator does not support a particular - * feature of the source language. - */ +/** Signals that the code generator does not support a particular feature of the source language. */ public class UnsupportedGeneratorFeatureException extends GenerationException { - public UnsupportedGeneratorFeatureException(String feature) { - super("Unsupported generator feature: " + feature); - } + public UnsupportedGeneratorFeatureException(String feature) { + super("Unsupported generator feature: " + feature); + } - public UnsupportedGeneratorFeatureException(EObject location, String feature) { - super(location, "Unsupported generator feature: " + feature); - } + public UnsupportedGeneratorFeatureException(EObject location, String feature) { + super(location, "Unsupported generator feature: " + feature); + } } diff --git a/org.lflang/src/org/lflang/generator/ValidationStrategy.java b/org.lflang/src/org/lflang/generator/ValidationStrategy.java index 7adff29527..674634ef7f 100644 --- a/org.lflang/src/org/lflang/generator/ValidationStrategy.java +++ b/org.lflang/src/org/lflang/generator/ValidationStrategy.java @@ -1,7 +1,6 @@ package org.lflang.generator; import java.nio.file.Path; - import org.lflang.util.LFCommand; /** @@ -11,36 +10,38 @@ */ public interface ValidationStrategy { - /** - * Return the command that produces validation output in association - * with {@code generatedFile}, or {@code null} if this strategy has no - * command that will successfully produce validation output. - */ - LFCommand getCommand(Path generatedFile); - - /** - * Return a strategy for parsing the stderr of the validation command. - * @return A strategy for parsing the stderr of the validation command. - */ - DiagnosticReporting.Strategy getErrorReportingStrategy(); - - /** - * Return a strategy for parsing the stdout of the validation command. - * @return A strategy for parsing the stdout of the validation command. - */ - DiagnosticReporting.Strategy getOutputReportingStrategy(); - - /** - * Return whether this strategy validates all generated files, as - * opposed to just the given one. - * @return whether this strategy validates all generated files - */ - boolean isFullBatch(); - - /** - * Return the priority of this. Strategies with higher - * priorities are more likely to be used. - * @return The priority of this. - */ - int getPriority(); + /** + * Return the command that produces validation output in association with {@code generatedFile}, + * or {@code null} if this strategy has no command that will successfully produce validation + * output. + */ + LFCommand getCommand(Path generatedFile); + + /** + * Return a strategy for parsing the stderr of the validation command. + * + * @return A strategy for parsing the stderr of the validation command. + */ + DiagnosticReporting.Strategy getErrorReportingStrategy(); + + /** + * Return a strategy for parsing the stdout of the validation command. + * + * @return A strategy for parsing the stdout of the validation command. + */ + DiagnosticReporting.Strategy getOutputReportingStrategy(); + + /** + * Return whether this strategy validates all generated files, as opposed to just the given one. + * + * @return whether this strategy validates all generated files + */ + boolean isFullBatch(); + + /** + * Return the priority of this. Strategies with higher priorities are more likely to be used. + * + * @return The priority of this. + */ + int getPriority(); } diff --git a/org.lflang/src/org/lflang/generator/Validator.java b/org.lflang/src/org/lflang/generator/Validator.java index 7a9b85c453..f9ae61f854 100644 --- a/org.lflang/src/org/lflang/generator/Validator.java +++ b/org.lflang/src/org/lflang/generator/Validator.java @@ -1,9 +1,6 @@ package org.lflang.generator; -import org.eclipse.xtext.util.CancelIndicator; - -import org.lflang.ErrorReporter; -import org.lflang.util.LFCommand; +import com.google.common.collect.ImmutableMap; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; @@ -17,8 +14,9 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.stream.Collectors; - -import com.google.common.collect.ImmutableMap; +import org.eclipse.xtext.util.CancelIndicator; +import org.lflang.ErrorReporter; +import org.lflang.util.LFCommand; /** * Validate generated code. @@ -27,159 +25,180 @@ */ public abstract class Validator { - /** - * Files older than {@code FILE_AGE_THRESHOLD_MILLIS} may be skipped in validation on the - * grounds that they probably have not been updated since the last validator pass. - */ - // This will cause silent validation failures if it takes too long to write all generated code to the file system. - private static final long FILE_AGE_THRESHOLD_MILLIS = 10000; - - protected static class Pair { - public final S first; - public final T second; - public Pair(S first, T second) { - this.first = first; - this.second = second; - } - } - - protected final ErrorReporter errorReporter; - protected final ImmutableMap codeMaps; - - /** - * Initialize a {@code Validator} that reports errors to {@code errorReporter} and adjusts - * document positions using {@code codeMaps}. - */ - protected Validator(ErrorReporter errorReporter, Map codeMaps) { - this.errorReporter = errorReporter; - this.codeMaps = ImmutableMap.copyOf(codeMaps); + /** + * Files older than {@code FILE_AGE_THRESHOLD_MILLIS} may be skipped in validation on the grounds + * that they probably have not been updated since the last validator pass. + */ + // This will cause silent validation failures if it takes too long to write all generated code to + // the file system. + private static final long FILE_AGE_THRESHOLD_MILLIS = 10000; + + protected static class Pair { + public final S first; + public final T second; + + public Pair(S first, T second) { + this.first = first; + this.second = second; } - - /** - * Validate this Validator's group of generated files. - * @param context The context of the current build. - */ - public final void doValidate(LFGeneratorContext context) throws ExecutionException, InterruptedException { - if (!validationEnabled(context)) return; - final List>> tasks = getValidationStrategies().stream().map( - it -> (Callable>) () -> { - it.second.run(context.getCancelIndicator()); - return it; - } - ).collect(Collectors.toList()); - for (Future> f : getFutures(tasks)) { - f.get().first.getErrorReportingStrategy().report(f.get().second.getErrors().toString(), errorReporter, codeMaps); - f.get().first.getOutputReportingStrategy().report(f.get().second.getOutput().toString(), errorReporter, codeMaps); - } - } - - /** - * Return whether generated code validation is enabled for this build. - * @param context The context of the current build. - */ - private boolean validationEnabled(LFGeneratorContext context) { - return context.getArgs().containsKey("lint") || validationEnabledByDefault(context); + } + + protected final ErrorReporter errorReporter; + protected final ImmutableMap codeMaps; + + /** + * Initialize a {@code Validator} that reports errors to {@code errorReporter} and adjusts + * document positions using {@code codeMaps}. + */ + protected Validator(ErrorReporter errorReporter, Map codeMaps) { + this.errorReporter = errorReporter; + this.codeMaps = ImmutableMap.copyOf(codeMaps); + } + + /** + * Validate this Validator's group of generated files. + * + * @param context The context of the current build. + */ + public final void doValidate(LFGeneratorContext context) + throws ExecutionException, InterruptedException { + if (!validationEnabled(context)) return; + final List>> tasks = + getValidationStrategies().stream() + .map( + it -> + (Callable>) + () -> { + it.second.run(context.getCancelIndicator()); + return it; + }) + .collect(Collectors.toList()); + for (Future> f : getFutures(tasks)) { + f.get() + .first + .getErrorReportingStrategy() + .report(f.get().second.getErrors().toString(), errorReporter, codeMaps); + f.get() + .first + .getOutputReportingStrategy() + .report(f.get().second.getOutput().toString(), errorReporter, codeMaps); } - - /** - * Return whether validation of generated code is enabled by default. - * @param context The context of the current build. - * @return Whether validation of generated code is enabled by default. - */ - protected boolean validationEnabledByDefault(LFGeneratorContext context) { - return context.getMode() != LFGeneratorContext.Mode.STANDALONE; - } - - /** - * Invoke all the given tasks. - * @param tasks Any set of tasks. - * @param The return type of the tasks. - * @return Futures corresponding to each task, or an empty list upon failure. - * @throws InterruptedException If interrupted while waiting. - */ - private static List> getFutures(List> tasks) throws InterruptedException { - List> futures = List.of(); - switch (tasks.size()) { - case 0: - break; - case 1: - try { - futures = List.of(CompletableFuture.completedFuture(tasks.get(0).call())); - } catch (Exception e) { - System.err.println(e.getMessage()); // This should never happen - } - break; - default: - ExecutorService service = Executors.newFixedThreadPool( - Math.min(Runtime.getRuntime().availableProcessors(), tasks.size()) - ); - futures = service.invokeAll(tasks); - service.shutdown(); + } + + /** + * Return whether generated code validation is enabled for this build. + * + * @param context The context of the current build. + */ + private boolean validationEnabled(LFGeneratorContext context) { + return context.getArgs().containsKey("lint") || validationEnabledByDefault(context); + } + + /** + * Return whether validation of generated code is enabled by default. + * + * @param context The context of the current build. + * @return Whether validation of generated code is enabled by default. + */ + protected boolean validationEnabledByDefault(LFGeneratorContext context) { + return context.getMode() != LFGeneratorContext.Mode.STANDALONE; + } + + /** + * Invoke all the given tasks. + * + * @param tasks Any set of tasks. + * @param The return type of the tasks. + * @return Futures corresponding to each task, or an empty list upon failure. + * @throws InterruptedException If interrupted while waiting. + */ + private static List> getFutures(List> tasks) + throws InterruptedException { + List> futures = List.of(); + switch (tasks.size()) { + case 0: + break; + case 1: + try { + futures = List.of(CompletableFuture.completedFuture(tasks.get(0).call())); + } catch (Exception e) { + System.err.println(e.getMessage()); // This should never happen } - return futures; + break; + default: + ExecutorService service = + Executors.newFixedThreadPool( + Math.min(Runtime.getRuntime().availableProcessors(), tasks.size())); + futures = service.invokeAll(tasks); + service.shutdown(); } - - /** - * Run the given command, report any messages produced using the reporting strategies - * given by {@code getBuildReportingStrategies}, and return its return code. - */ - public final int run(LFCommand command, CancelIndicator cancelIndicator) { - final int returnCode = command.run(cancelIndicator); - getBuildReportingStrategies().first.report(command.getErrors().toString(), errorReporter, codeMaps); - getBuildReportingStrategies().second.report(command.getOutput().toString(), errorReporter, codeMaps); - return returnCode; + return futures; + } + + /** + * Run the given command, report any messages produced using the reporting strategies given by + * {@code getBuildReportingStrategies}, and return its return code. + */ + public final int run(LFCommand command, CancelIndicator cancelIndicator) { + final int returnCode = command.run(cancelIndicator); + getBuildReportingStrategies() + .first + .report(command.getErrors().toString(), errorReporter, codeMaps); + getBuildReportingStrategies() + .second + .report(command.getOutput().toString(), errorReporter, codeMaps); + return returnCode; + } + + /** + * Return the validation strategies and validation commands corresponding to each generated file. + * + * @return the validation strategies and validation commands corresponding to each generated file + */ + private List> getValidationStrategies() { + final List> commands = new ArrayList<>(); + long mostRecentDateModified = + codeMaps.keySet().stream().map(it -> it.toFile().lastModified()).reduce(0L, Math::max); + for (Path generatedFile : codeMaps.keySet()) { + if (generatedFile.toFile().lastModified() + > mostRecentDateModified - FILE_AGE_THRESHOLD_MILLIS) { + final Pair p = getValidationStrategy(generatedFile); + if (p.first == null || p.second == null) continue; + commands.add(p); + if (p.first.isFullBatch()) break; + } } - - /** - * Return the validation strategies and validation - * commands corresponding to each generated file. - * @return the validation strategies and validation - * commands corresponding to each generated file - */ - private List> getValidationStrategies() { - final List> commands = new ArrayList<>(); - long mostRecentDateModified = codeMaps.keySet().stream() - .map(it -> it.toFile().lastModified()).reduce(0L, Math::max); - for (Path generatedFile : codeMaps.keySet()) { - if (generatedFile.toFile().lastModified() > mostRecentDateModified - FILE_AGE_THRESHOLD_MILLIS) { - final Pair p = getValidationStrategy(generatedFile); - if (p.first == null || p.second == null) continue; - commands.add(p); - if (p.first.isFullBatch()) break; - } - } - return commands; + return commands; + } + + /** + * Return the validation strategy and command corresponding to the given file if such a strategy + * and command are available. + * + * @return the validation strategy and command corresponding to the given file if such a strategy + * and command are available + */ + private Pair getValidationStrategy(Path generatedFile) { + List sorted = + getPossibleStrategies().stream() + .sorted(Comparator.comparingInt(vs -> -vs.getPriority())) + .toList(); + for (ValidationStrategy strategy : sorted) { + LFCommand validateCommand = strategy.getCommand(generatedFile); + if (validateCommand != null) { + return new Pair<>(strategy, validateCommand); + } } - - /** - * Return the validation strategy and command - * corresponding to the given file if such a strategy - * and command are available. - * @return the validation strategy and command - * corresponding to the given file if such a strategy - * and command are available - */ - private Pair getValidationStrategy(Path generatedFile) { - List sorted = getPossibleStrategies().stream() - .sorted(Comparator.comparingInt(vs -> -vs.getPriority())).toList(); - for (ValidationStrategy strategy : sorted) { - LFCommand validateCommand = strategy.getCommand(generatedFile); - if (validateCommand != null) { - return new Pair<>(strategy, validateCommand); - } - } - return new Pair<>(null, null); - } - - /** - * List all validation strategies that exist for the implementor - * without filtering by platform or availability. - */ - protected abstract Collection getPossibleStrategies(); - - /** - * Return the appropriate output and error reporting - * strategies for the main build process. - */ - protected abstract Pair getBuildReportingStrategies(); + return new Pair<>(null, null); + } + + /** + * List all validation strategies that exist for the implementor without filtering by platform or + * availability. + */ + protected abstract Collection getPossibleStrategies(); + + /** Return the appropriate output and error reporting strategies for the main build process. */ + protected abstract Pair + getBuildReportingStrategies(); } diff --git a/org.lflang/src/org/lflang/generator/WatchdogInstance.java b/org.lflang/src/org/lflang/generator/WatchdogInstance.java index 90823102d6..ac51f389cb 100644 --- a/org.lflang/src/org/lflang/generator/WatchdogInstance.java +++ b/org.lflang/src/org/lflang/generator/WatchdogInstance.java @@ -2,8 +2,8 @@ * @file * @author Benjamin Asch * @author Edward A. Lee - * @copyright (c) 2023, The University of California at Berkeley - * License in BSD 2-clause + * @copyright (c) 2023, The University of California at Berkeley License in BSD 2-clause * @brief Instance of a watchdog */ package org.lflang.generator; diff --git a/org.lflang/src/org/lflang/generator/c/CActionGenerator.java b/org.lflang/src/org/lflang/generator/c/CActionGenerator.java index 5f81de89aa..1a99c1b3e2 100644 --- a/org.lflang/src/org/lflang/generator/c/CActionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CActionGenerator.java @@ -1,15 +1,16 @@ package org.lflang.generator.c; -import java.util.List; +import static org.lflang.generator.c.CGenerator.variableStructType; + import java.util.ArrayList; -import org.lflang.ast.ASTUtils; +import java.util.List; import org.lflang.Target; +import org.lflang.ast.ASTUtils; import org.lflang.generator.ActionInstance; import org.lflang.generator.CodeBuilder; import org.lflang.generator.ReactorInstance; import org.lflang.lf.Action; -import static org.lflang.generator.c.CGenerator.variableStructType; /** * Generates code for actions (logical or physical) for the C and CCpp target. * @@ -23,153 +24,156 @@ * @author Hou Seng Wong */ public class CActionGenerator { - /** - * For each action of the specified reactor instance, generate initialization code - * for the offset and period fields. - * @param instance The reactor. - */ - public static String generateInitializers( - ReactorInstance instance - ) { - List code = new ArrayList<>(); - for (ActionInstance action : instance.actions) { - if (!action.isShutdown()) { - var triggerStructName = CUtil.reactorRef(action.getParent()) + "->_lf__" + action.getName(); - var minDelay = action.getMinDelay(); - var minSpacing = action.getMinSpacing(); - var offsetInitializer = triggerStructName+".offset = " + CTypes.getInstance().getTargetTimeExpr(minDelay) - + ";"; - var periodInitializer = triggerStructName+".period = " + (minSpacing != null ? - CTypes.getInstance().getTargetTimeExpr(minSpacing) : - CGenerator.UNDEFINED_MIN_SPACING) + ";"; - code.addAll(List.of( - "// Initializing action "+action.getFullName(), - offsetInitializer, - periodInitializer - )); + /** + * For each action of the specified reactor instance, generate initialization code for the offset + * and period fields. + * + * @param instance The reactor. + */ + public static String generateInitializers(ReactorInstance instance) { + List code = new ArrayList<>(); + for (ActionInstance action : instance.actions) { + if (!action.isShutdown()) { + var triggerStructName = CUtil.reactorRef(action.getParent()) + "->_lf__" + action.getName(); + var minDelay = action.getMinDelay(); + var minSpacing = action.getMinSpacing(); + var offsetInitializer = + triggerStructName + + ".offset = " + + CTypes.getInstance().getTargetTimeExpr(minDelay) + + ";"; + var periodInitializer = + triggerStructName + + ".period = " + + (minSpacing != null + ? CTypes.getInstance().getTargetTimeExpr(minSpacing) + : CGenerator.UNDEFINED_MIN_SPACING) + + ";"; + code.addAll( + List.of( + "// Initializing action " + action.getFullName(), + offsetInitializer, + periodInitializer)); - var mode = action.getMode(false); - if (mode != null) { - var modeParent = mode.getParent(); - var modeRef = "&"+CUtil.reactorRef(modeParent)+"->_lf__modes["+modeParent.modes.indexOf(mode)+"];"; - code.add(triggerStructName+".mode = "+modeRef+";"); - } else { - code.add(triggerStructName+".mode = NULL;"); - } - } + var mode = action.getMode(false); + if (mode != null) { + var modeParent = mode.getParent(); + var modeRef = + "&" + + CUtil.reactorRef(modeParent) + + "->_lf__modes[" + + modeParent.modes.indexOf(mode) + + "];"; + code.add(triggerStructName + ".mode = " + modeRef + ";"); + } else { + code.add(triggerStructName + ".mode = NULL;"); } - return String.join("\n", code); + } } + return String.join("\n", code); + } - /** - * Create a template token initialized to the payload size. - * This token is marked to not be freed so that the trigger_t struct - * always has a template token. - * At the start of each time step, we need to initialize the is_present field - * of each action's trigger object to false and free a previously - * allocated token if appropriate. This code sets up the table that does that. - * - * @param selfStruct The variable name of the self struct - * @param actionName The action name - * @param payloadSize The code that returns the size of the action's payload in C. - */ - public static String generateTokenInitializer( - String selfStruct, - String actionName, - String payloadSize - ) { - return String.join("\n", - "_lf_initialize_template((token_template_t*)", - " &("+selfStruct+"->_lf__"+actionName+"),", - payloadSize+");", - selfStruct+"->_lf__"+actionName+".status = absent;" - ); - } + /** + * Create a template token initialized to the payload size. This token is marked to not be freed + * so that the trigger_t struct always has a template token. At the start of each time step, we + * need to initialize the is_present field of each action's trigger object to false and free a + * previously allocated token if appropriate. This code sets up the table that does that. + * + * @param selfStruct The variable name of the self struct + * @param actionName The action name + * @param payloadSize The code that returns the size of the action's payload in C. + */ + public static String generateTokenInitializer( + String selfStruct, String actionName, String payloadSize) { + return String.join( + "\n", + "_lf_initialize_template((token_template_t*)", + " &(" + selfStruct + "->_lf__" + actionName + "),", + payloadSize + ");", + selfStruct + "->_lf__" + actionName + ".status = absent;"); + } - /** - * Generate the declarations of actions in the self struct - * - * @param body The content of the self struct - * @param constructorCode The constructor code of the reactor - */ - public static void generateDeclarations( - TypeParameterizedReactor tpr, - CodeBuilder body, - CodeBuilder constructorCode - ) { - for (Action action : ASTUtils.allActions(tpr.reactor())) { - var actionName = action.getName(); - body.pr(action, CGenerator.variableStructType(action, tpr, false)+" _lf_"+actionName+";"); - // Initialize the trigger pointer in the action. - constructorCode.pr(action, "self->_lf_"+actionName+".trigger = &self->_lf__"+actionName+";"); - } + /** + * Generate the declarations of actions in the self struct + * + * @param body The content of the self struct + * @param constructorCode The constructor code of the reactor + */ + public static void generateDeclarations( + TypeParameterizedReactor tpr, CodeBuilder body, CodeBuilder constructorCode) { + for (Action action : ASTUtils.allActions(tpr.reactor())) { + var actionName = action.getName(); + body.pr( + action, CGenerator.variableStructType(action, tpr, false) + " _lf_" + actionName + ";"); + // Initialize the trigger pointer in the action. + constructorCode.pr( + action, "self->_lf_" + actionName + ".trigger = &self->_lf__" + actionName + ";"); } + } - /** - * Generate the struct type definitions for the action of the - * reactor - * - * @param action The action to generate the struct for - * @param target The target of the code generation (C, CCpp or Python) - * @param types The helper object for types related stuff - * @param federatedExtension The code needed to support federated execution - * @return The auxiliary struct for the port as a string - */ - public static String generateAuxiliaryStruct( - TypeParameterizedReactor tpr, - Action action, - Target target, - CTypes types, - CodeBuilder federatedExtension, - boolean userFacing - ) { - var code = new CodeBuilder(); - code.pr("typedef struct {"); - code.indent(); - // NOTE: The following fields are required to be the first ones so that - // pointer to this struct can be cast to a (lf_action_base_t*) or to - // (token_template_t*) to access these fields for any port. - // IMPORTANT: These must match exactly the fields defined in port.h!! - code.pr(String.join("\n", - "token_type_t type;", // From token_template_t - "lf_token_t* token;", // From token_template_t - "size_t length;", // From token_template_t - "bool is_present;", // From lf_action_base_t - "bool has_value;", // From lf_action_base_t - "trigger_t* trigger;" // From lf_action_base_t - )); - code.pr(valueDeclaration(tpr, action, target, types)); - code.pr(federatedExtension.toString()); - code.unindent(); - code.pr("} " + variableStructType(action, tpr, userFacing) + ";"); - return code.toString(); - } + /** + * Generate the struct type definitions for the action of the reactor + * + * @param action The action to generate the struct for + * @param target The target of the code generation (C, CCpp or Python) + * @param types The helper object for types related stuff + * @param federatedExtension The code needed to support federated execution + * @return The auxiliary struct for the port as a string + */ + public static String generateAuxiliaryStruct( + TypeParameterizedReactor tpr, + Action action, + Target target, + CTypes types, + CodeBuilder federatedExtension, + boolean userFacing) { + var code = new CodeBuilder(); + code.pr("typedef struct {"); + code.indent(); + // NOTE: The following fields are required to be the first ones so that + // pointer to this struct can be cast to a (lf_action_base_t*) or to + // (token_template_t*) to access these fields for any port. + // IMPORTANT: These must match exactly the fields defined in port.h!! + code.pr( + String.join( + "\n", + "token_type_t type;", // From token_template_t + "lf_token_t* token;", // From token_template_t + "size_t length;", // From token_template_t + "bool is_present;", // From lf_action_base_t + "bool has_value;", // From lf_action_base_t + "trigger_t* trigger;" // From lf_action_base_t + )); + code.pr(valueDeclaration(tpr, action, target, types)); + code.pr(federatedExtension.toString()); + code.unindent(); + code.pr("} " + variableStructType(action, tpr, userFacing) + ";"); + return code.toString(); + } - /** - * For the specified action, return a declaration for action struct to - * contain the value of the action. An action of - * type int[10], for example, will result in this: - *


-     *     int* value;
-     * 
- * This will return an empty string for an action with no type. - * @param tpr {@link TypeParameterizedReactor} - * @param action The action. - * @return A string providing the value field of the action struct. - */ - private static String valueDeclaration( - TypeParameterizedReactor tpr, - Action action, - Target target, - CTypes types - ) { - if (target == Target.Python) { - return "PyObject* value;"; - } - // Do not convert to lf_token_t* using lfTypeToTokenType because there - // will be a separate field pointing to the token. - return action.getType() == null && target.requiresTypes ? - "" : - types.getTargetType(tpr.resolveType(action.getType())) + " value;"; + /** + * For the specified action, return a declaration for action struct to contain the value of the + * action. An action of type int[10], for example, will result in this: + * + *

+   *     int* value;
+   * 
+ * + * This will return an empty string for an action with no type. + * + * @param tpr {@link TypeParameterizedReactor} + * @param action The action. + * @return A string providing the value field of the action struct. + */ + private static String valueDeclaration( + TypeParameterizedReactor tpr, Action action, Target target, CTypes types) { + if (target == Target.Python) { + return "PyObject* value;"; } + // Do not convert to lf_token_t* using lfTypeToTokenType because there + // will be a separate field pointing to the token. + return action.getType() == null && target.requiresTypes + ? "" + : types.getTargetType(tpr.resolveType(action.getType())) + " value;"; + } } diff --git a/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java b/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java index fdbe4d396b..860c85a75d 100644 --- a/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java @@ -1,16 +1,16 @@ /************* * 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 @@ -31,7 +31,6 @@ import java.util.List; import java.util.Objects; import java.util.stream.Stream; - import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.TargetConfig; @@ -42,358 +41,364 @@ /** * A helper class that generates a CMakefile that can be used to compile the generated C code. * - * Adapted from @see org.lflang.generator.CppCmakeGenerator.kt + *

Adapted from @see org.lflang.generator.CppCmakeGenerator.kt * * @author Soroush Bateni * @author Peter Donovan */ public class CCmakeGenerator { - private static final String DEFAULT_INSTALL_CODE = """ + private static final String DEFAULT_INSTALL_CODE = + """ install( TARGETS ${LF_MAIN_TARGET} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) """; - public static final String MIN_CMAKE_VERSION = "3.19"; - - private final FileConfig fileConfig; - private final List additionalSources; - private final SetUpMainTarget setUpMainTarget; - private final String installCode; - - public CCmakeGenerator( - FileConfig fileConfig, - List additionalSources - ) { - this.fileConfig = fileConfig; - this.additionalSources = additionalSources; - this.setUpMainTarget = CCmakeGenerator::setUpMainTarget; - this.installCode = DEFAULT_INSTALL_CODE; + public static final String MIN_CMAKE_VERSION = "3.19"; + + private final FileConfig fileConfig; + private final List additionalSources; + private final SetUpMainTarget setUpMainTarget; + private final String installCode; + + public CCmakeGenerator(FileConfig fileConfig, List additionalSources) { + this.fileConfig = fileConfig; + this.additionalSources = additionalSources; + this.setUpMainTarget = CCmakeGenerator::setUpMainTarget; + this.installCode = DEFAULT_INSTALL_CODE; + } + + public CCmakeGenerator( + FileConfig fileConfig, + List additionalSources, + SetUpMainTarget setUpMainTarget, + String installCode) { + this.fileConfig = fileConfig; + this.additionalSources = additionalSources; + this.setUpMainTarget = setUpMainTarget; + this.installCode = installCode; + } + + /** + * Generate the contents of a CMakeLists.txt that builds the provided LF C 'sources'. Any error + * will be reported in the 'errorReporter'. + * + * @param sources A list of .c files to build. + * @param executableName The name of the output executable. + * @param errorReporter Used to report errors. + * @param CppMode Indicate if the compilation should happen in C++ mode + * @param hasMain Indicate if the .lf file has a main reactor or not. If not, a library target + * will be created instead of an executable. + * @param cMakeExtras CMake-specific code that should be appended to the CMakeLists.txt. + * @param targetConfig The TargetConfig instance to use. + * @return The content of the CMakeLists.txt. + */ + CodeBuilder generateCMakeCode( + List sources, + String executableName, + ErrorReporter errorReporter, + boolean CppMode, + boolean hasMain, + String cMakeExtras, + TargetConfig targetConfig) { + CodeBuilder cMakeCode = new CodeBuilder(); + + List additionalSources = new ArrayList<>(); + for (String file : targetConfig.compileAdditionalSources) { + var relativePath = + fileConfig + .getSrcGenPath() + .relativize(fileConfig.getSrcGenPath().resolve(Paths.get(file))); + additionalSources.add(FileUtil.toUnixString(relativePath)); } - - public CCmakeGenerator( - FileConfig fileConfig, - List additionalSources, - SetUpMainTarget setUpMainTarget, - String installCode - ) { - this.fileConfig = fileConfig; - this.additionalSources = additionalSources; - this.setUpMainTarget = setUpMainTarget; - this.installCode = installCode; + additionalSources.addAll(this.additionalSources); + cMakeCode.newLine(); + + cMakeCode.pr("cmake_minimum_required(VERSION " + MIN_CMAKE_VERSION + ")"); + + if (targetConfig.platformOptions.platform == Platform.ZEPHYR) { + cMakeCode.pr("# Set default configuration file. To add custom configurations,"); + cMakeCode.pr("# pass -- -DOVERLAY_CONFIG=my_config.prj to either cmake or west"); + cMakeCode.pr("set(CONF_FILE prj_lf.conf)"); + if (targetConfig.platformOptions.board != null) { + cMakeCode.pr("# Selecting board specified in target property"); + cMakeCode.pr("set(BOARD " + targetConfig.platformOptions.board + ")"); + } else { + cMakeCode.pr("# Selecting default board"); + cMakeCode.pr("set(BOARD qemu_cortex_m3)"); + } + cMakeCode.pr("# We require Zephyr version 3.2.0"); + cMakeCode.pr("find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE} 3.2.0)"); + cMakeCode.newLine(); } - /** - * Generate the contents of a CMakeLists.txt that builds the provided LF C 'sources'. Any error will be - * reported in the 'errorReporter'. - * - * @param sources A list of .c files to build. - * @param executableName The name of the output executable. - * @param errorReporter Used to report errors. - * @param CppMode Indicate if the compilation should happen in C++ mode - * @param hasMain Indicate if the .lf file has a main reactor or not. If not, - * a library target will be created instead of an executable. - * @param cMakeExtras CMake-specific code that should be appended to the CMakeLists.txt. - * @param targetConfig The TargetConfig instance to use. - * @return The content of the CMakeLists.txt. - */ - CodeBuilder generateCMakeCode( - List sources, - String executableName, - ErrorReporter errorReporter, - boolean CppMode, - boolean hasMain, - String cMakeExtras, - TargetConfig targetConfig - ) { - CodeBuilder cMakeCode = new CodeBuilder(); - - List additionalSources = new ArrayList<>(); - for (String file: targetConfig.compileAdditionalSources) { - var relativePath = fileConfig.getSrcGenPath().relativize( - fileConfig.getSrcGenPath().resolve(Paths.get(file))); - additionalSources.add(FileUtil.toUnixString(relativePath)); - } - additionalSources.addAll(this.additionalSources); - cMakeCode.newLine(); - - cMakeCode.pr("cmake_minimum_required(VERSION " + MIN_CMAKE_VERSION + ")"); - - if (targetConfig.platformOptions.platform == Platform.ZEPHYR) { - cMakeCode.pr("# Set default configuration file. To add custom configurations,"); - cMakeCode.pr("# pass -- -DOVERLAY_CONFIG=my_config.prj to either cmake or west"); - cMakeCode.pr("set(CONF_FILE prj_lf.conf)"); - if (targetConfig.platformOptions.board != null) { - cMakeCode.pr("# Selecting board specified in target property"); - cMakeCode.pr("set(BOARD "+targetConfig.platformOptions.board+")"); - } else { - cMakeCode.pr("# Selecting default board"); - cMakeCode.pr("set(BOARD qemu_cortex_m3)"); - } - cMakeCode.pr("# We require Zephyr version 3.2.0"); - cMakeCode.pr("find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE} 3.2.0)"); - cMakeCode.newLine(); - } - - cMakeCode.pr("project("+executableName+" LANGUAGES C)"); - cMakeCode.newLine(); - - // The Test build type is the Debug type plus coverage generation - cMakeCode.pr("if(CMAKE_BUILD_TYPE STREQUAL \"Test\")"); - cMakeCode.pr(" set(CMAKE_BUILD_TYPE \"Debug\")"); - cMakeCode.pr(" if(CMAKE_C_COMPILER_ID STREQUAL \"GNU\")"); - cMakeCode.pr(" find_program(LCOV_BIN lcov)"); - cMakeCode.pr(" if(LCOV_BIN MATCHES \"lcov$\")"); - cMakeCode.pr(" set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} --coverage -fprofile-arcs -ftest-coverage\")"); - cMakeCode.pr(" else()"); - cMakeCode.pr(" message(\"Not producing code coverage information since lcov was not found\")"); - cMakeCode.pr(" endif()"); - cMakeCode.pr(" else()"); - cMakeCode.pr(" message(\"Not producing code coverage information since the selected compiler is no gcc\")"); - cMakeCode.pr(" endif()"); - cMakeCode.pr("endif()"); - - cMakeCode.pr("# Require C11"); - cMakeCode.pr("set(CMAKE_C_STANDARD 11)"); - cMakeCode.pr("set(CMAKE_C_STANDARD_REQUIRED ON)"); - cMakeCode.newLine(); - - cMakeCode.pr("# Require C++17"); - cMakeCode.pr("set(CMAKE_CXX_STANDARD 17)"); - cMakeCode.pr("set(CMAKE_CXX_STANDARD_REQUIRED ON)"); - cMakeCode.newLine(); - if (!targetConfig.cmakeIncludes.isEmpty()) { - // The user might be using the non-keyword form of - // target_link_libraries. Ideally we would detect whether they are - // doing that, but it is easier to just always have a deprecation - // warning. - cMakeCode.pr(""" + cMakeCode.pr("project(" + executableName + " LANGUAGES C)"); + cMakeCode.newLine(); + + // The Test build type is the Debug type plus coverage generation + cMakeCode.pr("if(CMAKE_BUILD_TYPE STREQUAL \"Test\")"); + cMakeCode.pr(" set(CMAKE_BUILD_TYPE \"Debug\")"); + cMakeCode.pr(" if(CMAKE_C_COMPILER_ID STREQUAL \"GNU\")"); + cMakeCode.pr(" find_program(LCOV_BIN lcov)"); + cMakeCode.pr(" if(LCOV_BIN MATCHES \"lcov$\")"); + cMakeCode.pr( + " set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} --coverage -fprofile-arcs -ftest-coverage\")"); + cMakeCode.pr(" else()"); + cMakeCode.pr( + " message(\"Not producing code coverage information since lcov was not found\")"); + cMakeCode.pr(" endif()"); + cMakeCode.pr(" else()"); + cMakeCode.pr( + " message(\"Not producing code coverage information since the selected compiler is no" + + " gcc\")"); + cMakeCode.pr(" endif()"); + cMakeCode.pr("endif()"); + + cMakeCode.pr("# Require C11"); + cMakeCode.pr("set(CMAKE_C_STANDARD 11)"); + cMakeCode.pr("set(CMAKE_C_STANDARD_REQUIRED ON)"); + cMakeCode.newLine(); + + cMakeCode.pr("# Require C++17"); + cMakeCode.pr("set(CMAKE_CXX_STANDARD 17)"); + cMakeCode.pr("set(CMAKE_CXX_STANDARD_REQUIRED ON)"); + cMakeCode.newLine(); + if (!targetConfig.cmakeIncludes.isEmpty()) { + // The user might be using the non-keyword form of + // target_link_libraries. Ideally we would detect whether they are + // doing that, but it is easier to just always have a deprecation + // warning. + cMakeCode.pr( + """ cmake_policy(SET CMP0023 OLD) # This causes deprecation warnings - """ - ); - } - - // Set the build type - cMakeCode.pr("set(DEFAULT_BUILD_TYPE " + targetConfig.cmakeBuildType + ")\n"); - cMakeCode.pr("if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)\n"); - cMakeCode.pr(" set(CMAKE_BUILD_TYPE ${DEFAULT_BUILD_TYPE} CACHE STRING \"Choose the type of build.\" FORCE)\n"); - cMakeCode.pr("endif()\n"); - cMakeCode.newLine(); - - cMakeCode.pr("# do not print install messages\n"); - cMakeCode.pr("set(CMAKE_INSTALL_MESSAGE NEVER)\n"); - cMakeCode.newLine(); - - if (CppMode) { - // Suppress warnings about const char*. - cMakeCode.pr("set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wno-write-strings\")"); - cMakeCode.newLine(); - } - - if (targetConfig.platformOptions.platform != Platform.AUTO) { - cMakeCode.pr("set(CMAKE_SYSTEM_NAME "+targetConfig.platformOptions.platform.getcMakeName()+")"); - } - cMakeCode.newLine(); - - if (targetConfig.platformOptions.platform == Platform.ZEPHYR) { - cMakeCode.pr(setUpMainTargetZephyr( - hasMain, - executableName, - Stream.concat(additionalSources.stream(), sources.stream()) - )); - } else { - cMakeCode.pr(setUpMainTarget.getCmakeCode( - hasMain, - executableName, - Stream.concat(additionalSources.stream(), sources.stream()) - )); - } - - 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)"); - cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/core/platform)"); - cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/core/modal_models)"); - cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/core/utils)"); - - - if(targetConfig.auth) { - // If security is requested, add the auth option. - 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(); - } - if (osName.contains("mac")) { - cMakeCode.pr("set(OPENSSL_ROOT_DIR /usr/local/opt/openssl)"); - } - cMakeCode.pr("# Find OpenSSL and link to it"); - cMakeCode.pr("find_package(OpenSSL REQUIRED)"); - cMakeCode.pr("target_link_libraries( ${LF_MAIN_TARGET} PRIVATE OpenSSL::SSL)"); - cMakeCode.newLine(); - } - - if (targetConfig.threading || targetConfig.tracing != null) { - // If threaded computation is requested, add the threads option. - cMakeCode.pr("# Find threads and link to it"); - cMakeCode.pr("find_package(Threads REQUIRED)"); - cMakeCode.pr("target_link_libraries(${LF_MAIN_TARGET} PRIVATE Threads::Threads)"); - cMakeCode.newLine(); - - // If the LF program itself is threaded or if tracing is enabled, we need to define - // NUMBER_OF_WORKERS so that platform-specific C files will contain the appropriate functions - cMakeCode.pr("# Set the number of workers to enable threading/tracing"); - cMakeCode.pr("target_compile_definitions(${LF_MAIN_TARGET} PUBLIC NUMBER_OF_WORKERS="+targetConfig.workers+")"); - cMakeCode.newLine(); - } - - // Add additional flags so runtime can distinguish between multi-threaded and single-threaded mode - if (targetConfig.threading) { - cMakeCode.pr("# Set flag to indicate a multi-threaded runtime"); - cMakeCode.pr("target_compile_definitions( ${LF_MAIN_TARGET} PUBLIC LF_THREADED=1)"); - } else { - cMakeCode.pr("# Set flag to indicate a single-threaded runtime"); - cMakeCode.pr("target_compile_definitions( ${LF_MAIN_TARGET} PUBLIC LF_UNTHREADED=1)"); - } - cMakeCode.newLine(); - - - if (CppMode) cMakeCode.pr("enable_language(CXX)"); - - if (targetConfig.compiler != null && !targetConfig.compiler.isBlank()) { - if (CppMode) { - // Set the CXX compiler to what the user has requested. - cMakeCode.pr("set(CMAKE_CXX_COMPILER "+targetConfig.compiler+")"); - } else { - cMakeCode.pr("set(CMAKE_C_COMPILER "+targetConfig.compiler+")"); - } - cMakeCode.newLine(); - } - - // Set the compiler flags - // We can detect a few common libraries and use the proper target_link_libraries to find them - for (String compilerFlag : targetConfig.compilerFlags) { - switch(compilerFlag.trim()) { - case "-lm": - cMakeCode.pr("target_link_libraries(${LF_MAIN_TARGET} PRIVATE m)"); - break; - case "-lprotobuf-c": - cMakeCode.pr("include(FindPackageHandleStandardArgs)"); - cMakeCode.pr("FIND_PATH( PROTOBUF_INCLUDE_DIR protobuf-c/protobuf-c.h)"); - cMakeCode.pr(""" + """); + } + + // Set the build type + cMakeCode.pr("set(DEFAULT_BUILD_TYPE " + targetConfig.cmakeBuildType + ")\n"); + cMakeCode.pr("if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)\n"); + cMakeCode.pr( + " set(CMAKE_BUILD_TYPE ${DEFAULT_BUILD_TYPE} CACHE STRING \"Choose the type of build.\"" + + " FORCE)\n"); + cMakeCode.pr("endif()\n"); + cMakeCode.newLine(); + + cMakeCode.pr("# do not print install messages\n"); + cMakeCode.pr("set(CMAKE_INSTALL_MESSAGE NEVER)\n"); + cMakeCode.newLine(); + + if (CppMode) { + // Suppress warnings about const char*. + cMakeCode.pr("set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wno-write-strings\")"); + cMakeCode.newLine(); + } + + if (targetConfig.platformOptions.platform != Platform.AUTO) { + cMakeCode.pr( + "set(CMAKE_SYSTEM_NAME " + targetConfig.platformOptions.platform.getcMakeName() + ")"); + } + cMakeCode.newLine(); + + if (targetConfig.platformOptions.platform == Platform.ZEPHYR) { + cMakeCode.pr( + setUpMainTargetZephyr( + hasMain, + executableName, + Stream.concat(additionalSources.stream(), sources.stream()))); + } else { + cMakeCode.pr( + setUpMainTarget.getCmakeCode( + hasMain, + executableName, + Stream.concat(additionalSources.stream(), sources.stream()))); + } + + 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)"); + cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/core/platform)"); + cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/core/modal_models)"); + cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/core/utils)"); + + if (targetConfig.auth) { + // If security is requested, add the auth option. + 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(); + } + if (osName.contains("mac")) { + cMakeCode.pr("set(OPENSSL_ROOT_DIR /usr/local/opt/openssl)"); + } + cMakeCode.pr("# Find OpenSSL and link to it"); + cMakeCode.pr("find_package(OpenSSL REQUIRED)"); + cMakeCode.pr("target_link_libraries( ${LF_MAIN_TARGET} PRIVATE OpenSSL::SSL)"); + cMakeCode.newLine(); + } + + if (targetConfig.threading || targetConfig.tracing != null) { + // If threaded computation is requested, add the threads option. + cMakeCode.pr("# Find threads and link to it"); + cMakeCode.pr("find_package(Threads REQUIRED)"); + cMakeCode.pr("target_link_libraries(${LF_MAIN_TARGET} PRIVATE Threads::Threads)"); + cMakeCode.newLine(); + + // If the LF program itself is threaded or if tracing is enabled, we need to define + // NUMBER_OF_WORKERS so that platform-specific C files will contain the appropriate functions + cMakeCode.pr("# Set the number of workers to enable threading/tracing"); + cMakeCode.pr( + "target_compile_definitions(${LF_MAIN_TARGET} PUBLIC NUMBER_OF_WORKERS=" + + targetConfig.workers + + ")"); + cMakeCode.newLine(); + } + + // Add additional flags so runtime can distinguish between multi-threaded and single-threaded + // mode + if (targetConfig.threading) { + cMakeCode.pr("# Set flag to indicate a multi-threaded runtime"); + cMakeCode.pr("target_compile_definitions( ${LF_MAIN_TARGET} PUBLIC LF_THREADED=1)"); + } else { + cMakeCode.pr("# Set flag to indicate a single-threaded runtime"); + cMakeCode.pr("target_compile_definitions( ${LF_MAIN_TARGET} PUBLIC LF_UNTHREADED=1)"); + } + cMakeCode.newLine(); + + if (CppMode) cMakeCode.pr("enable_language(CXX)"); + + if (targetConfig.compiler != null && !targetConfig.compiler.isBlank()) { + if (CppMode) { + // Set the CXX compiler to what the user has requested. + cMakeCode.pr("set(CMAKE_CXX_COMPILER " + targetConfig.compiler + ")"); + } else { + cMakeCode.pr("set(CMAKE_C_COMPILER " + targetConfig.compiler + ")"); + } + cMakeCode.newLine(); + } + + // Set the compiler flags + // We can detect a few common libraries and use the proper target_link_libraries to find them + for (String compilerFlag : targetConfig.compilerFlags) { + switch (compilerFlag.trim()) { + case "-lm": + cMakeCode.pr("target_link_libraries(${LF_MAIN_TARGET} PRIVATE m)"); + break; + case "-lprotobuf-c": + cMakeCode.pr("include(FindPackageHandleStandardArgs)"); + cMakeCode.pr("FIND_PATH( PROTOBUF_INCLUDE_DIR protobuf-c/protobuf-c.h)"); + cMakeCode.pr( + """ find_library(PROTOBUF_LIBRARY\s NAMES libprotobuf-c.a libprotobuf-c.so libprotobuf-c.dylib protobuf-c.lib protobuf-c.dll )"""); - cMakeCode.pr("find_package_handle_standard_args(libprotobuf-c DEFAULT_MSG PROTOBUF_INCLUDE_DIR PROTOBUF_LIBRARY)"); - cMakeCode.pr("target_include_directories( ${LF_MAIN_TARGET} PUBLIC ${PROTOBUF_INCLUDE_DIR} )"); - cMakeCode.pr("target_link_libraries(${LF_MAIN_TARGET} PRIVATE ${PROTOBUF_LIBRARY})"); - break; - case "-O2": - if (Objects.equals(targetConfig.compiler, "gcc") || CppMode) { - // Workaround for the pre-added -O2 option in the CGenerator. - // This flag is specific to gcc/g++ and the clang compiler - cMakeCode.pr("add_compile_options(-O2)"); - cMakeCode.pr("add_link_options(-O2)"); - break; - } - default: - errorReporter.reportWarning("Using the flags target property with cmake is dangerous.\n"+ - " Use cmake-include instead."); - cMakeCode.pr("add_compile_options( "+compilerFlag+" )"); - cMakeCode.pr("add_link_options( "+compilerFlag+")"); - } - } - cMakeCode.newLine(); - - // Add the install option - cMakeCode.pr(installCode); - cMakeCode.newLine(); - - // Add the include file - for (String includeFile : targetConfig.cmakeIncludes) { - cMakeCode.pr("include(\""+ Path.of(includeFile).getFileName()+"\")"); - } - cMakeCode.newLine(); - - cMakeCode.pr(cMakeExtras); - cMakeCode.newLine(); - - return cMakeCode; + cMakeCode.pr( + "find_package_handle_standard_args(libprotobuf-c DEFAULT_MSG PROTOBUF_INCLUDE_DIR" + + " PROTOBUF_LIBRARY)"); + cMakeCode.pr( + "target_include_directories( ${LF_MAIN_TARGET} PUBLIC ${PROTOBUF_INCLUDE_DIR} )"); + cMakeCode.pr("target_link_libraries(${LF_MAIN_TARGET} PRIVATE ${PROTOBUF_LIBRARY})"); + break; + case "-O2": + if (Objects.equals(targetConfig.compiler, "gcc") || CppMode) { + // Workaround for the pre-added -O2 option in the CGenerator. + // This flag is specific to gcc/g++ and the clang compiler + cMakeCode.pr("add_compile_options(-O2)"); + cMakeCode.pr("add_link_options(-O2)"); + break; + } + default: + errorReporter.reportWarning( + "Using the flags target property with cmake is dangerous.\n" + + " Use cmake-include instead."); + cMakeCode.pr("add_compile_options( " + compilerFlag + " )"); + cMakeCode.pr("add_link_options( " + compilerFlag + ")"); + } } + cMakeCode.newLine(); - /** Provide a strategy for configuring the main target of the CMake build. */ - public interface SetUpMainTarget { - // Implementation note: This indirection is necessary because the Python - // target produces a shared object file, not an executable. - String getCmakeCode(boolean hasMain, String executableName, Stream cSources); - } + // Add the install option + cMakeCode.pr(installCode); + cMakeCode.newLine(); - /** Generate the C-target-specific code for configuring the executable produced by the build. */ - private static String setUpMainTarget( - boolean hasMain, - String executableName, - Stream cSources - ) { - var code = new CodeBuilder(); - code.pr("add_subdirectory(core)"); - code.newLine(); - code.pr("set(LF_MAIN_TARGET "+executableName+")"); - code.newLine(); - - if (hasMain) { - code.pr("# Declare a new executable target and list all its sources"); - code.pr("add_executable("); - } else { - code.pr("# Declare a new library target and list all its sources"); - code.pr("add_library("); - } - code.indent(); - code.pr("${LF_MAIN_TARGET}"); - cSources.forEach(code::pr); - code.unindent(); - code.pr(")"); - code.newLine(); - return code.toString(); + // Add the include file + for (String includeFile : targetConfig.cmakeIncludes) { + cMakeCode.pr("include(\"" + Path.of(includeFile).getFileName() + "\")"); } + cMakeCode.newLine(); + + cMakeCode.pr(cMakeExtras); + cMakeCode.newLine(); + + return cMakeCode; + } + + /** Provide a strategy for configuring the main target of the CMake build. */ + public interface SetUpMainTarget { + // Implementation note: This indirection is necessary because the Python + // target produces a shared object file, not an executable. + String getCmakeCode(boolean hasMain, String executableName, Stream cSources); + } + + /** Generate the C-target-specific code for configuring the executable produced by the build. */ + private static String setUpMainTarget( + boolean hasMain, String executableName, Stream cSources) { + var code = new CodeBuilder(); + code.pr("add_subdirectory(core)"); + code.newLine(); + code.pr("set(LF_MAIN_TARGET " + executableName + ")"); + code.newLine(); + + if (hasMain) { + code.pr("# Declare a new executable target and list all its sources"); + code.pr("add_executable("); + } else { + code.pr("# Declare a new library target and list all its sources"); + code.pr("add_library("); + } + code.indent(); + code.pr("${LF_MAIN_TARGET}"); + cSources.forEach(code::pr); + code.unindent(); + code.pr(")"); + code.newLine(); + return code.toString(); + } + + private static String setUpMainTargetZephyr( + boolean hasMain, String executableName, Stream cSources) { + var code = new CodeBuilder(); + code.pr("add_subdirectory(core)"); + code.pr("target_link_libraries(core PUBLIC zephyr_interface)"); + // FIXME: Linking the reactor-c corelib with the zephyr kernel lib + // resolves linker issues but I am not yet sure if it is safe + code.pr("target_link_libraries(core PRIVATE kernel)"); + code.newLine(); + + if (hasMain) { + code.pr("# Declare a new executable target and list all its sources"); + code.pr("set(LF_MAIN_TARGET app)"); + code.pr("target_sources("); + } else { + code.pr("# Declare a new library target and list all its sources"); + code.pr("set(LF_MAIN_TARGET" + executableName + ")"); + code.pr("add_library("); + } + code.indent(); + code.pr("${LF_MAIN_TARGET}"); - private static String setUpMainTargetZephyr( - boolean hasMain, - String executableName, - Stream cSources - ) { - var code = new CodeBuilder(); - code.pr("add_subdirectory(core)"); - code.pr("target_link_libraries(core PUBLIC zephyr_interface)"); - // FIXME: Linking the reactor-c corelib with the zephyr kernel lib - // resolves linker issues but I am not yet sure if it is safe - code.pr("target_link_libraries(core PRIVATE kernel)"); - code.newLine(); - - if (hasMain) { - code.pr("# Declare a new executable target and list all its sources"); - code.pr("set(LF_MAIN_TARGET app)"); - code.pr("target_sources("); - } else { - code.pr("# Declare a new library target and list all its sources"); - code.pr("set(LF_MAIN_TARGET"+executableName+")"); - code.pr("add_library("); - } - code.indent(); - code.pr("${LF_MAIN_TARGET}"); - - if (hasMain) { - code.pr("PRIVATE"); - } - - cSources.forEach(code::pr); - code.unindent(); - code.pr(")"); - code.newLine(); - return code.toString(); + if (hasMain) { + code.pr("PRIVATE"); } + + cSources.forEach(code::pr); + code.unindent(); + code.pr(")"); + code.newLine(); + return code.toString(); + } } diff --git a/org.lflang/src/org/lflang/generator/c/CCompiler.java b/org.lflang/src/org/lflang/generator/c/CCompiler.java index 9fcb72b4a6..f919977ba5 100644 --- a/org.lflang/src/org/lflang/generator/c/CCompiler.java +++ b/org.lflang/src/org/lflang/generator/c/CCompiler.java @@ -1,16 +1,16 @@ /************* * 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 @@ -33,7 +33,6 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; - import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.TargetConfig; @@ -47,8 +46,8 @@ import org.lflang.util.LFCommand; /** - * Responsible for creating and executing the necessary CMake command to compile - * code that is generated by the CGenerator. This class uses CMake to compile. + * Responsible for creating and executing the necessary CMake command to compile code that is + * generated by the CGenerator. This class uses CMake to compile. * * @author Soroush Bateni * @author Edward A. Lee @@ -59,392 +58,386 @@ */ public class CCompiler { - FileConfig fileConfig; - TargetConfig targetConfig; - ErrorReporter errorReporter; - - /** - * Indicate whether the compiler is in C++ mode. - * In C++ mode, the compiler produces .cpp files instead - * of .c files and uses a C++ compiler to compiler the code. - */ - private final boolean cppMode; - - /** - * A factory for compiler commands. - */ - GeneratorCommandFactory commandFactory; - - /** - * Create an instance of CCompiler. - * - * @param targetConfig The current target configuration. - * @param fileConfig The current file configuration. - * @param errorReporter Used to report errors. - * @param cppMode Whether the generated code should be compiled as if it - * were C++. - */ - public CCompiler( - TargetConfig targetConfig, - FileConfig fileConfig, - ErrorReporter errorReporter, - boolean cppMode - ) { - this.fileConfig = fileConfig; - this.targetConfig = targetConfig; - this.errorReporter = errorReporter; - this.commandFactory = new GeneratorCommandFactory(errorReporter, fileConfig); - this.cppMode = cppMode; + FileConfig fileConfig; + TargetConfig targetConfig; + ErrorReporter errorReporter; + + /** + * Indicate whether the compiler is in C++ mode. In C++ mode, the compiler produces .cpp files + * instead of .c files and uses a C++ compiler to compiler the code. + */ + private final boolean cppMode; + + /** A factory for compiler commands. */ + GeneratorCommandFactory commandFactory; + + /** + * Create an instance of CCompiler. + * + * @param targetConfig The current target configuration. + * @param fileConfig The current file configuration. + * @param errorReporter Used to report errors. + * @param cppMode Whether the generated code should be compiled as if it were C++. + */ + public CCompiler( + TargetConfig targetConfig, + FileConfig fileConfig, + ErrorReporter errorReporter, + boolean cppMode) { + this.fileConfig = fileConfig; + this.targetConfig = targetConfig; + this.errorReporter = errorReporter; + this.commandFactory = new GeneratorCommandFactory(errorReporter, fileConfig); + this.cppMode = cppMode; + } + + /** + * Run the C compiler by invoking cmake and make. + * + * @param generator An instance of GeneratorBase, only used to report error line numbers in the + * Eclipse IDE. + * @return true if compilation succeeds, false otherwise. + */ + public boolean runCCompiler(GeneratorBase generator, LFGeneratorContext context) + throws IOException { + // Set the build directory to be "build" + Path buildPath = fileConfig.getSrcGenPath().resolve("build"); + // Remove the previous build directory if it exists to + // avoid any error residue that can occur in CMake from + // a previous build. + // FIXME: This is slow and only needed if an error + // has previously occurred. Deleting the build directory + // if no prior errors have occurred can prolong the compilation + // substantially. See #1416 for discussion. + FileUtil.deleteDirectory(buildPath); + // Make sure the build directory exists + Files.createDirectories(buildPath); + + LFCommand compile = compileCmakeCommand(); + if (compile == null) { + return false; } - /** - * Run the C compiler by invoking cmake and make. - * @param generator An instance of GeneratorBase, only used to report error line numbers - * in the Eclipse IDE. - * - * @return true if compilation succeeds, false otherwise. - */ - public boolean runCCompiler( - GeneratorBase generator, - LFGeneratorContext context - ) throws IOException { - // Set the build directory to be "build" - Path buildPath = fileConfig.getSrcGenPath().resolve("build"); - // Remove the previous build directory if it exists to - // avoid any error residue that can occur in CMake from - // a previous build. - // FIXME: This is slow and only needed if an error - // has previously occurred. Deleting the build directory - // if no prior errors have occurred can prolong the compilation - // substantially. See #1416 for discussion. - FileUtil.deleteDirectory(buildPath); - // Make sure the build directory exists - Files.createDirectories(buildPath); - - LFCommand compile = compileCmakeCommand(); - if (compile == null) { - return false; - } - - // Use the user-specified compiler if any - if (targetConfig.compiler != null) { - if (cppMode) { - // Set the CXX environment variable to change the C++ compiler. - compile.replaceEnvironmentVariable("CXX", targetConfig.compiler); - } else { - // Set the CC environment variable to change the C compiler. - compile.replaceEnvironmentVariable("CC", targetConfig.compiler); - } - } - - int cMakeReturnCode = compile.run(context.getCancelIndicator()); - - if (cMakeReturnCode != 0 && - context.getMode() == LFGeneratorContext.Mode.STANDALONE && - !outputContainsKnownCMakeErrors(compile.getErrors().toString())) { - errorReporter.reportError(targetConfig.compiler + " failed with error code " + cMakeReturnCode); - } - - // For warnings (vs. errors), the return code is 0. - // But we still want to mark the IDE. - if (compile.getErrors().toString().length() > 0 && - context.getMode() != LFGeneratorContext.Mode.STANDALONE && - !outputContainsKnownCMakeErrors(compile.getErrors().toString())) { - generator.reportCommandErrors(compile.getErrors().toString()); - } - - int makeReturnCode = 0; - - if (cMakeReturnCode == 0) { - LFCommand build = buildCmakeCommand(); - - makeReturnCode = build.run(context.getCancelIndicator()); - - if (makeReturnCode != 0 && - context.getMode() == LFGeneratorContext.Mode.STANDALONE && - !outputContainsKnownCMakeErrors(build.getErrors().toString())) { - errorReporter.reportError(targetConfig.compiler + " failed with error code " + makeReturnCode); - } - - // For warnings (vs. errors), the return code is 0. - // But we still want to mark the IDE. - if (build.getErrors().toString().length() > 0 && - context.getMode() != LFGeneratorContext.Mode.STANDALONE && - !outputContainsKnownCMakeErrors(build.getErrors().toString())) { - generator.reportCommandErrors(build.getErrors().toString()); - } - - - if (makeReturnCode == 0 && build.getErrors().toString().length() == 0) { - System.out.println("SUCCESS: Compiling generated code for " + fileConfig.name + " finished with no errors."); - } - - if (targetConfig.platformOptions.platform == Platform.ZEPHYR && targetConfig.platformOptions.flash) { - System.out.println("Invoking flash command for Zephyr"); - LFCommand flash = buildWestFlashCommand(); - int flashRet = flash.run(); - if (flashRet != 0) { - errorReporter.reportError("West flash command failed with error code " + flashRet); - } else { - System.out.println("SUCCESS: Flashed application with west"); - } - } - - } - return cMakeReturnCode == 0 && makeReturnCode == 0; + // Use the user-specified compiler if any + if (targetConfig.compiler != null) { + if (cppMode) { + // Set the CXX environment variable to change the C++ compiler. + compile.replaceEnvironmentVariable("CXX", targetConfig.compiler); + } else { + // Set the CC environment variable to change the C compiler. + compile.replaceEnvironmentVariable("CC", targetConfig.compiler); + } } + int cMakeReturnCode = compile.run(context.getCancelIndicator()); - /** - * Return a command to compile the specified C file using CMake. - * This produces a C-specific compile command. - */ - public LFCommand compileCmakeCommand() { - // Set the build directory to be "build" - Path buildPath = fileConfig.getSrcGenPath().resolve("build"); - - LFCommand command = commandFactory.createCommand( - "cmake", cmakeOptions(targetConfig, fileConfig), - buildPath); - if (command == null) { - errorReporter.reportError( - "The C/CCpp target requires CMAKE >= " + CCmakeGenerator.MIN_CMAKE_VERSION - + " to compile the generated code. " + - "Auto-compiling can be disabled using the \"no-compile: true\" target property."); - } - return command; + if (cMakeReturnCode != 0 + && context.getMode() == LFGeneratorContext.Mode.STANDALONE + && !outputContainsKnownCMakeErrors(compile.getErrors().toString())) { + errorReporter.reportError( + targetConfig.compiler + " failed with error code " + cMakeReturnCode); } - static Stream cmakeCompileDefinitions(TargetConfig targetConfig) { - return targetConfig.compileDefinitions.entrySet().stream() - .map(entry -> "-D" + entry.getKey() + "=" + entry.getValue()); + // For warnings (vs. errors), the return code is 0. + // But we still want to mark the IDE. + if (compile.getErrors().toString().length() > 0 + && context.getMode() != LFGeneratorContext.Mode.STANDALONE + && !outputContainsKnownCMakeErrors(compile.getErrors().toString())) { + generator.reportCommandErrors(compile.getErrors().toString()); } - private static List cmakeOptions(TargetConfig targetConfig, FileConfig fileConfig) { - List arguments = new ArrayList<>(); - cmakeCompileDefinitions(targetConfig).forEachOrdered(arguments::add); - String separator = File.separator; - String maybeQuote = ""; // Windows seems to require extra level of quoting. - String srcPath = fileConfig.srcPath.toString(); // Windows requires escaping the backslashes. - String rootPath = fileConfig.srcPkgPath.toString(); - if (separator.equals("\\")) { - separator = "\\\\\\\\"; - maybeQuote = "\\\""; - srcPath = srcPath.replaceAll("\\\\", "\\\\\\\\"); - rootPath = rootPath.replaceAll("\\\\", "\\\\\\\\"); + int makeReturnCode = 0; + + if (cMakeReturnCode == 0) { + LFCommand build = buildCmakeCommand(); + + makeReturnCode = build.run(context.getCancelIndicator()); + + if (makeReturnCode != 0 + && context.getMode() == LFGeneratorContext.Mode.STANDALONE + && !outputContainsKnownCMakeErrors(build.getErrors().toString())) { + errorReporter.reportError( + targetConfig.compiler + " failed with error code " + makeReturnCode); + } + + // For warnings (vs. errors), the return code is 0. + // But we still want to mark the IDE. + if (build.getErrors().toString().length() > 0 + && context.getMode() != LFGeneratorContext.Mode.STANDALONE + && !outputContainsKnownCMakeErrors(build.getErrors().toString())) { + generator.reportCommandErrors(build.getErrors().toString()); + } + + if (makeReturnCode == 0 && build.getErrors().toString().length() == 0) { + System.out.println( + "SUCCESS: Compiling generated code for " + + fileConfig.name + + " finished with no errors."); + } + + if (targetConfig.platformOptions.platform == Platform.ZEPHYR + && targetConfig.platformOptions.flash) { + System.out.println("Invoking flash command for Zephyr"); + LFCommand flash = buildWestFlashCommand(); + int flashRet = flash.run(); + if (flashRet != 0) { + errorReporter.reportError("West flash command failed with error code " + flashRet); + } else { + System.out.println("SUCCESS: Flashed application with west"); } - arguments.addAll(List.of( - "-DCMAKE_BUILD_TYPE=" + ((targetConfig.cmakeBuildType!=null) ? targetConfig.cmakeBuildType.toString() : "Release"), + } + } + return cMakeReturnCode == 0 && makeReturnCode == 0; + } + + /** + * Return a command to compile the specified C file using CMake. This produces a C-specific + * compile command. + */ + public LFCommand compileCmakeCommand() { + // Set the build directory to be "build" + Path buildPath = fileConfig.getSrcGenPath().resolve("build"); + + LFCommand command = + commandFactory.createCommand("cmake", cmakeOptions(targetConfig, fileConfig), buildPath); + if (command == null) { + errorReporter.reportError( + "The C/CCpp target requires CMAKE >= " + + CCmakeGenerator.MIN_CMAKE_VERSION + + " to compile the generated code. " + + "Auto-compiling can be disabled using the \"no-compile: true\" target property."); + } + return command; + } + + static Stream cmakeCompileDefinitions(TargetConfig targetConfig) { + return targetConfig.compileDefinitions.entrySet().stream() + .map(entry -> "-D" + entry.getKey() + "=" + entry.getValue()); + } + + private static List cmakeOptions(TargetConfig targetConfig, FileConfig fileConfig) { + List arguments = new ArrayList<>(); + cmakeCompileDefinitions(targetConfig).forEachOrdered(arguments::add); + String separator = File.separator; + String maybeQuote = ""; // Windows seems to require extra level of quoting. + String srcPath = fileConfig.srcPath.toString(); // Windows requires escaping the backslashes. + String rootPath = fileConfig.srcPkgPath.toString(); + if (separator.equals("\\")) { + separator = "\\\\\\\\"; + maybeQuote = "\\\""; + srcPath = srcPath.replaceAll("\\\\", "\\\\\\\\"); + rootPath = rootPath.replaceAll("\\\\", "\\\\\\\\"); + } + arguments.addAll( + List.of( + "-DCMAKE_BUILD_TYPE=" + + ((targetConfig.cmakeBuildType != null) + ? targetConfig.cmakeBuildType.toString() + : "Release"), "-DCMAKE_INSTALL_PREFIX=" + FileUtil.toUnixString(fileConfig.getOutPath()), - "-DCMAKE_INSTALL_BINDIR=" + FileUtil.toUnixString( - fileConfig.getOutPath().relativize( - fileConfig.binPath - ) - ), - "-DLF_FILE_SEPARATOR=\"" + maybeQuote + separator + maybeQuote + "\"" - )); - // Add #define for source file directory. - // Do not do this for federated programs because for those, the definition is put - // into the cmake file (and fileConfig.srcPath is the wrong directory anyway). - if (!fileConfig.srcPath.toString().contains("fed-gen")) { - // Do not convert to Unix path - arguments.add("-DLF_SOURCE_DIRECTORY=\"" + maybeQuote + srcPath + maybeQuote + "\""); - arguments.add("-DLF_PACKAGE_DIRECTORY=\"" + maybeQuote + rootPath + maybeQuote + "\""); - } - arguments.add(FileUtil.toUnixString(fileConfig.getSrcGenPath())); - - if (GeneratorUtils.isHostWindows()) { - arguments.add("-DCMAKE_SYSTEM_VERSION=\"10.0\""); - } - return arguments; + "-DCMAKE_INSTALL_BINDIR=" + + FileUtil.toUnixString(fileConfig.getOutPath().relativize(fileConfig.binPath)), + "-DLF_FILE_SEPARATOR=\"" + maybeQuote + separator + maybeQuote + "\"")); + // Add #define for source file directory. + // Do not do this for federated programs because for those, the definition is put + // into the cmake file (and fileConfig.srcPath is the wrong directory anyway). + if (!fileConfig.srcPath.toString().contains("fed-gen")) { + // Do not convert to Unix path + arguments.add("-DLF_SOURCE_DIRECTORY=\"" + maybeQuote + srcPath + maybeQuote + "\""); + arguments.add("-DLF_PACKAGE_DIRECTORY=\"" + maybeQuote + rootPath + maybeQuote + "\""); } + arguments.add(FileUtil.toUnixString(fileConfig.getSrcGenPath())); - /** - * Return the cmake config name correspnding to a given build type. - */ - private String buildTypeToCmakeConfig(TargetProperty.BuildType type) { - if (type == null) { - return "Release"; - } - switch (type) { - case TEST: - return "Debug"; - default: - return type.toString(); - } + if (GeneratorUtils.isHostWindows()) { + arguments.add("-DCMAKE_SYSTEM_VERSION=\"10.0\""); } + return arguments; + } - /** - * Return a command to build the specified C file using CMake. - * This produces a C-specific build command. - * - *

Note: It appears that configuration and build cannot happen in one command. - * Therefore, this is separated into a compile command and a build command.

- */ - public LFCommand buildCmakeCommand() { - // Set the build directory to be "build" - Path buildPath = fileConfig.getSrcGenPath().resolve("build"); - String cores = String.valueOf(Runtime.getRuntime().availableProcessors()); - LFCommand command = commandFactory.createCommand( - "cmake", List.of( - "--build", ".", "--target", "install", "--parallel", cores, "--config", - buildTypeToCmakeConfig(targetConfig.cmakeBuildType) - ), - buildPath); - if (command == null) { - errorReporter.reportError( - "The C/CCpp target requires CMAKE >= 3.5 to compile the generated code. " - + "Auto-compiling can be disabled using the \"no-compile: true\" target property." - ); - } - return command; + /** Return the cmake config name correspnding to a given build type. */ + private String buildTypeToCmakeConfig(TargetProperty.BuildType type) { + if (type == null) { + return "Release"; } - - /** - * Return a flash/emulate command using west. - * If board is null (defaults to qemu_cortex_m3) or qemu_* - * Return a flash command which runs the target as an emulation - * If ordinary target, return {@code west flash} - */ - public LFCommand buildWestFlashCommand() { - // Set the build directory to be "build" - Path buildPath = fileConfig.getSrcGenPath().resolve("build"); - String board = targetConfig.platformOptions.board; - LFCommand cmd; - if (board == null || board.startsWith("qemu")) { - cmd = commandFactory.createCommand( - "west", List.of("build", "-t", "run"), buildPath); - } else { - cmd = commandFactory.createCommand( - "west", List.of("flash"), buildPath); - } - if (cmd == null) { - errorReporter.reportError( - "Could not create west flash command." - ); - } - - return cmd; + switch (type) { + case TEST: + return "Debug"; + default: + return type.toString(); } - - /** - * Check if the output produced by CMake has any known and common errors. - * If a known error is detected, a specialized, more informative message - * is shown. - * - * Errors currently detected: - *
    - *
  • C++ compiler used to compile C files: This error shows up as - * '#error "The CMAKE_C_COMPILER is set to a C++ compiler"' in - * the 'CMakeOutput' string.
  • - *
- * - * @param CMakeOutput The captured output from CMake. - * @return true if the provided 'CMakeOutput' contains a known error. - * false otherwise. - */ - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - private boolean outputContainsKnownCMakeErrors(String CMakeOutput) { - // Check if the error thrown is due to the wrong compiler - if (CMakeOutput.contains("The CMAKE_C_COMPILER is set to a C++ compiler")) { - // If so, print an appropriate error message - if (targetConfig.compiler != null) { - errorReporter.reportError( - "A C++ compiler was requested in the compiler target property." - + " Use the CCpp or the Cpp target instead."); - } else { - errorReporter.reportError("\"A C++ compiler was detected." - + " The C target works best with a C compiler." - + " Use the CCpp or the Cpp target instead.\""); - } - return true; - } - return false; + } + + /** + * Return a command to build the specified C file using CMake. This produces a C-specific build + * command. + * + *

Note: It appears that configuration and build cannot happen in one command. Therefore, this + * is separated into a compile command and a build command. + */ + public LFCommand buildCmakeCommand() { + // Set the build directory to be "build" + Path buildPath = fileConfig.getSrcGenPath().resolve("build"); + String cores = String.valueOf(Runtime.getRuntime().availableProcessors()); + LFCommand command = + commandFactory.createCommand( + "cmake", + List.of( + "--build", + ".", + "--target", + "install", + "--parallel", + cores, + "--config", + buildTypeToCmakeConfig(targetConfig.cmakeBuildType)), + buildPath); + if (command == null) { + errorReporter.reportError( + "The C/CCpp target requires CMAKE >= 3.5 to compile the generated code. " + + "Auto-compiling can be disabled using the \"no-compile: true\" target property."); + } + return command; + } + + /** + * Return a flash/emulate command using west. If board is null (defaults to qemu_cortex_m3) or + * qemu_* Return a flash command which runs the target as an emulation If ordinary target, return + * {@code west flash} + */ + public LFCommand buildWestFlashCommand() { + // Set the build directory to be "build" + Path buildPath = fileConfig.getSrcGenPath().resolve("build"); + String board = targetConfig.platformOptions.board; + LFCommand cmd; + if (board == null || board.startsWith("qemu")) { + cmd = commandFactory.createCommand("west", List.of("build", "-t", "run"), buildPath); + } else { + cmd = commandFactory.createCommand("west", List.of("flash"), buildPath); + } + if (cmd == null) { + errorReporter.reportError("Could not create west flash command."); } - /** - * Return a command to compile the specified C file using a native compiler - * (generally gcc unless overridden by the user). - * This produces a C specific compile command. - * - * @param fileToCompile The C filename without the .c extension. - * @param noBinary If true, the compiler will create a .o output instead of a binary. - * If false, the compile command will produce a binary. - */ - public LFCommand compileCCommand( - String fileToCompile, - boolean noBinary - ) { - String cFilename = getTargetFileName(fileToCompile, cppMode, targetConfig); - - Path relativeSrcPath = fileConfig.getOutPath().relativize( - fileConfig.getSrcGenPath().resolve(Paths.get(cFilename))); - Path relativeBinPath = fileConfig.getOutPath().relativize( - fileConfig.binPath.resolve(Paths.get(fileToCompile))); - - // NOTE: we assume that any C compiler takes Unix paths as arguments. - String relSrcPathString = FileUtil.toUnixString(relativeSrcPath); - String relBinPathString = FileUtil.toUnixString(relativeBinPath); - - // If there is no main reactor, then generate a .o file not an executable. - if (noBinary) { - relBinPathString += ".o"; - } - - ArrayList compileArgs = new ArrayList<>(); - compileArgs.add(relSrcPathString); - for (String file: targetConfig.compileAdditionalSources) { - var relativePath = fileConfig.getOutPath().relativize( - fileConfig.getSrcGenPath().resolve(Paths.get(file))); - compileArgs.add(FileUtil.toUnixString(relativePath)); - } - compileArgs.addAll(targetConfig.compileLibraries); + return cmd; + } + + /** + * Check if the output produced by CMake has any known and common errors. If a known error is + * detected, a specialized, more informative message is shown. + * + *

Errors currently detected: + * + *

    + *
  • C++ compiler used to compile C files: This error shows up as '#error "The + * CMAKE_C_COMPILER is set to a C++ compiler"' in the 'CMakeOutput' string. + *
+ * + * @param CMakeOutput The captured output from CMake. + * @return true if the provided 'CMakeOutput' contains a known error. false otherwise. + */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean outputContainsKnownCMakeErrors(String CMakeOutput) { + // Check if the error thrown is due to the wrong compiler + if (CMakeOutput.contains("The CMAKE_C_COMPILER is set to a C++ compiler")) { + // If so, print an appropriate error message + if (targetConfig.compiler != null) { + errorReporter.reportError( + "A C++ compiler was requested in the compiler target property." + + " Use the CCpp or the Cpp target instead."); + } else { + errorReporter.reportError( + "\"A C++ compiler was detected." + + " The C target works best with a C compiler." + + " Use the CCpp or the Cpp target instead.\""); + } + return true; + } + return false; + } + + /** + * Return a command to compile the specified C file using a native compiler (generally gcc unless + * overridden by the user). This produces a C specific compile command. + * + * @param fileToCompile The C filename without the .c extension. + * @param noBinary If true, the compiler will create a .o output instead of a binary. If false, + * the compile command will produce a binary. + */ + public LFCommand compileCCommand(String fileToCompile, boolean noBinary) { + String cFilename = getTargetFileName(fileToCompile, cppMode, targetConfig); + + Path relativeSrcPath = + fileConfig + .getOutPath() + .relativize(fileConfig.getSrcGenPath().resolve(Paths.get(cFilename))); + Path relativeBinPath = + fileConfig.getOutPath().relativize(fileConfig.binPath.resolve(Paths.get(fileToCompile))); + + // NOTE: we assume that any C compiler takes Unix paths as arguments. + String relSrcPathString = FileUtil.toUnixString(relativeSrcPath); + String relBinPathString = FileUtil.toUnixString(relativeBinPath); + + // If there is no main reactor, then generate a .o file not an executable. + if (noBinary) { + relBinPathString += ".o"; + } - // Add compile definitions - targetConfig.compileDefinitions.forEach((key,value) -> compileArgs.add("-D"+key+"="+value)); + ArrayList compileArgs = new ArrayList<>(); + compileArgs.add(relSrcPathString); + for (String file : targetConfig.compileAdditionalSources) { + var relativePath = + fileConfig.getOutPath().relativize(fileConfig.getSrcGenPath().resolve(Paths.get(file))); + compileArgs.add(FileUtil.toUnixString(relativePath)); + } + compileArgs.addAll(targetConfig.compileLibraries); - // Finally, add the compiler flags in target parameters (if any) - compileArgs.addAll(targetConfig.compilerFlags); + // Add compile definitions + targetConfig.compileDefinitions.forEach( + (key, value) -> compileArgs.add("-D" + key + "=" + value)); - // Only set the output file name if it hasn't already been set - // using a target property or Args line flag. - if (!compileArgs.contains("-o")) { - compileArgs.add("-o"); - compileArgs.add(relBinPathString); - } + // Finally, add the compiler flags in target parameters (if any) + compileArgs.addAll(targetConfig.compilerFlags); - // If there is no main reactor, then use the -c flag to prevent linking from occurring. - // FIXME: we could add a {@code -c} flag to {@code lfc} to make this explicit in stand-alone mode. - // Then again, I think this only makes sense when we can do linking. - if (noBinary) { - compileArgs.add("-c"); // FIXME: revisit - } + // Only set the output file name if it hasn't already been set + // using a target property or Args line flag. + if (!compileArgs.contains("-o")) { + compileArgs.add("-o"); + compileArgs.add(relBinPathString); + } - LFCommand command = commandFactory.createCommand(targetConfig.compiler, compileArgs, fileConfig.getOutPath()); - if (command == null) { - errorReporter.reportError( - "The C/CCpp target requires GCC >= 7 to compile the generated code. " + - "Auto-compiling can be disabled using the \"no-compile: true\" target property."); - } - return command; + // If there is no main reactor, then use the -c flag to prevent linking from occurring. + // FIXME: we could add a {@code -c} flag to {@code lfc} to make this explicit in stand-alone + // mode. + // Then again, I think this only makes sense when we can do linking. + if (noBinary) { + compileArgs.add("-c"); // FIXME: revisit } - /** - * Produces the filename including the target-specific extension - * - * @param fileName The base name of the file without any extensions - * @param cppMode Indicate whether the compiler is in C++ mode - * In C++ mode, the compiler produces .cpp files instead - * of .c files and uses a C++ compiler to compiler the code. - */ - static String getTargetFileName(String fileName, boolean cppMode, TargetConfig targetConfig) { - if (targetConfig.platformOptions.platform == Platform.ARDUINO) { - return fileName + ".ino"; - } - if (cppMode) { - // If the C++ mode is enabled, use a .cpp extension - return fileName + ".cpp"; - } - return fileName + ".c"; + LFCommand command = + commandFactory.createCommand(targetConfig.compiler, compileArgs, fileConfig.getOutPath()); + if (command == null) { + errorReporter.reportError( + "The C/CCpp target requires GCC >= 7 to compile the generated code. " + + "Auto-compiling can be disabled using the \"no-compile: true\" target property."); + } + return command; + } + + /** + * Produces the filename including the target-specific extension + * + * @param fileName The base name of the file without any extensions + * @param cppMode Indicate whether the compiler is in C++ mode In C++ mode, the compiler produces + * .cpp files instead of .c files and uses a C++ compiler to compiler the code. + */ + static String getTargetFileName(String fileName, boolean cppMode, TargetConfig targetConfig) { + if (targetConfig.platformOptions.platform == Platform.ARDUINO) { + return fileName + ".ino"; + } + if (cppMode) { + // If the C++ mode is enabled, use a .cpp extension + return fileName + ".cpp"; } + return fileName + ".c"; + } } diff --git a/org.lflang/src/org/lflang/generator/c/CConstructorGenerator.java b/org.lflang/src/org/lflang/generator/c/CConstructorGenerator.java index 41731106c3..529e37a975 100644 --- a/org.lflang/src/org/lflang/generator/c/CConstructorGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CConstructorGenerator.java @@ -1,36 +1,30 @@ package org.lflang.generator.c; import org.lflang.generator.CodeBuilder; -import org.lflang.lf.Reactor; -/** - * Generates C constructor code for a reactor. - * - */ +/** Generates C constructor code for a reactor. */ public class CConstructorGenerator { - /** - * 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. - */ - public static String generateConstructor( - TypeParameterizedReactor tpr, - String constructorCode - ) { - var structType = CUtil.selfType(tpr); - var code = new CodeBuilder(); - code.pr(structType+"* new_"+CUtil.getName(tpr)+"() {"); - 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 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. + */ + public static String generateConstructor(TypeParameterizedReactor tpr, String constructorCode) { + var structType = CUtil.selfType(tpr); + var code = new CodeBuilder(); + code.pr(structType + "* new_" + CUtil.getName(tpr) + "() {"); + 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(); + } - public static String generateConstructorPrototype(TypeParameterizedReactor tpr) { - return CUtil.selfType(tpr)+"* new_"+CUtil.getName(tpr)+"();"; - } + public static String generateConstructorPrototype(TypeParameterizedReactor tpr) { + return CUtil.selfType(tpr) + "* new_" + CUtil.getName(tpr) + "();"; + } } diff --git a/org.lflang/src/org/lflang/generator/c/CCoreFilesUtils.java b/org.lflang/src/org/lflang/generator/c/CCoreFilesUtils.java index 46503d87cc..2d83a105da 100644 --- a/org.lflang/src/org/lflang/generator/c/CCoreFilesUtils.java +++ b/org.lflang/src/org/lflang/generator/c/CCoreFilesUtils.java @@ -1,32 +1,28 @@ package org.lflang.generator.c; + import java.util.List; /** - * Generates the list of files to be included in the - * core library for each reactor given conditions listed - * as arguments of each function. + * Generates the list of files to be included in the core library for each reactor given conditions + * listed as arguments of each function. * * @author Hou Seng Wong */ public class CCoreFilesUtils { - public static List getCTargetSrc() { - return List.of( - "lib/schedule.c" - ); - } + public static List getCTargetSrc() { + return List.of("lib/schedule.c"); + } - public static List getCTargetHeader() { - return List.of( - "include/api/api.h" - ); - } + public static List getCTargetHeader() { + return List.of("include/api/api.h"); + } - public static String getCTargetSetHeader() { - return "include/api/set.h"; - } + public static String getCTargetSetHeader() { + return "include/api/set.h"; + } - public static String getCTargetSetUndefHeader() { - return "include/api/set_undef.h"; - } + public static String getCTargetSetUndefHeader() { + return "include/api/set_undef.h"; + } } diff --git a/org.lflang/src/org/lflang/generator/c/CDelayBodyGenerator.java b/org.lflang/src/org/lflang/generator/c/CDelayBodyGenerator.java index f5aa244e60..ec75412b42 100644 --- a/org.lflang/src/org/lflang/generator/c/CDelayBodyGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CDelayBodyGenerator.java @@ -9,54 +9,51 @@ public class CDelayBodyGenerator implements DelayBodyGenerator { - protected CTypes types; - - public CDelayBodyGenerator(CTypes types) { - this.types = types; - } - - /** - * Generate code for the body of a reaction that takes an input and - * schedules an action with the value of that input. - * @param action The action to schedule - * @param port The port to read from - */ - @Override - public String generateDelayBody(Action action, VarRef port) { - var ref = ASTUtils.generateVarRef(port); - return CReactionGenerator.generateDelayBody( - ref, - action.getName(), - CUtil.isTokenType(getInferredType(action), types) - ); - } - - /** - * Generate code for the body of a reaction that is triggered by the - * given action and writes its value to the given port. This realizes - * the receiving end of a logical delay specified with the 'after' - * keyword. - * @param action The action that triggers the reaction - * @param port The port to write to. - */ - @Override - public String generateForwardBody(Action action, VarRef port) { - var outputName = ASTUtils.generateVarRef(port); - return CReactionGenerator.generateForwardBody( - outputName, - types.getTargetType(action), - action.getName(), - CUtil.isTokenType(getInferredType(action), types) - ); - } - - @Override - public String generateDelayGeneric() { - throw new UnsupportedOperationException("TODO: auto-generated method stub"); - } - - @Override - public boolean generateAfterDelaysWithVariableWidth() { - return true; - } + protected CTypes types; + + public CDelayBodyGenerator(CTypes types) { + this.types = types; + } + + /** + * Generate code for the body of a reaction that takes an input and schedules an action with the + * value of that input. + * + * @param action The action to schedule + * @param port The port to read from + */ + @Override + public String generateDelayBody(Action action, VarRef port) { + var ref = ASTUtils.generateVarRef(port); + return CReactionGenerator.generateDelayBody( + ref, action.getName(), CUtil.isTokenType(getInferredType(action), types)); + } + + /** + * Generate code for the body of a reaction that is triggered by the given action and writes its + * value to the given port. This realizes the receiving end of a logical delay specified with the + * 'after' keyword. + * + * @param action The action that triggers the reaction + * @param port The port to write to. + */ + @Override + public String generateForwardBody(Action action, VarRef port) { + var outputName = ASTUtils.generateVarRef(port); + return CReactionGenerator.generateForwardBody( + outputName, + types.getTargetType(action), + action.getName(), + CUtil.isTokenType(getInferredType(action), types)); + } + + @Override + public String generateDelayGeneric() { + throw new UnsupportedOperationException("TODO: auto-generated method stub"); + } + + @Override + public boolean generateAfterDelaysWithVariableWidth() { + return true; + } } diff --git a/org.lflang/src/org/lflang/generator/c/CDockerGenerator.java b/org.lflang/src/org/lflang/generator/c/CDockerGenerator.java index ae9058dcbb..f52e23a949 100644 --- a/org.lflang/src/org/lflang/generator/c/CDockerGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CDockerGenerator.java @@ -1,9 +1,7 @@ package org.lflang.generator.c; import java.util.stream.Collectors; - import org.eclipse.xtext.xbase.lib.IterableExtensions; - import org.lflang.Target; import org.lflang.generator.DockerGenerator; import org.lflang.generator.LFGeneratorContext; @@ -15,62 +13,66 @@ * @author Hou Seng Wong */ public class CDockerGenerator extends DockerGenerator { - private static final String DEFAULT_BASE_IMAGE = "alpine:latest"; + private static final String DEFAULT_BASE_IMAGE = "alpine:latest"; - /** - * The constructor for the base docker file generation class. - * - * @param context The context of the code generator. - */ - public CDockerGenerator(LFGeneratorContext context) { - super(context); - } + /** + * The constructor for the base docker file generation class. + * + * @param context The context of the code generator. + */ + public CDockerGenerator(LFGeneratorContext context) { + super(context); + } - - /** - * Generate the contents of the docker file. - */ - @Override - protected String generateDockerFileContent() { - var lfModuleName = context.getFileConfig().name; - var config = context.getTargetConfig(); - var compileCommand = IterableExtensions.isNullOrEmpty(config.buildCommands) ? - generateDefaultCompileCommand() : - StringUtil.joinObjects(config.buildCommands, " "); - var compiler = config.target == Target.CCPP ? "g++" : "gcc"; - var baseImage = DEFAULT_BASE_IMAGE; - if (config.dockerOptions != null && config.dockerOptions.from != null) { - baseImage = config.dockerOptions.from; - } - return String.join("\n", - "# For instructions, see: https://www.lf-lang.org/docs/handbook/containerized-execution", - "FROM "+baseImage+" AS builder", - "WORKDIR /lingua-franca/"+lfModuleName, - "RUN set -ex && apk add --no-cache "+compiler+" musl-dev cmake make", - "COPY . src-gen", - compileCommand, - "", - "FROM "+baseImage, - "WORKDIR /lingua-franca", - "RUN mkdir bin", - "COPY --from=builder /lingua-franca/"+lfModuleName+"/bin/"+lfModuleName+" ./bin/"+lfModuleName, - "", - "# Use ENTRYPOINT not CMD so that command-line arguments go through", - "ENTRYPOINT [\"./bin/"+lfModuleName+"\"]", - "" - ); + /** Generate the contents of the docker file. */ + @Override + protected String generateDockerFileContent() { + var lfModuleName = context.getFileConfig().name; + var config = context.getTargetConfig(); + var compileCommand = + IterableExtensions.isNullOrEmpty(config.buildCommands) + ? generateDefaultCompileCommand() + : StringUtil.joinObjects(config.buildCommands, " "); + var compiler = config.target == Target.CCPP ? "g++" : "gcc"; + var baseImage = DEFAULT_BASE_IMAGE; + if (config.dockerOptions != null && config.dockerOptions.from != null) { + baseImage = config.dockerOptions.from; } + return String.join( + "\n", + "# For instructions, see: https://www.lf-lang.org/docs/handbook/containerized-execution", + "FROM " + baseImage + " AS builder", + "WORKDIR /lingua-franca/" + lfModuleName, + "RUN set -ex && apk add --no-cache " + compiler + " musl-dev cmake make", + "COPY . src-gen", + compileCommand, + "", + "FROM " + baseImage, + "WORKDIR /lingua-franca", + "RUN mkdir bin", + "COPY --from=builder /lingua-franca/" + + lfModuleName + + "/bin/" + + lfModuleName + + " ./bin/" + + lfModuleName, + "", + "# Use ENTRYPOINT not CMD so that command-line arguments go through", + "ENTRYPOINT [\"./bin/" + lfModuleName + "\"]", + ""); + } - /** Return the default compile command for the C docker container. */ - protected String generateDefaultCompileCommand() { - return String.join("\n", - "RUN set -ex && \\", - "mkdir bin && \\", - "cmake " + CCompiler.cmakeCompileDefinitions(context.getTargetConfig()) + /** Return the default compile command for the C docker container. */ + protected String generateDefaultCompileCommand() { + return String.join( + "\n", + "RUN set -ex && \\", + "mkdir bin && \\", + "cmake " + + CCompiler.cmakeCompileDefinitions(context.getTargetConfig()) .collect(Collectors.joining(" ")) - + " -S src-gen -B bin && \\", - "cd bin && \\", - "make all" - ); - } + + " -S src-gen -B bin && \\", + "cd bin && \\", + "make all"); + } } diff --git a/org.lflang/src/org/lflang/generator/c/CFileConfig.java b/org.lflang/src/org/lflang/generator/c/CFileConfig.java index 362b8645b5..db9447a833 100644 --- a/org.lflang/src/org/lflang/generator/c/CFileConfig.java +++ b/org.lflang/src/org/lflang/generator/c/CFileConfig.java @@ -2,24 +2,29 @@ import java.io.IOException; import java.nio.file.Path; - import org.eclipse.emf.ecore.resource.Resource; - 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]); - } + 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 Path getIncludePath() { + return includePath; + } - public String getRuntimeIncludePath() { - return "/lib/c/reactor-c/include"; - } + 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 fce667067c..e8606eee9a 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; @@ -34,6 +34,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import static org.lflang.ast.ASTUtils.toText; import static org.lflang.util.StringUtil.addDoubleQuotes; +import com.google.common.collect.Iterables; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -46,34 +47,27 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; - import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.xbase.lib.Exceptions; import org.eclipse.xtext.xbase.lib.IterableExtensions; import org.eclipse.xtext.xbase.lib.StringExtensions; - -import org.lflang.ast.ASTUtils; -import org.lflang.generator.CodeMap; -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.ASTUtils; 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.CodeMap; +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; @@ -99,165 +93,156 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 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 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 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. - * A few other smaller functions are also generated.

    Self Struct

    - * 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__". - * 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.

    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.

    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:
  • - *
  • 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. - * 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.
  • - *
  • 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.
  • - *
  • 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.
  • - *
  • 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.
      • - *
      - *
    • - *
    - *
  • - *
  • 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

    - * 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.

    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.
  • - *
  • _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_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.
  • + *
  • 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 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. 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. + *

    Self Struct

    + * 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__". 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. + *

    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. + *

    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: + *
  • 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. 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. + *
  • 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. + *
  • 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. + *
  • 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. + *
      + *
    + *
  • 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

    + * 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. + *

    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. + *
  • _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_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. *
* * @author Edward A. Lee @@ -273,1153 +258,1164 @@ 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 final String cMakeExtras = ""; - - /** Place to collect code to execute at the start of a time step. */ - private final 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 final String cMakeExtras = ""; + + /** Place to collect code to execute at the start of a time step. */ + private final 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 (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)) { + 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."); + return false; + } } + return true; + } + + /** + * Generate C code from the Lingua Franca model contained by the specified resource. This is the + * main entry point for code generation. + * + * @param resource The resource containing the source code. + * @param context The context in which the generator is invoked, including whether it is cancelled + * and whether it is a standalone context + */ + @Override + public void doGenerate(Resource resource, LFGeneratorContext context) { + super.doGenerate(resource, context); + if (!GeneratorUtils.canGenerate(errorsOccurred(), mainDef, errorReporter, context)) return; + if (!isOSCompatible()) return; // Incompatible OS and configuration - /** - * Look for physical actions in all resources. - * If found, set threads to be at least one to allow asynchronous schedule calls. - */ - public void accommodatePhysicalActionsIfPresent() { - // If there are any physical actions, ensure the threaded engine is used and that - // keepalive is set to true, unless the user has explicitly set it to false. - for (Resource resource : GeneratorUtils.getResources(reactors)) { - for (Action action : ASTUtils.allElementsOfClass(resource, Action.class)) { - if (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)) { - targetConfig.threading = true; - errorReporter.reportWarning( - action, - "Using the threaded C runtime to allow for asynchronous handling of physical action " + - action.getName() - ); - return; - } - } - } - } + // Perform set up that does not generate code + setUpGeneralParameters(); + + FileUtil.createDirectoryIfDoesNotExist(fileConfig.getSrcGenPath().toFile()); + FileUtil.createDirectoryIfDoesNotExist(fileConfig.binPath.toFile()); + FileUtil.createDirectoryIfDoesNotExist(fileConfig.getIncludePath().toFile()); + handleProtoFiles(); + + // Derive target filename from the .lf filename. + var lfModuleName = fileConfig.name; + var cFilename = CCompiler.getTargetFileName(lfModuleName, this.CCppMode, targetConfig); + var targetFile = fileConfig.getSrcGenPath() + File.separator + cFilename; + try { + generateCodeFor(lfModuleName); + copyTargetFiles(); + generateHeaders(); + code.writeToFile(targetFile); + } catch (IOException e) { + //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored + Exceptions.sneakyThrow(e); } - /** - * Return true if the host operating system is compatible and - * otherwise report an error and return false. - */ - protected boolean isOSCompatible() { - if (GeneratorUtils.isHostWindows()) { - if (CCppMode) { - errorReporter.reportError( - "LF programs with a CCpp target are currently not supported on Windows. " + - "Exiting code generation." - ); - return false; - } - } - return true; + // Create docker file. + if (targetConfig.dockerOptions != null && mainDef != null) { + try { + var dockerData = getDockerGenerator(context).generateDockerData(); + dockerData.writeDockerFile(); + (new DockerComposeGenerator(context)).writeDockerComposeFile(List.of(dockerData)); + } catch (IOException e) { + throw new RuntimeException("Error while writing Docker files", e); + } } - /** - * Generate C code from the Lingua Franca model contained by the - * specified resource. This is the main entry point for code - * generation. - * @param resource The resource containing the source code. - * @param context The context in which the generator is - * invoked, including whether it is cancelled and - * whether it is a standalone context - */ - @Override - public void doGenerate(Resource resource, LFGeneratorContext context) { - super.doGenerate(resource, context); - if (!GeneratorUtils.canGenerate(errorsOccurred(), mainDef, errorReporter, context)) return; - if (!isOSCompatible()) return; // Incompatible OS and configuration - - // Perform set up that does not generate code - setUpGeneralParameters(); - - FileUtil.createDirectoryIfDoesNotExist(fileConfig.getSrcGenPath().toFile()); - FileUtil.createDirectoryIfDoesNotExist(fileConfig.binPath.toFile()); - FileUtil.createDirectoryIfDoesNotExist(fileConfig.getIncludePath().toFile()); - handleProtoFiles(); - - // Derive target filename from the .lf filename. - var lfModuleName = fileConfig.name; - var cFilename = CCompiler.getTargetFileName(lfModuleName, this.CCppMode, targetConfig); - var targetFile = fileConfig.getSrcGenPath() + File.separator + cFilename; - try { - generateCodeFor(lfModuleName); - copyTargetFiles(); - generateHeaders(); - code.writeToFile(targetFile); - } catch (IOException e) { - //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored - Exceptions.sneakyThrow(e); - } - - // Create docker file. - if (targetConfig.dockerOptions != null && mainDef != null) { - try { - var dockerData = getDockerGenerator(context).generateDockerData(); - dockerData.writeDockerFile(); - (new DockerComposeGenerator(context)).writeDockerComposeFile(List.of(dockerData)); - } catch (IOException e) { - throw new RuntimeException("Error while writing Docker files", e); - } - } - - // If cmake is requested, generate the CMakeLists.txt - if (targetConfig.platformOptions.platform != Platform.ARDUINO) { - var cmakeFile = fileConfig.getSrcGenPath() + File.separator + "CMakeLists.txt"; - var sources = allTypeParameterizedReactors() - .map(CUtil::getName) - .map(it -> it + (CCppMode ? ".cpp" : ".c")) - .collect(Collectors.toCollection(ArrayList::new)); - 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")) + "\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 = + allTypeParameterizedReactors() + .map(CUtil::getName) + .map(it -> it + (CCppMode ? ".cpp" : ".c")) + .collect(Collectors.toCollection(ArrayList::new)); + 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; + } - // Create a .vscode/settings.json file in the target directory so that VSCode can - // immediately compile the generated code. - try { - String compileDefs = targetConfig.compileDefinitions.keySet().stream() - .map(key -> "\"-D" + key + "=" + targetConfig.compileDefinitions.get(key) + "\"") - .collect(Collectors.joining(",\n")); - String settings = "{\n" - + "\"cmake.configureArgs\": [\n" - + compileDefs - + "\n]\n}\n"; - Path vscodePath = Path.of(fileConfig.getSrcGenPath() + File.separator + ".vscode"); - if (!Files.exists(vscodePath)) - Files.createDirectory(vscodePath); - FileUtil.writeToFile( - settings, - Path.of(fileConfig.getSrcGenPath() - + File.separator + ".vscode" - + File.separator + "settings.json") - ); - } catch (IOException e) { - Exceptions.sneakyThrow(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")) + + "\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); - } - } + // Create a .vscode/settings.json file in the target directory so that VSCode can + // immediately compile the generated code. + try { + String compileDefs = + targetConfig.compileDefinitions.keySet().stream() + .map(key -> "\"-D" + key + "=" + targetConfig.compileDefinitions.get(key) + "\"") + .collect(Collectors.joining(",\n")); + String settings = "{\n" + "\"cmake.configureArgs\": [\n" + compileDefs + "\n]\n}\n"; + Path vscodePath = Path.of(fileConfig.getSrcGenPath() + File.separator + ".vscode"); + if (!Files.exists(vscodePath)) Files.createDirectory(vscodePath); + FileUtil.writeToFile( + settings, + Path.of( + fileConfig.getSrcGenPath() + + File.separator + + ".vscode" + + File.separator + + "settings.json")); + } 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); - } + // If this code generator is directly compiling the code, compile it now so that we + // clean it up after, removing the #line directives after errors have been reported. + if (!targetConfig.noCompile + && targetConfig.dockerOptions == null + && IterableExtensions.isNullOrEmpty(targetConfig.buildCommands) + // This code is unreachable in LSP_FAST mode, so that check is omitted. + && context.getMode() != LFGeneratorContext.Mode.LSP_MEDIUM) { + // FIXME: Currently, a lack of main is treated as a request to not produce + // a binary and produce a .o file instead. There should be a way to control + // this. + // Create an anonymous Runnable class and add it to the compileThreadPool + // so that compilation can happen in parallel. + var cleanCode = code.removeLines("#line"); + + var execName = lfModuleName; + var threadFileConfig = fileConfig; + var generator = + this; // FIXME: currently only passed to report errors with line numbers in the Eclipse + // IDE + var CppMode = CCppMode; + // generatingContext.reportProgress( + // String.format("Generated code for %d/%d executables. Compiling...", federateCount, + // federates.size()), + // 100 * federateCount / federates.size() + // ); // FIXME: Move to FedGenerator + // Create the compiler to be used later + + var cCompiler = new CCompiler(targetConfig, threadFileConfig, errorReporter, CppMode); + try { + if (!cCompiler.runCCompiler(generator, context)) { + // If compilation failed, remove any bin files that may have been created. + CUtil.deleteBinFiles(threadFileConfig); + // If finish has already been called, it is illegal and makes no sense. However, + // if finish has already been called, then this must be a federated execution. + context.unsuccessfulFinish(); } else { - context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(context, null)); + context.finish(GeneratorResult.Status.COMPILED, null); } + cleanCode.writeToFile(targetFile); + } catch (IOException e) { + Exceptions.sneakyThrow(e); + } + } - // In case we are in Eclipse, make sure the generated code is visible. - GeneratorUtils.refreshProject(resource, context.getMode()); + // 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.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(); - - // 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 watchdog_number = 0;", - "SUPPRESS_UNUSED_WARNING(watchdog_number);")); - // 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("}"); - } + // 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 watchdog_number = 0;", + "SUPPRESS_UNUSED_WARNING(watchdog_number);")); + // 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); + #endif"""); + + // Generate functions for modes + code.pr(CModesGenerator.generateLfInitializeModes(hasModalReactors)); + code.pr(CModesGenerator.generateLfHandleModeChanges(hasModalReactors, modalStateResetCount)); + code.pr( + CReactionGenerator.generateLfModeTriggeredReactions( + startupReactionCount, resetReactionCount, hasModalReactors)); } - - @Override - protected String getConflictingConnectionsInModalReactorsBody(String source, String dest) { - return String.join("\n", - "// Generated forwarding reaction for connections with the same destination", - "// but located in mutually exclusive modes.", - "lf_set("+dest+", "+source+"->value);" - ); - } - - /** Set the scheduler type in the target config as needed. */ - private void pickScheduler() { - // Don't use a scheduler that does not prioritize reactions based on deadlines - // if the program contains a deadline (handler). Use the GEDF_NP scheduler instead. - if (!targetConfig.schedulerType.prioritizesDeadline()) { - // Check if a deadline is assigned to any reaction - if (hasDeadlines(reactors)) { - if (!targetConfig.setByUser.contains(TargetProperty.SCHEDULER)) { - targetConfig.schedulerType = TargetProperty.SchedulerOption.GEDF_NP; - } - } + } + + @Override + public void checkModalReactorSupport(boolean __) { + // Modal reactors are currently only supported for non federated applications + super.checkModalReactorSupport(true); + } + + @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; } + } } + } - private boolean hasDeadlines(List reactors) { - for (Reactor reactor : reactors) { - for (Reaction reaction : allReactions(reactor)) { - if (reaction.getDeadline() != null) { - return true; - } - } + 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 {@code 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); - } - }); - } + 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 {@code 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); + } + }); + } } - - /** - * Copy all files or directories listed in the target property {@code files}, {@code cmake-include}, - * and {@code _fed_setup} into the src-gen folder of the main .lf file - * - * @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.copyFilesOrDirectories(targetConfig.cmakeIncludes, destination, fileConfig, errorReporter, true); - - // 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); - } - } + } + + /** + * Copy all files or directories listed in the target property {@code files}, {@code + * cmake-include}, and {@code _fed_setup} into the src-gen folder of the main .lf file + * + * @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.copyFilesOrDirectories( + targetConfig.cmakeIncludes, destination, fileConfig, errorReporter, true); + + // 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); + } } - - /** - * Generate code for defining all instantiated reactors. - * - * 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) { - resolveTemplatedTypes(this.main, this.main.tpr); - generateReactorChildren(this.main, generatedReactors); - generateReactorClass(this.main.getTypeParameterizedReactor()); - } - // do not generate code for reactors that are not instantiated + } + + /** + * Generate code for defining all instantiated reactors. + * + *

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) { + resolveTemplatedTypes(this.main, this.main.tpr); + generateReactorChildren(this.main, generatedReactors); + generateReactorClass(this.main.getTypeParameterizedReactor()); } - - /** - * Recursively Resolve all Templated Types of child Reactors to their respective - * concrete types - * - * @param reactorInstance The Reactor Class - * @param parentTpr {@link TypeParameterizedReactor} of Parent - */ - private void resolveTemplatedTypes(ReactorInstance reactorInstance, TypeParameterizedReactor parentTpr) { - for (var child : reactorInstance.children) { - if (parentTpr.typeArgs() != null) { - Map copy = new HashMap<>(); - child.tpr.typeArgs().forEach((literal, typename) -> { - var type = typename.getId(); - if (parentTpr.typeArgs().containsKey(type)) { - var basicType = parentTpr.typeArgs().get(type); - copy.put(literal, basicType); - } else { - // Typename is not inherited from Parent Reactor. Keep As Is! - copy.put(literal, typename); - } + // do not generate code for reactors that are not instantiated + } + + /** + * Recursively Resolve all Templated Types of child Reactors to their respective concrete types + * + * @param reactorInstance The Reactor Class + * @param parentTpr {@link TypeParameterizedReactor} of Parent + */ + private void resolveTemplatedTypes( + ReactorInstance reactorInstance, TypeParameterizedReactor parentTpr) { + for (var child : reactorInstance.children) { + if (parentTpr.typeArgs() != null) { + Map copy = new HashMap<>(); + child + .tpr + .typeArgs() + .forEach( + (literal, typename) -> { + var type = typename.getId(); + if (parentTpr.typeArgs().containsKey(type)) { + var basicType = parentTpr.typeArgs().get(type); + copy.put(literal, basicType); + } else { + // Typename is not inherited from Parent Reactor. Keep As Is! + copy.put(literal, typename); + } }); - if (!copy.isEmpty()) { // If we found some templated-types update the tpr with new map - child.tpr = new TypeParameterizedReactor(child.tpr.reactor(), copy); - } - resolveTemplatedTypes(child, child.tpr); - } - } - } - - private record TypeParameterizedReactorWithDecl(TypeParameterizedReactor tpr, ReactorDecl decl) {} - - /** Generate user-visible header files for all reactors instantiated. */ - private void generateHeaders() throws IOException { - FileUtil.deleteDirectory(fileConfig.getIncludePath()); - FileUtil.copyFromClassPath( - fileConfig.getRuntimeIncludePath(), - fileConfig.getIncludePath(), - false, - true - ); - for (TypeParameterizedReactor tpr : - (Iterable) () -> allTypeParameterizedReactors().iterator() - ) { - CReactorHeaderFileGenerator.doGenerate( - types, tpr, fileConfig, - (builder, rr, userFacing) -> { - generateAuxiliaryStructs(builder, rr, userFacing); - if (userFacing) { - rr.reactor().getInstantiations().stream() - .map(it -> new TypeParameterizedReactorWithDecl(new TypeParameterizedReactor(it), it.getReactorClass())).collect(Collectors.toSet()).forEach(it -> { - ASTUtils.allPorts(it.tpr.reactor()) - .forEach(p -> builder.pr(CPortGenerator.generateAuxiliaryStruct( - it.tpr, p, getTarget(), errorReporter, types, new CodeBuilder(), true, it.decl() - ))); - }); - } - }, - this::generateTopLevelPreambles); + if (!copy.isEmpty()) { // If we found some templated-types update the tpr with new map + child.tpr = new TypeParameterizedReactor(child.tpr.reactor(), copy); } - FileUtil.copyDirectoryContents(fileConfig.getIncludePath(), fileConfig.getSrcGenPath().resolve("include"), false); + resolveTemplatedTypes(child, child.tpr); + } } - - /** - * 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) { - var newTpr = r.tpr; - if (r.reactorDeclaration != null && - !generatedReactors.contains(newTpr)) { - generatedReactors.add(newTpr); - generateReactorChildren(r, generatedReactors); - inspectReactorEResource(r.reactorDeclaration); - generateReactorClass(newTpr); + } + + private record TypeParameterizedReactorWithDecl(TypeParameterizedReactor tpr, ReactorDecl decl) {} + + /** Generate user-visible header files for all reactors instantiated. */ + private void generateHeaders() throws IOException { + FileUtil.deleteDirectory(fileConfig.getIncludePath()); + FileUtil.copyFromClassPath( + fileConfig.getRuntimeIncludePath(), fileConfig.getIncludePath(), false, true); + for (TypeParameterizedReactor tpr : + (Iterable) () -> allTypeParameterizedReactors().iterator()) { + CReactorHeaderFileGenerator.doGenerate( + types, + tpr, + fileConfig, + (builder, rr, userFacing) -> { + generateAuxiliaryStructs(builder, rr, userFacing); + if (userFacing) { + rr.reactor().getInstantiations().stream() + .map( + it -> + new TypeParameterizedReactorWithDecl( + new TypeParameterizedReactor(it), it.getReactorClass())) + .collect(Collectors.toSet()) + .forEach( + it -> { + ASTUtils.allPorts(it.tpr.reactor()) + .forEach( + p -> + builder.pr( + CPortGenerator.generateAuxiliaryStruct( + it.tpr, + p, + getTarget(), + errorReporter, + types, + new CodeBuilder(), + true, + it.decl()))); + }); } - } - } - - /** - * 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"); - } + }, + this::generateTopLevelPreambles); } - - /** - * 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"); + FileUtil.copyDirectoryContents( + 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) { + var newTpr = r.tpr; + if (r.reactorDeclaration != null && !generatedReactors.contains(newTpr)) { + generatedReactors.add(newTpr); + generateReactorChildren(r, generatedReactors); + inspectReactorEResource(r.reactorDeclaration); + generateReactorClass(newTpr); } - if (coreLib != null) { - FileUtil.copyDirectoryContents(Path.of(coreLib), dest, true); - } else { - FileUtil.copyFromClassPath( - "/lib/c/reactor-c/core", - dest, - true, - false - ); - FileUtil.copyFromClassPath( - "/lib/c/reactor-c/lib", - dest, - true, - false - ); - } - - // For the Zephyr target, copy default config and board files. - if (targetConfig.platformOptions.platform == Platform.ZEPHYR) { - FileUtil.copyFromClassPath( - "/lib/platform/zephyr/boards", - fileConfig.getSrcGenPath(), - false, - false - ); - FileUtil.copyFileFromClassPath( - "/lib/platform/zephyr/prj_lf.conf", - fileConfig.getSrcGenPath(), - true - ); - - FileUtil.copyFileFromClassPath( - "/lib/platform/zephyr/Kconfig", - fileConfig.getSrcGenPath(), - 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.

- */ - private void generateReactorClass(TypeParameterizedReactor tpr) 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(tpr) + ".h"; - var guardMacro = headerName.toUpperCase().replace(".", "_"); - header.pr("#ifndef " + guardMacro); - header.pr("#define " + guardMacro); - generateReactorClassHeaders(tpr, headerName, header, src); - header.pr(generateTopLevelPreambles(tpr.reactor())); - generateUserPreamblesForReactor(tpr.reactor(), src); - generateReactorClassBody(tpr, header, src); - header.pr("#endif // " + guardMacro); - FileUtil.writeToFile(CodeMap.fromGeneratedCode(header.toString()).getGeneratedCode(), fileConfig.getSrcGenPath().resolve(headerName), true); - var extension = targetConfig.platformOptions.platform == Platform.ARDUINO ? ".ino" : - CCppMode ? ".cpp" : ".c"; - FileUtil.writeToFile(CodeMap.fromGeneratedCode(src.toString()).getGeneratedCode(), fileConfig.getSrcGenPath().resolve(CUtil.getName(tpr) + 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(TypeParameterizedReactor tpr, 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(tpr); - if (CCppMode) { - src.pr("}"); - header.pr("}"); - } - src.pr("#include \"include/" + CReactorHeaderFileGenerator.outputPath(tpr) + "\""); - src.pr("#include \"" + headerName + "\""); - tpr.doDefines(src); - ASTUtils.allIncludes(tpr.reactor()).stream().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"); } - - private void generateReactorClassBody(TypeParameterizedReactor tpr, 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, tpr, false); - // The following must go before the self struct so the #include watchdog.h ends up in the header. - CWatchdogGenerator.generateWatchdogs(src, header, tpr, errorReporter); - generateSelfStruct(header, tpr, constructorCode); - generateMethods(src, tpr); - generateReactions(src, tpr); - generateConstructor(src, header, tpr, constructorCode); + if (coreLib != null) { + FileUtil.copyDirectoryContents(Path.of(coreLib), dest, true); + } else { + FileUtil.copyFromClassPath("/lib/c/reactor-c/core", dest, true, false); + FileUtil.copyFromClassPath("/lib/c/reactor-c/lib", dest, true, false); } - /** - * Generate methods for {@code reactor}. - */ - protected void generateMethods(CodeBuilder src, TypeParameterizedReactor tpr) { - CMethodGenerator.generateMethods(tpr, src, types); - } + // For the Zephyr target, copy default config and board files. + if (targetConfig.platformOptions.platform == Platform.ZEPHYR) { + FileUtil.copyFromClassPath( + "/lib/platform/zephyr/boards", fileConfig.getSrcGenPath(), false, false); + FileUtil.copyFileFromClassPath( + "/lib/platform/zephyr/prj_lf.conf", fileConfig.getSrcGenPath(), 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 : ASTUtils.allPreambles(reactor)) { - src.pr("// *********** From the preamble, verbatim:"); - src.prSourceLineNumber(p.getCode()); - src.pr(toText(p.getCode())); - src.pr("\n// *********** End of preamble."); - } + FileUtil.copyFileFromClassPath( + "/lib/platform/zephyr/Kconfig", fileConfig.getSrcGenPath(), true); } - - /** - * Generate a constructor for the specified reactor in the specified federate. - * @param tpr 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, TypeParameterizedReactor tpr, CodeBuilder constructorCode - ) { - header.pr(CConstructorGenerator.generateConstructorPrototype(tpr)); - src.pr(CConstructorGenerator.generateConstructor( - tpr, - 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. + */ + private void generateReactorClass(TypeParameterizedReactor tpr) 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(tpr) + ".h"; + var guardMacro = headerName.toUpperCase().replace(".", "_"); + header.pr("#ifndef " + guardMacro); + header.pr("#define " + guardMacro); + generateReactorClassHeaders(tpr, headerName, header, src); + header.pr(generateTopLevelPreambles(tpr.reactor())); + generateUserPreamblesForReactor(tpr.reactor(), src); + generateReactorClassBody(tpr, header, src); + header.pr("#endif // " + guardMacro); + FileUtil.writeToFile( + CodeMap.fromGeneratedCode(header.toString()).getGeneratedCode(), + fileConfig.getSrcGenPath().resolve(headerName), + true); + var extension = + targetConfig.platformOptions.platform == Platform.ARDUINO + ? ".ino" + : CCppMode ? ".cpp" : ".c"; + FileUtil.writeToFile( + CodeMap.fromGeneratedCode(src.toString()).getGeneratedCode(), + fileConfig.getSrcGenPath().resolve(CUtil.getName(tpr) + extension), + true); + } + + protected void generateReactorClassHeaders( + TypeParameterizedReactor tpr, String headerName, CodeBuilder header, CodeBuilder src) { + if (CCppMode) { + src.pr("extern \"C\" {"); + header.pr("extern \"C\" {"); } - - protected void generateIncludes(TypeParameterizedReactor tpr) { - code.pr("#include \"" + CUtil.getName(tpr) + ".h\""); + header.pr("#include \"include/core/reactor.h\""); + src.pr("#include \"include/api/api.h\""); + src.pr("#include \"include/api/set.h\""); + generateIncludes(tpr); + if (CCppMode) { + src.pr("}"); + header.pr("}"); } - - /** - * Generate the struct type definitions for inputs, outputs, and - * actions of the specified reactor. - */ - protected void generateAuxiliaryStructs(CodeBuilder builder, TypeParameterizedReactor tpr, 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(String.format(""" + src.pr("#include \"include/" + CReactorHeaderFileGenerator.outputPath(tpr) + "\""); + src.pr("#include \"" + headerName + "\""); + tpr.doDefines(src); + ASTUtils.allIncludes(tpr.reactor()).stream() + .map(name -> "#include \"" + name + ".h\"") + .forEach(header::pr); + } + + private void generateReactorClassBody( + TypeParameterizedReactor tpr, 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, tpr, false); + // The following must go before the self struct so the #include watchdog.h ends up in the + // header. + CWatchdogGenerator.generateWatchdogs(src, header, tpr, errorReporter); + generateSelfStruct(header, tpr, constructorCode); + generateMethods(src, tpr); + generateReactions(src, tpr); + generateConstructor(src, header, tpr, constructorCode); + } + + /** Generate methods for {@code reactor}. */ + protected void generateMethods(CodeBuilder src, TypeParameterizedReactor tpr) { + CMethodGenerator.generateMethods(tpr, 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 : ASTUtils.allPreambles(reactor)) { + 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 tpr 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, + TypeParameterizedReactor tpr, + CodeBuilder constructorCode) { + header.pr(CConstructorGenerator.generateConstructorPrototype(tpr)); + src.pr(CConstructorGenerator.generateConstructor(tpr, constructorCode.toString())); + } + + protected void generateIncludes(TypeParameterizedReactor tpr) { + code.pr("#include \"" + CUtil.getName(tpr) + ".h\""); + } + + /** + * Generate the struct type definitions for inputs, outputs, and actions of the specified reactor. + */ + protected void generateAuxiliaryStructs( + CodeBuilder builder, TypeParameterizedReactor tpr, 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( + String.format( + """ #ifdef FEDERATED #ifdef FEDERATED_DECENTRALIZED %s intended_tag; #endif %s physical_time_of_arrival; #endif - """, types.getTargetTagType(), types.getTargetTimeType()) - ); - for (Port p : allPorts(tpr.reactor())) { - builder.pr(CPortGenerator.generateAuxiliaryStruct( - tpr, - 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(tpr.reactor())) { - builder.pr(CActionGenerator.generateAuxiliaryStruct( - tpr, - action, - getTarget(), - types, - federatedExtension, - userFacing - )); - } + """, + types.getTargetTagType(), types.getTargetTimeType())); + for (Port p : allPorts(tpr.reactor())) { + builder.pr( + CPortGenerator.generateAuxiliaryStruct( + tpr, p, getTarget(), errorReporter, types, federatedExtension, userFacing, null)); } - - /** - * Generate the self struct type definition for the specified reactor - * in the specified federate. - * @param constructorCode Place to put lines of code that need to - * go into the constructor. - */ - private void generateSelfStruct(CodeBuilder builder, TypeParameterizedReactor tpr, CodeBuilder constructorCode) { - var reactor = toDefinition(tpr.reactor()); - var selfType = CUtil.selfType(tpr); - - // 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, reactor, constructorCode); - - // Next handle parameters. - body.pr(CParameterGenerator.generateDeclarations(tpr, types)); - - // Next handle states. - body.pr(CStateGenerator.generateDeclarations(tpr, types)); - - // Next handle actions. - CActionGenerator.generateDeclarations(tpr, body, constructorCode); - - // Next handle inputs and outputs. - CPortGenerator.generateDeclarations(tpr, reactor, 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(tpr, body, constructorCode); - - // Next, generate the fields needed for each reaction. - CReactionGenerator.generateReactionAndTriggerStructs( - body, - tpr, - constructorCode, - types - ); - - // Generate the fields needed for each watchdog. - CWatchdogGenerator.generateWatchdogStruct(body, tpr, 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(tpr.reactor())) { + builder.pr( + CActionGenerator.generateAuxiliaryStruct( + tpr, 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 tpr {@link TypeParameterizedReactor} - * @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( - TypeParameterizedReactor tpr, - 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(tpr.reactor()); - // Next generate the relevant code. - for (Instantiation containedReactor : contained.containedReactors()) { - var containedTpr = new TypeParameterizedReactor(containedReactor); - // 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 instances. - 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, containedTpr, false)+" "+port.getName()+";"); - } else { - // Is a multiport. - // Memory will be malloc'd in initialization. - body.pr(port, String.join("\n", - variableStructType(port, containedTpr, 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, containedTpr, 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, containedTpr, 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 constructorCode Place to put lines of code that need to go into the constructor. + */ + private void generateSelfStruct( + CodeBuilder builder, TypeParameterizedReactor tpr, CodeBuilder constructorCode) { + var reactor = toDefinition(tpr.reactor()); + var selfType = CUtil.selfType(tpr); + + // 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, reactor, constructorCode); + + // Next handle parameters. + body.pr(CParameterGenerator.generateDeclarations(tpr, types)); + + // Next handle states. + body.pr(CStateGenerator.generateDeclarations(tpr, types)); + + // Next handle actions. + CActionGenerator.generateDeclarations(tpr, body, constructorCode); + + // Next handle inputs and outputs. + CPortGenerator.generateDeclarations(tpr, reactor, 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(tpr, body, constructorCode); + + // Next, generate the fields needed for each reaction. + CReactionGenerator.generateReactionAndTriggerStructs(body, tpr, constructorCode, types); + + // Generate the fields needed for each watchdog. + CWatchdogGenerator.generateWatchdogStruct(body, tpr, 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 tpr {@link TypeParameterizedReactor} + * @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( + TypeParameterizedReactor tpr, 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(tpr.reactor()); + // Next generate the relevant code. + for (Instantiation containedReactor : contained.containedReactors()) { + var containedTpr = new TypeParameterizedReactor(containedReactor); + // 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 instances. + 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, containedTpr, false) + " " + port.getName() + ";"); + } else { + // Is a multiport. + // Memory will be malloc'd in initialization. + body.pr( + port, + String.join( + "\n", + variableStructType(port, containedTpr, 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, containedTpr, 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, containedTpr, 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 reactor The reactor declaration for the self struct - * @param constructorCode Code that is executed when the reactor is instantiated - */ - protected void generateSelfStructExtension( - CodeBuilder body, - Reactor reactor, - 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 tpr The reactor. - */ - public void generateReactions(CodeBuilder src, TypeParameterizedReactor tpr) { - var reactionIndex = 0; - var reactor = ASTUtils.toDefinition(tpr.reactor()); - for (Reaction reaction : allReactions(reactor)) { - generateReaction(src, reaction, tpr, 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 reactor The reactor declaration for the self struct + * @param constructorCode Code that is executed when the reactor is instantiated + */ + protected void generateSelfStructExtension( + CodeBuilder body, Reactor reactor, 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 tpr The reactor. + */ + public void generateReactions(CodeBuilder src, TypeParameterizedReactor tpr) { + var reactionIndex = 0; + var reactor = ASTUtils.toDefinition(tpr.reactor()); + for (Reaction reaction : allReactions(reactor)) { + generateReaction(src, reaction, tpr, 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 tpr The reactor. - * @param reactionIndex The position of the reaction within the reactor. - */ - protected void generateReaction(CodeBuilder src, Reaction reaction, TypeParameterizedReactor tpr, 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 tpr The reactor. + * @param reactionIndex The position of the reaction within the reactor. + */ + protected void generateReaction( + CodeBuilder src, Reaction reaction, TypeParameterizedReactor tpr, int reactionIndex) { + src.pr( + CReactionGenerator.generateReaction( reaction, tpr, reactionIndex, @@ -1427,702 +1423,737 @@ protected void generateReaction(CodeBuilder src, Reaction reaction, TypeParamete 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)); + } + + /** + * Record startup, shutdown, and reset reactions. + * + * @param instance A reactor instance. + */ + private void recordBuiltinTriggers(ReactorInstance instance) { + // For each reaction instance, allocate the arrays that will be used to + // trigger downstream reactions. + for (ReactionInstance reaction : instance.reactions) { + var reactor = reaction.getParent(); + var temp = new CodeBuilder(); + var foundOne = false; + + var reactionRef = CUtil.reactionRef(reaction); + + // Next handle triggers of the reaction that come from a multiport output + // of a contained reactor. Also, handle startup and shutdown triggers. + for (TriggerInstance trigger : reaction.triggers) { + if (trigger.isStartup()) { + temp.pr("_lf_startup_reactions[_lf_startup_reactions_count++] = &" + reactionRef + ";"); + startupReactionCount += reactor.getTotalWidth(); + foundOne = true; + } else if (trigger.isShutdown()) { + temp.pr("_lf_shutdown_reactions[_lf_shutdown_reactions_count++] = &" + reactionRef + ";"); + foundOne = true; + shutdownReactionCount += reactor.getTotalWidth(); + + if (targetConfig.tracing != null) { + var description = CUtil.getShortenedName(reactor); + var reactorRef = CUtil.reactorRef(reactor); + temp.pr( + String.join( + "\n", + "_lf_register_trace_event(" + + reactorRef + + ", &(" + + reactorRef + + "->_lf__shutdown),", + "trace_trigger, " + addDoubleQuotes(description + ".shutdown") + ");")); + } + } else if (trigger.isReset()) { + temp.pr("_lf_reset_reactions[_lf_reset_reactions_count++] = &" + reactionRef + ";"); + resetReactionCount += reactor.getTotalWidth(); + foundOne = true; } + } + if (foundOne) initializeTriggerObjects.pr(temp.toString()); } - - /** - * Generate code to set up the tables used in _lf_start_time_step to decrement reference - * counts and mark outputs absent between time steps. This function puts the code - * into startTimeStep. - */ - private void generateStartTimeStep(ReactorInstance instance) { - // Avoid generating dead code if nothing is relevant. - var foundOne = false; - var temp = new CodeBuilder(); - var containerSelfStructName = CUtil.reactorRef(instance); - - // Handle inputs that get sent data from a reaction rather than from - // another contained reactor and reactions that are triggered by an - // output of a contained reactor. - // Note that there may be more than one reaction reacting to the same - // port so we have to avoid listing the port more than once. - var portsSeen = new LinkedHashSet(); - for (ReactionInstance reaction : instance.reactions) { - for (PortInstance port : Iterables.filter(reaction.effects, PortInstance.class)) { - if (port.getDefinition() instanceof Input && !portsSeen.contains(port)) { - portsSeen.add(port); - // This reaction is sending to an input. Must be - // the input of a contained reactor in the federate. - // NOTE: If instance == main and the federate is within a bank, - // this assumes that the reaction writes only to the bank member in the federate. - foundOne = true; - - temp.pr("// Add port "+port.getFullName()+" to array of is_present fields."); - - if (!instance.equals(port.getParent())) { - // 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 (!instance.equals(port.getParent())) { - temp.pr("count++;"); - temp.endScopedBlock(); - temp.endScopedBlock(); - temp.endScopedBankChannelIteration(port, null); - } else { - temp.endScopedBankChannelIteration(port, "count"); - } - } - } + } + + /** + * 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 (!instance.equals(port.getParent())) { + // 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 (!instance.equals(port.getParent())) { + temp.pr("count++;"); + temp.endScopedBlock(); + temp.endScopedBlock(); + temp.endScopedBankChannelIteration(port, null); + } else { + temp.endScopedBankChannelIteration(port, "count"); + } } - if (foundOne) startTimeStep.pr(temp.toString()); - temp = new CodeBuilder(); - foundOne = false; + } + } + if (foundOne) startTimeStep.pr(temp.toString()); + temp = new CodeBuilder(); + foundOne = false; + + for (ActionInstance action : instance.actions) { + foundOne = true; + temp.startScopedBlock(instance); + + temp.pr( + String.join( + "\n", + "// Add action " + action.getFullName() + " to array of is_present fields.", + "_lf_is_present_fields[" + startTimeStepIsPresentCount + "] ", + " = &" + + containerSelfStructName + + "->_lf_" + + action.getName() + + ".is_present;")); + + // Intended_tag is only applicable to actions in federated execution with decentralized + // coordination. + temp.pr( + CExtensionUtils.surroundWithIfFederatedDecentralized( + String.join( + "\n", + "// Add action " + action.getFullName() + " to array of intended_tag fields.", + "_lf_intended_tag_fields[" + startTimeStepIsPresentCount + "] ", + " = &" + + containerSelfStructName + + "->_lf_" + + action.getName() + + ".intended_tag;"))); + + startTimeStepIsPresentCount += action.getParent().getTotalWidth(); + temp.endScopedBlock(); + } + if (foundOne) startTimeStep.pr(temp.toString()); + temp = new CodeBuilder(); + foundOne = false; - for (ActionInstance action : instance.actions) { - foundOne = true; - temp.startScopedBlock(instance); + // Next, set up the table to mark each output of each contained reactor absent. + for (ReactorInstance child : instance.children) { + if (child.outputs.size() > 0) { - temp.pr(String.join("\n", - "// Add action "+action.getFullName()+" to array of is_present fields.", - "_lf_is_present_fields["+startTimeStepIsPresentCount+"] ", - " = &"+containerSelfStructName+"->_lf_"+action.getName()+".is_present;" - )); + temp.startScopedBlock(); + temp.pr("int count = 0; SUPPRESS_UNUSED_WARNING(count);"); + temp.startScopedBlock(child); - // Intended_tag is only applicable to actions in federated execution with decentralized coordination. + var channelCount = 0; + for (PortInstance output : child.outputs) { + if (!output.getDependsOnReactions().isEmpty()) { + foundOne = true; + temp.pr("// Add port " + output.getFullName() + " to array of is_present fields."); + temp.startChannelIteration(output); + temp.pr( + "_lf_is_present_fields[" + + startTimeStepIsPresentCount + + " + count] = &" + + CUtil.portRef(output) + + ".is_present;"); + + // Intended_tag is only applicable to ports in federated execution with decentralized + // coordination. temp.pr( CExtensionUtils.surroundWithIfFederatedDecentralized( - String.join("\n", - "// Add action " + action.getFullName() - + " to array of intended_tag fields.", - "_lf_intended_tag_fields[" - + startTimeStepIsPresentCount + "] ", - " = &" + containerSelfStructName - + "->_lf_" + action.getName() - + ".intended_tag;" - ))); - - startTimeStepIsPresentCount += action.getParent().getTotalWidth(); - temp.endScopedBlock(); + 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)}. - */ - public static String variableStructType(Variable variable, TypeParameterizedReactor tpr, boolean userFacing) { - return (userFacing ? tpr.getName().toLowerCase() : CUtil.getName(tpr)) +"_"+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, TypeParameterizedReactor, 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().tpr)+"_"+portOrAction.getName()+"_t"; + } + + /** + * Construct a unique type for the struct of the specified typed variable (port or action) of the + * specified reactor class. This is required to be the same as the type name returned by {@link + * #variableStructType(TriggerInstance)}. + */ + public static String variableStructType( + Variable variable, TypeParameterizedReactor tpr, boolean userFacing) { + return (userFacing ? tpr.getName().toLowerCase() : CUtil.getName(tpr)) + + "_" + + 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, + * TypeParameterizedReactor, 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().tpr) + "_" + portOrAction.getName() + "_t"; + } + + /** + * If tracing is turned on, then generate code that records the full name of the specified reactor + * instance in the trace table. If tracing is not turned on, do nothing. + * + * @param instance The reactor instance. + */ + private void generateTraceTableEntries(ReactorInstance instance) { + if (targetConfig.tracing != null) { + initializeTriggerObjects.pr(CTracingGenerator.generateTraceTableEntries(instance)); } - - /** - * If tracing is turned on, then generate code that records - * the full name of the specified reactor instance in the - * trace table. If tracing is not turned on, do nothing. - * @param instance The reactor instance. - */ - private void generateTraceTableEntries(ReactorInstance instance) { - if (targetConfig.tracing != null) { - initializeTriggerObjects.pr( - CTracingGenerator.generateTraceTableEntries(instance) - ); - } + } + + /** + * Generate code to instantiate the specified reactor instance and initialize it. + * + * @param instance A reactor instance. + */ + public void generateReactorInstance(ReactorInstance instance) { + var reactorClass = ASTUtils.toDefinition(instance.getDefinition().getReactorClass()); + var fullName = instance.getFullName(); + initializeTriggerObjects.pr( + "// ***** Start initializing " + fullName + " of class " + reactorClass.getName()); + // Generate the instance self struct containing parameters, state variables, + // and outputs (the "self" struct). + initializeTriggerObjects.pr( + CUtil.reactorRefName(instance) + + "[" + + CUtil.runtimeIndex(instance) + + "] = new_" + + CUtil.getName(instance.tpr) + + "();"); + // 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(); } - /** - * 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(instance.tpr)+"();"); - // 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); + } + + /** + * 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 = reactor.tpr.resolveType(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 = reactor.tpr.resolveType(getInferredType(action.getDefinition())); - var payloadSize = "0"; - if (!type.isUndefined()) { - var typeStr = types.getTargetType(type); - if (CUtil.isTokenType(type, types)) { - typeStr = CUtil.rootType(typeStr); - } - if (typeStr != null && !typeStr.equals("") && !typeStr.equals("void")) { - payloadSize = "sizeof("+typeStr+")"; - } - } - - var selfStruct = CUtil.reactorRef(action.getParent()); - initializeTriggerObjects.pr( - CActionGenerator.generateTokenInitializer( - selfStruct, action.getName(), payloadSize - ) - ); - } + } + + /** + * Generate code that is executed while the reactor instance is being initialized. This is + * provided as an extension point for subclasses. Normally, the reactions argument is the full + * list of reactions, but for the top-level of a federate, will be a subset of reactions that is + * relevant to the federate. + * + * @param instance The reactor instance. + */ + protected void generateReactorInstanceExtension(ReactorInstance instance) { + // Do nothing + } + + /** + * Generate code that initializes the state variables for a given instance. Unlike parameters, + * state variables are uniformly initialized for all instances of the same reactor. + * + * @param instance The reactor class instance + */ + protected void generateStateVariableInitializations(ReactorInstance instance) { + var reactorClass = instance.getDefinition().getReactorClass(); + var selfRef = CUtil.reactorRef(instance); + for (StateVar stateVar : allStateVars(toDefinition(reactorClass))) { + if (isInitialized(stateVar)) { + var mode = + stateVar.eContainer() instanceof Mode + ? instance.lookupModeInstance((Mode) stateVar.eContainer()) + : instance.getMode(false); + initializeTriggerObjects.pr( + CStateGenerator.generateInitializer(instance, selfRef, stateVar, mode, types)); + if (mode != null && stateVar.isReset()) { + modalStateResetCount += instance.getTotalWidth(); } + } } - - /** - * Generate code that is executed while the reactor instance is being initialized. - * This is provided as an extension point for subclasses. - * Normally, the reactions argument is the full list of reactions, - * but for the top-level of a federate, will be a subset of reactions that - * is relevant to the federate. - * @param instance The reactor instance. - */ - protected void generateReactorInstanceExtension(ReactorInstance instance) { - // Do nothing + } + + /** + * Generate code to set the deadline field of the reactions in the specified reactor instance. + * + * @param instance The reactor instance. + */ + private void generateSetDeadline(ReactorInstance instance) { + for (ReactionInstance reaction : instance.reactions) { + var selfRef = CUtil.reactorRef(reaction.getParent()) + "->_lf__reaction_" + reaction.index; + if (reaction.declaredDeadline != null) { + var deadline = reaction.declaredDeadline.maxDelay; + initializeTriggerObjects.pr( + selfRef + ".deadline = " + types.getTargetTimeExpr(deadline) + ";"); + } else { // No deadline. + initializeTriggerObjects.pr(selfRef + ".deadline = NEVER;"); + } } - - /** - * Generate code that initializes the state variables for a given instance. - * Unlike parameters, state variables are uniformly initialized for all instances - * of the same reactor. - * @param instance The reactor class instance - */ - protected void generateStateVariableInitializations(ReactorInstance instance) { - var reactorClass = instance.getDefinition().getReactorClass(); - var selfRef = CUtil.reactorRef(instance); - for (StateVar stateVar : allStateVars(toDefinition(reactorClass))) { - if (isInitialized(stateVar)) { - var mode = stateVar.eContainer() instanceof Mode ? - instance.lookupModeInstance((Mode) stateVar.eContainer()) : - instance.getMode(false); - initializeTriggerObjects.pr(CStateGenerator.generateInitializer( - instance, - selfRef, - stateVar, - mode, - types - )); - if (mode != null && stateVar.isReset()) { - modalStateResetCount += instance.getTotalWidth(); - } - } - } + } + + /** + * Generate code to initialize modes. + * + * @param instance The reactor instance. + */ + private void generateModeStructure(ReactorInstance instance) { + CModesGenerator.generateModeStructure(instance, initializeTriggerObjects); + if (!instance.modes.isEmpty()) { + modalReactorCount += instance.getTotalWidth(); } - - /** - * Generate code to set the deadline field of the reactions in the - * specified reactor instance. - * @param instance The reactor instance. - */ - private void generateSetDeadline(ReactorInstance instance) { - for (ReactionInstance reaction : instance.reactions) { - var selfRef = CUtil.reactorRef(reaction.getParent())+"->_lf__reaction_"+reaction.index; - if (reaction.declaredDeadline != null) { - var deadline = reaction.declaredDeadline.maxDelay; - initializeTriggerObjects.pr(selfRef+".deadline = "+types.getTargetTimeExpr(deadline)+";"); - } else { // No deadline. - initializeTriggerObjects.pr(selfRef+".deadline = NEVER;"); - } - } + } + + /** + * Generate runtime initialization code for parameters of a given reactor instance + * + * @param instance The reactor instance. + */ + protected void generateParameterInitialization(ReactorInstance instance) { + var selfRef = CUtil.reactorRef(instance); + // Set the local bank_index variable so that initializers can use it. + initializeTriggerObjects.pr( + "bank_index = " + + CUtil.bankIndex(instance) + + ";" + + " SUPPRESS_UNUSED_WARNING(bank_index);"); + for (ParameterInstance parameter : instance.parameters) { + // NOTE: we now use the resolved literal value. For better efficiency, we could + // store constants in a global array and refer to its elements to avoid duplicate + // memory allocations. + // NOTE: If the parameter is initialized with a static initializer for an array + // or struct (the initialization expression is surrounded by { ... }), then we + // have to declare a static variable to ensure that the memory is put in data space + // and not on the stack. + // FIXME: Is there a better way to determine this than the string comparison? + var initializer = CParameterGenerator.getInitializer(parameter); + if (initializer.startsWith("{")) { + var temporaryVariableName = parameter.uniqueID(); + initializeTriggerObjects.pr( + String.join( + "\n", + "static " + + types.getVariableDeclaration( + instance.tpr, parameter.type, temporaryVariableName, true) + + " = " + + initializer + + ";", + selfRef + "->" + parameter.getName() + " = " + temporaryVariableName + ";")); + } else { + initializeTriggerObjects.pr( + selfRef + "->" + parameter.getName() + " = " + initializer + ";"); + } } - - /** - * Generate code to initialize modes. - * @param instance The reactor instance. - */ - private void generateModeStructure(ReactorInstance instance) { - CModesGenerator.generateModeStructure(instance, initializeTriggerObjects); - if (!instance.modes.isEmpty()) { - modalReactorCount += instance.getTotalWidth(); - } + } + + /** + * Generate code that mallocs memory for any output multiports. + * + * @param reactor The reactor instance. + */ + private void initializeOutputMultiports(ReactorInstance reactor) { + var reactorSelfStruct = CUtil.reactorRef(reactor); + for (PortInstance output : reactor.outputs) { + initializeTriggerObjects.pr( + CPortGenerator.initializeOutputMultiport(output, reactorSelfStruct)); } - - /** - * Generate runtime initialization code for parameters of a given reactor instance - * @param instance The reactor instance. - */ - protected void generateParameterInitialization(ReactorInstance instance) { - var selfRef = CUtil.reactorRef(instance); - // Set the local bank_index variable so that initializers can use it. - initializeTriggerObjects.pr("bank_index = "+CUtil.bankIndex(instance)+";" - + " SUPPRESS_UNUSED_WARNING(bank_index);"); - for (ParameterInstance parameter : instance.parameters) { - // NOTE: we now use the resolved literal value. For better efficiency, we could - // store constants in a global array and refer to its elements to avoid duplicate - // memory allocations. - // NOTE: If the parameter is initialized with a static initializer for an array - // or struct (the initialization expression is surrounded by { ... }), then we - // have to declare a static variable to ensure that the memory is put in data space - // and not on the stack. - // FIXME: Is there a better way to determine this than the string comparison? - var initializer = CParameterGenerator.getInitializer(parameter); - if (initializer.startsWith("{")) { - var temporaryVariableName = parameter.uniqueID(); - initializeTriggerObjects.pr(String.join("\n", - "static "+types.getVariableDeclaration(instance.tpr, parameter.type, temporaryVariableName, true)+" = "+initializer+";", - selfRef+"->"+parameter.getName()+" = "+temporaryVariableName+";" - )); - } else { - initializeTriggerObjects.pr(selfRef+"->"+parameter.getName()+" = "+initializer+";"); - } - } + } + + /** + * Allocate memory for inputs. + * + * @param reactor The reactor. + */ + private void initializeInputMultiports(ReactorInstance reactor) { + var reactorSelfStruct = CUtil.reactorRef(reactor); + for (PortInstance input : reactor.inputs) { + initializeTriggerObjects.pr( + CPortGenerator.initializeInputMultiport(input, reactorSelfStruct)); } - - /** - * Generate code that mallocs memory for any output multiports. - * @param reactor The reactor instance. - */ - private void initializeOutputMultiports(ReactorInstance reactor) { - var reactorSelfStruct = CUtil.reactorRef(reactor); - for (PortInstance output : reactor.outputs) { - initializeTriggerObjects.pr(CPortGenerator.initializeOutputMultiport( - output, - reactorSelfStruct - )); - } + } + + @Override + public TargetTypes getTargetTypes() { + return types; + } + + /** + * Get the Docker generator. + * + * @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", String.valueOf(targetConfig.logLevel.ordinal())); + targetConfig.compileAdditionalSources.addAll(CCoreFilesUtils.getCTargetSrc()); + // Create the main reactor instance if there is a main reactor. + createMainReactorInstance(); + if (hasModalReactors) { + // So that each separate compile knows about modal reactors, do this: + targetConfig.compileDefinitions.put("MODAL_REACTORS", "TRUE"); } - - /** - * Allocate memory for inputs. - * @param reactor The reactor. - */ - private void initializeInputMultiports(ReactorInstance reactor) { - var reactorSelfStruct = CUtil.reactorRef(reactor); - for (PortInstance input : reactor.inputs) { - initializeTriggerObjects.pr(CPortGenerator.initializeInputMultiport( - input, - reactorSelfStruct - )); - } + if (targetConfig.threading + && targetConfig.platformOptions.platform == Platform.ARDUINO + && (targetConfig.platformOptions.board == null + || !targetConfig.platformOptions.board.contains("mbed"))) { + // non-MBED boards should not use threading + System.out.println( + "Threading is incompatible on your current Arduino flavor. Setting threading to false."); + targetConfig.threading = false; } - @Override - public TargetTypes getTargetTypes() { - return types; + if (targetConfig.platformOptions.platform == Platform.ARDUINO + && !targetConfig.noCompile + && targetConfig.platformOptions.board == null) { + System.out.println( + "To enable compilation for the Arduino platform, you must specify the fully-qualified" + + " board name (FQBN) in the target property. For example, platform: {name: arduino," + + " board: arduino:avr:leonardo}. Entering \"no-compile\" mode and generating target" + + " code only."); + targetConfig.noCompile = true; } - - /** - * Get the Docker generator. - * @param context - * @return - */ - protected DockerGenerator getDockerGenerator(LFGeneratorContext context) { - return new CDockerGenerator(context); + if (targetConfig.threading) { // FIXME: This logic is duplicated in CMake + pickScheduler(); + // FIXME: this and pickScheduler should be combined. + targetConfig.compileDefinitions.put("SCHEDULER", targetConfig.schedulerType.name()); + targetConfig.compileDefinitions.put( + "NUMBER_OF_WORKERS", String.valueOf(targetConfig.workers)); } + pickCompilePlatform(); + } - // ////////////////////////////////////////// - // // Protected methods. - - // Perform set up that does not generate code - protected void setUpGeneralParameters() { - accommodatePhysicalActionsIfPresent(); - targetConfig.compileDefinitions.put("LOG_LEVEL", String.valueOf(targetConfig.logLevel.ordinal())); - targetConfig.compileAdditionalSources.addAll(CCoreFilesUtils.getCTargetSrc()); - // Create the main reactor instance if there is a main reactor. - createMainReactorInstance(); - if (hasModalReactors) { - // So that each separate compile knows about modal reactors, do this: - targetConfig.compileDefinitions.put("MODAL_REACTORS", "TRUE"); - } - if (targetConfig.threading && targetConfig.platformOptions.platform == Platform.ARDUINO - && (targetConfig.platformOptions.board == null || !targetConfig.platformOptions.board.contains("mbed"))) { - //non-MBED boards should not use threading - System.out.println("Threading is incompatible on your current Arduino flavor. Setting threading to false."); - targetConfig.threading = false; - } - - if (targetConfig.platformOptions.platform == Platform.ARDUINO && !targetConfig.noCompile - && targetConfig.platformOptions.board == null) { - System.out.println("To enable compilation for the Arduino platform, you must specify the fully-qualified board name (FQBN) in the target property. For example, platform: {name: arduino, board: arduino:avr:leonardo}. Entering \"no-compile\" mode and generating target code only."); - targetConfig.noCompile = true; - } - if (targetConfig.threading) { // FIXME: This logic is duplicated in CMake - pickScheduler(); - // FIXME: this and pickScheduler should be combined. - targetConfig.compileDefinitions.put( - "SCHEDULER", - targetConfig.schedulerType.name() - ); - targetConfig.compileDefinitions.put( - "NUMBER_OF_WORKERS", - String.valueOf(targetConfig.workers) - ); - } - pickCompilePlatform(); + protected void handleProtoFiles() { + // Handle .proto files. + for (String file : targetConfig.protoFiles) { + this.processProtoFile(file); } - - protected void handleProtoFiles() { - // Handle .proto files. - for (String file : targetConfig.protoFiles) { - this.processProtoFile(file); - } + } + + /** + * Generate code that needs to appear at the top of the generated C file, such as #define and + * #include statements. + */ + public String generateDirectives() { + CodeBuilder code = new CodeBuilder(); + code.prComment("Code generated by the Lingua Franca compiler from:"); + code.prComment("file:/" + FileUtil.toUnixString(fileConfig.srcFile)); + code.pr( + CPreambleGenerator.generateDefineDirectives( + targetConfig, fileConfig.getSrcGenPath(), hasModalReactors)); + code.pr(CPreambleGenerator.generateIncludeStatements(targetConfig, CCppMode)); + return code.toString(); + } + + /** Generate top-level preamble code. */ + protected String generateTopLevelPreambles(Reactor reactor) { + CodeBuilder builder = new CodeBuilder(); + var guard = "TOP_LEVEL_PREAMBLE_" + reactor.eContainer().hashCode() + "_H"; + builder.pr("#ifndef " + guard); + builder.pr("#define " + guard); + // Reactors that are instantiated by the specified reactor need to have + // their file-level preambles included. This needs to also include file-level + // preambles of base classes of those reactors. + Stream.concat(Stream.of(reactor), ASTUtils.allNestedClasses(reactor)) + .flatMap(it -> ASTUtils.allFileLevelPreambles(it).stream()) + .collect(Collectors.toSet()) + .forEach(it -> builder.pr(toText(it.getCode()))); + for (String file : targetConfig.protoFiles) { + var dotIndex = file.lastIndexOf("."); + var rootFilename = file; + if (dotIndex > 0) { + rootFilename = file.substring(0, dotIndex); + } + code.pr("#include " + addDoubleQuotes(rootFilename + ".pb-c.h")); + builder.pr("#include " + addDoubleQuotes(rootFilename + ".pb-c.h")); } - - /** - * Generate code that needs to appear at the top of the generated - * C file, such as #define and #include statements. - */ - public String generateDirectives() { - CodeBuilder code = new CodeBuilder(); - code.prComment("Code generated by the Lingua Franca compiler from:"); - code.prComment("file:/" + FileUtil.toUnixString(fileConfig.srcFile)); - code.pr(CPreambleGenerator.generateDefineDirectives( - targetConfig, - fileConfig.getSrcGenPath(), - hasModalReactors - )); - code.pr(CPreambleGenerator.generateIncludeStatements( - targetConfig, - CCppMode - )); - return code.toString(); + builder.pr("#endif"); + return builder.toString(); + } + + protected boolean targetLanguageIsCpp() { + return CCppMode; + } + + /** + * Given a line of text from the output of a compiler, return an instance of ErrorFileAndLine if + * the line is recognized as the first line of an error message. Otherwise, return null. + * + * @param line A line of output from a compiler or other external tool that might generate errors. + * @return If the line is recognized as the start of an error message, then return a class + * containing the path to the file on which the error occurred (or null if there is none), the + * line number (or the string "1" if there is none), the character position (or the string "0" + * if there is none), and the message (or an empty string if there is none). + */ + @Override + public GeneratorBase.ErrorFileAndLine parseCommandOutput(String line) { + var matcher = compileErrorPattern.matcher(line); + if (matcher.find()) { + var result = new ErrorFileAndLine(); + result.filepath = matcher.group("path"); + result.line = matcher.group("line"); + result.character = matcher.group("column"); + result.message = matcher.group("message"); + + if (!result.message.toLowerCase().contains("error:")) { + result.isError = false; + } + return result; } - - /** - * Generate top-level preamble code. - */ - protected String generateTopLevelPreambles(Reactor reactor) { - CodeBuilder builder = new CodeBuilder(); - var guard = "TOP_LEVEL_PREAMBLE_" + reactor.eContainer().hashCode() + "_H"; - builder.pr("#ifndef " + guard); - builder.pr("#define " + guard); - // Reactors that are instantiated by the specified reactor need to have - // their file-level preambles included. This needs to also include file-level - // preambles of base classes of those reactors. - Stream.concat(Stream.of(reactor), ASTUtils.allNestedClasses(reactor)) - .flatMap(it -> ASTUtils.allFileLevelPreambles(it).stream()) - .collect(Collectors.toSet()) - .forEach(it -> builder.pr(toText(it.getCode()))); - for (String file : targetConfig.protoFiles) { - var dotIndex = file.lastIndexOf("."); - var rootFilename = file; - if (dotIndex > 0) { - rootFilename = file.substring(0, dotIndex); - } - code.pr("#include " + addDoubleQuotes(rootFilename + ".pb-c.h")); - builder.pr("#include " + addDoubleQuotes(rootFilename + ".pb-c.h")); + return null; + } + + //////////////////////////////////////////// + //// Private methods. + + /** Returns the Target enum for this generator */ + @Override + public Target getTarget() { + return Target.C; + } + + //////////////////////////////////////////////////////////// + //// Private methods + + /** + * If a main or federated reactor has been declared, create a ReactorInstance for this top level. + * This will also assign levels to reactions, then, if the program is federated, perform an AST + * transformation to disconnect connections between federates. + */ + private void createMainReactorInstance() { + if (this.mainDef != null) { + if (this.main == null) { + // Recursively build instances. + this.main = new ReactorInstance(toDefinition(mainDef.getReactorClass()), errorReporter); + var reactionInstanceGraph = this.main.assignLevels(); + if (reactionInstanceGraph.nodeCount() > 0) { + errorReporter.reportError("Main reactor has causality cycles. Skipping code generation."); + return; } - builder.pr("#endif"); - return builder.toString(); - } - - protected boolean targetLanguageIsCpp() { - return CCppMode; - } - - /** Given a line of text from the output of a compiler, return - * an instance of ErrorFileAndLine if the line is recognized as - * the first line of an error message. Otherwise, return null. - * @param line A line of output from a compiler or other external - * tool that might generate errors. - * @return If the line is recognized as the start of an error message, - * then return a class containing the path to the file on which the - * error occurred (or null if there is none), the line number (or the - * string "1" if there is none), the character position (or the string - * "0" if there is none), and the message (or an empty string if there - * is none). - */ - @Override - public GeneratorBase.ErrorFileAndLine parseCommandOutput(String line) { - var matcher = compileErrorPattern.matcher(line); - if (matcher.find()) { - var result = new ErrorFileAndLine(); - result.filepath = matcher.group("path"); - result.line = matcher.group("line"); - result.character = matcher.group("column"); - result.message = matcher.group("message"); - - if (!result.message.toLowerCase().contains("error:")) { - result.isError = false; - } - return result; + if (hasDeadlines) { + this.main.assignDeadlines(); } - return null; - } - - //////////////////////////////////////////// - //// Private methods. - - /** Returns the Target enum for this generator */ - @Override - public Target getTarget() { - return Target.C; - } - - //////////////////////////////////////////////////////////// - //// Private methods - - /** - * If a main or federated reactor has been declared, create a ReactorInstance - * for this top level. This will also assign levels to reactions, then, - * if the program is federated, perform an AST transformation to disconnect - * connections between federates. - */ - private void createMainReactorInstance() { - if (this.mainDef != null) { - if (this.main == null) { - // Recursively build instances. - this.main = new ReactorInstance(toDefinition(mainDef.getReactorClass()), errorReporter); - var reactionInstanceGraph = this.main.assignLevels(); - if (reactionInstanceGraph.nodeCount() > 0) { - errorReporter.reportError("Main reactor has causality cycles. Skipping code generation."); - return; - } - if (hasDeadlines) { - this.main.assignDeadlines(); - } - // Inform the run-time of the breadth/parallelism of the reaction graph - var breadth = reactionInstanceGraph.getBreadth(); - if (breadth == 0) { - errorReporter.reportWarning("The program has no reactions"); - } else { - targetConfig.compileDefinitions.put( - "LF_REACTION_GRAPH_BREADTH", - String.valueOf(reactionInstanceGraph.getBreadth()) - ); - } - } + // Inform the run-time of the breadth/parallelism of the reaction graph + var breadth = reactionInstanceGraph.getBreadth(); + if (breadth == 0) { + errorReporter.reportWarning("The program has no reactions"); + } else { + targetConfig.compileDefinitions.put( + "LF_REACTION_GRAPH_BREADTH", String.valueOf(reactionInstanceGraph.getBreadth())); } + } } - - /** - * Generate an array of self structs for the reactor - * and one for each of its children. - * @param r The reactor instance. - */ - private void generateSelfStructs(ReactorInstance r) { - initializeTriggerObjects.pr(CUtil.selfType(r)+"* "+CUtil.reactorRefName(r)+"["+r.getTotalWidth()+"];"); - initializeTriggerObjects.pr("SUPPRESS_UNUSED_WARNING("+CUtil.reactorRefName(r)+");"); - for (ReactorInstance child : r.children) { - generateSelfStructs(child); - } + } + + /** + * Generate an array of self structs for the reactor and one for each of its children. + * + * @param r The reactor instance. + */ + private void generateSelfStructs(ReactorInstance r) { + initializeTriggerObjects.pr( + CUtil.selfType(r) + "* " + CUtil.reactorRefName(r) + "[" + r.getTotalWidth() + "];"); + initializeTriggerObjects.pr("SUPPRESS_UNUSED_WARNING(" + CUtil.reactorRefName(r) + ");"); + for (ReactorInstance child : r.children) { + generateSelfStructs(child); } + } - private Stream allTypeParameterizedReactors() { - return ASTUtils.recursiveChildren(main).stream() - .map(it -> it.tpr) - .distinct(); - } + private Stream allTypeParameterizedReactors() { + return ASTUtils.recursiveChildren(main).stream().map(it -> it.tpr).distinct(); + } } diff --git a/org.lflang/src/org/lflang/generator/c/CMainFunctionGenerator.java b/org.lflang/src/org/lflang/generator/c/CMainFunctionGenerator.java index 533611368a..044874029f 100644 --- a/org.lflang/src/org/lflang/generator/c/CMainFunctionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CMainFunctionGenerator.java @@ -3,120 +3,113 @@ import java.util.ArrayList; import java.util.List; import org.lflang.TargetConfig; +import org.lflang.TargetProperty.Platform; import org.lflang.generator.CodeBuilder; import org.lflang.util.StringUtil; -import org.lflang.TargetProperty.Platform; public class CMainFunctionGenerator { - private TargetConfig targetConfig; - /** The command to run the generated code if specified in the target directive. */ - private List runCommand; + private TargetConfig targetConfig; + /** The command to run the generated code if specified in the target directive. */ + private List runCommand; - public CMainFunctionGenerator(TargetConfig targetConfig) { - this.targetConfig = targetConfig; - runCommand = new ArrayList<>(); - parseTargetParameters(); - } + public CMainFunctionGenerator(TargetConfig targetConfig) { + this.targetConfig = targetConfig; + runCommand = new ArrayList<>(); + parseTargetParameters(); + } - /** - * Generate the code that is the entry point - * of the program. - * - * Ideally, this code would belong to its own {@code main.c} - * file, but it currently lives in the same file - * as all the code generated for reactors. - */ - public String generateCode() { - CodeBuilder code = new CodeBuilder(); - code.pr(generateMainFunction()); - code.pr(generateSetDefaultCliOption()); - return code.toString(); - } + /** + * Generate the code that is the entry point of the program. + * + *

Ideally, this code would belong to its own {@code main.c} file, but it currently lives in + * the same file as all the code generated for reactors. + */ + public String generateCode() { + CodeBuilder code = new CodeBuilder(); + code.pr(generateMainFunction()); + code.pr(generateSetDefaultCliOption()); + return code.toString(); + } - /** - * Generate the {@code main} function. - */ - private String generateMainFunction() { - if (targetConfig.platformOptions.platform == Platform.ARDUINO) { - /** - By default, we must have a serial begin line prior to calling lf_reactor_c_main due to internal debugging messages requiring a print buffer. - For the future, we can check whether internal LF logging is enabled or not before removing this line. - - Logging - */ - return String.join("\n", - "\nvoid _lf_arduino_print_message_function(const char* format, va_list args) {", - "\tchar buf[128];", - "\tvsnprintf(buf, 128, format, args);", - "\tSerial.print(buf);", - "}\n", - "// Arduino setup() and loop() functions", - "void setup() {", - "\tSerial.begin(" + targetConfig.platformOptions.baudRate + ");", - "\tlf_register_print_function(&_lf_arduino_print_message_function, LOG_LEVEL);", - "\tlf_reactor_c_main(0, NULL);", - "}\n", - "void loop() {}" - ); - } else if (targetConfig.platformOptions.platform == Platform.ZEPHYR) { - // The Zephyr "runtime" does not terminate when main returns. - // Rather, {@code exit} should be called explicitly. - return String.join("\n", - "void main(void) {", - " int res = lf_reactor_c_main(0, NULL);", - " exit(res);", - "}" - ); - } else { - return String.join("\n", - "int main(int argc, const char* argv[]) {", - " return lf_reactor_c_main(argc, argv);", - "}" - ); - } + /** Generate the {@code main} function. */ + private String generateMainFunction() { + if (targetConfig.platformOptions.platform == Platform.ARDUINO) { + /** + * By default, we must have a serial begin line prior to calling lf_reactor_c_main due to + * internal debugging messages requiring a print buffer. For the future, we can check whether + * internal LF logging is enabled or not before removing this line. - Logging + */ + return String.join( + "\n", + "\nvoid _lf_arduino_print_message_function(const char* format, va_list args) {", + "\tchar buf[128];", + "\tvsnprintf(buf, 128, format, args);", + "\tSerial.print(buf);", + "}\n", + "// Arduino setup() and loop() functions", + "void setup() {", + "\tSerial.begin(" + targetConfig.platformOptions.baudRate + ");", + "\tlf_register_print_function(&_lf_arduino_print_message_function, LOG_LEVEL);", + "\tlf_reactor_c_main(0, NULL);", + "}\n", + "void loop() {}"); + } else if (targetConfig.platformOptions.platform == Platform.ZEPHYR) { + // The Zephyr "runtime" does not terminate when main returns. + // Rather, {@code exit} should be called explicitly. + return String.join( + "\n", + "void main(void) {", + " int res = lf_reactor_c_main(0, NULL);", + " exit(res);", + "}"); + } else { + return String.join( + "\n", + "int main(int argc, const char* argv[]) {", + " return lf_reactor_c_main(argc, argv);", + "}"); } + } - /** - * Generate code that is used to override the - * command line options to the {@code main} function - */ - private String generateSetDefaultCliOption() { - // Generate function to set default command-line options. - // A literal array needs to be given outside any function definition, - // so start with that. - return runCommand.size() > 0 ? - String.join("\n", - "const char* _lf_default_argv[] = { " + - StringUtil.addDoubleQuotes( - StringUtil.joinObjects(runCommand, - StringUtil.addDoubleQuotes(", ")))+" };", - "void _lf_set_default_command_line_options() {", - " default_argc = "+runCommand.size()+";", - " default_argv = _lf_default_argv;", - "}") - : "void _lf_set_default_command_line_options() {}"; - } + /** + * Generate code that is used to override the command line options to the {@code main} function + */ + private String generateSetDefaultCliOption() { + // Generate function to set default command-line options. + // A literal array needs to be given outside any function definition, + // so start with that. + return runCommand.size() > 0 + ? String.join( + "\n", + "const char* _lf_default_argv[] = { " + + StringUtil.addDoubleQuotes( + StringUtil.joinObjects(runCommand, StringUtil.addDoubleQuotes(", "))) + + " };", + "void _lf_set_default_command_line_options() {", + " default_argc = " + runCommand.size() + ";", + " default_argv = _lf_default_argv;", + "}") + : "void _lf_set_default_command_line_options() {}"; + } - /** - * Parse the target parameters and set flags to the runCommand - * accordingly. - */ - private void parseTargetParameters() { - if (targetConfig.fastMode) { - runCommand.add("-f"); - runCommand.add("true"); - } - if (targetConfig.keepalive) { - runCommand.add("-k"); - runCommand.add("true"); - } - if (targetConfig.timeout != null) { - runCommand.add("-o"); - runCommand.add(targetConfig.timeout.getMagnitude() + ""); - runCommand.add(targetConfig.timeout.unit.getCanonicalName()); - } - // The runCommand has a first entry that is ignored but needed. - if (runCommand.size() > 0) { - runCommand.add(0, "dummy"); - } + /** Parse the target parameters and set flags to the runCommand accordingly. */ + private void parseTargetParameters() { + if (targetConfig.fastMode) { + runCommand.add("-f"); + runCommand.add("true"); + } + if (targetConfig.keepalive) { + runCommand.add("-k"); + runCommand.add("true"); + } + if (targetConfig.timeout != null) { + runCommand.add("-o"); + runCommand.add(targetConfig.timeout.getMagnitude() + ""); + runCommand.add(targetConfig.timeout.unit.getCanonicalName()); + } + // The runCommand has a first entry that is ignored but needed. + if (runCommand.size() > 0) { + runCommand.add(0, "dummy"); } + } } diff --git a/org.lflang/src/org/lflang/generator/c/CMethodGenerator.java b/org.lflang/src/org/lflang/generator/c/CMethodGenerator.java index 5da00ac70e..1950e8df82 100644 --- a/org.lflang/src/org/lflang/generator/c/CMethodGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CMethodGenerator.java @@ -2,8 +2,8 @@ import static org.lflang.ast.ASTUtils.allMethods; -import org.lflang.ast.ASTUtils; import org.lflang.InferredType; +import org.lflang.ast.ASTUtils; import org.lflang.generator.CodeBuilder; import org.lflang.lf.Method; import org.lflang.lf.Reactor; @@ -15,165 +15,151 @@ */ public class CMethodGenerator { - /** - * Generate macro definitions for methods. - * @param tpr The reactor. - * @param body The place to put the macro definitions. - */ - public static void generateMacrosForMethods( - TypeParameterizedReactor tpr, - CodeBuilder body - ) { - for (Method method : allMethods(tpr.reactor())) { - var functionName = methodFunctionName(tpr, method); - // If the method has no arguments. Do not pass it any variadic arguments. - if (method.getArguments().size() > 0) { - body.pr("#define "+method.getName()+"(...) "+functionName+"(self, ##__VA_ARGS__)"); - } else { - body.pr("#define "+method.getName()+"() "+functionName+"(self)"); - } - } + /** + * Generate macro definitions for methods. + * + * @param tpr The reactor. + * @param body The place to put the macro definitions. + */ + public static void generateMacrosForMethods(TypeParameterizedReactor tpr, CodeBuilder body) { + for (Method method : allMethods(tpr.reactor())) { + var functionName = methodFunctionName(tpr, method); + // If the method has no arguments. Do not pass it any variadic arguments. + if (method.getArguments().size() > 0) { + body.pr("#define " + method.getName() + "(...) " + functionName + "(self, ##__VA_ARGS__)"); + } else { + body.pr("#define " + method.getName() + "() " + functionName + "(self)"); + } } + } - /** - * Generate macro undefinitions for methods. - * @param reactor The reactor. - * @param body The place to put the macro definitions. - */ - public static void generateMacroUndefsForMethods( - Reactor reactor, - CodeBuilder body - ) { - for (Method method : allMethods(reactor)) { - body.pr("#undef "+method.getName()); - } + /** + * Generate macro undefinitions for methods. + * + * @param reactor The reactor. + * @param body The place to put the macro definitions. + */ + public static void generateMacroUndefsForMethods(Reactor reactor, CodeBuilder body) { + for (Method method : allMethods(reactor)) { + body.pr("#undef " + method.getName()); } + } - /** - * Generate a method function definition for a reactor. - * This function will have a first argument that is a void* pointing to - * the self struct, followed by any arguments given in its definition. - * @param method The method. - * @param tpr The concrete reactor class. - * @param types The C-specific type conversion functions. - */ - public static String generateMethod( - Method method, - TypeParameterizedReactor tpr, - CTypes types - ) { - var code = new CodeBuilder(); - var body = ASTUtils.toText(method.getCode()); - - code.prSourceLineNumber(method); - code.prComment("Implementation of method "+method.getName()+"()"); - code.pr(generateMethodSignature(method, tpr, types) + " {"); - code.indent(); + /** + * Generate a method function definition for a reactor. This function will have a first argument + * that is a void* pointing to the self struct, followed by any arguments given in its definition. + * + * @param method The method. + * @param tpr The concrete reactor class. + * @param types The C-specific type conversion functions. + */ + public static String generateMethod(Method method, TypeParameterizedReactor tpr, CTypes types) { + var code = new CodeBuilder(); + var body = ASTUtils.toText(method.getCode()); - // Define the "self" struct. - String structType = CUtil.selfType(tpr); - // 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);" - )); - } + code.prSourceLineNumber(method); + code.prComment("Implementation of method " + method.getName() + "()"); + code.pr(generateMethodSignature(method, tpr, types) + " {"); + code.indent(); - code.prSourceLineNumber(method.getCode()); - code.pr(body); - code.unindent(); - code.pr("}"); - return code.toString(); + // Define the "self" struct. + String structType = CUtil.selfType(tpr); + // 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);")); } - /** - * Generate method functions definition for a reactor. - * These functions have a first argument that is a void* pointing to - * the self struct. - * @param tpr The reactor. - * @param code The place to put the code. - * @param types The C-specific type conversion functions. - */ - public static void generateMethods( - TypeParameterizedReactor tpr, - CodeBuilder code, - CTypes types - ) { - var reactor = tpr.reactor(); - code.prComment("***** Start of method declarations."); - signatures(tpr, code, types); - generateMacrosForMethods(tpr, code); - for (Method method : allMethods(reactor)) { - code.pr(CMethodGenerator.generateMethod(method, tpr, types)); - } - generateMacroUndefsForMethods(reactor, code); - code.prComment("***** End of method declarations."); - } + code.prSourceLineNumber(method.getCode()); + code.pr(body); + code.unindent(); + code.pr("}"); + return code.toString(); + } - /** - * Generate function signatures for methods. - * This can be used to declare all the methods with signatures only - * before giving the full definition so that methods may call each other - * (and themselves) regardless of the order of definition. - * @param tpr The reactor declaration. - * @param types The C-specific type conversion functions. - */ - public static void signatures( - TypeParameterizedReactor tpr, - CodeBuilder body, - CTypes types - ) { - Reactor reactor = tpr.reactor(); - for (Method method : allMethods(reactor)) { - body.pr(generateMethodSignature(method, tpr, types) + ";"); - } + /** + * Generate method functions definition for a reactor. These functions have a first argument that + * is a void* pointing to the self struct. + * + * @param tpr The reactor. + * @param code The place to put the code. + * @param types The C-specific type conversion functions. + */ + public static void generateMethods(TypeParameterizedReactor tpr, CodeBuilder code, CTypes types) { + var reactor = tpr.reactor(); + code.prComment("***** Start of method declarations."); + signatures(tpr, code, types); + generateMacrosForMethods(tpr, code); + for (Method method : allMethods(reactor)) { + code.pr(CMethodGenerator.generateMethod(method, tpr, types)); } + generateMacroUndefsForMethods(reactor, code); + code.prComment("***** End of method declarations."); + } - /** - * Return the function name for specified method of the specified reactor. - * @param tpr The reactor - * @param method The method. - * @return The function name for the method. - */ - private static String methodFunctionName(TypeParameterizedReactor tpr, Method method) { - return CUtil.getName(tpr) + "_method_" + method.getName(); + /** + * Generate function signatures for methods. This can be used to declare all the methods with + * signatures only before giving the full definition so that methods may call each other (and + * themselves) regardless of the order of definition. + * + * @param tpr The reactor declaration. + * @param types The C-specific type conversion functions. + */ + public static void signatures(TypeParameterizedReactor tpr, CodeBuilder body, CTypes types) { + Reactor reactor = tpr.reactor(); + for (Method method : allMethods(reactor)) { + body.pr(generateMethodSignature(method, tpr, types) + ";"); } + } + + /** + * Return the function name for specified method of the specified reactor. + * + * @param tpr The reactor + * @param method The method. + * @return The function name for the method. + */ + private static String methodFunctionName(TypeParameterizedReactor tpr, Method method) { + return CUtil.getName(tpr) + "_method_" + method.getName(); + } - /** - * Generate a method function signature for a reactor. - * This function will have a first argument that is a void* pointing to - * the self struct, followed by any arguments given in its definition. - * @param method The method. - * @param tpr The reactor declaration. - * @param types The C-specific type conversion functions. - */ - public static String generateMethodSignature( - Method method, - TypeParameterizedReactor tpr, - CTypes types - ) { - var functionName = methodFunctionName(tpr, method); + /** + * Generate a method function signature for a reactor. This function will have a first argument + * that is a void* pointing to the self struct, followed by any arguments given in its definition. + * + * @param method The method. + * @param tpr The reactor declaration. + * @param types The C-specific type conversion functions. + */ + public static String generateMethodSignature( + Method method, TypeParameterizedReactor tpr, CTypes types) { + var functionName = methodFunctionName(tpr, method); - StringBuilder result = new StringBuilder(); - if (method.getReturn() != null) { - result.append(types.getTargetType(InferredType.fromAST(method.getReturn()))); - result.append(" "); - } else { - result.append("void "); - } - result.append(functionName); - result.append("(void* instance_args"); - if (method.getArguments() != null) { - for (var arg : method.getArguments()) { - result.append(", "); - result.append(types.getTargetType(InferredType.fromAST(arg.getType()))); - result.append(" "); - result.append(arg.getName()); - } - } - result.append(")"); - return result.toString(); + StringBuilder result = new StringBuilder(); + if (method.getReturn() != null) { + result.append(types.getTargetType(InferredType.fromAST(method.getReturn()))); + result.append(" "); + } else { + result.append("void "); + } + result.append(functionName); + result.append("(void* instance_args"); + if (method.getArguments() != null) { + for (var arg : method.getArguments()) { + result.append(", "); + result.append(types.getTargetType(InferredType.fromAST(arg.getType()))); + result.append(" "); + result.append(arg.getName()); + } } + result.append(")"); + return result.toString(); + } } diff --git a/org.lflang/src/org/lflang/generator/c/CMixedRadixGenerator.java b/org.lflang/src/org/lflang/generator/c/CMixedRadixGenerator.java index 40717f3c4d..f2bf71ef66 100644 --- a/org.lflang/src/org/lflang/generator/c/CMixedRadixGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CMixedRadixGenerator.java @@ -1,16 +1,16 @@ package org.lflang.generator.c; public class CMixedRadixGenerator { - /** Standardized name for channel index variable for a source. */ - public static String sc = "src_channel"; - /** Standardized name for bank index variable for a source. */ - public static String sb = "src_bank"; - /** Standardized name for runtime index variable for a source. */ - public static String sr = "src_runtime"; - /** Standardized name for channel index variable for a destination. */ - public static String dc = "dst_channel"; - /** Standardized name for bank index variable for a destination. */ - public static String db = "dst_bank"; - /** Standardized name for runtime index variable for a destination. */ - public static String dr = "dst_runtime"; + /** Standardized name for channel index variable for a source. */ + public static String sc = "src_channel"; + /** Standardized name for bank index variable for a source. */ + public static String sb = "src_bank"; + /** Standardized name for runtime index variable for a source. */ + public static String sr = "src_runtime"; + /** Standardized name for channel index variable for a destination. */ + public static String dc = "dst_channel"; + /** Standardized name for bank index variable for a destination. */ + public static String db = "dst_bank"; + /** Standardized name for runtime index variable for a destination. */ + public static String dr = "dst_runtime"; } diff --git a/org.lflang/src/org/lflang/generator/c/CModesGenerator.java b/org.lflang/src/org/lflang/generator/c/CModesGenerator.java index 2a6217de64..7d72890639 100644 --- a/org.lflang/src/org/lflang/generator/c/CModesGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CModesGenerator.java @@ -1,7 +1,6 @@ package org.lflang.generator.c; import java.util.List; - import org.lflang.ast.ASTUtils; import org.lflang.generator.CodeBuilder; import org.lflang.generator.ReactionInstance; @@ -17,203 +16,228 @@ * @author Hou Seng Wong */ public class CModesGenerator { - /** - * Generate fields in the self struct for mode instances - * - * @param reactor - * @param body - * @param constructorCode - */ - public static void generateDeclarations( - Reactor reactor, - CodeBuilder body, - CodeBuilder constructorCode - ) { - List allModes = ASTUtils.allModes(reactor); - if (!allModes.isEmpty()) { - // Reactor's mode instances and its state. - body.pr(String.join("\n", - "reactor_mode_t _lf__modes["+reactor.getModes().size()+"];" - )); + /** + * Generate fields in the self struct for mode instances + * + * @param reactor + * @param body + * @param constructorCode + */ + public static void generateDeclarations( + Reactor reactor, CodeBuilder body, CodeBuilder constructorCode) { + List allModes = ASTUtils.allModes(reactor); + if (!allModes.isEmpty()) { + // Reactor's mode instances and its state. + body.pr(String.join("\n", "reactor_mode_t _lf__modes[" + reactor.getModes().size() + "];")); - // Initialize the mode instances - constructorCode.pr("// Initialize modes"); - constructorCode.pr("self_base_t* _lf_self_base = (self_base_t*)self;"); - int initialMode = -1; + // Initialize the mode instances + constructorCode.pr("// Initialize modes"); + constructorCode.pr("self_base_t* _lf_self_base = (self_base_t*)self;"); + int initialMode = -1; - for (int i = 0; i < allModes.size(); i++){ - var mode = allModes.get(i); - constructorCode.pr(mode, String.join("\n", - "self->_lf__modes["+i+"].state = &_lf_self_base->_lf__mode_state;", - "self->_lf__modes["+i+"].name = \""+mode.getName()+"\";", - "self->_lf__modes["+i+"].deactivation_time = 0;", - "self->_lf__modes["+i+"].flags = 0;" - )); - if (initialMode < 0 && mode.isInitial()) { - initialMode = i; - } - } + for (int i = 0; i < allModes.size(); i++) { + var mode = allModes.get(i); + constructorCode.pr( + mode, + String.join( + "\n", + "self->_lf__modes[" + i + "].state = &_lf_self_base->_lf__mode_state;", + "self->_lf__modes[" + i + "].name = \"" + mode.getName() + "\";", + "self->_lf__modes[" + i + "].deactivation_time = 0;", + "self->_lf__modes[" + i + "].flags = 0;")); + if (initialMode < 0 && mode.isInitial()) { + initialMode = i; + } + } - assert initialMode >= 0 : "initial mode must be non-negative!!"; + assert initialMode >= 0 : "initial mode must be non-negative!!"; - // Initialize mode state with initial mode active upon start - constructorCode.pr(String.join("\n", - "// Initialize mode state", - "_lf_self_base->_lf__mode_state.parent_mode = NULL;", - "_lf_self_base->_lf__mode_state.initial_mode = &self->_lf__modes["+initialMode+"];", - "_lf_self_base->_lf__mode_state.current_mode = _lf_self_base->_lf__mode_state.initial_mode;", - "_lf_self_base->_lf__mode_state.next_mode = NULL;", - "_lf_self_base->_lf__mode_state.mode_change = no_transition;" - )); - } + // Initialize mode state with initial mode active upon start + constructorCode.pr( + String.join( + "\n", + "// Initialize mode state", + "_lf_self_base->_lf__mode_state.parent_mode = NULL;", + "_lf_self_base->_lf__mode_state.initial_mode = &self->_lf__modes[" + + initialMode + + "];", + "_lf_self_base->_lf__mode_state.current_mode =" + + " _lf_self_base->_lf__mode_state.initial_mode;", + "_lf_self_base->_lf__mode_state.next_mode = NULL;", + "_lf_self_base->_lf__mode_state.mode_change = no_transition;")); } + } - /** - * Generate the declaration of modal models state table. - * - * @param hasModalReactors True if there is modal model reactors, false otherwise - * @param modalReactorCount The number of modal model reactors - * @param modalStateResetCount The number of modal model state resets - */ - public static String generateModeStatesTable( - boolean hasModalReactors, - int modalReactorCount, - int modalStateResetCount - ) { - if (hasModalReactors) { - return String.join("\n", - "// Array of pointers to mode states to be handled in _lf_handle_mode_changes().", - "reactor_mode_state_t* _lf_modal_reactor_states["+modalReactorCount+"];", - "int _lf_modal_reactor_states_size = "+modalReactorCount+";", - (modalStateResetCount > 0 ? - String.join("\n", - "// Array of reset data for state variables nested in modes. Used in _lf_handle_mode_changes().", - "mode_state_variable_reset_data_t _lf_modal_state_reset["+modalStateResetCount+"];", - "int _lf_modal_state_reset_size = "+modalStateResetCount+";") - : "" - )); - } - return ""; + /** + * Generate the declaration of modal models state table. + * + * @param hasModalReactors True if there is modal model reactors, false otherwise + * @param modalReactorCount The number of modal model reactors + * @param modalStateResetCount The number of modal model state resets + */ + public static String generateModeStatesTable( + boolean hasModalReactors, int modalReactorCount, int modalStateResetCount) { + if (hasModalReactors) { + return String.join( + "\n", + "// Array of pointers to mode states to be handled in _lf_handle_mode_changes().", + "reactor_mode_state_t* _lf_modal_reactor_states[" + modalReactorCount + "];", + "int _lf_modal_reactor_states_size = " + modalReactorCount + ";", + (modalStateResetCount > 0 + ? String.join( + "\n", + "// Array of reset data for state variables nested in modes. Used in" + + " _lf_handle_mode_changes().", + "mode_state_variable_reset_data_t _lf_modal_state_reset[" + + modalStateResetCount + + "];", + "int _lf_modal_state_reset_size = " + modalStateResetCount + ";") + : "")); } + return ""; + } - /** - * Generate counter variable declarations used for registering modal reactors. - * - * @param hasModalReactors True if there is modal model reactors, false otherwise - */ - public static String generateModalInitalizationCounters(boolean hasModalReactors) { - if (hasModalReactors) { - return String.join("\n", - "int _lf_modal_reactor_states_count = 0;", - "int _lf_modal_state_reset_count = 0;" - ); - } - return ""; + /** + * Generate counter variable declarations used for registering modal reactors. + * + * @param hasModalReactors True if there is modal model reactors, false otherwise + */ + public static String generateModalInitalizationCounters(boolean hasModalReactors) { + if (hasModalReactors) { + return String.join( + "\n", "int _lf_modal_reactor_states_count = 0;", "int _lf_modal_state_reset_count = 0;"); } + return ""; + } - /** - * Generate code for modal reactor registration and hierarchy. - * - * @param instance The reactor instance. - * @param code The code builder. - */ - public static void generateModeStructure(ReactorInstance instance, CodeBuilder code) { - var parentMode = instance.getMode(false); - var nameOfSelfStruct = CUtil.reactorRef(instance); - // If this instance is enclosed in another mode - if (parentMode != null) { - var parentModeRef = "&"+CUtil.reactorRef(parentMode.getParent())+"->_lf__modes["+parentMode.getParent().modes.indexOf(parentMode)+"]"; - code.pr("// Setup relation to enclosing mode"); + /** + * Generate code for modal reactor registration and hierarchy. + * + * @param instance The reactor instance. + * @param code The code builder. + */ + public static void generateModeStructure(ReactorInstance instance, CodeBuilder code) { + var parentMode = instance.getMode(false); + var nameOfSelfStruct = CUtil.reactorRef(instance); + // If this instance is enclosed in another mode + if (parentMode != null) { + var parentModeRef = + "&" + + CUtil.reactorRef(parentMode.getParent()) + + "->_lf__modes[" + + parentMode.getParent().modes.indexOf(parentMode) + + "]"; + code.pr("// Setup relation to enclosing mode"); - // If this reactor does not have its own modes, all reactions must be linked to enclosing mode - if (instance.modes.isEmpty()) { - int i = 0; - for (ReactionInstance reaction : instance.reactions) { - code.pr(CUtil.reactorRef(reaction.getParent())+"->_lf__reaction_"+i+".mode = "+parentModeRef+";"); - i++; - } - } else { // Otherwise, only reactions outside modes must be linked and the mode state itself gets a parent relation - code.pr("((self_base_t*)"+nameOfSelfStruct+")->_lf__mode_state.parent_mode = "+parentModeRef+";"); - for (var reaction : (Iterable) instance.reactions.stream().filter(it -> it.getMode(true) == null)::iterator) { - code.pr(CUtil.reactorRef(reaction.getParent())+"->_lf__reaction_"+instance.reactions.indexOf(reaction)+".mode = "+parentModeRef+";"); - } - } + // If this reactor does not have its own modes, all reactions must be linked to enclosing mode + if (instance.modes.isEmpty()) { + int i = 0; + for (ReactionInstance reaction : instance.reactions) { + code.pr( + CUtil.reactorRef(reaction.getParent()) + + "->_lf__reaction_" + + i + + ".mode = " + + parentModeRef + + ";"); + i++; } - // If this reactor has modes, register for mode change handling - if (!instance.modes.isEmpty()) { - code.pr("// Register for transition handling"); - code.pr("_lf_modal_reactor_states[_lf_modal_reactor_states_count++] = &((self_base_t*)"+nameOfSelfStruct+")->_lf__mode_state;"); + } else { // Otherwise, only reactions outside modes must be linked and the mode state itself + // gets a parent relation + code.pr( + "((self_base_t*)" + + nameOfSelfStruct + + ")->_lf__mode_state.parent_mode = " + + parentModeRef + + ";"); + for (var reaction : + (Iterable) + instance.reactions.stream().filter(it -> it.getMode(true) == null)::iterator) { + code.pr( + CUtil.reactorRef(reaction.getParent()) + + "->_lf__reaction_" + + instance.reactions.indexOf(reaction) + + ".mode = " + + parentModeRef + + ";"); } + } } + // If this reactor has modes, register for mode change handling + if (!instance.modes.isEmpty()) { + code.pr("// Register for transition handling"); + code.pr( + "_lf_modal_reactor_states[_lf_modal_reactor_states_count++] = &((self_base_t*)" + + nameOfSelfStruct + + ")->_lf__mode_state;"); + } + } - /** - * Generate code registering a state variable for automatic reset. - * - * @param modeRef The code to refer to the mode - * @param selfRef The code to refer to the self struct - * @param varName The variable name in the self struct - * @param source The variable that stores the initial value - * @param type The size of the initial value - */ - public static String generateStateResetStructure( - String modeRef, - String selfRef, - String varName, - String source, - String type - ) { - return String.join("\n", - "// Register for automatic reset", - "_lf_modal_state_reset[_lf_modal_state_reset_count].mode = "+modeRef+";", - "_lf_modal_state_reset[_lf_modal_state_reset_count].target = &("+selfRef+"->"+varName+");", - "_lf_modal_state_reset[_lf_modal_state_reset_count].source = &"+source+";", - "_lf_modal_state_reset[_lf_modal_state_reset_count].size = sizeof("+type+");", - "_lf_modal_state_reset_count++;" - ); - } + /** + * Generate code registering a state variable for automatic reset. + * + * @param modeRef The code to refer to the mode + * @param selfRef The code to refer to the self struct + * @param varName The variable name in the self struct + * @param source The variable that stores the initial value + * @param type The size of the initial value + */ + public static String generateStateResetStructure( + String modeRef, String selfRef, String varName, String source, String type) { + return String.join( + "\n", + "// Register for automatic reset", + "_lf_modal_state_reset[_lf_modal_state_reset_count].mode = " + modeRef + ";", + "_lf_modal_state_reset[_lf_modal_state_reset_count].target = &(" + + selfRef + + "->" + + varName + + ");", + "_lf_modal_state_reset[_lf_modal_state_reset_count].source = &" + source + ";", + "_lf_modal_state_reset[_lf_modal_state_reset_count].size = sizeof(" + type + ");", + "_lf_modal_state_reset_count++;"); + } - /** - * Generate code to call {@code _lf_process_mode_changes}. - * - * @param hasModalReactors True if there is modal model reactors, false otherwise - * @param modalStateResetCount The number of modal model state resets - */ - public static String generateLfHandleModeChanges( - boolean hasModalReactors, - int modalStateResetCount - ) { - if (!hasModalReactors) { - return ""; - } - return String.join("\n", - "void _lf_handle_mode_changes() {", - " _lf_process_mode_changes(", - " _lf_modal_reactor_states, ", - " _lf_modal_reactor_states_size, ", - " " + (modalStateResetCount > 0 ? "_lf_modal_state_reset" : "NULL") + ", ", - " " + (modalStateResetCount > 0 ? "_lf_modal_state_reset_size" : "0") + ", ", - " _lf_timer_triggers, ", - " _lf_timer_triggers_size", - " );", - "}" - ); + /** + * Generate code to call {@code _lf_process_mode_changes}. + * + * @param hasModalReactors True if there is modal model reactors, false otherwise + * @param modalStateResetCount The number of modal model state resets + */ + public static String generateLfHandleModeChanges( + boolean hasModalReactors, int modalStateResetCount) { + if (!hasModalReactors) { + return ""; } + return String.join( + "\n", + "void _lf_handle_mode_changes() {", + " _lf_process_mode_changes(", + " _lf_modal_reactor_states, ", + " _lf_modal_reactor_states_size, ", + " " + (modalStateResetCount > 0 ? "_lf_modal_state_reset" : "NULL") + ", ", + " " + (modalStateResetCount > 0 ? "_lf_modal_state_reset_size" : "0") + ", ", + " _lf_timer_triggers, ", + " _lf_timer_triggers_size", + " );", + "}"); + } - /** - * Generate code to call {@code _lf_initialize_modes}. - * - * @param hasModalReactors True if there is modal model reactors, false otherwise - */ - public static String generateLfInitializeModes(boolean hasModalReactors) { - if (!hasModalReactors) { - return ""; - } - return String.join("\n", - "void _lf_initialize_modes() {", - " _lf_initialize_mode_states(", - " _lf_modal_reactor_states, ", - " _lf_modal_reactor_states_size);", - "}" - ); + /** + * Generate code to call {@code _lf_initialize_modes}. + * + * @param hasModalReactors True if there is modal model reactors, false otherwise + */ + public static String generateLfInitializeModes(boolean hasModalReactors) { + if (!hasModalReactors) { + return ""; } + return String.join( + "\n", + "void _lf_initialize_modes() {", + " _lf_initialize_mode_states(", + " _lf_modal_reactor_states, ", + " _lf_modal_reactor_states_size);", + "}"); + } } diff --git a/org.lflang/src/org/lflang/generator/c/CParameterGenerator.java b/org.lflang/src/org/lflang/generator/c/CParameterGenerator.java index 786b4701de..caaa375056 100644 --- a/org.lflang/src/org/lflang/generator/c/CParameterGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CParameterGenerator.java @@ -1,8 +1,8 @@ package org.lflang.generator.c; -import org.lflang.generator.ParameterInstance; import org.lflang.ast.ASTUtils; import org.lflang.generator.CodeBuilder; +import org.lflang.generator.ParameterInstance; import org.lflang.lf.Initializer; import org.lflang.lf.Parameter; @@ -14,34 +14,39 @@ * @author Hou Seng Wong */ public class CParameterGenerator { - /** - * Return a C expression that can be used to initialize the specified - * parameter instance. If the parameter initializer refers to other - * parameters, then those parameter references are replaced with - * accesses to the self struct of the parents of those parameters. - */ - public static String getInitializer(ParameterInstance p) { - // Handle the bank_index parameter. - if (p.getName().equals("bank_index")) { - return CUtil.bankIndex(p.getParent()); - } - - CTypes ctypes = CTypes.generateParametersIn(p.getParent().getParent()); - Initializer values = p.getActualValue(); - return ctypes.getTargetInitializer(values, p.getDefinition().getType()); + /** + * Return a C expression that can be used to initialize the specified parameter instance. If the + * parameter initializer refers to other parameters, then those parameter references are replaced + * with accesses to the self struct of the parents of those parameters. + */ + public static String getInitializer(ParameterInstance p) { + // Handle the bank_index parameter. + if (p.getName().equals("bank_index")) { + return CUtil.bankIndex(p.getParent()); } - /** - * Generate code for parameters variables of a reactor in the form "parameter.type parameter.name;" - * @param reactor {@link TypeParameterizedReactor} - * @param types A helper class for types - */ - public static String generateDeclarations(TypeParameterizedReactor reactor, CTypes types) { - CodeBuilder code = new CodeBuilder(); - for (Parameter parameter : ASTUtils.allParameters(reactor.reactor())) { - code.prSourceLineNumber(parameter); - code.pr(types.getTargetType(reactor.resolveType(ASTUtils.getInferredType(parameter))) + " " + parameter.getName() + ";"); - } - return code.toString(); + CTypes ctypes = CTypes.generateParametersIn(p.getParent().getParent()); + Initializer values = p.getActualValue(); + return ctypes.getTargetInitializer(values, p.getDefinition().getType()); + } + + /** + * Generate code for parameters variables of a reactor in the form "parameter.type + * parameter.name;" + * + * @param reactor {@link TypeParameterizedReactor} + * @param types A helper class for types + */ + public static String generateDeclarations(TypeParameterizedReactor reactor, CTypes types) { + CodeBuilder code = new CodeBuilder(); + for (Parameter parameter : ASTUtils.allParameters(reactor.reactor())) { + code.prSourceLineNumber(parameter); + code.pr( + types.getTargetType(reactor.resolveType(ASTUtils.getInferredType(parameter))) + + " " + + parameter.getName() + + ";"); } + return code.toString(); + } } diff --git a/org.lflang/src/org/lflang/generator/c/CPortGenerator.java b/org.lflang/src/org/lflang/generator/c/CPortGenerator.java index f82bad661c..02697f4a85 100644 --- a/org.lflang/src/org/lflang/generator/c/CPortGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CPortGenerator.java @@ -1,9 +1,11 @@ package org.lflang.generator.c; -import org.lflang.ast.ASTUtils; +import static org.lflang.generator.c.CGenerator.variableStructType; + import org.lflang.AttributeUtils; import org.lflang.ErrorReporter; import org.lflang.Target; +import org.lflang.ast.ASTUtils; import org.lflang.generator.CodeBuilder; import org.lflang.generator.PortInstance; import org.lflang.lf.Input; @@ -11,9 +13,6 @@ import org.lflang.lf.Port; import org.lflang.lf.ReactorDecl; -import static org.lflang.generator.c.CGenerator.variableStructType; - - /** * Generates C code to declare and initialize ports. * @@ -22,256 +21,267 @@ * @author Hou Seng Wong */ public class CPortGenerator { - /** - * Generate fields in the self struct for input and output ports - */ - public static void generateDeclarations( - TypeParameterizedReactor tpr, - ReactorDecl decl, - CodeBuilder body, - CodeBuilder constructorCode - ) { - generateInputDeclarations(tpr, body, constructorCode); - generateOutputDeclarations(tpr, body, constructorCode); - } + /** Generate fields in the self struct for input and output ports */ + public static void generateDeclarations( + TypeParameterizedReactor tpr, + ReactorDecl decl, + CodeBuilder body, + CodeBuilder constructorCode) { + generateInputDeclarations(tpr, body, constructorCode); + generateOutputDeclarations(tpr, body, constructorCode); + } - /** - * Generate the struct type definitions for the port of 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( - TypeParameterizedReactor tpr, - Port port, - Target target, - ErrorReporter errorReporter, - CTypes types, - CodeBuilder federatedExtension, - boolean userFacing, - ReactorDecl decl - ) { - assert decl == null || userFacing; - var code = new CodeBuilder(); - code.pr("typedef struct {"); - code.indent(); - // NOTE: The following fields are required to be the first ones so that - // pointer to this struct can be cast to a (lf_port_base_t*) or to - // (token_template_t*) to access these fields for any port. - // IMPORTANT: These must match exactly the fields defined in port.h!! - code.pr(String.join("\n", - "token_type_t type;", // From token_template_t - "lf_token_t* token;", // From token_template_t - "size_t length;", // From token_template_t - "bool is_present;", // From lf_port_base_t - "lf_sparse_io_record_t* sparse_record;", // From lf_port_base_t - "int destination_channel;", // From lf_port_base_t - "int num_destinations;" // From lf_port_base_t - )); - code.pr(valueDeclaration(tpr, port, target, errorReporter, types)); - code.pr(federatedExtension.toString()); - code.unindent(); - var name = decl != null ? localPortName(decl, port.getName()) + /** + * Generate the struct type definitions for the port of 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( + TypeParameterizedReactor tpr, + Port port, + Target target, + ErrorReporter errorReporter, + CTypes types, + CodeBuilder federatedExtension, + boolean userFacing, + ReactorDecl decl) { + assert decl == null || userFacing; + var code = new CodeBuilder(); + code.pr("typedef struct {"); + code.indent(); + // NOTE: The following fields are required to be the first ones so that + // pointer to this struct can be cast to a (lf_port_base_t*) or to + // (token_template_t*) to access these fields for any port. + // IMPORTANT: These must match exactly the fields defined in port.h!! + code.pr( + String.join( + "\n", + "token_type_t type;", // From token_template_t + "lf_token_t* token;", // From token_template_t + "size_t length;", // From token_template_t + "bool is_present;", // From lf_port_base_t + "lf_sparse_io_record_t* sparse_record;", // From lf_port_base_t + "int destination_channel;", // From lf_port_base_t + "int num_destinations;" // From lf_port_base_t + )); + code.pr(valueDeclaration(tpr, port, target, errorReporter, types)); + code.pr(federatedExtension.toString()); + code.unindent(); + var name = + decl != null + ? localPortName(decl, port.getName()) : variableStructType(port, tpr, userFacing); - code.pr("} " + name + ";"); - return code.toString(); - } + code.pr("} " + name + ";"); + return code.toString(); + } - public static String localPortName(ReactorDecl decl, String portName) { - return decl.getName().toLowerCase() + "_" + portName + "_t"; - } + 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 - * @param reactorSelfStruct The name of the self struct - */ - public static String initializeInputMultiport( - PortInstance input, - String reactorSelfStruct - ) { - var portRefName = CUtil.portRefName(input); - // If the port is a multiport, create an array. - if (input.isMultiport()) { - String result = String.join("\n", - portRefName+"_width = "+input.getWidth()+";", - "// Allocate memory for multiport inputs.", - portRefName+" = ("+variableStructType(input)+"**)_lf_allocate(", - " "+input.getWidth()+", sizeof("+variableStructType(input)+"*),", - " &"+reactorSelfStruct+"->base.allocations); ", - "// Set inputs by default to an always absent default input.", - "for (int i = 0; i < "+input.getWidth()+"; i++) {", - " "+portRefName+"[i] = &"+reactorSelfStruct+"->_lf_default__"+input.getName()+";", - "}" - ); - if (AttributeUtils.isSparse(input.getDefinition())) { - return String.join("\n", result, - "if ("+input.getWidth()+" >= LF_SPARSE_WIDTH_THRESHOLD) {", - " "+portRefName+"__sparse = (lf_sparse_io_record_t*)_lf_allocate(1,", - " sizeof(lf_sparse_io_record_t) + sizeof(size_t) * "+input.getWidth()+"/LF_SPARSE_CAPACITY_DIVIDER,", - " &"+reactorSelfStruct+"->base.allocations);", - " "+portRefName+"__sparse->capacity = "+input.getWidth()+"/LF_SPARSE_CAPACITY_DIVIDER;", - " if (_lf_sparse_io_record_sizes.start == NULL) {", - " _lf_sparse_io_record_sizes = vector_new(1);", - " }", - " vector_push(&_lf_sparse_io_record_sizes, (void*)&"+portRefName+"__sparse->size);", - "}" - ); - } - return result; - } else { - return String.join("\n", - "// width of -2 indicates that it is not a multiport.", - portRefName+"_width = -2;" - ); - } + /** + * Allocate memory for the input port. + * + * @param input The input port + * @param reactorSelfStruct The name of the self struct + */ + public static String initializeInputMultiport(PortInstance input, String reactorSelfStruct) { + var portRefName = CUtil.portRefName(input); + // If the port is a multiport, create an array. + if (input.isMultiport()) { + String result = + String.join( + "\n", + portRefName + "_width = " + input.getWidth() + ";", + "// Allocate memory for multiport inputs.", + portRefName + " = (" + variableStructType(input) + "**)_lf_allocate(", + " " + input.getWidth() + ", sizeof(" + variableStructType(input) + "*),", + " &" + reactorSelfStruct + "->base.allocations); ", + "// Set inputs by default to an always absent default input.", + "for (int i = 0; i < " + input.getWidth() + "; i++) {", + " " + + portRefName + + "[i] = &" + + reactorSelfStruct + + "->_lf_default__" + + input.getName() + + ";", + "}"); + if (AttributeUtils.isSparse(input.getDefinition())) { + return String.join( + "\n", + result, + "if (" + input.getWidth() + " >= LF_SPARSE_WIDTH_THRESHOLD) {", + " " + portRefName + "__sparse = (lf_sparse_io_record_t*)_lf_allocate(1,", + " sizeof(lf_sparse_io_record_t) + sizeof(size_t) * " + + input.getWidth() + + "/LF_SPARSE_CAPACITY_DIVIDER,", + " &" + reactorSelfStruct + "->base.allocations);", + " " + + portRefName + + "__sparse->capacity = " + + input.getWidth() + + "/LF_SPARSE_CAPACITY_DIVIDER;", + " if (_lf_sparse_io_record_sizes.start == NULL) {", + " _lf_sparse_io_record_sizes = vector_new(1);", + " }", + " vector_push(&_lf_sparse_io_record_sizes, (void*)&" + + portRefName + + "__sparse->size);", + "}"); + } + return result; + } else { + return String.join( + "\n", + "// width of -2 indicates that it is not a multiport.", + portRefName + "_width = -2;"); } + } - /** - * Allocate memory for the output port. - * @param output The output port - * @param reactorSelfStruct The name of the self struct - */ - public static String initializeOutputMultiport( - PortInstance output, - String reactorSelfStruct - ) { - var portRefName = CUtil.portRefName(output); - var portStructType = variableStructType(output); - return output.isMultiport() ? - String.join("\n", - portRefName+"_width = "+output.getWidth()+";", - "// Allocate memory for multiport output.", - portRefName+" = ("+portStructType+"*)_lf_allocate(", - " "+output.getWidth()+", sizeof("+portStructType+"),", - " &"+reactorSelfStruct+"->base.allocations); ", - portRefName+"_pointers = ("+portStructType+"**)_lf_allocate(", - " "+output.getWidth()+", sizeof("+portStructType+"*),", - " &"+reactorSelfStruct+"->base.allocations); ", - "// Assign each output port pointer to be used in", - "// reactions to facilitate user access to output ports", - "for(int i=0; i < "+output.getWidth()+"; i++) {", - " "+portRefName+"_pointers[i] = &("+portRefName+"[i]);", - "}" - ) : - String.join("\n", - "// width of -2 indicates that it is not a multiport.", - portRefName+"_width = -2;" - ); - } + /** + * Allocate memory for the output port. + * + * @param output The output port + * @param reactorSelfStruct The name of the self struct + */ + public static String initializeOutputMultiport(PortInstance output, String reactorSelfStruct) { + var portRefName = CUtil.portRefName(output); + var portStructType = variableStructType(output); + return output.isMultiport() + ? String.join( + "\n", + portRefName + "_width = " + output.getWidth() + ";", + "// Allocate memory for multiport output.", + portRefName + " = (" + portStructType + "*)_lf_allocate(", + " " + output.getWidth() + ", sizeof(" + portStructType + "),", + " &" + reactorSelfStruct + "->base.allocations); ", + portRefName + "_pointers = (" + portStructType + "**)_lf_allocate(", + " " + output.getWidth() + ", sizeof(" + portStructType + "*),", + " &" + reactorSelfStruct + "->base.allocations); ", + "// Assign each output port pointer to be used in", + "// reactions to facilitate user access to output ports", + "for(int i=0; i < " + output.getWidth() + "; i++) {", + " " + portRefName + "_pointers[i] = &(" + portRefName + "[i]);", + "}") + : String.join( + "\n", + "// width of -2 indicates that it is not a multiport.", + portRefName + "_width = -2;"); + } - /** - * For the specified port, return a declaration for port struct to - * contain the value of the port. A multiport output with width 4 and - * type int[10], for example, will result in this: - *


-     *     int value[10];
-     * 
- * There will be an array of size 4 of structs, each containing this value - * array. - * @param port The port. - * @return A string providing the value field of the port struct. - */ - private static String valueDeclaration( - TypeParameterizedReactor tpr, - Port port, - Target target, - ErrorReporter errorReporter, - CTypes types - ) { - if (port.getType() == null && target.requiresTypes) { - // This should have been caught by the validator. - errorReporter.reportError(port, "Port is required to have a type: " + port.getName()); - return ""; - } - // Do not convert to lf_token_t* using lfTypeToTokenType because there - // will be a separate field pointing to the token. - return types.getVariableDeclaration(tpr, ASTUtils.getInferredType(port), "value", false) + ";"; + /** + * For the specified port, return a declaration for port struct to contain the value of the port. + * A multiport output with width 4 and type int[10], for example, will result in this: + * + *

+   *     int value[10];
+   * 
+ * + * There will be an array of size 4 of structs, each containing this value array. + * + * @param port The port. + * @return A string providing the value field of the port struct. + */ + private static String valueDeclaration( + TypeParameterizedReactor tpr, + Port port, + Target target, + ErrorReporter errorReporter, + CTypes types) { + if (port.getType() == null && target.requiresTypes) { + // This should have been caught by the validator. + errorReporter.reportError(port, "Port is required to have a type: " + port.getName()); + return ""; } + // Do not convert to lf_token_t* using lfTypeToTokenType because there + // will be a separate field pointing to the token. + return types.getVariableDeclaration(tpr, ASTUtils.getInferredType(port), "value", false) + ";"; + } - /** - * Generate fields in the self struct for input ports - * - * If the port is a multiport, the input field is an array of - * pointers that will be allocated separately for each instance - * because the sizes may be different. Otherwise, it is a simple - * pointer. - * - */ - private static void generateInputDeclarations( - TypeParameterizedReactor tpr, - CodeBuilder body, - CodeBuilder constructorCode - ) { - for (Input input : ASTUtils.allInputs(tpr.reactor())) { - var inputName = input.getName(); - if (ASTUtils.isMultiport(input)) { - body.pr(input, String.join("\n", - "// Multiport input array will be malloc'd later.", - variableStructType(input, tpr, false)+"** _lf_"+inputName+";", - "int _lf_"+inputName+"_width;", - "// Default input (in case it does not get connected)", - variableStructType(input, tpr, 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, tpr, 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, tpr, false)+" _lf_default__"+inputName+";" - )); + /** + * Generate fields in the self struct for input ports + * + *

If the port is a multiport, the input field is an array of pointers that will be allocated + * separately for each instance because the sizes may be different. Otherwise, it is a simple + * pointer. + */ + private static void generateInputDeclarations( + TypeParameterizedReactor tpr, CodeBuilder body, CodeBuilder constructorCode) { + for (Input input : ASTUtils.allInputs(tpr.reactor())) { + var inputName = input.getName(); + if (ASTUtils.isMultiport(input)) { + body.pr( + input, + String.join( + "\n", + "// Multiport input array will be malloc'd later.", + variableStructType(input, tpr, false) + "** _lf_" + inputName + ";", + "int _lf_" + inputName + "_width;", + "// Default input (in case it does not get connected)", + variableStructType(input, tpr, 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, tpr, 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, tpr, false) + " _lf_default__" + inputName + ";")); - constructorCode.pr(input, String.join("\n", - "// Set input by default to an always absent default input.", - "self->_lf_"+inputName+" = &self->_lf_default__"+inputName+";" - )); - } - } + constructorCode.pr( + input, + String.join( + "\n", + "// Set input by default to an always absent default input.", + "self->_lf_" + inputName + " = &self->_lf_default__" + inputName + ";")); + } } + } - /** - * Generate fields in the self struct for output ports - */ - private static void generateOutputDeclarations( - TypeParameterizedReactor tpr, - CodeBuilder body, - CodeBuilder constructorCode - ) { - for (Output output : ASTUtils.allOutputs(tpr.reactor())) { - // If the port is a multiport, create an array to be allocated - // at instantiation. - var outputName = output.getName(); - if (ASTUtils.isMultiport(output)) { - body.pr(output, String.join("\n", - "// Array of output ports.", - variableStructType(output, tpr, 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", - "// multiports in the body of reactions because their ", - "// 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, tpr, false)+"** _lf_"+outputName+"_pointers;" - )); - } else { - body.pr(output, String.join("\n", - variableStructType(output, tpr, false)+" _lf_"+outputName+";", - "int _lf_"+outputName+"_width;" - )); - } - } + /** Generate fields in the self struct for output ports */ + private static void generateOutputDeclarations( + TypeParameterizedReactor tpr, CodeBuilder body, CodeBuilder constructorCode) { + for (Output output : ASTUtils.allOutputs(tpr.reactor())) { + // If the port is a multiport, create an array to be allocated + // at instantiation. + var outputName = output.getName(); + if (ASTUtils.isMultiport(output)) { + body.pr( + output, + String.join( + "\n", + "// Array of output ports.", + variableStructType(output, tpr, 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", + "// multiports in the body of reactions because their ", + "// 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, tpr, false) + "** _lf_" + outputName + "_pointers;")); + } else { + body.pr( + output, + String.join( + "\n", + variableStructType(output, tpr, 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 8ca0404d75..45b81caab6 100644 --- a/org.lflang/src/org/lflang/generator/c/CPreambleGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CPreambleGenerator.java @@ -1,18 +1,16 @@ package org.lflang.generator.c; -import java.nio.file.Path; +import static org.lflang.util.StringUtil.addDoubleQuotes; +import java.nio.file.Path; import org.lflang.TargetConfig; import org.lflang.TargetProperty.Platform; import org.lflang.generator.CodeBuilder; import org.lflang.util.StringUtil; -import static org.lflang.util.StringUtil.addDoubleQuotes; - /** - * Generates code for preambles for the C and CCpp target. - * This includes #include and #define directives at the top - * of each generated ".c" file. + * Generates code for preambles for the C and CCpp target. This includes #include and #define + * directives at the top of each generated ".c" file. * * @author Edward A. Lee * @author Marten Lohstroh @@ -26,76 +24,69 @@ * @author Anirudh Rengarajan */ public class CPreambleGenerator { - /** Add necessary source files specific to the target language. */ - public static String generateIncludeStatements( - TargetConfig targetConfig, - boolean cppMode - ) { - var tracing = targetConfig.tracing; - CodeBuilder code = new CodeBuilder(); - if (cppMode || targetConfig.platformOptions.platform == Platform.ARDUINO) { - code.pr("extern \"C\" {"); - } - 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 \"include/core/threaded/scheduler.h\""); - } + /** Add necessary source files specific to the target language. */ + public static String generateIncludeStatements(TargetConfig targetConfig, boolean cppMode) { + var tracing = targetConfig.tracing; + CodeBuilder code = new CodeBuilder(); + if (cppMode || targetConfig.platformOptions.platform == Platform.ARDUINO) { + code.pr("extern \"C\" {"); + } + 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 \"include/core/threaded/scheduler.h\""); + } - if (tracing != null) { - code.pr("#include \"include/core/trace.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 \"include/core/federated/federate.h\""); - code.pr("#include \"include/core/federated/net_common.h\""); - } - if (cppMode || targetConfig.platformOptions.platform == Platform.ARDUINO) { - code.pr("}"); - } - return code.toString(); + if (tracing != null) { + code.pr("#include \"include/core/trace.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 \"include/core/federated/federate.h\""); + code.pr("#include \"include/core/federated/net_common.h\""); + } + if (cppMode || targetConfig.platformOptions.platform == Platform.ARDUINO) { + code.pr("}"); } + return code.toString(); + } - public static String generateDefineDirectives( - TargetConfig targetConfig, - Path srcGenPath, - boolean hasModalReactors - ) { - int logLevel = targetConfig.logLevel.ordinal(); - var coordinationType = targetConfig.coordination; - var tracing = targetConfig.tracing; - CodeBuilder code = new CodeBuilder(); - // TODO: Get rid of all of these - code.pr("#define LOG_LEVEL " + logLevel); - code.pr("#define TARGET_FILES_DIRECTORY " + addDoubleQuotes(srcGenPath.toString())); + public static String generateDefineDirectives( + TargetConfig targetConfig, Path srcGenPath, boolean hasModalReactors) { + int logLevel = targetConfig.logLevel.ordinal(); + var coordinationType = targetConfig.coordination; + var tracing = targetConfig.tracing; + CodeBuilder code = new CodeBuilder(); + // TODO: Get rid of all of these + code.pr("#define LOG_LEVEL " + logLevel); + code.pr("#define TARGET_FILES_DIRECTORY " + addDoubleQuotes(srcGenPath.toString())); - if (tracing != null) { - targetConfig.compileDefinitions.put("LF_TRACE", tracing.traceFileName); - } - // if (clockSyncIsOn) { - // code.pr(generateClockSyncDefineDirective( - // targetConfig.clockSync, - // targetConfig.clockSyncOptions - // )); - // } - if (targetConfig.threading) { - targetConfig.compileDefinitions.put("LF_THREADED", "1"); - } else { - targetConfig.compileDefinitions.put("LF_UNTHREADED", "1"); - } - if (targetConfig.threading) { - targetConfig.compileDefinitions.put("LF_THREADED", "1"); - } else { - targetConfig.compileDefinitions.put("LF_UNTHREADED", "1"); - } - code.newLine(); - return code.toString(); + if (tracing != null) { + targetConfig.compileDefinitions.put("LF_TRACE", tracing.traceFileName); + } + // if (clockSyncIsOn) { + // code.pr(generateClockSyncDefineDirective( + // targetConfig.clockSync, + // targetConfig.clockSyncOptions + // )); + // } + if (targetConfig.threading) { + targetConfig.compileDefinitions.put("LF_THREADED", "1"); + } else { + targetConfig.compileDefinitions.put("LF_UNTHREADED", "1"); + } + if (targetConfig.threading) { + targetConfig.compileDefinitions.put("LF_THREADED", "1"); + } else { + targetConfig.compileDefinitions.put("LF_UNTHREADED", "1"); } + code.newLine(); + return code.toString(); + } } diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index 26e6c8ab6f..6fcef92dd6 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -9,11 +9,10 @@ import java.util.List; import java.util.Map; import java.util.Set; - -import org.lflang.ast.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.InferredType; import org.lflang.TargetConfig; +import org.lflang.ast.ASTUtils; import org.lflang.federated.extensions.CExtensionUtils; import org.lflang.generator.CodeBuilder; import org.lflang.lf.Action; @@ -37,1154 +36,1335 @@ 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 tpr 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, - TypeParameterizedReactor tpr, - int reactionIndex, - CTypes types, - ErrorReporter errorReporter, - Instantiation mainDef, - boolean requiresTypes) { - // Construct the reactionInitialization code to go into - // the body of the function before the verbatim code. - CodeBuilder reactionInitialization = new CodeBuilder(); + /** + * Generate necessary initialization code inside the body of the reaction that belongs to reactor + * decl. + * + * @param body The body of the reaction. Used to check for the + * DISABLE_REACTION_INITIALIZATION_MARKER. + * @param reaction The initialization code will be generated for this specific reaction + * @param tpr 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, + TypeParameterizedReactor tpr, + int reactionIndex, + CTypes types, + ErrorReporter errorReporter, + Instantiation mainDef, + boolean requiresTypes) { + // 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(tpr); - // 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(); - } + // Define the "self" struct. + String structType = CUtil.selfType(tpr); + // A null structType means there are no inputs, state, + // or anything else. No need to declare it. + if (structType != null) { + code.pr( + String.join( + "\n", + structType + + "* self = (" + + structType + + "*)instance_args; SUPPRESS_UNUSED_WARNING(self);")); + } - // A reaction may send to or receive from multiple ports of - // a contained reactor. The variables for these ports need to - // all be declared as fields of the same struct. Hence, we first - // collect the fields to be defined in the structs and then - // generate the structs. - Map fieldsForStructsForContainedReactors = new LinkedHashMap<>(); + // Do not generate the initialization code if the body is marked + // to not generate it. + if (body.startsWith(DISABLE_REACTION_INITIALIZATION_MARKER)) { + return code.toString(); + } - // Actions may appear twice, first as a trigger, then with the outputs. - // But we need to declare it only once. Collect in this data structure - // the actions that are declared as triggered so that if they appear - // again with the outputs, they are not defined a second time. - // That second redefinition would trigger a compile error. - Set actionsAsTriggers = new LinkedHashSet<>(); + // A reaction may send to or receive from multiple ports of + // a contained reactor. The variables for these ports need to + // all be declared as fields of the same struct. Hence, we first + // collect the fields to be defined in the structs and then + // generate the structs. + Map fieldsForStructsForContainedReactors = new LinkedHashMap<>(); - // Next, add the triggers (input and actions; timers are not needed). - // This defines a local variable in the reaction function whose - // name matches that of the trigger. The value of the local variable - // is a struct with a value and is_present field, the latter a boolean - // that indicates whether the input/action is present. - // If the trigger is an output, then it is an output of a - // contained reactor. In this case, a struct with the name - // of the contained reactor is created with one field that is - // a pointer to a struct with a value and is_present field. - // E.g., if the contained reactor is named 'c' and its output - // port is named 'out', then c.out->value c.out->is_present are - // defined so that they can be used in the verbatim code. - for (TriggerRef trigger : ASTUtils.convertToEmptyListIfNull(reaction.getTriggers())) { - if (trigger instanceof VarRef triggerAsVarRef) { - if (triggerAsVarRef.getVariable() instanceof Port) { - generatePortVariablesInReaction( - reactionInitialization, - fieldsForStructsForContainedReactors, - triggerAsVarRef, - tpr, - types); - } else if (triggerAsVarRef.getVariable() instanceof Action) { - reactionInitialization.pr(generateActionVariablesInReaction( - (Action) triggerAsVarRef.getVariable(), - tpr, - 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 : tpr.reactor().getInputs()) { - reactionInitialization.pr(generateInputVariablesInReaction(input, tpr, types)); - } - } - // Define argument for non-triggering inputs. - for (VarRef src : ASTUtils.convertToEmptyListIfNull(reaction.getSources())) { - if (src.getVariable() instanceof Port) { - generatePortVariablesInReaction(reactionInitialization, fieldsForStructsForContainedReactors, src, tpr, 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(), - tpr, - types - )); - actionsAsTriggers.add((Action) src.getVariable()); - } - } + // Actions may appear twice, first as a trigger, then with the outputs. + // But we need to declare it only once. Collect in this data structure + // the actions that are declared as triggered so that if they appear + // again with the outputs, they are not defined a second time. + // That second redefinition would trigger a compile error. + Set actionsAsTriggers = new LinkedHashSet<>(); - // Define variables for each declared output or action. - // In the case of outputs, the variable is a pointer to where the - // output is stored. This gives the reaction code access to any previous - // value that may have been written to that output in an earlier reaction. - if (reaction.getEffects() != null) { - for (VarRef effect : reaction.getEffects()) { - Variable variable = effect.getVariable(); - if (variable instanceof Action) { - // It is an action, not an output. - // If it has already appeared as trigger, do not redefine it. - if (!actionsAsTriggers.contains(effect.getVariable())) { - reactionInitialization.pr(CGenerator.variableStructType(variable, tpr, false)+"* "+variable.getName()+" = &self->_lf_"+variable.getName()+";"); - } - } else if (effect.getVariable() instanceof Mode) { - // Mode change effect - int idx = ASTUtils.allModes(tpr.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, - tpr, - 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)); - } 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+";" - )); + // 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, + tpr, + types); + } else if (triggerAsVarRef.getVariable() instanceof Action) { + reactionInitialization.pr( + generateActionVariablesInReaction( + (Action) triggerAsVarRef.getVariable(), tpr, types)); + actionsAsTriggers.add((Action) triggerAsVarRef.getVariable()); } - // 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 : tpr.reactor().getInputs()) { + reactionInitialization.pr(generateInputVariablesInReaction(input, tpr, types)); + } + } + // Define argument for non-triggering inputs. + for (VarRef src : ASTUtils.convertToEmptyListIfNull(reaction.getSources())) { + if (src.getVariable() instanceof Port) { + generatePortVariablesInReaction( + reactionInitialization, fieldsForStructsForContainedReactors, src, tpr, 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(), tpr, 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, tpr, false) + + "* " + + variable.getName() + + " = &self->_lf_" + + variable.getName() + + ";"); + } + } else if (effect.getVariable() instanceof Mode) { + // Mode change effect + int idx = ASTUtils.allModes(tpr.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, tpr, 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)); + } else { + errorReporter.reportError( + reaction, "In generateReaction(): effect is not an input, output, or watchdog."); + } } - 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, new TypeParameterizedReactor(definition), 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, - TypeParameterizedReactor tpr, - CTypes types - ) { - if (port.getVariable() instanceof Input) { - builder.pr(generateInputVariablesInReaction((Input) port.getVariable(), tpr, types)); - } else { - // port is an output of a contained reactor. - Output output = (Output) port.getVariable(); - String portStructType = CGenerator.variableStructType(output, new TypeParameterizedReactor(port.getContainer()), 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 subName = port.getContainer().getName(); - String reactorWidth = generateWidthVariable(subName); - 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++) {", - " "+subName+"[i]."+outputName+" = self->_lf_"+subName+"[i]."+outputName+";", - "}" - )); - if (ASTUtils.isMultiport(output)) { - builder.pr(String.join("\n", - "for (int i = 0; i < "+reactorWidth+"; i++) {", - " "+subName+"[i]."+outputWidth+" = self->_lf_"+subName+"[i]."+outputWidth+";", - "}" - )); - } - } else { - // Output is not in a bank. - builder.pr(subName+"."+outputName+" = self->_lf_"+subName+"."+outputName+";"); - if (ASTUtils.isMultiport(output)) { - builder.pr(subName+"."+outputWidth+" = self->_lf_"+subName+"."+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, new TypeParameterizedReactor(definition), 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 + ";")); + } + } + } + + /** + * 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, + TypeParameterizedReactor tpr, + CTypes types) { + if (port.getVariable() instanceof Input) { + builder.pr(generateInputVariablesInReaction((Input) port.getVariable(), tpr, types)); + } else { + // port is an output of a contained reactor. + Output output = (Output) port.getVariable(); + String portStructType = + CGenerator.variableStructType( + output, new TypeParameterizedReactor(port.getContainer()), false); - /** Generate action variables for a reaction. - * @param action The action. - */ - private static String generateActionVariablesInReaction( - Action action, - TypeParameterizedReactor tpr, - CTypes types - ) { - String structType = CGenerator.variableStructType(action, tpr, 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(); + CodeBuilder structBuilder = structs.get(port.getContainer()); + if (structBuilder == null) { + structBuilder = new CodeBuilder(); + structs.put(port.getContainer(), structBuilder); + } + String subName = port.getContainer().getName(); + String reactorWidth = generateWidthVariable(subName); + 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", - "// 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("}"); + String.join( + "\n", + "for (int i = 0; i < " + reactorWidth + "; i++) {", + " " + + subName + + "[i]." + + outputName + + " = self->_lf_" + + subName + + "[i]." + + outputName + + ";", + "}")); + if (ASTUtils.isMultiport(output)) { + builder.pr( + String.join( + "\n", + "for (int i = 0; i < " + reactorWidth + "; i++) {", + " " + + subName + + "[i]." + + outputWidth + + " = self->_lf_" + + subName + + "[i]." + + outputWidth + + ";", + "}")); } - return builder.toString(); + } else { + // Output is not in a bank. + builder.pr(subName + "." + outputName + " = self->_lf_" + subName + "." + outputName + ";"); + if (ASTUtils.isMultiport(output)) { + builder.pr( + subName + "." + outputWidth + " = self->_lf_" + subName + "." + outputWidth + ";"); + } + } } + } - /** 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 tpr The reactor. - */ - private static String generateInputVariablesInReaction( - Input input, - TypeParameterizedReactor tpr, - CTypes types - ) { - String structType = CGenerator.variableStructType(input, tpr, false); - InferredType inputType = ASTUtils.getInferredType(input); - CodeBuilder builder = new CodeBuilder(); - String inputName = input.getName(); - String inputWidth = generateWidthVariable(inputName); + /** + * Generate action variables for a reaction. + * + * @param action The action. + */ + private static String generateActionVariablesInReaction( + Action action, TypeParameterizedReactor tpr, CTypes types) { + String structType = CGenerator.variableStructType(action, tpr, 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(); - // 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(); + 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 outputs in a reaction function - * from the "self" struct. - * @param effect The effect declared by the reaction. This must refer to an output. - * @param tpr The reactor containing the reaction. - */ - public static String generateOutputVariablesInReaction( - VarRef effect, - TypeParameterizedReactor tpr, - 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, tpr, false) - : - CGenerator.variableStructType(output, new TypeParameterizedReactor(effect.getContainer()), 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 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 tpr The reactor. + */ + private static String generateInputVariablesInReaction( + Input input, TypeParameterizedReactor tpr, CTypes types) { + String structType = CGenerator.variableStructType(input, tpr, 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 watchdogs - * in a reaction function from the "self" struct. - * - * @param effect The effect declared by the reaction. This must refer to a watchdog. - */ - public static String generateWatchdogVariablesInReaction(VarRef effect) { - Watchdog watchdog = (Watchdog) effect.getVariable(); - String watchdogName = watchdog.getName(); + /** + * 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 tpr The reactor containing the reaction. + */ + public static String generateOutputVariablesInReaction( + VarRef effect, + TypeParameterizedReactor tpr, + 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, tpr, false) + : CGenerator.variableStructType( + output, new TypeParameterizedReactor(effect.getContainer()), 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", - List.of("watchdog_t* " + watchdogName + " = &(self->_lf_watchdog_" + watchdogName + ");") - ); + "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 tpr {@link TypeParameterizedReactor} - * @param constructorCode The place to put the constructor code. - */ - public static void generateReactionAndTriggerStructs( - CodeBuilder body, - TypeParameterizedReactor tpr, - 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(tpr.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 watchdogs + * in a reaction function from the "self" struct. + * + * @param effect The effect declared by the reaction. This must refer to a watchdog. + */ + public static String generateWatchdogVariablesInReaction(VarRef effect) { + Watchdog watchdog = (Watchdog) effect.getVariable(); + String watchdogName = watchdog.getName(); + 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 triggerAsVarRef) { - 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 tpr {@link TypeParameterizedReactor} + * @param constructorCode The place to put the constructor code. + */ + public static void generateReactionAndTriggerStructs( + CodeBuilder body, TypeParameterizedReactor tpr, 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(tpr.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(tpr, 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 triggerAsVarRef) { + 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()); + } + } - // Assign the STP handler - var STPFunctionPointer = "NULL"; - if (reaction.getStp() != null) { - // The following has to match the name chosen in generateReactions - var STPFunctionName = generateStpFunctionName(tpr, reactionCount); - STPFunctionPointer = "&" + STPFunctionName; - } + var deadlineFunctionPointer = "NULL"; + if (reaction.getDeadline() != null) { + // The following has to match the name chosen in generateReactions + var deadlineFunctionName = generateDeadlineFunctionName(tpr, reactionCount); + deadlineFunctionPointer = "&" + deadlineFunctionName; + } - // 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(tpr, 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["+tpr.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++; - } + // Assign the STP handler + var STPFunctionPointer = "NULL"; + if (reaction.getStp() != null) { + // The following has to match the name chosen in generateReactions + var STPFunctionName = generateStpFunctionName(tpr, reactionCount); + STPFunctionPointer = "&" + STPFunctionName; + } - // Next, create and initialize the trigger_t objects. - // Start with the timers. - for (Timer timer : ASTUtils.allTimers(tpr.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};")); - } + // 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(tpr, 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[" + + tpr.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++; + } - // 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, create and initialize the trigger_t objects. + // Start with the timers. + for (Timer timer : ASTUtils.allTimers(tpr.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 actions. - for (Action action : ASTUtils.allActions(tpr.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 + ")"; - } + // 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); + } - // 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 actions. + for (Action action : ASTUtils.allActions(tpr.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 + ")"; + } - // Next handle inputs. - for (Input input : ASTUtils.allInputs(tpr.reactor())) { - createTriggerT(body, input, triggerMap, constructorCode, types); - } + // 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 watchdogs. - for (Watchdog watchdog : ASTUtils.allWatchdogs(tpr.reactor())) { - createTriggerT(body, watchdog, triggerMap, constructorCode, types); - } + // Next handle inputs. + for (Input input : ASTUtils.allInputs(tpr.reactor())) { + createTriggerT(body, input, triggerMap, constructorCode, types); } - /** - * 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 watchdogs. + for (Watchdog watchdog : ASTUtils.allWatchdogs(tpr.reactor())) { + createTriggerT(body, watchdog, triggerMap, constructorCode, types); + } + } - // 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+";" - )); + /** + * 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};")); - // 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+")"; + // 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 + ";")); - constructorCode.pr("self->_lf__"+varName+".tmplt.type.element_size = "+size+";"); - body.pr( - CExtensionUtils.surroundWithIfFederated( - CExtensionUtils.createPortStatusFieldForInput((Input) variable) - ) - ); - } + // 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 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;" - )); + 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 + + ";"); } + 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_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 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_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 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 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_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 tpr The reactor. - * @param reactionIndex The position of the reaction within the reactor. - */ - public static String generateReaction( - Reaction reaction, - TypeParameterizedReactor tpr, - int reactionIndex, - Instantiation mainDef, - ErrorReporter errorReporter, - CTypes types, - TargetConfig targetConfig, - boolean requiresType - ) { - var code = new CodeBuilder(); - var body = ASTUtils.toText(getCode(types, reaction, tpr)); - String init = generateInitializationForReaction( - body, reaction, tpr, 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 tpr The reactor. + * @param reactionIndex The position of the reaction within the reactor. + */ + public static String generateReaction( + Reaction reaction, + TypeParameterizedReactor tpr, + int reactionIndex, + Instantiation mainDef, + ErrorReporter errorReporter, + CTypes types, + TargetConfig targetConfig, + boolean requiresType) { + var code = new CodeBuilder(); + var body = ASTUtils.toText(getCode(types, reaction, tpr)); + String init = + generateInitializationForReaction( + body, reaction, tpr, reactionIndex, types, errorReporter, mainDef, requiresType); - code.pr( - "#include " + StringUtil.addDoubleQuotes( - CCoreFilesUtils.getCTargetSetHeader())); + code.pr("#include " + StringUtil.addDoubleQuotes(CCoreFilesUtils.getCTargetSetHeader())); - CMethodGenerator.generateMacrosForMethods(tpr, code); - code.pr(generateFunction( + CMethodGenerator.generateMacrosForMethods(tpr, code); + code.pr( + generateFunction( generateReactionFunctionHeader(tpr, reactionIndex), - init, getCode(types, reaction, tpr) - )); - // 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(tpr, 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(tpr, reactionIndex), - init, reaction.getDeadline().getCode())); - } - CMethodGenerator.generateMacroUndefsForMethods(tpr.reactor(), code); - code.pr( - "#include " + StringUtil.addDoubleQuotes( - CCoreFilesUtils.getCTargetSetUndefHeader())); - return code.toString(); + init, + getCode(types, reaction, tpr))); + // 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(tpr, reactionIndex), init, reaction.getStp().getCode())); } - private static Code getCode(CTypes types, Reaction r, TypeParameterizedReactor tpr) { - if (r.getCode() != null) return r.getCode(); - Code ret = LfFactory.eINSTANCE.createCode(); - ret.setBody( - CReactorHeaderFileGenerator.nonInlineInitialization(r, tpr) + "\n" - + r.getName() + "( " + CReactorHeaderFileGenerator.reactionArguments(r, tpr) + " );"); - return ret; + // Now generate code for the deadline violation function, if there is one. + if (reaction.getDeadline() != null) { + code.pr( + generateFunction( + generateDeadlineFunctionHeader(tpr, reactionIndex), + init, + reaction.getDeadline().getCode())); } + CMethodGenerator.generateMacroUndefsForMethods(tpr.reactor(), 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, TypeParameterizedReactor tpr) { + if (r.getCode() != null) return r.getCode(); + Code ret = LfFactory.eINSTANCE.createCode(); + ret.setBody( + CReactorHeaderFileGenerator.nonInlineInitialization(r, tpr) + + "\n" + + r.getName() + + "( " + + CReactorHeaderFileGenerator.reactionArguments(r, tpr) + + " );"); + return ret; + } - /** - * Returns the name of the deadline function for reaction. - * @param tpr The reactor with the deadline - * @param reactionIndex The number assigned to this reaction deadline - */ - public static String generateDeadlineFunctionName(TypeParameterizedReactor tpr, int reactionIndex) { - return CUtil.getName(tpr).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 tpr The reactor - * @param reactionIndex The reaction index. - * @return The function name for the reaction. - */ - public static String generateReactionFunctionName(TypeParameterizedReactor tpr, int reactionIndex) { - return CUtil.getName(tpr).toLowerCase() + "reaction_function_" + reactionIndex; - } + /** + * Returns the name of the deadline function for reaction. + * + * @param tpr The reactor with the deadline + * @param reactionIndex The number assigned to this reaction deadline + */ + public static String generateDeadlineFunctionName( + TypeParameterizedReactor tpr, int reactionIndex) { + return CUtil.getName(tpr).toLowerCase() + "_deadline_function" + reactionIndex; + } - /** - * Returns the name of the stp function for reaction. - * @param tpr The reactor with the stp - * @param reactionIndex The number assigned to this reaction deadline - */ - public static String generateStpFunctionName(TypeParameterizedReactor tpr, int reactionIndex) { - return CUtil.getName(tpr).toLowerCase() + "_STP_function" + reactionIndex; - } + /** + * Return the function name for specified reaction of the specified reactor. + * + * @param tpr The reactor + * @param reactionIndex The reaction index. + * @return The function name for the reaction. + */ + public static String generateReactionFunctionName( + TypeParameterizedReactor tpr, int reactionIndex) { + return CUtil.getName(tpr).toLowerCase() + "reaction_function_" + reactionIndex; + } - /** Return the top level C function header for the deadline function numbered "reactionIndex" in "r" - * @param tpr The reactor declaration - * @param reactionIndex The reaction index. - * @return The function name for the deadline function. - */ - public static String generateDeadlineFunctionHeader(TypeParameterizedReactor tpr, - int reactionIndex) { - String functionName = generateDeadlineFunctionName(tpr, reactionIndex); - return generateFunctionHeader(functionName); - } + /** + * Returns the name of the stp function for reaction. + * + * @param tpr The reactor with the stp + * @param reactionIndex The number assigned to this reaction deadline + */ + public static String generateStpFunctionName(TypeParameterizedReactor tpr, int reactionIndex) { + return CUtil.getName(tpr).toLowerCase() + "_STP_function" + reactionIndex; + } - /** Return the top level C function header for the reaction numbered "reactionIndex" in "r" - * @param tpr The reactor declaration - * @param reactionIndex The reaction index. - * @return The function name for the reaction. - */ - public static String generateReactionFunctionHeader(TypeParameterizedReactor tpr, - int reactionIndex) { - String functionName = generateReactionFunctionName(tpr, reactionIndex); - return generateFunctionHeader(functionName); - } + /** + * Return the top level C function header for the deadline function numbered "reactionIndex" in + * "r" + * + * @param tpr The reactor declaration + * @param reactionIndex The reaction index. + * @return The function name for the deadline function. + */ + public static String generateDeadlineFunctionHeader( + TypeParameterizedReactor tpr, int reactionIndex) { + String functionName = generateDeadlineFunctionName(tpr, reactionIndex); + return generateFunctionHeader(functionName); + } - public static String generateStpFunctionHeader(TypeParameterizedReactor tpr, - int reactionIndex) { - String functionName = generateStpFunctionName(tpr, reactionIndex); - return generateFunctionHeader(functionName); - } + /** + * Return the top level C function header for the reaction numbered "reactionIndex" in "r" + * + * @param tpr The reactor declaration + * @param reactionIndex The reaction index. + * @return The function name for the reaction. + */ + public static String generateReactionFunctionHeader( + TypeParameterizedReactor tpr, int reactionIndex) { + String functionName = generateReactionFunctionName(tpr, reactionIndex); + return generateFunctionHeader(functionName); + } - /** - * Return the start of a function declaration for a function that takes - * a {@code void*} argument and returns void. - * - * @param functionName - * @return - */ - public static String generateFunctionHeader(String functionName) { - return "void " + functionName + "(void* instance_args)"; - } + public static String generateStpFunctionHeader(TypeParameterizedReactor tpr, int reactionIndex) { + String functionName = generateStpFunctionName(tpr, reactionIndex); + return generateFunctionHeader(functionName); + } + + /** + * Return the start of a function declaration for a function that takes a {@code 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/CReactorHeaderFileGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactorHeaderFileGenerator.java index 993168cad9..4f6a29b405 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactorHeaderFileGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactorHeaderFileGenerator.java @@ -6,7 +6,6 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; - import org.lflang.ast.ASTUtils; import org.lflang.generator.CodeBuilder; import org.lflang.lf.Instantiation; @@ -23,44 +22,61 @@ /** 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, TypeParameterizedReactor r, boolean userFacing); - } - - /** Return the path to the user-visible header file that would be generated for {@code r}. */ - public static Path outputPath(TypeParameterizedReactor tpr) { - return Path.of(Path.of(tpr.reactor().eResource().getURI().toFileString()) - .getFileName().toString().replaceFirst("[.][^.]+$", "")) - .resolve(tpr.getName() + ".h"); - } - - public static void doGenerate(CTypes types, TypeParameterizedReactor tpr, CFileConfig fileConfig, GenerateAuxiliaryStructs generator, Function topLevelPreamble) throws IOException { - String contents = generateHeaderFile(types, tpr, generator, topLevelPreamble.apply(tpr.reactor())); - FileUtil.writeToFile(contents, fileConfig.getIncludePath().resolve(outputPath(tpr))); - } - private static String generateHeaderFile(CTypes types, TypeParameterizedReactor tpr, GenerateAuxiliaryStructs generator, String topLevelPreamble) { - CodeBuilder builder = new CodeBuilder(); - appendIncludeGuard(builder, tpr); - builder.pr(topLevelPreamble); - appendPoundIncludes(builder); - tpr.doDefines(builder); - appendSelfStruct(builder, types, tpr); - generator.generate(builder, tpr, true); - for (Reaction reaction : tpr.reactor().getReactions()) { - appendSignature(builder, types, reaction, tpr); - } - builder.pr("#endif"); - return builder.getCode(); - } - - private static void appendIncludeGuard(CodeBuilder builder, TypeParameterizedReactor r) { - String macro = CUtil.getName(r) + "_H"; - builder.pr("#ifndef " + macro); - builder.pr("#define " + macro); - } - private static void appendPoundIncludes(CodeBuilder builder) { - builder.pr(""" + /** Functional interface for generating auxiliary structs such as port structs. */ + public interface GenerateAuxiliaryStructs { + void generate(CodeBuilder b, TypeParameterizedReactor r, boolean userFacing); + } + + /** Return the path to the user-visible header file that would be generated for {@code r}. */ + public static Path outputPath(TypeParameterizedReactor tpr) { + return Path.of( + Path.of(tpr.reactor().eResource().getURI().toFileString()) + .getFileName() + .toString() + .replaceFirst("[.][^.]+$", "")) + .resolve(tpr.getName() + ".h"); + } + + public static void doGenerate( + CTypes types, + TypeParameterizedReactor tpr, + CFileConfig fileConfig, + GenerateAuxiliaryStructs generator, + Function topLevelPreamble) + throws IOException { + String contents = + generateHeaderFile(types, tpr, generator, topLevelPreamble.apply(tpr.reactor())); + FileUtil.writeToFile(contents, fileConfig.getIncludePath().resolve(outputPath(tpr))); + } + + private static String generateHeaderFile( + CTypes types, + TypeParameterizedReactor tpr, + GenerateAuxiliaryStructs generator, + String topLevelPreamble) { + CodeBuilder builder = new CodeBuilder(); + appendIncludeGuard(builder, tpr); + builder.pr(topLevelPreamble); + appendPoundIncludes(builder); + tpr.doDefines(builder); + appendSelfStruct(builder, types, tpr); + generator.generate(builder, tpr, true); + for (Reaction reaction : tpr.reactor().getReactions()) { + appendSignature(builder, types, reaction, tpr); + } + builder.pr("#endif"); + return builder.getCode(); + } + + private static void appendIncludeGuard(CodeBuilder builder, TypeParameterizedReactor 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 @@ -71,135 +87,167 @@ private static void appendPoundIncludes(CodeBuilder builder) { } #endif """); - } - - private static String userFacingSelfType(TypeParameterizedReactor tpr) { - return tpr.getName().toLowerCase() + "_self_t"; - } - - private static void appendSelfStruct(CodeBuilder builder, CTypes types, TypeParameterizedReactor tpr) { - builder.pr("typedef struct " + userFacingSelfType(tpr) + "{"); - for (Parameter p : tpr.reactor().getParameters()) { - builder.pr(types.getTargetType(p) + " " + p.getName() + ";"); - } - for (StateVar s : tpr.reactor().getStateVars()) { - builder.pr(types.getTargetType(s) + " " + s.getName() + ";"); - } - builder.pr("int end[0]; // placeholder; MSVC does not compile empty structs"); - builder.pr("} " + userFacingSelfType(tpr) + ";"); - } - - private static void appendSignature(CodeBuilder builder, CTypes types, Reaction r, TypeParameterizedReactor tpr) { - if (r.getName() != null) builder.pr("void " + r.getName() + "(" + reactionParameters(r, tpr) + ");"); - } - - private static String reactionParameters(Reaction r, TypeParameterizedReactor tpr) { - return Stream.concat(Stream.of(userFacingSelfType(tpr) + "* self"), portVariableStream(r, tpr) - .map(tv -> tv.getType(true) + tv.getName())) - .collect(Collectors.joining(", ")); - } - - private static String getApiSelfStruct(TypeParameterizedReactor tpr) { - return "(" + userFacingSelfType(tpr) + "*) (((char*) self) + sizeof(self_base_t))"; - } - - /** Generate initialization code that is needed if {@code r} is not inlined. */ - public static String nonInlineInitialization(Reaction r, TypeParameterizedReactor reactor) { - var mainDef = LfFactory.eINSTANCE.createInstantiation(); - mainDef.setName(reactor.getName()); - mainDef.setReactorClass(ASTUtils.findMainReactor(reactor.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(""" + } + + private static String userFacingSelfType(TypeParameterizedReactor tpr) { + return tpr.getName().toLowerCase() + "_self_t"; + } + + private static void appendSelfStruct( + CodeBuilder builder, CTypes types, TypeParameterizedReactor tpr) { + builder.pr("typedef struct " + userFacingSelfType(tpr) + "{"); + for (Parameter p : tpr.reactor().getParameters()) { + builder.pr(types.getTargetType(p) + " " + p.getName() + ";"); + } + for (StateVar s : tpr.reactor().getStateVars()) { + builder.pr(types.getTargetType(s) + " " + s.getName() + ";"); + } + builder.pr("int end[0]; // placeholder; MSVC does not compile empty structs"); + builder.pr("} " + userFacingSelfType(tpr) + ";"); + } + + private static void appendSignature( + CodeBuilder builder, CTypes types, Reaction r, TypeParameterizedReactor tpr) { + if (r.getName() != null) + builder.pr("void " + r.getName() + "(" + reactionParameters(r, tpr) + ");"); + } + + private static String reactionParameters(Reaction r, TypeParameterizedReactor tpr) { + return Stream.concat( + Stream.of(userFacingSelfType(tpr) + "* self"), + portVariableStream(r, tpr).map(tv -> tv.getType(true) + tv.getName())) + .collect(Collectors.joining(", ")); + } + + private static String getApiSelfStruct(TypeParameterizedReactor tpr) { + return "(" + userFacingSelfType(tpr) + "*) (((char*) self) + sizeof(self_base_t))"; + } + + /** Generate initialization code that is needed if {@code r} is not inlined. */ + public static String nonInlineInitialization(Reaction r, TypeParameterizedReactor reactor) { + var mainDef = LfFactory.eINSTANCE.createInstantiation(); + mainDef.setName(reactor.getName()); + mainDef.setReactorClass(ASTUtils.findMainReactor(reactor.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.reactor().getInstantiations().stream() - .filter(instantiation -> new TypeParameterizedReactor(instantiation).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, TypeParameterizedReactor reactor) { - return Stream.concat(Stream.of(getApiSelfStruct(reactor)), portVariableStream(r, reactor) + it.getType(true).replaceFirst("\\*", ""), + it.getAlias(), + CReactionGenerator.maxContainedReactorBankWidth( + reactor.reactor().getInstantiations().stream() + .filter( + instantiation -> + new TypeParameterizedReactor(instantiation) + .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, TypeParameterizedReactor 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 a stream of all ports referenced by the signature of {@code r}. */ - private static Stream portVariableStream(Reaction r, TypeParameterizedReactor reactorOfReaction) { - return varRefStream(r) - .map(it -> it.getVariable() instanceof TypedVariable tv ? - new PortVariable( - tv, - it.getContainer() != null ? new TypeParameterizedReactor(it.getContainer()) : reactorOfReaction, - 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 in which the port is being used. - * @param container The {@code Instantiation} referenced in the obtaining of {@code tv}, if - * applicable; {@code null} otherwise. - */ - private record PortVariable(TypedVariable tv, TypeParameterizedReactor 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.reactor()).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)); - } + .collect(Collectors.joining(", ")); + } + + /** Return a stream of all ports referenced by the signature of {@code r}. */ + private static Stream portVariableStream( + Reaction r, TypeParameterizedReactor reactorOfReaction) { + return varRefStream(r) + .map( + it -> + it.getVariable() instanceof TypedVariable tv + ? new PortVariable( + tv, + it.getContainer() != null + ? new TypeParameterizedReactor(it.getContainer()) + : reactorOfReaction, + 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 in which the port is being used. + * @param container The {@code Instantiation} referenced in the obtaining of {@code tv}, if + * applicable; {@code null} otherwise. + */ + private record PortVariable( + TypedVariable tv, TypeParameterizedReactor 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.reactor()).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/CStateGenerator.java b/org.lflang/src/org/lflang/generator/c/CStateGenerator.java index f43d975632..69e41d1008 100644 --- a/org.lflang/src/org/lflang/generator/c/CStateGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CStateGenerator.java @@ -7,121 +7,116 @@ import org.lflang.lf.StateVar; public class CStateGenerator { - /** - * Generate code for state variables of a reactor in the form "stateVar.type stateVar.name;" - * @param reactor {@link TypeParameterizedReactor} - * @param types A helper object for types - */ - public static String generateDeclarations(TypeParameterizedReactor reactor, CTypes types) { - CodeBuilder code = new CodeBuilder(); - for (StateVar stateVar : ASTUtils.allStateVars(reactor.reactor())) { - code.prSourceLineNumber(stateVar); - code.pr(types.getTargetType(reactor.resolveType(ASTUtils.getInferredType(stateVar))) + " " + stateVar.getName() + ";"); - } - return code.toString(); + /** + * Generate code for state variables of a reactor in the form "stateVar.type stateVar.name;" + * + * @param reactor {@link TypeParameterizedReactor} + * @param types A helper object for types + */ + public static String generateDeclarations(TypeParameterizedReactor reactor, CTypes types) { + CodeBuilder code = new CodeBuilder(); + for (StateVar stateVar : ASTUtils.allStateVars(reactor.reactor())) { + code.prSourceLineNumber(stateVar); + code.pr( + types.getTargetType(reactor.resolveType(ASTUtils.getInferredType(stateVar))) + + " " + + stateVar.getName() + + ";"); } + return code.toString(); + } - /** - * If the state is initialized with a parameter, then do not use - * a temporary variable. Otherwise, do, because - * static initializers for arrays and structs have to be handled - * this way, and there is no way to tell whether the type of the array - * is a struct. - * - * @param instance {@link ReactorInstance} - * @param stateVar {@link StateVar} - * @param mode {@link ModeInstance} - * @return String - */ - public static String generateInitializer( - ReactorInstance instance, - String selfRef, - StateVar stateVar, - ModeInstance mode, - CTypes types - ) { - var initExpr = getInitializerExpr(stateVar, instance); - String baseInitializer = generateBaseInitializer(instance.tpr, selfRef, stateVar, initExpr, types); - String modalReset = generateModalReset(instance, selfRef, stateVar, initExpr, mode, types); - return String.join("\n", - baseInitializer, - modalReset - ); - } + /** + * If the state is initialized with a parameter, then do not use a temporary variable. Otherwise, + * do, because static initializers for arrays and structs have to be handled this way, and there + * is no way to tell whether the type of the array is a struct. + * + * @param instance {@link ReactorInstance} + * @param stateVar {@link StateVar} + * @param mode {@link ModeInstance} + * @return String + */ + public static String generateInitializer( + ReactorInstance instance, + String selfRef, + StateVar stateVar, + ModeInstance mode, + CTypes types) { + var initExpr = getInitializerExpr(stateVar, instance); + String baseInitializer = + generateBaseInitializer(instance.tpr, selfRef, stateVar, initExpr, types); + String modalReset = generateModalReset(instance, selfRef, stateVar, initExpr, mode, types); + return String.join("\n", baseInitializer, modalReset); + } - private static String generateBaseInitializer( - TypeParameterizedReactor tpr, - String selfRef, - StateVar stateVar, - String initExpr, - CTypes types - ) { - if (ASTUtils.isOfTimeType(stateVar) || - ASTUtils.isParameterized(stateVar) && - !stateVar.getInit().getExprs().isEmpty() - ) { - return selfRef + "->" + stateVar.getName() + " = " + initExpr + ";"; - } else { - var declaration = types.getVariableDeclaration(tpr, - ASTUtils.getInferredType(stateVar), - "_initial", true); - return String.join("\n", - "{ // For scoping", - " static "+declaration+" = "+initExpr+";", - " "+selfRef+"->"+stateVar.getName()+" = _initial;", - "} // End scoping." - ); - } + private static String generateBaseInitializer( + TypeParameterizedReactor tpr, + String selfRef, + StateVar stateVar, + String initExpr, + CTypes types) { + if (ASTUtils.isOfTimeType(stateVar) + || ASTUtils.isParameterized(stateVar) && !stateVar.getInit().getExprs().isEmpty()) { + return selfRef + "->" + stateVar.getName() + " = " + initExpr + ";"; + } else { + var declaration = + types.getVariableDeclaration(tpr, ASTUtils.getInferredType(stateVar), "_initial", true); + return String.join( + "\n", + "{ // For scoping", + " static " + declaration + " = " + initExpr + ";", + " " + selfRef + "->" + stateVar.getName() + " = _initial;", + "} // End scoping."); } + } - private static String generateModalReset( - ReactorInstance instance, - String selfRef, - StateVar stateVar, - String initExpr, - ModeInstance mode, - CTypes types - ) { - if (mode == null || !stateVar.isReset()) { - return ""; - } - var modeRef = "&"+CUtil.reactorRef(mode.getParent())+"->_lf__modes["+mode.getParent().modes.indexOf(mode)+"]"; - var type = types.getTargetType(instance.tpr.resolveType(ASTUtils.getInferredType(stateVar))); - - if (ASTUtils.isOfTimeType(stateVar) || - ASTUtils.isParameterized(stateVar) && - !stateVar.getInit().getExprs().isEmpty()) { - return CModesGenerator.generateStateResetStructure( - modeRef, selfRef, - stateVar.getName(), - initExpr, type); - } else { - CodeBuilder code = new CodeBuilder(); - var source = "_initial"; - var declaration = types.getVariableDeclaration(instance.tpr, - ASTUtils.getInferredType(stateVar), - source, true); - code.pr("{ // For scoping"); - code.indent(); - code.pr("static "+declaration+" = "+initExpr+";"); - code.pr(CModesGenerator.generateStateResetStructure( - modeRef, selfRef, - stateVar.getName(), - source, type)); - code.unindent(); - code.pr("} // End scoping."); - return code.toString(); - } + private static String generateModalReset( + ReactorInstance instance, + String selfRef, + StateVar stateVar, + String initExpr, + ModeInstance mode, + CTypes types) { + if (mode == null || !stateVar.isReset()) { + return ""; } + var modeRef = + "&" + + CUtil.reactorRef(mode.getParent()) + + "->_lf__modes[" + + mode.getParent().modes.indexOf(mode) + + "]"; + var type = types.getTargetType(instance.tpr.resolveType(ASTUtils.getInferredType(stateVar))); - /** - * Return a C expression that can be used to initialize the specified - * state variable within the specified parent. If the state variable - * initializer refers to parameters of the parent, then those parameter - * references are replaced with accesses to the self struct of the parent. - */ - private static String getInitializerExpr(StateVar state, ReactorInstance parent) { - var ctypes = CTypes.generateParametersIn(parent); - return ctypes.getTargetInitializer(state.getInit(), state.getType()); + if (ASTUtils.isOfTimeType(stateVar) + || ASTUtils.isParameterized(stateVar) && !stateVar.getInit().getExprs().isEmpty()) { + return CModesGenerator.generateStateResetStructure( + modeRef, selfRef, stateVar.getName(), initExpr, type); + } else { + CodeBuilder code = new CodeBuilder(); + var source = "_initial"; + var declaration = + types.getVariableDeclaration( + instance.tpr, ASTUtils.getInferredType(stateVar), source, true); + code.pr("{ // For scoping"); + code.indent(); + code.pr("static " + declaration + " = " + initExpr + ";"); + code.pr( + CModesGenerator.generateStateResetStructure( + modeRef, selfRef, stateVar.getName(), source, type)); + code.unindent(); + code.pr("} // End scoping."); + return code.toString(); } + } + + /** + * Return a C expression that can be used to initialize the specified state variable within the + * specified parent. If the state variable initializer refers to parameters of the parent, then + * those parameter references are replaced with accesses to the self struct of the parent. + */ + private static String getInitializerExpr(StateVar state, ReactorInstance parent) { + 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 57e7556dfe..f0c55389f6 100644 --- a/org.lflang/src/org/lflang/generator/c/CTimerGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CTimerGenerator.java @@ -1,7 +1,6 @@ package org.lflang.generator.c; import java.util.List; - import org.lflang.generator.TimerInstance; /** @@ -11,61 +10,71 @@ * @author {Soroush Bateni */ public class CTimerGenerator { - /** - * Generate code to initialize the given timer. - * - * @param timer The timer to initialize for. - */ - public static String generateInitializer(TimerInstance timer) { - var triggerStructName = CUtil.reactorRef(timer.getParent()) + "->_lf__" + timer.getName(); - 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)+"];" : - "NULL"; + /** + * Generate code to initialize the given timer. + * + * @param timer The timer to initialize for. + */ + public static String generateInitializer(TimerInstance timer) { + var triggerStructName = CUtil.reactorRef(timer.getParent()) + "->_lf__" + timer.getName(); + 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) + + "];" + : "NULL"; - return String.join("\n", List.of( - "// Initializing timer "+timer.getFullName()+".", - triggerStructName+".offset = "+offset+";", - triggerStructName+".period = "+period+";", - "_lf_timer_triggers[_lf_timer_triggers_count++] = &"+triggerStructName+";", - triggerStructName+".mode = "+modeRef+";" - )); - } + return String.join( + "\n", + List.of( + "// Initializing timer " + timer.getFullName() + ".", + triggerStructName + ".offset = " + offset + ";", + triggerStructName + ".period = " + period + ";", + "_lf_timer_triggers[_lf_timer_triggers_count++] = &" + triggerStructName + ";", + triggerStructName + ".mode = " + modeRef + ";")); + } - /** - * Generate code to declare the timer table. - * - * @param timerCount The total number of timers in the program - */ - public static String generateDeclarations(int timerCount) { - return String.join("\n", List.of( - "// Array of pointers to timer triggers to be scheduled in _lf_initialize_timers().", - (timerCount > 0 ? - "trigger_t* _lf_timer_triggers["+timerCount+"]" : - "trigger_t** _lf_timer_triggers = NULL") + ";", - "int _lf_timer_triggers_size = "+timerCount+";" - )); - } + /** + * Generate code to declare the timer table. + * + * @param timerCount The total number of timers in the program + */ + public static String generateDeclarations(int timerCount) { + return String.join( + "\n", + List.of( + "// Array of pointers to timer triggers to be scheduled in _lf_initialize_timers().", + (timerCount > 0 + ? "trigger_t* _lf_timer_triggers[" + timerCount + "]" + : "trigger_t** _lf_timer_triggers = NULL") + + ";", + "int _lf_timer_triggers_size = " + timerCount + ";")); + } - /** - * Generate code to call {@code _lf_initialize_timer} on each timer. - * - * @param timerCount The total number of timers in the program - */ - public static String generateLfInitializeTimer(int timerCount) { - return String.join("\n", - "void _lf_initialize_timers() {", - timerCount > 0 ? - """ + /** + * Generate code to call {@code _lf_initialize_timer} on each timer. + * + * @param timerCount The total number of timers in the program + */ + public static String generateLfInitializeTimer(int timerCount) { + return String.join( + "\n", + "void _lf_initialize_timers() {", + timerCount > 0 + ? """ for (int i = 0; i < _lf_timer_triggers_size; i++) { if (_lf_timer_triggers[i] != NULL) { _lf_initialize_timer(_lf_timer_triggers[i]); } - }""".indent(4).stripTrailing() : - "", - "}" - ); - } + }""" + .indent(4) + .stripTrailing() + : "", + "}"); + } } diff --git a/org.lflang/src/org/lflang/generator/c/CTracingGenerator.java b/org.lflang/src/org/lflang/generator/c/CTracingGenerator.java index f999d14b3e..8439d562b0 100644 --- a/org.lflang/src/org/lflang/generator/c/CTracingGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CTracingGenerator.java @@ -1,13 +1,12 @@ package org.lflang.generator.c; +import static org.lflang.util.StringUtil.addDoubleQuotes; + import java.util.ArrayList; import java.util.List; - -import org.lflang.federated.generator.FederateInstance; import org.lflang.generator.ActionInstance; import org.lflang.generator.ReactorInstance; import org.lflang.generator.TimerInstance; -import static org.lflang.util.StringUtil.addDoubleQuotes; /** * Generates C code to support tracing. @@ -17,47 +16,54 @@ * @author Hou Seng Wong */ public class CTracingGenerator { - /** - * 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. - * - * If tracing is turned on, record the address of this reaction - * in the _lf_trace_object_descriptions table that is used to generate - * the header information in the trace file. - * - * @param instance The reactor instance. - */ - public static String generateTraceTableEntries( - ReactorInstance instance - ) { - List code = new ArrayList<>(); - var description = CUtil.getShortenedName(instance); - var selfStruct = CUtil.reactorRef(instance); - code.add(registerTraceEvent( - selfStruct, "NULL", - "trace_reactor", description) - ); - for (ActionInstance action : instance.actions) { - code.add(registerTraceEvent( - selfStruct, getTrigger(selfStruct, action.getName()), - "trace_trigger", description + "." + action.getName()) - ); - } - for (TimerInstance timer : instance.timers) { - code.add(registerTraceEvent( - selfStruct, getTrigger(selfStruct, timer.getName()), - "trace_trigger", description + "." + timer.getName()) - ); - } - return String.join("\n", code); + /** + * 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. + * + *

If tracing is turned on, record the address of this reaction in the + * _lf_trace_object_descriptions table that is used to generate the header information in the + * trace file. + * + * @param instance The reactor instance. + */ + public static String generateTraceTableEntries(ReactorInstance instance) { + List code = new ArrayList<>(); + var description = CUtil.getShortenedName(instance); + var selfStruct = CUtil.reactorRef(instance); + code.add(registerTraceEvent(selfStruct, "NULL", "trace_reactor", description)); + for (ActionInstance action : instance.actions) { + code.add( + registerTraceEvent( + selfStruct, + getTrigger(selfStruct, action.getName()), + "trace_trigger", + description + "." + action.getName())); } - - private static String registerTraceEvent(String obj, String trigger, String type, String description) { - return "_lf_register_trace_event("+obj+", "+trigger+", "+type+", "+addDoubleQuotes(description)+");"; + for (TimerInstance timer : instance.timers) { + code.add( + registerTraceEvent( + selfStruct, + getTrigger(selfStruct, timer.getName()), + "trace_trigger", + description + "." + timer.getName())); } + return String.join("\n", code); + } - private static String getTrigger(String obj, String triggerName) { - return "&("+obj+"->_lf__"+triggerName+")"; - } + private static String registerTraceEvent( + String obj, String trigger, String type, String description) { + return "_lf_register_trace_event(" + + obj + + ", " + + trigger + + ", " + + type + + ", " + + addDoubleQuotes(description) + + ");"; + } + + private static String getTrigger(String obj, String triggerName) { + return "&(" + obj + "->_lf__" + triggerName + ")"; + } } diff --git a/org.lflang/src/org/lflang/generator/c/CTriggerObjectsGenerator.java b/org.lflang/src/org/lflang/generator/c/CTriggerObjectsGenerator.java index 99df04e9d3..5f4d8d60ef 100644 --- a/org.lflang/src/org/lflang/generator/c/CTriggerObjectsGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CTriggerObjectsGenerator.java @@ -1,4 +1,5 @@ package org.lflang.generator.c; + import static org.lflang.generator.c.CMixedRadixGenerator.db; import static org.lflang.generator.c.CMixedRadixGenerator.dc; import static org.lflang.generator.c.CMixedRadixGenerator.dr; @@ -8,15 +9,14 @@ import static org.lflang.util.StringUtil.addDoubleQuotes; import static org.lflang.util.StringUtil.joinObjects; +import com.google.common.collect.Iterables; import java.util.Arrays; import java.util.HashSet; import java.util.stream.Collectors; - -import org.lflang.ast.ASTUtils; import org.lflang.AttributeUtils; import org.lflang.TargetConfig; - import org.lflang.TargetProperty.LogLevel; +import org.lflang.ast.ASTUtils; import org.lflang.federated.extensions.CExtensionUtils; import org.lflang.generator.CodeBuilder; import org.lflang.generator.PortInstance; @@ -25,8 +25,6 @@ import org.lflang.generator.RuntimeRange; import org.lflang.generator.SendRange; -import com.google.common.collect.Iterables; - /** * Generate code for the "_lf_initialize_trigger_objects" function * @@ -35,1027 +33,1088 @@ * @author Hou Seng Wong */ public class CTriggerObjectsGenerator { - /** - * Generate the _lf_initialize_trigger_objects function for 'federate'. - */ - public static String generateInitializeTriggerObjects( - ReactorInstance main, - TargetConfig targetConfig, - CodeBuilder initializeTriggerObjects, - CodeBuilder startTimeStep, - CTypes types, - String lfModuleName, - int startTimeStepIsPresentCount - ) { - var code = new CodeBuilder(); - code.pr("void _lf_initialize_trigger_objects() {"); - code.indent(); - // Initialize the LF clock. - code.pr(String.join("\n", - "// Initialize the _lf_clock", - "lf_initialize_clock();" - )); - - // Initialize tracing if it is enabled - if (targetConfig.tracing != null) { - var traceFileName = lfModuleName; - if (targetConfig.tracing.traceFileName != null) { - traceFileName = targetConfig.tracing.traceFileName; - } - code.pr(String.join("\n", - "// Initialize tracing", - "start_trace("+ addDoubleQuotes(traceFileName + ".lft") + ");" - )); // .lft is for Lingua Franca trace - } + /** Generate the _lf_initialize_trigger_objects function for 'federate'. */ + public static String generateInitializeTriggerObjects( + ReactorInstance main, + TargetConfig targetConfig, + CodeBuilder initializeTriggerObjects, + CodeBuilder startTimeStep, + CTypes types, + String lfModuleName, + int startTimeStepIsPresentCount) { + var code = new CodeBuilder(); + code.pr("void _lf_initialize_trigger_objects() {"); + code.indent(); + // Initialize the LF clock. + code.pr(String.join("\n", "// Initialize the _lf_clock", "lf_initialize_clock();")); + + // Initialize tracing if it is enabled + if (targetConfig.tracing != null) { + var traceFileName = lfModuleName; + if (targetConfig.tracing.traceFileName != null) { + traceFileName = targetConfig.tracing.traceFileName; + } + code.pr( + String.join( + "\n", + "// Initialize tracing", + "start_trace(" + + addDoubleQuotes(traceFileName + ".lft") + + ");")); // .lft is for Lingua Franca trace + } - // Create the table to initialize is_present fields to false between time steps. - if (startTimeStepIsPresentCount > 0) { - // Allocate the initial (before mutations) array of pointers to _is_present fields. - code.pr(String.join("\n", - "// Create the array that will contain pointers to is_present fields to reset on each step.", - "_lf_is_present_fields_size = "+startTimeStepIsPresentCount+";", - "_lf_is_present_fields = (bool**)calloc("+startTimeStepIsPresentCount+", sizeof(bool*));", - "if (_lf_is_present_fields == NULL) lf_print_error_and_exit(" + addDoubleQuotes("Out of memory!") + ");", - "_lf_is_present_fields_abbreviated = (bool**)calloc("+startTimeStepIsPresentCount+", sizeof(bool*));", - "if (_lf_is_present_fields_abbreviated == NULL) lf_print_error_and_exit(" + addDoubleQuotes("Out of memory!") + ");", - "_lf_is_present_fields_abbreviated_size = 0;" - )); - } + // Create the table to initialize is_present fields to false between time steps. + if (startTimeStepIsPresentCount > 0) { + // Allocate the initial (before mutations) array of pointers to _is_present fields. + code.pr( + String.join( + "\n", + "// Create the array that will contain pointers to is_present fields to reset on each" + + " step.", + "_lf_is_present_fields_size = " + startTimeStepIsPresentCount + ";", + "_lf_is_present_fields = (bool**)calloc(" + + startTimeStepIsPresentCount + + ", sizeof(bool*));", + "if (_lf_is_present_fields == NULL) lf_print_error_and_exit(" + + addDoubleQuotes("Out of memory!") + + ");", + "_lf_is_present_fields_abbreviated = (bool**)calloc(" + + startTimeStepIsPresentCount + + ", sizeof(bool*));", + "if (_lf_is_present_fields_abbreviated == NULL) lf_print_error_and_exit(" + + addDoubleQuotes("Out of memory!") + + ");", + "_lf_is_present_fields_abbreviated_size = 0;")); + } - // Create the table to initialize intended tag fields to 0 between time - // steps. - if (startTimeStepIsPresentCount > 0) { - // Allocate the initial (before mutations) array of pointers to - // intended_tag fields. - // There is a 1-1 map between structs containing is_present and - // intended_tag fields, - // thus, we reuse startTimeStepIsPresentCount as the counter. - code.pr(String.join("\n", - CExtensionUtils.surroundWithIfFederatedDecentralized(""" + // Create the table to initialize intended tag fields to 0 between time + // steps. + if (startTimeStepIsPresentCount > 0) { + // Allocate the initial (before mutations) array of pointers to + // intended_tag fields. + // There is a 1-1 map between structs containing is_present and + // intended_tag fields, + // thus, we reuse startTimeStepIsPresentCount as the counter. + code.pr( + String.join( + "\n", + CExtensionUtils.surroundWithIfFederatedDecentralized( + """ // Create the array that will contain pointers to intended_tag fields to reset on each step. _lf_intended_tag_fields_size = %s; _lf_intended_tag_fields = (tag_t**)malloc(_lf_intended_tag_fields_size * sizeof(tag_t*)); - """.formatted(startTimeStepIsPresentCount) - ) - )); - } - + """ + .formatted(startTimeStepIsPresentCount)))); + } - code.pr(initializeTriggerObjects.toString()); - - code.pr(deferredInitialize( - main, - main.reactions, - targetConfig, - types - )); - code.pr(deferredInitializeNonNested( - main, - main, - main.reactions, - types - )); - // Next, for every input port, populate its "self" struct - // fields with pointers to the output port that sends it data. - code.pr(deferredConnectInputsToOutputs( - main - )); - // Put the code here to set up the tables that drive resetting is_present and - // decrementing reference counts between time steps. This code has to appear - // in _lf_initialize_trigger_objects() after the code that makes connections - // between inputs and outputs. - code.pr(startTimeStep.toString()); - code.pr(setReactionPriorities( - main - )); - code.pr(generateSchedulerInitializer( - main, - targetConfig - )); - - code.pr(""" + code.pr(initializeTriggerObjects.toString()); + + code.pr(deferredInitialize(main, main.reactions, targetConfig, types)); + code.pr(deferredInitializeNonNested(main, main, main.reactions, types)); + // Next, for every input port, populate its "self" struct + // fields with pointers to the output port that sends it data. + code.pr(deferredConnectInputsToOutputs(main)); + // Put the code here to set up the tables that drive resetting is_present and + // decrementing reference counts between time steps. This code has to appear + // in _lf_initialize_trigger_objects() after the code that makes connections + // between inputs and outputs. + code.pr(startTimeStep.toString()); + code.pr(setReactionPriorities(main)); + code.pr(generateSchedulerInitializer(main, targetConfig)); + + code.pr( + """ #ifdef EXECUTABLE_PREAMBLE _lf_executable_preamble(); #endif """); - // Initialize triggers for federated execution. - code.pr(CExtensionUtils.surroundWithIfFederated("initialize_triggers_for_federate();")); + // Initialize triggers for federated execution. + code.pr(CExtensionUtils.surroundWithIfFederated("initialize_triggers_for_federate();")); - code.unindent(); - code.pr("}\n"); - return code.toString(); - } + code.unindent(); + code.pr("}\n"); + return code.toString(); + } - /** - * Generate code to initialize the scheduler for the threaded C runtime. - */ - public static String generateSchedulerInitializer( - ReactorInstance main, - TargetConfig targetConfig - ) { - if (!targetConfig.threading) { - return ""; - } - var code = new CodeBuilder(); - var numReactionsPerLevel = main.assignLevels().getNumReactionsPerLevel(); - var numReactionsPerLevelJoined = Arrays.stream(numReactionsPerLevel) - .map(String::valueOf) - .collect(Collectors.joining(", ")); - code.pr(String.join("\n", + /** Generate code to initialize the scheduler for the threaded C runtime. */ + public static String generateSchedulerInitializer( + ReactorInstance main, TargetConfig targetConfig) { + if (!targetConfig.threading) { + return ""; + } + var code = new CodeBuilder(); + var numReactionsPerLevel = main.assignLevels().getNumReactionsPerLevel(); + var numReactionsPerLevelJoined = + Arrays.stream(numReactionsPerLevel).map(String::valueOf).collect(Collectors.joining(", ")); + code.pr( + String.join( + "\n", "// Initialize the scheduler", - "size_t num_reactions_per_level["+numReactionsPerLevel.length+"] = ", + "size_t num_reactions_per_level[" + numReactionsPerLevel.length + "] = ", " {" + numReactionsPerLevelJoined + "};", "sched_params_t sched_params = (sched_params_t) {", " .num_reactions_per_level = &num_reactions_per_level[0],", - " .num_reactions_per_level_size = (size_t) "+numReactionsPerLevel.length+"};", + " .num_reactions_per_level_size = (size_t) " + + numReactionsPerLevel.length + + "};", "lf_sched_init(", " (size_t)_lf_number_of_workers,", " &sched_params", - ");" - )); - return code.toString(); + ");")); + return code.toString(); + } + + /** + * Set the reaction priorities based on dependency analysis. + * + * @param reactor The reactor on which to do this. + */ + private static String setReactionPriorities(ReactorInstance reactor) { + var code = new CodeBuilder(); + setReactionPriorities(reactor, code); + return code.toString(); + } + + /** + * Set the reaction priorities based on dependency analysis. + * + * @param reactor The reactor on which to do this. + * @param builder Where to write the code. + */ + private static boolean setReactionPriorities(ReactorInstance reactor, CodeBuilder builder) { + var foundOne = false; + // Force calculation of levels if it has not been done. + // FIXME: Comment out this as I think it is redundant. + // If it is NOT redundant then deadline propagation is not correct + // reactor.assignLevels(); + + // We handle four different scenarios + // 1) A reactionInstance has 1 level and 1 deadline + // 2) A reactionInstance has 1 level but multiple deadlines + // 3) A reaction instance has multiple levels but all have the same deadline + // 4) Multiple deadlines and levels + + var prolog = new CodeBuilder(); + var epilog = new CodeBuilder(); + + for (ReactionInstance r : reactor.reactions) { + var levelSet = r.getLevels(); + var deadlineSet = r.getInferredDeadlines(); + + if (levelSet.size() > 1 || deadlineSet.size() > 1) { + // Scenario 2-4 + if (prolog.length() == 0) { + prolog.startScopedBlock(); + epilog.endScopedBlock(); + } + } + if (deadlineSet.size() > 1) { + // Scenario (2) or (4) + var deadlines = + r.getInferredDeadlinesList().stream() + .map(elt -> ("0x" + Long.toString(elt.toNanoSeconds(), 16) + "LL")) + .collect(Collectors.toList()); + + prolog.pr( + "interval_t " + + r.uniqueID() + + "_inferred_deadlines[] = { " + + joinObjects(deadlines, ", ") + + " };"); + } + + if (levelSet.size() > 1) { + // Scenario (3) or (4) + // Cannot use the above set of levels because it is a set, not a list. + prolog.pr( + "int " + + r.uniqueID() + + "_levels[] = { " + + joinObjects(r.getLevelsList(), ", ") + + " };"); + } } - /** - * Set the reaction priorities based on dependency analysis. - * - * @param reactor The reactor on which to do this. - */ - private static String setReactionPriorities( - ReactorInstance reactor - ) { - var code = new CodeBuilder(); - setReactionPriorities(reactor, code); - return code.toString(); + var temp = new CodeBuilder(); + temp.pr("// Set reaction priorities for " + reactor); + temp.startScopedBlock(reactor); + for (ReactionInstance r : reactor.reactions) { + // if (currentFederate.contains(r.getDefinition())) { + foundOne = true; + var levelSet = r.getLevels(); + var deadlineSet = r.getInferredDeadlines(); + + // Get the head of the associated lists. To avoid duplication in + // several of the following cases + var level = r.getLevelsList().get(0); + var inferredDeadline = r.getInferredDeadlinesList().get(0); + var runtimeIdx = CUtil.runtimeIndex(r.getParent()); + + if (levelSet.size() == 1 && deadlineSet.size() == 1) { + // Scenario (1) + + var indexValue = inferredDeadline.toNanoSeconds() << 16 | level; + + var reactionIndex = "0x" + Long.toUnsignedString(indexValue, 16) + "LL"; + + temp.pr( + String.join( + "\n", + CUtil.reactionRef(r) + ".chain_id = " + r.chainID + ";", + "// index is the OR of level " + level + " and ", + "// deadline " + inferredDeadline.toNanoSeconds() + " shifted left 16 bits.", + CUtil.reactionRef(r) + ".index = " + reactionIndex + ";")); + } else if (levelSet.size() == 1 && deadlineSet.size() > 1) { + // Scenario 2 + temp.pr( + String.join( + "\n", + CUtil.reactionRef(r) + ".chain_id = " + r.chainID + ";", + "// index is the OR of levels[" + runtimeIdx + "] and ", + "// deadlines[" + runtimeIdx + "] shifted left 16 bits.", + CUtil.reactionRef(r) + + ".index = (" + + r.uniqueID() + + "_inferred_deadlines[" + + runtimeIdx + + "] << 16) | " + + level + + ";")); + + } else if (levelSet.size() > 1 && deadlineSet.size() == 1) { + // Scenarion (3) + temp.pr( + String.join( + "\n", + CUtil.reactionRef(r) + ".chain_id = " + r.chainID + ";", + "// index is the OR of levels[" + runtimeIdx + "] and ", + "// deadlines[" + runtimeIdx + "] shifted left 16 bits.", + CUtil.reactionRef(r) + + ".index = (" + + inferredDeadline.toNanoSeconds() + + " << 16) | " + + r.uniqueID() + + "_levels[" + + runtimeIdx + + "];")); + + } else if (levelSet.size() > 1 && deadlineSet.size() > 1) { + // Scenario (4) + temp.pr( + String.join( + "\n", + CUtil.reactionRef(r) + ".chain_id = " + r.chainID + ";", + "// index is the OR of levels[" + runtimeIdx + "] and ", + "// deadlines[" + runtimeIdx + "] shifted left 16 bits.", + CUtil.reactionRef(r) + + ".index = (" + + r.uniqueID() + + "_inferred_deadlines[" + + runtimeIdx + + "] << 16) | " + + r.uniqueID() + + "_levels[" + + runtimeIdx + + "];")); + } } - - /** - * Set the reaction priorities based on dependency analysis. - * - * @param reactor The reactor on which to do this. - * @param builder Where to write the code. - */ - private static boolean setReactionPriorities( - ReactorInstance reactor, - CodeBuilder builder - ) { - var foundOne = false; - // Force calculation of levels if it has not been done. - // FIXME: Comment out this as I think it is redundant. - // If it is NOT redundant then deadline propagation is not correct - // reactor.assignLevels(); - - // We handle four different scenarios - // 1) A reactionInstance has 1 level and 1 deadline - // 2) A reactionInstance has 1 level but multiple deadlines - // 3) A reaction instance has multiple levels but all have the same deadline - // 4) Multiple deadlines and levels - - var prolog = new CodeBuilder(); - var epilog = new CodeBuilder(); - - for (ReactionInstance r : reactor.reactions) { - var levelSet = r.getLevels(); - var deadlineSet = r.getInferredDeadlines(); - - if (levelSet.size() > 1 || deadlineSet.size() > 1) { - // Scenario 2-4 - if (prolog.length() == 0) { - prolog.startScopedBlock(); - epilog.endScopedBlock(); - } - } - if (deadlineSet.size() > 1) { - // Scenario (2) or (4) - var deadlines = r.getInferredDeadlinesList().stream() - .map(elt -> ("0x" + Long.toString(elt.toNanoSeconds(), 16) + "LL")) - .collect(Collectors.toList()); - - prolog.pr("interval_t "+r.uniqueID()+"_inferred_deadlines[] = { "+joinObjects(deadlines, ", ")+" };"); - } - - if (levelSet.size() > 1) { - // Scenario (3) or (4) - // Cannot use the above set of levels because it is a set, not a list. - prolog.pr("int "+r.uniqueID()+"_levels[] = { "+joinObjects(r.getLevelsList(), ", ")+" };"); - } - } - - - var temp = new CodeBuilder(); - temp.pr("// Set reaction priorities for " + reactor); - temp.startScopedBlock(reactor); - for (ReactionInstance r : reactor.reactions) { - //if (currentFederate.contains(r.getDefinition())) { - foundOne = true; - var levelSet = r.getLevels(); - var deadlineSet = r.getInferredDeadlines(); - - // Get the head of the associated lists. To avoid duplication in - // several of the following cases - var level = r.getLevelsList().get(0); - var inferredDeadline = r.getInferredDeadlinesList().get(0); - var runtimeIdx =CUtil.runtimeIndex(r.getParent()); - - if (levelSet.size() == 1 && deadlineSet.size() == 1) { - // Scenario (1) - - var indexValue = inferredDeadline.toNanoSeconds() << 16 | level; - - var reactionIndex = "0x" + Long.toUnsignedString(indexValue, 16) + "LL"; - - temp.pr(String.join("\n", - CUtil.reactionRef(r)+".chain_id = "+r.chainID+";", - "// index is the OR of level "+level+" and ", - "// deadline "+ inferredDeadline.toNanoSeconds()+" shifted left 16 bits.", - CUtil.reactionRef(r)+".index = "+reactionIndex+";" - )); - } else if (levelSet.size() == 1 && deadlineSet.size() > 1) { - // Scenario 2 - temp.pr(String.join("\n", - CUtil.reactionRef(r)+".chain_id = "+r.chainID+";", - "// index is the OR of levels["+runtimeIdx+"] and ", - "// deadlines["+runtimeIdx+"] shifted left 16 bits.", - CUtil.reactionRef(r)+".index = ("+r.uniqueID()+"_inferred_deadlines["+runtimeIdx+"] << 16) | " + - level+";" - )); - - } else if (levelSet.size() > 1 && deadlineSet.size() == 1) { - // Scenarion (3) - temp.pr(String.join("\n", - CUtil.reactionRef(r)+".chain_id = "+r.chainID+";", - "// index is the OR of levels["+runtimeIdx+"] and ", - "// deadlines["+runtimeIdx+"] shifted left 16 bits.", - CUtil.reactionRef(r)+".index = ("+inferredDeadline.toNanoSeconds()+" << 16) | " + - r.uniqueID()+"_levels["+runtimeIdx+"];" - )); - - } else if (levelSet.size() > 1 && deadlineSet.size() > 1) { - // Scenario (4) - temp.pr(String.join("\n", - CUtil.reactionRef(r)+".chain_id = "+r.chainID+";", - "// index is the OR of levels["+runtimeIdx+"] and ", - "// deadlines["+runtimeIdx+"] shifted left 16 bits.", - CUtil.reactionRef(r)+".index = ("+r.uniqueID()+"_inferred_deadlines["+runtimeIdx+"] << 16) | " + - r.uniqueID()+"_levels["+runtimeIdx+"];" - )); - } - - } - for (ReactorInstance child : reactor.children) { - foundOne = setReactionPriorities(child, temp) || foundOne; - } - temp.endScopedBlock(); - - if (foundOne) { - builder.pr(prolog.toString()); - builder.pr(temp.toString()); - builder.pr(epilog.toString()); - } - return foundOne; + for (ReactorInstance child : reactor.children) { + foundOne = setReactionPriorities(child, temp) || foundOne; } + temp.endScopedBlock(); - /** - * Generate assignments of pointers in the "self" struct of a destination - * port's reactor to the appropriate entries in the "self" struct of the - * source reactor. This has to be done after all reactors have been created - * because inputs point to outputs that are arbitrarily far away. - * @param instance The reactor instance. - */ - private static String deferredConnectInputsToOutputs( - ReactorInstance instance - ) { - var code = new CodeBuilder(); - code.pr("// Connect inputs and outputs for reactor "+instance.getFullName()+"."); - // Iterate over all ports of this reactor that depend on reactions. - for (PortInstance input : instance.inputs) { - if (!input.getDependsOnReactions().isEmpty()) { - // Input is written to by reactions in the parent of the port's parent. - code.pr(connectPortToEventualDestinations(input)); - } - } - for (PortInstance output : instance.outputs) { - if (!output.getDependsOnReactions().isEmpty()) { - // Output is written to by reactions in the port's parent. - code.pr(connectPortToEventualDestinations(output)); - } - } - for (ReactorInstance child: instance.children) { - code.pr(deferredConnectInputsToOutputs(child)); - } - return code.toString(); + if (foundOne) { + builder.pr(prolog.toString()); + builder.pr(temp.toString()); + builder.pr(epilog.toString()); } - - /** - * Generate assignments of pointers in the "self" struct of a destination - * port's reactor to the appropriate entries in the "self" struct of the - * source reactor. If this port is an input, then it is being written - * to by a reaction belonging to the parent of the port's parent. - * If it is an output, then it is being written to by a reaction belonging - * to the port's parent. - * @param src A port that is written to by reactions. - */ - private static String connectPortToEventualDestinations( - PortInstance src - ) { - var code = new CodeBuilder(); - for (SendRange srcRange: src.eventualDestinations()) { - for (RuntimeRange dstRange : srcRange.destinations) { - var dst = dstRange.instance; - var destStructType = CGenerator.variableStructType(dst); - - // NOTE: For federated execution, dst.getParent() should always be contained - // by the currentFederate because an AST transformation removes connections - // between ports of distinct federates. So the following check is not - // really necessary. - var mod = (dst.isMultiport() || (src.isInput() && src.isMultiport()))? "" : "&"; - code.pr("// Connect "+srcRange+" to port "+dstRange); - code.startScopedRangeBlock(srcRange, dstRange); - if (src.isInput()) { - // Source port is written to by reaction in port's parent's parent - // and ultimate destination is further downstream. - code.pr(CUtil.portRef(dst, dr, db, dc)+" = ("+destStructType+"*)"+mod+CUtil.portRefNested(src, sr, sb, sc)+";"); - } else if (dst.isOutput()) { - // An output port of a contained reactor is triggering a reaction. - code.pr(CUtil.portRefNested(dst, dr, db, dc)+" = ("+destStructType+"*)&"+CUtil.portRef(src, sr, sb, sc)+";"); - } else { - // An output port is triggering an input port. - code.pr(CUtil.portRef(dst, dr, db, dc)+" = ("+destStructType+"*)&"+CUtil.portRef(src, sr, sb, sc)+";"); - if (AttributeUtils.isSparse(dst.getDefinition())) { - code.pr(CUtil.portRef(dst, dr, db, dc)+"->sparse_record = "+CUtil.portRefName(dst, dr, db, dc)+"__sparse;"); - code.pr(CUtil.portRef(dst, dr, db, dc)+"->destination_channel = "+dc+";"); - } - } - code.endScopedRangeBlock(srcRange, dstRange); - } - } - return code.toString(); + return foundOne; + } + + /** + * Generate assignments of pointers in the "self" struct of a destination port's reactor to the + * appropriate entries in the "self" struct of the source reactor. This has to be done after all + * reactors have been created because inputs point to outputs that are arbitrarily far away. + * + * @param instance The reactor instance. + */ + private static String deferredConnectInputsToOutputs(ReactorInstance instance) { + var code = new CodeBuilder(); + code.pr("// Connect inputs and outputs for reactor " + instance.getFullName() + "."); + // Iterate over all ports of this reactor that depend on reactions. + for (PortInstance input : instance.inputs) { + if (!input.getDependsOnReactions().isEmpty()) { + // Input is written to by reactions in the parent of the port's parent. + code.pr(connectPortToEventualDestinations(input)); + } } - - /** - * For each reaction of the specified reactor, - * set the last_enabling_reaction field of the reaction struct to point - * to the single dominating upstream reaction if there is one, or to be - * NULL if there is none. - * - * @param r The reactor. - */ - private static String deferredOptimizeForSingleDominatingReaction( - ReactorInstance r - ) { - var code = new CodeBuilder(); - for (ReactionInstance reaction : r.reactions) { - // The following code attempts to gather into a loop assignments of successive - // bank members relations between reactions to avoid large chunks of inline code - // when a large bank sends to a large bank or when a large bank receives from - // one reaction that is either multicasting or sending through a multiport. - var start = 0; - var end = 0; - var domStart = 0; - var same = false; // Set to true when finding a string of identical dominating reactions. - ReactionInstance.Runtime previousRuntime = null; - var first = true; //First time through the loop. - for (ReactionInstance.Runtime runtime : reaction.getRuntimeInstances()) { - if (!first) { // Not the first time through the loop. - if (same) { // Previously seen at least two identical dominating. - if (runtime.dominating != previousRuntime.dominating) { - // End of streak of same dominating reaction runtime instance. - code.pr(printOptimizeForSingleDominatingReaction( - previousRuntime, start, end, domStart, same - )); - same = false; - start = runtime.id; - domStart = (runtime.dominating != null) ? runtime.dominating.id : 0; - } - } else if (runtime.dominating == previousRuntime.dominating) { - // Start of a streak of identical dominating reaction runtime instances. - same = true; - } else if (runtime.dominating != null && previousRuntime.dominating != null - && runtime.dominating.getReaction() == previousRuntime.dominating.getReaction() - ) { - // Same dominating reaction even if not the same dominating runtime. - if (runtime.dominating.id != previousRuntime.dominating.id + 1) { - // End of a streak of contiguous runtimes. - printOptimizeForSingleDominatingReaction( - previousRuntime, start, end, domStart, same - ); - same = false; - start = runtime.id; - domStart = runtime.dominating.id; - } - } else { - // Different dominating reaction. - printOptimizeForSingleDominatingReaction( - previousRuntime, start, end, domStart, same - ); - same = false; - start = runtime.id; - domStart = (runtime.dominating != null) ? runtime.dominating.id : 0; - } - } - first = false; - previousRuntime = runtime; - end++; - } - if (end > start) { - printOptimizeForSingleDominatingReaction( - previousRuntime, start, end, domStart, same - ); - } - } - return code.toString(); + for (PortInstance output : instance.outputs) { + if (!output.getDependsOnReactions().isEmpty()) { + // Output is written to by reactions in the port's parent. + code.pr(connectPortToEventualDestinations(output)); + } } - - /** - * Print statement that sets the last_enabling_reaction field of a reaction. - */ - private static String printOptimizeForSingleDominatingReaction( - ReactionInstance.Runtime runtime, - int start, - int end, - int domStart, - boolean same - ) { - var code = new CodeBuilder(); - var dominatingRef = "NULL"; - - if (end > start + 1) { - code.startScopedBlock(); - var reactionRef = CUtil.reactionRef(runtime.getReaction(), "i"); - if (runtime.dominating != null) { - if (same) { - dominatingRef = "&(" + CUtil.reactionRef(runtime.dominating.getReaction(), "" + domStart) + ")"; - } else { - dominatingRef = "&(" + CUtil.reactionRef(runtime.dominating.getReaction(), "j++") + ")"; - } - } - code.pr(String.join("\n", - "// "+runtime.getReaction().getFullName()+" dominating upstream reaction.", - "int j = "+domStart+";", - "for (int i = "+start+"; i < "+end+"; i++) {", - " "+reactionRef+".last_enabling_reaction = "+dominatingRef+";", - "}" - )); - code.endScopedBlock(); - } else if (end == start + 1) { - var reactionRef = CUtil.reactionRef(runtime.getReaction(), "" + start); - if (runtime.dominating != null) { - dominatingRef = "&(" + CUtil.reactionRef(runtime.dominating.getReaction(), "" + domStart) + ")"; - } - code.pr(String.join("\n", - "// "+runtime.getReaction().getFullName()+" dominating upstream reaction.", - reactionRef+".last_enabling_reaction = "+dominatingRef+";" - )); - } - return code.toString(); + for (ReactorInstance child : instance.children) { + code.pr(deferredConnectInputsToOutputs(child)); } - - /** - * For the specified reaction, for ports that it writes to, - * fill the trigger table for triggering downstream reactions. - * - * @param reactions The reactions. - */ - private static String deferredFillTriggerTable( - Iterable reactions - ) { - var code = new CodeBuilder(); - for (ReactionInstance reaction : reactions) { - var name = reaction.getParent().getFullName(); - - var reactorSelfStruct = CUtil.reactorRef(reaction.getParent(), sr); - - var foundPort = false; - - for (PortInstance port : Iterables.filter(reaction.effects, PortInstance.class)) { - if (!foundPort) { - // Need a separate index for the triggers array for each bank member. - code.startScopedBlock(); - code.pr("int triggers_index["+reaction.getParent().getTotalWidth()+"] = { 0 }; // Number of bank members with the reaction."); - foundPort = true; - } - // If the port is a multiport, then its channels may have different sets - // of destinations. For ordinary ports, there will be only one range and - // its width will be 1. - // We generate the code to fill the triggers array first in a temporary code buffer, - // so that we can simultaneously calculate the size of the total array. - for (SendRange srcRange : port.eventualDestinations()) { - var srcNested = port.isInput(); - code.startScopedRangeBlock(srcRange, sr, sb, sc, srcNested); - - var triggerArray = CUtil.reactionRef(reaction, sr)+".triggers[triggers_index["+sr+"]++]"; - // Skip ports whose parent is not in the federation. - // This can happen with reactions in the top-level that have - // as an effect a port in a bank. - code.pr(String.join("\n", - "// Reaction "+reaction.index+" of "+name+" triggers "+srcRange.destinations.size()+" downstream reactions", - "// through port "+port.getFullName()+".", - CUtil.reactionRef(reaction, sr)+".triggered_sizes[triggers_index["+sr+"]] = "+srcRange.destinations.size()+";", - "// For reaction "+reaction.index+" of "+name+", allocate an", - "// array of trigger pointers for downstream reactions through port "+port.getFullName(), - "trigger_t** trigger_array = (trigger_t**)_lf_allocate(", - " "+srcRange.destinations.size()+", sizeof(trigger_t*),", - " &"+reactorSelfStruct+"->base.allocations); ", - triggerArray+" = trigger_array;" - )); - code.endScopedRangeBlock(srcRange); - } - } - var cumulativePortWidth = 0; - for (PortInstance port : Iterables.filter(reaction.effects, PortInstance.class)) { - // If this port does not have any destinations, do not generate code for it. - if (port.eventualDestinations().isEmpty()) continue; - - code.pr("for (int i = 0; i < "+reaction.getParent().getTotalWidth()+"; i++) triggers_index[i] = "+cumulativePortWidth+";"); - for (SendRange srcRange : port.eventualDestinations()) { - var srcNested = srcRange.instance.isInput(); - var multicastCount = 0; - for (RuntimeRange dstRange : srcRange.destinations) { - var dst = dstRange.instance; - - code.startScopedRangeBlock(srcRange, dstRange); - - // If the source is nested, need to take into account the parent's bank index - // when indexing into the triggers array. - var triggerArray = ""; - if (srcNested && port.getParent().getWidth() > 1) { - triggerArray = CUtil.reactionRef(reaction, sr)+".triggers[triggers_index["+sr+"] + "+sc+" + src_range_mr.digits[1] * src_range_mr.radixes[0]]"; - } else { - triggerArray = CUtil.reactionRef(reaction, sr)+".triggers[triggers_index["+sr+"] + "+sc+"]"; - } - - if (dst.isOutput()) { - // Include this destination port only if it has at least one - // reaction in the federation. - var belongs = false; - for (ReactionInstance destinationReaction : dst.getDependentReactions()) { - belongs = true; - } - if (belongs) { - code.pr(String.join("\n", - "// Port "+port.getFullName()+" has reactions in its parent's parent.", - "// Point to the trigger struct for those reactions.", - triggerArray+"["+multicastCount+"] = &"+CUtil.triggerRefNested(dst, dr, db)+";" - )); - } else { - // Put in a NULL pointer. - code.pr(String.join("\n", - "// Port "+port.getFullName()+" has reactions in its parent's parent.", - "// But those are not in the federation.", - triggerArray+"["+multicastCount+"] = NULL;" - )); - } - } else { - // Destination is an input port. - code.pr(String.join("\n", - "// Point to destination port "+dst.getFullName()+"'s trigger struct.", - triggerArray+"["+multicastCount+"] = &"+CUtil.triggerRef(dst, dr)+";" - )); - } - code.endScopedRangeBlock(srcRange, dstRange); - multicastCount++; - } - } - // If the port is an input of a contained reactor, then we have to take - // into account the bank width of the contained reactor. - if (port.getParent() != reaction.getParent()) { - cumulativePortWidth += port.getWidth() * port.getParent().getWidth(); - } else { - cumulativePortWidth += port.getWidth(); - } - } - if (foundPort) code.endScopedBlock(); + return code.toString(); + } + + /** + * Generate assignments of pointers in the "self" struct of a destination port's reactor to the + * appropriate entries in the "self" struct of the source reactor. If this port is an input, then + * it is being written to by a reaction belonging to the parent of the port's parent. If it is an + * output, then it is being written to by a reaction belonging to the port's parent. + * + * @param src A port that is written to by reactions. + */ + private static String connectPortToEventualDestinations(PortInstance src) { + var code = new CodeBuilder(); + for (SendRange srcRange : src.eventualDestinations()) { + for (RuntimeRange dstRange : srcRange.destinations) { + var dst = dstRange.instance; + var destStructType = CGenerator.variableStructType(dst); + + // NOTE: For federated execution, dst.getParent() should always be contained + // by the currentFederate because an AST transformation removes connections + // between ports of distinct federates. So the following check is not + // really necessary. + var mod = (dst.isMultiport() || (src.isInput() && src.isMultiport())) ? "" : "&"; + code.pr("// Connect " + srcRange + " to port " + dstRange); + code.startScopedRangeBlock(srcRange, dstRange); + if (src.isInput()) { + // Source port is written to by reaction in port's parent's parent + // and ultimate destination is further downstream. + code.pr( + CUtil.portRef(dst, dr, db, dc) + + " = (" + + destStructType + + "*)" + + mod + + CUtil.portRefNested(src, sr, sb, sc) + + ";"); + } else if (dst.isOutput()) { + // An output port of a contained reactor is triggering a reaction. + code.pr( + CUtil.portRefNested(dst, dr, db, dc) + + " = (" + + destStructType + + "*)&" + + CUtil.portRef(src, sr, sb, sc) + + ";"); + } else { + // An output port is triggering an input port. + code.pr( + CUtil.portRef(dst, dr, db, dc) + + " = (" + + destStructType + + "*)&" + + CUtil.portRef(src, sr, sb, sc) + + ";"); + if (AttributeUtils.isSparse(dst.getDefinition())) { + code.pr( + CUtil.portRef(dst, dr, db, dc) + + "->sparse_record = " + + CUtil.portRefName(dst, dr, db, dc) + + "__sparse;"); + code.pr(CUtil.portRef(dst, dr, db, dc) + "->destination_channel = " + dc + ";"); + } } - return code.toString(); + code.endScopedRangeBlock(srcRange, dstRange); + } } - - /** - * For each input port of a contained reactor that receives data - * from one or more of the specified reactions, set the num_destinations - * field of the corresponding port structs on the self struct of - * the reaction's parent reactor equal to the total number of - * destination reactors. - * If the port has a token type, this also initializes it with a token. - * @param reactions The reactions. - * @param types The C types. - */ - private static String deferredInputNumDestinations( - Iterable reactions, - CTypes types - ) { - // We need the number of destination _reactors_, not the - // number of destination ports nor the number of destination reactions. - // One of the destination reactors may be the container of this - // instance because it may have a reaction to an output of this instance. - // Since a port may be written to by multiple reactions, - // ensure that this is done only once. - var portsHandled = new HashSet(); - var code = new CodeBuilder(); - for (ReactionInstance reaction : reactions) { - for (PortInstance port : Iterables.filter(reaction.effects, PortInstance.class)) { - if (port.isInput() && !portsHandled.contains(port)) { - // Port is an input of a contained reactor that gets data from a reaction of this reactor. - portsHandled.add(port); - code.pr("// Set number of destination reactors for port "+port.getParent().getName()+"."+port.getName()+"."); - // The input port may itself have multiple destinations. - for (SendRange sendingRange : port.eventualDestinations()) { - code.startScopedRangeBlock(sendingRange, sr, sb, sc, sendingRange.instance.isInput()); - // Syntax is slightly different for a multiport output vs. single port. - var connector = (port.isMultiport())? "->" : "."; - code.pr(CUtil.portRefNested(port, sr, sb, sc)+connector+"num_destinations = "+sendingRange.getNumberOfDestinationReactors()+";"); - - // Initialize token types. - var type = ASTUtils.getInferredType(port.getDefinition()); - if (CUtil.isTokenType(type, types)) { - // Create the template token that goes in the port struct. - var rootType = CUtil.rootType(types.getTargetType(type)); - // If the rootType is 'void', we need to avoid generating the code - // 'sizeof(void)', which some compilers reject. - var size = (rootType.equals("void")) ? "0" : "sizeof("+rootType+")"; - // If the port is a multiport, then the portRefNested is itself a pointer - // so we want its value, not its address. - var indirection = (port.isMultiport())? "" : "&"; - code.startChannelIteration(port); - code.pr(String.join("\n", - "_lf_initialize_template((token_template_t*)", - " "+indirection+"("+CUtil.portRefNested(port, sr, sb, sc)+"),", - size+");" - )); - code.endChannelIteration(port); - } - - code.endScopedRangeBlock(sendingRange); - } - } + return code.toString(); + } + + /** + * For each reaction of the specified reactor, set the last_enabling_reaction field of the + * reaction struct to point to the single dominating upstream reaction if there is one, or to be + * NULL if there is none. + * + * @param r The reactor. + */ + private static String deferredOptimizeForSingleDominatingReaction(ReactorInstance r) { + var code = new CodeBuilder(); + for (ReactionInstance reaction : r.reactions) { + // The following code attempts to gather into a loop assignments of successive + // bank members relations between reactions to avoid large chunks of inline code + // when a large bank sends to a large bank or when a large bank receives from + // one reaction that is either multicasting or sending through a multiport. + var start = 0; + var end = 0; + var domStart = 0; + var same = false; // Set to true when finding a string of identical dominating reactions. + ReactionInstance.Runtime previousRuntime = null; + var first = true; // First time through the loop. + for (ReactionInstance.Runtime runtime : reaction.getRuntimeInstances()) { + if (!first) { // Not the first time through the loop. + if (same) { // Previously seen at least two identical dominating. + if (runtime.dominating != previousRuntime.dominating) { + // End of streak of same dominating reaction runtime instance. + code.pr( + printOptimizeForSingleDominatingReaction( + previousRuntime, start, end, domStart, same)); + same = false; + start = runtime.id; + domStart = (runtime.dominating != null) ? runtime.dominating.id : 0; } - } - return code.toString(); - } - - /** - * For each output port of the specified reactor, - * set the num_destinations field of port structs on its self struct - * equal to the total number of destination reactors. This is used - * to initialize reference counts in dynamically allocated tokens - * sent to other reactors. - * @param reactor The reactor instance. - */ - private static String deferredOutputNumDestinations( - ReactorInstance reactor - ) { - // Reference counts are decremented by each destination reactor - // at the conclusion of a time step. Hence, the initial reference - // count should equal the number of destination _reactors_, not the - // number of destination ports nor the number of destination reactions. - // One of the destination reactors may be the container of this - // instance because it may have a reaction to an output of this instance. - var code = new CodeBuilder(); - for (PortInstance output : reactor.outputs) { - for (SendRange sendingRange : output.eventualDestinations()) { - code.pr("// For reference counting, set num_destinations for port " + output.getFullName() + "."); - code.startScopedRangeBlock(sendingRange, sr, sb, sc, sendingRange.instance.isInput()); - code.pr(CUtil.portRef(output, sr, sb, sc)+".num_destinations = "+sendingRange.getNumberOfDestinationReactors()+";"); - code.endScopedRangeBlock(sendingRange); + } else if (runtime.dominating == previousRuntime.dominating) { + // Start of a streak of identical dominating reaction runtime instances. + same = true; + } else if (runtime.dominating != null + && previousRuntime.dominating != null + && runtime.dominating.getReaction() == previousRuntime.dominating.getReaction()) { + // Same dominating reaction even if not the same dominating runtime. + if (runtime.dominating.id != previousRuntime.dominating.id + 1) { + // End of a streak of contiguous runtimes. + printOptimizeForSingleDominatingReaction(previousRuntime, start, end, domStart, same); + same = false; + start = runtime.id; + domStart = runtime.dominating.id; } + } else { + // Different dominating reaction. + printOptimizeForSingleDominatingReaction(previousRuntime, start, end, domStart, same); + same = false; + start = runtime.id; + domStart = (runtime.dominating != null) ? runtime.dominating.id : 0; + } } - return code.toString(); + first = false; + previousRuntime = runtime; + end++; + } + if (end > start) { + printOptimizeForSingleDominatingReaction(previousRuntime, start, end, domStart, same); + } } - - /** - * Perform initialization functions that must be performed after - * all reactor runtime instances have been created. - * This function does not create nested loops over nested banks, - * so each function it calls must handle its own iteration - * over all runtime instance. - * @param reactor The container. - * @param main The top-level reactor. - * @param reactions The list of reactions to consider. - * @param types The C types. - */ - private static String deferredInitializeNonNested( - ReactorInstance reactor, - ReactorInstance main, - Iterable reactions, - CTypes types - ) { - var code = new CodeBuilder(); - code.pr("// **** Start non-nested deferred initialize for "+reactor.getFullName()); - // Initialize the num_destinations fields of port structs on the self struct. - // This needs to be outside the above scoped block because it performs - // its own iteration over ranges. - code.pr(deferredInputNumDestinations( - reactions, - types - )); - - // Second batch of initializes cannot be within a for loop - // iterating over bank members because they iterate over send - // ranges which may span bank members. - if (reactor != main) { - code.pr(deferredOutputNumDestinations( - reactor - )); - } - code.pr(deferredFillTriggerTable( - reactions - )); - code.pr(deferredOptimizeForSingleDominatingReaction( - reactor - )); - for (ReactorInstance child: reactor.children) { - code.pr(deferredInitializeNonNested( - child, - main, - child.reactions, - types - )); + return code.toString(); + } + + /** Print statement that sets the last_enabling_reaction field of a reaction. */ + private static String printOptimizeForSingleDominatingReaction( + ReactionInstance.Runtime runtime, int start, int end, int domStart, boolean same) { + var code = new CodeBuilder(); + var dominatingRef = "NULL"; + + if (end > start + 1) { + code.startScopedBlock(); + var reactionRef = CUtil.reactionRef(runtime.getReaction(), "i"); + if (runtime.dominating != null) { + if (same) { + dominatingRef = + "&(" + CUtil.reactionRef(runtime.dominating.getReaction(), "" + domStart) + ")"; + } else { + dominatingRef = "&(" + CUtil.reactionRef(runtime.dominating.getReaction(), "j++") + ")"; } - code.pr("// **** End of non-nested deferred initialize for "+reactor.getFullName()); - return code.toString(); + } + code.pr( + String.join( + "\n", + "// " + runtime.getReaction().getFullName() + " dominating upstream reaction.", + "int j = " + domStart + ";", + "for (int i = " + start + "; i < " + end + "; i++) {", + " " + reactionRef + ".last_enabling_reaction = " + dominatingRef + ";", + "}")); + code.endScopedBlock(); + } else if (end == start + 1) { + var reactionRef = CUtil.reactionRef(runtime.getReaction(), "" + start); + if (runtime.dominating != null) { + dominatingRef = + "&(" + CUtil.reactionRef(runtime.dominating.getReaction(), "" + domStart) + ")"; + } + code.pr( + String.join( + "\n", + "// " + runtime.getReaction().getFullName() + " dominating upstream reaction.", + reactionRef + ".last_enabling_reaction = " + dominatingRef + ";")); } - - /** - * For each output of the specified reactor that has a token type - * (type* or type[]), create a template token and put it on the self struct. - * @param reactor The reactor. - */ - private static String deferredCreateTemplateTokens( - ReactorInstance reactor, - CTypes types - ) { - var code = new CodeBuilder(); - // Look for outputs with token types. - for (PortInstance output : reactor.outputs) { - var type = ASTUtils.getInferredType(output.getDefinition()); - if (CUtil.isTokenType(type, types)) { - // Create the template token that goes in the trigger struct. - // Its reference count is zero, enabling it to be used immediately. - var rootType = CUtil.rootType(types.getTargetType(type)); - // If the rootType is 'void', we need to avoid generating the code - // 'sizeof(void)', which some compilers reject. - var size = (rootType.equals("void")) ? "0" : "sizeof("+rootType+")"; - code.startChannelIteration(output); - code.pr(String.join("\n", - "_lf_initialize_template((token_template_t*)", - " &("+CUtil.portRef(output)+"),", - size+");" - )); - code.endChannelIteration(output); - } + return code.toString(); + } + + /** + * For the specified reaction, for ports that it writes to, fill the trigger table for triggering + * downstream reactions. + * + * @param reactions The reactions. + */ + private static String deferredFillTriggerTable(Iterable reactions) { + var code = new CodeBuilder(); + for (ReactionInstance reaction : reactions) { + var name = reaction.getParent().getFullName(); + + var reactorSelfStruct = CUtil.reactorRef(reaction.getParent(), sr); + + var foundPort = false; + + for (PortInstance port : Iterables.filter(reaction.effects, PortInstance.class)) { + if (!foundPort) { + // Need a separate index for the triggers array for each bank member. + code.startScopedBlock(); + code.pr( + "int triggers_index[" + + reaction.getParent().getTotalWidth() + + "] = { 0 }; // Number of bank members with the reaction."); + foundPort = true; } - return code.toString(); - } - - /** - * For the specified reaction, for ports that it writes to, - * set up the arrays that store the results (if necessary) and - * that are used to trigger downstream reactions if an effect is actually - * produced. The port may be an output of the reaction's parent - * or an input to a reactor contained by the parent. - * - * @param reaction The reaction instance. - */ - private static String deferredReactionOutputs( - ReactionInstance reaction, - TargetConfig targetConfig - ) { - var code = new CodeBuilder(); - // val selfRef = CUtil.reactorRef(reaction.getParent()); - var name = reaction.getParent().getFullName(); - // Insert a string name to facilitate debugging. - if (targetConfig.logLevel.compareTo(LogLevel.LOG) >= 0) { - code.pr(CUtil.reactionRef(reaction)+".name = "+addDoubleQuotes(name+" reaction "+reaction.index)+";"); + // If the port is a multiport, then its channels may have different sets + // of destinations. For ordinary ports, there will be only one range and + // its width will be 1. + // We generate the code to fill the triggers array first in a temporary code buffer, + // so that we can simultaneously calculate the size of the total array. + for (SendRange srcRange : port.eventualDestinations()) { + var srcNested = port.isInput(); + code.startScopedRangeBlock(srcRange, sr, sb, sc, srcNested); + + var triggerArray = + CUtil.reactionRef(reaction, sr) + ".triggers[triggers_index[" + sr + "]++]"; + // Skip ports whose parent is not in the federation. + // This can happen with reactions in the top-level that have + // as an effect a port in a bank. + code.pr( + String.join( + "\n", + "// Reaction " + + reaction.index + + " of " + + name + + " triggers " + + srcRange.destinations.size() + + " downstream reactions", + "// through port " + port.getFullName() + ".", + CUtil.reactionRef(reaction, sr) + + ".triggered_sizes[triggers_index[" + + sr + + "]] = " + + srcRange.destinations.size() + + ";", + "// For reaction " + reaction.index + " of " + name + ", allocate an", + "// array of trigger pointers for downstream reactions through port " + + port.getFullName(), + "trigger_t** trigger_array = (trigger_t**)_lf_allocate(", + " " + srcRange.destinations.size() + ", sizeof(trigger_t*),", + " &" + reactorSelfStruct + "->base.allocations); ", + triggerArray + " = trigger_array;")); + code.endScopedRangeBlock(srcRange); } - - var reactorSelfStruct = CUtil.reactorRef(reaction.getParent()); - - // Count the output ports and inputs of contained reactors that - // may be set by this reaction. This ignores actions in the effects. - // Collect initialization statements for the output_produced array for the reaction - // to point to the is_present field of the appropriate output. - // These statements must be inserted after the array is malloc'd, - // but we construct them while we are counting outputs. - var outputCount = 0; - var init = new CodeBuilder(); - - init.startScopedBlock(); - init.pr("int count = 0; SUPPRESS_UNUSED_WARNING(count);"); - for (PortInstance effect : Iterables.filter(reaction.effects, PortInstance.class)) { - // Create the entry in the output_produced array for this port. - // If the port is a multiport, then we need to create an entry for each - // individual channel. - - // If the port is an input of a contained reactor, then, if that - // contained reactor is a bank, we will have to iterate over bank - // members. - var bankWidth = 1; - var portRef = ""; - if (effect.isInput()) { - init.pr("// Reaction writes to an input of a contained reactor."); - bankWidth = effect.getParent().getWidth(); - init.startScopedBlock(effect.getParent()); - portRef = CUtil.portRefNestedName(effect); + } + var cumulativePortWidth = 0; + for (PortInstance port : Iterables.filter(reaction.effects, PortInstance.class)) { + // If this port does not have any destinations, do not generate code for it. + if (port.eventualDestinations().isEmpty()) continue; + + code.pr( + "for (int i = 0; i < " + + reaction.getParent().getTotalWidth() + + "; i++) triggers_index[i] = " + + cumulativePortWidth + + ";"); + for (SendRange srcRange : port.eventualDestinations()) { + var srcNested = srcRange.instance.isInput(); + var multicastCount = 0; + for (RuntimeRange dstRange : srcRange.destinations) { + var dst = dstRange.instance; + + code.startScopedRangeBlock(srcRange, dstRange); + + // If the source is nested, need to take into account the parent's bank index + // when indexing into the triggers array. + var triggerArray = ""; + if (srcNested && port.getParent().getWidth() > 1) { + triggerArray = + CUtil.reactionRef(reaction, sr) + + ".triggers[triggers_index[" + + sr + + "] + " + + sc + + " + src_range_mr.digits[1] * src_range_mr.radixes[0]]"; } else { - init.startScopedBlock(); - portRef = CUtil.portRefName(effect); + triggerArray = + CUtil.reactionRef(reaction, sr) + + ".triggers[triggers_index[" + + sr + + "] + " + + sc + + "]"; } - if (effect.isMultiport()) { - // Form is slightly different for inputs vs. outputs. - var connector = "."; - if (effect.isInput()) connector = "->"; - - // Point the output_produced field to where the is_present field of the port is. - init.pr(String.join("\n", - "for (int i = 0; i < "+effect.getWidth()+"; i++) {", - " "+CUtil.reactionRef(reaction)+".output_produced[i + count]", - " = &"+portRef+"[i]"+connector+"is_present;", - "}", - "count += "+effect.getWidth()+";" - )); - outputCount += effect.getWidth() * bankWidth; + if (dst.isOutput()) { + // Include this destination port only if it has at least one + // reaction in the federation. + var belongs = false; + for (ReactionInstance destinationReaction : dst.getDependentReactions()) { + belongs = true; + } + if (belongs) { + code.pr( + String.join( + "\n", + "// Port " + port.getFullName() + " has reactions in its parent's parent.", + "// Point to the trigger struct for those reactions.", + triggerArray + + "[" + + multicastCount + + "] = &" + + CUtil.triggerRefNested(dst, dr, db) + + ";")); + } else { + // Put in a NULL pointer. + code.pr( + String.join( + "\n", + "// Port " + port.getFullName() + " has reactions in its parent's parent.", + "// But those are not in the federation.", + triggerArray + "[" + multicastCount + "] = NULL;")); + } } else { - // The effect is not a multiport. - init.pr(CUtil.reactionRef(reaction)+".output_produced[count++] = &"+portRef+".is_present;"); - outputCount += bankWidth; + // Destination is an input port. + code.pr( + String.join( + "\n", + "// Point to destination port " + dst.getFullName() + "'s trigger struct.", + triggerArray + + "[" + + multicastCount + + "] = &" + + CUtil.triggerRef(dst, dr) + + ";")); } - init.endScopedBlock(); + code.endScopedRangeBlock(srcRange, dstRange); + multicastCount++; + } } - init.endScopedBlock(); - code.pr(String.join("\n", - "// Total number of outputs (single ports and multiport channels)", - "// produced by "+reaction+".", - CUtil.reactionRef(reaction)+".num_outputs = "+outputCount+";" - )); - if (outputCount > 0) { - code.pr(String.join("\n", - "// Allocate memory for triggers[] and triggered_sizes[] on the reaction_t", - "// struct for this reaction.", - CUtil.reactionRef(reaction)+".triggers = (trigger_t***)_lf_allocate(", - " "+outputCount+", sizeof(trigger_t**),", - " &"+reactorSelfStruct+"->base.allocations);", - CUtil.reactionRef(reaction)+".triggered_sizes = (int*)_lf_allocate(", - " "+outputCount+", sizeof(int),", - " &"+reactorSelfStruct+"->base.allocations);", - CUtil.reactionRef(reaction)+".output_produced = (bool**)_lf_allocate(", - " "+outputCount+", sizeof(bool*),", - " &"+reactorSelfStruct+"->base.allocations);" - )); + // If the port is an input of a contained reactor, then we have to take + // into account the bank width of the contained reactor. + if (port.getParent() != reaction.getParent()) { + cumulativePortWidth += port.getWidth() * port.getParent().getWidth(); + } else { + cumulativePortWidth += port.getWidth(); } - - code.pr(String.join("\n", - init.toString(), - "// ** End initialization for reaction "+reaction.index+" of "+name - )); - return code.toString(); + } + if (foundPort) code.endScopedBlock(); } - - /** - * Generate code to allocate the memory needed by reactions for triggering - * downstream reactions. - * @param reactions A list of reactions. - */ - private static String deferredReactionMemory( - Iterable reactions, - TargetConfig targetConfig - ) { - var code = new CodeBuilder(); - // For each reaction instance, allocate the arrays that will be used to - // trigger downstream reactions. - for (ReactionInstance reaction : reactions) { - code.pr(deferredReactionOutputs( - reaction, - targetConfig - )); - var reactorSelfStruct = CUtil.reactorRef(reaction.getParent()); - - // Next handle triggers of the reaction that come from a multiport output - // of a contained reactor. Also, handle startup and shutdown triggers. - for (PortInstance trigger : Iterables.filter(reaction.triggers, PortInstance.class)) { - // If the port is a multiport, then we need to create an entry for each - // individual port. - if (trigger.isMultiport() && trigger.getParent() != null && trigger.isOutput()) { - // Trigger is an output of a contained reactor or bank. - code.pr(String.join("\n", - "// Allocate memory to store pointers to the multiport output "+trigger.getName()+" ", - "// of a contained reactor "+trigger.getParent().getFullName() - )); - code.startScopedBlock(trigger.getParent()); - - var width = trigger.getWidth(); - var portStructType = CGenerator.variableStructType(trigger); - - code.pr(String.join("\n", - CUtil.reactorRefNested(trigger.getParent())+"."+trigger.getName()+"_width = "+width+";", - CUtil.reactorRefNested(trigger.getParent())+"."+trigger.getName(), - " = ("+portStructType+"**)_lf_allocate(", - " "+width+", sizeof("+portStructType+"*),", - " &"+reactorSelfStruct+"->base.allocations); " - )); - - code.endScopedBlock(); - } + return code.toString(); + } + + /** + * For each input port of a contained reactor that receives data from one or more of the specified + * reactions, set the num_destinations field of the corresponding port structs on the self struct + * of the reaction's parent reactor equal to the total number of destination reactors. If the port + * has a token type, this also initializes it with a token. + * + * @param reactions The reactions. + * @param types The C types. + */ + private static String deferredInputNumDestinations( + Iterable reactions, CTypes types) { + // We need the number of destination _reactors_, not the + // number of destination ports nor the number of destination reactions. + // One of the destination reactors may be the container of this + // instance because it may have a reaction to an output of this instance. + // Since a port may be written to by multiple reactions, + // ensure that this is done only once. + var portsHandled = new HashSet(); + var code = new CodeBuilder(); + for (ReactionInstance reaction : reactions) { + for (PortInstance port : Iterables.filter(reaction.effects, PortInstance.class)) { + if (port.isInput() && !portsHandled.contains(port)) { + // Port is an input of a contained reactor that gets data from a reaction of this reactor. + portsHandled.add(port); + code.pr( + "// Set number of destination reactors for port " + + port.getParent().getName() + + "." + + port.getName() + + "."); + // The input port may itself have multiple destinations. + for (SendRange sendingRange : port.eventualDestinations()) { + code.startScopedRangeBlock(sendingRange, sr, sb, sc, sendingRange.instance.isInput()); + // Syntax is slightly different for a multiport output vs. single port. + var connector = (port.isMultiport()) ? "->" : "."; + code.pr( + CUtil.portRefNested(port, sr, sb, sc) + + connector + + "num_destinations = " + + sendingRange.getNumberOfDestinationReactors() + + ";"); + + // Initialize token types. + var type = ASTUtils.getInferredType(port.getDefinition()); + if (CUtil.isTokenType(type, types)) { + // Create the template token that goes in the port struct. + var rootType = CUtil.rootType(types.getTargetType(type)); + // If the rootType is 'void', we need to avoid generating the code + // 'sizeof(void)', which some compilers reject. + var size = (rootType.equals("void")) ? "0" : "sizeof(" + rootType + ")"; + // If the port is a multiport, then the portRefNested is itself a pointer + // so we want its value, not its address. + var indirection = (port.isMultiport()) ? "" : "&"; + code.startChannelIteration(port); + code.pr( + String.join( + "\n", + "_lf_initialize_template((token_template_t*)", + " " + indirection + "(" + CUtil.portRefNested(port, sr, sb, sc) + "),", + size + ");")); + code.endChannelIteration(port); } + + code.endScopedRangeBlock(sendingRange); + } } - return code.toString(); + } + } + return code.toString(); + } + + /** + * For each output port of the specified reactor, set the num_destinations field of port structs + * on its self struct equal to the total number of destination reactors. This is used to + * initialize reference counts in dynamically allocated tokens sent to other reactors. + * + * @param reactor The reactor instance. + */ + private static String deferredOutputNumDestinations(ReactorInstance reactor) { + // Reference counts are decremented by each destination reactor + // at the conclusion of a time step. Hence, the initial reference + // count should equal the number of destination _reactors_, not the + // number of destination ports nor the number of destination reactions. + // One of the destination reactors may be the container of this + // instance because it may have a reaction to an output of this instance. + var code = new CodeBuilder(); + for (PortInstance output : reactor.outputs) { + for (SendRange sendingRange : output.eventualDestinations()) { + code.pr( + "// For reference counting, set num_destinations for port " + + output.getFullName() + + "."); + code.startScopedRangeBlock(sendingRange, sr, sb, sc, sendingRange.instance.isInput()); + code.pr( + CUtil.portRef(output, sr, sb, sc) + + ".num_destinations = " + + sendingRange.getNumberOfDestinationReactors() + + ";"); + code.endScopedRangeBlock(sendingRange); + } + } + return code.toString(); + } + + /** + * Perform initialization functions that must be performed after all reactor runtime instances + * have been created. This function does not create nested loops over nested banks, so each + * function it calls must handle its own iteration over all runtime instance. + * + * @param reactor The container. + * @param main The top-level reactor. + * @param reactions The list of reactions to consider. + * @param types The C types. + */ + private static String deferredInitializeNonNested( + ReactorInstance reactor, + ReactorInstance main, + Iterable reactions, + CTypes types) { + var code = new CodeBuilder(); + code.pr("// **** Start non-nested deferred initialize for " + reactor.getFullName()); + // Initialize the num_destinations fields of port structs on the self struct. + // This needs to be outside the above scoped block because it performs + // its own iteration over ranges. + code.pr(deferredInputNumDestinations(reactions, types)); + + // Second batch of initializes cannot be within a for loop + // iterating over bank members because they iterate over send + // ranges which may span bank members. + if (reactor != main) { + code.pr(deferredOutputNumDestinations(reactor)); + } + code.pr(deferredFillTriggerTable(reactions)); + code.pr(deferredOptimizeForSingleDominatingReaction(reactor)); + for (ReactorInstance child : reactor.children) { + code.pr(deferredInitializeNonNested(child, main, child.reactions, types)); + } + code.pr("// **** End of non-nested deferred initialize for " + reactor.getFullName()); + return code.toString(); + } + + /** + * For each output of the specified reactor that has a token type (type* or type[]), create a + * template token and put it on the self struct. + * + * @param reactor The reactor. + */ + private static String deferredCreateTemplateTokens(ReactorInstance reactor, CTypes types) { + var code = new CodeBuilder(); + // Look for outputs with token types. + for (PortInstance output : reactor.outputs) { + var type = ASTUtils.getInferredType(output.getDefinition()); + if (CUtil.isTokenType(type, types)) { + // Create the template token that goes in the trigger struct. + // Its reference count is zero, enabling it to be used immediately. + var rootType = CUtil.rootType(types.getTargetType(type)); + // If the rootType is 'void', we need to avoid generating the code + // 'sizeof(void)', which some compilers reject. + var size = (rootType.equals("void")) ? "0" : "sizeof(" + rootType + ")"; + code.startChannelIteration(output); + code.pr( + String.join( + "\n", + "_lf_initialize_template((token_template_t*)", + " &(" + CUtil.portRef(output) + "),", + size + ");")); + code.endChannelIteration(output); + } + } + return code.toString(); + } + + /** + * For the specified reaction, for ports that it writes to, set up the arrays that store the + * results (if necessary) and that are used to trigger downstream reactions if an effect is + * actually produced. The port may be an output of the reaction's parent or an input to a reactor + * contained by the parent. + * + * @param reaction The reaction instance. + */ + private static String deferredReactionOutputs( + ReactionInstance reaction, TargetConfig targetConfig) { + var code = new CodeBuilder(); + // val selfRef = CUtil.reactorRef(reaction.getParent()); + var name = reaction.getParent().getFullName(); + // Insert a string name to facilitate debugging. + if (targetConfig.logLevel.compareTo(LogLevel.LOG) >= 0) { + code.pr( + CUtil.reactionRef(reaction) + + ".name = " + + addDoubleQuotes(name + " reaction " + reaction.index) + + ";"); } - /** - * If any reaction of the specified reactor provides input - * to a contained reactor, then generate code to allocate - * memory to store the data produced by those reactions. - * The allocated memory is pointed to by a field called - * {@code _lf_containername.portname} on the self struct of the reactor. - * @param reactor The reactor. - */ - private static String deferredAllocationForEffectsOnInputs( - ReactorInstance reactor - ) { - var code = new CodeBuilder(); - // Keep track of ports already handled. There may be more than one reaction - // in the container writing to the port, but we want only one memory allocation. - var portsHandled = new HashSet(); - var reactorSelfStruct = CUtil.reactorRef(reactor); - // Find parent reactions that mention multiport inputs of this reactor. - for (ReactionInstance reaction : reactor.reactions) { - for (PortInstance effect : Iterables.filter(reaction.effects, PortInstance.class)) { - if (effect.getParent().getDepth() > reactor.getDepth() // port of a contained reactor. - && effect.isMultiport() - && !portsHandled.contains(effect) - ) { - code.pr("// A reaction writes to a multiport of a child. Allocate memory."); - portsHandled.add(effect); - code.startScopedBlock(effect.getParent()); - var portStructType = CGenerator.variableStructType(effect); - var effectRef = CUtil.portRefNestedName(effect); - code.pr(String.join("\n", - effectRef+"_width = "+effect.getWidth()+";", - "// Allocate memory to store output of reaction feeding ", - "// a multiport input of a contained reactor.", - effectRef+" = ("+portStructType+"**)_lf_allocate(", - " "+effect.getWidth()+", sizeof("+portStructType+"*),", - " &"+reactorSelfStruct+"->base.allocations); ", - "for (int i = 0; i < "+effect.getWidth()+"; i++) {", - " "+effectRef+"[i] = ("+portStructType+"*)_lf_allocate(", - " 1, sizeof("+portStructType+"),", - " &"+reactorSelfStruct+"->base.allocations); ", - "}" - )); - code.endScopedBlock(); - } - } - } - return code.toString(); + var reactorSelfStruct = CUtil.reactorRef(reaction.getParent()); + + // Count the output ports and inputs of contained reactors that + // may be set by this reaction. This ignores actions in the effects. + // Collect initialization statements for the output_produced array for the reaction + // to point to the is_present field of the appropriate output. + // These statements must be inserted after the array is malloc'd, + // but we construct them while we are counting outputs. + var outputCount = 0; + var init = new CodeBuilder(); + + init.startScopedBlock(); + init.pr("int count = 0; SUPPRESS_UNUSED_WARNING(count);"); + for (PortInstance effect : Iterables.filter(reaction.effects, PortInstance.class)) { + // Create the entry in the output_produced array for this port. + // If the port is a multiport, then we need to create an entry for each + // individual channel. + + // If the port is an input of a contained reactor, then, if that + // contained reactor is a bank, we will have to iterate over bank + // members. + var bankWidth = 1; + var portRef = ""; + if (effect.isInput()) { + init.pr("// Reaction writes to an input of a contained reactor."); + bankWidth = effect.getParent().getWidth(); + init.startScopedBlock(effect.getParent()); + portRef = CUtil.portRefNestedName(effect); + } else { + init.startScopedBlock(); + portRef = CUtil.portRefName(effect); + } + + if (effect.isMultiport()) { + // Form is slightly different for inputs vs. outputs. + var connector = "."; + if (effect.isInput()) connector = "->"; + + // Point the output_produced field to where the is_present field of the port is. + init.pr( + String.join( + "\n", + "for (int i = 0; i < " + effect.getWidth() + "; i++) {", + " " + CUtil.reactionRef(reaction) + ".output_produced[i + count]", + " = &" + portRef + "[i]" + connector + "is_present;", + "}", + "count += " + effect.getWidth() + ";")); + outputCount += effect.getWidth() * bankWidth; + } else { + // The effect is not a multiport. + init.pr( + CUtil.reactionRef(reaction) + + ".output_produced[count++] = &" + + portRef + + ".is_present;"); + outputCount += bankWidth; + } + init.endScopedBlock(); + } + init.endScopedBlock(); + code.pr( + String.join( + "\n", + "// Total number of outputs (single ports and multiport channels)", + "// produced by " + reaction + ".", + CUtil.reactionRef(reaction) + ".num_outputs = " + outputCount + ";")); + if (outputCount > 0) { + code.pr( + String.join( + "\n", + "// Allocate memory for triggers[] and triggered_sizes[] on the reaction_t", + "// struct for this reaction.", + CUtil.reactionRef(reaction) + ".triggers = (trigger_t***)_lf_allocate(", + " " + outputCount + ", sizeof(trigger_t**),", + " &" + reactorSelfStruct + "->base.allocations);", + CUtil.reactionRef(reaction) + ".triggered_sizes = (int*)_lf_allocate(", + " " + outputCount + ", sizeof(int),", + " &" + reactorSelfStruct + "->base.allocations);", + CUtil.reactionRef(reaction) + ".output_produced = (bool**)_lf_allocate(", + " " + outputCount + ", sizeof(bool*),", + " &" + reactorSelfStruct + "->base.allocations);")); } - /** - * Perform initialization functions that must be performed after - * all reactor runtime instances have been created. - * This function creates nested loops over nested banks. - * @param reactor The container. - */ - private static String deferredInitialize( - ReactorInstance reactor, - Iterable reactions, - TargetConfig targetConfig, - CTypes types - ) { - var code = new CodeBuilder(); - code.pr("// **** Start deferred initialize for "+reactor.getFullName()); - // First batch of initializations is within a for loop iterating - // over bank members for the reactor's parent. - code.startScopedBlock(reactor); - - // If the child has a multiport that is an effect of some reaction in its container, - // then we have to generate code to allocate memory for arrays pointing to - // its data. If the child is a bank, then memory is allocated for the entire - // bank width because a reaction cannot specify which bank members it writes - // to so we have to assume it can write to any. - code.pr(deferredAllocationForEffectsOnInputs( - reactor - )); - code.pr(deferredReactionMemory( - reactions, - targetConfig - )); - - // For outputs that are not primitive types (of form type* or type[]), - // create a default token on the self struct. - code.pr(deferredCreateTemplateTokens( - reactor, - types - )); - for (ReactorInstance child: reactor.children) { - code.pr(deferredInitialize( - child, - child.reactions, - targetConfig, - types) - ); + code.pr( + String.join( + "\n", + init.toString(), + "// ** End initialization for reaction " + reaction.index + " of " + name)); + return code.toString(); + } + + /** + * Generate code to allocate the memory needed by reactions for triggering downstream reactions. + * + * @param reactions A list of reactions. + */ + private static String deferredReactionMemory( + Iterable reactions, TargetConfig targetConfig) { + var code = new CodeBuilder(); + // For each reaction instance, allocate the arrays that will be used to + // trigger downstream reactions. + for (ReactionInstance reaction : reactions) { + code.pr(deferredReactionOutputs(reaction, targetConfig)); + var reactorSelfStruct = CUtil.reactorRef(reaction.getParent()); + + // Next handle triggers of the reaction that come from a multiport output + // of a contained reactor. Also, handle startup and shutdown triggers. + for (PortInstance trigger : Iterables.filter(reaction.triggers, PortInstance.class)) { + // If the port is a multiport, then we need to create an entry for each + // individual port. + if (trigger.isMultiport() && trigger.getParent() != null && trigger.isOutput()) { + // Trigger is an output of a contained reactor or bank. + code.pr( + String.join( + "\n", + "// Allocate memory to store pointers to the multiport output " + + trigger.getName() + + " ", + "// of a contained reactor " + trigger.getParent().getFullName())); + code.startScopedBlock(trigger.getParent()); + + var width = trigger.getWidth(); + var portStructType = CGenerator.variableStructType(trigger); + + code.pr( + String.join( + "\n", + CUtil.reactorRefNested(trigger.getParent()) + + "." + + trigger.getName() + + "_width = " + + width + + ";", + CUtil.reactorRefNested(trigger.getParent()) + "." + trigger.getName(), + " = (" + portStructType + "**)_lf_allocate(", + " " + width + ", sizeof(" + portStructType + "*),", + " &" + reactorSelfStruct + "->base.allocations); ")); + + code.endScopedBlock(); } - code.endScopedBlock(); - code.pr("// **** End of deferred initialize for "+reactor.getFullName()); - return code.toString(); + } + } + return code.toString(); + } + + /** + * If any reaction of the specified reactor provides input to a contained reactor, then generate + * code to allocate memory to store the data produced by those reactions. The allocated memory is + * pointed to by a field called {@code _lf_containername.portname} on the self struct of the + * reactor. + * + * @param reactor The reactor. + */ + private static String deferredAllocationForEffectsOnInputs(ReactorInstance reactor) { + var code = new CodeBuilder(); + // Keep track of ports already handled. There may be more than one reaction + // in the container writing to the port, but we want only one memory allocation. + var portsHandled = new HashSet(); + var reactorSelfStruct = CUtil.reactorRef(reactor); + // Find parent reactions that mention multiport inputs of this reactor. + for (ReactionInstance reaction : reactor.reactions) { + for (PortInstance effect : Iterables.filter(reaction.effects, PortInstance.class)) { + if (effect.getParent().getDepth() > reactor.getDepth() // port of a contained reactor. + && effect.isMultiport() + && !portsHandled.contains(effect)) { + code.pr("// A reaction writes to a multiport of a child. Allocate memory."); + portsHandled.add(effect); + code.startScopedBlock(effect.getParent()); + var portStructType = CGenerator.variableStructType(effect); + var effectRef = CUtil.portRefNestedName(effect); + code.pr( + String.join( + "\n", + effectRef + "_width = " + effect.getWidth() + ";", + "// Allocate memory to store output of reaction feeding ", + "// a multiport input of a contained reactor.", + effectRef + " = (" + portStructType + "**)_lf_allocate(", + " " + effect.getWidth() + ", sizeof(" + portStructType + "*),", + " &" + reactorSelfStruct + "->base.allocations); ", + "for (int i = 0; i < " + effect.getWidth() + "; i++) {", + " " + effectRef + "[i] = (" + portStructType + "*)_lf_allocate(", + " 1, sizeof(" + portStructType + "),", + " &" + reactorSelfStruct + "->base.allocations); ", + "}")); + code.endScopedBlock(); + } + } + } + return code.toString(); + } + + /** + * Perform initialization functions that must be performed after all reactor runtime instances + * have been created. This function creates nested loops over nested banks. + * + * @param reactor The container. + */ + private static String deferredInitialize( + ReactorInstance reactor, + Iterable reactions, + TargetConfig targetConfig, + CTypes types) { + var code = new CodeBuilder(); + code.pr("// **** Start deferred initialize for " + reactor.getFullName()); + // First batch of initializations is within a for loop iterating + // over bank members for the reactor's parent. + code.startScopedBlock(reactor); + + // If the child has a multiport that is an effect of some reaction in its container, + // then we have to generate code to allocate memory for arrays pointing to + // its data. If the child is a bank, then memory is allocated for the entire + // bank width because a reaction cannot specify which bank members it writes + // to so we have to assume it can write to any. + code.pr(deferredAllocationForEffectsOnInputs(reactor)); + code.pr(deferredReactionMemory(reactions, targetConfig)); + + // For outputs that are not primitive types (of form type* or type[]), + // create a default token on the self struct. + code.pr(deferredCreateTemplateTokens(reactor, types)); + for (ReactorInstance child : reactor.children) { + code.pr(deferredInitialize(child, child.reactions, targetConfig, types)); } + code.endScopedBlock(); + code.pr("// **** End of deferred initialize for " + reactor.getFullName()); + return code.toString(); + } } diff --git a/org.lflang/src/org/lflang/generator/c/CTypes.java b/org.lflang/src/org/lflang/generator/c/CTypes.java index 45edd59bd5..97ea482d83 100644 --- a/org.lflang/src/org/lflang/generator/c/CTypes.java +++ b/org.lflang/src/org/lflang/generator/c/CTypes.java @@ -4,162 +4,150 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; - 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 { - // Regular expression pattern for array types. - // 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(); - - public CTypes() { - } - - @Override - public boolean supportsGenerics() { - return false; - } - - @Override - public String getTargetTimeType() { - return "interval_t"; - } - - @Override - public String getTargetTagType() { - return "tag_t"; - } - - @Override - public String getTargetFixedSizeListType(String baseType, int size) { - return String.format("%s[%d]", baseType, size); - } - - @Override - public String getTargetVariableSizeListType(String baseType) { - return String.format("%s[]", baseType); - } - - @Override - public String getTargetUndefinedType() { - return "/*undefined*/"; - } - - /** - * Given a type, return a C representation of the type. Note that - * C types are very idiosyncratic. For example, {@code int[]} is not always accepted - * as a type, and {@code int*} must be used instead, except when initializing - * a variable using a static initializer, as in {@code int[] foo = {1, 2, 3};}. - * When initializing a variable using a static initializer, use - * {@link #getVariableDeclaration(TypeParameterizedReactor, InferredType, String, boolean)} instead. - * @param type The type. - */ - @Override - public String getTargetType(InferredType type) { - var result = TargetTypes.super.getTargetType(type); - Matcher matcher = arrayPattern.matcher(result); - if (matcher.find()) { - return matcher.group(1) + '*'; - } - 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 - * the array syntax {@code [size]} preferred over the pointer syntax - * {@code *} (if applicable). This also includes the variable name - * because C requires the array type specification to be placed after - * the variable name rather than with the type. - * The result is of the form {@code type variable_name[size]} or - * {@code type variable_name} depending on whether the given type - * is an array type, unless the array type has no size (it is given - * as {@code []}. In that case, the returned form depends on the - * third argument, initializer. If true, the then the returned - * declaration will have the form {@code type variable_name[]}, - * and otherwise it will have the form {@code type* variable_name}. - * @param type The type. - * @param variableName The name of the variable. - * @param initializer True to return a form usable in a static initializer. - */ - public String getVariableDeclaration( - TypeParameterizedReactor tpr, - InferredType type, - String variableName, - boolean initializer - ) { - String t = TargetTypes.super.getTargetType(tpr.resolveType(type)); - Matcher matcher = arrayPattern.matcher(t); - String declaration = String.format("%s %s", t, variableName); - if (matcher.find()) { - // For array types, we have to move the [] - // because C is very picky about where this goes. It has to go - // after the variable name. Also, in an initializer, it has to have - // form [], and in a struct definition, it has to use *. - if (matcher.group(2).equals("") && !initializer) { - declaration = String.format("%s* %s", - matcher.group(1), variableName); - } else { - declaration = String.format("%s %s[%s]", - matcher.group(1), variableName, matcher.group(2)); - } - } - 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(); - } - }; - } + // Regular expression pattern for array types. + // 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(); + + public CTypes() {} + + @Override + public boolean supportsGenerics() { + return false; + } + + @Override + public String getTargetTimeType() { + return "interval_t"; + } + + @Override + public String getTargetTagType() { + return "tag_t"; + } + + @Override + public String getTargetFixedSizeListType(String baseType, int size) { + return String.format("%s[%d]", baseType, size); + } + + @Override + public String getTargetVariableSizeListType(String baseType) { + return String.format("%s[]", baseType); + } + + @Override + public String getTargetUndefinedType() { + return "/*undefined*/"; + } + + /** + * Given a type, return a C representation of the type. Note that C types are very idiosyncratic. + * For example, {@code int[]} is not always accepted as a type, and {@code int*} must be used + * instead, except when initializing a variable using a static initializer, as in {@code int[] foo + * = {1, 2, 3};}. When initializing a variable using a static initializer, use {@link + * #getVariableDeclaration(TypeParameterizedReactor, InferredType, String, boolean)} instead. + * + * @param type The type. + */ + @Override + public String getTargetType(InferredType type) { + var result = TargetTypes.super.getTargetType(type); + Matcher matcher = arrayPattern.matcher(result); + if (matcher.find()) { + return matcher.group(1) + '*'; + } + 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 the array syntax {@code [size]} preferred + * over the pointer syntax {@code *} (if applicable). This also includes the variable name because + * C requires the array type specification to be placed after the variable name rather than with + * the type. The result is of the form {@code type variable_name[size]} or {@code type + * variable_name} depending on whether the given type is an array type, unless the array type has + * no size (it is given as {@code []}. In that case, the returned form depends on the third + * argument, initializer. If true, the then the returned declaration will have the form {@code + * type variable_name[]}, and otherwise it will have the form {@code type* variable_name}. + * + * @param type The type. + * @param variableName The name of the variable. + * @param initializer True to return a form usable in a static initializer. + */ + public String getVariableDeclaration( + TypeParameterizedReactor tpr, InferredType type, String variableName, boolean initializer) { + String t = TargetTypes.super.getTargetType(tpr.resolveType(type)); + Matcher matcher = arrayPattern.matcher(t); + String declaration = String.format("%s %s", t, variableName); + if (matcher.find()) { + // For array types, we have to move the [] + // because C is very picky about where this goes. It has to go + // after the variable name. Also, in an initializer, it has to have + // form [], and in a struct definition, it has to use *. + if (matcher.group(2).equals("") && !initializer) { + declaration = String.format("%s* %s", matcher.group(1), variableName); + } else { + declaration = String.format("%s %s[%s]", matcher.group(1), variableName, matcher.group(2)); + } + } + 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/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index dbb365e189..3a7f151a2c 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -1,28 +1,28 @@ /* Utilities for C code generation. */ /************* -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; @@ -32,12 +32,10 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.List; import java.util.Objects; import java.util.stream.Collectors; - import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.InferredType; import org.lflang.TargetConfig; - import org.lflang.generator.ActionInstance; import org.lflang.generator.GeneratorCommandFactory; import org.lflang.generator.LFGeneratorContext; @@ -55,751 +53,741 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.util.LFCommand; /** - * A collection of utilities for C code generation. - * This class codifies the coding conventions for the C target code generator. - * I.e., it defines how variables are named and referenced. + * A collection of utilities for C code generation. This class codifies the coding conventions for + * the C target code generator. I.e., it defines how variables are named and referenced. + * * @author Edward A. Lee */ public class CUtil { - /** - * Suffix that when appended to the name of a federated reactor yields - * the name of its corresponding RTI executable. - */ - public static final String RTI_BIN_SUFFIX = "_RTI"; - - /** - * Suffix that when appended to the name of a federated reactor yields - * the name of its corresponding distribution script. - */ - public static final String RTI_DISTRIBUTION_SCRIPT_SUFFIX = "_distribute.sh"; - - ////////////////////////////////////////////////////// - //// Public methods. - - /** - * Return a reference to the action struct of the specified - * action instance. This action_base_t struct is on the self struct. - * @param instance The action instance. - * @param runtimeIndex An optional index variable name to use to address runtime instances. - */ - public static String actionRef(ActionInstance instance, String runtimeIndex) { - return reactorRef(instance.getParent(), runtimeIndex) - + "->_lf_" - + instance.getName(); - } - - /** - * Return a default name of a variable to refer to the bank index of a reactor - * in a bank. This is has the form uniqueID_i where uniqueID - * is an identifier for the instance that is guaranteed to be different - * from the ID of any other instance in the program. - * If the instance is not a bank, return "0". - * @param instance A reactor instance. - */ - public static String bankIndex(ReactorInstance instance) { - if (!instance.isBank()) return "0"; - return bankIndexName(instance); - } - - /** - * Return a default name of a variable to refer to the bank index of a reactor - * in a bank. This is has the form uniqueID_i where uniqueID - * is an identifier for the instance that is guaranteed to be different - * from the ID of any other instance in the program. - * @param instance A reactor instance. - */ - public static String bankIndexName(ReactorInstance instance) { - return instance.uniqueID() + "_i"; - } - - /** - * Return a default name of a variable to refer to the channel index of a port - * in a bank. This has the form uniqueID_c where uniqueID - * is an identifier for the instance that is guaranteed to be different - * from the ID of any other instance in the program. - * If the port is not a multiport, then return the string "0". - */ - public static String channelIndex(PortInstance port) { - if (!port.isMultiport()) return "0"; - return channelIndexName(port); - } - - /** - * Return a default name of a variable to refer to the channel index of a port - * in a bank. This is has the form uniqueID_c where uniqueID - * is an identifier for the instance that is guaranteed to be different - * from the ID of any other instance in the program. - */ - public static String channelIndexName(PortInstance port) { - return port.uniqueID() + "_c"; - } - - /** - * Return the name of the reactor. A {@code _main} is appended to the name if the - * reactor is main (to allow for instantiations that have the same name as - * the main reactor or the .lf file). - */ - public static String getName(TypeParameterizedReactor reactor) { - String name = reactor.reactor().getName().toLowerCase() + reactor.hashCode(); - if (reactor.reactor().isMain()) { - return name + "_main"; - } - return name; - } - - /** - * Return a reference to the specified port. - * - * The returned string will have one of the following forms: - *

    - *
  • {@code selfStructs[k]->_lf_portName}
  • - *
  • {@code selfStructs[k]->_lf_portName}
  • - *
  • {@code selfStructs[k]->_lf_portName[i]}
  • - *
  • {@code selfStructs[k]->_lf_parent.portName}
  • - *
  • {@code selfStructs[k]->_lf_parent.portName[i]}
  • - *
  • {@code selfStructs[k]->_lf_parent[j].portName}
  • - *
  • {@code selfStructs[k]->_lf_parent[j].portName[i]}
  • - *
- * - * where {@code k} is the runtime index of either the port's parent - * or the port's parent's parent, the latter when isNested is {@code true}. - * The index {@code j} is present if the parent is a bank, and - * the index {@code i} is present if the port is a multiport. - * - * The first two forms are used if isNested is false, - * and the remaining four are used if isNested is true. - * Set {@code isNested} to {@code true} when referencing a port belonging - * to a contained reactor. - * - * @param port The port. - * @param isNested True to return a reference relative to the parent's parent. - * @param includeChannelIndex True to include the channel index at the end. - * @param runtimeIndex A variable name to use to index the runtime instance or - * null to use the default, the string returned by {@link CUtil#runtimeIndex(ReactorInstance)}. - * @param bankIndex A variable name to use to index the bank or null to use the - * default, the string returned by {@link CUtil#bankIndex(ReactorInstance)}. - * @param channelIndex A variable name to use to index the channel or null to - * use the default, the string returned by {@link CUtil#channelIndex(PortInstance)}. - */ - public static String portRef( - PortInstance port, - boolean isNested, - boolean includeChannelIndex, - String runtimeIndex, - String bankIndex, - String channelIndex - ) { - String channel = ""; - if (channelIndex == null) channelIndex = channelIndex(port); - if (port.isMultiport() && includeChannelIndex) { - channel = "[" + channelIndex + "]"; - } - if (isNested) { - return reactorRefNested(port.getParent(), runtimeIndex, bankIndex) + "." + port.getName() + channel; - } else { - String sourceStruct = CUtil.reactorRef(port.getParent(), runtimeIndex); - return sourceStruct + "->_lf_" + port.getName() + channel; - } - } - - /** - * Return a reference to the port on the self struct of the - * port's parent. This is used when an input port triggers a reaction - * in the port's parent or when an output port is written to by - * a reaction in the port's parent. - * This is equivalent to calling {@code portRef(port, false, true, null, null)}. - * @param port An instance of the port to be referenced. - */ - public static String portRef(PortInstance port) { - return portRef(port, false, true, null, null, null); - } - - /** - * Return a reference to the port on the self struct of the - * port's parent using the specified index variables. - * This is used when an input port triggers a reaction - * in the port's parent or when an output port is written to by - * a reaction in the port's parent. - * This is equivalent to calling {@code portRef(port, false, true, bankIndex, channelIndex)}. - * @param port An instance of the port to be referenced. - * @param runtimeIndex A variable name to use to index the runtime instance or - * null to use the default, the string returned by {@link CUtil#runtimeIndex(ReactorInstance)}. - * @param bankIndex A variable name to use to index the bank or null to use the - * default, the string returned by {@link CUtil#bankIndex(ReactorInstance)}. - * @param channelIndex A variable name to use to index the channel or null to - * use the default, the string returned by {@link CUtil#channelIndex(PortInstance)}. - */ - public static String portRef( - PortInstance port, String runtimeIndex, String bankIndex, String channelIndex - ) { - return portRef(port, false, true, runtimeIndex, bankIndex, channelIndex); - } - - /** - * Return a reference to a port without any channel indexing. - * This is useful for deriving a reference to the _width variable. - * @param port An instance of the port to be referenced. - */ - public static String portRefName(PortInstance port) { - return portRef(port, false, false, null, null, null); - } - - /** - * Return the portRef without the channel indexing. - * This is useful for deriving a reference to the _width variable. - * - * @param port An instance of the port to be referenced. - * @param runtimeIndex A variable name to use to index the runtime instance or - * null to use the default, the string returned by {@link CUtil#runtimeIndex(ReactorInstance)}. - * @param bankIndex A variable name to use to index the bank or null to use the - * default, the string returned by {@link CUtil#bankIndex(ReactorInstance)}. - * @param channelIndex A variable name to use to index the channel or null to - * use the default, the string returned by {@link CUtil#channelIndex(PortInstance)}. - */ - public static String portRefName( - PortInstance port, String runtimeIndex, String bankIndex, String channelIndex - ) { - return portRef(port, false, false, runtimeIndex, bankIndex, channelIndex); - } - - /** - * Return a port reference to a port on the self struct of the - * parent of the port's parent. This is used when an input port - * is written to by a reaction in the parent of the port's parent, - * or when an output port triggers a reaction in the parent of the - * port's parent. This is equivalent to calling - * {@code portRef(port, true, true, null, null, null)}. - * - * @param port The port. - */ - public static String portRefNested(PortInstance port) { - return portRef(port, true, true, null, null, null); - } - - /** - * Return a reference to the port on the self struct of the - * parent of the port's parent. This is used when an input port - * is written to by a reaction in the parent of the port's parent, - * or when an output port triggers a reaction in the parent of the - * port's parent. This is equivalent to calling - * {@code portRef(port, true, true, runtimeIndex, bankIndex, channelIndex)}. - * - * @param port The port. - * @param runtimeIndex A variable name to use to index the runtime instance or - * null to use the default, the string returned by {@link CUtil#runtimeIndex(ReactorInstance)}. - * @param bankIndex A variable name to use to index the bank or null to use the - * default, the string returned by {@link CUtil#bankIndex(ReactorInstance)}. - * @param channelIndex A variable name to use to index the channel or null to - * use the default, the string returned by {@link CUtil#channelIndex(PortInstance)}. - */ - public static String portRefNested( - PortInstance port, String runtimeIndex, String bankIndex, String channelIndex - ) { - return portRef(port, true, true, runtimeIndex, bankIndex, channelIndex); - } - - /** - * Return a reference to the port on the self struct of the - * parent of the port's parent, but without the channel indexing, - * even if it is a multiport. This is used when an input port - * is written to by a reaction in the parent of the port's parent, - * or when an output port triggers a reaction in the parent of the - * port's parent. - * This is equivalent to calling {@code portRef(port, true, false, null, null, null)}. - * - * @param port The port. - */ - public static String portRefNestedName(PortInstance port) { - return portRef(port, true, false, null, null, null); - } - - /** - * Return a reference to the port on the self struct of the - * parent of the port's parent, but without the channel indexing, - * even if it is a multiport. This is used when an input port - * is written to by a reaction in the parent of the port's parent, - * or when an output port triggers a reaction in the parent of the - * port's parent. This is equivalent to calling - * {@code portRefNested(port, true, false, runtimeIndex, bankIndex, channelIndex)}. - * - * @param port The port. - * @param runtimeIndex A variable name to use to index the runtime instance or - * null to use the default, the string returned by {@link CUtil#runtimeIndex(ReactorInstance)}. - * @param bankIndex A variable name to use to index the bank or null to use the - * default, the string returned by {@link CUtil#bankIndex(ReactorInstance)}. - * @param channelIndex A variable name to use to index the channel or null to - * use the default, the string returned by {@link CUtil#channelIndex(PortInstance)}. - */ - public static String portRefNestedName( - PortInstance port, String runtimeIndex, String bankIndex, String channelIndex - ) { - return portRef(port, true, false, runtimeIndex, bankIndex, channelIndex); - } - - /** - * Return code for referencing a port within a reaction body possibly indexed by - * a bank index and/or a multiport index. If the provided reference is - * not a port, then this returns the string "ERROR: not a port." - * @param reference The reference to the port. - * @param bankIndex A bank index or null or negative if not in a bank. - * @param multiportIndex A multiport index or null or negative if not in a multiport. - */ - public static String portRefInReaction(VarRef reference, Integer bankIndex, Integer multiportIndex) { - if (!(reference.getVariable() instanceof Port)) { - return "ERROR: not a port."; // FIXME: This is not the fail-fast approach, and it seems arbitrary. - } - var prefix = ""; - if (reference.getContainer() != null) { - var bank = ""; - if (reference.getContainer().getWidthSpec() != null && bankIndex != null && bankIndex >= 0) { - bank = "[" + bankIndex + "]"; - } - prefix = reference.getContainer().getName() + bank + "."; - } - var multiport = ""; - if (((Port) reference.getVariable()).getWidthSpec() != null && multiportIndex != null && multiportIndex >= 0) { - multiport = "[" + multiportIndex + "]"; + /** + * Suffix that when appended to the name of a federated reactor yields the name of its + * corresponding RTI executable. + */ + public static final String RTI_BIN_SUFFIX = "_RTI"; + + /** + * Suffix that when appended to the name of a federated reactor yields the name of its + * corresponding distribution script. + */ + public static final String RTI_DISTRIBUTION_SCRIPT_SUFFIX = "_distribute.sh"; + + ////////////////////////////////////////////////////// + //// Public methods. + + /** + * Return a reference to the action struct of the specified action instance. This action_base_t + * struct is on the self struct. + * + * @param instance The action instance. + * @param runtimeIndex An optional index variable name to use to address runtime instances. + */ + public static String actionRef(ActionInstance instance, String runtimeIndex) { + return reactorRef(instance.getParent(), runtimeIndex) + "->_lf_" + instance.getName(); + } + + /** + * Return a default name of a variable to refer to the bank index of a reactor in a bank. This is + * has the form uniqueID_i where uniqueID is an identifier for the instance that is guaranteed to + * be different from the ID of any other instance in the program. If the instance is not a bank, + * return "0". + * + * @param instance A reactor instance. + */ + public static String bankIndex(ReactorInstance instance) { + if (!instance.isBank()) return "0"; + return bankIndexName(instance); + } + + /** + * Return a default name of a variable to refer to the bank index of a reactor in a bank. This is + * has the form uniqueID_i where uniqueID is an identifier for the instance that is guaranteed to + * be different from the ID of any other instance in the program. + * + * @param instance A reactor instance. + */ + public static String bankIndexName(ReactorInstance instance) { + return instance.uniqueID() + "_i"; + } + + /** + * Return a default name of a variable to refer to the channel index of a port in a bank. This has + * the form uniqueID_c where uniqueID is an identifier for the instance that is guaranteed to be + * different from the ID of any other instance in the program. If the port is not a multiport, + * then return the string "0". + */ + public static String channelIndex(PortInstance port) { + if (!port.isMultiport()) return "0"; + return channelIndexName(port); + } + + /** + * Return a default name of a variable to refer to the channel index of a port in a bank. This is + * has the form uniqueID_c where uniqueID is an identifier for the instance that is guaranteed to + * be different from the ID of any other instance in the program. + */ + public static String channelIndexName(PortInstance port) { + return port.uniqueID() + "_c"; + } + + /** + * Return the name of the reactor. A {@code _main} is appended to the name if the reactor is main + * (to allow for instantiations that have the same name as the main reactor or the .lf file). + */ + public static String getName(TypeParameterizedReactor reactor) { + String name = reactor.reactor().getName().toLowerCase() + reactor.hashCode(); + if (reactor.reactor().isMain()) { + return name + "_main"; + } + return name; + } + + /** + * Return a reference to the specified port. + * + *

The returned string will have one of the following forms: + * + *

    + *
  • {@code selfStructs[k]->_lf_portName} + *
  • {@code selfStructs[k]->_lf_portName} + *
  • {@code selfStructs[k]->_lf_portName[i]} + *
  • {@code selfStructs[k]->_lf_parent.portName} + *
  • {@code selfStructs[k]->_lf_parent.portName[i]} + *
  • {@code selfStructs[k]->_lf_parent[j].portName} + *
  • {@code selfStructs[k]->_lf_parent[j].portName[i]} + *
+ * + * where {@code k} is the runtime index of either the port's parent or the port's parent's parent, + * the latter when isNested is {@code true}. The index {@code j} is present if the parent is a + * bank, and the index {@code i} is present if the port is a multiport. + * + *

The first two forms are used if isNested is false, and the remaining four are used if + * isNested is true. Set {@code isNested} to {@code true} when referencing a port belonging to a + * contained reactor. + * + * @param port The port. + * @param isNested True to return a reference relative to the parent's parent. + * @param includeChannelIndex True to include the channel index at the end. + * @param runtimeIndex A variable name to use to index the runtime instance or null to use the + * default, the string returned by {@link CUtil#runtimeIndex(ReactorInstance)}. + * @param bankIndex A variable name to use to index the bank or null to use the default, the + * string returned by {@link CUtil#bankIndex(ReactorInstance)}. + * @param channelIndex A variable name to use to index the channel or null to use the default, the + * string returned by {@link CUtil#channelIndex(PortInstance)}. + */ + public static String portRef( + PortInstance port, + boolean isNested, + boolean includeChannelIndex, + String runtimeIndex, + String bankIndex, + String channelIndex) { + String channel = ""; + if (channelIndex == null) channelIndex = channelIndex(port); + if (port.isMultiport() && includeChannelIndex) { + channel = "[" + channelIndex + "]"; + } + if (isNested) { + return reactorRefNested(port.getParent(), runtimeIndex, bankIndex) + + "." + + port.getName() + + channel; + } else { + String sourceStruct = CUtil.reactorRef(port.getParent(), runtimeIndex); + return sourceStruct + "->_lf_" + port.getName() + channel; + } + } + + /** + * Return a reference to the port on the self struct of the port's parent. This is used when an + * input port triggers a reaction in the port's parent or when an output port is written to by a + * reaction in the port's parent. This is equivalent to calling {@code portRef(port, false, true, + * null, null)}. + * + * @param port An instance of the port to be referenced. + */ + public static String portRef(PortInstance port) { + return portRef(port, false, true, null, null, null); + } + + /** + * Return a reference to the port on the self struct of the port's parent using the specified + * index variables. This is used when an input port triggers a reaction in the port's parent or + * when an output port is written to by a reaction in the port's parent. This is equivalent to + * calling {@code portRef(port, false, true, bankIndex, channelIndex)}. + * + * @param port An instance of the port to be referenced. + * @param runtimeIndex A variable name to use to index the runtime instance or null to use the + * default, the string returned by {@link CUtil#runtimeIndex(ReactorInstance)}. + * @param bankIndex A variable name to use to index the bank or null to use the default, the + * string returned by {@link CUtil#bankIndex(ReactorInstance)}. + * @param channelIndex A variable name to use to index the channel or null to use the default, the + * string returned by {@link CUtil#channelIndex(PortInstance)}. + */ + public static String portRef( + PortInstance port, String runtimeIndex, String bankIndex, String channelIndex) { + return portRef(port, false, true, runtimeIndex, bankIndex, channelIndex); + } + + /** + * Return a reference to a port without any channel indexing. This is useful for deriving a + * reference to the _width variable. + * + * @param port An instance of the port to be referenced. + */ + public static String portRefName(PortInstance port) { + return portRef(port, false, false, null, null, null); + } + + /** + * Return the portRef without the channel indexing. This is useful for deriving a reference to the + * _width variable. + * + * @param port An instance of the port to be referenced. + * @param runtimeIndex A variable name to use to index the runtime instance or null to use the + * default, the string returned by {@link CUtil#runtimeIndex(ReactorInstance)}. + * @param bankIndex A variable name to use to index the bank or null to use the default, the + * string returned by {@link CUtil#bankIndex(ReactorInstance)}. + * @param channelIndex A variable name to use to index the channel or null to use the default, the + * string returned by {@link CUtil#channelIndex(PortInstance)}. + */ + public static String portRefName( + PortInstance port, String runtimeIndex, String bankIndex, String channelIndex) { + return portRef(port, false, false, runtimeIndex, bankIndex, channelIndex); + } + + /** + * Return a port reference to a port on the self struct of the parent of the port's parent. This + * is used when an input port is written to by a reaction in the parent of the port's parent, or + * when an output port triggers a reaction in the parent of the port's parent. This is equivalent + * to calling {@code portRef(port, true, true, null, null, null)}. + * + * @param port The port. + */ + public static String portRefNested(PortInstance port) { + return portRef(port, true, true, null, null, null); + } + + /** + * Return a reference to the port on the self struct of the parent of the port's parent. This is + * used when an input port is written to by a reaction in the parent of the port's parent, or when + * an output port triggers a reaction in the parent of the port's parent. This is equivalent to + * calling {@code portRef(port, true, true, runtimeIndex, bankIndex, channelIndex)}. + * + * @param port The port. + * @param runtimeIndex A variable name to use to index the runtime instance or null to use the + * default, the string returned by {@link CUtil#runtimeIndex(ReactorInstance)}. + * @param bankIndex A variable name to use to index the bank or null to use the default, the + * string returned by {@link CUtil#bankIndex(ReactorInstance)}. + * @param channelIndex A variable name to use to index the channel or null to use the default, the + * string returned by {@link CUtil#channelIndex(PortInstance)}. + */ + public static String portRefNested( + PortInstance port, String runtimeIndex, String bankIndex, String channelIndex) { + return portRef(port, true, true, runtimeIndex, bankIndex, channelIndex); + } + + /** + * Return a reference to the port on the self struct of the parent of the port's parent, but + * without the channel indexing, even if it is a multiport. This is used when an input port is + * written to by a reaction in the parent of the port's parent, or when an output port triggers a + * reaction in the parent of the port's parent. This is equivalent to calling {@code portRef(port, + * true, false, null, null, null)}. + * + * @param port The port. + */ + public static String portRefNestedName(PortInstance port) { + return portRef(port, true, false, null, null, null); + } + + /** + * Return a reference to the port on the self struct of the parent of the port's parent, but + * without the channel indexing, even if it is a multiport. This is used when an input port is + * written to by a reaction in the parent of the port's parent, or when an output port triggers a + * reaction in the parent of the port's parent. This is equivalent to calling {@code + * portRefNested(port, true, false, runtimeIndex, bankIndex, channelIndex)}. + * + * @param port The port. + * @param runtimeIndex A variable name to use to index the runtime instance or null to use the + * default, the string returned by {@link CUtil#runtimeIndex(ReactorInstance)}. + * @param bankIndex A variable name to use to index the bank or null to use the default, the + * string returned by {@link CUtil#bankIndex(ReactorInstance)}. + * @param channelIndex A variable name to use to index the channel or null to use the default, the + * string returned by {@link CUtil#channelIndex(PortInstance)}. + */ + public static String portRefNestedName( + PortInstance port, String runtimeIndex, String bankIndex, String channelIndex) { + return portRef(port, true, false, runtimeIndex, bankIndex, channelIndex); + } + + /** + * Return code for referencing a port within a reaction body possibly indexed by a bank index + * and/or a multiport index. If the provided reference is not a port, then this returns the string + * "ERROR: not a port." + * + * @param reference The reference to the port. + * @param bankIndex A bank index or null or negative if not in a bank. + * @param multiportIndex A multiport index or null or negative if not in a multiport. + */ + public static String portRefInReaction( + VarRef reference, Integer bankIndex, Integer multiportIndex) { + if (!(reference.getVariable() instanceof Port)) { + return "ERROR: not a port."; // FIXME: This is not the fail-fast approach, and it seems + // arbitrary. + } + var prefix = ""; + if (reference.getContainer() != null) { + var bank = ""; + if (reference.getContainer().getWidthSpec() != null && bankIndex != null && bankIndex >= 0) { + bank = "[" + bankIndex + "]"; + } + prefix = reference.getContainer().getName() + bank + "."; + } + var multiport = ""; + if (((Port) reference.getVariable()).getWidthSpec() != null + && multiportIndex != null + && multiportIndex >= 0) { + multiport = "[" + multiportIndex + "]"; + } + return prefix + reference.getVariable().getName() + multiport; + } + + /** + * Return a reference to the reaction entry on the self struct of the parent of the specified + * reaction. + * + * @param reaction The reaction. + */ + public static String reactionRef(ReactionInstance reaction) { + return reactionRef(reaction, null); + } + + /** + * Return a reference to the reaction entry on the self struct of the parent of the specified + * reaction. + * + * @param reaction The reaction. + * @param runtimeIndex An index into the array of self structs for the parent. + */ + public static String reactionRef(ReactionInstance reaction, String runtimeIndex) { + return reactorRef(reaction.getParent(), runtimeIndex) + "->_lf__reaction_" + reaction.index; + } + + /** + * Return a reference to the "self" struct of the specified reactor instance. The returned string + * has the form self[j], where self is the name of the array of self structs for this reactor + * instance and j is the expression returned by {@link #runtimeIndex(ReactorInstance)} or 0 if + * there are no banks. + * + * @param instance The reactor instance. + */ + public static String reactorRef(ReactorInstance instance) { + return reactorRef(instance, null); + } + + /** + * Return the name of the array of "self" structs of the specified reactor instance. This is + * similar to {@link #reactorRef(ReactorInstance)} except that it does not index into the array. + * + * @param instance The reactor instance. + */ + public static String reactorRefName(ReactorInstance instance) { + return instance.uniqueID() + "_self"; + } + + /** + * Return a reference to the "self" struct of the specified reactor instance. The returned string + * has the form self[runtimeIndex], where self is the name of the array of self structs for this + * reactor instance. If runtimeIndex is null, then it is replaced by the expression returned by + * {@link #runtimeIndex(ReactorInstance)} or 0 if there are no banks. + * + * @param instance The reactor instance. + * @param runtimeIndex An optional expression to use to address bank members. If this is null, the + * expression used will be that returned by {@link #runtimeIndex(ReactorInstance)}. + */ + public static String reactorRef(ReactorInstance instance, String runtimeIndex) { + if (runtimeIndex == null) runtimeIndex = runtimeIndex(instance); + return reactorRefName(instance) + "[" + runtimeIndex + "]"; + } + + /** + * For situations where a reaction reacts to or reads from an output of a contained reactor or + * sends to an input of a contained reactor, then the container's self struct will have a field + * (or an array of fields if the contained reactor is a bank) that is a struct with fields + * corresponding to those inputs and outputs. This method returns a reference to that struct or + * array of structs. Note that the returned reference is not to the self struct of the contained + * reactor. Use {@link #reactorRef(ReactorInstance)} for that. + * + * @param reactor The contained reactor. + */ + public static String reactorRefNested(ReactorInstance reactor) { + return reactorRefNested(reactor, null, null); + } + + /** + * For situations where a reaction reacts to or reads from an output of a contained reactor or + * sends to an input of a contained reactor, then the container's self struct will have a field + * (or an array of fields if the contained reactor is a bank) that is a struct with fields + * corresponding to those inputs and outputs. This method returns a reference to that struct or + * array of structs. Note that the returned reference is not to the self struct of the contained + * reactor. Use {@link CUtil#reactorRef(ReactorInstance)} for that. + * + * @param reactor The contained reactor. + * @param runtimeIndex A variable name to use to index the runtime instance or null to use the + * default, the string returned by {@link CUtil#runtimeIndex(ReactorInstance)}. + * @param bankIndex A variable name to use to index the bank or null to use the default, the + * string returned by {@link CUtil#bankIndex(ReactorInstance)}. + */ + public static String reactorRefNested( + ReactorInstance reactor, String runtimeIndex, String bankIndex) { + String result = reactorRef(reactor.getParent(), runtimeIndex) + "->_lf_" + reactor.getName(); + if (reactor.isBank()) { + // Need the bank index not the runtimeIndex. + if (bankIndex == null) bankIndex = bankIndex(reactor); + result += "[" + bankIndex + "]"; + } + return result; + } + + /** + * Return an expression that, when evaluated, gives the index of a runtime instance of the + * specified ReactorInstance. If the reactor is not within any banks, then this will return "0". + * Otherwise, it will return an expression that evaluates a mixed-radix number d0%w0, d1%w1, ... , + * dn%wn, where n is the depth minus one of the reactor. The radixes, w0 to wn, are the widths of + * this reactor, its parent reactor, on up to the top-level reactor. Since the top-level reactor + * is never a bank, dn = 0 and wn = 1. The digits, di, are either 0 (of the parent is not a bank) + * or the variable name returned by {@link #bankIndexName(ReactorInstance)} if the parent is a + * bank. The returned expression, when evaluated, will yield the following value: + * + *

+   *     d0 + w0 * (d1 + w1 * ( ... (dn-1 + wn-1 * dn) ... )
+   * 
+ * + * @param reactor The reactor. + */ + public static String runtimeIndex(ReactorInstance reactor) { + StringBuilder result = new StringBuilder(); + int width = 0; + int parens = 0; + while (reactor != null) { + if (reactor.isBank() && reactor.getWidth() > 1) { + if (width > 0) { + result.append(" + " + width + " * ("); + parens++; } - return prefix + reference.getVariable().getName() + multiport; - } - - /** - * Return a reference to the reaction entry on the self struct - * of the parent of the specified reaction. - * @param reaction The reaction. - */ - public static String reactionRef(ReactionInstance reaction) { - return reactionRef(reaction, null); - } - - /** - * Return a reference to the reaction entry on the self struct - * of the parent of the specified reaction. - * @param reaction The reaction. - * @param runtimeIndex An index into the array of self structs for the parent. - */ - public static String reactionRef(ReactionInstance reaction, String runtimeIndex) { - return reactorRef(reaction.getParent(), runtimeIndex) - + "->_lf__reaction_" + reaction.index; - } - - /** - * Return a reference to the "self" struct of the specified - * reactor instance. The returned string has the form - * self[j], where self is the name of the array of self structs - * for this reactor instance and j is the expression returned - * by {@link #runtimeIndex(ReactorInstance)} or 0 if there are no banks. - * @param instance The reactor instance. - */ - public static String reactorRef(ReactorInstance instance) { - return reactorRef(instance, null); - } - - /** - * Return the name of the array of "self" structs of the specified - * reactor instance. This is similar to {@link #reactorRef(ReactorInstance)} - * except that it does not index into the array. - * @param instance The reactor instance. - */ - public static String reactorRefName(ReactorInstance instance) { - return instance.uniqueID() + "_self"; - } - - /** - * Return a reference to the "self" struct of the specified - * reactor instance. The returned string has the form - * self[runtimeIndex], where self is the name of the array of self structs - * for this reactor instance. If runtimeIndex is null, then it is replaced by - * the expression returned - * by {@link #runtimeIndex(ReactorInstance)} or 0 if there are no banks. - * @param instance The reactor instance. - * @param runtimeIndex An optional expression to use to address bank members. - * If this is null, the expression used will be that returned by - * {@link #runtimeIndex(ReactorInstance)}. - */ - public static String reactorRef(ReactorInstance instance, String runtimeIndex) { - if (runtimeIndex == null) runtimeIndex = runtimeIndex(instance); - return reactorRefName(instance) + "[" + runtimeIndex + "]"; - } - - /** - * For situations where a reaction reacts to or reads from an output - * of a contained reactor or sends to an input of a contained reactor, - * then the container's self struct will have a field - * (or an array of fields if the contained reactor is a bank) that is - * a struct with fields corresponding to those inputs and outputs. - * This method returns a reference to that struct or array of structs. - * Note that the returned reference is not to the self struct of the - * contained reactor. Use {@link #reactorRef(ReactorInstance)} for that. - * - * @param reactor The contained reactor. - */ - public static String reactorRefNested(ReactorInstance reactor) { - return reactorRefNested(reactor, null, null); - } - - /** - * For situations where a reaction reacts to or reads from an output - * of a contained reactor or sends to an input of a contained reactor, - * then the container's self struct will have a field - * (or an array of fields if the contained reactor is a bank) that is - * a struct with fields corresponding to those inputs and outputs. - * This method returns a reference to that struct or array of structs. - * Note that the returned reference is not to the self struct of the - * contained reactor. Use {@link CUtil#reactorRef(ReactorInstance)} for that. - * - * @param reactor The contained reactor. - * @param runtimeIndex A variable name to use to index the runtime instance or - * null to use the default, the string returned by {@link CUtil#runtimeIndex(ReactorInstance)}. - * @param bankIndex A variable name to use to index the bank or null to use the - * default, the string returned by {@link CUtil#bankIndex(ReactorInstance)}. - */ - public static String reactorRefNested(ReactorInstance reactor, String runtimeIndex, String bankIndex) { - String result = reactorRef(reactor.getParent(), runtimeIndex) + "->_lf_" + reactor.getName(); - if (reactor.isBank()) { - // Need the bank index not the runtimeIndex. - if (bankIndex == null) bankIndex = bankIndex(reactor); - result += "[" + bankIndex + "]"; - } - return result; - } - - /** - * Return an expression that, when evaluated, gives the index of - * a runtime instance of the specified ReactorInstance. If the reactor - * is not within any banks, then this will return "0". Otherwise, it - * will return an expression that evaluates a mixed-radix number - * d0%w0, d1%w1, ... , dn%wn, where n is the depth minus one of the - * reactor. The radixes, w0 to wn, are the widths of this reactor, - * its parent reactor, on up to the top-level reactor. Since the top-level - * reactor is never a bank, dn = 0 and wn = 1. The digits, di, are - * either 0 (of the parent is not a bank) or the variable name returned - * by {@link #bankIndexName(ReactorInstance)} if the parent is a bank. - * The returned expression, when evaluated, will yield the following value: - * - *
-     *     d0 + w0 * (d1 + w1 * ( ... (dn-1 + wn-1 * dn) ... )
-     * 
- * - * @param reactor The reactor. - */ - public static String runtimeIndex(ReactorInstance reactor) { - StringBuilder result = new StringBuilder(); - int width = 0; - int parens = 0; - while (reactor != null) { - if (reactor.isBank() && reactor.getWidth() > 1) { - if (width > 0) { - result.append(" + " + width + " * ("); - parens++; - } - result.append(bankIndexName(reactor)); - width = reactor.getWidth(); - } - reactor = reactor.getParent(); - } - while (parens-- > 0) { - result.append(")"); - } - if (result.length() == 0) return "0"; - return result.toString(); - } - - /** - * Return a unique type for the "self" struct of the specified - * reactor class from the reactor class. - * @param reactor The reactor class. - * @return The type of a self struct for the specified reactor class. - */ - public static String selfType(TypeParameterizedReactor reactor) { - if (reactor.reactor().isMain()) { - return "_" + CUtil.getName(reactor) + "_main_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.tpr); - } - - /** - * Return a reference to the trigger_t struct of the specified - * trigger instance (input port or action). This trigger_t struct - * is on the self struct. - * @param instance The port or action instance. - */ - public static String triggerRef(TriggerInstance instance) { - return triggerRef(instance, null); - } - - /** - * Return a reference to the trigger_t struct of the specified - * trigger instance (input port or action). This trigger_t struct - * is on the self struct. - * @param instance The port or action instance. - * @param runtimeIndex An optional index variable name to use to address runtime instances. - */ - public static String triggerRef(TriggerInstance instance, String runtimeIndex) { - return reactorRef(instance.getParent(), runtimeIndex) - + "->_lf__" - + instance.getName(); - } - - /** - * Return a reference to the trigger_t struct for the specified - * port of a contained reactor. - * @param port The output port of a contained reactor. - */ - public static String triggerRefNested(PortInstance port) { - return triggerRefNested(port, null, null); - } - - /** - * Return a reference to the trigger_t struct for the specified - * port of a contained reactor. - * @param port The output port of a contained reactor. - * @param runtimeIndex An optional index variable name to use to index - * the runtime instance of the port's parent's parent, or null to get the - * default returned by {@link CUtil#runtimeIndex(ReactorInstance)}. - * @param bankIndex An optional index variable name to use to index - * the the bank of the port's parent, or null to get the default returned by - * {@link CUtil#bankIndex(ReactorInstance)}. - */ - public static String triggerRefNested(PortInstance port, String runtimeIndex, String bankIndex) { - return reactorRefNested(port.getParent(), runtimeIndex, bankIndex) + "." + port.getName() + "_trigger"; - } - - - ////////////////////////////////////////////////////// - //// FIXME: Not clear what the strategy is with the following inner interface. - // The {@code ReportCommandErrors} interface allows the - // method runBuildCommand to call a protected - // method from the CGenerator if that method is passed - // using a method reference. The method that is passed - // is then interpreted as a ReportCommandErrors instance. - // This is a convenient way to factor out part of the - // internals of the CGenerator while maintaining - // encapsulation, even though the internals of the CGenerator - // might seem to be tightly coupled. FIXME: Delete this comment - - /** - * A {@code ReportCommandErrors} is a way to analyze command - * output and report any errors that it describes. - * FIXME: If the VSCode branch passes code review - * without significant revision, this mechanism will probably be replaced. - */ - public interface ReportCommandErrors { - void report(String errors); - } - - /** - * Run the custom build command specified with the "build" parameter. - * This command is executed in the same directory as the source file. - *

- * The following environment variables will be available to the command: - *

    - *
  • {@code: LF_CURRENT_WORKING_DIRECTORY}: The directory in which the command is invoked. - *
  • {@code:LF_SOURCE_DIRECTORY}: The directory containing the .lf file being compiled. - *
  • {@code:LF_PACKAGE_DIRECTORY}: The directory that is the root of the package. - *
  • {@code:LF_SOURCE_GEN_DIRECTORY}: The directory in which generated files are placed. - *
  • {@code:LF_BIN_DIRECTORY}: The directory into which to put binaries. - *
- */ - public static void runBuildCommand( - FileConfig fileConfig, - TargetConfig targetConfig, - GeneratorCommandFactory commandFactory, - ErrorReporter errorReporter, - ReportCommandErrors reportCommandErrors, - LFGeneratorContext.Mode mode - ) { - List commands = getCommands(targetConfig.buildCommands, commandFactory, fileConfig.srcPath); - // If the build command could not be found, abort. - // An error has already been reported in createCommand. - if (commands.stream().anyMatch(Objects::isNull)) return; - - for (LFCommand cmd : commands) { - int returnCode = cmd.run(); - if (returnCode != 0 && mode != LFGeneratorContext.Mode.EPOCH) { - errorReporter.reportError(String.format( - // FIXME: Why is the content of stderr not provided to the user in this error message? - "Build command \"%s\" failed with error code %d.", - targetConfig.buildCommands, returnCode - )); - return; - } - // For warnings (vs. errors), the return code is 0. - // But we still want to mark the IDE. - if (!cmd.getErrors().toString().isEmpty() && mode == LFGeneratorContext.Mode.EPOCH) { - reportCommandErrors.report(cmd.getErrors().toString()); - return; // FIXME: Why do we return here? Even if there are warnings, the build process should proceed. - } - } - } - - - /** - * Remove files in the bin directory that may have been created. - * Call this if a compilation occurs so that files from a previous - * version do not accidentally get executed. - * @param fileConfig - */ - public static void deleteBinFiles(FileConfig fileConfig) { - String name = FileUtil.nameWithoutExtension(fileConfig.srcFile); - String[] files = fileConfig.binPath.toFile().list(); - List federateNames = new LinkedList<>(); // FIXME: put this in ASTUtils? - fileConfig.resource.getAllContents().forEachRemaining(node -> { - if (node instanceof Reactor r) { + result.append(bankIndexName(reactor)); + width = reactor.getWidth(); + } + reactor = reactor.getParent(); + } + while (parens-- > 0) { + result.append(")"); + } + if (result.length() == 0) return "0"; + return result.toString(); + } + + /** + * Return a unique type for the "self" struct of the specified reactor class from the reactor + * class. + * + * @param reactor The reactor class. + * @return The type of a self struct for the specified reactor class. + */ + public static String selfType(TypeParameterizedReactor reactor) { + if (reactor.reactor().isMain()) { + return "_" + CUtil.getName(reactor) + "_main_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.tpr); + } + + /** + * Return a reference to the trigger_t struct of the specified trigger instance (input port or + * action). This trigger_t struct is on the self struct. + * + * @param instance The port or action instance. + */ + public static String triggerRef(TriggerInstance instance) { + return triggerRef(instance, null); + } + + /** + * Return a reference to the trigger_t struct of the specified trigger instance (input port or + * action). This trigger_t struct is on the self struct. + * + * @param instance The port or action instance. + * @param runtimeIndex An optional index variable name to use to address runtime instances. + */ + public static String triggerRef( + TriggerInstance instance, String runtimeIndex) { + return reactorRef(instance.getParent(), runtimeIndex) + "->_lf__" + instance.getName(); + } + + /** + * Return a reference to the trigger_t struct for the specified port of a contained reactor. + * + * @param port The output port of a contained reactor. + */ + public static String triggerRefNested(PortInstance port) { + return triggerRefNested(port, null, null); + } + + /** + * Return a reference to the trigger_t struct for the specified port of a contained reactor. + * + * @param port The output port of a contained reactor. + * @param runtimeIndex An optional index variable name to use to index the runtime instance of the + * port's parent's parent, or null to get the default returned by {@link + * CUtil#runtimeIndex(ReactorInstance)}. + * @param bankIndex An optional index variable name to use to index the the bank of the port's + * parent, or null to get the default returned by {@link CUtil#bankIndex(ReactorInstance)}. + */ + public static String triggerRefNested(PortInstance port, String runtimeIndex, String bankIndex) { + return reactorRefNested(port.getParent(), runtimeIndex, bankIndex) + + "." + + port.getName() + + "_trigger"; + } + + ////////////////////////////////////////////////////// + //// FIXME: Not clear what the strategy is with the following inner interface. + // The {@code ReportCommandErrors} interface allows the + // method runBuildCommand to call a protected + // method from the CGenerator if that method is passed + // using a method reference. The method that is passed + // is then interpreted as a ReportCommandErrors instance. + // This is a convenient way to factor out part of the + // internals of the CGenerator while maintaining + // encapsulation, even though the internals of the CGenerator + // might seem to be tightly coupled. FIXME: Delete this comment + + /** + * A {@code ReportCommandErrors} is a way to analyze command output and report any errors that it + * describes. FIXME: If the VSCode branch passes code review without significant revision, this + * mechanism will probably be replaced. + */ + public interface ReportCommandErrors { + void report(String errors); + } + + /** + * Run the custom build command specified with the "build" parameter. This command is executed in + * the same directory as the source file. + * + *

The following environment variables will be available to the command: + * + *

    + *
  • {@code: LF_CURRENT_WORKING_DIRECTORY}: The directory in which the command is invoked. + *
  • {@code:LF_SOURCE_DIRECTORY}: The directory containing the .lf file being compiled. + *
  • {@code:LF_PACKAGE_DIRECTORY}: The directory that is the root of the package. + *
  • {@code:LF_SOURCE_GEN_DIRECTORY}: The directory in which generated files are placed. + *
  • {@code:LF_BIN_DIRECTORY}: The directory into which to put binaries. + *
+ */ + public static void runBuildCommand( + FileConfig fileConfig, + TargetConfig targetConfig, + GeneratorCommandFactory commandFactory, + ErrorReporter errorReporter, + ReportCommandErrors reportCommandErrors, + LFGeneratorContext.Mode mode) { + List commands = + getCommands(targetConfig.buildCommands, commandFactory, fileConfig.srcPath); + // If the build command could not be found, abort. + // An error has already been reported in createCommand. + if (commands.stream().anyMatch(Objects::isNull)) return; + + for (LFCommand cmd : commands) { + int returnCode = cmd.run(); + if (returnCode != 0 && mode != LFGeneratorContext.Mode.EPOCH) { + errorReporter.reportError( + String.format( + // FIXME: Why is the content of stderr not provided to the user in this error + // message? + "Build command \"%s\" failed with error code %d.", + targetConfig.buildCommands, returnCode)); + return; + } + // For warnings (vs. errors), the return code is 0. + // But we still want to mark the IDE. + if (!cmd.getErrors().toString().isEmpty() && mode == LFGeneratorContext.Mode.EPOCH) { + reportCommandErrors.report(cmd.getErrors().toString()); + return; // FIXME: Why do we return here? Even if there are warnings, the build process + // should proceed. + } + } + } + + /** + * Remove files in the bin directory that may have been created. Call this if a compilation occurs + * so that files from a previous version do not accidentally get executed. + * + * @param fileConfig + */ + public static void deleteBinFiles(FileConfig fileConfig) { + String name = FileUtil.nameWithoutExtension(fileConfig.srcFile); + String[] files = fileConfig.binPath.toFile().list(); + List federateNames = new LinkedList<>(); // FIXME: put this in ASTUtils? + fileConfig + .resource + .getAllContents() + .forEachRemaining( + node -> { + if (node instanceof Reactor r) { if (r.isFederated()) { - r.getInstantiations().forEach(inst -> federateNames - .add(inst.getName())); + r.getInstantiations().forEach(inst -> federateNames.add(inst.getName())); } - } - }); - for (String f : files) { - // Delete executable file or launcher script, if any. - // Delete distribution file, if any. - // Delete RTI file, if any. - if (f.equals(name) || f.equals(name + RTI_BIN_SUFFIX) - || f.equals(name + RTI_DISTRIBUTION_SCRIPT_SUFFIX)) { - //noinspection ResultOfMethodCallIgnored - fileConfig.binPath.resolve(f).toFile().delete(); - } - // Delete federate executable files, if any. - for (String federateName : federateNames) { - if (f.equals(name + "_" + federateName)) { - //noinspection ResultOfMethodCallIgnored - fileConfig.binPath.resolve(f).toFile().delete(); - } - } + } + }); + for (String f : files) { + // Delete executable file or launcher script, if any. + // Delete distribution file, if any. + // Delete RTI file, if any. + if (f.equals(name) + || f.equals(name + RTI_BIN_SUFFIX) + || f.equals(name + RTI_DISTRIBUTION_SCRIPT_SUFFIX)) { + //noinspection ResultOfMethodCallIgnored + fileConfig.binPath.resolve(f).toFile().delete(); + } + // Delete federate executable files, if any. + for (String federateName : federateNames) { + if (f.equals(name + "_" + federateName)) { + //noinspection ResultOfMethodCallIgnored + fileConfig.binPath.resolve(f).toFile().delete(); } - } - - ////////////////////////////////////////////////////// - //// Private functions. - - /** - * If the argument is a multiport, then return a string that - * gives the width as an expression, and otherwise, return null. - * The string will be empty if the width is variable (specified - * as '[]'). Otherwise, if is a single term or a sum of terms - * (separated by '+'), where each term is either an integer - * or a parameter reference in the target language. - */ - public static String multiportWidthExpression(Variable variable) { - List spec = multiportWidthTerms(variable); - return spec == null ? null : String.join(" + ", spec); - } - - ////////////////////////////////////////////////////// - //// Private methods. - - /** - * Convert the given commands from strings to their LFCommand - * representation and return a list of LFCommand. - * @param commands A list of commands as strings. - * @param factory A command factory. - * @param dir The directory in which the commands should be executed. - * @return The LFCommand representations of the given commands, - * where {@code null} is a placeholder for commands that cannot be - * executed. - */ - private static List getCommands(List commands, GeneratorCommandFactory factory, Path dir) { - return commands.stream() - .map(cmd -> List.of(cmd.split("\\s+"))) - .filter(tokens -> tokens.size() > 0) - .map(tokens -> factory.createCommand(tokens.get(0), tokens.subList(1, tokens.size()), dir)) - .collect(Collectors.toList()); - } - - /** - * Return target code for a parameter reference, which in - * this case is just the parameter name. - * - * @param param The parameter to generate code for. - * @return Parameter reference in target code. - */ - private static String getTargetReference(Parameter param) { - return param.getName(); - } - - /** - * If the argument is a multiport, return a list of strings - * describing the width of the port, and otherwise, return null. - * If the list is empty, then the width is variable (specified - * as '[]'). Otherwise, it is a list of integers and/or parameter - * references. - * @param variable The port. - * @return The width specification for a multiport or null if it is - * not a multiport. - */ - private static List multiportWidthTerms(Variable variable) { - List result = null; - if (variable instanceof Port) { - if (((Port) variable).getWidthSpec() != null) { - result = new ArrayList<>(); - if (!((Port) variable).getWidthSpec().isOfVariableLength()) { - for (WidthTerm term : ((Port) variable).getWidthSpec().getTerms()) { - if (term.getParameter() != null) { - result.add(getTargetReference(term.getParameter())); - } else { - result.add(String.valueOf(term.getWidth())); - } - } - } + } + } + } + + ////////////////////////////////////////////////////// + //// Private functions. + + /** + * If the argument is a multiport, then return a string that gives the width as an expression, and + * otherwise, return null. The string will be empty if the width is variable (specified as '[]'). + * Otherwise, if is a single term or a sum of terms (separated by '+'), where each term is either + * an integer or a parameter reference in the target language. + */ + public static String multiportWidthExpression(Variable variable) { + List spec = multiportWidthTerms(variable); + return spec == null ? null : String.join(" + ", spec); + } + + ////////////////////////////////////////////////////// + //// Private methods. + + /** + * Convert the given commands from strings to their LFCommand representation and return a list of + * LFCommand. + * + * @param commands A list of commands as strings. + * @param factory A command factory. + * @param dir The directory in which the commands should be executed. + * @return The LFCommand representations of the given commands, where {@code null} is a + * placeholder for commands that cannot be executed. + */ + private static List getCommands( + List commands, GeneratorCommandFactory factory, Path dir) { + return commands.stream() + .map(cmd -> List.of(cmd.split("\\s+"))) + .filter(tokens -> tokens.size() > 0) + .map(tokens -> factory.createCommand(tokens.get(0), tokens.subList(1, tokens.size()), dir)) + .collect(Collectors.toList()); + } + + /** + * Return target code for a parameter reference, which in this case is just the parameter name. + * + * @param param The parameter to generate code for. + * @return Parameter reference in target code. + */ + private static String getTargetReference(Parameter param) { + return param.getName(); + } + + /** + * If the argument is a multiport, return a list of strings describing the width of the port, and + * otherwise, return null. If the list is empty, then the width is variable (specified as '[]'). + * Otherwise, it is a list of integers and/or parameter references. + * + * @param variable The port. + * @return The width specification for a multiport or null if it is not a multiport. + */ + private static List multiportWidthTerms(Variable variable) { + List result = null; + if (variable instanceof Port) { + if (((Port) variable).getWidthSpec() != null) { + result = new ArrayList<>(); + if (!((Port) variable).getWidthSpec().isOfVariableLength()) { + for (WidthTerm term : ((Port) variable).getWidthSpec().getTerms()) { + if (term.getParameter() != null) { + result.add(getTargetReference(term.getParameter())); + } else { + result.add(String.valueOf(term.getWidth())); } + } } - return result; - } - - /** - * Given a type for an input or output, return true if it should be - * carried by a lf_token_t struct rather than the type itself. - * It should be carried by such a struct if the type ends with * - * (it is a pointer) or [] (it is a array with unspecified length). - * @param type The type specification. - */ - public static boolean isTokenType(InferredType type, CTypes types) { - if (type.isUndefined()) return false; - // This is a hacky way to do this. It is now considered to be a bug (#657) - return type.isVariableSizeList || type.astType != null && (!type.astType.getStars().isEmpty() || - type.astType.getCode() != null && type.astType.getCode().getBody().stripTrailing().endsWith("*")); - } - - public static String generateWidthVariable(String var) { - return var + "_width"; - } - - /** If the type specification of the form {@code type[]}, - * {@code type*}, or {@code type}, return the type. - * @param type A string describing the type. - */ - public static String rootType(String type) { - if (type.endsWith("]")) { - return type.substring(0, type.indexOf("[")).trim(); - } else if (type.endsWith("*")) { - return type.substring(0, type.length() - 1).trim(); - } else { - return type.trim(); - } - } - - /** - * Return the full name of the specified instance without - * the leading name of the top-level reactor, unless this - * is the top-level reactor, in which case return its name. - * @param instance The instance. - * @return A shortened instance name. - */ - public static String getShortenedName(ReactorInstance instance) { - var description = instance.getFullName(); - // If not at the top level, strip off the name of the top level. - var period = description.indexOf("."); - if (period > 0) { - description = description.substring(period + 1); - } - return description; - } + } + } + return result; + } + + /** + * Given a type for an input or output, return true if it should be carried by a lf_token_t struct + * rather than the type itself. It should be carried by such a struct if the type ends with * (it + * is a pointer) or [] (it is a array with unspecified length). + * + * @param type The type specification. + */ + public static boolean isTokenType(InferredType type, CTypes types) { + if (type.isUndefined()) return false; + // This is a hacky way to do this. It is now considered to be a bug (#657) + return type.isVariableSizeList + || type.astType != null + && (!type.astType.getStars().isEmpty() + || type.astType.getCode() != null + && type.astType.getCode().getBody().stripTrailing().endsWith("*")); + } + + public static String generateWidthVariable(String var) { + return var + "_width"; + } + + /** + * If the type specification of the form {@code type[]}, {@code type*}, or {@code type}, return + * the type. + * + * @param type A string describing the type. + */ + public static String rootType(String type) { + if (type.endsWith("]")) { + return type.substring(0, type.indexOf("[")).trim(); + } else if (type.endsWith("*")) { + return type.substring(0, type.length() - 1).trim(); + } else { + return type.trim(); + } + } + + /** + * Return the full name of the specified instance without the leading name of the top-level + * reactor, unless this is the top-level reactor, in which case return its name. + * + * @param instance The instance. + * @return A shortened instance name. + */ + public static String getShortenedName(ReactorInstance instance) { + var description = instance.getFullName(); + // If not at the top level, strip off the name of the top level. + var period = description.indexOf("."); + if (period > 0) { + description = description.substring(period + 1); + } + return description; + } } diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java index d403c1cddc..f864ca2661 100644 --- a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -2,15 +2,15 @@ * @file * @author Benjamin Asch * @author Edward A. Lee - * @copyright (c) 2023, The University of California at Berkeley. - * License: BSD 2-clause + * @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.ast.ASTUtils; import org.lflang.ErrorReporter; +import org.lflang.ast.ASTUtils; import org.lflang.generator.CodeBuilder; import org.lflang.generator.ReactorInstance; import org.lflang.lf.Mode; @@ -21,11 +21,9 @@ import org.lflang.lf.Watchdog; /** - * @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. - * + * @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 Edward A. Lee */ @@ -33,6 +31,7 @@ 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. */ @@ -46,9 +45,10 @@ public static boolean hasWatchdogs(Reactor reactor) { // 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. + * 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 @@ -58,19 +58,24 @@ protected static int generateInitializeWatchdogs(CodeBuilder code, ReactorInstan var temp = new CodeBuilder(); var reactorRef = CUtil.reactorRef(instance); int watchdogCount = 0; - for (Watchdog watchdog - : ASTUtils.allWatchdogs(ASTUtils.toDefinition(instance.getDefinition().getReactorClass()))) { + for (Watchdog watchdog : + ASTUtils.allWatchdogs(ASTUtils.toDefinition(instance.getDefinition().getReactorClass()))) { var watchdogField = reactorRef + "->_lf_watchdog_" + watchdog.getName(); - temp.pr(String.join("\n", - "_lf_watchdogs[watchdog_number++] = &" + 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));", - "}" - )); + temp.pr( + String.join( + "\n", + "_lf_watchdogs[watchdog_number++] = &" + 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; } @@ -92,8 +97,10 @@ protected static int generateInitializeWatchdogs(CodeBuilder code, ReactorInstan * @param tpr The reactor declaration */ protected static void generateWatchdogs( - CodeBuilder src, CodeBuilder header, TypeParameterizedReactor tpr, ErrorReporter errorReporter - ) { + CodeBuilder src, + CodeBuilder header, + TypeParameterizedReactor tpr, + ErrorReporter errorReporter) { if (hasWatchdogs(tpr.reactor())) { header.pr("#include \"core/threaded/watchdog.h\""); for (Watchdog watchdog : ASTUtils.allWatchdogs(tpr.reactor())) { @@ -104,6 +111,7 @@ protected static void generateWatchdogs( /** * Generate watchdog definitions in the reactor's self struct. + * * @param body The place to put the definitions * @param tpr The concrete reactor class * @param constructorCode The place to put initialization code. @@ -135,34 +143,31 @@ protected static void generateWatchdogStruct( + watchdogName + ".trigger = &(self->_lf__" + watchdogName - + ");" - ) - ); + + ");")); } } /** * 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", + return String.join( + "\n", "// No watchdogs found.", "typedef void watchdog_t;", "watchdog_t* _lf_watchdogs = NULL;", - "int _lf_watchdog_count = 0;" - ); + "int _lf_watchdog_count = 0;"); } return String.join( "\n", List.of( "// Array of pointers to watchdog structs.", "watchdog_t* _lf_watchdogs[" + count + "];", - "int _lf_watchdog_count = " + count + ";" - ) - ); + "int _lf_watchdog_count = " + count + ";")); } ///////////////////////////////////////////////////////////////// @@ -175,10 +180,7 @@ protected static String generateWatchdogTable(int count) { * @param tpr The concrete reactor class that has the watchdog */ private static String generateInitializationForWatchdog( - Watchdog watchdog, - TypeParameterizedReactor tpr, - ErrorReporter errorReporter - ) { + Watchdog watchdog, TypeParameterizedReactor tpr, ErrorReporter errorReporter) { // Construct the reactionInitialization code to go into // the body of the function before the verbatim code. @@ -219,14 +221,15 @@ private static String generateInitializationForWatchdog( + name + "_change_type = " + (effect.getTransition() == ModeTransition.HISTORY - ? "history_transition" - : "reset_transition") + ? "history_transition" + : "reset_transition") + ";"); } else { - errorReporter.reportError( - watchdog, - "In generateInitializationForWatchdog(): " + name + " not a valid mode of this reactor." - ); + errorReporter.reportError( + watchdog, + "In generateInitializationForWatchdog(): " + + name + + " not a valid mode of this reactor."); } } } @@ -264,11 +267,9 @@ private static String generateFunction(String header, String init, Watchdog watc return function.toString(); } - /** Generate the watchdog handler function. */ private static String generateWatchdogFunction( - Watchdog watchdog, TypeParameterizedReactor tpr, ErrorReporter errorReporter - ) { + Watchdog watchdog, TypeParameterizedReactor tpr, ErrorReporter errorReporter) { return generateFunction( generateWatchdogFunctionHeader(watchdog, tpr), generateInitializationForWatchdog(watchdog, tpr, errorReporter), @@ -282,7 +283,8 @@ private static String generateWatchdogFunction( * @param tpr The concrete reactor class * @return The function name for the watchdog function. */ - private static String generateWatchdogFunctionHeader(Watchdog watchdog, TypeParameterizedReactor tpr) { + private static String generateWatchdogFunctionHeader( + Watchdog watchdog, TypeParameterizedReactor tpr) { String functionName = watchdogFunctionName(watchdog, tpr); return CReactionGenerator.generateFunctionHeader(functionName); } diff --git a/org.lflang/src/org/lflang/generator/c/InteractingContainedReactors.java b/org.lflang/src/org/lflang/generator/c/InteractingContainedReactors.java index 20ed65687b..16f5ceb565 100644 --- a/org.lflang/src/org/lflang/generator/c/InteractingContainedReactors.java +++ b/org.lflang/src/org/lflang/generator/c/InteractingContainedReactors.java @@ -5,7 +5,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Set; - import org.lflang.ast.ASTUtils; import org.lflang.lf.Input; import org.lflang.lf.Instantiation; @@ -23,131 +22,125 @@ * @author Soroush Bateni * @author Hou Seng Wong */ - public class InteractingContainedReactors { - /** - * Data structure that for each instantiation of a contained - * reactor. This provides a set of input and output ports that trigger - * reactions of the container, are read by a reaction of the - * container, or that receive data from a reaction of the container. - * For each port, this provides a list of reaction indices that - * are triggered by the port, or an empty list if there are no - * reactions triggered by the port. - */ - // This horrible data structure is a collection, indexed by instantiation - // of a contained reactor, of lists, indexed by ports of the contained reactor - // that are referenced by reactions of the container, of reactions that are - // triggered by the port of the contained reactor. The list is empty if - // the port does not trigger reactions but is read by the reaction or - // is written to by the reaction. - LinkedHashMap< - Instantiation, LinkedHashMap< - Port, LinkedList - > - > portsByContainedReactor = new LinkedHashMap<>(); + /** + * Data structure that for each instantiation of a contained reactor. This provides a set of input + * and output ports that trigger reactions of the container, are read by a reaction of the + * container, or that receive data from a reaction of the container. For each port, this provides + * a list of reaction indices that are triggered by the port, or an empty list if there are no + * reactions triggered by the port. + */ + // This horrible data structure is a collection, indexed by instantiation + // of a contained reactor, of lists, indexed by ports of the contained reactor + // that are referenced by reactions of the container, of reactions that are + // triggered by the port of the contained reactor. The list is empty if + // the port does not trigger reactions but is read by the reaction or + // is written to by the reaction. + LinkedHashMap>> portsByContainedReactor = + new LinkedHashMap<>(); - /** - * Scan the reactions of the specified reactor and record which ports are - * referenced by reactions and which reactions are triggered by such ports. - */ - public InteractingContainedReactors(Reactor reactor) { - var reactionCount = 0; - for (Reaction reaction : ASTUtils.allReactions(reactor)) { - // First, handle reactions that produce data sent to inputs - // of contained reactors. - for (VarRef effect : ASTUtils.convertToEmptyListIfNull(reaction.getEffects())) { - // If an effect is an input, then it must be an input - // of a contained reactor. - if (effect.getVariable() instanceof Input) { - // This reaction is not triggered by the port, so - // we do not add it to the list returned by the following. - addPort(effect.getContainer(), (Input) effect.getVariable()); - } - } - // Second, handle reactions that are triggered by outputs - // of contained reactors. - for (TriggerRef trigger : ASTUtils.convertToEmptyListIfNull(reaction.getTriggers())) { - if (trigger instanceof VarRef triggerAsVarRef) { - // If an trigger is an output, then it must be an output - // of a contained reactor. - if (triggerAsVarRef.getVariable() instanceof Output) { - var list = addPort(triggerAsVarRef.getContainer(), (Output) triggerAsVarRef.getVariable()); - list.add(reactionCount); - } - } - } - // Third, handle reading (but not triggered by) - // outputs of contained reactors. - for (VarRef source : ASTUtils.convertToEmptyListIfNull(reaction.getSources())) { - if (source.getVariable() instanceof Output) { - // If an source is an output, then it must be an output - // of a contained reactor. - // This reaction is not triggered by the port, so - // we do not add it to the list returned by the following. - addPort(source.getContainer(), (Output) source.getVariable()); - } - } - // Increment the reaction count even if not in the federate for consistency. - reactionCount++; + /** + * Scan the reactions of the specified reactor and record which ports are referenced by reactions + * and which reactions are triggered by such ports. + */ + public InteractingContainedReactors(Reactor reactor) { + var reactionCount = 0; + for (Reaction reaction : ASTUtils.allReactions(reactor)) { + // First, handle reactions that produce data sent to inputs + // of contained reactors. + for (VarRef effect : ASTUtils.convertToEmptyListIfNull(reaction.getEffects())) { + // If an effect is an input, then it must be an input + // of a contained reactor. + if (effect.getVariable() instanceof Input) { + // This reaction is not triggered by the port, so + // we do not add it to the list returned by the following. + addPort(effect.getContainer(), (Input) effect.getVariable()); + } + } + // Second, handle reactions that are triggered by outputs + // of contained reactors. + for (TriggerRef trigger : ASTUtils.convertToEmptyListIfNull(reaction.getTriggers())) { + if (trigger instanceof VarRef triggerAsVarRef) { + // If an trigger is an output, then it must be an output + // of a contained reactor. + if (triggerAsVarRef.getVariable() instanceof Output) { + var list = + addPort(triggerAsVarRef.getContainer(), (Output) triggerAsVarRef.getVariable()); + list.add(reactionCount); + } + } + } + // Third, handle reading (but not triggered by) + // outputs of contained reactors. + for (VarRef source : ASTUtils.convertToEmptyListIfNull(reaction.getSources())) { + if (source.getVariable() instanceof Output) { + // If an source is an output, then it must be an output + // of a contained reactor. + // This reaction is not triggered by the port, so + // we do not add it to the list returned by the following. + addPort(source.getContainer(), (Output) source.getVariable()); } + } + // Increment the reaction count even if not in the federate for consistency. + reactionCount++; } + } - /** - * Return or create the list to which reactions triggered by the specified port - * are to be added. This also records that the port is referenced by the - * container's reactions. - * @param containedReactor The contained reactor. - * @param port The port. - */ - private List addPort(Instantiation containedReactor, Port port) { - // Get or create the entry for the containedReactor. - var containedReactorEntry = portsByContainedReactor.computeIfAbsent( - containedReactor, - k -> new LinkedHashMap<>() - ); - // Get or create the entry for the port. - return containedReactorEntry.computeIfAbsent(port, k -> new LinkedList<>()); - } + /** + * Return or create the list to which reactions triggered by the specified port are to be added. + * This also records that the port is referenced by the container's reactions. + * + * @param containedReactor The contained reactor. + * @param port The port. + */ + private List addPort(Instantiation containedReactor, Port port) { + // Get or create the entry for the containedReactor. + var containedReactorEntry = + portsByContainedReactor.computeIfAbsent(containedReactor, k -> new LinkedHashMap<>()); + // Get or create the entry for the port. + return containedReactorEntry.computeIfAbsent(port, k -> new LinkedList<>()); + } - /** - * Return the set of contained reactors that have ports that are referenced - * by reactions of the container reactor. - */ - public Set containedReactors() { - return portsByContainedReactor.keySet(); - } + /** + * Return the set of contained reactors that have ports that are referenced by reactions of the + * container reactor. + */ + public Set containedReactors() { + return portsByContainedReactor.keySet(); + } - /** - * Return the set of ports of the specified contained reactor that are - * referenced by reactions of the container reactor. Return an empty - * set if there are none. - * @param containedReactor The contained reactor. - */ - public Set portsOfInstance(Instantiation containedReactor) { - Set result = null; - var ports = portsByContainedReactor.get(containedReactor); - if (ports == null) { - result = new LinkedHashSet<>(); - } else { - result = ports.keySet(); - } - return result; + /** + * Return the set of ports of the specified contained reactor that are referenced by reactions of + * the container reactor. Return an empty set if there are none. + * + * @param containedReactor The contained reactor. + */ + public Set portsOfInstance(Instantiation containedReactor) { + Set result = null; + var ports = portsByContainedReactor.get(containedReactor); + if (ports == null) { + result = new LinkedHashSet<>(); + } else { + result = ports.keySet(); } + return result; + } - /** - * Return the indices of the reactions triggered by the specified port - * of the specified contained reactor or an empty list if there are none. - * @param containedReactor The contained reactor. - * @param port The port. - */ - public List reactionsTriggered(Instantiation containedReactor, Port port) { - var ports = portsByContainedReactor.get(containedReactor); - if (ports != null) { - var list = ports.get(port); - if (list != null) { - return list; - } - } - return new LinkedList<>(); + /** + * Return the indices of the reactions triggered by the specified port of the specified contained + * reactor or an empty list if there are none. + * + * @param containedReactor The contained reactor. + * @param port The port. + */ + public List reactionsTriggered(Instantiation containedReactor, Port port) { + var ports = portsByContainedReactor.get(containedReactor); + if (ports != null) { + var list = ports.get(port); + if (list != null) { + return list; + } } + return new LinkedList<>(); + } } diff --git a/org.lflang/src/org/lflang/generator/c/TypeParameterizedReactor.java b/org.lflang/src/org/lflang/generator/c/TypeParameterizedReactor.java index 5f832e692a..c5e062399b 100644 --- a/org.lflang/src/org/lflang/generator/c/TypeParameterizedReactor.java +++ b/org.lflang/src/org/lflang/generator/c/TypeParameterizedReactor.java @@ -1,12 +1,10 @@ package org.lflang.generator.c; - import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; - -import org.lflang.ast.ASTUtils; import org.lflang.InferredType; +import org.lflang.ast.ASTUtils; import org.lflang.generator.CodeBuilder; import org.lflang.lf.Instantiation; import org.lflang.lf.Reactor; @@ -14,62 +12,79 @@ /** * A reactor class combined with concrete type arguments bound to its type parameters. + * * @param reactor The syntactic reactor class definition * @param typeArgs The type arguments associated with this particular variant of the reactor class. */ public record TypeParameterizedReactor(Reactor reactor, Map typeArgs) { - public TypeParameterizedReactor(Instantiation i) { - this(ASTUtils.toDefinition(i.getReactorClass()), addTypeArgs(i, ASTUtils.toDefinition(i.getReactorClass()))); - } + public TypeParameterizedReactor(Instantiation i) { + this( + ASTUtils.toDefinition(i.getReactorClass()), + addTypeArgs(i, ASTUtils.toDefinition(i.getReactorClass()))); + } - private static Map addTypeArgs(Instantiation instantiation, Reactor r) { - HashMap ret = new HashMap<>(); - if (instantiation.getTypeArgs() != null) { - for (int i = 0; i < r.getTypeParms().size(); i++) { - ret.put(r.getTypeParms().get(i).getLiteral(), instantiation.getTypeArgs().get(i)); - } - } - return ret; + private static Map addTypeArgs(Instantiation instantiation, Reactor r) { + HashMap ret = new HashMap<>(); + if (instantiation.getTypeArgs() != null) { + for (int i = 0; i < r.getTypeParms().size(); i++) { + ret.put(r.getTypeParms().get(i).getLiteral(), instantiation.getTypeArgs().get(i)); + } } + return ret; + } - /** Return the name of the reactor given its type arguments. */ - public String getName() { - // FIXME: Types that are not just a single token need to be escaped or hashed - return reactor.getName() + typeArgs.values().stream().map(ASTUtils::toOriginalText).collect(Collectors.joining("_")); - } + /** Return the name of the reactor given its type arguments. */ + public String getName() { + // FIXME: Types that are not just a single token need to be escaped or hashed + return reactor.getName() + + typeArgs.values().stream().map(ASTUtils::toOriginalText).collect(Collectors.joining("_")); + } - /** #define type names as concrete types. */ - public void doDefines(CodeBuilder b) { - typeArgs.forEach((literal, concreteType) -> b.pr( - "#if defined " + literal + "\n" + - "#undef " + literal + "\n" + - "#endif // " + literal + "\n" + - "#define " + literal + " " + ASTUtils.toOriginalText(concreteType))); - } + /** #define type names as concrete types. */ + public void doDefines(CodeBuilder b) { + typeArgs.forEach( + (literal, concreteType) -> + b.pr( + "#if defined " + + literal + + "\n" + + "#undef " + + literal + + "\n" + + "#endif // " + + literal + + "\n" + + "#define " + + literal + + " " + + ASTUtils.toOriginalText(concreteType))); + } - /** Resolve type arguments if the given type is defined in terms of generics. */ - public Type resolveType(Type t) { - if (t.getId() != null && typeArgs.get(t.getId()) != null) return typeArgs.get(t.getId()); - if (t.getCode() == null) return t; - var arg = typeArgs.get(t.getCode().getBody()); - if (arg != null) return arg; - return t; - } + /** Resolve type arguments if the given type is defined in terms of generics. */ + public Type resolveType(Type t) { + if (t.getId() != null && typeArgs.get(t.getId()) != null) return typeArgs.get(t.getId()); + if (t.getCode() == null) return t; + var arg = typeArgs.get(t.getCode().getBody()); + if (arg != null) return arg; + return t; + } - /** Resolve type arguments if the given type is defined in terms of generics. */ - public InferredType resolveType(InferredType t) { - if (t.astType == null) return t; - return InferredType.fromAST(resolveType(t.astType)); - } + /** Resolve type arguments if the given type is defined in terms of generics. */ + public InferredType resolveType(InferredType t) { + if (t.astType == null) return t; + return InferredType.fromAST(resolveType(t.astType)); + } - @Override - public int hashCode() { - return Math.abs(reactor.hashCode() * 31 + typeArgs.hashCode()); - } + @Override + public int hashCode() { + return Math.abs(reactor.hashCode() * 31 + typeArgs.hashCode()); + } - @Override - public boolean equals(Object obj) { - return obj instanceof TypeParameterizedReactor other && reactor.equals(other.reactor) && typeArgs.equals(other.typeArgs); - } + @Override + public boolean equals(Object obj) { + return obj instanceof TypeParameterizedReactor other + && reactor.equals(other.reactor) + && typeArgs.equals(other.typeArgs); + } } diff --git a/org.lflang/src/org/lflang/generator/python/PyFileConfig.java b/org.lflang/src/org/lflang/generator/python/PyFileConfig.java index bb6a9e9796..7c67b92493 100644 --- a/org.lflang/src/org/lflang/generator/python/PyFileConfig.java +++ b/org.lflang/src/org/lflang/generator/python/PyFileConfig.java @@ -3,33 +3,29 @@ import java.io.IOException; import java.nio.file.Path; import java.util.List; - import org.eclipse.emf.ecore.resource.Resource; - -import org.lflang.FileConfig; import org.lflang.generator.c.CFileConfig; import org.lflang.util.LFCommand; public class PyFileConfig extends CFileConfig { - public PyFileConfig(Resource resource, Path srcGenBasePath, boolean useHierarchicalBin) throws IOException { - super(resource, srcGenBasePath, useHierarchicalBin); - } + public PyFileConfig(Resource resource, Path srcGenBasePath, boolean useHierarchicalBin) + throws IOException { + super(resource, srcGenBasePath, useHierarchicalBin); + } - @Override - public LFCommand getCommand() { - return LFCommand.get("python3", - List.of(srcPkgPath.relativize(getExecutable()).toString()), - true, - srcPkgPath); - } + @Override + public LFCommand getCommand() { + return LFCommand.get( + "python3", List.of(srcPkgPath.relativize(getExecutable()).toString()), true, srcPkgPath); + } - @Override - public Path getExecutable() { - return srcGenPath.resolve(name + getExecutableExtension()); - } + @Override + public Path getExecutable() { + return srcGenPath.resolve(name + getExecutableExtension()); + } - @Override - protected String getExecutableExtension() { - return ".py"; - } + @Override + protected String getExecutableExtension() { + return ".py"; + } } diff --git a/org.lflang/src/org/lflang/generator/python/PyUtil.java b/org.lflang/src/org/lflang/generator/python/PyUtil.java index 48cc8f9e10..35aeed6fbb 100644 --- a/org.lflang/src/org/lflang/generator/python/PyUtil.java +++ b/org.lflang/src/org/lflang/generator/python/PyUtil.java @@ -1,28 +1,28 @@ /* Utilities for Python code generation. */ /************* -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.python; @@ -31,116 +31,126 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.c.CUtil; import org.lflang.lf.Expression; - /** - * A collection of utilities for Python code generation. - * This class inherits from CUtil but overrides a few methods to - * codify the coding conventions for the Python target code generator. + * A collection of utilities for Python code generation. This class inherits from CUtil but + * overrides a few methods to codify the coding conventions for the Python target code generator. * I.e., it defines how some variables are named and referenced. + * * @author Edward A. Lee * @author Soroush Bateni */ public class PyUtil extends CUtil { - /** - * Return the name of the list of Python class instances that contains the - * specified reactor instance. This is similar to - * {@link #reactorRef(ReactorInstance)} except that it does not index into - * the list. - * - * @param instance The reactor instance. - */ - public static String reactorRefName(ReactorInstance instance) { - return instance.uniqueID() + "_lf"; - } - - /** - * Return a reference to the list of Python class instances that contains - * the specified reactor instance. The returned string has the form - * list_name[runtimeIndex], where list_name is the name of the list of - * Python class instances that contains this reactor instance. If - * runtimeIndex is null, then it is replaced by the expression returned by - * {@link runtimeIndex(ReactorInstance)} or 0 if there are no banks. - * - * @param instance The reactor instance. - * @param runtimeIndex An optional expression to use to address bank - * members. If this is null, the expression used will be - * that returned by - * {@link #runtimeIndex(ReactorInstance)}. - */ - public static String reactorRef(ReactorInstance instance, String runtimeIndex) { - if (runtimeIndex == null) runtimeIndex = runtimeIndex(instance); - return PyUtil.reactorRefName(instance) + "[" + runtimeIndex + "]"; - } - - /** - * Return a reference to the list of Python class instances that contains - * the specified reactor instance. The returned string has the form - * list_name[j], where list_name is the name of the list of of Python class - * instances that contains this reactor instance and j is the expression - * returned by {@link #runtimeIndex(ReactorInstance)} or 0 if there are no - * banks. - * - * @param instance The reactor instance. - */ - public static String reactorRef(ReactorInstance instance) { - return PyUtil.reactorRef(instance, null); - } - - /** - * Convert C types to formats used in Py_BuildValue and PyArg_PurseTuple. - * This is unused but will be useful to enable inter-compatibility between - * C and Python reactors. - * @param type C type - */ - public static String pyBuildValueArgumentType(String type) { - switch (type) { - case "int": return "i"; - case "string": return "s"; - case "char": return "b"; - case "short int": return "h"; - case "long": return "l"; - case "unsigned char": return "B"; - case "unsigned short int": return "H"; - case "unsigned int": return "I"; - case "unsigned long": return "k"; - case "long long": return "L"; - case "interval_t": return "L"; - case "unsigned long long": return "K"; - case "double": return "d"; - case "float": return "f"; - case "Py_complex": return "D"; - case "Py_complex*": return "D"; - case "Py_Object": return "O"; - case "Py_Object*": return "O"; - default: return "O"; - } - } - - public static String generateGILAcquireCode() { - return String.join("\n", - "// Acquire the GIL (Global Interpreter Lock) to be able to call Python APIs.", - "PyGILState_STATE gstate;", - "gstate = PyGILState_Ensure();" - ); - } - - public static String generateGILReleaseCode() { - return String.join("\n", - "/* Release the thread. No Python API allowed beyond this point. */", - "PyGILState_Release(gstate);" - ); - } - - /** - * Override to convert some C types to their - * Python equivalent. - * Examples: - * true/false -> True/False - * @param expr A value - * @return A value string in the target language - */ - protected static String getPythonTargetValue(Expression expr) { - return PythonTypes.getInstance().getTargetExpr(expr, InferredType.undefined()); + /** + * Return the name of the list of Python class instances that contains the specified reactor + * instance. This is similar to {@link #reactorRef(ReactorInstance)} except that it does not index + * into the list. + * + * @param instance The reactor instance. + */ + public static String reactorRefName(ReactorInstance instance) { + return instance.uniqueID() + "_lf"; + } + + /** + * Return a reference to the list of Python class instances that contains the specified reactor + * instance. The returned string has the form list_name[runtimeIndex], where list_name is the name + * of the list of Python class instances that contains this reactor instance. If runtimeIndex is + * null, then it is replaced by the expression returned by {@link runtimeIndex(ReactorInstance)} + * or 0 if there are no banks. + * + * @param instance The reactor instance. + * @param runtimeIndex An optional expression to use to address bank members. If this is null, the + * expression used will be that returned by {@link #runtimeIndex(ReactorInstance)}. + */ + public static String reactorRef(ReactorInstance instance, String runtimeIndex) { + if (runtimeIndex == null) runtimeIndex = runtimeIndex(instance); + return PyUtil.reactorRefName(instance) + "[" + runtimeIndex + "]"; + } + + /** + * Return a reference to the list of Python class instances that contains the specified reactor + * instance. The returned string has the form list_name[j], where list_name is the name of the + * list of of Python class instances that contains this reactor instance and j is the expression + * returned by {@link #runtimeIndex(ReactorInstance)} or 0 if there are no banks. + * + * @param instance The reactor instance. + */ + public static String reactorRef(ReactorInstance instance) { + return PyUtil.reactorRef(instance, null); + } + + /** + * Convert C types to formats used in Py_BuildValue and PyArg_PurseTuple. This is unused but will + * be useful to enable inter-compatibility between C and Python reactors. + * + * @param type C type + */ + public static String pyBuildValueArgumentType(String type) { + switch (type) { + case "int": + return "i"; + case "string": + return "s"; + case "char": + return "b"; + case "short int": + return "h"; + case "long": + return "l"; + case "unsigned char": + return "B"; + case "unsigned short int": + return "H"; + case "unsigned int": + return "I"; + case "unsigned long": + return "k"; + case "long long": + return "L"; + case "interval_t": + return "L"; + case "unsigned long long": + return "K"; + case "double": + return "d"; + case "float": + return "f"; + case "Py_complex": + return "D"; + case "Py_complex*": + return "D"; + case "Py_Object": + return "O"; + case "Py_Object*": + return "O"; + default: + return "O"; } + } + + public static String generateGILAcquireCode() { + return String.join( + "\n", + "// Acquire the GIL (Global Interpreter Lock) to be able to call Python APIs.", + "PyGILState_STATE gstate;", + "gstate = PyGILState_Ensure();"); + } + + public static String generateGILReleaseCode() { + return String.join( + "\n", + "/* Release the thread. No Python API allowed beyond this point. */", + "PyGILState_Release(gstate);"); + } + + /** + * Override to convert some C types to their Python equivalent. Examples: true/false -> True/False + * + * @param expr A value + * @return A value string in the target language + */ + protected static String getPythonTargetValue(Expression expr) { + return PythonTypes.getInstance().getTargetExpr(expr, InferredType.undefined()); + } } diff --git a/org.lflang/src/org/lflang/generator/python/PythonActionGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonActionGenerator.java index 13dbed0f0f..5270a4d69e 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonActionGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonActionGenerator.java @@ -1,14 +1,17 @@ package org.lflang.generator.python; +import org.lflang.generator.c.CGenerator; import org.lflang.generator.c.TypeParameterizedReactor; import org.lflang.lf.Action; -import org.lflang.lf.Reactor; -import org.lflang.generator.c.CGenerator; public class PythonActionGenerator { - public static String generateAliasTypeDef(TypeParameterizedReactor tpr, Action action, - String genericActionType) { + public static String generateAliasTypeDef( + TypeParameterizedReactor tpr, Action action, String genericActionType) { - return "typedef "+genericActionType+" "+CGenerator.variableStructType(action, tpr, false)+";"; - } + return "typedef " + + genericActionType + + " " + + CGenerator.variableStructType(action, tpr, false) + + ";"; + } } diff --git a/org.lflang/src/org/lflang/generator/python/PythonDelayBodyGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonDelayBodyGenerator.java index b69cfc7da0..6171f3ca40 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonDelayBodyGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonDelayBodyGenerator.java @@ -1,6 +1,5 @@ package org.lflang.generator.python; - import org.lflang.ast.ASTUtils; import org.lflang.generator.c.CDelayBodyGenerator; import org.lflang.generator.c.CUtil; @@ -10,69 +9,74 @@ public class PythonDelayBodyGenerator extends CDelayBodyGenerator { - public PythonDelayBodyGenerator(PythonTypes types) { - super(types); - } + public PythonDelayBodyGenerator(PythonTypes types) { + super(types); + } - /** - * Generate code for the body of a reaction that takes an input and - * schedules an action with the value of that input. - * @param action The action to schedule - * @param port The port to read from - */ - @Override - public String generateDelayBody(Action action, VarRef port) { - boolean isTokenType = CUtil.isTokenType(ASTUtils.getInferredType(action), types); - String ref = ASTUtils.generateVarRef(port); - // Note that the action.type set by the base class is actually - // the port type. - if (isTokenType) { - return 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("+action.getName()+", 0, "+ref+"->token);", - "}" - ); - } else { - return String.join("\n", - "// Create a token.", - "#if NUMBER_OF_WORKERS > 0", - "// Need to lock the mutex first.", - "lf_mutex_lock(&mutex);", - "#endif", - "lf_token_t* t = _lf_new_token((token_type_t*)"+action.getName()+", self->_lf_"+ref+"->value, 1);", - "#if NUMBER_OF_WORKERS > 0", - "lf_mutex_unlock(&mutex);", - "#endif", - "", - "// Pass the token along", - "lf_schedule_token("+action.getName()+", 0, t);" - ); - } + /** + * Generate code for the body of a reaction that takes an input and schedules an action with the + * value of that input. + * + * @param action The action to schedule + * @param port The port to read from + */ + @Override + public String generateDelayBody(Action action, VarRef port) { + boolean isTokenType = CUtil.isTokenType(ASTUtils.getInferredType(action), types); + String ref = ASTUtils.generateVarRef(port); + // Note that the action.type set by the base class is actually + // the port type. + if (isTokenType) { + return 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(" + action.getName() + ", 0, " + ref + "->token);", + "}"); + } else { + return String.join( + "\n", + "// Create a token.", + "#if NUMBER_OF_WORKERS > 0", + "// Need to lock the mutex first.", + "lf_mutex_lock(&mutex);", + "#endif", + "lf_token_t* t = _lf_new_token((token_type_t*)" + + action.getName() + + ", self->_lf_" + + ref + + "->value, 1);", + "#if NUMBER_OF_WORKERS > 0", + "lf_mutex_unlock(&mutex);", + "#endif", + "", + "// Pass the token along", + "lf_schedule_token(" + action.getName() + ", 0, t);"); } + } - /** - * Generate code for the body of a reaction that is triggered by the - * given action and writes its value to the given port. This realizes - * the receiving end of a logical delay specified with the 'after' - * keyword. - * @param action The action that triggers the reaction - * @param port The port to write to. - */ - @Override - public String generateForwardBody(Action action, VarRef port) { - String outputName = ASTUtils.generateVarRef(port); - if (CUtil.isTokenType(ASTUtils.getInferredType(action), types)) { - return super.generateForwardBody(action, port); - } else { - return "lf_set("+outputName+", "+action.getName()+"->token->value);"; - } + /** + * Generate code for the body of a reaction that is triggered by the given action and writes its + * value to the given port. This realizes the receiving end of a logical delay specified with the + * 'after' keyword. + * + * @param action The action that triggers the reaction + * @param port The port to write to. + */ + @Override + public String generateForwardBody(Action action, VarRef port) { + String outputName = ASTUtils.generateVarRef(port); + if (CUtil.isTokenType(ASTUtils.getInferredType(action), types)) { + return super.generateForwardBody(action, port); + } else { + return "lf_set(" + outputName + ", " + action.getName() + "->token->value);"; } + } - @Override - public void finalizeReactions(Reaction delayReaction, Reaction forwardReaction) { - ASTUtils.addReactionAttribute(delayReaction, "_c_body"); - ASTUtils.addReactionAttribute(forwardReaction, "_c_body"); - } + @Override + public void finalizeReactions(Reaction delayReaction, Reaction forwardReaction) { + ASTUtils.addReactionAttribute(delayReaction, "_c_body"); + ASTUtils.addReactionAttribute(forwardReaction, "_c_body"); + } } diff --git a/org.lflang/src/org/lflang/generator/python/PythonDockerGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonDockerGenerator.java index ed69ac01fc..9d1bf0fcd4 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonDockerGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonDockerGenerator.java @@ -1,7 +1,5 @@ - package org.lflang.generator.python; -import org.lflang.TargetConfig; import org.lflang.generator.LFGeneratorContext; import org.lflang.generator.c.CDockerGenerator; @@ -11,26 +9,25 @@ * @author Hou Seng Wong */ public class PythonDockerGenerator extends CDockerGenerator { - final String defaultBaseImage = "python:slim"; + final String defaultBaseImage = "python:slim"; - public PythonDockerGenerator(LFGeneratorContext context) { - super(context); - } + public PythonDockerGenerator(LFGeneratorContext context) { + super(context); + } - /** - * Generates the contents of the docker file. - */ - @Override - protected String generateDockerFileContent() { - var baseImage = defaultBaseImage; - return String.join("\n", - "# For instructions, see: https://www.lf-lang.org/docs/handbook/containerized-execution?target=py", - "FROM "+baseImage, - "WORKDIR /lingua-franca/"+context.getFileConfig().name, - "RUN set -ex && apt-get update && apt-get install -y python3-pip && pip install cmake", - "COPY . src-gen", - super.generateDefaultCompileCommand(), - "ENTRYPOINT [\"python3\", \"src-gen/"+context.getFileConfig().name+".py\"]" - ); - } + /** Generates the contents of the docker file. */ + @Override + protected String generateDockerFileContent() { + var baseImage = defaultBaseImage; + return String.join( + "\n", + "# For instructions, see:" + + " https://www.lf-lang.org/docs/handbook/containerized-execution?target=py", + "FROM " + baseImage, + "WORKDIR /lingua-franca/" + context.getFileConfig().name, + "RUN set -ex && apt-get update && apt-get install -y python3-pip && pip install cmake", + "COPY . src-gen", + super.generateDefaultCompileCommand(), + "ENTRYPOINT [\"python3\", \"src-gen/" + context.getFileConfig().name + ".py\"]"); + } } diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java index 83053d6897..91fdfaffad 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.ast.ASTUtils; import org.lflang.AttributeUtils; import org.lflang.Target; import org.lflang.TargetProperty; +import org.lflang.ast.ASTUtils; 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; @@ -66,530 +63,516 @@ 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/python/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/python/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." - ); - } - } - 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( + // 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/python/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/python/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."); + } + } + 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()); + 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 tpr The concrete reactor class. + */ + @Override + public void generateAuxiliaryStructs( + CodeBuilder builder, TypeParameterizedReactor tpr, boolean userFacing) { + for (Input input : ASTUtils.allInputs(tpr.reactor())) { + generateAuxiliaryStructsForPort(builder, tpr, input); + } + for (Output output : ASTUtils.allOutputs(tpr.reactor())) { + generateAuxiliaryStructsForPort(builder, tpr, output); + } + for (Action action : ASTUtils.allActions(tpr.reactor())) { + generateAuxiliaryStructsForAction(builder, tpr, action); + } + } + + private void generateAuxiliaryStructsForPort( + CodeBuilder builder, TypeParameterizedReactor tpr, Port port) { + boolean isTokenType = CUtil.isTokenType(ASTUtils.getInferredType(port), types); + builder.pr( + port, PythonPortGenerator.generateAliasTypeDef(tpr, port, isTokenType, genericPortType)); + } + + private void generateAuxiliaryStructsForAction( + CodeBuilder builder, TypeParameterizedReactor tpr, Action action) { + builder.pr(action, PythonActionGenerator.generateAliasTypeDef(tpr, 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; + } + 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)); } - 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 tpr The concrete reactor class. - */ - @Override - public void generateAuxiliaryStructs( - CodeBuilder builder, TypeParameterizedReactor tpr, boolean userFacing - ) { - for (Input input : ASTUtils.allInputs(tpr.reactor())) { - generateAuxiliaryStructsForPort(builder, tpr, input); - } - for (Output output : ASTUtils.allOutputs(tpr.reactor())) { - generateAuxiliaryStructsForPort(builder, tpr, output); - } - for (Action action : ASTUtils.allActions(tpr.reactor())) { - generateAuxiliaryStructsForAction(builder, tpr, action); - } - } - - private void generateAuxiliaryStructsForPort(CodeBuilder builder, TypeParameterizedReactor tpr, - Port port) { - boolean isTokenType = CUtil.isTokenType(ASTUtils.getInferredType(port), types); - builder.pr(port, - PythonPortGenerator.generateAliasTypeDef(tpr, port, isTokenType, - genericPortType)); - } - - private void generateAuxiliaryStructsForAction(CodeBuilder builder, TypeParameterizedReactor tpr, - Action action) { - builder.pr(action, PythonActionGenerator.generateAliasTypeDef(tpr, 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; - } - 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 tpr The reactor. - * @param reactionIndex The position of the reaction within the reactor. - */ - @Override - protected void generateReaction(CodeBuilder src, Reaction reaction, TypeParameterizedReactor tpr, int reactionIndex) { - Reactor reactor = ASTUtils.toDefinition(tpr.reactor()); - - // Reactions marked with a {@code @_c_body} attribute are generated in C - if (AttributeUtils.hasCBody(reaction)) { - super.generateReaction(src, reaction, tpr, reactionIndex); - return; - } - src.pr(PythonReactionGenerator.generateCReaction(reaction, tpr, 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, TypeParameterizedReactor 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 - } - - @Override - protected void generateReactorClassHeaders(TypeParameterizedReactor tpr, String headerName, CodeBuilder header, CodeBuilder src) { - header.pr(PythonPreambleGenerator.generateCIncludeStatements(targetConfig, targetLanguageIsCpp(), hasModalReactors)); - super.generateReactorClassHeaders(tpr, headerName, header, src); - } - - /** - * 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 reactor The reactor declaration for the self struct - * @param constructorCode Code that is executed when the reactor is instantiated - */ - @Override - protected void generateSelfStructExtension( - CodeBuilder selfStructBody, - Reactor reactor, - CodeBuilder constructorCode - ) { - // 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 ( - """ + } 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 tpr The reactor. + * @param reactionIndex The position of the reaction within the reactor. + */ + @Override + protected void generateReaction( + CodeBuilder src, Reaction reaction, TypeParameterizedReactor tpr, int reactionIndex) { + Reactor reactor = ASTUtils.toDefinition(tpr.reactor()); + + // Reactions marked with a {@code @_c_body} attribute are generated in C + if (AttributeUtils.hasCBody(reaction)) { + super.generateReaction(src, reaction, tpr, reactionIndex); + return; + } + src.pr( + PythonReactionGenerator.generateCReaction( + reaction, tpr, 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, TypeParameterizedReactor 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 + } + + @Override + protected void generateReactorClassHeaders( + TypeParameterizedReactor tpr, String headerName, CodeBuilder header, CodeBuilder src) { + header.pr( + PythonPreambleGenerator.generateCIncludeStatements( + targetConfig, targetLanguageIsCpp(), hasModalReactors)); + super.generateReactorClassHeaders(tpr, headerName, header, src); + } + + /** + * 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 reactor The reactor declaration for the self struct + * @param constructorCode Code that is executed when the reactor is instantiated + */ + @Override + protected void generateSelfStructExtension( + CodeBuilder selfStructBody, Reactor reactor, CodeBuilder constructorCode) { + // 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 (""" set(CMAKE_POSITION_INDEPENDENT_CODE ON) add_compile_definitions(_LF_GARBAGE_COLLECTED) add_subdirectory(core) @@ -614,74 +597,59 @@ 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 ({@code key}, {@code val}) tuple pair for the {@code 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 ({@code key}, {@code 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 {@code 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 {@code lfModuleName}. - * - * Ideally, this function would belong in a class like {@code 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.copyFromClassPath( - "/lib/c/reactor-c/python/include", - fileConfig.getSrcGenPath(), - true, - false - ); - FileUtil.copyFromClassPath( - "/lib/c/reactor-c/python/lib", - fileConfig.getSrcGenPath(), - true, - false - ); - FileUtil.copyFromClassPath( - "/lib/py/lf-python-support/LinguaFrancaBase", - fileConfig.getSrcGenPath(), - true, - false - ); - } - + """) + .replace("", generatePythonModuleName(executableName)) + .replace("executableName", executableName); + // The use of fileConfig.name will break federated execution, but that's fine + } + + /** + * Generate a ({@code key}, {@code val}) tuple pair for the {@code 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 ({@code key}, {@code 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 {@code 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 {@code lfModuleName}. + * + *

Ideally, this function would belong in a class like {@code 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.copyFromClassPath( + "/lib/c/reactor-c/python/include", fileConfig.getSrcGenPath(), true, false); + FileUtil.copyFromClassPath( + "/lib/c/reactor-c/python/lib", fileConfig.getSrcGenPath(), true, false); + FileUtil.copyFromClassPath( + "/lib/py/lf-python-support/LinguaFrancaBase", fileConfig.getSrcGenPath(), true, false); + } } diff --git a/org.lflang/src/org/lflang/generator/python/PythonInfoGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonInfoGenerator.java index 23512f447a..01cadbcc5c 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonInfoGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonInfoGenerator.java @@ -4,53 +4,49 @@ import org.lflang.FileConfig; public class PythonInfoGenerator { - /** - * Print information about necessary steps to install the supporting - * Python C extension for the generated program. - * - * @note Only needed if no-compile is set to true - */ - public static String generateSetupInfo(FileConfig fileConfig) { - return String.join("\n", - "", - "#####################################", - "To compile and install the generated code, do:", - " ", - " cd "+fileConfig.getSrcGenPath()+File.separator, - " python3 -m pip install --force-reinstall .", - "" - ); - } + /** + * Print information about necessary steps to install the supporting Python C extension for the + * generated program. + * + * @note Only needed if no-compile is set to true + */ + public static String generateSetupInfo(FileConfig fileConfig) { + return String.join( + "\n", + "", + "#####################################", + "To compile and install the generated code, do:", + " ", + " cd " + fileConfig.getSrcGenPath() + File.separator, + " python3 -m pip install --force-reinstall .", + ""); + } - /** - * Print information on how to execute the generated program. - */ - public static String generateRunInfo(FileConfig fileConfig, String lfModuleName) { - return String.join("\n", - "", - "#####################################", - "To run the generated program, use:", - " ", - " python3 "+fileConfig.getSrcGenPath()+File.separator+lfModuleName+".py", - "", - "#####################################", - "" - ); - } + /** Print information on how to execute the generated program. */ + public static String generateRunInfo(FileConfig fileConfig, String lfModuleName) { + return String.join( + "\n", + "", + "#####################################", + "To run the generated program, use:", + " ", + " python3 " + fileConfig.getSrcGenPath() + File.separator + lfModuleName + ".py", + "", + "#####################################", + ""); + } - /** - * Print information on how to execute the generated federation. - */ - public static String generateFedRunInfo(FileConfig fileConfig) { - return String.join("\n", - "", - "#####################################", - "To run the generated program, run:", - " ", - " bash "+fileConfig.binPath+"/"+fileConfig.name, - "", - "#####################################", - "" - ); - } + /** Print information on how to execute the generated federation. */ + public static String generateFedRunInfo(FileConfig fileConfig) { + return String.join( + "\n", + "", + "#####################################", + "To run the generated program, run:", + " ", + " bash " + fileConfig.binPath + "/" + fileConfig.name, + "", + "#####################################", + ""); + } } diff --git a/org.lflang/src/org/lflang/generator/python/PythonMainFunctionGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonMainFunctionGenerator.java index a655402408..5a43788138 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonMainFunctionGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonMainFunctionGenerator.java @@ -26,29 +26,26 @@ package org.lflang.generator.python; /** - * Responsible for creating the main function for - * the generated Python target programs. + * Responsible for creating the main function for the generated Python target programs. * * @author Soroush Bateni - * */ public final class PythonMainFunctionGenerator { - /* - * Generate the main function code - */ - public static String generateCode() { - StringBuilder code = new StringBuilder(); - code.append( - "# The main function\n" - + "def main(argv):\n" - + " start(argv)\n" - + "\n" - + "# As is customary in Python programs, the main() function\n" - + "# should only be executed if the main module is active.\n" - + "if __name__==\"__main__\":\n" - + " main(sys.argv)\n" - ); - return code.toString(); - } + /* + * Generate the main function code + */ + public static String generateCode() { + StringBuilder code = new StringBuilder(); + code.append( + "# The main function\n" + + "def main(argv):\n" + + " start(argv)\n" + + "\n" + + "# As is customary in Python programs, the main() function\n" + + "# should only be executed if the main module is active.\n" + + "if __name__==\"__main__\":\n" + + " main(sys.argv)\n"); + return code.toString(); + } } diff --git a/org.lflang/src/org/lflang/generator/python/PythonMethodGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonMethodGenerator.java index a2d1e240d1..e2d2e85a6b 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonMethodGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonMethodGenerator.java @@ -1,7 +1,6 @@ package org.lflang.generator.python; import java.util.stream.Collectors; - import org.lflang.ast.ASTUtils; import org.lflang.lf.Method; import org.lflang.lf.MethodArgument; @@ -14,39 +13,30 @@ */ public class PythonMethodGenerator { - /** - * Generate a Python method definition for {@code method}. - */ - public static String generateMethod(Method method) { - return String.join("\n", - "# Implementation of method "+method.getName()+"().", - "def "+method.getName()+"(self, "+generateMethodArgumentList(method)+"):", - ASTUtils.toText(method.getCode()).indent(4) - ); - } + /** Generate a Python method definition for {@code method}. */ + public static String generateMethod(Method method) { + return String.join( + "\n", + "# Implementation of method " + method.getName() + "().", + "def " + method.getName() + "(self, " + generateMethodArgumentList(method) + "):", + ASTUtils.toText(method.getCode()).indent(4)); + } - /** - * Generate methods for a reactor class. - * - * @param reactor The reactor. - */ - public static String generateMethods( - Reactor reactor - ) { - return ASTUtils.allMethods(reactor) - .stream() - .map(m -> generateMethod(m)) - .collect(Collectors.joining()); - } + /** + * Generate methods for a reactor class. + * + * @param reactor The reactor. + */ + public static String generateMethods(Reactor reactor) { + return ASTUtils.allMethods(reactor).stream() + .map(m -> generateMethod(m)) + .collect(Collectors.joining()); + } - /** - * Generate a list of arguments for {@code method} delimited with ', '. - */ - private static String generateMethodArgumentList(Method method) { - return String.join(", ", - method.getArguments() - .stream() - .map(MethodArgument::getName) - .collect(Collectors.toList())); - } + /** Generate a list of arguments for {@code method} delimited with ', '. */ + private static String generateMethodArgumentList(Method method) { + return String.join( + ", ", + method.getArguments().stream().map(MethodArgument::getName).collect(Collectors.toList())); + } } diff --git a/org.lflang/src/org/lflang/generator/python/PythonModeGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonModeGenerator.java index 90af635727..9bcbe6607d 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonModeGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonModeGenerator.java @@ -1,7 +1,6 @@ package org.lflang.generator.python; import java.util.List; - import org.eclipse.emf.ecore.util.EcoreUtil; import org.lflang.generator.CodeBuilder; import org.lflang.lf.BuiltinTrigger; @@ -15,86 +14,94 @@ * Helper class to handle modes in Python programs. * * @author Soroush Bateni - * */ public class PythonModeGenerator { - /** - * Generate reset reactions in modes to reset state variables. - * - * @param reactors A list of reactors in the program, some of which could contain modes. - */ - public static void generateResetReactionsIfNeeded(List reactors) { - for (Reactor reactor : reactors) { - generateStartupReactionsInReactor(reactor); - } + /** + * Generate reset reactions in modes to reset state variables. + * + * @param reactors A list of reactors in the program, some of which could contain modes. + */ + public static void generateResetReactionsIfNeeded(List reactors) { + for (Reactor reactor : reactors) { + generateStartupReactionsInReactor(reactor); } + } - /** - * Generate reset reactions that reset state variables in - *

    - *
  • the reactor, and,
  • - *
  • the modes within the reactor.
  • - *
- * - * @param reactor The reactor. - */ - private static void generateStartupReactionsInReactor(Reactor reactor) { - - // Create a reaction with a reset trigger - BuiltinTriggerRef resetTrigger = LfFactory.eINSTANCE.createBuiltinTriggerRef(); - resetTrigger.setType(BuiltinTrigger.RESET); - Reaction baseReaction = LfFactory.eINSTANCE.createReaction(); - baseReaction.getTriggers().add(resetTrigger); + /** + * Generate reset reactions that reset state variables in + * + *
    + *
  • the reactor, and, + *
  • the modes within the reactor. + *
+ * + * @param reactor The reactor. + */ + private static void generateStartupReactionsInReactor(Reactor reactor) { - if (!reactor.getStateVars().isEmpty() && reactor.getStateVars().stream().anyMatch(s -> s.isReset())) { - // Create a reaction body that resets all state variables (that are not in a mode) - // to their initial value. - var reactionBody = LfFactory.eINSTANCE.createCode(); - CodeBuilder code = new CodeBuilder(); - code.pr("# Reset the following state variables to their initial value."); - for (var state: reactor.getStateVars()) { - if (state.isReset()) { - code.pr("self."+state.getName()+" = "+ PythonStateGenerator.generatePythonInitializer(state)); - } - } - reactionBody.setBody(code.toString()); - baseReaction.setCode(reactionBody); + // Create a reaction with a reset trigger + BuiltinTriggerRef resetTrigger = LfFactory.eINSTANCE.createBuiltinTriggerRef(); + resetTrigger.setType(BuiltinTrigger.RESET); + Reaction baseReaction = LfFactory.eINSTANCE.createReaction(); + baseReaction.getTriggers().add(resetTrigger); - reactor.getReactions().add(0, baseReaction); + if (!reactor.getStateVars().isEmpty() + && reactor.getStateVars().stream().anyMatch(s -> s.isReset())) { + // Create a reaction body that resets all state variables (that are not in a mode) + // to their initial value. + var reactionBody = LfFactory.eINSTANCE.createCode(); + CodeBuilder code = new CodeBuilder(); + code.pr("# Reset the following state variables to their initial value."); + for (var state : reactor.getStateVars()) { + if (state.isReset()) { + code.pr( + "self." + + state.getName() + + " = " + + PythonStateGenerator.generatePythonInitializer(state)); } + } + reactionBody.setBody(code.toString()); + baseReaction.setCode(reactionBody); + reactor.getReactions().add(0, baseReaction); + } - var reactorModes = reactor.getModes(); - if (!reactorModes.isEmpty()) { - for (Mode mode : reactorModes) { - if (mode.getStateVars().isEmpty() || mode.getStateVars().stream().allMatch(s -> !s.isReset())) { - continue; - } - Reaction reaction = EcoreUtil.copy(baseReaction); - - // Create a reaction body that resets all state variables to their initial value. - var reactionBody = LfFactory.eINSTANCE.createCode(); - CodeBuilder code = new CodeBuilder(); - code.pr("# Reset the following state variables to their initial value."); - for (var state: mode.getStateVars()) { - if (state.isReset()) { - code.pr("self."+state.getName()+" = "+ PythonStateGenerator.generatePythonInitializer(state)); - } - } - reactionBody.setBody(code.toString()); - reaction.setCode(reactionBody); + var reactorModes = reactor.getModes(); + if (!reactorModes.isEmpty()) { + for (Mode mode : reactorModes) { + if (mode.getStateVars().isEmpty() + || mode.getStateVars().stream().allMatch(s -> !s.isReset())) { + continue; + } + Reaction reaction = EcoreUtil.copy(baseReaction); - try { - mode.getReactions().add(0, reaction); - } catch (IndexOutOfBoundsException e) { - // There are no scoping for state variables. - // We add this reaction for now so that it - // still resets state variables even if there - // are no reactions in this mode. - mode.getReactions().add(reaction); - } + // Create a reaction body that resets all state variables to their initial value. + var reactionBody = LfFactory.eINSTANCE.createCode(); + CodeBuilder code = new CodeBuilder(); + code.pr("# Reset the following state variables to their initial value."); + for (var state : mode.getStateVars()) { + if (state.isReset()) { + code.pr( + "self." + + state.getName() + + " = " + + PythonStateGenerator.generatePythonInitializer(state)); + } + } + reactionBody.setBody(code.toString()); + reaction.setCode(reactionBody); - } + try { + mode.getReactions().add(0, reaction); + } catch (IndexOutOfBoundsException e) { + // There are no scoping for state variables. + // We add this reaction for now so that it + // still resets state variables even if there + // are no reactions in this mode. + mode.getReactions().add(reaction); } + } } + } } diff --git a/org.lflang/src/org/lflang/generator/python/PythonParameterGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonParameterGenerator.java index 838e7a27a3..0c13b111f7 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonParameterGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonParameterGenerator.java @@ -2,107 +2,103 @@ import java.util.ArrayList; import java.util.List; - import org.lflang.ast.ASTUtils; import org.lflang.generator.ParameterInstance; -import org.lflang.lf.ReactorDecl; import org.lflang.lf.Parameter; - +import org.lflang.lf.ReactorDecl; public class PythonParameterGenerator { - /** - * Generate Python code that instantiates and initializes parameters for a reactor 'decl'. - * - * @param decl The reactor declaration - * @return The generated code as a StringBuilder - */ - public static String generatePythonInstantiations(ReactorDecl decl, PythonTypes types) { - List lines = new ArrayList<>(); - lines.add("# Define parameters and their default values"); + /** + * Generate Python code that instantiates and initializes parameters for a reactor 'decl'. + * + * @param decl The reactor declaration + * @return The generated code as a StringBuilder + */ + public static String generatePythonInstantiations(ReactorDecl decl, PythonTypes types) { + List lines = new ArrayList<>(); + lines.add("# Define parameters and their default values"); - for (Parameter param : getAllParameters(decl)) { - if (!types.getTargetType(param).equals("PyObject*")) { - // If type is given, use it - String type = types.getPythonType(ASTUtils.getInferredType(param)); - lines.add("self._"+param.getName()+":"+type+" = "+generatePythonInitializer(param)); - } else { - // If type is not given, just pass along the initialization - lines.add("self._"+param.getName()+" = "+generatePythonInitializer(param)); - } - } - // Handle parameters that are set in instantiation - lines.addAll(List.of( + for (Parameter param : getAllParameters(decl)) { + if (!types.getTargetType(param).equals("PyObject*")) { + // If type is given, use it + String type = types.getPythonType(ASTUtils.getInferredType(param)); + lines.add( + "self._" + param.getName() + ":" + type + " = " + generatePythonInitializer(param)); + } else { + // If type is not given, just pass along the initialization + lines.add("self._" + param.getName() + " = " + generatePythonInitializer(param)); + } + } + // Handle parameters that are set in instantiation + lines.addAll( + List.of( "# Handle parameters that are set in instantiation", "self.__dict__.update(kwargs)", - "" - )); - return String.join("\n", lines); - } + "")); + return String.join("\n", lines); + } - /** - * Generate Python code getters for parameters of reactor 'decl'. - * - * @param decl The reactor declaration - * @return The generated code as a StringBuilder - */ - public static String generatePythonGetters(ReactorDecl decl) { - List lines = new ArrayList<>(); - for (Parameter param : getAllParameters(decl)) { - if (!param.getName().equals("bank_index")) { - lines.addAll(List.of( - "", - "@property", - "def "+param.getName()+"(self):", - " return self._"+param.getName()+" # pylint: disable=no-member", - "" - )); - } - } - // Create a special property for bank_index - lines.addAll(List.of( + /** + * Generate Python code getters for parameters of reactor 'decl'. + * + * @param decl The reactor declaration + * @return The generated code as a StringBuilder + */ + public static String generatePythonGetters(ReactorDecl decl) { + List lines = new ArrayList<>(); + for (Parameter param : getAllParameters(decl)) { + if (!param.getName().equals("bank_index")) { + lines.addAll( + List.of( + "", + "@property", + "def " + param.getName() + "(self):", + " return self._" + param.getName() + " # pylint: disable=no-member", + "")); + } + } + // Create a special property for bank_index + lines.addAll( + List.of( "", "@property", "def bank_index(self):", " return self._bank_index # pylint: disable=no-member", - "" - )); - lines.add("\n"); - return String.join("\n", lines); - } - - /** - * Return a list of all parameters of reactor 'decl'. - * - * @param decl The reactor declaration - * @return The list of all parameters of 'decl' - */ - private static List getAllParameters(ReactorDecl decl) { - return ASTUtils.allParameters(ASTUtils.toDefinition(decl)); - } + "")); + lines.add("\n"); + return String.join("\n", lines); + } - /** - * Create a Python list for parameter initialization in target code. - * - * @param p The parameter to create initializers for - * @return Initialization code - */ - private static String generatePythonInitializer(Parameter p) { - return PythonTypes.getInstance().getTargetInitializer(p.getInit(), p.getType()); - } + /** + * Return a list of all parameters of reactor 'decl'. + * + * @param decl The reactor declaration + * @return The list of all parameters of 'decl' + */ + private static List getAllParameters(ReactorDecl decl) { + return ASTUtils.allParameters(ASTUtils.toDefinition(decl)); + } - /** - * Return a Python expression that can be used to initialize the specified - * parameter instance. If the parameter initializer refers to other - * parameters, then those parameter references are replaced with - * accesses to the Python reactor instance class of the parents of - * those parameters. - * - * @param p The parameter instance to create initializer for - * @return Initialization code - */ - public static String generatePythonInitializer(ParameterInstance p) { - PythonTypes pyTypes = PythonTypes.generateParametersIn(p.getParent().getParent()); - return pyTypes.getTargetInitializer(p.getActualValue(), p.getDefinition().getType()); - } + /** + * Create a Python list for parameter initialization in target code. + * + * @param p The parameter to create initializers for + * @return Initialization code + */ + private static String generatePythonInitializer(Parameter p) { + return PythonTypes.getInstance().getTargetInitializer(p.getInit(), p.getType()); + } + /** + * Return a Python expression that can be used to initialize the specified parameter instance. If + * the parameter initializer refers to other parameters, then those parameter references are + * replaced with accesses to the Python reactor instance class of the parents of those parameters. + * + * @param p The parameter instance to create initializer for + * @return Initialization code + */ + public static String generatePythonInitializer(ParameterInstance p) { + PythonTypes pyTypes = PythonTypes.generateParametersIn(p.getParent().getParent()); + return pyTypes.getTargetInitializer(p.getActualValue(), p.getDefinition().getType()); + } } diff --git a/org.lflang/src/org/lflang/generator/python/PythonPortGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonPortGenerator.java index 5109c322c3..d8ece7d206 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonPortGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonPortGenerator.java @@ -1,237 +1,260 @@ package org.lflang.generator.python; +import static org.lflang.generator.c.CUtil.generateWidthVariable; + +import java.util.List; +import org.lflang.ast.ASTUtils; +import org.lflang.generator.CodeBuilder; +import org.lflang.generator.c.CGenerator; import org.lflang.generator.c.TypeParameterizedReactor; +import org.lflang.lf.Action; import org.lflang.lf.Input; import org.lflang.lf.Instantiation; import org.lflang.lf.Output; import org.lflang.lf.Port; -import org.lflang.lf.Action; import org.lflang.lf.ReactorDecl; import org.lflang.lf.VarRef; -import java.util.List; -import org.lflang.ast.ASTUtils; -import org.lflang.generator.CodeBuilder; -import org.lflang.generator.c.CGenerator; -import static org.lflang.generator.c.CUtil.generateWidthVariable; public class PythonPortGenerator { - public static final String NONMULTIPORT_WIDTHSPEC = "-2"; + public static final String NONMULTIPORT_WIDTHSPEC = "-2"; - /** - * Generate code to convert C actions to Python action capsules. See - * pythontarget.h for details. - * @param pyObjects A string containing a list of comma-separated expressions that will create the - * action capsules. - * @param action The action itself. - * @param decl The reactor decl that contains the action. - */ - public static void generateActionVariableToSendToPythonReaction(List pyObjects, - Action action, ReactorDecl decl) { - // Values passed to an action are always stored in the token->value. - // However, sometimes token might not be initialized. Therefore, this function has an internal check for NULL in case token is not initialized. - pyObjects.add(String.format("convert_C_action_to_py(%s)", action.getName())); - } + /** + * Generate code to convert C actions to Python action capsules. See pythontarget.h for details. + * + * @param pyObjects A string containing a list of comma-separated expressions that will create the + * action capsules. + * @param action The action itself. + * @param decl The reactor decl that contains the action. + */ + public static void generateActionVariableToSendToPythonReaction( + List pyObjects, Action action, ReactorDecl decl) { + // Values passed to an action are always stored in the token->value. + // However, sometimes token might not be initialized. Therefore, this function has an internal + // check for NULL in case token is not initialized. + pyObjects.add(String.format("convert_C_action_to_py(%s)", action.getName())); + } - /** - * Generate code to convert C ports to Python ports capsules (@see pythontarget.h). - * - * The port may be an input of the reactor or an output of a contained reactor. - * @param pyObjects A string containing a list of comma-separated expressions that will create the - * port capsules. - * @param port The port itself. - * @param decl The reactor decl that contains the port. - */ - public static String generatePortVariablesToSendToPythonReaction( - List pyObjects, - VarRef port, - ReactorDecl decl - ) { - if (port.getVariable() instanceof Input) { - generateInputVariablesToSendToPythonReaction(pyObjects, (Input) port.getVariable(), decl); - return ""; - } else { - // port is an output of a contained reactor. - return generateVariablesForSendingToContainedReactors(pyObjects, port.getContainer(), (Port) port.getVariable()); - } + /** + * Generate code to convert C ports to Python ports capsules (@see pythontarget.h). + * + *

The port may be an input of the reactor or an output of a contained reactor. + * + * @param pyObjects A string containing a list of comma-separated expressions that will create the + * port capsules. + * @param port The port itself. + * @param decl The reactor decl that contains the port. + */ + public static String generatePortVariablesToSendToPythonReaction( + List pyObjects, VarRef port, ReactorDecl decl) { + if (port.getVariable() instanceof Input) { + generateInputVariablesToSendToPythonReaction(pyObjects, (Input) port.getVariable(), decl); + return ""; + } else { + // port is an output of a contained reactor. + return generateVariablesForSendingToContainedReactors( + pyObjects, port.getContainer(), (Port) port.getVariable()); } + } - /** Generate into the specified string builder the code to - * send local variables for output ports to a Python reaction function - * from the "self" struct. - * @param output The output port. - */ - public static void generateOutputVariablesToSendToPythonReaction( - List pyObjects, - Output output - ) { - // Unfortunately, for the lf_set macros to work out-of-the-box for - // multiports, we need an array of pointers to the output structs, - // but what we have on the self struct is an array of output structs. - // So we have to handle multiports specially here a construct that - // array of pointers. - // FIXME: The C Generator also has this awkwardness. It makes the code generators - // unnecessarily difficult to maintain, and it may have performance consequences as well. - // Maybe we should change the lf_set macros. - if (!ASTUtils.isMultiport(output)) { - pyObjects.add(generateConvertCPortToPy(output.getName(), NONMULTIPORT_WIDTHSPEC)); - } else { - pyObjects.add(generateConvertCPortToPy(output.getName())); - } + /** + * Generate into the specified string builder the code to send local variables for output ports to + * a Python reaction function from the "self" struct. + * + * @param output The output port. + */ + public static void generateOutputVariablesToSendToPythonReaction( + List pyObjects, Output output) { + // Unfortunately, for the lf_set macros to work out-of-the-box for + // multiports, we need an array of pointers to the output structs, + // but what we have on the self struct is an array of output structs. + // So we have to handle multiports specially here a construct that + // array of pointers. + // FIXME: The C Generator also has this awkwardness. It makes the code generators + // unnecessarily difficult to maintain, and it may have performance consequences as well. + // Maybe we should change the lf_set macros. + if (!ASTUtils.isMultiport(output)) { + pyObjects.add(generateConvertCPortToPy(output.getName(), NONMULTIPORT_WIDTHSPEC)); + } else { + pyObjects.add(generateConvertCPortToPy(output.getName())); } + } - /** Generate into the specified string builder the code to - * send local variables for input ports to a Python reaction function - * from the "self" struct. - * @param input The input port. - */ - public static void generateInputVariablesToSendToPythonReaction( - List pyObjects, - Input input, - ReactorDecl decl - ) { - // Create the local variable whose name matches the input.getName(). - // 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. 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() && !ASTUtils.isMultiport(input)) { - // Non-mutable, non-multiport, primitive type. - pyObjects.add(generateConvertCPortToPy(input.getName())); - } else if (input.isMutable() && !ASTUtils.isMultiport(input)) { - // Mutable, non-multiport, primitive type. - // TODO: handle mutable - pyObjects.add(generateConvertCPortToPy(input.getName())); - } else if (!input.isMutable() && ASTUtils.isMultiport(input)) { - // Non-mutable, multiport, primitive. - // TODO: support multiports - pyObjects.add(generateConvertCPortToPy(input.getName())); - } else { - // Mutable, multiport, primitive type - // TODO: support mutable multiports - pyObjects.add(generateConvertCPortToPy(input.getName())); - } + /** + * Generate into the specified string builder the code to send local variables for input ports to + * a Python reaction function from the "self" struct. + * + * @param input The input port. + */ + public static void generateInputVariablesToSendToPythonReaction( + List pyObjects, Input input, ReactorDecl decl) { + // Create the local variable whose name matches the input.getName(). + // 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. 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() && !ASTUtils.isMultiport(input)) { + // Non-mutable, non-multiport, primitive type. + pyObjects.add(generateConvertCPortToPy(input.getName())); + } else if (input.isMutable() && !ASTUtils.isMultiport(input)) { + // Mutable, non-multiport, primitive type. + // TODO: handle mutable + pyObjects.add(generateConvertCPortToPy(input.getName())); + } else if (!input.isMutable() && ASTUtils.isMultiport(input)) { + // Non-mutable, multiport, primitive. + // TODO: support multiports + pyObjects.add(generateConvertCPortToPy(input.getName())); + } else { + // Mutable, multiport, primitive type + // TODO: support mutable multiports + pyObjects.add(generateConvertCPortToPy(input.getName())); } + } - /** Generate into the specified string builder the code to - * pass local variables for sending data to an input - * of a contained reaction (e.g. for a deadline violation). - * @param definition AST node defining the reactor within which this occurs - * @param port Input of the contained reactor. - */ - public static String generateVariablesForSendingToContainedReactors( - List pyObjects, - Instantiation definition, - Port port - ) { - CodeBuilder code = new CodeBuilder(); - if (definition.getWidthSpec() != null) { - String widthSpec = NONMULTIPORT_WIDTHSPEC; - if (ASTUtils.isMultiport(port)) { - widthSpec = "self->_lf_"+definition.getName()+"[i]."+generateWidthVariable(port.getName()); - } - // Contained reactor is a bank. - // Create a Python list - code.pr(generatePythonListForContainedBank(definition.getName(), port, widthSpec)); - pyObjects.add(definition.getName()+"_py_list"); - } - else { - if (ASTUtils.isMultiport(port)) { - pyObjects.add(generateConvertCPortToPy(definition.getName()+"."+port.getName())); - } else { - pyObjects.add(generateConvertCPortToPy(definition.getName()+"."+port.getName(), NONMULTIPORT_WIDTHSPEC)); - } - } - return code.toString(); + /** + * Generate into the specified string builder the code to pass local variables for sending data to + * an input of a contained reaction (e.g. for a deadline violation). + * + * @param definition AST node defining the reactor within which this occurs + * @param port Input of the contained reactor. + */ + public static String generateVariablesForSendingToContainedReactors( + List pyObjects, Instantiation definition, Port port) { + CodeBuilder code = new CodeBuilder(); + if (definition.getWidthSpec() != null) { + String widthSpec = NONMULTIPORT_WIDTHSPEC; + if (ASTUtils.isMultiport(port)) { + widthSpec = + "self->_lf_" + definition.getName() + "[i]." + generateWidthVariable(port.getName()); + } + // Contained reactor is a bank. + // Create a Python list + code.pr(generatePythonListForContainedBank(definition.getName(), port, widthSpec)); + pyObjects.add(definition.getName() + "_py_list"); + } else { + if (ASTUtils.isMultiport(port)) { + pyObjects.add(generateConvertCPortToPy(definition.getName() + "." + port.getName())); + } else { + pyObjects.add( + generateConvertCPortToPy( + definition.getName() + "." + port.getName(), NONMULTIPORT_WIDTHSPEC)); + } } + return code.toString(); + } + /** + * Generate code that creates a Python list (i.e., []) for contained banks to be passed to Python reactions. + * The Python reaction will then subsequently be able to address each individual bank member of the contained + * bank using an index or an iterator. Each list member will contain the given port + * (which could be a multiport with a width determined by widthSpec). + * + * This is to accommodate reactions like reaction() -> s.out where s is a bank. In this example, + * the generated Python function will have the signature reaction_function_0(self, s_out), where + * s_out is a list of out ports. This will later be turned into the proper s.out format using the + * Python code generated in {@link #generatePythonPortVariableInReaction}. + * + * @param reactorName The name of the bank of reactors (which is the name of the reactor class). + * @param port The port that should be put in the Python list. + * @param widthSpec A string that should be -2 for non-multiports and the width expression for multiports. + */ + public static String generatePythonListForContainedBank( + String reactorName, Port port, String widthSpec) { + return String.join( + "\n", + "PyObject* " + + reactorName + + "_py_list = PyList_New(" + + generateWidthVariable(reactorName) + + ");", + "if(" + reactorName + "_py_list == NULL) {", + " lf_print_error(\"Could not create the list needed for " + reactorName + ".\");", + " if (PyErr_Occurred()) {", + " PyErr_PrintEx(0);", + " PyErr_Clear(); // this will reset the error indicator so we can run Python code" + + " again", + " }", + " /* Release the thread. No Python API allowed beyond this point. */", + " PyGILState_Release(gstate);", + " Py_FinalizeEx();", + " exit(1);", + "}", + "for (int i = 0; i < " + generateWidthVariable(reactorName) + "; i++) {", + " if (PyList_SetItem(" + reactorName + "_py_list,", + " i,", + " " + generateConvertCPortToPy(reactorName + "[i]." + port.getName(), widthSpec), + " ) != 0) {", + " lf_print_error(\"Could not add elements to the list for " + reactorName + ".\");", + " if (PyErr_Occurred()) {", + " PyErr_PrintEx(0);", + " PyErr_Clear(); // this will reset the error indicator so we can run Python" + + " code again", + " }", + " /* Release the thread. No Python API allowed beyond this point. */", + " PyGILState_Release(gstate);", + " Py_FinalizeEx();", + " exit(1);", + " }", + "}"); + } - /** - * Generate code that creates a Python list (i.e., []) for contained banks to be passed to Python reactions. - * The Python reaction will then subsequently be able to address each individual bank member of the contained - * bank using an index or an iterator. Each list member will contain the given port - * (which could be a multiport with a width determined by widthSpec). - * - * This is to accommodate reactions like reaction() -> s.out where s is a bank. In this example, - * the generated Python function will have the signature reaction_function_0(self, s_out), where - * s_out is a list of out ports. This will later be turned into the proper s.out format using the - * Python code generated in {@link #generatePythonPortVariableInReaction}. - * - * @param reactorName The name of the bank of reactors (which is the name of the reactor class). - * @param port The port that should be put in the Python list. - * @param widthSpec A string that should be -2 for non-multiports and the width expression for multiports. - */ - public static String generatePythonListForContainedBank(String reactorName, Port port, String widthSpec) { - return String.join("\n", - "PyObject* "+reactorName+"_py_list = PyList_New("+generateWidthVariable(reactorName)+");", - "if("+reactorName+"_py_list == NULL) {", - " lf_print_error(\"Could not create the list needed for "+reactorName+".\");", - " if (PyErr_Occurred()) {", - " PyErr_PrintEx(0);", - " PyErr_Clear(); // this will reset the error indicator so we can run Python code again", - " }", - " /* Release the thread. No Python API allowed beyond this point. */", - " PyGILState_Release(gstate);", - " Py_FinalizeEx();", - " exit(1);", - "}", - "for (int i = 0; i < "+generateWidthVariable(reactorName)+"; i++) {", - " if (PyList_SetItem("+reactorName+"_py_list,", - " i,", - " "+generateConvertCPortToPy(reactorName + "[i]." + port.getName(), widthSpec), - " ) != 0) {", - " lf_print_error(\"Could not add elements to the list for "+reactorName+".\");", - " if (PyErr_Occurred()) {", - " PyErr_PrintEx(0);", - " PyErr_Clear(); // this will reset the error indicator so we can run Python code again", - " }", - " /* Release the thread. No Python API allowed beyond this point. */", - " PyGILState_Release(gstate);", - " Py_FinalizeEx();", - " exit(1);", - " }", - "}" - ); - } + public static String generateAliasTypeDef( + TypeParameterizedReactor tpr, Port port, boolean isTokenType, String genericPortType) { + return "typedef " + + genericPortType + + " " + + CGenerator.variableStructType(port, tpr, false) + + ";"; + } - public static String generateAliasTypeDef(TypeParameterizedReactor tpr, Port port, boolean isTokenType, String genericPortType) { - return "typedef "+genericPortType+" "+CGenerator.variableStructType(port, tpr, false)+";"; - } - - private static String generateConvertCPortToPy(String port) { - return String.format("convert_C_port_to_py(%s, %s)", port, generateWidthVariable(port)); - } + private static String generateConvertCPortToPy(String port) { + return String.format("convert_C_port_to_py(%s, %s)", port, generateWidthVariable(port)); + } - private static String generateConvertCPortToPy(String port, String widthSpec) { - return String.format("convert_C_port_to_py(%s, %s)", port, widthSpec); - } + private static String generateConvertCPortToPy(String port, String widthSpec) { + return String.format("convert_C_port_to_py(%s, %s)", port, widthSpec); + } - /** - * Generate into the specified string builder (inits) the code to - * initialize local variable for port so that it can be used in the body of - * the Python reaction. - * @param port The port to generate code for. - */ - public static String generatePythonPortVariableInReaction(VarRef port) { - String containerName = port.getContainer().getName(); - String variableName = port.getVariable().getName(); - String tryStatement = "try: "+containerName+" # pylint: disable=used-before-assignment"; - if (port.getContainer().getWidthSpec() != null) { - // It's a bank - return String.join("\n", - tryStatement, - "except NameError: "+containerName+" = [None] * len("+containerName+"_"+variableName+")", - "for i in range(len("+containerName+"_"+variableName+")):", - " if "+containerName+"[i] is None: "+containerName+"[i] = Make()", - " "+containerName+"[i]."+variableName+" = "+containerName+"_"+variableName+"[i]" - ); - } else { - return String.join("\n", - tryStatement, - "except NameError: "+containerName+" = Make()", - containerName+"."+variableName+" = "+containerName+"_"+variableName - ); - } + /** + * Generate into the specified string builder (inits) the code to + * initialize local variable for port so that it can be used in the body of + * the Python reaction. + * @param port The port to generate code for. + */ + public static String generatePythonPortVariableInReaction(VarRef port) { + String containerName = port.getContainer().getName(); + String variableName = port.getVariable().getName(); + String tryStatement = "try: " + containerName + " # pylint: disable=used-before-assignment"; + if (port.getContainer().getWidthSpec() != null) { + // It's a bank + return String.join( + "\n", + tryStatement, + "except NameError: " + + containerName + + " = [None] * len(" + + containerName + + "_" + + variableName + + ")", + "for i in range(len(" + containerName + "_" + variableName + ")):", + " if " + containerName + "[i] is None: " + containerName + "[i] = Make()", + " " + + containerName + + "[i]." + + variableName + + " = " + + containerName + + "_" + + variableName + + "[i]"); + } else { + return String.join( + "\n", + tryStatement, + "except NameError: " + containerName + " = Make()", + containerName + "." + variableName + " = " + containerName + "_" + variableName); } + } } diff --git a/org.lflang/src/org/lflang/generator/python/PythonPreambleGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonPreambleGenerator.java index b69320f6af..bd1291425f 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonPreambleGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonPreambleGenerator.java @@ -3,60 +3,53 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -import org.lflang.ast.ASTUtils; import org.lflang.TargetConfig; +import org.lflang.ast.ASTUtils; import org.lflang.generator.CodeBuilder; import org.lflang.generator.c.CPreambleGenerator; import org.lflang.lf.Preamble; - /** - * Generates user-defined preambles and #define and #include directives - * for the Python target. + * Generates user-defined preambles and #define and #include directives for the Python target. * * @author Edward A. Lee * @author Soroush Bateni * @author Hou Seng Wong */ public class PythonPreambleGenerator { - /** - * Generates preambles defined by user for a given reactor. - * The preamble code is put inside the reactor class. - */ - public static String generatePythonPreambles(List preambles) { - List preamblesCode = new ArrayList<>(); - preambles.forEach(p -> preamblesCode.add(ASTUtils.toText(p.getCode()))); - return preamblesCode.size() > 0 ? String.join("\n", + /** + * Generates preambles defined by user for a given reactor. The preamble code is put inside the + * reactor class. + */ + public static String generatePythonPreambles(List preambles) { + List preamblesCode = new ArrayList<>(); + preambles.forEach(p -> preamblesCode.add(ASTUtils.toText(p.getCode()))); + return preamblesCode.size() > 0 + ? String.join( + "\n", "# From the preamble, verbatim:", String.join("\n", preamblesCode), - "# End of preamble." - ) : ""; - } + "# End of preamble.") + : ""; + } - public static String generateCDefineDirectives( - TargetConfig targetConfig, - Path srcGenPath, - boolean hasModalReactors - ) { - // TODO: Delete all of this. It is not used. - CodeBuilder code = new CodeBuilder(); - code.pr(CPreambleGenerator.generateDefineDirectives( - targetConfig, srcGenPath, hasModalReactors) - ); - return code.toString(); - } + public static String generateCDefineDirectives( + TargetConfig targetConfig, Path srcGenPath, boolean hasModalReactors) { + // TODO: Delete all of this. It is not used. + CodeBuilder code = new CodeBuilder(); + code.pr( + CPreambleGenerator.generateDefineDirectives(targetConfig, srcGenPath, hasModalReactors)); + return code.toString(); + } - public static String generateCIncludeStatements( - TargetConfig targetConfig, - boolean CCppMode, - boolean hasModalReactors - ) { - CodeBuilder code = new CodeBuilder(); - code.pr(CPreambleGenerator.generateIncludeStatements(targetConfig, CCppMode)); - code.pr("#include \"pythontarget.h\""); - if (hasModalReactors) { - code.pr("#include \"include/modal_models/definitions.h\""); - } - return code.toString(); + public static String generateCIncludeStatements( + TargetConfig targetConfig, boolean CCppMode, boolean hasModalReactors) { + CodeBuilder code = new CodeBuilder(); + code.pr(CPreambleGenerator.generateIncludeStatements(targetConfig, CCppMode)); + code.pr("#include \"pythontarget.h\""); + if (hasModalReactors) { + 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 3d307b6b52..87f594a71a 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonReactionGenerator.java @@ -4,560 +4,632 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; - -import org.lflang.ast.ASTUtils; import org.lflang.AttributeUtils; import org.lflang.ErrorReporter; import org.lflang.Target; - +import org.lflang.ast.ASTUtils; +import org.lflang.generator.CodeBuilder; +import org.lflang.generator.ReactionInstance; +import org.lflang.generator.ReactorInstance; +import org.lflang.generator.c.CCoreFilesUtils; import org.lflang.generator.c.CReactionGenerator; +import org.lflang.generator.c.CTypes; +import org.lflang.generator.c.CUtil; import org.lflang.generator.c.TypeParameterizedReactor; -import org.lflang.lf.ReactorDecl; -import org.lflang.lf.Reaction; -import org.lflang.lf.Reactor; import org.lflang.lf.Action; import org.lflang.lf.Code; +import org.lflang.lf.Input; +import org.lflang.lf.Instantiation; +import org.lflang.lf.Mode; +import org.lflang.lf.Output; +import org.lflang.lf.Port; +import org.lflang.lf.Reaction; +import org.lflang.lf.Reactor; +import org.lflang.lf.ReactorDecl; import org.lflang.lf.TriggerRef; import org.lflang.lf.VarRef; import org.lflang.util.StringUtil; -import org.lflang.lf.Instantiation; -import org.lflang.lf.Port; -import org.lflang.lf.Input; -import org.lflang.lf.Output; -import org.lflang.generator.c.CCoreFilesUtils; -import org.lflang.generator.c.CTypes; -import org.lflang.generator.c.CUtil; -import org.lflang.generator.CodeBuilder; -import org.lflang.generator.ReactionInstance; -import org.lflang.generator.ReactorInstance; -import org.lflang.lf.Mode; public class PythonReactionGenerator { - /** - * 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 pyObjects CPython related objects - */ - public static String generateCPythonReactionCaller(TypeParameterizedReactor reactor, - int reactionIndex, - List pyObjects, - String inits) { - String pythonFunctionName = generatePythonReactionFunctionName(reactionIndex); - String cpythonFunctionName = generateCPythonReactionFunctionName(reactionIndex); - return generateCPythonFunctionCaller(CUtil.getName(reactor), pythonFunctionName, cpythonFunctionName, pyObjects, inits); - } - - /** - * 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 pyObjects CPython related objects - */ - public static String generateCPythonDeadlineCaller(TypeParameterizedReactor r, - int reactionIndex, - List pyObjects) { - String pythonFunctionName = generatePythonDeadlineFunctionName(reactionIndex); - String cpythonFunctionName = generateCPythonDeadlineFunctionName(reactionIndex); - return generateCPythonFunctionCaller(CUtil.getName(r), pythonFunctionName, cpythonFunctionName, pyObjects, ""); - } - - /** - * 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 pyObjects CPython related objects - */ - public static String generateCPythonSTPCaller(TypeParameterizedReactor r, - int reactionIndex, - List pyObjects) { - String pythonFunctionName = generatePythonSTPFunctionName(reactionIndex); - String cpythonFunctionName = generateCPythonSTPFunctionName(reactionIndex); - return generateCPythonFunctionCaller(CUtil.getName(r), pythonFunctionName, cpythonFunctionName, pyObjects, ""); + /** + * 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 pyObjects CPython related objects + */ + public static String generateCPythonReactionCaller( + TypeParameterizedReactor reactor, int reactionIndex, List pyObjects, String inits) { + String pythonFunctionName = generatePythonReactionFunctionName(reactionIndex); + String cpythonFunctionName = generateCPythonReactionFunctionName(reactionIndex); + return generateCPythonFunctionCaller( + CUtil.getName(reactor), pythonFunctionName, cpythonFunctionName, pyObjects, inits); + } + + /** + * 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 pyObjects CPython related objects + */ + public static String generateCPythonDeadlineCaller( + TypeParameterizedReactor r, int reactionIndex, List pyObjects) { + String pythonFunctionName = generatePythonDeadlineFunctionName(reactionIndex); + String cpythonFunctionName = generateCPythonDeadlineFunctionName(reactionIndex); + return generateCPythonFunctionCaller( + CUtil.getName(r), pythonFunctionName, cpythonFunctionName, pyObjects, ""); + } + + /** + * 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 pyObjects CPython related objects + */ + public static String generateCPythonSTPCaller( + TypeParameterizedReactor r, int reactionIndex, List pyObjects) { + String pythonFunctionName = generatePythonSTPFunctionName(reactionIndex); + String cpythonFunctionName = generateCPythonSTPFunctionName(reactionIndex); + return generateCPythonFunctionCaller( + CUtil.getName(r), pythonFunctionName, cpythonFunctionName, pyObjects, ""); + } + + /** + * Generate code to call a CPython function. + * + * @param reactorDeclName The name of the reactor for debugging purposes + * @param pythonFunctionName The name of the function in the .py file. + * @param cpythonFunctionName The name of the function in self struct of the .c file. + * @param pyObjects CPython related objects + */ + private static String generateCPythonFunctionCaller( + String reactorDeclName, + String pythonFunctionName, + String cpythonFunctionName, + List pyObjects, + String inits) { + String pyObjectsJoined = pyObjects.size() > 0 ? ", " + String.join(", ", pyObjects) : ""; + CodeBuilder code = new CodeBuilder(); + code.pr(PyUtil.generateGILAcquireCode()); + code.pr(inits); + code.pr( + String.join( + "\n", + "LF_PRINT_DEBUG(\"Calling reaction function " + + reactorDeclName + + "." + + pythonFunctionName + + "\");", + "PyObject *rValue = PyObject_CallObject(", + " self->" + cpythonFunctionName + ", ", + " Py_BuildValue(\"(" + "O".repeat(pyObjects.size()) + ")\"" + pyObjectsJoined + ")", + ");", + "if (rValue == NULL) {", + " lf_print_error(\"FATAL: Calling reaction " + + reactorDeclName + + "." + + pythonFunctionName + + " failed.\");", + " if (PyErr_Occurred()) {", + " PyErr_PrintEx(0);", + " PyErr_Clear(); // this will reset the error indicator so we can run Python" + + " code again", + " }", + " " + PyUtil.generateGILReleaseCode(), + " Py_FinalizeEx();", + " exit(1);", + "}", + "", + "/* Release the thread. No Python API allowed beyond this point. */", + PyUtil.generateGILReleaseCode())); + return code.toString(); + } + + /** + * 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 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. + * @param types A helper class for type-related stuff. + */ + public static String generateCReaction( + Reaction reaction, + TypeParameterizedReactor tpr, + Reactor r, + int reactionIndex, + Instantiation mainDef, + ErrorReporter errorReporter, + CTypes types) { + // Contains the actual comma separated list of inputs to the reaction of type + // generic_port_instance_struct. + // 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, r, pyObjects, errorReporter); + String cInit = + CReactionGenerator.generateInitializationForReaction( + "", + reaction, + tpr, + reactionIndex, + types, + errorReporter, + mainDef, + Target.Python.requiresTypes); + code.pr("#include " + StringUtil.addDoubleQuotes(CCoreFilesUtils.getCTargetSetHeader())); + code.pr( + generateFunction( + CReactionGenerator.generateReactionFunctionHeader(tpr, reactionIndex), + cInit, + reaction.getCode(), + generateCPythonReactionCaller(tpr, reactionIndex, pyObjects, cPyInit))); + + // Generate code for the STP violation handler, if there is one. + if (reaction.getStp() != null) { + code.pr( + generateFunction( + CReactionGenerator.generateStpFunctionHeader(tpr, reactionIndex), + cInit, + reaction.getStp().getCode(), + generateCPythonSTPCaller(tpr, reactionIndex, pyObjects))); } - - /** - * Generate code to call a CPython function. - * @param reactorDeclName The name of the reactor for debugging purposes - * @param pythonFunctionName The name of the function in the .py file. - * @param cpythonFunctionName The name of the function in self struct of the .c file. - * @param pyObjects CPython related objects - */ - private static String generateCPythonFunctionCaller(String reactorDeclName, - String pythonFunctionName, - String cpythonFunctionName, - List pyObjects, - String inits) { - String pyObjectsJoined = pyObjects.size() > 0 ? ", " + String.join(", ", pyObjects) : ""; - CodeBuilder code = new CodeBuilder(); - code.pr(PyUtil.generateGILAcquireCode()); - code.pr(inits); - code.pr(String.join("\n", - "LF_PRINT_DEBUG(\"Calling reaction function "+reactorDeclName+"."+pythonFunctionName+"\");", - "PyObject *rValue = PyObject_CallObject(", - " self->"+cpythonFunctionName+", ", - " Py_BuildValue(\"("+"O".repeat(pyObjects.size())+")\""+pyObjectsJoined+")", - ");", - "if (rValue == NULL) {", - " lf_print_error(\"FATAL: Calling reaction "+reactorDeclName+"."+pythonFunctionName+" failed.\");", - " if (PyErr_Occurred()) {", - " PyErr_PrintEx(0);", - " PyErr_Clear(); // this will reset the error indicator so we can run Python code again", - " }", - " "+PyUtil.generateGILReleaseCode(), - " Py_FinalizeEx();", - " exit(1);", - "}", - "", - "/* Release the thread. No Python API allowed beyond this point. */", - PyUtil.generateGILReleaseCode() - )); - return code.toString(); + // Generate code for the deadline violation function, if there is one. + if (reaction.getDeadline() != null) { + code.pr( + generateFunction( + CReactionGenerator.generateDeadlineFunctionHeader(tpr, reactionIndex), + cInit, + reaction.getDeadline().getCode(), + generateCPythonDeadlineCaller(tpr, reactionIndex, pyObjects))); } - - /** - * 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 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. - * @param types A helper class for type-related stuff. - */ - public static String generateCReaction( - Reaction reaction, - TypeParameterizedReactor tpr, - Reactor r, - int reactionIndex, - Instantiation mainDef, - ErrorReporter errorReporter, - CTypes types - ) { - // Contains the actual comma separated list of inputs to the reaction of type generic_port_instance_struct. - // 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, r, pyObjects, errorReporter); - String cInit = CReactionGenerator.generateInitializationForReaction( - "", reaction, tpr, reactionIndex, - types, errorReporter, mainDef, - Target.Python.requiresTypes); + code.pr("#include " + StringUtil.addDoubleQuotes(CCoreFilesUtils.getCTargetSetUndefHeader())); + return code.toString(); + } + + public static String generateFunction(String header, String init, Code code, String pyCaller) { + var function = new CodeBuilder(); + function.pr(header + "{"); + function.indent(); + function.pr(init); + function.prSourceLineNumber(code); + function.pr(pyCaller); + function.unindent(); + function.pr("}"); + return function.toString(); + } + + /** + * Generate necessary Python-specific initialization code for reaction that belongs to reactor + * decl. + * + * @param reaction The reaction to generate Python-specific initialization for. + * @param decl The reactor to which reaction belongs to. + * @param pyObjects A list of expressions that can be used as additional arguments to Py_BuildValue + * (@see docs.python.org/3/c-api). + * We will use as a format string, "(O...O)" where the number of O's is equal to the length of the list. + */ + private static String generateCPythonInitializers( + Reaction reaction, ReactorDecl decl, List pyObjects, ErrorReporter errorReporter) { + Set actionsAsTriggers = new LinkedHashSet<>(); + Reactor reactor = ASTUtils.toDefinition(decl); + CodeBuilder code = new CodeBuilder(); + // Next, add the triggers (input and actions; timers are not needed). + // TODO: handle triggers + for (TriggerRef trigger : ASTUtils.convertToEmptyListIfNull(reaction.getTriggers())) { + if (trigger instanceof VarRef triggerAsVarRef) { code.pr( - "#include " + StringUtil.addDoubleQuotes( - CCoreFilesUtils.getCTargetSetHeader())); - code.pr(generateFunction( - CReactionGenerator.generateReactionFunctionHeader(tpr, reactionIndex), - cInit, reaction.getCode(), - generateCPythonReactionCaller(tpr, reactionIndex, pyObjects, cPyInit) - )); - - // Generate code for the STP violation handler, if there is one. - if (reaction.getStp() != null) { - code.pr(generateFunction( - CReactionGenerator.generateStpFunctionHeader(tpr, reactionIndex), - cInit, reaction.getStp().getCode(), - generateCPythonSTPCaller(tpr, reactionIndex, pyObjects) - )); - } - // Generate code for the deadline violation function, if there is one. - if (reaction.getDeadline() != null) { - code.pr(generateFunction( - CReactionGenerator.generateDeadlineFunctionHeader(tpr, reactionIndex), - cInit, reaction.getDeadline().getCode(), - generateCPythonDeadlineCaller(tpr, reactionIndex, pyObjects) - )); - } - code.pr( - "#include " + StringUtil.addDoubleQuotes( - CCoreFilesUtils.getCTargetSetUndefHeader())); - return code.toString(); + generateVariableToSendPythonReaction( + triggerAsVarRef, actionsAsTriggers, decl, pyObjects)); + } } - - public static String generateFunction( - String header, String init, Code code, String pyCaller - ) { - var function = new CodeBuilder(); - function.pr(header + "{"); - function.indent(); - function.pr(init); - function.prSourceLineNumber(code); - function.pr(pyCaller); - function.unindent(); - function.pr("}"); - return function.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()) { + PythonPortGenerator.generateInputVariablesToSendToPythonReaction(pyObjects, input, decl); + } } - /** - * Generate necessary Python-specific initialization code for reaction that belongs to reactor - * decl. - * - * @param reaction The reaction to generate Python-specific initialization for. - * @param decl The reactor to which reaction belongs to. - * @param pyObjects A list of expressions that can be used as additional arguments to Py_BuildValue - * (@see docs.python.org/3/c-api). - * We will use as a format string, "(O...O)" where the number of O's is equal to the length of the list. - */ - private static String generateCPythonInitializers(Reaction reaction, - ReactorDecl decl, - List pyObjects, - ErrorReporter errorReporter) { - Set actionsAsTriggers = new LinkedHashSet<>(); - Reactor reactor = ASTUtils.toDefinition(decl); - CodeBuilder code = new CodeBuilder(); - // Next, add the triggers (input and actions; timers are not needed). - // TODO: handle triggers - for (TriggerRef trigger : ASTUtils.convertToEmptyListIfNull(reaction.getTriggers())) { - if (trigger instanceof VarRef triggerAsVarRef) { - code.pr(generateVariableToSendPythonReaction(triggerAsVarRef, actionsAsTriggers, decl, pyObjects)); - } - } - 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()) { - PythonPortGenerator.generateInputVariablesToSendToPythonReaction(pyObjects, input, decl); - } - } - - // Next add non-triggering inputs. - for (VarRef src : ASTUtils.convertToEmptyListIfNull(reaction.getSources())) { - code.pr(generateVariableToSendPythonReaction(src, actionsAsTriggers, decl, pyObjects)); - } - - // Next, handle effects - if (reaction.getEffects() != null) { - for (VarRef effect : reaction.getEffects()) { - if (effect.getVariable() 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())) { - PythonPortGenerator.generateActionVariableToSendToPythonReaction(pyObjects, - (Action) effect.getVariable(), decl); - } - } else if (effect.getVariable() instanceof Mode) { - String name = effect.getVariable().getName(); - pyObjects.add("convert_C_mode_to_py("+name+",(self_base_t*)self, _lf_"+name+"_change_type)"); - } else { - if (effect.getVariable() instanceof Output) { - PythonPortGenerator.generateOutputVariablesToSendToPythonReaction(pyObjects, (Output) effect.getVariable()); - } else if (effect.getVariable() instanceof Input) { - // It is the input of a contained reactor. - code.pr(PythonPortGenerator.generateVariablesForSendingToContainedReactors(pyObjects, effect.getContainer(), (Input) effect.getVariable())); - } else { - errorReporter.reportError( - reaction, - "In generateReaction(): " + effect.getVariable().getName() + " is neither an input nor an output." - ); - } - } - } - } - return code.toString(); + // Next add non-triggering inputs. + for (VarRef src : ASTUtils.convertToEmptyListIfNull(reaction.getSources())) { + code.pr(generateVariableToSendPythonReaction(src, actionsAsTriggers, decl, pyObjects)); } - /** - * Generate parameters and their respective initialization code for a reaction function - * The initialization code is put at the beginning of the reaction before user code - * @param parameters The parameters used for function definition - * @param inits The initialization code for those paramters - * @param decl Reactor declaration - * @param reaction The reaction to be used to generate parameters for - */ - public static void generatePythonReactionParametersAndInitializations(List parameters, CodeBuilder inits, - ReactorDecl decl, Reaction reaction) { - Reactor reactor = ASTUtils.toDefinition(decl); - LinkedHashSet generatedParams = new LinkedHashSet<>(); - - // Handle triggers - for (TriggerRef trigger : ASTUtils.convertToEmptyListIfNull(reaction.getTriggers())) { - if (!(trigger instanceof VarRef triggerAsVarRef)) { - continue; - } - if (triggerAsVarRef.getVariable() instanceof Port) { - if (triggerAsVarRef.getVariable() instanceof Input) { - if (((Input) triggerAsVarRef.getVariable()).isMutable()) { - generatedParams.add("mutable_"+triggerAsVarRef.getVariable().getName()); - - // Create a deep copy - if (ASTUtils.isMultiport((Input) triggerAsVarRef.getVariable())) { - inits. - pr(triggerAsVarRef.getVariable().getName()+" = [Make() for i in range(len(mutable_"+triggerAsVarRef.getVariable().getName()+"))]"); - inits.pr("for i in range(len(mutable_"+triggerAsVarRef.getVariable().getName()+")):"); - inits.pr(" "+triggerAsVarRef.getVariable().getName()+"[i].value = copy.deepcopy(mutable_"+triggerAsVarRef.getVariable().getName()+"[i].value)"); - } else { - inits.pr(triggerAsVarRef.getVariable().getName()+" = Make()"); - inits. - pr(triggerAsVarRef.getVariable().getName()+".value = copy.deepcopy(mutable_"+triggerAsVarRef.getVariable().getName()+".value)"); - } - } else { - generatedParams.add(triggerAsVarRef.getVariable().getName()); - } - } else { - // Handle contained reactors' ports - generatedParams.add(triggerAsVarRef.getContainer().getName()+"_"+triggerAsVarRef.getVariable().getName()); - inits.pr(PythonPortGenerator.generatePythonPortVariableInReaction(triggerAsVarRef)); - } - } else if (triggerAsVarRef.getVariable() instanceof Action) { - generatedParams.add(triggerAsVarRef.getVariable().getName()); - } - } - - // Handle non-triggering inputs - if (reaction.getTriggers() == null || reaction.getTriggers().size() == 0) { - for (Input input : ASTUtils.convertToEmptyListIfNull(reactor.getInputs())) { - generatedParams.add(input.getName()); - if (input.isMutable()) { - // Create a deep copy - inits.pr(input.getName()+" = copy.deepcopy("+input.getName()+")"); - } - } - } - for (VarRef src : ASTUtils.convertToEmptyListIfNull(reaction.getSources())) { - if (src.getVariable() instanceof Output) { - // Output of a contained reactor - generatedParams.add(src.getContainer().getName()+"_"+src.getVariable().getName()); - inits.pr(PythonPortGenerator.generatePythonPortVariableInReaction(src)); - } else { - generatedParams.add(src.getVariable().getName()); - if (src.getVariable() instanceof Input) { - if (((Input) src.getVariable()).isMutable()) { - // Create a deep copy - inits.pr(src.getVariable().getName()+" = copy.deepcopy("+src.getVariable().getName()+")"); - } - } - } + // Next, handle effects + if (reaction.getEffects() != null) { + for (VarRef effect : reaction.getEffects()) { + if (effect.getVariable() 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())) { + PythonPortGenerator.generateActionVariableToSendToPythonReaction( + pyObjects, (Action) effect.getVariable(), decl); + } + } else if (effect.getVariable() instanceof Mode) { + String name = effect.getVariable().getName(); + pyObjects.add( + "convert_C_mode_to_py(" + + name + + ",(self_base_t*)self, _lf_" + + name + + "_change_type)"); + } else { + if (effect.getVariable() instanceof Output) { + PythonPortGenerator.generateOutputVariablesToSendToPythonReaction( + pyObjects, (Output) effect.getVariable()); + } else if (effect.getVariable() instanceof Input) { + // It is the input of a contained reactor. + code.pr( + PythonPortGenerator.generateVariablesForSendingToContainedReactors( + pyObjects, effect.getContainer(), (Input) effect.getVariable())); + } else { + errorReporter.reportError( + reaction, + "In generateReaction(): " + + effect.getVariable().getName() + + " is neither an input nor an output."); + } } - - // Handle effects - for (VarRef effect : ASTUtils.convertToEmptyListIfNull(reaction.getEffects())) { - if (effect.getVariable() instanceof Input) { - generatedParams.add(effect.getContainer().getName()+"_"+effect.getVariable().getName()); - inits.pr(PythonPortGenerator.generatePythonPortVariableInReaction(effect)); + } + } + return code.toString(); + } + + /** + * Generate parameters and their respective initialization code for a reaction function The + * initialization code is put at the beginning of the reaction before user code + * + * @param parameters The parameters used for function definition + * @param inits The initialization code for those paramters + * @param decl Reactor declaration + * @param reaction The reaction to be used to generate parameters for + */ + public static void generatePythonReactionParametersAndInitializations( + List parameters, CodeBuilder inits, ReactorDecl decl, Reaction reaction) { + Reactor reactor = ASTUtils.toDefinition(decl); + LinkedHashSet generatedParams = new LinkedHashSet<>(); + + // Handle triggers + for (TriggerRef trigger : ASTUtils.convertToEmptyListIfNull(reaction.getTriggers())) { + if (!(trigger instanceof VarRef triggerAsVarRef)) { + continue; + } + if (triggerAsVarRef.getVariable() instanceof Port) { + if (triggerAsVarRef.getVariable() instanceof Input) { + if (((Input) triggerAsVarRef.getVariable()).isMutable()) { + generatedParams.add("mutable_" + triggerAsVarRef.getVariable().getName()); + + // Create a deep copy + if (ASTUtils.isMultiport((Input) triggerAsVarRef.getVariable())) { + inits.pr( + triggerAsVarRef.getVariable().getName() + + " = [Make() for i in range(len(mutable_" + + triggerAsVarRef.getVariable().getName() + + "))]"); + inits.pr( + "for i in range(len(mutable_" + triggerAsVarRef.getVariable().getName() + ")):"); + inits.pr( + " " + + triggerAsVarRef.getVariable().getName() + + "[i].value = copy.deepcopy(mutable_" + + triggerAsVarRef.getVariable().getName() + + "[i].value)"); } else { - generatedParams.add(effect.getVariable().getName()); - if (effect.getVariable() instanceof Port) { - if (ASTUtils.isMultiport((Port) effect.getVariable())) { - // Handle multiports - } - } + inits.pr(triggerAsVarRef.getVariable().getName() + " = Make()"); + inits.pr( + triggerAsVarRef.getVariable().getName() + + ".value = copy.deepcopy(mutable_" + + triggerAsVarRef.getVariable().getName() + + ".value)"); } + } else { + generatedParams.add(triggerAsVarRef.getVariable().getName()); + } + } else { + // Handle contained reactors' ports + generatedParams.add( + triggerAsVarRef.getContainer().getName() + + "_" + + triggerAsVarRef.getVariable().getName()); + inits.pr(PythonPortGenerator.generatePythonPortVariableInReaction(triggerAsVarRef)); } - - for (String s : generatedParams) { - parameters.add(s); - } + } else if (triggerAsVarRef.getVariable() instanceof Action) { + generatedParams.add(triggerAsVarRef.getVariable().getName()); + } } - private static String generateVariableToSendPythonReaction(VarRef varRef, - Set actionsAsTriggers, - ReactorDecl decl, - List pyObjects) { - if (varRef.getVariable() instanceof Port) { - return PythonPortGenerator.generatePortVariablesToSendToPythonReaction(pyObjects, varRef, decl); - } else if (varRef.getVariable() instanceof Action) { - actionsAsTriggers.add((Action) varRef.getVariable()); - PythonPortGenerator.generateActionVariableToSendToPythonReaction(pyObjects, (Action) varRef.getVariable(), decl); + // Handle non-triggering inputs + if (reaction.getTriggers() == null || reaction.getTriggers().size() == 0) { + for (Input input : ASTUtils.convertToEmptyListIfNull(reactor.getInputs())) { + generatedParams.add(input.getName()); + if (input.isMutable()) { + // Create a deep copy + inits.pr(input.getName() + " = copy.deepcopy(" + input.getName() + ")"); } - return ""; + } } - - /** - * Generate Python code to link cpython functions to python functions for each reaction. - * @param instance The reactor instance. - * @param mainDef The definition of the main reactor - */ - public static String generateCPythonReactionLinkers( - ReactorInstance instance, - Instantiation mainDef - ) { - String nameOfSelfStruct = CUtil.reactorRef(instance); - Reactor reactor = ASTUtils.toDefinition(instance.getDefinition().getReactorClass()); - CodeBuilder code = new CodeBuilder(); - - // Initialize the name field to the unique name of the instance - code.pr(nameOfSelfStruct+"->_lf_name = \""+instance.uniqueID()+"_lf\";"); - - for (ReactionInstance reaction : instance.reactions) { - // Reactions marked with a {@code @_c_body} attribute are generated in C - if (AttributeUtils.hasCBody(reaction.getDefinition())) continue; - // Create a PyObject for each reaction - code.pr(generateCPythonReactionLinker(instance, reaction, nameOfSelfStruct)); + for (VarRef src : ASTUtils.convertToEmptyListIfNull(reaction.getSources())) { + if (src.getVariable() instanceof Output) { + // Output of a contained reactor + generatedParams.add(src.getContainer().getName() + "_" + src.getVariable().getName()); + inits.pr(PythonPortGenerator.generatePythonPortVariableInReaction(src)); + } else { + generatedParams.add(src.getVariable().getName()); + if (src.getVariable() instanceof Input) { + if (((Input) src.getVariable()).isMutable()) { + // Create a deep copy + inits.pr( + src.getVariable().getName() + + " = copy.deepcopy(" + + src.getVariable().getName() + + ")"); + } } - return code.toString(); + } } - /** - * Generate Python code to link cpython functions to python functions for a reaction. - * @param instance The reactor instance. - * @param reaction The reaction of this instance to link. - * @param nameOfSelfStruct The name of the self struct in cpython. - */ - public static String generateCPythonReactionLinker( - ReactorInstance instance, - ReactionInstance reaction, - String nameOfSelfStruct - ) { - CodeBuilder code = new CodeBuilder(); - code.pr(generateCPythonFunctionLinker( - nameOfSelfStruct, generateCPythonReactionFunctionName(reaction.index), - instance, generatePythonReactionFunctionName(reaction.index)) - ); - if (reaction.getDefinition().getStp() != null) { - code.pr(generateCPythonFunctionLinker( - nameOfSelfStruct, generateCPythonSTPFunctionName(reaction.index), - instance, generatePythonSTPFunctionName(reaction.index)) - ); - } - if (reaction.getDefinition().getDeadline() != null) { - code.pr(generateCPythonFunctionLinker( - nameOfSelfStruct, generateCPythonDeadlineFunctionName(reaction.index), - instance, generatePythonDeadlineFunctionName(reaction.index)) - ); + // Handle effects + for (VarRef effect : ASTUtils.convertToEmptyListIfNull(reaction.getEffects())) { + if (effect.getVariable() instanceof Input) { + generatedParams.add(effect.getContainer().getName() + "_" + effect.getVariable().getName()); + inits.pr(PythonPortGenerator.generatePythonPortVariableInReaction(effect)); + } else { + generatedParams.add(effect.getVariable().getName()); + if (effect.getVariable() instanceof Port) { + if (ASTUtils.isMultiport((Port) effect.getVariable())) { + // Handle multiports + } } - return code.toString(); + } } - /** - * Generate code to link "pythonFunctionName" to "cpythonFunctionName" in "nameOfSelfStruct" of "instance". - * @param nameOfSelfStruct the self struct name of instance - * @param cpythonFunctionName the name of the cpython function - * @param instance the reactor instance - * @param pythonFunctionName the name of the python function - */ - private static String generateCPythonFunctionLinker(String nameOfSelfStruct, String cpythonFunctionName, ReactorInstance instance, String pythonFunctionName) { - return String.join("\n", - nameOfSelfStruct+"->"+cpythonFunctionName+" = ", - "get_python_function(\"__main__\", ", - " "+nameOfSelfStruct+"->_lf_name,", - " "+CUtil.runtimeIndex(instance)+",", - " \""+pythonFunctionName+"\");", - "if("+nameOfSelfStruct+"->"+cpythonFunctionName+" == NULL) {", - " lf_print_error_and_exit(\"Could not load function "+pythonFunctionName+"\");", - "}" - ); + for (String s : generatedParams) { + parameters.add(s); } - - /** - * Generate the function that is executed whenever the deadline of the reaction - * with the given reaction index is missed - * @param reaction The reaction to generate deadline miss code for - * @param reactionIndex The agreed-upon index of the reaction in the reactor (should match the C generated code) - * @param reactionParameters The parameters to the deadline violation function, which are the same as the reaction function - */ - public static String generatePythonFunction(String pythonFunctionName, String inits, String reactionBody, List reactionParameters) { - String params = reactionParameters.size() > 0 ? ", " + String.join(", ", reactionParameters) : ""; - CodeBuilder code = new CodeBuilder(); - code.pr("def "+pythonFunctionName+"(self"+params+"):"); - code.indent(); - code.pr(inits); - code.pr(reactionBody); - code.pr("return 0"); - return code.toString(); - } - - - /** - * Generate the Python code for reactions in reactor - * @param reactor The reactor - * @param reactions The reactions of reactor - */ - public static String generatePythonReactions(Reactor reactor, List reactions) { - CodeBuilder code = new CodeBuilder(); - int reactionIndex = 0; - for (Reaction reaction : reactions) { - code.pr(generatePythonReaction(reactor, reaction, reactionIndex)); - reactionIndex++; - } - return code.toString(); - } - - /** - * Generate the Python code for reaction in reactor - * @param reactor The reactor - * @param reaction The reaction of reactor - */ - public static String generatePythonReaction(Reactor reactor, Reaction reaction, int reactionIndex) { - // Reactions marked with a {@code @_c_body} attribute are generated in C - if (AttributeUtils.hasCBody(reaction)) return ""; - - CodeBuilder code = new CodeBuilder(); - List reactionParameters = new ArrayList<>(); // Will contain parameters for the function (e.g., Foo(x,y,z,...) - CodeBuilder inits = new CodeBuilder(); // Will contain initialization code for some parameters - PythonReactionGenerator.generatePythonReactionParametersAndInitializations(reactionParameters, inits, reactor, reaction); - code.pr(generatePythonFunction( - generatePythonReactionFunctionName(reactionIndex), - inits.toString(), - ASTUtils.toText(reaction.getCode()), - reactionParameters - )); - // Generate code for the STP violation handler function, if there is one. - if (reaction.getStp() != null) { - code.pr(generatePythonFunction( - generatePythonSTPFunctionName(reactionIndex), - "", - ASTUtils.toText(reaction.getStp().getCode()), - reactionParameters - )); - } - // Generate code for the deadline violation function, if there is one. - if (reaction.getDeadline() != null) { - code.pr(generatePythonFunction( - generatePythonDeadlineFunctionName(reactionIndex), - "", - ASTUtils.toText(reaction.getDeadline().getCode()), - reactionParameters - )); - } - return code.toString(); + } + + private static String generateVariableToSendPythonReaction( + VarRef varRef, Set actionsAsTriggers, ReactorDecl decl, List pyObjects) { + if (varRef.getVariable() instanceof Port) { + return PythonPortGenerator.generatePortVariablesToSendToPythonReaction( + pyObjects, varRef, decl); + } else if (varRef.getVariable() instanceof Action) { + actionsAsTriggers.add((Action) varRef.getVariable()); + PythonPortGenerator.generateActionVariableToSendToPythonReaction( + pyObjects, (Action) varRef.getVariable(), decl); } - - /** Return the function name of the reaction inside the self struct in the .c file. - * @param reactionIndex The reaction index. - * @return The function name for the reaction. - */ - public static String generateCPythonReactionFunctionName(int reactionIndex) { - return "_lf_py_reaction_function_"+reactionIndex; + return ""; + } + + /** + * Generate Python code to link cpython functions to python functions for each reaction. + * + * @param instance The reactor instance. + * @param mainDef The definition of the main reactor + */ + public static String generateCPythonReactionLinkers( + ReactorInstance instance, Instantiation mainDef) { + String nameOfSelfStruct = CUtil.reactorRef(instance); + Reactor reactor = ASTUtils.toDefinition(instance.getDefinition().getReactorClass()); + CodeBuilder code = new CodeBuilder(); + + // Initialize the name field to the unique name of the instance + code.pr(nameOfSelfStruct + "->_lf_name = \"" + instance.uniqueID() + "_lf\";"); + + for (ReactionInstance reaction : instance.reactions) { + // Reactions marked with a {@code @_c_body} attribute are generated in C + if (AttributeUtils.hasCBody(reaction.getDefinition())) continue; + // Create a PyObject for each reaction + code.pr(generateCPythonReactionLinker(instance, reaction, nameOfSelfStruct)); } - - /** Return the function name of the deadline function inside the self struct in the .c file. - * @param reactionIndex The reaction index. - * @return The function name for the reaction. - */ - public static String generateCPythonDeadlineFunctionName(int reactionIndex) { - return "_lf_py_deadline_function_"+reactionIndex; + return code.toString(); + } + + /** + * Generate Python code to link cpython functions to python functions for a reaction. + * + * @param instance The reactor instance. + * @param reaction The reaction of this instance to link. + * @param nameOfSelfStruct The name of the self struct in cpython. + */ + public static String generateCPythonReactionLinker( + ReactorInstance instance, ReactionInstance reaction, String nameOfSelfStruct) { + CodeBuilder code = new CodeBuilder(); + code.pr( + generateCPythonFunctionLinker( + nameOfSelfStruct, generateCPythonReactionFunctionName(reaction.index), + instance, generatePythonReactionFunctionName(reaction.index))); + if (reaction.getDefinition().getStp() != null) { + code.pr( + generateCPythonFunctionLinker( + nameOfSelfStruct, generateCPythonSTPFunctionName(reaction.index), + instance, generatePythonSTPFunctionName(reaction.index))); } - - /** Return the function name of the STP violation handler function inside the self struct in the .c file. - * @param reactionIndex The reaction index. - * @return The function name for the reaction. - */ - public static String generateCPythonSTPFunctionName(int reactionIndex) { - return "_lf_py_STP_function_"+reactionIndex; + if (reaction.getDefinition().getDeadline() != null) { + code.pr( + generateCPythonFunctionLinker( + nameOfSelfStruct, generateCPythonDeadlineFunctionName(reaction.index), + instance, generatePythonDeadlineFunctionName(reaction.index))); } - - /** Return the function name of the reaction in the .py file. - * @param reactionIndex The reaction index. - * @return The function name for the reaction. - */ - public static String generatePythonReactionFunctionName(int reactionIndex) { - return "reaction_function_" + reactionIndex; + return code.toString(); + } + + /** + * Generate code to link "pythonFunctionName" to "cpythonFunctionName" in "nameOfSelfStruct" of + * "instance". + * + * @param nameOfSelfStruct the self struct name of instance + * @param cpythonFunctionName the name of the cpython function + * @param instance the reactor instance + * @param pythonFunctionName the name of the python function + */ + private static String generateCPythonFunctionLinker( + String nameOfSelfStruct, + String cpythonFunctionName, + ReactorInstance instance, + String pythonFunctionName) { + return String.join( + "\n", + nameOfSelfStruct + "->" + cpythonFunctionName + " = ", + "get_python_function(\"__main__\", ", + " " + nameOfSelfStruct + "->_lf_name,", + " " + CUtil.runtimeIndex(instance) + ",", + " \"" + pythonFunctionName + "\");", + "if(" + nameOfSelfStruct + "->" + cpythonFunctionName + " == NULL) {", + " lf_print_error_and_exit(\"Could not load function " + pythonFunctionName + "\");", + "}"); + } + + /** + * Generate the function that is executed whenever the deadline of the reaction with the given + * reaction index is missed + * + * @param reaction The reaction to generate deadline miss code for + * @param reactionIndex The agreed-upon index of the reaction in the reactor (should match the C + * generated code) + * @param reactionParameters The parameters to the deadline violation function, which are the same + * as the reaction function + */ + public static String generatePythonFunction( + String pythonFunctionName, + String inits, + String reactionBody, + List reactionParameters) { + String params = + reactionParameters.size() > 0 ? ", " + String.join(", ", reactionParameters) : ""; + CodeBuilder code = new CodeBuilder(); + code.pr("def " + pythonFunctionName + "(self" + params + "):"); + code.indent(); + code.pr(inits); + code.pr(reactionBody); + code.pr("return 0"); + return code.toString(); + } + + /** + * Generate the Python code for reactions in reactor + * + * @param reactor The reactor + * @param reactions The reactions of reactor + */ + public static String generatePythonReactions(Reactor reactor, List reactions) { + CodeBuilder code = new CodeBuilder(); + int reactionIndex = 0; + for (Reaction reaction : reactions) { + code.pr(generatePythonReaction(reactor, reaction, reactionIndex)); + reactionIndex++; } - - /** Return the function name of the deadline function in the .py file. - * @param reactionIndex The reaction index. - * @return The function name for the reaction. - */ - public static String generatePythonDeadlineFunctionName(int reactionIndex) { - return "deadline_function_" + reactionIndex; + return code.toString(); + } + + /** + * Generate the Python code for reaction in reactor + * + * @param reactor The reactor + * @param reaction The reaction of reactor + */ + public static String generatePythonReaction( + Reactor reactor, Reaction reaction, int reactionIndex) { + // Reactions marked with a {@code @_c_body} attribute are generated in C + if (AttributeUtils.hasCBody(reaction)) return ""; + + CodeBuilder code = new CodeBuilder(); + List reactionParameters = + new ArrayList<>(); // Will contain parameters for the function (e.g., Foo(x,y,z,...) + CodeBuilder inits = new CodeBuilder(); // Will contain initialization code for some parameters + PythonReactionGenerator.generatePythonReactionParametersAndInitializations( + reactionParameters, inits, reactor, reaction); + code.pr( + generatePythonFunction( + generatePythonReactionFunctionName(reactionIndex), + inits.toString(), + ASTUtils.toText(reaction.getCode()), + reactionParameters)); + // Generate code for the STP violation handler function, if there is one. + if (reaction.getStp() != null) { + code.pr( + generatePythonFunction( + generatePythonSTPFunctionName(reactionIndex), + "", + ASTUtils.toText(reaction.getStp().getCode()), + reactionParameters)); } - - /** Return the function name of the STP violation handler function in the .py file. - * @param reactionIndex The reaction index. - * @return The function name for the reaction. - */ - public static String generatePythonSTPFunctionName(int reactionIndex) { - return "STP_function_" + reactionIndex; + // Generate code for the deadline violation function, if there is one. + if (reaction.getDeadline() != null) { + code.pr( + generatePythonFunction( + generatePythonDeadlineFunctionName(reactionIndex), + "", + ASTUtils.toText(reaction.getDeadline().getCode()), + reactionParameters)); } + return code.toString(); + } + + /** + * Return the function name of the reaction inside the self struct in the .c file. + * + * @param reactionIndex The reaction index. + * @return The function name for the reaction. + */ + public static String generateCPythonReactionFunctionName(int reactionIndex) { + return "_lf_py_reaction_function_" + reactionIndex; + } + + /** + * Return the function name of the deadline function inside the self struct in the .c file. + * + * @param reactionIndex The reaction index. + * @return The function name for the reaction. + */ + public static String generateCPythonDeadlineFunctionName(int reactionIndex) { + return "_lf_py_deadline_function_" + reactionIndex; + } + + /** + * Return the function name of the STP violation handler function inside the self struct in the .c + * file. + * + * @param reactionIndex The reaction index. + * @return The function name for the reaction. + */ + public static String generateCPythonSTPFunctionName(int reactionIndex) { + return "_lf_py_STP_function_" + reactionIndex; + } + + /** + * Return the function name of the reaction in the .py file. + * + * @param reactionIndex The reaction index. + * @return The function name for the reaction. + */ + public static String generatePythonReactionFunctionName(int reactionIndex) { + return "reaction_function_" + reactionIndex; + } + + /** + * Return the function name of the deadline function in the .py file. + * + * @param reactionIndex The reaction index. + * @return The function name for the reaction. + */ + public static String generatePythonDeadlineFunctionName(int reactionIndex) { + return "deadline_function_" + reactionIndex; + } + + /** + * Return the function name of the STP violation handler function in the .py file. + * + * @param reactionIndex The reaction index. + * @return The function name for the reaction. + */ + public static String generatePythonSTPFunctionName(int reactionIndex) { + return "STP_function_" + reactionIndex; + } } diff --git a/org.lflang/src/org/lflang/generator/python/PythonReactorGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonReactorGenerator.java index 6e1a7f0183..8f2d3fd8ad 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonReactorGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonReactorGenerator.java @@ -11,149 +11,162 @@ import org.lflang.lf.ReactorDecl; public class PythonReactorGenerator { - /** - * Wrapper function for the more elaborate generatePythonReactorClass that keeps track - * of visited reactors to avoid duplicate generation - * @param instance The reactor instance to be generated - */ - public static String generatePythonClass(ReactorInstance instance, ReactorInstance main, PythonTypes types) { - List instantiatedClasses = new ArrayList<>(); - return generatePythonClass(instance, instantiatedClasses, main, types); - } - - /** - * Generate a Python class corresponding to decl - * @param instance The reactor instance to be generated - * @param instantiatedClasses A list of visited instances to avoid generating duplicates - */ - public static String generatePythonClass(ReactorInstance instance, - List instantiatedClasses, - ReactorInstance main, PythonTypes types) { - CodeBuilder pythonClasses = new CodeBuilder(); - ReactorDecl decl = instance.getDefinition().getReactorClass(); - Reactor reactor = ASTUtils.toDefinition(decl); - String className = PyUtil.getName(instance.tpr); - if (instantiatedClasses == null) { - return ""; - } - - if (!instantiatedClasses.contains(className)) { - pythonClasses.pr(generatePythonClassHeader(className)); - // Generate preamble code - pythonClasses.indent(); - pythonClasses.pr(PythonPreambleGenerator.generatePythonPreambles(reactor.getPreambles())); - // Handle runtime initializations - pythonClasses.pr(generatePythonConstructor(decl, types)); - pythonClasses.pr(PythonParameterGenerator.generatePythonGetters(decl)); - // Generate methods - pythonClasses.pr(PythonMethodGenerator.generateMethods(reactor)); - // Generate reactions - List reactionToGenerate = ASTUtils.allReactions(reactor); - pythonClasses.pr(PythonReactionGenerator.generatePythonReactions(reactor, reactionToGenerate)); - pythonClasses.unindent(); - pythonClasses.pr("\n"); - instantiatedClasses.add(className); - } + /** + * Wrapper function for the more elaborate generatePythonReactorClass that keeps track of visited + * reactors to avoid duplicate generation + * + * @param instance The reactor instance to be generated + */ + public static String generatePythonClass( + ReactorInstance instance, ReactorInstance main, PythonTypes types) { + List instantiatedClasses = new ArrayList<>(); + return generatePythonClass(instance, instantiatedClasses, main, types); + } - for (ReactorInstance child : instance.children) { - pythonClasses.pr(generatePythonClass(child, instantiatedClasses, main, types)); - } - return pythonClasses.getCode(); + /** + * Generate a Python class corresponding to decl + * + * @param instance The reactor instance to be generated + * @param instantiatedClasses A list of visited instances to avoid generating duplicates + */ + public static String generatePythonClass( + ReactorInstance instance, + List instantiatedClasses, + ReactorInstance main, + PythonTypes types) { + CodeBuilder pythonClasses = new CodeBuilder(); + ReactorDecl decl = instance.getDefinition().getReactorClass(); + Reactor reactor = ASTUtils.toDefinition(decl); + String className = PyUtil.getName(instance.tpr); + if (instantiatedClasses == null) { + return ""; } - private static String generatePythonClassHeader(String className) { - return String.join("\n", - "# Python class for reactor "+className+"", - "class _"+className+":" - ); + if (!instantiatedClasses.contains(className)) { + pythonClasses.pr(generatePythonClassHeader(className)); + // Generate preamble code + pythonClasses.indent(); + pythonClasses.pr(PythonPreambleGenerator.generatePythonPreambles(reactor.getPreambles())); + // Handle runtime initializations + pythonClasses.pr(generatePythonConstructor(decl, types)); + pythonClasses.pr(PythonParameterGenerator.generatePythonGetters(decl)); + // Generate methods + pythonClasses.pr(PythonMethodGenerator.generateMethods(reactor)); + // Generate reactions + List reactionToGenerate = ASTUtils.allReactions(reactor); + pythonClasses.pr( + PythonReactionGenerator.generatePythonReactions(reactor, reactionToGenerate)); + pythonClasses.unindent(); + pythonClasses.pr("\n"); + instantiatedClasses.add(className); } - /** - * Generate code that instantiates and initializes parameters and state variables for a reactor 'decl'. - * - * @param decl The reactor declaration - * @return The generated code as a StringBuilder - */ - private static String generatePythonConstructor(ReactorDecl decl, PythonTypes types) { - CodeBuilder code = new CodeBuilder(); - code.pr("# Constructor"); - code.pr("def __init__(self, **kwargs):"); - code.indent(); - code.pr(PythonParameterGenerator.generatePythonInstantiations(decl, types)); - code.pr(PythonStateGenerator.generatePythonInstantiations(decl)); - return code.toString(); + for (ReactorInstance child : instance.children) { + pythonClasses.pr(generatePythonClass(child, instantiatedClasses, main, types)); } + return pythonClasses.getCode(); + } - /** - * Generate code to instantiate a Python list that will hold the Python - * class instance of reactor instance. Will recursively do - * the same for the children of instance as well. - * - * @param instance The reactor instance for which the Python list will be created. - */ - public static String generateListsToHoldClassInstances(ReactorInstance instance) { - CodeBuilder code = new CodeBuilder(); - code.pr(PyUtil.reactorRefName(instance)+" = [None] * "+instance.getTotalWidth()); - for (ReactorInstance child : instance.children) { - code.pr(generateListsToHoldClassInstances(child)); - } - return code.toString(); + private static String generatePythonClassHeader(String className) { + return String.join( + "\n", "# Python class for reactor " + className + "", "class _" + className + ":"); + } + + /** + * Generate code that instantiates and initializes parameters and state variables for a reactor + * 'decl'. + * + * @param decl The reactor declaration + * @return The generated code as a StringBuilder + */ + private static String generatePythonConstructor(ReactorDecl decl, PythonTypes types) { + CodeBuilder code = new CodeBuilder(); + code.pr("# Constructor"); + code.pr("def __init__(self, **kwargs):"); + code.indent(); + code.pr(PythonParameterGenerator.generatePythonInstantiations(decl, types)); + code.pr(PythonStateGenerator.generatePythonInstantiations(decl)); + return code.toString(); + } + + /** + * Generate code to instantiate a Python list that will hold the Python + * class instance of reactor instance. Will recursively do + * the same for the children of instance as well. + * + * @param instance The reactor instance for which the Python list will be created. + */ + public static String generateListsToHoldClassInstances(ReactorInstance instance) { + CodeBuilder code = new CodeBuilder(); + code.pr(PyUtil.reactorRefName(instance) + " = [None] * " + instance.getTotalWidth()); + for (ReactorInstance child : instance.children) { + code.pr(generateListsToHoldClassInstances(child)); } + return code.toString(); + } - /** - * Instantiate classes in Python, as well as subclasses. - * Instances are always instantiated as a list of className = [_className, _className, ...] depending on the size of the bank. - * If there is no bank or the size is 1, the instance would be generated as className = [_className] - * @param instance The reactor instance to be instantiated - * @param main The main reactor - */ - public static String generatePythonClassInstantiations(ReactorInstance instance, - ReactorInstance main) { - CodeBuilder code = new CodeBuilder(); + /** + * Instantiate classes in Python, as well as subclasses. Instances are always instantiated as a + * list of className = [_className, _className, ...] depending on the size of the bank. If there + * is no bank or the size is 1, the instance would be generated as className = [_className] + * + * @param instance The reactor instance to be instantiated + * @param main The main reactor + */ + public static String generatePythonClassInstantiations( + ReactorInstance instance, ReactorInstance main) { + CodeBuilder code = new CodeBuilder(); - String className = PyUtil.getName(instance.tpr); + String className = PyUtil.getName(instance.tpr); - if (instance.getWidth() > 0) { - // For each reactor instance, create a list regardless of whether it is a bank or not. - // Non-bank reactor instances will be a list of size 1. - String fullName = instance.getFullName(); - code.pr(String.join("\n", - "# Start initializing "+fullName+" of class "+className, - "for "+PyUtil.bankIndexName(instance)+" in range("+instance.getWidth()+"):" - )); - code.indent(); - // Define a bank_index local variable so that it can be used while - // setting parameter values. - code.pr("bank_index = "+PyUtil.bankIndexName(instance)); - code.pr(generatePythonClassInstantiation(instance, className)); - } + if (instance.getWidth() > 0) { + // For each reactor instance, create a list regardless of whether it is a bank or not. + // Non-bank reactor instances will be a list of size 1. + String fullName = instance.getFullName(); + code.pr( + String.join( + "\n", + "# Start initializing " + fullName + " of class " + className, + "for " + PyUtil.bankIndexName(instance) + " in range(" + instance.getWidth() + "):")); + code.indent(); + // Define a bank_index local variable so that it can be used while + // setting parameter values. + code.pr("bank_index = " + PyUtil.bankIndexName(instance)); + code.pr(generatePythonClassInstantiation(instance, className)); + } - for (ReactorInstance child : instance.children) { - code.pr(generatePythonClassInstantiations(child, main)); - } - code.unindent(); - return code.toString(); + for (ReactorInstance child : instance.children) { + code.pr(generatePythonClassInstantiations(child, main)); } + code.unindent(); + return code.toString(); + } - /** - * Instantiate a class with className in instance. - * @param instance The reactor instance to be instantiated - * @param className The name of the class to instantiate - */ - private static String generatePythonClassInstantiation(ReactorInstance instance, - String className) { - CodeBuilder code = new CodeBuilder(); - code.pr(PyUtil.reactorRef(instance)+" = _"+className+"("); - code.indent(); - // Always add the bank_index - code.pr("_bank_index = "+PyUtil.bankIndex(instance)+","); - for (ParameterInstance param : instance.parameters) { - if (!param.getName().equals("bank_index")) { - code.pr("_"+param.getName()+"="+ PythonParameterGenerator.generatePythonInitializer(param)+","); - } - } - code.unindent(); - code.pr(")"); - return code.toString(); + /** + * Instantiate a class with className in instance. + * + * @param instance The reactor instance to be instantiated + * @param className The name of the class to instantiate + */ + private static String generatePythonClassInstantiation( + ReactorInstance instance, String className) { + CodeBuilder code = new CodeBuilder(); + code.pr(PyUtil.reactorRef(instance) + " = _" + className + "("); + code.indent(); + // Always add the bank_index + code.pr("_bank_index = " + PyUtil.bankIndex(instance) + ","); + for (ParameterInstance param : instance.parameters) { + if (!param.getName().equals("bank_index")) { + code.pr( + "_" + + param.getName() + + "=" + + PythonParameterGenerator.generatePythonInitializer(param) + + ","); + } } + code.unindent(); + code.pr(")"); + return code.toString(); + } } diff --git a/org.lflang/src/org/lflang/generator/python/PythonStateGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonStateGenerator.java index e318a8812c..45dc8cf7d5 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonStateGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonStateGenerator.java @@ -2,36 +2,38 @@ import java.util.ArrayList; import java.util.List; - import org.lflang.ast.ASTUtils; import org.lflang.lf.ReactorDecl; import org.lflang.lf.StateVar; public class PythonStateGenerator { - /** - * Generate state variable instantiations for reactor "decl" - * @param decl The reactor declaration to generate state variables. - */ - public static String generatePythonInstantiations(ReactorDecl decl) { - List lines = new ArrayList<>(); - lines.add("# Define state variables"); - // Next, handle state variables - for (StateVar state : ASTUtils.allStateVars(ASTUtils.toDefinition(decl))) { - lines.add("self."+state.getName()+" = "+generatePythonInitializer(state)); - } - lines.add(""); - return String.join("\n", lines); + /** + * Generate state variable instantiations for reactor "decl" + * + * @param decl The reactor declaration to generate state variables. + */ + public static String generatePythonInstantiations(ReactorDecl decl) { + List lines = new ArrayList<>(); + lines.add("# Define state variables"); + // Next, handle state variables + for (StateVar state : ASTUtils.allStateVars(ASTUtils.toDefinition(decl))) { + lines.add("self." + state.getName() + " = " + generatePythonInitializer(state)); } + lines.add(""); + return String.join("\n", lines); + } - /** - * Handle initialization for state variable - * @param state a state variable - */ - public static String generatePythonInitializer(StateVar state) { - if (!ASTUtils.isInitialized(state)) { - return "None"; - } - List list = state.getInit().getExprs().stream().map(PyUtil::getPythonTargetValue).toList(); - return list.size() > 1 ? "[" + String.join(", ", list) + "]" : list.get(0); + /** + * Handle initialization for state variable + * + * @param state a state variable + */ + public static String generatePythonInitializer(StateVar state) { + if (!ASTUtils.isInitialized(state)) { + return "None"; } + List list = + state.getInit().getExprs().stream().map(PyUtil::getPythonTargetValue).toList(); + return list.size() > 1 ? "[" + String.join(", ", list) + "]" : list.get(0); + } } diff --git a/org.lflang/src/org/lflang/generator/python/PythonTypes.java b/org.lflang/src/org/lflang/generator/python/PythonTypes.java index 5b62ee5f97..66abfabf5f 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonTypes.java +++ b/org.lflang/src/org/lflang/generator/python/PythonTypes.java @@ -3,74 +3,74 @@ import java.util.List; import java.util.regex.Pattern; import java.util.stream.Collectors; - 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*$"); - private static final PythonTypes INSTANCE = new PythonTypes(); + // 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*$"); + private static final PythonTypes INSTANCE = new PythonTypes(); - @Override - public String getTargetUndefinedType() { - return "PyObject*"; - } + @Override + public String getTargetUndefinedType() { + return "PyObject*"; + } - /** - * This generator inherits types from the CGenerator. - * This function reverts them back to Python types - * For example, the types double is converted to float, - * the * for pointer types is removed, etc. - * @param type The type - * @return The Python equivalent of a C type - */ - public String getPythonType(InferredType type) { - var result = super.getTargetType(type); + /** + * This generator inherits types from the CGenerator. This function reverts them back to Python + * types For example, the types double is converted to float, the * for pointer types is removed, + * etc. + * + * @param type The type + * @return The Python equivalent of a C type + */ + public String getPythonType(InferredType type) { + var result = super.getTargetType(type); - result = switch (result) { - case "double" -> "float"; - case "string" -> "object"; - default -> result; + result = + switch (result) { + case "double" -> "float"; + case "string" -> "object"; + default -> result; }; - var matcher = pointerPatternVariable.matcher(result); - if (matcher.find()) { - return matcher.group(1); - } - - return result; + var matcher = pointerPatternVariable.matcher(result); + if (matcher.find()) { + return matcher.group(1); } - @Override - public String getTargetParamRef(ParameterReference expr, InferredType typeOrNull) { - return "self." + expr.getParameter().getName(); - } + return result; + } - @Override - public String getFixedSizeListInitExpression(List contents, int listSize, boolean withBraces) { - return contents.stream().collect(Collectors.joining(", ", "[ ", " ]")); - } + @Override + public String getTargetParamRef(ParameterReference expr, InferredType typeOrNull) { + return "self." + expr.getParameter().getName(); + } - @Override - public String getVariableSizeListInitExpression(List contents, boolean withBraces) { - return contents.stream().collect(Collectors.joining(", ", "[ ", " ]")); - } + @Override + public String getFixedSizeListInitExpression( + List contents, int listSize, boolean withBraces) { + return contents.stream().collect(Collectors.joining(", ", "[ ", " ]")); + } - public static PythonTypes getInstance() { - return INSTANCE; - } + @Override + public String getVariableSizeListInitExpression(List contents, boolean withBraces) { + return contents.stream().collect(Collectors.joining(", ", "[ ", " ]")); + } - public static PythonTypes generateParametersIn(ReactorInstance instance) { - return new PythonTypes() { - @Override - public String getTargetParamRef(ParameterReference expr, InferredType typeOrNull) { - return PyUtil.reactorRef(instance) + "." + expr.getParameter().getName(); - } - }; - } + 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/python/PythonValidator.java b/org.lflang/src/org/lflang/generator/python/PythonValidator.java index 989d4181fc..dc87417c51 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonValidator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonValidator.java @@ -1,5 +1,11 @@ package org.lflang.generator.python; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import java.nio.file.Path; import java.util.Collection; import java.util.List; @@ -8,9 +14,7 @@ import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; - import org.eclipse.lsp4j.DiagnosticSeverity; - import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.generator.CodeMap; @@ -20,13 +24,6 @@ import org.lflang.generator.ValidationStrategy; import org.lflang.util.LFCommand; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; - /** * A validator for generated Python. * @@ -34,317 +31,375 @@ */ public class PythonValidator extends org.lflang.generator.Validator { - /** The pattern that diagnostics from the Python compiler typically follow. */ - private static final Pattern DIAGNOSTIC_MESSAGE_PATTERN = Pattern.compile( - "(\\*\\*\\*)?\\s*File \"(?.*?\\.py)\", line (?\\d+)" - ); - /** The pattern typically followed by the message that typically follows the main diagnostic line. */ - private static final Pattern MESSAGE = Pattern.compile("\\w*Error: .*"); - /** An alternative pattern that at least some diagnostics from the Python compiler may follow. */ - private static final Pattern ALT_DIAGNOSTIC_MESSAGE_PATTERN = Pattern.compile( - ".*Error:.*line (?\\d+)\\)" - ); - - /** The JSON parser. */ - private static final ObjectMapper mapper = new ObjectMapper() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - - private final Set protoNames; - - /** - * The message format of Pylint's JSON output. - */ - @SuppressWarnings( {"FieldCanBeLocal", "unused"}) // Unused fields are included for completeness. - private static final class PylintMessage { - private String type; - private String module; - private String obj; - private Integer line; - private Integer column; - private Integer endLine; - private Integer endColumn; - private Path path; - private String symbol; - private String message; - private String messageId; - public void setType(String type) { this.type = type; } - public void setModule(String module) { this.module = module; } - public void setObj(String obj) { this.obj = obj; } - public void setLine(int line) { this.line = line; } - public void setColumn(int column) { this.column = column; } - public void setEndLine(int endLine) { this.endLine = endLine; } - public void setEndColumn(int endColumn) { this.endColumn = endColumn; } - public void setPath(String path) { this.path = Path.of(path); } - public void setSymbol(String symbol) { this.symbol = symbol; } - public void setMessage(String message) { this.message = message; } - @JsonProperty("message-id") - public void setMessageId(String messageId) { this.messageId = messageId; } - public Position getStart() { - if (line != null && column != null) return Position.fromZeroBased(line - 1, column); - // Use 0 as fallback for the column. This will cause bugs by taking some positions out of the line - // adjuster's range. - if (line != null) return Position.fromZeroBased(line - 1, 0); - // This fallback will always fail with the line adjuster, but at least the program will not crash. - return Position.ORIGIN; - } - public Position getEnd() { - return endLine == null || endColumn == null ? getStart().plus(" ") : - Position.fromZeroBased(endLine - 1, endColumn); - } - public Path getPath(Path relativeTo) { return relativeTo.resolve(path); } - public DiagnosticSeverity getSeverity() { - // The following is consistent with VS Code's default behavior for pure Python: - // https://code.visualstudio.com/docs/python/linting#_pylint - switch (type.toLowerCase()) { - case "refactor": - return DiagnosticSeverity.Hint; - case "warning": - return DiagnosticSeverity.Warning; - case "error": - case "fatal": - return DiagnosticSeverity.Error; - case "convention": - default: - return DiagnosticSeverity.Information; - } - } + /** The pattern that diagnostics from the Python compiler typically follow. */ + private static final Pattern DIAGNOSTIC_MESSAGE_PATTERN = + Pattern.compile("(\\*\\*\\*)?\\s*File \"(?.*?\\.py)\", line (?\\d+)"); + /** + * The pattern typically followed by the message that typically follows the main diagnostic line. + */ + private static final Pattern MESSAGE = Pattern.compile("\\w*Error: .*"); + /** An alternative pattern that at least some diagnostics from the Python compiler may follow. */ + private static final Pattern ALT_DIAGNOSTIC_MESSAGE_PATTERN = + Pattern.compile(".*Error:.*line (?\\d+)\\)"); + + /** The JSON parser. */ + private static final ObjectMapper mapper = + new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + private final Set protoNames; + + /** The message format of Pylint's JSON output. */ + @SuppressWarnings({"FieldCanBeLocal", "unused"}) // Unused fields are included for completeness. + private static final class PylintMessage { + private String type; + private String module; + private String obj; + private Integer line; + private Integer column; + private Integer endLine; + private Integer endColumn; + private Path path; + private String symbol; + private String message; + private String messageId; + + public void setType(String type) { + this.type = type; } - private static final Pattern PylintNoNamePattern = Pattern.compile("Instance of '(?\\w+)' has no .*"); - - private final FileConfig fileConfig; - private final ErrorReporter errorReporter; - private final ImmutableMap codeMaps; - - /** - * Initialize a {@code PythonValidator} for a build process using {@code fileConfig} and - * report errors to {@code errorReporter}. - * @param fileConfig The file configuration of this build. - * @param errorReporter The reporter to which diagnostics should be sent. - * @param codeMaps A mapping from generated file paths to code maps that map them back to - * LF sources. - * @param protoNames The names of any protocol buffer message types that are used in the LF - * program being built. - */ - public PythonValidator( - FileConfig fileConfig, - ErrorReporter errorReporter, - Map codeMaps, - Set protoNames - ) { - super(errorReporter, codeMaps); - this.fileConfig = fileConfig; - this.errorReporter = errorReporter; - this.codeMaps = ImmutableMap.copyOf(codeMaps); - this.protoNames = ImmutableSet.copyOf(protoNames); + public void setModule(String module) { + this.module = module; } - @Override - protected Collection getPossibleStrategies() { return List.of( + public void setObj(String obj) { + this.obj = obj; + } + + public void setLine(int line) { + this.line = line; + } + + public void setColumn(int column) { + this.column = column; + } + + public void setEndLine(int endLine) { + this.endLine = endLine; + } + + public void setEndColumn(int endColumn) { + this.endColumn = endColumn; + } + + public void setPath(String path) { + this.path = Path.of(path); + } + + public void setSymbol(String symbol) { + this.symbol = symbol; + } + + public void setMessage(String message) { + this.message = message; + } + + @JsonProperty("message-id") + public void setMessageId(String messageId) { + this.messageId = messageId; + } + + public Position getStart() { + if (line != null && column != null) return Position.fromZeroBased(line - 1, column); + // Use 0 as fallback for the column. This will cause bugs by taking some positions out of the + // line + // adjuster's range. + if (line != null) return Position.fromZeroBased(line - 1, 0); + // This fallback will always fail with the line adjuster, but at least the program will not + // crash. + return Position.ORIGIN; + } + + public Position getEnd() { + return endLine == null || endColumn == null + ? getStart().plus(" ") + : Position.fromZeroBased(endLine - 1, endColumn); + } + + public Path getPath(Path relativeTo) { + return relativeTo.resolve(path); + } + + public DiagnosticSeverity getSeverity() { + // The following is consistent with VS Code's default behavior for pure Python: + // https://code.visualstudio.com/docs/python/linting#_pylint + switch (type.toLowerCase()) { + case "refactor": + return DiagnosticSeverity.Hint; + case "warning": + return DiagnosticSeverity.Warning; + case "error": + case "fatal": + return DiagnosticSeverity.Error; + case "convention": + default: + return DiagnosticSeverity.Information; + } + } + } + + private static final Pattern PylintNoNamePattern = + Pattern.compile("Instance of '(?\\w+)' has no .*"); + + private final FileConfig fileConfig; + private final ErrorReporter errorReporter; + private final ImmutableMap codeMaps; + + /** + * Initialize a {@code PythonValidator} for a build process using {@code fileConfig} and report + * errors to {@code errorReporter}. + * + * @param fileConfig The file configuration of this build. + * @param errorReporter The reporter to which diagnostics should be sent. + * @param codeMaps A mapping from generated file paths to code maps that map them back to LF + * sources. + * @param protoNames The names of any protocol buffer message types that are used in the LF + * program being built. + */ + public PythonValidator( + FileConfig fileConfig, + ErrorReporter errorReporter, + Map codeMaps, + Set protoNames) { + super(errorReporter, codeMaps); + this.fileConfig = fileConfig; + this.errorReporter = errorReporter; + this.codeMaps = ImmutableMap.copyOf(codeMaps); + this.protoNames = ImmutableSet.copyOf(protoNames); + } + + @Override + protected Collection getPossibleStrategies() { + return List.of( new ValidationStrategy() { - @Override - public LFCommand getCommand(Path generatedFile) { - return LFCommand.get( - "python3", - List.of("-c", "import compileall; compileall.compile_dir('.', quiet=1)"), - true, - fileConfig.getSrcGenPkgPath() - ); - } + @Override + public LFCommand getCommand(Path generatedFile) { + return LFCommand.get( + "python3", + List.of("-c", "import compileall; compileall.compile_dir('.', quiet=1)"), + true, + fileConfig.getSrcGenPkgPath()); + } - @Override - public Strategy getErrorReportingStrategy() { - return (a, b, c) -> {}; - } + @Override + public Strategy getErrorReportingStrategy() { + return (a, b, c) -> {}; + } - @Override - public Strategy getOutputReportingStrategy() { - return (String validationOutput, ErrorReporter errorReporter, Map map) -> { - String[] lines = (validationOutput + "\n\n\n").lines().toArray(String[]::new); - for (int i = 0; i < lines.length - 3; i++) { - if (!tryReportTypical(lines, i)) { - tryReportAlternative(lines, i); - } - } - }; - } + @Override + public Strategy getOutputReportingStrategy() { + return (String validationOutput, + ErrorReporter errorReporter, + Map map) -> { + String[] lines = (validationOutput + "\n\n\n").lines().toArray(String[]::new); + for (int i = 0; i < lines.length - 3; i++) { + if (!tryReportTypical(lines, i)) { + tryReportAlternative(lines, i); + } + } + }; + } - /** - * Try to report a typical error message from the Python compiler. - * - * @param lines The lines of output from the compiler. - * @param i The current index at which a message may start. Guaranteed to be less - * than - * {@code lines.length - 3}. - * @return Whether an error message was reported. - */ - private boolean tryReportTypical(String[] lines, int i) { - Matcher main = DIAGNOSTIC_MESSAGE_PATTERN.matcher(lines[i]); - Matcher messageMatcher = MESSAGE.matcher(lines[i + 3]); - String message = messageMatcher.matches() ? messageMatcher.group() : "Syntax Error"; - if (main.matches()) { - int line = Integer.parseInt(main.group("line")); - CodeMap map = codeMaps.get(fileConfig.getSrcGenPkgPath().resolve(Path.of(main.group("path"))).normalize()); - Position genPosition = Position.fromOneBased(line, Integer.MAX_VALUE); // Column is just a placeholder. - if (map == null) { - errorReporter.report(null, DiagnosticSeverity.Error, message, 1); // Undesirable fallback - } else { - for (Path lfFile : map.lfSourcePaths()) { - Position lfPosition = map.adjusted(lfFile, genPosition); - // TODO: We could be more precise than just getting the right line, but the way the output - // is formatted (with leading whitespace possibly trimmed) does not make it easy. - errorReporter.report(lfFile, DiagnosticSeverity.Error, message, lfPosition.getOneBasedLine()); - } - } - return true; + /** + * Try to report a typical error message from the Python compiler. + * + * @param lines The lines of output from the compiler. + * @param i The current index at which a message may start. Guaranteed to be less than + * {@code lines.length - 3}. + * @return Whether an error message was reported. + */ + private boolean tryReportTypical(String[] lines, int i) { + Matcher main = DIAGNOSTIC_MESSAGE_PATTERN.matcher(lines[i]); + Matcher messageMatcher = MESSAGE.matcher(lines[i + 3]); + String message = messageMatcher.matches() ? messageMatcher.group() : "Syntax Error"; + if (main.matches()) { + int line = Integer.parseInt(main.group("line")); + CodeMap map = + codeMaps.get( + fileConfig + .getSrcGenPkgPath() + .resolve(Path.of(main.group("path"))) + .normalize()); + Position genPosition = + Position.fromOneBased(line, Integer.MAX_VALUE); // Column is just a placeholder. + if (map == null) { + errorReporter.report( + null, DiagnosticSeverity.Error, message, 1); // Undesirable fallback + } else { + for (Path lfFile : map.lfSourcePaths()) { + Position lfPosition = map.adjusted(lfFile, genPosition); + // TODO: We could be more precise than just getting the right line, but the way + // the output + // is formatted (with leading whitespace possibly trimmed) does not make it easy. + errorReporter.report( + lfFile, DiagnosticSeverity.Error, message, lfPosition.getOneBasedLine()); } - return false; + } + return true; } + return false; + } - /** - * Try to report an alternative error message from the Python compiler. - * - * @param lines The lines of output from the compiler. - * @param i The current index at which a message may start. - */ - private void tryReportAlternative(String[] lines, int i) { - Matcher main = ALT_DIAGNOSTIC_MESSAGE_PATTERN.matcher(lines[i]); - if (main.matches()) { - int line = Integer.parseInt(main.group("line")); - Iterable relevantMaps = codeMaps.keySet().stream() - .filter(p -> main.group().contains(p.getFileName().toString())) - .map(codeMaps::get)::iterator; - for (CodeMap map : relevantMaps) { // There should almost always be exactly one of these - for (Path lfFile : map.lfSourcePaths()) { - errorReporter.report( - lfFile, - DiagnosticSeverity.Error, - main.group().replace("*** ", "").replace("Sorry: ", ""), - map.adjusted(lfFile, Position.fromOneBased(line, 1)).getOneBasedLine() - ); - } - } + /** + * Try to report an alternative error message from the Python compiler. + * + * @param lines The lines of output from the compiler. + * @param i The current index at which a message may start. + */ + private void tryReportAlternative(String[] lines, int i) { + Matcher main = ALT_DIAGNOSTIC_MESSAGE_PATTERN.matcher(lines[i]); + if (main.matches()) { + int line = Integer.parseInt(main.group("line")); + Iterable relevantMaps = + codeMaps.keySet().stream() + .filter(p -> main.group().contains(p.getFileName().toString())) + .map(codeMaps::get) + ::iterator; + for (CodeMap map : + relevantMaps) { // There should almost always be exactly one of these + for (Path lfFile : map.lfSourcePaths()) { + errorReporter.report( + lfFile, + DiagnosticSeverity.Error, + main.group().replace("*** ", "").replace("Sorry: ", ""), + map.adjusted(lfFile, Position.fromOneBased(line, 1)).getOneBasedLine()); } + } } + } - @Override - public boolean isFullBatch() { - return true; - } + @Override + public boolean isFullBatch() { + return true; + } - @Override - public int getPriority() { - return 0; - } + @Override + public int getPriority() { + return 0; + } }, new ValidationStrategy() { - @Override - public LFCommand getCommand(Path generatedFile) { - return LFCommand.get( - "pylint", - List.of("--output-format=json", generatedFile.getFileName().toString()), - true, - fileConfig.getSrcGenPath() - ); - } + @Override + public LFCommand getCommand(Path generatedFile) { + return LFCommand.get( + "pylint", + List.of("--output-format=json", generatedFile.getFileName().toString()), + true, + fileConfig.getSrcGenPath()); + } - @Override - public Strategy getErrorReportingStrategy() { - return (a, b, c) -> {}; - } + @Override + public Strategy getErrorReportingStrategy() { + return (a, b, c) -> {}; + } - @Override - public Strategy getOutputReportingStrategy() { - return (validationOutput, errorReporter, codeMaps) -> { - if (validationOutput.isBlank()) return; - try { - for (PylintMessage message : mapper.readValue(validationOutput, PylintMessage[].class)) { - if (shouldIgnore(message)) continue; - CodeMap map = codeMaps.get(message.getPath(fileConfig.getSrcGenPath())); - if (map != null) { - for (Path lfFile : map.lfSourcePaths()) { - Function adjust = p -> map.adjusted(lfFile, p); - String humanMessage = DiagnosticReporting.messageOf( - message.message, - message.getPath(fileConfig.getSrcGenPath()), - message.getStart() - ); - Position lfStart = adjust.apply(message.getStart()); - Position lfEnd = adjust.apply(message.getEnd()); - bestEffortReport( - errorReporter, - adjust, - lfStart, - lfEnd, - lfFile, - message.getSeverity(), - humanMessage - ); - } - } - } - } catch (JsonProcessingException e) { - System.err.printf("Failed to parse \"%s\":%n", validationOutput); - e.printStackTrace(); - errorReporter.reportWarning( - "Failed to parse linter output. The Lingua Franca code generator is tested with Pylint " - + "version 2.12.2. Consider updating Pylint if you have an older version." - ); + @Override + public Strategy getOutputReportingStrategy() { + return (validationOutput, errorReporter, codeMaps) -> { + if (validationOutput.isBlank()) return; + try { + for (PylintMessage message : + mapper.readValue(validationOutput, PylintMessage[].class)) { + if (shouldIgnore(message)) continue; + CodeMap map = codeMaps.get(message.getPath(fileConfig.getSrcGenPath())); + if (map != null) { + for (Path lfFile : map.lfSourcePaths()) { + Function adjust = p -> map.adjusted(lfFile, p); + String humanMessage = + DiagnosticReporting.messageOf( + message.message, + message.getPath(fileConfig.getSrcGenPath()), + message.getStart()); + Position lfStart = adjust.apply(message.getStart()); + Position lfEnd = adjust.apply(message.getEnd()); + bestEffortReport( + errorReporter, + adjust, + lfStart, + lfEnd, + lfFile, + message.getSeverity(), + humanMessage); } - }; - } + } + } + } catch (JsonProcessingException e) { + System.err.printf("Failed to parse \"%s\":%n", validationOutput); + e.printStackTrace(); + errorReporter.reportWarning( + "Failed to parse linter output. The Lingua Franca code generator is tested with" + + " Pylint version 2.12.2. Consider updating Pylint if you have an older" + + " version."); + } + }; + } - /** - * Return whether the given message should be ignored. - * @param message A Pylint message that is a candidate to be reported. - * @return whether {@code message} should be reported. - */ - private boolean shouldIgnore(PylintMessage message) { - // Code generation does not preserve whitespace, so this check is unreliable. - if (message.symbol.equals("trailing-whitespace") || message.symbol.equals("line-too-long")) return true; - // This filters out Pylint messages concerning missing members in types defined by protocol buffers. - // FIXME: Make this unnecessary, perhaps using https://github.com/nelfin/pylint-protobuf. - Matcher matcher = PylintNoNamePattern.matcher(message.message); - return message.symbol.equals("no-member") - && matcher.matches() && protoNames.contains(matcher.group("name")); - } + /** + * Return whether the given message should be ignored. + * + * @param message A Pylint message that is a candidate to be reported. + * @return whether {@code message} should be reported. + */ + private boolean shouldIgnore(PylintMessage message) { + // Code generation does not preserve whitespace, so this check is unreliable. + if (message.symbol.equals("trailing-whitespace") + || message.symbol.equals("line-too-long")) return true; + // This filters out Pylint messages concerning missing members in types defined by + // protocol buffers. + // FIXME: Make this unnecessary, perhaps using + // https://github.com/nelfin/pylint-protobuf. + Matcher matcher = PylintNoNamePattern.matcher(message.message); + return message.symbol.equals("no-member") + && matcher.matches() + && protoNames.contains(matcher.group("name")); + } - /** Make a best-effort attempt to place the diagnostic on the correct line. */ - private void bestEffortReport( - ErrorReporter errorReporter, - Function adjust, - Position lfStart, - Position lfEnd, - Path file, - DiagnosticSeverity severity, - String humanMessage - ) { - if (!lfEnd.equals(Position.ORIGIN) && !lfStart.equals(Position.ORIGIN)) { // Ideal case - errorReporter.report(file, severity, humanMessage, lfStart, lfEnd); - } else { // Fallback: Try to report on the correct line, or failing that, just line 1. - if (lfStart.equals(Position.ORIGIN)) lfStart = adjust.apply( - Position.fromZeroBased(lfStart.getZeroBasedLine(), Integer.MAX_VALUE) - ); - // FIXME: It might be better to improve style of generated code instead of quietly returning here. - if (lfStart.equals(Position.ORIGIN) && severity != DiagnosticSeverity.Error) return; - errorReporter.report(file, severity, humanMessage, lfStart.getOneBasedLine()); - } + /** Make a best-effort attempt to place the diagnostic on the correct line. */ + private void bestEffortReport( + ErrorReporter errorReporter, + Function adjust, + Position lfStart, + Position lfEnd, + Path file, + DiagnosticSeverity severity, + String humanMessage) { + if (!lfEnd.equals(Position.ORIGIN) && !lfStart.equals(Position.ORIGIN)) { // Ideal case + errorReporter.report(file, severity, humanMessage, lfStart, lfEnd); + } else { // Fallback: Try to report on the correct line, or failing that, just line 1. + if (lfStart.equals(Position.ORIGIN)) + lfStart = + adjust.apply( + Position.fromZeroBased(lfStart.getZeroBasedLine(), Integer.MAX_VALUE)); + // FIXME: It might be better to improve style of generated code instead of quietly + // returning here. + if (lfStart.equals(Position.ORIGIN) && severity != DiagnosticSeverity.Error) return; + errorReporter.report(file, severity, humanMessage, lfStart.getOneBasedLine()); } + } - @Override - public boolean isFullBatch() { - return false; - } + @Override + public boolean isFullBatch() { + return false; + } - @Override - public int getPriority() { - return 1; - } - } - ); } + @Override + public int getPriority() { + return 1; + } + }); + } - @Override - protected Pair getBuildReportingStrategies() { - return new Pair<>((a, b, c) -> {}, (a, b, c) -> {}); - } + @Override + protected Pair getBuildReportingStrategies() { + return new Pair<>((a, b, c) -> {}, (a, b, c) -> {}); + } } diff --git a/org.lflang/src/org/lflang/generator/rust/CargoDependencySpec.java b/org.lflang/src/org/lflang/generator/rust/CargoDependencySpec.java index 69efcd3fab..ff97860970 100644 --- a/org.lflang/src/org/lflang/generator/rust/CargoDependencySpec.java +++ b/org.lflang/src/org/lflang/generator/rust/CargoDependencySpec.java @@ -31,9 +31,9 @@ import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; -import org.lflang.ast.ASTUtils; import org.lflang.TargetProperty; import org.lflang.TargetProperty.TargetPropertyType; +import org.lflang.ast.ASTUtils; import org.lflang.generator.InvalidLfSourceException; import org.lflang.lf.Array; import org.lflang.lf.Element; @@ -202,14 +202,12 @@ private static CargoDependencySpec parseValue(Element element, boolean isRuntime pair.getValue(), "Expected string literal for key '" + name + "'"); } switch (name) { - case "version" -> version = literal; - case "git" -> gitRepo = literal; - case "rev" -> rev = literal; - case "tag" -> tag = literal; - case "path" -> localPath = literal; - default -> throw new InvalidLfSourceException(pair, - "Unknown key: '" + name - + "'"); + case "version" -> version = literal; + case "git" -> gitRepo = literal; + case "rev" -> rev = literal; + case "tag" -> tag = literal; + case "path" -> localPath = literal; + default -> throw new InvalidLfSourceException(pair, "Unknown key: '" + name + "'"); } } if (isRuntimeCrate || version != null || localPath != null || gitRepo != null) { diff --git a/org.lflang/src/org/lflang/generator/rust/RustTargetConfig.java b/org.lflang/src/org/lflang/generator/rust/RustTargetConfig.java index f2ff9d9c3f..b4e9a1778b 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustTargetConfig.java +++ b/org.lflang/src/org/lflang/generator/rust/RustTargetConfig.java @@ -29,11 +29,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; - import org.eclipse.emf.ecore.EObject; - import org.lflang.ErrorReporter; import org.lflang.TargetProperty.BuildType; @@ -44,83 +41,66 @@ */ public final class RustTargetConfig { - - /** - * List of Cargo features of the generated crate to enable. - */ - private List cargoFeatures = new ArrayList<>(); - - /** - * Map of Cargo dependency to dependency properties. - */ - private Map cargoDependencies = new HashMap<>(); - - /** - * List of top-level modules, those are absolute paths. - */ - private final List rustTopLevelModules = new ArrayList<>(); - - /** - * Cargo profile, default is debug (corresponds to cargo dev profile). - */ - private BuildType profile = BuildType.DEBUG; - - public void setCargoFeatures(List cargoFeatures) { - this.cargoFeatures = cargoFeatures; - } - - public void setCargoDependencies(Map cargoDependencies) { - this.cargoDependencies = cargoDependencies; + /** List of Cargo features of the generated crate to enable. */ + private List cargoFeatures = new ArrayList<>(); + + /** Map of Cargo dependency to dependency properties. */ + private Map cargoDependencies = new HashMap<>(); + + /** List of top-level modules, those are absolute paths. */ + private final List rustTopLevelModules = new ArrayList<>(); + + /** Cargo profile, default is debug (corresponds to cargo dev profile). */ + private BuildType profile = BuildType.DEBUG; + + public void setCargoFeatures(List cargoFeatures) { + this.cargoFeatures = cargoFeatures; + } + + public void setCargoDependencies(Map cargoDependencies) { + this.cargoDependencies = cargoDependencies; + } + + public void addAndCheckTopLevelModule(Path path, EObject errorOwner, ErrorReporter err) { + String fileName = path.getFileName().toString(); + if (!Files.exists(path)) { + err.reportError(errorOwner, "File not found"); + } else if (Files.isRegularFile(path) && !fileName.endsWith(".rs")) { + err.reportError(errorOwner, "Not a rust file"); + } else if (fileName.equals("main.rs")) { + err.reportError(errorOwner, "Cannot use 'main.rs' as a module name (reserved)"); + } else if (fileName.equals("reactors") || fileName.equals("reactors.rs")) { + err.reportError(errorOwner, "Cannot use 'reactors' as a module name (reserved)"); + } else if (Files.isDirectory(path) && !Files.exists(path.resolve("mod.rs"))) { + err.reportError(errorOwner, "Cannot find module descriptor in directory"); } - - public void addAndCheckTopLevelModule(Path path, EObject errorOwner, ErrorReporter err) { - String fileName = path.getFileName().toString(); - if (!Files.exists(path)) { - err.reportError(errorOwner, "File not found"); - } else if (Files.isRegularFile(path) && !fileName.endsWith(".rs")) { - err.reportError(errorOwner, "Not a rust file"); - } else if (fileName.equals("main.rs")) { - err.reportError(errorOwner, "Cannot use 'main.rs' as a module name (reserved)"); - } else if (fileName.equals("reactors") || fileName.equals("reactors.rs")) { - err.reportError(errorOwner, "Cannot use 'reactors' as a module name (reserved)"); - } else if (Files.isDirectory(path) && !Files.exists(path.resolve("mod.rs"))) { - err.reportError(errorOwner, "Cannot find module descriptor in directory"); - } - this.rustTopLevelModules.add(path); - } - - public List getCargoFeatures() { - return cargoFeatures; - } - - /** - * Returns a map of cargo dependencies. - */ - public Map getCargoDependencies() { - return cargoDependencies; - } - - /** - * Returns the list of top-level module files to include in main.rs. - * Those files were checked to exists previously. - */ - public List getRustTopLevelModules() { - return rustTopLevelModules; - } - - /** - * The build type to use. Corresponds to a Cargo profile. - */ - public BuildType getBuildType() { - return profile; - } - - - /** - * Set a build profile chosen based on a cmake profile. - */ - public void setBuildType(BuildType profile) { - this.profile = profile; - } - + this.rustTopLevelModules.add(path); + } + + public List getCargoFeatures() { + return cargoFeatures; + } + + /** Returns a map of cargo dependencies. */ + public Map getCargoDependencies() { + return cargoDependencies; + } + + /** + * Returns the list of top-level module files to include in main.rs. Those files were checked to + * exists previously. + */ + public List getRustTopLevelModules() { + return rustTopLevelModules; + } + + /** The build type to use. Corresponds to a Cargo profile. */ + public BuildType getBuildType() { + return profile; + } + + /** Set a build profile chosen based on a cmake profile. */ + public void setBuildType(BuildType profile) { + this.profile = profile; + } } diff --git a/org.lflang/src/org/lflang/generator/ts/TSDockerGenerator.java b/org.lflang/src/org/lflang/generator/ts/TSDockerGenerator.java index cf89596a56..1e88ba4c12 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSDockerGenerator.java +++ b/org.lflang/src/org/lflang/generator/ts/TSDockerGenerator.java @@ -10,20 +10,19 @@ */ public class TSDockerGenerator extends DockerGenerator { - /** Construct a new Docker generator. */ - public TSDockerGenerator(LFGeneratorContext context) { - super(context); - } + /** Construct a new Docker generator. */ + public TSDockerGenerator(LFGeneratorContext context) { + super(context); + } - /** - * Return the content of the docker file for [tsFileName]. - */ - public String generateDockerFileContent() { - return """ + /** 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); - } + """ + .formatted(context.getFileConfig().name); + } } diff --git a/org.lflang/src/org/lflang/generator/ts/TSTypes.java b/org.lflang/src/org/lflang/generator/ts/TSTypes.java index 2e2c6a6dff..0fe4fc8a5e 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSTypes.java +++ b/org.lflang/src/org/lflang/generator/ts/TSTypes.java @@ -1,75 +1,73 @@ package org.lflang.generator.ts; import java.util.List; - -import org.lflang.ast.ASTUtils; import org.lflang.TimeValue; +import org.lflang.ast.ASTUtils; 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"; - } + private static TSTypes INSTANCE = new TSTypes(); - @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()"; - } - } + private TSTypes() {} - @Override - public String getTargetFixedSizeListType(String baseType, int size) { - throw new UnsupportedGeneratorFeatureException("TypeScript does not support fixed-size array types."); + @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 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; + } + + @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/graph/DirectedGraph.java b/org.lflang/src/org/lflang/graph/DirectedGraph.java index 771bbca539..0e506dbe30 100644 --- a/org.lflang/src/org/lflang/graph/DirectedGraph.java +++ b/org.lflang/src/org/lflang/graph/DirectedGraph.java @@ -1,25 +1,25 @@ /************* - 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.graph; @@ -27,7 +27,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.*; import java.util.Map.Entry; import java.util.stream.Collectors; - import org.lflang.util.CollectionUtil; /** @@ -38,287 +37,254 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY */ public class DirectedGraph implements Graph { - // Note that while both those maps are mutable, the sets - // they use as values may not be. They should only be - // manipulated through CollectionUtil - - // If a node has no neighbors, it is still in the map with an empty set as a value. - - /** - * Adjacency map from vertices to their downstream immediate neighbors. - */ - private final Map> downstreamAdjacentNodes = new LinkedHashMap<>(); - - /** - * Adjacency map from vertices to their upstream immediate neighbors. - */ - private final Map> upstreamAdjacentNodes = new LinkedHashMap<>(); - - - /** - * Mark the graph to have changed so that any cached analysis is refreshed - * accordingly. - */ - protected void graphChanged() { - // To be overridden by subclasses that perform analysis. - } - - - /** - * Return true if this graph has the given node in it. - * - * @param node The node to look for. - */ - @Override - public boolean hasNode(T node) { - return nodes().contains(node); - } - - - /** - * Return all immediate upstream neighbors of a given node. - * - * @param node The node to report the immediate upstream neighbors of. - */ - public Set getUpstreamAdjacentNodes(T node) { - return Collections.unmodifiableSet(this.upstreamAdjacentNodes.getOrDefault(node, Set.of())); - } - - - /** - * Return all immediate downstream neighbors of a given node. - * - * @param node The node to report the immediate downstream neighbors of. - */ - public Set getDownstreamAdjacentNodes(T node) { - return Collections.unmodifiableSet(this.downstreamAdjacentNodes.getOrDefault(node, Set.of())); - } - - - @Override - public void addNode(T node) { - this.graphChanged(); - this.upstreamAdjacentNodes.putIfAbsent(node, Set.of()); - this.downstreamAdjacentNodes.putIfAbsent(node, Set.of()); - } - - - @Override - public void removeNode(T node) { - this.graphChanged(); - this.upstreamAdjacentNodes.remove(node); - this.downstreamAdjacentNodes.remove(node); - // The node also needs to be removed from the sets that represent connections to the node. - CollectionUtil.removeFromValues(this.upstreamAdjacentNodes, node); - CollectionUtil.removeFromValues(this.downstreamAdjacentNodes, node); - } - - - /** - * Add a new directed edge to the graph. The first argument is - * the downstream node, the second argument the upstream node. - * If either argument is null, do nothing. - * - * @param sink The downstream immediate neighbor. - * @param source The upstream immediate neighbor. - */ - @Override - public void addEdge(T sink, T source) { - this.graphChanged(); - if (sink != null && source != null) { - this.downstreamAdjacentNodes.compute(source, (k, set) -> CollectionUtil.plus(set, sink)); - this.upstreamAdjacentNodes.compute(sink, (k, set) -> CollectionUtil.plus(set, source)); - } - } - - - /** - * Add new directed edges to the graph. The first argument is the - * downstream node, the second argument a set of upstream nodes. - * - * @param sink The downstream immediate neighbor. - * @param sources The upstream immediate neighbors. - */ - @Override - public void addEdges(T sink, List sources) { - for (T source : sources) { - this.addEdge(sink, source); - } - } - - - /** - * Remove a directed edge from the graph. - * - * @param sink The downstream immediate neighbor. - * @param source The upstream immediate neighbor. - */ - @Override - public void removeEdge(T sink, T source) { - this.graphChanged(); - this.upstreamAdjacentNodes.computeIfPresent(sink, (k, upstream) -> CollectionUtil.minus(upstream, source)); - this.downstreamAdjacentNodes.computeIfPresent(source, (k, downstream) -> CollectionUtil.minus(downstream, sink)); - } - - - /** - * Obtain a copy of this graph by creating an new instance and copying - * the adjacency maps. - */ - public DirectedGraph copy() { - var graph = new DirectedGraph(); - for (var entry : this.upstreamAdjacentNodes.entrySet()) { - graph.upstreamAdjacentNodes.put(entry.getKey(), CollectionUtil.copy(entry.getValue())); - } - for (var entry : this.downstreamAdjacentNodes.entrySet()) { - graph.downstreamAdjacentNodes.put(entry.getKey(), CollectionUtil.copy(entry.getValue())); - } - return graph; - } - - - /** - * For a given a two adjacency maps, copy missing edges from the first - * map to the second. - * - * @param srcMap The adjacency map to copy edges from. - * @param dstMap The adjacency map to copy edges to. - */ - private void mirror(Map> srcMap, Map> dstMap) { - if (srcMap != null && dstMap != null) { - for (Entry> entry : srcMap.entrySet()) { - var node = entry.getKey(); - var srcEdges = entry.getValue(); - dstMap.compute(node, (_node, dstEdges) -> { - // Node does not exist; add it. - if (dstEdges == null) { - return CollectionUtil.copy(srcEdges); - } - - // Node does exist; add the missing edges. - var set = dstEdges; - for (T edge : srcEdges) { - set = CollectionUtil.plus(set, edge); - } - return set; - }); - } - } - } - - - /** - * Merge another directed graph into this one. - * - * @param another The graph to merge into this one. - */ - public void merge(DirectedGraph another) { - this.graphChanged(); - mirror(another.upstreamAdjacentNodes, this.upstreamAdjacentNodes); - mirror(another.downstreamAdjacentNodes, this.downstreamAdjacentNodes); - } - - - /** - * Return the set of nodes that have no neighbors listed in the given - * adjacency map. - */ - private Set independentNodes(Map> adjacencyMap) { - var independent = new LinkedHashSet(); - for (T node : nodes()) { - var neighbors = adjacencyMap.get(node); - if (neighbors == null || neighbors.size() == 0) { - independent.add(node); - } - } - return independent; + // Note that while both those maps are mutable, the sets + // they use as values may not be. They should only be + // manipulated through CollectionUtil + + // If a node has no neighbors, it is still in the map with an empty set as a value. + + /** Adjacency map from vertices to their downstream immediate neighbors. */ + private final Map> downstreamAdjacentNodes = new LinkedHashMap<>(); + + /** Adjacency map from vertices to their upstream immediate neighbors. */ + private final Map> upstreamAdjacentNodes = new LinkedHashMap<>(); + + /** Mark the graph to have changed so that any cached analysis is refreshed accordingly. */ + protected void graphChanged() { + // To be overridden by subclasses that perform analysis. + } + + /** + * Return true if this graph has the given node in it. + * + * @param node The node to look for. + */ + @Override + public boolean hasNode(T node) { + return nodes().contains(node); + } + + /** + * Return all immediate upstream neighbors of a given node. + * + * @param node The node to report the immediate upstream neighbors of. + */ + public Set getUpstreamAdjacentNodes(T node) { + return Collections.unmodifiableSet(this.upstreamAdjacentNodes.getOrDefault(node, Set.of())); + } + + /** + * Return all immediate downstream neighbors of a given node. + * + * @param node The node to report the immediate downstream neighbors of. + */ + public Set getDownstreamAdjacentNodes(T node) { + return Collections.unmodifiableSet(this.downstreamAdjacentNodes.getOrDefault(node, Set.of())); + } + + @Override + public void addNode(T node) { + this.graphChanged(); + this.upstreamAdjacentNodes.putIfAbsent(node, Set.of()); + this.downstreamAdjacentNodes.putIfAbsent(node, Set.of()); + } + + @Override + public void removeNode(T node) { + this.graphChanged(); + this.upstreamAdjacentNodes.remove(node); + this.downstreamAdjacentNodes.remove(node); + // The node also needs to be removed from the sets that represent connections to the node. + CollectionUtil.removeFromValues(this.upstreamAdjacentNodes, node); + CollectionUtil.removeFromValues(this.downstreamAdjacentNodes, node); + } + + /** + * Add a new directed edge to the graph. The first argument is the downstream node, the second + * argument the upstream node. If either argument is null, do nothing. + * + * @param sink The downstream immediate neighbor. + * @param source The upstream immediate neighbor. + */ + @Override + public void addEdge(T sink, T source) { + this.graphChanged(); + if (sink != null && source != null) { + this.downstreamAdjacentNodes.compute(source, (k, set) -> CollectionUtil.plus(set, sink)); + this.upstreamAdjacentNodes.compute(sink, (k, set) -> CollectionUtil.plus(set, source)); } - - - /** - * Return the root nodes of this graph. - * Root nodes have no upstream neighbors. - */ - public Set rootNodes() { - return independentNodes(this.upstreamAdjacentNodes); + } + + /** + * Add new directed edges to the graph. The first argument is the downstream node, the second + * argument a set of upstream nodes. + * + * @param sink The downstream immediate neighbor. + * @param sources The upstream immediate neighbors. + */ + @Override + public void addEdges(T sink, List sources) { + for (T source : sources) { + this.addEdge(sink, source); } - - - /** - * Return the leaf nodes of this graph. - * Leaf nodes have no downstream neighbors. - */ - public Set leafNodes() { - return independentNodes(this.downstreamAdjacentNodes); + } + + /** + * Remove a directed edge from the graph. + * + * @param sink The downstream immediate neighbor. + * @param source The upstream immediate neighbor. + */ + @Override + public void removeEdge(T sink, T source) { + this.graphChanged(); + this.upstreamAdjacentNodes.computeIfPresent( + sink, (k, upstream) -> CollectionUtil.minus(upstream, source)); + this.downstreamAdjacentNodes.computeIfPresent( + source, (k, downstream) -> CollectionUtil.minus(downstream, sink)); + } + + /** Obtain a copy of this graph by creating an new instance and copying the adjacency maps. */ + public DirectedGraph copy() { + var graph = new DirectedGraph(); + for (var entry : this.upstreamAdjacentNodes.entrySet()) { + graph.upstreamAdjacentNodes.put(entry.getKey(), CollectionUtil.copy(entry.getValue())); } - - - @Override - public int nodeCount() { - return downstreamAdjacentNodes.size(); + for (var entry : this.downstreamAdjacentNodes.entrySet()) { + graph.downstreamAdjacentNodes.put(entry.getKey(), CollectionUtil.copy(entry.getValue())); } - - - @Override - public int edgeCount() { - return this.upstreamAdjacentNodes.values().stream().mapToInt(Set::size).sum(); + return graph; + } + + /** + * For a given a two adjacency maps, copy missing edges from the first map to the second. + * + * @param srcMap The adjacency map to copy edges from. + * @param dstMap The adjacency map to copy edges to. + */ + private void mirror(Map> srcMap, Map> dstMap) { + if (srcMap != null && dstMap != null) { + for (Entry> entry : srcMap.entrySet()) { + var node = entry.getKey(); + var srcEdges = entry.getValue(); + dstMap.compute( + node, + (_node, dstEdges) -> { + // Node does not exist; add it. + if (dstEdges == null) { + return CollectionUtil.copy(srcEdges); + } + + // Node does exist; add the missing edges. + var set = dstEdges; + for (T edge : srcEdges) { + set = CollectionUtil.plus(set, edge); + } + return set; + }); + } } - - - @Override - public Set nodes() { - return Collections.unmodifiableSet(this.downstreamAdjacentNodes.keySet()); + } + + /** + * Merge another directed graph into this one. + * + * @param another The graph to merge into this one. + */ + public void merge(DirectedGraph another) { + this.graphChanged(); + mirror(another.upstreamAdjacentNodes, this.upstreamAdjacentNodes); + mirror(another.downstreamAdjacentNodes, this.downstreamAdjacentNodes); + } + + /** Return the set of nodes that have no neighbors listed in the given adjacency map. */ + private Set independentNodes(Map> adjacencyMap) { + var independent = new LinkedHashSet(); + for (T node : nodes()) { + var neighbors = adjacencyMap.get(node); + if (neighbors == null || neighbors.size() == 0) { + independent.add(node); + } } - - - public void clear() { - this.graphChanged(); - this.downstreamAdjacentNodes.clear(); - this.upstreamAdjacentNodes.clear(); + return independent; + } + + /** Return the root nodes of this graph. Root nodes have no upstream neighbors. */ + public Set rootNodes() { + return independentNodes(this.upstreamAdjacentNodes); + } + + /** Return the leaf nodes of this graph. Leaf nodes have no downstream neighbors. */ + public Set leafNodes() { + return independentNodes(this.downstreamAdjacentNodes); + } + + @Override + public int nodeCount() { + return downstreamAdjacentNodes.size(); + } + + @Override + public int edgeCount() { + return this.upstreamAdjacentNodes.values().stream().mapToInt(Set::size).sum(); + } + + @Override + public Set nodes() { + return Collections.unmodifiableSet(this.downstreamAdjacentNodes.keySet()); + } + + public void clear() { + this.graphChanged(); + this.downstreamAdjacentNodes.clear(); + this.upstreamAdjacentNodes.clear(); + } + + /** Return a textual list of the nodes. */ + @Override + public String toString() { + return nodes().stream().map(Objects::toString).collect(Collectors.joining(", ", "{", "}")); + } + + /** Return the DOT (GraphViz) representation of the graph. */ + @Override + public String toDOT() { + StringBuilder dotRepresentation = new StringBuilder(); + StringBuilder edges = new StringBuilder(); + + // Start the digraph with a left-write rank + dotRepresentation.append("digraph {\n"); + dotRepresentation.append(" rankdir=LF;\n"); + + Set nodes = nodes(); + for (T node : nodes) { + // Draw the node + dotRepresentation.append( + " node_" + + (node.toString().hashCode() & 0xfffffff) + + " [label=\"" + + node.toString() + + "\"]\n"); + + // Draw the edges + Set downstreamNodes = getDownstreamAdjacentNodes(node); + for (T downstreamNode : downstreamNodes) { + edges.append( + " node_" + + (node.toString().hashCode() & 0xfffffff) + + " -> node_" + + (downstreamNode.toString().hashCode() & 0xfffffff) + + "\n"); + } } + // Add the edges to the definition of the graph at the bottom + dotRepresentation.append(edges); - /** - * Return a textual list of the nodes. - */ - @Override - public String toString() { - return nodes().stream().map(Objects::toString).collect(Collectors.joining(", ", "{", "}")); - } + // Close the digraph + dotRepresentation.append("}\n"); - /** - * Return the DOT (GraphViz) representation of the graph. - */ - @Override - public String toDOT() { - StringBuilder dotRepresentation = new StringBuilder(); - StringBuilder edges = new StringBuilder(); - - // Start the digraph with a left-write rank - dotRepresentation.append("digraph {\n"); - dotRepresentation.append(" rankdir=LF;\n"); - - Set nodes = nodes(); - for (T node: nodes) { - // Draw the node - dotRepresentation.append(" node_" + (node.toString().hashCode() & 0xfffffff) - + " [label=\""+ node.toString() +"\"]\n"); - - // Draw the edges - Set downstreamNodes = getDownstreamAdjacentNodes(node); - for (T downstreamNode: downstreamNodes) { - edges.append(" node_" + (node.toString().hashCode() & 0xfffffff) - + " -> node_" + (downstreamNode.toString().hashCode() & 0xfffffff) + "\n"); - } - } - - // Add the edges to the definition of the graph at the bottom - dotRepresentation.append(edges); - - // Close the digraph - dotRepresentation.append("}\n"); - - // Return the DOT representation - return dotRepresentation.toString(); - } + // Return the DOT representation + return dotRepresentation.toString(); + } } diff --git a/org.lflang/src/org/lflang/graph/Graph.java b/org.lflang/src/org/lflang/graph/Graph.java index 4e9a1723d9..07371c1255 100644 --- a/org.lflang/src/org/lflang/graph/Graph.java +++ b/org.lflang/src/org/lflang/graph/Graph.java @@ -1,26 +1,26 @@ /************* - 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.graph; @@ -35,49 +35,36 @@ */ public interface Graph { - /** - * Return an unmodifiable set of nodes in this graph. - */ - Set nodes(); - - - boolean hasNode(T node); - - - /** Add the given node to the graph. */ - void addNode(T node); - - - /** - * Remove the given node from the graph. This also eliminates any - * edges from upstream and to downstream neighbors of this node. - * - * @param node The node to remove. - */ - void removeNode(T node); - - - // todo order of parameters here is unintuitive... from -> to is more usual - - void addEdge(T to, T from); + /** Return an unmodifiable set of nodes in this graph. */ + Set nodes(); + boolean hasNode(T node); - void addEdges(T to, List from); + /** Add the given node to the graph. */ + void addNode(T node); + /** + * Remove the given node from the graph. This also eliminates any edges from upstream and to + * downstream neighbors of this node. + * + * @param node The node to remove. + */ + void removeNode(T node); - void removeEdge(T to, T from); + // todo order of parameters here is unintuitive... from -> to is more usual + void addEdge(T to, T from); - /** - * Return the number of nodes in this graph. - */ - int nodeCount(); + void addEdges(T to, List from); + void removeEdge(T to, T from); - /** Return the number of directed edges in this graph. */ - int edgeCount(); + /** Return the number of nodes in this graph. */ + int nodeCount(); + /** Return the number of directed edges in this graph. */ + int edgeCount(); - /** Return the DOT (GraphViz) representation of the graph. */ - String toDOT(); + /** Return the DOT (GraphViz) representation of the graph. */ + String toDOT(); } diff --git a/org.lflang/src/org/lflang/graph/InstantiationGraph.java b/org.lflang/src/org/lflang/graph/InstantiationGraph.java index b32c97e2f2..dca5492fd0 100644 --- a/org.lflang/src/org/lflang/graph/InstantiationGraph.java +++ b/org.lflang/src/org/lflang/graph/InstantiationGraph.java @@ -1,35 +1,34 @@ /** * 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: + *

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. + *

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. + *

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. + *

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.graph; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Iterables; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; - import org.eclipse.emf.ecore.resource.Resource; import org.lflang.ast.ASTUtils; import org.lflang.lf.Instantiation; @@ -38,138 +37,124 @@ import org.lflang.lf.ReactorDecl; import org.lflang.util.IteratorUtil; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Iterables; - /** - * A graph with vertices that are Reactors (not ReactorInstances) and edges that denote - * dependencies between them. A "dependency" from reactor class A to - * reactor class B (A depends on B) means that A instantiates within - * it at least one instance of B. Note that there a potentially - * confusing and subtle distinction here between an "instantiation" - * and an "instance". They are not the same thing at all. An - * "instantiation" is an AST node representing a statement like - * {@code a = new A();}. This can result in many instances of reactor - * class A (if the containing reactor class is instantiated multiple times). + * A graph with vertices that are Reactors (not ReactorInstances) and edges that denote dependencies + * between them. A "dependency" from reactor class A to reactor class B (A depends on B) means that + * A instantiates within it at least one instance of B. Note that there a potentially confusing and + * subtle distinction here between an "instantiation" and an "instance". They are not the same thing + * at all. An "instantiation" is an AST node representing a statement like {@code a = new A();}. + * This can result in many instances of reactor class A (if the containing reactor class is + * instantiated multiple times). * - * In addition to the graph, this class keeps track of the instantiations - * that induce the dependencies. These can be retrieved using the method - * {@code getInstantiations(Reactor)}. + *

In addition to the graph, this class keeps track of the instantiations that induce the + * dependencies. These can be retrieved using the method {@code getInstantiations(Reactor)}. * * @author Marten Lohstroh */ public class InstantiationGraph extends PrecedenceGraph { - /** - * A mapping from reactors to the sites of their instantiation. - */ - protected final HashMultimap reactorToInstantiation = - HashMultimap.create(); + /** A mapping from reactors to the sites of their instantiation. */ + protected final HashMultimap reactorToInstantiation = + HashMultimap.create(); - /** - * A mapping from reactor classes to their declarations. - */ - protected final HashMultimap reactorToDecl = - HashMultimap.create(); + /** A mapping from reactor classes to their declarations. */ + protected final HashMultimap reactorToDecl = HashMultimap.create(); - /** - * Return the instantiations that point to a given reactor definition. - * If none are known, returns an empty set. * The returned set may be - * unmodifiable. - */ - public Set getInstantiations(final Reactor definition) { - Set instantiations = this.reactorToInstantiation.get(definition); - if (instantiations != null) { - return instantiations; - } else { - return Collections.emptySet(); - } + /** + * Return the instantiations that point to a given reactor definition. If none are known, returns + * an empty set. * The returned set may be unmodifiable. + */ + public Set getInstantiations(final Reactor definition) { + Set instantiations = this.reactorToInstantiation.get(definition); + if (instantiations != null) { + return instantiations; + } else { + return Collections.emptySet(); } + } - /** - * Return the declarations that point to a given reactor definition. - * A declaration is either a reactor definition or an import statement. - */ - public Set getDeclarations(final Reactor definition) { - return this.reactorToDecl.get(definition); - } + /** + * Return the declarations that point to a given reactor definition. A declaration is either a + * reactor definition or an import statement. + */ + public Set getDeclarations(final Reactor definition) { + return this.reactorToDecl.get(definition); + } - /** - * Return the reactor definitions referenced by instantiations in this graph - * ordered topologically. Each reactor in the returned list is preceded by - * any reactors that it may instantiate. - */ - public List getReactors() { - return this.nodesInTopologicalOrder(); - } + /** + * Return the reactor definitions referenced by instantiations in this graph ordered + * topologically. Each reactor in the returned list is preceded by any reactors that it may + * instantiate. + */ + public List getReactors() { + return this.nodesInTopologicalOrder(); + } - /** - * Construct an instantiation graph based on the given AST and, if the - * detectCycles argument is true, run Tarjan's algorithm to detect cyclic - * dependencies between instantiations. - * @param resource The resource associated with the AST. - * @param detectCycles Whether or not to detect cycles. - */ - public InstantiationGraph(final Resource resource, final boolean detectCycles) { - final Iterable instantiations = Iterables.filter( - IteratorUtil.asIterable(resource.getAllContents()), - Instantiation.class); - Optional main = IteratorUtil - .asFilteredStream(resource.getAllContents(), Reactor.class) - .filter(reactor -> reactor.isMain() || reactor.isFederated()) - .findFirst(); + /** + * Construct an instantiation graph based on the given AST and, if the detectCycles argument is + * true, run Tarjan's algorithm to detect cyclic dependencies between instantiations. + * + * @param resource The resource associated with the AST. + * @param detectCycles Whether or not to detect cycles. + */ + public InstantiationGraph(final Resource resource, final boolean detectCycles) { + final Iterable instantiations = + Iterables.filter(IteratorUtil.asIterable(resource.getAllContents()), Instantiation.class); + Optional main = + IteratorUtil.asFilteredStream(resource.getAllContents(), Reactor.class) + .filter(reactor -> reactor.isMain() || reactor.isFederated()) + .findFirst(); - if (main.isPresent()) { - this.addNode(main.get()); - } - for (final Instantiation i : instantiations) { - this.buildGraph(i, new HashSet<>()); - } - if (detectCycles) { - this.detectCycles(); - } + if (main.isPresent()) { + this.addNode(main.get()); + } + for (final Instantiation i : instantiations) { + this.buildGraph(i, new HashSet<>()); + } + if (detectCycles) { + this.detectCycles(); } + } - /** - * Construct an instantiation graph based on the given AST and, if the - * detectCycles argument is true, run Tarjan's algorithm to detect cyclic - * dependencies between instantiations. - * @param model The root of the AST. - * @param detectCycles Whether or not to detect cycles. - */ - public InstantiationGraph(final Model model, final boolean detectCycles) { - for (final Reactor r : model.getReactors()) { - for (final Instantiation i : r.getInstantiations()) { - this.buildGraph(i, new HashSet<>()); - } - } - if (detectCycles) { - this.detectCycles(); - } + /** + * Construct an instantiation graph based on the given AST and, if the detectCycles argument is + * true, run Tarjan's algorithm to detect cyclic dependencies between instantiations. + * + * @param model The root of the AST. + * @param detectCycles Whether or not to detect cycles. + */ + public InstantiationGraph(final Model model, final boolean detectCycles) { + for (final Reactor r : model.getReactors()) { + for (final Instantiation i : r.getInstantiations()) { + this.buildGraph(i, new HashSet<>()); + } + } + if (detectCycles) { + this.detectCycles(); } + } - /** - * Traverse the AST and build this precedence graph relating the - * encountered instantiations. Also map each reactor to all - * declarations associated with it and each reactor to the sites of - * its instantiations. - */ - private void buildGraph(final Instantiation instantiation, final Set visited) { - final ReactorDecl decl = instantiation.getReactorClass(); - final Reactor reactor = ASTUtils.toDefinition(decl); - if (reactor != null) { - Reactor container = ASTUtils.getEnclosingReactor(instantiation); - if (visited.add(instantiation)) { - this.reactorToInstantiation.put(reactor, instantiation); - this.reactorToDecl.put(reactor, decl); - if (container != null) { - this.addEdge(container, reactor); - } else { - this.addNode(reactor); - } - for (final Instantiation inst : reactor.getInstantiations()) { - this.buildGraph(inst, visited); - } - } + /** + * Traverse the AST and build this precedence graph relating the encountered instantiations. Also + * map each reactor to all declarations associated with it and each reactor to the sites of its + * instantiations. + */ + private void buildGraph(final Instantiation instantiation, final Set visited) { + final ReactorDecl decl = instantiation.getReactorClass(); + final Reactor reactor = ASTUtils.toDefinition(decl); + if (reactor != null) { + Reactor container = ASTUtils.getEnclosingReactor(instantiation); + if (visited.add(instantiation)) { + this.reactorToInstantiation.put(reactor, instantiation); + this.reactorToDecl.put(reactor, decl); + if (container != null) { + this.addEdge(container, reactor); + } else { + this.addNode(reactor); + } + for (final Instantiation inst : reactor.getInstantiations()) { + this.buildGraph(inst, visited); } + } } + } } diff --git a/org.lflang/src/org/lflang/graph/NodeAnnotation.java b/org.lflang/src/org/lflang/graph/NodeAnnotation.java index b8b91c92ad..191967d043 100644 --- a/org.lflang/src/org/lflang/graph/NodeAnnotation.java +++ b/org.lflang/src/org/lflang/graph/NodeAnnotation.java @@ -1,66 +1,54 @@ /** * 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. + * + *

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.graph; /** - * Note annotations used in Tarjan's algorithm for finding strongly connected - * components. - * + * Note annotations used in Tarjan's algorithm for finding strongly connected components. + * * @author Marten Lohstroh */ public class NodeAnnotation { - /** - * Sequence number that is assigned when this node is discovered. - * A node with a lower index was discovered later than this one; - * a node with a higher index was discovered later than this one. - */ - public int index = -1; - - /** - * Temporary mark do be used in topological sort algorithm. - */ - public boolean hasTempMark = false; - - /** - * Temporary mark do be used in topological sort algorithm. - */ - public boolean hasPermMark = false; - - /** - * The smallest index of any node known to be reachable from this node. - */ - public int lowLink = -1; - - /** - * Whether or not this node is currently on the stack that - * keeps track of visited nodes that potentially form a cycle. - */ - public boolean onStack = false; - - /** - * Whether or not this node has a dependency on itself. - */ - public boolean selfLoop = false; -} + /** + * Sequence number that is assigned when this node is discovered. A node with a lower index was + * discovered later than this one; a node with a higher index was discovered later than this one. + */ + public int index = -1; + + /** Temporary mark do be used in topological sort algorithm. */ + public boolean hasTempMark = false; + + /** Temporary mark do be used in topological sort algorithm. */ + public boolean hasPermMark = false; + /** The smallest index of any node known to be reachable from this node. */ + public int lowLink = -1; + + /** + * Whether or not this node is currently on the stack that keeps track of visited nodes that + * potentially form a cycle. + */ + public boolean onStack = false; + + /** Whether or not this node has a dependency on itself. */ + public boolean selfLoop = false; +} diff --git a/org.lflang/src/org/lflang/graph/NodeAnnotations.java b/org.lflang/src/org/lflang/graph/NodeAnnotations.java index 346149a8c4..6efe85760a 100644 --- a/org.lflang/src/org/lflang/graph/NodeAnnotations.java +++ b/org.lflang/src/org/lflang/graph/NodeAnnotations.java @@ -1,46 +1,42 @@ /** * 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. + * + *

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.graph; import java.util.HashMap; -/** - * Maps a node in the graph to its annotation. - * Creates a new annotation if no annotation exists. - */ +/** Maps a node in the graph to its annotation. Creates a new annotation if no annotation exists. */ public class NodeAnnotations { private HashMap annotations = new HashMap(); - + public NodeAnnotation get(final T node) { NodeAnnotation annotation = this.annotations.get(node); if (annotation == null) { - annotation = new NodeAnnotation(); - this.annotations.put(node, annotation); + annotation = new NodeAnnotation(); + this.annotations.put(node, annotation); } return annotation; } - + public NodeAnnotation put(final T node, final NodeAnnotation annotation) { return this.annotations.put(node, annotation); } diff --git a/org.lflang/src/org/lflang/graph/PrecedenceGraph.java b/org.lflang/src/org/lflang/graph/PrecedenceGraph.java index 03fd266f6a..80abb21455 100644 --- a/org.lflang/src/org/lflang/graph/PrecedenceGraph.java +++ b/org.lflang/src/org/lflang/graph/PrecedenceGraph.java @@ -1,242 +1,219 @@ /************* -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.graph; +import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.Stack; - import org.eclipse.xtext.xbase.lib.ListExtensions; -import java.util.ArrayList; - -/** - * Elaboration of {@code DirectedGraph} that is capable of identifying strongly - * connected components and topologically sorting its nodes. - * +/** + * Elaboration of {@code DirectedGraph} that is capable of identifying strongly connected components + * and topologically sorting its nodes. + * * @author Marten Lohstroh */ public class PrecedenceGraph extends DirectedGraph { - /** - * Annotations used during the execution of Tarjan's algorithm. - */ - private NodeAnnotations annotations = new NodeAnnotations(); - - /** - * Indicates whether or not the graph has been analyzed for cycles. - * If this variable is false, Tarjan's algorithm need to be ran to find out - * whether or not this graph has cycles. - */ - private boolean cycleAnalysisDone = false; - - /** - * Indicates whether or not the graph has been sorted. - * If this variable is false, a new DFS has to be done to compute a - * topological sort. - */ - private boolean isSorted = false; - - /** - * Index used in Tarjan's algorithm. - */ - private int index = 0; - - /** - * After analysis has completed, this list contains all nodes in reverse - * topological order. - */ - private List sortedNodes = new ArrayList<>(); - - /** - * Stack used in Tarjan's algorithm. - */ - private Stack stack = new Stack<>(); - - /** - * After analysis has completed, this list contains all all sets of nodes - * that are part of the same strongly connected component. - */ - protected List> cycles = new ArrayList<>(); - - /** - * Invalidate cached analysis due to changes in the graph structure. - */ - @Override - public void graphChanged() { - this.cycleAnalysisDone = false; - this.isSorted = false; - } - - /** - * Construct a new dependency graph. - */ - public PrecedenceGraph() {} - - /** - * Topologically sort the nodes in the graph. - */ - private void sortNodes() { - if (!this.isSorted) { - // Cleanup. - this.sortedNodes = new ArrayList<>(); - this.nodes().forEach( - it -> { - this.annotations.get(it).hasTempMark = false; - this.annotations.get(it).hasPermMark = false; - } - ); - - // Start sorting. - for (T node : this.nodes()) { - if (!this.annotations.get(node).hasPermMark) { - // Unmarked node. - this.visit(node); - } - } - this.isSorted = true; + /** Annotations used during the execution of Tarjan's algorithm. */ + private NodeAnnotations annotations = new NodeAnnotations(); + + /** + * Indicates whether or not the graph has been analyzed for cycles. If this variable is false, + * Tarjan's algorithm need to be ran to find out whether or not this graph has cycles. + */ + private boolean cycleAnalysisDone = false; + + /** + * Indicates whether or not the graph has been sorted. If this variable is false, a new DFS has to + * be done to compute a topological sort. + */ + private boolean isSorted = false; + + /** Index used in Tarjan's algorithm. */ + private int index = 0; + + /** After analysis has completed, this list contains all nodes in reverse topological order. */ + private List sortedNodes = new ArrayList<>(); + + /** Stack used in Tarjan's algorithm. */ + private Stack stack = new Stack<>(); + + /** + * After analysis has completed, this list contains all all sets of nodes that are part of the + * same strongly connected component. + */ + protected List> cycles = new ArrayList<>(); + + /** Invalidate cached analysis due to changes in the graph structure. */ + @Override + public void graphChanged() { + this.cycleAnalysisDone = false; + this.isSorted = false; + } + + /** Construct a new dependency graph. */ + public PrecedenceGraph() {} + + /** Topologically sort the nodes in the graph. */ + private void sortNodes() { + if (!this.isSorted) { + // Cleanup. + this.sortedNodes = new ArrayList<>(); + this.nodes() + .forEach( + it -> { + this.annotations.get(it).hasTempMark = false; + this.annotations.get(it).hasPermMark = false; + }); + + // Start sorting. + for (T node : this.nodes()) { + if (!this.annotations.get(node).hasPermMark) { + // Unmarked node. + this.visit(node); } + } + this.isSorted = true; } - - /** - * Recursively visit all nodes reachable from the given node; after all - * those nodes have been visited add the current node to a list which will - * be sorted in reverse order. - */ - private void visit(T node) { - NodeAnnotation annotation = this.annotations.get(node); - if (annotation.hasPermMark) { - return; - } - if (annotation.hasTempMark) { - // Not a DAG. - throw new Error("Cannot order nodes due to cycle in the graph."); - } - annotation.hasTempMark = true; - for (T dep : this.getDownstreamAdjacentNodes(node)) { - visit(dep); - } - annotation.hasTempMark = false; - annotation.hasPermMark = true; - this.sortedNodes.add(node); - } - - /** - * Run Tarjan's algorithm for finding strongly connected components. - * After invoking this method, the detected cycles with be listed - * in the class variable {@code cycles}. - */ - public void detectCycles() { - if (!this.cycleAnalysisDone) { - this.index = 0; - this.stack = new Stack<>(); - this.cycles = new ArrayList<>(); - this.nodes().forEach(it -> { - this.annotations.get(it).index = -1; - }); - for (T node : this.nodes()) { - if (this.annotations.get(node).index == -1) { - this.strongConnect(node); - } - } - this.cycleAnalysisDone = true; - stack = null; - } + } + + /** + * Recursively visit all nodes reachable from the given node; after all those nodes have been + * visited add the current node to a list which will be sorted in reverse order. + */ + private void visit(T node) { + NodeAnnotation annotation = this.annotations.get(node); + if (annotation.hasPermMark) { + return; } - - /** - * Report whether this graph has any cycles in it. - */ - public boolean hasCycles() { - this.detectCycles(); - return this.cycles.size() > 0; + if (annotation.hasTempMark) { + // Not a DAG. + throw new Error("Cannot order nodes due to cycle in the graph."); } - - /** - * Return a list of strongly connected components that exist in this graph. - */ - public List> getCycles() { - this.detectCycles(); - return this.cycles; + annotation.hasTempMark = true; + for (T dep : this.getDownstreamAdjacentNodes(node)) { + visit(dep); } - - /** - * Traverse the graph to visit unvisited dependencies and determine - * whether they are part of a cycle. - */ - public void strongConnect(T node) { - NodeAnnotation annotation = this.annotations.get(node); - annotation.index = this.index; - annotation.lowLink = this.index; - annotation.onStack = true; - this.index++; - this.stack.push(node); - for (T dep : this.getUpstreamAdjacentNodes(node)) { - NodeAnnotation depAnnotation = this.annotations.get(dep); - if (depAnnotation.onStack) { - annotation.lowLink = Math.min(annotation.lowLink, depAnnotation.index); - if (node.equals(dep)) { - annotation.selfLoop = true; - } - } else if (depAnnotation.index == -1) { - strongConnect(dep); - annotation.lowLink = Math.min(annotation.lowLink, depAnnotation.lowLink); - } - } - - if (annotation.lowLink == annotation.index) { - Set scc = new LinkedHashSet<>(); - T dep = null; - do { - dep = this.stack.pop(); - this.annotations.get(dep).onStack = false; - scc.add(dep); - } while (!node.equals(dep)); - // Only report self loops or cycles with two or more nodes. - if (scc.size() > 1 || annotation.selfLoop) { - this.cycles.add(scc); - } + annotation.hasTempMark = false; + annotation.hasPermMark = true; + this.sortedNodes.add(node); + } + + /** + * Run Tarjan's algorithm for finding strongly connected components. After invoking this method, + * the detected cycles with be listed in the class variable {@code cycles}. + */ + public void detectCycles() { + if (!this.cycleAnalysisDone) { + this.index = 0; + this.stack = new Stack<>(); + this.cycles = new ArrayList<>(); + this.nodes() + .forEach( + it -> { + this.annotations.get(it).index = -1; + }); + for (T node : this.nodes()) { + if (this.annotations.get(node).index == -1) { + this.strongConnect(node); } + } + this.cycleAnalysisDone = true; + stack = null; } - - /** - * Return the nodes of this graph in reverse topological order. Each node - * in the returned list is succeeded by the nodes that it depends on. - */ - public List nodesInReverseTopologicalOrder() { - this.sortNodes(); - return this.sortedNodes; + } + + /** Report whether this graph has any cycles in it. */ + public boolean hasCycles() { + this.detectCycles(); + return this.cycles.size() > 0; + } + + /** Return a list of strongly connected components that exist in this graph. */ + public List> getCycles() { + this.detectCycles(); + return this.cycles; + } + + /** + * Traverse the graph to visit unvisited dependencies and determine whether they are part of a + * cycle. + */ + public void strongConnect(T node) { + NodeAnnotation annotation = this.annotations.get(node); + annotation.index = this.index; + annotation.lowLink = this.index; + annotation.onStack = true; + this.index++; + this.stack.push(node); + for (T dep : this.getUpstreamAdjacentNodes(node)) { + NodeAnnotation depAnnotation = this.annotations.get(dep); + if (depAnnotation.onStack) { + annotation.lowLink = Math.min(annotation.lowLink, depAnnotation.index); + if (node.equals(dep)) { + annotation.selfLoop = true; + } + } else if (depAnnotation.index == -1) { + strongConnect(dep); + annotation.lowLink = Math.min(annotation.lowLink, depAnnotation.lowLink); + } } - - /** - * Return the nodes of this graph in reverse topological order. Each node - * in the returned list is preceded by the nodes that it depends on. - */ - public List nodesInTopologicalOrder() { - this.sortNodes(); - return ListExtensions.reverse(this.sortedNodes); + + if (annotation.lowLink == annotation.index) { + Set scc = new LinkedHashSet<>(); + T dep = null; + do { + dep = this.stack.pop(); + this.annotations.get(dep).onStack = false; + scc.add(dep); + } while (!node.equals(dep)); + // Only report self loops or cycles with two or more nodes. + if (scc.size() > 1 || annotation.selfLoop) { + this.cycles.add(scc); + } } + } + + /** + * Return the nodes of this graph in reverse topological order. Each node in the returned list is + * succeeded by the nodes that it depends on. + */ + public List nodesInReverseTopologicalOrder() { + this.sortNodes(); + return this.sortedNodes; + } + + /** + * Return the nodes of this graph in reverse topological order. Each node in the returned list is + * preceded by the nodes that it depends on. + */ + public List nodesInTopologicalOrder() { + this.sortNodes(); + return ListExtensions.reverse(this.sortedNodes); + } } diff --git a/org.lflang/src/org/lflang/graph/TopologyGraph.java b/org.lflang/src/org/lflang/graph/TopologyGraph.java index c680c5f7e1..300d09048f 100644 --- a/org.lflang/src/org/lflang/graph/TopologyGraph.java +++ b/org.lflang/src/org/lflang/graph/TopologyGraph.java @@ -1,16 +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 @@ -28,7 +28,6 @@ import java.util.Arrays; import java.util.Collection; - import org.lflang.generator.NamedInstance; import org.lflang.generator.PortInstance; import org.lflang.generator.ReactionInstance; @@ -39,138 +38,139 @@ import org.lflang.lf.Variable; /** - * A graph with vertices that are ports or reactions and edges that denote - * dependencies between them. - * - * NOTE: This is not used anywhere anymore, but we keep it in case this particular - * graph structure proves useful in the future. - * + * A graph with vertices that are ports or reactions and edges that denote dependencies between + * them. + * + *

NOTE: This is not used anywhere anymore, but we keep it in case this particular graph + * structure proves useful in the future. + * * @author Marten Lohstroh */ public class TopologyGraph extends PrecedenceGraph> { - /** - * Construct a graph with vertices that are reactions or ports and edges - * that represent (zero-delay) dependencies. - * - * After constructing the graph, run Tarjan's algorithm to detect cyclic - * dependencies between reactions. It is assumed that no instantiation - * cycles are present in the program. Checks for instantiation cycles thus - * must be carried out prior to constructing this graph. - * - * @param reactors The reactor instances to construct the graph for. - */ - public TopologyGraph(Collection reactors) { - for (var r : reactors) { - collectNodesFrom(r); - } - this.detectCycles(); + /** + * Construct a graph with vertices that are reactions or ports and edges that represent + * (zero-delay) dependencies. + * + *

After constructing the graph, run Tarjan's algorithm to detect cyclic dependencies between + * reactions. It is assumed that no instantiation cycles are present in the program. Checks for + * instantiation cycles thus must be carried out prior to constructing this graph. + * + * @param reactors The reactor instances to construct the graph for. + */ + public TopologyGraph(Collection reactors) { + for (var r : reactors) { + collectNodesFrom(r); } + this.detectCycles(); + } - /** See description on other constructor. */ - public TopologyGraph(ReactorInstance... reactors) { - this(Arrays.asList(reactors)); - } + /** See description on other constructor. */ + public TopologyGraph(ReactorInstance... reactors) { + this(Arrays.asList(reactors)); + } - /** - * Build the graph by recursively visiting reactor instances contained in - * the passed in reactor instance. - * - * @param reactor A reactor instance to harvest dependencies from. - */ - public void collectNodesFrom(ReactorInstance reactor) { - ReactionInstance previousReaction = null; - for (var reaction : reactor.reactions) { - this.addNode(reaction); + /** + * Build the graph by recursively visiting reactor instances contained in the passed in reactor + * instance. + * + * @param reactor A reactor instance to harvest dependencies from. + */ + public void collectNodesFrom(ReactorInstance reactor) { + ReactionInstance previousReaction = null; + for (var reaction : reactor.reactions) { + this.addNode(reaction); - this.addSources(reaction); - this.addEffects(reaction); + this.addSources(reaction); + this.addEffects(reaction); - // If there is an earlier reaction in this same reactor, then - // create a link in the reaction graph. - if (previousReaction != null) { - this.addEdge(reaction, previousReaction); - } - previousReaction = reaction; - } - // Recursively add nodes and edges from contained reactors. - for (var child : reactor.children) { - collectNodesFrom(child); - } + // If there is an earlier reaction in this same reactor, then + // create a link in the reaction graph. + if (previousReaction != null) { + this.addEdge(reaction, previousReaction); + } + previousReaction = reaction; } + // Recursively add nodes and edges from contained reactors. + for (var child : reactor.children) { + collectNodesFrom(child); + } + } - /** - * Given a reaction instance, record dependencies implied by its effects. - * - * Excluded from the recorded dependencies are those that are broken by - * physical connections or "after" delays. - * - * @param reaction The reaction to record the dependencies of. - */ - private void addEffects(ReactionInstance reaction) { - for (TriggerInstance effect : reaction.effects) { - if (effect instanceof PortInstance) { - addEdge(effect, reaction); - PortInstance orig = (PortInstance) effect; - for (SendRange sendRange : orig.getDependentPorts()) { - sendRange.destinations.forEach(dest -> { - recordDependency(reaction, orig, dest.instance, sendRange.connection); - }); - } - } + /** + * Given a reaction instance, record dependencies implied by its effects. + * + *

Excluded from the recorded dependencies are those that are broken by physical connections or + * "after" delays. + * + * @param reaction The reaction to record the dependencies of. + */ + private void addEffects(ReactionInstance reaction) { + for (TriggerInstance effect : reaction.effects) { + if (effect instanceof PortInstance) { + addEdge(effect, reaction); + PortInstance orig = (PortInstance) effect; + for (SendRange sendRange : orig.getDependentPorts()) { + sendRange.destinations.forEach( + dest -> { + recordDependency(reaction, orig, dest.instance, sendRange.connection); + }); } + } } + } - /** - * Given a reaction instance, record dependencies implied by its sources. - * - * Excluded from the recorded dependencies are those that are broken by - * physical connections or "after" delays. - * - * @param reaction The reaction to record the dependencies of. - */ - private void addSources(ReactionInstance reaction) { - for (TriggerInstance source : reaction.sources) { - if (source instanceof PortInstance) { - addEdge(reaction, source); - PortInstance dest = (PortInstance) source; - dest.getDependsOnPorts().forEach(orig -> { - // FIXME: Don't have connection information here, hence the null argument. - // This will like result in invalid cycle detection. - recordDependency(reaction, orig.instance, dest, null); + /** + * Given a reaction instance, record dependencies implied by its sources. + * + *

Excluded from the recorded dependencies are those that are broken by physical connections or + * "after" delays. + * + * @param reaction The reaction to record the dependencies of. + */ + private void addSources(ReactionInstance reaction) { + for (TriggerInstance source : reaction.sources) { + if (source instanceof PortInstance) { + addEdge(reaction, source); + PortInstance dest = (PortInstance) source; + dest.getDependsOnPorts() + .forEach( + orig -> { + // FIXME: Don't have connection information here, hence the null argument. + // This will like result in invalid cycle detection. + recordDependency(reaction, orig.instance, dest, null); }); - } - } + } } + } - /** - * Record a dependency between two port instances, but only if there is a - * zero-delay path from origin to destination. - * - * @param reaction A reaction that has one of the given ports as a source or - * effect. - * @param orig The upstream port. - * @param dest The downstream port. - * @param connection The connection creating this dependency or null if not - * created by a connection. - */ - private void recordDependency(ReactionInstance reaction, PortInstance orig, - PortInstance dest, Connection connection) { - if (!dependencyBroken(connection)) { - addEdge(dest, orig); - } + /** + * Record a dependency between two port instances, but only if there is a zero-delay path from + * origin to destination. + * + * @param reaction A reaction that has one of the given ports as a source or effect. + * @param orig The upstream port. + * @param dest The downstream port. + * @param connection The connection creating this dependency or null if not created by a + * connection. + */ + private void recordDependency( + ReactionInstance reaction, PortInstance orig, PortInstance dest, Connection connection) { + if (!dependencyBroken(connection)) { + addEdge(dest, orig); } + } - /** - * Report whether or not the given connection breaks dependencies or not. - * - * @param c An AST object that represents a connection. - * @return true if the connection is physical or has a delay. - */ - private boolean dependencyBroken(Connection c) { - if (c != null && (c.isPhysical() || c.getDelay() != null)) { - return true; - } - return false; + /** + * Report whether or not the given connection breaks dependencies or not. + * + * @param c An AST object that represents a connection. + * @return true if the connection is physical or has a delay. + */ + private boolean dependencyBroken(Connection c) { + if (c != null && (c.isPhysical() || c.getDelay() != null)) { + return true; } + return false; + } } diff --git a/org.lflang/src/org/lflang/ide/LFIdeModule.java b/org.lflang/src/org/lflang/ide/LFIdeModule.java index 6c63d9c6e0..81aa863bd1 100644 --- a/org.lflang/src/org/lflang/ide/LFIdeModule.java +++ b/org.lflang/src/org/lflang/ide/LFIdeModule.java @@ -3,9 +3,5 @@ */ package org.lflang.ide; - -/** - * Use this class to register ide components. - */ -public class LFIdeModule extends AbstractLFIdeModule { -} +/** Use this class to register ide components. */ +public class LFIdeModule extends AbstractLFIdeModule {} diff --git a/org.lflang/src/org/lflang/ide/LFIdeSetup.java b/org.lflang/src/org/lflang/ide/LFIdeSetup.java index 2e6b969952..7465bda78b 100644 --- a/org.lflang/src/org/lflang/ide/LFIdeSetup.java +++ b/org.lflang/src/org/lflang/ide/LFIdeSetup.java @@ -6,17 +6,14 @@ import org.lflang.LFRuntimeModule; import org.lflang.LFStandaloneSetup; -/** - * Initialization support for running Xtext languages as language servers. - */ +/** Initialization support for running Xtext languages as language servers. */ public class LFIdeSetup extends LFStandaloneSetup { - public LFIdeSetup() { - super(new LFRuntimeModule(), new LFIdeModule()); - } - - public static void doSetup() { - new LFIdeSetup().createInjectorAndDoEMFRegistration(); - } + public LFIdeSetup() { + super(new LFRuntimeModule(), new LFIdeModule()); + } + public static void doSetup() { + new LFIdeSetup().createInjectorAndDoEMFRegistration(); + } } diff --git a/org.lflang/src/org/lflang/scoping/LFGlobalScopeProvider.java b/org.lflang/src/org/lflang/scoping/LFGlobalScopeProvider.java index 119ccae842..18c7a3a8af 100644 --- a/org.lflang/src/org/lflang/scoping/LFGlobalScopeProvider.java +++ b/org.lflang/src/org/lflang/scoping/LFGlobalScopeProvider.java @@ -25,134 +25,121 @@ import com.google.common.base.Splitter; import com.google.inject.Inject; import com.google.inject.Provider; - import java.util.LinkedHashSet; import java.util.Set; - import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.EcoreUtil2; import org.eclipse.xtext.resource.IResourceDescription; import org.eclipse.xtext.scoping.impl.ImportUriGlobalScopeProvider; import org.eclipse.xtext.util.IResourceScopeCache; - -import org.lflang.lf.LfPackage; import org.lflang.LFResourceDescriptionStrategy; +import org.lflang.lf.LfPackage; /** - * Global scope provider that limits access to only those files that were - * explicitly imported. - *

- * Adapted from from Xtext manual, Chapter 8.7. + * Global scope provider that limits access to only those files that were explicitly imported. + * + *

Adapted from from Xtext manual, Chapter 8.7. * * @author Marten Lohstroh - * @see xtext doc + * @see xtext + * doc */ public class LFGlobalScopeProvider extends ImportUriGlobalScopeProvider { - /** - * Splitter used to process user-data annotations of Model nodes. - */ - static final Splitter SPLITTER = Splitter.on(LFResourceDescriptionStrategy.DELIMITER); - - static final String IMPORTED_URIS = "IMPORTED_URIS"; - - static final String IMPORTED_RESOURCES = "IMPORTED_RESOURCES"; - - @Inject - private IResourceDescription.Manager descriptionManager; - - @Inject - private IResourceScopeCache cache; - - /** - * Return the set of URI objects pointing to the resources that must be - * included for compilation. - */ - @Override - protected LinkedHashSet getImportedUris(Resource resource) { - return cache.get(IMPORTED_URIS, resource, - new Provider>() { - /** - * Collect unique URIs in case the cache is not populated yet. - */ - @Override - public LinkedHashSet get() { - var uniqueImportURIs = new LinkedHashSet(5); - collectImportUris(resource, uniqueImportURIs); - uniqueImportURIs.removeIf(uri -> !EcoreUtil2.isValidUri(resource, uri)); - return uniqueImportURIs; - } - - /** - * Helper method to recursively collect unique URIs. - */ - void collectImportUris(Resource resource, LinkedHashSet uniqueImportURIs) { - for (var imported : getImportedResources(resource, uniqueImportURIs)) { - collectImportUris(imported, uniqueImportURIs); - } - } - }); - } - - /** - * Return the resources imported by the given resource. - */ - public Set getImportedResources(Resource resource) { - return cache.get(IMPORTED_RESOURCES, resource, () -> getImportedResources(resource, null)); - } - - /** - * Resolve a resource identifier. - * - * @param uriStr resource identifier to resolve. - * @param resource resource to (initially) resolve it relative to. - */ - protected URI resolve(String uriStr, Resource resource) { - var uriObj = URI.createURI(uriStr); - if (uriObj != null && "lf".equalsIgnoreCase(uriObj.fileExtension())) { - // FIXME: If this doesn't work, try other things: - // (1) Look for a .project file up the file structure and try to - // resolve relative to the directory in which it is found. - // (2) Look for package description files try to resolve relative - // to the paths it includes. - // FIXME: potentially use a cache here to speed things up. - // See OnChangeEvictingCache - return uriObj.resolve(resource.getURI()); - } - return null; + /** Splitter used to process user-data annotations of Model nodes. */ + static final Splitter SPLITTER = Splitter.on(LFResourceDescriptionStrategy.DELIMITER); + + static final String IMPORTED_URIS = "IMPORTED_URIS"; + + static final String IMPORTED_RESOURCES = "IMPORTED_RESOURCES"; + + @Inject private IResourceDescription.Manager descriptionManager; + + @Inject private IResourceScopeCache cache; + + /** + * Return the set of URI objects pointing to the resources that must be included for compilation. + */ + @Override + protected LinkedHashSet getImportedUris(Resource resource) { + return cache.get( + IMPORTED_URIS, + resource, + new Provider>() { + /** Collect unique URIs in case the cache is not populated yet. */ + @Override + public LinkedHashSet get() { + var uniqueImportURIs = new LinkedHashSet(5); + collectImportUris(resource, uniqueImportURIs); + uniqueImportURIs.removeIf(uri -> !EcoreUtil2.isValidUri(resource, uri)); + return uniqueImportURIs; + } + + /** Helper method to recursively collect unique URIs. */ + void collectImportUris(Resource resource, LinkedHashSet uniqueImportURIs) { + for (var imported : getImportedResources(resource, uniqueImportURIs)) { + collectImportUris(imported, uniqueImportURIs); + } + } + }); + } + + /** Return the resources imported by the given resource. */ + public Set getImportedResources(Resource resource) { + return cache.get(IMPORTED_RESOURCES, resource, () -> getImportedResources(resource, null)); + } + + /** + * Resolve a resource identifier. + * + * @param uriStr resource identifier to resolve. + * @param resource resource to (initially) resolve it relative to. + */ + protected URI resolve(String uriStr, Resource resource) { + var uriObj = URI.createURI(uriStr); + if (uriObj != null && "lf".equalsIgnoreCase(uriObj.fileExtension())) { + // FIXME: If this doesn't work, try other things: + // (1) Look for a .project file up the file structure and try to + // resolve relative to the directory in which it is found. + // (2) Look for package description files try to resolve relative + // to the paths it includes. + // FIXME: potentially use a cache here to speed things up. + // See OnChangeEvictingCache + return uriObj.resolve(resource.getURI()); } - - /** - * Return the resources imported by a given resource, excluding those - * already discovered and therefore are present in the given set of - * import URIs. - * - * @param resource The resource to analyze. - * @param uniqueImportURIs The set of discovered import URIs - */ - protected Set getImportedResources(Resource resource, LinkedHashSet uniqueImportURIs) { - var resourceDescription = descriptionManager.getResourceDescription(resource); - var models = resourceDescription.getExportedObjectsByType(LfPackage.Literals.MODEL); - var resources = new LinkedHashSet(); - for (var model : models) { - var userData = model.getUserData(LFResourceDescriptionStrategy.INCLUDES); - if (userData != null) { - for (String uri : SPLITTER.split(userData)) {// Attempt to resolve the URI - var includedUri = this.resolve(uri, resource); - if (includedUri != null) { - try { - if (uniqueImportURIs == null || uniqueImportURIs.add(includedUri)) { - resources.add(resource.getResourceSet().getResource(includedUri, true)); - } - } catch (RuntimeException e) { - System.err.println("Unable to import " + includedUri + ": " + e.getMessage()); - } - } - - } + return null; + } + + /** + * Return the resources imported by a given resource, excluding those already discovered and + * therefore are present in the given set of import URIs. + * + * @param resource The resource to analyze. + * @param uniqueImportURIs The set of discovered import URIs + */ + protected Set getImportedResources( + Resource resource, LinkedHashSet uniqueImportURIs) { + var resourceDescription = descriptionManager.getResourceDescription(resource); + var models = resourceDescription.getExportedObjectsByType(LfPackage.Literals.MODEL); + var resources = new LinkedHashSet(); + for (var model : models) { + var userData = model.getUserData(LFResourceDescriptionStrategy.INCLUDES); + if (userData != null) { + for (String uri : SPLITTER.split(userData)) { // Attempt to resolve the URI + var includedUri = this.resolve(uri, resource); + if (includedUri != null) { + try { + if (uniqueImportURIs == null || uniqueImportURIs.add(includedUri)) { + resources.add(resource.getResourceSet().getResource(includedUri, true)); + } + } catch (RuntimeException e) { + System.err.println("Unable to import " + includedUri + ": " + e.getMessage()); } + } } - return resources; + } } + return resources; + } } diff --git a/org.lflang/src/org/lflang/scoping/LFScopeProvider.java b/org.lflang/src/org/lflang/scoping/LFScopeProvider.java index 9967ddaabe..6a303b0eaf 100644 --- a/org.lflang/src/org/lflang/scoping/LFScopeProvider.java +++ b/org.lflang/src/org/lflang/scoping/LFScopeProvider.java @@ -3,13 +3,10 @@ */ package org.lflang.scoping; - /** * This class contains custom scoping description. - * - * See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#scoping - * on how and when to use it. + * + *

See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#scoping on how and + * when to use it. */ -public class LFScopeProvider extends LFScopeProviderImpl { - -} +public class LFScopeProvider extends LFScopeProviderImpl {} diff --git a/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java b/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java index 0e521e8cc4..c5cbea4e7e 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,16 +29,13 @@ import static org.lflang.ast.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; @@ -63,220 +60,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); - } - 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/util/ArduinoUtil.java b/org.lflang/src/org/lflang/util/ArduinoUtil.java index 85eac8ae8f..af741da7f2 100644 --- a/org.lflang/src/org/lflang/util/ArduinoUtil.java +++ b/org.lflang/src/org/lflang/util/ArduinoUtil.java @@ -5,123 +5,153 @@ import java.io.FileWriter; import java.io.IOException; import java.util.List; - +import org.eclipse.xtext.xbase.lib.Exceptions; import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.TargetConfig; import org.lflang.generator.GeneratorCommandFactory; import org.lflang.generator.LFGeneratorContext; -import org.eclipse.xtext.xbase.lib.Exceptions; - /** * Utilities for Building using Arduino CLI. * - * We take in a Generator Context, Command Factory, and Error Reporter and - * make subsequent calls to arduino-cli given a FileConfig and TargetConfig. - * - * This should be used immediately after CodeGen to compile if the user provides - * a board type within their LF file. If the user also provides a port with flash enabled, - * we will also attempt to upload the compiled sketch directly to the board. + *

We take in a Generator Context, Command Factory, and Error Reporter and make subsequent calls + * to arduino-cli given a FileConfig and TargetConfig. + * + *

This should be used immediately after CodeGen to compile if the user provides a board type + * within their LF file. If the user also provides a port with flash enabled, we will also attempt + * to upload the compiled sketch directly to the board. */ public class ArduinoUtil { - private LFGeneratorContext context; - private GeneratorCommandFactory commandFactory; - private ErrorReporter errorReporter; + private LFGeneratorContext context; + private GeneratorCommandFactory commandFactory; + private ErrorReporter errorReporter; - public ArduinoUtil (LFGeneratorContext context, GeneratorCommandFactory commandFactory, ErrorReporter errorReporter) { - this.context = context; - this.commandFactory = commandFactory; - this.errorReporter = errorReporter; - } + public ArduinoUtil( + LFGeneratorContext context, + GeneratorCommandFactory commandFactory, + ErrorReporter errorReporter) { + this.context = context; + this.commandFactory = commandFactory; + this.errorReporter = errorReporter; + } - /** - * Return true if arduino-cli exists, false otherwise. - */ - private boolean checkArduinoCLIExists() { - LFCommand checkCommand = LFCommand.get("arduino-cli", List.of("version")); - return checkCommand != null && checkCommand.run() == 0; - } + /** Return true if arduino-cli exists, false otherwise. */ + private boolean checkArduinoCLIExists() { + LFCommand checkCommand = LFCommand.get("arduino-cli", List.of("version")); + return checkCommand != null && checkCommand.run() == 0; + } - /** - * Generate an LF style command for Arduino compilation based on FQBN - * @param fileConfig - * @param targetConfig - * @return LFCommand to compile an Arduino program given an FQBN. - * @throws IOException - */ - private LFCommand arduinoCompileCommand(FileConfig fileConfig, TargetConfig targetConfig) throws IOException { - if (!checkArduinoCLIExists()) { - throw new IOException("Must have arduino-cli installed to auto-compile."); - } else { - var srcGenPath = fileConfig.getSrcGenPath(); - try { - // Write to a temporary file to execute since ProcessBuilder does not like spaces and double quotes in its arguments. - File testScript = File.createTempFile("arduino", null); - testScript.deleteOnExit(); - if (!testScript.setExecutable(true)) { - throw new IOException("Failed to make compile script executable"); - } - var fileWriter = new FileWriter(testScript.getAbsoluteFile(), true); - BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); - String board = targetConfig.platformOptions.board != null ? targetConfig.platformOptions.board : "arduino:avr:leonardo"; - String isThreaded = targetConfig.platformOptions.board.contains("mbed") ? "-DLF_THREADED" : "-DLF_UNTHREADED"; - bufferedWriter.write("arduino-cli compile -b " + board + " --build-property " + - "compiler.c.extra_flags=\"" + isThreaded + " -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10\" " + - "--build-property compiler.cpp.extra_flags=\"" + isThreaded + " -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10\" " - + srcGenPath.toString()); - bufferedWriter.close(); - return commandFactory.createCommand( - testScript.getAbsolutePath(), List.of(), null); - } catch (IOException e) { - e.printStackTrace(); - throw new IOException(e); - } + /** + * Generate an LF style command for Arduino compilation based on FQBN + * + * @param fileConfig + * @param targetConfig + * @return LFCommand to compile an Arduino program given an FQBN. + * @throws IOException + */ + private LFCommand arduinoCompileCommand(FileConfig fileConfig, TargetConfig targetConfig) + throws IOException { + if (!checkArduinoCLIExists()) { + throw new IOException("Must have arduino-cli installed to auto-compile."); + } else { + var srcGenPath = fileConfig.getSrcGenPath(); + try { + // Write to a temporary file to execute since ProcessBuilder does not like spaces and double + // quotes in its arguments. + File testScript = File.createTempFile("arduino", null); + testScript.deleteOnExit(); + if (!testScript.setExecutable(true)) { + throw new IOException("Failed to make compile script executable"); } + var fileWriter = new FileWriter(testScript.getAbsoluteFile(), true); + BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); + String board = + targetConfig.platformOptions.board != null + ? targetConfig.platformOptions.board + : "arduino:avr:leonardo"; + String isThreaded = + targetConfig.platformOptions.board.contains("mbed") + ? "-DLF_THREADED" + : "-DLF_UNTHREADED"; + bufferedWriter.write( + "arduino-cli compile -b " + + board + + " --build-property " + + "compiler.c.extra_flags=\"" + + isThreaded + + " -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10" + + " -DINITIAL_REACT_QUEUE_SIZE=10\" --build-property compiler.cpp.extra_flags=\"" + + isThreaded + + " -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10" + + " -DINITIAL_REACT_QUEUE_SIZE=10\" " + + srcGenPath.toString()); + bufferedWriter.close(); + return commandFactory.createCommand(testScript.getAbsolutePath(), List.of(), null); + } catch (IOException e) { + e.printStackTrace(); + throw new IOException(e); + } } + } - /** - * Compiles (and possibly auto-flashes) an Arduino program once code generation is completed. - * @param fileConfig - * @param targetConfig - */ - public void buildArduino(FileConfig fileConfig, TargetConfig targetConfig) { - System.out.println("Retrieving Arduino Compile Script"); - try { - LFCommand command = arduinoCompileCommand(fileConfig, targetConfig); // Use ProcessBuilder for finer control. - int retCode = 0; - retCode = command.run(context.getCancelIndicator()); - if (retCode != 0 && context.getMode() == LFGeneratorContext.Mode.STANDALONE) { - errorReporter.reportError("arduino-cli failed with error code " + retCode); - throw new IOException("arduino-cli failure"); - } - } catch (IOException e){ - Exceptions.sneakyThrow(e); + /** + * Compiles (and possibly auto-flashes) an Arduino program once code generation is completed. + * + * @param fileConfig + * @param targetConfig + */ + public void buildArduino(FileConfig fileConfig, TargetConfig targetConfig) { + System.out.println("Retrieving Arduino Compile Script"); + try { + LFCommand command = + arduinoCompileCommand(fileConfig, targetConfig); // Use ProcessBuilder for finer control. + int retCode = 0; + retCode = command.run(context.getCancelIndicator()); + if (retCode != 0 && context.getMode() == LFGeneratorContext.Mode.STANDALONE) { + errorReporter.reportError("arduino-cli failed with error code " + retCode); + throw new IOException("arduino-cli failure"); + } + } catch (IOException e) { + Exceptions.sneakyThrow(e); + } + System.out.println( + "SUCCESS: Compiling generated code for " + fileConfig.name + " finished with no errors."); + if (targetConfig.platformOptions.flash) { + if (targetConfig.platformOptions.port != null) { + System.out.println("Invoking flash command for Arduino"); + LFCommand flash = + commandFactory.createCommand( + "arduino-cli", + List.of( + "upload", + "-b", + targetConfig.platformOptions.board, + "-p", + targetConfig.platformOptions.port), + fileConfig.getSrcGenPath()); + if (flash == null) { + errorReporter.reportError("Could not create arduino-cli flash command."); } - System.out.println("SUCCESS: Compiling generated code for " + fileConfig.name + " finished with no errors."); - if (targetConfig.platformOptions.flash) { - if (targetConfig.platformOptions.port != null) { - System.out.println("Invoking flash command for Arduino"); - LFCommand flash = commandFactory.createCommand( - "arduino-cli", List.of("upload", "-b", targetConfig.platformOptions.board, "-p", targetConfig.platformOptions.port), fileConfig.getSrcGenPath()); - if (flash == null) { - errorReporter.reportError( - "Could not create arduino-cli flash command." - ); - } - int flashRet = flash.run(); - if (flashRet != 0) { - errorReporter.reportError("arduino-cli flash command failed with error code " + flashRet); - } else { - System.out.println("SUCCESS: Flashed board using arduino-cli"); - } - } else { - errorReporter.reportError("Need to provide a port on which to automatically flash."); - } + int flashRet = flash.run(); + if (flashRet != 0) { + errorReporter.reportError("arduino-cli flash command failed with error code " + flashRet); } else { - System.out.println("********"); - System.out.println("To flash 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 upload -b -p \n"); + System.out.println("SUCCESS: Flashed board using arduino-cli"); } + } else { + errorReporter.reportError("Need to provide a port on which to automatically flash."); + } + } else { + System.out.println("********"); + System.out.println( + "To flash 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 upload -b -p \n"); } + } } diff --git a/org.lflang/src/org/lflang/util/Averager.java b/org.lflang/src/org/lflang/util/Averager.java index 81349c9db1..f156eafb22 100644 --- a/org.lflang/src/org/lflang/util/Averager.java +++ b/org.lflang/src/org/lflang/util/Averager.java @@ -1,27 +1,26 @@ 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; + 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]; - } + /** 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); - } + /** + * 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/org.lflang/src/org/lflang/util/CollectionUtil.java b/org.lflang/src/org/lflang/util/CollectionUtil.java index 221cbc68ec..8510856c78 100644 --- a/org.lflang/src/org/lflang/util/CollectionUtil.java +++ b/org.lflang/src/org/lflang/util/CollectionUtil.java @@ -14,176 +14,159 @@ /** * Utilities to manipulate collections. * - *

Most of these methods are using specialized collection - * implementations (possibly unmodifiable) for small collection - * sizes. No guarantee is made on the mutability of the collections - * returned from these functions, meaning, callers should always - * assume they are unmodifiable. Functions that take a collection - * parameter as input to produce a new one with a transformation - * require the input collection to have been obtained from one of - * the utility functions of this class in the first place. + *

Most of these methods are using specialized collection implementations (possibly unmodifiable) + * for small collection sizes. No guarantee is made on the mutability of the collections returned + * from these functions, meaning, callers should always assume they are unmodifiable. Functions that + * take a collection parameter as input to produce a new one with a transformation require the input + * collection to have been obtained from one of the utility functions of this class in the first + * place. */ public class CollectionUtil { - /** - * Returns a set which contains the elements of the given - * set plus the given element. The returned set should be - * considered unmodifiable. - * - * @param set initial set, nullable - * @param t new element - * @param Type of elements - * @return A new set, or the same - */ - public static Set plus(Set set, T t) { - if (set == null || set.isEmpty()) { - return Set.of(t); - } else if (set.size() == 1) { - if (set.contains(t)) { - return set; - } - // make mutable - set = new LinkedHashSet<>(set); - } // else it's already mutable. - - set.add(t); + /** + * Returns a set which contains the elements of the given set plus the given element. The returned + * set should be considered unmodifiable. + * + * @param set initial set, nullable + * @param t new element + * @param Type of elements + * @return A new set, or the same + */ + public static Set plus(Set set, T t) { + if (set == null || set.isEmpty()) { + return Set.of(t); + } else if (set.size() == 1) { + if (set.contains(t)) { return set; + } + // make mutable + set = new LinkedHashSet<>(set); + } // else it's already mutable. + + set.add(t); + return set; + } + + public static Map plus(Map map, K k, V v) { + if (map == null || map.isEmpty()) { + return Collections.singletonMap(k, v); + } else if (map.size() == 1) { + Entry e = map.entrySet().iterator().next(); + if (e.getKey().equals(k)) { + return Map.of(k, v); + } + // make mutable + map = new LinkedHashMap<>(map); + } // else it's already mutable. + + map.put(k, v); + return map; + } + + /** + * Remove the given value from all the sets that are values in the given map. Use this if the + * values of the map (the sets) were build with {@link #plus(Set, Object)}. + * + *

In {@link org.lflang.graph.DirectedGraph}, this is used to properly remove nodes from a + * graph. There, we use maps to represent edges, where a value in a map is a set of nodes adjacent + * to the key for that value. Hence, when a node is removed, it needs to be removed not just as a + * key, but it also needs to be removed from the neighbors sets of any other keys that may contain + * it. + * + * @param map A modifiable map + * @param valueToRemove Value to remove + */ + public static void removeFromValues(Map> map, V valueToRemove) { + mapValuesInPlace(map, set -> minus(set, valueToRemove)); + } + + /** + * Apply a transform to the values of the map. If the mapping function returns null, the entry is + * removed. + */ + private static void mapValuesInPlace(Map map, Function mapper) { + Iterator> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + Entry e = iterator.next(); + V existing = e.getValue(); + V mapped = mapper.apply(existing); + if (mapped == null) { + iterator.remove(); + } else if (mapped != existing) { + e.setValue(mapped); + } } - - - public static Map plus(Map map, K k, V v) { - if (map == null || map.isEmpty()) { - return Collections.singletonMap(k, v); - } else if (map.size() == 1) { - Entry e = map.entrySet().iterator().next(); - if (e.getKey().equals(k)) { - return Map.of(k, v); - } - // make mutable - map = new LinkedHashMap<>(map); - } // else it's already mutable. - - map.put(k, v); - return map; + } + + /** + * Returns a set that contains the elements of the given set minus one element. An empty set is + * considered empty. + */ + public static Set minus(Set set, T eltToRemove) { + if (set instanceof LinkedHashSet) { + set.remove(eltToRemove); + return set; } - - /** - * Remove the given value from all the sets that are values - * in the given map. Use this if the values of the map (the - * sets) were build with {@link #plus(Set, Object)}. - *

- * In {@link org.lflang.graph.DirectedGraph}, this is used - * to properly remove nodes from a graph. There, we use - * maps to represent edges, where a value in a map is a - * set of nodes adjacent to the key for that value. - * Hence, when a node is removed, it needs to be removed - * not just as a key, but it also needs to be removed - * from the neighbors sets of any other keys that may contain it. - * - * @param map A modifiable map - * @param valueToRemove Value to remove - */ - public static void removeFromValues(Map> map, V valueToRemove) { - mapValuesInPlace(map, set -> minus(set, valueToRemove)); + if (set == null || set.isEmpty()) { + return Collections.emptySet(); + } else if (set.size() == 1) { + return set.contains(eltToRemove) ? Collections.emptySet() : set; } - - - /** - * Apply a transform to the values of the map. If the mapping - * function returns null, the entry is removed. - */ - private static void mapValuesInPlace(Map map, Function mapper) { - Iterator> iterator = map.entrySet().iterator(); - while (iterator.hasNext()) { - Entry e = iterator.next(); - V existing = e.getValue(); - V mapped = mapper.apply(existing); - if (mapped == null) { - iterator.remove(); - } else if (mapped != existing) { - e.setValue(mapped); - } - } + throw new AssertionError("should be unreachable"); + } + + /** + * Returns a map that is identical to the original map, except the value for key {@code k} is + * transformed using the given function. The transformation function takes the key and current + * value (null if the key is not present) as inputs, and returns the new value to associate to the + * key (null if the mapping should be removed). + * + * @see Map#compute(Object, BiFunction) + */ + public static Map compute(Map map, K k, BiFunction computation) { + if (map == null || map.isEmpty()) { + return Collections.singletonMap(k, computation.apply(k, null)); + } else if (map.size() == 1) { + Entry e = map.entrySet().iterator().next(); + if (e.getKey().equals(k)) { + return Collections.singletonMap(k, computation.apply(k, e.getValue())); + } + // make mutable + map = new LinkedHashMap<>(map); + } // else it's already mutable. + + map.compute(k, computation); + return map; + } + + /** + * Returns a copy of the set. The returned set should be considered unmodifiable. + * + * @param set initial set, nullable + * @param Type of elements + * @return A new set, or the same + */ + public static Set copy(Set set) { + if (set == null || set.size() <= 1) { + return set; // it's unmodifiable + } else { + return new LinkedHashSet<>(set); } - - - /** - * Returns a set that contains the elements of the given - * set minus one element. An empty set is considered empty. - */ - public static Set minus(Set set, T eltToRemove) { - if (set instanceof LinkedHashSet) { - set.remove(eltToRemove); - return set; - } - - if (set == null || set.isEmpty()) { - return Collections.emptySet(); - } else if (set.size() == 1) { - return set.contains(eltToRemove) ? Collections.emptySet() : set; - } - throw new AssertionError("should be unreachable"); + } + + /** + * Returns an immutable Set that contains all argument values. Duplicate elements are removed + * without error (contrary to {@link Set#of()} and friends). + */ + @SafeVarargs + public static Set immutableSetOf(T first, T... rest) { + if (rest.length == 0) { + return Set.of(first); } - - - /** - * Returns a map that is identical to the original map, - * except the value for key {@code k} is transformed using - * the given function. The transformation function takes the - * key and current value (null if the key is not present) as inputs, - * and returns the new value to associate to the key (null if the mapping should be removed). - * - * @see Map#compute(Object, BiFunction) - */ - public static Map compute(Map map, K k, BiFunction computation) { - if (map == null || map.isEmpty()) { - return Collections.singletonMap(k, computation.apply(k, null)); - } else if (map.size() == 1) { - Entry e = map.entrySet().iterator().next(); - if (e.getKey().equals(k)) { - return Collections.singletonMap(k, computation.apply(k, e.getValue())); - } - // make mutable - map = new LinkedHashMap<>(map); - } // else it's already mutable. - - map.compute(k, computation); - return map; - } - - - /** - * Returns a copy of the set. The returned set should be - * considered unmodifiable. - * - * @param set initial set, nullable - * @param Type of elements - * @return A new set, or the same - */ - public static Set copy(Set set) { - if (set == null || set.size() <= 1) { - return set; // it's unmodifiable - } else { - return new LinkedHashSet<>(set); - } - } - - - /** - * Returns an immutable Set that contains all argument values. - * Duplicate elements are removed without error (contrary - * to {@link Set#of()} and friends). - */ - @SafeVarargs - public static Set immutableSetOf(T first, T... rest) { - if (rest.length == 0) { - return Set.of(first); - } - Set result = new LinkedHashSet<>(); - result.add(first); - result.addAll(Arrays.asList(rest)); - return result; - } - + Set result = new LinkedHashSet<>(); + result.add(first); + result.addAll(Arrays.asList(rest)); + return result; + } } diff --git a/org.lflang/src/org/lflang/util/FileUtil.java b/org.lflang/src/org/lflang/util/FileUtil.java index eb2550127e..e46a30d501 100644 --- a/org.lflang/src/org/lflang/util/FileUtil.java +++ b/org.lflang/src/org/lflang/util/FileUtil.java @@ -23,9 +23,7 @@ import java.util.jar.JarFile; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; import java.util.stream.Stream; - import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspaceRoot; @@ -35,890 +33,906 @@ import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.util.RuntimeIOException; - import org.lflang.ErrorReporter; import org.lflang.FileConfig; public class FileUtil { - /** - * Return the name of the file excluding its file extension. - * @param file A Path object - * @return The name of the file excluding its file extension. - */ - public static String nameWithoutExtension(Path file) { - String name = file.getFileName().toString(); - int idx = name.lastIndexOf('.'); - return idx < 0 ? name : name.substring(0, idx); - } - - /** - * Return the name of the file associated with the given resource, - * excluding its file extension. - * @param r Any {@code Resource}. - * @return The name of the file associated with the given resource, - * excluding its file extension. - * @throws IOException If the resource has an invalid URI. - */ - public static String nameWithoutExtension(Resource r) throws IOException { - return nameWithoutExtension(toPath(r)); - } - - /** - * Return a java.nio.Path object corresponding to the given URI. - * @throws IOException If the given URI is invalid. - */ - public static Path toPath(URI uri) throws IOException { - return Paths.get(toIPath(uri).toFile().getAbsolutePath()); - } - - /** - * Return a java.nio.Path object corresponding to the given Resource. - * @throws IOException If the given resource has an invalid URI. - */ - public static Path toPath(Resource resource) throws IOException { - return toPath(resource.getURI()); - } - - /** - * Return an org.eclipse.core.runtime.Path object corresponding to the - * given URI. - * @throws IOException If the given URI is invalid. - */ - public static IPath toIPath(URI uri) throws IOException { - if (uri.isPlatform()) { - IPath path = new org.eclipse.core.runtime.Path(uri.toPlatformString(true)); - if (path.segmentCount() == 1) { - return ResourcesPlugin.getWorkspace().getRoot().getProject(path.lastSegment()).getLocation(); - } else { - return ResourcesPlugin.getWorkspace().getRoot().getFile(path).getLocation(); - } - } else if (uri.isFile()) { - return new org.eclipse.core.runtime.Path(uri.toFileString()); - } else { - throw new IOException("Unrecognized file protocol in URI " + uri); - } - } - - /** - * Convert a given path to a unix-style string. - * - * This ensures that '/' is used instead of '\' as file separator. - */ - public static String toUnixString(Path path) { - return path.toString().replace('\\', '/'); - } - - /** - * Parse the string as file location and return it as URI. - * Supports URIs, plain file paths, and paths relative to a model. - * - * @param path the file location as string. - * @param resource the model resource this file should be resolved relatively. May be null. - * @return the (Java) URI or null if no file can be located. - */ - public static java.net.URI locateFile(String path, Resource resource) { - // Check if path is URL + /** + * Return the name of the file excluding its file extension. + * + * @param file A Path object + * @return The name of the file excluding its file extension. + */ + public static String nameWithoutExtension(Path file) { + String name = file.getFileName().toString(); + int idx = name.lastIndexOf('.'); + return idx < 0 ? name : name.substring(0, idx); + } + + /** + * Return the name of the file associated with the given resource, excluding its file extension. + * + * @param r Any {@code Resource}. + * @return The name of the file associated with the given resource, excluding its file extension. + * @throws IOException If the resource has an invalid URI. + */ + public static String nameWithoutExtension(Resource r) throws IOException { + return nameWithoutExtension(toPath(r)); + } + + /** + * Return a java.nio.Path object corresponding to the given URI. + * + * @throws IOException If the given URI is invalid. + */ + public static Path toPath(URI uri) throws IOException { + return Paths.get(toIPath(uri).toFile().getAbsolutePath()); + } + + /** + * Return a java.nio.Path object corresponding to the given Resource. + * + * @throws IOException If the given resource has an invalid URI. + */ + public static Path toPath(Resource resource) throws IOException { + return toPath(resource.getURI()); + } + + /** + * Return an org.eclipse.core.runtime.Path object corresponding to the given URI. + * + * @throws IOException If the given URI is invalid. + */ + public static IPath toIPath(URI uri) throws IOException { + if (uri.isPlatform()) { + IPath path = new org.eclipse.core.runtime.Path(uri.toPlatformString(true)); + if (path.segmentCount() == 1) { + return ResourcesPlugin.getWorkspace() + .getRoot() + .getProject(path.lastSegment()) + .getLocation(); + } else { + return ResourcesPlugin.getWorkspace().getRoot().getFile(path).getLocation(); + } + } else if (uri.isFile()) { + return new org.eclipse.core.runtime.Path(uri.toFileString()); + } else { + throw new IOException("Unrecognized file protocol in URI " + uri); + } + } + + /** + * Convert a given path to a unix-style string. + * + *

This ensures that '/' is used instead of '\' as file separator. + */ + public static String toUnixString(Path path) { + return path.toString().replace('\\', '/'); + } + + /** + * Parse the string as file location and return it as URI. Supports URIs, plain file paths, and + * paths relative to a model. + * + * @param path the file location as string. + * @param resource the model resource this file should be resolved relatively. May be null. + * @return the (Java) URI or null if no file can be located. + */ + public static java.net.URI locateFile(String path, Resource resource) { + // Check if path is URL + try { + var uri = new java.net.URI(path); + if (uri.getScheme() != null) { // check if path was meant to be a URI + return uri; + } + } catch (Exception e) { + // nothing + } + // Check if path exists as it is + File file = new File(path); + if (file.exists()) { + try { + return file.toURI(); + } catch (Exception e) { + // nothing + } + } + // Check if path is relative to LF file + if (resource != null) { + URI eURI = resource.getURI(); + if (eURI != null) { + java.net.URI sourceURI = null; try { - var uri = new java.net.URI(path); - if(uri.getScheme() != null) { // check if path was meant to be a URI - return uri; - } + if (eURI.isFile()) { + sourceURI = new java.net.URI(eURI.toString()); + sourceURI = + new java.net.URI( + sourceURI.getScheme(), + null, + sourceURI.getPath().substring(0, sourceURI.getPath().lastIndexOf("/")), + null); + } else if (eURI.isPlatformResource()) { + IResource iFile = + ResourcesPlugin.getWorkspace().getRoot().findMember(eURI.toPlatformString(true)); + sourceURI = + iFile != null ? iFile.getRawLocation().toFile().getParentFile().toURI() : null; + } + if (sourceURI != null) { + return sourceURI.resolve(path); + } } catch (Exception e) { - // nothing - } - // Check if path exists as it is - File file = new File(path); - if (file.exists()) { - try { - return file.toURI(); - } catch (Exception e) { - // nothing - } - } - // Check if path is relative to LF file - if (resource != null) { - URI eURI = resource.getURI(); - if (eURI != null) { - java.net.URI sourceURI = null; - try { - if (eURI.isFile()) { - sourceURI = new java.net.URI(eURI.toString()); - sourceURI = new java.net.URI(sourceURI.getScheme(), null, - sourceURI.getPath().substring(0, sourceURI.getPath().lastIndexOf("/")), null); - } else if (eURI.isPlatformResource()) { - IResource iFile = ResourcesPlugin.getWorkspace().getRoot().findMember(eURI.toPlatformString(true)); - sourceURI = iFile != null ? iFile.getRawLocation().toFile().getParentFile().toURI() : null; - } - if (sourceURI != null) { - return sourceURI.resolve(path); - } - } catch (Exception e) { - // nothing - } + // nothing + } + } + } + // fail + return null; + } + + /** + * Recursively copy the contents of the given source directory into the given destination + * directory. Existing files of the destination may be overwritten. + * + * @param srcDir The source directory path. + * @param dstDir The destination directory path. + * @param skipIfUnchanged If true, don't overwrite anything in the destination if its content + * would not be changed. + * @throws IOException If the operation fails. + */ + public static void copyDirectoryContents( + final Path srcDir, final Path dstDir, final boolean skipIfUnchanged) throws IOException { + try (Stream stream = Files.walk(srcDir)) { + stream.forEach( + source -> { + // Handling checked exceptions in lambda expressions is + // hard. See + // https://www.baeldung.com/java-lambda-exceptions#handling-checked-exceptions. + // An alternative would be to create a custom Consumer interface and use that + // here. + if (Files.isRegularFile(source)) { // do not copy directories + try { + Path target = dstDir.resolve(srcDir.relativize(source)); + Files.createDirectories(target.getParent()); + copyFile(source, target, skipIfUnchanged); + } catch (IOException e) { + throw new RuntimeIOException(e); + } catch (Exception e) { + throw new RuntimeException(e); + } } - } - // fail + }); + } + } + + /** + * Copy the given source directory into the given destination directory. For example, if the + * source directory is {@code foo/bar} and the destination is {@code baz}, then copies of the + * contents of {@code foo/bar} will be located in {@code baz/bar}. + * + * @param srcDir The source directory path. + * @param dstDir The destination directory path. + * @param skipIfUnchanged If true, don't overwrite anything in the destination if its content + * would not be changed. + * @throws IOException If the operation fails. + */ + public static void copyDirectory( + final Path srcDir, final Path dstDir, final boolean skipIfUnchanged) throws IOException { + copyDirectoryContents(srcDir, dstDir.resolve(srcDir.getFileName()), skipIfUnchanged); + } + + /** + * Recursively copy the contents of the given source directory into the given destination + * directory. Existing files of the destination may be overwritten. + * + * @param srcDir The directory to copy files from. + * @param dstDir The directory to copy files to. + * @throws IOException if copy fails. + */ + public static void copyDirectoryContents(final Path srcDir, final Path dstDir) + throws IOException { + copyDirectoryContents(srcDir, dstDir, false); + } + + /** + * Copy a given source file to a given destination file. + * + *

This also creates new directories on the path to {@code dstFile} that do not yet exist. + * + * @param srcFile The source file path. + * @param dstFile The destination file path. + * @param skipIfUnchanged If true, don't overwrite the destination file if its content would not + * be changed. + * @throws IOException If the operation fails. + */ + public static void copyFile(Path srcFile, Path dstFile, boolean skipIfUnchanged) + throws IOException { + BufferedInputStream stream = new BufferedInputStream(new FileInputStream(srcFile.toFile())); + try (stream) { + copyInputStream(stream, dstFile, skipIfUnchanged); + } + } + + /** + * Copy a given source file to a given destination file. + * + *

This also creates new directories for any directories on the path to {@code dstFile} that do + * not yet exist. + * + * @param srcFile The source file path. + * @param dstFile The destination file path. + * @throws IOException if copy fails. + */ + public static void copyFile(Path srcFile, Path dstFile) throws IOException { + copyFile(srcFile, dstFile, false); + } + + /** + * Find the given {@code file} in the package and return the path to the file that was found; null + * if it was not found. + * + * @param file The file to look for. + * @param dstDir The directory to copy it to. + * @param fileConfig The file configuration that specifies where look for the file. + * @return The path to the file that was found, or null if it was not found. + */ + public static Path findAndCopyFile(String file, Path dstDir, FileConfig fileConfig) { + var path = Paths.get(file); + var found = FileUtil.findInPackage(path, fileConfig); + if (found != null) { + try { + FileUtil.copyFile(found, dstDir.resolve(path.getFileName())); + return found; + } catch (IOException e) { return null; - } - - /** - * Recursively copy the contents of the given source directory into the given destination - * directory. Existing files of the destination may be overwritten. - * - * @param srcDir The source directory path. - * @param dstDir The destination directory path. - * @param skipIfUnchanged If true, don't overwrite anything in the destination if its content - * would not be changed. - * @throws IOException If the operation fails. - */ - public static void copyDirectoryContents(final Path srcDir, final Path dstDir, final boolean skipIfUnchanged) throws IOException { - try (Stream stream = Files.walk(srcDir)) { - stream.forEach(source -> { - // Handling checked exceptions in lambda expressions is - // hard. See - // https://www.baeldung.com/java-lambda-exceptions#handling-checked-exceptions. - // An alternative would be to create a custom Consumer interface and use that - // here. - if (Files.isRegularFile(source)) { // do not copy directories - try { - Path target = dstDir.resolve(srcDir.relativize(source)); - Files.createDirectories(target.getParent()); - copyFile(source, target, skipIfUnchanged); - } catch (IOException e) { - throw new RuntimeIOException(e); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - }); - } - } - - /** - * Copy the given source directory into the given destination directory. For example, if the - * source directory is {@code foo/bar} and the destination is {@code baz}, then copies of the - * contents of {@code foo/bar} will be located in {@code baz/bar}. - * @param srcDir The source directory path. - * @param dstDir The destination directory path. - * @param skipIfUnchanged If true, don't overwrite anything in the destination if its content - * would not be changed. - * @throws IOException If the operation fails. - */ - public static void copyDirectory( - final Path srcDir, final Path dstDir, final boolean skipIfUnchanged) throws IOException { - copyDirectoryContents(srcDir, dstDir.resolve(srcDir.getFileName()), skipIfUnchanged); - } - - /** - * Recursively copy the contents of the given source directory into the given destination - * directory. Existing files of the destination may be overwritten. - * - * @param srcDir The directory to copy files from. - * @param dstDir The directory to copy files to. - * @throws IOException if copy fails. - */ - public static void copyDirectoryContents(final Path srcDir, final Path dstDir) throws IOException { - copyDirectoryContents(srcDir, dstDir, false); - } - - /** - * Copy a given source file to a given destination file. - * - * This also creates new directories on the path to {@code dstFile} that do not yet exist. - * - * @param srcFile The source file path. - * @param dstFile The destination file path. - * @param skipIfUnchanged If true, don't overwrite the destination file if its content - * would not be changed. - * @throws IOException If the operation fails. - */ - public static void copyFile(Path srcFile, Path dstFile, boolean skipIfUnchanged) throws IOException { - BufferedInputStream stream = new BufferedInputStream(new FileInputStream(srcFile.toFile())); - try (stream) { - copyInputStream(stream, dstFile, skipIfUnchanged); - } - } - - /** - * Copy a given source file to a given destination file. - * - * This also creates new directories for any directories - * on the path to {@code dstFile} that do not yet exist. - * - * @param srcFile The source file path. - * @param dstFile The destination file path. - * @throws IOException if copy fails. - */ - public static void copyFile(Path srcFile, Path dstFile) throws IOException { - copyFile(srcFile, dstFile, false); - } - - /** - * Find the given {@code file} in the package and return the path to the file that was found; null - * if it was not found. - * - * @param file The file to look for. - * @param dstDir The directory to copy it to. - * @param fileConfig The file configuration that specifies where look for the file. - * @return The path to the file that was found, or null if it was not found. - */ - public static Path findAndCopyFile( - String file, - Path dstDir, - FileConfig fileConfig - ) { - var path = Paths.get(file); - var found = FileUtil.findInPackage(path, fileConfig); - if (found != null) { - try { - FileUtil.copyFile(found, dstDir.resolve(path.getFileName())); - return found; - } catch (IOException e) { - return null; - } - } else { - return null; - } - } - - /** - * Given a list of files or directories, attempt to find each entry based on the given generator - * context and copy it to the destination directory. Entries are searched for in the file system - * first, relative to the source file and relative to the package root. Entries that cannot be - * found in the file system are looked for on the class path. - *

- * If {@code contentsOnly} is true, then for each entry that is a directory, only its contents - * are copied, not the directory itself. - * For example, if the entry is a directory {@code foo/bar} and the destination is {@code baz}, - * then copies of the contents of {@code foo/bar} will be located directly in {@code baz}. - * If {@code contentsOnly} is false, then copies of the contents of {@code foo/bar} will be - * located in {@code baz/bar}. - * - * @param entries The files or directories to copy from. - * @param dstDir The location to copy the files to. - * @param fileConfig The file configuration that specifies where the find entries the given entries. - * @param errorReporter An error reporter to report problems. - */ - public static void copyFilesOrDirectories( - List entries, - Path dstDir, - FileConfig fileConfig, - ErrorReporter errorReporter, - boolean fileEntriesOnly - ) { - for (String fileOrDirectory : entries) { - var path = Paths.get(fileOrDirectory); - var found = FileUtil.findInPackage(path, fileConfig); - if (found != null) { - try { - if (fileEntriesOnly) { - FileUtil.copyFile(found, dstDir.resolve(path.getFileName())); - } else { - FileUtil.copyFromFileSystem(found, dstDir, false); - } - System.out.println("Copied '" + fileOrDirectory + "' from the file system."); - } catch (IOException e) { - errorReporter.reportError( - "Unable to copy '" + fileOrDirectory + "' from the file system. Reason: " + e.toString() - ); - } - } else { - try { - if (fileEntriesOnly) { - copyFileFromClassPath(fileOrDirectory, dstDir, false); - } else { - FileUtil.copyFromClassPath( - fileOrDirectory, - dstDir, - false, - false - ); - System.out.println("Copied '" + fileOrDirectory + "' from the class path."); - } - } catch(IOException e) { - errorReporter.reportError( - "Unable to copy '" + fileOrDirectory + "' from the class path. Reason: " + e.toString() - ); - } - } - } - } - - /** - * If the given {@code entry} is a file, then copy it into the destination. If the {@code entry} - * is a directory and {@code contentsOnly} is true, then copy its contents to the destination - * directory. If the {@code entry} is a directory and {@code contentsOnly} is true, then copy it - * including its contents to the destination directory. - * - * @param entry A file or directory to copy to the destination directory. - * @param dstDir A directory to copy the entry or its contents to. - * @param contentsOnly If true and {@code entry} is a directory, then copy its contents but not - * the directory itself. - * @throws IOException If the operation fails. - */ - public static void copyFromFileSystem(Path entry, Path dstDir, boolean contentsOnly) throws IOException { - if (Files.isDirectory(entry)) { - if (contentsOnly) { - copyDirectoryContents(entry, dstDir); - } else { - copyDirectory(entry, dstDir, false); - } - } else if (Files.isRegularFile(entry)) { - FileUtil.copyFile(entry, dstDir.resolve(entry.getFileName())); - } else { - throw new IllegalArgumentException("Source is neither a directory nor a regular file."); - } - } - /** - * Copy a given input stream to a destination file. - * - * This also creates new directories for any directories on the destination - * path that do not yet exist. - * - * @param source The source input stream. - * @param destination The destination file path. - * @param skipIfUnchanged If true, don't overwrite the destination file if its content would - * not be changed. - * @throws IOException If the operation fails. - */ - private static void copyInputStream(InputStream source, Path destination, boolean skipIfUnchanged) throws IOException { - // Read the stream once and keep a copy of all bytes. This is required as a stream cannot be read twice. - final var bytes = source.readAllBytes(); - final var parent = destination.getParent(); - if (Files.isRegularFile(destination)) { - if (skipIfUnchanged) { - if (Arrays.equals(bytes, Files.readAllBytes(destination))) { - // Abort if the file contents are the same. - return; - } - } else { - // Delete the file exists but the contents don't match. - Files.delete(destination); - } - } else if (Files.isDirectory(destination)) { - deleteDirectory(destination); - } else if (!Files.exists(parent)) { - Files.createDirectories(parent); - } - - Files.write(destination, bytes); - } - - /** - * Look up the given {@code entry} in the classpath. If it is found and is a file, copy it into - * the destination directory. If the entry is not found or not a file, throw an exception. - * - * @param entry A file copy to the destination directory. - * @param dstDir A directory to copy the entry to. - * @param skipIfUnchanged If true, don't overwrite the destination file if its content would - * not be changed. - * @throws IOException If the operation failed. - */ - public static void copyFileFromClassPath(final String entry, final Path dstDir, final boolean skipIfUnchanged) throws IOException { - final URL resource = FileConfig.class.getResource(entry); - - if (resource == null) { - throw new TargetResourceNotFoundException(entry); - } + } + } else { + return null; + } + } + + /** + * Given a list of files or directories, attempt to find each entry based on the given generator + * context and copy it to the destination directory. Entries are searched for in the file system + * first, relative to the source file and relative to the package root. Entries that cannot be + * found in the file system are looked for on the class path. + * + *

If {@code contentsOnly} is true, then for each entry that is a directory, only its contents + * are copied, not the directory itself. For example, if the entry is a directory {@code foo/bar} + * and the destination is {@code baz}, then copies of the contents of {@code foo/bar} will be + * located directly in {@code baz}. If {@code contentsOnly} is false, then copies of the contents + * of {@code foo/bar} will be located in {@code baz/bar}. + * + * @param entries The files or directories to copy from. + * @param dstDir The location to copy the files to. + * @param fileConfig The file configuration that specifies where the find entries the given + * entries. + * @param errorReporter An error reporter to report problems. + */ + public static void copyFilesOrDirectories( + List entries, + Path dstDir, + FileConfig fileConfig, + ErrorReporter errorReporter, + boolean fileEntriesOnly) { + for (String fileOrDirectory : entries) { + var path = Paths.get(fileOrDirectory); + var found = FileUtil.findInPackage(path, fileConfig); + if (found != null) { + try { + if (fileEntriesOnly) { + FileUtil.copyFile(found, dstDir.resolve(path.getFileName())); + } else { + FileUtil.copyFromFileSystem(found, dstDir, false); + } + System.out.println("Copied '" + fileOrDirectory + "' from the file system."); + } catch (IOException e) { + errorReporter.reportError( + "Unable to copy '" + + fileOrDirectory + + "' from the file system. Reason: " + + e.toString()); + } + } else { + try { + if (fileEntriesOnly) { + copyFileFromClassPath(fileOrDirectory, dstDir, false); + } else { + FileUtil.copyFromClassPath(fileOrDirectory, dstDir, false, false); + System.out.println("Copied '" + fileOrDirectory + "' from the class path."); + } + } catch (IOException e) { + errorReporter.reportError( + "Unable to copy '" + + fileOrDirectory + + "' from the class path. Reason: " + + e.toString()); + } + } + } + } + + /** + * If the given {@code entry} is a file, then copy it into the destination. If the {@code entry} + * is a directory and {@code contentsOnly} is true, then copy its contents to the destination + * directory. If the {@code entry} is a directory and {@code contentsOnly} is true, then copy it + * including its contents to the destination directory. + * + * @param entry A file or directory to copy to the destination directory. + * @param dstDir A directory to copy the entry or its contents to. + * @param contentsOnly If true and {@code entry} is a directory, then copy its contents but not + * the directory itself. + * @throws IOException If the operation fails. + */ + public static void copyFromFileSystem(Path entry, Path dstDir, boolean contentsOnly) + throws IOException { + if (Files.isDirectory(entry)) { + if (contentsOnly) { + copyDirectoryContents(entry, dstDir); + } else { + copyDirectory(entry, dstDir, false); + } + } else if (Files.isRegularFile(entry)) { + FileUtil.copyFile(entry, dstDir.resolve(entry.getFileName())); + } else { + throw new IllegalArgumentException("Source is neither a directory nor a regular file."); + } + } + /** + * Copy a given input stream to a destination file. + * + *

This also creates new directories for any directories on the destination path that do not + * yet exist. + * + * @param source The source input stream. + * @param destination The destination file path. + * @param skipIfUnchanged If true, don't overwrite the destination file if its content would not + * be changed. + * @throws IOException If the operation fails. + */ + private static void copyInputStream(InputStream source, Path destination, boolean skipIfUnchanged) + throws IOException { + // Read the stream once and keep a copy of all bytes. This is required as a stream cannot be + // read twice. + final var bytes = source.readAllBytes(); + final var parent = destination.getParent(); + if (Files.isRegularFile(destination)) { + if (skipIfUnchanged) { + if (Arrays.equals(bytes, Files.readAllBytes(destination))) { + // Abort if the file contents are the same. + return; + } + } else { + // Delete the file exists but the contents don't match. + Files.delete(destination); + } + } else if (Files.isDirectory(destination)) { + deleteDirectory(destination); + } else if (!Files.exists(parent)) { + Files.createDirectories(parent); + } + + Files.write(destination, bytes); + } + + /** + * Look up the given {@code entry} in the classpath. If it is found and is a file, copy it into + * the destination directory. If the entry is not found or not a file, throw an exception. + * + * @param entry A file copy to the destination directory. + * @param dstDir A directory to copy the entry to. + * @param skipIfUnchanged If true, don't overwrite the destination file if its content would not + * be changed. + * @throws IOException If the operation failed. + */ + public static void copyFileFromClassPath( + final String entry, final Path dstDir, final boolean skipIfUnchanged) throws IOException { + final URL resource = FileConfig.class.getResource(entry); + + if (resource == null) { + throw new TargetResourceNotFoundException(entry); + } + + final URLConnection connection = resource.openConnection(); + if (connection instanceof JarURLConnection) { + if (!copyFileFromJar((JarURLConnection) connection, dstDir, skipIfUnchanged)) { + throw new IOException("'" + entry + "' is not a file"); + } + } else { + try { + Path path = Paths.get(FileLocator.toFileURL(resource).toURI()); + copyFile(path, dstDir.resolve(path.getFileName()), skipIfUnchanged); + } catch (URISyntaxException e) { + // This should never happen as toFileURL should always return a valid URL + throw new IOException("Unexpected error while resolving " + entry + " on the classpath"); + } + } + } + + /** + * Look up the given {@code entry} in the classpath. If it is a file, copy it into the destination + * directory. If the {@code entry} is a directory and {@code contentsOnly} is true, then copy its + * contents to the destination directory. If the {@code entry} is a directory and {@code + * contentsOnly} is true, then copy it including its contents to the destination directory. + * + *

This also creates new directories for any directories on the destination path that do not + * yet exist. + * + * @param entry The entry to be found on the class path and copied to the given destination. + * @param dstDir The file system path that found files are to be copied to. + * @param skipIfUnchanged If true, don't overwrite the file or directory if its content would not + * be changed + * @param contentsOnly If true and the entry is a directory, then copy its contents but not the + * directory itself. + * @throws IOException If the operation failed. + */ + public static void copyFromClassPath( + final String entry, + final Path dstDir, + final boolean skipIfUnchanged, + final boolean contentsOnly) + throws IOException { + final URL resource = FileConfig.class.getResource(entry); + + if (resource == null) { + throw new TargetResourceNotFoundException(entry); + } + + final URLConnection connection = resource.openConnection(); + if (connection instanceof JarURLConnection) { + boolean copiedFiles = + copyFromJar((JarURLConnection) connection, dstDir, skipIfUnchanged, contentsOnly); + if (!copiedFiles) { + throw new TargetResourceNotFoundException(entry); + } + } else { + try { + Path path = Paths.get(FileLocator.toFileURL(resource).toURI()); + if (path.toFile().isDirectory()) { + if (contentsOnly) { + copyDirectoryContents(path, dstDir, skipIfUnchanged); + } else { + copyDirectory(path, dstDir, skipIfUnchanged); + } - final URLConnection connection = resource.openConnection(); - if (connection instanceof JarURLConnection) { - if (!copyFileFromJar((JarURLConnection) connection, dstDir, skipIfUnchanged)) { - throw new IOException("'" + entry + "' is not a file"); - } } else { - try { - Path path = Paths.get(FileLocator.toFileURL(resource).toURI()); - copyFile(path, dstDir.resolve(path.getFileName()), skipIfUnchanged); - } catch(URISyntaxException e) { - // This should never happen as toFileURL should always return a valid URL - throw new IOException("Unexpected error while resolving " + entry + " on the classpath"); - } - } - } - - /** - * Look up the given {@code entry} in the classpath. If it is a file, copy it into the destination - * directory. - * If the {@code entry} is a directory and {@code contentsOnly} is true, then copy its contents - * to the destination directory. If the {@code entry} is a directory and {@code contentsOnly} is - * true, then copy it including its contents to the destination directory. - * - * This also creates new directories for any directories on the destination - * path that do not yet exist. - * - * @param entry The entry to be found on the class path and copied to the given destination. - * @param dstDir The file system path that found files are to be copied to. - * @param skipIfUnchanged If true, don't overwrite the file or directory if its content would not be changed - * @param contentsOnly If true and the entry is a directory, then copy its contents but not the directory itself. - * @throws IOException If the operation failed. - */ - public static void copyFromClassPath( - final String entry, - final Path dstDir, - final boolean skipIfUnchanged, - final boolean contentsOnly - ) throws IOException { - final URL resource = FileConfig.class.getResource(entry); - - if (resource == null) { - throw new TargetResourceNotFoundException(entry); - } - - final URLConnection connection = resource.openConnection(); - if (connection instanceof JarURLConnection) { - boolean copiedFiles = copyFromJar((JarURLConnection) connection, dstDir, skipIfUnchanged, contentsOnly); - if (!copiedFiles) { - throw new TargetResourceNotFoundException(entry); - } + copyFile(path, dstDir.resolve(path.getFileName()), skipIfUnchanged); + } + } catch (URISyntaxException e) { + // This should never happen as toFileURL should always return a valid URL + throw new IOException("Unexpected error while resolving " + entry + " on the classpath"); + } + } + } + + /** + * Return true if the given connection points to a file. + * + * @param connection A connection to a JAR file. + * @throws IOException If the connection is faulty. + */ + private static boolean isFileInJar(JarURLConnection connection) throws IOException { + return connection.getJarFile().stream() + .anyMatch(it -> it.getName().equals(connection.getEntryName())); + } + + /** + * Given a JAR file and a {@code srcFile} entry, copy it into the given destination directory. + * + * @param jar The JAR file from which to copy {@code srcFile}. + * @param srcFile The source file to copy from the given {@code jar}. + * @param dstDir The directory to top the source file into. + * @param skipIfUnchanged If true, don't overwrite the destination file if its content would * not + * be changed. + * @throws IOException If the operation fails. + */ + private static void copyFileFromJar( + JarFile jar, String srcFile, Path dstDir, boolean skipIfUnchanged) throws IOException { + var entry = jar.getJarEntry(srcFile); + var filename = Paths.get(entry.getName()).getFileName(); + InputStream is = jar.getInputStream(entry); + try (is) { + copyInputStream(is, dstDir.resolve(filename), skipIfUnchanged); + } + } + + /** + * Copy the contents from an entry in a JAR to destination directory in the filesystem. The entry + * may be a file, in which case it will be copied under the same name into the destination + * directory. If the entry is a directory, then if {@code contentsOnly} is true, only the contents + * of the directory will be copied into the destination directory (not the directory itself). A + * directory will be copied as a whole, including its contents, if {@code contentsOnly} is false. + * + *

This method should only be used in standalone mode (lfc). + * + *

This also creates new directories for any directories on the destination path that do not + * yet exist. + * + * @param connection a URLConnection to the source entry within the jar + * @param dstDir The file system path that entries are copied to. + * @param skipIfUnchanged If true, don't overwrite the file if its content would not be changed. + * @param contentsOnly If true, and the connection points to a directory, copy its contents only + * (not the directory itself). + * @return true if any files were copied + * @throws IOException If the given source cannot be copied. + */ + private static boolean copyFromJar( + JarURLConnection connection, + Path dstDir, + final boolean skipIfUnchanged, + final boolean contentsOnly) + throws IOException { + + if (copyFileFromJar(connection, dstDir, skipIfUnchanged)) { + return true; + } + return copyDirectoryFromJar(connection, dstDir, skipIfUnchanged, contentsOnly); + } + + /** + * Given a connection to a JAR file that points to an entry that is a directory, recursively copy + * all entries located in that directory into the given {@code dstDir}. + * + *

If {@code contentsOnly} is true, only the contents of the directory will be copied into the + * destination directory (not the directory itself). The directory will be copied as a whole, + * including its contents, if {@code contentsOnly} is false. + * + * @param connection A connection to a JAR file that points to a directory entry. + * @param dstDir The destination directory to copy the matching entries to. + * @param skipIfUnchanged + * @param contentsOnly + * @return + * @throws IOException + */ + private static boolean copyDirectoryFromJar( + JarURLConnection connection, + Path dstDir, + final boolean skipIfUnchanged, + final boolean contentsOnly) + throws IOException { + final JarFile jar = connection.getJarFile(); + final String source = connection.getEntryName(); + + boolean copiedFiles = false; + if (!contentsOnly) { + dstDir = dstDir.resolve(Paths.get(source).getFileName()); + } + // Iterate all entries in the jar file. + for (Enumeration e = jar.entries(); e.hasMoreElements(); ) { + final JarEntry entry = e.nextElement(); + final String entryName = entry.getName(); + if (entryName.startsWith(source)) { + String filename = entry.getName().substring(source.length() + 1); + Path currentFile = dstDir.resolve(filename); + if (entry.isDirectory()) { + Files.createDirectories(currentFile); } else { - try { - Path path = Paths.get(FileLocator.toFileURL(resource).toURI()); - if (path.toFile().isDirectory()) { - if (contentsOnly) { - copyDirectoryContents(path, dstDir, skipIfUnchanged); - } else { - copyDirectory(path, dstDir, skipIfUnchanged); - } - - } else { - copyFile(path, dstDir.resolve(path.getFileName()), skipIfUnchanged); - } - } catch(URISyntaxException e) { - // This should never happen as toFileURL should always return a valid URL - throw new IOException("Unexpected error while resolving " + entry + " on the classpath"); - } - } - } - - /** - * Return true if the given connection points to a file. - * @param connection A connection to a JAR file. - * @throws IOException If the connection is faulty. - */ - private static boolean isFileInJar(JarURLConnection connection) throws IOException { - return connection.getJarFile().stream().anyMatch( - it -> it.getName().equals(connection.getEntryName()) - ); - } - - /** - * Given a JAR file and a {@code srcFile} entry, copy it into the given destination directory. - * - * @param jar The JAR file from which to copy {@code srcFile}. - * @param srcFile The source file to copy from the given {@code jar}. - * @param dstDir The directory to top the source file into. - * @param skipIfUnchanged If true, don't overwrite the destination file if its content would - * * not be changed. - * @throws IOException If the operation fails. - */ - private static void copyFileFromJar(JarFile jar, String srcFile, Path dstDir, boolean skipIfUnchanged) throws IOException { - var entry = jar.getJarEntry(srcFile); - var filename = Paths.get(entry.getName()).getFileName(); - InputStream is = jar.getInputStream(entry); - try (is) { - copyInputStream(is, dstDir.resolve(filename), skipIfUnchanged); - } - } - - /** - * Copy the contents from an entry in a JAR to destination directory in the filesystem. The entry - * may be a file, in which case it will be copied under the same name into the destination - * directory. If the entry is a directory, then if {@code contentsOnly} is true, only the - * contents of the directory will be copied into the destination directory (not the directory - * itself). A directory will be copied as a whole, including its contents, if - * {@code contentsOnly} is false. - * - * This method should only be used in standalone mode (lfc). - * - * This also creates new directories for any directories on - * the destination path that do not yet exist. - * - * @param connection a URLConnection to the source entry within the jar - * @param dstDir The file system path that entries are copied to. - * @param skipIfUnchanged If true, don't overwrite the file if its content would not be changed. - * @param contentsOnly If true, and the connection points to a directory, copy its contents only - * (not the directory itself). - * @return true if any files were copied - * @throws IOException If the given source cannot be copied. - */ - private static boolean copyFromJar( - JarURLConnection connection, - Path dstDir, - final boolean skipIfUnchanged, - final boolean contentsOnly - ) throws IOException { - - if (copyFileFromJar(connection, dstDir, skipIfUnchanged)) { - return true; - } - return copyDirectoryFromJar(connection, dstDir, skipIfUnchanged, contentsOnly); - } - - /** - * Given a connection to a JAR file that points to an entry that is a directory, recursively copy - * all entries located in that directory into the given {@code dstDir}. - *

- * If {@code contentsOnly} is true, only the contents of the directory will be copied into the - * destination directory (not the directory itself). The directory will be copied as a whole, - * including its contents, if {@code contentsOnly} is false. - * @param connection A connection to a JAR file that points to a directory entry. - * @param dstDir The destination directory to copy the matching entries to. - * @param skipIfUnchanged - * @param contentsOnly - * @return - * @throws IOException - */ - private static boolean copyDirectoryFromJar(JarURLConnection connection, - Path dstDir, - final boolean skipIfUnchanged, - final boolean contentsOnly) throws IOException { - final JarFile jar = connection.getJarFile(); - final String source = connection.getEntryName(); - - boolean copiedFiles = false; - if (!contentsOnly) { - dstDir = dstDir.resolve(Paths.get(source).getFileName()); - } - // Iterate all entries in the jar file. - for (Enumeration e = jar.entries(); e.hasMoreElements(); ) { - final JarEntry entry = e.nextElement(); - final String entryName = entry.getName(); - if (entryName.startsWith(source)) { - String filename = entry.getName().substring(source.length() + 1); - Path currentFile = dstDir.resolve(filename); - if (entry.isDirectory()) { - Files.createDirectories(currentFile); - } else { - InputStream is = jar.getInputStream(entry); - try (is) { - copyInputStream(is, currentFile, skipIfUnchanged); - copiedFiles = true; - } - } - } - } - return copiedFiles; - } - - /** - * Given a connection to a JAR file that points to an entry that is a file, copy the file into the - * given {@code dstDir}. - * @param connection A connection to a JAR file that points to a directory entry. - * @param dstDir The destination directory to copy the file to. - * @param skipIfUnchanged - * @return {@code true} the connection entry is a file, and it was copied successfully; - * {@code false} if the connection entry is not a file and the copy operation was aborted. - * @throws IOException If the operation failed. - */ - private static boolean copyFileFromJar( - JarURLConnection connection, - Path dstDir, - final boolean skipIfUnchanged - ) throws IOException { - final JarFile jar = connection.getJarFile(); - final String source = connection.getEntryName(); - - if (!isFileInJar(connection)) { - return false; - } - copyFileFromJar(jar, source, dstDir, skipIfUnchanged); - - return true; - } - - /** - * Delete unused Files from Arduino-CLI based compilation. - * - * Arduino-CLI (the build system) uses lazy compilation (i.e. compiles every file recursively from - * a source directory). This does the work of CMake by explicitly deleting files that - * shouldn't get compiled by the CLI. Generally, we delete all CMake artifacts and multithreaded - * support files (including semaphores and thread folders) - * - * @param dir The folder to search for folders and files to delete. - * @throws IOException If the given folder and unneeded files cannot be deleted. - */ - public static void arduinoDeleteHelper(Path dir, boolean threadingOn) throws IOException { - deleteDirectory(dir.resolve("core/federated")); // TODO: Add Federated Support to Arduino - deleteDirectory(dir.resolve("include/core/federated")); // TODO: Add Federated Support to Arduino - - if (!threadingOn) { - deleteDirectory(dir.resolve("core/threaded")); // No Threaded Support for Arduino - deleteDirectory(dir.resolve("include/core/threaded")); // No Threaded Support for Arduino - deleteDirectory(dir.resolve("core/platform/arduino_mbed")); // No Threaded Support for Arduino - } - - List allPaths = Files.walk(dir) - .sorted(Comparator.reverseOrder()) - .toList(); - for (Path path : allPaths) { - String toCheck = path.toString().toLowerCase(); - if (toCheck.contains("cmake")) { - Files.delete(path); - } - } - } - - /** - * Helper function for getting the string representation of the relative path - * to take to get from one file (currPath) to get to the other (fileName). - * - * Generally, this is useful for converting includes to have relative pathing when - * you lack access to adding additional include paths when compiling. - * - * @param fileName File to search for. - * @param currPath The current path to the file whose include statements we are modifying. - * @param fileStringToFilePath Mapping of File Names to their paths. - */ - private static String fileNameMatchConverter(String fileName, Path currPath, Map fileStringToFilePath) - throws NullPointerException { - // First get the child file - int lastPath = fileName.lastIndexOf(File.separator); - if (lastPath != -1){ - fileName = fileName.substring(lastPath+1); - } - Path p = fileStringToFilePath.get(fileName); - if(p == null) { - return "#include \"" + fileName + "\""; - } - String relativePath = currPath.getParent().relativize(p).toString(); - return "#include \"" + relativePath + "\""; - } - - /** - * Return true if the given path points to a C file, false otherwise. - */ - public static boolean isCFile(Path path) { - String fileName = path.getFileName().toString(); - return fileName.endsWith(".c") || fileName.endsWith(".cpp") || fileName.endsWith(".h"); - } - - /** - * Convert all includes recursively inside files within a specified folder to relative links - * - * @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, Path includePath) throws IOException { - System.out.println("Relativizing all includes in " + dir.toString()); - List includePaths = Files.walk(includePath) + InputStream is = jar.getInputStream(entry); + try (is) { + copyInputStream(is, currentFile, skipIfUnchanged); + copiedFiles = true; + } + } + } + } + return copiedFiles; + } + + /** + * Given a connection to a JAR file that points to an entry that is a file, copy the file into the + * given {@code dstDir}. + * + * @param connection A connection to a JAR file that points to a directory entry. + * @param dstDir The destination directory to copy the file to. + * @param skipIfUnchanged + * @return {@code true} the connection entry is a file, and it was copied successfully; {@code + * false} if the connection entry is not a file and the copy operation was aborted. + * @throws IOException If the operation failed. + */ + private static boolean copyFileFromJar( + JarURLConnection connection, Path dstDir, final boolean skipIfUnchanged) throws IOException { + final JarFile jar = connection.getJarFile(); + final String source = connection.getEntryName(); + + if (!isFileInJar(connection)) { + return false; + } + copyFileFromJar(jar, source, dstDir, skipIfUnchanged); + + return true; + } + + /** + * Delete unused Files from Arduino-CLI based compilation. + * + *

Arduino-CLI (the build system) uses lazy compilation (i.e. compiles every file recursively + * from a source directory). This does the work of CMake by explicitly deleting files that + * shouldn't get compiled by the CLI. Generally, we delete all CMake artifacts and multithreaded + * support files (including semaphores and thread folders) + * + * @param dir The folder to search for folders and files to delete. + * @throws IOException If the given folder and unneeded files cannot be deleted. + */ + public static void arduinoDeleteHelper(Path dir, boolean threadingOn) throws IOException { + deleteDirectory(dir.resolve("core/federated")); // TODO: Add Federated Support to Arduino + deleteDirectory( + dir.resolve("include/core/federated")); // TODO: Add Federated Support to Arduino + + if (!threadingOn) { + deleteDirectory(dir.resolve("core/threaded")); // No Threaded Support for Arduino + deleteDirectory(dir.resolve("include/core/threaded")); // No Threaded Support for Arduino + deleteDirectory(dir.resolve("core/platform/arduino_mbed")); // No Threaded Support for Arduino + } + + List allPaths = Files.walk(dir).sorted(Comparator.reverseOrder()).toList(); + for (Path path : allPaths) { + String toCheck = path.toString().toLowerCase(); + if (toCheck.contains("cmake")) { + Files.delete(path); + } + } + } + + /** + * Helper function for getting the string representation of the relative path to take to get from + * one file (currPath) to get to the other (fileName). + * + *

Generally, this is useful for converting includes to have relative pathing when you lack + * access to adding additional include paths when compiling. + * + * @param fileName File to search for. + * @param currPath The current path to the file whose include statements we are modifying. + * @param fileStringToFilePath Mapping of File Names to their paths. + */ + private static String fileNameMatchConverter( + String fileName, Path currPath, Map fileStringToFilePath) + throws NullPointerException { + // First get the child file + int lastPath = fileName.lastIndexOf(File.separator); + if (lastPath != -1) { + fileName = fileName.substring(lastPath + 1); + } + Path p = fileStringToFilePath.get(fileName); + if (p == null) { + return "#include \"" + fileName + "\""; + } + String relativePath = currPath.getParent().relativize(p).toString(); + return "#include \"" + relativePath + "\""; + } + + /** Return true if the given path points to a C file, false otherwise. */ + public static boolean isCFile(Path path) { + String fileName = path.getFileName().toString(); + return fileName.endsWith(".c") || fileName.endsWith(".cpp") || fileName.endsWith(".h"); + } + + /** + * Convert all includes recursively inside files within a specified folder to relative links + * + * @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, Path includePath) throws IOException { + System.out.println("Relativizing all includes in " + dir.toString()); + List includePaths = + Files.walk(includePath) .filter(Files::isRegularFile) .filter(FileUtil::isCFile) .sorted(Comparator.reverseOrder()) .toList(); - List srcPaths = Files.walk(dir) + List srcPaths = + Files.walk(dir) .filter(Files::isRegularFile) .filter(FileUtil::isCFile) .sorted(Comparator.reverseOrder()) .toList(); - Map fileStringToFilePath = new HashMap(); - for (Path path : includePaths) { - String fileName = path.getFileName().toString(); - if (path.getFileName().toString().contains("CMakeLists.txt")) continue; - if (fileStringToFilePath.put(fileName, path) != null) { - throw new IOException("Directory has different files with the same name. Cannot Relativize."); - } - } - Pattern regexExpression = Pattern.compile("#include\s+[\"]([^\"]+)*[\"]"); - for (Path path : srcPaths) { - String fileContents = Files.readString(path); - Matcher matcher = regexExpression.matcher(fileContents); - int lastIndex = 0; - StringBuilder output = new StringBuilder(); - while (matcher.find()) { - output.append(fileContents, lastIndex, matcher.start()) - .append(fileNameMatchConverter(matcher.group(1), path, fileStringToFilePath)); - lastIndex = matcher.end(); - } - if (lastIndex < fileContents.length()) { - output.append(fileContents, lastIndex, fileContents.length()); - } - writeToFile(output.toString(), path); - } - } - - /** - * Delete the given file or directory if it exists. If {@code fileOrDirectory} is a directory, - * deletion is recursive. - * - * @param fileOrDirectory The file or directory to delete. - * @throws IOException If the operation failed. - */ - public static void delete(Path fileOrDirectory) throws IOException { - if (Files.isRegularFile(fileOrDirectory)) { - Files.deleteIfExists(fileOrDirectory); - } - if (Files.isDirectory(fileOrDirectory)) { - deleteDirectory(fileOrDirectory); - } - } - - /** - * Recursively delete a directory if it exists. - * - * @throws IOException If an I/O error occurs. - */ - public static void deleteDirectory(Path dir) throws IOException { - if (Files.isDirectory(dir)) { - System.out.println("Cleaning " + dir); - List pathsToDelete = Files.walk(dir) - .sorted(Comparator.reverseOrder()) - .toList(); - for (Path path : pathsToDelete) { - Files.deleteIfExists(path); - } - } - } - - /** - * 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; + Map fileStringToFilePath = new HashMap(); + for (Path path : includePaths) { + String fileName = path.getFileName().toString(); + if (path.getFileName().toString().contains("CMakeLists.txt")) continue; + if (fileStringToFilePath.put(fileName, path) != null) { + throw new IOException( + "Directory has different files with the same name. Cannot Relativize."); + } + } + Pattern regexExpression = Pattern.compile("#include\s+[\"]([^\"]+)*[\"]"); + for (Path path : srcPaths) { + String fileContents = Files.readString(path); + Matcher matcher = regexExpression.matcher(fileContents); + int lastIndex = 0; + StringBuilder output = new StringBuilder(); + while (matcher.find()) { + output + .append(fileContents, lastIndex, matcher.start()) + .append(fileNameMatchConverter(matcher.group(1), path, fileStringToFilePath)); + lastIndex = matcher.end(); + } + if (lastIndex < fileContents.length()) { + output.append(fileContents, lastIndex, fileContents.length()); + } + writeToFile(output.toString(), path); + } + } + + /** + * Delete the given file or directory if it exists. If {@code fileOrDirectory} is a directory, + * deletion is recursive. + * + * @param fileOrDirectory The file or directory to delete. + * @throws IOException If the operation failed. + */ + public static void delete(Path fileOrDirectory) throws IOException { + if (Files.isRegularFile(fileOrDirectory)) { + Files.deleteIfExists(fileOrDirectory); + } + if (Files.isDirectory(fileOrDirectory)) { + deleteDirectory(fileOrDirectory); + } + } + + /** + * Recursively delete a directory if it exists. + * + * @throws IOException If an I/O error occurs. + */ + public static void deleteDirectory(Path dir) throws IOException { + if (Files.isDirectory(dir)) { + System.out.println("Cleaning " + dir); + List pathsToDelete = Files.walk(dir).sorted(Comparator.reverseOrder()).toList(); + for (Path path : pathsToDelete) { + Files.deleteIfExists(path); + } + } + } + + /** + * 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).toAbsolutePath(); + } + } + return null; + } + + /** Get the iResource corresponding to the provided resource if it can be found. */ + public static IResource getIResource(Resource r) throws IOException { + return getIResource(FileUtil.toPath(r).toFile().toURI()); + } + + /** Get the specified path as an Eclipse IResource or null if it is not found. */ + public static IResource getIResource(Path path) { + IResource ret = getIResource(path.toUri()); + if (ret != null) return ret; + try { + // Handle a bug that not everyone can reproduce in which a path originating in the Ecore model + // is a relative + // path prefixed with a segment named "resource". + return ResourcesPlugin.getWorkspace() + .getRoot() + .findMember( + org.eclipse.core.runtime.Path.fromOSString( + path.subpath(1, path.getNameCount()).toString())); + } catch (IllegalStateException e) { + // We are outside of Eclipse. + } + return null; + } + + /** + * Get the specified uri as an Eclipse IResource or null if it is not found. + * + *

Also returns null if this is not called from within a running Eclipse instance. + * + * @param uri A java.net.uri of the form "file://path". + */ + public static IResource getIResource(java.net.URI uri) { + // For some peculiar reason known only to Eclipse developers, + // the resource cannot be used directly but has to be converted + // a resource relative to the workspace root. + try { + IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); + + IFile[] files = workspaceRoot.findFilesForLocationURI(uri); + if (files != null && files.length > 0 && files[0] != null) { + return files[0]; + } + } catch (IllegalStateException e) { + // We are outside of Eclipse. + } + return null; + } + + /** + * Write text to a file. + * + * @param text The text to be written. + * @param path The file to write the code to. + * @param skipIfUnchanged If true, don't overwrite the destination file if its content would not + * be changed + */ + public static void writeToFile(String text, Path path, boolean skipIfUnchanged) + throws IOException { + Files.createDirectories(path.getParent()); + final byte[] bytes = text.getBytes(); + if (skipIfUnchanged && Files.isRegularFile(path)) { + if (Arrays.equals(bytes, Files.readAllBytes(path))) { + return; + } + } + Files.write(path, text.getBytes()); + } + + /** + * Write text to a file. + * + * @param text The text to be written. + * @param path The file to write the code to. + */ + public static void writeToFile(String text, Path path) throws IOException { + writeToFile(text, path, false); + } + + /** + * Write text to a file. + * + * @param text The text to be written. + * @param path The file to write the code to. + */ + public static void writeToFile(CharSequence text, Path path) throws IOException { + writeToFile(text.toString(), path, false); + } + + public static void createDirectoryIfDoesNotExist(File dir) { + if (!dir.exists()) dir.mkdirs(); + } + + /** + * Return a list of files ending with "str". + * + * @param currentDir The current directory. + * @param str The pattern to match against. + */ + public static List globFilesEndsWith(Path currentDir, String str) { + List matches = new ArrayList<>(); + File[] files = currentDir.toFile().listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + matches.addAll(globFilesEndsWith(file.toPath(), str)); } 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).toAbsolutePath(); - } - } - return null; - } - - /** - * Get the iResource corresponding to the provided resource if it can be - * found. - */ - public static IResource getIResource(Resource r) throws IOException { - return getIResource(FileUtil.toPath(r).toFile().toURI()); - } - - /** - * Get the specified path as an Eclipse IResource or null if it is not found. - */ - public static IResource getIResource(Path path) { - IResource ret = getIResource(path.toUri()); - if (ret != null) return ret; - try { - // Handle a bug that not everyone can reproduce in which a path originating in the Ecore model is a relative - // path prefixed with a segment named "resource". - return ResourcesPlugin.getWorkspace().getRoot().findMember(org.eclipse.core.runtime.Path.fromOSString( - path.subpath(1, path.getNameCount()).toString() - )); - } catch (IllegalStateException e) { - // We are outside of Eclipse. - } - return null; - } - - /** - * Get the specified uri as an Eclipse IResource or null if it is not found. - * - * Also returns null if this is not called from within a running Eclipse instance. - * - * @param uri A java.net.uri of the form "file://path". - */ - public static IResource getIResource(java.net.URI uri) { - // For some peculiar reason known only to Eclipse developers, - // the resource cannot be used directly but has to be converted - // a resource relative to the workspace root. - try { - IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); - - IFile[] files = workspaceRoot.findFilesForLocationURI(uri); - if (files != null && files.length > 0 && files[0] != null) { - return files[0]; - } - } catch (IllegalStateException e) { - // We are outside of Eclipse. - } - return null; - } - - /** - * Write text to a file. - * @param text The text to be written. - * @param path The file to write the code to. - * @param skipIfUnchanged If true, don't overwrite the destination file if its content would not be changed - */ - public static void writeToFile(String text, Path path, boolean skipIfUnchanged) throws IOException { - Files.createDirectories(path.getParent()); - final byte[] bytes = text.getBytes(); - if (skipIfUnchanged && Files.isRegularFile(path)) { - if (Arrays.equals(bytes, Files.readAllBytes(path))) { - return; - } - } - Files.write(path, text.getBytes()); - } - - /** - * Write text to a file. - * @param text The text to be written. - * @param path The file to write the code to. - */ - public static void writeToFile(String text, Path path) throws IOException { - writeToFile(text, path, false); - } - - /** - * Write text to a file. - * @param text The text to be written. - * @param path The file to write the code to. - */ - public static void writeToFile(CharSequence text, Path path) throws IOException { - writeToFile(text.toString(), path, false); - } - - public static void createDirectoryIfDoesNotExist(File dir) { - if (!dir.exists()) dir.mkdirs(); - } - - /** - * Return a list of files ending with "str". - * - * @param currentDir The current directory. - * @param str The pattern to match against. - */ - public static List globFilesEndsWith(Path currentDir, String str) { - List matches = new ArrayList<>(); - File[] files = currentDir.toFile().listFiles(); - if (files != null) { - for (File file : files) { - if (file.isDirectory()) { - matches.addAll(globFilesEndsWith(file.toPath(), str)); - } else { - if (file.getName().endsWith(str)) { - matches.add(file.getAbsoluteFile().toPath()); - } - } - } + if (file.getName().endsWith(str)) { + matches.add(file.getAbsoluteFile().toPath()); + } } - return matches; + } } + return matches; + } } diff --git a/org.lflang/src/org/lflang/util/IteratorUtil.java b/org.lflang/src/org/lflang/util/IteratorUtil.java index 37ddadf56d..559739ed00 100644 --- a/org.lflang/src/org/lflang/util/IteratorUtil.java +++ b/org.lflang/src/org/lflang/util/IteratorUtil.java @@ -4,8 +4,6 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; -import org.lflang.lf.Reactor; - /** * A utility class for Iterator. * @@ -14,55 +12,53 @@ */ public final class IteratorUtil { - private IteratorUtil() { - // Don't let anyone instantiate this class. - } + private IteratorUtil() { + // Don't let anyone instantiate this class. + } + + /** + * Turn an iterator into a sequential stream. + * + * @param iterator The iterator to create a sequential stream for. + * @return A stream. + */ + public static Stream asStream(Iterator iterator) { + return asStream(iterator, false); + } - /** - * Turn an iterator into a sequential stream. - * - * @param iterator The iterator to create a sequential stream for. - * @return A stream. - */ - public static Stream asStream(Iterator iterator) { - return asStream(iterator, false); - } + /** + * Given an iterator of type T, turn it into a stream containing only the instances of the given + * class of type S. + * + * @param The type of elements the iterator iterates over. + * @param The type of class to filter out instance of. + * @param iterator An iterator of type T. + * @param cls A given class of type S. + * @return A filtered stream that only has in it instances of the given class. + */ + public static Stream asFilteredStream(Iterator iterator, Class cls) { + return asStream(iterator, false).filter(cls::isInstance).map(cls::cast); + } - /** - * Given an iterator of type T, turn it into a stream containing only the - * instances of the given class of type S. - * - * @param The type of elements the iterator iterates over. - * @param The type of class to filter out instance of. - * @param iterator An iterator of type T. - * @param cls A given class of type S. - * @return A filtered stream that only has in it instances of the given - * class. - */ - public static Stream asFilteredStream(Iterator iterator, - Class cls) { - return asStream(iterator, false).filter(cls::isInstance).map(cls::cast); - } - - /** - * Turn an iterator into a sequential or parallel stream. - * - * @param iterator The iterator to create a stream for. - * @param parallel Whether or not the stream should be parallel. - * @return A stream. - */ - public static Stream asStream(Iterator iterator, boolean parallel) { - Iterable iterable = () -> iterator; - return StreamSupport.stream(iterable.spliterator(), parallel); - } + /** + * Turn an iterator into a sequential or parallel stream. + * + * @param iterator The iterator to create a stream for. + * @param parallel Whether or not the stream should be parallel. + * @return A stream. + */ + public static Stream asStream(Iterator iterator, boolean parallel) { + Iterable iterable = () -> iterator; + return StreamSupport.stream(iterable.spliterator(), parallel); + } - /** - * Function to get Iterable from Iterator. - * - * @param iterator The iterator to get an iterable from. - * @return An iterable. - */ - public static Iterable asIterable(Iterator iterator) { - return () -> iterator; - } + /** + * Function to get Iterable from Iterator. + * + * @param iterator The iterator to get an iterable from. + * @return An iterable. + */ + public static Iterable asIterable(Iterator iterator) { + return () -> iterator; + } } diff --git a/org.lflang/src/org/lflang/util/LFCommand.java b/org.lflang/src/org/lflang/util/LFCommand.java index 4f6927dcf0..4b4116f264 100644 --- a/org.lflang/src/org/lflang/util/LFCommand.java +++ b/org.lflang/src/org/lflang/util/LFCommand.java @@ -1,26 +1,26 @@ /************* - Copyright (c) 2019-2021 TU Dresden - Copyright (c) 2019-2021 UC 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 TU Dresden + * Copyright (c) 2019-2021 UC 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.util; @@ -42,370 +42,353 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; - import org.eclipse.xtext.util.CancelIndicator; /** * An abstraction for an external command - *

- * This is a wrapper around ProcessBuilder which allows for a more convenient usage in our code base. + * + *

This is a wrapper around ProcessBuilder which allows for a more convenient usage in our code + * base. */ public class LFCommand { - /** - * The period with which the cancel indicator is - * checked and the output and error streams are - * forwarded. - */ - private static final int PERIOD_MILLISECONDS = 200; - /** - * The maximum amount of time to wait for the - * forwarding of output and error streams to finish. - */ - private static final int READ_TIMEOUT_MILLISECONDS = 1000; - - protected ProcessBuilder processBuilder; - protected boolean didRun = false; - protected ByteArrayOutputStream output = new ByteArrayOutputStream(); - protected ByteArrayOutputStream errors = new ByteArrayOutputStream(); - protected boolean quiet; - - - /** - * Construct an LFCommand that executes the command carried by {@code pb}. - */ - protected LFCommand(ProcessBuilder pb, boolean quiet) { - this.processBuilder = pb; - this.quiet = quiet; - } - - - /** - * Get the output collected during command execution - */ - public OutputStream getOutput() { return output; } - - - /** - * Get the error output collected during command execution - */ - public OutputStream getErrors() { return errors; } - - /** Get this command's program and arguments. */ - public List command() { return processBuilder.command(); } - - /** Get this command's working directory. */ - public File directory() { return processBuilder.directory(); } - - /** - * Get a String representation of the stored command - */ - public String toString() { return String.join(" ", processBuilder.command()); } - - - /** - * Collect as much output as possible from {@code in} without blocking, print it to - * {@code print} if not quiet, and store it in {@code store}. - */ - private void collectOutput(InputStream in, ByteArrayOutputStream store, PrintStream print) { - byte[] buffer = new byte[64]; - int len; - do { - try { - // This depends on in.available() being - // greater than zero if data is available - // (so that all data is collected) - // and upper-bounded by maximum number of - // bytes that can be read without blocking. - // Only the latter of these two conditions - // is guaranteed by the spec. - len = in.read(buffer, 0, Math.min(in.available(), buffer.length)); - if (len > 0) { - store.write(buffer, 0, len); - if (!quiet) print.write(buffer, 0, len); - } - } catch (IOException e) { - e.printStackTrace(); - break; - } - } while (len > 0); // Do not necessarily continue - // to EOF (len == -1) because a blocking read - // operation is hard to interrupt. - } - - /** - * Handle user cancellation if necessary, and handle any output from {@code process} - * otherwise. - * @param process a {@code Process} - * @param cancelIndicator a flag indicating whether a - * cancellation of {@code process} - * is requested - * directly to stderr and stdout). - */ - private void poll(Process process, CancelIndicator cancelIndicator) { - if (cancelIndicator != null && cancelIndicator.isCanceled()) { - process.descendants().forEach(ProcessHandle::destroyForcibly); - process.destroyForcibly(); - } else { - collectOutput(process.getInputStream(), output, System.out); - collectOutput(process.getErrorStream(), errors, System.err); + /** + * The period with which the cancel indicator is checked and the output and error streams are + * forwarded. + */ + private static final int PERIOD_MILLISECONDS = 200; + /** + * The maximum amount of time to wait for the forwarding of output and error streams to finish. + */ + private static final int READ_TIMEOUT_MILLISECONDS = 1000; + + protected ProcessBuilder processBuilder; + protected boolean didRun = false; + protected ByteArrayOutputStream output = new ByteArrayOutputStream(); + protected ByteArrayOutputStream errors = new ByteArrayOutputStream(); + protected boolean quiet; + + /** Construct an LFCommand that executes the command carried by {@code pb}. */ + protected LFCommand(ProcessBuilder pb, boolean quiet) { + this.processBuilder = pb; + this.quiet = quiet; + } + + /** Get the output collected during command execution */ + public OutputStream getOutput() { + return output; + } + + /** Get the error output collected during command execution */ + public OutputStream getErrors() { + return errors; + } + + /** Get this command's program and arguments. */ + public List command() { + return processBuilder.command(); + } + + /** Get this command's working directory. */ + public File directory() { + return processBuilder.directory(); + } + + /** Get a String representation of the stored command */ + public String toString() { + return String.join(" ", processBuilder.command()); + } + + /** + * Collect as much output as possible from {@code in} without blocking, print it to {@code print} + * if not quiet, and store it in {@code store}. + */ + private void collectOutput(InputStream in, ByteArrayOutputStream store, PrintStream print) { + byte[] buffer = new byte[64]; + int len; + do { + try { + // This depends on in.available() being + // greater than zero if data is available + // (so that all data is collected) + // and upper-bounded by maximum number of + // bytes that can be read without blocking. + // Only the latter of these two conditions + // is guaranteed by the spec. + len = in.read(buffer, 0, Math.min(in.available(), buffer.length)); + if (len > 0) { + store.write(buffer, 0, len); + if (!quiet) print.write(buffer, 0, len); } + } catch (IOException e) { + e.printStackTrace(); + break; + } + } while (len > 0); // Do not necessarily continue + // to EOF (len == -1) because a blocking read + // operation is hard to interrupt. + } + + /** + * Handle user cancellation if necessary, and handle any output from {@code process} otherwise. + * + * @param process a {@code Process} + * @param cancelIndicator a flag indicating whether a cancellation of {@code process} is requested + * directly to stderr and stdout). + */ + private void poll(Process process, CancelIndicator cancelIndicator) { + if (cancelIndicator != null && cancelIndicator.isCanceled()) { + process.descendants().forEach(ProcessHandle::destroyForcibly); + process.destroyForcibly(); + } else { + collectOutput(process.getInputStream(), output, System.out); + collectOutput(process.getErrorStream(), errors, System.err); } - - - /** - * Execute the command. - *

- * Executing a process directly with {@code processBuilder.start()} could - * lead to a deadlock as the subprocess blocks when output or error - * buffers are full. This method ensures that output and error messages - * are continuously read and forwards them to the system output and - * error streams as well as to the output and error streams hold in - * this class. - *

- *

- * If the current operation is cancelled (as indicated - * by cancelIndicator), the subprocess - * is destroyed. Output and error streams until that - * point are still collected. - *

- * - * @param cancelIndicator The indicator of whether the underlying process - * should be terminated. - * @return the process' return code - * @author Christian Menard - */ - public int run(CancelIndicator cancelIndicator) { - assert !didRun; - didRun = true; - - System.out.println("--- Current working directory: " + processBuilder.directory().toString()); - System.out.println("--- Executing command: " + String.join(" ", processBuilder.command())); - - final Process process = startProcess(); - if (process == null) return -1; - - ScheduledExecutorService poller = Executors.newSingleThreadScheduledExecutor(); - poller.scheduleAtFixedRate( - () -> poll(process, cancelIndicator), - 0, PERIOD_MILLISECONDS, TimeUnit.MILLISECONDS - ); - - try { - final int returnCode = process.waitFor(); - poller.shutdown(); - poller.awaitTermination(READ_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS); - // Finish collecting any remaining data - poll(process, cancelIndicator); - return returnCode; - } catch (InterruptedException e) { - e.printStackTrace(); - return -2; - } + } + + /** + * Execute the command. + * + *

Executing a process directly with {@code processBuilder.start()} could lead to a deadlock as + * the subprocess blocks when output or error buffers are full. This method ensures that output + * and error messages are continuously read and forwards them to the system output and error + * streams as well as to the output and error streams hold in this class. + * + *

If the current operation is cancelled (as indicated by cancelIndicator), the + * subprocess is destroyed. Output and error streams until that point are still collected. + * + * @param cancelIndicator The indicator of whether the underlying process should be terminated. + * @return the process' return code + * @author Christian Menard + */ + public int run(CancelIndicator cancelIndicator) { + assert !didRun; + didRun = true; + + System.out.println("--- Current working directory: " + processBuilder.directory().toString()); + System.out.println("--- Executing command: " + String.join(" ", processBuilder.command())); + + final Process process = startProcess(); + if (process == null) return -1; + + ScheduledExecutorService poller = Executors.newSingleThreadScheduledExecutor(); + poller.scheduleAtFixedRate( + () -> poll(process, cancelIndicator), 0, PERIOD_MILLISECONDS, TimeUnit.MILLISECONDS); + + try { + final int returnCode = process.waitFor(); + poller.shutdown(); + poller.awaitTermination(READ_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS); + // Finish collecting any remaining data + poll(process, cancelIndicator); + return returnCode; + } catch (InterruptedException e) { + e.printStackTrace(); + return -2; } - - /** - * Execute the command. Do not allow user cancellation. - * @return the process' return code - */ - public int run() { - return run(null); - } - - - /** - * Add the given variables and their values to the command's environment. - * - * @param variables A map of variable names and their values - */ - public void setEnvironmentVariables(Map variables) { - processBuilder.environment().putAll(variables); + } + + /** + * Execute the command. Do not allow user cancellation. + * + * @return the process' return code + */ + public int run() { + return run(null); + } + + /** + * Add the given variables and their values to the command's environment. + * + * @param variables A map of variable names and their values + */ + public void setEnvironmentVariables(Map variables) { + processBuilder.environment().putAll(variables); + } + + /** + * Add the given variable and its value to the command's environment. + * + * @param variableName name of the variable to add + * @param value the variable's value + */ + public void setEnvironmentVariable(String variableName, String value) { + processBuilder.environment().put(variableName, value); + } + + /** + * Replace the given variable and its value in the command's environment. + * + * @param variableName name of the variable to add + * @param value the variable's value + */ + public void replaceEnvironmentVariable(String variableName, String value) { + processBuilder.environment().remove(variableName); + processBuilder.environment().put(variableName, value); + } + + /** Require this to be quiet, overriding the verbosity specified at construction time. */ + public void setQuiet() { + quiet = true; + } + + /** + * Create a LFCommand instance from a given command and argument list in the current working + * directory. + * + * @see #get(String, List, boolean, Path) + */ + public static LFCommand get(final String cmd, final List args) { + return get(cmd, args, false, Paths.get("")); + } + + /** + * Create a LFCommand instance from a given command and argument list in the current working + * directory. + * + * @see #get(String, List, boolean, Path) + */ + public static LFCommand get(final String cmd, final List args, boolean quiet) { + return get(cmd, args, quiet, Paths.get("")); + } + + /** + * Create a LFCommand instance from a given command, an argument list and a directory. + * + *

This will check first if the command can actually be found and executed. If the command is + * not found, null is returned. In order to find the command, different methods are applied in the + * following order: + * + *

1. Check if the given command {@code cmd} is an executable file within {@code dir}. 2. If + * the above fails 'which ' (or 'where ' on Windows) is executed to see if the command + * is available on the PATH. 3. If both points above fail, a third attempt is started using bash + * to indirectly execute the command (see below for an explanation). + * + *

A bit more context: If the command cannot be found directly, then a second attempt is made + * using a Bash shell with the --login option, which sources the user's ~/.bash_profile, + * ~/.bash_login, or ~/.bashrc (whichever is first found) before running the command. This helps + * to ensure that the user's PATH variable is set according to their usual environment, assuming + * that they use a bash shell. + * + *

More information: Unfortunately, at least on a Mac if you are running within Eclipse, the + * PATH variable is extremely limited; supposedly, it is given by the default provided in + * /etc/paths, but at least on my machine, it does not even include directories in that file for + * some reason. One way to add a directory like /usr/local/bin to the path once-and-for-all is + * this: + * + *

sudo launchctl config user path /usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin + * + *

But asking users to do that is not ideal. Hence, we try a more hack-y approach of just + * trying to execute using a bash shell. Also note that while ProcessBuilder can be configured to + * use custom environment variables, these variables do not affect the command that is to be + * executed but merely the environment in which the command executes. + * + * @param cmd The command + * @param args A list of arguments to pass to the command + * @param quiet If true, the commands stdout and stderr will be suppressed + * @param dir The directory in which the command should be executed + * @return Returns an LFCommand if the given command could be found or null otherwise. + */ + public static LFCommand get(final String cmd, final List args, boolean quiet, Path dir) { + assert cmd != null && args != null && dir != null; + dir = dir.toAbsolutePath(); + + // a list containing the command as first element and then all arguments + List cmdList = new ArrayList<>(); + cmdList.add(cmd); + cmdList.addAll(args); + + ProcessBuilder builder = null; + + // First, see if the command is a local executable file + final File cmdFile = dir.resolve(cmd).toFile(); + if (cmdFile.exists() && cmdFile.canExecute()) { + builder = new ProcessBuilder(cmdList); + } else if (findCommand(cmd) != null) { + builder = new ProcessBuilder(cmdList); + } else if (checkIfCommandIsExecutableWithBash(cmd, dir)) { + builder = + new ProcessBuilder( + "bash", "--login", "-c", String.format("\"%s\"", String.join(" ", cmdList))); } - - /** - * Add the given variable and its value to the command's environment. - * - * @param variableName name of the variable to add - * @param value the variable's value - */ - public void setEnvironmentVariable(String variableName, String value) { - processBuilder.environment().put(variableName, value); - } - - /** - * Replace the given variable and its value in the command's environment. - * - * @param variableName name of the variable to add - * @param value the variable's value - */ - public void replaceEnvironmentVariable(String variableName, String value) { - processBuilder.environment().remove(variableName); - processBuilder.environment().put(variableName, value); + if (builder != null) { + builder.directory(dir.toFile()); + return new LFCommand(builder, quiet); } - /** - * Require this to be quiet, overriding the verbosity specified at construction time. - */ - public void setQuiet() { - quiet = true; + return null; + } + + /** + * Search for matches to the given command by following the PATH environment variable. + * + * @param command A command for which to search. + * @return The file locations of matches to the given command. + */ + private static List findCommand(final String command) { + final String whichCmd = System.getProperty("os.name").startsWith("Windows") ? "where" : "which"; + final ProcessBuilder whichBuilder = new ProcessBuilder(List.of(whichCmd, command)); + try { + Process p = whichBuilder.start(); + if (p.waitFor() != 0) return null; + return Arrays.stream(new String(p.getInputStream().readAllBytes()).split("\n")) + .map(String::strip) + .map(File::new) + .filter(File::canExecute) + .collect(Collectors.toList()); + } catch (InterruptedException | IOException e) { + e.printStackTrace(); + return null; } - - /** - * Create a LFCommand instance from a given command and argument list in the current working directory. - * - * @see #get(String, List, boolean, Path) - */ - public static LFCommand get(final String cmd, final List args) { - return get(cmd, args, false, Paths.get("")); + } + + /** + * Attempt to start the execution of this command. + * + *

First collect a list of paths where the executable might be found, then select an executable + * that successfully executes from the list of paths. Return the {@code Process} instance that is + * the result of a successful execution, or {@code null} if no successful execution happened. + * + * @return The {@code Process} that is started by this command, or {@code null} in case of + * failure. + */ + private Process startProcess() { + ArrayDeque commands = new ArrayDeque<>(); + List matchesOnPath = findCommand(processBuilder.command().get(0)); + if (matchesOnPath != null) { + matchesOnPath.stream().map(File::toString).forEach(commands::addLast); } - - /** - * Create a LFCommand instance from a given command and argument list in the current working directory. - * - * @see #get(String, List, boolean, Path) - */ - public static LFCommand get(final String cmd, final List args, boolean quiet) { - return get(cmd, args, quiet, Paths.get("")); - } - - - /** - * Create a LFCommand instance from a given command, an argument list and a directory. - *

- * This will check first if the command can actually be found and executed. If the command is not found, null is - * returned. In order to find the command, different methods are applied in the following order: - *

- * 1. Check if the given command {@code cmd} is an executable file within {@code dir}. - * 2. If the above fails 'which ' (or 'where ' on Windows) is executed to see if the command is available - * on the PATH. - * 3. If both points above fail, a third attempt is started using bash to indirectly execute the command (see below - * for an explanation). - *

- * A bit more context: - * If the command cannot be found directly, then a second attempt is made using a Bash shell with the --login - * option, which sources the user's ~/.bash_profile, ~/.bash_login, or ~/.bashrc (whichever is first found) before - * running the command. This helps to ensure that the user's PATH variable is set according to their usual - * environment, assuming that they use a bash shell. - *

- * More information: Unfortunately, at least on a Mac if you are running within Eclipse, the PATH variable is - * extremely limited; supposedly, it is given by the default provided in /etc/paths, but at least on my machine, it - * does not even include directories in that file for some reason. One way to add a directory like /usr/local/bin - * to - * the path once-and-for-all is this: - *

- * sudo launchctl config user path /usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin - *

- * But asking users to do that is not ideal. Hence, we try a more hack-y approach of just trying to execute using a - * bash shell. Also note that while ProcessBuilder can be configured to use custom environment variables, these - * variables do not affect the command that is to be executed but merely the environment in which the command - * executes. - * - * @param cmd The command - * @param args A list of arguments to pass to the command - * @param quiet If true, the commands stdout and stderr will be suppressed - * @param dir The directory in which the command should be executed - * @return Returns an LFCommand if the given command could be found or null otherwise. - */ - public static LFCommand get(final String cmd, final List args, boolean quiet, Path dir) { - assert cmd != null && args != null && dir != null; - dir = dir.toAbsolutePath(); - - // a list containing the command as first element and then all arguments - List cmdList = new ArrayList<>(); - cmdList.add(cmd); - cmdList.addAll(args); - - ProcessBuilder builder = null; - - // First, see if the command is a local executable file - final File cmdFile = dir.resolve(cmd).toFile(); - if (cmdFile.exists() && cmdFile.canExecute()) { - builder = new ProcessBuilder(cmdList); - } else if (findCommand(cmd) != null) { - builder = new ProcessBuilder(cmdList); - } else if (checkIfCommandIsExecutableWithBash(cmd, dir)) { - builder = new ProcessBuilder("bash", "--login", "-c", String.format("\"%s\"", String.join(" ", cmdList))); - } - - if (builder != null) { - builder.directory(dir.toFile()); - return new LFCommand(builder, quiet); - } - - return null; - } - - - /** - * Search for matches to the given command by following the PATH environment variable. - * @param command A command for which to search. - * @return The file locations of matches to the given command. - */ - private static List findCommand(final String command) { - final String whichCmd = System.getProperty("os.name").startsWith("Windows") ? "where" : "which"; - final ProcessBuilder whichBuilder = new ProcessBuilder(List.of(whichCmd, command)); - try { - Process p = whichBuilder.start(); - if (p.waitFor() != 0) return null; - return Arrays.stream(new String(p.getInputStream().readAllBytes()).split("\n")) - .map(String::strip).map(File::new).filter(File::canExecute).collect(Collectors.toList()); - } catch (InterruptedException | IOException e) { - e.printStackTrace(); - return null; + while (true) { + try { + return processBuilder.start(); + } catch (IOException e) { + if (commands.isEmpty()) { + e.printStackTrace(); + return null; } + } + processBuilder.command().set(0, commands.removeFirst()); } + } - /** - * Attempt to start the execution of this command. - * - * First collect a list of paths where the executable might be found, - * then select an executable that successfully executes from the - * list of paths. Return the {@code Process} instance that is the - * result of a successful execution, or {@code null} if no successful - * execution happened. - * @return The {@code Process} that is started by this command, or {@code null} in case of failure. - */ - private Process startProcess() { - ArrayDeque commands = new ArrayDeque<>(); - List matchesOnPath = findCommand(processBuilder.command().get(0)); - if (matchesOnPath != null) { - matchesOnPath.stream().map(File::toString).forEach(commands::addLast); - } - while (true) { - try { - return processBuilder.start(); - } catch (IOException e) { - if (commands.isEmpty()) { - e.printStackTrace(); - return null; - } - } - processBuilder.command().set(0, commands.removeFirst()); - } + private static boolean checkIfCommandIsExecutableWithBash(final String command, final Path dir) { + // check first if bash is installed + if (findCommand("bash") == null) { + return false; } - - private static boolean checkIfCommandIsExecutableWithBash(final String command, final Path dir) { - // check first if bash is installed - if (findCommand("bash") == null) { - return false; - } - - // then try to find command with bash - final ProcessBuilder bashBuilder = new ProcessBuilder(List.of( - "bash", - "--login", - "-c", - String.format("\"which %s\"", command) - )); - bashBuilder.directory(dir.toFile()); - try { - int bashReturn = bashBuilder.start().waitFor(); - return bashReturn == 0; - } catch (InterruptedException | IOException e) { - e.printStackTrace(); - return false; - } + // then try to find command with bash + final ProcessBuilder bashBuilder = + new ProcessBuilder( + List.of("bash", "--login", "-c", String.format("\"which %s\"", command))); + bashBuilder.directory(dir.toFile()); + try { + int bashReturn = bashBuilder.start().waitFor(); + return bashReturn == 0; + } catch (InterruptedException | IOException e) { + e.printStackTrace(); + return false; } + } } diff --git a/org.lflang/src/org/lflang/util/Pair.java b/org.lflang/src/org/lflang/util/Pair.java index 2de5923a30..a1fb07ac7b 100644 --- a/org.lflang/src/org/lflang/util/Pair.java +++ b/org.lflang/src/org/lflang/util/Pair.java @@ -1,21 +1,21 @@ package org.lflang.util; public class Pair { - private final F first; - private final S second; + private final F first; + private final S second; - public Pair(F first, S second) { - this.first = first; - this.second = second; - } + public Pair(F first, S second) { + this.first = first; + this.second = second; + } - public F getFirst() { - return first; - } + public F getFirst() { + return first; + } - public S getSecond() { - return second; - } + public S getSecond() { + return second; + } } -//TimeValue maxSTP = findMaxSTP(connection, coordination); \ No newline at end of file +// TimeValue maxSTP = findMaxSTP(connection, coordination); diff --git a/org.lflang/src/org/lflang/util/StringUtil.java b/org.lflang/src/org/lflang/util/StringUtil.java index 31beef8a9b..516f340c06 100644 --- a/org.lflang/src/org/lflang/util/StringUtil.java +++ b/org.lflang/src/org/lflang/util/StringUtil.java @@ -36,137 +36,133 @@ */ public final class StringUtil { - /** - * Matches the boundary of a camel-case word. That's a zero-length match. - */ - private static final Pattern CAMEL_WORD_BOUNDARY = - Pattern.compile("(? !it.isEmpty()) - .map(it -> it.toLowerCase(Locale.ROOT)) - .collect(Collectors.joining("_")); - } + /** + * Convert a string in Camel case to snake case. E.g. {@code MinimalReactor} will be converted to + * {@code minimal_reactor}. The string is assumed to be a single camel case identifier (no + * whitespace). + */ + public static String camelToSnakeCase(String str) { + return CAMEL_WORD_BOUNDARY + .splitAsStream(str) + .filter(it -> !it.isEmpty()) + .map(it -> it.toLowerCase(Locale.ROOT)) + .collect(Collectors.joining("_")); + } - /** - * If the given string is surrounded by single or double - * quotes, returns what's inside the quotes. Otherwise - * returns the same string. - * - *

Returns null if the parameter is null. - */ - public static String removeQuotes(String str) { - if (str == null) { - return null; - } - if (str.length() < 2) { - return str; - } - if (hasQuotes(str)) { - return str.substring(1, str.length() - 1); - } - return str; + /** + * If the given string is surrounded by single or double quotes, returns what's inside the quotes. + * Otherwise returns the same string. + * + *

Returns null if the parameter is null. + */ + public static String removeQuotes(String str) { + if (str == null) { + return null; + } + if (str.length() < 2) { + return str; + } + if (hasQuotes(str)) { + return str.substring(1, str.length() - 1); } + return str; + } - /** - * Return true if the given string is surrounded by single or double - * quotes, - */ - public static boolean hasQuotes(String str) { - if (str == null) { - return false; - } - return str.startsWith("\"") && str.endsWith("\"") || str.startsWith("'") && str.endsWith("'"); + /** Return true if the given string is surrounded by single or double quotes, */ + public static boolean hasQuotes(String str) { + if (str == null) { + return false; } + return str.startsWith("\"") && str.endsWith("\"") || str.startsWith("'") && str.endsWith("'"); + } - /** - * Intelligently trim the white space in a code block. - * - * The leading whitespaces of the first non-empty - * code line is considered as a common prefix across all code lines. If the - * remaining code lines indeed start with this prefix, it removes the prefix - * from the code line. - * - * For examples, this code - *

{@code
-     *        int test = 4;
-     *        if (test == 42) {
-     *            printf("Hello\n");
-     *        }
-     * }
- * will be trimmed to this: - *
{@code
-     * int test = 4;
-     * if (test == 42) {
-     *     printf("Hello\n");
-     * }
-     * }
- * - * @param code the code block to be trimmed - * @param firstLineToConsider The first line not to ignore. - * @return trimmed code block - */ - public static String trimCodeBlock(String code, int firstLineToConsider) { - String[] codeLines = code.split("(\r\n?)|\n"); - int prefix = getWhitespacePrefix(code, firstLineToConsider); - StringBuilder buffer = new StringBuilder(); - boolean stillProcessingLeadingBlankLines = true; - for (int i = 0; i < firstLineToConsider; i++) { - var endIndex = codeLines[i].contains("//") ? - codeLines[i].indexOf("//") : codeLines[i].length(); - // The following will break Rust attributes in multiline code blocks - // where they appear next to the opening {= brace. - endIndex = codeLines[i].contains("#") ? - Math.min(endIndex, codeLines[i].indexOf("#")) : endIndex; - String toAppend = codeLines[i].substring(0, endIndex).strip(); - if (!toAppend.isBlank()) buffer.append(toAppend).append("\n"); - } - for (int i = firstLineToConsider; i < codeLines.length; i++) { - final String line = codeLines[i]; - if (!line.isBlank()) stillProcessingLeadingBlankLines = false; - if (stillProcessingLeadingBlankLines) continue; - if (!line.isBlank()) buffer.append(line.substring(prefix)); - buffer.append("\n"); - } - return buffer.toString().stripTrailing(); + /** + * Intelligently trim the white space in a code block. + * + *

The leading whitespaces of the first non-empty code line is considered as a common prefix + * across all code lines. If the remaining code lines indeed start with this prefix, it removes + * the prefix from the code line. + * + *

For examples, this code + * + *

{@code
+   * int test = 4;
+   * if (test == 42) {
+   *     printf("Hello\n");
+   * }
+   * }
+ * + * will be trimmed to this: + * + *
{@code
+   * int test = 4;
+   * if (test == 42) {
+   *     printf("Hello\n");
+   * }
+   * }
+ * + * @param code the code block to be trimmed + * @param firstLineToConsider The first line not to ignore. + * @return trimmed code block + */ + public static String trimCodeBlock(String code, int firstLineToConsider) { + String[] codeLines = code.split("(\r\n?)|\n"); + int prefix = getWhitespacePrefix(code, firstLineToConsider); + StringBuilder buffer = new StringBuilder(); + boolean stillProcessingLeadingBlankLines = true; + for (int i = 0; i < firstLineToConsider; i++) { + var endIndex = + codeLines[i].contains("//") ? codeLines[i].indexOf("//") : codeLines[i].length(); + // The following will break Rust attributes in multiline code blocks + // where they appear next to the opening {= brace. + endIndex = + codeLines[i].contains("#") ? Math.min(endIndex, codeLines[i].indexOf("#")) : endIndex; + String toAppend = codeLines[i].substring(0, endIndex).strip(); + if (!toAppend.isBlank()) buffer.append(toAppend).append("\n"); + } + for (int i = firstLineToConsider; i < codeLines.length; i++) { + final String line = codeLines[i]; + if (!line.isBlank()) stillProcessingLeadingBlankLines = false; + if (stillProcessingLeadingBlankLines) continue; + if (!line.isBlank()) buffer.append(line.substring(prefix)); + buffer.append("\n"); } + return buffer.toString().stripTrailing(); + } - private static int getWhitespacePrefix(String code, int firstLineToConsider) { - String[] codeLines = code.split("(\r\n?)|\n"); - int minLength = Integer.MAX_VALUE; - for (int j = firstLineToConsider; j < codeLines.length; j++) { - String line = codeLines[j]; - for (var i = 0; i < line.length(); i++) { - if (!Character.isWhitespace(line.charAt(i))) { - minLength = Math.min(minLength, i); - break; - } - } + private static int getWhitespacePrefix(String code, int firstLineToConsider) { + String[] codeLines = code.split("(\r\n?)|\n"); + int minLength = Integer.MAX_VALUE; + for (int j = firstLineToConsider; j < codeLines.length; j++) { + String line = codeLines[j]; + for (var i = 0; i < line.length(); i++) { + if (!Character.isWhitespace(line.charAt(i))) { + minLength = Math.min(minLength, i); + break; } - return minLength == Integer.MAX_VALUE ? 0 : minLength; + } } + return minLength == Integer.MAX_VALUE ? 0 : minLength; + } - public static String addDoubleQuotes(String str) { - return "\""+str+"\""; - } + public static String addDoubleQuotes(String str) { + return "\"" + str + "\""; + } - public static String joinObjects(List things, String delimiter) { - return things.stream().map(T::toString).collect(Collectors.joining(delimiter)); - } + public static String joinObjects(List things, String delimiter) { + return things.stream().map(T::toString).collect(Collectors.joining(delimiter)); + } - /** Normalize end-of-line sequences to the Linux style. */ - public static String normalizeEol(String s) { - return s.replaceAll("(\\r\\n?)|\\n", "\n"); - } + /** Normalize end-of-line sequences to the Linux style. */ + public static String normalizeEol(String s) { + return s.replaceAll("(\\r\\n?)|\\n", "\n"); + } } diff --git a/org.lflang/src/org/lflang/util/TargetResourceNotFoundException.java b/org.lflang/src/org/lflang/util/TargetResourceNotFoundException.java index dfcc1d671a..acc55fb0f3 100644 --- a/org.lflang/src/org/lflang/util/TargetResourceNotFoundException.java +++ b/org.lflang/src/org/lflang/util/TargetResourceNotFoundException.java @@ -3,12 +3,15 @@ import java.io.IOException; public class TargetResourceNotFoundException extends IOException { - public TargetResourceNotFoundException(String resourcePath) { - super(String.format(""" + public TargetResourceNotFoundException(String resourcePath) { + super( + String.format( + """ A required resource could not be found on the classpath or is an empty directory: %s Perhaps a git submodule is missing or not up to date. Try running 'git submodule update --init --recursive' and rebuild. - Also see https://www.lf-lang.org/docs/handbook/developer-setup. - """, resourcePath)); - } + Also see https://www.lf-lang.org/docs/handbook/developer-setup. + """, + resourcePath)); + } } diff --git a/org.lflang/src/org/lflang/validation/AttributeSpec.java b/org.lflang/src/org/lflang/validation/AttributeSpec.java index 59c69c255f..71ec56e53c 100644 --- a/org.lflang/src/org/lflang/validation/AttributeSpec.java +++ b/org.lflang/src/org/lflang/validation/AttributeSpec.java @@ -1,16 +1,16 @@ /************* * 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 @@ -31,7 +31,6 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; - import org.lflang.ast.ASTUtils; import org.lflang.lf.AttrParm; import org.lflang.lf.Attribute; @@ -40,192 +39,189 @@ /** * Specification of the structure of an attribute annotation. + * * @author Clément Fournier * @author Shaokai Lin */ public class AttributeSpec { - private final Map paramSpecByName; + private final Map paramSpecByName; - public static final String VALUE_ATTR = "value"; - public static final String NETWORK_MESSAGE_ACTIONS = "network_message_actions"; - public static final String DEPENDENCY_PAIRS = "dependencyPairs"; - public static final String EACH_ATTR = "each"; + public static final String VALUE_ATTR = "value"; + public static final String NETWORK_MESSAGE_ACTIONS = "network_message_actions"; + public static final String DEPENDENCY_PAIRS = "dependencyPairs"; + public static final String EACH_ATTR = "each"; - /** A map from a string to a supported AttributeSpec */ - public static final Map ATTRIBUTE_SPECS_BY_NAME = new HashMap<>(); + /** A map from a string to a supported AttributeSpec */ + public static final Map ATTRIBUTE_SPECS_BY_NAME = new HashMap<>(); - public AttributeSpec(List params) { - if (params != null) { - paramSpecByName = params.stream().collect(Collectors.toMap(it -> it.name, it -> it)); - } else { - paramSpecByName = null; - } + public AttributeSpec(List params) { + if (params != null) { + paramSpecByName = params.stream().collect(Collectors.toMap(it -> it.name, it -> it)); + } else { + paramSpecByName = null; + } + } + + /** Check that the attribute conforms to this spec and whether attr has the correct name. */ + public void check(LFValidator validator, Attribute attr) { + Set seen; + // If there is just one parameter, it is required to be named "value". + if (attr.getAttrParms() != null + && attr.getAttrParms().size() == 1 + && attr.getAttrParms().get(0).getName() == null) { + // If we are in this branch, + // then the user has provided @attr("value"), + // which is a shorthand for @attr(value="value"). + if (paramSpecByName == null) { + validator.error("Attribute doesn't take a parameter.", Literals.ATTRIBUTE__ATTR_NAME); + return; + } + AttrParamSpec valueSpec = paramSpecByName.get(VALUE_ATTR); + if (valueSpec == null) { + validator.error("Attribute doesn't have a 'value' parameter.", Literals.ATTR_PARM__NAME); + return; + } + + valueSpec.check(validator, attr.getAttrParms().get(0)); + seen = Set.of(VALUE_ATTR); + } else { + // Process multiple parameters, each of which has to be named. + seen = processNamedAttrParms(validator, attr); } - /** - * Check that the attribute conforms to this spec and whether - * attr has the correct name. - */ - public void check(LFValidator validator, Attribute attr) { - Set seen; - // If there is just one parameter, it is required to be named "value". - if (attr.getAttrParms() != null - && attr.getAttrParms().size() == 1 - && attr.getAttrParms().get(0).getName() == null) { - // If we are in this branch, - // then the user has provided @attr("value"), - // which is a shorthand for @attr(value="value"). - if (paramSpecByName == null) { - validator.error("Attribute doesn't take a parameter.", Literals.ATTRIBUTE__ATTR_NAME); - return; + // Check if there are any missing parameters required by this attribute. + if (paramSpecByName != null) { + Map missingParams = new HashMap<>(paramSpecByName); + missingParams.keySet().removeAll(seen); + missingParams.forEach( + (name, paramSpec) -> { + if (!paramSpec.isOptional) { + validator.error( + "Missing required attribute parameter '" + name + "'.", + Literals.ATTRIBUTE__ATTR_PARMS); } - AttrParamSpec valueSpec = paramSpecByName.get(VALUE_ATTR); - if (valueSpec == null) { - validator.error("Attribute doesn't have a 'value' parameter.", Literals.ATTR_PARM__NAME); - return; - } - - valueSpec.check(validator, attr.getAttrParms().get(0)); - seen = Set.of(VALUE_ATTR); - } else { - // Process multiple parameters, each of which has to be named. - seen = processNamedAttrParms(validator, attr); + }); + } + } + + /** + * Check whether the attribute parameters are named, whether these names are known, and whether + * the named parameters conform to the param spec (whether the param has the right type, etc.). + * + * @param validator The current validator in use. + * @param attr The attribute being checked. + * @return A set of named attribute parameters the user provides. + */ + private Set processNamedAttrParms(LFValidator validator, Attribute attr) { + Set seen = new HashSet<>(); + if (attr.getAttrParms() != null) { + for (AttrParm parm : attr.getAttrParms()) { + if (paramSpecByName == null) { + validator.error("Attribute does not take parameters.", Literals.ATTRIBUTE__ATTR_NAME); + break; } - - // Check if there are any missing parameters required by this attribute. - if (paramSpecByName != null) { - Map missingParams = new HashMap<>(paramSpecByName); - missingParams.keySet().removeAll(seen); - missingParams.forEach((name, paramSpec) -> { - if (!paramSpec.isOptional) { - validator.error("Missing required attribute parameter '" + name + "'.", Literals.ATTRIBUTE__ATTR_PARMS); - } - }); + if (parm.getName() == null) { + validator.error("Missing name for attribute parameter.", Literals.ATTRIBUTE__ATTR_NAME); + continue; } - } - /** - * Check whether the attribute parameters are named, whether - * these names are known, and whether the named parameters - * conform to the param spec (whether the param has the - * right type, etc.). - * - * @param validator The current validator in use. - * @param attr The attribute being checked. - * @return A set of named attribute parameters the user provides. - */ - private Set processNamedAttrParms(LFValidator validator, Attribute attr) { - Set seen = new HashSet<>(); - if (attr.getAttrParms() != null) { - for (AttrParm parm : attr.getAttrParms()) { - if (paramSpecByName == null) { - validator.error("Attribute does not take parameters.", Literals.ATTRIBUTE__ATTR_NAME); - break; - } - if (parm.getName() == null) { - validator.error("Missing name for attribute parameter.", Literals.ATTRIBUTE__ATTR_NAME); - continue; - } - - AttrParamSpec parmSpec = paramSpecByName.get(parm.getName()); - if (parmSpec == null) { - validator.error("\"" + parm.getName() + "\"" + " is an unknown attribute parameter.", - Literals.ATTRIBUTE__ATTR_NAME); - continue; - } - // Check whether a parameter conforms to its spec. - parmSpec.check(validator, parm); - seen.add(parm.getName()); - } + AttrParamSpec parmSpec = paramSpecByName.get(parm.getName()); + if (parmSpec == null) { + validator.error( + "\"" + parm.getName() + "\"" + " is an unknown attribute parameter.", + Literals.ATTRIBUTE__ATTR_NAME); + continue; } - return seen; + // Check whether a parameter conforms to its spec. + parmSpec.check(validator, parm); + seen.add(parm.getName()); + } } - - /** - * The specification of the attribute parameter. - * - * @param name The name of the attribute parameter - * @param type The type of the parameter - * @param isOptional True if the parameter is optional. - */ - record AttrParamSpec(String name, AttrParamType type, boolean isOptional) { - - // Check if a parameter has the right type. - // Currently, only String, Int, Boolean, Float, and target language are supported. - public void check(LFValidator validator, AttrParm parm) { - switch (type) { - case STRING -> { - if (!StringUtil.hasQuotes(parm.getValue())) { - validator.error("Incorrect type: \"" + parm.getName() + "\"" - + " should have type String.", - Literals.ATTRIBUTE__ATTR_NAME); - } - } - case INT -> { - if (!ASTUtils.isInteger(parm.getValue())) { - validator.error( - "Incorrect type: \"" + parm.getName() + "\"" - + " should have type Int.", - Literals.ATTRIBUTE__ATTR_NAME); - } - } - case BOOLEAN -> { - if (!ASTUtils.isBoolean(parm.getValue())) { - validator.error( - "Incorrect type: \"" + parm.getName() + "\"" - + " should have type Boolean.", - Literals.ATTRIBUTE__ATTR_NAME); - } - } - case FLOAT -> { - if (!ASTUtils.isFloat(parm.getValue())) { - validator.error( - "Incorrect type: \"" + parm.getName() + "\"" - + " should have type Float.", - Literals.ATTRIBUTE__ATTR_NAME); - } - } - default -> throw new IllegalArgumentException("unexpected type"); - } + return seen; + } + + /** + * The specification of the attribute parameter. + * + * @param name The name of the attribute parameter + * @param type The type of the parameter + * @param isOptional True if the parameter is optional. + */ + record AttrParamSpec(String name, AttrParamType type, boolean isOptional) { + + // Check if a parameter has the right type. + // Currently, only String, Int, Boolean, Float, and target language are supported. + public void check(LFValidator validator, AttrParm parm) { + switch (type) { + case STRING -> { + if (!StringUtil.hasQuotes(parm.getValue())) { + validator.error( + "Incorrect type: \"" + parm.getName() + "\"" + " should have type String.", + Literals.ATTRIBUTE__ATTR_NAME); + } } + case INT -> { + if (!ASTUtils.isInteger(parm.getValue())) { + validator.error( + "Incorrect type: \"" + parm.getName() + "\"" + " should have type Int.", + Literals.ATTRIBUTE__ATTR_NAME); + } + } + case BOOLEAN -> { + if (!ASTUtils.isBoolean(parm.getValue())) { + validator.error( + "Incorrect type: \"" + parm.getName() + "\"" + " should have type Boolean.", + Literals.ATTRIBUTE__ATTR_NAME); + } + } + case FLOAT -> { + if (!ASTUtils.isFloat(parm.getValue())) { + validator.error( + "Incorrect type: \"" + parm.getName() + "\"" + " should have type Float.", + Literals.ATTRIBUTE__ATTR_NAME); + } + } + default -> throw new IllegalArgumentException("unexpected type"); + } } - - /** - * The type of attribute parameters currently supported. - */ - enum AttrParamType { - STRING, - INT, - BOOLEAN, - FLOAT, - } - - /* - * The specs of the known annotations are declared here. - * Note: If an attribute only has one parameter, the parameter name should be "value." - */ - static { - // @label("value") - ATTRIBUTE_SPECS_BY_NAME.put("label", new AttributeSpec( - List.of(new AttrParamSpec(VALUE_ATTR, AttrParamType.STRING, false)) - )); - // @sparse - ATTRIBUTE_SPECS_BY_NAME.put("sparse", new AttributeSpec(null)); - // @icon("value") - ATTRIBUTE_SPECS_BY_NAME.put("icon", new AttributeSpec( - List.of(new AttrParamSpec(VALUE_ATTR, AttrParamType.STRING, false)) - )); - // @enclave(each=boolean) - ATTRIBUTE_SPECS_BY_NAME.put("enclave", new AttributeSpec( - List.of(new AttrParamSpec(EACH_ATTR, AttrParamType.BOOLEAN, true)) - )); - - ATTRIBUTE_SPECS_BY_NAME.put("_fed_config", new AttributeSpec( - List.of(new AttrParamSpec(AttributeSpec.NETWORK_MESSAGE_ACTIONS, - AttrParamType.STRING, false), - new AttrParamSpec(AttributeSpec.DEPENDENCY_PAIRS, - AttrParamType.STRING, false)))); - ATTRIBUTE_SPECS_BY_NAME.put("_c_body", new AttributeSpec(null)); - } + } + + /** The type of attribute parameters currently supported. */ + enum AttrParamType { + STRING, + INT, + BOOLEAN, + FLOAT, + } + + /* + * The specs of the known annotations are declared here. + * Note: If an attribute only has one parameter, the parameter name should be "value." + */ + static { + // @label("value") + ATTRIBUTE_SPECS_BY_NAME.put( + "label", + new AttributeSpec(List.of(new AttrParamSpec(VALUE_ATTR, AttrParamType.STRING, false)))); + // @sparse + ATTRIBUTE_SPECS_BY_NAME.put("sparse", new AttributeSpec(null)); + // @icon("value") + ATTRIBUTE_SPECS_BY_NAME.put( + "icon", + new AttributeSpec(List.of(new AttrParamSpec(VALUE_ATTR, AttrParamType.STRING, false)))); + // @enclave(each=boolean) + ATTRIBUTE_SPECS_BY_NAME.put( + "enclave", + new AttributeSpec(List.of(new AttrParamSpec(EACH_ATTR, AttrParamType.BOOLEAN, true)))); + + ATTRIBUTE_SPECS_BY_NAME.put( + "_fed_config", + new AttributeSpec( + List.of( + new AttrParamSpec( + AttributeSpec.NETWORK_MESSAGE_ACTIONS, AttrParamType.STRING, false), + new AttrParamSpec(AttributeSpec.DEPENDENCY_PAIRS, AttrParamType.STRING, false)))); + ATTRIBUTE_SPECS_BY_NAME.put("_c_body", new AttributeSpec(null)); + } } diff --git a/org.lflang/src/org/lflang/validation/BaseLFValidator.java b/org.lflang/src/org/lflang/validation/BaseLFValidator.java index 34e184a720..bcdc14dd94 100644 --- a/org.lflang/src/org/lflang/validation/BaseLFValidator.java +++ b/org.lflang/src/org/lflang/validation/BaseLFValidator.java @@ -28,55 +28,53 @@ import java.lang.reflect.Method; import java.util.Map; - import org.eclipse.emf.common.util.DiagnosticChain; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.validation.Check; import org.eclipse.xtext.validation.CheckMode; import org.eclipse.xtext.validation.CheckType; -import org.lflang.ast.ASTUtils; import org.lflang.TimeUnit; +import org.lflang.ast.ASTUtils; import org.lflang.lf.LfPackage.Literals; import org.lflang.lf.Time; public class BaseLFValidator extends AbstractLFValidator { - @Check(CheckType.FAST) - public void checkTime(Time time) { - if (!ASTUtils.isValidTime(time)) { - error("Invalid time unit. " + - "Should be one of " + - TimeUnit.list() + ".", Literals.TIME__UNIT); - } + @Check(CheckType.FAST) + public void checkTime(Time time) { + if (!ASTUtils.isValidTime(time)) { + error( + "Invalid time unit. " + "Should be one of " + TimeUnit.list() + ".", Literals.TIME__UNIT); } + } - /** - * Provides convenient access to the inner state of the validator. - *

- * The validator only gives protected access to its own state. With - * this class, we can grant access to the inner state to other objects. - * - * @author Christian Menard - */ - protected class ValidatorStateAccess { - public EObject getCurrentObject() { - return BaseLFValidator.this.getCurrentObject(); - } + /** + * Provides convenient access to the inner state of the validator. + * + *

The validator only gives protected access to its own state. With this class, we can grant + * access to the inner state to other objects. + * + * @author Christian Menard + */ + protected class ValidatorStateAccess { + public EObject getCurrentObject() { + return BaseLFValidator.this.getCurrentObject(); + } - public Method getCurrentMethod() { - return BaseLFValidator.this.getCurrentMethod(); - } + public Method getCurrentMethod() { + return BaseLFValidator.this.getCurrentMethod(); + } - public DiagnosticChain getChain() { - return BaseLFValidator.this.getChain(); - } + public DiagnosticChain getChain() { + return BaseLFValidator.this.getChain(); + } - public CheckMode getCheckMode() { - return BaseLFValidator.this.getCheckMode(); - } + public CheckMode getCheckMode() { + return BaseLFValidator.this.getCheckMode(); + } - public Map getContext() { - return BaseLFValidator.this.getContext(); - } + public Map getContext() { + return BaseLFValidator.this.getContext(); } + } } diff --git a/org.lflang/src/org/lflang/validation/LFNamesAreUniqueValidationHelper.java b/org.lflang/src/org/lflang/validation/LFNamesAreUniqueValidationHelper.java index 75020fdc2b..9c6267cb0a 100644 --- a/org.lflang/src/org/lflang/validation/LFNamesAreUniqueValidationHelper.java +++ b/org.lflang/src/org/lflang/validation/LFNamesAreUniqueValidationHelper.java @@ -2,27 +2,24 @@ import org.eclipse.emf.ecore.EClass; import org.eclipse.xtext.validation.NamesAreUniqueValidationHelper; - import org.lflang.lf.LfPackage; public class LFNamesAreUniqueValidationHelper extends NamesAreUniqueValidationHelper { - /** - * Lump all inputs, outputs, timers, actions, parameters, and - * instantiations in the same cluster type. This ensures that - * names amongst them are checked for uniqueness. - */ - @Override public EClass getAssociatedClusterType(EClass eClass) { - if (LfPackage.Literals.INPUT == eClass || - LfPackage.Literals.OUTPUT == eClass || - LfPackage.Literals.TIMER == eClass || - LfPackage.Literals.ACTION == eClass || - LfPackage.Literals.PARAMETER == eClass || - LfPackage.Literals.INSTANTIATION == eClass - ) { - return LfPackage.Literals.VARIABLE; - } - return super.getAssociatedClusterType(eClass); + /** + * Lump all inputs, outputs, timers, actions, parameters, and instantiations in the same cluster + * type. This ensures that names amongst them are checked for uniqueness. + */ + @Override + public EClass getAssociatedClusterType(EClass eClass) { + if (LfPackage.Literals.INPUT == eClass + || LfPackage.Literals.OUTPUT == eClass + || LfPackage.Literals.TIMER == eClass + || LfPackage.Literals.ACTION == eClass + || LfPackage.Literals.PARAMETER == eClass + || LfPackage.Literals.INSTANTIATION == eClass) { + return LfPackage.Literals.VARIABLE; } - + return super.getAssociatedClusterType(eClass); + } } diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 36c09cf678..747b22ffc3 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.ast.ASTUtils.inferPortWidth; import static org.lflang.ast.ASTUtils.isGeneric; -import static org.lflang.ast.ASTUtils.isInteger; -import static org.lflang.ast.ASTUtils.isOfTimeType; -import static org.lflang.ast.ASTUtils.isZero; import static org.lflang.ast.ASTUtils.toDefinition; import static org.lflang.ast.ASTUtils.toOriginalText; +import com.google.inject.Inject; import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -46,7 +44,6 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; - import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.ecore.EAttribute; @@ -56,13 +53,13 @@ import org.eclipse.xtext.validation.Check; import org.eclipse.xtext.validation.CheckType; import org.eclipse.xtext.validation.ValidationMessageAcceptor; -import org.lflang.ast.ASTUtils; import org.lflang.AttributeUtils; import org.lflang.InferredType; import org.lflang.ModelInfo; import org.lflang.Target; import org.lflang.TargetProperty; import org.lflang.TimeValue; +import org.lflang.ast.ASTUtils; import org.lflang.federated.serialization.SupportedSerializers; import org.lflang.federated.validation.FedValidator; import org.lflang.generator.NamedInstance; @@ -117,12 +114,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 @@ -133,1763 +128,1853 @@ */ 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 {@code logical} or {@code 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 {@code logical} or {@code 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); - } - } - } - } - } - - // 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 : (Iterable) () -> Stream.concat( - connection.getLeftPorts().stream(), - connection.getRightPorts().stream() - ).iterator()) { - // 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 { - var portType = ((Port) port.getVariable()).getType(); - portType = port.getContainer() == null ? portType : new TypeParameterizedReactor(port.getContainer()).resolveType(portType); - if (!sameType(type, portType)) { - error("Types do not match.", Literals.CONNECTION__LEFT_PORTS); - } - } - } - } - } + @Check(CheckType.FAST) + public void checkConnection(Connection connection) { - // 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; - } - } + // 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; - 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); - } - } - } + 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 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); - } - } - } + 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 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); + // 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 : + (Iterable) + () -> + Stream.concat( + connection.getLeftPorts().stream(), connection.getRightPorts().stream()) + .iterator()) { + // 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 { + var portType = ((Port) port.getVariable()).getType(); + portType = + port.getContainer() == null + ? portType + : new TypeParameterizedReactor(port.getContainer()).resolveType(portType); + if (!sameType(type, portType)) { + error("Types do not match.", Literals.CONNECTION__LEFT_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; + } } - - @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 - ); - } + 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 - 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); + 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 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); - } + 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(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 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); } + } } - - // 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 - ); - } + // 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); + } + } - // 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 + 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 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); - } + // FIXME: report error if resource cannot be resolved. + for (ImportedReactor reactor : imp.getReactorClasses()) { + if (!isUnused(reactor)) { + return; + } + } + warning("Unused import.", Literals.IMPORT__IMPORT_URI); + } - // Report problem with the assigned value. - prop.type.check(param.getValue(), param.getName(), this); - } + @Check + public void checkImportedReactor(ImportedReactor reactor) { + if (isUnused(reactor)) { + warning("Unused reactor class.", Literals.IMPORTED_REACTOR__REACTOR_CLASS); + } - for (String it : targetPropertyErrors) { - error(it, Literals.KEY_VALUE_PAIR__VALUE); - } - targetPropertyErrors.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); + } + } + } - for (String it : targetPropertyWarnings) { - error(it, Literals.KEY_VALUE_PAIR__VALUE); - } - targetPropertyWarnings.clear(); - } + @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.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); - } + // 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.NORMAL) - public void updateModelInfo(Model model) { - info.update(model, errorReporter); + // 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); } - @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); - } - } + // 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()); + } - // 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); + 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); - - 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; - } + // 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 (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 checkParameter(Parameter param) { + checkName(param.getName(), Literals.PARAMETER__NAME); - if (param.getType() != null) { - typeCheck(param.getInit(), ASTUtils.getInferredType(param), Literals.PARAMETER__INIT); - } + 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.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.requiresTypes) { + // Report missing target type. param.inferredType.undefine + if (ASTUtils.getInferredType(param).isUndefined()) { + error("Type declaration missing.", Literals.PARAMETER__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); - } - } - } + if (param.getType() != null) { + typeCheck(param.getInit(), ASTUtils.getInferredType(param), 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 (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); + } + } } - @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); - } - } - } - } - - // 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); - } + @Check(CheckType.FAST) + public void checkReaction(Reaction reaction) { - // 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); } - - public void checkReactorName(String name) throws IOException { - // Check for illegal names. - checkName(name, Literals.REACTOR_DECL__NAME); - - // C++ reactors may not be called 'preamble' - if (this.target == Target.CPP && name.equalsIgnoreCase("preamble")) { + 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( - "Reactor cannot be named '" + name + "'", - Literals.REACTOR_DECL__NAME - ); + 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 checkReactor(Reactor reactor) throws IOException { - String fileName = FileUtil.nameWithoutExtension(reactor.eResource()); - - if (reactor.isFederated() || reactor.isMain()) { - // Do not allow multiple main/federated reactors. - TreeIterator iter = reactor.eResource().getAllContents(); - 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 - ); - } - - if(reactor.getName() != null && !reactor.getName().equals(fileName)) { - // 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 - ); - } - - // check the reactor name indicated by the file name - // Skip this check if the file is named __synthetic0. This Name is used during testing, - // and we would get an unexpected error due to the '__' prefix otherwise. - if (!fileName.equals("__synthetic0")) { - checkReactorName(fileName); - } - } else { - // Not federated or main. - if (reactor.getName() == null) { - error( - "Reactor must be named.", - Literals.REACTOR_DECL__NAME - ); - } else { - checkReactorName(reactor.getName()); - - TreeIterator iter = reactor.eResource().getAllContents(); - int nMain = countMainOrFederated(iter); - if (nMain > 0 && reactor.getName().equals(fileName)) { - error( - "Name conflict with main reactor.", - Literals.REACTOR_DECL__NAME - ); - } - } - } + // 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); + } + } + } - 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<>(); + // // 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 (reactor.getHost() != null) { - if (!reactor.isFederated()) { - error( - "Cannot assign a host to reactor '" + reactor.getName() + - "' because it is not federated.", - Literals.REACTOR__HOST - ); - } + 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; + } } - - List variables = new ArrayList<>(); - variables.addAll(reactor.getInputs()); - variables.addAll(reactor.getOutputs()); - variables.addAll(reactor.getActions()); - variables.addAll(reactor.getTimers()); - - if (!reactor.getSuperClasses().isEmpty() && !target.supportsInheritance()) { - error("The " + target.getDisplayName() + " target does not support reactor inheritance.", - Literals.REACTOR__SUPER_CLASSES); + 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. + } - // 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 - ); - } - } + public void checkReactorName(String name) throws IOException { + // Check for illegal names. + checkName(name, Literals.REACTOR_DECL__NAME); + // C++ reactors may not be called 'preamble' + if (this.target == Target.CPP && name.equalsIgnoreCase("preamble")) { + error("Reactor cannot be named '" + name + "'", Literals.REACTOR_DECL__NAME); + } + } + + @Check(CheckType.FAST) + public void checkReactor(Reactor reactor) throws IOException { + String fileName = FileUtil.nameWithoutExtension(reactor.eResource()); + + if (reactor.isFederated() || reactor.isMain()) { + // Do not allow multiple main/federated reactors. + TreeIterator iter = reactor.eResource().getAllContents(); + int nMain = countMainOrFederated(iter); + if (nMain > 1) { + EAttribute attribute = Literals.REACTOR__MAIN; if (reactor.isFederated()) { - if (!target.supportsFederated()) { - error("The " + target.getDisplayName() + " target does not support federated execution.", - Literals.REACTOR__FEDERATED); - } + attribute = Literals.REACTOR__FEDERATED; + } + error("Multiple definitions of main or federated reactor.", attribute); + } + + if (reactor.getName() != null && !reactor.getName().equals(fileName)) { + // 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); + } + + // check the reactor name indicated by the file name + // Skip this check if the file is named __synthetic0. This Name is used during testing, + // and we would get an unexpected error due to the '__' prefix otherwise. + if (!fileName.equals("__synthetic0")) { + checkReactorName(fileName); + } + } else { + // Not federated or main. + if (reactor.getName() == null) { + error("Reactor must be named.", Literals.REACTOR_DECL__NAME); + } else { + checkReactorName(reactor.getName()); + + TreeIterator iter = reactor.eResource().getAllContents(); + int nMain = countMainOrFederated(iter); + if (nMain > 0 && reactor.getName().equals(fileName)) { + error("Name conflict with main reactor.", Literals.REACTOR_DECL__NAME); + } + } + } - FedValidator.validateFederatedReactor(reactor, this.errorReporter); - } + 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 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 (reactor.getHost() != null) { + if (!reactor.isFederated()) { + error( + "Cannot assign a host to reactor '" + + reactor.getName() + + "' because it is not federated.", + Literals.REACTOR__HOST); + } + } - if (!isValidSerializer) { - error( - "Serializer can be " + Arrays.asList(SupportedSerializers.values()), - Literals.SERIALIZER__TYPE - ); - } + List variables = new ArrayList<>(); + variables.addAll(reactor.getInputs()); + variables.addAll(reactor.getOutputs()); + variables.addAll(reactor.getActions()); + variables.addAll(reactor.getTimers()); + + if (!reactor.getSuperClasses().isEmpty() && !target.supportsInheritance()) { + error( + "The " + target.getDisplayName() + " target does not support reactor inheritance.", + Literals.REACTOR__SUPER_CLASSES); } - @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); - } + // 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()) { + if (!target.supportsFederated()) { + error( + "The " + target.getDisplayName() + " target does not support federated execution.", + Literals.REACTOR__FEDERATED); + } - 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); - } + 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; - } - } + assert (properties.size() <= 1); + return properties.size() > 0 ? properties.get(0) : null; + } - // 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 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 {@code 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 {@code widthof()} are not supported (yet?). + // This feature is currently only used for after delays. + error("widthof is not supported.", Literals.WIDTH_SPEC__TERMS); + } else if (term.getCode() != null) { + if (this.target != Target.CPP) { + error("This target does not support width given as code.", Literals.WIDTH_SPEC__TERMS); + } + } else if (term.getWidth() < 0) { + error("Width must be a positive integer.", Literals.WIDTH_SPEC__TERMS); } + } } - - @Check(CheckType.FAST) - public void checkReactorIconAttribute(Reactor reactor) { - var path = AttributeUtils.getIconPath(reactor); - if (path != null) { - var param = AttributeUtils.findAttributeByName(reactor, "icon").getAttrParms().get(0); - // Check file extension - var validExtensions = Set.of("bmp", "png", "gif", "ico", "jpeg"); - var extensionStrart = path.lastIndexOf("."); - var extension = extensionStrart != -1 ? path.substring(extensionStrart + 1) : ""; - if (!validExtensions.contains(extension.toLowerCase())) { - warning("File extension '" + extension + "' is not supported. Provide any of: " + String.join(", ", validExtensions), - param, Literals.ATTR_PARM__VALUE); - return; - } - - // Check file location - var iconLocation = FileUtil.locateFile(path, reactor.eResource()); - if (iconLocation == null) { - warning("Cannot locate icon file.", param, Literals.ATTR_PARM__VALUE); - } - if (("file".equals(iconLocation.getScheme()) || iconLocation.getScheme() == null) && !(new File(iconLocation.getPath()).exists())) { - warning("Icon does not exist.", param, Literals.ATTR_PARM__VALUE); - } - } + } + + @Check(CheckType.FAST) + public void checkReactorIconAttribute(Reactor reactor) { + var path = AttributeUtils.getIconPath(reactor); + if (path != null) { + var param = AttributeUtils.findAttributeByName(reactor, "icon").getAttrParms().get(0); + // Check file extension + var validExtensions = Set.of("bmp", "png", "gif", "ico", "jpeg"); + var extensionStrart = path.lastIndexOf("."); + var extension = extensionStrart != -1 ? path.substring(extensionStrart + 1) : ""; + if (!validExtensions.contains(extension.toLowerCase())) { + warning( + "File extension '" + + extension + + "' is not supported. Provide any of: " + + String.join(", ", validExtensions), + param, + Literals.ATTR_PARM__VALUE); + return; + } + + // Check file location + var iconLocation = FileUtil.locateFile(path, reactor.eResource()); + if (iconLocation == null) { + warning("Cannot locate icon file.", param, Literals.ATTR_PARM__VALUE); + } + if (("file".equals(iconLocation.getScheme()) || iconLocation.getScheme() == null) + && !(new File(iconLocation.getPath()).exists())) { + warning("Icon does not exist.", param, Literals.ATTR_PARM__VALUE); + } } - - @Check(CheckType.FAST) - public void checkInitialMode(Reactor reactor) { - if (!reactor.getModes().isEmpty()) { - long initialModesCount = reactor.getModes().stream().filter(m -> m.isInitial()).count(); - if (initialModesCount == 0) { - error("Every modal reactor requires one initial mode.", Literals.REACTOR__MODES, 0); - } else if (initialModesCount > 1) { - reactor.getModes().stream().filter(m -> m.isInitial()).skip(1).forEach(m -> { - error("A modal reactor can only have one initial mode.", - Literals.REACTOR__MODES, reactor.getModes().indexOf(m)); + } + + @Check(CheckType.FAST) + public void checkInitialMode(Reactor reactor) { + if (!reactor.getModes().isEmpty()) { + long initialModesCount = reactor.getModes().stream().filter(m -> m.isInitial()).count(); + if (initialModesCount == 0) { + error("Every modal reactor requires one initial mode.", Literals.REACTOR__MODES, 0); + } else if (initialModesCount > 1) { + reactor.getModes().stream() + .filter(m -> m.isInitial()) + .skip(1) + .forEach( + m -> { + error( + "A modal reactor can only have one initial mode.", + Literals.REACTOR__MODES, + reactor.getModes().indexOf(m)); }); - } + } + } + } + + @Check(CheckType.FAST) + public void checkModeStateNamespace(Reactor reactor) { + if (!reactor.getModes().isEmpty()) { + var names = new ArrayList(); + reactor.getStateVars().stream().map(it -> it.getName()).forEach(it -> names.add(it)); + for (var mode : reactor.getModes()) { + for (var stateVar : mode.getStateVars()) { + if (names.contains(stateVar.getName())) { + error( + String.format( + "Duplicate state variable '%s'. (State variables are currently scoped on" + + " reactor level not modes)", + stateVar.getName()), + stateVar, + Literals.STATE_VAR__NAME); + } + names.add(stateVar.getName()); } + } } - - @Check(CheckType.FAST) - public void checkModeStateNamespace(Reactor reactor) { - if (!reactor.getModes().isEmpty()) { - var names = new ArrayList(); - reactor.getStateVars().stream().map(it -> it.getName()).forEach(it -> names.add(it)); - for (var mode : reactor.getModes()) { - for (var stateVar : mode.getStateVars()) { - if (names.contains(stateVar.getName())) { - error(String.format("Duplicate state variable '%s'. (State variables are currently scoped on reactor level not modes)", - stateVar.getName()), stateVar, Literals.STATE_VAR__NAME); - } - names.add(stateVar.getName()); - } - } + } + + @Check(CheckType.FAST) + public void checkModeTimerNamespace(Reactor reactor) { + if (!reactor.getModes().isEmpty()) { + var names = new ArrayList(); + reactor.getTimers().stream().map(it -> it.getName()).forEach(it -> names.add(it)); + for (var mode : reactor.getModes()) { + for (var timer : mode.getTimers()) { + if (names.contains(timer.getName())) { + error( + String.format( + "Duplicate Timer '%s'. (Timers are currently scoped on reactor level not" + + " modes)", + timer.getName()), + timer, + Literals.VARIABLE__NAME); + } + names.add(timer.getName()); } + } } - - @Check(CheckType.FAST) - public void checkModeTimerNamespace(Reactor reactor) { - if (!reactor.getModes().isEmpty()) { - var names = new ArrayList(); - reactor.getTimers().stream().map(it -> it.getName()).forEach(it -> names.add(it)); - for (var mode : reactor.getModes()) { - for (var timer : mode.getTimers()) { - if (names.contains(timer.getName())) { - error(String.format("Duplicate Timer '%s'. (Timers are currently scoped on reactor level not modes)", - timer.getName()), timer, Literals.VARIABLE__NAME); - } - names.add(timer.getName()); - } - } + } + + @Check(CheckType.FAST) + public void checkModeActionNamespace(Reactor reactor) { + if (!reactor.getModes().isEmpty()) { + var names = new ArrayList(); + reactor.getActions().stream().map(it -> it.getName()).forEach(it -> names.add(it)); + for (var mode : reactor.getModes()) { + for (var action : mode.getActions()) { + if (names.contains(action.getName())) { + error( + String.format( + "Duplicate Action '%s'. (Actions are currently scoped on reactor level not" + + " modes)", + action.getName()), + action, + Literals.VARIABLE__NAME); + } + names.add(action.getName()); } + } } - - @Check(CheckType.FAST) - public void checkModeActionNamespace(Reactor reactor) { - if (!reactor.getModes().isEmpty()) { - var names = new ArrayList(); - reactor.getActions().stream().map(it -> it.getName()).forEach(it -> names.add(it)); - for (var mode : reactor.getModes()) { - for (var action : mode.getActions()) { - if (names.contains(action.getName())) { - error(String.format("Duplicate Action '%s'. (Actions are currently scoped on reactor level not modes)", - action.getName()), action, Literals.VARIABLE__NAME); - } - names.add(action.getName()); - } - } + } + + @Check(CheckType.FAST) + public void checkModeInstanceNamespace(Reactor reactor) { + if (!reactor.getModes().isEmpty()) { + var names = new ArrayList(); + reactor.getActions().stream().map(it -> it.getName()).forEach(it -> names.add(it)); + for (var mode : reactor.getModes()) { + for (var instantiation : mode.getInstantiations()) { + if (names.contains(instantiation.getName())) { + error( + String.format( + "Duplicate Instantiation '%s'. (Instantiations are currently scoped on reactor" + + " level not modes)", + instantiation.getName()), + instantiation, + Literals.INSTANTIATION__NAME); + } + names.add(instantiation.getName()); } + } } - - @Check(CheckType.FAST) - public void checkModeInstanceNamespace(Reactor reactor) { - if (!reactor.getModes().isEmpty()) { - var names = new ArrayList(); - reactor.getActions().stream().map(it -> it.getName()).forEach(it -> names.add(it)); - for (var mode : reactor.getModes()) { - for (var instantiation : mode.getInstantiations()) { - if (names.contains(instantiation.getName())) { - error(String.format("Duplicate Instantiation '%s'. (Instantiations are currently scoped on reactor level not modes)", - instantiation.getName()), instantiation, Literals.INSTANTIATION__NAME); - } - names.add(instantiation.getName()); - } + } + + @Check(CheckType.FAST) + public void checkMissingStateResetInMode(Reactor reactor) { + if (!reactor.getModes().isEmpty()) { + var resetModes = new HashSet(); + // Collect all modes that may be reset + for (var m : reactor.getModes()) { + for (var r : m.getReactions()) { + for (var e : r.getEffects()) { + if (e.getVariable() instanceof Mode && e.getTransition() != ModeTransition.HISTORY) { + resetModes.add((Mode) e.getVariable()); } + } } - } - - @Check(CheckType.FAST) - public void checkMissingStateResetInMode(Reactor reactor) { - if (!reactor.getModes().isEmpty()) { - var resetModes = new HashSet(); - // Collect all modes that may be reset - for (var m : reactor.getModes()) { - for (var r : m.getReactions()) { - for (var e : r.getEffects()) { - if (e.getVariable() instanceof Mode && e.getTransition() != ModeTransition.HISTORY) { - resetModes.add((Mode) e.getVariable()); - } - } - } + } + for (var m : resetModes) { + // Check state variables in this mode + if (!m.getStateVars().isEmpty()) { + var hasResetReaction = + m.getReactions().stream() + .anyMatch( + r -> + r.getTriggers().stream() + .anyMatch( + t -> + (t instanceof BuiltinTriggerRef + && ((BuiltinTriggerRef) t).getType() + == BuiltinTrigger.RESET))); + if (!hasResetReaction) { + for (var s : m.getStateVars()) { + if (!s.isReset()) { + error( + "State variable is not reset upon mode entry. It is neither marked for" + + " automatic reset nor is there a reset reaction.", + m, + Literals.MODE__STATE_VARS, + m.getStateVars().indexOf(s)); + } } - for (var m : resetModes) { - // Check state variables in this mode - if (!m.getStateVars().isEmpty()) { - var hasResetReaction = m.getReactions().stream().anyMatch( - r -> r.getTriggers().stream().anyMatch( - t -> (t instanceof BuiltinTriggerRef && - ((BuiltinTriggerRef) t).getType() == BuiltinTrigger.RESET))); - if (!hasResetReaction) { - for (var s : m.getStateVars()) { - if (!s.isReset()) { - error("State variable is not reset upon mode entry. It is neither marked for automatic reset nor is there a reset reaction.", - m, Literals.MODE__STATE_VARS, m.getStateVars().indexOf(s)); - } - } - } + } + } + // Check state variables in instantiated reactors + if (!m.getInstantiations().isEmpty()) { + for (var i : m.getInstantiations()) { + var error = new LinkedHashSet(); + var checked = new HashSet(); + var toCheck = new LinkedList(); + toCheck.add((Reactor) i.getReactorClass()); + while (!toCheck.isEmpty()) { + var check = toCheck.pop(); + checked.add(check); + if (!check.getStateVars().isEmpty()) { + var hasResetReaction = + check.getReactions().stream() + .anyMatch( + r -> + r.getTriggers().stream() + .anyMatch( + t -> + (t instanceof BuiltinTriggerRef + && ((BuiltinTriggerRef) t).getType() + == BuiltinTrigger.RESET))); + if (!hasResetReaction) { + // Add state vars that are not self-resetting to the error + check.getStateVars().stream() + .filter(s -> !s.isReset()) + .forEachOrdered(error::add); } - // Check state variables in instantiated reactors - if (!m.getInstantiations().isEmpty()) { - for (var i : m.getInstantiations()) { - var error = new LinkedHashSet(); - var checked = new HashSet(); - var toCheck = new LinkedList(); - toCheck.add((Reactor) i.getReactorClass()); - while (!toCheck.isEmpty()) { - var check = toCheck.pop(); - checked.add(check); - if (!check.getStateVars().isEmpty()) { - var hasResetReaction = check.getReactions().stream().anyMatch( - r -> r.getTriggers().stream().anyMatch( - t -> (t instanceof BuiltinTriggerRef && - ((BuiltinTriggerRef) t).getType() == BuiltinTrigger.RESET))); - if (!hasResetReaction) { - // Add state vars that are not self-resetting to the error - check.getStateVars().stream().filter(s -> !s.isReset()).forEachOrdered(error::add); - } - } - // continue with inner - for (var innerInstance : check.getInstantiations()) { - var next = (Reactor) innerInstance.getReactorClass(); - if (!checked.contains(next)) { - toCheck.push(next); - } - } - } - if (!error.isEmpty()) { - error("This reactor contains state variables that are not reset upon mode entry: " - + error.stream().map(e -> e.getName() + " in " - + ASTUtils.getEnclosingReactor(e).getName()).collect(Collectors.joining(", ")) - + ".\nThe state variables are neither marked for automatic reset nor have a dedicated reset reaction. " - + "It is unsafe to instantiate this reactor inside a mode entered with reset.", - m, Literals.MODE__INSTANTIATIONS, - m.getInstantiations().indexOf(i)); - } - } + } + // continue with inner + for (var innerInstance : check.getInstantiations()) { + var next = (Reactor) innerInstance.getReactorClass(); + if (!checked.contains(next)) { + toCheck.push(next); } + } + } + if (!error.isEmpty()) { + error( + "This reactor contains state variables that are not reset upon mode entry: " + + error.stream() + .map( + e -> e.getName() + " in " + ASTUtils.getEnclosingReactor(e).getName()) + .collect(Collectors.joining(", ")) + + ".\n" + + "The state variables are neither marked for automatic reset nor have a" + + " dedicated reset reaction. It is unsafe to instantiate this reactor inside" + + " a mode entered with reset.", + m, + Literals.MODE__INSTANTIATIONS, + m.getInstantiations().indexOf(i)); } + } } + } } - - @Check(CheckType.FAST) - public void checkStateResetWithoutInitialValue(StateVar state) { - if (state.isReset() && (state.getInit() == null || state.getInit().getExprs().isEmpty())) { - error("The state variable can not be automatically reset without an initial value.", state, Literals.STATE_VAR__RESET); - } + } + + @Check(CheckType.FAST) + public void checkStateResetWithoutInitialValue(StateVar state) { + if (state.isReset() && (state.getInit() == null || state.getInit().getExprs().isEmpty())) { + error( + "The state variable can not be automatically reset without an initial value.", + state, + Literals.STATE_VAR__RESET); } - - @Check(CheckType.FAST) - public void checkUnspecifiedTransitionType(Reaction reaction) { - for (var effect : reaction.getEffects()) { - var variable = effect.getVariable(); - if (variable instanceof Mode) { - // The transition type is always set to default by Xtext. - // Hence, check if there is an explicit node for the transition type in the AST. - var transitionAssignment = NodeModelUtils.findNodesForFeature((EObject) effect, Literals.VAR_REF__TRANSITION); - if (transitionAssignment.isEmpty()) { // Transition type not explicitly specified. - var mode = (Mode) variable; - // Check if reset or history transition would make a difference. - var makesDifference = !mode.getStateVars().isEmpty() - || !mode.getTimers().isEmpty() - || !mode.getActions().isEmpty() - || mode.getConnections().stream().anyMatch(c -> c.getDelay() != null); - if (!makesDifference && !mode.getInstantiations().isEmpty()) { - // Also check instantiated reactors - for (var i : mode.getInstantiations()) { - var checked = new HashSet(); - var toCheck = new LinkedList(); - toCheck.add((Reactor) i.getReactorClass()); - while (!toCheck.isEmpty() && !makesDifference) { - var check = toCheck.pop(); - checked.add(check); - - makesDifference |= !check.getModes().isEmpty() - || !ASTUtils.allStateVars(check).isEmpty() - || !ASTUtils.allTimers(check).isEmpty() - || !ASTUtils.allActions(check).isEmpty() - || ASTUtils.allConnections(check).stream().anyMatch(c -> c.getDelay() != null); - - // continue with inner - for (var innerInstance : check.getInstantiations()) { - var next = (Reactor) innerInstance.getReactorClass(); - if (!checked.contains(next)) { - toCheck.push(next); - } - } - } - } - } - if (makesDifference) { - warning("You should specify a transition type! " - + "Reset and history transitions have different effects on this target mode. " - + "Currently, a reset type is implicitly assumed.", - reaction, Literals.REACTION__EFFECTS, reaction.getEffects().indexOf(effect)); - } + } + + @Check(CheckType.FAST) + public void checkUnspecifiedTransitionType(Reaction reaction) { + for (var effect : reaction.getEffects()) { + var variable = effect.getVariable(); + if (variable instanceof Mode) { + // The transition type is always set to default by Xtext. + // Hence, check if there is an explicit node for the transition type in the AST. + var transitionAssignment = + NodeModelUtils.findNodesForFeature((EObject) effect, Literals.VAR_REF__TRANSITION); + if (transitionAssignment.isEmpty()) { // Transition type not explicitly specified. + var mode = (Mode) variable; + // Check if reset or history transition would make a difference. + var makesDifference = + !mode.getStateVars().isEmpty() + || !mode.getTimers().isEmpty() + || !mode.getActions().isEmpty() + || mode.getConnections().stream().anyMatch(c -> c.getDelay() != null); + if (!makesDifference && !mode.getInstantiations().isEmpty()) { + // Also check instantiated reactors + for (var i : mode.getInstantiations()) { + var checked = new HashSet(); + var toCheck = new LinkedList(); + toCheck.add((Reactor) i.getReactorClass()); + while (!toCheck.isEmpty() && !makesDifference) { + var check = toCheck.pop(); + checked.add(check); + + makesDifference |= + !check.getModes().isEmpty() + || !ASTUtils.allStateVars(check).isEmpty() + || !ASTUtils.allTimers(check).isEmpty() + || !ASTUtils.allActions(check).isEmpty() + || ASTUtils.allConnections(check).stream() + .anyMatch(c -> c.getDelay() != null); + + // continue with inner + for (var innerInstance : check.getInstantiations()) { + var next = (Reactor) innerInstance.getReactorClass(); + if (!checked.contains(next)) { + toCheck.push(next); + } } + } } + } + if (makesDifference) { + warning( + "You should specify a transition type! " + + "Reset and history transitions have different effects on this target mode. " + + "Currently, a reset type is implicitly assumed.", + reaction, + Literals.REACTION__EFFECTS, + reaction.getEffects().indexOf(effect)); + } } + } + } + } + + ////////////////////////////////////////////////////////////// + //// Public methods. + + /** Return the error reporter for this validator. */ + public ValidatorErrorReporter getErrorReporter() { + return this.errorReporter; + } + + /** Implementation required by xtext to report validation errors. */ + @Override + public ValidationMessageAcceptor getMessageAcceptor() { + return messageAcceptor == null ? this : messageAcceptor; + } + + /** Return a list of error messages for the target declaration. */ + public List getTargetPropertyErrors() { + return this.targetPropertyErrors; + } + + ////////////////////////////////////////////////////////////// + //// Protected methods. + + /** Generate an error message for an AST node. */ + @Override + protected void error(java.lang.String message, org.eclipse.emf.ecore.EStructuralFeature feature) { + super.error(message, feature); + } + + ////////////////////////////////////////////////////////////// + //// Private methods. + + /** + * For each input, report a conflict if: 1) the input exists and the type doesn't match; or 2) the + * input has a name clash with variable that is not an input. + * + * @param superVars List of typed variables of a particular kind (i.e., inputs, outputs, or + * actions), found in a super class. + * @param sameKind Typed variables of the same kind, found in the subclass. + * @param allOwn Accumulator of non-conflicting variables incorporated in the subclass. + * @param conflicts Set of variables that are in conflict, to be used by this function to report + * conflicts. + */ + private void checkConflict( + EList superVars, EList sameKind, List allOwn, HashSet conflicts) { + for (T superVar : superVars) { + T match = null; + for (T it : sameKind) { + if (it.getName().equals(superVar.getName())) { + match = it; + break; + } + } + List rest = new ArrayList<>(allOwn); + rest.removeIf(it -> sameKind.contains(it)); + + if ((match != null && superVar.getType() != match.getType()) + || hasNameConflict(superVar, rest)) { + conflicts.add(superVar); + } else { + allOwn.add(superVar); + } + } + } + + /** + * Check the name of a feature for illegal substrings such as reserved identifiers and names with + * double leading underscores. + * + * @param name The name. + * @param feature The feature containing the name (for error reporting). + */ + private void checkName(String name, EStructuralFeature feature) { + + // Raises an error if the string starts with two underscores. + if (name.length() >= 2 && name.substring(0, 2).equals("__")) { + error(UNDERSCORE_MESSAGE + name, feature); } - ////////////////////////////////////////////////////////////// - //// Public methods. - - /** - * Return the error reporter for this validator. - */ - public ValidatorErrorReporter getErrorReporter() { - return this.errorReporter; - } - - /** - * Implementation required by xtext to report validation errors. - */ - @Override - public ValidationMessageAcceptor getMessageAcceptor() { - return messageAcceptor == null ? this : messageAcceptor; - } - - /** - * Return a list of error messages for the target declaration. - */ - public List getTargetPropertyErrors() { - return this.targetPropertyErrors; - } - - ////////////////////////////////////////////////////////////// - //// Protected methods. - - /** - * Generate an error message for an AST node. - */ - @Override - protected void error(java.lang.String message, - org.eclipse.emf.ecore.EStructuralFeature feature) { - super.error(message, feature); - } - - ////////////////////////////////////////////////////////////// - //// Private methods. - - /** - * For each input, report a conflict if: - * 1) the input exists and the type doesn't match; or - * 2) the input has a name clash with variable that is not an input. - * @param superVars List of typed variables of a particular kind (i.e., - * inputs, outputs, or actions), found in a super class. - * @param sameKind Typed variables of the same kind, found in the subclass. - * @param allOwn Accumulator of non-conflicting variables incorporated in the - * subclass. - * @param conflicts Set of variables that are in conflict, to be used by this - * function to report conflicts. - */ - private void checkConflict ( - EList superVars, EList sameKind, List allOwn, HashSet conflicts - ) { - for (T superVar : superVars) { - T match = null; - for (T it : sameKind) { - if (it.getName().equals(superVar.getName())) { - match = it; - break; - } - } - List rest = new ArrayList<>(allOwn); - rest.removeIf(it -> sameKind.contains(it)); + if (this.target.isReservedIdent(name)) { + error(RESERVED_MESSAGE + name, feature); + } - if ((match != null && superVar.getType() != match.getType()) || hasNameConflict(superVar, rest)) { - conflicts.add(superVar); - } else { - allOwn.add(superVar); - } - } + if (this.target == Target.TS) { + // "actions" is a reserved word within a TS reaction + if (name.equals("actions")) { + error(ACTIONS_MESSAGE + name, feature); + } + } + } + + /** + * Check that the initializer is compatible with the type. Note that if the type is inferred it + * will necessarily be compatible so this method is not harmful. + */ + public void typeCheck(Initializer init, InferredType type, EStructuralFeature feature) { + if (init == null) { + return; } - /** - * Check the name of a feature for illegal substrings such as reserved - * identifiers and names with double leading underscores. - * @param name The name. - * @param feature The feature containing the name (for error reporting). - */ - private void checkName(String name, EStructuralFeature feature) { + // TODO: + // type is list => init is list + // type is fixed with size n => init is fixed with size n + // Specifically for C: list can only be literal or time lists - // Raises an error if the string starts with two underscores. - if (name.length() >= 2 && name.substring(0, 2).equals("__")) { - error(UNDERSCORE_MESSAGE + name, feature); + if (type.isTime) { + if (type.isList) { + // list of times + var exprs = init.getExprs(); + if (exprs.isEmpty()) { + error("Expected at least one time value.", feature); + return; } - - if (this.target.isReservedIdent(name)) { - error(RESERVED_MESSAGE + name, feature); + if (exprs.size() == 1 && exprs.get(0) instanceof BracedListExpression) { + exprs = ((BracedListExpression) exprs.get(0)).getItems(); } - - if (this.target == Target.TS) { - // "actions" is a reserved word within a TS reaction - if (name.equals("actions")) { - error(ACTIONS_MESSAGE + name, feature); - } + for (var component : exprs) { + checkExpressionIsTime(component, feature); } + } else { + checkExpressionIsTime(init, feature); + } } + } - - /** - * Check that the initializer is compatible with the type. - * Note that if the type is inferred it will necessarily be compatible - * so this method is not harmful. - */ - public void typeCheck(Initializer init, InferredType type, EStructuralFeature feature) { - if (init == null) { - return; - } - - // TODO: - // type is list => init is list - // type is fixed with size n => init is fixed with size n - // Specifically for C: list can only be literal or time lists - - if (type.isTime) { - if (type.isList) { - // list of times - var exprs = init.getExprs(); - if (exprs.isEmpty()) { - error("Expected at least one time value.", feature); - return; - } - if (exprs.size() == 1 && exprs.get(0) instanceof BracedListExpression) { - exprs = ((BracedListExpression) exprs.get(0)).getItems(); - } - for (var component : exprs) { - checkExpressionIsTime(component, feature); - } - } else { - checkExpressionIsTime(init, feature); - } - } + private void checkExpressionIsTime(Initializer init, EStructuralFeature feature) { + if (init == null) { + return; } - private void checkExpressionIsTime(Initializer init, EStructuralFeature feature) { - if (init == null) { - return; - } - - if (init.getExprs().size() != 1) { - error("Expected exactly one time value.", feature); - } else { - checkExpressionIsTime(ASTUtils.asSingleExpr(init), feature); - } + if (init.getExprs().size() != 1) { + error("Expected exactly one time value.", feature); + } else { + checkExpressionIsTime(ASTUtils.asSingleExpr(init), feature); } + } - private void checkExpressionIsTime(Expression value, EStructuralFeature feature) { - if (value == null || value instanceof Time) { - return; - } - - if (value instanceof ParameterReference) { - if (!ASTUtils.isOfTimeType(((ParameterReference) value).getParameter()) - && target.requiresTypes) { - error("Referenced parameter is not of time type.", feature); - } - return; - } else if (value instanceof Literal) { - if (ASTUtils.isZero(((Literal) value).getLiteral())) { - return; - } - - if (ASTUtils.isInteger(((Literal) value).getLiteral())) { - error("Missing time unit.", feature); - return; - } - // fallthrough - } - - error("Invalid time value.", feature); + private void checkExpressionIsTime(Expression value, EStructuralFeature feature) { + if (value == null || value instanceof Time) { + return; } - /** - * Return the number of main or federated reactors declared. - * - * @param iter An iterator over all objects in the resource. - */ - private int countMainOrFederated(TreeIterator iter) { - int nMain = 0; - while (iter.hasNext()) { - EObject obj = iter.next(); - if (!(obj instanceof Reactor)) { - continue; - } - Reactor r = (Reactor) obj; - if (r.isMain() || r.isFederated()) { - nMain++; - } - } - return nMain; - } - - /** - * Report whether a given reactor has dependencies on a cyclic - * instantiation pattern. This means the reactor has an instantiation - * in it -- directly or in one of its contained reactors -- that is - * self-referential. - * @param reactor The reactor definition to find out whether it has any - * dependencies on cyclic instantiations. - * @param cycleSet The set of all reactors that are part of an - * instantiation cycle. - * @param visited The set of nodes already visited in this graph traversal. - */ - private boolean dependsOnCycle( - Reactor reactor, Set cycleSet, Set visited - ) { - Set origins = info.instantiationGraph.getUpstreamAdjacentNodes(reactor); - if (visited.contains(reactor)) { - return false; - } else { - visited.add(reactor); - for (Reactor it : origins) { - if (cycleSet.contains(it) || dependsOnCycle(it, cycleSet, visited)) { - // Reached a cycle. - return true; - } - } - } - return false; - } - - /** - * Report whether the name of the given element matches any variable in - * the ones to check against. - * @param element The element to compare against all variables in the given iterable. - * @param toCheckAgainst Iterable variables to compare the given element against. - */ - private boolean hasNameConflict(Variable element, - Iterable toCheckAgainst) { - int numNameConflicts = 0; - for (Variable it : toCheckAgainst) { - if (it.getName().equals(element.getName())) { - numNameConflicts++; - } - } - return numNameConflicts > 0; + if (value instanceof ParameterReference) { + if (!ASTUtils.isOfTimeType(((ParameterReference) value).getParameter()) + && target.requiresTypes) { + error("Referenced parameter is not of time type.", feature); + } + return; + } else if (value instanceof Literal) { + if (ASTUtils.isZero(((Literal) value).getLiteral())) { + return; + } + + if (ASTUtils.isInteger(((Literal) value).getLiteral())) { + error("Missing time unit.", feature); + return; + } + // fallthrough } - /** - * Return true if target is C or a C-based target like CCpp. - */ - private boolean isCBasedTarget() { - return (this.target == Target.C || this.target == Target.CCPP); + error("Invalid time value.", feature); + } + + /** + * Return the number of main or federated reactors declared. + * + * @param iter An iterator over all objects in the resource. + */ + private int countMainOrFederated(TreeIterator iter) { + int nMain = 0; + while (iter.hasNext()) { + EObject obj = iter.next(); + if (!(obj instanceof Reactor)) { + continue; + } + Reactor r = (Reactor) obj; + if (r.isMain() || r.isFederated()) { + nMain++; + } } - - /** - * Report whether a given imported reactor is used in this resource or not. - * @param reactor The imported reactor to check whether it is used. - */ - private boolean isUnused(ImportedReactor reactor) { - TreeIterator instantiations = reactor.eResource().getAllContents(); - TreeIterator subclasses = reactor.eResource().getAllContents(); - - boolean instantiationsCheck = true; - while (instantiations.hasNext() && instantiationsCheck) { - EObject obj = instantiations.next(); - if (!(obj instanceof Instantiation)) { - continue; - } - Instantiation inst = (Instantiation) obj; - instantiationsCheck &= (inst.getReactorClass() != reactor && inst.getReactorClass() != reactor.getReactorClass()); - } - - boolean subclassesCheck = true; - while (subclasses.hasNext() && subclassesCheck) { - EObject obj = subclasses.next(); - if (!(obj instanceof Reactor)) { - continue; - } - Reactor subclass = (Reactor) obj; - for (ReactorDecl decl : subclass.getSuperClasses()) { - subclassesCheck &= (decl != reactor && decl != reactor.getReactorClass()); - } - } - return instantiationsCheck && subclassesCheck; + return nMain; + } + + /** + * Report whether a given reactor has dependencies on a cyclic instantiation pattern. This means + * the reactor has an instantiation in it -- directly or in one of its contained reactors -- that + * is self-referential. + * + * @param reactor The reactor definition to find out whether it has any dependencies on cyclic + * instantiations. + * @param cycleSet The set of all reactors that are part of an instantiation cycle. + * @param visited The set of nodes already visited in this graph traversal. + */ + private boolean dependsOnCycle(Reactor reactor, Set cycleSet, Set visited) { + Set origins = info.instantiationGraph.getUpstreamAdjacentNodes(reactor); + if (visited.contains(reactor)) { + return false; + } else { + visited.add(reactor); + for (Reactor it : origins) { + if (cycleSet.contains(it) || dependsOnCycle(it, cycleSet, visited)) { + // Reached a cycle. + return true; + } + } } - - /** - * Return true if the two types match. Unfortunately, xtext does not - * seem to create a suitable equals() method for Type, so we have to - * do this manually. - */ - private boolean sameType(Type type1, Type type2) { - if (type1 == null) { - return type2 == null; - } - if (type2 == null) { - return type1 == null; - } - // Most common case first. - if (type1.getId() != null) { - if (type1.getStars() != null) { - if (type2.getStars() == null) return false; - if (type1.getStars().size() != type2.getStars().size()) return false; - } - return (type1.getId().equals(type2.getId())); - } - - // Type specification in the grammar is: - // (time?='time' (arraySpec=ArraySpec)?) | ((id=(DottedName) (stars+='*')* ('<' typeParms+=TypeParm (',' typeParms+=TypeParm)* '>')? (arraySpec=ArraySpec)?) | code=Code); - if (type1.isTime()) { - if (!type2.isTime()) return false; - // Ignore the arraySpec because that is checked when connection - // is checked for balance. - return true; - } - // Type must be given in a code body - return type1.getCode().getBody().equals(type2.getCode().getBody()); + return false; + } + + /** + * Report whether the name of the given element matches any variable in the ones to check against. + * + * @param element The element to compare against all variables in the given iterable. + * @param toCheckAgainst Iterable variables to compare the given element against. + */ + private boolean hasNameConflict(Variable element, Iterable toCheckAgainst) { + int numNameConflicts = 0; + for (Variable it : toCheckAgainst) { + if (it.getName().equals(element.getName())) { + numNameConflicts++; + } + } + return numNameConflicts > 0; + } + + /** Return true if target is C or a C-based target like CCpp. */ + private boolean isCBasedTarget() { + return (this.target == Target.C || this.target == Target.CCPP); + } + + /** + * Report whether a given imported reactor is used in this resource or not. + * + * @param reactor The imported reactor to check whether it is used. + */ + private boolean isUnused(ImportedReactor reactor) { + TreeIterator instantiations = reactor.eResource().getAllContents(); + TreeIterator subclasses = reactor.eResource().getAllContents(); + + boolean instantiationsCheck = true; + while (instantiations.hasNext() && instantiationsCheck) { + EObject obj = instantiations.next(); + if (!(obj instanceof Instantiation)) { + continue; + } + Instantiation inst = (Instantiation) obj; + instantiationsCheck &= + (inst.getReactorClass() != reactor + && inst.getReactorClass() != reactor.getReactorClass()); } - ////////////////////////////////////////////////////////////// - //// Private fields. - - /** The error reporter. */ - private ValidatorErrorReporter errorReporter - = new ValidatorErrorReporter(getMessageAcceptor(), new ValidatorStateAccess()); - - /** Helper class containing information about the model. */ - private ModelInfo info = new ModelInfo(); - - @Inject(optional = true) - private ValidationMessageAcceptor messageAcceptor; - - /** The declared target. */ - private Target target; - - private List targetPropertyErrors = new ArrayList<>(); - - private List targetPropertyWarnings = new ArrayList<>(); - - ////////////////////////////////////////////////////////////// - //// Private static constants. - - private static String ACTIONS_MESSAGE - = "\"actions\" is a reserved word for the TypeScript target for objects " - + "(inputs, outputs, actions, timers, parameters, state, reactor definitions, " - + "and reactor instantiation): "; - - private static String HOST_OR_FQN_REGEX - = "^([a-z0-9]+(-[a-z0-9]+)*)|(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})$"; - - /** - * Regular expression to check the validity of IPV4 addresses (due to David M. Syzdek). - */ - private static String IPV4_REGEX = "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" + - "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"; - - /** - * Regular expression to check the validity of IPV6 addresses (due to David M. Syzdek), - * with minor adjustment to allow up to six IPV6 segments (without truncation) in front - * of an embedded IPv4-address. - **/ - private static String IPV6_REGEX = - "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" + - "([0-9a-fA-F]{1,4}:){1,7}:|" + - "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" + - "([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" + - "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" + - "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" + - "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" + - "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" + - ":((:[0-9a-fA-F]{1,4}){1,7}|:)|" + - "fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|" + - "::(ffff(:0{1,4}){0,1}:){0,1}" + IPV4_REGEX + "|" + - "([0-9a-fA-F]{1,4}:){1,4}:" + IPV4_REGEX + "|" + - "([0-9a-fA-F]{1,4}:){1,6}" + IPV4_REGEX + ")"; - - private static String RESERVED_MESSAGE = "Reserved words in the target language are not allowed for objects " - + "(inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation): "; - - private static List SPACING_VIOLATION_POLICIES = List.of("defer", "drop", "replace"); - - private static String UNDERSCORE_MESSAGE = "Names of objects (inputs, outputs, actions, timers, parameters, " - + "state, reactor definitions, and reactor instantiation) may not start with \"__\": "; - - private static String USERNAME_REGEX = "^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\\$)$"; + boolean subclassesCheck = true; + while (subclasses.hasNext() && subclassesCheck) { + EObject obj = subclasses.next(); + if (!(obj instanceof Reactor)) { + continue; + } + Reactor subclass = (Reactor) obj; + for (ReactorDecl decl : subclass.getSuperClasses()) { + subclassesCheck &= (decl != reactor && decl != reactor.getReactorClass()); + } + } + return instantiationsCheck && subclassesCheck; + } + + /** + * Return true if the two types match. Unfortunately, xtext does not seem to create a suitable + * equals() method for Type, so we have to do this manually. + */ + private boolean sameType(Type type1, Type type2) { + if (type1 == null) { + return type2 == null; + } + if (type2 == null) { + return type1 == null; + } + // Most common case first. + if (type1.getId() != null) { + if (type1.getStars() != null) { + if (type2.getStars() == null) return false; + if (type1.getStars().size() != type2.getStars().size()) return false; + } + return (type1.getId().equals(type2.getId())); + } + // Type specification in the grammar is: + // (time?='time' (arraySpec=ArraySpec)?) | ((id=(DottedName) (stars+='*')* ('<' + // typeParms+=TypeParm (',' typeParms+=TypeParm)* '>')? (arraySpec=ArraySpec)?) | code=Code); + if (type1.isTime()) { + if (!type2.isTime()) return false; + // Ignore the arraySpec because that is checked when connection + // is checked for balance. + return true; + } + // Type must be given in a code body + return type1.getCode().getBody().equals(type2.getCode().getBody()); + } + + ////////////////////////////////////////////////////////////// + //// Private fields. + + /** The error reporter. */ + private ValidatorErrorReporter errorReporter = + new ValidatorErrorReporter(getMessageAcceptor(), new ValidatorStateAccess()); + + /** Helper class containing information about the model. */ + private ModelInfo info = new ModelInfo(); + + @Inject(optional = true) + private ValidationMessageAcceptor messageAcceptor; + + /** The declared target. */ + private Target target; + + private List targetPropertyErrors = new ArrayList<>(); + + private List targetPropertyWarnings = new ArrayList<>(); + + ////////////////////////////////////////////////////////////// + //// Private static constants. + + private static String ACTIONS_MESSAGE = + "\"actions\" is a reserved word for the TypeScript target for objects " + + "(inputs, outputs, actions, timers, parameters, state, reactor definitions, " + + "and reactor instantiation): "; + + private static String HOST_OR_FQN_REGEX = + "^([a-z0-9]+(-[a-z0-9]+)*)|(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})$"; + + /** Regular expression to check the validity of IPV4 addresses (due to David M. Syzdek). */ + private static String IPV4_REGEX = + "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" + + "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"; + + /** + * Regular expression to check the validity of IPV6 addresses (due to David M. Syzdek), with minor + * adjustment to allow up to six IPV6 segments (without truncation) in front of an embedded + * IPv4-address. + */ + private static String IPV6_REGEX = + "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" + + "([0-9a-fA-F]{1,4}:){1,7}:|" + + "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" + + "([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" + + "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" + + "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" + + "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" + + "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" + + ":((:[0-9a-fA-F]{1,4}){1,7}|:)|" + + "fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|" + + "::(ffff(:0{1,4}){0,1}:){0,1}" + + IPV4_REGEX + + "|" + + "([0-9a-fA-F]{1,4}:){1,4}:" + + IPV4_REGEX + + "|" + + "([0-9a-fA-F]{1,4}:){1,6}" + + IPV4_REGEX + + ")"; + + private static String RESERVED_MESSAGE = + "Reserved words in the target language are not allowed for objects (inputs, outputs, actions," + + " timers, parameters, state, reactor definitions, and reactor instantiation): "; + + private static List SPACING_VIOLATION_POLICIES = List.of("defer", "drop", "replace"); + + private static String UNDERSCORE_MESSAGE = + "Names of objects (inputs, outputs, actions, timers, parameters, " + + "state, reactor definitions, and reactor instantiation) may not start with \"__\": "; + + private static String USERNAME_REGEX = "^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\\$)$"; } diff --git a/org.lflang/src/org/lflang/validation/ValidatorErrorReporter.java b/org.lflang/src/org/lflang/validation/ValidatorErrorReporter.java index ace55683f6..d21458a3a9 100644 --- a/org.lflang/src/org/lflang/validation/ValidatorErrorReporter.java +++ b/org.lflang/src/org/lflang/validation/ValidatorErrorReporter.java @@ -1,16 +1,16 @@ /************* * Copyright (c) 2021, 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 @@ -27,155 +27,160 @@ package org.lflang.validation; import java.nio.file.Path; - import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.validation.ValidationMessageAcceptor; - import org.lflang.ErrorReporter; /** - * This class translates messages reported via the ErrorReporrter interface to - * the interface of a given ValidationMessageAcceptor. - * - * Effectively this allows to report errors via the ErrorReporter interface - * during validator checks, while having the validator still track all the - * reported warnings and messages. This is required for some functionality, like - * the construction of an instance graph in LFValidator.checkModel(). Since the - * instance graph is also used in other components, it does not report directly - * to the validator, but uses our custom ErrorReporter interface that we use - * during code generation. This class bridges the gap between the ErrorReporter - * interface and the messages that the validator expects. - * + * This class translates messages reported via the ErrorReporrter interface to the interface of a + * given ValidationMessageAcceptor. + * + *

Effectively this allows to report errors via the ErrorReporter interface during validator + * checks, while having the validator still track all the reported warnings and messages. This is + * required for some functionality, like the construction of an instance graph in + * LFValidator.checkModel(). Since the instance graph is also used in other components, it does not + * report directly to the validator, but uses our custom ErrorReporter interface that we use during + * code generation. This class bridges the gap between the ErrorReporter interface and the messages + * that the validator expects. + * * @author Christian Menard */ public class ValidatorErrorReporter implements ErrorReporter { - private ValidationMessageAcceptor acceptor; - private BaseLFValidator.ValidatorStateAccess validatorState; - private boolean errorsOccurred = false; - - public ValidatorErrorReporter(ValidationMessageAcceptor acceptor, - BaseLFValidator.ValidatorStateAccess stateAccess) { - this.acceptor = acceptor; - this.validatorState = stateAccess; - } - - /** - * Report the given message as an error on the object currently under - * validation. - */ - @Override - public String reportError(String message) { - errorsOccurred = true; - acceptor.acceptError(message, validatorState.getCurrentObject(), null, - ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); - return message; - } - - /** - * Report the given message as an error on the given object. - */ - @Override - public String reportError(EObject object, String message) { - errorsOccurred = true; - acceptor.acceptError(message, object, null, - ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); - return message; - } - - /** - * Report the given message as an error on the current object. - * - * Unfortunately, there is no way to provide a path and a line number to the - * ValidationMessageAcceptor as messages can only be reported directly as - * EObjects. While it is not an ideal solution, this method composes a - * messages indicating the location of the error and reports this on the - * object currently under validation. This way, the error message is not - * lost, but it is not necessarily reported precisely at the location of the - * actual error. - */ - @Override - public String reportError(Path file, Integer line, String message) { - errorsOccurred = true; - String fullMessage = message + " (Reported from " + file.toString() + " on line " - + line.toString() + ")"; - acceptor.acceptError(fullMessage, validatorState.getCurrentObject(), - null, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); - return fullMessage; - } - - /** - * Report the given message as a waring on the object currently under - * validation. - */ - @Override - public String reportWarning(String message) { - acceptor.acceptWarning(message, validatorState.getCurrentObject(), null, - ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); - return message; - } - - @Override - public String reportInfo(String message) { - acceptor.acceptInfo(message, validatorState.getCurrentObject(), null, - ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); - return message; - } - - /** - * Report the given message as a warning on the given object. - */ - @Override - public String reportWarning(EObject object, String message) { - acceptor.acceptWarning(message, object, null, - ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); - return message; - } - - @Override - public String reportInfo(EObject object, String message) { - acceptor.acceptInfo(message, object, null, - ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); - return message; - } - - - /** - * Report the given message as an warning on the current object. - * - * Unfortunately, there is no way to provide a path and a line number to the - * ValidationMessageAcceptor as messages can only be reported directly as - * EObjects. While it is not an ideal solution, this method composes a - * messages indicating the location of the warning and reports this on the - * object currently under validation. This way, the warning message is not - * lost, but it is not necessarily reported precisely at the location of the - * actual warning. - */ - @Override - public String reportWarning(Path file, Integer line, String message) { - String fullMessage = message + " (Reported from " + file.toString() + " on line " - + line.toString() + ")"; - acceptor.acceptWarning(fullMessage, validatorState.getCurrentObject(), - null, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); - return fullMessage; - } - - @Override - public String reportInfo(Path file, Integer line, String message) { - String fullMessage = message + " (Reported from " + file.toString() + " on line " - + line.toString() + ")"; - acceptor.acceptInfo(fullMessage, validatorState.getCurrentObject(), - null, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); - return fullMessage; - } - - /** - * Check if errors where reported. - * - * @return true if errors where reported - */ - @Override - public boolean getErrorsOccurred() { - return errorsOccurred; - } + private ValidationMessageAcceptor acceptor; + private BaseLFValidator.ValidatorStateAccess validatorState; + private boolean errorsOccurred = false; + + public ValidatorErrorReporter( + ValidationMessageAcceptor acceptor, BaseLFValidator.ValidatorStateAccess stateAccess) { + this.acceptor = acceptor; + this.validatorState = stateAccess; + } + + /** Report the given message as an error on the object currently under validation. */ + @Override + public String reportError(String message) { + errorsOccurred = true; + acceptor.acceptError( + message, + validatorState.getCurrentObject(), + null, + ValidationMessageAcceptor.INSIGNIFICANT_INDEX, + null); + return message; + } + + /** Report the given message as an error on the given object. */ + @Override + public String reportError(EObject object, String message) { + errorsOccurred = true; + acceptor.acceptError( + message, object, null, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); + return message; + } + + /** + * Report the given message as an error on the current object. + * + *

Unfortunately, there is no way to provide a path and a line number to the + * ValidationMessageAcceptor as messages can only be reported directly as EObjects. While it is + * not an ideal solution, this method composes a messages indicating the location of the error and + * reports this on the object currently under validation. This way, the error message is not lost, + * but it is not necessarily reported precisely at the location of the actual error. + */ + @Override + public String reportError(Path file, Integer line, String message) { + errorsOccurred = true; + String fullMessage = + message + " (Reported from " + file.toString() + " on line " + line.toString() + ")"; + acceptor.acceptError( + fullMessage, + validatorState.getCurrentObject(), + null, + ValidationMessageAcceptor.INSIGNIFICANT_INDEX, + null); + return fullMessage; + } + + /** Report the given message as a waring on the object currently under validation. */ + @Override + public String reportWarning(String message) { + acceptor.acceptWarning( + message, + validatorState.getCurrentObject(), + null, + ValidationMessageAcceptor.INSIGNIFICANT_INDEX, + null); + return message; + } + + @Override + public String reportInfo(String message) { + acceptor.acceptInfo( + message, + validatorState.getCurrentObject(), + null, + ValidationMessageAcceptor.INSIGNIFICANT_INDEX, + null); + return message; + } + + /** Report the given message as a warning on the given object. */ + @Override + public String reportWarning(EObject object, String message) { + acceptor.acceptWarning( + message, object, null, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); + return message; + } + + @Override + public String reportInfo(EObject object, String message) { + acceptor.acceptInfo(message, object, null, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); + return message; + } + + /** + * Report the given message as an warning on the current object. + * + *

Unfortunately, there is no way to provide a path and a line number to the + * ValidationMessageAcceptor as messages can only be reported directly as EObjects. While it is + * not an ideal solution, this method composes a messages indicating the location of the warning + * and reports this on the object currently under validation. This way, the warning message is not + * lost, but it is not necessarily reported precisely at the location of the actual warning. + */ + @Override + public String reportWarning(Path file, Integer line, String message) { + String fullMessage = + message + " (Reported from " + file.toString() + " on line " + line.toString() + ")"; + acceptor.acceptWarning( + fullMessage, + validatorState.getCurrentObject(), + null, + ValidationMessageAcceptor.INSIGNIFICANT_INDEX, + null); + return fullMessage; + } + + @Override + public String reportInfo(Path file, Integer line, String message) { + String fullMessage = + message + " (Reported from " + file.toString() + " on line " + line.toString() + ")"; + acceptor.acceptInfo( + fullMessage, + validatorState.getCurrentObject(), + null, + ValidationMessageAcceptor.INSIGNIFICANT_INDEX, + null); + return fullMessage; + } + + /** + * Check if errors where reported. + * + * @return true if errors where reported + */ + @Override + public boolean getErrorsOccurred() { + return errorsOccurred; + } } From a6a111e553afbe067fd22fd13dbdb00e9945bbd9 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 2 Jun 2023 15:15:23 -0700 Subject: [PATCH 125/516] Fix compilation errors from merge. --- .../org/lflang/federated/generator/FedASTUtils.java | 6 +----- .../src/org/lflang/generator/GeneratorBase.java | 13 ------------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java b/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java index 66a5a95a15..2da640fa27 100644 --- a/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java +++ b/org.lflang/src/org/lflang/federated/generator/FedASTUtils.java @@ -129,7 +129,6 @@ public static Reactor findFederatedReactor(Resource resource) { * * @param connection Network connection between two federates. * @param resource - * @param mainDef * @param coordination One of CoordinationType.DECENTRALIZED or CoordinationType.CENTRALIZED. * @param errorReporter Used to report errors encountered. */ @@ -203,8 +202,6 @@ private static Action createNetworkAction(FedConnectionInstance connection) { } else { action.setOrigin(ActionOrigin.LOGICAL); } - reactionBankIndices.put(reaction, bankIndex); - } return action; } @@ -547,7 +544,7 @@ else if (federate.contains(port.getParent())) { /** * Follow reactions upstream. This is part of the algorithm of {@link - * #findUpstreamPortsInFederate(FederateInstance, PortInstance, Set)}. + * #findUpstreamPortsInFederate}. */ private static void followReactionUpstream( FederateInstance federate, @@ -798,7 +795,6 @@ private static Reactor getNetworkSenderReactor( * @param connection Network connection between two federates. * @param coordination One of CoordinationType.DECENTRALIZED or CoordinationType.CENTRALIZED. * @param resource - * @param mainDef * @param errorReporter FIXME * @note Used in federated execution */ diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.java b/org.lflang/src/org/lflang/generator/GeneratorBase.java index 078f608ffa..942956c2d5 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.java +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.java @@ -415,19 +415,6 @@ protected void checkWatchdogSupport(boolean isSupported) { } } - /** - * 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( - "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. From 904efa1d48e2c963379a4978837bb6822de7c0ef Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 2 Jun 2023 16:02:51 -0700 Subject: [PATCH 126/516] Bugfix in CI. --- .github/workflows/check-diff.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-diff.yml b/.github/workflows/check-diff.yml index af14720136..f714c3cd3e 100644 --- a/.github/workflows/check-diff.yml +++ b/.github/workflows/check-diff.yml @@ -36,7 +36,7 @@ jobs: - id: do run: | wget https://raw.githubusercontent.com/lf-lang/lingua-franca/master/.github/scripts/check-diff.sh - source check-diff.sh "core/src/main/java/generator/c\|/home/peter/lingua-franca/core/src/main/resources/lib/c\|core/src/main/resources/lib/platform\|test/C" C + source check-diff.sh "core/src/main/java/generator/c\|core/src/main/resources/lib/c\|core/src/main/resources/lib/platform\|test/C" C source check-diff.sh "core/src/main/kotlin/generator/cpp\|core/src/main/resources/lib/cpp\|test/Cpp" CPP source check-diff.sh "core/src/main/java/generator/python\|core/src/main/resources/lib/py\|test/Python" PY source check-diff.sh "core/src/main/kotlin/generator/rust\|core/src/main/java/generator/rust\|core/src/main/resources/lib/rs\|test/Rust" RS From cd8542dd8ab9e79064729ab0455a6b2a9c2e51dd Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 2 Jun 2023 16:20:45 -0700 Subject: [PATCH 127/516] Address failing unit tests. --- .../main/java/org/lflang/generator/ReactionInstanceGraph.java | 2 ++ core/src/main/resources/lib/c/reactor-c | 2 +- .../test/java/org/lflang/tests/compiler/PortInstanceTests.java | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java index 11567c961b..2628b30262 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java @@ -107,6 +107,8 @@ public void rebuild() { * @param main */ private void addDependentNetworkEdges(ReactorInstance main) { + // FIXME: I do not think this belongs here because it pertains to federated execution. Also, it + // seems to relate to a design that we do not intend to use? Attribute attribute = AttributeUtils.findAttributeByName(main.definition.getReactorClass(), "_fed_config"); String actionsStr = diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 3e82ce9c65..42ae9c2539 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 3e82ce9c655f4671d03738f14d2d367902c9389a +Subproject commit 42ae9c2539381835e7a8c74ff796ef05578b034d diff --git a/core/src/test/java/org/lflang/tests/compiler/PortInstanceTests.java b/core/src/test/java/org/lflang/tests/compiler/PortInstanceTests.java index 8b8ef52a8f..802bfdadd9 100644 --- a/core/src/test/java/org/lflang/tests/compiler/PortInstanceTests.java +++ b/core/src/test/java/org/lflang/tests/compiler/PortInstanceTests.java @@ -205,7 +205,7 @@ protected PortInstance newOutputPort(String name, ReactorInstance container) { protected ReactionInstance newReaction(PortInstance trigger) { Reaction r = factory.createReaction(); ReactionInstance result = - new ReactionInstance(r, trigger.getParent(), false, trigger.getDependentReactions().size()); + new ReactionInstance(r, trigger.getParent(), trigger.getDependentReactions().size()); trigger.getDependentReactions().add(result); trigger.getParent().reactions.add(result); return result; From 26aba407e3b1b2fb1fb5757fd7439ec4b6e45b4f Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 2 Jun 2023 17:16:28 -0700 Subject: [PATCH 128/516] Run all categories of C tests. I do not want to stop running tests when one category fails. I would also like to experiment with increasing the amount of parallelism. --- .github/workflows/c-tests.yml | 19 ++++++-- .github/workflows/only-c.yml | 87 ++++++++++++++++++++++++++++++++++- 2 files changed, 102 insertions(+), 4 deletions(-) diff --git a/.github/workflows/c-tests.yml b/.github/workflows/c-tests.yml index 956fb4c967..2bf7a3a371 100644 --- a/.github/workflows/c-tests.yml +++ b/.github/workflows/c-tests.yml @@ -20,9 +20,14 @@ on: required: false default: true type: boolean + selected-tests: + required: false + default: "" # "" means run all tests until failure + type: string jobs: regular-tests: + name: ${{ inputs.selected-tests }} strategy: matrix: platform: ${{ (inputs.all-platforms && fromJSON('["ubuntu-latest", "macos-latest", "windows-latest"]')) || fromJSON('["ubuntu-latest"]') }} @@ -55,15 +60,23 @@ jobs: if: ${{ runner.os == 'macOS' || runner.os == 'Linux' }} - name: Perform tests for C target with default scheduler run: ./gradlew targetTest -Ptarget=C - if: ${{ !inputs.use-cpp && !inputs.scheduler }} + if: ${{ !inputs.use-cpp && !inputs.scheduler && inputs.selected-tests == '' }} - name: Perform tests for C target with specified scheduler (no LSP tests) run: | echo "Specified scheduler: ${{ inputs.scheduler }}" ./gradlew targetTest -Ptarget=C -Dscheduler=${{ inputs.scheduler }} - if: ${{ !inputs.use-cpp && inputs.scheduler }} + if: ${{ !inputs.use-cpp && inputs.scheduler && inputs.selected-tests == '' }} - name: Perform tests for CCpp target with default scheduler run: ./gradlew targetTest -Ptarget=CCpp - if: ${{ inputs.use-cpp && !inputs.scheduler }} + if: ${{ inputs.use-cpp && !inputs.scheduler && inputs.selected-tests == '' }} + - name: Perform tests for C target with default scheduler + run: ./gradlew core:integrationTest --tests org.lflang.tests.runtime.CTest.${{ inputs.selected-tests }} + if: ${{ !inputs.use-cpp && !inputs.scheduler && inputs.selected-tests != '' }} + - name: Perform tests for C target with specified scheduler (no LSP tests) + run: | + echo "Specified scheduler: ${{ inputs.scheduler }}" + ./gradlew core:integrationTest --tests org.lflang.tests.runtime.CTest.${{ inputs.selected-tests }} -Dscheduler=${{ inputs.scheduler }} + if: ${{ inputs.use-cpp && !inputs.scheduler && inputs.selected-tests != '' }} - name: Collect code coverage run: ./gradlew jacocoTestReport - name: Report to CodeCov diff --git a/.github/workflows/only-c.yml b/.github/workflows/only-c.yml index bb4752471b..195624cedc 100644 --- a/.github/workflows/only-c.yml +++ b/.github/workflows/only-c.yml @@ -17,11 +17,96 @@ concurrency: jobs: # Run the C integration tests. - default: + default0: if: ${{ inputs.all || github.event.pull_request.draft }} uses: ./.github/workflows/c-tests.yml with: all-platforms: ${{ !github.event.pull_request.draft }} + selected-tests: runGenericTests + + default1: + if: ${{ inputs.all || github.event.pull_request.draft }} + uses: ./.github/workflows/c-tests.yml + with: + all-platforms: ${{ !github.event.pull_request.draft }} + selected-tests: runTargetSpecificTests + + default2: + if: ${{ inputs.all || github.event.pull_request.draft }} + uses: ./.github/workflows/c-tests.yml + with: + all-platforms: ${{ !github.event.pull_request.draft }} + selected-tests: runMultiportTests + + default3: + if: ${{ inputs.all || github.event.pull_request.draft }} + uses: ./.github/workflows/c-tests.yml + with: + all-platforms: ${{ !github.event.pull_request.draft }} + selected-tests: runTypeParameterTests + + default4: + if: ${{ inputs.all || github.event.pull_request.draft }} + uses: ./.github/workflows/c-tests.yml + with: + all-platforms: ${{ !github.event.pull_request.draft }} + selected-tests: runAsFederated + + default5: + if: ${{ inputs.all || github.event.pull_request.draft }} + uses: ./.github/workflows/c-tests.yml + with: + all-platforms: ${{ !github.event.pull_request.draft }} + selected-tests: runConcurrentTests + + default6: + if: ${{ inputs.all || github.event.pull_request.draft }} + uses: ./.github/workflows/c-tests.yml + with: + all-platforms: ${{ !github.event.pull_request.draft }} + selected-tests: runFederatedTests + + default7: + if: ${{ inputs.all || github.event.pull_request.draft }} + uses: ./.github/workflows/c-tests.yml + with: + all-platforms: ${{ !github.event.pull_request.draft }} + selected-tests: runModalTests + + default8: + if: ${{ inputs.all || github.event.pull_request.draft }} + uses: ./.github/workflows/c-tests.yml + with: + all-platforms: ${{ !github.event.pull_request.draft }} + selected-tests: runNoInliningTests + + default9: + if: ${{ inputs.all || github.event.pull_request.draft }} + uses: ./.github/workflows/c-tests.yml + with: + all-platforms: ${{ !github.event.pull_request.draft }} + selected-tests: runDockerTests + + default10: + if: ${{ inputs.all || github.event.pull_request.draft }} + uses: ./.github/workflows/c-tests.yml + with: + all-platforms: ${{ !github.event.pull_request.draft }} + selected-tests: runDockerFederatedTests + + default11: + if: ${{ inputs.all || github.event.pull_request.draft }} + uses: ./.github/workflows/c-tests.yml + with: + all-platforms: ${{ !github.event.pull_request.draft }} + selected-tests: runWithThreadingOff + + default12: + if: ${{ inputs.all || github.event.pull_request.draft }} + uses: ./.github/workflows/c-tests.yml + with: + all-platforms: ${{ !github.event.pull_request.draft }} + selected-tests: runEnclaveTests # Run the C benchmark tests. benchmarking: From 40b9ce26b03b46630b52fd3879d8841a370683e4 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sun, 4 Jun 2023 23:26:51 +0800 Subject: [PATCH 129/516] Only extend horizon when it becomes larger --- org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java index 381a98a49b..9cc6d308f3 100644 --- a/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java +++ b/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java @@ -174,7 +174,7 @@ public String visitUntil(MTLParser.UntilContext ctx, long lowerBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); long currentHorizon = horizon + upperBoundNanoSec; - this.horizon = currentHorizon; + if (currentHorizon > this.horizon) this.horizon = currentHorizon; String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, upperBoundNanoSec, "j" + QFIdx, prefixIdx); @@ -204,7 +204,7 @@ public String visitNegation(MTLParser.NegationContext ctx, public String visitNext(MTLParser.NextContext ctx, String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - + // FIXME: Horizon needs to advance! return visitPrimary(ctx.formula, ("(" + prefixIdx + "+1)"), QFIdx, prevPrefixIdx, horizon); } @@ -221,7 +221,7 @@ public String visitGlobally(MTLParser.GloballyContext ctx, long lowerBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); long currentHorizon = horizon + upperBoundNanoSec; - this.horizon = currentHorizon; + if (currentHorizon > this.horizon) this.horizon = currentHorizon; String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, upperBoundNanoSec, "j" + QFIdx, prefixIdx); return "(" + "finite_forall " + "(" + "j" + QFIdx + " : integer) in indices :: " @@ -248,7 +248,7 @@ public String visitFinally(MTLParser.FinallyContext ctx, long lowerBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); long currentHorizon = horizon + upperBoundNanoSec; - this.horizon = currentHorizon; + if (currentHorizon > this.horizon) this.horizon = currentHorizon; String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, upperBoundNanoSec, "j" + QFIdx, prefixIdx); return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " From a36c9918ba7e4c175dc07e36acbffda55e2da5c8 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 5 Jun 2023 01:14:54 +0800 Subject: [PATCH 130/516] Remove top level org.lflang --- org.lflang/build.gradle | 334 --- org.lflang/src/org/lflang/ASTUtils.java | 1808 ---------------- org.lflang/src/org/lflang/FileConfig.java | 358 ---- org.lflang/src/org/lflang/TargetConfig.java | 514 ----- org.lflang/src/org/lflang/TargetProperty.java | 1872 ----------------- org.lflang/src/org/lflang/TimeValue.java | 234 --- .../analyses/cast/AbstractAstVisitor.java | 17 - .../org/lflang/analyses/cast/AstUtils.java | 78 - .../org/lflang/analyses/cast/AstVisitor.java | 25 - .../cast/BuildAstParseTreeVisitor.java | 681 ------ .../src/org/lflang/analyses/cast/CAst.java | 361 ---- .../org/lflang/analyses/cast/CAstVisitor.java | 78 - .../lflang/analyses/cast/CBaseAstVisitor.java | 360 ---- .../lflang/analyses/cast/CToUclidVisitor.java | 291 --- .../analyses/cast/IfNormalFormAstVisitor.java | 95 - .../cast/VariablePrecedenceVisitor.java | 50 - .../org/lflang/analyses/cast/Visitable.java | 12 - .../org/lflang/analyses/statespace/Event.java | 55 - .../analyses/statespace/EventQueue.java | 24 - .../lflang/analyses/statespace/StateInfo.java | 29 - .../statespace/StateSpaceDiagram.java | 192 -- .../statespace/StateSpaceExplorer.java | 376 ---- .../analyses/statespace/StateSpaceNode.java | 114 - .../org/lflang/analyses/statespace/Tag.java | 67 - .../org/lflang/analyses/uclid/MTLVisitor.java | 465 ---- .../lflang/analyses/uclid/UclidGenerator.java | 1611 -------------- .../lflang/analyses/uclid/UclidRunner.java | 264 --- org.lflang/src/org/lflang/cli/Lfc.java | 286 --- org.lflang/src/org/lflang/dsl/antlr4/C.g4 | 908 -------- .../src/org/lflang/dsl/antlr4/MTLLexer.g4 | 117 -- .../src/org/lflang/dsl/antlr4/MTLParser.g4 | 82 - .../org/lflang/generator/GeneratorBase.java | 705 ------- .../src/org/lflang/generator/LFGenerator.java | 238 --- .../lflang/generator/LFGeneratorContext.java | 165 -- .../org/lflang/generator/NamedInstance.java | 334 --- .../lflang/generator/ReactionInstance.java | 598 ------ .../generator/ReactionInstanceGraph.java | 440 ---- .../org/lflang/generator/ReactorInstance.java | 1156 ---------- .../org/lflang/generator/TriggerInstance.java | 180 -- .../org/lflang/validation/AttributeSpec.java | 241 --- 40 files changed, 15815 deletions(-) delete mode 100644 org.lflang/build.gradle delete mode 100644 org.lflang/src/org/lflang/ASTUtils.java delete mode 100644 org.lflang/src/org/lflang/FileConfig.java delete mode 100644 org.lflang/src/org/lflang/TargetConfig.java delete mode 100644 org.lflang/src/org/lflang/TargetProperty.java delete mode 100644 org.lflang/src/org/lflang/TimeValue.java delete mode 100644 org.lflang/src/org/lflang/analyses/cast/AbstractAstVisitor.java delete mode 100644 org.lflang/src/org/lflang/analyses/cast/AstUtils.java delete mode 100644 org.lflang/src/org/lflang/analyses/cast/AstVisitor.java delete mode 100644 org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java delete mode 100644 org.lflang/src/org/lflang/analyses/cast/CAst.java delete mode 100644 org.lflang/src/org/lflang/analyses/cast/CAstVisitor.java delete mode 100644 org.lflang/src/org/lflang/analyses/cast/CBaseAstVisitor.java delete mode 100644 org.lflang/src/org/lflang/analyses/cast/CToUclidVisitor.java delete mode 100644 org.lflang/src/org/lflang/analyses/cast/IfNormalFormAstVisitor.java delete mode 100644 org.lflang/src/org/lflang/analyses/cast/VariablePrecedenceVisitor.java delete mode 100644 org.lflang/src/org/lflang/analyses/cast/Visitable.java delete mode 100644 org.lflang/src/org/lflang/analyses/statespace/Event.java delete mode 100644 org.lflang/src/org/lflang/analyses/statespace/EventQueue.java delete mode 100644 org.lflang/src/org/lflang/analyses/statespace/StateInfo.java delete mode 100644 org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java delete mode 100644 org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java delete mode 100644 org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java delete mode 100644 org.lflang/src/org/lflang/analyses/statespace/Tag.java delete mode 100644 org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java delete mode 100644 org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java delete mode 100644 org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java delete mode 100644 org.lflang/src/org/lflang/cli/Lfc.java delete mode 100644 org.lflang/src/org/lflang/dsl/antlr4/C.g4 delete mode 100644 org.lflang/src/org/lflang/dsl/antlr4/MTLLexer.g4 delete mode 100644 org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 delete mode 100644 org.lflang/src/org/lflang/generator/GeneratorBase.java delete mode 100644 org.lflang/src/org/lflang/generator/LFGenerator.java delete mode 100644 org.lflang/src/org/lflang/generator/LFGeneratorContext.java delete mode 100644 org.lflang/src/org/lflang/generator/NamedInstance.java delete mode 100644 org.lflang/src/org/lflang/generator/ReactionInstance.java delete mode 100644 org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java delete mode 100644 org.lflang/src/org/lflang/generator/ReactorInstance.java delete mode 100644 org.lflang/src/org/lflang/generator/TriggerInstance.java delete mode 100644 org.lflang/src/org/lflang/validation/AttributeSpec.java diff --git a/org.lflang/build.gradle b/org.lflang/build.gradle deleted file mode 100644 index 849b46aed4..0000000000 --- a/org.lflang/build.gradle +++ /dev/null @@ -1,334 +0,0 @@ -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/" - } -} -dependencies { - implementation "org.eclipse.xtext:org.eclipse.xtext:${xtextVersion}" - implementation "org.eclipse.xtext:org.eclipse.xtext.xbase.lib:${xtextVersion}" - implementation "org.eclipse.xtext:org.eclipse.xtext.ide:${xtextVersion}" - // https://mvnrepository.com/artifact/org.eclipse.platform/org.eclipse.core.resources - implementation group: 'org.eclipse.platform', name: 'org.eclipse.core.resources', version: "${resourcesVersion}" - // https://mvnrepository.com/artifact/org.eclipse.emf/org.eclipse.emf.mwe2.launch - implementation group: 'org.eclipse.emf', name: 'org.eclipse.emf.mwe2.launch', version: "${mwe2LaunchVersion}" - // https://mvnrepository.com/artifact/org.eclipse.lsp4j/org.eclipse.lsp4j - implementation group: 'org.eclipse.lsp4j', name: 'org.eclipse.lsp4j', version: "${lsp4jVersion}" - 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' - } -} - -configurations { - mwe2 { - extendsFrom implementation - } -} - -dependencies { - mwe2 'org.eclipse.emf:org.eclipse.emf.mwe2.launch' - mwe2 "org.eclipse.xtext:org.eclipse.xtext.common.types:${xtextVersion}" - mwe2 "org.eclipse.xtext:org.eclipse.xtext.xtext.generator:${xtextVersion}" -} - -task generateXtextLanguage(type: JavaExec) { - main = 'org.eclipse.emf.mwe2.launch.runtime.Mwe2Launcher' - classpath = configurations.mwe2 - inputs.file "src/org/lflang/GenerateLinguaFranca.mwe2" - inputs.file "src/org/lflang/LinguaFranca.xtext" - outputs.dir "src-gen" - args += "src/org/lflang/GenerateLinguaFranca.mwe2" - args += "-p" - args += "rootPath=/${projectDir}/.." - - // Currently we don't need to delete any generated files because we use the - // Java/Xtend implementations. However, if we commit to porting such files - // to Kotlin, we might to reintroduce the deletion mechanism below. - /*doLast { - def filesToDelete = [ - "org.lflang.validation.LFValidator", - "org.lflang.LFRuntimeModule", - "org.lflang.LFStandaloneSetup", - "org.lflang.generator.LFGenerator", - "org.lflang.scoping.LFScopeProvider" - ] - - filesToDelete.each { qname -> - def path = qname.replace('.', '/') - def ktFile = file("src/${path}.kt") - def javaFile = file("src/${path}.java") - def xtendFile = file("src/${path}.xtend") - - if (ktFile.exists() || xtendFile.exists()) { - def chosenLang = ktFile.exists() ? "Kotlin" : "Xtend" - project.logger.info("deleting ${projectDir.relativePath(javaFile)}, the ${chosenLang} file prevails") - project.delete(javaFile) // generated by Xtend - } else { - project.logger.info("no ${projectDir.relativePath(ktFile)}, leaving the Java implementation in") - } - } - }*/ -} -processResources.dependsOn(generateXtextLanguage) -generateXtext.dependsOn(generateXtextLanguage) -clean.dependsOn(cleanGenerateXtextLanguage) -eclipse.classpath.plusConfigurations += [configurations.mwe2] - -configurations { - cli_impl { - extendsFrom implementation - } -} - -task getSubmoduleVersions(type: Exec) { - description('Run a Git command to get the current status of submodules') - workingDir project.rootDir - // This will make gradle execute git submodule status every time updateRustRuntime is called - outputs.upToDateWhen { false } - - def command = "git submodule status" - if (System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows')) { - commandLine 'cmd', '/c', command - } else { - commandLine 'sh', '-c', command - } - standardOutput = new ByteArrayOutputStream() - - ext.outputFile = file("$project.buildDir/submodule-status.properties") - outputs.file(outputFile) - - doLast { - def matcher = standardOutput.toString() =~ ~"(?m)^[-+ ]?([0-9a-f]+) org.lflang/src/lib/\\w+/reactor-([-a-zA-Z]+)" - def properties = new StringBuilder() - while (matcher.find()) { - def rev = matcher.group(1) - def language = matcher.group(2) - properties << "$language = $rev\n" - } - outputFile.text = properties - } -} - -task updateRustRuntime { - description('Record the VCS revisions of the language runtimes into a properties file available at runtime.') - - dependsOn getSubmoduleVersions - // If the output of the git submodule status did not change (the getSubmoduleVersions task), then this task is considered up to date. - inputs.files(getSubmoduleVersions.outputs) - ext.outputFile = file("$project.projectDir/src/lib/rs/runtime-version.properties") - outputs.file(outputFile) - - doLast { - def upToDateProps = new Properties() - getSubmoduleVersions.outputFile.withReader { r -> upToDateProps.load(r) } - outputFile.text = "rs = " + upToDateProps.get("rs") + "\n" - } -} -// add the generated file as an output -sourceSets.main.output.dir tasks.updateRustRuntime.outputFile, builtBy: updateRustRuntime -tasks.processResources.dependsOn(updateRustRuntime) - -task checkRuntimeVersionFileUpToDate { - description('Check that the runtime version recorded in the built Jar for LFC matches the version of the checked out submodule') - dependsOn getSubmoduleVersions - inputs.file updateRustRuntime.outputFile - - doLast { - def rtProps = new Properties() - updateRustRuntime.outputFile.withReader { r -> rtProps.load(r) } - def upToDateProps = new Properties() - getSubmoduleVersions.outputFile.withReader { r -> upToDateProps.load(r) } - - upToDateProps.each { language, rev -> - def actualLanguage = rtProps.get(language) - if (actualLanguage == null) - return - if (actualLanguage != rev) { - logger.error("Runtime for $language is not checked out at correct revision:\n" + - "expected: $rev\n" + - "actual: $actualLanguage\n" + - "You may need to call `./gradlew updateRustRuntime`.") - } else { - logger.info("Success: Runtime for $language is checked out at expected revision $rev") - } - } - } -} - -apply plugin: 'application' -apply plugin: 'com.github.johnrengelman.shadow' - -task buildAll() { - apply plugin: 'application' - apply plugin: 'com.github.johnrengelman.shadow' - mainClassName = 'org.lflang.cli.Lfc' -} - -// define buildLfc as an alias to buildAll -task buildLfc { - dependsOn buildAll - doFirst({{logger.warn("The buildLfc task is deprecated! Please use buildAll instead.")}}) -} - -task jarCliTools(type: ShadowJar) { - manifest { - attributes('Main-Class': 'org.lflang.cli.Lfc') - } - configurations = [project.configurations.cli_impl] - exclude 'test/*' - exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA' - // We should use minimize() here to reduce the size of the JAR, but it causes problems - // with regard to our Kotlin classes. Since we don't use imports to load them but load - // the classes manually, minimize does not see the dependency. While we can add an exclude - // rule, this does not seem to work very well and causes problems when compiling for a - // second time. Also see https://github.com/lf-lang/lingua-franca/pull/1285 - transform(com.github.jengelman.gradle.plugins.shadow.transformers.AppendingTransformer){ - resource = 'plugin.properties' - } - from sourceSets.main.output -} - -buildAll.finalizedBy jarCliTools - -// Tasks for running specific CLI tools. -// The working directory will be the root directory of the lingua franca project -// CLI arguments can be passed to lfc by using --args. Note that you need -// to escape cli flags which start with --.For instance --args ' --help'. -// Otherwise they're parsed as arguments to the Gradle CLI, not lfc. - -task runLfc(type: JavaExec) { - // builds and runs lfc - description = "Build and run lfc, use --args to pass arguments" - group = "application" - classpath = sourceSets.main.runtimeClasspath - mainClass = 'org.lflang.cli.Lfc' - workingDir = '..' -} - -task runLff(type: JavaExec) { - // builds and runs lff - description = "Build and run lff, use --args to pass arguments" - group = "application" - classpath = sourceSets.main.runtimeClasspath - mainClass = 'org.lflang.cli.Lff' - workingDir = '..' -} - -// Add Antlr4 for various DSLs, including MTL for verification. -task runAntlr4(type:JavaExec) { - //see incremental task api, prevents rerun if nothing has changed. - inputs.dir "$projectDir/src/org/lflang/dsl/antlr4/" - outputs.dir "$projectDir/src/org/lflang/dsl/generated/antlr4/main/" - - classpath = configurations.antlr4 - - main = "org.antlr.v4.Tool" - - args = [ "-visitor", - "-o", "$projectDir/src/org/lflang/dsl/generated/antlr4/main/", - "-package", "org.lflang.dsl", - "$projectDir/src/org/lflang/dsl/antlr4/MTLLexer.g4", - "$projectDir/src/org/lflang/dsl/antlr4/MTLParser.g4", - "$projectDir/src/org/lflang/dsl/antlr4/C.g4"] -} -processResources.dependsOn(runAntlr4) -compileKotlin.dependsOn(runAntlr4) -compileJava.dependsOn(runAntlr4) - -clean { - delete file("${projectDir}/src/org/lflang/dsl/generated") -} - -task generateLanguageDiagramServer { - description 'Creates a jar that implements a language server with diagram support for LF.' - - apply plugin: 'java' - apply plugin: 'application' - apply plugin: 'com.github.johnrengelman.shadow' - - mainClassName = "org.lflang.diagram.lsp.LanguageDiagramServer" - - compileJava { - options.compilerArgs << '-Xlint:unchecked' - } - - shadowJar { - classifier = 'lds' - - // Handling of service loader registrations via META-INF/services/* - mergeServiceFiles() - - // Merge properties - transform(com.github.jengelman.gradle.plugins.shadow.transformers.AppendingTransformer){ - resource = 'plugin.properties' - } - - // Exclude files that are known to be dispensable for a language server - exclude( - '*._trace', - '*.ecore', - '*.ecorediag', - '*.g', - '*.genmodel', - '*.html', - '*.mwe2', - '*.profile', - '*.xtext', - '*readme.txt', - '.api_description', - '.options', - 'about.*', - 'about_*', - 'about_files/*', - 'ant_tasks/*', - 'cheatsheets/*', - 'com/*/*.java', - 'de/*/*.java', - 'docs/*', - 'log4j.properties', - 'META-INF/*.DSA', - 'META-INF/*.RSA', - 'META-INF/*.SF', - 'META-INF/changelog.txt', - 'META-INF/DEPENDENCIES', - 'META-INF/eclipse.inf', - 'META-INF/INDEX.LIST', - 'META-INF/maven/*', - 'META-INF/NOTICE', - 'META-INF/NOTICE.txt', - 'META-INF/p2.inf', - 'META-INF/versions/*/module-info.class', - 'modeling32.png', - 'module-info.class', - 'org/*/*.java', - 'OSGI-INF/l10n/bundle.properties', - 'plugin.xml', - 'profile.list', - 'schema/*', - 'systembundle.properties', - 'xtend-gen/*', - 'xtext32.png', - ) - - // Minimizing should be enabled with caution because some classes are only - // loaded via services (e.g. diagram syntheses and some xtext aspects) - // and would be removed when minimized - // minimize() { - // exclude(dependency('log4j:log4j:.*')) - // exclude(dependency('org.eclipse.xtext:.*ide:.*')) - // } - } -} - -generateLanguageDiagramServer.finalizedBy shadowJar diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java deleted file mode 100644 index 5b6b5ff276..0000000000 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ /dev/null @@ -1,1808 +0,0 @@ -/* -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; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -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; -import java.util.regex.Pattern; -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; -import org.eclipse.emf.ecore.resource.Resource; -import org.eclipse.xtext.TerminalRule; -import org.eclipse.xtext.nodemodel.ICompositeNode; -import org.eclipse.xtext.nodemodel.INode; -import org.eclipse.xtext.nodemodel.impl.HiddenLeafNode; -import org.eclipse.xtext.nodemodel.util.NodeModelUtils; -import org.eclipse.xtext.util.Pair; -import org.eclipse.xtext.util.Tuples; -import org.eclipse.xtext.xbase.lib.IterableExtensions; -import org.eclipse.xtext.xbase.lib.IteratorExtensions; -import org.eclipse.xtext.xbase.lib.StringExtensions; -import org.lflang.ast.ToText; -import org.lflang.generator.CodeMap; -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; -import org.lflang.lf.Expression; -import org.lflang.lf.ImportedReactor; -import org.lflang.lf.Initializer; -import org.lflang.lf.Input; -import org.lflang.lf.Instantiation; -import org.lflang.lf.LfFactory; -import org.lflang.lf.LfPackage; -import org.lflang.lf.Literal; -import org.lflang.lf.Method; -import org.lflang.lf.Mode; -import org.lflang.lf.Model; -import org.lflang.lf.Output; -import org.lflang.lf.Parameter; -import org.lflang.lf.ParameterReference; -import org.lflang.lf.Port; -import org.lflang.lf.Reaction; -import org.lflang.lf.Reactor; -import org.lflang.lf.ReactorDecl; -import org.lflang.lf.StateVar; -import org.lflang.lf.TargetDecl; -import org.lflang.lf.Time; -import org.lflang.lf.Timer; -import org.lflang.lf.Type; -import org.lflang.lf.VarRef; -import org.lflang.lf.Variable; -import org.lflang.lf.WidthSpec; -import org.lflang.lf.WidthTerm; -import org.lflang.util.StringUtil; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Iterables; -import com.google.common.collect.Iterators; - -/** - * A helper class for modifying and analyzing the AST. - * @author Marten Lohstroh - * @author Edward A. Lee - * @author Christian Menard - */ -public class ASTUtils { - - /** - * The Lingua Franca factory for creating new AST nodes. - */ - public static final LfFactory factory = LfFactory.eINSTANCE; - - /** - * The Lingua Franca feature package. - */ - public static final LfPackage featurePackage = LfPackage.eINSTANCE; - - /* Match an abbreviated form of a float literal. */ - private static final Pattern ABBREVIATED_FLOAT = Pattern.compile("[+\\-]?\\.\\d+[\\deE+\\-]*"); - - /** - * A mapping from Reactor features to corresponding Mode features for collecting contained elements. - */ - private static final Map reactorModeFeatureMap = Map.of( - featurePackage.getReactor_Actions(), featurePackage.getMode_Actions(), - featurePackage.getReactor_Connections(), featurePackage.getMode_Connections(), - featurePackage.getReactor_Instantiations(), featurePackage.getMode_Instantiations(), - featurePackage.getReactor_Reactions(), featurePackage.getMode_Reactions(), - featurePackage.getReactor_StateVars(), featurePackage.getMode_StateVars(), - featurePackage.getReactor_Timers(), featurePackage.getMode_Timers() - ); - - - /** - * Get all reactors defined in the given resource. - * @param resource the resource to extract reactors from - * @return An iterable over all reactors found in the resource - */ - public static List getAllReactors(Resource resource) { - return StreamSupport.stream(IteratorExtensions.toIterable(resource.getAllContents()).spliterator(), false) - .filter(Reactor.class::isInstance) - .map(Reactor.class::cast) - .collect(Collectors.toList()); - } - - /** - * Get the main reactor 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 Reactor getMainReactor(Resource resource) { - return StreamSupport.stream(IteratorExtensions.toIterable(resource.getAllContents()).spliterator(), false) - .filter(Reactor.class::isInstance) - .map(Reactor.class::cast) - .filter(it -> it.isMain()) - .findFirst() - .get(); - } - - /** - * Find connections in the given resource that would be conflicting writes if they were not located in mutually - * exclusive modes. - * - * @param resource The AST. - * @return a list of connections being able to be transformed - */ - public static Collection findConflictingConnectionsInModalReactors(Resource resource) { - var transform = new HashSet(); - - for (Reactor reactor : getAllReactors(resource)) { - if (!reactor.getModes().isEmpty()) { // Only for modal reactors - var allWriters = HashMultimap., EObject>create(); - - // Collect destinations - for (var rea : allReactions(reactor)) { - for (var eff : rea.getEffects()) { - if (eff.getVariable() instanceof Port) { - allWriters.put(Tuples.pair(eff.getContainer(), eff.getVariable()), rea); - } - } - } - for (var con : ASTUtils.collectElements(reactor, featurePackage.getReactor_Connections(), false, true)) { - for (var port : con.getRightPorts()) { - allWriters.put(Tuples.pair(port.getContainer(), port.getVariable()), con); - } - } - - // Handle conflicting writers - for (var key : allWriters.keySet()) { - var writers = allWriters.get(key); - if (writers.size() > 1) { // has multiple sources - var writerModes = HashMultimap.create(); - // find modes - for (var writer : writers) { - if (writer.eContainer() instanceof Mode) { - writerModes.put((Mode) writer.eContainer(), writer); - } else { - writerModes.put(null, writer); - } - } - // Conflicting connection can only be handled if.. - if (!writerModes.containsKey(null) && // no writer is on root level (outside of modes) and... - writerModes.keySet().stream().map(writerModes::get).allMatch(writersInMode -> // all writers in a mode are either... - writersInMode.size() == 1 || // the only writer or... - writersInMode.stream().allMatch(w -> w instanceof Reaction) // all are reactions and hence ordered - )) { - // Add connections to transform list - writers.stream().filter(w -> w instanceof Connection).forEach(c -> transform.add((Connection) c)); - } - } - } - } - } - - return transform; - } - - /** - * Return the enclosing reactor of an LF EObject in a reactor or mode. - * @param obj the LF model element - * @return the reactor or null - */ - public static Reactor getEnclosingReactor(EObject obj) { - if (obj.eContainer() instanceof Reactor) { - return (Reactor) obj.eContainer(); - } else if (obj.eContainer() instanceof Mode) { - return (Reactor) obj.eContainer().eContainer(); - } - return null; - } - - /** - * Return the main reactor in the given resource if there is one, null otherwise. - */ - public static Reactor findMainReactor(Resource resource) { - return IteratorExtensions.findFirst( - Iterators.filter(resource.getAllContents(), Reactor.class), - Reactor::isMain - ); - } - - /** - * Find the main reactor and change it to a federated reactor. - * Return true if the transformation was successful (or the given resource - * already had a federated reactor); return false otherwise. - */ - public static boolean makeFederated(Resource resource) { - // Find the main reactor - Reactor r = findMainReactor(resource); - if (r == null) { - return false; - } - r.setMain(false); - r.setFederated(true); - return true; - } - - /** - * Change the target name to 'newTargetName'. - * For example, change C to CCpp. - */ - public static boolean changeTargetName(Resource resource, String newTargetName) { - targetDecl(resource).setName(newTargetName); - return true; - } - - /** - * 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 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)); - } - } - } - - // 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; - } 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()); - - 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; - } - } 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; - } - if (!ASTUtils.isZero(e)) { - foundNonZero = true; - } - } - - if (allValidTime && foundNonZero) { - // Conservatively, no bounds are inferred; the returned type - // is a variable-size list. - return InferredType.timeList(); - } - } - return InferredType.undefined(); - } - - /** - * Given a parameter, return an inferred type. Only two types can be - * inferred: "time" and "timeList". Return the "undefined" type if - * neither can be inferred. - * - * @param p A parameter to infer the type of. - * @return The inferred type, or "undefined" if none could be inferred. - */ - public static InferredType getInferredType(Parameter p) { - return getInferredType(p.getType(), p.getInit()); - } - - /** - * Given a state variable, return an inferred type. Only two types can be - * inferred: "time" and "timeList". Return the "undefined" type if - * neither can be inferred. - * - * @param s A state variable to infer the type of. - * @return The inferred type, or "undefined" if none could be inferred. - */ - public static InferredType getInferredType(StateVar s) { - return getInferredType(s.getType(), s.getInit()); - } - - /** - * Construct an inferred type from an "action" AST node based - * on its declared type. If no type is declared, return the "undefined" - * type. - * - * @param a An action to construct an inferred type object for. - * @return The inferred type, or "undefined" if none was declared. - */ - public static InferredType getInferredType(Action a) { - return getInferredType(a.getType(), null); - } - - /** - * Construct an inferred type from a "port" AST node based on its declared - * type. If no type is declared, return the "undefined" type. - * - * @param p A port to construct an inferred type object for. - * @return The inferred type, or "undefined" if none was declared. - */ - public static InferredType getInferredType(Port p) { - return getInferredType(p.getType(), null); - } - - - - /** - * If the given string can be recognized as a floating-point number that has a leading decimal point, - * prepend the string with a zero and return it. Otherwise, return the original string. - * - * @param literal A string might be recognizable as a floating point number with a leading decimal point. - * @return an equivalent representation of literal - * - */ - public static String addZeroToLeadingDot(String literal) { - Matcher m = ABBREVIATED_FLOAT.matcher(literal); - if (m.matches()) { - return literal.replace(".", "0."); - } - return literal; - } - - /** - * Return true if the specified port is a multiport. - * @param port The port. - * @return True if the port is a multiport. - */ - public static boolean isMultiport(Port port) { - return port.getWidthSpec() != null; - } - - //////////////////////////////// - //// Utility functions for translating AST nodes into text - // This is a continuation of a large section of ASTUtils.xtend - // with the same name. - - /** - * Generate code for referencing a port, action, or timer. - * @param reference The reference to the variable. - */ - public static String generateVarRef(VarRef reference) { - var prefix = ""; - if (reference.getContainer() != null) { - prefix = reference.getContainer().getName() + "."; - } - return prefix + reference.getVariable().getName(); - } - - /** - * Assuming that the given expression denotes a valid time literal, - * return a time value. - */ - public static TimeValue getLiteralTimeValue(Expression expr) { - if (expr instanceof Time) { - return toTimeValue((Time)expr); - } else if (expr instanceof Literal && isZero(((Literal) expr).getLiteral())) { - return TimeValue.ZERO; - } else { - return null; - } - } - - /** - * If the parameter is of time type, return its default value. - * Otherwise, return null. - */ - public static TimeValue getDefaultAsTimeValue(Parameter p) { - if (isOfTimeType(p)) { - var init = asSingleExpr(p.getInit()); - if (init != null) { - return getLiteralTimeValue(init); - } - } - return null; - } - - /** - * Return whether the given state variable is inferred - * to a time type. - */ - public static boolean isOfTimeType(StateVar state) { - InferredType t = getInferredType(state); - return t.isTime && !t.isList; - } - - /** - * Return whether the given parameter is inferred - * to a time type. - */ - public static boolean isOfTimeType(Parameter param) { - InferredType t = getInferredType(param); - return t.isTime && !t.isList; - } - - /** - * Given a parameter, return its initial value. - * The initial value is a list of instances of Expressions. - * - * If the instantiations argument is null or an empty list, then the - * value returned is simply the default value given when the parameter - * is defined. - * - * If a list of instantiations is given, then the first instantiation - * is required to be an instantiation of the reactor class that is - * parameterized by the parameter. I.e., - * ``` - * parameter.eContainer == instantiations.get(0).reactorClass - * ``` - * If a second instantiation is given, then it is required to be an instantiation of a - * reactor class that contains the first instantiation. That is, - * ``` - * instantiations.get(0).eContainer == instantiations.get(1).reactorClass - * ``` - * More generally, for all 0 <= i < instantiations.size - 1, - * ``` - * instantiations.get(i).eContainer == instantiations.get(i + 1).reactorClass - * ``` - * If any of these conditions is not satisfied, then an IllegalArgumentException - * will be thrown. - * - * Note that this chain of reactions cannot be inferred from the parameter because - * in each of the predicates above, there may be more than one instantiation that - * can appear on the right hand side of the predicate. - * - * For example, consider the following program: - * ``` - * reactor A(x:int(1)) {} - * reactor B(y:int(2)) { - * a1 = new A(x = y); - * a2 = new A(x = -1); - * } - * reactor C(z:int(3)) { - * b1 = new B(y = z); - * b2 = new B(y = -2); - * } - * ``` - * Notice that there are a total of four instances of reactor class A. - * Then - * ``` - * initialValue(x, null) returns 1 - * initialValue(x, [a1]) returns 2 - * initialValue(x, [a2]) returns -1 - * initialValue(x, [a1, b1]) returns 3 - * initialValue(x, [a2, b1]) returns -1 - * initialValue(x, [a1, b2]) returns -2 - * initialValue(x, [a2, b2]) returns -1 - * ``` - * (Actually, in each of the above cases, the returned value is a list with - * one entry, a Literal, e.g. ["1"]). - * - * There are two instances of reactor class B. - * ``` - * initialValue(y, null) returns 2 - * initialValue(y, [a1]) throws an IllegalArgumentException - * initialValue(y, [b1]) returns 3 - * initialValue(y, [b2]) returns -2 - * ``` - * - * @param parameter The parameter. - * @param instantiations The (optional) list of instantiations. - * - * @return The value of the parameter. - * - * @throws IllegalArgumentException If an instantiation provided is not an - * instantiation of the reactor class that is parameterized by the - * respective parameter or if the chain of instantiations is not nested. - */ - public static List initialValue(Parameter parameter, List instantiations) { - // If instantiations are given, then check to see whether this parameter gets overridden in - // the first of those instantiations. - if (instantiations != null && instantiations.size() > 0) { - // Check to be sure that the instantiation is in fact an instantiation - // of the reactor class for which this is a parameter. - Instantiation instantiation = instantiations.get(0); - - if (!belongsTo(parameter, instantiation)) { - throw new IllegalArgumentException("Parameter " - + parameter.getName() - + " is not a parameter of reactor instance " - + instantiation.getName() - + "." - ); - } - // In case there is more than one assignment to this parameter, we need to - // find the last one. - Assignment lastAssignment = null; - for (Assignment assignment: instantiation.getParameters()) { - if (assignment.getLhs().equals(parameter)) { - lastAssignment = assignment; - } - } - if (lastAssignment != null) { - // Right hand side can be a list. Collect the entries. - List result = new ArrayList<>(); - for (Expression expr: lastAssignment.getRhs().getExprs()) { - if (expr instanceof ParameterReference) { - if (instantiations.size() > 1 - && instantiation.eContainer() != instantiations.get(1).getReactorClass() - ) { - throw new IllegalArgumentException("Reactor instance " - + instantiation.getName() - + " is not contained by instance " - + instantiations.get(1).getName() - + "." - ); - } - result.addAll(initialValue(((ParameterReference)expr).getParameter(), - instantiations.subList(1, instantiations.size()))); - } else { - result.add(expr); - } - } - return result; - } - } - // If we reach here, then either no instantiation was supplied or - // there was no assignment in the instantiation. So just use the - // parameter's initial value. - return parameter.getInit().getExprs(); - } - - /** - * Return true if the specified object (a Parameter, Port, Action, or Timer) - * belongs to the specified instantiation, meaning that it is defined in - * the reactor class being instantiated or one of its base classes. - * @param eobject The object. - * @param instantiation The instantiation. - */ - public static boolean belongsTo(EObject eobject, Instantiation instantiation) { - Reactor reactor = toDefinition(instantiation.getReactorClass()); - return belongsTo(eobject, reactor); - } - - /** - * Return true if the specified object (a Parameter, Port, Action, or Timer) - * belongs to the specified reactor, meaning that it is defined in - * reactor class or one of its base classes. - * @param eobject The object. - * @param reactor The reactor. - */ - public static boolean belongsTo(EObject eobject, Reactor reactor) { - if (eobject.eContainer() == reactor) return true; - for (ReactorDecl baseClass : reactor.getSuperClasses()) { - if (belongsTo(eobject, toDefinition(baseClass))) { - return true; - } - } - return false; - } - - /** - * Given a parameter return its integer value or null - * if it does not have an integer value. - * If the value of the parameter is a list of integers, - * return the sum of value in the list. - * The instantiations parameter is as in - * {@link #initialValue(Parameter, List)}. - * - * @param parameter The parameter. - * @param instantiations The (optional) list of instantiations. - * - * @return The integer value of the parameter, or null if it does not have an integer value. - * - * @throws IllegalArgumentException If an instantiation provided is not an - * instantiation of the reactor class that is parameterized by the - * respective parameter or if the chain of instantiations is not nested. - */ - public static Integer initialValueInt(Parameter parameter, List instantiations) { - List expressions = initialValue(parameter, instantiations); - int result = 0; - for (Expression expr: expressions) { - if (!(expr instanceof Literal)) { - return null; - } - try { - result += Integer.decode(((Literal) expr).getLiteral()); - } catch (NumberFormatException ex) { - return null; - } - } - 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; - } - } 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); - } - } - - /** - * 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/FileConfig.java b/org.lflang/src/org/lflang/FileConfig.java deleted file mode 100644 index 69b952fa6f..0000000000 --- a/org.lflang/src/org/lflang/FileConfig.java +++ /dev/null @@ -1,358 +0,0 @@ -package org.lflang; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.function.Consumer; - -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.emf.common.util.URI; -import org.eclipse.emf.ecore.resource.Resource; -import org.eclipse.xtext.generator.IFileSystemAccess2; - - -import org.lflang.federated.generator.FedFileConfig; -import org.lflang.generator.GeneratorUtils; -import org.lflang.util.FileUtil; -import org.lflang.util.LFCommand; - -/** - * Base class that governs the interactions between code generators and the file system. - * - * @author Marten Lohstroh - * - */ -public abstract class FileConfig { - - // Public static fields. - - public static final String DEFAULT_SRC_DIR = "src"; - - /** - * Default name of the directory to store binaries in. - */ - public static final String DEFAULT_BIN_DIR = "bin"; - - /** - * Default name of the directory to store generated sources in. - */ - public static final String DEFAULT_SRC_GEN_DIR = "src-gen"; - - /** - * Default name of the directory to store generated verification models in. - */ - public static final String DEFAULT_MODEL_GEN_DIR = "model-gen"; - - // Public fields. - - /** - * The directory in which to put binaries, if the code generator produces any. - */ - public final Path binPath; - - /** - * The name of the main reactor, which has to match the file name (without - * the '.lf' extension). - */ - public final String name; - - /** - * The directory that is the root of the package in which the .lf source file resides. This path is determined - * differently depending on whether the compiler is invoked through the IDE or from the command line. In the former - * case, the package is the project root that the source resides in. In the latter case, it is the parent directory - * of the nearest `src` directory up the hierarchy, if there is one, or just the `outPath` if there is none. It is - * recommended to always keep the sources in a `src` directory regardless of the workflow, in which case the - * output behavior will be identical irrespective of the way the compiler is invoked. - */ - public final Path srcPkgPath; - - /** - * The file containing the main source code. - * This is the Eclipse eCore view of the file, which is distinct - * from the XText view of the file and the OS view of the file. - */ - public final Resource resource; - - /** - * If running in an Eclipse IDE, the iResource refers to the - * IFile representing the Lingua Franca program. - * This is the XText view of the file, which is distinct - * from the Eclipse eCore view of the file and the OS view of the file. - *

- * This is null if running outside an Eclipse IDE. - */ - public final IResource iResource; - - /** - * The full path to the file containing the .lf file including the - * full filename with the .lf extension. - */ - public final Path srcFile; - - /** - * The directory in which the source .lf file was found. - */ - public final Path srcPath; // FIXME: rename this to srcDir? - - /** - * Indicate whether the bin directory should be hierarchical. - */ - public final boolean useHierarchicalBin; - - // Protected fields. - - /** - * Path representation of srcGenRoot, the root directory for generated - * sources. - */ - protected Path srcGenBasePath; - - /** - * The directory in which to put the generated sources. - * This takes into account the location of the source file relative to the - * package root. Specifically, if the source file is x/y/Z.lf relative - * to the package root, then the generated sources will be put in x/y/Z - * relative to srcGenBasePath. - */ - protected Path srcGenPath; - - /** - * Path representation of the root directory for generated - * verification models. - */ - protected Path modelGenBasePath; - - /** - * The directory in which to put the generated verification models. - */ - protected Path modelGenPath; - - // private fields - - /** - * The parent of the directory designated for placing generated sources into (`./src-gen` by default). Additional - * directories (such as `bin` or `build`) should be created as siblings of the directory for generated sources, - * which means that such directories should be created relative to the path assigned to this class variable. - *

- * The generated source directory is specified in the IDE (Project Properties->LF->Compiler->Output Folder). When - * invoking the standalone compiler, the output path is specified directly using the `-o` or `--output-path` option. - */ - private final Path outPath; - - /** - * The directory that denotes the root of the package to which the - * generated sources belong. Even if the target language does not have a - * notion of packages, this directory groups all files associated with a - * single main reactor. - * of packages. - */ - private final Path srcGenPkgPath; - - public FileConfig(Resource resource, Path srcGenBasePath, boolean useHierarchicalBin) throws IOException { - this.resource = resource; - this.useHierarchicalBin = useHierarchicalBin; - - this.srcFile = FileUtil.toPath(this.resource); - - this.srcPath = srcFile.getParent(); - this.srcPkgPath = getPkgPath(resource); - - this.srcGenBasePath = srcGenBasePath; - this.name = FileUtil.nameWithoutExtension(this.srcFile); - this.srcGenPath = srcGenBasePath.resolve(getSubPkgPath(srcPath)).resolve(name); - this.srcGenPkgPath = this.srcGenPath; - this.outPath = srcGenBasePath.getParent(); - - Path binRoot = outPath.resolve(DEFAULT_BIN_DIR); - this.binPath = useHierarchicalBin ? binRoot.resolve(getSubPkgPath(srcPath)) : binRoot; - - this.modelGenBasePath = outPath.resolve(DEFAULT_MODEL_GEN_DIR); - this.modelGenPath = modelGenBasePath.resolve(getSubPkgPath(srcPath)).resolve(name); - - this.iResource = FileUtil.getIResource(resource); - } - - /** - * Get the directory a resource is located in relative to the root package - */ - public Path getDirectory(Resource r) throws IOException { - return getSubPkgPath(FileUtil.toPath(r).getParent()); - } - - /** - * The parent of the directory designated for placing generated sources into (`./src-gen` by default). Additional - * directories (such as `bin` or `build`) should be created as siblings of the directory for generated sources, - * which means that such directories should be created relative to the path assigned to this class variable. - *

- * The generated source directory is specified in the IDE (Project Properties->LF->Compiler->Output Folder). When - * invoking the standalone compiler, the output path is specified directly using the `-o` or `--output-path` option. - */ - public Path getOutPath() { - return outPath; - } - - /** - * The directory in which to put the generated sources. - * This takes into account the location of the source file relative to the - * package root. Specifically, if the source file is x/y/Z.lf relative - * to the package root, then the generated sources will be put in x/y/Z - * relative to srcGenBasePath. - */ - public Path getSrcGenPath() { - return srcGenPath; - } - - - /** - * Path representation of srcGenRoot, the root directory for generated - * sources. This is the root, meaning that if the source file is x/y/Z.lf - * relative to the package root, then the generated sources will be put in x/y/Z - * relative to this URI. - */ - public Path getSrcGenBasePath() { - return srcGenBasePath; - } - - /** - * The directory that denotes the root of the package to which the - * generated sources belong. Even if the target language does not have a - * notion of packages, this directory groups all files associated with a - * single main reactor. - */ - public Path getSrcGenPkgPath() { - return srcGenPkgPath; - } - - /** - * Returns the root directory for generated sources. - */ - public static Path getSrcGenRoot(IFileSystemAccess2 fsa) throws IOException { - URI srcGenURI = fsa.getURI(""); - if (srcGenURI.hasTrailingPathSeparator()) { - srcGenURI = srcGenURI.trimSegments(1); - } - return FileUtil.toPath(srcGenURI); - } - - /** - * Given a path that denotes the full path to a source file (not including the - * file itself), return the relative path from the root of the 'src' - * directory, or, if there is no 'src' directory, the relative path - * from the root of the package. - * @param srcPath The path to the source. - * @return the relative path from the root of the 'src' - * directory, or, if there is no 'src' directory, the relative path - * from the root of the package - */ - protected Path getSubPkgPath(Path srcPath) { - Path relSrcPath = srcPkgPath.relativize(srcPath); - if (relSrcPath.startsWith(DEFAULT_SRC_DIR)) { - int segments = relSrcPath.getNameCount(); - if (segments == 1) { - return Paths.get(""); - } else { - relSrcPath = relSrcPath.subpath(1, segments); - } - } - return relSrcPath; - } - - /** - * Path representation of the root directory for generated - * verification models. - * This is the root, meaning that if the source file is x/y/Z.lf - * relative to the package root, then the generated sources will be put in x/y/Z - * relative to this URI. - */ - public Path getModelGenBasePath() { - return modelGenBasePath; - } - - /** - * The directory in which to put the generated verification models. - */ - public Path getModelGenPath() { - return modelGenPath; - } - - /** - * Clean any artifacts produced by the code generator and target compilers. - *

- * The base implementation deletes the bin and src-gen directories. If the - * target code generator creates additional files or directories, the - * corresponding generator should override this method. - * - * @throws IOException If an I/O error occurs. - */ - public void doClean() throws IOException { - FileUtil.deleteDirectory(binPath); - FileUtil.deleteDirectory(srcGenBasePath); - FileUtil.deleteDirectory(modelGenBasePath); - } - - private static Path getPkgPath(Resource resource) throws IOException { - if (resource.getURI().isPlatform()) { - // We are in the RCA. - Path srcFile = FileUtil.toPath(resource); - for (IProject r : ResourcesPlugin.getWorkspace().getRoot().getProjects()) { - Path p = Paths.get(r.getLocation().toFile().getAbsolutePath()); - Path f = srcFile.toAbsolutePath(); - if (f.startsWith(p)) { - return p; - } - } - } - return findPackageRoot(FileUtil.toPath(resource), s -> {}); - } - - /** - * Find the package root by looking for an 'src' - * directory. If none can be found, return the current - * working directory instead. - * - * @param input The *.lf file to find the package root - * for. - * @return The package root, or the current working - * directory if none exists. - */ - public static Path findPackageRoot(final Path input, final Consumer printWarning) { - Path p = input; - do { - p = p.getParent(); - if (p == null) { - printWarning.accept("File '" + input.getFileName() + "' is not located in an 'src' directory."); - printWarning.accept("Adopting the current working directory as the package root."); - return Paths.get(".").toAbsolutePath(); // todo #1478 replace with Io::getWd - } - } while (!p.toFile().getName().equals("src")); - return p.getParent(); - } - - /** - * Return an LFCommand instance that can be used to execute the program under compilation. - */ - public LFCommand getCommand() { - String cmd = GeneratorUtils.isHostWindows() ? - getExecutable().toString() : - srcPkgPath.relativize(getExecutable()).toString(); - return LFCommand.get(cmd, List.of(), true, srcPkgPath); - } - - /** - * Return the extension used for binaries on the platform on which compilation takes place. - */ - protected String getExecutableExtension() { - return (GeneratorUtils.isHostWindows() ? ".exe" : ""); - } - - /** - * Return a path to an executable version of the program under compilation. - */ - public Path getExecutable() { - return binPath.resolve(name + getExecutableExtension()); - } -} diff --git a/org.lflang/src/org/lflang/TargetConfig.java b/org.lflang/src/org/lflang/TargetConfig.java deleted file mode 100644 index 3f401427fb..0000000000 --- a/org.lflang/src/org/lflang/TargetConfig.java +++ /dev/null @@ -1,514 +0,0 @@ -/************* -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; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -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; -import org.lflang.TargetProperty.ClockSyncMode; -import org.lflang.TargetProperty.CoordinationType; -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; - -/** - * A class for keeping the current target configuration. - * - * Class members of type String are initialized as empty strings, - * unless otherwise stated. - * @author Marten Lohstroh - */ -public class TargetConfig { - - /** - * The target of this configuration (e.g., C, TypeScript, Python). - */ - public final Target 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("no-verify")) { - this.noVerify = 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. - */ - public Set setByUser = new HashSet<>(); - - /** - * A list of custom build commands that replace the default build process of - * directly invoking a designated compiler. A common usage of this target - * property is to set the command to build on the basis of a Makefile. - */ - public List buildCommands = new ArrayList<>(); - - /** - * The mode of clock synchronization to be used in federated programs. - * The default is 'initial'. - */ - public ClockSyncMode clockSync = ClockSyncMode.INIT; - - /** - * Clock sync options. - */ - public ClockSyncOptions clockSyncOptions = new ClockSyncOptions(); - - /** - * Parameter passed to cmake. The default is 'Release'. - */ - public BuildType cmakeBuildType = BuildType.RELEASE; - - /** - * Optional additional extensions to include in the generated CMakeLists.txt. - */ - 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. - */ - public String compiler = ""; - - /** - * Additional sources to add to the compile command if appropriate. - */ - public List compileAdditionalSources = new ArrayList<>(); - - /** - * Additional (preprocessor) definitions to add to the compile command if appropriate. - * - * The first string is the definition itself, and the second string is the value to attribute to that definition, if any. - * The second value could be left empty. - */ - public Map compileDefinitions = new HashMap<>(); - - /** - * Additional libraries to add to the compile command using the "-l" command-line option. - */ - public List compileLibraries = new ArrayList<>(); - - /** - * Flags to pass to the compiler, unless a build command has been specified. - */ - public List compilerFlags = new ArrayList<>(); - - /** - * The type of coordination used during the execution of a federated program. - * The default is 'centralized'. - */ - public CoordinationType coordination = CoordinationType.CENTRALIZED; - - /** - * Docker options. - */ - public DockerOptions dockerOptions = null; - - /** - * Coordination options. - */ - public CoordinationOptions coordinationOptions = new CoordinationOptions(); - - /** - * Link to an external runtime library instead of the default one. - */ - public String externalRuntimePath = null; - - /** - * If true, configure the execution environment such that it does not - * wait for physical time to match logical time. The default is false. - */ - public boolean fastMode = false; - - /** - * 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<>(); - - /** - * If true, configure the execution environment to keep executing if there - * are no more events on the event queue. The default is false. - */ - public boolean keepalive = false; - - /** - * The level of logging during execution. The default is INFO. - */ - public LogLevel logLevel = LogLevel.INFO; - - /** - * Flags to pass to the linker, unless a build command has been specified. - */ - public String linkerFlags = ""; - - /** - * If true, do not invoke the target compiler or build command. - * The default is false. - */ - public boolean noCompile = false; - - /** - * If true, do not perform runtime validation. The default is false. - */ - public boolean noRuntimeValidation = false; - - /** - * If true, do not check the generated verification model. - * The default is false. - */ - public boolean noVerify = false; - - /** - * Set the target platform config. - * This tells the build system what platform-specific support - * files it needs to incorporate at compile time. - * - * This is now a wrapped class to account for overloaded definitions - * of defining platform (either a string or dictionary of values) - */ - public PlatformOptions platformOptions = new PlatformOptions(); - - /** - * List of proto files to be processed by the code generator. - */ - public List protoFiles = new ArrayList<>(); - - /** - * If true, generate ROS2 specific code. - */ - public boolean ros2 = false; - - /** - * Additional ROS2 packages that the LF program depends on. - */ - public List ros2Dependencies = null; - - /** - * The version of the runtime library to be used in the generated target. - */ - public String runtimeVersion = null; - - /** Whether all reactors are to be generated into a single target language file. */ - public boolean singleFileProject = false; - - /** What runtime scheduler to use. */ - public SchedulerOption schedulerType = SchedulerOption.getDefault(); - - /** - * The number of worker threads to deploy. The default is zero, which indicates that - * the runtime is allowed to freely choose the number of workers. - */ - public int workers = 0; - - /** - * Indicate whether HMAC authentication is used. - */ - public boolean auth = false; - - /** - * Indicate whether the runtime should use multithreaded execution. - */ - public boolean threading = true; - - /** - * The timeout to be observed during execution of the program. - */ - public TimeValue timeout; - - /** - * If non-null, configure the runtime environment to perform tracing. - * The default is null. - */ - public TracingOptions tracing = null; - - - /** - * If true, the resulting binary will output a graph visualizing all reaction dependencies. - * - * This option is currently only used for C++ and Rust. This export function is a valuable tool - * for debugging LF programs and helps to understand the dependencies inferred by the runtime. - */ - public boolean exportDependencyGraph = false; - - - /** - * If true, the resulting binary will output a yaml file describing the whole reactor structure - * of the program. - * - * This option is currently only used for C++. This export function is a valuable tool for debugging - * LF programs and performing external analysis. - */ - public boolean exportToYaml = false; - - /** Rust-specific configuration. */ - public final RustTargetConfig rust = new RustTargetConfig(); // FIXME: https://issue.lf-lang.org/1558 - - /** Path to a C file used by the Python target to setup federated execution. */ - public String fedSetupPreamble = null; // FIXME: https://issue.lf-lang.org/1558 - - /** - * Settings related to clock synchronization. - */ - public static class ClockSyncOptions { - - /** - * Dampen the adjustments to the clock synchronization offset by this rate. - * The default is 10. - */ - public int attenuation = 10; - - /** - * Whether to collect statistics while performing clock synchronization. - * This setting is only considered when clock synchronization has been activated. - * The default is true. - */ - public boolean collectStats = true; - - /** - * Enable clock synchronization for federates on the same machine. - * Default is false. - */ - public boolean localFederatesOn = false; - - - /** - * Interval at which clock synchronization is initiated by the RTI (will be passed - * to it as an argument on the command-line). - * The default is 5 milliseconds. - */ - public TimeValue period = new TimeValue(5, TimeUnit.MILLI); - - /** - * Indicate the number of exchanges to be had per each clock synchronization round. - * See /lib/core/federated/clock-sync.h for more details. - * The default is 10. - */ - public int trials = 10; - - /** - * Used to create an artificial clock synchronization error for the purpose of testing. - * The default is null. - */ - public TimeValue testOffset; - } - - /** - * Settings related to coordination of federated execution. - */ - public static class CoordinationOptions { - - /** - * For centralized coordination, if a federate has a physical action that can trigger - * an output, directly or indirectly, then it will send NET (next event tag) messages - * to the RTI periodically as its physical clock advances. This option sets the amount - * of time to wait between sending such messages. Increasing this value results in - * downstream federates that lag further behind physical time (if the "after" delays - * are insufficient). - * The default is null, which means it is up the implementation to choose an interval. - */ - public TimeValue advance_message_interval = null; - } - - /** - * Settings related to Docker options. - */ - public static class DockerOptions { - /** - * The base image and tag from which to build the Docker image. The default is "alpine:latest". - */ - public String from = "alpine:latest"; - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - DockerOptions that = (DockerOptions) o; - return from.equals(that.from); - } - } - - /** - * Settings related to Platform Options. - */ - public static class PlatformOptions { - - /** - * The base platform we build our LF Files on. Should be set to AUTO by default unless developing for specific OS/Embedded Platform - */ - public Platform platform = Platform.AUTO; - - /** - * The string value used to determine what type of embedded board we work with and can be used to simplify the build process. For example, - * when we want to flash to an Arduino Nano 33 BLE board, we can use the string arduino:mbed_nano:nano33ble - */ - public String board = null; - - - /** - * The string value used to determine the port on which to flash the compiled program (i.e. /dev/cu.usbmodem21301) - */ - public String port = null; - - /** - * The baud rate used as a parameter to certain embedded platforms. 9600 is a standard rate amongst systems like Arduino, so it's the default value. - */ - public int baudRate = 9600; - - /** - * The boolean statement used to determine whether we should automatically attempt to flash once we compile. This may require the use of board and - * port values depending on the infrastructure you use to flash the boards. - */ - public boolean flash = false; - } - - /** - * Settings related to tracing options. - */ - public static class TracingOptions { - /** - * The name to use as the root of the trace file produced. - * This defaults to the name of the .lf file. - */ - public String traceFileName = null; - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - TracingOptions that = (TracingOptions) o; - return Objects.equals(traceFileName, that.traceFileName); // traceFileName may be null - } - } -} diff --git a/org.lflang/src/org/lflang/TargetProperty.java b/org.lflang/src/org/lflang/TargetProperty.java deleted file mode 100644 index 70f10f8560..0000000000 --- a/org.lflang/src/org/lflang/TargetProperty.java +++ /dev/null @@ -1,1872 +0,0 @@ -/************* -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; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -import org.eclipse.xtext.util.RuntimeIOException; -import org.lflang.TargetConfig.DockerOptions; -import org.lflang.TargetConfig.PlatformOptions; -import org.lflang.TargetConfig.TracingOptions; -import org.lflang.generator.InvalidLfSourceException; -import org.lflang.generator.rust.CargoDependencySpec; -import org.lflang.generator.rust.CargoDependencySpec.CargoDependenciesPropertyType; -import org.lflang.lf.Array; -import org.lflang.lf.Element; -import org.lflang.lf.KeyValuePair; -import org.lflang.lf.KeyValuePairs; -import org.lflang.lf.LfFactory; -import org.lflang.lf.TargetDecl; -import org.lflang.util.FileUtil; -import org.lflang.util.StringUtil; -import org.lflang.validation.LFValidator; - -import com.google.common.collect.ImmutableList; - -/** - * A target properties along with a type and a list of supporting targets - * that supports it, as well as a function for configuration updates. - * - * @author Marten Lohstroh - */ -public enum TargetProperty { - /** - * Directive to allow including OpenSSL libraries and process HMAC authentication. - */ - AUTH("auth", PrimitiveType.BOOLEAN, - Arrays.asList(Target.C, Target.CCPP), - (config) -> ASTUtils.toElement(config.auth), - (config, value, err) -> { - config.auth = ASTUtils.toBoolean(value); - }), - /** - * Directive to let the generator use the custom build command. - */ - BUILD("build", UnionType.STRING_OR_STRING_ARRAY, - Arrays.asList(Target.C, Target.CCPP), - (config) -> ASTUtils.toElement(config.buildCommands), - (config, value, err) -> { - config.buildCommands = ASTUtils.elementToListOfStrings(value); - }), - - /** - * Directive to specify the target build type such as 'Release' or 'Debug'. - * This is also used in the Rust target to select a Cargo profile. - */ - BUILD_TYPE("build-type", UnionType.BUILD_TYPE_UNION, - Arrays.asList(Target.C, Target.CCPP, Target.CPP, Target.Rust), - (config) -> ASTUtils.toElement(config.cmakeBuildType.toString()), - (config, value, err) -> { - config.cmakeBuildType = (BuildType) UnionType.BUILD_TYPE_UNION - .forName(ASTUtils.elementToSingleString(value)); - // set it there too, because the default is different. - config.rust.setBuildType(config.cmakeBuildType); - }), - - /** - * Directive to let the federate execution handle clock synchronization in software. - */ - CLOCK_SYNC("clock-sync", UnionType.CLOCK_SYNC_UNION, - Arrays.asList(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.clockSync.toString()), - (config, value, err) -> { - config.clockSync = (ClockSyncMode) UnionType.CLOCK_SYNC_UNION - .forName(ASTUtils.elementToSingleString(value)); - }), - /** - * Key-value pairs giving options for clock synchronization. - */ - CLOCK_SYNC_OPTIONS("clock-sync-options", - DictionaryType.CLOCK_SYNC_OPTION_DICT, Arrays.asList(Target.C, Target.CCPP, Target.Python), - (config) -> { - Element e = LfFactory.eINSTANCE.createElement(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (ClockSyncOption opt : ClockSyncOption.values()) { - KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); - pair.setName(opt.toString()); - switch (opt) { - case ATTENUATION: - pair.setValue(ASTUtils.toElement(config.clockSyncOptions.attenuation)); - break; - case COLLECT_STATS: - pair.setValue(ASTUtils.toElement(config.clockSyncOptions.collectStats)); - break; - case LOCAL_FEDERATES_ON: - pair.setValue(ASTUtils.toElement(config.clockSyncOptions.localFederatesOn)); - break; - case PERIOD: - if (config.clockSyncOptions.period == null) continue; // don't set if null - pair.setValue(ASTUtils.toElement(config.clockSyncOptions.period)); - break; - case TEST_OFFSET: - if (config.clockSyncOptions.testOffset == null) continue; // don't set if null - pair.setValue(ASTUtils.toElement(config.clockSyncOptions.testOffset)); - break; - case TRIALS: - pair.setValue(ASTUtils.toElement(config.clockSyncOptions.trials)); - break; - } - kvp.getPairs().add(pair); - } - e.setKeyvalue(kvp); - // kvp will never be empty - return e; - }, - (config, value, err) -> { - for (KeyValuePair entry : value.getKeyvalue().getPairs()) { - ClockSyncOption option = (ClockSyncOption) DictionaryType.CLOCK_SYNC_OPTION_DICT - .forName(entry.getName()); - switch (option) { - case ATTENUATION: - config.clockSyncOptions.attenuation = ASTUtils - .toInteger(entry.getValue()); - break; - case COLLECT_STATS: - config.clockSyncOptions.collectStats = ASTUtils - .toBoolean(entry.getValue()); - break; - case LOCAL_FEDERATES_ON: - config.clockSyncOptions.localFederatesOn = ASTUtils - .toBoolean(entry.getValue()); - break; - case PERIOD: - config.clockSyncOptions.period = ASTUtils - .toTimeValue(entry.getValue()); - break; - case TEST_OFFSET: - config.clockSyncOptions.testOffset = ASTUtils - .toTimeValue(entry.getValue()); - break; - case TRIALS: - config.clockSyncOptions.trials = ASTUtils - .toInteger(entry.getValue()); - break; - default: - break; - } - } - }), - - /** - * Directive to specify a cmake to be included by the generated build - * systems. - * - * This gives full control over the C/C++ build as any cmake parameters - * can be adjusted in the included file. - */ - CMAKE_INCLUDE("cmake-include", UnionType.FILE_OR_FILE_ARRAY, - Arrays.asList(Target.CPP, Target.C, Target.CCPP), - (config) -> ASTUtils.toElement(config.cmakeIncludes), - (config, value, err) -> { - config.cmakeIncludes = ASTUtils.elementToListOfStrings(value); - }, - // FIXME: This merging of lists is potentially dangerous since - // the incoming list of cmake-includes 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.cmakeIncludes.addAll(ASTUtils.elementToListOfStrings(value)); - }), - /** - * Directive to specify the target compiler. - */ - COMPILER("compiler", PrimitiveType.STRING, Target.ALL, - (config) -> ASTUtils.toElement(config.compiler), - (config, value, err) -> { - config.compiler = ASTUtils.elementToSingleString(value); - }), - - /** - * Directive to specify compile-time definitions. - */ - COMPILE_DEFINITIONS("compile-definitions", StringDictionaryType.COMPILE_DEFINITION, - Arrays.asList(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.compileDefinitions), - (config, value, err) -> { - config.compileDefinitions = ASTUtils.elementToStringMaps(value); - }), - - /** - * Directive to generate a Dockerfile. This is either a boolean, - * true or false, or a dictionary of options. - */ - DOCKER("docker", UnionType.DOCKER_UNION, - Arrays.asList(Target.C, Target.CCPP, Target.Python, Target.TS), - (config) -> { - if (config.dockerOptions == null) { - return null; - } else if(config.dockerOptions.equals(new DockerOptions())) { - // default configuration - return ASTUtils.toElement(true); - } else { - Element e = LfFactory.eINSTANCE.createElement(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (DockerOption opt : DockerOption.values()) { - KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); - pair.setName(opt.toString()); - switch (opt) { - case FROM: - if (config.dockerOptions.from == null) continue; - pair.setValue(ASTUtils.toElement(config.dockerOptions.from)); - break; - } - kvp.getPairs().add(pair); - } - e.setKeyvalue(kvp); - if (kvp.getPairs().isEmpty()) return null; - return e; - } - }, - (config, value, err) -> setDockerProperty(config, value), - (config, value, err) -> setDockerProperty(config, value)), - - /** - * Directive for specifying a path to an external runtime to be used for the - * compiled binary. - */ - EXTERNAL_RUNTIME_PATH("external-runtime-path", PrimitiveType.STRING, - List.of(Target.CPP), - (config) -> ASTUtils.toElement(config.externalRuntimePath), - (config, value, err) -> { - config.externalRuntimePath = ASTUtils.elementToSingleString(value); - }), - - /** - * Directive to let the execution engine allow logical time to elapse - * faster than physical time. - */ - FAST("fast", PrimitiveType.BOOLEAN, Target.ALL, - (config) -> ASTUtils.toElement(config.fastMode), - (config, value, err) -> { - config.fastMode = ASTUtils.toBoolean(value); - }), - - /** - * Directive to stage particular files on the class path to be - * 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, value, err) -> { - config.fileNames = 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)); - }), - - /** - * Flags to be passed on to the target compiler. - */ - FLAGS("flags", UnionType.STRING_OR_STRING_ARRAY, - Arrays.asList(Target.C, Target.CCPP), - (config) -> ASTUtils.toElement(config.compilerFlags), - (config, value, err) -> { - config.compilerFlags = ASTUtils.elementToListOfStrings(value); - }), - - /** - * Directive to specify the coordination mode - */ - COORDINATION("coordination", UnionType.COORDINATION_UNION, - Arrays.asList(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.coordination.toString()), - (config, value, err) -> { - config.coordination = (CoordinationType) UnionType.COORDINATION_UNION - .forName(ASTUtils.elementToSingleString(value)); - }, - (config, value, err) -> { - config.coordination = (CoordinationType) UnionType.COORDINATION_UNION - .forName(ASTUtils.elementToSingleString(value)); - }), - - /** - * Key-value pairs giving options for clock synchronization. - */ - COORDINATION_OPTIONS("coordination-options", - DictionaryType.COORDINATION_OPTION_DICT, Arrays.asList(Target.C, Target.CCPP, Target.Python, Target.TS), - (config) -> { - Element e = LfFactory.eINSTANCE.createElement(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (CoordinationOption opt : CoordinationOption.values()) { - KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); - pair.setName(opt.toString()); - switch (opt) { - case ADVANCE_MESSAGE_INTERVAL: - if (config.coordinationOptions.advance_message_interval == null) continue; - pair.setValue(ASTUtils.toElement(config.coordinationOptions.advance_message_interval)); - break; - } - kvp.getPairs().add(pair); - } - e.setKeyvalue(kvp); - if (kvp.getPairs().isEmpty()) return null; - return e; - }, - (config, value, err) -> { - for (KeyValuePair entry : value.getKeyvalue().getPairs()) { - CoordinationOption option = (CoordinationOption) DictionaryType.COORDINATION_OPTION_DICT - .forName(entry.getName()); - switch (option) { - case ADVANCE_MESSAGE_INTERVAL: - config.coordinationOptions.advance_message_interval = ASTUtils - .toTimeValue(entry.getValue()); - break; - default: - break; - } - } - }, - (config, value, err) -> { - for (KeyValuePair entry : value.getKeyvalue().getPairs()) { - CoordinationOption option = (CoordinationOption) DictionaryType.COORDINATION_OPTION_DICT - .forName(entry.getName()); - switch (option) { - case ADVANCE_MESSAGE_INTERVAL: - config.coordinationOptions.advance_message_interval = ASTUtils - .toTimeValue(entry.getValue()); - break; - default: - break; - } - } - }), - - /** - * Directive to let the execution engine remain active also if there - * are no more events in the event queue. - */ - KEEPALIVE("keepalive", PrimitiveType.BOOLEAN, Target.ALL, - (config) -> ASTUtils.toElement(config.keepalive), - (config, value, err) -> { - config.keepalive = ASTUtils.toBoolean(value); - }), - - /** - * Directive to specify the grain at which to report log messages during execution. - */ - LOGGING("logging", UnionType.LOGGING_UNION, Target.ALL, - (config) -> ASTUtils.toElement(config.logLevel.toString()), - (config, value, err) -> { - config.logLevel = (LogLevel) UnionType.LOGGING_UNION - .forName(ASTUtils.elementToSingleString(value)); - }, - (config, value, err) -> { - config.logLevel = (LogLevel) UnionType.LOGGING_UNION - .forName(ASTUtils.elementToSingleString(value)); - }), - - /** - * Directive to not invoke the target compiler. - */ - NO_COMPILE("no-compile", PrimitiveType.BOOLEAN, - Arrays.asList(Target.C, Target.CPP, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.noCompile), - (config, value, err) -> { - config.noCompile = ASTUtils.toBoolean(value); - }), - - /** - * Directive to disable validation of reactor rules at runtime. - */ - NO_RUNTIME_VALIDATION("no-runtime-validation", PrimitiveType.BOOLEAN, - Arrays.asList(Target.CPP), - (config) -> ASTUtils.toElement(config.noRuntimeValidation), - (config, value, err) -> { - config.noRuntimeValidation = ASTUtils.toBoolean(value); - }), - - /** - * Directive to not check the generated verification model. - */ - NO_VERIFY("no-verify", PrimitiveType.BOOLEAN, - Arrays.asList(Target.C), - (config) -> ASTUtils.toElement(config.noVerify), - (config, value, err) -> { - config.noVerify = ASTUtils.toBoolean(value); - }), - - /** - * Directive to specify the platform for cross code generation. This is either a string of the platform - * or a dictionary of options that includes the string name. - */ - PLATFORM("platform", UnionType.PLATFORM_STRING_OR_DICTIONARY, Target.ALL, - (config) -> { - Element e = LfFactory.eINSTANCE.createElement(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (PlatformOption opt : PlatformOption.values()) { - KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); - pair.setName(opt.toString()); - switch (opt) { - case NAME: - pair.setValue(ASTUtils.toElement(config.platformOptions.platform.toString())); - break; - case BAUDRATE: - pair.setValue(ASTUtils.toElement(config.platformOptions.baudRate)); - break; - case BOARD: - pair.setValue(ASTUtils.toElement(config.platformOptions.board)); - break; - case FLASH: - pair.setValue(ASTUtils.toElement(config.platformOptions.flash)); - break; - case PORT: - pair.setValue(ASTUtils.toElement(config.platformOptions.port)); - break; - } - kvp.getPairs().add(pair); - } - e.setKeyvalue(kvp); - if (kvp.getPairs().isEmpty()) return null; - return e; - }, - (config, value, err) -> { - if (value.getLiteral() != null) { - config.platformOptions = new PlatformOptions(); - config.platformOptions.platform = (Platform) UnionType.PLATFORM_UNION - .forName(ASTUtils.elementToSingleString(value)); - } else { - config.platformOptions= new PlatformOptions(); - for (KeyValuePair entry : value.getKeyvalue().getPairs()) { - PlatformOption option = (PlatformOption) DictionaryType.PLATFORM_DICT - .forName(entry.getName()); - switch (option) { - case NAME: - Platform p = (Platform) UnionType.PLATFORM_UNION - .forName(ASTUtils.elementToSingleString(entry.getValue())); - if(p == null) { - String s = "Unidentified Platform Type, LF supports the following platform types: " + Arrays.asList(Platform.values()).toString(); - err.reportError(s); - throw new AssertionError(s); - } - config.platformOptions.platform = p; - break; - case BAUDRATE: - config.platformOptions.baudRate = ASTUtils.toInteger(entry.getValue()); - break; - case BOARD: - config.platformOptions.board = ASTUtils.elementToSingleString(entry.getValue()); - break; - case FLASH: - config.platformOptions.flash = ASTUtils.toBoolean(entry.getValue()); - break; - case PORT: - config.platformOptions.port = ASTUtils.elementToSingleString(entry.getValue()); - break; - default: - break; - } - } - } - }), - - /** - * Directive for specifying .proto files that need to be compiled and their - * code included in the sources. - */ - PROTOBUFS("protobufs", UnionType.FILE_OR_FILE_ARRAY, - Arrays.asList(Target.C, Target.CCPP, Target.TS, Target.Python), - (config) -> ASTUtils.toElement(config.protoFiles), - (config, value, err) -> { - config.protoFiles = ASTUtils.elementToListOfStrings(value); - }), - - - /** - * Directive to specify that ROS2 specific code is generated, - */ - ROS2("ros2", PrimitiveType.BOOLEAN, - List.of(Target.CPP), - (config) -> ASTUtils.toElement(config.ros2), - (config, value, err) -> { - config.ros2 = ASTUtils.toBoolean(value); - }), - - /** - * Directive to specify additional ROS2 packages that this LF program depends on. - */ - ROS2_DEPENDENCIES("ros2-dependencies", ArrayType.STRING_ARRAY, - List.of(Target.CPP), - (config) -> ASTUtils.toElement(config.ros2Dependencies), - (config, value, err) -> { - config.ros2Dependencies = ASTUtils.elementToListOfStrings(value); - }), - - /** - * Directive for specifying a specific version of the reactor runtime library. - */ - RUNTIME_VERSION("runtime-version", PrimitiveType.STRING, - Arrays.asList(Target.CPP), - (config) -> ASTUtils.toElement(config.runtimeVersion), - (config, value, err) -> { - config.runtimeVersion = ASTUtils.elementToSingleString(value); - }), - - - /** - * Directive for specifying a specific runtime scheduler, if supported. - */ - SCHEDULER("scheduler", UnionType.SCHEDULER_UNION, - Arrays.asList(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.schedulerType.toString()), - (config, value, err) -> { - config.schedulerType = (SchedulerOption) UnionType.SCHEDULER_UNION - .forName(ASTUtils.elementToSingleString(value)); - }), - - /** - * Directive to specify that all code is generated in a single file. - */ - SINGLE_FILE_PROJECT("single-file-project", PrimitiveType.BOOLEAN, - List.of(Target.Rust), - (config) -> ASTUtils.toElement(config.singleFileProject), - (config, value, err) -> { - config.singleFileProject = ASTUtils.toBoolean(value); - }), - - /** - * Directive to indicate whether the runtime should use multi-threading. - */ - THREADING("threading", PrimitiveType.BOOLEAN, - List.of(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.threading), - (config, value, err) -> { - config.threading = ASTUtils.toBoolean(value); - }), - - /** - * Directive to specify the number of worker threads used by the runtime. - */ - WORKERS("workers", PrimitiveType.NON_NEGATIVE_INTEGER, - List.of(Target.C, Target.CCPP, Target.Python, Target.CPP, Target.Rust), - (config) -> ASTUtils.toElement(config.workers), - (config, value, err) -> { - config.workers = ASTUtils.toInteger(value); - }), - - /** - * Directive to specify the execution timeout. - */ - TIMEOUT("timeout", PrimitiveType.TIME_VALUE, Target.ALL, - (config) -> ASTUtils.toElement(config.timeout), - (config, value, err) -> { - config.timeout = ASTUtils.toTimeValue(value); - }, - (config, value, err) -> { - config.timeout = ASTUtils.toTimeValue(value); - }), - - /** - * Directive to generate a Dockerfile. This is either a boolean, - * true or false, or a dictionary of options. - */ - TRACING("tracing", UnionType.TRACING_UNION, - Arrays.asList(Target.C, Target.CCPP, Target.CPP, Target.Python), - (config) -> { - if (config.tracing == null) { - return null; - } else if (config.tracing.equals(new TracingOptions())) { - // default values - return ASTUtils.toElement(true); - } else { - Element e = LfFactory.eINSTANCE.createElement(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (TracingOption opt : TracingOption.values()) { - KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); - pair.setName(opt.toString()); - switch (opt) { - case TRACE_FILE_NAME: - if (config.tracing.traceFileName == null) continue; - pair.setValue(ASTUtils.toElement(config.tracing.traceFileName)); - } - kvp.getPairs().add(pair); - } - e.setKeyvalue(kvp); - if (kvp.getPairs().isEmpty()) return null; - return e; - } - }, - (config, value, err) -> { - if (value.getLiteral() != null) { - if (ASTUtils.toBoolean(value)) { - config.tracing = new TracingOptions(); - } else { - config.tracing = null; - } - } else { - config.tracing = new TracingOptions(); - for (KeyValuePair entry : value.getKeyvalue().getPairs()) { - TracingOption option = (TracingOption) DictionaryType.TRACING_DICT - .forName(entry.getName()); - switch (option) { - case TRACE_FILE_NAME: - config.tracing.traceFileName = ASTUtils.elementToSingleString(entry.getValue()); - break; - default: - break; - } - } - } - }), - - /** - * Directive to let the runtime export its internal dependency graph. - * - * This is a debugging feature and currently only used for C++ and Rust programs. - */ - EXPORT_DEPENDENCY_GRAPH("export-dependency-graph", PrimitiveType.BOOLEAN, - List.of(Target.CPP, Target.Rust), - (config) -> ASTUtils.toElement(config.exportDependencyGraph), - (config, value, err) -> { - config.exportDependencyGraph = ASTUtils.toBoolean(value); - }), - - /** - * Directive to let the runtime export the program structure to a yaml file. - * - * This is a debugging feature and currently only used for C++ programs. - */ - EXPORT_TO_YAML("export-to-yaml", PrimitiveType.BOOLEAN, - List.of(Target.CPP), - (config) -> ASTUtils.toElement(config.exportToYaml), - (config, value, err) -> { - config.exportToYaml = ASTUtils.toBoolean(value); - }), - - /** - * List of module files to link into the crate as top-level. - * For instance, a {@code target Rust { rust-modules: [ "foo.rs" ] }} - * will cause the file to be copied into the generated project, - * and the generated `main.rs` will include it with a `mod foo;`. - * If one of the paths is a directory, it must contain a `mod.rs` - * file, and all its contents are copied. - */ - RUST_INCLUDE("rust-include", - UnionType.FILE_OR_FILE_ARRAY, - List.of(Target.Rust), - (config) -> { - // do not check paths here, and simply copy the absolute path over - List paths = config.rust.getRustTopLevelModules(); - if (paths.isEmpty()) return null; - else if (paths.size() == 1) { - return ASTUtils.toElement(paths.get(0).toString()); - } else { - Element e = LfFactory.eINSTANCE.createElement(); - Array arr = LfFactory.eINSTANCE.createArray(); - for (Path p : paths) { - arr.getElements().add(ASTUtils.toElement(p.toString())); - } - e.setArray(arr); - return e; - } - }, - (config, value, err) -> { - Path referencePath; - try { - referencePath = FileUtil.toPath(value.eResource().getURI()).toAbsolutePath(); - } catch (IOException e) { - err.reportError(value, "Invalid path? " + e.getMessage()); - throw new RuntimeIOException(e); - } - - // we'll resolve relative paths to check that the files - // are as expected. - - if (value.getLiteral() != null) { - Path resolved = referencePath.resolveSibling(StringUtil.removeQuotes(value.getLiteral())); - - config.rust.addAndCheckTopLevelModule(resolved, value, err); - } else if (value.getArray() != null) { - for (Element element : value.getArray().getElements()) { - String literal = StringUtil.removeQuotes(element.getLiteral()); - Path resolved = referencePath.resolveSibling(literal); - config.rust.addAndCheckTopLevelModule(resolved, element, err); - } - } - }), - - /** - * Directive for specifying Cargo features of the generated - * program to enable. - */ - CARGO_FEATURES("cargo-features", ArrayType.STRING_ARRAY, - List.of(Target.Rust), - (config) -> ASTUtils.toElement(config.rust.getCargoFeatures()), - (config, value, err) -> { - config.rust.setCargoFeatures(ASTUtils.elementToListOfStrings(value)); - }), - - /** - * Dependency specifications for Cargo. This property looks like this: - *

{@code
-     * cargo-dependencies: {
-     *    // Name-of-the-crate: "version"
-     *    rand: "0.8",
-     *    // Equivalent to using an explicit map:
-     *    rand: {
-     *      version: "0.8"
-     *    },
-     *    // The map allows specifying more details
-     *    rand: {
-     *      // A path to a local unpublished crate.
-     *      // Note 'path' is mutually exclusive with 'version'.
-     *      path: "/home/me/Git/local-rand-clone"
-     *    },
-     *    rand: {
-     *      version: "0.8",
-     *      // you can specify cargo features
-     *      features: ["some-cargo-feature",]
-     *    }
-     * }
-     * }
- */ - CARGO_DEPENDENCIES("cargo-dependencies", - CargoDependenciesPropertyType.INSTANCE, - List.of(Target.Rust), - (config) -> { - var deps = config.rust.getCargoDependencies(); - if (deps.size() == 0) return null; - else { - Element e = LfFactory.eINSTANCE.createElement(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (var ent : deps.entrySet()) { - KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); - pair.setName(ent.getKey()); - pair.setValue(CargoDependencySpec.extractSpec(ent.getValue())); - kvp.getPairs().add(pair); - } - e.setKeyvalue(kvp); - return e; - } - }, - (config, value, err) -> { - config.rust.setCargoDependencies(CargoDependencySpec.parseAll(value)); - }), - - /** - * Directs the C or Python target to include the associated C file used for - * setting up federated execution before processing the first tag. - */ - FED_SETUP("_fed_setup", PrimitiveType.FILE, - Arrays.asList(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.fedSetupPreamble), - (config, value, err) -> - config.fedSetupPreamble = StringUtil.removeQuotes(ASTUtils.elementToSingleString(value)) - ) - ; - - /** - * Update {@code config}.dockerOptions based on value. - */ - private static void setDockerProperty(TargetConfig config, Element value) { - if (value.getLiteral() != null) { - if (ASTUtils.toBoolean(value)) { - config.dockerOptions = new DockerOptions(); - } else { - config.dockerOptions = null; - } - } else { - config.dockerOptions = new DockerOptions(); - for (KeyValuePair entry : value.getKeyvalue().getPairs()) { - DockerOption option = (DockerOption) DictionaryType.DOCKER_DICT - .forName(entry.getName()); - switch (option) { - case FROM: - config.dockerOptions.from = ASTUtils.elementToSingleString(entry.getValue()); - break; - default: - break; - } - } - } - } - - /** - * String representation of this target property. - */ - public final String description; - - /** - * List of targets that support this property. If a property is used for - * a target that does not support it, a warning reported during - * validation. - */ - public final List supportedBy; - - /** - * The type of values that can be assigned to this property. - */ - public final TargetPropertyType type; - - /** - * Function that given a configuration object and an Element AST node - * sets the configuration. It is assumed that validation already - * occurred, so this code should be straightforward. - */ - public final PropertyParser setter; - - /** - * Function that given a configuration object and an Element AST node - * sets the configuration. It is assumed that validation already - * occurred, so this code should be straightforward. - */ - public final PropertyParser updater; - - @FunctionalInterface - private interface PropertyParser { - - /** - * Parse the given element into the given target config. - * May use the error reporter to report format errors. - */ - void parseIntoTargetConfig(TargetConfig config, Element element, ErrorReporter err); - } - - public final PropertyGetter getter; - - @FunctionalInterface - private interface PropertyGetter { - - /** - * Read this property from the target config and build an element which represents it for the AST. - * May return null if the given value of this property is the same as the default. - */ - Element getPropertyElement(TargetConfig config); - } - - /** - * Private constructor for target properties. - * - * @param description String representation of this property. - * @param type The type that values assigned to this property - * should conform to. - * @param supportedBy List of targets that support this property. - * @param setter Function for configuration updates. - */ - TargetProperty(String description, TargetPropertyType type, - List supportedBy, - PropertyGetter getter, - PropertyParser setter) { - this.description = description; - this.type = type; - this.supportedBy = supportedBy; - this.getter = getter; - this.setter = setter; - this.updater = setter; // (Re)set by default - } - - /** - * Private constructor for target properties. This will take an additional - * `updater`, which will be used to merge target properties from imported resources. - * - * @param description String representation of this property. - * @param type The type that values assigned to this property - * should conform to. - * @param supportedBy List of targets that support this property. - * @param setter Function for setting configuration values. - * @param updater Function for updating configuration values. - */ - TargetProperty(String description, TargetPropertyType type, - List supportedBy, - PropertyGetter getter, - PropertyParser setter, - PropertyParser updater) { - this.description = description; - this.type = type; - this.supportedBy = supportedBy; - this.getter = getter; - this.setter = setter; - this.updater = updater; - } - - /** - * Return the name of the property in lingua franca. This - * is suitable for use as a key in a target properties block. - * It may be an invalid identifier in other languages (may - * contains dashes {@code -}). - */ - public String getDisplayName() { - return description; - } - - /** - * Set the given configuration using the given target properties. - * - * @param config The configuration object to update. - * @param properties AST node that holds all the target properties. - * @param err Error reporter on which property format errors will be reported - */ - public static void set(TargetConfig config, List properties, ErrorReporter err) { - properties.forEach(property -> { - TargetProperty p = forName(property.getName()); - if (p != null) { - // Mark the specified target property as set by the user - config.setByUser.add(p); - try { - p.setter.parseIntoTargetConfig(config, property.getValue(), err); - } catch (InvalidLfSourceException e) { - err.reportError(e.getNode(), e.getProblem()); - } - } - }); - } - - /** - * Extracts all properties as a list of key-value pairs from a TargetConfig. Only extracts properties explicitly set by user. - * @param config The TargetConfig to extract from. - * @return The extracted properties. - */ - public static List extractProperties(TargetConfig config) { - var res = new LinkedList(); - for (TargetProperty p : config.setByUser) { - KeyValuePair kv = LfFactory.eINSTANCE.createKeyValuePair(); - kv.setName(p.toString()); - kv.setValue(p.getter.getPropertyElement(config)); - if (kv.getValue() != null) - res.add(kv); - } - return res; - } - - /** - * Constructs a TargetDecl by extracting the fields of the given TargetConfig. - * @param target The target to generate for. - * @param config The TargetConfig to extract from. - * @return A generated TargetDecl. - */ - public static TargetDecl extractTargetDecl(Target target, TargetConfig config) { - TargetDecl decl = LfFactory.eINSTANCE.createTargetDecl(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (KeyValuePair p : extractProperties(config)) { - kvp.getPairs().add(p); - } - decl.setName(target.toString()); - decl.setConfig(kvp); - return decl; - } - - /** - * Update the given configuration using the given target properties. - * - * @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) { - properties.forEach(property -> { - TargetProperty p = forName(property.getName()); - if (p != null) { - // Mark the specified target property as set by the user - config.setByUser.add(p); - p.updater.parseIntoTargetConfig(config, property.getValue(), err); - } - }); - } - - /** - * Update one of the target properties, given by 'propertyName'. - * For convenience, a list of target properties (e.g., taken from - * a file or resource) can be passed without any filtering. This - * function will do nothing if the list of target properties doesn't - * include the property given by 'propertyName'. - * - * @param config The target config to apply the update to. - * @param property The target property. - * @param properties AST node that holds all the target properties. - * @param err Error reporter on which property format errors will be reported - */ - public static void updateOne(TargetConfig config, TargetProperty property, List properties, ErrorReporter err) { - properties.stream() - .filter(p -> p.getName().equals(property.getDisplayName())) - .findFirst() - .map(KeyValuePair::getValue) - .ifPresent(value -> property.updater.parseIntoTargetConfig( - config, - value, - err - )); - } - - /** - * Return the entry that matches the given string. - * @param name The string to match against. - */ - public static TargetProperty forName(String name) { - return Target.match(name, TargetProperty.values()); - } - - /** - * Return a list with all target properties. - * - * @return All existing target properties. - */ - public static List getOptions() { - return Arrays.asList(TargetProperty.values()); - } - - /** - * Return the description. - */ - @Override - public String toString() { - return this.description; - } - - // Inner classes for the various supported types. - - /** - * Dictionary type that allows for keys that will be interpreted as strings - * and string values. - */ - public enum StringDictionaryType implements TargetPropertyType { - COMPILE_DEFINITION(); - @Override - public boolean validate(Element e) { - if (e.getKeyvalue() != null) { - return true; - } - return false; - } - - @Override - public void check(Element e, String name, LFValidator v) { - KeyValuePairs kv = e.getKeyvalue(); - if (kv == null) { - TargetPropertyType.produceError(name, this.toString(), v); - } else { - for (KeyValuePair pair : kv.getPairs()) { - String key = pair.getName(); - Element val = pair.getValue(); - - // Make sure the type is string - PrimitiveType.STRING.check(val, name + "." + key, v); - } - } - - } - } - - /** - * Interface for dictionary elements. It associates an entry with a type. - */ - public interface DictionaryElement { - - TargetPropertyType getType(); - } - - /** - * A dictionary type with a predefined set of possible keys and assignable - * types. - * - * @author Marten Lohstroh - * - */ - public enum DictionaryType implements TargetPropertyType { - CLOCK_SYNC_OPTION_DICT(Arrays.asList(ClockSyncOption.values())), - DOCKER_DICT(Arrays.asList(DockerOption.values())), - PLATFORM_DICT(Arrays.asList(PlatformOption.values())), - COORDINATION_OPTION_DICT(Arrays.asList(CoordinationOption.values())), - TRACING_DICT(Arrays.asList(TracingOption.values())); - - /** - * The keys and assignable types that are allowed in this dictionary. - */ - public List options; - - /** - * A dictionary type restricted to sets of predefined keys and types of - * values. - * - * @param options The dictionary elements allowed by this type. - */ - private DictionaryType(List options) { - this.options = options; - } - - /** - * Return the dictionary element of which the key matches the given - * string. - * - * @param name The string to match against. - * @return The matching dictionary element (or null if there is none). - */ - public DictionaryElement forName(String name) { - return Target.match(name, options); - } - - /** - * Recursively check that the passed in element conforms to the rules of - * this dictionary. - */ - @Override - public void check(Element e, String name, LFValidator v) { - KeyValuePairs kv = e.getKeyvalue(); - if (kv == null) { - TargetPropertyType.produceError(name, this.toString(), v); - } else { - for (KeyValuePair pair : kv.getPairs()) { - String key = pair.getName(); - Element val = pair.getValue(); - Optional match = this.options.stream() - .filter(element -> key.equalsIgnoreCase(element.toString())).findAny(); - if (match.isPresent()) { - // Make sure the type is correct, too. - TargetPropertyType type = match.get().getType(); - type.check(val, name + "." + key, v); - } else { - // No match found; report error. - TargetPropertyType.produceError(name, - this.toString(), v); - } - } - } - } - - /** - * Return true if the given element represents a dictionary, false - * otherwise. - */ - @Override - public boolean validate(Element e) { - if (e.getKeyvalue() != null) { - return true; - } - return false; - } - - /** - * Return a human-readable description of this type. - */ - @Override - public String toString() { - return "a dictionary with one or more of the following keys: " - + options.stream().map(option -> option.toString()) - .collect(Collectors.joining(", ")); - } - } - /** - * A type that can assume one of several types. - * - * @author Marten Lohstroh - * - */ - public enum UnionType implements TargetPropertyType { - STRING_OR_STRING_ARRAY( - Arrays.asList(PrimitiveType.STRING, ArrayType.STRING_ARRAY), - null), - PLATFORM_STRING_OR_DICTIONARY( - Arrays.asList(PrimitiveType.STRING, DictionaryType.PLATFORM_DICT), - null), - FILE_OR_FILE_ARRAY( - Arrays.asList(PrimitiveType.FILE, ArrayType.FILE_ARRAY), null), - BUILD_TYPE_UNION(Arrays.asList(BuildType.values()), null), - COORDINATION_UNION(Arrays.asList(CoordinationType.values()), - CoordinationType.CENTRALIZED), - SCHEDULER_UNION(Arrays.asList(SchedulerOption.values()), SchedulerOption.getDefault()), - LOGGING_UNION(Arrays.asList(LogLevel.values()), LogLevel.INFO), - PLATFORM_UNION(Arrays.asList(Platform.values()), Platform.AUTO), - CLOCK_SYNC_UNION(Arrays.asList(ClockSyncMode.values()), - ClockSyncMode.INIT), - DOCKER_UNION(Arrays.asList(PrimitiveType.BOOLEAN, DictionaryType.DOCKER_DICT), - null), - TRACING_UNION(Arrays.asList(PrimitiveType.BOOLEAN, DictionaryType.TRACING_DICT), - null); - - /** - * The constituents of this type union. - */ - public final List> options; - - /** - * The default type, if there is one. - */ - private final Enum defaultOption; - - /** - * Private constructor for creating unions types. - * - * @param options The types that that are part of the union. - * @param defaultOption The default type. - */ - private UnionType(List> options, Enum defaultOption) { - this.options = options; - this.defaultOption = defaultOption; - } - - /** - * Return the type among those in this type union that matches the given - * name. - * - * @param name The string to match against. - * @return The matching dictionary element (or null if there is none). - */ - public Enum forName(String name) { - return Target.match(name, options); - } - - /** - * Recursively check that the passed in element conforms to the rules of - * this union. - */ - @Override - public void check(Element e, String name, LFValidator v) { - Optional> match = this.match(e); - if (match.isPresent()) { - // Go deeper if the element is an array or dictionary. - Enum type = match.get(); - if (type instanceof DictionaryType) { - ((DictionaryType) type).check(e, name, v); - } else if (type instanceof ArrayType) { - ((ArrayType) type).check(e, name, v); - } else if (type instanceof PrimitiveType) { - ((PrimitiveType) type).check(e, name, v); - } else if (!(type instanceof Enum)) { - throw new RuntimeException("Encountered an unknown type."); - } - } else { - // No match found; report error. - TargetPropertyType.produceError(name, this.toString(), v); - } - } - - /** - * Internal method for matching a given element against the allowable - * types. - * - * @param e AST node that represents the value of a target property. - * @return The matching type wrapped in an Optional object. - */ - private Optional> match(Element e) { - return this.options.stream().filter(option -> { - if (option instanceof TargetPropertyType) { - return ((TargetPropertyType) option).validate(e); - } else { - return ASTUtils.elementToSingleString(e) - .equalsIgnoreCase(option.toString()); - } - }).findAny(); - } - - /** - * Return true if this union has an option that matches the given - * element. - * @param e The element to match against this type. - */ - @Override - public boolean validate(Element e) { - if (this.match(e).isPresent()) { - return true; - } - return false; - } - - /** - * Return a human-readable description of this type. If three is a - * default option, then indicate it. - */ - @Override - public String toString() { - return "one of the following: " + options.stream().map(option -> { - if (option == this.defaultOption) { - return option.toString() + " (default)"; - } else { - return option.toString(); - } - }).collect(Collectors.joining(", ")); - } - - } - - /** - * An array type of which the elements confirm to a given type. - * - * @author Marten Lohstroh - * - */ - public enum ArrayType implements TargetPropertyType { - STRING_ARRAY(PrimitiveType.STRING), - FILE_ARRAY(PrimitiveType.FILE); - - /** - * Type parameter of this array type. - */ - public TargetPropertyType type; - - /** - * Private constructor to create a new array type. - * - * @param type The type of elements in the array. - */ - private ArrayType(TargetPropertyType type) { - this.type = type; - } - - /** - * Check that the passed in element represents an array and ensure that - * its elements are all of the correct type. - */ - @Override - public void check(Element e, String name, LFValidator v) { - Array array = e.getArray(); - if (array == null) { - TargetPropertyType.produceError(name, this.toString(), v); - } else { - List elements = array.getElements(); - for (int i = 0; i < elements.size(); i++) { - this.type.check(elements.get(i), name + "[" + i + "]", v); - } - } - } - - /** - * Return true of the given element is an array. - */ - @Override - public boolean validate(Element e) { - if (e.getArray() != null) { - return true; - } - return false; - } - - /** - * Return a human-readable description of this type. - */ - @Override - public String toString() { - return "an array of which each element is " + this.type.toString(); - } - } - - /** - * Enumeration of Cmake build types. These are also mapped - * to Cargo profiles for the Rust target (see {@link org.lflang.generator.rust.RustTargetConfig}) - * - * @author Christian Menard - */ - public enum BuildType { - RELEASE("Release"), - DEBUG("Debug"), - TEST("Test"), - REL_WITH_DEB_INFO("RelWithDebInfo"), - MIN_SIZE_REL("MinSizeRel"); - - /** - * Alias used in toString method. - */ - private final String alias; - - /** - * Private constructor for Cmake build types. - */ - BuildType(String alias) { - this.alias = alias; - } - - /** - * Return the alias. - */ - @Override - public String toString() { - return this.alias; - } - } - - /** - * Enumeration of coordination types. - * - * @author Marten Lohstroh - */ - public enum CoordinationType { - CENTRALIZED, DECENTRALIZED; - - /** - * Return the name in lower case. - */ - @Override - public String toString() { - return this.name().toLowerCase(); - } - } - - - /** - * Enumeration of clock synchronization modes. - * - * - OFF: The clock synchronization is universally off. - * - STARTUP: Clock synchronization occurs at startup only. - * - ON: Clock synchronization occurs at startup and at runtime. - * - * @author Edward A. Lee - */ - public enum ClockSyncMode { - OFF, INIT, ON; // TODO Discuss initial in now a mode keyword (same as startup) and cannot be used as target property value, thus changed it to init - // FIXME I could not test if this change breaks anything - - /** - * Return the name in lower case. - */ - @Override - public String toString() { - return this.name().toLowerCase(); - } - } - - /** - * An interface for types associated with target properties. - * - * @author Marten Lohstroh - */ - public interface TargetPropertyType { - - /** - * Return true if the the given Element is a valid instance of this - * type. - * - * @param e The Element to validate. - * @return True if the element conforms to this type, false otherwise. - */ - public boolean validate(Element e); - - /** - * Check (recursively) the given Element against its associated type(s) - * and add found problems to the given list of errors. - * - * @param e The Element to type check. - * @param name The name of the target property. - * @param v A reference to the validator to report errors to. - */ - public void check(Element e, String name, LFValidator v); - - /** - * Helper function to produce an error during type checking. - * - * @param name The description of the target property. - * @param description The description of the type. - * @param v A reference to the validator to report errors to. - */ - public static void produceError(String name, String description, - LFValidator v) { - v.getTargetPropertyErrors().add("Target property '" + name - + "' is required to be " + description + "."); - } - } - - /** - * Primitive types for target properties, each with a description used in - * error messages and predicate used for validating values. - * - * @author Marten Lohstroh - */ - public enum PrimitiveType implements TargetPropertyType { - BOOLEAN("'true' or 'false'", - v -> ASTUtils.elementToSingleString(v).equalsIgnoreCase("true") - || ASTUtils.elementToSingleString(v).equalsIgnoreCase("false")), - INTEGER("an integer", v -> { - try { - Integer.parseInt(ASTUtils.elementToSingleString(v)); - } catch (NumberFormatException e) { - return false; - } - return true; - }), - NON_NEGATIVE_INTEGER("a non-negative integer", v -> { - try { - int result = Integer.parseInt(ASTUtils.elementToSingleString(v)); - if (result < 0) - return false; - } catch (NumberFormatException e) { - return false; - } - return true; - }), - TIME_VALUE("a time value with units", v -> - v.getKeyvalue() == null && v.getArray() == null - && v.getLiteral() == null && v.getId() == null - && (v.getTime() == 0 || v.getUnit() != null)), - STRING("a string", v -> v.getLiteral() != null && !isCharLiteral(v.getLiteral()) || v.getId() != null), - FILE("a path to a file", STRING.validator); - - /** - * A description of this type, featured in error messages. - */ - private final String description; - - /** - * A predicate for determining whether a given Element conforms to this - * type. - */ - public final Predicate validator; - - /** - * Private constructor to create a new primitive type. - * @param description A textual description of the type that should - * start with "a/an". - * @param validator A predicate that returns true if a given Element - * conforms to this type. - */ - private PrimitiveType(String description, - Predicate validator) { - this.description = description; - this.validator = validator; - } - - /** - * Return true if the the given Element is a valid instance of this type. - */ - public boolean validate(Element e) { - return this.validator.test(e); - } - - /** - * Check (recursively) the given Element against its associated type(s) - * and add found problems to the given list of errors. - * - * @param e The element to type check. - * @param name The name of the target property. - * @param v The LFValidator to append errors to. - */ - public void check(Element e, String name, LFValidator v) { - if (!this.validate(e)) { - TargetPropertyType.produceError(name, this.description, v); - } - // If this is a file, perform an additional check to make sure - // the file actually exists. - // FIXME: premature because we first need a mechanism for looking up files! - // Looking in the same directory is too restrictive. Disabling this check for now. - /* - if (this == FILE) { - String file = ASTUtils.toSingleString(e); - - if (!FileConfig.fileExists(file, FileConfig.toPath(e.eResource().getURI()).toFile().getParent())) { - v.targetPropertyWarnings - .add("Could not find file: '" + file + "'."); - } - } - */ - } - - /** - * Return a textual description of this type. - */ - @Override - public String toString() { - return this.description; - } - - - private static boolean isCharLiteral(String s) { - return s.length() > 2 - && '\'' == s.charAt(0) - && '\'' == s.charAt(s.length() - 1); - } - } - - /** - * Clock synchronization options. - * @author Marten Lohstroh - */ - public enum ClockSyncOption implements DictionaryElement { - ATTENUATION("attenuation", PrimitiveType.NON_NEGATIVE_INTEGER), - LOCAL_FEDERATES_ON("local-federates-on", PrimitiveType.BOOLEAN), - PERIOD("period", PrimitiveType.TIME_VALUE), - TEST_OFFSET("test-offset", PrimitiveType.TIME_VALUE), - TRIALS("trials", PrimitiveType.NON_NEGATIVE_INTEGER), - COLLECT_STATS("collect-stats", PrimitiveType.BOOLEAN); - - public final PrimitiveType type; - - private final String description; - - private ClockSyncOption(String alias, PrimitiveType type) { - this.description = alias; - this.type = type; - } - - - /** - * Return the description of this dictionary element. - */ - @Override - public String toString() { - return this.description; - } - - /** - * Return the type associated with this dictionary element. - */ - public TargetPropertyType getType() { - return this.type; - } - } - - /** - * Docker options. - * @author Edward A. Lee - */ - public enum DockerOption implements DictionaryElement { - FROM("FROM", PrimitiveType.STRING); - - public final PrimitiveType type; - - private final String description; - - private DockerOption(String alias, PrimitiveType type) { - this.description = alias; - this.type = type; - } - - /** - * Return the description of this dictionary element. - */ - @Override - public String toString() { - return this.description; - } - - /** - * Return the type associated with this dictionary element. - */ - public TargetPropertyType getType() { - return this.type; - } - } - - - /** - * Platform options. - * @author Anirudh Rengarajan - */ - public enum PlatformOption implements DictionaryElement { - NAME("name", PrimitiveType.STRING), - BAUDRATE("baud-rate", PrimitiveType.NON_NEGATIVE_INTEGER), - BOARD("board", PrimitiveType.STRING), - FLASH("flash", PrimitiveType.BOOLEAN), - PORT("port", PrimitiveType.STRING); - - public final PrimitiveType type; - - private final String description; - - private PlatformOption(String alias, PrimitiveType type) { - this.description = alias; - this.type = type; - } - - - /** - * Return the description of this dictionary element. - */ - @Override - public String toString() { - return this.description; - } - - /** - * Return the type associated with this dictionary element. - */ - public TargetPropertyType getType() { - return this.type; - } - } - - /** - * Coordination options. - * @author Edward A. Lee - */ - public enum CoordinationOption implements DictionaryElement { - ADVANCE_MESSAGE_INTERVAL("advance-message-interval", PrimitiveType.TIME_VALUE); - - public final PrimitiveType type; - - private final String description; - - private CoordinationOption(String alias, PrimitiveType type) { - this.description = alias; - this.type = type; - } - - - /** - * Return the description of this dictionary element. - */ - @Override - public String toString() { - return this.description; - } - - /** - * Return the type associated with this dictionary element. - */ - public TargetPropertyType getType() { - return this.type; - } - } - - /** - * Log levels in descending order of severity. - * @author Marten Lohstroh - */ - public enum LogLevel { - ERROR, WARN, INFO, LOG, DEBUG; - - /** - * Return the name in lower case. - */ - @Override - public String toString() { - return this.name().toLowerCase(); - } - } - - /** - * Enumeration of supported platforms - */ - public enum Platform { - AUTO, - ARDUINO, - NRF52("Nrf52"), - LINUX("Linux"), - MAC("Darwin"), - ZEPHYR("Zephyr"), - WINDOWS("Windows"); - - String cMakeName; - Platform() { - this.cMakeName = this.toString(); - } - Platform(String cMakeName) { - this.cMakeName = cMakeName; - } - - /** - * Return the name in lower case. - */ - @Override - public String toString() { - return this.name().toLowerCase(); - } - - /** - * Get the CMake name for the platform. - */ - public String getcMakeName() { - return this.cMakeName; - } - } - - /** - * Supported schedulers. - * @author Soroush Bateni - */ - public enum SchedulerOption { - NP(false), // Non-preemptive - ADAPTIVE(false, List.of( - Path.of("scheduler_adaptive.c"), - Path.of("worker_assignments.h"), - Path.of("worker_states.h"), - Path.of("data_collection.h") - )), - GEDF_NP(true), // Global EDF non-preemptive - GEDF_NP_CI(true); // Global EDF non-preemptive with chain ID - // PEDF_NP(true); // Partitioned EDF non-preemptive (FIXME: To be re-added in a future PR) - - /** - * Indicate whether or not the scheduler prioritizes reactions by deadline. - */ - private final boolean prioritizesDeadline; - - /** Relative paths to files required by this scheduler. */ - private final List relativePaths; - - SchedulerOption(boolean prioritizesDeadline) { - this(prioritizesDeadline, null); - } - - SchedulerOption(boolean prioritizesDeadline, List relativePaths) { - this.prioritizesDeadline = prioritizesDeadline; - this.relativePaths = relativePaths; - } - - /** - * Return true if the scheduler prioritizes reactions by deadline. - */ - public boolean prioritizesDeadline() { - return this.prioritizesDeadline; - } - - public List getRelativePaths() { - return relativePaths != null ? ImmutableList.copyOf(relativePaths) : - List.of(Path.of("scheduler_" + this + ".c")); - } - - public static SchedulerOption getDefault() { - return NP; - } - } - - /** - * Tracing options. - * @author Edward A. Lee - */ - public enum TracingOption implements DictionaryElement { - TRACE_FILE_NAME("trace-file-name", PrimitiveType.STRING); - - public final PrimitiveType type; - - private final String description; - - private TracingOption(String alias, PrimitiveType type) { - this.description = alias; - this.type = type; - } - - /** - * Return the description of this dictionary element. - */ - @Override - public String toString() { - return this.description; - } - - /** - * Return the type associated with this dictionary element. - */ - public TargetPropertyType getType() { - return this.type; - } - } - -} diff --git a/org.lflang/src/org/lflang/TimeValue.java b/org.lflang/src/org/lflang/TimeValue.java deleted file mode 100644 index 9eab5aa68b..0000000000 --- a/org.lflang/src/org/lflang/TimeValue.java +++ /dev/null @@ -1,234 +0,0 @@ -/************* -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; - -/** - * Represents an amount of time (a duration). - * - * @author Marten Lohstroh - * @author Clément Fournier - TU Dresden, INSA Rennes - */ -public final class TimeValue implements Comparable { - - /** - * The maximum value of this type. This is approximately equal to 292 years. - */ - public static final TimeValue MAX_VALUE = new TimeValue(Long.MAX_VALUE, TimeUnit.NANO); - /** - * A time value equal to zero. - */ - public static final TimeValue ZERO = new TimeValue(0, null); - - - /** - * Primitive numerical representation of this time value, - * to be interpreted in terms the associated time unit. - */ - public final long time; - - /** - * Units associated with this time value. May be null. - */ - public final TimeUnit unit; - - /** - * Maximum size of a deadline in primitive representation. - * NOTE: if we were to use an unsigned data type this would be - * 0xFFFFFFFFFFFF - */ - public static final long MAX_LONG_DEADLINE = Long.decode("0x7FFFFFFFFFFF"); - - /** - * Create a new time value. - * - * @throws IllegalArgumentException If time is non-zero and the unit is null - */ - public TimeValue(long time, TimeUnit unit) { - if (unit == null && time != 0) { - throw new IllegalArgumentException("Non-zero time values must have a unit"); - } - this.time = time; - this.unit = unit; - } - - @Override - public boolean equals(Object t1) { - if (t1 instanceof TimeValue) { - return this.compareTo((TimeValue) t1) == 0; - } - return false; - } - - public static int compare(TimeValue t1, TimeValue t2) { - if (t1.isEarlierThan(t2)) { - return -1; - } - if (t2.isEarlierThan(t1)) { - return 1; - } - return 0; - } - - private static long makeNanosecs(long time, TimeUnit unit) { - if (unit == null) { - return time; // == 0, see constructor. - } - switch (unit) { - case NANO: - return time; - case MICRO: - return time * 1000; - case MILLI: - return time * 1_000_000; - case SECOND: - return time * 1_000_000_000; - case MINUTE: - return time * 60_000_000_000L; - case HOUR: - return time * 3_600_000_000_000L; - case DAY: - return time * 86_400_000_000_000L; - case WEEK: - return time * 604_800_016_558_522L; - } - throw new AssertionError("unreachable"); - } - - /** - * Returns whether this time value is earlier than another. - */ - public boolean isEarlierThan(TimeValue other) { - return this.compareTo(other) < 0; - } - - @Override - public int compareTo(TimeValue o) { - return Long.compare(this.toNanoSeconds(), o.toNanoSeconds()); - } - - /** - * Return the magnitude of this value, as expressed in the - * {@linkplain #getUnit() unit} of this value. - */ - public long getMagnitude() { - return time; - } - - /** - * Units associated with this time value. May be null, - * but only if the magnitude is zero. - */ - public TimeUnit getUnit() { - return unit; - } - - /** - * Get this time value in number of nanoseconds. - */ - public long toNanoSeconds() { - return makeNanosecs(time, unit); - } - - /** - * Return a TimeValue based on a nanosecond value. - */ - public static TimeValue fromNanoSeconds(long ns) { - if (ns == 0) return ZERO; - long time; - if ((time = ns / 604_800_016_558_522L) > 0 && ns % 604_800_016_558_522L == 0) { - return new TimeValue(time, TimeUnit.WEEK); - } - if ((time = ns / 86_400_000_000_000L) > 0 && ns % 86_400_000_000_000L == 0) { - return new TimeValue(time, TimeUnit.DAY); - } - if ((time = ns / 3_600_000_000_000L) > 0 && ns % 3_600_000_000_000L == 0) { - return new TimeValue(time, TimeUnit.HOUR); - } - if ((time = ns / 60_000_000_000L) > 0 && ns % 60_000_000_000L == 0) { - return new TimeValue(time, TimeUnit.MINUTE); - } - if ((time = ns / 1_000_000_000) > 0 && ns % 1_000_000_000 == 0) { - return new TimeValue(time, TimeUnit.SECOND); - } - if ((time = ns / 1_000_000) > 0 && ns % 1_000_000 == 0) { - return new TimeValue(time, TimeUnit.MILLI); - } - if ((time = ns / 1000) > 0 && ns % 1000 == 0) { - return new TimeValue(time, TimeUnit.MICRO); - } - return new TimeValue(ns, TimeUnit.NANO); - } - - /** - * Return a string representation of this time value. - */ - public String toString() { - return unit != null ? time + " " + unit.getCanonicalName() - : Long.toString(time); - } - - /** Return the latest of both values. */ - public static TimeValue max(TimeValue t1, TimeValue t2) { - return t1.isEarlierThan(t2) ? t2 : t1; - } - - /** Return the earliest of both values. */ - public static TimeValue min(TimeValue t1, TimeValue t2) { - return t1.isEarlierThan(t2) ? t1 : t2; - } - - /** - * Return the sum of this duration and the one represented by b. - *

- * The unit of the returned TimeValue will be the minimum - * of the units of both operands except if only one of the units - * is TimeUnit.NONE. In that case, the unit of the other input is used. - * - * @param b The right operand - * @return A new TimeValue (the current value will not be affected) - */ - public TimeValue add(TimeValue b) { - // Figure out the actual sum - final long sumOfNumbers; - try { - sumOfNumbers = Math.addExact(this.toNanoSeconds(), b.toNanoSeconds()); - } catch (ArithmeticException overflow) { - return MAX_VALUE; - } - - if (this.unit == null || b.unit == null) { - // A time value with no unit is necessarily zero. So - // if this is null, (this + b) == b, if b is none, (this+b) == this. - return b.unit == null ? this : b; - } - boolean isThisUnitSmallerThanBUnit = this.unit.compareTo(b.unit) <= 0; - TimeUnit smallestUnit = isThisUnitSmallerThanBUnit ? this.unit : b.unit; - // Find the appropriate divider to bring sumOfNumbers from nanoseconds to returnUnit - var unitDivider = makeNanosecs(1, smallestUnit); - return new TimeValue(sumOfNumbers / unitDivider, smallestUnit); - } - -} diff --git a/org.lflang/src/org/lflang/analyses/cast/AbstractAstVisitor.java b/org.lflang/src/org/lflang/analyses/cast/AbstractAstVisitor.java deleted file mode 100644 index c3483e2d3e..0000000000 --- a/org.lflang/src/org/lflang/analyses/cast/AbstractAstVisitor.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.lflang.analyses.cast; - -import java.util.List; - -/** Modeled after AbstractParseTreeVisitor.class */ -public abstract class AbstractAstVisitor implements AstVisitor { - - @Override - public T visit(CAst.AstNode tree) { - return tree.accept(this); - } - - @Override - public T visit(CAst.AstNode tree, List nodeList) { - return tree.accept(this, nodeList); - } -} diff --git a/org.lflang/src/org/lflang/analyses/cast/AstUtils.java b/org.lflang/src/org/lflang/analyses/cast/AstUtils.java deleted file mode 100644 index bed532e424..0000000000 --- a/org.lflang/src/org/lflang/analyses/cast/AstUtils.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.lflang.analyses.cast; - -import java.util.List; - -import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.misc.Interval; - -public class AstUtils { - - public static CAst.AstNode takeConjunction(List conditions) { - if (conditions.size() == 0) { - return new CAst.LiteralNode("true"); - } else if (conditions.size() == 1) { - return conditions.get(0); - } else { - // Take the conjunction of all the conditions. - CAst.LogicalAndNode top = new CAst.LogicalAndNode(); - CAst.LogicalAndNode cur = top; - for (int i = 0; i < conditions.size()-1; i++) { - cur.left = conditions.get(i); - if (i == conditions.size()-2) { - cur.right = conditions.get(i+1); - } else { - cur.right = new CAst.LogicalAndNode(); - cur =(CAst.LogicalAndNode)cur.right; - } - } - return top; - } - } - - public static CAst.AstNode takeDisjunction(List conditions) { - if (conditions.size() == 0) { - return new CAst.LiteralNode("true"); - } else if (conditions.size() == 1) { - return conditions.get(0); - } else { - // Take the conjunction of all the conditions. - CAst.LogicalOrNode top = new CAst.LogicalOrNode(); - CAst.LogicalOrNode cur = top; - for (int i = 0; i < conditions.size()-1; i++) { - cur.left = conditions.get(i); - if (i == conditions.size()-2) { - cur.right = conditions.get(i+1); - } else { - cur.right = new CAst.LogicalOrNode(); - cur =(CAst.LogicalOrNode)cur.right; - } - } - return top; - } - } - - // A handy function for debugging ASTs. - // It prints the stack trace of the visitor functions - // and shows the text matched by the ANTLR rules. - public static void printStackTraceAndMatchedText(ParserRuleContext ctx) { - System.out.println("========== AST DEBUG =========="); - - // Print matched text - int a = ctx.start.getStartIndex(); - int b = ctx.stop.getStopIndex(); - Interval interval = new Interval(a,b); - String matchedText = ctx.start.getInputStream().getText(interval); - System.out.println("Matched text: " + matchedText); - - // Print stack trace - StackTraceElement[] cause = Thread.currentThread().getStackTrace(); - System.out.print("Stack trace: "); - for (int i = 0; i < cause.length; i++) { - System.out.print(cause[i].getMethodName()); - if (i != cause.length - 1) System.out.print(", "); - } - System.out.println("."); - - System.out.println("==============================="); - } -} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/analyses/cast/AstVisitor.java b/org.lflang/src/org/lflang/analyses/cast/AstVisitor.java deleted file mode 100644 index 47861e303b..0000000000 --- a/org.lflang/src/org/lflang/analyses/cast/AstVisitor.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.lflang.analyses.cast; - -import java.util.List; - -/** Modeled after ParseTreeVisitor.class */ -public interface AstVisitor { - - /** - * Visit an AST, and return a user-defined result of the operation. - * - * @param tree The {@link CAst.AstNode} to visit. - * @return The result of visiting the parse tree. - */ - T visit(CAst.AstNode tree); - - /** - * Visit an AST with a list of other AST nodes holding some information, - * and return a user-defined result of the operation. - * - * @param tree The {@link CAst.AstNode} to visit. - * @param nodeList A list of {@link CAst.AstNode} passed down the recursive call. - * @return The result of visiting the parse tree. - */ - T visit(CAst.AstNode tree, List nodeList); -} diff --git a/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java b/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java deleted file mode 100644 index 450a4798e4..0000000000 --- a/org.lflang/src/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java +++ /dev/null @@ -1,681 +0,0 @@ -package org.lflang.analyses.cast; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.lflang.dsl.CBaseVisitor; -import org.lflang.dsl.CParser.*; - -public class BuildAstParseTreeVisitor extends CBaseVisitor { - - @Override - public CAst.AstNode visitBlockItemList(BlockItemListContext ctx) { - CAst.StatementSequenceNode stmtSeq = new CAst.StatementSequenceNode(); - // Populate the children. - for (BlockItemContext blockItem : ctx.blockItem()) { - stmtSeq.children.add(visit(blockItem)); - } - return stmtSeq; - } - - @Override - public CAst.AstNode visitBlockItem(BlockItemContext ctx) { - if (ctx.statement() != null) - return visit(ctx.statement()); - else - return visit(ctx.declaration()); - } - - @Override - public CAst.AstNode visitDeclaration(DeclarationContext ctx) { - if (ctx.declarationSpecifiers() != null - && ctx.initDeclaratorList() != null) { - //// Extract type from declarationSpecifiers. - List declSpecList - = ctx.declarationSpecifiers().declarationSpecifier(); - - // Cannot handle more than 1 specifiers, e.g. static const int. - // We can augment the analytical capability later. - if (declSpecList.size() > 1) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "the analyzer cannot handle more than 1 specifiers,", - "e.g. static const int.", - "Marking the declaration as opaque." - )); - return new CAst.OpaqueNode(); - } - - // Check if the declaration specifier is a type specifier: e.g. int or long. - DeclarationSpecifierContext declSpec = declSpecList.get(0); - if (declSpec.typeSpecifier() == null) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "only type specifiers are supported.", - "e.g. \"static const int\" is not analyzable.", - "Marking the declaration as opaque." - )); - return new CAst.OpaqueNode(); - } - - // Check if the type specifier is what we currently support. - // Right now we only support int, long, & double. - CAst.VariableNode.Type type; - ArrayList supportedTypes = new ArrayList( - Arrays.asList( - "int", - "long", - "double", - "_Bool" - ) - ); - if (declSpec.typeSpecifier().Int() != null - || declSpec.typeSpecifier().Long() != null - || declSpec.typeSpecifier().Double() != null) - type = CAst.VariableNode.Type.INT; - else if (declSpec.typeSpecifier().Bool() != null) - type = CAst.VariableNode.Type.BOOLEAN; - // Mark the declaration unanalyzable if the type is unsupported. - else { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "unsupported type detected at " + declSpec.typeSpecifier(), - "Only " + supportedTypes + " are supported.", - "Marking the declaration as opaque." - )); - return new CAst.OpaqueNode(); - } - - //// Extract variable name and value from initDeclaratorList. - List initDeclList - = ctx.initDeclaratorList().initDeclarator(); - - // Cannot handle more than 1 initDeclarator: e.g. x = 1, y = 2; - // We can augment the analytical capability later. - if (initDeclList.size() > 1) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "more than 1 declarators are detected on a single line,", - "e.g. \"int x = 1, y = 2;\" is not yet analyzable.", - "Marking the declaration as opaque." - )); - return new CAst.OpaqueNode(); - } - - // Get the variable name from the declarator. - DeclaratorContext decl = initDeclList.get(0).declarator(); - if (decl.pointer() != null) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "pointers are currently not supported,", - "e.g. \"int *x;\" is not yet analyzable.", - "Marking the declaration as opaque." - )); - return new CAst.OpaqueNode(); - } - if (decl.gccDeclaratorExtension().size() > 0) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "GCC declarator extensions are currently not supported,", - "e.g. \"__asm\" and \"__attribute__\" are not yet analyzable.", - "Marking the declaration as opaque." - )); - return new CAst.OpaqueNode(); - } - DirectDeclaratorContext directDecl = decl.directDeclarator(); - if (directDecl.Identifier() == null) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "the variable identifier is missing.", - "Marking the declaration as opaque." - )); - return new CAst.OpaqueNode(); - } - - // Extract the name of the variable. - String name = directDecl.Identifier().getText(); - // Create a variable Ast node. - CAst.VariableNode variable = new CAst.VariableNode(type, name); - - - //// Convert the initializer to a value. - - // Make sure that there is an initializer. - InitDeclaratorContext initDecl = initDeclList.get(0); - if (initDecl.initializer() == null) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "the initializer is missing,", - "e.g. \"int x;\" is not yet analyzable.", - "Marking the declaration as opaque." - )); - return new CAst.OpaqueNode(); - - // FIXME: Use UninitCAst.VariableNode to perform special encoding. - // return new UninitCAst.VariableNode(type, name); - } - - // Extract the primaryExpression from the initializer. - if (initDecl.initializer().assignmentExpression() == null - || initDecl.initializer().assignmentExpression() - .conditionalExpression() == null) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "assignmentExpression or conditionalExpression is missing.", - "Marking the declaration as opaque." - )); - return new CAst.OpaqueNode(); - } - - // Finally return the assignment node. - CAst.AssignmentNode assignmentNode = new CAst.AssignmentNode(); - CAst.AstNode initNode = visitAssignmentExpression(initDecl.initializer().assignmentExpression()); - assignmentNode.left = variable; - assignmentNode.right = initNode; - return assignmentNode; - } - // Return OpaqueNode as default. - return new CAst.OpaqueNode(); - } - - /** - * This visit function builds StatementSequenceNode, AssignmentNode, - * OpaqueNode, IfBlockNode, - * AdditionNode, SubtractionNode, MultiplicationNode, DivisionNode, - * EqualNode, NotEqualNode, LessThanNode, GreaterThanNode, LessEqualNode, GreaterEqualNode, - * SetPortNode, ScheduleActionNode. - * - * @param ctx - * @return - */ - @Override - public CAst.AstNode visitStatement(StatementContext ctx) { - if (ctx.compoundStatement() != null) { - BlockItemListContext bilCtx = ctx.compoundStatement().blockItemList(); - if (bilCtx != null) { - return visitBlockItemList(bilCtx); - } - } else if (ctx.expressionStatement() != null) { - ExpressionContext exprCtx = ctx.expressionStatement().expression(); - if (exprCtx != null) { - return visitExpression(exprCtx); - } - } else if (ctx.selectionStatement() != null) { - return visitSelectionStatement(ctx.selectionStatement()); - } - return new CAst.OpaqueNode(); - } - - @Override - public CAst.AstNode visitExpression(ExpressionContext ctx) { - if (ctx.assignmentExpression().size() == 1) { - return visitAssignmentExpression(ctx.assignmentExpression().get(0)); - } - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "only one assignmentExpression in an expression is currently supported.", - "Marking the statement as opaque." - )); - return new CAst.OpaqueNode(); - } - - @Override - public CAst.AstNode visitPrimaryExpression(PrimaryExpressionContext ctx) { - if (ctx.Identifier() != null) { - return new CAst.VariableNode(ctx.Identifier().getText()); - } else if (ctx.Constant() != null) { - return new CAst.LiteralNode(ctx.Constant().getText()); - } else if (ctx.expression() != null) { - return visitExpression(ctx.expression()); - } - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "only identifier, constant, and expressions are supported in a primary expression.", - "Marking the declaration as opaque." - )); - return new CAst.OpaqueNode(); - } - - // FIXME: More checks needed. This implementation currently silently omit - // certain cases, such as arr[1]. - @Override - public CAst.AstNode visitPostfixExpression(PostfixExpressionContext ctx) { - if (ctx.PlusPlus().size() > 0 - || ctx.MinusMinus().size() > 0 - || ctx.Dot().size() > 0 - || (ctx.LeftBracket().size() > 0 && ctx.RightBracket().size() > 0)) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Postfix '++', '--', '.', '[]' are currently not supported.", - "Marking the statement as opaque." - )); - return new CAst.OpaqueNode(); - } - // State variables on the self struct, ports and actions. - if (ctx.primaryExpression() != null - && ctx.Identifier().size() == 1 - && ctx.Arrow().size() == 1) { - CAst.AstNode primaryExprNode = visitPrimaryExpression(ctx.primaryExpression()); - if (primaryExprNode instanceof CAst.LiteralNode) { - // Unreachable. - System.out.println("Unreachable!"); - return new CAst.OpaqueNode(); // FIXME: Throw an exception instead. - } - CAst.VariableNode varNode = (CAst.VariableNode) primaryExprNode; - if (varNode.name.equals("self")) { - // return a state variable node. - return new CAst.StateVarNode(ctx.Identifier().get(0).getText()); - } else if (ctx.Identifier().get(0).getText().equals("value")) { - // return a trigger present node. - return new CAst.TriggerValueNode(varNode.name); - } else if (ctx.Identifier().get(0).getText().equals("is_present")) { - // return a trigger value node. - return new CAst.TriggerIsPresentNode(varNode.name); - } else { - // Generic pointer dereference, unanalyzable. - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Generic pointer dereference is not supported in a postfix expression.", - "Marking the declaration as opaque." - )); - return new CAst.OpaqueNode(); - } - } - // LF built-in function calls (set or schedule) - if (ctx.primaryExpression() != null - && ctx.argumentExpressionList().size() == 1 - && ctx.LeftParen() != null && ctx.RightParen() != null) { - CAst.AstNode primaryExprNode = visitPrimaryExpression(ctx.primaryExpression()); - List params = ctx.argumentExpressionList().get(0).assignmentExpression(); - if (primaryExprNode instanceof CAst.LiteralNode) { - // Unreachable. - System.out.println("Unreachable!"); - return new CAst.OpaqueNode(); // FIXME: Throw an exception instead. - } - CAst.VariableNode varNode = (CAst.VariableNode) primaryExprNode; - if (varNode.name.equals("lf_set")) { - // return a set port node. - if (params.size() != 2) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "lf_set must have 2 arguments. Detected " + ctx.argumentExpressionList().size(), - "Marking the function call as opaque." - )); - return new CAst.OpaqueNode(); - } - CAst.SetPortNode node = new CAst.SetPortNode(); - node.left = visitAssignmentExpression(params.get(0)); - node.right = visitAssignmentExpression(params.get(1)); - return node; - } else if (varNode.name.equals("lf_schedule")) { - // return a set port node. - if (params.size() != 2) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "lf_schedule must have two arguments. Detected " + ctx.argumentExpressionList().size(), - "Marking the function call as opaque." - )); - return new CAst.OpaqueNode(); - } - CAst.ScheduleActionNode node = new CAst.ScheduleActionNode(); - for (AssignmentExpressionContext param : params) { - node.children.add(visitAssignmentExpression(param)); - } - return node; - } else if (varNode.name.equals("lf_schedule_int")) { - // return a set port node. - if (params.size() != 3) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "lf_schedule_int must have three arguments. Detected " + ctx.argumentExpressionList().size(), - "Marking the function call as opaque." - )); - return new CAst.OpaqueNode(); - } - CAst.ScheduleActionIntNode node = new CAst.ScheduleActionIntNode(); - for (AssignmentExpressionContext param : params) { - node.children.add(visitAssignmentExpression(param)); - } - return node; - } else { - // Generic pointer dereference, unanalyzable. - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Generic pointer dereference and function calls are not supported in a postfix expression.", - "Marking the declaration as opaque." - )); - return new CAst.OpaqueNode(); - } - } - // Variable or literal - if (ctx.primaryExpression() != null) { - return visitPrimaryExpression(ctx.primaryExpression()); - } - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "only an identifier, constant, state variable, port, and action are supported in a primary expression.", - "Marking the declaration as opaque." - )); - return new CAst.OpaqueNode(); - } - - @Override - public CAst.AstNode visitUnaryExpression(UnaryExpressionContext ctx) { - // Check for prefixes and mark them as opaque (unsupported for now). - if (ctx.PlusPlus().size() > 0 - || ctx.MinusMinus().size() > 0 - || ctx.Sizeof().size() > 0) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Prefix '++', '--', and 'sizeof' are currently not supported.", - "Marking the statement as opaque." - )); - AstUtils.printStackTraceAndMatchedText(ctx); - return new CAst.OpaqueNode(); - } - // Handle the postfixExpression rule - // (look up the grammar in C.g4 to get more details). - if (ctx.postfixExpression() != null) { - return visitPostfixExpression(ctx.postfixExpression()); - } - // Handle the unary operators '!' (logical not) - // and '-' (negative). - if (ctx.unaryOperator() != null - && ctx.castExpression() != null) { - CAst.AstNodeUnary node; - if (ctx.unaryOperator().Not() != null) { - // Handle the logical not expression. - node = new CAst.LogicalNotNode(); - node.child = visitCastExpression(ctx.castExpression()); - return node; - } - else if (ctx.unaryOperator().Minus() != null) { - // Handle negative numbers. - // -5 will be translated as -1 * (5) - // which is a NegativeNode with a - // LiteralNode inside. - // - // FIXME: Need to perform precise error handling - // because we will go from a castExpression to - // a Constant under primaryExpression and anything - // else matching besides Constant could be problematic. - // For example, we cannot have a NegativeNode with - // a StringLiteralNode inside. This can be caught by - // the GCC, but if compilation is not involved, it - // would be useful to catch it here ourselves. - node = new CAst.NegativeNode(); - node.child = visitCastExpression(ctx.castExpression()); - return node; - } - } - - // Mark all the remaining cases as opaque. - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "only postfixExpression and '!' in a unaryExpression is currently supported.", - "Marking the statement as opaque." - )); - AstUtils.printStackTraceAndMatchedText(ctx); - return new CAst.OpaqueNode(); - } - - @Override - public CAst.AstNode visitCastExpression(CastExpressionContext ctx) { - if (ctx.unaryExpression() != null) { - return visitUnaryExpression(ctx.unaryExpression()); - } - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "only unaryExpression in a castExpression is currently supported.", - "Marking the statement as opaque." - )); - return new CAst.OpaqueNode(); - } - - @Override - public CAst.AstNode visitMultiplicativeExpression(MultiplicativeExpressionContext ctx) { - if (ctx.castExpression().size() > 1) { - CAst.AstNodeBinary node; - if (ctx.Star().size() > 0) { - node = new CAst.MultiplicationNode(); - } else if (ctx.Div().size() > 0) { - node = new CAst.DivisionNode(); - } else if (ctx.Mod().size() > 0) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Mod expression '%' is currently unsupported.", - "Marking the expression as opaque." - )); - return new CAst.OpaqueNode(); - } else { - node = new CAst.AstNodeBinary(); - } - node.left = visitCastExpression(ctx.castExpression().get(0)); - node.right = visitCastExpression(ctx.castExpression().get(1)); - return node; - } - return visitCastExpression(ctx.castExpression().get(0)); - } - - @Override - public CAst.AstNode visitAdditiveExpression(AdditiveExpressionContext ctx) { - if (ctx.multiplicativeExpression().size() > 1) { - CAst.AstNodeBinary node; - if (ctx.Plus().size() > 0) { - node = new CAst.AdditionNode(); - } else if (ctx.Minus().size() > 0) { - node = new CAst.SubtractionNode(); - } else { - node = new CAst.AstNodeBinary(); - } - node.left = visitMultiplicativeExpression(ctx.multiplicativeExpression().get(0)); - node.right = visitMultiplicativeExpression(ctx.multiplicativeExpression().get(1)); - return node; - } - return visitMultiplicativeExpression(ctx.multiplicativeExpression().get(0)); - } - - @Override - public CAst.AstNode visitShiftExpression(ShiftExpressionContext ctx) { - if (ctx.additiveExpression().size() > 1) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Shift expression '<<' or '>>' is currently unsupported.", - "Marking the expression as opaque." - )); - return new CAst.OpaqueNode(); - } - return visitAdditiveExpression(ctx.additiveExpression().get(0)); - } - - @Override - public CAst.AstNode visitRelationalExpression(RelationalExpressionContext ctx) { - if (ctx.shiftExpression().size() > 1) { - CAst.AstNodeBinary node; - if (ctx.Less().size() > 0) { - node = new CAst.LessThanNode(); - } else if (ctx.LessEqual().size() > 0) { - node = new CAst.LessEqualNode(); - } else if (ctx.Greater().size() > 0) { - node = new CAst.GreaterThanNode(); - } else if (ctx.GreaterEqual().size() > 0) { - node = new CAst.GreaterEqualNode(); - } else { - node = new CAst.AstNodeBinary(); - } - node.left = visitShiftExpression(ctx.shiftExpression().get(0)); - node.right = visitShiftExpression(ctx.shiftExpression().get(1)); - return node; - } - return visitShiftExpression(ctx.shiftExpression().get(0)); - } - - @Override - public CAst.AstNode visitEqualityExpression(EqualityExpressionContext ctx) { - if (ctx.relationalExpression().size() > 1) { - CAst.AstNodeBinary node; - if (ctx.Equal().size() > 0) { - node = new CAst.EqualNode(); - } - else if (ctx.NotEqual().size() > 0) { - node = new CAst.NotEqualNode(); - } else { - node = new CAst.AstNodeBinary(); - } - node.left = visitRelationalExpression(ctx.relationalExpression().get(0)); - node.right = visitRelationalExpression(ctx.relationalExpression().get(1)); - return node; - } - return visitRelationalExpression(ctx.relationalExpression().get(0)); - } - - @Override - public CAst.AstNode visitAndExpression(AndExpressionContext ctx) { - if (ctx.equalityExpression().size() > 1) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "And expression '&' is currently unsupported.", - "Marking the expression as opaque." - )); - return new CAst.OpaqueNode(); - } - return visitEqualityExpression(ctx.equalityExpression().get(0)); - } - - @Override - public CAst.AstNode visitExclusiveOrExpression(ExclusiveOrExpressionContext ctx) { - if (ctx.andExpression().size() > 1) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Exclusive Or '^' is currently unsupported.", - "Marking the expression as opaque." - )); - return new CAst.OpaqueNode(); - } - return visitAndExpression(ctx.andExpression().get(0)); - } - - @Override - public CAst.AstNode visitInclusiveOrExpression(InclusiveOrExpressionContext ctx) { - if (ctx.exclusiveOrExpression().size() > 1) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Inclusive Or '|' is currently unsupported.", - "Marking the expression as opaque." - )); - return new CAst.OpaqueNode(); - } - return visitExclusiveOrExpression(ctx.exclusiveOrExpression().get(0)); - } - - @Override - public CAst.AstNode visitLogicalAndExpression(LogicalAndExpressionContext ctx) { - if (ctx.inclusiveOrExpression().size() > 1) { - CAst.LogicalAndNode node = new CAst.LogicalAndNode(); - node.left = visitInclusiveOrExpression(ctx.inclusiveOrExpression().get(0)); - node.right = visitInclusiveOrExpression(ctx.inclusiveOrExpression().get(1)); - return node; - } - return visitInclusiveOrExpression(ctx.inclusiveOrExpression().get(0)); - } - - @Override - public CAst.AstNode visitLogicalOrExpression(LogicalOrExpressionContext ctx) { - if (ctx.logicalAndExpression().size() > 1) { - CAst.LogicalOrNode node = new CAst.LogicalOrNode(); - node.left = visitLogicalAndExpression(ctx.logicalAndExpression().get(0)); - node.right = visitLogicalAndExpression(ctx.logicalAndExpression().get(1)); - return node; - } - return visitLogicalAndExpression(ctx.logicalAndExpression().get(0)); - } - - @Override - public CAst.AstNode visitConditionalExpression(ConditionalExpressionContext ctx) { - if (ctx.expression() != null) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Currently do not support inline conditional expression.", - "Marking the expression as opaque." - )); - return new CAst.OpaqueNode(); - } - return visitLogicalOrExpression(ctx.logicalOrExpression()); - } - - @Override - public CAst.AstNode visitAssignmentExpression(AssignmentExpressionContext ctx) { - if (ctx.conditionalExpression() != null) { - return visitConditionalExpression(ctx.conditionalExpression()); - } - if (ctx.unaryExpression() != null - && ctx.assignmentExpression() != null) { - CAst.AstNodeBinary assignmentNode = new CAst.AssignmentNode(); - assignmentNode.left = visitUnaryExpression(ctx.unaryExpression()); - if (ctx.assignmentOperator().getText().equals("=")) { - assignmentNode.right = visitAssignmentExpression(ctx.assignmentExpression()); - } - else if (ctx.assignmentOperator().getText().equals("+=")) { - CAst.AdditionNode subnode = new CAst.AdditionNode(); - subnode.left = visitUnaryExpression(ctx.unaryExpression()); - subnode.right = visitAssignmentExpression(ctx.assignmentExpression()); - assignmentNode.right = subnode; - } - else if (ctx.assignmentOperator().getText().equals("-=")) { - CAst.SubtractionNode subnode = new CAst.SubtractionNode(); - subnode.left = visitUnaryExpression(ctx.unaryExpression()); - subnode.right = visitAssignmentExpression(ctx.assignmentExpression()); - assignmentNode.right = subnode; - } - else if (ctx.assignmentOperator().getText().equals("*=")) { - CAst.MultiplicationNode subnode = new CAst.MultiplicationNode(); - subnode.left = visitUnaryExpression(ctx.unaryExpression()); - subnode.right = visitAssignmentExpression(ctx.assignmentExpression()); - assignmentNode.right = subnode; - } - else if (ctx.assignmentOperator().getText().equals("/=")) { - CAst.DivisionNode subnode = new CAst.DivisionNode(); - subnode.left = visitUnaryExpression(ctx.unaryExpression()); - subnode.right = visitAssignmentExpression(ctx.assignmentExpression()); - assignmentNode.right = subnode; - } - else { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Only '=', '+=', '-=', '*=', '/=' assignment operators are supported.", - "Marking the expression as opaque." - )); - return new CAst.OpaqueNode(); - } - return assignmentNode; - } - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "DigitSequence in an assignmentExpression is currently not supported.", - "Marking the expression as opaque." - )); - return new CAst.OpaqueNode(); - } - - @Override - public CAst.AstNode visitSelectionStatement(SelectionStatementContext ctx) { - if (ctx.Switch() != null) { - System.out.println(String.join(" ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Switch case statement is currently not supported.", - "Marking the expression as opaque." - )); - return new CAst.OpaqueNode(); - } - CAst.IfBlockNode ifBlockNode = new CAst.IfBlockNode(); - CAst.IfBodyNode ifBodyNode = new CAst.IfBodyNode(); - ifBlockNode.left = visitExpression(ctx.expression()); - ifBlockNode.right = ifBodyNode; - ifBodyNode.left = visitStatement(ctx.statement().get(0)); - if (ctx.statement().size() > 1) { - ifBodyNode.right = visitStatement(ctx.statement().get(1)); - } - return ifBlockNode; - } -} diff --git a/org.lflang/src/org/lflang/analyses/cast/CAst.java b/org.lflang/src/org/lflang/analyses/cast/CAst.java deleted file mode 100644 index d5ce1ee896..0000000000 --- a/org.lflang/src/org/lflang/analyses/cast/CAst.java +++ /dev/null @@ -1,361 +0,0 @@ -package org.lflang.analyses.cast; - -import java.util.ArrayList; -import java.util.List; - -public class CAst { - - public static class AstNode implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitAstNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitAstNode(this, nodeList); - } - } - - public static class AstNodeUnary extends AstNode implements Visitable { - public AstNode child; - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitAstNodeUnary(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitAstNodeUnary(this, nodeList); - } - } - - public static class AstNodeBinary extends AstNode implements Visitable { - public AstNode left; - public AstNode right; - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitAstNodeBinary(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitAstNodeBinary(this, nodeList); - } - } - - public static class AstNodeDynamic extends AstNode implements Visitable { - public ArrayList children = new ArrayList<>(); - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitAstNodeDynamic(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitAstNodeDynamic(this, nodeList); - } - } - - public static class AssignmentNode extends AstNodeBinary implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitAssignmentNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitAssignmentNode(this, nodeList); - } - } - - /** - * AST node for an IF block. - * The left node is the condition. - * The right node is the IF body. - */ - public static class IfBlockNode extends AstNodeBinary implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitIfBlockNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitIfBlockNode(this, nodeList); - } - } - - /** - * AST node for the body of an IF block. - * The left node is the THEN branch. - * The right node is the ELSE branch. - */ - public static class IfBodyNode extends AstNodeBinary implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitIfBodyNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitIfBodyNode(this, nodeList); - } - } - - public static class LiteralNode extends AstNode implements Visitable { - public String literal; - public LiteralNode(String literal) { - super(); - this.literal = literal; - } - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitLiteralNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitLiteralNode(this, nodeList); - } - } - - public static class LogicalNotNode extends AstNodeUnary implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitLogicalNotNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitLogicalNotNode(this, nodeList); - } - } - - public static class LogicalAndNode extends AstNodeBinary implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitLogicalAndNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitLogicalAndNode(this, nodeList); - } - } - - public static class LogicalOrNode extends AstNodeBinary implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitLogicalOrNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitLogicalOrNode(this, nodeList); - } - } - - /** - * An Ast node that indicates the code - * represented by this node is unanalyzable. - */ - public static class OpaqueNode extends AstNode implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitOpaqueNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitOpaqueNode(this, nodeList); - } - } - - public static class StatementSequenceNode extends AstNodeDynamic implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitStatementSequenceNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitStatementSequenceNode(this, nodeList); - } - } - - public static class VariableNode extends AstNode implements Visitable { - public enum Type { - UNKNOWN, INT, BOOLEAN - } - public Type type; - public String name; - public VariableNode(String name) { - super(); - this.type = Type.UNKNOWN; - this.name = name; - } - public VariableNode(Type type, String name) { - super(); - this.type = type; - this.name = name; - } - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitVariableNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitVariableNode(this, nodeList); - } - } - - /** Arithmetic operations */ - - public static class AdditionNode extends AstNodeBinary implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitAdditionNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitAdditionNode(this, nodeList); - } - } - - public static class SubtractionNode extends AstNodeBinary implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitSubtractionNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitSubtractionNode(this, nodeList); - } - } - - public static class MultiplicationNode extends AstNodeBinary implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitMultiplicationNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitMultiplicationNode(this, nodeList); - } - } - - public static class DivisionNode extends AstNodeBinary implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitDivisionNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitDivisionNode(this, nodeList); - } - } - - /** Comparison operators */ - - public static class EqualNode extends AstNodeBinary implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitEqualNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitEqualNode(this, nodeList); - } - } - - public static class NotEqualNode extends AstNodeBinary implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitNotEqualNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitNotEqualNode(this, nodeList); - } - } - - public static class NegativeNode extends AstNodeUnary implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitNegativeNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitNegativeNode(this, nodeList); - } - } - - public static class LessThanNode extends AstNodeBinary implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitLessThanNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitLessThanNode(this, nodeList); - } - } - - public static class LessEqualNode extends AstNodeBinary implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitLessEqualNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitLessEqualNode(this, nodeList); - } - } - - public static class GreaterThanNode extends AstNodeBinary implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitGreaterThanNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitGreaterThanNode(this, nodeList); - } - } - - public static class GreaterEqualNode extends AstNodeBinary implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitGreaterEqualNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitGreaterEqualNode(this, nodeList); - } - } - - /** LF built-in operations */ - /** - * AST node for an lf_set call. The left child is the port being set. - * The right node is the value of the port. - */ - public static class SetPortNode extends AstNodeBinary implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitSetPortNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitSetPortNode(this, nodeList); - } - } - - /** - * AST node for a `lf_schedule(action, additional_delay)` call. - */ - public static class ScheduleActionNode extends AstNodeDynamic implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitScheduleActionNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitScheduleActionNode(this, nodeList); - } - } - - /** - * AST node for a `lf_schedule_int(action, additional_delay, integer)` call. - */ - public static class ScheduleActionIntNode extends AstNodeDynamic implements Visitable { - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitScheduleActionIntNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitScheduleActionIntNode(this, nodeList); - } - } - - /** - * Handle state variables appearing as self-> - */ - public static class StateVarNode extends AstNode implements Visitable { - public String name; - public boolean prev = false; // By default, this is not a previous state. - public StateVarNode(String name) { - this.name = name; - } - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitStateVarNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitStateVarNode(this, nodeList); - } - } - - /** - * Handle trigger values appearing as ->value - */ - public static class TriggerValueNode extends AstNode implements Visitable { - public String name; - public TriggerValueNode(String name) { - this.name = name; - } - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitTriggerValueNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitTriggerValueNode(this, nodeList); - } - } - - /** - * Handle trigger presence appearing as ->is_present - */ - public static class TriggerIsPresentNode extends AstNode implements Visitable { - public String name; - public TriggerIsPresentNode(String name) { - this.name = name; - } - @Override public T accept(AstVisitor visitor) { - return ((CAstVisitor)visitor).visitTriggerIsPresentNode(this); - } - @Override public T accept(AstVisitor visitor, List nodeList) { - return ((CAstVisitor)visitor).visitTriggerIsPresentNode(this, nodeList); - } - } -} diff --git a/org.lflang/src/org/lflang/analyses/cast/CAstVisitor.java b/org.lflang/src/org/lflang/analyses/cast/CAstVisitor.java deleted file mode 100644 index 0f4f0763dd..0000000000 --- a/org.lflang/src/org/lflang/analyses/cast/CAstVisitor.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.lflang.analyses.cast; - -import java.util.List; - -/** Modeled after CVisitor.java */ -public interface CAstVisitor extends AstVisitor { - - T visitAstNode(CAst.AstNode node); - T visitAstNodeUnary(CAst.AstNodeUnary node); - T visitAstNodeBinary(CAst.AstNodeBinary node); - T visitAstNodeDynamic(CAst.AstNodeDynamic node); - T visitAssignmentNode(CAst.AssignmentNode node); - T visitIfBlockNode(CAst.IfBlockNode node); - T visitIfBodyNode(CAst.IfBodyNode node); - T visitLiteralNode(CAst.LiteralNode node); - T visitLogicalNotNode(CAst.LogicalNotNode node); - T visitLogicalAndNode(CAst.LogicalAndNode node); - T visitLogicalOrNode(CAst.LogicalOrNode node); - T visitOpaqueNode(CAst.OpaqueNode node); - T visitStatementSequenceNode(CAst.StatementSequenceNode node); - T visitVariableNode(CAst.VariableNode node); - - T visitAdditionNode(CAst.AdditionNode node); - T visitSubtractionNode(CAst.SubtractionNode node); - T visitMultiplicationNode(CAst.MultiplicationNode node); - T visitDivisionNode(CAst.DivisionNode node); - - T visitEqualNode(CAst.EqualNode node); - T visitNotEqualNode(CAst.NotEqualNode node); - T visitNegativeNode(CAst.NegativeNode node); - T visitLessThanNode(CAst.LessThanNode node); - T visitLessEqualNode(CAst.LessEqualNode node); - T visitGreaterThanNode(CAst.GreaterThanNode node); - T visitGreaterEqualNode(CAst.GreaterEqualNode node); - - T visitSetPortNode(CAst.SetPortNode node); - T visitScheduleActionNode(CAst.ScheduleActionNode node); - T visitScheduleActionIntNode(CAst.ScheduleActionIntNode node); - T visitStateVarNode(CAst.StateVarNode node); - T visitTriggerValueNode(CAst.TriggerValueNode node); - T visitTriggerIsPresentNode(CAst.TriggerIsPresentNode node); - - /** Used for converting an AST into If Normal Form. */ - T visitAstNode(CAst.AstNode node, List nodeList); - T visitAstNodeUnary(CAst.AstNodeUnary node, List nodeList); - T visitAstNodeBinary(CAst.AstNodeBinary node, List nodeList); - T visitAstNodeDynamic(CAst.AstNodeDynamic node, List nodeList); - T visitAssignmentNode(CAst.AssignmentNode node, List nodeList); - T visitIfBlockNode(CAst.IfBlockNode node, List nodeList); - T visitIfBodyNode(CAst.IfBodyNode node, List nodeList); - T visitLiteralNode(CAst.LiteralNode node, List nodeList); - T visitLogicalNotNode(CAst.LogicalNotNode node, List nodeList); - T visitLogicalAndNode(CAst.LogicalAndNode node, List nodeList); - T visitLogicalOrNode(CAst.LogicalOrNode node, List nodeList); - T visitOpaqueNode(CAst.OpaqueNode node, List nodeList); - T visitStatementSequenceNode(CAst.StatementSequenceNode node, List nodeList); - T visitVariableNode(CAst.VariableNode node, List nodeList); - - T visitAdditionNode(CAst.AdditionNode node, List nodeList); - T visitSubtractionNode(CAst.SubtractionNode node, List nodeList); - T visitMultiplicationNode(CAst.MultiplicationNode node, List nodeList); - T visitDivisionNode(CAst.DivisionNode node, List nodeList); - - T visitEqualNode(CAst.EqualNode node, List nodeList); - T visitNotEqualNode(CAst.NotEqualNode node, List nodeList); - T visitNegativeNode(CAst.NegativeNode node, List nodeList); - T visitLessThanNode(CAst.LessThanNode node, List nodeList); - T visitLessEqualNode(CAst.LessEqualNode node, List nodeList); - T visitGreaterThanNode(CAst.GreaterThanNode node, List nodeList); - T visitGreaterEqualNode(CAst.GreaterEqualNode node, List nodeList); - - T visitSetPortNode(CAst.SetPortNode node, List nodeList); - T visitScheduleActionNode(CAst.ScheduleActionNode node, List nodeList); - T visitScheduleActionIntNode(CAst.ScheduleActionIntNode node, List nodeList); - T visitStateVarNode(CAst.StateVarNode node, List nodeList); - T visitTriggerValueNode(CAst.TriggerValueNode node, List nodeList); - T visitTriggerIsPresentNode(CAst.TriggerIsPresentNode node, List nodeList); -} diff --git a/org.lflang/src/org/lflang/analyses/cast/CBaseAstVisitor.java b/org.lflang/src/org/lflang/analyses/cast/CBaseAstVisitor.java deleted file mode 100644 index d30d4f75e2..0000000000 --- a/org.lflang/src/org/lflang/analyses/cast/CBaseAstVisitor.java +++ /dev/null @@ -1,360 +0,0 @@ -package org.lflang.analyses.cast; - -import java.util.List; - -/** - * A base class that provides default implementations - * of the visit functions. Other C AST visitors extend - * this class. - */ -public class CBaseAstVisitor extends AbstractAstVisitor implements CAstVisitor { - - /** - * These default implementations are not meant to be used. - * They should be overriden by the child class. - * In theory, this base visitor can be deleted? - * Let's keep it here for now for consistency. - */ - @Override - public T visitAstNode(CAst.AstNode node) { - return null; - } - - @Override - public T visitAstNodeUnary(CAst.AstNodeUnary node) { - if (node.child != null) { - T result = visit(node.child); - } else { - System.out.println("*** Child is empty in " + node + "!"); - } - return null; - } - - @Override - public T visitAstNodeBinary(CAst.AstNodeBinary node) { - if (node.left != null) { - T leftResult = visit(node.left); - } else { - System.out.println("*** Left child is empty in " + node + "!"); - } - if (node.right != null) { - T rightResult = visit(node.right); - } else { - System.out.println("*** Right child is empty in " + node + "!"); - } - // Aggregate results... - return null; - } - - @Override - public T visitAstNodeDynamic(CAst.AstNodeDynamic node) { - for (CAst.AstNode n : node.children) { - T result = visit(n); - } - return null; - } - - @Override - public T visitAssignmentNode(CAst.AssignmentNode node) { - return visitAstNodeBinary(node); - } - - @Override - public T visitIfBlockNode(CAst.IfBlockNode node) { - return visitAstNodeBinary(node); - } - - @Override - public T visitIfBodyNode(CAst.IfBodyNode node) { - return visitAstNodeBinary(node); - } - - @Override - public T visitLiteralNode(CAst.LiteralNode node) { - return null; - } - - @Override - public T visitLogicalNotNode(CAst.LogicalNotNode node) { - return visitAstNodeUnary(node); - } - - @Override - public T visitLogicalAndNode(CAst.LogicalAndNode node) { - return visitAstNodeBinary(node); - } - - @Override - public T visitLogicalOrNode(CAst.LogicalOrNode node) { - return visitAstNodeBinary(node); - } - - @Override - public T visitOpaqueNode(CAst.OpaqueNode node) { - return visitAstNode(node); - } - - @Override - public T visitStatementSequenceNode(CAst.StatementSequenceNode node) { - return visitAstNodeDynamic(node); - } - - @Override - public T visitVariableNode(CAst.VariableNode node) { - return null; - } - - /** Arithmetic operators */ - - @Override - public T visitAdditionNode(CAst.AdditionNode node) { - return visitAstNodeBinary(node); - } - - @Override - public T visitSubtractionNode(CAst.SubtractionNode node) { - return visitAstNodeBinary(node); - } - - @Override - public T visitMultiplicationNode(CAst.MultiplicationNode node) { - return visitAstNodeBinary(node); - } - - @Override - public T visitDivisionNode(CAst.DivisionNode node) { - return visitAstNodeBinary(node); - } - - /** Comparison operators */ - - @Override - public T visitEqualNode(CAst.EqualNode node) { - return visitAstNodeBinary(node); - } - - @Override - public T visitNotEqualNode(CAst.NotEqualNode node) { - return visitAstNodeBinary(node); - } - - @Override - public T visitNegativeNode(CAst.NegativeNode node) { - return visitNegativeNode(node); - } - - @Override - public T visitLessThanNode(CAst.LessThanNode node) { - return visitAstNodeBinary(node); - } - - @Override - public T visitLessEqualNode(CAst.LessEqualNode node) { - return visitAstNodeBinary(node); - } - - @Override - public T visitGreaterThanNode(CAst.GreaterThanNode node) { - return visitAstNodeBinary(node); - } - - @Override - public T visitGreaterEqualNode(CAst.GreaterEqualNode node) { - return visitAstNodeBinary(node); - } - - /** LF built-in operations */ - - @Override - public T visitSetPortNode(CAst.SetPortNode node) { - return visitAstNodeBinary(node); - } - - @Override - public T visitScheduleActionNode(CAst.ScheduleActionNode node) { - return visitAstNodeDynamic(node); - } - - @Override - public T visitScheduleActionIntNode(CAst.ScheduleActionIntNode node) { - return visitAstNodeDynamic(node); - } - - @Override - public T visitStateVarNode(CAst.StateVarNode node) { - return null; - } - - @Override - public T visitTriggerValueNode(CAst.TriggerValueNode node) { - return null; - } - - @Override - public T visitTriggerIsPresentNode(CAst.TriggerIsPresentNode node) { - return null; - } - - //// With one more parameter. - @Override - public T visitAstNode(CAst.AstNode node, List nodeList) { - return visitAstNode(node); - } - - @Override - public T visitAstNodeUnary(CAst.AstNodeUnary node, List nodeList) { - return visitAstNodeUnary(node); - } - - @Override - public T visitAstNodeBinary(CAst.AstNodeBinary node, List nodeList) { - return visitAstNodeBinary(node); - } - - @Override - public T visitAstNodeDynamic(CAst.AstNodeDynamic node, List nodeList) { - return visitAstNodeDynamic(node); - } - - @Override - public T visitAssignmentNode(CAst.AssignmentNode node, List nodeList) { - return visitAssignmentNode(node); - } - - @Override - public T visitIfBlockNode(CAst.IfBlockNode node, List nodeList) { - return visitIfBlockNode(node); - } - - @Override - public T visitIfBodyNode(CAst.IfBodyNode node, List nodeList) { - return visitIfBodyNode(node); - } - - @Override - public T visitLiteralNode(CAst.LiteralNode node, List nodeList) { - return visitLiteralNode(node); - } - - @Override - public T visitLogicalNotNode(CAst.LogicalNotNode node, List nodeList) { - return visitLogicalNotNode(node); - } - - @Override - public T visitLogicalAndNode(CAst.LogicalAndNode node, List nodeList) { - return visitLogicalAndNode(node); - } - - @Override - public T visitLogicalOrNode(CAst.LogicalOrNode node, List nodeList) { - return visitLogicalOrNode(node); - } - - @Override - public T visitOpaqueNode(CAst.OpaqueNode node, List nodeList) { - return visitOpaqueNode(node); - } - - @Override - public T visitStatementSequenceNode(CAst.StatementSequenceNode node, List nodeList) { - return visitStatementSequenceNode(node); - } - - @Override - public T visitVariableNode(CAst.VariableNode node, List nodeList) { - return visitVariableNode(node); - } - - /** Arithmetic operators */ - - @Override - public T visitAdditionNode(CAst.AdditionNode node, List nodeList) { - return visitAdditionNode(node); - } - - @Override - public T visitSubtractionNode(CAst.SubtractionNode node, List nodeList) { - return visitSubtractionNode(node); - } - - @Override - public T visitMultiplicationNode(CAst.MultiplicationNode node, List nodeList) { - return visitMultiplicationNode(node); - } - - @Override - public T visitDivisionNode(CAst.DivisionNode node, List nodeList) { - return visitDivisionNode(node); - } - - /** Comparison operators */ - - @Override - public T visitEqualNode(CAst.EqualNode node, List nodeList) { - return visitEqualNode(node); - } - - @Override - public T visitNotEqualNode(CAst.NotEqualNode node, List nodeList) { - return visitNotEqualNode(node); - } - - @Override - public T visitNegativeNode(CAst.NegativeNode node, List nodeList) { - return visitNegativeNode(node); - } - - @Override - public T visitLessThanNode(CAst.LessThanNode node, List nodeList) { - return visitLessThanNode(node); - } - - @Override - public T visitLessEqualNode(CAst.LessEqualNode node, List nodeList) { - return visitLessEqualNode(node); - } - - @Override - public T visitGreaterThanNode(CAst.GreaterThanNode node, List nodeList) { - return visitGreaterThanNode(node); - } - - @Override - public T visitGreaterEqualNode(CAst.GreaterEqualNode node, List nodeList) { - return visitGreaterEqualNode(node); - } - - /** LF built-in operations */ - - @Override - public T visitSetPortNode(CAst.SetPortNode node, List nodeList) { - return visitSetPortNode(node); - } - - @Override - public T visitScheduleActionNode(CAst.ScheduleActionNode node, List nodeList) { - return visitScheduleActionNode(node); - } - - @Override - public T visitScheduleActionIntNode(CAst.ScheduleActionIntNode node, List nodeList) { - return visitScheduleActionIntNode(node); - } - - @Override - public T visitStateVarNode(CAst.StateVarNode node, List nodeList) { - return visitStateVarNode(node); - } - - @Override - public T visitTriggerValueNode(CAst.TriggerValueNode node, List nodeList) { - return visitTriggerValueNode(node); - } - - @Override - public T visitTriggerIsPresentNode(CAst.TriggerIsPresentNode node, List nodeList) { - return visitTriggerIsPresentNode(node); - } - -} diff --git a/org.lflang/src/org/lflang/analyses/cast/CToUclidVisitor.java b/org.lflang/src/org/lflang/analyses/cast/CToUclidVisitor.java deleted file mode 100644 index c794186cbe..0000000000 --- a/org.lflang/src/org/lflang/analyses/cast/CToUclidVisitor.java +++ /dev/null @@ -1,291 +0,0 @@ -package org.lflang.analyses.cast; - -import java.util.ArrayList; -import java.util.List; - -import org.lflang.generator.ActionInstance; -import org.lflang.generator.NamedInstance; -import org.lflang.generator.PortInstance; -import org.lflang.generator.ReactionInstance; -import org.lflang.generator.ReactorInstance; -import org.lflang.generator.StateVariableInstance; -import org.lflang.generator.TriggerInstance; -import org.lflang.analyses.uclid.UclidGenerator; -import org.lflang.analyses.cast.CAst.*; - -public class CToUclidVisitor extends CBaseAstVisitor { - - // The Uclid generator instance - protected UclidGenerator generator; - - // The reaction instance for the generated axiom - protected ReactionInstance.Runtime reaction; - - // The reactor that contains the reaction - protected ReactorInstance reactor; - - // A list of all the named instances - protected List instances = new ArrayList(); - - // Quantified variable - protected String qv = "i"; - protected String qv2 = "j"; - - // Unchanged variables and triggers - protected List unchangedStates; - protected List unchangedTriggers; - - // FIXME: Make this more flexible and infer value from program. - // Default reset value - String defaultValue = "0"; - String defaultPresence = "false"; - - public CToUclidVisitor(UclidGenerator generator, ReactionInstance.Runtime reaction) { - this.generator = generator; - this.reaction = reaction; - this.reactor = reaction.getReaction().getParent(); - instances.addAll(this.reactor.inputs); - instances.addAll(this.reactor.outputs); - instances.addAll(this.reactor.actions); - instances.addAll(this.reactor.states); - } - - @Override - public String visitAdditionNode(AdditionNode node) { - String lhs = visit(node.left); - String rhs = visit(node.right); - return "(" + lhs + " + " + rhs + ")"; - } - - @Override - public String visitAssignmentNode(AssignmentNode node) { - String lhs = visit(node.left); - String rhs = visit(node.right); - return "(" + lhs + " == " + rhs + ")"; - } - - @Override - public String visitDivisionNode(DivisionNode node) { - String lhs = visit(node.left); - String rhs = visit(node.right); - return "(" + lhs + " / " + rhs + ")"; - } - - @Override - public String visitEqualNode(EqualNode node) { - String lhs = visit(node.left); - String rhs = visit(node.right); - return "(" + lhs + " == " + rhs + ")"; - } - - @Override - public String visitGreaterEqualNode(GreaterEqualNode node) { - String lhs = visit(node.left); - String rhs = visit(node.right); - return "(" + lhs + " >= " + rhs + ")"; - } - - @Override - public String visitGreaterThanNode(GreaterThanNode node) { - String lhs = visit(node.left); - String rhs = visit(node.right); - return "(" + lhs + " > " + rhs + ")"; - } - - @Override - public String visitIfBlockNode(IfBlockNode node) { - String antecedent = visit(node.left); // Process if condition - String consequent = visit(((IfBodyNode)node.right).left); - return "(" + antecedent + " ==> " + "(" + consequent + "\n))"; - } - - @Override - public String visitLessEqualNode(LessEqualNode node) { - String lhs = visit(node.left); - String rhs = visit(node.right); - return "(" + lhs + " <= " + rhs + ")"; - } - - @Override - public String visitLessThanNode(LessThanNode node) { - String lhs = visit(node.left); - String rhs = visit(node.right); - return "(" + lhs + " < " + rhs + ")"; - } - - @Override - public String visitLiteralNode(LiteralNode node) { - return node.literal; - } - - @Override - public String visitLogicalAndNode(LogicalAndNode node) { - String lhs = visit(node.left); - String rhs = visit(node.right); - return "(" + lhs + " && " + rhs + ")"; - } - - @Override - public String visitLogicalNotNode(LogicalNotNode node) { - return "!" + "(" + visit(node.child) + ")"; - } - - @Override - public String visitLogicalOrNode(LogicalOrNode node) { - String lhs = visit(node.left); - String rhs = visit(node.right); - return "(" + lhs + " || " + rhs + ")"; - } - - @Override - public String visitMultiplicationNode(MultiplicationNode node) { - String lhs = visit(node.left); - String rhs = visit(node.right); - return "(" + lhs + " * " + rhs + ")"; - } - - @Override - public String visitNotEqualNode(NotEqualNode node) { - String lhs = visit(node.left); - String rhs = visit(node.right); - return "(" + lhs + " != " + rhs + ")"; - } - - @Override - public String visitNegativeNode(NegativeNode node) { - return "(" + "-1*(" + visit(node.child) + "))"; - } - - @Override - public String visitScheduleActionNode(ScheduleActionNode node) { - String name = ((VariableNode)node.children.get(0)).name; - NamedInstance instance = getInstanceByName(name); - ActionInstance action = (ActionInstance)instance; - String additionalDelay = visit(node.children.get(1)); - String str = "\n(" - + "(finite_exists (" + this.qv2 + " : integer) in indices :: (" + this.qv2 + " > " + this.qv + " && " + this.qv2 + " <= END_TRACE) && (" - + "\n " + action.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv2 + ")" + ")" - + "\n " + "&& " + "tag_same" + "(" + "g(" + this.qv2 + ")" + ", " + "tag_schedule" + "(" + "g" + "(" + this.qv + ")" + ", " + "(" + action.getMinDelay().toNanoSeconds() + "+" + additionalDelay + ")" + ")" + ")" - + "\n " + "&& " + action.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv2 + ")" + ")" + " == " + "0" - + "\n)) // Closes finite_exists" - + "\n&& " + action.getFullNameWithJoiner("_") + "_scheduled" + "(" + "d" + "(" + this.qv + ")" + ")" - + "\n)"; - return str; - } - - @Override - public String visitScheduleActionIntNode(ScheduleActionIntNode node) { - String name = ((VariableNode)node.children.get(0)).name; - NamedInstance instance = getInstanceByName(name); - ActionInstance action = (ActionInstance)instance; - String additionalDelay = visit(node.children.get(1)); - String intValue = visit(node.children.get(2)); - String str = "\n(" - + "(finite_exists (" + this.qv2 + " : integer) in indices :: (" + this.qv2 + " > " + this.qv + " && " + this.qv2 + " <= END_TRACE) && (" - + "\n " + action.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv2 + ")" + ")" - + "\n " + "&& " + "tag_same" + "(" + "g(" + this.qv2 + ")" + ", " + "tag_schedule" + "(" + "g" + "(" + this.qv + ")" + ", " + "(" + action.getMinDelay().toNanoSeconds() + "+" + additionalDelay + ")" + ")" + ")" - + "\n)) // Closes finite_exists" - + "\n&& " + action.getFullNameWithJoiner("_") + "_scheduled" + "(" + "d" + "(" + this.qv + ")" + ")" - + "\n&& " + action.getFullNameWithJoiner("_") + "_scheduled_payload" + "(" + "pl" + "(" + this.qv + ")" + ")" + " == " + intValue - + "\n)"; - return str; - } - - @Override - public String visitSetPortNode(SetPortNode node) { - NamedInstance port = getInstanceByName(((VariableNode)node.left).name); - String value = visit(node.right); - // Remove this port from the unchanged list. - // this.unchangedTriggers.remove(port); - return "(" - + "(" - + port.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")" - + " == " + value - + ")" - + " && " - + "(" + port.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv + ")" + ")" + ")" - + ")"; - } - - @Override - public String visitStateVarNode(StateVarNode node) { - NamedInstance instance = getInstanceByName(node.name); - if (instance != null) { - return instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + (node.prev ? "-1" : "") + ")" + ")"; - } - // FIXME: Throw exception - return ""; - } - - @Override - public String visitStatementSequenceNode(StatementSequenceNode node) { - String axiom = ""; - for (int i = 0; i < node.children.size(); i++) { - axiom += visit(node.children.get(i)); - if (i != node.children.size() - 1) axiom += "\n" + " " + "&& "; - } - return axiom; - } - - @Override - public String visitSubtractionNode(SubtractionNode node) { - String lhs = visit(node.left); - String rhs = visit(node.right); - return "(" + lhs + " - " + rhs + ")"; - } - - @Override - public String visitTriggerIsPresentNode(TriggerIsPresentNode node) { - // Find the trigger instance by name. - NamedInstance instance = getInstanceByName(node.name); - if (instance != null) { - return instance.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + this.qv + ")" + ")"; - } - // FIXME: Throw exception - return ""; - } - - @Override - public String visitTriggerValueNode(TriggerValueNode node) { - // Find the trigger instance by name. - NamedInstance instance = getInstanceByName(node.name); - if (instance != null) { - return instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; - } - // FIXME: Throw exception - return ""; - } - - @Override - public String visitVariableNode(VariableNode node) { - NamedInstance instance = getInstanceByName(node.name); - if (instance != null) { - return instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; - } - // FIXME: Throw exception - return ""; - } - - ///////////////////////////// - //// Private functions - - private NamedInstance getInstanceByName(String name) { - for (NamedInstance i : this.instances) { - if (i instanceof ActionInstance) { - if (((ActionInstance)i).getDefinition().getName().equals(name)) { - return i; - } - } else if (i instanceof PortInstance) { - if (((PortInstance)i).getDefinition().getName().equals(name)) { - return i; - } - } else if (i instanceof StateVariableInstance) { - if (((StateVariableInstance)i).getDefinition().getName().equals(name)) { - return i; - } - } - } - System.out.println("Named instance" + "not found."); - return null; - } -} diff --git a/org.lflang/src/org/lflang/analyses/cast/IfNormalFormAstVisitor.java b/org.lflang/src/org/lflang/analyses/cast/IfNormalFormAstVisitor.java deleted file mode 100644 index 31b2f4c5a7..0000000000 --- a/org.lflang/src/org/lflang/analyses/cast/IfNormalFormAstVisitor.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.lflang.analyses.cast; - -import java.util.ArrayList; -import java.util.List; - -/** - * An AST visitor that converts an original AST into the If Normal Form. - * See "Bounded Model Checking of Software using SMT Solvers instead of - * SAT Solvers" for details about the If Normal Form - * (https://link.springer.com/chapter/10.1007/11691617_9). - * - * There are several requirements for an AST to be in INF: - * 1. There should be a single StatementSequence (SS) root node; - * 2. Each child of the SS node should be an IfBlockNode; - * 3. Variables in a subsequent child should be marked as "primed" - * versions of those in the previous child. - * - * Limitations: - * 1. The implementation does not take the scope into account. - * E.g. "int i = 0; { int j = 0; }" is treated the same as - * "int i = 0; int j = 0;". - * 2. Due to the above limitation, the implementation assumes - * that each variable has a unique name. - * E.g. "{ int i = 0; }{ int i = 0; }" is an ill-formed program. - * - * In this program, visit() is the normalise() in the paper. - */ -public class IfNormalFormAstVisitor extends CBaseAstVisitor { - - public CAst.StatementSequenceNode INF = new CAst.StatementSequenceNode(); - - @Override - public Void visitStatementSequenceNode(CAst.StatementSequenceNode node, List conditions) { - // Create a new StatementSequenceNode. - for (CAst.AstNode child : node.children) { - visit(child, conditions); - } - return null; - } - - @Override - public Void visitAssignmentNode(CAst.AssignmentNode node, List conditions) { - this.INF.children.add(generateIfBlock(node, conditions)); - return null; - } - - @Override - public Void visitSetPortNode(CAst.SetPortNode node, List conditions) { - this.INF.children.add(generateIfBlock(node, conditions)); - return null; - } - - @Override - public Void visitScheduleActionNode(CAst.ScheduleActionNode node, List conditions) { - this.INF.children.add(generateIfBlock(node, conditions)); - return null; - } - - @Override - public Void visitScheduleActionIntNode(CAst.ScheduleActionIntNode node, List conditions) { - this.INF.children.add(generateIfBlock(node, conditions)); - return null; - } - - @Override - public Void visitIfBlockNode(CAst.IfBlockNode node, List conditions) { - List leftConditions = new ArrayList<>(conditions); - leftConditions.add(node.left); - visit(((CAst.IfBodyNode)node.right).left, leftConditions); - if (((CAst.IfBodyNode)node.right).right != null) { - List rightConditions = new ArrayList<>(conditions); - // Add a not node. - CAst.LogicalNotNode notNode = new CAst.LogicalNotNode(); - notNode.child = node.left; // Negate the condition. - rightConditions.add(notNode); - visit(((CAst.IfBodyNode)node.right).right, rightConditions); - } - return null; - } - - private CAst.IfBlockNode generateIfBlock(CAst.AstNode node, List conditions) { - // Create an If Block node. - CAst.IfBlockNode ifNode = new CAst.IfBlockNode(); - // Set the condition of the if block node. - CAst.AstNode conjunction = AstUtils.takeConjunction(conditions); - ifNode.left = conjunction; - // Create a new body node. - CAst.IfBodyNode body = new CAst.IfBodyNode(); - ifNode.right = body; - // Set the then branch to the assignment. - body.left = node; - - return ifNode; - } -} diff --git a/org.lflang/src/org/lflang/analyses/cast/VariablePrecedenceVisitor.java b/org.lflang/src/org/lflang/analyses/cast/VariablePrecedenceVisitor.java deleted file mode 100644 index bada1be519..0000000000 --- a/org.lflang/src/org/lflang/analyses/cast/VariablePrecedenceVisitor.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.lflang.analyses.cast; - -import java.util.ArrayList; -import java.util.List; - -import org.lflang.analyses.cast.CAst.*; - -/** - * This visitor marks certain variable node as "previous." - */ -public class VariablePrecedenceVisitor extends CBaseAstVisitor { - - // This is a temporary solution and cannot handle, - // e.g., self->s = (self->s + 1) - (2 * 2). - @Override - public Void visitAssignmentNode(AssignmentNode node) { - // System.out.println("******* In assignment!!!"); - if (node.left instanceof StateVarNode) { - if (node.right instanceof AstNodeBinary) { - AstNodeBinary n = (AstNodeBinary)node.right; - if (n.left instanceof StateVarNode) { - ((StateVarNode)n.left).prev = true; - } - if (n.right instanceof StateVarNode) { - ((StateVarNode)n.right).prev = true; - } - } - } else { - System.out.println("Unreachable!"); // FIXME: Throw exception. - } - return null; - } - - @Override - public Void visitIfBlockNode(IfBlockNode node) { - if (((IfBodyNode)node.right).left != null) - visit(((IfBodyNode)node.right).left); - if (((IfBodyNode)node.right).right != null) - visit(((IfBodyNode)node.right).right); - return null; - } - - @Override - public Void visitStatementSequenceNode(StatementSequenceNode node) { - for (int i = 0; i < node.children.size(); i++) { - visit(node.children.get(i)); - } - return null; - } -} diff --git a/org.lflang/src/org/lflang/analyses/cast/Visitable.java b/org.lflang/src/org/lflang/analyses/cast/Visitable.java deleted file mode 100644 index 835894f06b..0000000000 --- a/org.lflang/src/org/lflang/analyses/cast/Visitable.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.lflang.analyses.cast; - -import java.util.List; - -public interface Visitable { - - /** The {@link AstVisitor} needs a double dispatch method. */ - T accept(AstVisitor visitor); - - /** The {@link AstVisitor} needs a double dispatch method. */ - T accept(AstVisitor visitor, List nodeList); -} diff --git a/org.lflang/src/org/lflang/analyses/statespace/Event.java b/org.lflang/src/org/lflang/analyses/statespace/Event.java deleted file mode 100644 index dc8613af92..0000000000 --- a/org.lflang/src/org/lflang/analyses/statespace/Event.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * A node in the state space diagram representing a step - * in the execution of an LF program. - * - * - */ -package org.lflang.analyses.statespace; - -import org.lflang.generator.TriggerInstance; - -public class Event implements Comparable { - - public TriggerInstance trigger; - public Tag tag; - - public Event(TriggerInstance trigger, Tag tag) { - this.trigger = trigger; - this.tag = tag; - } - - public TriggerInstance getTrigger() { - return this.trigger; - } - - @Override - public int compareTo(Event e) { - // Compare tags first. - int ret = this.tag.compareTo(e.tag); - // If tags match, compare trigger names. - if (ret == 0) - ret = this.trigger.getFullName() - .compareTo(e.trigger.getFullName()); - return ret; - } - - /** - * This equals() method does NOT compare tags, - * only compares triggers. - */ - @Override - public boolean equals(Object o) { - if (o == null) return false; - if (o instanceof Event) { - Event e = (Event) o; - if (this.trigger.equals(e.trigger)) - return true; - } - return false; - } - - @Override - public String toString() { - return "(" + trigger.getFullName() + ", " + tag + ")"; - } -} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/analyses/statespace/EventQueue.java b/org.lflang/src/org/lflang/analyses/statespace/EventQueue.java deleted file mode 100644 index 4c809d091a..0000000000 --- a/org.lflang/src/org/lflang/analyses/statespace/EventQueue.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * An event queue implementation that - * sorts events by time tag order - * - * - */ -package org.lflang.analyses.statespace; - -import java.util.PriorityQueue; - -public class EventQueue extends PriorityQueue { - - /** - * Modify the original add() by enforcing uniqueness. - * There cannot be duplicate events in the event queue. - */ - @Override - public boolean add(Event e) { - if (this.contains(e)) - return false; - super.add(e); - return true; - } -} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateInfo.java b/org.lflang/src/org/lflang/analyses/statespace/StateInfo.java deleted file mode 100644 index 7930336d7e..0000000000 --- a/org.lflang/src/org/lflang/analyses/statespace/StateInfo.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * A class that represents information in a step - * in a counterexample trace - * - * - */ -package org.lflang.analyses.statespace; - -import java.util.ArrayList; -import java.util.HashMap; - -public class StateInfo { - - public ArrayList reactions = new ArrayList<>(); - public Tag tag; - public HashMap variables = new HashMap<>(); - public HashMap triggers = new HashMap<>(); - public HashMap scheduled = new HashMap<>(); - public HashMap payloads = new HashMap<>(); - - public void display() { - System.out.println("reactions: " + reactions); - System.out.println("tag: " + tag); - System.out.println("variables: " + variables); - System.out.println("triggers: " + triggers); - System.out.println("scheduled: " + scheduled); - System.out.println("payloads: " + payloads); - } -} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java deleted file mode 100644 index 4615dececd..0000000000 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceDiagram.java +++ /dev/null @@ -1,192 +0,0 @@ -/** - * A directed graph representing the state space of an LF program. - * - * - */ -package org.lflang.analyses.statespace; - -import java.util.List; -import java.util.Set; -import java.util.stream.Collector; -import java.util.stream.Collectors; - -import org.lflang.TimeValue; -import org.lflang.generator.CodeBuilder; -import org.lflang.generator.ReactionInstance; -import org.lflang.graph.DirectedGraph; - -// FIXME: Use a linkedlist instead. -public class StateSpaceDiagram extends DirectedGraph { - - /** - * The first node of the state space diagram. - */ - public StateSpaceNode head; - - /** - * The last node of the state space diagram. - */ - public StateSpaceNode tail; - - /** - * The previously encountered node which the tail node - * goes back to, i.e. the location where the back loop happens. - */ - public StateSpaceNode loopNode; - - /** - * Store the state when the loop node is reached for - * the 2nd time. This is used to calculate the elapsed - * logical time on the back loop edge. - */ - public StateSpaceNode loopNodeNext; - - /** - * The logical time elapsed for each loop iteration. - */ - public long loopPeriod; - - /** - * A dot file that represents the diagram - */ - private CodeBuilder dot; - - /** - * - */ - private final boolean compactDot = false; - - /** - * Before adding the node, assign it an index. - */ - @Override - public void addNode(StateSpaceNode node) { - node.index = this.nodeCount(); - super.addNode(node); - } - - /** - * Get the immediately downstream node. - */ - public StateSpaceNode getDownstreamNode(StateSpaceNode node) { - Set downstream = this.getDownstreamAdjacentNodes(node); - if (downstream == null || downstream.size() == 0) - return null; - return (StateSpaceNode)downstream.toArray()[0]; - } - - /** - * Pretty print the diagram. - */ - public void display() { - System.out.println("*************************************************"); - System.out.println("* Pretty printing worst-case state space diagram:"); - long timestamp; - StateSpaceNode node = this.head; - if (node == null) { - System.out.println("* EMPTY"); - System.out.println("*************************************************"); - return; - } - while(node != this.tail) { - System.out.print("* State " + node.index + ": "); - node.display(); - - // Store the tag of the prior step. - timestamp = node.tag.timestamp; - - // Assume a unique next state. - node = getDownstreamNode(node); - - // Compute time difference - if (node != null) { - TimeValue tsDiff = TimeValue.fromNanoSeconds(node.tag.timestamp - timestamp); - System.out.println("* => Advance time by " + tsDiff); - } - } - - // Print tail node - System.out.print("* (Tail) state " + node.index + ": "); - node.display(); - - if (this.loopNode != null) { - // Compute time difference - TimeValue tsDiff = TimeValue.fromNanoSeconds(loopNodeNext.tag.timestamp - tail.tag.timestamp); - System.out.println("* => Advance time by " + tsDiff); - - System.out.println("* Goes back to loop node: state " + this.loopNode.index); - System.out.print("* Loop node reached 2nd time: "); - this.loopNodeNext.display(); - } - System.out.println("*************************************************"); - } - - /** - * Generate a dot file from the state space diagram. - * - * @return a CodeBuilder with the generated code - */ - public CodeBuilder generateDot() { - if (dot == null) { - dot = new CodeBuilder(); - dot.pr("digraph G {"); - dot.indent(); - if (this.loopNode != null) { - dot.pr("layout=circo;"); - } - dot.pr("rankdir=LR;"); - if (this.compactDot) { - dot.pr("mindist=1.5;"); - dot.pr("overlap=false"); - dot.pr("node [shape=Mrecord fontsize=40]"); - dot.pr("edge [fontsize=40 penwidth=3 arrowsize=2]"); - } else { - dot.pr("node [shape=Mrecord]"); - } - // Generate a node for each state. - if (this.compactDot) { - for (StateSpaceNode n : this.nodes()) { - dot.pr("S" + n.index + " [" + "label = \" {" + "S" + n.index - + " | " + n.reactionsInvoked.size() + " | " + n.eventQ.size() + "}" - + " | " + n.tag - + "\"" + "]"); - } - } else { - for (StateSpaceNode n : this.nodes()) { - List reactions = n.reactionsInvoked.stream() - .map(ReactionInstance::getFullName).collect(Collectors.toList()); - String reactionsStr = String.join("\\n", reactions); - List events = n.eventQ.stream() - .map(Event::toString).collect(Collectors.toList()); - String eventsStr = String.join("\\n", events); - dot.pr("S" + n.index + " [" + "label = \"" + "S" + n.index - + " | " + n.tag - + " | " + "Reactions invoked:\\n" + reactionsStr - + " | " + "Pending events:\\n" + eventsStr - + "\"" + "]"); - } - } - - StateSpaceNode current = this.head; - StateSpaceNode next = getDownstreamNode(this.head); - while (current != null && next != null && current != this.tail) { - TimeValue tsDiff = TimeValue.fromNanoSeconds(next.tag.timestamp - current.tag.timestamp); - dot.pr("S" + current.index + " -> " + "S" + next.index + " [label = " + "\"" + "+" + tsDiff + "\"" + "]"); - current = next; - next = getDownstreamNode(next); - } - - if (loopNode != null) { - TimeValue tsDiff = TimeValue.fromNanoSeconds(loopNodeNext.tag.timestamp - tail.tag.timestamp); - TimeValue period = TimeValue.fromNanoSeconds(loopPeriod); - dot.pr("S" + current.index + " -> " + "S" + next.index - + " [label = " + "\"" + "+" + tsDiff + " -" + period + "\"" - + " weight = 0 " + "]"); - } - - dot.unindent(); - dot.pr("}"); - } - return this.dot; - } -} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java deleted file mode 100644 index df708228de..0000000000 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceExplorer.java +++ /dev/null @@ -1,376 +0,0 @@ -/** - * Explores the state space of an LF program. - */ -package org.lflang.analyses.statespace; - -import java.lang.Integer; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; - -import org.eclipse.xtext.xbase.lib.Exceptions; - -import org.lflang.TimeUnit; -import org.lflang.TimeValue; -import org.lflang.analyses.statespace.Event; -import org.lflang.analyses.statespace.Tag; -import org.lflang.generator.ActionInstance; -import org.lflang.generator.CodeBuilder; -import org.lflang.generator.PortInstance; -import org.lflang.generator.ReactionInstance; -import org.lflang.generator.ReactorInstance; -import org.lflang.generator.RuntimeRange; -import org.lflang.generator.SendRange; -import org.lflang.generator.TimerInstance; -import org.lflang.generator.TriggerInstance; -import org.lflang.lf.Expression; -import org.lflang.lf.Time; -import org.lflang.lf.Variable; - -public class StateSpaceExplorer { - - // Instantiate an empty state space diagram. - public StateSpaceDiagram diagram = new StateSpaceDiagram(); - - // Indicate whether a back loop is found in the state space. - // A back loop suggests periodic behavior. - public boolean loopFound = false; - - /** - * Instantiate a global event queue. - * We will use this event queue to symbolically simulate - * the logical timeline. This simulation is also valid - * for runtime implementations that are federated or relax - * global barrier synchronization, since an LF program - * defines a unique logical timeline (assuming all reactions - * behave _consistently_ throughout the execution). - */ - public EventQueue eventQ = new EventQueue(); - - /** - * The main reactor instance based on which the state space - * is explored. - */ - public ReactorInstance main; - - // Constructor - public StateSpaceExplorer(ReactorInstance main) { - this.main = main; - } - - /** - * Recursively add the first events to the event queue. - */ - public void addInitialEvents(ReactorInstance reactor) { - // Add the startup trigger, if exists. - var startup = reactor.getStartupTrigger(); - if (startup != null) - eventQ.add(new Event(startup, new Tag(0, 0, false))); - - // Add the initial timer firings, if exist. - for (TimerInstance timer : reactor.timers) { - eventQ.add( - new Event( - timer, - new Tag(timer.getOffset().toNanoSeconds(), 0, false) - ) - ); - } - - // Recursion - for (var child : reactor.children) { - addInitialEvents(child); - } - } - - /** - * Explore the state space and populate the state space diagram - * until the specified horizon (i.e. the end tag) is reached - * OR until the event queue is empty. - * - * As an optimization, if findLoop is true, the algorithm - * tries to find a loop in the state space during exploration. - * If a loop is found (i.e. a previously encountered state - * is reached again) during exploration, the function returns early. - * - * TODOs: - * 1. Handle action with 0 min delay. - * 2. Check if zero-delay connection works. - */ - public void explore(Tag horizon, boolean findLoop) { - // Traverse the main reactor instance recursively to find - // the known initial events (startup and timers' first firings). - // FIXME: It seems that we need to handle shutdown triggers - // separately, because they could break the back loop. - addInitialEvents(this.main); - - Tag previousTag = null; // Tag in the previous loop ITERATION - Tag currentTag = null; // Tag in the current loop ITERATION - StateSpaceNode currentNode = null; - StateSpaceNode previousNode = null; - HashMap uniqueNodes = new HashMap<>(); - boolean stop = true; - if (this.eventQ.size() > 0) { - stop = false; - currentTag = (eventQ.peek()).tag; - } - - // A list of reactions invoked at the current logical tag - Set reactionsInvoked; - // A temporary list of reactions processed in the current LOOP ITERATION - Set reactionsTemp; - - while (!stop) { - - // Pop the events from the earliest tag off the event queue. - ArrayList currentEvents = new ArrayList(); - // FIXME: Use stream methods here? - while ( - eventQ.size() > 0 - && eventQ.peek().tag.compareTo(currentTag) == 0 - ) { - Event e = eventQ.poll(); - // System.out.println("DEBUG: Popped an event: " + e); - currentEvents.add(e); - } - - // Collect all the reactions invoked in this current LOOP ITERATION - // triggered by the earliest events. - // Using a hash set here to make sure the reactions invoked - // are unique. Sometimes multiple events can trigger the same reaction, - // and we do not want to record duplicate reaction invocations. - reactionsTemp = new HashSet(); - for (Event e : currentEvents) { - Set dependentReactions - = e.trigger.getDependentReactions(); - reactionsTemp.addAll(dependentReactions); - // System.out.println("DEBUG: ReactionTemp: " + reactionsTemp); - - // If the event is a timer firing, enqueue the next firing. - if (e.trigger instanceof TimerInstance) { - TimerInstance timer = (TimerInstance) e.trigger; - eventQ.add(new Event( - timer, - new Tag( - e.tag.timestamp + timer.getPeriod().toNanoSeconds(), - 0, // A time advancement resets microstep to 0. - false - )) - ); - } - } - - // For each reaction invoked, compute the new events produced. - for (ReactionInstance reaction : reactionsTemp) { - // Iterate over all the effects produced by this reaction. - // If the effect is a port, obtain the downstream port along - // a connection and enqueue a future event for that port. - // If the effect is an action, enqueue a future event for - // this action. - for (TriggerInstance effect : reaction.effects) { - if (effect instanceof PortInstance) { - - for (SendRange senderRange - : ((PortInstance)effect).getDependentPorts()) { - - for (RuntimeRange destinationRange - : senderRange.destinations) { - PortInstance downstreamPort = destinationRange.instance; - - // Getting delay from connection - // FIXME: Is there a more concise way to do this? - long delay = 0; - Expression delayExpr = senderRange.connection.getDelay(); - if (delayExpr instanceof Time) { - long interval = ((Time) delayExpr).getInterval(); - String unit = ((Time) delayExpr).getUnit(); - TimeValue timeValue = new TimeValue(interval, TimeUnit.fromName(unit)); - delay = timeValue.toNanoSeconds(); - } - - // Create and enqueue a new event. - Event e = new Event( - downstreamPort, - new Tag(currentTag.timestamp + delay, 0, false) - ); - // System.out.println("DEBUG: Added a port event: " + e); - eventQ.add(e); - } - } - } - else if (effect instanceof ActionInstance) { - // Get the minimum delay of this action. - long min_delay = ((ActionInstance)effect).getMinDelay().toNanoSeconds(); - long microstep = 0; - if (min_delay == 0) { - microstep = currentTag.microstep + 1; - } - // Create and enqueue a new event. - Event e = new Event( - effect, - new Tag(currentTag.timestamp + min_delay, microstep, false) - ); - // System.out.println("DEBUG: Added an action event: " + e); - eventQ.add(e); - } - } - } - - // We are at the first iteration. - // Initialize currentNode. - if (previousTag == null) { - // System.out.println("DEBUG: Case 1"); - //// Now we are done with the node at the previous tag, - //// work on the new node at the current timestamp. - // Copy the reactions in reactionsTemp. - reactionsInvoked = new HashSet(reactionsTemp); - - // Create a new state in the SSD for the current tag, - // add the reactions triggered to the state, - // and add a snapshot of the event queue (with new events - // generated by reaction invocations in the curren tag) - // to the state. - StateSpaceNode node = new StateSpaceNode( - currentTag, // Current tag - reactionsInvoked, // Reactions invoked at this tag - new ArrayList(eventQ) // A snapshot of the event queue - ); - - // Initialize currentNode. - currentNode = node; - } - // When we advance to a new TIMESTAMP (not a new tag), - // create a new node in the state space diagram - // for everything processed in the previous timestamp. - // This makes sure that the granularity of nodes is - // at the timestamp-level, so that we don't have to - // worry about microsteps. - else if ( - previousTag != null - && currentTag.timestamp > previousTag.timestamp - ) { - // System.out.println("DEBUG: Case 2"); - // Whenever we finish a tag, check for loops fist. - // If currentNode matches an existing node in uniqueNodes, - // duplicate is set to the existing node. - StateSpaceNode duplicate; - if (findLoop && - (duplicate = uniqueNodes.put( - currentNode.hash(), currentNode)) != null) { - - // Mark the loop in the diagram. - loopFound = true; - this.diagram.loopNode = duplicate; - this.diagram.loopNodeNext = currentNode; - this.diagram.tail = previousNode; - // Loop period is the time difference between the 1st time - // the node is reached and the 2nd time the node is reached. - this.diagram.loopPeriod = this.diagram.loopNodeNext.tag.timestamp - - this.diagram.loopNode.tag.timestamp; - this.diagram.addEdge(this.diagram.loopNode, this.diagram.tail); - return; // Exit the while loop early. - } - - // Now we are at a new tag, and a loop is not found, - // add the node to the state space diagram. - // Adding a node to the graph once it is finalized - // because this makes checking duplicate nodes easier. - // We don't have to remove a node from the graph. - this.diagram.addNode(currentNode); - this.diagram.tail = currentNode; // Update the current tail. - - // If the head is not empty, add an edge from the previous state - // to the next state. Otherwise initialize the head to the new node. - if (previousNode != null) { - // System.out.println("--- Add a new edge between " + currentNode + " and " + node); - // this.diagram.addEdge(currentNode, previousNode); // Sink first, then source - if (previousNode != currentNode) - this.diagram.addEdge(currentNode, previousNode); - } - else - this.diagram.head = currentNode; // Initialize the head. - - //// Now we are done with the node at the previous tag, - //// work on the new node at the current timestamp. - // Copy the reactions in reactionsTemp. - reactionsInvoked = new HashSet(reactionsTemp); - - // Create a new state in the SSD for the current tag, - // add the reactions triggered to the state, - // and add a snapshot of the event queue (with new events - // generated by reaction invocations in the curren tag) - // to the state. - StateSpaceNode node = new StateSpaceNode( - currentTag, // Current tag - reactionsInvoked, // Reactions invoked at this tag - new ArrayList(eventQ) // A snapshot of the event queue - ); - - // Update the previous node. - previousNode = currentNode; - // Update the current node to the new (potentially incomplete) node. - currentNode = node; - } - // Timestamp does not advance because we are processing - // connections with zero delay. - else if ( - previousTag != null - && currentTag.timestamp == previousTag.timestamp - ) { - // System.out.println("DEBUG: Case 3"); - // Add reactions explored in the current loop iteration - // to the existing state space node. - currentNode.reactionsInvoked.addAll(reactionsTemp); - // Update the eventQ snapshot. - currentNode.eventQ = new ArrayList(eventQ); } - else { - // Unreachable - Exceptions.sneakyThrow(new Exception("Reached an unreachable part.")); - } - - // Update the current tag for the next iteration. - if (eventQ.size() > 0) { - previousTag = currentTag; - currentTag = eventQ.peek().tag; - } - - // Stop if: - // 1. the event queue is empty, or - // 2. the horizon is reached. - if (eventQ.size() == 0) { - // System.out.println("DEBUG: Stopping because eventQ is empty!"); - stop = true; - } - else if (currentTag.timestamp > horizon.timestamp) { - // System.out.println("DEBUG: Stopping because horizon is reached! Horizon: " + horizon + " Current Tag: " + currentTag); - // System.out.println("DEBUG: EventQ: " + eventQ); - stop = true; - } - } - - // Check if the last current node is added to the graph yet. - // If not, add it now. - // This could happen when condition (previousTag == null) - // or (previousTag != null - // && currentTag.compareTo(previousTag) > 0) is true and then - // the simulation ends, leaving a new node dangling. - if (previousNode == null - || previousNode.tag.timestamp < currentNode.tag.timestamp) { - this.diagram.addNode(currentNode); - this.diagram.tail = currentNode; // Update the current tail. - if (previousNode != null) { - this.diagram.addEdge(currentNode, previousNode); - } - } - - // When we exit and we still don't have a head, - // that means there is only one node in the diagram. - // Set the current node as the head. - if (this.diagram.head == null) - this.diagram.head = currentNode; - - return; - } -} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java b/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java deleted file mode 100644 index 35c588bcbc..0000000000 --- a/org.lflang/src/org/lflang/analyses/statespace/StateSpaceNode.java +++ /dev/null @@ -1,114 +0,0 @@ -/** - * A node in the state space diagram representing a step - * in the execution of an LF program. - * - * - */ -package org.lflang.analyses.statespace; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import org.lflang.TimeValue; -import org.lflang.generator.ReactionInstance; -import org.lflang.generator.TriggerInstance; - -public class StateSpaceNode { - - public int index; // Set in StateSpaceDiagram.java - public Tag tag; - public TimeValue time; // Readable representation of tag.timestamp - public Set reactionsInvoked; - public ArrayList eventQ; - - public StateSpaceNode( - Tag tag, - Set reactionsInvoked, - ArrayList eventQ - ) { - this.tag = tag; - this.eventQ = eventQ; - this.reactionsInvoked = reactionsInvoked; - this.time = TimeValue.fromNanoSeconds(tag.timestamp); - } - - /** - * Assuming both eventQs have the same length, - * for each pair of events in eventQ1 and eventQ2, - * check if the time distances between the node's tag - * and the two events' tags are equal. - */ - private boolean equidistant(StateSpaceNode n1, - StateSpaceNode n2) { - if (n1.eventQ.size() != n2.eventQ.size()) - return false; - for (int i = 0; i < n1.eventQ.size(); i++) { - if (n1.eventQ.get(i).tag.timestamp - n1.tag.timestamp - != n2.eventQ.get(i).tag.timestamp - n2.tag.timestamp) { - return false; - } - } - return true; - } - - /** - * Two methods for pretty printing - */ - public void display() { - System.out.println("(" + this.time + ", " + reactionsInvoked + ", " + eventQ + ")"); - } - public String toString() { - return "(" + this.time + ", " + reactionsInvoked + ", " + eventQ + ")"; - } - - /** - * This equals method does NOT compare tags, - * only compares reactionsInvoked, eventQ, - * and whether future events are equally distant. - */ - @Override - public boolean equals(Object o) { - if (o == null) return false; - if (o instanceof StateSpaceNode) { - StateSpaceNode node = (StateSpaceNode) o; - if (this.reactionsInvoked.equals(node.reactionsInvoked) - && this.eventQ.equals(node.eventQ) - && equidistant(this, node)) - return true; - } - return false; - } - - /** - * Generate hash for the node. - * This hash function is used for checking - * the uniqueness of nodes. - * It is not meant to be used as a hashCode() - * because doing so interferes with node - * insertion in the state space diagram. - */ - public int hash() { - // Initial value - int result = 17; - - // Generate hash for the reactions invoked. - result = 31 * result + reactionsInvoked.hashCode(); - - // Generate hash for the triggers in the queued events. - List eventNames = this.eventQ.stream() - .map(Event::getTrigger) - .map(TriggerInstance::getFullName) - .collect(Collectors.toList()); - result = 31 * result + eventNames.hashCode(); - - // Generate hash for the time differences. - List timeDiff = this.eventQ.stream().map(e -> { - return e.tag.timestamp - this.tag.timestamp; - }).collect(Collectors.toList()); - result = 31 * result + timeDiff.hashCode(); - - return result; - } -} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/analyses/statespace/Tag.java b/org.lflang/src/org/lflang/analyses/statespace/Tag.java deleted file mode 100644 index 362632ea3d..0000000000 --- a/org.lflang/src/org/lflang/analyses/statespace/Tag.java +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Class representing a logical time tag, - * which is a pair that consists of a - * timestamp (type long) and a microstep (type long). - * - * - */ -package org.lflang.analyses.statespace; - -import org.lflang.TimeValue; - -public class Tag implements Comparable { - - public long timestamp; - public long microstep; - public boolean forever; // Whether the tag is FOREVER into the future. - - public Tag(long timestamp, long microstep, boolean forever) { - this.timestamp = timestamp; - this.microstep = microstep; - this.forever = forever; - } - - @Override - public int compareTo(Tag t) { - // If one tag is forever, and the other is not, - // then forever tag is later. If both tags are - // forever, then they are equal. - if (this.forever && !t.forever) return 1; - else if (!this.forever && t.forever) return -1; - else if (this.forever && t.forever) return 0; - - // Compare the timestamps if they are not equal. - if (this.timestamp != t.timestamp) { - if (this.timestamp > t.timestamp) return 1; - else if (this.timestamp < t.timestamp) return -1; - else return 0; - } - - // Otherwise, compare the microsteps. - if (this.microstep > t.microstep) return 1; - else if (this.microstep < t.microstep) return -1; - else return 0; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o instanceof Tag) { - Tag t = (Tag) o; - if (this.timestamp == t.timestamp - && this.microstep == t.microstep - && this.forever == t.forever) - return true; - } - return false; - } - - @Override - public String toString() { - if (this.forever) return "(FOREVER, " + this.microstep + ")"; - else { - TimeValue time = TimeValue.fromNanoSeconds(this.timestamp); - return "(" + time + ", " + this.microstep + ")"; - } - } -} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java b/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java deleted file mode 100644 index 9cc6d308f3..0000000000 --- a/org.lflang/src/org/lflang/analyses/uclid/MTLVisitor.java +++ /dev/null @@ -1,465 +0,0 @@ -/************* -Copyright (c) 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. -***************/ - -/** - * (EXPERIMENTAL) Transpiler from an MTL specification to a Uclid axiom. - * - * - */ -package org.lflang.analyses.uclid; - -import org.lflang.TimeUnit; -import org.lflang.TimeValue; -import org.lflang.dsl.MTLParser; -import org.lflang.dsl.MTLParserBaseVisitor; -import org.lflang.generator.CodeBuilder; - -public class MTLVisitor extends MTLParserBaseVisitor { - - //////////////////////////////////////////// - //// Protected fields - - /** The main place to put generated code. */ - protected CodeBuilder code = new CodeBuilder(); - - /** Tactic to be used to prove the property. */ - protected String tactic; - - /** Time horizon (in nanoseconds) of the property */ - protected long horizon = 0; - - // Constructor - public MTLVisitor(String tactic) { - this.tactic = tactic; - } - - //////////////////////////////////////////// - //// Public methods - public String visitMtl(MTLParser.MtlContext ctx, - String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - - return visitEquivalence(ctx.equivalence(), - prefixIdx, QFIdx, prevPrefixIdx, horizon); - } - - public String visitEquivalence(MTLParser.EquivalenceContext ctx, - String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - - if (ctx.right == null) { - return visitImplication(ctx.left, - prefixIdx, QFIdx, prevPrefixIdx, horizon); - } - return "(" + visitImplication(ctx.left, - prefixIdx, QFIdx, prevPrefixIdx, horizon) - + ")" - + " <==> " - + "(" + visitImplication(ctx.right, - prefixIdx, QFIdx, prevPrefixIdx, horizon) - + ")"; - } - - public String visitImplication(MTLParser.ImplicationContext ctx, - String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - - if (ctx.right == null) { - return visitDisjunction(ctx.left, - prefixIdx, QFIdx, prevPrefixIdx, horizon); - } - return "(" + visitDisjunction(ctx.left, - prefixIdx, QFIdx, prevPrefixIdx, horizon) - + ")" - + " ==> " - + "(" + visitDisjunction(ctx.right, - prefixIdx, QFIdx, prevPrefixIdx, horizon) - + ")"; - } - - public String visitDisjunction(MTLParser.DisjunctionContext ctx, - String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - - String str = ""; - for (int i = 0; i < ctx.terms.size(); i++) { - str += "(" - + visitConjunction(ctx.terms.get(i), - prefixIdx, QFIdx, prevPrefixIdx, horizon) - + ")" - + (i == ctx.terms.size()-1 ? "" : "||"); - } - return str; - } - - public String visitConjunction(MTLParser.ConjunctionContext ctx, - String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - - String str = ""; - for (int i = 0; i < ctx.terms.size(); i++) { - str += "(" - + visitUntil((MTLParser.UntilContext)ctx.terms.get(i), - prefixIdx, QFIdx, prevPrefixIdx, horizon) - + ")" - + (i == ctx.terms.size()-1 ? "" : "&&"); - } - return str; - } - - // A custom dispatch function - public String _visitUnaryOp(MTLParser.UnaryOpContext ctx, - String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - - // FIXME: Is there a more "antlr" way to do dispatch here? - if (ctx instanceof MTLParser.NoUnaryOpContext) { - return visitNoUnaryOp((MTLParser.NoUnaryOpContext)ctx, - prefixIdx, QFIdx, prevPrefixIdx, horizon); - } - if (ctx instanceof MTLParser.NegationContext) { - return visitNegation((MTLParser.NegationContext)ctx, - prefixIdx, QFIdx, prevPrefixIdx, horizon); - } - if (ctx instanceof MTLParser.NextContext) { - return visitNext((MTLParser.NextContext)ctx, - prefixIdx, QFIdx, prevPrefixIdx, horizon); - } - if (ctx instanceof MTLParser.GloballyContext) { - return visitGlobally((MTLParser.GloballyContext)ctx, - prefixIdx, QFIdx, prevPrefixIdx, horizon); - } - if (ctx instanceof MTLParser.FinallyContext) { - return visitFinally((MTLParser.FinallyContext)ctx, - prefixIdx, QFIdx, prevPrefixIdx, horizon); - } - - // FIXME: Throw an exception. - return ""; - } - - public String visitUntil(MTLParser.UntilContext ctx, - String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - - // If right is null, continue recursion. - if (ctx.right == null) { - return _visitUnaryOp(ctx.left, - prefixIdx, QFIdx, prevPrefixIdx, horizon); - } - - String end; - if (this.tactic.equals("induction")) { - end = "(" + prefixIdx + " + CT)"; - } else { - end = "END"; - } - - // Otherwise, create the Until formula. - // Check if the time interval is a range or a singleton. - long lowerBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); - long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); - long currentHorizon = horizon + upperBoundNanoSec; - if (currentHorizon > this.horizon) this.horizon = currentHorizon; - String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, - upperBoundNanoSec, "j" + QFIdx, prefixIdx); - - return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " - + "j" + QFIdx + " >= " + prefixIdx + " && " + "j" + QFIdx + " <= " + end - + " && " + "!" + "isNULL" + "(" + "j" + QFIdx + ")" - + " && " + "(" + _visitUnaryOp(ctx.right, ("j"+QFIdx), QFIdx+1, prefixIdx, currentHorizon) + ")" - + " && " + "(" + "\n" - + "// Time Predicate\n" - + timePredicate + "\n" - + ")" + " && " + "(" + "finite_forall " + "(" + "i" + QFIdx + " : integer) in indices :: " - + "(" + "i" + QFIdx + " >= " + prefixIdx + " && " + "i" + QFIdx + " < " + "j" + QFIdx + ")" - + " ==> " + "(" + _visitUnaryOp(ctx.left, ("i"+QFIdx), QFIdx+1, ("j"+QFIdx), currentHorizon) + ")" + ")"; - } - - public String visitNoUnaryOp(MTLParser.NoUnaryOpContext ctx, - String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - - return visitPrimary(ctx.formula, prefixIdx, QFIdx, prevPrefixIdx, horizon); - } - - public String visitNegation(MTLParser.NegationContext ctx, - String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - - return "!(" + visitPrimary(ctx.formula, prefixIdx, QFIdx, prevPrefixIdx, horizon) + ")"; - } - - public String visitNext(MTLParser.NextContext ctx, - String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - // FIXME: Horizon needs to advance! - return visitPrimary(ctx.formula, ("(" + prefixIdx + "+1)"), QFIdx, prevPrefixIdx, horizon); - } - - public String visitGlobally(MTLParser.GloballyContext ctx, - String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - - String end; - if (this.tactic.equals("induction")) { - end = "(" + prefixIdx + " + CT)"; - } else { - end = "END"; - } - - long lowerBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); - long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); - long currentHorizon = horizon + upperBoundNanoSec; - if (currentHorizon > this.horizon) this.horizon = currentHorizon; - String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, - upperBoundNanoSec, "j" + QFIdx, prefixIdx); - return "(" + "finite_forall " + "(" + "j" + QFIdx + " : integer) in indices :: " - + "(" + "j" + QFIdx + " >= " + prefixIdx + " && " + "j" + QFIdx + " <= " + end - + " && " + "!" + "isNULL" + "(" + "j" + QFIdx + ")" - + " && " + "(" + "\n" - + "// Time Predicate\n" - + timePredicate + "\n" - + ")" + ")" - + " ==> " + "(" + visitPrimary(ctx.formula, ("j"+QFIdx), QFIdx+1, prefixIdx, currentHorizon) + ")" - + ")"; - } - - public String visitFinally(MTLParser.FinallyContext ctx, - String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - - String end; - if (this.tactic.equals("induction")) { - end = "(" + prefixIdx + " + CT)"; - } else { - end = "END"; - } - - long lowerBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); - long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); - long currentHorizon = horizon + upperBoundNanoSec; - if (currentHorizon > this.horizon) this.horizon = currentHorizon; - String timePredicate = generateTimePredicate(ctx.timeInterval, lowerBoundNanoSec, - upperBoundNanoSec, "j" + QFIdx, prefixIdx); - return "finite_exists " + "(" + "j" + QFIdx + " : integer) in indices :: " - + "j" + QFIdx + " >= " + prefixIdx + " && " + "j" + QFIdx + " <= " + end - + " && " + "!" + "isNULL" + "(" + "j" + QFIdx + ")" - + " && " + "(" + visitPrimary(ctx.formula, ("j"+QFIdx), QFIdx+1, prefixIdx, currentHorizon) + ")" - + " && " + "(" + "\n" - + "// Time Predicate\n" - + timePredicate + "\n" - + ")"; - } - - public String visitPrimary(MTLParser.PrimaryContext ctx, - String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - - if (ctx.atom != null) - return visitAtomicProp(ctx.atom, prefixIdx, QFIdx, prevPrefixIdx, horizon); - else if (ctx.id != null) { - // Check if the ID is a reaction. - // FIXME: Not robust. - if (ctx.id.getText().contains("_reaction_")) { - return ctx.id.getText() + "(" + "rxn(" + prefixIdx + ")" + ")"; - } else if (ctx.id.getText().contains("_is_present")) { - return ctx.id.getText() + "(" + "t(" + prefixIdx + ")" + ")"; - } else { - return ctx.id.getText() + "(" + "s(" + prefixIdx + ")" + ")"; - } - } - else - return visitMtl(ctx.formula, prefixIdx, QFIdx, prevPrefixIdx, horizon); - } - - public String visitAtomicProp(MTLParser.AtomicPropContext ctx, - String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - - if (ctx.primitive != null) - return ctx.primitive.getText(); - else - return visitExpr(ctx.left, prefixIdx, QFIdx, prevPrefixIdx, horizon) - + " " + ctx.op.getText() + " " - + visitExpr(ctx.right, prefixIdx, QFIdx, prevPrefixIdx, horizon); - } - - public String visitExpr(MTLParser.ExprContext ctx, - String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - - if (ctx.ID() != null) - return ctx.ID().getText() + "(" + "s(" + prefixIdx + ")" + ")"; - else if (ctx.INTEGER() != null) - return ctx.INTEGER().getText(); - else - return visitSum(ctx.sum(), prefixIdx, QFIdx, prevPrefixIdx, horizon); - } - - public String visitSum(MTLParser.SumContext ctx, - String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - - String str = ""; - for (int i = 0; i < ctx.terms.size(); i++) { - str += "(" - + visitDifference(ctx.terms.get(i), - prefixIdx, QFIdx, prevPrefixIdx, horizon) - + ")" - + (i == ctx.terms.size()-1 ? "" : "+"); - } - return str; - } - - public String visitDifference(MTLParser.DifferenceContext ctx, - String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - - String str = ""; - for (int i = 0; i < ctx.terms.size(); i++) { - str += "(" - + visitProduct(ctx.terms.get(i), - prefixIdx, QFIdx, prevPrefixIdx, horizon) - + ")" - + (i == ctx.terms.size()-1 ? "" : "-"); - } - return str; - } - - public String visitProduct(MTLParser.ProductContext ctx, - String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - - String str = ""; - for (int i = 0; i < ctx.terms.size(); i++) { - str += "(" - + visitQuotient(ctx.terms.get(i), - prefixIdx, QFIdx, prevPrefixIdx, horizon) - + ")" - + (i == ctx.terms.size()-1 ? "" : "*"); - } - return str; - } - - public String visitQuotient(MTLParser.QuotientContext ctx, - String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { - - String str = ""; - for (int i = 0; i < ctx.terms.size(); i++) { - str += "(" - + visitExpr(ctx.terms.get(i), - prefixIdx, QFIdx, prevPrefixIdx, horizon) - + ")" - + (i == ctx.terms.size()-1 ? "" : "/"); - } - return str; - } - - /////////////////////////////////////// - //// Private methods - - /** - * Return a time value in nanoseconds from an IntervalContext. - * - * @param ctx - * @param getUpper - * @return - */ - private long getNanoSecFromIntervalContext(MTLParser.IntervalContext ctx, boolean getUpper) { - // If we have a singleton, the return value is the same regardless of getUpper. - if (ctx instanceof MTLParser.SingletonContext) { - MTLParser.SingletonContext singletonCtx = (MTLParser.SingletonContext)ctx; - String timeInstantValue = singletonCtx.instant.value.getText(); - String timeInstantUnit = ""; - long timeInstantNanoSec = 0; - if (!timeInstantValue.equals("0")) { - timeInstantUnit = singletonCtx.instant.unit.getText(); - TimeValue timeValue = new TimeValue( - Integer.valueOf(timeInstantValue), - TimeUnit.fromName(timeInstantUnit)); - timeInstantNanoSec = timeValue.toNanoSeconds(); - } - return timeInstantNanoSec; - } - - MTLParser.RangeContext rangeCtx = (MTLParser.RangeContext)ctx; - if (!getUpper) { - String lowerBoundTimeValue = rangeCtx.lowerbound.value.getText(); - String lowerBoundTimeUnit = ""; - long lowerBoundNanoSec = 0; - if (!lowerBoundTimeValue.equals("0")) { - lowerBoundTimeUnit = rangeCtx.lowerbound.unit.getText(); - TimeValue lowerTimeValue = new TimeValue( - Integer.valueOf(lowerBoundTimeValue), - TimeUnit.fromName(lowerBoundTimeUnit)); - lowerBoundNanoSec = lowerTimeValue.toNanoSeconds(); - } - return lowerBoundNanoSec; - } else { - String upperBoundTimeValue = rangeCtx.upperbound.value.getText(); - String upperBoundTimeUnit = ""; - long upperBoundNanoSec = 0; - if (!upperBoundTimeValue.equals("0")) { - upperBoundTimeUnit = rangeCtx.upperbound.unit.getText(); - TimeValue upperTimeValue = new TimeValue( - Integer.valueOf(upperBoundTimeValue), - TimeUnit.fromName(upperBoundTimeUnit)); - upperBoundNanoSec = upperTimeValue.toNanoSeconds(); - } - return upperBoundNanoSec; - } - } - - /** - * Generate a time predicate from a range. - * - * @param ctx - * @param lowerBoundNanoSec - * @param upperBoundNanoSec - * @return - */ - private String generateTimePredicate(MTLParser.IntervalContext ctx, - long lowerBoundNanoSec, long upperBoundNanoSec, - String prefixIdx, String prevPrefixIdx) { - String timePredicate = ""; - - if (ctx instanceof MTLParser.SingletonContext) { - MTLParser.SingletonContext singletonCtx = (MTLParser.SingletonContext)ctx; - timePredicate += "tag_same(g(" + prefixIdx + "), " + "tag_delay(g(" - + prevPrefixIdx + "), " + upperBoundNanoSec + "))"; - } else { - // Since MTL doesn't include microsteps, we only compare timestamps here. - MTLParser.RangeContext rangeCtx = (MTLParser.RangeContext)ctx; - timePredicate += "("; - if (rangeCtx.LBRACKET() != null) { - timePredicate += "pi1(g(" + prefixIdx + "))" + " >= " - + "(" + "pi1(g(" + prevPrefixIdx + "))" + " + " + lowerBoundNanoSec + ")"; - } else { - timePredicate += "pi1(g(" + prefixIdx + "))" + " > " - + "(" + "pi1(g(" + prevPrefixIdx + "))" + " + " + lowerBoundNanoSec + ")"; - } - timePredicate += ") && ("; - if (rangeCtx.RBRACKET() != null) { - timePredicate += "pi1(g(" + prefixIdx + "))" + " <= " - + "(" + "pi1(g(" + prevPrefixIdx + "))" + " + " + upperBoundNanoSec + ")"; - } else { - timePredicate += "pi1(g(" + prefixIdx + "))" + " < " - + "(" + "pi1(g(" + prevPrefixIdx + "))" + " + " + upperBoundNanoSec + ")"; - } - timePredicate += ")"; - } - - return timePredicate; - } - - /** - * Get the time horizon (in nanoseconds) of the property. - */ - public long getHorizon() { - return this.horizon; - } -} diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java b/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java deleted file mode 100644 index 2b3ec21dfc..0000000000 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidGenerator.java +++ /dev/null @@ -1,1611 +0,0 @@ -/************* -Copyright (c) 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. -***************/ - -/** - * (EXPERIMENTAL) Generator for Uclid5 models. - * - * - */ -package org.lflang.analyses.uclid; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; -import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.ParseTreeWalker; - -import org.eclipse.emf.common.util.EList; -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.Exceptions; - -import org.lflang.ASTUtils; -import org.lflang.ErrorReporter; -import org.lflang.FileConfig; -import org.lflang.Target; -import org.lflang.TimeUnit; -import org.lflang.TimeValue; -import org.lflang.analyses.cast.AstUtils; -import org.lflang.analyses.cast.BuildAstParseTreeVisitor; -import org.lflang.analyses.cast.CAst; -import org.lflang.analyses.cast.CBaseAstVisitor; -import org.lflang.analyses.cast.CToUclidVisitor; -import org.lflang.analyses.cast.IfNormalFormAstVisitor; -import org.lflang.analyses.cast.VariablePrecedenceVisitor; -import org.lflang.analyses.statespace.Event; -import org.lflang.analyses.statespace.StateSpaceDiagram; -import org.lflang.analyses.statespace.StateSpaceExplorer; -import org.lflang.analyses.statespace.StateSpaceNode; -import org.lflang.analyses.statespace.Tag; -import org.lflang.dsl.CLexer; -import org.lflang.dsl.CParser; -import org.lflang.dsl.MTLLexer; -import org.lflang.dsl.MTLParser; -import org.lflang.dsl.CParser.BlockItemListContext; -import org.lflang.dsl.MTLParser.MtlContext; -import org.lflang.generator.ActionInstance; -import org.lflang.generator.CodeBuilder; -import org.lflang.generator.GeneratorBase; -import org.lflang.generator.GeneratorUtils; -import org.lflang.generator.LFGeneratorContext; -import org.lflang.generator.NamedInstance; -import org.lflang.generator.PortInstance; -import org.lflang.generator.ReactionInstance; -import org.lflang.generator.ReactionInstanceGraph; -import org.lflang.generator.ReactorInstance; -import org.lflang.generator.RuntimeRange; -import org.lflang.generator.SendRange; -import org.lflang.generator.StateVariableInstance; -import org.lflang.generator.TargetTypes; -import org.lflang.generator.TimerInstance; -import org.lflang.generator.TriggerInstance; -import org.lflang.generator.c.CGenerator; -import org.lflang.lf.Action; -import org.lflang.lf.AttrParm; -import org.lflang.lf.Attribute; -import org.lflang.lf.Code; -import org.lflang.lf.Connection; -import org.lflang.lf.Expression; -import org.lflang.lf.Reaction; -import org.lflang.lf.Time; -import org.lflang.lf.VarRef; -import org.lflang.util.StringUtil; - -import static org.lflang.ASTUtils.*; - -public class UclidGenerator extends GeneratorBase { - - //////////////////////////////////////////// - //// Public fields - // Data structures for storing info about the runtime instances. - public List reactorInstances = new ArrayList(); - public List reactionInstances = new ArrayList(); - - // State variables in the system - public List stateVariables = new ArrayList(); - - // Triggers in the system - public List actionInstances = new ArrayList(); - public List inputInstances = new ArrayList(); - public List outputInstances = new ArrayList(); - public List portInstances = new ArrayList(); - public List timerInstances = new ArrayList(); - - // Joint lists of the lists above. - public List triggerInstances; // Triggers = ports + actions + timers - public List namedInstances; // Named instances = triggers + state variables - - // A list of paths to the uclid files generated - public List generatedFiles = new ArrayList<>(); - public Map expectations = new HashMap<>(); - - // The directory where the generated files are placed - public Path outputDir; - - // A runner for the generated Uclid files - public UclidRunner runner; - - // If true, use logical time-based semantics; - // otherwise, use event-based semantics, - // as described in Sirjani et. al (2020). - public boolean logicalTimeBased = false; - - //////////////////////////////////////////// - //// Protected fields - - // A list of MTL properties represented in Attributes. - protected List properties; - - /** The main place to put generated code. */ - protected CodeBuilder code = new CodeBuilder(); - - /** Strings from the property attribute */ - protected String name; - protected String tactic; - protected String spec; // SMTL - protected String expect; - - /** - * The horizon (the total time interval required for evaluating - * an MTL property, which is derived from the MTL spec), - * the completeness threshold (CT) (the number of transitions - * required for evaluating the FOL spec in the trace), - * and the transpiled FOL spec. - */ - protected long horizon = 0; // in nanoseconds - protected String FOLSpec = ""; - protected int CT = 0; - protected static final int CT_MAX_SUPPORTED = 100; - - // Constructor - public UclidGenerator(LFGeneratorContext context, List properties) { - super(context); - this.properties = properties; - this.runner = new UclidRunner(this); - } - - //////////////////////////////////////////////////////////// - //// Public methods - public void doGenerate(Resource resource, LFGeneratorContext context) { - - // FIXME: How much of doGenerate() from GeneratorBase is needed? - super.printInfo(context.getMode()); - ASTUtils.setMainName( - context.getFileConfig().resource, - context.getFileConfig().name); - // FIXME: Perform an analysis on the property and remove unrelevant components. - super.createMainInstantiation(); - - //////////////////////////////////////// - - // Create the main reactor instance if there is a main reactor. - createMainReactorInstance(); - - // Extract information from the named instances. - populateDataStructures(); - - // Create the src-gen directory - setupDirectories(); - - // Generate a Uclid model for each property. - for (Attribute prop : this.properties) { - this.name = StringUtil.removeQuotes( - prop.getAttrParms().stream() - .filter(attr -> attr.getName().equals("name")) - .findFirst() - .get() - .getValue()); - this.tactic = StringUtil.removeQuotes( - prop.getAttrParms().stream() - .filter(attr -> attr.getName().equals("tactic")) - .findFirst() - .get() - .getValue()); - this.spec = StringUtil.removeQuotes( - prop.getAttrParms().stream() - .filter(attr -> attr.getName().equals("spec")) - .findFirst() - .get() - .getValue()); - - processMTLSpec(); - - Optional CTAttr = prop.getAttrParms().stream() - .filter(attr -> attr.getName().equals("CT")) - .findFirst(); - if (CTAttr.isPresent()) { - this.CT = Integer.parseInt(CTAttr.get().getValue()); - } else { - computeCT(); - } - // For automating data collection, print the CT to stderr. - System.err.println("CT: " + this.CT); - if (this.CT > CT_MAX_SUPPORTED) { - System.out.println("ERROR: The maximum steps supported is " + CT_MAX_SUPPORTED - + " but checking this property requires " + this.CT + " steps. " - + "This property will NOT be checked."); - continue; - } - - Optional ExpectAttr = prop.getAttrParms().stream() - .filter(attr -> attr.getName().equals("expect")) - .findFirst(); - if (ExpectAttr.isPresent()) - this.expect = ExpectAttr.get().getValue(); - - generateUclidFile(); - } - } - - //////////////////////////////////////////////////////////// - //// Protected methods - - /** - * Generate the Uclid model. - */ - protected void generateUclidFile() { - try { - // Generate main.ucl and print to file - code = new CodeBuilder(); - Path file = this.outputDir.resolve(this.tactic + "_" + this.name + ".ucl"); - String filename = file.toString(); - generateUclidCode(); - code.writeToFile(filename); - this.generatedFiles.add(file); - if (this.expect != null) - this.expectations.put(file, this.expect); - } catch (IOException e) { - Exceptions.sneakyThrow(e); - } - } - - /** - * The main function that generates Uclid code. - */ - protected void generateUclidCode() { - code.pr(String.join("\n", - "/*******************************", - " * Auto-generated UCLID5 model *", - " ******************************/" - )); - - code.pr("module main {"); - code.indent(); - - // Timing semantics - generateTimingSemantics(); - - // Trace definition - generateTraceDefinition(); - - // Reaction IDs and state variables - generateReactionIdsAndStateVars(); - - // Reactor semantics - generateReactorSemantics(); - - // Triggers and reactions - generateTriggersAndReactions(); - - // Initial Condition - generateInitialConditions(); - - // Reaction bodies - generateReactionAxioms(); - - // Properties - generateProperty(); - - // Control block - generateControlBlock(); - - code.unindent(); - code.pr("}"); - } - - /** - * Macros for timing semantics - */ - protected void generateTimingSemantics() { - code.pr(String.join("\n", - "/*******************************", - " * Time and Related Operations *", - " ******************************/", - "type timestamp_t = integer; // The unit is nanoseconds", - "type microstep_t = integer;", - "type tag_t = {", - " timestamp_t,", - " microstep_t", - "};", - "type interval_t = tag_t;", - "", - "// Projection macros", - "define pi1(t : tag_t) : timestamp_t = t._1; // Get timestamp from tag", - "define pi2(t : tag_t) : microstep_t = t._2; // Get microstep from tag", - "", - "// Interval constructor", - "define zero() : interval_t", - "= {0, 0};", - "define startup() : interval_t", - "= zero();", - "define mstep() : interval_t", - "= {0, 1};", - "define nsec(t : integer) : interval_t", - "= {t, 0};", - "define usec(t : integer) : interval_t", - "= {t * 1000, 0};", - "define msec(t : integer) : interval_t", - "= {t * 1000000, 0};", - "define sec(t : integer) : interval_t", - "= {t * 1000000000, 0};", - "define inf() : interval_t", - "= {-1, 0};", - "", - "// Helper function", - "define isInf(i : interval_t) : boolean", - "= pi1(i) < 0;", - "", - "// Tag comparison", - "define tag_later(t1 : tag_t, t2 : tag_t) : boolean", - "= pi1(t1) > pi1(t2)", - " || (pi1(t1) == pi1(t2) && pi2(t1) > pi2(t2))", - " || (isInf(t1) && !isInf(t2));", - "", - "define tag_same(t1 : tag_t, t2 : tag_t) : boolean", - "= t1 == t2;", - "", - "define tag_earlier(t1 : tag_t, t2 : tag_t) : boolean", - "= pi1(t1) < pi1(t2)", - " || (pi1(t1) == pi1(t2) && pi2(t1) < pi2(t2))", - " || (!isInf(t1) && isInf(t2));", - "", - "// Used for incrementing a tag through an action", - "define tag_schedule(t : tag_t, i : integer) : tag_t", - "= if (i == 0) then { pi1(t), pi2(t)+1 } else { pi1(t)+i, 0 };", - "", - "// Used for incrementing a tag along a connection", - "define tag_delay(t : tag_t, i : integer) : tag_t", - "= if (i == 0) then { pi1(t), pi2(t) } else { pi1(t)+i, 0 };", - "", - "// Only consider timestamp for now.", - "define tag_diff(t1, t2: tag_t) : interval_t", - "= if (!isInf(t1) && !isInf(t2))", - " then { pi1(t1) - pi1(t2), pi2(t1) - pi2(t2) }", - " else inf();", - "\n" - )); - } - - /** - * Macros, type definitions, and variable declarations for trace (path) - */ - protected void generateTraceDefinition() { - //// Why do we have a padding at the end of the trace? - // - // To handle bounded traces for a potentially unbounded execution - // (due to, for example, timers or systems forming a loop), - // we need to let certain axioms "terminate" the execution and - // put any "spilled-over" states in the trace padding. - // - // For example, an axiom could say "if a reaction is triggered - // at step i, it schedules an action that appears 1 sec later - // in some future step." Assuming our completeness threshold is 10, - // this axiom can block the model (i.e. returns TRIVIALLY true) - // at step 10 because there are not any more steps in the bounded trace. - // To avoid this, a padding of the size of the number of reactions - // is added to the trace. In addition, an antecedent of the form - // "i >= START && i <= END" is added to all the axioms. The padding - // will store all the spilled-over states in order to prevent - // model blocking. - int traceEndIndex = this.CT + this.reactionInstances.size(); - - // Define various constants. - code.pr(String.join("\n", - "/********************", - " * Trace Definition *", - " *******************/", - "const START : integer = 0; // The start index of the trace.", - "const END : integer = " + String.valueOf(this.CT) - + "; // The end index of the trace (without padding)", - "const END_TRACE : integer = " - + String.valueOf(traceEndIndex) - + "; // The end index of the trace with padding", - "\n" - )); - - // Define trace indices as a group, - // so that we can use finite quantifiers. - code.pr("group indices : integer = {"); - code.indent(); - String indices = ""; - for (int i = 0; i <= traceEndIndex; i++) { - indices += String.valueOf(i) + (i == traceEndIndex ? "" : ", "); - } - code.pr(indices); - code.unindent(); - code.pr("};\n\n"); - - // Define step, event, and trace types. - code.pr(String.join("\n", - "// Define step and event types.", - "type step_t = integer;", - "type event_t = { rxn_t, tag_t, state_t, trigger_t, sched_t, payload_t };", - "", - "// Create a bounded trace with length " + String.valueOf(this.CT) - )); - code.pr("// Potentially unbounded trace, we bound this later."); - code.pr("type trace_t = [integer]event_t;"); - - // Declare start time and trace. - code.pr(String.join("\n", - "// Declare start time.", - "var start_time : timestamp_t;", - "", - "// Declare trace.", - "var trace : trace_t;" - )); - - // Start generating helper macros. - code.pr(String.join("\n", - "/*****************", - " * Helper Macros *", - " ****************/" - )); - - // Define a getter for uclid arrays. - String initialReactions = ""; - if (this.reactionInstances.size() > 0) { - initialReactions = "false, ".repeat(this.reactionInstances.size()); - initialReactions = initialReactions.substring(0, initialReactions.length()-2); - } else { - // Initialize a dummy variable just to make the code compile. - initialReactions = "false"; - } - initialReactions = "{" + initialReactions + "}"; - String initialStates = ""; - if (this.namedInstances.size() > 0) { - initialStates = "0, ".repeat(this.namedInstances.size()); - initialStates = initialStates.substring(0, initialStates.length()-2); - } else { - // Initialize a dummy variable just to make the code compile. - initialStates = "0"; - } - String initialTriggerPresence = ""; - if (this.triggerInstances.size() > 0) { - initialTriggerPresence = "false, ".repeat(this.triggerInstances.size()); - initialTriggerPresence = initialTriggerPresence.substring(0, initialTriggerPresence.length()-2); - } else { - // Initialize a dummy variable just to make the code compile. - initialTriggerPresence = "false"; - } - String initialActionsScheduled = ""; - if (this.actionInstances.size() > 0) { - initialActionsScheduled = "false, ".repeat(this.actionInstances.size()); - initialActionsScheduled = initialActionsScheduled.substring(0, initialActionsScheduled.length()-2); - } else { - // Initialize a dummy variable just to make the code compile. - initialActionsScheduled = "false"; - } - String initialActionsScheduledPayload = ""; - if (this.actionInstances.size() > 0) { - initialActionsScheduledPayload = "0, ".repeat(this.actionInstances.size()); - initialActionsScheduledPayload = initialActionsScheduledPayload.substring(0, initialActionsScheduledPayload.length()-2); - } else { - // Initialize a dummy variable just to make the code compile. - initialActionsScheduledPayload = "0"; - } - code.pr("// Helper macro that returns an element based on index."); - code.pr("define get(tr : trace_t, i : step_t) : event_t ="); - code.pr("if (i >= START || i <= END_TRACE) then tr[i] else"); - code.pr("{ " + initialReactions + ", inf(), { " + initialStates + " }, { " + initialTriggerPresence - + " }, {" + initialActionsScheduled + " }, {" + initialActionsScheduledPayload + "} };"); - - // Define an event getter from the trace. - code.pr(String.join("\n", - "define elem(i : step_t) : event_t", - "= get(trace, i);", - "", - "// projection macros", - "define rxn (i : step_t) : rxn_t = elem(i)._1;", - "define g (i : step_t) : tag_t = elem(i)._2;", - "define s (i : step_t) : state_t = elem(i)._3;", - "define t (i : step_t) : trigger_t = elem(i)._4;", - "define d (i : step_t) : sched_t = elem(i)._5;", - "define pl (i : step_t) : payload_t = elem(i)._6;", - "define isNULL (i : step_t) : boolean = rxn(i) == " + initialReactions + ";" - )); - } - - /** - * Type definitions for runtime reaction Ids and state variables - */ - protected void generateReactionIdsAndStateVars() { - // Encode the components and the logical delays - // present in a reactor system. - code.pr(String.join("\n", - "/**********************************", - " * Reaction IDs & State Variables *", - " *********************************/", - "", - "//////////////////////////", - "// Application Specific" - )); - - // Enumerate over all reactions. - code.pr(String.join("\n", - "// Reactions", - "type rxn_t = {" - )); - code.indent(); - for (var i = 0 ; i < this.reactionInstances.size(); i++) { - // Print a list of reaction IDs. - // Add a comma if not last. - code.pr("boolean" + ((i == this.reactionInstances.size() - 1) ? "" : ",") - + "\t// " + this.reactionInstances.get(i)); - } - code.unindent(); - code.pr("};\n"); - - // Generate projection macros. - code.pr("// Reaction projection macros"); - for (var i = 0 ; i < this.reactionInstances.size(); i++) { - code.pr("define " + this.reactionInstances.get(i).getReaction().getFullNameWithJoiner("_") - + "(n : rxn_t) : boolean = n._" + (i+1) + ";"); - } - code.pr("\n"); // Newline - - // State variables and triggers - // FIXME: expand to data types other than integer - code.pr("// State variables and triggers"); - code.pr("type state_t = {"); - code.indent(); - if (this.namedInstances.size() > 0) { - for (var i = 0 ; i < this.namedInstances.size(); i++) { - code.pr("integer" + ((i == this.namedInstances.size() - 1) ? "" : ",") - + "\t// " + this.namedInstances.get(i)); - } - } else { - code.pr(String.join("\n", - "// There are no ports or state variables.", - "// Insert a dummy integer to make the model compile.", - "integer" - )); - } - code.unindent(); - code.pr("};"); - code.pr("// State variable projection macros"); - for (var i = 0; i < this.namedInstances.size(); i++) { - code.pr("define " + this.namedInstances.get(i).getFullNameWithJoiner("_") - + "(s : state_t) : integer = s._" + (i+1) + ";"); - } - code.pr("\n"); // Newline - - // A boolean tuple indicating whether triggers are present. - code.pr("// A boolean tuple indicating whether triggers are present."); - code.pr("type trigger_t = {"); - code.indent(); - if (this.triggerInstances.size() > 0) { - for (var i = 0 ; i < this.triggerInstances.size(); i++) { - code.pr("boolean" + ((i == this.triggerInstances.size() - 1) ? "" : ",") - + "\t// " + this.triggerInstances.get(i)); - } - } else { - code.pr(String.join("\n", - "// There are no triggers.", - "// Insert a dummy boolean to make the model compile.", - "boolean" - )); - } - code.unindent(); - code.pr("};"); - code.pr("// Trigger presence projection macros"); - for (var i = 0; i < this.triggerInstances.size(); i++) { - code.pr("define " + this.triggerInstances.get(i).getFullNameWithJoiner("_") - + "_is_present" + "(t : trigger_t) : boolean = t._" + (i+1) + ";"); - } - - // A boolean tuple indicating whether actions are scheduled by reactions - // at the instant when they are triggered. - code.pr(String.join("\n", - "// A boolean tuple indicating whether actions are scheduled by reactions", - "// at the instant when they are triggered." - )); - code.pr("type sched_t = {"); - code.indent(); - if (this.actionInstances.size() > 0) { - for (var i = 0 ; i < this.actionInstances.size(); i++) { - code.pr("boolean" + ((i == this.actionInstances.size() - 1) ? "" : ",") - + "\t// " + this.actionInstances.get(i)); - } - } else { - code.pr(String.join("\n", - "// There are no actions.", - "// Insert a dummy boolean to make the model compile.", - "boolean" - )); - } - code.unindent(); - code.pr("};"); - code.pr("// Projection macros for action schedule flag"); - for (var i = 0; i < this.actionInstances.size(); i++) { - code.pr("define " + this.actionInstances.get(i).getFullNameWithJoiner("_") - + "_scheduled" + "(d : sched_t) : boolean = d._" + (i+1) + ";"); - } - - // A integer tuple indicating the integer payloads scheduled by reactions - // at the instant when they are triggered. - code.pr(String.join("\n", - "// A integer tuple indicating the integer payloads scheduled by reactions", - "// at the instant when they are triggered." - )); - code.pr("type payload_t = {"); - code.indent(); - if (this.actionInstances.size() > 0) { - for (var i = 0 ; i < this.actionInstances.size(); i++) { - code.pr("integer" + ((i == this.actionInstances.size() - 1) ? "" : ",") - + "\t// " + this.actionInstances.get(i)); - } - } else { - code.pr(String.join("\n", - "// There are no actions.", - "// Insert a dummy integer to make the model compile.", - "integer" - )); - } - code.unindent(); - code.pr("};"); - code.pr("// Projection macros for scheduled payloads"); - for (var i = 0; i < this.actionInstances.size(); i++) { - code.pr("define " + this.actionInstances.get(i).getFullNameWithJoiner("_") - + "_scheduled_payload" + "(payload : payload_t) : integer = payload._" + (i+1) + ";"); - } - } - - /** - * Axioms for reactor semantics - */ - protected void generateReactorSemantics() { - code.pr(String.join("\n", - "/*********************", - " * Reactor Semantics *", - " *********************/", - "" - )); - - // Non-federated "happened-before" - code.pr(String.join("\n", - "// Non-federated \"happened-before\"", - "define hb(e1, e2 : event_t) : boolean", - "= tag_earlier(e1._2, e2._2)" - )); - if (!this.logicalTimeBased) { - code.indent(); - // Get happen-before relation between two reactions. - code.pr("|| (tag_same(e1._2, e2._2) && ( false"); - // Iterate over reactions based on upstream/downstream relations. - for (var upstreamRuntime : this.reactionInstances) { - var downstreamReactions = upstreamRuntime.getReaction().dependentReactions(); - for (var downstream : downstreamReactions) { - // If the downstream reaction and the upstream - // reaction are in the same reactor, skip, since - // they will be accounted for in the for loop below. - if (downstream.getParent().equals(upstreamRuntime.getReaction().getParent())) continue; - for (var downstreamRuntime : downstream.getRuntimeInstances()) { - code.pr("|| (" + upstreamRuntime.getReaction().getFullNameWithJoiner("_") + "(e1._1)" - + " && " + downstreamRuntime.getReaction().getFullNameWithJoiner("_") + "(e2._1)" + ")"); - } - } - } - // Iterate over reactions based on priorities. - for (var reactor : this.reactorInstances) { - for (int i = 0; i < reactor.reactions.size(); i++) { - for (int j = i+1; j < reactor.reactions.size(); j++) { - code.pr("|| (" + reactor.reactions.get(i).getFullNameWithJoiner("_") + "(e1._1)" - + " && " + reactor.reactions.get(j).getFullNameWithJoiner("_") + "(e2._1)" + ")"); - } - } - } - code.unindent(); - code.pr("))"); - } - code.pr(";"); - - code.pr(String.join("\n", - "/** transition relation **/", - "// transition relation constrains future states", - "// based on previous states.", - "", - "// Events are ordered by \"happened-before\" relation.", - "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END_TRACE) ==> (finite_forall (j : integer) in indices ::", - " (j >= START && j <= END_TRACE) ==> (hb(elem(i), elem(j)) ==> i < j)));", - "", - "// Tags should be non-negative.", - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END_TRACE)", - " ==> pi1(g(i)) >= 0);", - "", - "// Microsteps should be non-negative.", - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END_TRACE)", - " ==> pi2(g(i)) >= 0);", - "", - "// Begin the frame at the start time specified.", - "define start_frame(i : step_t) : boolean =", - " (tag_same(g(i), {start_time, 0}) || tag_later(g(i), {start_time, 0}));", - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END_TRACE)", - " ==> start_frame(i));", - "", - "// NULL events should appear in the suffix, except for START.", - "axiom(finite_forall (j : integer) in indices :: (j > START && j <= END_TRACE) ==> (", - " !isNULL(j)) ==> ", - " (finite_forall (i : integer) in indices :: (i > START && i < j) ==> (!isNULL(i))));", - "" - )); - - //// Axioms for the event-based semantics - if (!this.logicalTimeBased) { - code.pr("//// Axioms for the event-based semantics"); - - // the same event can only trigger once in a logical instant - code.pr(String.join("\n", - "// the same event can only trigger once in a logical instant", - "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> (finite_forall (j : integer) in indices ::", - " (j >= START && j <= END) ==> ((rxn(i) == rxn(j) && i != j)", - " ==> !tag_same(g(i), g(j)))));", - "" - )); - - // Only one reaction gets triggered at a time. - ArrayList reactionsStatus = new ArrayList<>(); - for (int i = 0; i < this.reactionInstances.size(); i++) - reactionsStatus.add("false"); - code.pr(String.join("\n", - "// Only one reaction gets triggered at a time.", - "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> (", - " isNULL(i)")); - code.indent(); - for (int i = 0; i < this.reactionInstances.size(); i++) { - String[] li = reactionsStatus.toArray(String[]::new); - li[i] = "true"; - code.pr("|| rxn(i) == " + "{" + String.join(", ", li) + "}"); - } - code.unindent(); - code.pr("));"); - } - } - - /** - * Axioms for the trigger mechanism - */ - protected void generateTriggersAndReactions() { - code.pr(String.join("\n", - "/***************", - " * Connections *", - " ***************/" - )); - // FIXME: Support banks and multiports. Figure out how to work with ranges. - // Iterate over all the ports. Generate an axiom for each connection - // (i.e. a pair of input-output ports). - // A "range" holds the connection information. - // See connectPortInstances() in ReactorInstance.java for more details. - for (var port : this.portInstances) { - for (SendRange range : port.getDependentPorts()) { - PortInstance source = range.instance; - Connection connection = range.connection; - List> destinations = range.destinations; - - // Extract delay value - long delay = 0; - if (connection.getDelay() != null) { - // Somehow delay is an Expression, - // which makes it hard to convert to nanoseconds. - Expression delayExpr = connection.getDelay(); - if (delayExpr instanceof Time) { - long interval = ((Time) delayExpr).getInterval(); - String unit = ((Time) delayExpr).getUnit(); - TimeValue timeValue = new TimeValue(interval, TimeUnit.fromName(unit)); - delay = timeValue.toNanoSeconds(); - } - } - - for (var portRange : destinations) { - var destination = portRange.instance; - - // We have extracted the source, destination, and connection AST node. - // Now we are ready to generate an axiom for this triple. - code.pr("// " + source.getFullNameWithJoiner("_") + " " - + (connection.isPhysical() ? "~>" : "->") + " " - + destination.getFullNameWithJoiner("_")); - code.pr(String.join("\n", - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", - "// If " + source.getFullNameWithJoiner("_") + " is present, then " - + destination.getFullNameWithJoiner("_") + " will be present.", - "// with the same value after some fixed delay of " + delay + " nanoseconds.", - "(" + source.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> ((", - " finite_exists (j : integer) in indices :: j > i && j <= END", - " && " + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(j))", - " && " + destination.getFullNameWithJoiner("_") + "(s(j)) == " + source.getFullNameWithJoiner("_") + "(s(i))", - connection.isPhysical() ? "" : "&& g(j) == tag_delay(g(i), " + delay + ")", - ")", - ")) // Closes (" + source.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> ((.", - //// Separator - "// If " + destination.getFullNameWithJoiner("_") + " is present, there exists an " + source.getFullNameWithJoiner("_") + " in prior steps.", - "// This additional term establishes a one-to-one relationship between two ports' signals.", - "&& (" + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", - " finite_exists (j : integer) in indices :: j >= START && j < i", - " && " + source.getFullNameWithJoiner("_") + "_is_present" + "(t(j))", - connection.isPhysical() ? "" : " && g(i) == tag_delay(g(j), " + delay + ")", - ")) // Closes the one-to-one relationship.", - "));" - )); - - // If destination is not present, then its value resets to 0. - code.pr(String.join("\n", - "// If " + destination.getFullNameWithJoiner("_") + " is not present, then its value resets to 0.", - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END && !isNULL(i)) ==> (", - " (!" + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", - " " + destination.getFullNameWithJoiner("_") + "(s(i)) == 0", - " ))", - "));" - )); - } - } - } - - if (this.actionInstances.size() > 0) { - code.pr(String.join("\n", - "/***********", - " * Actions *", - " ***********/" - )); - for (var action : this.actionInstances) { - Set dependsOnReactions = action.getDependsOnReactions(); - String comment = "If " + action.getFullNameWithJoiner("_") - + " is present, these reactions could schedule it: "; - String triggerStr = ""; - for (var reaction : dependsOnReactions) { - comment += reaction.getFullNameWithJoiner("_") + ", "; - triggerStr += String.join("\n", - "// " + reaction.getFullNameWithJoiner("_"), - // OR because only any present trigger can trigger the reaction. - "|| (" + action.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", - " finite_exists (j : integer) in indices :: j >= START && j < i", - " && " + reaction.getFullNameWithJoiner("_") + "(rxn(j))", - " && g(i) == tag_schedule(g(j), " + action.getMinDelay().toNanoSeconds() + ")", - " && " + action.getFullNameWithJoiner("_") + "_scheduled" + "(d(j))", - " && " + action.getFullNameWithJoiner("_") + "(s(i))" + " == " + action.getFullNameWithJoiner("_") + "_scheduled_payload" + "(pl(j))", - "))" - ); - } - - // After populating the string segments, - // print the generated code string. - code.pr(String.join("\n", - "// " + comment, - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> ( false", - triggerStr, - "));" - )); - - // If the action is not present, then its value resets to 0. - code.pr(String.join("\n", - "// If " + action.getFullNameWithJoiner("_") + " is not present, then its value resets to 0.", - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END && !isNULL(i)) ==> (", - " (!" + action.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", - " " + action.getFullNameWithJoiner("_") + "(s(i)) == 0", - " ))", - "));" - )); - } - } - - if (this.timerInstances.size() > 0) { - code.pr(String.join("\n", - "/**********", - " * Timers *", - " **********/" - )); - /** - * The timer axioms take the following form: - * - * // An initial firing at {offset, 0} - * axiom( - * ((g(END)._1 >= 500000000) ==> ( - * finite_exists (j : integer) in indices :: (j > START && j <= END) - * && Timer_t_is_present(t(j)) - * && tag_same(g(j), {500000000, 0}) - * )) - * && ((g(END)._1 < 500000000) ==> ( - * finite_forall (i : integer) in indices :: (i > START && i <= END) - * ==> (!isNULL(i)) - * )) - * ); - * // Schedule subsequent firings. - * axiom( - * finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> ( - * Timer_t_is_present(t(i)) ==> ( - * ( - * finite_exists (j : integer) in indices :: (j >= START && j > i) - * && Timer_t_is_present(t(j)) - * && (g(j) == tag_schedule(g(i), 1000000000)) - * ) - * ) - * ) - * ); - * // All firings must be evenly spaced out. - * axiom( - * finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> ( - * Timer_t_is_present(t(i)) ==> ( - * // Timestamp must be offset + n * period - * ( - * exists (n : integer) :: ( - * n >= 0 && - * g(i)._1 == 500000000 + n * 1000000000 - * ) - * ) - * // Microstep must be 0 - * && ( - * g(i)._2 == 0 - * ) - * ) - * ) - * ); - */ - for (var timer : this.timerInstances) { - long offset = timer.getOffset().toNanoSeconds(); - long period = timer.getPeriod().toNanoSeconds(); - - code.pr("//// Axioms for " + timer.getFullName()); - - // An initial firing at {offset, 0} - code.pr(String.join("\n", - "// " + timer.getFullName() + ": an initial firing at (" + offset + ", 0)", - "axiom(", - " ((pi1(g(END)) >= " + offset + ") ==> (", - " finite_exists (j : integer) in indices :: (j > START && j <= END)", - " && " + timer.getFullNameWithJoiner("_") + "_is_present(t(j))", - " && tag_same(g(j), {" + offset + ", 0})", - " ))", - " && ((pi1(g(END)) < " + offset + ") ==> (", - " finite_forall (i : integer) in indices :: (i > START && i <= END)", - " ==> (!isNULL(i))", - " ))", - ");" - )); - - // Schedule subsequent firings. - code.pr(String.join("\n", - "// " + timer.getFullName() + ": schedule subsequent firings every " + period + " ns", - "axiom(", - " finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> (", - " " + timer.getFullNameWithJoiner("_") + "_is_present(t(i)) ==> (", - " (", - " finite_exists (j : integer) in indices :: (j >= START && j > i)", - " && " + timer.getFullNameWithJoiner("_") + "_is_present(t(j))", - " && (g(j) == tag_schedule(g(i), " + period + "))", - " )", - " )", - " )", - ");" - )); - - // All firings must be evenly spaced out. - code.pr(String.join("\n", - "// " + timer.getFullName() + ": all firings must be evenly spaced out.", - "axiom(", - " finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> (", - " " + timer.getFullNameWithJoiner("_") + "_is_present(t(i)) ==> (", - " // Timestamp must be offset + n * period.", - " (", - " exists (n : integer) :: (", - " n >= 0 &&", - " pi1(g(i)) == " + offset + " + n * " + period, - " )", - " )", - " // Microstep must be 0.", - " && (pi2(g(i)) == 0)", - " )", - " )", - ");" - )); - } - } - - code.pr(String.join("\n", - "/********************************", - " * Reactions and Their Triggers *", - " ********************************/" - )); - // Iterate over all reactions, generate conditions for them - // to be triggered. - outerLoop: - for (ReactionInstance.Runtime reaction : this.reactionInstances) { - String str = String.join("\n", - "// " + reaction.getReaction().getFullNameWithJoiner("_") + " is invoked when any of it triggers are present.", - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END_TRACE) ==> ((", - " false" - ); - - // Iterate over the triggers of the reaction. - for (TriggerInstance trigger : reaction.getReaction().triggers) { - String triggerPresentStr = ""; - - if (trigger.isStartup()) { - // FIXME: Treat startup as a variable. - // triggerPresentStr = "g(i) == zero()"; - - // Handle startup. - code.pr(String.join("\n", - "// If startup is within frame (and all variables have default values),", - "// " + reaction.getReaction().getFullNameWithJoiner("_") + " must be invoked.", - "axiom(", - " ((start_time == 0) ==> (", - " finite_exists (i : integer) in indices :: i > START && i <= END", - " && " + reaction.getReaction().getFullNameWithJoiner("_") + "(rxn(i))" + " && tag_same(g(i), zero())", - " && !(", - " finite_exists (j : integer) in indices :: j > START && j <= END", - " && " + reaction.getReaction().getFullNameWithJoiner("_") + "(rxn(j))", - " && j != i", - " )", - " ))", - ");" - )); - continue outerLoop; - } else if (trigger.isShutdown()) { - // FIXME: For a future version. - System.out.println("Not implemented!"); - } - else { - // If the trigger is a port/action/timer. - triggerPresentStr = trigger.getFullNameWithJoiner("_") + "_is_present" + "(t(i))"; - } - - // Check if the trigger triggers other reactions. - // If so, we need to assert in the axioms that - // the other reactions are excluded (not invoked), - // to preserve the interleaving semantics. - String exclusion = ""; - for (var instance : trigger.getDependentReactions()) { - for (var runtime : ((ReactionInstance)instance).getRuntimeInstances()) { - if (runtime == reaction) continue; // Skip the current reaction. - exclusion += " && !" + runtime.getReaction().getFullNameWithJoiner("_") + "(rxn(i))"; - } - } - - // code.pr("|| (" + triggerPresentStr + exclusion + ")"); - str += "\n|| (" + triggerPresentStr + exclusion + ")"; - } - - // If any of the above trigger is present, then trigger the reaction. - str += "\n) <==> (" + reaction.getReaction().getFullNameWithJoiner("_") + "(rxn(i))" + ")));"; - code.pr(str); - } - } - - /** - * Macros for initial conditions - */ - protected void generateInitialConditions() { - code.pr(String.join("\n", - "/*********************", - " * Initial Condition *", - " *********************/", - "define initial_condition() : boolean", - "= start_time == 0", - " && isNULL(0)", - " && g(0) == {0, 0}" - )); - code.indent(); - for (var v : this.stateVariables) { - code.pr("&& " + v.getFullNameWithJoiner("_") + "(s(0))" + " == " + "0"); - } - for (var t : this.triggerInstances) { - code.pr("&& " + t.getFullNameWithJoiner("_") + "(s(0))" + " == " + "0"); - } - for (var t : this.triggerInstances) { - code.pr("&& !" + t.getFullNameWithJoiner("_") + "_is_present" + "(t(0))"); - } - for (var d : this.actionInstances) { - code.pr("&& !" + d.getFullNameWithJoiner("_") + "_scheduled" + "(d(0))"); - } - code.unindent(); - code.pr(";\n"); - } - - /** - * Lift reaction bodies into Uclid axioms. - */ - protected void generateReactionAxioms() { - code.pr(String.join("\n", - "/*************", - " * Reactions *", - " *************/" - )); - - for (ReactionInstance.Runtime reaction : this.reactionInstances) { - String body = reaction.getReaction().getDefinition().getCode().getBody(); - // System.out.println("DEBUG: Printing reaction body of " + reaction); - // System.out.println(body); - - // Generate a parse tree. - CLexer lexer = new CLexer(CharStreams.fromString(body)); - CommonTokenStream tokens = new CommonTokenStream(lexer); - CParser parser = new CParser(tokens); - BlockItemListContext parseTree = parser.blockItemList(); - - // Build an AST. - BuildAstParseTreeVisitor buildAstVisitor = new BuildAstParseTreeVisitor(); - CAst.AstNode ast = buildAstVisitor.visitBlockItemList(parseTree); - - // VariablePrecedenceVisitor - VariablePrecedenceVisitor precVisitor = new VariablePrecedenceVisitor(); - precVisitor.visit(ast); - - // Convert the AST to If Normal Form (INF). - IfNormalFormAstVisitor infVisitor = new IfNormalFormAstVisitor(); - infVisitor.visit(ast, new ArrayList()); - CAst.StatementSequenceNode inf = infVisitor.INF; - - // For the variables that are USED inside this reaction, extract the conditions - // for setting them, and take the negation of their conjunction - // to get the condition for maintaining their values. - List unusedStates = new ArrayList<>(this.stateVariables); - List unusedOutputs = new ArrayList<>(this.outputInstances); - List unusedActions = new ArrayList<>(this.actionInstances); - HashMap> defaultBehaviorConditions = new HashMap<>(); - for (CAst.AstNode node : inf.children) { - CAst.IfBlockNode ifBlockNode = (CAst.IfBlockNode)node; - // Under the INF form, a C statement is - // the THEN branch of an IF block. - CAst.AstNode stmt = ((CAst.IfBodyNode)ifBlockNode.right).left; - NamedInstance instance = null; - // Match stmt with different cases - if ((stmt instanceof CAst.AssignmentNode - && ((CAst.AssignmentNode)stmt).left instanceof CAst.StateVarNode)) { - CAst.StateVarNode n = (CAst.StateVarNode)((CAst.AssignmentNode)stmt).left; - instance = reaction.getReaction().getParent().states.stream() - .filter(s -> s.getName().equals(n.name)).findFirst().get(); - unusedStates.remove(instance); - } else if (stmt instanceof CAst.SetPortNode) { - CAst.SetPortNode n = (CAst.SetPortNode)stmt; - String name = ((CAst.VariableNode)n.left).name; - instance = reaction.getReaction().getParent().outputs.stream() - .filter(s -> s.getName().equals(name)).findFirst().get(); - unusedOutputs.remove(instance); - } else if (stmt instanceof CAst.ScheduleActionNode) { - CAst.ScheduleActionNode n = (CAst.ScheduleActionNode)stmt; - String name = ((CAst.VariableNode)n.children.get(0)).name; - instance = reaction.getReaction().getParent().actions.stream() - .filter(s -> s.getName().equals(name)).findFirst().get(); - unusedActions.remove(instance); - } else if (stmt instanceof CAst.ScheduleActionIntNode) { - CAst.ScheduleActionIntNode n = (CAst.ScheduleActionIntNode)stmt; - String name = ((CAst.VariableNode)n.children.get(0)).name; - instance = reaction.getReaction().getParent().actions.stream() - .filter(s -> s.getName().equals(name)).findFirst().get(); - unusedStates.remove(instance); - unusedActions.remove(instance); - } else continue; - // Create a new entry in the list if there isn't one yet. - if (defaultBehaviorConditions.get(instance) == null) { - defaultBehaviorConditions.put(instance, new ArrayList()); - } - defaultBehaviorConditions.get(instance).add(ifBlockNode.left); - // System.out.println("DEBUG: Added a reset condition: " + ifBlockNode.left); - } - - // Generate Uclid axiom for the C AST. - CToUclidVisitor c2uVisitor = new CToUclidVisitor(this, reaction); - String axiom = c2uVisitor.visit(inf); - code.pr(String.join("\n", - "// Reaction body of " + reaction, - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", - " (" + reaction.getReaction().getFullNameWithJoiner("_") + "(rxn(i))" + ")", - " ==> " + "(" + "(" + axiom + ")", - "&& " + "( " + "true" - )); - for (NamedInstance key : defaultBehaviorConditions.keySet()) { - CAst.AstNode disjunction = AstUtils.takeDisjunction(defaultBehaviorConditions.get(key)); - CAst.LogicalNotNode notNode = new CAst.LogicalNotNode(); - notNode.child = disjunction; - String resetCondition = c2uVisitor.visit(notNode); - - // Check for invalid reset conditions. - // If found, stop the execution. - // FIXME: A more systematic check is needed - // to ensure that the generated Uclid file - // is valid. - try { - if (resetCondition.contains("null")) { - throw new Exception("Null detected in a reset condition. Stop."); - } - } catch (Exception e) { - Exceptions.sneakyThrow(e); - } - code.pr("// Unused state variables and ports are reset by default."); - code.pr("&& " + "(" + "(" + resetCondition + ")" + " ==> " + "("); - if (key instanceof StateVariableInstance) { - StateVariableInstance n = (StateVariableInstance)key; - code.pr(n.getFullNameWithJoiner("_") + "(" + "s" + "(" + "i" + ")" + ")" - + " == " - + n.getFullNameWithJoiner("_") + "(" + "s" + "(" + "i" + "-1" + ")" + ")"); - } else if (key instanceof PortInstance) { - PortInstance n = (PortInstance)key; - code.pr( - "(" - + " true" - // Reset value - + "\n&& " + n.getFullNameWithJoiner("_") + "(" + "s" + "(" + "i" + ")" + ")" - + " == " - + "0" // Default value - // Reset presence - + "\n&& " + n.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + "i" + ")" + ")" - + " == " - + "false" // default presence - + ")" - ); - } else if (key instanceof ActionInstance) { - ActionInstance n = (ActionInstance)key; - code.pr(n.getFullNameWithJoiner("_") + "_scheduled" + "(" + "d" + "(" + "i" + ")" + ")" + " == " + "false"); - } else { - System.out.println("Unreachable!"); - } - code.pr("))"); - } - - // For state variables and ports that are NOT used in this reaction, - // their values stay the same by default. - code.pr("// By default, the value of the variables used in this reaction stay the same."); - if (this.logicalTimeBased) { - // If all other reactions that can modify the SAME state variable - // are not triggered, then the state variable stay the same. - // - // FIXME: What if two reactions modifying the same state variable - // are triggered at the same time? - // How to use axioms to model reaction priority? - // The main difficulty of logical time based semantics is composing - // the effect of simultaneous reactions. - // - // A path way to implement it in the future: - // 1. For each variable, port, and action, determine a list of - // reactions that can modify/schedule it. - // 2. Reaction axioms should be generated wrt each reactor. - // For example, assuming a reactor with two input ports, - // each triggering a distinct reaction. The axioms will need - // to handle four cases: i. reaction 1 gets triggered and 2 - // does not; ii. reaction 2 gets triggered and 1 does not; - // iii. both reactions get triggered; iv. none of them get - // triggered. Since it is hard to specify in an independent way, - // due to reaction priorities, - // what happens when two reactions (modifying the same state var.) - // get triggered simultaneously, some combinatorial blowup will - // be incurred. In this example, four axioms (instead of two), - // each handling one case, seems needed. The good news is that - // axioms across reactors may be specified independently. - // For example, if there is another reactor of the same class, - // Only four more axioms need to be added (in total 2^2 + 2^2), - // instead of 16 axioms (2^4). - } else { - for (StateVariableInstance s : unusedStates) { - code.pr("&& (true ==> ("); - code.pr(s.getFullNameWithJoiner("_") + "(" + "s" + "(" + "i" + ")" + ")" - + " == " - + s.getFullNameWithJoiner("_") + "(" + "s" + "(" + "i" + "-1" + ")" + ")"); - code.pr("))"); - } - for (PortInstance p : unusedOutputs) { - code.pr("&& (true ==> ("); - code.pr( - "(" - + " true" - // Reset value - + "\n&& " + p.getFullNameWithJoiner("_") + "(" + "s" + "(" + "i" + ")" + ")" - + " == " - + "0" // Default value - // Reset presence - + "\n&& " + p.getFullNameWithJoiner("_") + "_is_present" + "(" + "t" + "(" + "i" + ")" + ")" - + " == " - + false // default presence - + ")" - ); - code.pr("))"); - } - for (ActionInstance a : unusedActions) { - code.pr("&& (true ==> ("); - code.pr(a.getFullNameWithJoiner("_") + "_scheduled" + "(" + "d" + "(" + "i" + ")" + ")" + " == " + "false"); - code.pr("))"); - } - code.pr("))));"); - } - } - } - - protected void generateProperty() { - code.pr(String.join("\n", - "/************", - " * Property *", - " ************/" - )); - - code.pr("// The FOL property translated from user-defined MTL property:"); - code.pr("// " + this.spec); - code.pr("define p(i : step_t) : boolean ="); - code.indent(); - code.pr(this.FOLSpec + ";"); - code.unindent(); - - if (this.tactic.equals("bmc")) { - code.pr(String.join("\n", - "// BMC", - "property " + "bmc_" + this.name + " : " + "initial_condition() ==> p(0);" - )); - } else { - code.pr(String.join("\n", - "// Induction: initiation step", - "property " + "initiation_" + this.name + " : " + "initial_condition() ==> p(0);", - "// Induction: consecution step", - "property " + "consecution_" + this.name + " : " + "p(0) ==> p(1);" - )); - } - } - - /** - * Uclid5 control block - */ - protected void generateControlBlock() { - code.pr(String.join("\n", - "control {", - " v = bmc(0);", - " check;", - " print_results;", - " v.print_cex_json;", - "}" - )); - } - - //////////////////////////////////////////////////////////// - //// 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 is done once because - // it is the same for all federates. - 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; - } - } - } - } - - private void setupDirectories() { - // Make sure the target directory exists. - Path modelGenDir = context.getFileConfig().getModelGenPath(); - this.outputDir = Paths.get(modelGenDir.toString()); - try { - Files.createDirectories(outputDir); - } catch (IOException e) { - Exceptions.sneakyThrow(e); - } - System.out.println("The models will be located in: " + outputDir); - } - - /** - * Populate the data structures. - */ - private void populateDataStructures() { - // Populate lists of reactor/reaction instances, - // state variables, actions, ports, and timers. - populateLists(this.main); - - // Join actions, ports, and timers into a list of triggers. - this.triggerInstances = new ArrayList(this.actionInstances); - this.triggerInstances.addAll(portInstances); - this.triggerInstances.addAll(timerInstances); - - // Join state variables and triggers - this.namedInstances = new ArrayList(this.stateVariables); - namedInstances.addAll(this.triggerInstances); - } - - private void populateLists(ReactorInstance reactor) { - // Reactor and reaction instances - this.reactorInstances.add(reactor); - for (var reaction : reactor.reactions) { - this.reactionInstances.addAll(reaction.getRuntimeInstances()); - } - - // State variables, actions, ports, timers. - for (var state : reactor.states) { - this.stateVariables.add(state); - } - for (var action : reactor.actions) { - this.actionInstances.add(action); - } - for (var port : reactor.inputs) { - this.inputInstances.add(port); - this.portInstances.add(port); - } - for (var port : reactor.outputs) { - this.outputInstances.add(port); - this.portInstances.add(port); - } - for (var timer : reactor.timers) { - this.timerInstances.add(timer); - } - - // Recursion - for (var child : reactor.children) { - populateLists(child); - } - } - - /** - * Compute a completeness threadhold for each property - * by simulating a worst-case execution by traversing - * the reactor instance graph and building a - * state space diagram. - */ - private void computeCT() { - - StateSpaceExplorer explorer = new StateSpaceExplorer(this.main); - explorer.explore( - new Tag(this.horizon, 0, false), - true // findLoop - ); - StateSpaceDiagram diagram = explorer.diagram; - diagram.display(); - - // Generate a dot file. - try { - CodeBuilder dot = diagram.generateDot(); - Path file = this.outputDir.resolve(this.tactic + "_" + this.name + ".dot"); - String filename = file.toString(); - dot.writeToFile(filename); - } catch (IOException e) { - Exceptions.sneakyThrow(e); - } - - //// Compute CT - if (!explorer.loopFound) { - if (this.logicalTimeBased) - this.CT = diagram.nodeCount(); - else { - // FIXME: This could be much more efficient with - // a linkedlist implementation. We can go straight - // to the next node. - StateSpaceNode node = diagram.head; - this.CT = diagram.head.reactionsInvoked.size(); - while (node != diagram.tail) { - node = diagram.getDownstreamNode(node); - this.CT += node.reactionsInvoked.size(); - } - } - } - // Over-approximate CT by estimating the number of loop iterations required. - else { - // Subtract the non-periodic logical time - // interval from the total horizon. - long horizonRemained = - Math.subtractExact(this.horizon, diagram.loopNode.tag.timestamp); - - // Check how many loop iteration is required - // to check the remaining horizon. - int loopIterations = 0; - if (diagram.loopPeriod == 0 && horizonRemained != 0) - Exceptions.sneakyThrow(new Exception( - "ERROR: Zeno behavior detected while the horizon is non-zero. The program has no finite CT.")); - else if (diagram.loopPeriod == 0 && horizonRemained == 0) { - // Handle this edge case. - Exceptions.sneakyThrow(new Exception( - "Unhandled case: both the horizon and period are 0!")); - } - else { - loopIterations = (int) Math.ceil( - (double) horizonRemained / diagram.loopPeriod); - } - - if (this.logicalTimeBased) { - /* - CT = steps required for the non-periodic part - + steps required for the periodic part - - this.CT = (diagram.loopNode.index + 1) - + (diagram.tail.index - diagram.loopNode.index + 1) * loopIterations; - - An overflow-safe version of the line above - */ - int t0 = Math.addExact(diagram.loopNode.index, 1); - int t1 = Math.subtractExact(diagram.tail.index, diagram.loopNode.index); - int t2 = Math.addExact(t1, 1); - int t3 = Math.multiplyExact(t2, loopIterations); - this.CT = Math.addExact(t0, t3); - - } else { - // Get the number of events before the loop starts. - // This stops right before the loopNode is encountered. - StateSpaceNode node = diagram.head; - int numReactionInvocationsBeforeLoop = 0; - while (node != diagram.loopNode) { - numReactionInvocationsBeforeLoop += node.reactionsInvoked.size(); - node = diagram.getDownstreamNode(node); - } - // Account for the loop node in numReactionInvocationsBeforeLoop. - numReactionInvocationsBeforeLoop += node.reactionsInvoked.size(); - - // Count the events from the loop node until - // loop node is reached again. - int numReactionInvocationsInsideLoop = 0; - do { - node = diagram.getDownstreamNode(node); - numReactionInvocationsInsideLoop += node.reactionsInvoked.size(); - } while (node != diagram.loopNode); - - /* - CT = steps required for the non-periodic part - + steps required for the periodic part - - this.CT = numReactionInvocationsBeforeLoop - + numReactionInvocationsInsideLoop * loopIterations; - - An overflow-safe version of the line above - */ - // System.out.println("DEBUG: numReactionInvocationsBeforeLoop: " + numReactionInvocationsBeforeLoop); - // System.out.println("DEBUG: numReactionInvocationsInsideLoop: " + numReactionInvocationsInsideLoop); - // System.out.println("DEBUG: loopIterations: " + loopIterations); - int t0 = Math.multiplyExact(numReactionInvocationsInsideLoop, loopIterations); - this.CT = Math.addExact(numReactionInvocationsBeforeLoop, t0); - } - } - } - - /** - * Process an MTL property. - */ - private void processMTLSpec() { - MTLLexer lexer = new MTLLexer(CharStreams.fromString(this.spec)); - CommonTokenStream tokens = new CommonTokenStream(lexer); - MTLParser parser = new MTLParser(tokens); - MtlContext mtlCtx = parser.mtl(); - MTLVisitor visitor = new MTLVisitor(this.tactic); - - // The visitor transpiles the MTL into a Uclid axiom. - this.FOLSpec = visitor.visitMtl(mtlCtx, "i", 0, "0", 0); - this.horizon = visitor.getHorizon(); - } - - ///////////////////////////////////////////////// - //// Functions from generatorBase - - @Override - public Target getTarget() { - return Target.C; // Works with a C subset. - } - - @Override - public TargetTypes getTargetTypes() { - throw new UnsupportedOperationException("TODO: auto-generated method stub"); - } -} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java b/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java deleted file mode 100644 index 2469e2da8c..0000000000 --- a/org.lflang/src/org/lflang/analyses/uclid/UclidRunner.java +++ /dev/null @@ -1,264 +0,0 @@ -/** - * (EXPERIMENTAL) Runner for Uclid5 models. - * - * - */ -package org.lflang.analyses.uclid; - -import java.io.IOException; -import java.io.OutputStream; - -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import org.lflang.ErrorReporter; -import org.lflang.FileConfig; -import org.lflang.analyses.statespace.StateInfo; -import org.lflang.analyses.statespace.Tag; -import org.lflang.generator.GeneratorCommandFactory; -import org.lflang.util.LFCommand; - -public class UclidRunner { - - /** - * A list of paths to the generated files - */ - List filePaths; - - /** - * The directory where the generated files are placed - */ - public Path outputDir; - - /** - * A factory for compiler commands. - */ - GeneratorCommandFactory commandFactory; - - /** - * A UclidGenerator instance - */ - UclidGenerator generator; - - // Constructor - public UclidRunner(UclidGenerator generator) { - this.generator = generator; - this.commandFactory = - new GeneratorCommandFactory( - generator.context.getErrorReporter(), - generator.context.getFileConfig()); - } - - /** - * Parse information from an SMT model - * for a step in the trace. - */ - public StateInfo parseStateInfo(String smtStr) { - StateInfo info = new StateInfo(); - - // Check for any let bindings. - Pattern p = Pattern.compile("\\(let \\(\\((a!\\d+) \\(_tuple_\\d+ ((.)+?)\\)\\)\\)(\\\\n|\\s)+\\(_tuple_\\d+ ((.|\\n)+)\\)"); - HashMap symbolTable = new HashMap<>(); - Matcher m = p.matcher(smtStr.strip()); - String itemized = ""; - if (m.find()) { - // FIXME: Handle multiple let bindings. - String symbol = m.group(1).strip(); - String value = m.group(2).strip(); - symbolTable.put(symbol, value); - itemized = m.group(5).strip(); - } - else { - // Remove the outer tuple layer. - p = Pattern.compile("\\(_tuple_\\d+((.|\\n)*)\\)"); - m = p.matcher(smtStr.strip()); - m.find(); - itemized = m.group(1).strip(); - } - - // Process each sub-tuple by matching (_tuple_n ...) - // or matching a!n, where n is an integer. - p = Pattern.compile("(a!\\d+)|\\(_tuple_\\d+((\\s|\\\\n|\\d|\\(- \\d+\\)|true|false)+)\\)"); - m = p.matcher(itemized); - - // Reactions - m.find(); - String reactionsStr = ""; - // Found a let binding. - if (m.group(1) != null) { - reactionsStr = symbolTable.get(m.group(1)).strip(); - } - // The rest falls into group 2. - else { - reactionsStr = m.group(2).strip(); - } - String[] reactions = reactionsStr.split("\\s+"); - // Iterating over generator lists avoids accounting for - // the single dummy Uclid variable inserted earlier. - for (int i = 0; i < generator.reactionInstances.size(); i++) { - if (reactions[i].equals("true")) - info.reactions.add(generator.reactionInstances.get(i).getReaction().getFullName()); - } - - // Time tag - m.find(); - String tagStr = ""; - // Found a let binding. - if (m.group(1) != null) - tagStr = symbolTable.get(m.group(1)).strip(); - // The rest falls into group 2. - else tagStr = m.group(2).strip(); - String[] tag = tagStr.split("\\s+"); - info.tag = new Tag( - Long.parseLong(tag[0]), - Long.parseLong(tag[1]), false); - - // Variables - // Currently all integers. - // Negative numbers could appear. - m.find(); - String variablesStr = ""; - // Found a let binding. - if (m.group(1) != null) - variablesStr = symbolTable.get(m.group(1)).strip(); - // The rest falls into group 2. - else variablesStr = m.group(2).strip(); - String[] variables = variablesStr.replaceAll("\\(-\\s(.*?)\\)", "-$1") - .split("\\s+"); - for (int i = 0; i < generator.namedInstances.size(); i++) { - info.variables.put(generator.namedInstances.get(i).getFullName(), variables[i]); - } - - // Triggers - m.find(); - String triggersStr = ""; - // Found a let binding. - if (m.group(1) != null) - triggersStr = symbolTable.get(m.group(1)).strip(); - // The rest falls into group 2. - else triggersStr = m.group(2).strip(); - String[] triggers = triggersStr.split("\\s+"); - for (int i = 0; i < generator.triggerInstances.size(); i++) { - info.triggers.put(generator.triggerInstances.get(i).getFullName(), triggers[i]); - } - - // Actions scheduled - m.find(); - String scheduledStr = ""; - // Found a let binding. - if (m.group(1) != null) - scheduledStr = symbolTable.get(m.group(1)).strip(); - // The rest falls into group 2. - else scheduledStr = m.group(2).strip(); - String[] scheduled = scheduledStr.split("\\s+"); - for (int i = 0; i < generator.actionInstances.size(); i++) { - info.scheduled.put(generator.actionInstances.get(i).getFullName(), scheduled[i]); - } - - // Scheduled payloads - // Currently all integers. - // Negative numbers could appear. - m.find(); - String payloadsStr = ""; - // Found a let binding. - if (m.group(1) != null) - payloadsStr = symbolTable.get(m.group(1)).strip(); - // The rest falls into group 2. - else payloadsStr = m.group(2).strip(); - String[] payloads = payloadsStr - .replaceAll("\\(-\\s(.*?)\\)", "-$1") - .split("\\s+"); - for (int i = 0; i < generator.actionInstances.size(); i++) { - info.payloads.put(generator.actionInstances.get(i).getFullName(), payloads[i]); - } - - return info; - } - - /** - * Run all the generated Uclid models, report outputs, - * and generate counterexample trace diagrams. - */ - public void run() { - for (Path path : generator.generatedFiles) { - // Execute uclid for each property. - LFCommand command = commandFactory.createCommand( - "uclid", List.of( - path.toString(), - // Any counterexample will be in .json - "--json-cex", path.toString()), - generator.outputDir); - command.run(); - - String output = command.getOutput().toString(); - boolean valid = !output.contains("FAILED"); - if (valid) { - System.out.println("Valid!"); - } else { - System.out.println("Not valid!"); - try { - // Read from the JSON counterexample (cex). - String cexJSONStr = Files.readString( - Paths.get(path.toString() + ".json"), - StandardCharsets.UTF_8); - JSONObject cexJSON = new JSONObject(cexJSONStr); - - //// Extract the counterexample trace from JSON. - // Get the first key "property_*" - Iterator keys = cexJSON.keys(); - String firstKey = keys.next(); - JSONObject propertyObj = cexJSON.getJSONObject(firstKey); - - // Get Uclid trace. - JSONArray uclidTrace = propertyObj.getJSONArray("trace"); - - // Get the first step of the Uclid trace. - JSONObject uclidTraceStepOne = uclidTrace.getJSONObject(0); - - // Get the actual trace defined in the verification model. - JSONObject trace = uclidTraceStepOne.getJSONArray("trace").getJSONObject(0); - - String stepStr = ""; - for (int i = 0; i <= generator.CT; i++) { - try { - stepStr = trace.getString(String.valueOf(i)); - } catch(JSONException e) { - stepStr = trace.getString("-"); - } - System.out.println("============ Step " + i + " ============"); - StateInfo info = parseStateInfo(stepStr); - info.display(); - } - } catch (IOException e) { - System.out.println("ERROR: Not able to read from " + path.toString()); - } - } - - // If "expect" is set, check if the result matches it. - // If not, exit with error code 1. - String expect = generator.expectations.get(path); - if (expect != null) { - boolean expectValid = Boolean.parseBoolean(expect); - if (expectValid != valid) { - System.out.println( - "ERROR: The expected result does not match the actual result. Expected: " - + expectValid + ", Result: " + valid); - System.exit(1); - } - } - } - } -} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/cli/Lfc.java b/org.lflang/src/org/lflang/cli/Lfc.java deleted file mode 100644 index adf04be4e5..0000000000 --- a/org.lflang/src/org/lflang/cli/Lfc.java +++ /dev/null @@ -1,286 +0,0 @@ -package org.lflang.cli; - - -import java.nio.file.Path; -import java.util.List; -import java.util.Properties; - -import picocli.CommandLine.Command; -import picocli.CommandLine.Option; -import org.eclipse.emf.ecore.resource.Resource; -import org.eclipse.xtext.generator.GeneratorDelegate; -import org.eclipse.xtext.generator.JavaIoFileSystemAccess; -import org.eclipse.xtext.util.CancelIndicator; - -import org.lflang.ASTUtils; -import org.lflang.FileConfig; - -import org.lflang.generator.LFGeneratorContext; -import org.lflang.generator.LFGeneratorContext.BuildParm; -import org.lflang.generator.MainContext; - -import com.google.inject.Inject; - -/** - * Standalone version of the Lingua Franca compiler (lfc). - * - * @author Marten Lohstroh - * @author Christian Menard - * @author Atharva Patil - */ -@Command( - name = "lfc", - // Enable usageHelp (--help) and versionHelp (--version) options. - mixinStandardHelpOptions = true, - versionProvider = VersionProvider.class) -public class Lfc extends CliBase { - /** - * Injected code generator. - */ - @Inject - private GeneratorDelegate generator; - - /** - * Injected file access object. - */ - @Inject - private JavaIoFileSystemAccess fileAccess; - - /* - * Supported CLI options. - */ - - @Option( - names = "--build-type", - description = "The build type to use.") - private String buildType; - - @Option( - names = {"-c", "--clean"}, - arity = "0", - description = "Clean before building.") - private boolean clean; - - @Option( - names = "--target-compiler", - description = "Target compiler to invoke.") - private String targetCompiler; - - @Option( - names = "--external-runtime-path", - description = "Specify an external runtime library to be used by the" - + " compiled binary.") - private Path externalRuntimePath; - - @Option( - names = {"-f", "--federated"}, - arity = "0", - description = "Treat main reactor as federated.") - private boolean federated; - - @Option( - names = "--logging", - description = "The logging level to use by the generated binary") - private String logging; - - @Option( - names = {"-l", "--lint"}, - arity = "0", - description = "Enable linting of generated code.") - private boolean lint; - - @Option( - names = {"-n", "--no-compile"}, - arity = "0", - description = "Do not invoke target compiler.") - private boolean noCompile; - - @Option( - names = {"--no-verify"}, - arity = "0", - description = "Do not run the generated verification models.") - private boolean noVerify; - - @Option( - names = {"-q", "--quiet"}, - arity = "0", - description = - "Suppress output of the target compiler and other commands") - private boolean quiet; - - @Option( - names = {"-r", "--rti"}, - description = "Specify the location of the RTI.") - private String rti; - - @Option( - names = "--runtime-version", - description = "Specify the version of the runtime library used for" - + " compiling LF programs.") - private String runtimeVersion; - - @Option( - names = {"-s", "--scheduler"}, - description = "Specify the runtime scheduler (if supported).") - private String scheduler; - - @Option( - names = {"-t", "--threading"}, - paramLabel = "", - description = "Specify whether the runtime should use multi-threading" - + " (true/false).") - private String threading; - - @Option( - names = {"-w", "--workers"}, - description = "Specify the default number of worker threads.") - private Integer workers; - - /** - * Main function of the stand-alone compiler. - * Caution: this will invoke System.exit. - * - * @param args CLI arguments - */ - public static void main(final String[] args) { - main(Io.SYSTEM, args); - } - - /** - * Main function of the standalone compiler, with a custom IO. - * - * @param io IO streams. - * @param args Command-line arguments. - */ - public static void main(Io io, final String... args) { - cliMain("lfc", Lfc.class, io, args); - } - - /** - * Load the resource, validate it, and, invoke the code generator. - */ - @Override - public void run() { - List paths = getInputPaths(); - final Path outputRoot = getOutputRoot(); - // Hard code the props based on the options we want. - Properties properties = this.filterPassOnProps(); - - try { - // Invoke the generator on all input file paths. - invokeGenerator(paths, outputRoot, properties); - } catch (RuntimeException e) { - reporter.printFatalErrorAndExit("An unexpected error occurred:", e); - } - } - - /** - * Invoke the code generator on the given validated file paths. - */ - private void invokeGenerator( - List files, Path root, Properties properties) { - for (Path path : files) { - path = toAbsolutePath(path); - String outputPath = getActualOutputPath(root, path).toString(); - this.fileAccess.setOutputPath(outputPath); - - final Resource resource = getResource(path); - if (resource == null) { - reporter.printFatalErrorAndExit(path - + " is not an LF file. Use the .lf file extension to" - + " denote LF files."); - } else if (federated) { - if (!ASTUtils.makeFederated(resource)) { - reporter.printError( - "Unable to change main reactor to federated reactor."); - } - } - - validateResource(resource); - exitIfCollectedErrors(); - - LFGeneratorContext context = new MainContext( - LFGeneratorContext.Mode.STANDALONE, CancelIndicator.NullImpl, - (m, p) -> {}, properties, resource, this.fileAccess, - fileConfig -> errorReporter - ); - - try { - this.generator.generate(resource, this.fileAccess, context); - } catch (Exception e) { - reporter.printFatalErrorAndExit("Error running generator", e); - } - - exitIfCollectedErrors(); - // Print all other issues (not errors). - issueCollector.getAllIssues().forEach(reporter::printIssue); - - this.io.getOut().println("Code generation finished."); - } - } - - private Path getActualOutputPath(Path root, Path path) { - if (root != null) { - return root.resolve("src-gen"); - } else { - Path pkgRoot = FileConfig.findPackageRoot( - path, reporter::printWarning); - return pkgRoot.resolve("src-gen"); - } - } - - /** - * Filter the command-line arguments needed by the code generator, and - * return them as properties. - * - * @return Properties for the code generator. - */ - protected Properties filterPassOnProps() { - Properties props = new Properties(); - - 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 (noVerify) { - props.setProperty(BuildParm.NO_VERIFY.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/dsl/antlr4/C.g4 b/org.lflang/src/org/lflang/dsl/antlr4/C.g4 deleted file mode 100644 index 2fd6c3aedf..0000000000 --- a/org.lflang/src/org/lflang/dsl/antlr4/C.g4 +++ /dev/null @@ -1,908 +0,0 @@ -/* - [The "BSD licence"] - Copyright (c) 2013 Sam Harwell - All rights reserved. - - 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. - 3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. -*/ - -/** C 2011 grammar built from the C11 Spec */ -grammar C; - - -primaryExpression - : Identifier - | Constant - | StringLiteral+ - | '(' expression ')' - | genericSelection - | '__extension__'? '(' compoundStatement ')' // Blocks (GCC extension) - | '__builtin_va_arg' '(' unaryExpression ',' typeName ')' - | '__builtin_offsetof' '(' typeName ',' unaryExpression ')' - ; - -genericSelection - : '_Generic' '(' assignmentExpression ',' genericAssocList ')' - ; - -genericAssocList - : genericAssociation (',' genericAssociation)* - ; - -genericAssociation - : (typeName | 'default') ':' assignmentExpression - ; - -postfixExpression - : - ( primaryExpression - | '__extension__'? '(' typeName ')' '{' initializerList ','? '}' - ) - ('[' expression ']' - | '(' argumentExpressionList? ')' - | ('.' | '->') Identifier - | ('++' | '--') - )* - ; - -argumentExpressionList - : assignmentExpression (',' assignmentExpression)* - ; - -unaryExpression - : - ('++' | '--' | 'sizeof')* - (postfixExpression - | unaryOperator castExpression - | ('sizeof' | '_Alignof') '(' typeName ')' - | '&&' Identifier // GCC extension address of label - ) - ; - -unaryOperator - : '&' | '*' | '+' | '-' | '~' | '!' - ; - -castExpression - : '__extension__'? '(' typeName ')' castExpression - | unaryExpression - | DigitSequence // for - ; - -multiplicativeExpression - : castExpression (('*'|'/'|'%') castExpression)* - ; - -additiveExpression - : multiplicativeExpression (('+'|'-') multiplicativeExpression)* - ; - -shiftExpression - : additiveExpression (('<<'|'>>') additiveExpression)* - ; - -relationalExpression - : shiftExpression (('<'|'>'|'<='|'>=') shiftExpression)* - ; - -equalityExpression - : relationalExpression (('=='| '!=') relationalExpression)* - ; - -andExpression - : equalityExpression ( '&' equalityExpression)* - ; - -exclusiveOrExpression - : andExpression ('^' andExpression)* - ; - -inclusiveOrExpression - : exclusiveOrExpression ('|' exclusiveOrExpression)* - ; - -logicalAndExpression - : inclusiveOrExpression ('&&' inclusiveOrExpression)* - ; - -logicalOrExpression - : logicalAndExpression ( '||' logicalAndExpression)* - ; - -conditionalExpression - : logicalOrExpression ('?' expression ':' conditionalExpression)? - ; - -assignmentExpression - : conditionalExpression - | unaryExpression assignmentOperator assignmentExpression - | DigitSequence // for - ; - -assignmentOperator - : '=' | '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' | '>>=' | '&=' | '^=' | '|=' - ; - -expression - : assignmentExpression (',' assignmentExpression)* - ; - -constantExpression - : conditionalExpression - ; - -declaration - : declarationSpecifiers initDeclaratorList? ';' - | staticAssertDeclaration - ; - -declarationSpecifiers - : declarationSpecifier+ - ; - -declarationSpecifiers2 - : declarationSpecifier+ - ; - -declarationSpecifier - : storageClassSpecifier - | typeSpecifier - | typeQualifier - | functionSpecifier - | alignmentSpecifier - ; - -initDeclaratorList - : initDeclarator (',' initDeclarator)* - ; - -initDeclarator - : declarator ('=' initializer)? - ; - -storageClassSpecifier - : 'typedef' - | 'extern' - | 'static' - | '_Thread_local' - | 'auto' - | 'register' - ; - -typeSpecifier - : ('void' - | 'char' - | 'short' - | 'int' - | 'long' - | 'float' - | 'double' - | 'signed' - | 'unsigned' - | '_Bool' - | '_Complex' - | '__m128' - | '__m128d' - | '__m128i') - | '__extension__' '(' ('__m128' | '__m128d' | '__m128i') ')' - | atomicTypeSpecifier - | structOrUnionSpecifier - | enumSpecifier - | typedefName - | '__typeof__' '(' constantExpression ')' // GCC extension - ; - -structOrUnionSpecifier - : structOrUnion Identifier? '{' structDeclarationList '}' - | structOrUnion Identifier - ; - -structOrUnion - : 'struct' - | 'union' - ; - -structDeclarationList - : structDeclaration+ - ; - -structDeclaration // The first two rules have priority order and cannot be simplified to one expression. - : specifierQualifierList structDeclaratorList ';' - | specifierQualifierList ';' - | staticAssertDeclaration - ; - -specifierQualifierList - : (typeSpecifier| typeQualifier) specifierQualifierList? - ; - -structDeclaratorList - : structDeclarator (',' structDeclarator)* - ; - -structDeclarator - : declarator - | declarator? ':' constantExpression - ; - -enumSpecifier - : 'enum' Identifier? '{' enumeratorList ','? '}' - | 'enum' Identifier - ; - -enumeratorList - : enumerator (',' enumerator)* - ; - -enumerator - : enumerationConstant ('=' constantExpression)? - ; - -enumerationConstant - : Identifier - ; - -atomicTypeSpecifier - : '_Atomic' '(' typeName ')' - ; - -typeQualifier - : 'const' - | 'restrict' - | 'volatile' - | '_Atomic' - ; - -functionSpecifier - : ('inline' - | '_Noreturn' - | '__inline__' // GCC extension - | '__stdcall') - | gccAttributeSpecifier - | '__declspec' '(' Identifier ')' - ; - -alignmentSpecifier - : '_Alignas' '(' (typeName | constantExpression) ')' - ; - -declarator - : pointer? directDeclarator gccDeclaratorExtension* - ; - -directDeclarator - : Identifier - | '(' declarator ')' - | directDeclarator '[' typeQualifierList? assignmentExpression? ']' - | directDeclarator '[' 'static' typeQualifierList? assignmentExpression ']' - | directDeclarator '[' typeQualifierList 'static' assignmentExpression ']' - | directDeclarator '[' typeQualifierList? '*' ']' - | directDeclarator '(' parameterTypeList ')' - | directDeclarator '(' identifierList? ')' - | Identifier ':' DigitSequence // bit field - | vcSpecificModifer Identifier // Visual C Extension - | '(' vcSpecificModifer declarator ')' // Visual C Extension - ; - -vcSpecificModifer - : ('__cdecl' - | '__clrcall' - | '__stdcall' - | '__fastcall' - | '__thiscall' - | '__vectorcall') - ; - - -gccDeclaratorExtension - : '__asm' '(' StringLiteral+ ')' - | gccAttributeSpecifier - ; - -gccAttributeSpecifier - : '__attribute__' '(' '(' gccAttributeList ')' ')' - ; - -gccAttributeList - : gccAttribute? (',' gccAttribute?)* - ; - -gccAttribute - : ~(',' | '(' | ')') // relaxed def for "identifier or reserved word" - ('(' argumentExpressionList? ')')? - ; - -nestedParenthesesBlock - : ( ~('(' | ')') - | '(' nestedParenthesesBlock ')' - )* - ; - -pointer - : (('*'|'^') typeQualifierList?)+ // ^ - Blocks language extension - ; - -typeQualifierList - : typeQualifier+ - ; - -parameterTypeList - : parameterList (',' '...')? - ; - -parameterList - : parameterDeclaration (',' parameterDeclaration)* - ; - -parameterDeclaration - : declarationSpecifiers declarator - | declarationSpecifiers2 abstractDeclarator? - ; - -identifierList - : Identifier (',' Identifier)* - ; - -typeName - : specifierQualifierList abstractDeclarator? - ; - -abstractDeclarator - : pointer - | pointer? directAbstractDeclarator gccDeclaratorExtension* - ; - -directAbstractDeclarator - : '(' abstractDeclarator ')' gccDeclaratorExtension* - | '[' typeQualifierList? assignmentExpression? ']' - | '[' 'static' typeQualifierList? assignmentExpression ']' - | '[' typeQualifierList 'static' assignmentExpression ']' - | '[' '*' ']' - | '(' parameterTypeList? ')' gccDeclaratorExtension* - | directAbstractDeclarator '[' typeQualifierList? assignmentExpression? ']' - | directAbstractDeclarator '[' 'static' typeQualifierList? assignmentExpression ']' - | directAbstractDeclarator '[' typeQualifierList 'static' assignmentExpression ']' - | directAbstractDeclarator '[' '*' ']' - | directAbstractDeclarator '(' parameterTypeList? ')' gccDeclaratorExtension* - ; - -typedefName - : Identifier - ; - -initializer - : assignmentExpression - | '{' initializerList ','? '}' - ; - -initializerList - : designation? initializer (',' designation? initializer)* - ; - -designation - : designatorList '=' - ; - -designatorList - : designator+ - ; - -designator - : '[' constantExpression ']' - | '.' Identifier - ; - -staticAssertDeclaration - : '_Static_assert' '(' constantExpression ',' StringLiteral+ ')' ';' - ; - -statement - : labeledStatement - | compoundStatement - | expressionStatement - | selectionStatement - | iterationStatement - | jumpStatement - | ('__asm' | '__asm__') ('volatile' | '__volatile__') '(' (logicalOrExpression (',' logicalOrExpression)*)? (':' (logicalOrExpression (',' logicalOrExpression)*)?)* ')' ';' - ; - -labeledStatement - : Identifier ':' statement - | 'case' constantExpression ':' statement - | 'default' ':' statement - ; - -compoundStatement - : '{' blockItemList? '}' - ; - -blockItemList - : blockItem+ - ; - -// Reaction body is a blockItem. -blockItem - : statement - | declaration - ; - -expressionStatement - : expression? ';' - ; - -selectionStatement - : 'if' '(' expression ')' statement ('else' statement)? - | 'switch' '(' expression ')' statement - ; - -iterationStatement - : While '(' expression ')' statement - | Do statement While '(' expression ')' ';' - | For '(' forCondition ')' statement - ; - -// | 'for' '(' expression? ';' expression? ';' forUpdate? ')' statement -// | For '(' declaration expression? ';' expression? ')' statement - -forCondition - : (forDeclaration | expression?) ';' forExpression? ';' forExpression? - ; - -forDeclaration - : declarationSpecifiers initDeclaratorList? - ; - -forExpression - : assignmentExpression (',' assignmentExpression)* - ; - -jumpStatement - : ('goto' Identifier - | ('continue'| 'break') - | 'return' expression? - | 'goto' unaryExpression // GCC extension - ) - ';' - ; - -compilationUnit - : translationUnit? EOF - ; - -translationUnit - : externalDeclaration+ - ; - -externalDeclaration - : functionDefinition - | declaration - | ';' // stray ; - ; - -functionDefinition - : declarationSpecifiers? declarator declarationList? compoundStatement - ; - -declarationList - : declaration+ - ; - -Auto : 'auto'; -Break : 'break'; -Case : 'case'; -Char : 'char'; -Const : 'const'; -Continue : 'continue'; -Default : 'default'; -Do : 'do'; -Double : 'double'; -Else : 'else'; -Enum : 'enum'; -Extern : 'extern'; -Float : 'float'; -For : 'for'; -Goto : 'goto'; -If : 'if'; -Inline : 'inline'; -Int : 'int'; -Long : 'long'; -Register : 'register'; -Restrict : 'restrict'; -Return : 'return'; -Short : 'short'; -Signed : 'signed'; -Sizeof : 'sizeof'; -Static : 'static'; -Struct : 'struct'; -Switch : 'switch'; -Typedef : 'typedef'; -Union : 'union'; -Unsigned : 'unsigned'; -Void : 'void'; -Volatile : 'volatile'; -While : 'while'; - -Alignas : '_Alignas'; -Alignof : '_Alignof'; -Atomic : '_Atomic'; -Bool : '_Bool'; -Complex : '_Complex'; -Generic : '_Generic'; -Imaginary : '_Imaginary'; -Noreturn : '_Noreturn'; -StaticAssert : '_Static_assert'; -ThreadLocal : '_Thread_local'; - -LeftParen : '('; -RightParen : ')'; -LeftBracket : '['; -RightBracket : ']'; -LeftBrace : '{'; -RightBrace : '}'; - -Less : '<'; -LessEqual : '<='; -Greater : '>'; -GreaterEqual : '>='; -LeftShift : '<<'; -RightShift : '>>'; - -Plus : '+'; -PlusPlus : '++'; -Minus : '-'; -MinusMinus : '--'; -Star : '*'; -Div : '/'; -Mod : '%'; - -And : '&'; -Or : '|'; -AndAnd : '&&'; -OrOr : '||'; -Caret : '^'; -Not : '!'; -Tilde : '~'; - -Question : '?'; -Colon : ':'; -Semi : ';'; -Comma : ','; - -Assign : '='; -// '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' | '>>=' | '&=' | '^=' | '|=' -StarAssign : '*='; -DivAssign : '/='; -ModAssign : '%='; -PlusAssign : '+='; -MinusAssign : '-='; -LeftShiftAssign : '<<='; -RightShiftAssign : '>>='; -AndAssign : '&='; -XorAssign : '^='; -OrAssign : '|='; - -Equal : '=='; -NotEqual : '!='; - -Arrow : '->'; -Dot : '.'; -Ellipsis : '...'; - -Identifier - : IdentifierNondigit - ( IdentifierNondigit - | Digit - )* - ; - -fragment -IdentifierNondigit - : Nondigit - | UniversalCharacterName - //| // other implementation-defined characters... - ; - -fragment -Nondigit - : [a-zA-Z_] - ; - -fragment -Digit - : [0-9] - ; - -fragment -UniversalCharacterName - : '\\u' HexQuad - | '\\U' HexQuad HexQuad - ; - -fragment -HexQuad - : HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit - ; - -Constant - : IntegerConstant - | FloatingConstant - //| EnumerationConstant - | CharacterConstant - ; - -fragment -IntegerConstant - : DecimalConstant IntegerSuffix? - | OctalConstant IntegerSuffix? - | HexadecimalConstant IntegerSuffix? - | BinaryConstant - ; - -fragment -BinaryConstant - : '0' [bB] [0-1]+ - ; - -fragment -DecimalConstant - : NonzeroDigit Digit* - ; - -fragment -OctalConstant - : '0' OctalDigit* - ; - -fragment -HexadecimalConstant - : HexadecimalPrefix HexadecimalDigit+ - ; - -fragment -HexadecimalPrefix - : '0' [xX] - ; - -fragment -NonzeroDigit - : [1-9] - ; - -fragment -OctalDigit - : [0-7] - ; - -fragment -HexadecimalDigit - : [0-9a-fA-F] - ; - -fragment -IntegerSuffix - : UnsignedSuffix LongSuffix? - | UnsignedSuffix LongLongSuffix - | LongSuffix UnsignedSuffix? - | LongLongSuffix UnsignedSuffix? - ; - -fragment -UnsignedSuffix - : [uU] - ; - -fragment -LongSuffix - : [lL] - ; - -fragment -LongLongSuffix - : 'll' | 'LL' - ; - -fragment -FloatingConstant - : DecimalFloatingConstant - | HexadecimalFloatingConstant - ; - -fragment -DecimalFloatingConstant - : FractionalConstant ExponentPart? FloatingSuffix? - | DigitSequence ExponentPart FloatingSuffix? - ; - -fragment -HexadecimalFloatingConstant - : HexadecimalPrefix (HexadecimalFractionalConstant | HexadecimalDigitSequence) BinaryExponentPart FloatingSuffix? - ; - -fragment -FractionalConstant - : DigitSequence? '.' DigitSequence - | DigitSequence '.' - ; - -fragment -ExponentPart - : [eE] Sign? DigitSequence - ; - -fragment -Sign - : [+-] - ; - -DigitSequence - : Digit+ - ; - -fragment -HexadecimalFractionalConstant - : HexadecimalDigitSequence? '.' HexadecimalDigitSequence - | HexadecimalDigitSequence '.' - ; - -fragment -BinaryExponentPart - : [pP] Sign? DigitSequence - ; - -fragment -HexadecimalDigitSequence - : HexadecimalDigit+ - ; - -fragment -FloatingSuffix - : [flFL] - ; - -fragment -CharacterConstant - : '\'' CCharSequence '\'' - | 'L\'' CCharSequence '\'' - | 'u\'' CCharSequence '\'' - | 'U\'' CCharSequence '\'' - ; - -fragment -CCharSequence - : CChar+ - ; - -fragment -CChar - : ~['\\\r\n] - | EscapeSequence - ; - -fragment -EscapeSequence - : SimpleEscapeSequence - | OctalEscapeSequence - | HexadecimalEscapeSequence - | UniversalCharacterName - ; - -fragment -SimpleEscapeSequence - : '\\' ['"?abfnrtv\\] - ; - -fragment -OctalEscapeSequence - : '\\' OctalDigit OctalDigit? OctalDigit? - ; - -fragment -HexadecimalEscapeSequence - : '\\x' HexadecimalDigit+ - ; - -StringLiteral - : EncodingPrefix? '"' SCharSequence? '"' - ; - -fragment -EncodingPrefix - : 'u8' - | 'u' - | 'U' - | 'L' - ; - -fragment -SCharSequence - : SChar+ - ; - -fragment -SChar - : ~["\\\r\n] - | EscapeSequence - | '\\\n' // Added line - | '\\\r\n' // Added line - ; - -ComplexDefine - : '#' Whitespace? 'define' ~[#\r\n]* - -> skip - ; - -IncludeDirective - : '#' Whitespace? 'include' Whitespace? (('"' ~[\r\n]* '"') | ('<' ~[\r\n]* '>' )) Whitespace? Newline - -> skip - ; - -// ignore the following asm blocks: -/* - asm - { - mfspr x, 286; - } - */ -AsmBlock - : 'asm' ~'{'* '{' ~'}'* '}' - -> skip - ; - -// ignore the lines generated by c preprocessor -// sample line : '#line 1 "/home/dm/files/dk1.h" 1' -LineAfterPreprocessing - : '#line' Whitespace* ~[\r\n]* - -> skip - ; - -LineDirective - : '#' Whitespace? DecimalConstant Whitespace? StringLiteral ~[\r\n]* - -> skip - ; - -PragmaDirective - : '#' Whitespace? 'pragma' Whitespace ~[\r\n]* - -> skip - ; - -Whitespace - : [ \t]+ - -> skip - ; - -Newline - : ( '\r' '\n'? - | '\n' - ) - -> skip - ; - -BlockComment - : '/*' .*? '*/' - -> skip - ; - -LineComment - : '//' ~[\r\n]* - -> skip - ; \ No newline at end of file diff --git a/org.lflang/src/org/lflang/dsl/antlr4/MTLLexer.g4 b/org.lflang/src/org/lflang/dsl/antlr4/MTLLexer.g4 deleted file mode 100644 index b8fe9c118d..0000000000 --- a/org.lflang/src/org/lflang/dsl/antlr4/MTLLexer.g4 +++ /dev/null @@ -1,117 +0,0 @@ -lexer grammar MTLLexer; - -COMMA - : ',' - ; - -LPAREN - : '(' - ; - -RPAREN - : ')' - ; - -LBRACKET - : '[' - ; - -RBRACKET - : ']' - ; - -LAND - : '&&' - ; - -LOR - : '||' - ; - -EQUI - : '<==>' - ; - -IMPL - : '==>' - ; - -UNTIL - : 'U' - ; - -NEGATION - : '!' - ; - -NEXT - : 'X' - ; - -GLOBALLY - : 'G' - ; - -FINALLY - : 'F' - ; - -WS - : [ \t\r\n]+ -> skip - ; - -TRUE - : 'true' - ; - -FALSE - : 'false' - ; - -PLUS - : '+' - ; - -MINUS - : '-' - ; - -TIMES - : '*' - ; - -DIV - : '/' - ; - -EQ - : '==' - ; - -NEQ - : '!=' - ; - -LT - : '<' - ; - -LE - : '<=' - ; - -GT - : '>' - ; - -GE - : '>=' - ; - -INTEGER - : [0-9]+ - ; - -ID - : ([a-zA-Z0-9]|'_')+ - ; \ No newline at end of file diff --git a/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 b/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 deleted file mode 100644 index 1a5d39b9b4..0000000000 --- a/org.lflang/src/org/lflang/dsl/antlr4/MTLParser.g4 +++ /dev/null @@ -1,82 +0,0 @@ -parser grammar MTLParser; - -options { tokenVocab=MTLLexer; } - -mtl - : equivalence - ; - -equivalence - : left=implication ( EQUI right=implication )? - ; - -implication - : left=disjunction ( IMPL right=disjunction )? - ; - -disjunction - : terms+=conjunction ( LOR terms+=conjunction )* - ; - -conjunction - : terms+=binaryOp ( LAND terms+=binaryOp )* - ; - -binaryOp - : left=unaryOp ( UNTIL timeInterval=interval right=unaryOp )? # Until - ; - -unaryOp - : formula=primary # NoUnaryOp - | NEGATION formula=primary # Negation - | NEXT timeInterval=interval formula=primary # Next - | GLOBALLY timeInterval=interval formula=primary # Globally - | FINALLY timeInterval=interval formula=primary # Finally - ; - -primary - : atom=atomicProp - | id=ID - | LPAREN formula=mtl RPAREN - ; - -atomicProp - : primitive=TRUE - | primitive=FALSE - | left=expr op=relOp right=expr - ; - -interval - : (LPAREN|LBRACKET) lowerbound=time COMMA upperbound=time (RPAREN|RBRACKET) # Range - | LBRACKET instant=time RBRACKET # Singleton - ; - -time - : value=INTEGER (unit=ID)? - ; - -sum - : terms+=difference (PLUS terms+=difference)* - ; - -difference - : terms+=product (MINUS terms+=product)* - ; - -product - : terms+=quotient (TIMES terms+=quotient)* - ; - -quotient - : terms+=expr (DIV terms+=expr)* - ; - -relOp - : EQ | NEQ | LT | LE | GT | GE - ; - -expr - : ID - | LPAREN sum RPAREN - | INTEGER - ; diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.java b/org.lflang/src/org/lflang/generator/GeneratorBase.java deleted file mode 100644 index 9e1e07034a..0000000000 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.java +++ /dev/null @@ -1,705 +0,0 @@ -/************* - * 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 - * 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 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.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; -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; -import com.google.common.collect.Iterables; - -/** - * Generator base class for specifying core functionality - * that all code generators should have. - * - * @author Edward A. Lee - * @author Marten Lohstroh - * @author Christian Menard - * @author Matt Weber - * @author Soroush Bateni - */ -public abstract class GeneratorBase extends AbstractLFValidator { - - //////////////////////////////////////////// - //// Public fields. - - /** - * The main (top-level) reactor instance. - */ - public ReactorInstance main; - - /** An error reporter for reporting any errors or warnings during the code generation */ - public ErrorReporter errorReporter; - - //////////////////////////////////////////// - //// Protected fields. - - /** - * The current target configuration. - */ - protected final TargetConfig targetConfig; - - public TargetConfig getTargetConfig() { return this.targetConfig;} - - public final LFGeneratorContext context; - - /** - * A factory for compiler commands. - */ - protected GeneratorCommandFactory commandFactory; - - public GeneratorCommandFactory getCommandFactory() { return commandFactory; } - - /** - * Definition of the main (top-level) reactor. - * This is an automatically generated AST node for the top-level - * reactor. - */ - protected Instantiation mainDef; - public Instantiation getMainDef() { return mainDef; } - - /** - * A list of Reactor definitions in the main resource, including non-main - * reactors defined in imported resources. These are ordered in the list in - * such a way that each reactor is preceded by any reactor that it instantiates - * using a command like `foo = new Foo();` - */ - protected List reactors = new ArrayList<>(); - - /** - * The set of resources referenced reactor classes reside in. - */ - protected Set resources = new LinkedHashSet<>(); // FIXME: Why do we need this? - - /** - * Graph that tracks dependencies between instantiations. - * This is a graph where each node is a Reactor (not a ReactorInstance) - * and an arc from Reactor A to Reactor B means that B contains an instance of A, constructed with a statement - * like `a = new A();` After creating the graph, - * sort the reactors in topological order and assign them to the reactors class variable. - * Hence, after this method returns, `this.reactors` will be a list of Reactors such that any - * reactor is preceded in the list by reactors that it instantiates. - */ - protected InstantiationGraph instantiationGraph; - - /** - * The set of unordered reactions. An unordered reaction is one that does - * not have any dependency on other reactions in the containing reactor, - * and where no other reaction in the containing reactor depends on it. - * There is currently no way in the syntax of LF to make a reaction - * unordered, deliberately, because it can introduce unexpected - * nondeterminacy. However, certain automatically generated reactions are - * known to be safe to be unordered because they do not interact with the - * state of the containing reactor. To make a reaction unordered, when - * the Reaction instance is created, add that instance to this set. - */ - protected Set unorderedReactions = null; - - /** - * Map from reactions to bank indices - */ - protected Map reactionBankIndices = null; - - /** - * Indicates whether the current Lingua Franca program - * contains model reactors. - */ - public boolean hasModalReactors = false; - - /** - * Indicates whether the program has any deadlines and thus - * needs to propagate deadlines through the reaction instance graph - */ - public boolean hasDeadlines = false; - - // ////////////////////////////////////////// - // // Private fields. - - /** - * A list ot AST transformations to apply before code generation - */ - private final List astTransformations = new ArrayList<>(); - - /** - * Create a new GeneratorBase object. - */ - public GeneratorBase(LFGeneratorContext context) { - this.context = context; - this.targetConfig = context.getTargetConfig(); - this.errorReporter = context.getErrorReporter(); - this.commandFactory = new GeneratorCommandFactory(errorReporter, context.getFileConfig()); - } - - /** - * Register an AST transformation to be applied to the AST. - * - * The transformations will be applied in the order that they are registered in. - */ - protected void registerTransformation(AstTransformation transformation) { - astTransformations.add(transformation); - } - - // ////////////////////////////////////////// - // // Code generation functions to override for a concrete code generator. - - /** - * If there is a main or federated reactor, then create a synthetic Instantiation - * for that top-level reactor and set the field mainDef to refer to it. - */ - protected void createMainInstantiation() { - // Find the main reactor and create an AST node for its instantiation. - Iterable nodes = IteratorExtensions.toIterable(context.getFileConfig().resource.getAllContents()); - for (Reactor reactor : Iterables.filter(nodes, Reactor.class)) { - if (reactor.isMain()) { - // Creating a definition for the main reactor because there isn't one. - this.mainDef = LfFactory.eINSTANCE.createInstantiation(); - this.mainDef.setName(reactor.getName()); - this.mainDef.setReactorClass(reactor); - } - } - } - - /** - * Generate code from the Lingua Franca model contained by the specified resource. - * - * This is the main entry point for code generation. This base class finds all - * reactor class definitions, including any reactors defined in imported .lf files - * (except any main reactors in those imported files), and adds them to the - * {@link GeneratorBase#reactors reactors} list. If errors occur during - * generation, then a subsequent call to errorsOccurred() will return true. - * @param resource The resource containing the source code. - * @param context Context relating to invocation of the code generator. - * In standalone mode, this object is also used to relay CLI arguments. - */ - public void doGenerate(Resource resource, LFGeneratorContext context) { - - printInfo(context.getMode()); - - // Clear any IDE markers that may have been created by a previous build. - // Markers mark problems in the Eclipse IDE when running in integrated mode. - errorReporter.clearHistory(); - - ASTUtils.setMainName(context.getFileConfig().resource, context.getFileConfig().name); - - createMainInstantiation(); - - // Check if there are any conflicting main reactors elsewhere in the package. - if (Objects.equal(context.getMode(), LFGeneratorContext.Mode.STANDALONE) && mainDef != null) { - for (String conflict : new MainConflictChecker(context.getFileConfig()).conflicts) { - errorReporter.reportError(this.mainDef.getReactorClass(), "Conflicting main reactor in " + conflict); - } - } - - // Configure the command factory - commandFactory.setVerbose(); - if (Objects.equal(context.getMode(), LFGeneratorContext.Mode.STANDALONE) && context.getArgs().containsKey("quiet")) { - commandFactory.setQuiet(); - } - - // Process target files. Copy each of them into the src-gen dir. - // FIXME: Should we do this here? This doesn't make sense for federates the way it is - // done here. - copyUserFiles(this.targetConfig, context.getFileConfig()); - - // Collect reactors and create an instantiation graph. - // These are needed to figure out which resources we need - // to validate, which happens in setResources(). - setReactorsAndInstantiationGraph(context.getMode()); - - List allResources = GeneratorUtils.getResources(reactors); - resources.addAll(allResources.stream() // FIXME: This filter reproduces the behavior of the method it replaces. But why must it be so complicated? Why are we worried about weird corner cases like this? - .filter(it -> !Objects.equal(it, context.getFileConfig().resource) || mainDef != null && it == mainDef.getReactorClass().eResource()) - .map(it -> GeneratorUtils.getLFResource(it, context.getFileConfig().getSrcGenBasePath(), context, errorReporter)) - .toList() - ); - GeneratorUtils.accommodatePhysicalActionsIfPresent( - allResources, - getTarget().setsKeepAliveOptionAutomatically(), - targetConfig, - errorReporter - ); - // FIXME: Should the GeneratorBase pull in `files` from imported - // resources? - - for (AstTransformation transformation : astTransformations) { - transformation.applyTransformation(reactors); - } - - // Transform connections that reside in mutually exclusive modes and are otherwise conflicting - // This should be done before creating the instantiation graph - transformConflictingConnectionsInModalReactors(); - - // Invoke these functions a second time because transformations - // may have introduced new reactors! - setReactorsAndInstantiationGraph(context.getMode()); - - // Check for existence and support of modes - hasModalReactors = IterableExtensions.exists(reactors, it -> !it.getModes().isEmpty()); - checkModalReactorSupport(false); - additionalPostProcessingForModes(); - } - - /** - * 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<>(); - } - 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; - } - if (reactionBankIndices == null) { - reactionBankIndices = new LinkedHashMap<>(); - } - reactionBankIndices.put(reaction, bankIndex); - } - - /** - * Return the reaction bank index. - * @see #setReactionBankIndex(Reaction reaction, int bankIndex) - * @param reaction The reaction. - * @return The reaction bank index, if one has been set, and -1 otherwise. - */ - public int getReactionBankIndex(Reaction reaction) { - if (reactionBankIndices == null) return -1; - if (reactionBankIndices.get(reaction) == null) return -1; - return reactionBankIndices.get(reaction); - } - - /** - * 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. - - /** - * 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 - ) { - 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"; - } - - /** - * 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); - } - } - 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()); - } - - /** - * 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 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/LFGenerator.java b/org.lflang/src/org/lflang/generator/LFGenerator.java deleted file mode 100644 index c96dfa95cc..0000000000 --- a/org.lflang/src/org/lflang/generator/LFGenerator.java +++ /dev/null @@ -1,238 +0,0 @@ -package org.lflang.generator; - -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.nio.file.Path; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import org.eclipse.emf.ecore.EObject; -import org.eclipse.emf.ecore.resource.Resource; -import org.eclipse.xtext.generator.AbstractGenerator; -import org.eclipse.xtext.generator.IFileSystemAccess2; -import org.eclipse.xtext.generator.IGeneratorContext; -import org.eclipse.xtext.util.RuntimeIOException; - -import org.lflang.ASTUtils; -import org.lflang.AttributeUtils; -import org.lflang.ErrorReporter; -import org.lflang.FileConfig; -import org.lflang.Target; -import org.lflang.analyses.uclid.UclidGenerator; -import org.lflang.analyses.uclid.UclidRunner; -import org.lflang.federated.generator.FedASTUtils; -import org.lflang.federated.generator.FedFileConfig; -import org.lflang.federated.generator.FedGenerator; -import org.lflang.generator.c.CFileConfig; -import org.lflang.generator.c.CGenerator; -import org.lflang.generator.python.PyFileConfig; -import org.lflang.generator.python.PythonGenerator; -import org.lflang.lf.Attribute; -import org.lflang.lf.Reactor; -import org.lflang.scoping.LFGlobalScopeProvider; - -import com.google.inject.Inject; - -/** - * Generates code from your model files on save. - * - * See - * https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#code-generation - */ -public class LFGenerator extends AbstractGenerator { - - @Inject - private LFGlobalScopeProvider scopeProvider; - - // Indicator of whether generator errors occurred. - protected boolean generatorErrorsOccurred = false; - - /** - * Create a target-specific FileConfig object in Kotlin - *

- * Since the CppFileConfig and TSFileConfig class are implemented in Kotlin, the classes are - * not visible from all contexts. If the RCA is run from within Eclipse via - * "Run as Eclipse Application", the Kotlin classes are unfortunately not - * available at runtime due to bugs in the Eclipse Kotlin plugin. (See - * https://stackoverflow.com/questions/68095816/is-ist-possible-to-build-mixed-kotlin-and-java-applications-with-a-recent-eclips) - *

- * If the FileConfig class is found, this method returns an instance. - * Otherwise, it returns an Instance of FileConfig. - * - * @return A FileConfig object in Kotlin if the class can be found. - * @throws IOException If the file config could not be created properly - */ - public static FileConfig createFileConfig(Resource resource, Path srcGenBasePath, - boolean useHierarchicalBin) { - - final Target target = Target.fromDecl(ASTUtils.targetDecl(resource)); - assert target != null; - - // Since our Eclipse Plugin uses code injection via guice, we need to - // play a few tricks here so that FileConfig does not appear as an - // import. Instead, we look the class up at runtime and instantiate it if - // found. - try { - if (FedASTUtils.findFederatedReactor(resource) != null) { - return new FedFileConfig(resource, srcGenBasePath, useHierarchicalBin); - } - switch (target) { - case CCPP: - case C: return new CFileConfig(resource, srcGenBasePath, useHierarchicalBin); - case Python: return new PyFileConfig(resource, srcGenBasePath, useHierarchicalBin); - case CPP: - case Rust: - case TS: - String className = "org.lflang.generator." + target.packageName + "." + target.classNamePrefix + "FileConfig"; - try { - return (FileConfig) Class.forName(className) - .getDeclaredConstructor(Resource.class, Path.class, boolean.class) - .newInstance(resource, srcGenBasePath, useHierarchicalBin); - } catch (ReflectiveOperationException e) { - throw new RuntimeException( - "Exception instantiating " + className, e.getCause()); - } - default: - throw new RuntimeException("Could not find FileConfig implementation for target " + target); - } - } catch (IOException e) { - throw new RuntimeException("Unable to create FileConfig object for target " + target + ": " + e.getStackTrace()); - } - } - - /** - * Create a generator object for the given target. - * Returns null if the generator could not be created. - */ - private GeneratorBase createGenerator(LFGeneratorContext context) { - final Target target = Target.fromDecl(ASTUtils.targetDecl(context.getFileConfig().resource)); - assert target != null; - return switch (target) { - case C -> new CGenerator(context, false); - case CCPP -> new CGenerator(context, true); - case Python -> new PythonGenerator(context); - case CPP, TS, Rust -> - createKotlinBaseGenerator(target, context); - // If no case matched, then throw a runtime exception. - default -> throw new RuntimeException("Unexpected target!"); - }; - } - - - /** - * Create a code generator in Kotlin. - *

- * Since the CppGenerator and TSGenerator class are implemented in Kotlin, the classes are - * not visible from all contexts. If the RCA is run from within Eclipse via - * "Run as Eclipse Application", the Kotlin classes are unfortunately not - * available at runtime due to bugs in the Eclipse Kotlin plugin. (See - * https://stackoverflow.com/questions/68095816/is-ist-possible-to-build-mixed-kotlin-and-java-applications-with-a-recent-eclips) - * In this case, the method returns null - * - * @return A Kotlin Generator object if the class can be found - */ - private GeneratorBase createKotlinBaseGenerator(Target target, LFGeneratorContext context) { - // Since our Eclipse Plugin uses code injection via guice, we need to - // play a few tricks here so that Kotlin FileConfig and - // Kotlin Generator do not appear as an import. Instead, we look the - // class up at runtime and instantiate it if found. - String classPrefix = "org.lflang.generator." + target.packageName + "." + target.classNamePrefix; - try { - Class generatorClass = Class.forName(classPrefix + "Generator"); - Constructor ctor = generatorClass - .getDeclaredConstructor(LFGeneratorContext.class, LFGlobalScopeProvider.class); - - return (GeneratorBase) ctor.newInstance(context, scopeProvider); - } catch (ReflectiveOperationException e) { - generatorErrorsOccurred = true; - context.getErrorReporter().reportError( - "The code generator for the " + target + " target could not be found. " - + "This is likely because you built Epoch using " - + "Eclipse. The " + target + " code generator is written in Kotlin " - + "and, unfortunately, the plugin that Eclipse uses " - + "for compiling Kotlin code is broken. " - + "Please consider building Epoch using Maven.\n" - + "For step-by-step instructions, see: " - + "https://github.com/icyphy/lingua-franca/wiki/Running-Lingua-Franca-IDE-%28Epoch%29-with-Kotlin-based-Code-Generators-Enabled-%28without-Eclipse-Environment%29"); - return null; - } - } - - @Override - public void doGenerate(Resource resource, IFileSystemAccess2 fsa, - IGeneratorContext context) { - final LFGeneratorContext lfContext; - if (context instanceof LFGeneratorContext) { - lfContext = (LFGeneratorContext)context; - } else { - lfContext = LFGeneratorContext.lfGeneratorContextOf(resource, fsa, context); - } - - // The fastest way to generate code is to not generate any code. - if (lfContext.getMode() == LFGeneratorContext.Mode.LSP_FAST) return; - - if (FedASTUtils.findFederatedReactor(resource) != null) { - try { - generatorErrorsOccurred = (new FedGenerator(lfContext)).doGenerate(resource, lfContext); - } catch (IOException e) { - throw new RuntimeIOException("Error during federated code generation", e); - } - - } else { - - // If "-c" or "--clean" is specified, delete any existing generated directories. - cleanIfNeeded(lfContext); - - // Check if @property is used. If so, instantiate a UclidGenerator. - // The verification model needs to be generated before the target code - // since code generation changes LF program (desugar connections, etc.). - Reactor main = ASTUtils.getMainReactor(resource); - List properties = AttributeUtils.getAttributes(main) - .stream() - .filter(attr -> attr.getAttrName().equals("property")) - .collect(Collectors.toList()); - if (properties.size() > 0) { - UclidGenerator uclidGenerator = new UclidGenerator(lfContext, properties); - // Generate uclid files. - uclidGenerator.doGenerate(resource, lfContext); - if (!uclidGenerator.targetConfig.noVerify) { - // Invoke the generated uclid files. - uclidGenerator.runner.run(); - } else { - System.out.println("\"no-verify\" is set to true. Skip checking the verification model."); - } - } - - final GeneratorBase generator = createGenerator(lfContext); - - if (generator != null) { - generator.doGenerate(resource, lfContext); - generatorErrorsOccurred = generator.errorsOccurred(); - } - } - final ErrorReporter errorReporter = lfContext.getErrorReporter(); - if (errorReporter instanceof LanguageServerErrorReporter) { - ((LanguageServerErrorReporter) errorReporter).publishDiagnostics(); - } - } - - /** Return true if errors occurred in the last call to doGenerate(). */ - public boolean errorsOccurred() { - return generatorErrorsOccurred; - } - - /** - * 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"); - } - } - } -} diff --git a/org.lflang/src/org/lflang/generator/LFGeneratorContext.java b/org.lflang/src/org/lflang/generator/LFGeneratorContext.java deleted file mode 100644 index e6ca3c1103..0000000000 --- a/org.lflang/src/org/lflang/generator/LFGeneratorContext.java +++ /dev/null @@ -1,165 +0,0 @@ -package org.lflang.generator; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Map; -import java.util.Objects; -import java.util.Properties; - -import org.eclipse.emf.ecore.resource.Resource; -import org.eclipse.xtext.generator.IFileSystemAccess2; -import org.eclipse.xtext.generator.IGeneratorContext; -import org.eclipse.xtext.util.RuntimeIOException; - -import org.lflang.ErrorReporter; -import org.lflang.FileConfig; -import org.lflang.TargetConfig; - -/** - * An {@code LFGeneratorContext} is the context of a Lingua Franca build process. - * It is the point of communication between a build process and the environment - * in which it is executed. - * - * @author Peter Donovan - */ -public interface LFGeneratorContext extends IGeneratorContext { - - /** - * Enumeration of keys used to parameterize the build process. - */ - enum BuildParm { - BUILD_TYPE("The build type to use"), - CLEAN("Clean before building."), - EXTERNAL_RUNTIME_PATH("Specify an external runtime library to be used by the compiled binary."), - FEDERATED("Treat main reactor as federated."), - HELP("Display this information."), - LOGGING("The logging level to use by the generated binary"), - LINT("Enable or disable linting of generated code."), - NO_COMPILE("Do not invoke target compiler."), - NO_VERIFY("Do not check the generated verification model."), - OUTPUT_PATH("Specify the root output directory."), - QUIET("Suppress output of the target compiler and other commands"), - RTI("Specify the location of the RTI."), - RUNTIME_VERSION("Specify the version of the runtime library used for compiling LF programs."), - SCHEDULER("Specify the runtime scheduler (if supported)."), - TARGET_COMPILER("Target compiler to invoke."), - THREADING("Specify whether the runtime should use multi-threading (true/false)."), - VERSION("Print version information."), - WORKERS("Specify the default number of worker threads."); - - public final String description; - - BuildParm(String description) { - this.description = description; - } - - /** - * Return the string to use as the key to store a value relating to this parameter. - */ - public String getKey() { - return this.name().toLowerCase().replace('_', '-'); - } - - /** - * Return the value corresponding to this parameter or `null` if there is none. - * @param context The context passed to the code generator. - */ - public String getValue(LFGeneratorContext context) { - return context.getArgs().getProperty(this.getKey()); - } - } - - - enum Mode { - STANDALONE, - EPOCH, - LSP_FAST, - LSP_MEDIUM, - LSP_SLOW, - UNDEFINED - } - - /** - * Return the mode of operation, which indicates how the compiler has been invoked - * (e.g., from within Epoch, from the command line, or via a Language Server). - */ - Mode getMode(); - - /** - * Return any arguments that will override target properties. - */ - Properties getArgs(); - - /** - * Get the error reporter for this context; construct one if it hasn't been - * constructed yet. - */ - ErrorReporter getErrorReporter(); - - /** - * Mark the code generation process performed in this - * context as finished with the result {@code result}. - * @param result The result of the code generation - * process that was performed in this - * context. - */ - void finish(GeneratorResult result); - - /** - * Return the result of the code generation process that was performed in - * this context. - * @return the result of the code generation process that was performed in - * this context - */ - GeneratorResult getResult(); - - FileConfig getFileConfig(); - - TargetConfig getTargetConfig(); - - /** - * Report the progress of a build. - * @param message A message for the LF programmer to read. - * @param percentage The approximate percent completion of the build. - */ - void reportProgress(String message, int percentage); - - /** - * Conclude this build and record the result if necessary. - * @param status The status of the result. - * @param codeMaps The generated files and their corresponding code maps. - */ - default void finish( - GeneratorResult.Status status, - Map codeMaps - ) { - finish(new GeneratorResult(status, this, codeMaps)); - } - - /** - * Conclude this build and record that it was unsuccessful. - */ - default void unsuccessfulFinish() { - finish( - getCancelIndicator() != null && getCancelIndicator().isCanceled() ? - GeneratorResult.CANCELLED : GeneratorResult.FAILED - ); - } - - /** - * Return the {@code LFGeneratorContext} that best describes the given {@code context} when - * building {@code Resource}. - * @param resource - * @param fsa - * @param context The context of a Lingua Franca build process. - * @return The {@code LFGeneratorContext} that best describes the given {@code context} when - * building {@code Resource}. - */ - static LFGeneratorContext lfGeneratorContextOf(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) { - if (context instanceof LFGeneratorContext) return (LFGeneratorContext) context; - - if (resource.getURI().isPlatform()) return new MainContext(Mode.EPOCH, resource, fsa, context.getCancelIndicator()); - - return new MainContext(Mode.LSP_FAST, resource, fsa, context.getCancelIndicator()); - } -} diff --git a/org.lflang/src/org/lflang/generator/NamedInstance.java b/org.lflang/src/org/lflang/generator/NamedInstance.java deleted file mode 100644 index c056300ffe..0000000000 --- a/org.lflang/src/org/lflang/generator/NamedInstance.java +++ /dev/null @@ -1,334 +0,0 @@ -/* Base class for instances with names in Lingua Franca. */ - -/************* -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.generator; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import org.eclipse.emf.ecore.EObject; - -/** - * Base class for compile-time instances with names in Lingua Franca. - * An instance of concrete subclasses of this class represents one or - * more runtime instances of a reactor, port, reaction, etc. There - * will be more than one runtime instance if the object or any of its - * parents is a bank of reactors. - * - * @author Marten Lohstroh - * @author Edward A. Lee - */ -public abstract class NamedInstance { - - /** - * Construct a new instance with the specified definition - * and parent. E.g., for a reactor instance, the definition - * is Instantiation, and for a port instance, it is Port. These are - * nodes in the AST. This is protected because only subclasses - * should be constructed. - * @param definition The definition in the AST for this instance. - * @param parent The reactor instance that creates this instance. - */ - protected NamedInstance(T definition, ReactorInstance parent) { - this.definition = definition; - this.parent = parent; - - // Calculate the depth. - this.depth = 0; - ReactorInstance p = parent; - while (p != null) { - p = p.parent; - this.depth++; - } - } - - ////////////////////////////////////////////////////// - //// Public fields. - - /** A limit on the number of characters returned by uniqueID. */ - public static int identifierLengthLimit = 40; - - ////////////////////////////////////////////////////// - //// Public methods. - - /** - * Return the definition, which is the AST node for this object. - */ - public T getDefinition() { - return definition; - } - - /** - * Get the depth of the reactor instance. This is 0 for the main reactor, - * 1 for reactors immediately contained therein, etc. - */ - public int getDepth() { - return depth; - } - - /** - * Return the full name of this instance, which has the form - * "a.b.c", where "c" is the name of this instance, "b" is the name - * of its container, and "a" is the name of its container, stopping - * at the container in main. If any reactor in the hierarchy is - * in a bank of reactors then, it will appear as a[index]. - * Similarly, if c is a port in a multiport, it will appear as - * c[index]. - * @return The full name of this instance. - */ - public String getFullName() { - return getFullNameWithJoiner("."); - } - - /** - * Return the name of this instance as given in its definition. - * Note that this is unique only relative to other instances with - * the same parent. - * @return The name of this instance within its parent. - */ - public abstract String getName(); - - /** - * Return the parent or null if this is a top-level reactor. - */ - public ReactorInstance getParent() { - return parent; - } - - /** - * Return the parent at the given depth or null if there is - * no parent at the given depth. - * @param d The depth. - */ - public ReactorInstance getParent(int d) { - if (d >= depth || d < 0) return null; - ReactorInstance p = parent; - while (p != null) { - if (p.depth == d) return p; - p = p.parent; - } - return null; - } - - /** - * Return the width of this instance, which in this base class is 1. - * Subclasses PortInstance and ReactorInstance change this to the - * multiport and bank widths respectively. - */ - public int getWidth() { - return width; - } - - /** - * Return true if this instance has the specified parent - * (possibly indirectly, anywhere up the hierarchy). - */ - public boolean hasParent(ReactorInstance container) { - - ReactorInstance p = parent; - - while (p != null) { - if (p == container) return true; - p = p.parent; - } - return false; - } - - /** - * Return a list of all the parents starting with the root(). - */ - public List parents() { - List result = new ArrayList(depth + 1); - if (this instanceof ReactorInstance && parent == null) { - // This is the top level, so it must be a reactor. - result.add((ReactorInstance) this); - } - ReactorInstance container = parent; - while (container != null) { - result.add(container); - container = container.parent; - } - return result; - } - - /** - * Return the root reactor, which is the top-level parent. - * @return The top-level parent. - */ - public ReactorInstance root() { - if (parent != null) { - return parent.root(); - } else { - return (ReactorInstance)this; - } - } - - /** - * Set the width. This method is here for testing only and should - * not be used for any other purpose. - * @param width The new width. - */ - public void setWidth(int width) { - this.width = width; - } - - /** - * Return an identifier for this instance, which has the form "a_b_c" - * or "a_b_c_n", where "c" is the name of this instance, "b" is the name - * of its container, and "a" is the name of its container, stopping - * at the container in main. All names are converted to lower case. - * The suffix _n is usually omitted, but it is possible to get name - * collisions using the above scheme, in which case _n will be an - * increasing integer until there is no collision. - * If the length of the root of the name as calculated above (the root - * is without the _n suffix) is longer than - * the static variable identifierLengthLimit, then the name will be - * truncated. The returned name will be the tail of the name calculated - * above with the prefix '_'. - * @return An identifier for this instance that is guaranteed to be - * unique within the top-level parent. - */ - public String uniqueID() { - if (_uniqueID == null) { - // Construct the unique ID only if it has not been - // previously constructed. - String prefix = getFullNameWithJoiner("_").toLowerCase(); - - // Replace all non-alphanumeric (Latin) characters with underscore. - prefix = prefix.replaceAll("[^A-Za-z0-9]", "_"); - - // Truncate, if necessary. - if (prefix.length() > identifierLengthLimit) { - prefix = '_' - + prefix.substring(prefix.length() - identifierLengthLimit + 1); - } - - // Ensure uniqueness. - ReactorInstance toplevel = root(); - if (toplevel.uniqueIDCount == null) { - toplevel.uniqueIDCount = new HashMap(); - } - var count = toplevel.uniqueIDCount.get(prefix); - if (count == null) { - toplevel.uniqueIDCount.put(prefix, 1); - _uniqueID = prefix; - } else { - toplevel.uniqueIDCount.put(prefix, count + 1); - // NOTE: The length of this name could exceed - // identifierLengthLimit. Is this OK? - _uniqueID = prefix + '_' + (count + 1); - } - } - return _uniqueID; - } - - /** - * Returns the directly/indirectly enclosing mode. - * @param direct flag whether to check only for direct enclosing mode - * or also consider modes of parent reactor instances. - * @return The mode, if any, null otherwise. - */ - public ModeInstance getMode(boolean direct) { - ModeInstance mode = null; - if (parent != null) { - if (!parent.modes.isEmpty()) { - mode = parent.modes.stream().filter(it -> it.contains(this)).findFirst().orElse(null); - } - if (mode == null && !direct) { - mode = parent.getMode(false); - } - } - return mode; - } - - /** - * Return a string of the form - * "a.b.c", where "." is replaced by the specified joiner, - * "c" is the name of this instance, "b" is the name - * of its container, and "a" is the name of its container, stopping - * at the container in main. - * @return A string representing this instance. - */ - public String getFullNameWithJoiner(String joiner) { - // This is not cached because _uniqueID is cached. - if (parent == null) { - return this.getName(); - } else if (getMode(true) != null) { - return parent.getFullNameWithJoiner(joiner) + joiner + getMode(true).getName() + joiner + this.getName(); - } else { - return parent.getFullNameWithJoiner(joiner) + joiner + this.getName(); - } - } - - ////////////////////////////////////////////////////// - //// Protected fields. - - /** The Instantiation AST object from which this was created. */ - T definition; - - /** The reactor instance that creates this instance. */ - ReactorInstance parent; - - /** - * Map from a name of the form a_b_c to the number of - * unique IDs with that prefix that have been already - * assigned. If none have been assigned, then there is - * no entry in this map. This map should be non-null only - * for the main reactor (the top level). - */ - HashMap uniqueIDCount; - - /** - * The width of this instance. This is 1 for everything - * except a PortInstance representing a multiport and a - * ReactorInstance representing a bank. - */ - int width = 1; - - ////////////////////////////////////////////////////// - //// Protected fields. - - /** - * The depth in the hierarchy of this instance. - * This is 0 for main or federated, 1 for the reactors immediately contained, etc. - */ - protected int depth = 0; - - ////////////////////////////////////////////////////// - //// Private fields. - - /** - * The full name of this instance. - */ - private String _fullName = null; - - /** - * Unique ID for this instance. This is null until - * uniqueID() is called. - */ - private String _uniqueID = null; -} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java deleted file mode 100644 index c036a931bd..0000000000 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ /dev/null @@ -1,598 +0,0 @@ -/** Representation of a runtime instance of a reaction. */ - -/************* -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 java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -import org.lflang.ASTUtils; -import org.lflang.TimeValue; -import org.lflang.lf.Action; -import org.lflang.lf.BuiltinTriggerRef; -import org.lflang.lf.Port; -import org.lflang.lf.Reaction; -import org.lflang.lf.Timer; -import org.lflang.lf.TriggerRef; -import org.lflang.lf.VarRef; -import org.lflang.lf.Variable; - -/** - * Representation of a compile-time instance of a reaction. - * Like {@link ReactorInstance}, if one or more parents of this reaction - * is a bank of reactors, then there will be more than one runtime instance - * corresponding to this compile-time instance. The {@link #getRuntimeInstances()} - * method returns a list of these runtime instances, each an instance of the - * inner class {@link ReactionInstance.Runtime}. Each runtime instance has a "level", which is - * its depth an acyclic precedence graph representing the dependencies between - * reactions at a tag. - * - * @author Edward A. Lee - * @author Marten Lohstroh - */ -public class ReactionInstance extends NamedInstance { - - /** - * Create a new reaction instance from the specified definition - * within the specified parent. This constructor should be called - * only by the ReactorInstance class, but it is public to enable unit tests. - * @param definition A reaction definition. - * @param parent The parent reactor instance, which cannot be null. - * @param isUnordered Indicator that this reaction is unordered w.r.t. other reactions. - * @param index The index of the reaction within the reactor (0 for the - * first reaction, 1 for the second, etc.). - */ - public ReactionInstance( - Reaction definition, - ReactorInstance parent, - boolean isUnordered, - int index - ) { - super(definition, parent); - this.index = index; - this.isUnordered = isUnordered; - - // If the reaction body starts with the magic string - // UNORDERED_REACTION_MARKER, then mark it unordered, - // overriding the argument. - String body = ASTUtils.toText(definition.getCode()); - if (body.contains(UNORDERED_REACTION_MARKER)) { - this.isUnordered = true; - } - - // Identify the dependencies for this reaction. - // First handle the triggers. - for (TriggerRef trigger : definition.getTriggers()) { - if (trigger instanceof VarRef) { - Variable variable = ((VarRef)trigger).getVariable(); - if (variable instanceof Port) { - PortInstance portInstance = parent.lookupPortInstance((Port)variable); - // If the trigger is the port of a contained bank, then the - // portInstance will be null and we have to instead search for - // each port instance in the bank. - if (portInstance != null) { - this.sources.add(portInstance); - portInstance.dependentReactions.add(this); - this.triggers.add(portInstance); - } else if (((VarRef)trigger).getContainer() != null) { - // Port belongs to a contained reactor or bank. - ReactorInstance containedReactor - = parent.lookupReactorInstance(((VarRef)trigger).getContainer()); - if (containedReactor != null) { - portInstance = containedReactor.lookupPortInstance((Port)variable); - if (portInstance != null) { - this.sources.add(portInstance); - portInstance.dependentReactions.add(this); - this.triggers.add(portInstance); - } - } - } - } else if (variable instanceof Action) { - var actionInstance = parent.lookupActionInstance( - (Action)((VarRef)trigger).getVariable()); - this.triggers.add(actionInstance); - actionInstance.dependentReactions.add(this); - this.sources.add(actionInstance); - } else if (variable instanceof Timer) { - var timerInstance = parent.lookupTimerInstance( - (Timer)((VarRef)trigger).getVariable()); - this.triggers.add(timerInstance); - timerInstance.dependentReactions.add(this); - this.sources.add(timerInstance); - } - } else if (trigger instanceof BuiltinTriggerRef) { - var builtinTriggerInstance - = parent.getOrCreateBuiltinTrigger((BuiltinTriggerRef) trigger); - this.triggers.add(builtinTriggerInstance); - builtinTriggerInstance.dependentReactions.add(this); - } - } - // Next handle the ports that this reaction reads. - for (VarRef source : definition.getSources()) { - Variable variable = source.getVariable(); - if (variable instanceof Port) { - var portInstance = parent.lookupPortInstance((Port)variable); - - // If the trigger is the port of a contained bank, then the - // portInstance will be null and we have to instead search for - // each port instance in the bank. - if (portInstance != null) { - this.sources.add(portInstance); - this.reads.add(portInstance); - portInstance.dependentReactions.add(this); - } else if (source.getContainer() != null) { - ReactorInstance containedReactor - = parent.lookupReactorInstance(source.getContainer()); - if (containedReactor != null) { - portInstance = containedReactor.lookupPortInstance((Port)variable); - if (portInstance != null) { - this.sources.add(portInstance); - portInstance.dependentReactions.add(this); - this.triggers.add(portInstance); - } - } - } - } - } - - // Finally, handle the effects. - for (VarRef effect : definition.getEffects()) { - Variable variable = effect.getVariable(); - if (variable instanceof Port) { - var portInstance = parent.lookupPortInstance(effect); - if (portInstance != null) { - this.effects.add(portInstance); - portInstance.dependsOnReactions.add(this); - } else { - throw new InvalidSourceException( - "Unexpected effect. Cannot find port " + variable.getName()); - } - } else if (variable instanceof Action) { - // Effect is an Action. - var actionInstance = parent.lookupActionInstance( - (Action)variable); - this.effects.add(actionInstance); - actionInstance.dependsOnReactions.add(this); - } else { - // Effect is either a mode or an unresolved reference. - // Do nothing, transitions will be set up by the ModeInstance. - } - } - // Create a deadline instance if one has been defined. - if (this.definition.getDeadline() != null) { - this.declaredDeadline = new DeadlineInstance( - this.definition.getDeadline(), this); - } - } - - ////////////////////////////////////////////////////// - //// Public fields. - - /** - * Indicates the chain this reaction is a part of. It is constructed - * through a bit-wise or among all upstream chains. Each fork in the - * dependency graph setting a new, unused bit to true in order to - * disambiguate it from parallel chains. Note that zero results in - * no overlap with any other reaction, which means the reaction can - * execute in parallel with any other reaction. The default is 1L. - * If left at the default, parallel execution will be based purely - * on levels. - */ - public long chainID = 1L; - - /** - * The ports or actions that this reaction may write to. - */ - public Set> effects = new LinkedHashSet<>(); - - /** - * The ports, actions, or timers that this reaction is triggered by or uses. - */ - public Set> sources = new LinkedHashSet<>(); - // FIXME: Above sources is misnamed because in the grammar, - // "sources" are only the inputs a reaction reads without being - // triggered by them. The name "reads" used here would be a better - // choice in the grammar. - - /** - * 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. - * This is that magic comment. - */ - public static String UNORDERED_REACTION_MARKER - = "**** This reaction is unordered."; - - /** - * Index of order of occurrence within the reactor definition. - * The first reaction has index 0, the second index 1, etc. - */ - public int index; - - /** - * Whether or not this reaction is ordered with respect to other - * reactions in the same reactor. - */ - public boolean isUnordered; - - /** - * The ports that this reaction reads but that do not trigger it. - */ - public Set> reads = new LinkedHashSet<>(); - - /** - * The trigger instances (input ports, timers, and actions - * that trigger reactions) that trigger this reaction. - */ - public Set> triggers = new LinkedHashSet<>(); - - ////////////////////////////////////////////////////// - //// Public methods. - - /** - * Clear caches used in reporting dependentReactions() and dependsOnReactions(). - * This method should be called if any changes are made to triggers, sources, - * or effects. - * @param includingRuntimes If false, leave the runtime instances 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) { - dependentReactionsCache = null; - dependsOnReactionsCache = null; - if (includingRuntimes) runtimeInstances = null; - } - - /** - * Return the set of immediate downstream reactions, which are reactions - * that receive data produced by this reaction plus - * at most one reaction in the same reactor whose definition - * lexically follows this one (unless this reaction is unordered). - */ - public Set dependentReactions() { - // Cache the result. - if (dependentReactionsCache != null) return dependentReactionsCache; - dependentReactionsCache = new LinkedHashSet<>(); - - // First, add the next lexical reaction, if appropriate. - if (!isUnordered && parent.reactions.size() > index + 1) { - // Find the next reaction in the parent's reaction list. - dependentReactionsCache.add(parent.reactions.get(index + 1)); - } - - // Next, add reactions that get data from this one via a port. - for (TriggerInstance effect : effects) { - if (effect instanceof PortInstance) { - for (SendRange senderRange - : ((PortInstance)effect).eventualDestinations()) { - for (RuntimeRange destinationRange - : senderRange.destinations) { - dependentReactionsCache.addAll( - destinationRange.instance.dependentReactions); - } - } - } - } - return dependentReactionsCache; - } - - /** - * Return the set of immediate upstream reactions, which are reactions - * that send data to this one plus at most one reaction in the same - * reactor whose definition immediately precedes the definition of this one - * (unless this reaction is unordered). - */ - public Set dependsOnReactions() { - // Cache the result. - if (dependsOnReactionsCache != null) return dependsOnReactionsCache; - dependsOnReactionsCache = new LinkedHashSet<>(); - - // First, add the previous lexical reaction, if appropriate. - if (!isUnordered && index > 0) { - // Find the previous ordered reaction in the parent's reaction list. - int earlierIndex = index - 1; - ReactionInstance earlierOrderedReaction = parent.reactions.get(earlierIndex); - while (earlierOrderedReaction.isUnordered && --earlierIndex >= 0) { - earlierOrderedReaction = parent.reactions.get(earlierIndex); - } - if (earlierIndex >= 0) { - dependsOnReactionsCache.add(parent.reactions.get(index - 1)); - } - } - - // Next, add reactions that send data to this one. - for (TriggerInstance source : sources) { - if (source instanceof PortInstance) { - // First, add reactions that send data through an intermediate port. - for (RuntimeRange senderRange - : ((PortInstance)source).eventualSources()) { - dependsOnReactionsCache.addAll(senderRange.instance.dependsOnReactions); - } - // Then, add reactions that send directly to this port. - dependsOnReactionsCache.addAll(source.dependsOnReactions); - } - } - return dependsOnReactionsCache; - } - - /** - * Return a set of levels that runtime instances of this reaction have. - * A ReactionInstance may have more than one level if it lies within - * a bank and its dependencies on other reactions pass through multiports. - */ - public Set getLevels() { - Set result = new LinkedHashSet<>(); - // Force calculation of levels if it has not been done. - // FIXME: Comment out this as I think it is redundant. - // If it is NOT redundant then deadline propagation is not correct - // parent.assignLevels(); - for (Runtime runtime : runtimeInstances) { - result.add(runtime.level); - } - return result; - } - - /** - * Return a set of deadlines that runtime instances of this reaction have. - * A ReactionInstance may have more than one deadline if it lies within. - */ - public Set getInferredDeadlines() { - Set result = new LinkedHashSet<>(); - for (Runtime runtime : runtimeInstances) { - result.add(runtime.deadline); - } - return result; - } - - - /** - * Return a list of levels that runtime instances of this reaction have. - * The size of this list is the total number of runtime instances. - * A ReactionInstance may have more than one level if it lies within - * a bank and its dependencies on other reactions pass through multiports. - */ - public List getLevelsList() { - List result = new LinkedList<>(); - // Force calculation of levels if it has not been done. - // FIXME: Comment out this as I think it is redundant. - // If it is NOT redundant then deadline propagation is not correct - // parent.assignLevels(); - for (Runtime runtime : runtimeInstances) { - result.add(runtime.level); - } - return result; - } - - /** - * Return a list of the deadlines that runtime instances of this reaction have. - * The size of this list is the total number of runtime instances. - * A ReactionInstance may have more than one deadline if it lies within - */ - public List getInferredDeadlinesList() { - List result = new LinkedList<>(); - for (Runtime runtime : runtimeInstances) { - result.add(runtime.deadline); - } - return result; - } - - - /** - * Return the name of this reaction, which is 'reaction_n', - * where n is replaced by the reaction index. - * @return The name of this reaction. - */ - @Override - public String getName() { - return "reaction_" + this.index; - } - - /** - * Return an array of runtime instances of this reaction in a - * **natural order**, defined as follows. The position within the - * returned list of the runtime instance is given by a mixed-radix - * number where the low-order digit is the bank index within the - * container reactor (or 0 if it is not a bank), the second low order - * digit is the bank index of the container's container (or 0 if - * it is not a bank), etc., until the container that is directly - * contained by the top level (the top-level reactor need not be - * included because its index is always 0). - * - * The size of the returned array is the product of the widths of all of the - * container ReactorInstance objects. If none of these is a bank, - * then the size will be 1. - * - * This method creates this array the first time it is called, but then - * holds on to it. The array is used by {@link ReactionInstanceGraph} - * to determine and record levels and deadline for runtime instances - * of reactors. - */ - public List getRuntimeInstances() { - if (runtimeInstances != null) return runtimeInstances; - int size = parent.getTotalWidth(); - // If the width cannot be determined, assume there is only one instance. - if (size < 0) size = 1; - runtimeInstances = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - Runtime r = new Runtime(); - r.id = i; - if (declaredDeadline != null) { - r.deadline = declaredDeadline.maxDelay; - } - runtimeInstances.add(r); - } - return runtimeInstances; - } - - /** - * Purge 'portInstance' from this reaction, removing it from the list - * of triggers, sources, effects, and reads. Note that this leaves - * the runtime instances intact, including their level information. - */ - public void removePortInstance(PortInstance portInstance) { - this.triggers.remove(portInstance); - this.sources.remove(portInstance); - this.effects.remove(portInstance); - this.reads.remove(portInstance); - clearCaches(false); - portInstance.clearCaches(); - } - - /** - * Return a descriptive string. - */ - @Override - public String toString() { - return getName() + " of " + parent.getFullName(); - } - - /** - * Determine logical execution time for each reaction during compile - * time based on immediate downstream logical delays (after delays and actions) - * and label each reaction with the minimum of all such delays. - */ - public TimeValue assignLogicalExecutionTime() { - if (this.let != null) { - return this.let; - } - - if (this.parent.isGeneratedDelay()) { - return this.let = TimeValue.ZERO; - } - - TimeValue let = null; - - // Iterate over effect and find minimum delay. - for (TriggerInstance effect : effects) { - if (effect instanceof PortInstance) { - var afters = this.parent.getParent().children.stream().filter(c -> { - if (c.isGeneratedDelay()) { - return c.inputs.get(0).getDependsOnPorts().get(0).instance - .equals((PortInstance) effect); - } - return false; - }).map(c -> c.actions.get(0).getMinDelay()) - .min(TimeValue::compare); - - if (afters.isPresent()) { - if (let == null) { - let = afters.get(); - } else { - let = TimeValue.min(afters.get(), let); - } - } - } else if (effect instanceof ActionInstance) { - var action = ((ActionInstance) effect).getMinDelay(); - if (let == null) { - let = action; - } else { - let = TimeValue.min(action, let); - } - } - } - - if (let == null) { - let = TimeValue.ZERO; - } - return this.let = let; - } - - ////////////////////////////////////////////////////// - //// Private variables. - - /** Cache of the set of downstream reactions. */ - private Set dependentReactionsCache; - - /** Cache of the set of upstream reactions. */ - private Set dependsOnReactionsCache; - - /** - * Array of runtime instances of this reaction. - * This has length 1 unless the reaction is contained - * by one or more banks. Suppose that this reaction - * has depth 3, with full name r0.r1.r2.r. The top-level - * reactor is r0, which contains r1, which contains r2, - * which contains this reaction r. Suppose the widths - * of the containing reactors are w0, w1, and w2, and - * we are interested in the instance at bank indexes - * b0, b1, and b2. That instance is in this array at - * location given by the **natural ordering**, which - * is the mixed radix number b2%w2; b1%w1. - */ - private List runtimeInstances; - - private TimeValue let = null; - - /////////////////////////////////////////////////////////// - //// Inner classes - - /** Inner class representing a runtime instance of a reaction. */ - public class Runtime { - public TimeValue deadline; - // If this reaction instance depends on exactly one upstream - // reaction (via a port), then the "dominating" field will - // point to that upstream reaction. - public Runtime dominating; - /** ID ranging from 0 to parent.getTotalWidth() - 1. */ - public int id; - public int level; - - public ReactionInstance getReaction() { - return ReactionInstance.this; - } - - @Override - public String toString() { - String result = ReactionInstance.this + "(level: " + level; - if (deadline != null && deadline != TimeValue.MAX_VALUE) { - result += ", deadline: " + deadline; - } - if (dominating != null) { - result += ", dominating: " + dominating.getReaction(); - } - result += ")"; - return result; - } - - public Runtime() { - this.dominating = null; - this.id = 0; - this.level = 0; - if (ReactionInstance.this.declaredDeadline != null) { - this.deadline = ReactionInstance.this.declaredDeadline.maxDelay; - } else { - this.deadline = TimeValue.MAX_VALUE; - } - } - } -} diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java deleted file mode 100644 index 9b601cae53..0000000000 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ /dev/null @@ -1,440 +0,0 @@ -/** A graph that represents causality cycles formed by reaction instances. */ - -/************* -Copyright (c) 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; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import org.lflang.generator.ReactionInstance.Runtime; -import org.lflang.generator.c.CUtil; -import org.lflang.graph.PrecedenceGraph; -import org.lflang.lf.Variable; - -/** - * This class analyzes the dependencies between reaction runtime instances. - * For each ReactionInstance, there may be more than one runtime instance because - * the ReactionInstance may be nested within one or more banks. - * 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. - */ - public ReactionInstanceGraph(ReactorInstance main) { - this.main = main; - rebuild(); - } - - /////////////////////////////////////////////////////////// - //// Public fields - - /** - * 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 - * adds all the nodes and edges. - */ - public void rebuild() { - this.clear(); - addNodesAndEdges(main); - - // FIXME: Use {@link TargetProperty#EXPORT_DEPENDENCY_GRAPH}. - // System.out.println(toDOT()); - - // Assign a level to each reaction. - // If there are cycles present in the graph, it will be detected here. - assignLevels(); - if (nodeCount() != 0) { - // The graph has cycles. - // main.reporter.reportError("Reactions form a cycle! " + toString()); - // Do not throw an exception so that cycle visualization can proceed. - // throw new InvalidSourceException("Reactions form a cycle!"); - } - } - /** - * This function rebuilds the graph and propagates and assigns deadlines - * to all reactions. - */ - public void rebuildAndAssignDeadlines() { - this.clear(); - addNodesAndEdges(main); - assignInferredDeadlines(); - this.clear(); - } - - /* - * 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() { - return numReactionsPerLevel.toArray(new Integer[0]); - } - - /** - * Return the max breadth of the reaction dependency graph - */ - public int getBreadth() { - var maxBreadth = 0; - for (Integer breadth: numReactionsPerLevel ) { - if (breadth > maxBreadth) { - maxBreadth = breadth; - } - } - return maxBreadth; - } - - /////////////////////////////////////////////////////////// - //// Protected methods - - /** - * Add to the graph edges between the given reaction and all the reactions - * that depend on the specified port. - * @param port The port that the given reaction has as an effect. - * @param reaction The reaction to relate downstream reactions to. - */ - protected void addDownstreamReactions(PortInstance port, ReactionInstance reaction) { - // 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); - for (ReactionInstance dstReaction : dstRange.instance.dependentReactions) { - List dstRuntimes = dstReaction.getRuntimeInstances(); - Runtime srcRuntime = srcRuntimes.get(srcIndex); - Runtime dstRuntime = dstRuntimes.get(dstIndex); - // Only add this dependency if the reactions are not in modes at all or in the same mode or in modes of separate reactors - // This allows modes to break cycles since modes are always mutually exclusive. - if (srcRuntime.getReaction().getMode(true) == null || - dstRuntime.getReaction().getMode(true) == null || - srcRuntime.getReaction().getMode(true) == dstRuntime.getReaction().getMode(true) || - srcRuntime.getReaction().getParent() != dstRuntime.getReaction().getParent()) { - addEdge(dstRuntime, srcRuntime); - } - - // Propagate the deadlines, if any. - 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. - if (this.getUpstreamAdjacentNodes(dstRuntime).size() == 1 - && (dstRuntime.getReaction().isUnordered - || dstRuntime.getReaction().index == 0)) { - dstRuntime.dominating = srcRuntime; - } else { - dstRuntime.dominating = null; - } - } - dstRangePosition.increment(); - sendRangePosition.increment(); - sendRangeCount++; - if (sendRangeCount >= sendRange.width) { - // Reset to multicast. - sendRangeCount = 0; - sendRangePosition = sendRange.startMR(); - } - } - } - } - } - - /** - * 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. - */ - 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); - } - - // If this is not an unordered reaction, then create a dependency - // on any previously defined reaction. - if (!reaction.isUnordered) { - // If there is an earlier reaction in this same reactor, then - // create a link in the reaction graph for all runtime instances. - if (previousReaction != null) { - List previousRuntimes = previousReaction.getRuntimeInstances(); - int count = 0; - for (Runtime runtime : runtimes) { - // Only add the reaction order edge if previous reaction is outside of a mode or both are in the same mode - // This allows modes to break cycles since modes are always mutually exclusive. - if (runtime.getReaction().getMode(true) == null || runtime.getReaction().getMode(true) == reaction.getMode(true)) { - this.addEdge(runtime, previousRuntimes.get(count)); - count++; - } - } - } - previousReaction = reaction; - } - - // Add downstream reactions. Note that this is sufficient. - // We don't need to also add upstream reactions because this reaction - // will be downstream of those upstream reactions. - for (TriggerInstance effect : reaction.effects) { - if (effect instanceof PortInstance) { - addDownstreamReactions((PortInstance)effect, reaction); - } - } - } - // Recursively add nodes and edges from contained reactors. - for (ReactorInstance child : reactor.children) { - addNodesAndEdges(child); - } - } - - /////////////////////////////////////////////////////////// - //// Private fields - - /** - * 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)); - - /////////////////////////////////////////////////////////// - //// Private methods - - /** - * 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 - * the levels of the reactions it depends on. - */ - 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; - // the graph must be cyclic. - while (!start.isEmpty()) { - Runtime origin = start.remove(0); - Set toRemove = new LinkedHashSet<>(); - Set downstreamAdjacentNodes = getDownstreamAdjacentNodes(origin); - - // Visit effect nodes. - 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; - } - // Remove visited edges. - for (Runtime effect : toRemove) { - removeEdge(effect, origin); - // If the effect node has no more incoming edges, - // then move it in the start set. - if (getUpstreamAdjacentNodes(effect).size() == 0) { - start.add(effect); - } - } - - // Remove visited origin. - removeNode(origin); - - // Update numReactionsPerLevel info - 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); - Set toRemove = new LinkedHashSet<>(); - Set upstreamAdjacentNodes = getUpstreamAdjacentNodes(origin); - - // Visit effect nodes. - 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; - } - } - // Remove visited edges. - for (Runtime upstream : toRemove) { - removeEdge(origin, upstream); - // If the upstream node has no more outgoing edges, - // then move it in the start set. - if (getDownstreamAdjacentNodes(upstream).size() == 0) { - start.add(upstream); - } - } - - // Remove visited origin. - removeNode(origin); - } - } - - /** - * Adjust {@link #numReactionsPerLevel} at index level by - * adding to the previously recorded number valueToAdd. - * If there is no previously recorded number for this level, then - * create one with index level and value valueToAdd. - * @param level The level. - * @param valueToAdd The value to add to the number of levels. - */ - private void adjustNumReactionsPerLevel(int level, int valueToAdd) { - if (numReactionsPerLevel.size() > level) { - numReactionsPerLevel.set(level, numReactionsPerLevel.get(level) + valueToAdd); - } else { - while (numReactionsPerLevel.size() < level) { - numReactionsPerLevel.add(0); - } - numReactionsPerLevel.add(valueToAdd); - } - } - - /** - * Return the DOT (GraphViz) representation of the graph. - */ - @Override - public String toDOT() { - var dotRepresentation = new CodeBuilder(); - var edges = new StringBuilder(); - - // Start the digraph with a left-write rank - dotRepresentation.pr( - """ - digraph { - rankdir=LF; - graph [compound=True, rank=LR, rankdir=LR]; - node [fontname=Times, shape=rectangle]; - edge [fontname=Times]; - """); - - var nodes = nodes(); - // Group nodes by levels - var groupedNodes = - nodes.stream() - .collect( - Collectors.groupingBy(it -> it.level) - ); - - dotRepresentation.indent(); - // For each level - for (var level : groupedNodes.keySet()) { - // Create a subgraph - dotRepresentation.pr("subgraph cluster_level_" + level + " {"); - dotRepresentation.pr(" graph [compound=True, label = \"level " + level + "\"];"); - - // Get the nodes at the current level - var currentLevelNodes = groupedNodes.get(level); - for (var node: currentLevelNodes) { - // Draw the node - var label = CUtil.getName(node.getReaction().getParent().reactorDeclaration) + "." + node.getReaction().getName(); - // Need a positive number to name the nodes in GraphViz - var labelHashCode = label.hashCode() & 0xfffffff; - dotRepresentation.pr(" node_" + labelHashCode + " [label=\""+ label +"\"];"); - - // Draw the edges - var downstreamNodes = getDownstreamAdjacentNodes(node); - for (var downstreamNode: downstreamNodes) { - var downstreamLabel = CUtil.getName(downstreamNode.getReaction().getParent().reactorDeclaration) + "." + downstreamNode.getReaction().getName(); - edges.append(" node_" + labelHashCode + " -> node_" + - (downstreamLabel.hashCode() & 0xfffffff) + ";\n" - ); - } - } - // Close the subgraph - dotRepresentation.pr("}"); - } - dotRepresentation.unindent(); - // Add the edges to the definition of the graph at the bottom - dotRepresentation.pr(edges); - // Close the digraph - dotRepresentation.pr("}"); - - // Return the DOT representation - return dotRepresentation.toString(); - } -} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java deleted file mode 100644 index 5a183421be..0000000000 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ /dev/null @@ -1,1156 +0,0 @@ -/** 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. -***************/ - -package org.lflang.generator; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -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; -import org.lflang.TimeValue; -import org.lflang.generator.TriggerInstance.BuiltinTriggerVariable; -import org.lflang.lf.Action; -import org.lflang.lf.BuiltinTrigger; -import org.lflang.lf.BuiltinTriggerRef; -import org.lflang.lf.Connection; -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; -import org.lflang.lf.ParameterReference; -import org.lflang.lf.Port; -import org.lflang.lf.Reaction; -import org.lflang.lf.Reactor; -import org.lflang.lf.ReactorDecl; -import org.lflang.lf.StateVar; -import org.lflang.lf.Timer; -import org.lflang.lf.VarRef; -import org.lflang.lf.Variable; -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. - * - * @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 final 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 state variable instances belonging to this reactor instance. */ - public final List states = new ArrayList<>(); - - /** The parameters of this instance. */ - public final List parameters = new ArrayList<>(); - - /** List of reaction instances for this reactor instance. */ - public final List reactions = new ArrayList<>(); - - /** The timer instances belonging to this reactor instance. */ - public final List timers = new ArrayList<>(); - - /** The mode instances belonging to this reactor instance. */ - public final List modes = new ArrayList<>(); - - /** The reactor declaration in the AST. This is either an import or Reactor declaration. */ - public final ReactorDecl reactorDeclaration; - - /** The reactor after imports are resolve. */ - public final Reactor reactorDefinition; - - /** Indicator that this reactor has itself as a parent, an error condition. */ - public final boolean recursive; - - ////////////////////////////////////////////////////// - //// Public methods. - - /** - * Assign levels to all reactions within the same root as this - * reactor. The level of a reaction r is equal to the length of the - * longest chain of reactions that must have the opportunity to - * execute before r at each logical tag. This fails and returns - * false if a causality cycle exists. - * - * This method uses a variant of Kahn's algorithm, which is linear - * in V + E, where V is the number of vertices (reactions) and E - * is the number of edges (dependencies between reactions). - * - * @return An empty graph if successful and otherwise a graph - * with runtime reaction instances that form cycles. - */ - public ReactionInstanceGraph assignLevels() { - if (depth != 0) return root().assignLevels(); - if (cachedReactionLoopGraph == null) { - cachedReactionLoopGraph = new ReactionInstanceGraph(this); - } - return cachedReactionLoopGraph; - } - - /** - * 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; - } - - /** - * 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 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 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 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 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 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 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()); - } - - /** - * 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()); - } - - /** - * 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()); - } 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 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) { - 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); - } - } - - ////////////////////////////////////////////////////// - //// 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)); - } - - // Instantiate state variables for this reactor instance. - for (StateVar state : ASTUtils.allStateVars(reactorDefinition)) { - this.states.add(new StateVariableInstance(state, 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; - } - // 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; - } - // 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; - } - - /** - * 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/TriggerInstance.java b/org.lflang/src/org/lflang/generator/TriggerInstance.java deleted file mode 100644 index 7130dc8b90..0000000000 --- a/org.lflang/src/org/lflang/generator/TriggerInstance.java +++ /dev/null @@ -1,180 +0,0 @@ -/** Instance of a trigger (port, action, or timer). */ - -/************* -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.generator; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.lflang.lf.BuiltinTrigger; -import org.lflang.lf.BuiltinTriggerRef; -import org.lflang.lf.TriggerRef; -import org.lflang.lf.Variable; -import org.lflang.lf.impl.VariableImpl; - -/** Instance of a trigger (port, action, or timer). - * - * @author Marten Lohstroh - * @author Edward A. Lee - * @author Alexander Schulz-Rosengarten - */ -public class TriggerInstance extends NamedInstance { - - /** Construct a new instance with the specified definition - * and parent. E.g., for a action instance, the definition - * is Action, and for a port instance, it is Port. These are - * nodes in the AST. This is protected because only subclasses - * should be constructed. - * @param definition The definition in the AST for this instance. - * @param parent The reactor instance that creates this instance. - */ - protected TriggerInstance(T definition, ReactorInstance parent) { - super(definition, parent); - } - - /** - * Construct a new instance for a special builtin trigger. - * - * @param trigger The actual trigger definition. - * @param parent The reactor instance that creates this instance. - */ - static TriggerInstance builtinTrigger(BuiltinTriggerRef trigger, ReactorInstance parent) { - return new TriggerInstance<>(new BuiltinTriggerVariable(trigger), parent); - } - - ///////////////////////////////////////////// - //// Public Methods - - /** - * Return the reaction instances that are triggered or read by this trigger. - * If this port is an output, then the reaction instances - * belong to the parent of the port's parent. If the port - * is an input, then the reaction instances belong to the - * port's parent. - */ - public Set getDependentReactions() { - return dependentReactions; - } - - /** - * Return the reaction instances that may send data via this port. - * If this port is an input, then the reaction instance - * belongs to parent of the port's parent. If it is an output, - * the reaction instance belongs to the port's parent. - */ - public Set getDependsOnReactions() { - return dependsOnReactions; - } - - /** - * Return the name of this trigger. - * @return The name of this trigger. - */ - @Override - public String getName() { - return definition.getName(); - } - - /** - * Return true if this trigger is "shutdown". - */ - public boolean isShutdown() { - return isBuiltInType(BuiltinTrigger.SHUTDOWN); - } - - /** - * Return true if this trigger is "startup"./ - */ - public boolean isStartup() { - return isBuiltInType(BuiltinTrigger.STARTUP); - } - - /** - * Return true if this trigger is "startup"./ - */ - public boolean isReset() { - return isBuiltInType(BuiltinTrigger.RESET); - } - - ///////////////////////////////////////////// - //// Private Methods - - private boolean isBuiltInType(BuiltinTrigger type) { - return this.definition instanceof BuiltinTriggerVariable - && ((BuiltinTriggerRef) ((BuiltinTriggerVariable) this.definition).definition).getType().equals(type); - } - - ///////////////////////////////////////////// - //// Protected Fields - - /** - * Reaction instances that are triggered or read by this trigger. - * If this port is an output, then the reaction instances - * belong to the parent of the port's parent. If the port - * is an input, then the reaction instances belong to the - * port's parent. - */ - Set dependentReactions = new LinkedHashSet<>(); - - /** - * Reaction instances that may send data via this port. - * If this port is an input, then the reaction instance - * belongs to parent of the port's parent. If it is an output, - * the reaction instance belongs to the port's parent. - */ - Set dependsOnReactions = new LinkedHashSet<>(); - - ///////////////////////////////////////////// - //// Special class for builtin triggers - - /** - * This class allows to have BuiltinTriggers represented by a Variable type. - */ - static public class BuiltinTriggerVariable extends VariableImpl { - - /** The builtin trigger type represented by this variable. */ - public final BuiltinTrigger type; - - /** The actual TriggerRef definition in the AST. */ - public final TriggerRef definition; - - public BuiltinTriggerVariable(BuiltinTriggerRef trigger) { - this.type = trigger.getType(); - this.definition = trigger; - } - - @Override - public String getName() { - return this.type.name().toLowerCase(); - } - - @Override - public void setName(String newName) { - throw new UnsupportedOperationException( - this.getClass().getName() + " has an immutable name."); - } - } -} diff --git a/org.lflang/src/org/lflang/validation/AttributeSpec.java b/org.lflang/src/org/lflang/validation/AttributeSpec.java deleted file mode 100644 index 006e6c228c..0000000000 --- a/org.lflang/src/org/lflang/validation/AttributeSpec.java +++ /dev/null @@ -1,241 +0,0 @@ -/************* - * 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.validation; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import org.lflang.ASTUtils; -import org.lflang.lf.AttrParm; -import org.lflang.lf.Attribute; -import org.lflang.lf.LfPackage.Literals; -import org.lflang.util.StringUtil; - -/** - * Specification of the structure of an attribute annotation. - * @author Clément Fournier - * @author Shaokai Lin - */ -public class AttributeSpec { - - private final Map paramSpecByName; - - public static final String VALUE_ATTR = "value"; - public static final String NETWORK_MESSAGE_ACTIONS = "network_message_actions"; - public static final String EACH_ATTR = "each"; - - /** A map from a string to a supported AttributeSpec */ - public static final Map ATTRIBUTE_SPECS_BY_NAME = new HashMap<>(); - - public AttributeSpec(List params) { - if (params != null) { - paramSpecByName = params.stream().collect(Collectors.toMap(it -> it.name, it -> it)); - } else { - paramSpecByName = null; - } - } - - /** - * Check that the attribute conforms to this spec and whether - * attr has the correct name. - */ - public void check(LFValidator validator, Attribute attr) { - Set seen; - // If there is just one parameter, it is required to be named "value". - if (attr.getAttrParms() != null - && attr.getAttrParms().size() == 1 - && attr.getAttrParms().get(0).getName() == null) { - // If we are in this branch, - // then the user has provided @attr("value"), - // which is a shorthand for @attr(value="value"). - if (paramSpecByName == null) { - validator.error("Attribute doesn't take a parameter.", Literals.ATTRIBUTE__ATTR_NAME); - return; - } - AttrParamSpec valueSpec = paramSpecByName.get(VALUE_ATTR); - if (valueSpec == null) { - validator.error("Attribute doesn't have a 'value' parameter.", Literals.ATTR_PARM__NAME); - return; - } - - valueSpec.check(validator, attr.getAttrParms().get(0)); - seen = Set.of(VALUE_ATTR); - } else { - // Process multiple parameters, each of which has to be named. - seen = processNamedAttrParms(validator, attr); - } - - // Check if there are any missing parameters required by this attribute. - if (paramSpecByName != null) { - Map missingParams = new HashMap<>(paramSpecByName); - missingParams.keySet().removeAll(seen); - missingParams.forEach((name, paramSpec) -> { - if (!paramSpec.isOptional) { - validator.error("Missing required attribute parameter '" + name + "'.", Literals.ATTRIBUTE__ATTR_PARMS); - } - }); - } - } - - /** - * Check whether the attribute parameters are named, whether - * these names are known, and whether the named parameters - * conform to the param spec (whether the param has the - * right type, etc.). - * - * @param validator The current validator in use. - * @param attr The attribute being checked. - * @return A set of named attribute parameters the user provides. - */ - private Set processNamedAttrParms(LFValidator validator, Attribute attr) { - Set seen = new HashSet<>(); - if (attr.getAttrParms() != null) { - for (AttrParm parm : attr.getAttrParms()) { - if (paramSpecByName == null) { - validator.error("Attribute does not take parameters.", Literals.ATTRIBUTE__ATTR_NAME); - break; - } - if (parm.getName() == null) { - validator.error("Missing name for attribute parameter.", Literals.ATTRIBUTE__ATTR_NAME); - continue; - } - - AttrParamSpec parmSpec = paramSpecByName.get(parm.getName()); - if (parmSpec == null) { - validator.error("\"" + parm.getName() + "\"" + " is an unknown attribute parameter.", - Literals.ATTRIBUTE__ATTR_NAME); - continue; - } - // Check whether a parameter conforms to its spec. - parmSpec.check(validator, parm); - seen.add(parm.getName()); - } - } - return seen; - } - - /** - * The specification of the attribute parameter. - * - * @param name The name of the attribute parameter - * @param type The type of the parameter - * @param isOptional True if the parameter is optional. - */ - record AttrParamSpec(String name, AttrParamType type, boolean isOptional) { - - // Check if a parameter has the right type. - // Currently, only String, Int, Boolean, Float, and target language are supported. - public void check(LFValidator validator, AttrParm parm) { - switch (type) { - case STRING -> { - if (!StringUtil.hasQuotes(parm.getValue())) { - validator.error("Incorrect type: \"" + parm.getName() + "\"" - + " should have type String.", - Literals.ATTRIBUTE__ATTR_NAME); - } - } - case INT -> { - if (!ASTUtils.isInteger(parm.getValue())) { - validator.error( - "Incorrect type: \"" + parm.getName() + "\"" - + " should have type Int.", - Literals.ATTRIBUTE__ATTR_NAME); - } - } - case BOOLEAN -> { - if (!ASTUtils.isBoolean(parm.getValue())) { - validator.error( - "Incorrect type: \"" + parm.getName() + "\"" - + " should have type Boolean.", - Literals.ATTRIBUTE__ATTR_NAME); - } - } - case FLOAT -> { - if (!ASTUtils.isFloat(parm.getValue())) { - validator.error( - "Incorrect type: \"" + parm.getName() + "\"" - + " should have type Float.", - Literals.ATTRIBUTE__ATTR_NAME); - } - } - default -> throw new IllegalArgumentException("unexpected type"); - } - } - } - - /** - * The type of attribute parameters currently supported. - */ - enum AttrParamType { - STRING, - INT, - BOOLEAN, - FLOAT, - } - - /* - * The specs of the known annotations are declared here. - * Note: If an attribute only has one parameter, the parameter name should be "value." - */ - static { - // @label("value") - ATTRIBUTE_SPECS_BY_NAME.put("label", new AttributeSpec( - List.of(new AttrParamSpec(VALUE_ATTR, AttrParamType.STRING, false)) - )); - // @sparse - ATTRIBUTE_SPECS_BY_NAME.put("sparse", new AttributeSpec(null)); - // @icon("value") - ATTRIBUTE_SPECS_BY_NAME.put("icon", new AttributeSpec( - List.of(new AttrParamSpec(VALUE_ATTR, AttrParamType.STRING, false)) - )); - // @property(name="", tactic="", spec="") - // SMTL is the safety fragment of Metric Temporal Logic (MTL). - ATTRIBUTE_SPECS_BY_NAME.put("property", new AttributeSpec( - List.of( - new AttrParamSpec("name", AttrParamType.STRING, false), - new AttrParamSpec("tactic", AttrParamType.STRING, false), - new AttrParamSpec("spec", AttrParamType.STRING, false), - new AttrParamSpec("CT", AttrParamType.INT, true), - new AttrParamSpec("expect", AttrParamType.BOOLEAN, true) - ) - )); - // @enclave(each=boolean) - ATTRIBUTE_SPECS_BY_NAME.put("enclave", new AttributeSpec( - List.of(new AttrParamSpec(EACH_ATTR, AttrParamType.BOOLEAN, true)) - )); - - // attributes that are used internally only by the federated code generation - ATTRIBUTE_SPECS_BY_NAME.put("_unordered", new AttributeSpec(null)); - ATTRIBUTE_SPECS_BY_NAME.put("_fed_config", new AttributeSpec( - List.of(new AttrParamSpec(AttributeSpec.NETWORK_MESSAGE_ACTIONS, - AttrParamType.STRING, false)))); - ATTRIBUTE_SPECS_BY_NAME.put("_c_body", new AttributeSpec(null)); - } -} From 8b20b53f95ddc16b2f3891b9641ede8e6a1f272d Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 5 Jun 2023 01:15:45 +0800 Subject: [PATCH 131/516] Update gradle and relocate analyses and dsl folders --- build.gradle | 11 - core/build.gradle | 36 +- .../analyses/cast/AbstractAstVisitor.java | 17 + .../org/lflang/analyses/cast/AstUtils.java | 77 + .../org/lflang/analyses/cast/AstVisitor.java | 25 + .../cast/BuildAstParseTreeVisitor.java | 691 +++++++ .../java/org/lflang/analyses/cast/CAst.java | 451 +++++ .../org/lflang/analyses/cast/CAstVisitor.java | 132 ++ .../lflang/analyses/cast/CBaseAstVisitor.java | 352 ++++ .../lflang/analyses/cast/CToUclidVisitor.java | 432 ++++ .../analyses/cast/IfNormalFormAstVisitor.java | 92 + .../cast/VariablePrecedenceVisitor.java | 43 + .../org/lflang/analyses/cast/Visitable.java | 12 + .../org/lflang/analyses/statespace/Event.java | 44 + .../analyses/statespace/EventQueue.java | 18 + .../lflang/analyses/statespace/StateInfo.java | 24 + .../statespace/StateSpaceDiagram.java | 217 ++ .../statespace/StateSpaceExplorer.java | 326 ++++ .../analyses/statespace/StateSpaceNode.java | 99 + .../org/lflang/analyses/statespace/Tag.java | 63 + .../org/lflang/analyses/uclid/MTLVisitor.java | 659 +++++++ .../lflang/analyses/uclid/UclidGenerator.java | 1737 +++++++++++++++++ .../lflang/analyses/uclid/UclidRunner.java | 236 +++ core/src/main/java/org/lflang/dsl/antlr4/C.g4 | 908 +++++++++ .../java/org/lflang/dsl/antlr4/MTLLexer.g4 | 117 ++ .../java/org/lflang/dsl/antlr4/MTLParser.g4 | 82 + 26 files changed, 6886 insertions(+), 15 deletions(-) create mode 100644 core/src/main/java/org/lflang/analyses/cast/AbstractAstVisitor.java create mode 100644 core/src/main/java/org/lflang/analyses/cast/AstUtils.java create mode 100644 core/src/main/java/org/lflang/analyses/cast/AstVisitor.java create mode 100644 core/src/main/java/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java create mode 100644 core/src/main/java/org/lflang/analyses/cast/CAst.java create mode 100644 core/src/main/java/org/lflang/analyses/cast/CAstVisitor.java create mode 100644 core/src/main/java/org/lflang/analyses/cast/CBaseAstVisitor.java create mode 100644 core/src/main/java/org/lflang/analyses/cast/CToUclidVisitor.java create mode 100644 core/src/main/java/org/lflang/analyses/cast/IfNormalFormAstVisitor.java create mode 100644 core/src/main/java/org/lflang/analyses/cast/VariablePrecedenceVisitor.java create mode 100644 core/src/main/java/org/lflang/analyses/cast/Visitable.java create mode 100644 core/src/main/java/org/lflang/analyses/statespace/Event.java create mode 100644 core/src/main/java/org/lflang/analyses/statespace/EventQueue.java create mode 100644 core/src/main/java/org/lflang/analyses/statespace/StateInfo.java create mode 100644 core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java create mode 100644 core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java create mode 100644 core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java create mode 100644 core/src/main/java/org/lflang/analyses/statespace/Tag.java create mode 100644 core/src/main/java/org/lflang/analyses/uclid/MTLVisitor.java create mode 100644 core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java create mode 100644 core/src/main/java/org/lflang/analyses/uclid/UclidRunner.java create mode 100644 core/src/main/java/org/lflang/dsl/antlr4/C.g4 create mode 100644 core/src/main/java/org/lflang/dsl/antlr4/MTLLexer.g4 create mode 100644 core/src/main/java/org/lflang/dsl/antlr4/MTLParser.g4 diff --git a/build.gradle b/build.gradle index 33eb1ec553..a6aa3d6eef 100644 --- a/build.gradle +++ b/build.gradle @@ -6,17 +6,6 @@ plugins { id 'idea' } -// Antlr4 -configurations { - antlr4 -} - -dependencies { - antlr4 'org.antlr:antlr4:4.7.2' - implementation 'org.antlr:antlr4-runtime:4.7.2' - implementation 'org.json:json:20200518' // JSON -} - spotless { format 'misc', { target rootProject.fileTree(rootProject.rootDir) { diff --git a/core/build.gradle b/core/build.gradle index 59b91d3674..10dae91f90 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -24,6 +24,11 @@ sourceSets { } } +// Antlr4 +configurations { + antlr4 +} + dependencies { api "org.eclipse.xtext:org.eclipse.xtext:$xtextVersion" api "org.eclipse.xtext:org.eclipse.xtext.xbase.lib:$xtextVersion" @@ -44,6 +49,10 @@ dependencies { exclude group: 'org.eclipse.platform', module: 'org.eclipse.swt' } + antlr4 'org.antlr:antlr4:4.7.2' + implementation 'org.antlr:antlr4-runtime:4.7.2' + implementation 'org.json:json:20200518' // JSON + testImplementation "org.junit.jupiter:junit-jupiter-api:$jupiterVersion" testImplementation "org.junit.jupiter:junit-jupiter-engine:$jupiterVersion" testImplementation "org.junit.platform:junit-platform-commons:$jUnitPlatformVersion" @@ -79,12 +88,31 @@ tasks.register('generateXtextLanguage', JavaExec) { args += "rootPath=/${projectDir}/.." } -compileJava.dependsOn(generateXtextLanguage) -compileKotlin.dependsOn(generateXtextLanguage) -processResources.dependsOn(generateXtextLanguage) +// Add Antlr4 for various DSLs, including MTL for verification. +tasks.register('runAntlr4', JavaExec) { + //see incremental task api, prevents rerun if nothing has changed. + inputs.dir "$projectDir/src/main/java/org/lflang/dsl/antlr4/" + outputs.dir "$projectDir/src/main/java/org/lflang/dsl/generated/antlr4/main/" + + classpath = configurations.antlr4 + + main = "org.antlr.v4.Tool" + + args = [ "-visitor", + "-o", "$projectDir/src/main/java/org/lflang/dsl/generated/antlr4/main/", + "-package", "org.lflang.dsl", + "$projectDir/src/main/java/org/lflang/dsl/antlr4/MTLLexer.g4", + "$projectDir/src/main/java/org/lflang/dsl/antlr4/MTLParser.g4", + "$projectDir/src/main/java/org/lflang/dsl/antlr4/C.g4"] +} + +compileJava.dependsOn(generateXtextLanguage, runAntlr4) +compileKotlin.dependsOn(generateXtextLanguage, runAntlr4) +processResources.dependsOn(generateXtextLanguage, runAntlr4) clean.dependsOn(cleanGenerateXtextLanguage) spotlessJava.mustRunAfter(generateXtextLanguage) -rootProject.spotlessMisc.mustRunAfter(generateXtextLanguage) +runAntlr4.mustRunAfter(spotlessJava) +rootProject.spotlessMisc.mustRunAfter(generateXtextLanguage, runAntlr4) tasks.register('getSubmoduleVersions', Exec) { description('Run a Git command to get the current status of submodules') diff --git a/core/src/main/java/org/lflang/analyses/cast/AbstractAstVisitor.java b/core/src/main/java/org/lflang/analyses/cast/AbstractAstVisitor.java new file mode 100644 index 0000000000..c780a9c682 --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/cast/AbstractAstVisitor.java @@ -0,0 +1,17 @@ +package org.lflang.analyses.cast; + +import java.util.List; + +/** Modeled after AbstractParseTreeVisitor.class */ +public abstract class AbstractAstVisitor implements AstVisitor { + + @Override + public T visit(CAst.AstNode tree) { + return tree.accept(this); + } + + @Override + public T visit(CAst.AstNode tree, List nodeList) { + return tree.accept(this, nodeList); + } +} diff --git a/core/src/main/java/org/lflang/analyses/cast/AstUtils.java b/core/src/main/java/org/lflang/analyses/cast/AstUtils.java new file mode 100644 index 0000000000..1b87562cb4 --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/cast/AstUtils.java @@ -0,0 +1,77 @@ +package org.lflang.analyses.cast; + +import java.util.List; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.misc.Interval; + +public class AstUtils { + + public static CAst.AstNode takeConjunction(List conditions) { + if (conditions.size() == 0) { + return new CAst.LiteralNode("true"); + } else if (conditions.size() == 1) { + return conditions.get(0); + } else { + // Take the conjunction of all the conditions. + CAst.LogicalAndNode top = new CAst.LogicalAndNode(); + CAst.LogicalAndNode cur = top; + for (int i = 0; i < conditions.size() - 1; i++) { + cur.left = conditions.get(i); + if (i == conditions.size() - 2) { + cur.right = conditions.get(i + 1); + } else { + cur.right = new CAst.LogicalAndNode(); + cur = (CAst.LogicalAndNode) cur.right; + } + } + return top; + } + } + + public static CAst.AstNode takeDisjunction(List conditions) { + if (conditions.size() == 0) { + return new CAst.LiteralNode("true"); + } else if (conditions.size() == 1) { + return conditions.get(0); + } else { + // Take the conjunction of all the conditions. + CAst.LogicalOrNode top = new CAst.LogicalOrNode(); + CAst.LogicalOrNode cur = top; + for (int i = 0; i < conditions.size() - 1; i++) { + cur.left = conditions.get(i); + if (i == conditions.size() - 2) { + cur.right = conditions.get(i + 1); + } else { + cur.right = new CAst.LogicalOrNode(); + cur = (CAst.LogicalOrNode) cur.right; + } + } + return top; + } + } + + // A handy function for debugging ASTs. + // It prints the stack trace of the visitor functions + // and shows the text matched by the ANTLR rules. + public static void printStackTraceAndMatchedText(ParserRuleContext ctx) { + System.out.println("========== AST DEBUG =========="); + + // Print matched text + int a = ctx.start.getStartIndex(); + int b = ctx.stop.getStopIndex(); + Interval interval = new Interval(a, b); + String matchedText = ctx.start.getInputStream().getText(interval); + System.out.println("Matched text: " + matchedText); + + // Print stack trace + StackTraceElement[] cause = Thread.currentThread().getStackTrace(); + System.out.print("Stack trace: "); + for (int i = 0; i < cause.length; i++) { + System.out.print(cause[i].getMethodName()); + if (i != cause.length - 1) System.out.print(", "); + } + System.out.println("."); + + System.out.println("==============================="); + } +} diff --git a/core/src/main/java/org/lflang/analyses/cast/AstVisitor.java b/core/src/main/java/org/lflang/analyses/cast/AstVisitor.java new file mode 100644 index 0000000000..b19e5cbfc7 --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/cast/AstVisitor.java @@ -0,0 +1,25 @@ +package org.lflang.analyses.cast; + +import java.util.List; + +/** Modeled after ParseTreeVisitor.class */ +public interface AstVisitor { + + /** + * Visit an AST, and return a user-defined result of the operation. + * + * @param tree The {@link CAst.AstNode} to visit. + * @return The result of visiting the parse tree. + */ + T visit(CAst.AstNode tree); + + /** + * Visit an AST with a list of other AST nodes holding some information, and return a user-defined + * result of the operation. + * + * @param tree The {@link CAst.AstNode} to visit. + * @param nodeList A list of {@link CAst.AstNode} passed down the recursive call. + * @return The result of visiting the parse tree. + */ + T visit(CAst.AstNode tree, List nodeList); +} diff --git a/core/src/main/java/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java b/core/src/main/java/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java new file mode 100644 index 0000000000..97e1bd40be --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java @@ -0,0 +1,691 @@ +package org.lflang.analyses.cast; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.lflang.dsl.CBaseVisitor; +import org.lflang.dsl.CParser.*; + +public class BuildAstParseTreeVisitor extends CBaseVisitor { + + @Override + public CAst.AstNode visitBlockItemList(BlockItemListContext ctx) { + CAst.StatementSequenceNode stmtSeq = new CAst.StatementSequenceNode(); + // Populate the children. + for (BlockItemContext blockItem : ctx.blockItem()) { + stmtSeq.children.add(visit(blockItem)); + } + return stmtSeq; + } + + @Override + public CAst.AstNode visitBlockItem(BlockItemContext ctx) { + if (ctx.statement() != null) return visit(ctx.statement()); + else return visit(ctx.declaration()); + } + + @Override + public CAst.AstNode visitDeclaration(DeclarationContext ctx) { + if (ctx.declarationSpecifiers() != null && ctx.initDeclaratorList() != null) { + //// Extract type from declarationSpecifiers. + List declSpecList = + ctx.declarationSpecifiers().declarationSpecifier(); + + // Cannot handle more than 1 specifiers, e.g. static const int. + // We can augment the analytical capability later. + if (declSpecList.size() > 1) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "the analyzer cannot handle more than 1 specifiers,", + "e.g. static const int.", + "Marking the declaration as opaque.")); + return new CAst.OpaqueNode(); + } + + // Check if the declaration specifier is a type specifier: e.g. int or long. + DeclarationSpecifierContext declSpec = declSpecList.get(0); + if (declSpec.typeSpecifier() == null) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "only type specifiers are supported.", + "e.g. \"static const int\" is not analyzable.", + "Marking the declaration as opaque.")); + return new CAst.OpaqueNode(); + } + + // Check if the type specifier is what we currently support. + // Right now we only support int, long, & double. + CAst.VariableNode.Type type; + ArrayList supportedTypes = + new ArrayList(Arrays.asList("int", "long", "double", "_Bool")); + if (declSpec.typeSpecifier().Int() != null + || declSpec.typeSpecifier().Long() != null + || declSpec.typeSpecifier().Double() != null) type = CAst.VariableNode.Type.INT; + else if (declSpec.typeSpecifier().Bool() != null) type = CAst.VariableNode.Type.BOOLEAN; + // Mark the declaration unanalyzable if the type is unsupported. + else { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "unsupported type detected at " + declSpec.typeSpecifier(), + "Only " + supportedTypes + " are supported.", + "Marking the declaration as opaque.")); + return new CAst.OpaqueNode(); + } + + //// Extract variable name and value from initDeclaratorList. + List initDeclList = ctx.initDeclaratorList().initDeclarator(); + + // Cannot handle more than 1 initDeclarator: e.g. x = 1, y = 2; + // We can augment the analytical capability later. + if (initDeclList.size() > 1) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "more than 1 declarators are detected on a single line,", + "e.g. \"int x = 1, y = 2;\" is not yet analyzable.", + "Marking the declaration as opaque.")); + return new CAst.OpaqueNode(); + } + + // Get the variable name from the declarator. + DeclaratorContext decl = initDeclList.get(0).declarator(); + if (decl.pointer() != null) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "pointers are currently not supported,", + "e.g. \"int *x;\" is not yet analyzable.", + "Marking the declaration as opaque.")); + return new CAst.OpaqueNode(); + } + if (decl.gccDeclaratorExtension().size() > 0) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "GCC declarator extensions are currently not supported,", + "e.g. \"__asm\" and \"__attribute__\" are not yet analyzable.", + "Marking the declaration as opaque.")); + return new CAst.OpaqueNode(); + } + DirectDeclaratorContext directDecl = decl.directDeclarator(); + if (directDecl.Identifier() == null) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "the variable identifier is missing.", + "Marking the declaration as opaque.")); + return new CAst.OpaqueNode(); + } + + // Extract the name of the variable. + String name = directDecl.Identifier().getText(); + // Create a variable Ast node. + CAst.VariableNode variable = new CAst.VariableNode(type, name); + + //// Convert the initializer to a value. + + // Make sure that there is an initializer. + InitDeclaratorContext initDecl = initDeclList.get(0); + if (initDecl.initializer() == null) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "the initializer is missing,", + "e.g. \"int x;\" is not yet analyzable.", + "Marking the declaration as opaque.")); + return new CAst.OpaqueNode(); + + // FIXME: Use UninitCAst.VariableNode to perform special encoding. + // return new UninitCAst.VariableNode(type, name); + } + + // Extract the primaryExpression from the initializer. + if (initDecl.initializer().assignmentExpression() == null + || initDecl.initializer().assignmentExpression().conditionalExpression() == null) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "assignmentExpression or conditionalExpression is missing.", + "Marking the declaration as opaque.")); + return new CAst.OpaqueNode(); + } + + // Finally return the assignment node. + CAst.AssignmentNode assignmentNode = new CAst.AssignmentNode(); + CAst.AstNode initNode = + visitAssignmentExpression(initDecl.initializer().assignmentExpression()); + assignmentNode.left = variable; + assignmentNode.right = initNode; + return assignmentNode; + } + // Return OpaqueNode as default. + return new CAst.OpaqueNode(); + } + + /** + * This visit function builds StatementSequenceNode, AssignmentNode, OpaqueNode, IfBlockNode, + * AdditionNode, SubtractionNode, MultiplicationNode, DivisionNode, EqualNode, NotEqualNode, + * LessThanNode, GreaterThanNode, LessEqualNode, GreaterEqualNode, SetPortNode, + * ScheduleActionNode. + * + * @param ctx + * @return + */ + @Override + public CAst.AstNode visitStatement(StatementContext ctx) { + if (ctx.compoundStatement() != null) { + BlockItemListContext bilCtx = ctx.compoundStatement().blockItemList(); + if (bilCtx != null) { + return visitBlockItemList(bilCtx); + } + } else if (ctx.expressionStatement() != null) { + ExpressionContext exprCtx = ctx.expressionStatement().expression(); + if (exprCtx != null) { + return visitExpression(exprCtx); + } + } else if (ctx.selectionStatement() != null) { + return visitSelectionStatement(ctx.selectionStatement()); + } + return new CAst.OpaqueNode(); + } + + @Override + public CAst.AstNode visitExpression(ExpressionContext ctx) { + if (ctx.assignmentExpression().size() == 1) { + return visitAssignmentExpression(ctx.assignmentExpression().get(0)); + } + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "only one assignmentExpression in an expression is currently supported.", + "Marking the statement as opaque.")); + return new CAst.OpaqueNode(); + } + + @Override + public CAst.AstNode visitPrimaryExpression(PrimaryExpressionContext ctx) { + if (ctx.Identifier() != null) { + return new CAst.VariableNode(ctx.Identifier().getText()); + } else if (ctx.Constant() != null) { + return new CAst.LiteralNode(ctx.Constant().getText()); + } else if (ctx.expression() != null) { + return visitExpression(ctx.expression()); + } + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "only identifier, constant, and expressions are supported in a primary expression.", + "Marking the declaration as opaque.")); + return new CAst.OpaqueNode(); + } + + // FIXME: More checks needed. This implementation currently silently omit + // certain cases, such as arr[1]. + @Override + public CAst.AstNode visitPostfixExpression(PostfixExpressionContext ctx) { + if (ctx.PlusPlus().size() > 0 + || ctx.MinusMinus().size() > 0 + || ctx.Dot().size() > 0 + || (ctx.LeftBracket().size() > 0 && ctx.RightBracket().size() > 0)) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Postfix '++', '--', '.', '[]' are currently not supported.", + "Marking the statement as opaque.")); + return new CAst.OpaqueNode(); + } + // State variables on the self struct, ports and actions. + if (ctx.primaryExpression() != null + && ctx.Identifier().size() == 1 + && ctx.Arrow().size() == 1) { + CAst.AstNode primaryExprNode = visitPrimaryExpression(ctx.primaryExpression()); + if (primaryExprNode instanceof CAst.LiteralNode) { + // Unreachable. + System.out.println("Unreachable!"); + return new CAst.OpaqueNode(); // FIXME: Throw an exception instead. + } + CAst.VariableNode varNode = (CAst.VariableNode) primaryExprNode; + if (varNode.name.equals("self")) { + // return a state variable node. + return new CAst.StateVarNode(ctx.Identifier().get(0).getText()); + } else if (ctx.Identifier().get(0).getText().equals("value")) { + // return a trigger present node. + return new CAst.TriggerValueNode(varNode.name); + } else if (ctx.Identifier().get(0).getText().equals("is_present")) { + // return a trigger value node. + return new CAst.TriggerIsPresentNode(varNode.name); + } else { + // Generic pointer dereference, unanalyzable. + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Generic pointer dereference is not supported in a postfix expression.", + "Marking the declaration as opaque.")); + return new CAst.OpaqueNode(); + } + } + // LF built-in function calls (set or schedule) + if (ctx.primaryExpression() != null + && ctx.argumentExpressionList().size() == 1 + && ctx.LeftParen() != null + && ctx.RightParen() != null) { + CAst.AstNode primaryExprNode = visitPrimaryExpression(ctx.primaryExpression()); + List params = + ctx.argumentExpressionList().get(0).assignmentExpression(); + if (primaryExprNode instanceof CAst.LiteralNode) { + // Unreachable. + System.out.println("Unreachable!"); + return new CAst.OpaqueNode(); // FIXME: Throw an exception instead. + } + CAst.VariableNode varNode = (CAst.VariableNode) primaryExprNode; + if (varNode.name.equals("lf_set")) { + // return a set port node. + if (params.size() != 2) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "lf_set must have 2 arguments. Detected " + ctx.argumentExpressionList().size(), + "Marking the function call as opaque.")); + return new CAst.OpaqueNode(); + } + CAst.SetPortNode node = new CAst.SetPortNode(); + node.left = visitAssignmentExpression(params.get(0)); + node.right = visitAssignmentExpression(params.get(1)); + return node; + } else if (varNode.name.equals("lf_schedule")) { + // return a set port node. + if (params.size() != 2) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "lf_schedule must have two arguments. Detected " + + ctx.argumentExpressionList().size(), + "Marking the function call as opaque.")); + return new CAst.OpaqueNode(); + } + CAst.ScheduleActionNode node = new CAst.ScheduleActionNode(); + for (AssignmentExpressionContext param : params) { + node.children.add(visitAssignmentExpression(param)); + } + return node; + } else if (varNode.name.equals("lf_schedule_int")) { + // return a set port node. + if (params.size() != 3) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "lf_schedule_int must have three arguments. Detected " + + ctx.argumentExpressionList().size(), + "Marking the function call as opaque.")); + return new CAst.OpaqueNode(); + } + CAst.ScheduleActionIntNode node = new CAst.ScheduleActionIntNode(); + for (AssignmentExpressionContext param : params) { + node.children.add(visitAssignmentExpression(param)); + } + return node; + } else { + // Generic pointer dereference, unanalyzable. + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Generic pointer dereference and function calls are not supported in a postfix" + + " expression.", + "Marking the declaration as opaque.")); + return new CAst.OpaqueNode(); + } + } + // Variable or literal + if (ctx.primaryExpression() != null) { + return visitPrimaryExpression(ctx.primaryExpression()); + } + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "only an identifier, constant, state variable, port, and action are supported in a" + + " primary expression.", + "Marking the declaration as opaque.")); + return new CAst.OpaqueNode(); + } + + @Override + public CAst.AstNode visitUnaryExpression(UnaryExpressionContext ctx) { + // Check for prefixes and mark them as opaque (unsupported for now). + if (ctx.PlusPlus().size() > 0 || ctx.MinusMinus().size() > 0 || ctx.Sizeof().size() > 0) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Prefix '++', '--', and 'sizeof' are currently not supported.", + "Marking the statement as opaque.")); + AstUtils.printStackTraceAndMatchedText(ctx); + return new CAst.OpaqueNode(); + } + // Handle the postfixExpression rule + // (look up the grammar in C.g4 to get more details). + if (ctx.postfixExpression() != null) { + return visitPostfixExpression(ctx.postfixExpression()); + } + // Handle the unary operators '!' (logical not) + // and '-' (negative). + if (ctx.unaryOperator() != null && ctx.castExpression() != null) { + CAst.AstNodeUnary node; + if (ctx.unaryOperator().Not() != null) { + // Handle the logical not expression. + node = new CAst.LogicalNotNode(); + node.child = visitCastExpression(ctx.castExpression()); + return node; + } else if (ctx.unaryOperator().Minus() != null) { + // Handle negative numbers. + // -5 will be translated as -1 * (5) + // which is a NegativeNode with a + // LiteralNode inside. + // + // FIXME: Need to perform precise error handling + // because we will go from a castExpression to + // a Constant under primaryExpression and anything + // else matching besides Constant could be problematic. + // For example, we cannot have a NegativeNode with + // a StringLiteralNode inside. This can be caught by + // the GCC, but if compilation is not involved, it + // would be useful to catch it here ourselves. + node = new CAst.NegativeNode(); + node.child = visitCastExpression(ctx.castExpression()); + return node; + } + } + + // Mark all the remaining cases as opaque. + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "only postfixExpression and '!' in a unaryExpression is currently supported.", + "Marking the statement as opaque.")); + AstUtils.printStackTraceAndMatchedText(ctx); + return new CAst.OpaqueNode(); + } + + @Override + public CAst.AstNode visitCastExpression(CastExpressionContext ctx) { + if (ctx.unaryExpression() != null) { + return visitUnaryExpression(ctx.unaryExpression()); + } + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "only unaryExpression in a castExpression is currently supported.", + "Marking the statement as opaque.")); + return new CAst.OpaqueNode(); + } + + @Override + public CAst.AstNode visitMultiplicativeExpression(MultiplicativeExpressionContext ctx) { + if (ctx.castExpression().size() > 1) { + CAst.AstNodeBinary node; + if (ctx.Star().size() > 0) { + node = new CAst.MultiplicationNode(); + } else if (ctx.Div().size() > 0) { + node = new CAst.DivisionNode(); + } else if (ctx.Mod().size() > 0) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Mod expression '%' is currently unsupported.", + "Marking the expression as opaque.")); + return new CAst.OpaqueNode(); + } else { + node = new CAst.AstNodeBinary(); + } + node.left = visitCastExpression(ctx.castExpression().get(0)); + node.right = visitCastExpression(ctx.castExpression().get(1)); + return node; + } + return visitCastExpression(ctx.castExpression().get(0)); + } + + @Override + public CAst.AstNode visitAdditiveExpression(AdditiveExpressionContext ctx) { + if (ctx.multiplicativeExpression().size() > 1) { + CAst.AstNodeBinary node; + if (ctx.Plus().size() > 0) { + node = new CAst.AdditionNode(); + } else if (ctx.Minus().size() > 0) { + node = new CAst.SubtractionNode(); + } else { + node = new CAst.AstNodeBinary(); + } + node.left = visitMultiplicativeExpression(ctx.multiplicativeExpression().get(0)); + node.right = visitMultiplicativeExpression(ctx.multiplicativeExpression().get(1)); + return node; + } + return visitMultiplicativeExpression(ctx.multiplicativeExpression().get(0)); + } + + @Override + public CAst.AstNode visitShiftExpression(ShiftExpressionContext ctx) { + if (ctx.additiveExpression().size() > 1) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Shift expression '<<' or '>>' is currently unsupported.", + "Marking the expression as opaque.")); + return new CAst.OpaqueNode(); + } + return visitAdditiveExpression(ctx.additiveExpression().get(0)); + } + + @Override + public CAst.AstNode visitRelationalExpression(RelationalExpressionContext ctx) { + if (ctx.shiftExpression().size() > 1) { + CAst.AstNodeBinary node; + if (ctx.Less().size() > 0) { + node = new CAst.LessThanNode(); + } else if (ctx.LessEqual().size() > 0) { + node = new CAst.LessEqualNode(); + } else if (ctx.Greater().size() > 0) { + node = new CAst.GreaterThanNode(); + } else if (ctx.GreaterEqual().size() > 0) { + node = new CAst.GreaterEqualNode(); + } else { + node = new CAst.AstNodeBinary(); + } + node.left = visitShiftExpression(ctx.shiftExpression().get(0)); + node.right = visitShiftExpression(ctx.shiftExpression().get(1)); + return node; + } + return visitShiftExpression(ctx.shiftExpression().get(0)); + } + + @Override + public CAst.AstNode visitEqualityExpression(EqualityExpressionContext ctx) { + if (ctx.relationalExpression().size() > 1) { + CAst.AstNodeBinary node; + if (ctx.Equal().size() > 0) { + node = new CAst.EqualNode(); + } else if (ctx.NotEqual().size() > 0) { + node = new CAst.NotEqualNode(); + } else { + node = new CAst.AstNodeBinary(); + } + node.left = visitRelationalExpression(ctx.relationalExpression().get(0)); + node.right = visitRelationalExpression(ctx.relationalExpression().get(1)); + return node; + } + return visitRelationalExpression(ctx.relationalExpression().get(0)); + } + + @Override + public CAst.AstNode visitAndExpression(AndExpressionContext ctx) { + if (ctx.equalityExpression().size() > 1) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "And expression '&' is currently unsupported.", + "Marking the expression as opaque.")); + return new CAst.OpaqueNode(); + } + return visitEqualityExpression(ctx.equalityExpression().get(0)); + } + + @Override + public CAst.AstNode visitExclusiveOrExpression(ExclusiveOrExpressionContext ctx) { + if (ctx.andExpression().size() > 1) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Exclusive Or '^' is currently unsupported.", + "Marking the expression as opaque.")); + return new CAst.OpaqueNode(); + } + return visitAndExpression(ctx.andExpression().get(0)); + } + + @Override + public CAst.AstNode visitInclusiveOrExpression(InclusiveOrExpressionContext ctx) { + if (ctx.exclusiveOrExpression().size() > 1) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Inclusive Or '|' is currently unsupported.", + "Marking the expression as opaque.")); + return new CAst.OpaqueNode(); + } + return visitExclusiveOrExpression(ctx.exclusiveOrExpression().get(0)); + } + + @Override + public CAst.AstNode visitLogicalAndExpression(LogicalAndExpressionContext ctx) { + if (ctx.inclusiveOrExpression().size() > 1) { + CAst.LogicalAndNode node = new CAst.LogicalAndNode(); + node.left = visitInclusiveOrExpression(ctx.inclusiveOrExpression().get(0)); + node.right = visitInclusiveOrExpression(ctx.inclusiveOrExpression().get(1)); + return node; + } + return visitInclusiveOrExpression(ctx.inclusiveOrExpression().get(0)); + } + + @Override + public CAst.AstNode visitLogicalOrExpression(LogicalOrExpressionContext ctx) { + if (ctx.logicalAndExpression().size() > 1) { + CAst.LogicalOrNode node = new CAst.LogicalOrNode(); + node.left = visitLogicalAndExpression(ctx.logicalAndExpression().get(0)); + node.right = visitLogicalAndExpression(ctx.logicalAndExpression().get(1)); + return node; + } + return visitLogicalAndExpression(ctx.logicalAndExpression().get(0)); + } + + @Override + public CAst.AstNode visitConditionalExpression(ConditionalExpressionContext ctx) { + if (ctx.expression() != null) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Currently do not support inline conditional expression.", + "Marking the expression as opaque.")); + return new CAst.OpaqueNode(); + } + return visitLogicalOrExpression(ctx.logicalOrExpression()); + } + + @Override + public CAst.AstNode visitAssignmentExpression(AssignmentExpressionContext ctx) { + if (ctx.conditionalExpression() != null) { + return visitConditionalExpression(ctx.conditionalExpression()); + } + if (ctx.unaryExpression() != null && ctx.assignmentExpression() != null) { + CAst.AstNodeBinary assignmentNode = new CAst.AssignmentNode(); + assignmentNode.left = visitUnaryExpression(ctx.unaryExpression()); + if (ctx.assignmentOperator().getText().equals("=")) { + assignmentNode.right = visitAssignmentExpression(ctx.assignmentExpression()); + } else if (ctx.assignmentOperator().getText().equals("+=")) { + CAst.AdditionNode subnode = new CAst.AdditionNode(); + subnode.left = visitUnaryExpression(ctx.unaryExpression()); + subnode.right = visitAssignmentExpression(ctx.assignmentExpression()); + assignmentNode.right = subnode; + } else if (ctx.assignmentOperator().getText().equals("-=")) { + CAst.SubtractionNode subnode = new CAst.SubtractionNode(); + subnode.left = visitUnaryExpression(ctx.unaryExpression()); + subnode.right = visitAssignmentExpression(ctx.assignmentExpression()); + assignmentNode.right = subnode; + } else if (ctx.assignmentOperator().getText().equals("*=")) { + CAst.MultiplicationNode subnode = new CAst.MultiplicationNode(); + subnode.left = visitUnaryExpression(ctx.unaryExpression()); + subnode.right = visitAssignmentExpression(ctx.assignmentExpression()); + assignmentNode.right = subnode; + } else if (ctx.assignmentOperator().getText().equals("/=")) { + CAst.DivisionNode subnode = new CAst.DivisionNode(); + subnode.left = visitUnaryExpression(ctx.unaryExpression()); + subnode.right = visitAssignmentExpression(ctx.assignmentExpression()); + assignmentNode.right = subnode; + } else { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Only '=', '+=', '-=', '*=', '/=' assignment operators are supported.", + "Marking the expression as opaque.")); + return new CAst.OpaqueNode(); + } + return assignmentNode; + } + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "DigitSequence in an assignmentExpression is currently not supported.", + "Marking the expression as opaque.")); + return new CAst.OpaqueNode(); + } + + @Override + public CAst.AstNode visitSelectionStatement(SelectionStatementContext ctx) { + if (ctx.Switch() != null) { + System.out.println( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Switch case statement is currently not supported.", + "Marking the expression as opaque.")); + return new CAst.OpaqueNode(); + } + CAst.IfBlockNode ifBlockNode = new CAst.IfBlockNode(); + CAst.IfBodyNode ifBodyNode = new CAst.IfBodyNode(); + ifBlockNode.left = visitExpression(ctx.expression()); + ifBlockNode.right = ifBodyNode; + ifBodyNode.left = visitStatement(ctx.statement().get(0)); + if (ctx.statement().size() > 1) { + ifBodyNode.right = visitStatement(ctx.statement().get(1)); + } + return ifBlockNode; + } +} diff --git a/core/src/main/java/org/lflang/analyses/cast/CAst.java b/core/src/main/java/org/lflang/analyses/cast/CAst.java new file mode 100644 index 0000000000..ea04d45426 --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/cast/CAst.java @@ -0,0 +1,451 @@ +package org.lflang.analyses.cast; + +import java.util.ArrayList; +import java.util.List; + +public class CAst { + + public static class AstNode implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitAstNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitAstNode(this, nodeList); + } + } + + public static class AstNodeUnary extends AstNode implements Visitable { + public AstNode child; + + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitAstNodeUnary(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitAstNodeUnary(this, nodeList); + } + } + + public static class AstNodeBinary extends AstNode implements Visitable { + public AstNode left; + public AstNode right; + + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitAstNodeBinary(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitAstNodeBinary(this, nodeList); + } + } + + public static class AstNodeDynamic extends AstNode implements Visitable { + public ArrayList children = new ArrayList<>(); + + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitAstNodeDynamic(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitAstNodeDynamic(this, nodeList); + } + } + + public static class AssignmentNode extends AstNodeBinary implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitAssignmentNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitAssignmentNode(this, nodeList); + } + } + + /** AST node for an IF block. The left node is the condition. The right node is the IF body. */ + public static class IfBlockNode extends AstNodeBinary implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitIfBlockNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitIfBlockNode(this, nodeList); + } + } + + /** + * AST node for the body of an IF block. The left node is the THEN branch. The right node is the + * ELSE branch. + */ + public static class IfBodyNode extends AstNodeBinary implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitIfBodyNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitIfBodyNode(this, nodeList); + } + } + + public static class LiteralNode extends AstNode implements Visitable { + public String literal; + + public LiteralNode(String literal) { + super(); + this.literal = literal; + } + + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitLiteralNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitLiteralNode(this, nodeList); + } + } + + public static class LogicalNotNode extends AstNodeUnary implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitLogicalNotNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitLogicalNotNode(this, nodeList); + } + } + + public static class LogicalAndNode extends AstNodeBinary implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitLogicalAndNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitLogicalAndNode(this, nodeList); + } + } + + public static class LogicalOrNode extends AstNodeBinary implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitLogicalOrNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitLogicalOrNode(this, nodeList); + } + } + + /** An Ast node that indicates the code represented by this node is unanalyzable. */ + public static class OpaqueNode extends AstNode implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitOpaqueNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitOpaqueNode(this, nodeList); + } + } + + public static class StatementSequenceNode extends AstNodeDynamic implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitStatementSequenceNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitStatementSequenceNode(this, nodeList); + } + } + + public static class VariableNode extends AstNode implements Visitable { + public enum Type { + UNKNOWN, + INT, + BOOLEAN + } + + public Type type; + public String name; + + public VariableNode(String name) { + super(); + this.type = Type.UNKNOWN; + this.name = name; + } + + public VariableNode(Type type, String name) { + super(); + this.type = type; + this.name = name; + } + + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitVariableNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitVariableNode(this, nodeList); + } + } + + /** Arithmetic operations */ + public static class AdditionNode extends AstNodeBinary implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitAdditionNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitAdditionNode(this, nodeList); + } + } + + public static class SubtractionNode extends AstNodeBinary implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitSubtractionNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitSubtractionNode(this, nodeList); + } + } + + public static class MultiplicationNode extends AstNodeBinary implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitMultiplicationNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitMultiplicationNode(this, nodeList); + } + } + + public static class DivisionNode extends AstNodeBinary implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitDivisionNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitDivisionNode(this, nodeList); + } + } + + /** Comparison operators */ + public static class EqualNode extends AstNodeBinary implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitEqualNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitEqualNode(this, nodeList); + } + } + + public static class NotEqualNode extends AstNodeBinary implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitNotEqualNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitNotEqualNode(this, nodeList); + } + } + + public static class NegativeNode extends AstNodeUnary implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitNegativeNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitNegativeNode(this, nodeList); + } + } + + public static class LessThanNode extends AstNodeBinary implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitLessThanNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitLessThanNode(this, nodeList); + } + } + + public static class LessEqualNode extends AstNodeBinary implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitLessEqualNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitLessEqualNode(this, nodeList); + } + } + + public static class GreaterThanNode extends AstNodeBinary implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitGreaterThanNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitGreaterThanNode(this, nodeList); + } + } + + public static class GreaterEqualNode extends AstNodeBinary implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitGreaterEqualNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitGreaterEqualNode(this, nodeList); + } + } + + /** LF built-in operations */ + /** + * AST node for an lf_set call. The left child is the port being set. The right node is the value + * of the port. + */ + public static class SetPortNode extends AstNodeBinary implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitSetPortNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitSetPortNode(this, nodeList); + } + } + + /** AST node for a `lf_schedule(action, additional_delay)` call. */ + public static class ScheduleActionNode extends AstNodeDynamic implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitScheduleActionNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitScheduleActionNode(this, nodeList); + } + } + + /** AST node for a `lf_schedule_int(action, additional_delay, integer)` call. */ + public static class ScheduleActionIntNode extends AstNodeDynamic implements Visitable { + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitScheduleActionIntNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitScheduleActionIntNode(this, nodeList); + } + } + + /** Handle state variables appearing as self-> */ + public static class StateVarNode extends AstNode implements Visitable { + public String name; + public boolean prev = false; // By default, this is not a previous state. + + public StateVarNode(String name) { + this.name = name; + } + + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitStateVarNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitStateVarNode(this, nodeList); + } + } + + /** Handle trigger values appearing as ->value */ + public static class TriggerValueNode extends AstNode implements Visitable { + public String name; + + public TriggerValueNode(String name) { + this.name = name; + } + + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitTriggerValueNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitTriggerValueNode(this, nodeList); + } + } + + /** Handle trigger presence appearing as ->is_present */ + public static class TriggerIsPresentNode extends AstNode implements Visitable { + public String name; + + public TriggerIsPresentNode(String name) { + this.name = name; + } + + @Override + public T accept(AstVisitor visitor) { + return ((CAstVisitor) visitor).visitTriggerIsPresentNode(this); + } + + @Override + public T accept(AstVisitor visitor, List nodeList) { + return ((CAstVisitor) visitor).visitTriggerIsPresentNode(this, nodeList); + } + } +} diff --git a/core/src/main/java/org/lflang/analyses/cast/CAstVisitor.java b/core/src/main/java/org/lflang/analyses/cast/CAstVisitor.java new file mode 100644 index 0000000000..034a93449c --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/cast/CAstVisitor.java @@ -0,0 +1,132 @@ +package org.lflang.analyses.cast; + +import java.util.List; + +/** Modeled after CVisitor.java */ +public interface CAstVisitor extends AstVisitor { + + T visitAstNode(CAst.AstNode node); + + T visitAstNodeUnary(CAst.AstNodeUnary node); + + T visitAstNodeBinary(CAst.AstNodeBinary node); + + T visitAstNodeDynamic(CAst.AstNodeDynamic node); + + T visitAssignmentNode(CAst.AssignmentNode node); + + T visitIfBlockNode(CAst.IfBlockNode node); + + T visitIfBodyNode(CAst.IfBodyNode node); + + T visitLiteralNode(CAst.LiteralNode node); + + T visitLogicalNotNode(CAst.LogicalNotNode node); + + T visitLogicalAndNode(CAst.LogicalAndNode node); + + T visitLogicalOrNode(CAst.LogicalOrNode node); + + T visitOpaqueNode(CAst.OpaqueNode node); + + T visitStatementSequenceNode(CAst.StatementSequenceNode node); + + T visitVariableNode(CAst.VariableNode node); + + T visitAdditionNode(CAst.AdditionNode node); + + T visitSubtractionNode(CAst.SubtractionNode node); + + T visitMultiplicationNode(CAst.MultiplicationNode node); + + T visitDivisionNode(CAst.DivisionNode node); + + T visitEqualNode(CAst.EqualNode node); + + T visitNotEqualNode(CAst.NotEqualNode node); + + T visitNegativeNode(CAst.NegativeNode node); + + T visitLessThanNode(CAst.LessThanNode node); + + T visitLessEqualNode(CAst.LessEqualNode node); + + T visitGreaterThanNode(CAst.GreaterThanNode node); + + T visitGreaterEqualNode(CAst.GreaterEqualNode node); + + T visitSetPortNode(CAst.SetPortNode node); + + T visitScheduleActionNode(CAst.ScheduleActionNode node); + + T visitScheduleActionIntNode(CAst.ScheduleActionIntNode node); + + T visitStateVarNode(CAst.StateVarNode node); + + T visitTriggerValueNode(CAst.TriggerValueNode node); + + T visitTriggerIsPresentNode(CAst.TriggerIsPresentNode node); + + /** Used for converting an AST into If Normal Form. */ + T visitAstNode(CAst.AstNode node, List nodeList); + + T visitAstNodeUnary(CAst.AstNodeUnary node, List nodeList); + + T visitAstNodeBinary(CAst.AstNodeBinary node, List nodeList); + + T visitAstNodeDynamic(CAst.AstNodeDynamic node, List nodeList); + + T visitAssignmentNode(CAst.AssignmentNode node, List nodeList); + + T visitIfBlockNode(CAst.IfBlockNode node, List nodeList); + + T visitIfBodyNode(CAst.IfBodyNode node, List nodeList); + + T visitLiteralNode(CAst.LiteralNode node, List nodeList); + + T visitLogicalNotNode(CAst.LogicalNotNode node, List nodeList); + + T visitLogicalAndNode(CAst.LogicalAndNode node, List nodeList); + + T visitLogicalOrNode(CAst.LogicalOrNode node, List nodeList); + + T visitOpaqueNode(CAst.OpaqueNode node, List nodeList); + + T visitStatementSequenceNode(CAst.StatementSequenceNode node, List nodeList); + + T visitVariableNode(CAst.VariableNode node, List nodeList); + + T visitAdditionNode(CAst.AdditionNode node, List nodeList); + + T visitSubtractionNode(CAst.SubtractionNode node, List nodeList); + + T visitMultiplicationNode(CAst.MultiplicationNode node, List nodeList); + + T visitDivisionNode(CAst.DivisionNode node, List nodeList); + + T visitEqualNode(CAst.EqualNode node, List nodeList); + + T visitNotEqualNode(CAst.NotEqualNode node, List nodeList); + + T visitNegativeNode(CAst.NegativeNode node, List nodeList); + + T visitLessThanNode(CAst.LessThanNode node, List nodeList); + + T visitLessEqualNode(CAst.LessEqualNode node, List nodeList); + + T visitGreaterThanNode(CAst.GreaterThanNode node, List nodeList); + + T visitGreaterEqualNode(CAst.GreaterEqualNode node, List nodeList); + + T visitSetPortNode(CAst.SetPortNode node, List nodeList); + + T visitScheduleActionNode(CAst.ScheduleActionNode node, List nodeList); + + T visitScheduleActionIntNode(CAst.ScheduleActionIntNode node, List nodeList); + + T visitStateVarNode(CAst.StateVarNode node, List nodeList); + + T visitTriggerValueNode(CAst.TriggerValueNode node, List nodeList); + + T visitTriggerIsPresentNode(CAst.TriggerIsPresentNode node, List nodeList); +} diff --git a/core/src/main/java/org/lflang/analyses/cast/CBaseAstVisitor.java b/core/src/main/java/org/lflang/analyses/cast/CBaseAstVisitor.java new file mode 100644 index 0000000000..ce212c53a7 --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/cast/CBaseAstVisitor.java @@ -0,0 +1,352 @@ +package org.lflang.analyses.cast; + +import java.util.List; + +/** + * A base class that provides default implementations of the visit functions. Other C AST visitors + * extend this class. + */ +public class CBaseAstVisitor extends AbstractAstVisitor implements CAstVisitor { + + /** + * These default implementations are not meant to be used. They should be overriden by the child + * class. In theory, this base visitor can be deleted? Let's keep it here for now for consistency. + */ + @Override + public T visitAstNode(CAst.AstNode node) { + return null; + } + + @Override + public T visitAstNodeUnary(CAst.AstNodeUnary node) { + if (node.child != null) { + T result = visit(node.child); + } else { + System.out.println("*** Child is empty in " + node + "!"); + } + return null; + } + + @Override + public T visitAstNodeBinary(CAst.AstNodeBinary node) { + if (node.left != null) { + T leftResult = visit(node.left); + } else { + System.out.println("*** Left child is empty in " + node + "!"); + } + if (node.right != null) { + T rightResult = visit(node.right); + } else { + System.out.println("*** Right child is empty in " + node + "!"); + } + // Aggregate results... + return null; + } + + @Override + public T visitAstNodeDynamic(CAst.AstNodeDynamic node) { + for (CAst.AstNode n : node.children) { + T result = visit(n); + } + return null; + } + + @Override + public T visitAssignmentNode(CAst.AssignmentNode node) { + return visitAstNodeBinary(node); + } + + @Override + public T visitIfBlockNode(CAst.IfBlockNode node) { + return visitAstNodeBinary(node); + } + + @Override + public T visitIfBodyNode(CAst.IfBodyNode node) { + return visitAstNodeBinary(node); + } + + @Override + public T visitLiteralNode(CAst.LiteralNode node) { + return null; + } + + @Override + public T visitLogicalNotNode(CAst.LogicalNotNode node) { + return visitAstNodeUnary(node); + } + + @Override + public T visitLogicalAndNode(CAst.LogicalAndNode node) { + return visitAstNodeBinary(node); + } + + @Override + public T visitLogicalOrNode(CAst.LogicalOrNode node) { + return visitAstNodeBinary(node); + } + + @Override + public T visitOpaqueNode(CAst.OpaqueNode node) { + return visitAstNode(node); + } + + @Override + public T visitStatementSequenceNode(CAst.StatementSequenceNode node) { + return visitAstNodeDynamic(node); + } + + @Override + public T visitVariableNode(CAst.VariableNode node) { + return null; + } + + /** Arithmetic operators */ + @Override + public T visitAdditionNode(CAst.AdditionNode node) { + return visitAstNodeBinary(node); + } + + @Override + public T visitSubtractionNode(CAst.SubtractionNode node) { + return visitAstNodeBinary(node); + } + + @Override + public T visitMultiplicationNode(CAst.MultiplicationNode node) { + return visitAstNodeBinary(node); + } + + @Override + public T visitDivisionNode(CAst.DivisionNode node) { + return visitAstNodeBinary(node); + } + + /** Comparison operators */ + @Override + public T visitEqualNode(CAst.EqualNode node) { + return visitAstNodeBinary(node); + } + + @Override + public T visitNotEqualNode(CAst.NotEqualNode node) { + return visitAstNodeBinary(node); + } + + @Override + public T visitNegativeNode(CAst.NegativeNode node) { + return visitNegativeNode(node); + } + + @Override + public T visitLessThanNode(CAst.LessThanNode node) { + return visitAstNodeBinary(node); + } + + @Override + public T visitLessEqualNode(CAst.LessEqualNode node) { + return visitAstNodeBinary(node); + } + + @Override + public T visitGreaterThanNode(CAst.GreaterThanNode node) { + return visitAstNodeBinary(node); + } + + @Override + public T visitGreaterEqualNode(CAst.GreaterEqualNode node) { + return visitAstNodeBinary(node); + } + + /** LF built-in operations */ + @Override + public T visitSetPortNode(CAst.SetPortNode node) { + return visitAstNodeBinary(node); + } + + @Override + public T visitScheduleActionNode(CAst.ScheduleActionNode node) { + return visitAstNodeDynamic(node); + } + + @Override + public T visitScheduleActionIntNode(CAst.ScheduleActionIntNode node) { + return visitAstNodeDynamic(node); + } + + @Override + public T visitStateVarNode(CAst.StateVarNode node) { + return null; + } + + @Override + public T visitTriggerValueNode(CAst.TriggerValueNode node) { + return null; + } + + @Override + public T visitTriggerIsPresentNode(CAst.TriggerIsPresentNode node) { + return null; + } + + //// With one more parameter. + @Override + public T visitAstNode(CAst.AstNode node, List nodeList) { + return visitAstNode(node); + } + + @Override + public T visitAstNodeUnary(CAst.AstNodeUnary node, List nodeList) { + return visitAstNodeUnary(node); + } + + @Override + public T visitAstNodeBinary(CAst.AstNodeBinary node, List nodeList) { + return visitAstNodeBinary(node); + } + + @Override + public T visitAstNodeDynamic(CAst.AstNodeDynamic node, List nodeList) { + return visitAstNodeDynamic(node); + } + + @Override + public T visitAssignmentNode(CAst.AssignmentNode node, List nodeList) { + return visitAssignmentNode(node); + } + + @Override + public T visitIfBlockNode(CAst.IfBlockNode node, List nodeList) { + return visitIfBlockNode(node); + } + + @Override + public T visitIfBodyNode(CAst.IfBodyNode node, List nodeList) { + return visitIfBodyNode(node); + } + + @Override + public T visitLiteralNode(CAst.LiteralNode node, List nodeList) { + return visitLiteralNode(node); + } + + @Override + public T visitLogicalNotNode(CAst.LogicalNotNode node, List nodeList) { + return visitLogicalNotNode(node); + } + + @Override + public T visitLogicalAndNode(CAst.LogicalAndNode node, List nodeList) { + return visitLogicalAndNode(node); + } + + @Override + public T visitLogicalOrNode(CAst.LogicalOrNode node, List nodeList) { + return visitLogicalOrNode(node); + } + + @Override + public T visitOpaqueNode(CAst.OpaqueNode node, List nodeList) { + return visitOpaqueNode(node); + } + + @Override + public T visitStatementSequenceNode( + CAst.StatementSequenceNode node, List nodeList) { + return visitStatementSequenceNode(node); + } + + @Override + public T visitVariableNode(CAst.VariableNode node, List nodeList) { + return visitVariableNode(node); + } + + /** Arithmetic operators */ + @Override + public T visitAdditionNode(CAst.AdditionNode node, List nodeList) { + return visitAdditionNode(node); + } + + @Override + public T visitSubtractionNode(CAst.SubtractionNode node, List nodeList) { + return visitSubtractionNode(node); + } + + @Override + public T visitMultiplicationNode(CAst.MultiplicationNode node, List nodeList) { + return visitMultiplicationNode(node); + } + + @Override + public T visitDivisionNode(CAst.DivisionNode node, List nodeList) { + return visitDivisionNode(node); + } + + /** Comparison operators */ + @Override + public T visitEqualNode(CAst.EqualNode node, List nodeList) { + return visitEqualNode(node); + } + + @Override + public T visitNotEqualNode(CAst.NotEqualNode node, List nodeList) { + return visitNotEqualNode(node); + } + + @Override + public T visitNegativeNode(CAst.NegativeNode node, List nodeList) { + return visitNegativeNode(node); + } + + @Override + public T visitLessThanNode(CAst.LessThanNode node, List nodeList) { + return visitLessThanNode(node); + } + + @Override + public T visitLessEqualNode(CAst.LessEqualNode node, List nodeList) { + return visitLessEqualNode(node); + } + + @Override + public T visitGreaterThanNode(CAst.GreaterThanNode node, List nodeList) { + return visitGreaterThanNode(node); + } + + @Override + public T visitGreaterEqualNode(CAst.GreaterEqualNode node, List nodeList) { + return visitGreaterEqualNode(node); + } + + /** LF built-in operations */ + @Override + public T visitSetPortNode(CAst.SetPortNode node, List nodeList) { + return visitSetPortNode(node); + } + + @Override + public T visitScheduleActionNode(CAst.ScheduleActionNode node, List nodeList) { + return visitScheduleActionNode(node); + } + + @Override + public T visitScheduleActionIntNode( + CAst.ScheduleActionIntNode node, List nodeList) { + return visitScheduleActionIntNode(node); + } + + @Override + public T visitStateVarNode(CAst.StateVarNode node, List nodeList) { + return visitStateVarNode(node); + } + + @Override + public T visitTriggerValueNode(CAst.TriggerValueNode node, List nodeList) { + return visitTriggerValueNode(node); + } + + @Override + public T visitTriggerIsPresentNode(CAst.TriggerIsPresentNode node, List nodeList) { + return visitTriggerIsPresentNode(node); + } +} diff --git a/core/src/main/java/org/lflang/analyses/cast/CToUclidVisitor.java b/core/src/main/java/org/lflang/analyses/cast/CToUclidVisitor.java new file mode 100644 index 0000000000..231f0c5ae3 --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/cast/CToUclidVisitor.java @@ -0,0 +1,432 @@ +package org.lflang.analyses.cast; + +import java.util.ArrayList; +import java.util.List; +import org.lflang.analyses.cast.CAst.*; +import org.lflang.analyses.uclid.UclidGenerator; +import org.lflang.generator.ActionInstance; +import org.lflang.generator.NamedInstance; +import org.lflang.generator.PortInstance; +import org.lflang.generator.ReactionInstance; +import org.lflang.generator.ReactorInstance; +import org.lflang.generator.StateVariableInstance; +import org.lflang.generator.TriggerInstance; + +public class CToUclidVisitor extends CBaseAstVisitor { + + // The Uclid generator instance + protected UclidGenerator generator; + + // The reaction instance for the generated axiom + protected ReactionInstance.Runtime reaction; + + // The reactor that contains the reaction + protected ReactorInstance reactor; + + // A list of all the named instances + protected List instances = new ArrayList(); + + // Quantified variable + protected String qv = "i"; + protected String qv2 = "j"; + + // Unchanged variables and triggers + protected List unchangedStates; + protected List unchangedTriggers; + + // FIXME: Make this more flexible and infer value from program. + // Default reset value + String defaultValue = "0"; + String defaultPresence = "false"; + + public CToUclidVisitor(UclidGenerator generator, ReactionInstance.Runtime reaction) { + this.generator = generator; + this.reaction = reaction; + this.reactor = reaction.getReaction().getParent(); + instances.addAll(this.reactor.inputs); + instances.addAll(this.reactor.outputs); + instances.addAll(this.reactor.actions); + instances.addAll(this.reactor.states); + } + + @Override + public String visitAdditionNode(AdditionNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " + " + rhs + ")"; + } + + @Override + public String visitAssignmentNode(AssignmentNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " == " + rhs + ")"; + } + + @Override + public String visitDivisionNode(DivisionNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " / " + rhs + ")"; + } + + @Override + public String visitEqualNode(EqualNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " == " + rhs + ")"; + } + + @Override + public String visitGreaterEqualNode(GreaterEqualNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " >= " + rhs + ")"; + } + + @Override + public String visitGreaterThanNode(GreaterThanNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " > " + rhs + ")"; + } + + @Override + public String visitIfBlockNode(IfBlockNode node) { + String antecedent = visit(node.left); // Process if condition + String consequent = visit(((IfBodyNode) node.right).left); + return "(" + antecedent + " ==> " + "(" + consequent + "\n))"; + } + + @Override + public String visitLessEqualNode(LessEqualNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " <= " + rhs + ")"; + } + + @Override + public String visitLessThanNode(LessThanNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " < " + rhs + ")"; + } + + @Override + public String visitLiteralNode(LiteralNode node) { + return node.literal; + } + + @Override + public String visitLogicalAndNode(LogicalAndNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " && " + rhs + ")"; + } + + @Override + public String visitLogicalNotNode(LogicalNotNode node) { + return "!" + "(" + visit(node.child) + ")"; + } + + @Override + public String visitLogicalOrNode(LogicalOrNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " || " + rhs + ")"; + } + + @Override + public String visitMultiplicationNode(MultiplicationNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " * " + rhs + ")"; + } + + @Override + public String visitNotEqualNode(NotEqualNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " != " + rhs + ")"; + } + + @Override + public String visitNegativeNode(NegativeNode node) { + return "(" + "-1*(" + visit(node.child) + "))"; + } + + @Override + public String visitScheduleActionNode(ScheduleActionNode node) { + String name = ((VariableNode) node.children.get(0)).name; + NamedInstance instance = getInstanceByName(name); + ActionInstance action = (ActionInstance) instance; + String additionalDelay = visit(node.children.get(1)); + String str = + "\n(" + + "(finite_exists (" + + this.qv2 + + " : integer) in indices :: (" + + this.qv2 + + " > " + + this.qv + + " && " + + this.qv2 + + " <= END_TRACE) && (" + + "\n " + + action.getFullNameWithJoiner("_") + + "_is_present" + + "(" + + "t" + + "(" + + this.qv2 + + ")" + + ")" + + "\n " + + "&& " + + "tag_same" + + "(" + + "g(" + + this.qv2 + + ")" + + ", " + + "tag_schedule" + + "(" + + "g" + + "(" + + this.qv + + ")" + + ", " + + "(" + + action.getMinDelay().toNanoSeconds() + + "+" + + additionalDelay + + ")" + + ")" + + ")" + + "\n " + + "&& " + + action.getFullNameWithJoiner("_") + + "(" + + "s" + + "(" + + this.qv2 + + ")" + + ")" + + " == " + + "0" + + "\n)) // Closes finite_exists" + + "\n&& " + + action.getFullNameWithJoiner("_") + + "_scheduled" + + "(" + + "d" + + "(" + + this.qv + + ")" + + ")" + + "\n)"; + return str; + } + + @Override + public String visitScheduleActionIntNode(ScheduleActionIntNode node) { + String name = ((VariableNode) node.children.get(0)).name; + NamedInstance instance = getInstanceByName(name); + ActionInstance action = (ActionInstance) instance; + String additionalDelay = visit(node.children.get(1)); + String intValue = visit(node.children.get(2)); + String str = + "\n(" + + "(finite_exists (" + + this.qv2 + + " : integer) in indices :: (" + + this.qv2 + + " > " + + this.qv + + " && " + + this.qv2 + + " <= END_TRACE) && (" + + "\n " + + action.getFullNameWithJoiner("_") + + "_is_present" + + "(" + + "t" + + "(" + + this.qv2 + + ")" + + ")" + + "\n " + + "&& " + + "tag_same" + + "(" + + "g(" + + this.qv2 + + ")" + + ", " + + "tag_schedule" + + "(" + + "g" + + "(" + + this.qv + + ")" + + ", " + + "(" + + action.getMinDelay().toNanoSeconds() + + "+" + + additionalDelay + + ")" + + ")" + + ")" + + "\n)) // Closes finite_exists" + + "\n&& " + + action.getFullNameWithJoiner("_") + + "_scheduled" + + "(" + + "d" + + "(" + + this.qv + + ")" + + ")" + + "\n&& " + + action.getFullNameWithJoiner("_") + + "_scheduled_payload" + + "(" + + "pl" + + "(" + + this.qv + + ")" + + ")" + + " == " + + intValue + + "\n)"; + return str; + } + + @Override + public String visitSetPortNode(SetPortNode node) { + NamedInstance port = getInstanceByName(((VariableNode) node.left).name); + String value = visit(node.right); + // Remove this port from the unchanged list. + // this.unchangedTriggers.remove(port); + return "(" + + "(" + + port.getFullNameWithJoiner("_") + + "(" + + "s" + + "(" + + this.qv + + ")" + + ")" + + " == " + + value + + ")" + + " && " + + "(" + + port.getFullNameWithJoiner("_") + + "_is_present" + + "(" + + "t" + + "(" + + this.qv + + ")" + + ")" + + ")" + + ")"; + } + + @Override + public String visitStateVarNode(StateVarNode node) { + NamedInstance instance = getInstanceByName(node.name); + if (instance != null) { + return instance.getFullNameWithJoiner("_") + + "(" + + "s" + + "(" + + this.qv + + (node.prev ? "-1" : "") + + ")" + + ")"; + } + // FIXME: Throw exception + return ""; + } + + @Override + public String visitStatementSequenceNode(StatementSequenceNode node) { + String axiom = ""; + for (int i = 0; i < node.children.size(); i++) { + axiom += visit(node.children.get(i)); + if (i != node.children.size() - 1) axiom += "\n" + " " + "&& "; + } + return axiom; + } + + @Override + public String visitSubtractionNode(SubtractionNode node) { + String lhs = visit(node.left); + String rhs = visit(node.right); + return "(" + lhs + " - " + rhs + ")"; + } + + @Override + public String visitTriggerIsPresentNode(TriggerIsPresentNode node) { + // Find the trigger instance by name. + NamedInstance instance = getInstanceByName(node.name); + if (instance != null) { + return instance.getFullNameWithJoiner("_") + + "_is_present" + + "(" + + "t" + + "(" + + this.qv + + ")" + + ")"; + } + // FIXME: Throw exception + return ""; + } + + @Override + public String visitTriggerValueNode(TriggerValueNode node) { + // Find the trigger instance by name. + NamedInstance instance = getInstanceByName(node.name); + if (instance != null) { + return instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; + } + // FIXME: Throw exception + return ""; + } + + @Override + public String visitVariableNode(VariableNode node) { + NamedInstance instance = getInstanceByName(node.name); + if (instance != null) { + return instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; + } + // FIXME: Throw exception + return ""; + } + + ///////////////////////////// + //// Private functions + + private NamedInstance getInstanceByName(String name) { + for (NamedInstance i : this.instances) { + if (i instanceof ActionInstance) { + if (((ActionInstance) i).getDefinition().getName().equals(name)) { + return i; + } + } else if (i instanceof PortInstance) { + if (((PortInstance) i).getDefinition().getName().equals(name)) { + return i; + } + } else if (i instanceof StateVariableInstance) { + if (((StateVariableInstance) i).getDefinition().getName().equals(name)) { + return i; + } + } + } + System.out.println("Named instance" + "not found."); + return null; + } +} diff --git a/core/src/main/java/org/lflang/analyses/cast/IfNormalFormAstVisitor.java b/core/src/main/java/org/lflang/analyses/cast/IfNormalFormAstVisitor.java new file mode 100644 index 0000000000..180fe1f45f --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/cast/IfNormalFormAstVisitor.java @@ -0,0 +1,92 @@ +package org.lflang.analyses.cast; + +import java.util.ArrayList; +import java.util.List; + +/** + * An AST visitor that converts an original AST into the If Normal Form. See "Bounded Model Checking + * of Software using SMT Solvers instead of SAT Solvers" for details about the If Normal Form + * (https://link.springer.com/chapter/10.1007/11691617_9). + * + *

There are several requirements for an AST to be in INF: 1. There should be a single + * StatementSequence (SS) root node; 2. Each child of the SS node should be an IfBlockNode; 3. + * Variables in a subsequent child should be marked as "primed" versions of those in the previous + * child. + * + *

Limitations: 1. The implementation does not take the scope into account. E.g. "int i = 0; { + * int j = 0; }" is treated the same as "int i = 0; int j = 0;". 2. Due to the above limitation, the + * implementation assumes that each variable has a unique name. E.g. "{ int i = 0; }{ int i = 0; }" + * is an ill-formed program. + * + *

In this program, visit() is the normalise() in the paper. + */ +public class IfNormalFormAstVisitor extends CBaseAstVisitor { + + public CAst.StatementSequenceNode INF = new CAst.StatementSequenceNode(); + + @Override + public Void visitStatementSequenceNode( + CAst.StatementSequenceNode node, List conditions) { + // Create a new StatementSequenceNode. + for (CAst.AstNode child : node.children) { + visit(child, conditions); + } + return null; + } + + @Override + public Void visitAssignmentNode(CAst.AssignmentNode node, List conditions) { + this.INF.children.add(generateIfBlock(node, conditions)); + return null; + } + + @Override + public Void visitSetPortNode(CAst.SetPortNode node, List conditions) { + this.INF.children.add(generateIfBlock(node, conditions)); + return null; + } + + @Override + public Void visitScheduleActionNode(CAst.ScheduleActionNode node, List conditions) { + this.INF.children.add(generateIfBlock(node, conditions)); + return null; + } + + @Override + public Void visitScheduleActionIntNode( + CAst.ScheduleActionIntNode node, List conditions) { + this.INF.children.add(generateIfBlock(node, conditions)); + return null; + } + + @Override + public Void visitIfBlockNode(CAst.IfBlockNode node, List conditions) { + List leftConditions = new ArrayList<>(conditions); + leftConditions.add(node.left); + visit(((CAst.IfBodyNode) node.right).left, leftConditions); + if (((CAst.IfBodyNode) node.right).right != null) { + List rightConditions = new ArrayList<>(conditions); + // Add a not node. + CAst.LogicalNotNode notNode = new CAst.LogicalNotNode(); + notNode.child = node.left; // Negate the condition. + rightConditions.add(notNode); + visit(((CAst.IfBodyNode) node.right).right, rightConditions); + } + return null; + } + + private CAst.IfBlockNode generateIfBlock(CAst.AstNode node, List conditions) { + // Create an If Block node. + CAst.IfBlockNode ifNode = new CAst.IfBlockNode(); + // Set the condition of the if block node. + CAst.AstNode conjunction = AstUtils.takeConjunction(conditions); + ifNode.left = conjunction; + // Create a new body node. + CAst.IfBodyNode body = new CAst.IfBodyNode(); + ifNode.right = body; + // Set the then branch to the assignment. + body.left = node; + + return ifNode; + } +} diff --git a/core/src/main/java/org/lflang/analyses/cast/VariablePrecedenceVisitor.java b/core/src/main/java/org/lflang/analyses/cast/VariablePrecedenceVisitor.java new file mode 100644 index 0000000000..e6124bc516 --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/cast/VariablePrecedenceVisitor.java @@ -0,0 +1,43 @@ +package org.lflang.analyses.cast; + +import org.lflang.analyses.cast.CAst.*; + +/** This visitor marks certain variable node as "previous." */ +public class VariablePrecedenceVisitor extends CBaseAstVisitor { + + // This is a temporary solution and cannot handle, + // e.g., self->s = (self->s + 1) - (2 * 2). + @Override + public Void visitAssignmentNode(AssignmentNode node) { + // System.out.println("******* In assignment!!!"); + if (node.left instanceof StateVarNode) { + if (node.right instanceof AstNodeBinary) { + AstNodeBinary n = (AstNodeBinary) node.right; + if (n.left instanceof StateVarNode) { + ((StateVarNode) n.left).prev = true; + } + if (n.right instanceof StateVarNode) { + ((StateVarNode) n.right).prev = true; + } + } + } else { + System.out.println("Unreachable!"); // FIXME: Throw exception. + } + return null; + } + + @Override + public Void visitIfBlockNode(IfBlockNode node) { + if (((IfBodyNode) node.right).left != null) visit(((IfBodyNode) node.right).left); + if (((IfBodyNode) node.right).right != null) visit(((IfBodyNode) node.right).right); + return null; + } + + @Override + public Void visitStatementSequenceNode(StatementSequenceNode node) { + for (int i = 0; i < node.children.size(); i++) { + visit(node.children.get(i)); + } + return null; + } +} diff --git a/core/src/main/java/org/lflang/analyses/cast/Visitable.java b/core/src/main/java/org/lflang/analyses/cast/Visitable.java new file mode 100644 index 0000000000..458d629bc2 --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/cast/Visitable.java @@ -0,0 +1,12 @@ +package org.lflang.analyses.cast; + +import java.util.List; + +public interface Visitable { + + /** The {@link AstVisitor} needs a double dispatch method. */ + T accept(AstVisitor visitor); + + /** The {@link AstVisitor} needs a double dispatch method. */ + T accept(AstVisitor visitor, List nodeList); +} diff --git a/core/src/main/java/org/lflang/analyses/statespace/Event.java b/core/src/main/java/org/lflang/analyses/statespace/Event.java new file mode 100644 index 0000000000..ce96c878aa --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/statespace/Event.java @@ -0,0 +1,44 @@ +/** A node in the state space diagram representing a step in the execution of an LF program. */ +package org.lflang.analyses.statespace; + +import org.lflang.generator.TriggerInstance; + +public class Event implements Comparable { + + public TriggerInstance trigger; + public Tag tag; + + public Event(TriggerInstance trigger, Tag tag) { + this.trigger = trigger; + this.tag = tag; + } + + public TriggerInstance getTrigger() { + return this.trigger; + } + + @Override + public int compareTo(Event e) { + // Compare tags first. + int ret = this.tag.compareTo(e.tag); + // If tags match, compare trigger names. + if (ret == 0) ret = this.trigger.getFullName().compareTo(e.trigger.getFullName()); + return ret; + } + + /** This equals() method does NOT compare tags, only compares triggers. */ + @Override + public boolean equals(Object o) { + if (o == null) return false; + if (o instanceof Event) { + Event e = (Event) o; + if (this.trigger.equals(e.trigger)) return true; + } + return false; + } + + @Override + public String toString() { + return "(" + trigger.getFullName() + ", " + tag + ")"; + } +} diff --git a/core/src/main/java/org/lflang/analyses/statespace/EventQueue.java b/core/src/main/java/org/lflang/analyses/statespace/EventQueue.java new file mode 100644 index 0000000000..67cc485f68 --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/statespace/EventQueue.java @@ -0,0 +1,18 @@ +/** An event queue implementation that sorts events by time tag order */ +package org.lflang.analyses.statespace; + +import java.util.PriorityQueue; + +public class EventQueue extends PriorityQueue { + + /** + * Modify the original add() by enforcing uniqueness. There cannot be duplicate events in the + * event queue. + */ + @Override + public boolean add(Event e) { + if (this.contains(e)) return false; + super.add(e); + return true; + } +} diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateInfo.java b/core/src/main/java/org/lflang/analyses/statespace/StateInfo.java new file mode 100644 index 0000000000..8226d784d7 --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/statespace/StateInfo.java @@ -0,0 +1,24 @@ +/** A class that represents information in a step in a counterexample trace */ +package org.lflang.analyses.statespace; + +import java.util.ArrayList; +import java.util.HashMap; + +public class StateInfo { + + public ArrayList reactions = new ArrayList<>(); + public Tag tag; + public HashMap variables = new HashMap<>(); + public HashMap triggers = new HashMap<>(); + public HashMap scheduled = new HashMap<>(); + public HashMap payloads = new HashMap<>(); + + public void display() { + System.out.println("reactions: " + reactions); + System.out.println("tag: " + tag); + System.out.println("variables: " + variables); + System.out.println("triggers: " + triggers); + System.out.println("scheduled: " + scheduled); + System.out.println("payloads: " + payloads); + } +} diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java new file mode 100644 index 0000000000..82d46f0e4a --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java @@ -0,0 +1,217 @@ +/** A directed graph representing the state space of an LF program. */ +package org.lflang.analyses.statespace; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.lflang.TimeValue; +import org.lflang.generator.CodeBuilder; +import org.lflang.generator.ReactionInstance; +import org.lflang.graph.DirectedGraph; + +// FIXME: Use a linkedlist instead. +public class StateSpaceDiagram extends DirectedGraph { + + /** The first node of the state space diagram. */ + public StateSpaceNode head; + + /** The last node of the state space diagram. */ + public StateSpaceNode tail; + + /** + * The previously encountered node which the tail node goes back to, i.e. the location where the + * back loop happens. + */ + public StateSpaceNode loopNode; + + /** + * Store the state when the loop node is reached for the 2nd time. This is used to calculate the + * elapsed logical time on the back loop edge. + */ + public StateSpaceNode loopNodeNext; + + /** The logical time elapsed for each loop iteration. */ + public long loopPeriod; + + /** A dot file that represents the diagram */ + private CodeBuilder dot; + + /** */ + private final boolean compactDot = false; + + /** Before adding the node, assign it an index. */ + @Override + public void addNode(StateSpaceNode node) { + node.index = this.nodeCount(); + super.addNode(node); + } + + /** Get the immediately downstream node. */ + public StateSpaceNode getDownstreamNode(StateSpaceNode node) { + Set downstream = this.getDownstreamAdjacentNodes(node); + if (downstream == null || downstream.size() == 0) return null; + return (StateSpaceNode) downstream.toArray()[0]; + } + + /** Pretty print the diagram. */ + public void display() { + System.out.println("*************************************************"); + System.out.println("* Pretty printing worst-case state space diagram:"); + long timestamp; + StateSpaceNode node = this.head; + if (node == null) { + System.out.println("* EMPTY"); + System.out.println("*************************************************"); + return; + } + while (node != this.tail) { + System.out.print("* State " + node.index + ": "); + node.display(); + + // Store the tag of the prior step. + timestamp = node.tag.timestamp; + + // Assume a unique next state. + node = getDownstreamNode(node); + + // Compute time difference + if (node != null) { + TimeValue tsDiff = TimeValue.fromNanoSeconds(node.tag.timestamp - timestamp); + System.out.println("* => Advance time by " + tsDiff); + } + } + + // Print tail node + System.out.print("* (Tail) state " + node.index + ": "); + node.display(); + + if (this.loopNode != null) { + // Compute time difference + TimeValue tsDiff = TimeValue.fromNanoSeconds(loopNodeNext.tag.timestamp - tail.tag.timestamp); + System.out.println("* => Advance time by " + tsDiff); + + System.out.println("* Goes back to loop node: state " + this.loopNode.index); + System.out.print("* Loop node reached 2nd time: "); + this.loopNodeNext.display(); + } + System.out.println("*************************************************"); + } + + /** + * Generate a dot file from the state space diagram. + * + * @return a CodeBuilder with the generated code + */ + public CodeBuilder generateDot() { + if (dot == null) { + dot = new CodeBuilder(); + dot.pr("digraph G {"); + dot.indent(); + if (this.loopNode != null) { + dot.pr("layout=circo;"); + } + dot.pr("rankdir=LR;"); + if (this.compactDot) { + dot.pr("mindist=1.5;"); + dot.pr("overlap=false"); + dot.pr("node [shape=Mrecord fontsize=40]"); + dot.pr("edge [fontsize=40 penwidth=3 arrowsize=2]"); + } else { + dot.pr("node [shape=Mrecord]"); + } + // Generate a node for each state. + if (this.compactDot) { + for (StateSpaceNode n : this.nodes()) { + dot.pr( + "S" + + n.index + + " [" + + "label = \" {" + + "S" + + n.index + + " | " + + n.reactionsInvoked.size() + + " | " + + n.eventQ.size() + + "}" + + " | " + + n.tag + + "\"" + + "]"); + } + } else { + for (StateSpaceNode n : this.nodes()) { + List reactions = + n.reactionsInvoked.stream() + .map(ReactionInstance::getFullName) + .collect(Collectors.toList()); + String reactionsStr = String.join("\\n", reactions); + List events = n.eventQ.stream().map(Event::toString).collect(Collectors.toList()); + String eventsStr = String.join("\\n", events); + dot.pr( + "S" + + n.index + + " [" + + "label = \"" + + "S" + + n.index + + " | " + + n.tag + + " | " + + "Reactions invoked:\\n" + + reactionsStr + + " | " + + "Pending events:\\n" + + eventsStr + + "\"" + + "]"); + } + } + + StateSpaceNode current = this.head; + StateSpaceNode next = getDownstreamNode(this.head); + while (current != null && next != null && current != this.tail) { + TimeValue tsDiff = TimeValue.fromNanoSeconds(next.tag.timestamp - current.tag.timestamp); + dot.pr( + "S" + + current.index + + " -> " + + "S" + + next.index + + " [label = " + + "\"" + + "+" + + tsDiff + + "\"" + + "]"); + current = next; + next = getDownstreamNode(next); + } + + if (loopNode != null) { + TimeValue tsDiff = + TimeValue.fromNanoSeconds(loopNodeNext.tag.timestamp - tail.tag.timestamp); + TimeValue period = TimeValue.fromNanoSeconds(loopPeriod); + dot.pr( + "S" + + current.index + + " -> " + + "S" + + next.index + + " [label = " + + "\"" + + "+" + + tsDiff + + " -" + + period + + "\"" + + " weight = 0 " + + "]"); + } + + dot.unindent(); + dot.pr("}"); + } + return this.dot; + } +} diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java new file mode 100644 index 0000000000..97befc4a58 --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java @@ -0,0 +1,326 @@ +/** Explores the state space of an LF program. */ +package org.lflang.analyses.statespace; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import org.eclipse.xtext.xbase.lib.Exceptions; +import org.lflang.TimeUnit; +import org.lflang.TimeValue; +import org.lflang.generator.ActionInstance; +import org.lflang.generator.PortInstance; +import org.lflang.generator.ReactionInstance; +import org.lflang.generator.ReactorInstance; +import org.lflang.generator.RuntimeRange; +import org.lflang.generator.SendRange; +import org.lflang.generator.TimerInstance; +import org.lflang.generator.TriggerInstance; +import org.lflang.lf.Expression; +import org.lflang.lf.Time; +import org.lflang.lf.Variable; + +public class StateSpaceExplorer { + + // Instantiate an empty state space diagram. + public StateSpaceDiagram diagram = new StateSpaceDiagram(); + + // Indicate whether a back loop is found in the state space. + // A back loop suggests periodic behavior. + public boolean loopFound = false; + + /** + * Instantiate a global event queue. We will use this event queue to symbolically simulate the + * logical timeline. This simulation is also valid for runtime implementations that are federated + * or relax global barrier synchronization, since an LF program defines a unique logical timeline + * (assuming all reactions behave _consistently_ throughout the execution). + */ + public EventQueue eventQ = new EventQueue(); + + /** The main reactor instance based on which the state space is explored. */ + public ReactorInstance main; + + // Constructor + public StateSpaceExplorer(ReactorInstance main) { + this.main = main; + } + + /** Recursively add the first events to the event queue. */ + public void addInitialEvents(ReactorInstance reactor) { + // Add the startup trigger, if exists. + var startup = reactor.getStartupTrigger(); + if (startup != null) eventQ.add(new Event(startup, new Tag(0, 0, false))); + + // Add the initial timer firings, if exist. + for (TimerInstance timer : reactor.timers) { + eventQ.add(new Event(timer, new Tag(timer.getOffset().toNanoSeconds(), 0, false))); + } + + // Recursion + for (var child : reactor.children) { + addInitialEvents(child); + } + } + + /** + * Explore the state space and populate the state space diagram until the specified horizon (i.e. + * the end tag) is reached OR until the event queue is empty. + * + *

As an optimization, if findLoop is true, the algorithm tries to find a loop in the state + * space during exploration. If a loop is found (i.e. a previously encountered state is reached + * again) during exploration, the function returns early. + * + *

TODOs: 1. Handle action with 0 min delay. 2. Check if zero-delay connection works. + */ + public void explore(Tag horizon, boolean findLoop) { + // Traverse the main reactor instance recursively to find + // the known initial events (startup and timers' first firings). + // FIXME: It seems that we need to handle shutdown triggers + // separately, because they could break the back loop. + addInitialEvents(this.main); + + Tag previousTag = null; // Tag in the previous loop ITERATION + Tag currentTag = null; // Tag in the current loop ITERATION + StateSpaceNode currentNode = null; + StateSpaceNode previousNode = null; + HashMap uniqueNodes = new HashMap<>(); + boolean stop = true; + if (this.eventQ.size() > 0) { + stop = false; + currentTag = (eventQ.peek()).tag; + } + + // A list of reactions invoked at the current logical tag + Set reactionsInvoked; + // A temporary list of reactions processed in the current LOOP ITERATION + Set reactionsTemp; + + while (!stop) { + + // Pop the events from the earliest tag off the event queue. + ArrayList currentEvents = new ArrayList(); + // FIXME: Use stream methods here? + while (eventQ.size() > 0 && eventQ.peek().tag.compareTo(currentTag) == 0) { + Event e = eventQ.poll(); + // System.out.println("DEBUG: Popped an event: " + e); + currentEvents.add(e); + } + + // Collect all the reactions invoked in this current LOOP ITERATION + // triggered by the earliest events. + // Using a hash set here to make sure the reactions invoked + // are unique. Sometimes multiple events can trigger the same reaction, + // and we do not want to record duplicate reaction invocations. + reactionsTemp = new HashSet(); + for (Event e : currentEvents) { + Set dependentReactions = e.trigger.getDependentReactions(); + reactionsTemp.addAll(dependentReactions); + // System.out.println("DEBUG: ReactionTemp: " + reactionsTemp); + + // If the event is a timer firing, enqueue the next firing. + if (e.trigger instanceof TimerInstance) { + TimerInstance timer = (TimerInstance) e.trigger; + eventQ.add( + new Event( + timer, + new Tag( + e.tag.timestamp + timer.getPeriod().toNanoSeconds(), + 0, // A time advancement resets microstep to 0. + false))); + } + } + + // For each reaction invoked, compute the new events produced. + for (ReactionInstance reaction : reactionsTemp) { + // Iterate over all the effects produced by this reaction. + // If the effect is a port, obtain the downstream port along + // a connection and enqueue a future event for that port. + // If the effect is an action, enqueue a future event for + // this action. + for (TriggerInstance effect : reaction.effects) { + if (effect instanceof PortInstance) { + + for (SendRange senderRange : ((PortInstance) effect).getDependentPorts()) { + + for (RuntimeRange destinationRange : senderRange.destinations) { + PortInstance downstreamPort = destinationRange.instance; + + // Getting delay from connection + // FIXME: Is there a more concise way to do this? + long delay = 0; + Expression delayExpr = senderRange.connection.getDelay(); + if (delayExpr instanceof Time) { + long interval = ((Time) delayExpr).getInterval(); + String unit = ((Time) delayExpr).getUnit(); + TimeValue timeValue = new TimeValue(interval, TimeUnit.fromName(unit)); + delay = timeValue.toNanoSeconds(); + } + + // Create and enqueue a new event. + Event e = + new Event(downstreamPort, new Tag(currentTag.timestamp + delay, 0, false)); + // System.out.println("DEBUG: Added a port event: " + e); + eventQ.add(e); + } + } + } else if (effect instanceof ActionInstance) { + // Get the minimum delay of this action. + long min_delay = ((ActionInstance) effect).getMinDelay().toNanoSeconds(); + long microstep = 0; + if (min_delay == 0) { + microstep = currentTag.microstep + 1; + } + // Create and enqueue a new event. + Event e = + new Event(effect, new Tag(currentTag.timestamp + min_delay, microstep, false)); + // System.out.println("DEBUG: Added an action event: " + e); + eventQ.add(e); + } + } + } + + // We are at the first iteration. + // Initialize currentNode. + if (previousTag == null) { + // System.out.println("DEBUG: Case 1"); + //// Now we are done with the node at the previous tag, + //// work on the new node at the current timestamp. + // Copy the reactions in reactionsTemp. + reactionsInvoked = new HashSet(reactionsTemp); + + // Create a new state in the SSD for the current tag, + // add the reactions triggered to the state, + // and add a snapshot of the event queue (with new events + // generated by reaction invocations in the curren tag) + // to the state. + StateSpaceNode node = + new StateSpaceNode( + currentTag, // Current tag + reactionsInvoked, // Reactions invoked at this tag + new ArrayList(eventQ) // A snapshot of the event queue + ); + + // Initialize currentNode. + currentNode = node; + } + // When we advance to a new TIMESTAMP (not a new tag), + // create a new node in the state space diagram + // for everything processed in the previous timestamp. + // This makes sure that the granularity of nodes is + // at the timestamp-level, so that we don't have to + // worry about microsteps. + else if (previousTag != null && currentTag.timestamp > previousTag.timestamp) { + // System.out.println("DEBUG: Case 2"); + // Whenever we finish a tag, check for loops fist. + // If currentNode matches an existing node in uniqueNodes, + // duplicate is set to the existing node. + StateSpaceNode duplicate; + if (findLoop && (duplicate = uniqueNodes.put(currentNode.hash(), currentNode)) != null) { + + // Mark the loop in the diagram. + loopFound = true; + this.diagram.loopNode = duplicate; + this.diagram.loopNodeNext = currentNode; + this.diagram.tail = previousNode; + // Loop period is the time difference between the 1st time + // the node is reached and the 2nd time the node is reached. + this.diagram.loopPeriod = + this.diagram.loopNodeNext.tag.timestamp - this.diagram.loopNode.tag.timestamp; + this.diagram.addEdge(this.diagram.loopNode, this.diagram.tail); + return; // Exit the while loop early. + } + + // Now we are at a new tag, and a loop is not found, + // add the node to the state space diagram. + // Adding a node to the graph once it is finalized + // because this makes checking duplicate nodes easier. + // We don't have to remove a node from the graph. + this.diagram.addNode(currentNode); + this.diagram.tail = currentNode; // Update the current tail. + + // If the head is not empty, add an edge from the previous state + // to the next state. Otherwise initialize the head to the new node. + if (previousNode != null) { + // System.out.println("--- Add a new edge between " + currentNode + " and " + node); + // this.diagram.addEdge(currentNode, previousNode); // Sink first, then source + if (previousNode != currentNode) this.diagram.addEdge(currentNode, previousNode); + } else this.diagram.head = currentNode; // Initialize the head. + + //// Now we are done with the node at the previous tag, + //// work on the new node at the current timestamp. + // Copy the reactions in reactionsTemp. + reactionsInvoked = new HashSet(reactionsTemp); + + // Create a new state in the SSD for the current tag, + // add the reactions triggered to the state, + // and add a snapshot of the event queue (with new events + // generated by reaction invocations in the curren tag) + // to the state. + StateSpaceNode node = + new StateSpaceNode( + currentTag, // Current tag + reactionsInvoked, // Reactions invoked at this tag + new ArrayList(eventQ) // A snapshot of the event queue + ); + + // Update the previous node. + previousNode = currentNode; + // Update the current node to the new (potentially incomplete) node. + currentNode = node; + } + // Timestamp does not advance because we are processing + // connections with zero delay. + else if (previousTag != null && currentTag.timestamp == previousTag.timestamp) { + // System.out.println("DEBUG: Case 3"); + // Add reactions explored in the current loop iteration + // to the existing state space node. + currentNode.reactionsInvoked.addAll(reactionsTemp); + // Update the eventQ snapshot. + currentNode.eventQ = new ArrayList(eventQ); + } else { + // Unreachable + Exceptions.sneakyThrow(new Exception("Reached an unreachable part.")); + } + + // Update the current tag for the next iteration. + if (eventQ.size() > 0) { + previousTag = currentTag; + currentTag = eventQ.peek().tag; + } + + // Stop if: + // 1. the event queue is empty, or + // 2. the horizon is reached. + if (eventQ.size() == 0) { + // System.out.println("DEBUG: Stopping because eventQ is empty!"); + stop = true; + } else if (currentTag.timestamp > horizon.timestamp) { + // System.out.println("DEBUG: Stopping because horizon is reached! Horizon: " + horizon + " + // Current Tag: " + currentTag); + // System.out.println("DEBUG: EventQ: " + eventQ); + stop = true; + } + } + + // Check if the last current node is added to the graph yet. + // If not, add it now. + // This could happen when condition (previousTag == null) + // or (previousTag != null + // && currentTag.compareTo(previousTag) > 0) is true and then + // the simulation ends, leaving a new node dangling. + if (previousNode == null || previousNode.tag.timestamp < currentNode.tag.timestamp) { + this.diagram.addNode(currentNode); + this.diagram.tail = currentNode; // Update the current tail. + if (previousNode != null) { + this.diagram.addEdge(currentNode, previousNode); + } + } + + // When we exit and we still don't have a head, + // that means there is only one node in the diagram. + // Set the current node as the head. + if (this.diagram.head == null) this.diagram.head = currentNode; + + return; + } +} diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java new file mode 100644 index 0000000000..a2f0918b02 --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java @@ -0,0 +1,99 @@ +/** A node in the state space diagram representing a step in the execution of an LF program. */ +package org.lflang.analyses.statespace; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.lflang.TimeValue; +import org.lflang.generator.ReactionInstance; +import org.lflang.generator.TriggerInstance; + +public class StateSpaceNode { + + public int index; // Set in StateSpaceDiagram.java + public Tag tag; + public TimeValue time; // Readable representation of tag.timestamp + public Set reactionsInvoked; + public ArrayList eventQ; + + public StateSpaceNode(Tag tag, Set reactionsInvoked, ArrayList eventQ) { + this.tag = tag; + this.eventQ = eventQ; + this.reactionsInvoked = reactionsInvoked; + this.time = TimeValue.fromNanoSeconds(tag.timestamp); + } + + /** + * Assuming both eventQs have the same length, for each pair of events in eventQ1 and eventQ2, + * check if the time distances between the node's tag and the two events' tags are equal. + */ + private boolean equidistant(StateSpaceNode n1, StateSpaceNode n2) { + if (n1.eventQ.size() != n2.eventQ.size()) return false; + for (int i = 0; i < n1.eventQ.size(); i++) { + if (n1.eventQ.get(i).tag.timestamp - n1.tag.timestamp + != n2.eventQ.get(i).tag.timestamp - n2.tag.timestamp) { + return false; + } + } + return true; + } + + /** Two methods for pretty printing */ + public void display() { + System.out.println("(" + this.time + ", " + reactionsInvoked + ", " + eventQ + ")"); + } + + public String toString() { + return "(" + this.time + ", " + reactionsInvoked + ", " + eventQ + ")"; + } + + /** + * This equals method does NOT compare tags, only compares reactionsInvoked, eventQ, and whether + * future events are equally distant. + */ + @Override + public boolean equals(Object o) { + if (o == null) return false; + if (o instanceof StateSpaceNode) { + StateSpaceNode node = (StateSpaceNode) o; + if (this.reactionsInvoked.equals(node.reactionsInvoked) + && this.eventQ.equals(node.eventQ) + && equidistant(this, node)) return true; + } + return false; + } + + /** + * Generate hash for the node. This hash function is used for checking the uniqueness of nodes. It + * is not meant to be used as a hashCode() because doing so interferes with node insertion in the + * state space diagram. + */ + public int hash() { + // Initial value + int result = 17; + + // Generate hash for the reactions invoked. + result = 31 * result + reactionsInvoked.hashCode(); + + // Generate hash for the triggers in the queued events. + List eventNames = + this.eventQ.stream() + .map(Event::getTrigger) + .map(TriggerInstance::getFullName) + .collect(Collectors.toList()); + result = 31 * result + eventNames.hashCode(); + + // Generate hash for the time differences. + List timeDiff = + this.eventQ.stream() + .map( + e -> { + return e.tag.timestamp - this.tag.timestamp; + }) + .collect(Collectors.toList()); + result = 31 * result + timeDiff.hashCode(); + + return result; + } +} diff --git a/core/src/main/java/org/lflang/analyses/statespace/Tag.java b/core/src/main/java/org/lflang/analyses/statespace/Tag.java new file mode 100644 index 0000000000..718db3e6ad --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/statespace/Tag.java @@ -0,0 +1,63 @@ +/** + * Class representing a logical time tag, which is a pair that consists of a timestamp (type long) + * and a microstep (type long). + */ +package org.lflang.analyses.statespace; + +import org.lflang.TimeValue; + +public class Tag implements Comparable { + + public long timestamp; + public long microstep; + public boolean forever; // Whether the tag is FOREVER into the future. + + public Tag(long timestamp, long microstep, boolean forever) { + this.timestamp = timestamp; + this.microstep = microstep; + this.forever = forever; + } + + @Override + public int compareTo(Tag t) { + // If one tag is forever, and the other is not, + // then forever tag is later. If both tags are + // forever, then they are equal. + if (this.forever && !t.forever) return 1; + else if (!this.forever && t.forever) return -1; + else if (this.forever && t.forever) return 0; + + // Compare the timestamps if they are not equal. + if (this.timestamp != t.timestamp) { + if (this.timestamp > t.timestamp) return 1; + else if (this.timestamp < t.timestamp) return -1; + else return 0; + } + + // Otherwise, compare the microsteps. + if (this.microstep > t.microstep) return 1; + else if (this.microstep < t.microstep) return -1; + else return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof Tag) { + Tag t = (Tag) o; + if (this.timestamp == t.timestamp + && this.microstep == t.microstep + && this.forever == t.forever) return true; + } + return false; + } + + @Override + public String toString() { + if (this.forever) return "(FOREVER, " + this.microstep + ")"; + else { + TimeValue time = TimeValue.fromNanoSeconds(this.timestamp); + return "(" + time + ", " + this.microstep + ")"; + } + } +} diff --git a/core/src/main/java/org/lflang/analyses/uclid/MTLVisitor.java b/core/src/main/java/org/lflang/analyses/uclid/MTLVisitor.java new file mode 100644 index 0000000000..b83c3849a6 --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/uclid/MTLVisitor.java @@ -0,0 +1,659 @@ +/************* + * Copyright (c) 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. + ***************/ + +/** (EXPERIMENTAL) Transpiler from an MTL specification to a Uclid axiom. */ +package org.lflang.analyses.uclid; + +import org.lflang.TimeUnit; +import org.lflang.TimeValue; +import org.lflang.dsl.MTLParser; +import org.lflang.dsl.MTLParserBaseVisitor; +import org.lflang.generator.CodeBuilder; + +public class MTLVisitor extends MTLParserBaseVisitor { + + //////////////////////////////////////////// + //// Protected fields + + /** The main place to put generated code. */ + protected CodeBuilder code = new CodeBuilder(); + + /** Tactic to be used to prove the property. */ + protected String tactic; + + /** Time horizon (in nanoseconds) of the property */ + protected long horizon = 0; + + // Constructor + public MTLVisitor(String tactic) { + this.tactic = tactic; + } + + //////////////////////////////////////////// + //// Public methods + public String visitMtl( + MTLParser.MtlContext ctx, String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { + + return visitEquivalence(ctx.equivalence(), prefixIdx, QFIdx, prevPrefixIdx, horizon); + } + + public String visitEquivalence( + MTLParser.EquivalenceContext ctx, + String prefixIdx, + int QFIdx, + String prevPrefixIdx, + long horizon) { + + if (ctx.right == null) { + return visitImplication(ctx.left, prefixIdx, QFIdx, prevPrefixIdx, horizon); + } + return "(" + + visitImplication(ctx.left, prefixIdx, QFIdx, prevPrefixIdx, horizon) + + ")" + + " <==> " + + "(" + + visitImplication(ctx.right, prefixIdx, QFIdx, prevPrefixIdx, horizon) + + ")"; + } + + public String visitImplication( + MTLParser.ImplicationContext ctx, + String prefixIdx, + int QFIdx, + String prevPrefixIdx, + long horizon) { + + if (ctx.right == null) { + return visitDisjunction(ctx.left, prefixIdx, QFIdx, prevPrefixIdx, horizon); + } + return "(" + + visitDisjunction(ctx.left, prefixIdx, QFIdx, prevPrefixIdx, horizon) + + ")" + + " ==> " + + "(" + + visitDisjunction(ctx.right, prefixIdx, QFIdx, prevPrefixIdx, horizon) + + ")"; + } + + public String visitDisjunction( + MTLParser.DisjunctionContext ctx, + String prefixIdx, + int QFIdx, + String prevPrefixIdx, + long horizon) { + + String str = ""; + for (int i = 0; i < ctx.terms.size(); i++) { + str += + "(" + + visitConjunction(ctx.terms.get(i), prefixIdx, QFIdx, prevPrefixIdx, horizon) + + ")" + + (i == ctx.terms.size() - 1 ? "" : "||"); + } + return str; + } + + public String visitConjunction( + MTLParser.ConjunctionContext ctx, + String prefixIdx, + int QFIdx, + String prevPrefixIdx, + long horizon) { + + String str = ""; + for (int i = 0; i < ctx.terms.size(); i++) { + str += + "(" + + visitUntil( + (MTLParser.UntilContext) ctx.terms.get(i), + prefixIdx, + QFIdx, + prevPrefixIdx, + horizon) + + ")" + + (i == ctx.terms.size() - 1 ? "" : "&&"); + } + return str; + } + + // A custom dispatch function + public String _visitUnaryOp( + MTLParser.UnaryOpContext ctx, + String prefixIdx, + int QFIdx, + String prevPrefixIdx, + long horizon) { + + // FIXME: Is there a more "antlr" way to do dispatch here? + if (ctx instanceof MTLParser.NoUnaryOpContext) { + return visitNoUnaryOp( + (MTLParser.NoUnaryOpContext) ctx, prefixIdx, QFIdx, prevPrefixIdx, horizon); + } + if (ctx instanceof MTLParser.NegationContext) { + return visitNegation( + (MTLParser.NegationContext) ctx, prefixIdx, QFIdx, prevPrefixIdx, horizon); + } + if (ctx instanceof MTLParser.NextContext) { + return visitNext((MTLParser.NextContext) ctx, prefixIdx, QFIdx, prevPrefixIdx, horizon); + } + if (ctx instanceof MTLParser.GloballyContext) { + return visitGlobally( + (MTLParser.GloballyContext) ctx, prefixIdx, QFIdx, prevPrefixIdx, horizon); + } + if (ctx instanceof MTLParser.FinallyContext) { + return visitFinally((MTLParser.FinallyContext) ctx, prefixIdx, QFIdx, prevPrefixIdx, horizon); + } + + // FIXME: Throw an exception. + return ""; + } + + public String visitUntil( + MTLParser.UntilContext ctx, String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { + + // If right is null, continue recursion. + if (ctx.right == null) { + return _visitUnaryOp(ctx.left, prefixIdx, QFIdx, prevPrefixIdx, horizon); + } + + String end; + if (this.tactic.equals("induction")) { + end = "(" + prefixIdx + " + CT)"; + } else { + end = "END"; + } + + // Otherwise, create the Until formula. + // Check if the time interval is a range or a singleton. + long lowerBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); + long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); + long currentHorizon = horizon + upperBoundNanoSec; + if (currentHorizon > this.horizon) this.horizon = currentHorizon; + String timePredicate = + generateTimePredicate( + ctx.timeInterval, lowerBoundNanoSec, upperBoundNanoSec, "j" + QFIdx, prefixIdx); + + return "finite_exists " + + "(" + + "j" + + QFIdx + + " : integer) in indices :: " + + "j" + + QFIdx + + " >= " + + prefixIdx + + " && " + + "j" + + QFIdx + + " <= " + + end + + " && " + + "!" + + "isNULL" + + "(" + + "j" + + QFIdx + + ")" + + " && " + + "(" + + _visitUnaryOp(ctx.right, ("j" + QFIdx), QFIdx + 1, prefixIdx, currentHorizon) + + ")" + + " && " + + "(" + + "\n" + + "// Time Predicate\n" + + timePredicate + + "\n" + + ")" + + " && " + + "(" + + "finite_forall " + + "(" + + "i" + + QFIdx + + " : integer) in indices :: " + + "(" + + "i" + + QFIdx + + " >= " + + prefixIdx + + " && " + + "i" + + QFIdx + + " < " + + "j" + + QFIdx + + ")" + + " ==> " + + "(" + + _visitUnaryOp(ctx.left, ("i" + QFIdx), QFIdx + 1, ("j" + QFIdx), currentHorizon) + + ")" + + ")"; + } + + public String visitNoUnaryOp( + MTLParser.NoUnaryOpContext ctx, + String prefixIdx, + int QFIdx, + String prevPrefixIdx, + long horizon) { + + return visitPrimary(ctx.formula, prefixIdx, QFIdx, prevPrefixIdx, horizon); + } + + public String visitNegation( + MTLParser.NegationContext ctx, + String prefixIdx, + int QFIdx, + String prevPrefixIdx, + long horizon) { + + return "!(" + visitPrimary(ctx.formula, prefixIdx, QFIdx, prevPrefixIdx, horizon) + ")"; + } + + public String visitNext( + MTLParser.NextContext ctx, String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { + // FIXME: Horizon needs to advance! + return visitPrimary(ctx.formula, ("(" + prefixIdx + "+1)"), QFIdx, prevPrefixIdx, horizon); + } + + public String visitGlobally( + MTLParser.GloballyContext ctx, + String prefixIdx, + int QFIdx, + String prevPrefixIdx, + long horizon) { + + String end; + if (this.tactic.equals("induction")) { + end = "(" + prefixIdx + " + CT)"; + } else { + end = "END"; + } + + long lowerBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); + long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); + long currentHorizon = horizon + upperBoundNanoSec; + if (currentHorizon > this.horizon) this.horizon = currentHorizon; + String timePredicate = + generateTimePredicate( + ctx.timeInterval, lowerBoundNanoSec, upperBoundNanoSec, "j" + QFIdx, prefixIdx); + return "(" + + "finite_forall " + + "(" + + "j" + + QFIdx + + " : integer) in indices :: " + + "(" + + "j" + + QFIdx + + " >= " + + prefixIdx + + " && " + + "j" + + QFIdx + + " <= " + + end + + " && " + + "!" + + "isNULL" + + "(" + + "j" + + QFIdx + + ")" + + " && " + + "(" + + "\n" + + "// Time Predicate\n" + + timePredicate + + "\n" + + ")" + + ")" + + " ==> " + + "(" + + visitPrimary(ctx.formula, ("j" + QFIdx), QFIdx + 1, prefixIdx, currentHorizon) + + ")" + + ")"; + } + + public String visitFinally( + MTLParser.FinallyContext ctx, + String prefixIdx, + int QFIdx, + String prevPrefixIdx, + long horizon) { + + String end; + if (this.tactic.equals("induction")) { + end = "(" + prefixIdx + " + CT)"; + } else { + end = "END"; + } + + long lowerBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, false); + long upperBoundNanoSec = getNanoSecFromIntervalContext(ctx.timeInterval, true); + long currentHorizon = horizon + upperBoundNanoSec; + if (currentHorizon > this.horizon) this.horizon = currentHorizon; + String timePredicate = + generateTimePredicate( + ctx.timeInterval, lowerBoundNanoSec, upperBoundNanoSec, "j" + QFIdx, prefixIdx); + return "finite_exists " + + "(" + + "j" + + QFIdx + + " : integer) in indices :: " + + "j" + + QFIdx + + " >= " + + prefixIdx + + " && " + + "j" + + QFIdx + + " <= " + + end + + " && " + + "!" + + "isNULL" + + "(" + + "j" + + QFIdx + + ")" + + " && " + + "(" + + visitPrimary(ctx.formula, ("j" + QFIdx), QFIdx + 1, prefixIdx, currentHorizon) + + ")" + + " && " + + "(" + + "\n" + + "// Time Predicate\n" + + timePredicate + + "\n" + + ")"; + } + + public String visitPrimary( + MTLParser.PrimaryContext ctx, + String prefixIdx, + int QFIdx, + String prevPrefixIdx, + long horizon) { + + if (ctx.atom != null) + return visitAtomicProp(ctx.atom, prefixIdx, QFIdx, prevPrefixIdx, horizon); + else if (ctx.id != null) { + // Check if the ID is a reaction. + // FIXME: Not robust. + if (ctx.id.getText().contains("_reaction_")) { + return ctx.id.getText() + "(" + "rxn(" + prefixIdx + ")" + ")"; + } else if (ctx.id.getText().contains("_is_present")) { + return ctx.id.getText() + "(" + "t(" + prefixIdx + ")" + ")"; + } else { + return ctx.id.getText() + "(" + "s(" + prefixIdx + ")" + ")"; + } + } else return visitMtl(ctx.formula, prefixIdx, QFIdx, prevPrefixIdx, horizon); + } + + public String visitAtomicProp( + MTLParser.AtomicPropContext ctx, + String prefixIdx, + int QFIdx, + String prevPrefixIdx, + long horizon) { + + if (ctx.primitive != null) return ctx.primitive.getText(); + else + return visitExpr(ctx.left, prefixIdx, QFIdx, prevPrefixIdx, horizon) + + " " + + ctx.op.getText() + + " " + + visitExpr(ctx.right, prefixIdx, QFIdx, prevPrefixIdx, horizon); + } + + public String visitExpr( + MTLParser.ExprContext ctx, String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { + + if (ctx.ID() != null) return ctx.ID().getText() + "(" + "s(" + prefixIdx + ")" + ")"; + else if (ctx.INTEGER() != null) return ctx.INTEGER().getText(); + else return visitSum(ctx.sum(), prefixIdx, QFIdx, prevPrefixIdx, horizon); + } + + public String visitSum( + MTLParser.SumContext ctx, String prefixIdx, int QFIdx, String prevPrefixIdx, long horizon) { + + String str = ""; + for (int i = 0; i < ctx.terms.size(); i++) { + str += + "(" + + visitDifference(ctx.terms.get(i), prefixIdx, QFIdx, prevPrefixIdx, horizon) + + ")" + + (i == ctx.terms.size() - 1 ? "" : "+"); + } + return str; + } + + public String visitDifference( + MTLParser.DifferenceContext ctx, + String prefixIdx, + int QFIdx, + String prevPrefixIdx, + long horizon) { + + String str = ""; + for (int i = 0; i < ctx.terms.size(); i++) { + str += + "(" + + visitProduct(ctx.terms.get(i), prefixIdx, QFIdx, prevPrefixIdx, horizon) + + ")" + + (i == ctx.terms.size() - 1 ? "" : "-"); + } + return str; + } + + public String visitProduct( + MTLParser.ProductContext ctx, + String prefixIdx, + int QFIdx, + String prevPrefixIdx, + long horizon) { + + String str = ""; + for (int i = 0; i < ctx.terms.size(); i++) { + str += + "(" + + visitQuotient(ctx.terms.get(i), prefixIdx, QFIdx, prevPrefixIdx, horizon) + + ")" + + (i == ctx.terms.size() - 1 ? "" : "*"); + } + return str; + } + + public String visitQuotient( + MTLParser.QuotientContext ctx, + String prefixIdx, + int QFIdx, + String prevPrefixIdx, + long horizon) { + + String str = ""; + for (int i = 0; i < ctx.terms.size(); i++) { + str += + "(" + + visitExpr(ctx.terms.get(i), prefixIdx, QFIdx, prevPrefixIdx, horizon) + + ")" + + (i == ctx.terms.size() - 1 ? "" : "/"); + } + return str; + } + + /////////////////////////////////////// + //// Private methods + + /** + * Return a time value in nanoseconds from an IntervalContext. + * + * @param ctx + * @param getUpper + * @return + */ + private long getNanoSecFromIntervalContext(MTLParser.IntervalContext ctx, boolean getUpper) { + // If we have a singleton, the return value is the same regardless of getUpper. + if (ctx instanceof MTLParser.SingletonContext) { + MTLParser.SingletonContext singletonCtx = (MTLParser.SingletonContext) ctx; + String timeInstantValue = singletonCtx.instant.value.getText(); + String timeInstantUnit = ""; + long timeInstantNanoSec = 0; + if (!timeInstantValue.equals("0")) { + timeInstantUnit = singletonCtx.instant.unit.getText(); + TimeValue timeValue = + new TimeValue(Integer.valueOf(timeInstantValue), TimeUnit.fromName(timeInstantUnit)); + timeInstantNanoSec = timeValue.toNanoSeconds(); + } + return timeInstantNanoSec; + } + + MTLParser.RangeContext rangeCtx = (MTLParser.RangeContext) ctx; + if (!getUpper) { + String lowerBoundTimeValue = rangeCtx.lowerbound.value.getText(); + String lowerBoundTimeUnit = ""; + long lowerBoundNanoSec = 0; + if (!lowerBoundTimeValue.equals("0")) { + lowerBoundTimeUnit = rangeCtx.lowerbound.unit.getText(); + TimeValue lowerTimeValue = + new TimeValue( + Integer.valueOf(lowerBoundTimeValue), TimeUnit.fromName(lowerBoundTimeUnit)); + lowerBoundNanoSec = lowerTimeValue.toNanoSeconds(); + } + return lowerBoundNanoSec; + } else { + String upperBoundTimeValue = rangeCtx.upperbound.value.getText(); + String upperBoundTimeUnit = ""; + long upperBoundNanoSec = 0; + if (!upperBoundTimeValue.equals("0")) { + upperBoundTimeUnit = rangeCtx.upperbound.unit.getText(); + TimeValue upperTimeValue = + new TimeValue( + Integer.valueOf(upperBoundTimeValue), TimeUnit.fromName(upperBoundTimeUnit)); + upperBoundNanoSec = upperTimeValue.toNanoSeconds(); + } + return upperBoundNanoSec; + } + } + + /** + * Generate a time predicate from a range. + * + * @param ctx + * @param lowerBoundNanoSec + * @param upperBoundNanoSec + * @return + */ + private String generateTimePredicate( + MTLParser.IntervalContext ctx, + long lowerBoundNanoSec, + long upperBoundNanoSec, + String prefixIdx, + String prevPrefixIdx) { + String timePredicate = ""; + + if (ctx instanceof MTLParser.SingletonContext) { + MTLParser.SingletonContext singletonCtx = (MTLParser.SingletonContext) ctx; + timePredicate += + "tag_same(g(" + + prefixIdx + + "), " + + "tag_delay(g(" + + prevPrefixIdx + + "), " + + upperBoundNanoSec + + "))"; + } else { + // Since MTL doesn't include microsteps, we only compare timestamps here. + MTLParser.RangeContext rangeCtx = (MTLParser.RangeContext) ctx; + timePredicate += "("; + if (rangeCtx.LBRACKET() != null) { + timePredicate += + "pi1(g(" + + prefixIdx + + "))" + + " >= " + + "(" + + "pi1(g(" + + prevPrefixIdx + + "))" + + " + " + + lowerBoundNanoSec + + ")"; + } else { + timePredicate += + "pi1(g(" + + prefixIdx + + "))" + + " > " + + "(" + + "pi1(g(" + + prevPrefixIdx + + "))" + + " + " + + lowerBoundNanoSec + + ")"; + } + timePredicate += ") && ("; + if (rangeCtx.RBRACKET() != null) { + timePredicate += + "pi1(g(" + + prefixIdx + + "))" + + " <= " + + "(" + + "pi1(g(" + + prevPrefixIdx + + "))" + + " + " + + upperBoundNanoSec + + ")"; + } else { + timePredicate += + "pi1(g(" + + prefixIdx + + "))" + + " < " + + "(" + + "pi1(g(" + + prevPrefixIdx + + "))" + + " + " + + upperBoundNanoSec + + ")"; + } + timePredicate += ")"; + } + + return timePredicate; + } + + /** Get the time horizon (in nanoseconds) of the property. */ + public long getHorizon() { + return this.horizon; + } +} diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java new file mode 100644 index 0000000000..2f34d04c05 --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java @@ -0,0 +1,1737 @@ +/************* + * Copyright (c) 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. + ***************/ + +/** (EXPERIMENTAL) Generator for Uclid5 models. */ +package org.lflang.analyses.uclid; + +import static org.lflang.ASTUtils.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.xbase.lib.Exceptions; +import org.lflang.ASTUtils; +import org.lflang.Target; +import org.lflang.TimeUnit; +import org.lflang.TimeValue; +import org.lflang.analyses.cast.AstUtils; +import org.lflang.analyses.cast.BuildAstParseTreeVisitor; +import org.lflang.analyses.cast.CAst; +import org.lflang.analyses.cast.CToUclidVisitor; +import org.lflang.analyses.cast.IfNormalFormAstVisitor; +import org.lflang.analyses.cast.VariablePrecedenceVisitor; +import org.lflang.analyses.statespace.StateSpaceDiagram; +import org.lflang.analyses.statespace.StateSpaceExplorer; +import org.lflang.analyses.statespace.StateSpaceNode; +import org.lflang.analyses.statespace.Tag; +import org.lflang.dsl.CLexer; +import org.lflang.dsl.CParser; +import org.lflang.dsl.CParser.BlockItemListContext; +import org.lflang.dsl.MTLLexer; +import org.lflang.dsl.MTLParser; +import org.lflang.dsl.MTLParser.MtlContext; +import org.lflang.generator.ActionInstance; +import org.lflang.generator.CodeBuilder; +import org.lflang.generator.GeneratorBase; +import org.lflang.generator.LFGeneratorContext; +import org.lflang.generator.NamedInstance; +import org.lflang.generator.PortInstance; +import org.lflang.generator.ReactionInstance; +import org.lflang.generator.ReactorInstance; +import org.lflang.generator.RuntimeRange; +import org.lflang.generator.SendRange; +import org.lflang.generator.StateVariableInstance; +import org.lflang.generator.TargetTypes; +import org.lflang.generator.TimerInstance; +import org.lflang.generator.TriggerInstance; +import org.lflang.lf.AttrParm; +import org.lflang.lf.Attribute; +import org.lflang.lf.Connection; +import org.lflang.lf.Expression; +import org.lflang.lf.Time; +import org.lflang.util.StringUtil; + +public class UclidGenerator extends GeneratorBase { + + //////////////////////////////////////////// + //// Public fields + // Data structures for storing info about the runtime instances. + public List reactorInstances = new ArrayList(); + public List reactionInstances = + new ArrayList(); + + // State variables in the system + public List stateVariables = new ArrayList(); + + // Triggers in the system + public List actionInstances = new ArrayList(); + public List inputInstances = new ArrayList(); + public List outputInstances = new ArrayList(); + public List portInstances = new ArrayList(); + public List timerInstances = new ArrayList(); + + // Joint lists of the lists above. + public List triggerInstances; // Triggers = ports + actions + timers + public List namedInstances; // Named instances = triggers + state variables + + // A list of paths to the uclid files generated + public List generatedFiles = new ArrayList<>(); + public Map expectations = new HashMap<>(); + + // The directory where the generated files are placed + public Path outputDir; + + // A runner for the generated Uclid files + public UclidRunner runner; + + // If true, use logical time-based semantics; + // otherwise, use event-based semantics, + // as described in Sirjani et. al (2020). + public boolean logicalTimeBased = false; + + //////////////////////////////////////////// + //// Protected fields + + // A list of MTL properties represented in Attributes. + protected List properties; + + /** The main place to put generated code. */ + protected CodeBuilder code = new CodeBuilder(); + + /** Strings from the property attribute */ + protected String name; + + protected String tactic; + protected String spec; // SMTL + protected String expect; + + /** + * The horizon (the total time interval required for evaluating an MTL property, which is derived + * from the MTL spec), the completeness threshold (CT) (the number of transitions required for + * evaluating the FOL spec in the trace), and the transpiled FOL spec. + */ + protected long horizon = 0; // in nanoseconds + + protected String FOLSpec = ""; + protected int CT = 0; + protected static final int CT_MAX_SUPPORTED = 100; + + // Constructor + public UclidGenerator(LFGeneratorContext context, List properties) { + super(context); + this.properties = properties; + this.runner = new UclidRunner(this); + } + + //////////////////////////////////////////////////////////// + //// Public methods + public void doGenerate(Resource resource, LFGeneratorContext context) { + + // FIXME: How much of doGenerate() from GeneratorBase is needed? + super.printInfo(context.getMode()); + ASTUtils.setMainName(context.getFileConfig().resource, context.getFileConfig().name); + // FIXME: Perform an analysis on the property and remove unrelevant components. + super.createMainInstantiation(); + + //////////////////////////////////////// + + // Create the main reactor instance if there is a main reactor. + createMainReactorInstance(); + + // Extract information from the named instances. + populateDataStructures(); + + // Create the src-gen directory + setupDirectories(); + + // Generate a Uclid model for each property. + for (Attribute prop : this.properties) { + this.name = + StringUtil.removeQuotes( + prop.getAttrParms().stream() + .filter(attr -> attr.getName().equals("name")) + .findFirst() + .get() + .getValue()); + this.tactic = + StringUtil.removeQuotes( + prop.getAttrParms().stream() + .filter(attr -> attr.getName().equals("tactic")) + .findFirst() + .get() + .getValue()); + this.spec = + StringUtil.removeQuotes( + prop.getAttrParms().stream() + .filter(attr -> attr.getName().equals("spec")) + .findFirst() + .get() + .getValue()); + + processMTLSpec(); + + Optional CTAttr = + prop.getAttrParms().stream().filter(attr -> attr.getName().equals("CT")).findFirst(); + if (CTAttr.isPresent()) { + this.CT = Integer.parseInt(CTAttr.get().getValue()); + } else { + computeCT(); + } + // For automating data collection, print the CT to stderr. + System.err.println("CT: " + this.CT); + if (this.CT > CT_MAX_SUPPORTED) { + System.out.println( + "ERROR: The maximum steps supported is " + + CT_MAX_SUPPORTED + + " but checking this property requires " + + this.CT + + " steps. " + + "This property will NOT be checked."); + continue; + } + + Optional ExpectAttr = + prop.getAttrParms().stream().filter(attr -> attr.getName().equals("expect")).findFirst(); + if (ExpectAttr.isPresent()) this.expect = ExpectAttr.get().getValue(); + + generateUclidFile(); + } + } + + //////////////////////////////////////////////////////////// + //// Protected methods + + /** Generate the Uclid model. */ + protected void generateUclidFile() { + try { + // Generate main.ucl and print to file + code = new CodeBuilder(); + Path file = this.outputDir.resolve(this.tactic + "_" + this.name + ".ucl"); + String filename = file.toString(); + generateUclidCode(); + code.writeToFile(filename); + this.generatedFiles.add(file); + if (this.expect != null) this.expectations.put(file, this.expect); + } catch (IOException e) { + Exceptions.sneakyThrow(e); + } + } + + /** The main function that generates Uclid code. */ + protected void generateUclidCode() { + code.pr( + String.join( + "\n", + "/*******************************", + " * Auto-generated UCLID5 model *", + " ******************************/")); + + code.pr("module main {"); + code.indent(); + + // Timing semantics + generateTimingSemantics(); + + // Trace definition + generateTraceDefinition(); + + // Reaction IDs and state variables + generateReactionIdsAndStateVars(); + + // Reactor semantics + generateReactorSemantics(); + + // Triggers and reactions + generateTriggersAndReactions(); + + // Initial Condition + generateInitialConditions(); + + // Reaction bodies + generateReactionAxioms(); + + // Properties + generateProperty(); + + // Control block + generateControlBlock(); + + code.unindent(); + code.pr("}"); + } + + /** Macros for timing semantics */ + protected void generateTimingSemantics() { + code.pr( + String.join( + "\n", + "/*******************************", + " * Time and Related Operations *", + " ******************************/", + "type timestamp_t = integer; // The unit is nanoseconds", + "type microstep_t = integer;", + "type tag_t = {", + " timestamp_t,", + " microstep_t", + "};", + "type interval_t = tag_t;", + "", + "// Projection macros", + "define pi1(t : tag_t) : timestamp_t = t._1; // Get timestamp from tag", + "define pi2(t : tag_t) : microstep_t = t._2; // Get microstep from tag", + "", + "// Interval constructor", + "define zero() : interval_t", + "= {0, 0};", + "define startup() : interval_t", + "= zero();", + "define mstep() : interval_t", + "= {0, 1};", + "define nsec(t : integer) : interval_t", + "= {t, 0};", + "define usec(t : integer) : interval_t", + "= {t * 1000, 0};", + "define msec(t : integer) : interval_t", + "= {t * 1000000, 0};", + "define sec(t : integer) : interval_t", + "= {t * 1000000000, 0};", + "define inf() : interval_t", + "= {-1, 0};", + "", + "// Helper function", + "define isInf(i : interval_t) : boolean", + "= pi1(i) < 0;", + "", + "// Tag comparison", + "define tag_later(t1 : tag_t, t2 : tag_t) : boolean", + "= pi1(t1) > pi1(t2)", + " || (pi1(t1) == pi1(t2) && pi2(t1) > pi2(t2))", + " || (isInf(t1) && !isInf(t2));", + "", + "define tag_same(t1 : tag_t, t2 : tag_t) : boolean", + "= t1 == t2;", + "", + "define tag_earlier(t1 : tag_t, t2 : tag_t) : boolean", + "= pi1(t1) < pi1(t2)", + " || (pi1(t1) == pi1(t2) && pi2(t1) < pi2(t2))", + " || (!isInf(t1) && isInf(t2));", + "", + "// Used for incrementing a tag through an action", + "define tag_schedule(t : tag_t, i : integer) : tag_t", + "= if (i == 0) then { pi1(t), pi2(t)+1 } else { pi1(t)+i, 0 };", + "", + "// Used for incrementing a tag along a connection", + "define tag_delay(t : tag_t, i : integer) : tag_t", + "= if (i == 0) then { pi1(t), pi2(t) } else { pi1(t)+i, 0 };", + "", + "// Only consider timestamp for now.", + "define tag_diff(t1, t2: tag_t) : interval_t", + "= if (!isInf(t1) && !isInf(t2))", + " then { pi1(t1) - pi1(t2), pi2(t1) - pi2(t2) }", + " else inf();", + "\n")); + } + + /** Macros, type definitions, and variable declarations for trace (path) */ + protected void generateTraceDefinition() { + //// Why do we have a padding at the end of the trace? + // + // To handle bounded traces for a potentially unbounded execution + // (due to, for example, timers or systems forming a loop), + // we need to let certain axioms "terminate" the execution and + // put any "spilled-over" states in the trace padding. + // + // For example, an axiom could say "if a reaction is triggered + // at step i, it schedules an action that appears 1 sec later + // in some future step." Assuming our completeness threshold is 10, + // this axiom can block the model (i.e. returns TRIVIALLY true) + // at step 10 because there are not any more steps in the bounded trace. + // To avoid this, a padding of the size of the number of reactions + // is added to the trace. In addition, an antecedent of the form + // "i >= START && i <= END" is added to all the axioms. The padding + // will store all the spilled-over states in order to prevent + // model blocking. + int traceEndIndex = this.CT + this.reactionInstances.size(); + + // Define various constants. + code.pr( + String.join( + "\n", + "/********************", + " * Trace Definition *", + " *******************/", + "const START : integer = 0; // The start index of the trace.", + "const END : integer = " + + String.valueOf(this.CT) + + "; // The end index of the trace (without padding)", + "const END_TRACE : integer = " + + String.valueOf(traceEndIndex) + + "; // The end index of the trace with padding", + "\n")); + + // Define trace indices as a group, + // so that we can use finite quantifiers. + code.pr("group indices : integer = {"); + code.indent(); + String indices = ""; + for (int i = 0; i <= traceEndIndex; i++) { + indices += String.valueOf(i) + (i == traceEndIndex ? "" : ", "); + } + code.pr(indices); + code.unindent(); + code.pr("};\n\n"); + + // Define step, event, and trace types. + code.pr( + String.join( + "\n", + "// Define step and event types.", + "type step_t = integer;", + "type event_t = { rxn_t, tag_t, state_t, trigger_t, sched_t, payload_t };", + "", + "// Create a bounded trace with length " + String.valueOf(this.CT))); + code.pr("// Potentially unbounded trace, we bound this later."); + code.pr("type trace_t = [integer]event_t;"); + + // Declare start time and trace. + code.pr( + String.join( + "\n", + "// Declare start time.", + "var start_time : timestamp_t;", + "", + "// Declare trace.", + "var trace : trace_t;")); + + // Start generating helper macros. + code.pr(String.join("\n", "/*****************", " * Helper Macros *", " ****************/")); + + // Define a getter for uclid arrays. + String initialReactions = ""; + if (this.reactionInstances.size() > 0) { + initialReactions = "false, ".repeat(this.reactionInstances.size()); + initialReactions = initialReactions.substring(0, initialReactions.length() - 2); + } else { + // Initialize a dummy variable just to make the code compile. + initialReactions = "false"; + } + initialReactions = "{" + initialReactions + "}"; + String initialStates = ""; + if (this.namedInstances.size() > 0) { + initialStates = "0, ".repeat(this.namedInstances.size()); + initialStates = initialStates.substring(0, initialStates.length() - 2); + } else { + // Initialize a dummy variable just to make the code compile. + initialStates = "0"; + } + String initialTriggerPresence = ""; + if (this.triggerInstances.size() > 0) { + initialTriggerPresence = "false, ".repeat(this.triggerInstances.size()); + initialTriggerPresence = + initialTriggerPresence.substring(0, initialTriggerPresence.length() - 2); + } else { + // Initialize a dummy variable just to make the code compile. + initialTriggerPresence = "false"; + } + String initialActionsScheduled = ""; + if (this.actionInstances.size() > 0) { + initialActionsScheduled = "false, ".repeat(this.actionInstances.size()); + initialActionsScheduled = + initialActionsScheduled.substring(0, initialActionsScheduled.length() - 2); + } else { + // Initialize a dummy variable just to make the code compile. + initialActionsScheduled = "false"; + } + String initialActionsScheduledPayload = ""; + if (this.actionInstances.size() > 0) { + initialActionsScheduledPayload = "0, ".repeat(this.actionInstances.size()); + initialActionsScheduledPayload = + initialActionsScheduledPayload.substring(0, initialActionsScheduledPayload.length() - 2); + } else { + // Initialize a dummy variable just to make the code compile. + initialActionsScheduledPayload = "0"; + } + code.pr("// Helper macro that returns an element based on index."); + code.pr("define get(tr : trace_t, i : step_t) : event_t ="); + code.pr("if (i >= START || i <= END_TRACE) then tr[i] else"); + code.pr( + "{ " + + initialReactions + + ", inf(), { " + + initialStates + + " }, { " + + initialTriggerPresence + + " }, {" + + initialActionsScheduled + + " }, {" + + initialActionsScheduledPayload + + "} };"); + + // Define an event getter from the trace. + code.pr( + String.join( + "\n", + "define elem(i : step_t) : event_t", + "= get(trace, i);", + "", + "// projection macros", + "define rxn (i : step_t) : rxn_t = elem(i)._1;", + "define g (i : step_t) : tag_t = elem(i)._2;", + "define s (i : step_t) : state_t = elem(i)._3;", + "define t (i : step_t) : trigger_t = elem(i)._4;", + "define d (i : step_t) : sched_t = elem(i)._5;", + "define pl (i : step_t) : payload_t = elem(i)._6;", + "define isNULL (i : step_t) : boolean = rxn(i) == " + initialReactions + ";")); + } + + /** Type definitions for runtime reaction Ids and state variables */ + protected void generateReactionIdsAndStateVars() { + // Encode the components and the logical delays + // present in a reactor system. + code.pr( + String.join( + "\n", + "/**********************************", + " * Reaction IDs & State Variables *", + " *********************************/", + "", + "//////////////////////////", + "// Application Specific")); + + // Enumerate over all reactions. + code.pr(String.join("\n", "// Reactions", "type rxn_t = {")); + code.indent(); + for (var i = 0; i < this.reactionInstances.size(); i++) { + // Print a list of reaction IDs. + // Add a comma if not last. + code.pr( + "boolean" + + ((i == this.reactionInstances.size() - 1) ? "" : ",") + + "\t// " + + this.reactionInstances.get(i)); + } + code.unindent(); + code.pr("};\n"); + + // Generate projection macros. + code.pr("// Reaction projection macros"); + for (var i = 0; i < this.reactionInstances.size(); i++) { + code.pr( + "define " + + this.reactionInstances.get(i).getReaction().getFullNameWithJoiner("_") + + "(n : rxn_t) : boolean = n._" + + (i + 1) + + ";"); + } + code.pr("\n"); // Newline + + // State variables and triggers + // FIXME: expand to data types other than integer + code.pr("// State variables and triggers"); + code.pr("type state_t = {"); + code.indent(); + if (this.namedInstances.size() > 0) { + for (var i = 0; i < this.namedInstances.size(); i++) { + code.pr( + "integer" + + ((i == this.namedInstances.size() - 1) ? "" : ",") + + "\t// " + + this.namedInstances.get(i)); + } + } else { + code.pr( + String.join( + "\n", + "// There are no ports or state variables.", + "// Insert a dummy integer to make the model compile.", + "integer")); + } + code.unindent(); + code.pr("};"); + code.pr("// State variable projection macros"); + for (var i = 0; i < this.namedInstances.size(); i++) { + code.pr( + "define " + + this.namedInstances.get(i).getFullNameWithJoiner("_") + + "(s : state_t) : integer = s._" + + (i + 1) + + ";"); + } + code.pr("\n"); // Newline + + // A boolean tuple indicating whether triggers are present. + code.pr("// A boolean tuple indicating whether triggers are present."); + code.pr("type trigger_t = {"); + code.indent(); + if (this.triggerInstances.size() > 0) { + for (var i = 0; i < this.triggerInstances.size(); i++) { + code.pr( + "boolean" + + ((i == this.triggerInstances.size() - 1) ? "" : ",") + + "\t// " + + this.triggerInstances.get(i)); + } + } else { + code.pr( + String.join( + "\n", + "// There are no triggers.", + "// Insert a dummy boolean to make the model compile.", + "boolean")); + } + code.unindent(); + code.pr("};"); + code.pr("// Trigger presence projection macros"); + for (var i = 0; i < this.triggerInstances.size(); i++) { + code.pr( + "define " + + this.triggerInstances.get(i).getFullNameWithJoiner("_") + + "_is_present" + + "(t : trigger_t) : boolean = t._" + + (i + 1) + + ";"); + } + + // A boolean tuple indicating whether actions are scheduled by reactions + // at the instant when they are triggered. + code.pr( + String.join( + "\n", + "// A boolean tuple indicating whether actions are scheduled by reactions", + "// at the instant when they are triggered.")); + code.pr("type sched_t = {"); + code.indent(); + if (this.actionInstances.size() > 0) { + for (var i = 0; i < this.actionInstances.size(); i++) { + code.pr( + "boolean" + + ((i == this.actionInstances.size() - 1) ? "" : ",") + + "\t// " + + this.actionInstances.get(i)); + } + } else { + code.pr( + String.join( + "\n", + "// There are no actions.", + "// Insert a dummy boolean to make the model compile.", + "boolean")); + } + code.unindent(); + code.pr("};"); + code.pr("// Projection macros for action schedule flag"); + for (var i = 0; i < this.actionInstances.size(); i++) { + code.pr( + "define " + + this.actionInstances.get(i).getFullNameWithJoiner("_") + + "_scheduled" + + "(d : sched_t) : boolean = d._" + + (i + 1) + + ";"); + } + + // A integer tuple indicating the integer payloads scheduled by reactions + // at the instant when they are triggered. + code.pr( + String.join( + "\n", + "// A integer tuple indicating the integer payloads scheduled by reactions", + "// at the instant when they are triggered.")); + code.pr("type payload_t = {"); + code.indent(); + if (this.actionInstances.size() > 0) { + for (var i = 0; i < this.actionInstances.size(); i++) { + code.pr( + "integer" + + ((i == this.actionInstances.size() - 1) ? "" : ",") + + "\t// " + + this.actionInstances.get(i)); + } + } else { + code.pr( + String.join( + "\n", + "// There are no actions.", + "// Insert a dummy integer to make the model compile.", + "integer")); + } + code.unindent(); + code.pr("};"); + code.pr("// Projection macros for scheduled payloads"); + for (var i = 0; i < this.actionInstances.size(); i++) { + code.pr( + "define " + + this.actionInstances.get(i).getFullNameWithJoiner("_") + + "_scheduled_payload" + + "(payload : payload_t) : integer = payload._" + + (i + 1) + + ";"); + } + } + + /** Axioms for reactor semantics */ + protected void generateReactorSemantics() { + code.pr( + String.join( + "\n", + "/*********************", + " * Reactor Semantics *", + " *********************/", + "")); + + // Non-federated "happened-before" + code.pr( + String.join( + "\n", + "// Non-federated \"happened-before\"", + "define hb(e1, e2 : event_t) : boolean", + "= tag_earlier(e1._2, e2._2)")); + if (!this.logicalTimeBased) { + code.indent(); + // Get happen-before relation between two reactions. + code.pr("|| (tag_same(e1._2, e2._2) && ( false"); + // Iterate over reactions based on upstream/downstream relations. + for (var upstreamRuntime : this.reactionInstances) { + var downstreamReactions = upstreamRuntime.getReaction().dependentReactions(); + for (var downstream : downstreamReactions) { + // If the downstream reaction and the upstream + // reaction are in the same reactor, skip, since + // they will be accounted for in the for loop below. + if (downstream.getParent().equals(upstreamRuntime.getReaction().getParent())) continue; + for (var downstreamRuntime : downstream.getRuntimeInstances()) { + code.pr( + "|| (" + + upstreamRuntime.getReaction().getFullNameWithJoiner("_") + + "(e1._1)" + + " && " + + downstreamRuntime.getReaction().getFullNameWithJoiner("_") + + "(e2._1)" + + ")"); + } + } + } + // Iterate over reactions based on priorities. + for (var reactor : this.reactorInstances) { + for (int i = 0; i < reactor.reactions.size(); i++) { + for (int j = i + 1; j < reactor.reactions.size(); j++) { + code.pr( + "|| (" + + reactor.reactions.get(i).getFullNameWithJoiner("_") + + "(e1._1)" + + " && " + + reactor.reactions.get(j).getFullNameWithJoiner("_") + + "(e2._1)" + + ")"); + } + } + } + code.unindent(); + code.pr("))"); + } + code.pr(";"); + + code.pr( + String.join( + "\n", + "/** transition relation **/", + "// transition relation constrains future states", + "// based on previous states.", + "", + "// Events are ordered by \"happened-before\" relation.", + "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END_TRACE) ==>" + + " (finite_forall (j : integer) in indices ::", + " (j >= START && j <= END_TRACE) ==> (hb(elem(i), elem(j)) ==> i < j)));", + "", + "// Tags should be non-negative.", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END_TRACE)", + " ==> pi1(g(i)) >= 0);", + "", + "// Microsteps should be non-negative.", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END_TRACE)", + " ==> pi2(g(i)) >= 0);", + "", + "// Begin the frame at the start time specified.", + "define start_frame(i : step_t) : boolean =", + " (tag_same(g(i), {start_time, 0}) || tag_later(g(i), {start_time, 0}));", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END_TRACE)", + " ==> start_frame(i));", + "", + "// NULL events should appear in the suffix, except for START.", + "axiom(finite_forall (j : integer) in indices :: (j > START && j <= END_TRACE) ==> (", + " !isNULL(j)) ==> ", + " (finite_forall (i : integer) in indices :: (i > START && i < j) ==>" + + " (!isNULL(i))));", + "")); + + //// Axioms for the event-based semantics + if (!this.logicalTimeBased) { + code.pr("//// Axioms for the event-based semantics"); + + // the same event can only trigger once in a logical instant + code.pr( + String.join( + "\n", + "// the same event can only trigger once in a logical instant", + "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END) ==>" + + " (finite_forall (j : integer) in indices ::", + " (j >= START && j <= END) ==> ((rxn(i) == rxn(j) && i != j)", + " ==> !tag_same(g(i), g(j)))));", + "")); + + // Only one reaction gets triggered at a time. + ArrayList reactionsStatus = new ArrayList<>(); + for (int i = 0; i < this.reactionInstances.size(); i++) reactionsStatus.add("false"); + code.pr( + String.join( + "\n", + "// Only one reaction gets triggered at a time.", + "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> (", + " isNULL(i)")); + code.indent(); + for (int i = 0; i < this.reactionInstances.size(); i++) { + String[] li = reactionsStatus.toArray(String[]::new); + li[i] = "true"; + code.pr("|| rxn(i) == " + "{" + String.join(", ", li) + "}"); + } + code.unindent(); + code.pr("));"); + } + } + + /** Axioms for the trigger mechanism */ + protected void generateTriggersAndReactions() { + code.pr(String.join("\n", "/***************", " * Connections *", " ***************/")); + // FIXME: Support banks and multiports. Figure out how to work with ranges. + // Iterate over all the ports. Generate an axiom for each connection + // (i.e. a pair of input-output ports). + // A "range" holds the connection information. + // See connectPortInstances() in ReactorInstance.java for more details. + for (var port : this.portInstances) { + for (SendRange range : port.getDependentPorts()) { + PortInstance source = range.instance; + Connection connection = range.connection; + List> destinations = range.destinations; + + // Extract delay value + long delay = 0; + if (connection.getDelay() != null) { + // Somehow delay is an Expression, + // which makes it hard to convert to nanoseconds. + Expression delayExpr = connection.getDelay(); + if (delayExpr instanceof Time) { + long interval = ((Time) delayExpr).getInterval(); + String unit = ((Time) delayExpr).getUnit(); + TimeValue timeValue = new TimeValue(interval, TimeUnit.fromName(unit)); + delay = timeValue.toNanoSeconds(); + } + } + + for (var portRange : destinations) { + var destination = portRange.instance; + + // We have extracted the source, destination, and connection AST node. + // Now we are ready to generate an axiom for this triple. + code.pr( + "// " + + source.getFullNameWithJoiner("_") + + " " + + (connection.isPhysical() ? "~>" : "->") + + " " + + destination.getFullNameWithJoiner("_")); + code.pr( + String.join( + "\n", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", + "// If " + + source.getFullNameWithJoiner("_") + + " is present, then " + + destination.getFullNameWithJoiner("_") + + " will be present.", + "// with the same value after some fixed delay of " + delay + " nanoseconds.", + "(" + source.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> ((", + " finite_exists (j : integer) in indices :: j > i && j <= END", + " && " + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(j))", + " && " + + destination.getFullNameWithJoiner("_") + + "(s(j)) == " + + source.getFullNameWithJoiner("_") + + "(s(i))", + connection.isPhysical() ? "" : "&& g(j) == tag_delay(g(i), " + delay + ")", + ")", + ")) // Closes (" + + source.getFullNameWithJoiner("_") + + "_is_present" + + "(t(i)) ==> ((.", + //// Separator + "// If " + + destination.getFullNameWithJoiner("_") + + " is present, there exists an " + + source.getFullNameWithJoiner("_") + + " in prior steps.", + "// This additional term establishes a one-to-one relationship between two ports'" + + " signals.", + "&& (" + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", + " finite_exists (j : integer) in indices :: j >= START && j < i", + " && " + source.getFullNameWithJoiner("_") + "_is_present" + "(t(j))", + connection.isPhysical() ? "" : " && g(i) == tag_delay(g(j), " + delay + ")", + ")) // Closes the one-to-one relationship.", + "));")); + + // If destination is not present, then its value resets to 0. + code.pr( + String.join( + "\n", + "// If " + + destination.getFullNameWithJoiner("_") + + " is not present, then its value resets to 0.", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END &&" + + " !isNULL(i)) ==> (", + " (!" + + destination.getFullNameWithJoiner("_") + + "_is_present" + + "(t(i)) ==> (", + " " + destination.getFullNameWithJoiner("_") + "(s(i)) == 0", + " ))", + "));")); + } + } + } + + if (this.actionInstances.size() > 0) { + code.pr(String.join("\n", "/***********", " * Actions *", " ***********/")); + for (var action : this.actionInstances) { + Set dependsOnReactions = action.getDependsOnReactions(); + String comment = + "If " + + action.getFullNameWithJoiner("_") + + " is present, these reactions could schedule it: "; + String triggerStr = ""; + for (var reaction : dependsOnReactions) { + comment += reaction.getFullNameWithJoiner("_") + ", "; + triggerStr += + String.join( + "\n", + "// " + reaction.getFullNameWithJoiner("_"), + // OR because only any present trigger can trigger the reaction. + "|| (" + action.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", + " finite_exists (j : integer) in indices :: j >= START && j < i", + " && " + reaction.getFullNameWithJoiner("_") + "(rxn(j))", + " && g(i) == tag_schedule(g(j), " + action.getMinDelay().toNanoSeconds() + ")", + " && " + action.getFullNameWithJoiner("_") + "_scheduled" + "(d(j))", + " && " + + action.getFullNameWithJoiner("_") + + "(s(i))" + + " == " + + action.getFullNameWithJoiner("_") + + "_scheduled_payload" + + "(pl(j))", + "))"); + } + + // After populating the string segments, + // print the generated code string. + code.pr( + String.join( + "\n", + "// " + comment, + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (" + + " false", + triggerStr, + "));")); + + // If the action is not present, then its value resets to 0. + code.pr( + String.join( + "\n", + "// If " + + action.getFullNameWithJoiner("_") + + " is not present, then its value resets to 0.", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END &&" + + " !isNULL(i)) ==> (", + " (!" + action.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", + " " + action.getFullNameWithJoiner("_") + "(s(i)) == 0", + " ))", + "));")); + } + } + + if (this.timerInstances.size() > 0) { + code.pr(String.join("\n", "/**********", " * Timers *", " **********/")); + /** + * The timer axioms take the following form: + * + *

// An initial firing at {offset, 0} axiom( ((g(END)._1 >= 500000000) ==> ( finite_exists + * (j : integer) in indices :: (j > START && j <= END) && Timer_t_is_present(t(j)) && + * tag_same(g(j), {500000000, 0}) )) && ((g(END)._1 < 500000000) ==> ( finite_forall (i : + * integer) in indices :: (i > START && i <= END) ==> (!isNULL(i)) )) ); // Schedule + * subsequent firings. axiom( finite_forall (i : integer) in indices :: (i >= START && i <= + * END) ==> ( Timer_t_is_present(t(i)) ==> ( ( finite_exists (j : integer) in indices :: (j >= + * START && j > i) && Timer_t_is_present(t(j)) && (g(j) == tag_schedule(g(i), 1000000000)) ) ) + * ) ); // All firings must be evenly spaced out. axiom( finite_forall (i : integer) in + * indices :: (i >= START && i <= END) ==> ( Timer_t_is_present(t(i)) ==> ( // Timestamp must + * be offset + n * period ( exists (n : integer) :: ( n >= 0 && g(i)._1 == 500000000 + n * + * 1000000000 ) ) // Microstep must be 0 && ( g(i)._2 == 0 ) ) ) ); + */ + for (var timer : this.timerInstances) { + long offset = timer.getOffset().toNanoSeconds(); + long period = timer.getPeriod().toNanoSeconds(); + + code.pr("//// Axioms for " + timer.getFullName()); + + // An initial firing at {offset, 0} + code.pr( + String.join( + "\n", + "// " + timer.getFullName() + ": an initial firing at (" + offset + ", 0)", + "axiom(", + " ((pi1(g(END)) >= " + offset + ") ==> (", + " finite_exists (j : integer) in indices :: (j > START && j <= END)", + " && " + timer.getFullNameWithJoiner("_") + "_is_present(t(j))", + " && tag_same(g(j), {" + offset + ", 0})", + " ))", + " && ((pi1(g(END)) < " + offset + ") ==> (", + " finite_forall (i : integer) in indices :: (i > START && i <= END)", + " ==> (!isNULL(i))", + " ))", + ");")); + + // Schedule subsequent firings. + code.pr( + String.join( + "\n", + "// " + + timer.getFullName() + + ": schedule subsequent firings every " + + period + + " ns", + "axiom(", + " finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> (", + " " + timer.getFullNameWithJoiner("_") + "_is_present(t(i)) ==> (", + " (", + " finite_exists (j : integer) in indices :: (j >= START && j > i)", + " && " + timer.getFullNameWithJoiner("_") + "_is_present(t(j))", + " && (g(j) == tag_schedule(g(i), " + period + "))", + " )", + " )", + " )", + ");")); + + // All firings must be evenly spaced out. + code.pr( + String.join( + "\n", + "// " + timer.getFullName() + ": all firings must be evenly spaced out.", + "axiom(", + " finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> (", + " " + timer.getFullNameWithJoiner("_") + "_is_present(t(i)) ==> (", + " // Timestamp must be offset + n * period.", + " (", + " exists (n : integer) :: (", + " n >= 0 &&", + " pi1(g(i)) == " + offset + " + n * " + period, + " )", + " )", + " // Microstep must be 0.", + " && (pi2(g(i)) == 0)", + " )", + " )", + ");")); + } + } + + code.pr( + String.join( + "\n", + "/********************************", + " * Reactions and Their Triggers *", + " ********************************/")); + // Iterate over all reactions, generate conditions for them + // to be triggered. + outerLoop: + for (ReactionInstance.Runtime reaction : this.reactionInstances) { + String str = + String.join( + "\n", + "// " + + reaction.getReaction().getFullNameWithJoiner("_") + + " is invoked when any of it triggers are present.", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END_TRACE) ==>" + + " ((", + " false"); + + // Iterate over the triggers of the reaction. + for (TriggerInstance trigger : reaction.getReaction().triggers) { + String triggerPresentStr = ""; + + if (trigger.isStartup()) { + // FIXME: Treat startup as a variable. + // triggerPresentStr = "g(i) == zero()"; + + // Handle startup. + code.pr( + String.join( + "\n", + "// If startup is within frame (and all variables have default values),", + "// " + reaction.getReaction().getFullNameWithJoiner("_") + " must be invoked.", + "axiom(", + " ((start_time == 0) ==> (", + " finite_exists (i : integer) in indices :: i > START && i <= END", + " && " + + reaction.getReaction().getFullNameWithJoiner("_") + + "(rxn(i))" + + " && tag_same(g(i), zero())", + " && !(", + " finite_exists (j : integer) in indices :: j > START && j <= END", + " && " + + reaction.getReaction().getFullNameWithJoiner("_") + + "(rxn(j))", + " && j != i", + " )", + " ))", + ");")); + continue outerLoop; + } else if (trigger.isShutdown()) { + // FIXME: For a future version. + System.out.println("Not implemented!"); + } else { + // If the trigger is a port/action/timer. + triggerPresentStr = trigger.getFullNameWithJoiner("_") + "_is_present" + "(t(i))"; + } + + // Check if the trigger triggers other reactions. + // If so, we need to assert in the axioms that + // the other reactions are excluded (not invoked), + // to preserve the interleaving semantics. + String exclusion = ""; + for (var instance : trigger.getDependentReactions()) { + for (var runtime : ((ReactionInstance) instance).getRuntimeInstances()) { + if (runtime == reaction) continue; // Skip the current reaction. + exclusion += " && !" + runtime.getReaction().getFullNameWithJoiner("_") + "(rxn(i))"; + } + } + + // code.pr("|| (" + triggerPresentStr + exclusion + ")"); + str += "\n|| (" + triggerPresentStr + exclusion + ")"; + } + + // If any of the above trigger is present, then trigger the reaction. + str += "\n) <==> (" + reaction.getReaction().getFullNameWithJoiner("_") + "(rxn(i))" + ")));"; + code.pr(str); + } + } + + /** Macros for initial conditions */ + protected void generateInitialConditions() { + code.pr( + String.join( + "\n", + "/*********************", + " * Initial Condition *", + " *********************/", + "define initial_condition() : boolean", + "= start_time == 0", + " && isNULL(0)", + " && g(0) == {0, 0}")); + code.indent(); + for (var v : this.stateVariables) { + code.pr("&& " + v.getFullNameWithJoiner("_") + "(s(0))" + " == " + "0"); + } + for (var t : this.triggerInstances) { + code.pr("&& " + t.getFullNameWithJoiner("_") + "(s(0))" + " == " + "0"); + } + for (var t : this.triggerInstances) { + code.pr("&& !" + t.getFullNameWithJoiner("_") + "_is_present" + "(t(0))"); + } + for (var d : this.actionInstances) { + code.pr("&& !" + d.getFullNameWithJoiner("_") + "_scheduled" + "(d(0))"); + } + code.unindent(); + code.pr(";\n"); + } + + /** Lift reaction bodies into Uclid axioms. */ + protected void generateReactionAxioms() { + code.pr(String.join("\n", "/*************", " * Reactions *", " *************/")); + + for (ReactionInstance.Runtime reaction : this.reactionInstances) { + String body = reaction.getReaction().getDefinition().getCode().getBody(); + // System.out.println("DEBUG: Printing reaction body of " + reaction); + // System.out.println(body); + + // Generate a parse tree. + CLexer lexer = new CLexer(CharStreams.fromString(body)); + CommonTokenStream tokens = new CommonTokenStream(lexer); + CParser parser = new CParser(tokens); + BlockItemListContext parseTree = parser.blockItemList(); + + // Build an AST. + BuildAstParseTreeVisitor buildAstVisitor = new BuildAstParseTreeVisitor(); + CAst.AstNode ast = buildAstVisitor.visitBlockItemList(parseTree); + + // VariablePrecedenceVisitor + VariablePrecedenceVisitor precVisitor = new VariablePrecedenceVisitor(); + precVisitor.visit(ast); + + // Convert the AST to If Normal Form (INF). + IfNormalFormAstVisitor infVisitor = new IfNormalFormAstVisitor(); + infVisitor.visit(ast, new ArrayList()); + CAst.StatementSequenceNode inf = infVisitor.INF; + + // For the variables that are USED inside this reaction, extract the conditions + // for setting them, and take the negation of their conjunction + // to get the condition for maintaining their values. + List unusedStates = new ArrayList<>(this.stateVariables); + List unusedOutputs = new ArrayList<>(this.outputInstances); + List unusedActions = new ArrayList<>(this.actionInstances); + HashMap> defaultBehaviorConditions = new HashMap<>(); + for (CAst.AstNode node : inf.children) { + CAst.IfBlockNode ifBlockNode = (CAst.IfBlockNode) node; + // Under the INF form, a C statement is + // the THEN branch of an IF block. + CAst.AstNode stmt = ((CAst.IfBodyNode) ifBlockNode.right).left; + NamedInstance instance = null; + // Match stmt with different cases + if ((stmt instanceof CAst.AssignmentNode + && ((CAst.AssignmentNode) stmt).left instanceof CAst.StateVarNode)) { + CAst.StateVarNode n = (CAst.StateVarNode) ((CAst.AssignmentNode) stmt).left; + instance = + reaction.getReaction().getParent().states.stream() + .filter(s -> s.getName().equals(n.name)) + .findFirst() + .get(); + unusedStates.remove(instance); + } else if (stmt instanceof CAst.SetPortNode) { + CAst.SetPortNode n = (CAst.SetPortNode) stmt; + String name = ((CAst.VariableNode) n.left).name; + instance = + reaction.getReaction().getParent().outputs.stream() + .filter(s -> s.getName().equals(name)) + .findFirst() + .get(); + unusedOutputs.remove(instance); + } else if (stmt instanceof CAst.ScheduleActionNode) { + CAst.ScheduleActionNode n = (CAst.ScheduleActionNode) stmt; + String name = ((CAst.VariableNode) n.children.get(0)).name; + instance = + reaction.getReaction().getParent().actions.stream() + .filter(s -> s.getName().equals(name)) + .findFirst() + .get(); + unusedActions.remove(instance); + } else if (stmt instanceof CAst.ScheduleActionIntNode) { + CAst.ScheduleActionIntNode n = (CAst.ScheduleActionIntNode) stmt; + String name = ((CAst.VariableNode) n.children.get(0)).name; + instance = + reaction.getReaction().getParent().actions.stream() + .filter(s -> s.getName().equals(name)) + .findFirst() + .get(); + unusedStates.remove(instance); + unusedActions.remove(instance); + } else continue; + // Create a new entry in the list if there isn't one yet. + if (defaultBehaviorConditions.get(instance) == null) { + defaultBehaviorConditions.put(instance, new ArrayList()); + } + defaultBehaviorConditions.get(instance).add(ifBlockNode.left); + // System.out.println("DEBUG: Added a reset condition: " + ifBlockNode.left); + } + + // Generate Uclid axiom for the C AST. + CToUclidVisitor c2uVisitor = new CToUclidVisitor(this, reaction); + String axiom = c2uVisitor.visit(inf); + code.pr( + String.join( + "\n", + "// Reaction body of " + reaction, + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (", + " (" + reaction.getReaction().getFullNameWithJoiner("_") + "(rxn(i))" + ")", + " ==> " + "(" + "(" + axiom + ")", + "&& " + "( " + "true")); + for (NamedInstance key : defaultBehaviorConditions.keySet()) { + CAst.AstNode disjunction = AstUtils.takeDisjunction(defaultBehaviorConditions.get(key)); + CAst.LogicalNotNode notNode = new CAst.LogicalNotNode(); + notNode.child = disjunction; + String resetCondition = c2uVisitor.visit(notNode); + + // Check for invalid reset conditions. + // If found, stop the execution. + // FIXME: A more systematic check is needed + // to ensure that the generated Uclid file + // is valid. + try { + if (resetCondition.contains("null")) { + throw new Exception("Null detected in a reset condition. Stop."); + } + } catch (Exception e) { + Exceptions.sneakyThrow(e); + } + code.pr("// Unused state variables and ports are reset by default."); + code.pr("&& " + "(" + "(" + resetCondition + ")" + " ==> " + "("); + if (key instanceof StateVariableInstance) { + StateVariableInstance n = (StateVariableInstance) key; + code.pr( + n.getFullNameWithJoiner("_") + + "(" + + "s" + + "(" + + "i" + + ")" + + ")" + + " == " + + n.getFullNameWithJoiner("_") + + "(" + + "s" + + "(" + + "i" + + "-1" + + ")" + + ")"); + } else if (key instanceof PortInstance) { + PortInstance n = (PortInstance) key; + code.pr( + "(" + + " true" + // Reset value + + "\n&& " + + n.getFullNameWithJoiner("_") + + "(" + + "s" + + "(" + + "i" + + ")" + + ")" + + " == " + + "0" // Default value + // Reset presence + + "\n&& " + + n.getFullNameWithJoiner("_") + + "_is_present" + + "(" + + "t" + + "(" + + "i" + + ")" + + ")" + + " == " + + "false" // default presence + + ")"); + } else if (key instanceof ActionInstance) { + ActionInstance n = (ActionInstance) key; + code.pr( + n.getFullNameWithJoiner("_") + + "_scheduled" + + "(" + + "d" + + "(" + + "i" + + ")" + + ")" + + " == " + + "false"); + } else { + System.out.println("Unreachable!"); + } + code.pr("))"); + } + + // For state variables and ports that are NOT used in this reaction, + // their values stay the same by default. + code.pr("// By default, the value of the variables used in this reaction stay the same."); + if (this.logicalTimeBased) { + // If all other reactions that can modify the SAME state variable + // are not triggered, then the state variable stay the same. + // + // FIXME: What if two reactions modifying the same state variable + // are triggered at the same time? + // How to use axioms to model reaction priority? + // The main difficulty of logical time based semantics is composing + // the effect of simultaneous reactions. + // + // A path way to implement it in the future: + // 1. For each variable, port, and action, determine a list of + // reactions that can modify/schedule it. + // 2. Reaction axioms should be generated wrt each reactor. + // For example, assuming a reactor with two input ports, + // each triggering a distinct reaction. The axioms will need + // to handle four cases: i. reaction 1 gets triggered and 2 + // does not; ii. reaction 2 gets triggered and 1 does not; + // iii. both reactions get triggered; iv. none of them get + // triggered. Since it is hard to specify in an independent way, + // due to reaction priorities, + // what happens when two reactions (modifying the same state var.) + // get triggered simultaneously, some combinatorial blowup will + // be incurred. In this example, four axioms (instead of two), + // each handling one case, seems needed. The good news is that + // axioms across reactors may be specified independently. + // For example, if there is another reactor of the same class, + // Only four more axioms need to be added (in total 2^2 + 2^2), + // instead of 16 axioms (2^4). + } else { + for (StateVariableInstance s : unusedStates) { + code.pr("&& (true ==> ("); + code.pr( + s.getFullNameWithJoiner("_") + + "(" + + "s" + + "(" + + "i" + + ")" + + ")" + + " == " + + s.getFullNameWithJoiner("_") + + "(" + + "s" + + "(" + + "i" + + "-1" + + ")" + + ")"); + code.pr("))"); + } + for (PortInstance p : unusedOutputs) { + code.pr("&& (true ==> ("); + code.pr( + "(" + + " true" + // Reset value + + "\n&& " + + p.getFullNameWithJoiner("_") + + "(" + + "s" + + "(" + + "i" + + ")" + + ")" + + " == " + + "0" // Default value + // Reset presence + + "\n&& " + + p.getFullNameWithJoiner("_") + + "_is_present" + + "(" + + "t" + + "(" + + "i" + + ")" + + ")" + + " == " + + false // default presence + + ")"); + code.pr("))"); + } + for (ActionInstance a : unusedActions) { + code.pr("&& (true ==> ("); + code.pr( + a.getFullNameWithJoiner("_") + + "_scheduled" + + "(" + + "d" + + "(" + + "i" + + ")" + + ")" + + " == " + + "false"); + code.pr("))"); + } + code.pr("))));"); + } + } + } + + protected void generateProperty() { + code.pr(String.join("\n", "/************", " * Property *", " ************/")); + + code.pr("// The FOL property translated from user-defined MTL property:"); + code.pr("// " + this.spec); + code.pr("define p(i : step_t) : boolean ="); + code.indent(); + code.pr(this.FOLSpec + ";"); + code.unindent(); + + if (this.tactic.equals("bmc")) { + code.pr( + String.join( + "\n", + "// BMC", + "property " + "bmc_" + this.name + " : " + "initial_condition() ==> p(0);")); + } else { + code.pr( + String.join( + "\n", + "// Induction: initiation step", + "property " + "initiation_" + this.name + " : " + "initial_condition() ==> p(0);", + "// Induction: consecution step", + "property " + "consecution_" + this.name + " : " + "p(0) ==> p(1);")); + } + } + + /** Uclid5 control block */ + protected void generateControlBlock() { + code.pr( + String.join( + "\n", + "control {", + " v = bmc(0);", + " check;", + " print_results;", + " v.print_cex_json;", + "}")); + } + + //////////////////////////////////////////////////////////// + //// 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 is done once because + // it is the same for all federates. + 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; + } + } + } + } + + private void setupDirectories() { + // Make sure the target directory exists. + Path modelGenDir = context.getFileConfig().getModelGenPath(); + this.outputDir = Paths.get(modelGenDir.toString()); + try { + Files.createDirectories(outputDir); + } catch (IOException e) { + Exceptions.sneakyThrow(e); + } + System.out.println("The models will be located in: " + outputDir); + } + + /** Populate the data structures. */ + private void populateDataStructures() { + // Populate lists of reactor/reaction instances, + // state variables, actions, ports, and timers. + populateLists(this.main); + + // Join actions, ports, and timers into a list of triggers. + this.triggerInstances = new ArrayList(this.actionInstances); + this.triggerInstances.addAll(portInstances); + this.triggerInstances.addAll(timerInstances); + + // Join state variables and triggers + this.namedInstances = new ArrayList(this.stateVariables); + namedInstances.addAll(this.triggerInstances); + } + + private void populateLists(ReactorInstance reactor) { + // Reactor and reaction instances + this.reactorInstances.add(reactor); + for (var reaction : reactor.reactions) { + this.reactionInstances.addAll(reaction.getRuntimeInstances()); + } + + // State variables, actions, ports, timers. + for (var state : reactor.states) { + this.stateVariables.add(state); + } + for (var action : reactor.actions) { + this.actionInstances.add(action); + } + for (var port : reactor.inputs) { + this.inputInstances.add(port); + this.portInstances.add(port); + } + for (var port : reactor.outputs) { + this.outputInstances.add(port); + this.portInstances.add(port); + } + for (var timer : reactor.timers) { + this.timerInstances.add(timer); + } + + // Recursion + for (var child : reactor.children) { + populateLists(child); + } + } + + /** + * Compute a completeness threadhold for each property by simulating a worst-case execution by + * traversing the reactor instance graph and building a state space diagram. + */ + private void computeCT() { + + StateSpaceExplorer explorer = new StateSpaceExplorer(this.main); + explorer.explore( + new Tag(this.horizon, 0, false), true // findLoop + ); + StateSpaceDiagram diagram = explorer.diagram; + diagram.display(); + + // Generate a dot file. + try { + CodeBuilder dot = diagram.generateDot(); + Path file = this.outputDir.resolve(this.tactic + "_" + this.name + ".dot"); + String filename = file.toString(); + dot.writeToFile(filename); + } catch (IOException e) { + Exceptions.sneakyThrow(e); + } + + //// Compute CT + if (!explorer.loopFound) { + if (this.logicalTimeBased) this.CT = diagram.nodeCount(); + else { + // FIXME: This could be much more efficient with + // a linkedlist implementation. We can go straight + // to the next node. + StateSpaceNode node = diagram.head; + this.CT = diagram.head.reactionsInvoked.size(); + while (node != diagram.tail) { + node = diagram.getDownstreamNode(node); + this.CT += node.reactionsInvoked.size(); + } + } + } + // Over-approximate CT by estimating the number of loop iterations required. + else { + // Subtract the non-periodic logical time + // interval from the total horizon. + long horizonRemained = Math.subtractExact(this.horizon, diagram.loopNode.tag.timestamp); + + // Check how many loop iteration is required + // to check the remaining horizon. + int loopIterations = 0; + if (diagram.loopPeriod == 0 && horizonRemained != 0) + Exceptions.sneakyThrow( + new Exception( + "ERROR: Zeno behavior detected while the horizon is non-zero. The program has no" + + " finite CT.")); + else if (diagram.loopPeriod == 0 && horizonRemained == 0) { + // Handle this edge case. + Exceptions.sneakyThrow(new Exception("Unhandled case: both the horizon and period are 0!")); + } else { + loopIterations = (int) Math.ceil((double) horizonRemained / diagram.loopPeriod); + } + + if (this.logicalTimeBased) { + /* + CT = steps required for the non-periodic part + + steps required for the periodic part + + this.CT = (diagram.loopNode.index + 1) + + (diagram.tail.index - diagram.loopNode.index + 1) * loopIterations; + + An overflow-safe version of the line above + */ + int t0 = Math.addExact(diagram.loopNode.index, 1); + int t1 = Math.subtractExact(diagram.tail.index, diagram.loopNode.index); + int t2 = Math.addExact(t1, 1); + int t3 = Math.multiplyExact(t2, loopIterations); + this.CT = Math.addExact(t0, t3); + + } else { + // Get the number of events before the loop starts. + // This stops right before the loopNode is encountered. + StateSpaceNode node = diagram.head; + int numReactionInvocationsBeforeLoop = 0; + while (node != diagram.loopNode) { + numReactionInvocationsBeforeLoop += node.reactionsInvoked.size(); + node = diagram.getDownstreamNode(node); + } + // Account for the loop node in numReactionInvocationsBeforeLoop. + numReactionInvocationsBeforeLoop += node.reactionsInvoked.size(); + + // Count the events from the loop node until + // loop node is reached again. + int numReactionInvocationsInsideLoop = 0; + do { + node = diagram.getDownstreamNode(node); + numReactionInvocationsInsideLoop += node.reactionsInvoked.size(); + } while (node != diagram.loopNode); + + /* + CT = steps required for the non-periodic part + + steps required for the periodic part + + this.CT = numReactionInvocationsBeforeLoop + + numReactionInvocationsInsideLoop * loopIterations; + + An overflow-safe version of the line above + */ + // System.out.println("DEBUG: numReactionInvocationsBeforeLoop: " + + // numReactionInvocationsBeforeLoop); + // System.out.println("DEBUG: numReactionInvocationsInsideLoop: " + + // numReactionInvocationsInsideLoop); + // System.out.println("DEBUG: loopIterations: " + loopIterations); + int t0 = Math.multiplyExact(numReactionInvocationsInsideLoop, loopIterations); + this.CT = Math.addExact(numReactionInvocationsBeforeLoop, t0); + } + } + } + + /** Process an MTL property. */ + private void processMTLSpec() { + MTLLexer lexer = new MTLLexer(CharStreams.fromString(this.spec)); + CommonTokenStream tokens = new CommonTokenStream(lexer); + MTLParser parser = new MTLParser(tokens); + MtlContext mtlCtx = parser.mtl(); + MTLVisitor visitor = new MTLVisitor(this.tactic); + + // The visitor transpiles the MTL into a Uclid axiom. + this.FOLSpec = visitor.visitMtl(mtlCtx, "i", 0, "0", 0); + this.horizon = visitor.getHorizon(); + } + + ///////////////////////////////////////////////// + //// Functions from generatorBase + + @Override + public Target getTarget() { + return Target.C; // Works with a C subset. + } + + @Override + public TargetTypes getTargetTypes() { + throw new UnsupportedOperationException("TODO: auto-generated method stub"); + } +} diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidRunner.java b/core/src/main/java/org/lflang/analyses/uclid/UclidRunner.java new file mode 100644 index 0000000000..4b34fd9cfa --- /dev/null +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidRunner.java @@ -0,0 +1,236 @@ +/** (EXPERIMENTAL) Runner for Uclid5 models. */ +package org.lflang.analyses.uclid; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.lflang.analyses.statespace.StateInfo; +import org.lflang.analyses.statespace.Tag; +import org.lflang.generator.GeneratorCommandFactory; +import org.lflang.util.LFCommand; + +public class UclidRunner { + + /** A list of paths to the generated files */ + List filePaths; + + /** The directory where the generated files are placed */ + public Path outputDir; + + /** A factory for compiler commands. */ + GeneratorCommandFactory commandFactory; + + /** A UclidGenerator instance */ + UclidGenerator generator; + + // Constructor + public UclidRunner(UclidGenerator generator) { + this.generator = generator; + this.commandFactory = + new GeneratorCommandFactory( + generator.context.getErrorReporter(), generator.context.getFileConfig()); + } + + /** Parse information from an SMT model for a step in the trace. */ + public StateInfo parseStateInfo(String smtStr) { + StateInfo info = new StateInfo(); + + // Check for any let bindings. + Pattern p = + Pattern.compile( + "\\(let \\(\\((a!\\d+) \\(_tuple_\\d+ ((.)+?)\\)\\)\\)(\\\\n" + + "|\\s)+\\(_tuple_\\d+ ((.|\\n" + + ")+)\\)"); + HashMap symbolTable = new HashMap<>(); + Matcher m = p.matcher(smtStr.strip()); + String itemized = ""; + if (m.find()) { + // FIXME: Handle multiple let bindings. + String symbol = m.group(1).strip(); + String value = m.group(2).strip(); + symbolTable.put(symbol, value); + itemized = m.group(5).strip(); + } else { + // Remove the outer tuple layer. + p = Pattern.compile("\\(_tuple_\\d+((.|\\n)*)\\)"); + m = p.matcher(smtStr.strip()); + m.find(); + itemized = m.group(1).strip(); + } + + // Process each sub-tuple by matching (_tuple_n ...) + // or matching a!n, where n is an integer. + p = Pattern.compile("(a!\\d+)|\\(_tuple_\\d+((\\s|\\\\n|\\d|\\(- \\d+\\)|true|false)+)\\)"); + m = p.matcher(itemized); + + // Reactions + m.find(); + String reactionsStr = ""; + // Found a let binding. + if (m.group(1) != null) { + reactionsStr = symbolTable.get(m.group(1)).strip(); + } + // The rest falls into group 2. + else { + reactionsStr = m.group(2).strip(); + } + String[] reactions = reactionsStr.split("\\s+"); + // Iterating over generator lists avoids accounting for + // the single dummy Uclid variable inserted earlier. + for (int i = 0; i < generator.reactionInstances.size(); i++) { + if (reactions[i].equals("true")) + info.reactions.add(generator.reactionInstances.get(i).getReaction().getFullName()); + } + + // Time tag + m.find(); + String tagStr = ""; + // Found a let binding. + if (m.group(1) != null) tagStr = symbolTable.get(m.group(1)).strip(); + // The rest falls into group 2. + else tagStr = m.group(2).strip(); + String[] tag = tagStr.split("\\s+"); + info.tag = new Tag(Long.parseLong(tag[0]), Long.parseLong(tag[1]), false); + + // Variables + // Currently all integers. + // Negative numbers could appear. + m.find(); + String variablesStr = ""; + // Found a let binding. + if (m.group(1) != null) variablesStr = symbolTable.get(m.group(1)).strip(); + // The rest falls into group 2. + else variablesStr = m.group(2).strip(); + String[] variables = variablesStr.replaceAll("\\(-\\s(.*?)\\)", "-$1").split("\\s+"); + for (int i = 0; i < generator.namedInstances.size(); i++) { + info.variables.put(generator.namedInstances.get(i).getFullName(), variables[i]); + } + + // Triggers + m.find(); + String triggersStr = ""; + // Found a let binding. + if (m.group(1) != null) triggersStr = symbolTable.get(m.group(1)).strip(); + // The rest falls into group 2. + else triggersStr = m.group(2).strip(); + String[] triggers = triggersStr.split("\\s+"); + for (int i = 0; i < generator.triggerInstances.size(); i++) { + info.triggers.put(generator.triggerInstances.get(i).getFullName(), triggers[i]); + } + + // Actions scheduled + m.find(); + String scheduledStr = ""; + // Found a let binding. + if (m.group(1) != null) scheduledStr = symbolTable.get(m.group(1)).strip(); + // The rest falls into group 2. + else scheduledStr = m.group(2).strip(); + String[] scheduled = scheduledStr.split("\\s+"); + for (int i = 0; i < generator.actionInstances.size(); i++) { + info.scheduled.put(generator.actionInstances.get(i).getFullName(), scheduled[i]); + } + + // Scheduled payloads + // Currently all integers. + // Negative numbers could appear. + m.find(); + String payloadsStr = ""; + // Found a let binding. + if (m.group(1) != null) payloadsStr = symbolTable.get(m.group(1)).strip(); + // The rest falls into group 2. + else payloadsStr = m.group(2).strip(); + String[] payloads = payloadsStr.replaceAll("\\(-\\s(.*?)\\)", "-$1").split("\\s+"); + for (int i = 0; i < generator.actionInstances.size(); i++) { + info.payloads.put(generator.actionInstances.get(i).getFullName(), payloads[i]); + } + + return info; + } + + /** + * Run all the generated Uclid models, report outputs, and generate counterexample trace diagrams. + */ + public void run() { + for (Path path : generator.generatedFiles) { + // Execute uclid for each property. + LFCommand command = + commandFactory.createCommand( + "uclid", + List.of( + path.toString(), + // Any counterexample will be in .json + "--json-cex", + path.toString()), + generator.outputDir); + command.run(); + + String output = command.getOutput().toString(); + boolean valid = !output.contains("FAILED"); + if (valid) { + System.out.println("Valid!"); + } else { + System.out.println("Not valid!"); + try { + // Read from the JSON counterexample (cex). + String cexJSONStr = + Files.readString(Paths.get(path.toString() + ".json"), StandardCharsets.UTF_8); + JSONObject cexJSON = new JSONObject(cexJSONStr); + + //// Extract the counterexample trace from JSON. + // Get the first key "property_*" + Iterator keys = cexJSON.keys(); + String firstKey = keys.next(); + JSONObject propertyObj = cexJSON.getJSONObject(firstKey); + + // Get Uclid trace. + JSONArray uclidTrace = propertyObj.getJSONArray("trace"); + + // Get the first step of the Uclid trace. + JSONObject uclidTraceStepOne = uclidTrace.getJSONObject(0); + + // Get the actual trace defined in the verification model. + JSONObject trace = uclidTraceStepOne.getJSONArray("trace").getJSONObject(0); + + String stepStr = ""; + for (int i = 0; i <= generator.CT; i++) { + try { + stepStr = trace.getString(String.valueOf(i)); + } catch (JSONException e) { + stepStr = trace.getString("-"); + } + System.out.println("============ Step " + i + " ============"); + StateInfo info = parseStateInfo(stepStr); + info.display(); + } + } catch (IOException e) { + System.out.println("ERROR: Not able to read from " + path.toString()); + } + } + + // If "expect" is set, check if the result matches it. + // If not, exit with error code 1. + String expect = generator.expectations.get(path); + if (expect != null) { + boolean expectValid = Boolean.parseBoolean(expect); + if (expectValid != valid) { + System.out.println( + "ERROR: The expected result does not match the actual result. Expected: " + + expectValid + + ", Result: " + + valid); + System.exit(1); + } + } + } + } +} diff --git a/core/src/main/java/org/lflang/dsl/antlr4/C.g4 b/core/src/main/java/org/lflang/dsl/antlr4/C.g4 new file mode 100644 index 0000000000..2fd6c3aedf --- /dev/null +++ b/core/src/main/java/org/lflang/dsl/antlr4/C.g4 @@ -0,0 +1,908 @@ +/* + [The "BSD licence"] + Copyright (c) 2013 Sam Harwell + All rights reserved. + + 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. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. +*/ + +/** C 2011 grammar built from the C11 Spec */ +grammar C; + + +primaryExpression + : Identifier + | Constant + | StringLiteral+ + | '(' expression ')' + | genericSelection + | '__extension__'? '(' compoundStatement ')' // Blocks (GCC extension) + | '__builtin_va_arg' '(' unaryExpression ',' typeName ')' + | '__builtin_offsetof' '(' typeName ',' unaryExpression ')' + ; + +genericSelection + : '_Generic' '(' assignmentExpression ',' genericAssocList ')' + ; + +genericAssocList + : genericAssociation (',' genericAssociation)* + ; + +genericAssociation + : (typeName | 'default') ':' assignmentExpression + ; + +postfixExpression + : + ( primaryExpression + | '__extension__'? '(' typeName ')' '{' initializerList ','? '}' + ) + ('[' expression ']' + | '(' argumentExpressionList? ')' + | ('.' | '->') Identifier + | ('++' | '--') + )* + ; + +argumentExpressionList + : assignmentExpression (',' assignmentExpression)* + ; + +unaryExpression + : + ('++' | '--' | 'sizeof')* + (postfixExpression + | unaryOperator castExpression + | ('sizeof' | '_Alignof') '(' typeName ')' + | '&&' Identifier // GCC extension address of label + ) + ; + +unaryOperator + : '&' | '*' | '+' | '-' | '~' | '!' + ; + +castExpression + : '__extension__'? '(' typeName ')' castExpression + | unaryExpression + | DigitSequence // for + ; + +multiplicativeExpression + : castExpression (('*'|'/'|'%') castExpression)* + ; + +additiveExpression + : multiplicativeExpression (('+'|'-') multiplicativeExpression)* + ; + +shiftExpression + : additiveExpression (('<<'|'>>') additiveExpression)* + ; + +relationalExpression + : shiftExpression (('<'|'>'|'<='|'>=') shiftExpression)* + ; + +equalityExpression + : relationalExpression (('=='| '!=') relationalExpression)* + ; + +andExpression + : equalityExpression ( '&' equalityExpression)* + ; + +exclusiveOrExpression + : andExpression ('^' andExpression)* + ; + +inclusiveOrExpression + : exclusiveOrExpression ('|' exclusiveOrExpression)* + ; + +logicalAndExpression + : inclusiveOrExpression ('&&' inclusiveOrExpression)* + ; + +logicalOrExpression + : logicalAndExpression ( '||' logicalAndExpression)* + ; + +conditionalExpression + : logicalOrExpression ('?' expression ':' conditionalExpression)? + ; + +assignmentExpression + : conditionalExpression + | unaryExpression assignmentOperator assignmentExpression + | DigitSequence // for + ; + +assignmentOperator + : '=' | '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' | '>>=' | '&=' | '^=' | '|=' + ; + +expression + : assignmentExpression (',' assignmentExpression)* + ; + +constantExpression + : conditionalExpression + ; + +declaration + : declarationSpecifiers initDeclaratorList? ';' + | staticAssertDeclaration + ; + +declarationSpecifiers + : declarationSpecifier+ + ; + +declarationSpecifiers2 + : declarationSpecifier+ + ; + +declarationSpecifier + : storageClassSpecifier + | typeSpecifier + | typeQualifier + | functionSpecifier + | alignmentSpecifier + ; + +initDeclaratorList + : initDeclarator (',' initDeclarator)* + ; + +initDeclarator + : declarator ('=' initializer)? + ; + +storageClassSpecifier + : 'typedef' + | 'extern' + | 'static' + | '_Thread_local' + | 'auto' + | 'register' + ; + +typeSpecifier + : ('void' + | 'char' + | 'short' + | 'int' + | 'long' + | 'float' + | 'double' + | 'signed' + | 'unsigned' + | '_Bool' + | '_Complex' + | '__m128' + | '__m128d' + | '__m128i') + | '__extension__' '(' ('__m128' | '__m128d' | '__m128i') ')' + | atomicTypeSpecifier + | structOrUnionSpecifier + | enumSpecifier + | typedefName + | '__typeof__' '(' constantExpression ')' // GCC extension + ; + +structOrUnionSpecifier + : structOrUnion Identifier? '{' structDeclarationList '}' + | structOrUnion Identifier + ; + +structOrUnion + : 'struct' + | 'union' + ; + +structDeclarationList + : structDeclaration+ + ; + +structDeclaration // The first two rules have priority order and cannot be simplified to one expression. + : specifierQualifierList structDeclaratorList ';' + | specifierQualifierList ';' + | staticAssertDeclaration + ; + +specifierQualifierList + : (typeSpecifier| typeQualifier) specifierQualifierList? + ; + +structDeclaratorList + : structDeclarator (',' structDeclarator)* + ; + +structDeclarator + : declarator + | declarator? ':' constantExpression + ; + +enumSpecifier + : 'enum' Identifier? '{' enumeratorList ','? '}' + | 'enum' Identifier + ; + +enumeratorList + : enumerator (',' enumerator)* + ; + +enumerator + : enumerationConstant ('=' constantExpression)? + ; + +enumerationConstant + : Identifier + ; + +atomicTypeSpecifier + : '_Atomic' '(' typeName ')' + ; + +typeQualifier + : 'const' + | 'restrict' + | 'volatile' + | '_Atomic' + ; + +functionSpecifier + : ('inline' + | '_Noreturn' + | '__inline__' // GCC extension + | '__stdcall') + | gccAttributeSpecifier + | '__declspec' '(' Identifier ')' + ; + +alignmentSpecifier + : '_Alignas' '(' (typeName | constantExpression) ')' + ; + +declarator + : pointer? directDeclarator gccDeclaratorExtension* + ; + +directDeclarator + : Identifier + | '(' declarator ')' + | directDeclarator '[' typeQualifierList? assignmentExpression? ']' + | directDeclarator '[' 'static' typeQualifierList? assignmentExpression ']' + | directDeclarator '[' typeQualifierList 'static' assignmentExpression ']' + | directDeclarator '[' typeQualifierList? '*' ']' + | directDeclarator '(' parameterTypeList ')' + | directDeclarator '(' identifierList? ')' + | Identifier ':' DigitSequence // bit field + | vcSpecificModifer Identifier // Visual C Extension + | '(' vcSpecificModifer declarator ')' // Visual C Extension + ; + +vcSpecificModifer + : ('__cdecl' + | '__clrcall' + | '__stdcall' + | '__fastcall' + | '__thiscall' + | '__vectorcall') + ; + + +gccDeclaratorExtension + : '__asm' '(' StringLiteral+ ')' + | gccAttributeSpecifier + ; + +gccAttributeSpecifier + : '__attribute__' '(' '(' gccAttributeList ')' ')' + ; + +gccAttributeList + : gccAttribute? (',' gccAttribute?)* + ; + +gccAttribute + : ~(',' | '(' | ')') // relaxed def for "identifier or reserved word" + ('(' argumentExpressionList? ')')? + ; + +nestedParenthesesBlock + : ( ~('(' | ')') + | '(' nestedParenthesesBlock ')' + )* + ; + +pointer + : (('*'|'^') typeQualifierList?)+ // ^ - Blocks language extension + ; + +typeQualifierList + : typeQualifier+ + ; + +parameterTypeList + : parameterList (',' '...')? + ; + +parameterList + : parameterDeclaration (',' parameterDeclaration)* + ; + +parameterDeclaration + : declarationSpecifiers declarator + | declarationSpecifiers2 abstractDeclarator? + ; + +identifierList + : Identifier (',' Identifier)* + ; + +typeName + : specifierQualifierList abstractDeclarator? + ; + +abstractDeclarator + : pointer + | pointer? directAbstractDeclarator gccDeclaratorExtension* + ; + +directAbstractDeclarator + : '(' abstractDeclarator ')' gccDeclaratorExtension* + | '[' typeQualifierList? assignmentExpression? ']' + | '[' 'static' typeQualifierList? assignmentExpression ']' + | '[' typeQualifierList 'static' assignmentExpression ']' + | '[' '*' ']' + | '(' parameterTypeList? ')' gccDeclaratorExtension* + | directAbstractDeclarator '[' typeQualifierList? assignmentExpression? ']' + | directAbstractDeclarator '[' 'static' typeQualifierList? assignmentExpression ']' + | directAbstractDeclarator '[' typeQualifierList 'static' assignmentExpression ']' + | directAbstractDeclarator '[' '*' ']' + | directAbstractDeclarator '(' parameterTypeList? ')' gccDeclaratorExtension* + ; + +typedefName + : Identifier + ; + +initializer + : assignmentExpression + | '{' initializerList ','? '}' + ; + +initializerList + : designation? initializer (',' designation? initializer)* + ; + +designation + : designatorList '=' + ; + +designatorList + : designator+ + ; + +designator + : '[' constantExpression ']' + | '.' Identifier + ; + +staticAssertDeclaration + : '_Static_assert' '(' constantExpression ',' StringLiteral+ ')' ';' + ; + +statement + : labeledStatement + | compoundStatement + | expressionStatement + | selectionStatement + | iterationStatement + | jumpStatement + | ('__asm' | '__asm__') ('volatile' | '__volatile__') '(' (logicalOrExpression (',' logicalOrExpression)*)? (':' (logicalOrExpression (',' logicalOrExpression)*)?)* ')' ';' + ; + +labeledStatement + : Identifier ':' statement + | 'case' constantExpression ':' statement + | 'default' ':' statement + ; + +compoundStatement + : '{' blockItemList? '}' + ; + +blockItemList + : blockItem+ + ; + +// Reaction body is a blockItem. +blockItem + : statement + | declaration + ; + +expressionStatement + : expression? ';' + ; + +selectionStatement + : 'if' '(' expression ')' statement ('else' statement)? + | 'switch' '(' expression ')' statement + ; + +iterationStatement + : While '(' expression ')' statement + | Do statement While '(' expression ')' ';' + | For '(' forCondition ')' statement + ; + +// | 'for' '(' expression? ';' expression? ';' forUpdate? ')' statement +// | For '(' declaration expression? ';' expression? ')' statement + +forCondition + : (forDeclaration | expression?) ';' forExpression? ';' forExpression? + ; + +forDeclaration + : declarationSpecifiers initDeclaratorList? + ; + +forExpression + : assignmentExpression (',' assignmentExpression)* + ; + +jumpStatement + : ('goto' Identifier + | ('continue'| 'break') + | 'return' expression? + | 'goto' unaryExpression // GCC extension + ) + ';' + ; + +compilationUnit + : translationUnit? EOF + ; + +translationUnit + : externalDeclaration+ + ; + +externalDeclaration + : functionDefinition + | declaration + | ';' // stray ; + ; + +functionDefinition + : declarationSpecifiers? declarator declarationList? compoundStatement + ; + +declarationList + : declaration+ + ; + +Auto : 'auto'; +Break : 'break'; +Case : 'case'; +Char : 'char'; +Const : 'const'; +Continue : 'continue'; +Default : 'default'; +Do : 'do'; +Double : 'double'; +Else : 'else'; +Enum : 'enum'; +Extern : 'extern'; +Float : 'float'; +For : 'for'; +Goto : 'goto'; +If : 'if'; +Inline : 'inline'; +Int : 'int'; +Long : 'long'; +Register : 'register'; +Restrict : 'restrict'; +Return : 'return'; +Short : 'short'; +Signed : 'signed'; +Sizeof : 'sizeof'; +Static : 'static'; +Struct : 'struct'; +Switch : 'switch'; +Typedef : 'typedef'; +Union : 'union'; +Unsigned : 'unsigned'; +Void : 'void'; +Volatile : 'volatile'; +While : 'while'; + +Alignas : '_Alignas'; +Alignof : '_Alignof'; +Atomic : '_Atomic'; +Bool : '_Bool'; +Complex : '_Complex'; +Generic : '_Generic'; +Imaginary : '_Imaginary'; +Noreturn : '_Noreturn'; +StaticAssert : '_Static_assert'; +ThreadLocal : '_Thread_local'; + +LeftParen : '('; +RightParen : ')'; +LeftBracket : '['; +RightBracket : ']'; +LeftBrace : '{'; +RightBrace : '}'; + +Less : '<'; +LessEqual : '<='; +Greater : '>'; +GreaterEqual : '>='; +LeftShift : '<<'; +RightShift : '>>'; + +Plus : '+'; +PlusPlus : '++'; +Minus : '-'; +MinusMinus : '--'; +Star : '*'; +Div : '/'; +Mod : '%'; + +And : '&'; +Or : '|'; +AndAnd : '&&'; +OrOr : '||'; +Caret : '^'; +Not : '!'; +Tilde : '~'; + +Question : '?'; +Colon : ':'; +Semi : ';'; +Comma : ','; + +Assign : '='; +// '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' | '>>=' | '&=' | '^=' | '|=' +StarAssign : '*='; +DivAssign : '/='; +ModAssign : '%='; +PlusAssign : '+='; +MinusAssign : '-='; +LeftShiftAssign : '<<='; +RightShiftAssign : '>>='; +AndAssign : '&='; +XorAssign : '^='; +OrAssign : '|='; + +Equal : '=='; +NotEqual : '!='; + +Arrow : '->'; +Dot : '.'; +Ellipsis : '...'; + +Identifier + : IdentifierNondigit + ( IdentifierNondigit + | Digit + )* + ; + +fragment +IdentifierNondigit + : Nondigit + | UniversalCharacterName + //| // other implementation-defined characters... + ; + +fragment +Nondigit + : [a-zA-Z_] + ; + +fragment +Digit + : [0-9] + ; + +fragment +UniversalCharacterName + : '\\u' HexQuad + | '\\U' HexQuad HexQuad + ; + +fragment +HexQuad + : HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit + ; + +Constant + : IntegerConstant + | FloatingConstant + //| EnumerationConstant + | CharacterConstant + ; + +fragment +IntegerConstant + : DecimalConstant IntegerSuffix? + | OctalConstant IntegerSuffix? + | HexadecimalConstant IntegerSuffix? + | BinaryConstant + ; + +fragment +BinaryConstant + : '0' [bB] [0-1]+ + ; + +fragment +DecimalConstant + : NonzeroDigit Digit* + ; + +fragment +OctalConstant + : '0' OctalDigit* + ; + +fragment +HexadecimalConstant + : HexadecimalPrefix HexadecimalDigit+ + ; + +fragment +HexadecimalPrefix + : '0' [xX] + ; + +fragment +NonzeroDigit + : [1-9] + ; + +fragment +OctalDigit + : [0-7] + ; + +fragment +HexadecimalDigit + : [0-9a-fA-F] + ; + +fragment +IntegerSuffix + : UnsignedSuffix LongSuffix? + | UnsignedSuffix LongLongSuffix + | LongSuffix UnsignedSuffix? + | LongLongSuffix UnsignedSuffix? + ; + +fragment +UnsignedSuffix + : [uU] + ; + +fragment +LongSuffix + : [lL] + ; + +fragment +LongLongSuffix + : 'll' | 'LL' + ; + +fragment +FloatingConstant + : DecimalFloatingConstant + | HexadecimalFloatingConstant + ; + +fragment +DecimalFloatingConstant + : FractionalConstant ExponentPart? FloatingSuffix? + | DigitSequence ExponentPart FloatingSuffix? + ; + +fragment +HexadecimalFloatingConstant + : HexadecimalPrefix (HexadecimalFractionalConstant | HexadecimalDigitSequence) BinaryExponentPart FloatingSuffix? + ; + +fragment +FractionalConstant + : DigitSequence? '.' DigitSequence + | DigitSequence '.' + ; + +fragment +ExponentPart + : [eE] Sign? DigitSequence + ; + +fragment +Sign + : [+-] + ; + +DigitSequence + : Digit+ + ; + +fragment +HexadecimalFractionalConstant + : HexadecimalDigitSequence? '.' HexadecimalDigitSequence + | HexadecimalDigitSequence '.' + ; + +fragment +BinaryExponentPart + : [pP] Sign? DigitSequence + ; + +fragment +HexadecimalDigitSequence + : HexadecimalDigit+ + ; + +fragment +FloatingSuffix + : [flFL] + ; + +fragment +CharacterConstant + : '\'' CCharSequence '\'' + | 'L\'' CCharSequence '\'' + | 'u\'' CCharSequence '\'' + | 'U\'' CCharSequence '\'' + ; + +fragment +CCharSequence + : CChar+ + ; + +fragment +CChar + : ~['\\\r\n] + | EscapeSequence + ; + +fragment +EscapeSequence + : SimpleEscapeSequence + | OctalEscapeSequence + | HexadecimalEscapeSequence + | UniversalCharacterName + ; + +fragment +SimpleEscapeSequence + : '\\' ['"?abfnrtv\\] + ; + +fragment +OctalEscapeSequence + : '\\' OctalDigit OctalDigit? OctalDigit? + ; + +fragment +HexadecimalEscapeSequence + : '\\x' HexadecimalDigit+ + ; + +StringLiteral + : EncodingPrefix? '"' SCharSequence? '"' + ; + +fragment +EncodingPrefix + : 'u8' + | 'u' + | 'U' + | 'L' + ; + +fragment +SCharSequence + : SChar+ + ; + +fragment +SChar + : ~["\\\r\n] + | EscapeSequence + | '\\\n' // Added line + | '\\\r\n' // Added line + ; + +ComplexDefine + : '#' Whitespace? 'define' ~[#\r\n]* + -> skip + ; + +IncludeDirective + : '#' Whitespace? 'include' Whitespace? (('"' ~[\r\n]* '"') | ('<' ~[\r\n]* '>' )) Whitespace? Newline + -> skip + ; + +// ignore the following asm blocks: +/* + asm + { + mfspr x, 286; + } + */ +AsmBlock + : 'asm' ~'{'* '{' ~'}'* '}' + -> skip + ; + +// ignore the lines generated by c preprocessor +// sample line : '#line 1 "/home/dm/files/dk1.h" 1' +LineAfterPreprocessing + : '#line' Whitespace* ~[\r\n]* + -> skip + ; + +LineDirective + : '#' Whitespace? DecimalConstant Whitespace? StringLiteral ~[\r\n]* + -> skip + ; + +PragmaDirective + : '#' Whitespace? 'pragma' Whitespace ~[\r\n]* + -> skip + ; + +Whitespace + : [ \t]+ + -> skip + ; + +Newline + : ( '\r' '\n'? + | '\n' + ) + -> skip + ; + +BlockComment + : '/*' .*? '*/' + -> skip + ; + +LineComment + : '//' ~[\r\n]* + -> skip + ; \ No newline at end of file diff --git a/core/src/main/java/org/lflang/dsl/antlr4/MTLLexer.g4 b/core/src/main/java/org/lflang/dsl/antlr4/MTLLexer.g4 new file mode 100644 index 0000000000..b8fe9c118d --- /dev/null +++ b/core/src/main/java/org/lflang/dsl/antlr4/MTLLexer.g4 @@ -0,0 +1,117 @@ +lexer grammar MTLLexer; + +COMMA + : ',' + ; + +LPAREN + : '(' + ; + +RPAREN + : ')' + ; + +LBRACKET + : '[' + ; + +RBRACKET + : ']' + ; + +LAND + : '&&' + ; + +LOR + : '||' + ; + +EQUI + : '<==>' + ; + +IMPL + : '==>' + ; + +UNTIL + : 'U' + ; + +NEGATION + : '!' + ; + +NEXT + : 'X' + ; + +GLOBALLY + : 'G' + ; + +FINALLY + : 'F' + ; + +WS + : [ \t\r\n]+ -> skip + ; + +TRUE + : 'true' + ; + +FALSE + : 'false' + ; + +PLUS + : '+' + ; + +MINUS + : '-' + ; + +TIMES + : '*' + ; + +DIV + : '/' + ; + +EQ + : '==' + ; + +NEQ + : '!=' + ; + +LT + : '<' + ; + +LE + : '<=' + ; + +GT + : '>' + ; + +GE + : '>=' + ; + +INTEGER + : [0-9]+ + ; + +ID + : ([a-zA-Z0-9]|'_')+ + ; \ No newline at end of file diff --git a/core/src/main/java/org/lflang/dsl/antlr4/MTLParser.g4 b/core/src/main/java/org/lflang/dsl/antlr4/MTLParser.g4 new file mode 100644 index 0000000000..1a5d39b9b4 --- /dev/null +++ b/core/src/main/java/org/lflang/dsl/antlr4/MTLParser.g4 @@ -0,0 +1,82 @@ +parser grammar MTLParser; + +options { tokenVocab=MTLLexer; } + +mtl + : equivalence + ; + +equivalence + : left=implication ( EQUI right=implication )? + ; + +implication + : left=disjunction ( IMPL right=disjunction )? + ; + +disjunction + : terms+=conjunction ( LOR terms+=conjunction )* + ; + +conjunction + : terms+=binaryOp ( LAND terms+=binaryOp )* + ; + +binaryOp + : left=unaryOp ( UNTIL timeInterval=interval right=unaryOp )? # Until + ; + +unaryOp + : formula=primary # NoUnaryOp + | NEGATION formula=primary # Negation + | NEXT timeInterval=interval formula=primary # Next + | GLOBALLY timeInterval=interval formula=primary # Globally + | FINALLY timeInterval=interval formula=primary # Finally + ; + +primary + : atom=atomicProp + | id=ID + | LPAREN formula=mtl RPAREN + ; + +atomicProp + : primitive=TRUE + | primitive=FALSE + | left=expr op=relOp right=expr + ; + +interval + : (LPAREN|LBRACKET) lowerbound=time COMMA upperbound=time (RPAREN|RBRACKET) # Range + | LBRACKET instant=time RBRACKET # Singleton + ; + +time + : value=INTEGER (unit=ID)? + ; + +sum + : terms+=difference (PLUS terms+=difference)* + ; + +difference + : terms+=product (MINUS terms+=product)* + ; + +product + : terms+=quotient (TIMES terms+=quotient)* + ; + +quotient + : terms+=expr (DIV terms+=expr)* + ; + +relOp + : EQ | NEQ | LT | LE | GT | GE + ; + +expr + : ID + | LPAREN sum RPAREN + | INTEGER + ; From 224104797c6bf7c88bd967d420725d910a70a6e2 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 5 Jun 2023 08:54:56 +0800 Subject: [PATCH 132/516] Update CI and spotless --- .github/workflows/ci.yml | 130 ------------------ .github/workflows/uclid-verifier-c-tests.yml | 2 +- .../generator/StateVariableInstance.java | 126 ++++++++--------- 3 files changed, 64 insertions(+), 194 deletions(-) delete mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 06ff46d06a..0000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,130 +0,0 @@ -# Main workflow for testing the Lingua Franca compiler. -name: CI - -on: - # Trigger this workflow on push events, but only on master. - push: - branches: - - master - # Trigger this workflow also on pull_request events, but ignore the 'nightly' branch. - pull_request: - branches-ignore: - - 'nightly' - -env: - # 2020.11 - vcpkgGitRef: 0bf3923f9fab4001c00f0f429682a0853b5749e0 - -jobs: - # Cancel previous workflow runs. - cancel: - uses: lf-lang/lingua-franca/.github/workflows/cancel.yml@master - - # Test the Gradle build. - build: - uses: lf-lang/lingua-franca/.github/workflows/build.yml@master - needs: cancel - - # Build the tools used for processing execution traces - build-tracing-tools: - uses: lf-lang/lingua-franca/.github/workflows/build-trace-tools.yml@master - needs: cancel - - # Check that automatic code formatting works. - # TODO: Uncomment after fed-gen is merged. - # format: - # uses: lf-lang/lingua-franca/.github/workflows/format.yml@master - # needs: cancel - - # Run the unit tests. - unit-tests: - uses: lf-lang/lingua-franca/.github/workflows/unit-tests.yml@master - needs: cancel - - # Run tests for the standalone compiler. - cli-tests: - uses: lf-lang/lingua-franca/.github/workflows/cli-tests.yml@master - needs: cancel - - # Run the C benchmark tests. - c-benchmark-tests: - uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main - with: - target: 'C' - needs: cancel - - # Run language server tests. - lsp-tests: - uses: lf-lang/lingua-franca/.github/workflows/lsp-tests.yml@master - needs: cancel - - # Run the C integration tests. - c-tests: - uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master - needs: cancel - - # Run the C Arduino integration tests. - c-arduino-tests: - uses: lf-lang/lingua-franca/.github/workflows/c-arduino-tests.yml@master - needs: cancel - - # Run the C Zephyr integration tests. - c-zephyr-tests: - uses: lf-lang/lingua-franca/.github/workflows/c-zephyr-tests.yml@master - needs: cancel - - # Run the CCpp integration tests. - ccpp-tests: - uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master - with: - use-cpp: true - needs: cancel - - # Run the C++ benchmark tests. - cpp-benchmark-tests: - uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main - with: - target: 'Cpp' - needs: cancel - - # Run the C++ integration tests. - cpp-tests: - uses: lf-lang/lingua-franca/.github/workflows/cpp-tests.yml@master - needs: cancel - - # Run the C++ integration tests on ROS2. - cpp-ros2-tests: - uses: lf-lang/lingua-franca/.github/workflows/cpp-ros2-tests.yml@master - needs: cancel - - # Run the Python integration tests. - py-tests: - uses: lf-lang/lingua-franca/.github/workflows/py-tests.yml@master - needs: cancel - - # Run the Rust integration tests. - rs-tests: - uses: lf-lang/lingua-franca/.github/workflows/rs-tests.yml@master - needs: cancel - - # Run the Rust benchmark tests. - rs-benchmark-tests: - uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main - with: - target: 'Rust' - needs: cancel - - # Run the TypeScript integration tests. - ts-tests: - uses: lf-lang/lingua-franca/.github/workflows/ts-tests.yml@master - needs: cancel - - # Run the serialization tests - serialization-tests: - uses: lf-lang/lingua-franca/.github/workflows/serialization-tests.yml@master - needs: cancel - - # Run the Uclid benchmark tests. - uclid-verifier-c-tests: - uses: ./.github/workflows/uclid-verifier-c-tests.yml - needs: cancel diff --git a/.github/workflows/uclid-verifier-c-tests.yml b/.github/workflows/uclid-verifier-c-tests.yml index cb1c9e7e1b..083065ff93 100644 --- a/.github/workflows/uclid-verifier-c-tests.yml +++ b/.github/workflows/uclid-verifier-c-tests.yml @@ -66,4 +66,4 @@ jobs: echo "$(pwd)/uclid-0.9.5/bin" >> $GITHUB_PATH - name: Run the test script working-directory: lf-verifier-benchmarks/ - run: ./scripts/test-lf-verifier \ No newline at end of file + run: ./scripts/test-lf-verifier diff --git a/core/src/main/java/org/lflang/generator/StateVariableInstance.java b/core/src/main/java/org/lflang/generator/StateVariableInstance.java index 661602b01c..8c7916e7a0 100644 --- a/core/src/main/java/org/lflang/generator/StateVariableInstance.java +++ b/core/src/main/java/org/lflang/generator/StateVariableInstance.java @@ -1,79 +1,79 @@ /** A data structure for a state variable. */ /************* -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 org.lflang.ErrorReporter; import org.lflang.lf.StateVar; -/** - * Representation of a compile-time instance of a state variable. - * - * - */ +/** Representation of a compile-time instance of a state variable. */ public class StateVariableInstance extends NamedInstance { - /** - * Create a runtime instance from the specified definition - * and with the specified parent that instantiated it. - * @param definition The declaration in the AST. - * @param parent The parent. - */ - public StateVariableInstance(StateVar definition, ReactorInstance parent) { - this(definition, parent, null); - } + /** + * Create a runtime instance from the specified definition and with the specified parent that + * instantiated it. + * + * @param definition The declaration in the AST. + * @param parent The parent. + */ + public StateVariableInstance(StateVar definition, ReactorInstance parent) { + this(definition, parent, null); + } + + /** + * Create a port instance from the specified definition and with the specified parent that + * instantiated it. + * + * @param definition The declaration in the AST. + * @param parent The parent. + * @param errorReporter An error reporter, or null to throw exceptions. + */ + public StateVariableInstance( + StateVar definition, ReactorInstance parent, ErrorReporter errorReporter) { + super(definition, parent); - /** - * Create a port instance from the specified definition - * and with the specified parent that instantiated it. - * @param definition The declaration in the AST. - * @param parent The parent. - * @param errorReporter An error reporter, or null to throw exceptions. - */ - public StateVariableInstance(StateVar definition, ReactorInstance parent, ErrorReporter errorReporter) { - super(definition, parent); - - if (parent == null) { - throw new NullPointerException("Cannot create a StateVariableInstance with no parent."); - } + if (parent == null) { + throw new NullPointerException("Cannot create a StateVariableInstance with no parent."); } + } - ////////////////////////////////////////////////////// - //// Public methods + ////////////////////////////////////////////////////// + //// Public methods - /** - * Return the name of this trigger. - * @return The name of this trigger. - */ - @Override - public String getName() { - return definition.getName(); - } + /** + * Return the name of this trigger. + * + * @return The name of this trigger. + */ + @Override + public String getName() { + return definition.getName(); + } - @Override - public String toString() { - return "StateVariableInstance " + getFullName(); - } + @Override + public String toString() { + return "StateVariableInstance " + getFullName(); + } } From 7aa75c73c249720b10a83b61d7bf195086838897 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 6 Jun 2023 13:43:21 -0700 Subject: [PATCH 133/516] Comment out addDependentNetworkEdges. --- .../main/java/org/lflang/generator/ReactionInstanceGraph.java | 4 ++-- core/src/testFixtures/java/org/lflang/tests/TestRegistry.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java index 2628b30262..3dba4029f9 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java @@ -87,7 +87,7 @@ public void rebuild() { // FIXME: Use {@link TargetProperty#EXPORT_DEPENDENCY_GRAPH}. // System.out.println(toDOT()); - addDependentNetworkEdges(main); +// addDependentNetworkEdges(main); // Assign a level to each reaction. // If there are cycles present in the graph, it will be detected here. @@ -141,7 +141,7 @@ private void addDependentNetworkEdges(ReactorInstance main) { public void rebuildAndAssignDeadlines() { this.clear(); addNodesAndEdges(main); - addDependentNetworkEdges(main); +// addDependentNetworkEdges(main); assignInferredDeadlines(); this.clear(); } diff --git a/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java b/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java index 483448bd8f..f8d0d22bbd 100644 --- a/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java +++ b/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java @@ -204,7 +204,7 @@ public String getHeader() { if (Files.exists(dir)) { new TestDirVisitor(rs, target, dir).walk(); } else { - System.out.println("WARNING: No test directory for target " + target + "\n"); + System.out.println("WARNING: No test directory for target " + target + "\n"); } } catch (IOException e) { From ab308d3661bf8d314be4678b3023859484b889c1 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 6 Jun 2023 13:58:19 -0700 Subject: [PATCH 134/516] Trivial changes. --- .github/workflows/only-c.yml | 6 +++--- .../java/org/lflang/generator/ReactionInstanceGraph.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/only-c.yml b/.github/workflows/only-c.yml index 195624cedc..18a712fe7f 100644 --- a/.github/workflows/only-c.yml +++ b/.github/workflows/only-c.yml @@ -22,7 +22,7 @@ jobs: uses: ./.github/workflows/c-tests.yml with: all-platforms: ${{ !github.event.pull_request.draft }} - selected-tests: runGenericTests + selected-tests: runBasicTests default1: if: ${{ inputs.all || github.event.pull_request.draft }} @@ -43,7 +43,7 @@ jobs: uses: ./.github/workflows/c-tests.yml with: all-platforms: ${{ !github.event.pull_request.draft }} - selected-tests: runTypeParameterTests + selected-tests: runGenericsTests default4: if: ${{ inputs.all || github.event.pull_request.draft }} @@ -113,7 +113,7 @@ jobs: if: ${{ inputs.all || github.event.pull_request.draft }} uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@gradle with: - target: 'C' + target: "C" # Run the C Arduino integration tests. arduino: diff --git a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java index 3dba4029f9..a0dd1df962 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java @@ -87,7 +87,7 @@ public void rebuild() { // FIXME: Use {@link TargetProperty#EXPORT_DEPENDENCY_GRAPH}. // System.out.println(toDOT()); -// addDependentNetworkEdges(main); + // addDependentNetworkEdges(main); // Assign a level to each reaction. // If there are cycles present in the graph, it will be detected here. @@ -141,7 +141,7 @@ private void addDependentNetworkEdges(ReactorInstance main) { public void rebuildAndAssignDeadlines() { this.clear(); addNodesAndEdges(main); -// addDependentNetworkEdges(main); + // addDependentNetworkEdges(main); assignInferredDeadlines(); this.clear(); } From 98c8af6e391dbe71cbfd6003891ab4bf40b14375 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 6 Jun 2023 14:55:34 -0700 Subject: [PATCH 135/516] Temporarily comment out portAbsentReaction. --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 42ae9c2539..191a98d2a8 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 42ae9c2539381835e7a8c74ff796ef05578b034d +Subproject commit 191a98d2a837df3062066e52c900ee28fb99308f From 82da1c0efbb608ca75b48d3af8ae751bdcda5a70 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 6 Jun 2023 16:03:05 -0700 Subject: [PATCH 136/516] Update submodule. --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 191a98d2a8..bcdde365e8 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 191a98d2a837df3062066e52c900ee28fb99308f +Subproject commit bcdde365e8a46cb9a6657fc02be76b248fd0a252 From 00172c7551083795243db6fd2b4f8d6c17aef0b9 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 7 Jun 2023 16:55:34 +0800 Subject: [PATCH 137/516] Fix git merge issues --- cli/lfc/src/main/java/org/lflang/cli/Lfc.java | 10 ++++ core/src/main/java/org/lflang/FileConfig.java | 38 +++++++++++++++ .../main/java/org/lflang/TargetConfig.java | 9 ++++ .../main/java/org/lflang/TargetProperty.java | 10 ++++ core/src/main/java/org/lflang/TimeValue.java | 30 ++++++++++++ .../statespace/StateSpaceExplorer.java | 6 ++- .../lflang/analyses/uclid/UclidGenerator.java | 9 +--- .../main/java/org/lflang/ast/ASTUtils.java | 14 ++++++ .../org/lflang/generator/GeneratorBase.java | 43 ++++++----------- .../org/lflang/generator/LFGenerator.java | 46 ++++++++++++++++++ .../lflang/generator/LFGeneratorContext.java | 1 + .../org/lflang/generator/NamedInstance.java | 47 +++++++++---------- .../lflang/generator/ReactionInstance.java | 5 +- .../org/lflang/generator/ReactorInstance.java | 17 +++++-- .../org/lflang/validation/AttributeSpec.java | 12 ++++- 15 files changed, 228 insertions(+), 69 deletions(-) diff --git a/cli/lfc/src/main/java/org/lflang/cli/Lfc.java b/cli/lfc/src/main/java/org/lflang/cli/Lfc.java index 19869f9394..6f3e9fe6b6 100644 --- a/cli/lfc/src/main/java/org/lflang/cli/Lfc.java +++ b/cli/lfc/src/main/java/org/lflang/cli/Lfc.java @@ -79,6 +79,12 @@ public class Lfc extends CliBase { description = "Do not invoke target compiler.") private boolean noCompile; + @Option( + names = {"--no-verify"}, + arity = "0", + description = "Do not run the generated verification models.") + private boolean noVerify; + @Option( names = {"--print-statistics"}, arity = "0", @@ -250,6 +256,10 @@ public Properties getGeneratorArgs() { props.setProperty(BuildParm.NO_COMPILE.getKey(), "true"); } + if (noVerify) { + props.setProperty(BuildParm.NO_VERIFY.getKey(), "true"); + } + if (targetCompiler != null) { props.setProperty(BuildParm.TARGET_COMPILER.getKey(), targetCompiler); } diff --git a/core/src/main/java/org/lflang/FileConfig.java b/core/src/main/java/org/lflang/FileConfig.java index 831cb7957f..c30a237461 100644 --- a/core/src/main/java/org/lflang/FileConfig.java +++ b/core/src/main/java/org/lflang/FileConfig.java @@ -32,6 +32,11 @@ public abstract class FileConfig { /** Default name of the directory to store generated sources in. */ public static final String DEFAULT_SRC_GEN_DIR = "src-gen"; + /** + * Default name of the directory to store generated verification models in. + */ + public static final String DEFAULT_MODEL_GEN_DIR = "model-gen"; + // Public fields. /** The directory in which to put binaries, if the code generator produces any. */ @@ -93,6 +98,17 @@ public abstract class FileConfig { */ protected Path srcGenPath; + /** + * Path representation of the root directory for generated + * verification models. + */ + protected Path modelGenBasePath; + + /** + * The directory in which to put the generated verification models. + */ + protected Path modelGenPath; + // private fields /** @@ -133,6 +149,9 @@ public FileConfig(Resource resource, Path srcGenBasePath, boolean useHierarchica Path binRoot = outPath.resolve(DEFAULT_BIN_DIR); this.binPath = useHierarchicalBin ? binRoot.resolve(getSubPkgPath(srcPath)) : binRoot; + this.modelGenBasePath = outPath.resolve(DEFAULT_MODEL_GEN_DIR); + this.modelGenPath = modelGenBasePath.resolve(getSubPkgPath(srcPath)).resolve(name); + this.iResource = FileUtil.getIResource(resource); } @@ -214,6 +233,24 @@ protected Path getSubPkgPath(Path srcPath) { return relSrcPath; } + /** + * Path representation of the root directory for generated + * verification models. + * This is the root, meaning that if the source file is x/y/Z.lf + * relative to the package root, then the generated sources will be put in x/y/Z + * relative to this URI. + */ + public Path getModelGenBasePath() { + return modelGenBasePath; + } + + /** + * The directory in which to put the generated verification models. + */ + public Path getModelGenPath() { + return modelGenPath; + } + /** * Clean any artifacts produced by the code generator and target compilers. * @@ -226,6 +263,7 @@ protected Path getSubPkgPath(Path srcPath) { public void doClean() throws IOException { FileUtil.deleteDirectory(binPath); FileUtil.deleteDirectory(srcGenBasePath); + FileUtil.deleteDirectory(modelGenBasePath); } private static Path getPkgPath(Resource resource) throws IOException { diff --git a/core/src/main/java/org/lflang/TargetConfig.java b/core/src/main/java/org/lflang/TargetConfig.java index fcb8c88a62..0b97f29e06 100644 --- a/core/src/main/java/org/lflang/TargetConfig.java +++ b/core/src/main/java/org/lflang/TargetConfig.java @@ -82,6 +82,9 @@ public TargetConfig(Properties cliArgs, TargetDecl target, ErrorReporter errorRe if (cliArgs.containsKey("no-compile")) { this.noCompile = true; } + if (cliArgs.containsKey("no-verify")) { + this.noVerify = true; + } if (cliArgs.containsKey("docker")) { var arg = cliArgs.getProperty("docker"); if (Boolean.parseBoolean(arg)) { @@ -232,6 +235,12 @@ public TargetConfig(Properties cliArgs, TargetDecl target, ErrorReporter errorRe /** If true, do not perform runtime validation. The default is false. */ public boolean noRuntimeValidation = false; + /** + * If true, do not check the generated verification model. + * The default is false. + */ + public boolean noVerify = false; + /** * Set the target platform config. This tells the build system what platform-specific support * files it needs to incorporate at compile time. diff --git a/core/src/main/java/org/lflang/TargetProperty.java b/core/src/main/java/org/lflang/TargetProperty.java index 4a68fc3796..b7e4ec7233 100644 --- a/core/src/main/java/org/lflang/TargetProperty.java +++ b/core/src/main/java/org/lflang/TargetProperty.java @@ -412,6 +412,16 @@ public enum TargetProperty { config.noRuntimeValidation = ASTUtils.toBoolean(value); }), + /** + * Directive to not check the generated verification model. + */ + NO_VERIFY("no-verify", PrimitiveType.BOOLEAN, + Arrays.asList(Target.C), + (config) -> ASTUtils.toElement(config.noVerify), + (config, value, err) -> { + config.noVerify = ASTUtils.toBoolean(value); + }), + /** * Directive to specify the platform for cross code generation. This is either a string of the * platform or a dictionary of options that includes the string name. diff --git a/core/src/main/java/org/lflang/TimeValue.java b/core/src/main/java/org/lflang/TimeValue.java index f0bb94820c..bec3b6cdbb 100644 --- a/core/src/main/java/org/lflang/TimeValue.java +++ b/core/src/main/java/org/lflang/TimeValue.java @@ -137,6 +137,36 @@ public long toNanoSeconds() { return makeNanosecs(time, unit); } + /** + * Return a TimeValue based on a nanosecond value. + */ + public static TimeValue fromNanoSeconds(long ns) { + if (ns == 0) return ZERO; + long time; + if ((time = ns / 604_800_016_558_522L) > 0 && ns % 604_800_016_558_522L == 0) { + return new TimeValue(time, TimeUnit.WEEK); + } + if ((time = ns / 86_400_000_000_000L) > 0 && ns % 86_400_000_000_000L == 0) { + return new TimeValue(time, TimeUnit.DAY); + } + if ((time = ns / 3_600_000_000_000L) > 0 && ns % 3_600_000_000_000L == 0) { + return new TimeValue(time, TimeUnit.HOUR); + } + if ((time = ns / 60_000_000_000L) > 0 && ns % 60_000_000_000L == 0) { + return new TimeValue(time, TimeUnit.MINUTE); + } + if ((time = ns / 1_000_000_000) > 0 && ns % 1_000_000_000 == 0) { + return new TimeValue(time, TimeUnit.SECOND); + } + if ((time = ns / 1_000_000) > 0 && ns % 1_000_000 == 0) { + return new TimeValue(time, TimeUnit.MILLI); + } + if ((time = ns / 1000) > 0 && ns % 1000 == 0) { + return new TimeValue(time, TimeUnit.MICRO); + } + return new TimeValue(ns, TimeUnit.NANO); + } + /** Return a string representation of this time value. */ public String toString() { return unit != null ? time + " " + unit.getCanonicalName() : Long.toString(time); diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java index 97befc4a58..394a3f26bc 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java +++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java @@ -78,6 +78,7 @@ public void explore(Tag horizon, boolean findLoop) { // FIXME: It seems that we need to handle shutdown triggers // separately, because they could break the back loop. addInitialEvents(this.main); + // System.out.println("DEBUG: Event queue: " + this.eventQ); Tag previousTag = null; // Tag in the previous loop ITERATION Tag currentTag = null; // Tag in the current loop ITERATION @@ -105,6 +106,7 @@ public void explore(Tag horizon, boolean findLoop) { // System.out.println("DEBUG: Popped an event: " + e); currentEvents.add(e); } + // System.out.println("DEBUG: currentEvents: " + currentEvents); // Collect all the reactions invoked in this current LOOP ITERATION // triggered by the earliest events. @@ -115,6 +117,7 @@ public void explore(Tag horizon, boolean findLoop) { for (Event e : currentEvents) { Set dependentReactions = e.trigger.getDependentReactions(); reactionsTemp.addAll(dependentReactions); + // System.out.println("DEBUG: dependentReactions: " + dependentReactions); // System.out.println("DEBUG: ReactionTemp: " + reactionsTemp); // If the event is a timer firing, enqueue the next firing. @@ -295,8 +298,7 @@ else if (previousTag != null && currentTag.timestamp == previousTag.timestamp) { // System.out.println("DEBUG: Stopping because eventQ is empty!"); stop = true; } else if (currentTag.timestamp > horizon.timestamp) { - // System.out.println("DEBUG: Stopping because horizon is reached! Horizon: " + horizon + " - // Current Tag: " + currentTag); + // System.out.println("DEBUG: Stopping because horizon is reached! Horizon: " + horizon + "Current Tag: " + currentTag); // System.out.println("DEBUG: EventQ: " + eventQ); stop = true; } diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java index 2f34d04c05..a22beb7071 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java @@ -25,8 +25,6 @@ /** (EXPERIMENTAL) Generator for Uclid5 models. */ package org.lflang.analyses.uclid; -import static org.lflang.ASTUtils.*; - import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -41,7 +39,6 @@ import org.antlr.v4.runtime.CommonTokenStream; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.xbase.lib.Exceptions; -import org.lflang.ASTUtils; import org.lflang.Target; import org.lflang.TimeUnit; import org.lflang.TimeValue; @@ -55,6 +52,7 @@ import org.lflang.analyses.statespace.StateSpaceExplorer; import org.lflang.analyses.statespace.StateSpaceNode; import org.lflang.analyses.statespace.Tag; +import org.lflang.ast.ASTUtils; import org.lflang.dsl.CLexer; import org.lflang.dsl.CParser; import org.lflang.dsl.CParser.BlockItemListContext; @@ -1096,8 +1094,6 @@ protected void generateTriggersAndReactions() { if (trigger.isStartup()) { // FIXME: Treat startup as a variable. - // triggerPresentStr = "g(i) == zero()"; - // Handle startup. code.pr( String.join( @@ -1141,7 +1137,6 @@ protected void generateTriggersAndReactions() { } } - // code.pr("|| (" + triggerPresentStr + exclusion + ")"); str += "\n|| (" + triggerPresentStr + exclusion + ")"; } @@ -1524,7 +1519,7 @@ private void createMainReactorInstance() { if (this.main == null) { // Recursively build instances. This is done once because // it is the same for all federates. - this.main = new ReactorInstance(toDefinition(mainDef.getReactorClass()), errorReporter); + this.main = new ReactorInstance(ASTUtils.toDefinition(mainDef.getReactorClass()), errorReporter); var reactionInstanceGraph = this.main.assignLevels(); if (reactionInstanceGraph.nodeCount() > 0) { errorReporter.reportError("Main reactor has causality cycles. Skipping code generation."); diff --git a/core/src/main/java/org/lflang/ast/ASTUtils.java b/core/src/main/java/org/lflang/ast/ASTUtils.java index 078505d958..40930fedb5 100644 --- a/core/src/main/java/org/lflang/ast/ASTUtils.java +++ b/core/src/main/java/org/lflang/ast/ASTUtils.java @@ -144,6 +144,20 @@ public static List getAllReactors(Resource resource) { .collect(Collectors.toList()); } + /** + * Get the main reactor 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 Reactor getMainReactor(Resource resource) { + return StreamSupport.stream(IteratorExtensions.toIterable(resource.getAllContents()).spliterator(), false) + .filter(Reactor.class::isInstance) + .map(Reactor.class::cast) + .filter(it -> it.isMain()) + .findFirst() + .get(); + } + /** * Find connections in the given resource that would be conflicting writes if they were not * located in mutually exclusive modes. diff --git a/core/src/main/java/org/lflang/generator/GeneratorBase.java b/core/src/main/java/org/lflang/generator/GeneratorBase.java index c8ebbc38a2..dd00740aca 100644 --- a/core/src/main/java/org/lflang/generator/GeneratorBase.java +++ b/core/src/main/java/org/lflang/generator/GeneratorBase.java @@ -179,24 +179,6 @@ protected void registerTransformation(AstTransformation transformation) { // ////////////////////////////////////////// // // Code generation functions to override for a concrete code generator. - /** - * If there is a main or federated reactor, then create a synthetic Instantiation for that - * top-level reactor and set the field mainDef to refer to it. - */ - private void createMainInstantiation() { - // Find the main reactor and create an AST node for its instantiation. - Iterable nodes = - IteratorExtensions.toIterable(context.getFileConfig().resource.getAllContents()); - for (Reactor reactor : Iterables.filter(nodes, Reactor.class)) { - if (reactor.isMain()) { - // Creating a definition for the main reactor because there isn't one. - this.mainDef = LfFactory.eINSTANCE.createInstantiation(); - this.mainDef.setName(reactor.getName()); - this.mainDef.setReactorClass(reactor); - } - } - } - /** * Generate code from the Lingua Franca model contained by the specified resource. * @@ -211,10 +193,6 @@ private void createMainInstantiation() { */ 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. @@ -292,13 +270,20 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { 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"); + /** + * 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. + */ + protected 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); } } } diff --git a/core/src/main/java/org/lflang/generator/LFGenerator.java b/core/src/main/java/org/lflang/generator/LFGenerator.java index f2881c9fb8..d340211d73 100644 --- a/core/src/main/java/org/lflang/generator/LFGenerator.java +++ b/core/src/main/java/org/lflang/generator/LFGenerator.java @@ -4,14 +4,19 @@ import java.io.IOException; import java.lang.reflect.Constructor; import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; + import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.generator.AbstractGenerator; import org.eclipse.xtext.generator.IFileSystemAccess2; import org.eclipse.xtext.generator.IGeneratorContext; import org.eclipse.xtext.util.RuntimeIOException; +import org.lflang.AttributeUtils; import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.Target; +import org.lflang.analyses.uclid.UclidGenerator; import org.lflang.ast.ASTUtils; import org.lflang.federated.generator.FedASTUtils; import org.lflang.federated.generator.FedFileConfig; @@ -20,6 +25,8 @@ import org.lflang.generator.c.CGenerator; import org.lflang.generator.python.PyFileConfig; import org.lflang.generator.python.PythonGenerator; +import org.lflang.lf.Attribute; +import org.lflang.lf.Reactor; import org.lflang.scoping.LFGlobalScopeProvider; /** @@ -180,6 +187,29 @@ public void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorCont } else { + // If "-c" or "--clean" is specified, delete any existing generated directories. + cleanIfNeeded(lfContext); + + // Check if @property is used. If so, instantiate a UclidGenerator. + // The verification model needs to be generated before the target code + // since code generation changes LF program (desugar connections, etc.). + Reactor main = ASTUtils.getMainReactor(resource); + List properties = AttributeUtils.getAttributes(main) + .stream() + .filter(attr -> attr.getAttrName().equals("property")) + .collect(Collectors.toList()); + if (properties.size() > 0) { + UclidGenerator uclidGenerator = new UclidGenerator(lfContext, properties); + // Generate uclid files. + uclidGenerator.doGenerate(resource, lfContext); + if (!uclidGenerator.targetConfig.noVerify) { + // Invoke the generated uclid files. + uclidGenerator.runner.run(); + } else { + System.out.println("\"no-verify\" is set to true. Skip checking the verification model."); + } + } + final GeneratorBase generator = createGenerator(lfContext); if (generator != null) { @@ -197,4 +227,20 @@ public void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorCont public boolean errorsOccurred() { return generatorErrorsOccurred; } + + /** + * Check if a clean was requested from the standalone compiler and perform + * the clean step. + * + * FIXME: the signature can be reduced to only take context. + */ + 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"); + } + } + } } diff --git a/core/src/main/java/org/lflang/generator/LFGeneratorContext.java b/core/src/main/java/org/lflang/generator/LFGeneratorContext.java index 727ce229b9..0deff053ce 100644 --- a/core/src/main/java/org/lflang/generator/LFGeneratorContext.java +++ b/core/src/main/java/org/lflang/generator/LFGeneratorContext.java @@ -28,6 +28,7 @@ enum BuildParm { LOGGING("The logging level to use by the generated binary"), LINT("Enable or disable linting of generated code."), NO_COMPILE("Do not invoke target compiler."), + NO_VERIFY("Do not check the generated verification model."), OUTPUT_PATH("Specify the root output directory."), PRINT_STATISTICS("Instruct the runtime to collect and print statistics."), QUIET("Suppress output of the target compiler and other commands"), diff --git a/core/src/main/java/org/lflang/generator/NamedInstance.java b/core/src/main/java/org/lflang/generator/NamedInstance.java index 3455bcdf40..51b55f026f 100644 --- a/core/src/main/java/org/lflang/generator/NamedInstance.java +++ b/core/src/main/java/org/lflang/generator/NamedInstance.java @@ -97,6 +97,28 @@ public String getFullName() { return getFullNameWithJoiner("."); } + /** + * Return a string of the form "a.b.c", where "." is replaced by the specified joiner, "c" is the + * name of this instance, "b" is the name of its container, and "a" is the name of its container, + * stopping at the container in main. + * + * @return A string representing this instance. + */ + public String getFullNameWithJoiner(String joiner) { + // This is not cached because _uniqueID is cached. + if (parent == null) { + return this.getName(); + } else if (getMode(true) != null) { + return parent.getFullNameWithJoiner(joiner) + + joiner + + getMode(true).getName() + + joiner + + this.getName(); + } else { + return parent.getFullNameWithJoiner(joiner) + joiner + this.getName(); + } + } + /** * Return the name of this instance as given in its definition. Note that this is unique only * relative to other instances with the same parent. @@ -274,31 +296,6 @@ public ModeInstance getMode(boolean direct) { */ int width = 1; - ////////////////////////////////////////////////////// - //// Protected methods. - - /** - * Return a string of the form "a.b.c", where "." is replaced by the specified joiner, "c" is the - * name of this instance, "b" is the name of its container, and "a" is the name of its container, - * stopping at the container in main. - * - * @return A string representing this instance. - */ - protected String getFullNameWithJoiner(String joiner) { - // This is not cached because _uniqueID is cached. - if (parent == null) { - return this.getName(); - } else if (getMode(true) != null) { - return parent.getFullNameWithJoiner(joiner) - + joiner - + getMode(true).getName() - + joiner - + this.getName(); - } else { - return parent.getFullNameWithJoiner(joiner) + joiner + this.getName(); - } - } - ////////////////////////////////////////////////////// //// Protected fields. diff --git a/core/src/main/java/org/lflang/generator/ReactionInstance.java b/core/src/main/java/org/lflang/generator/ReactionInstance.java index a1e35ffb19..81c79d5712 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstance.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstance.java @@ -120,7 +120,10 @@ public ReactionInstance( this.sources.add(timerInstance); } } else if (trigger instanceof BuiltinTriggerRef) { - this.triggers.add(parent.getOrCreateBuiltinTrigger((BuiltinTriggerRef) trigger)); + var builtinTriggerInstance + = parent.getOrCreateBuiltinTrigger((BuiltinTriggerRef) trigger); + this.triggers.add(builtinTriggerInstance); + builtinTriggerInstance.dependentReactions.add(this); } } // Next handle the ports that this reaction reads. diff --git a/core/src/main/java/org/lflang/generator/ReactorInstance.java b/core/src/main/java/org/lflang/generator/ReactorInstance.java index 9288744d65..aa0aeced29 100644 --- a/core/src/main/java/org/lflang/generator/ReactorInstance.java +++ b/core/src/main/java/org/lflang/generator/ReactorInstance.java @@ -60,6 +60,7 @@ import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; +import org.lflang.lf.StateVar; import org.lflang.lf.Timer; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; @@ -123,7 +124,7 @@ public ReactorInstance(Reactor reactor, ReactorInstance parent, ErrorReporter re //// Public fields. /** The action instances belonging to this reactor instance. */ - public List actions = new ArrayList<>(); + public final List actions = new ArrayList<>(); /** * The contained reactor instances, in order of declaration. For banks of reactors, this includes @@ -138,6 +139,9 @@ public ReactorInstance(Reactor reactor, ReactorInstance parent, ErrorReporter re /** The output port instances belonging to this reactor instance. */ public final List outputs = new ArrayList<>(); + /** The state variable instances belonging to this reactor instance. */ + public final List states = new ArrayList<>(); + /** The parameters of this instance. */ public final List parameters = new ArrayList<>(); @@ -830,17 +834,22 @@ public ReactorInstance( this.parameters.add(new ParameterInstance(parameter, this)); } - // Instantiate inputs for this reactor instance + // 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 + // 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 + // Instantiate state variables for this reactor instance. + for (StateVar state : ASTUtils.allStateVars(reactorDefinition)) { + this.states.add(new StateVariableInstance(state, 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. diff --git a/core/src/main/java/org/lflang/validation/AttributeSpec.java b/core/src/main/java/org/lflang/validation/AttributeSpec.java index 45c984204e..6eb4277cc3 100644 --- a/core/src/main/java/org/lflang/validation/AttributeSpec.java +++ b/core/src/main/java/org/lflang/validation/AttributeSpec.java @@ -213,7 +213,17 @@ enum AttrParamType { ATTRIBUTE_SPECS_BY_NAME.put( "enclave", new AttributeSpec(List.of(new AttrParamSpec(EACH_ATTR, AttrParamType.BOOLEAN, true)))); - + // @property(name="", tactic="", spec="") + // SMTL is the safety fragment of Metric Temporal Logic (MTL). + ATTRIBUTE_SPECS_BY_NAME.put("property", new AttributeSpec( + List.of( + new AttrParamSpec("name", AttrParamType.STRING, false), + new AttrParamSpec("tactic", AttrParamType.STRING, false), + new AttrParamSpec("spec", AttrParamType.STRING, false), + new AttrParamSpec("CT", AttrParamType.INT, true), + new AttrParamSpec("expect", AttrParamType.BOOLEAN, true) + ) + )); // attributes that are used internally only by the federated code generation ATTRIBUTE_SPECS_BY_NAME.put("_unordered", new AttributeSpec(null)); ATTRIBUTE_SPECS_BY_NAME.put( From a297808406889c4b6ffa5b363b5fc83af38ac383 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 7 Jun 2023 18:49:16 +0800 Subject: [PATCH 138/516] Add warnings and check dependencies --- .../org/lflang/generator/LFGenerator.java | 82 ++++++++++++++----- 1 file changed, 63 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/LFGenerator.java b/core/src/main/java/org/lflang/generator/LFGenerator.java index d340211d73..397622cc2e 100644 --- a/core/src/main/java/org/lflang/generator/LFGenerator.java +++ b/core/src/main/java/org/lflang/generator/LFGenerator.java @@ -1,7 +1,10 @@ package org.lflang.generator; import com.google.inject.Inject; + +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.lang.reflect.Constructor; import java.nio.file.Path; import java.util.List; @@ -190,25 +193,8 @@ public void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorCont // If "-c" or "--clean" is specified, delete any existing generated directories. cleanIfNeeded(lfContext); - // Check if @property is used. If so, instantiate a UclidGenerator. - // The verification model needs to be generated before the target code - // since code generation changes LF program (desugar connections, etc.). - Reactor main = ASTUtils.getMainReactor(resource); - List properties = AttributeUtils.getAttributes(main) - .stream() - .filter(attr -> attr.getAttrName().equals("property")) - .collect(Collectors.toList()); - if (properties.size() > 0) { - UclidGenerator uclidGenerator = new UclidGenerator(lfContext, properties); - // Generate uclid files. - uclidGenerator.doGenerate(resource, lfContext); - if (!uclidGenerator.targetConfig.noVerify) { - // Invoke the generated uclid files. - uclidGenerator.runner.run(); - } else { - System.out.println("\"no-verify\" is set to true. Skip checking the verification model."); - } - } + // If @property annotations are used, run the LF verifier. + runVerifierIfPropertiesDetected(resource, lfContext); final GeneratorBase generator = createGenerator(lfContext); @@ -243,4 +229,62 @@ protected void cleanIfNeeded(LFGeneratorContext context) { } } } + + /** + * Check if @property is used. If so, instantiate a UclidGenerator. + * The verification model needs to be generated before the target code + * since code generation changes LF program (desugar connections, etc.). + */ + private void runVerifierIfPropertiesDetected(Resource resource, LFGeneratorContext lfContext) { + Reactor main = ASTUtils.getMainReactor(resource); + List properties = AttributeUtils.getAttributes(main) + .stream() + .filter(attr -> attr.getAttrName().equals("property")) + .collect(Collectors.toList()); + if (properties.size() > 0) { + + System.out.println("*** WARNING: @property is an experimental feature. Use it with caution. ***"); + + // Check if Uclid5 and Z3 are installed. + if (execInstalled("uclid", "--help", "uclid 0.9.5") + && execInstalled("z3", "--version", "Z3 version")) { + UclidGenerator uclidGenerator = new UclidGenerator(lfContext, properties); + // Generate uclid files. + uclidGenerator.doGenerate(resource, lfContext); + if (!uclidGenerator.targetConfig.noVerify) { + // Invoke the generated uclid files. + uclidGenerator.runner.run(); + } else { + System.out.println("\"no-verify\" is set to true. Skip checking the verification model."); + } + } else { + System.out.println("*** WARNING: Uclid5 or Z3 is not installed. @property is skipped. ***"); + } + } + } + + /** + * A helper function for checking if a dependency is installed on the command line. + * + * @param binaryName The name of the binary + * @param arg An argument following the binary name + * @param expectedSubstring An expected substring in the output + * @return + */ + public static boolean execInstalled(String binaryName, String arg, String expectedSubstring) { + ProcessBuilder processBuilder = new ProcessBuilder(binaryName, arg); + try { + Process process = processBuilder.start(); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + if (line.contains(expectedSubstring)) { + return true; + } + } + } catch (IOException e) { + return false; // binary not present + } + return false; + } } From 8525fb0c44fcacbfe74f847ea4bc0bd18fa256b8 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 7 Jun 2023 21:34:21 +0800 Subject: [PATCH 139/516] Update CI --- .github/workflows/only-c.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/only-c.yml b/.github/workflows/only-c.yml index b353474e94..c0d1dd9797 100644 --- a/.github/workflows/only-c.yml +++ b/.github/workflows/only-c.yml @@ -50,3 +50,11 @@ jobs: with: use-cpp: true all-platforms: ${{ !github.event.pull_request.draft }} + + # Run the Uclid-based LF Verifier tests. + uclid: + if: ${{ inputs.all || github.event.pull_request.draft }} + uses: ./.github/workflows/uclid-verifier-c-tests.yml + with: + use-cpp: true + all-platforms: ${{ !github.event.pull_request.draft }} From 9cc84c31881d8757e27bf6e2fab164d3eff289b9 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 7 Jun 2023 21:42:51 +0800 Subject: [PATCH 140/516] Update CI --- .github/workflows/only-c.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/only-c.yml b/.github/workflows/only-c.yml index c0d1dd9797..8c547a7f28 100644 --- a/.github/workflows/only-c.yml +++ b/.github/workflows/only-c.yml @@ -55,6 +55,3 @@ jobs: uclid: if: ${{ inputs.all || github.event.pull_request.draft }} uses: ./.github/workflows/uclid-verifier-c-tests.yml - with: - use-cpp: true - all-platforms: ${{ !github.event.pull_request.draft }} From aa7ed02040bfee78c8f562a9aeaa6326ac3a7fdc Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 7 Jun 2023 22:00:38 +0800 Subject: [PATCH 141/516] Update CI --- .github/workflows/uclid-verifier-c-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/uclid-verifier-c-tests.yml b/.github/workflows/uclid-verifier-c-tests.yml index 083065ff93..9e21f3b9e5 100644 --- a/.github/workflows/uclid-verifier-c-tests.yml +++ b/.github/workflows/uclid-verifier-c-tests.yml @@ -41,7 +41,7 @@ jobs: - name: Install Lingua Franca working-directory: lingua-franca/ run: | - ./bin/build-lf-cli + ./gradlew assemble ./bin/lfc --version echo "$(pwd)/bin" >> $GITHUB_PATH - name: Download Z3 From 622b496033529041fc8101ac3c8da81fdc310a49 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 7 Jun 2023 10:34:17 -0700 Subject: [PATCH 142/516] Format Java files. --- core/src/main/java/org/lflang/FileConfig.java | 25 ++++---------- .../main/java/org/lflang/TargetConfig.java | 5 +-- .../main/java/org/lflang/TargetProperty.java | 10 +++--- core/src/main/java/org/lflang/TimeValue.java | 18 +++++----- .../statespace/StateSpaceExplorer.java | 3 +- .../lflang/analyses/uclid/UclidGenerator.java | 3 +- .../main/java/org/lflang/ast/ASTUtils.java | 14 ++++---- .../org/lflang/generator/GeneratorBase.java | 1 - .../org/lflang/generator/LFGenerator.java | 34 +++++++++---------- .../lflang/generator/ReactionInstance.java | 3 +- .../org/lflang/validation/AttributeSpec.java | 18 +++++----- 11 files changed, 59 insertions(+), 75 deletions(-) diff --git a/core/src/main/java/org/lflang/FileConfig.java b/core/src/main/java/org/lflang/FileConfig.java index c30a237461..9012e0963d 100644 --- a/core/src/main/java/org/lflang/FileConfig.java +++ b/core/src/main/java/org/lflang/FileConfig.java @@ -32,9 +32,7 @@ public abstract class FileConfig { /** Default name of the directory to store generated sources in. */ public static final String DEFAULT_SRC_GEN_DIR = "src-gen"; - /** - * Default name of the directory to store generated verification models in. - */ + /** Default name of the directory to store generated verification models in. */ public static final String DEFAULT_MODEL_GEN_DIR = "model-gen"; // Public fields. @@ -98,15 +96,10 @@ public abstract class FileConfig { */ protected Path srcGenPath; - /** - * Path representation of the root directory for generated - * verification models. - */ + /** Path representation of the root directory for generated verification models. */ protected Path modelGenBasePath; - /** - * The directory in which to put the generated verification models. - */ + /** The directory in which to put the generated verification models. */ protected Path modelGenPath; // private fields @@ -234,19 +227,15 @@ protected Path getSubPkgPath(Path srcPath) { } /** - * Path representation of the root directory for generated - * verification models. - * This is the root, meaning that if the source file is x/y/Z.lf - * relative to the package root, then the generated sources will be put in x/y/Z - * relative to this URI. + * Path representation of the root directory for generated verification models. This is the root, + * meaning that if the source file is x/y/Z.lf relative to the package root, then the generated + * sources will be put in x/y/Z relative to this URI. */ public Path getModelGenBasePath() { return modelGenBasePath; } - /** - * The directory in which to put the generated verification models. - */ + /** The directory in which to put the generated verification models. */ public Path getModelGenPath() { return modelGenPath; } diff --git a/core/src/main/java/org/lflang/TargetConfig.java b/core/src/main/java/org/lflang/TargetConfig.java index 0b97f29e06..25924670fc 100644 --- a/core/src/main/java/org/lflang/TargetConfig.java +++ b/core/src/main/java/org/lflang/TargetConfig.java @@ -235,10 +235,7 @@ public TargetConfig(Properties cliArgs, TargetDecl target, ErrorReporter errorRe /** If true, do not perform runtime validation. The default is false. */ public boolean noRuntimeValidation = false; - /** - * If true, do not check the generated verification model. - * The default is false. - */ + /** If true, do not check the generated verification model. The default is false. */ public boolean noVerify = false; /** diff --git a/core/src/main/java/org/lflang/TargetProperty.java b/core/src/main/java/org/lflang/TargetProperty.java index b7e4ec7233..14e3db0ea0 100644 --- a/core/src/main/java/org/lflang/TargetProperty.java +++ b/core/src/main/java/org/lflang/TargetProperty.java @@ -412,14 +412,14 @@ public enum TargetProperty { config.noRuntimeValidation = ASTUtils.toBoolean(value); }), - /** - * Directive to not check the generated verification model. - */ - NO_VERIFY("no-verify", PrimitiveType.BOOLEAN, + /** Directive to not check the generated verification model. */ + NO_VERIFY( + "no-verify", + PrimitiveType.BOOLEAN, Arrays.asList(Target.C), (config) -> ASTUtils.toElement(config.noVerify), (config, value, err) -> { - config.noVerify = ASTUtils.toBoolean(value); + config.noVerify = ASTUtils.toBoolean(value); }), /** diff --git a/core/src/main/java/org/lflang/TimeValue.java b/core/src/main/java/org/lflang/TimeValue.java index bec3b6cdbb..899fe97aa8 100644 --- a/core/src/main/java/org/lflang/TimeValue.java +++ b/core/src/main/java/org/lflang/TimeValue.java @@ -137,32 +137,30 @@ public long toNanoSeconds() { return makeNanosecs(time, unit); } - /** - * Return a TimeValue based on a nanosecond value. - */ + /** Return a TimeValue based on a nanosecond value. */ public static TimeValue fromNanoSeconds(long ns) { if (ns == 0) return ZERO; long time; if ((time = ns / 604_800_016_558_522L) > 0 && ns % 604_800_016_558_522L == 0) { - return new TimeValue(time, TimeUnit.WEEK); + return new TimeValue(time, TimeUnit.WEEK); } if ((time = ns / 86_400_000_000_000L) > 0 && ns % 86_400_000_000_000L == 0) { - return new TimeValue(time, TimeUnit.DAY); + return new TimeValue(time, TimeUnit.DAY); } if ((time = ns / 3_600_000_000_000L) > 0 && ns % 3_600_000_000_000L == 0) { - return new TimeValue(time, TimeUnit.HOUR); + return new TimeValue(time, TimeUnit.HOUR); } if ((time = ns / 60_000_000_000L) > 0 && ns % 60_000_000_000L == 0) { - return new TimeValue(time, TimeUnit.MINUTE); + return new TimeValue(time, TimeUnit.MINUTE); } if ((time = ns / 1_000_000_000) > 0 && ns % 1_000_000_000 == 0) { - return new TimeValue(time, TimeUnit.SECOND); + return new TimeValue(time, TimeUnit.SECOND); } if ((time = ns / 1_000_000) > 0 && ns % 1_000_000 == 0) { - return new TimeValue(time, TimeUnit.MILLI); + return new TimeValue(time, TimeUnit.MILLI); } if ((time = ns / 1000) > 0 && ns % 1000 == 0) { - return new TimeValue(time, TimeUnit.MICRO); + return new TimeValue(time, TimeUnit.MICRO); } return new TimeValue(ns, TimeUnit.NANO); } diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java index 394a3f26bc..8e05b95cc2 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java +++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java @@ -298,7 +298,8 @@ else if (previousTag != null && currentTag.timestamp == previousTag.timestamp) { // System.out.println("DEBUG: Stopping because eventQ is empty!"); stop = true; } else if (currentTag.timestamp > horizon.timestamp) { - // System.out.println("DEBUG: Stopping because horizon is reached! Horizon: " + horizon + "Current Tag: " + currentTag); + // System.out.println("DEBUG: Stopping because horizon is reached! Horizon: " + horizon + + // "Current Tag: " + currentTag); // System.out.println("DEBUG: EventQ: " + eventQ); stop = true; } diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java index a22beb7071..fdedc7d818 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java @@ -1519,7 +1519,8 @@ private void createMainReactorInstance() { if (this.main == null) { // Recursively build instances. This is done once because // it is the same for all federates. - this.main = new ReactorInstance(ASTUtils.toDefinition(mainDef.getReactorClass()), errorReporter); + this.main = + new ReactorInstance(ASTUtils.toDefinition(mainDef.getReactorClass()), errorReporter); var reactionInstanceGraph = this.main.assignLevels(); if (reactionInstanceGraph.nodeCount() > 0) { errorReporter.reportError("Main reactor has causality cycles. Skipping code generation."); diff --git a/core/src/main/java/org/lflang/ast/ASTUtils.java b/core/src/main/java/org/lflang/ast/ASTUtils.java index 40930fedb5..9ff57cdb79 100644 --- a/core/src/main/java/org/lflang/ast/ASTUtils.java +++ b/core/src/main/java/org/lflang/ast/ASTUtils.java @@ -146,16 +146,18 @@ public static List getAllReactors(Resource resource) { /** * Get the main reactor 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 Reactor getMainReactor(Resource resource) { - return StreamSupport.stream(IteratorExtensions.toIterable(resource.getAllContents()).spliterator(), false) - .filter(Reactor.class::isInstance) - .map(Reactor.class::cast) - .filter(it -> it.isMain()) - .findFirst() - .get(); + return StreamSupport.stream( + IteratorExtensions.toIterable(resource.getAllContents()).spliterator(), false) + .filter(Reactor.class::isInstance) + .map(Reactor.class::cast) + .filter(it -> it.isMain()) + .findFirst() + .get(); } /** diff --git a/core/src/main/java/org/lflang/generator/GeneratorBase.java b/core/src/main/java/org/lflang/generator/GeneratorBase.java index dd00740aca..7181cd6ce9 100644 --- a/core/src/main/java/org/lflang/generator/GeneratorBase.java +++ b/core/src/main/java/org/lflang/generator/GeneratorBase.java @@ -26,7 +26,6 @@ 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; diff --git a/core/src/main/java/org/lflang/generator/LFGenerator.java b/core/src/main/java/org/lflang/generator/LFGenerator.java index 1e9918e69c..893e3c5b20 100644 --- a/core/src/main/java/org/lflang/generator/LFGenerator.java +++ b/core/src/main/java/org/lflang/generator/LFGenerator.java @@ -1,15 +1,13 @@ package org.lflang.generator; import com.google.inject.Inject; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -import java.lang.reflect.Constructor; import java.nio.file.Path; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; -import java.util.Arrays; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.generator.AbstractGenerator; import org.eclipse.xtext.generator.IFileSystemAccess2; @@ -24,8 +22,6 @@ import org.lflang.federated.generator.FedASTUtils; import org.lflang.federated.generator.FedFileConfig; import org.lflang.federated.generator.FedGenerator; -import org.lflang.lf.Attribute; -import org.lflang.lf.Reactor; import org.lflang.generator.c.CFileConfig; import org.lflang.generator.c.CGenerator; import org.lflang.generator.cpp.CppFileConfig; @@ -36,6 +32,8 @@ import org.lflang.generator.rust.RustGenerator; import org.lflang.generator.ts.TSFileConfig; import org.lflang.generator.ts.TSGenerator; +import org.lflang.lf.Attribute; +import org.lflang.lf.Reactor; import org.lflang.scoping.LFGlobalScopeProvider; /** Generates code from your model files on save. */ @@ -139,10 +137,9 @@ public boolean errorsOccurred() { } /** - * Check if a clean was requested from the standalone compiler and perform - * the clean step. - * - * FIXME: the signature can be reduced to only take context. + * Check if a clean was requested from the standalone compiler and perform the clean step. + * + *

FIXME: the signature can be reduced to only take context. */ protected void cleanIfNeeded(LFGeneratorContext context) { if (context.getArgs().containsKey("clean")) { @@ -155,19 +152,20 @@ protected void cleanIfNeeded(LFGeneratorContext context) { } /** - * Check if @property is used. If so, instantiate a UclidGenerator. - * The verification model needs to be generated before the target code - * since code generation changes LF program (desugar connections, etc.). + * Check if @property is used. If so, instantiate a UclidGenerator. The verification model needs + * to be generated before the target code since code generation changes LF program (desugar + * connections, etc.). */ private void runVerifierIfPropertiesDetected(Resource resource, LFGeneratorContext lfContext) { Reactor main = ASTUtils.getMainReactor(resource); - List properties = AttributeUtils.getAttributes(main) - .stream() - .filter(attr -> attr.getAttrName().equals("property")) - .collect(Collectors.toList()); + List properties = + AttributeUtils.getAttributes(main).stream() + .filter(attr -> attr.getAttrName().equals("property")) + .collect(Collectors.toList()); if (properties.size() > 0) { - System.out.println("*** WARNING: @property is an experimental feature. Use it with caution. ***"); + System.out.println( + "*** WARNING: @property is an experimental feature. Use it with caution. ***"); // Check if Uclid5 and Z3 are installed. if (execInstalled("uclid", "--help", "uclid 0.9.5") @@ -189,7 +187,7 @@ && execInstalled("z3", "--version", "Z3 version")) { /** * A helper function for checking if a dependency is installed on the command line. - * + * * @param binaryName The name of the binary * @param arg An argument following the binary name * @param expectedSubstring An expected substring in the output diff --git a/core/src/main/java/org/lflang/generator/ReactionInstance.java b/core/src/main/java/org/lflang/generator/ReactionInstance.java index 81c79d5712..41958d921a 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstance.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstance.java @@ -120,8 +120,7 @@ public ReactionInstance( this.sources.add(timerInstance); } } else if (trigger instanceof BuiltinTriggerRef) { - var builtinTriggerInstance - = parent.getOrCreateBuiltinTrigger((BuiltinTriggerRef) trigger); + var builtinTriggerInstance = parent.getOrCreateBuiltinTrigger((BuiltinTriggerRef) trigger); this.triggers.add(builtinTriggerInstance); builtinTriggerInstance.dependentReactions.add(this); } diff --git a/core/src/main/java/org/lflang/validation/AttributeSpec.java b/core/src/main/java/org/lflang/validation/AttributeSpec.java index 6eb4277cc3..9ac1f95eec 100644 --- a/core/src/main/java/org/lflang/validation/AttributeSpec.java +++ b/core/src/main/java/org/lflang/validation/AttributeSpec.java @@ -215,15 +215,15 @@ enum AttrParamType { new AttributeSpec(List.of(new AttrParamSpec(EACH_ATTR, AttrParamType.BOOLEAN, true)))); // @property(name="", tactic="", spec="") // SMTL is the safety fragment of Metric Temporal Logic (MTL). - ATTRIBUTE_SPECS_BY_NAME.put("property", new AttributeSpec( - List.of( - new AttrParamSpec("name", AttrParamType.STRING, false), - new AttrParamSpec("tactic", AttrParamType.STRING, false), - new AttrParamSpec("spec", AttrParamType.STRING, false), - new AttrParamSpec("CT", AttrParamType.INT, true), - new AttrParamSpec("expect", AttrParamType.BOOLEAN, true) - ) - )); + ATTRIBUTE_SPECS_BY_NAME.put( + "property", + new AttributeSpec( + List.of( + new AttrParamSpec("name", AttrParamType.STRING, false), + new AttrParamSpec("tactic", AttrParamType.STRING, false), + new AttrParamSpec("spec", AttrParamType.STRING, false), + new AttrParamSpec("CT", AttrParamType.INT, true), + new AttrParamSpec("expect", AttrParamType.BOOLEAN, true)))); // attributes that are used internally only by the federated code generation ATTRIBUTE_SPECS_BY_NAME.put("_unordered", new AttributeSpec(null)); ATTRIBUTE_SPECS_BY_NAME.put( From 402fda74d3387a4fa6dc52e7b2ada40d6bdb7abe Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 8 Jun 2023 11:14:24 -0700 Subject: [PATCH 143/516] Update submodule. --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index bcdde365e8..032855f151 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit bcdde365e8a46cb9a6657fc02be76b248fd0a252 +Subproject commit 032855f15187e004caa620b90f51e046edcbc74a From 99baeeb9ee032840e7da9f8d0e1d7e415cddd5ca Mon Sep 17 00:00:00 2001 From: Abhi Gundrala Date: Thu, 8 Jun 2023 13:14:43 -0700 Subject: [PATCH 144/516] target property pico --- core/src/main/java/org/lflang/TargetProperty.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/org/lflang/TargetProperty.java b/core/src/main/java/org/lflang/TargetProperty.java index 4a68fc3796..e0562367cd 100644 --- a/core/src/main/java/org/lflang/TargetProperty.java +++ b/core/src/main/java/org/lflang/TargetProperty.java @@ -1731,6 +1731,7 @@ public enum Platform { LINUX("Linux"), MAC("Darwin"), ZEPHYR("Zephyr"), + PICO("Pico"), WINDOWS("Windows"); String cMakeName; From b79ba35b10422659ce3bc35012740ea858edbeb2 Mon Sep 17 00:00:00 2001 From: Abhi Gundrala Date: Thu, 8 Jun 2023 13:59:33 -0700 Subject: [PATCH 145/516] tests and main --- .../org/lflang/generator/c/CMainFunctionGenerator.java | 7 +++++++ test/C/src/pico/HelloPico.lf | 9 +++++++++ 2 files changed, 16 insertions(+) create mode 100644 test/C/src/pico/HelloPico.lf diff --git a/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java b/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java index 044874029f..811eda498e 100644 --- a/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java @@ -62,6 +62,13 @@ private String generateMainFunction() { " int res = lf_reactor_c_main(0, NULL);", " exit(res);", "}"); + } else if (targetConfig.platformOptions.platform == Platform.PICO) { + // Pico platform cannont use command line args. + return String.join( + "\n", + "void main(void) {", + " return lf_reactor_c_main(0, NULL);", + "}"); } else { return String.join( "\n", diff --git a/test/C/src/pico/HelloPico.lf b/test/C/src/pico/HelloPico.lf new file mode 100644 index 0000000000..ef25cdeba8 --- /dev/null +++ b/test/C/src/pico/HelloPico.lf @@ -0,0 +1,9 @@ +target C { + platform: "Pico" +} + +main reactor { + reaction(startup) {= + printf("Hello World!\n"); + =} +} \ No newline at end of file From 462f70c83bc40235f984ef86758e7785df18b232 Mon Sep 17 00:00:00 2001 From: Abhi Gundrala Date: Thu, 8 Jun 2023 15:34:47 -0700 Subject: [PATCH 146/516] pico-sdk find and include --- .../lflang/generator/c/CCmakeGenerator.java | 60 ++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index fd818b45a5..53f7b045e8 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -133,7 +133,24 @@ CodeBuilder generateCMakeCode( cMakeCode.newLine(); } - cMakeCode.pr("project(" + executableName + " LANGUAGES C)"); + if (targetConfig.platformOptions.platform == Platform.Pico) { + // resolve pico-sdk path + cMakeCode.pr("# Recommended to place the pico-sdk path in the shell environment variable") + cMakeCode.pr("if(DEFINED ENV{PICO_SDK_PATH})"); + cMakeCode.pr(" message(Found SDK path in ENV)"); + cMakeCode.pr(" set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})"); + cMakeCode.pr("else()"); + cMakeCode.pr(" message(Could not find SDK path in ENV)"); + cMakeCode.pr(" set(PICO_SDK_PATH ./pico-sdk)"); + cMakeCode.pr("endif()"); + cMakeCode.pr("message(PICO_SDK_PATH=\"${PICO_SDK_PATH}\")"); + cMakeCode.newLine(); + // include cmake + cMakeCode.pr("include(\"{PICO_SDK_PATH}/pico_sdk_init.cmake\")"); + cMakeCode.newLine(); + } + // FIXME: added ASM for pico platform might not be needed + cMakeCode.pr("project(" + executableName + " LANGUAGES C ASM)"); cMakeCode.newLine(); // The Test build type is the Debug type plus coverage generation @@ -207,6 +224,12 @@ CodeBuilder generateCMakeCode( hasMain, executableName, Stream.concat(additionalSources.stream(), sources.stream()))); + } else if (targetConfig.platformOptions.platform == Platform.Pico) { + cMakeCode.pr( + setUpMainTargetPico( + hasMain, + executableName, + Stream.concat(additionalSources.stream(), sources.stream()))); } else { cMakeCode.pr( setUpMainTarget.getCmakeCode( @@ -401,4 +424,39 @@ private static String setUpMainTargetZephyr( code.newLine(); return code.toString(); } + + private static String setUpMainTargetPico( + boolean hasMain, String executableName, Stream cSources) { + var code = new CodeBuilder(); + code.pr("add_subdirectory(core)"); + code.pr("target_link_libraries(core PUBLIC zephyr_interface)"); + // FIXME: Linking the reactor-c corelib with the zephyr kernel lib + // resolves linker issues but I am not yet sure if it is safe + code.pr("target_link_libraries(core PRIVATE kernel)"); + code.newLine(); + + if (hasMain) { + code.pr("# Declare a new executable target and list all its sources"); + code.pr("set(LF_MAIN_TARGET app)"); + code.pr("target_sources("); + } else { + code.pr("# Declare a new library target and list all its sources"); + code.pr("set(LF_MAIN_TARGET" + executableName + ")"); + code.pr("add_library("); + } + code.indent(); + code.pr("${LF_MAIN_TARGET}"); + + if (hasMain) { + code.pr("PRIVATE"); + } + + cSources.forEach(code::pr); + code.unindent(); + code.pr(")"); + code.newLine(); + return code.toString(); + } } + + From 223165f7e489f858be36a9374701e55271522de7 Mon Sep 17 00:00:00 2001 From: Abhi Gundrala Date: Thu, 8 Jun 2023 17:17:56 -0700 Subject: [PATCH 147/516] cmake generator for pico --- .../lflang/generator/c/CCmakeGenerator.java | 57 ++++++++++--------- .../generator/c/CMainFunctionGenerator.java | 2 +- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 53f7b045e8..dcbe52521a 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -133,25 +133,28 @@ CodeBuilder generateCMakeCode( cMakeCode.newLine(); } - if (targetConfig.platformOptions.platform == Platform.Pico) { + if (targetConfig.platformOptions.platform == Platform.PICO) { // resolve pico-sdk path - cMakeCode.pr("# Recommended to place the pico-sdk path in the shell environment variable") + cMakeCode.pr("# Recommended to place the pico-sdk path in the shell environment variable"); cMakeCode.pr("if(DEFINED ENV{PICO_SDK_PATH})"); - cMakeCode.pr(" message(Found SDK path in ENV)"); + cMakeCode.pr(" message(\"Found SDK path in ENV\")"); cMakeCode.pr(" set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})"); cMakeCode.pr("else()"); - cMakeCode.pr(" message(Could not find SDK path in ENV)"); + cMakeCode.pr(" message(\"Could not find SDK path in ENV\")"); cMakeCode.pr(" set(PICO_SDK_PATH ./pico-sdk)"); cMakeCode.pr("endif()"); cMakeCode.pr("message(PICO_SDK_PATH=\"${PICO_SDK_PATH}\")"); cMakeCode.newLine(); // include cmake - cMakeCode.pr("include(\"{PICO_SDK_PATH}/pico_sdk_init.cmake\")"); + cMakeCode.pr("include(\"${PICO_SDK_PATH}/pico_sdk_init.cmake\")"); + cMakeCode.newLine(); + // pico sdk uses asm cpp and c sources + cMakeCode.pr("project(" + executableName + " LANGUAGES C CXX ASM)"); + cMakeCode.newLine(); + } else { + cMakeCode.pr("project(" + executableName + " LANGUAGES C)"); cMakeCode.newLine(); } - // FIXME: added ASM for pico platform might not be needed - cMakeCode.pr("project(" + executableName + " LANGUAGES C ASM)"); - cMakeCode.newLine(); // The Test build type is the Debug type plus coverage generation cMakeCode.pr("if(CMAKE_BUILD_TYPE STREQUAL \"Test\")"); @@ -224,7 +227,7 @@ CodeBuilder generateCMakeCode( hasMain, executableName, Stream.concat(additionalSources.stream(), sources.stream()))); - } else if (targetConfig.platformOptions.platform == Platform.Pico) { + } else if (targetConfig.platformOptions.platform == Platform.PICO) { cMakeCode.pr( setUpMainTargetPico( hasMain, @@ -265,12 +268,14 @@ CodeBuilder generateCMakeCode( } if (targetConfig.threading || targetConfig.tracing != null) { - // If threaded computation is requested, add the threads option. - cMakeCode.pr("# Find threads and link to it"); - cMakeCode.pr("find_package(Threads REQUIRED)"); - cMakeCode.pr("target_link_libraries(${LF_MAIN_TARGET} PRIVATE Threads::Threads)"); - cMakeCode.newLine(); - + // dont include thread library for pico platform + if (targetConfig.platformOptions.platform != Platform.PICO) { + // If threaded computation is requested, add the threads option. + cMakeCode.pr("# Find threads and link to it"); + cMakeCode.pr("find_package(Threads REQUIRED)"); + cMakeCode.pr("target_link_libraries(${LF_MAIN_TARGET} PRIVATE Threads::Threads)"); + cMakeCode.newLine(); + } // If the LF program itself is threaded or if tracing is enabled, we need to define // NUMBER_OF_WORKERS so that platform-specific C files will contain the appropriate functions cMakeCode.pr("# Set the number of workers to enable threading/tracing"); @@ -428,35 +433,33 @@ private static String setUpMainTargetZephyr( private static String setUpMainTargetPico( boolean hasMain, String executableName, Stream cSources) { var code = new CodeBuilder(); + // FIXME: remove this and move to lingo build + code.pr("add_compile_options(-Wall -Wextra -DLF_UNTHREADED)"); + // + code.pr("pico_sdk_init()"); code.pr("add_subdirectory(core)"); - code.pr("target_link_libraries(core PUBLIC zephyr_interface)"); - // FIXME: Linking the reactor-c corelib with the zephyr kernel lib - // resolves linker issues but I am not yet sure if it is safe - code.pr("target_link_libraries(core PRIVATE kernel)"); + code.pr("target_link_libraries(core PUBLIC pico_stdlib)"); + code.pr("target_link_libraries(core PUBLIC pico_multicore)"); + code.pr("target_link_libraries(core PUBLIC pico_sync)"); code.newLine(); + code.pr("set(LF_MAIN_TARGET " + executableName + ")"); if (hasMain) { code.pr("# Declare a new executable target and list all its sources"); - code.pr("set(LF_MAIN_TARGET app)"); - code.pr("target_sources("); + code.pr("add_executable("); } else { code.pr("# Declare a new library target and list all its sources"); - code.pr("set(LF_MAIN_TARGET" + executableName + ")"); code.pr("add_library("); } code.indent(); code.pr("${LF_MAIN_TARGET}"); - - if (hasMain) { - code.pr("PRIVATE"); - } - cSources.forEach(code::pr); code.unindent(); code.pr(")"); code.newLine(); return code.toString(); } + } diff --git a/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java b/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java index 811eda498e..3e9dcd5fe2 100644 --- a/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java @@ -66,7 +66,7 @@ private String generateMainFunction() { // Pico platform cannont use command line args. return String.join( "\n", - "void main(void) {", + "int main(void) {", " return lf_reactor_c_main(0, NULL);", "}"); } else { From e42b4bd3e7545de929b975862cb9ad79a9f32a9e Mon Sep 17 00:00:00 2001 From: Abhi Gundrala Date: Thu, 8 Jun 2023 17:19:33 -0700 Subject: [PATCH 148/516] swithc to reactor-c pico support branch --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index c5613560e1..fa78ba216c 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit c5613560e160349154785ff5328ab8f1f764e504 +Subproject commit fa78ba216c375681baa73137782eed47ed3a9577 From 914028642ae5c445ecfeb674f796e99e25598689 Mon Sep 17 00:00:00 2001 From: Abhi Gundrala Date: Thu, 8 Jun 2023 17:28:02 -0700 Subject: [PATCH 149/516] patch newline --- core/src/main/java/org/lflang/generator/CodeBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/generator/CodeBuilder.java b/core/src/main/java/org/lflang/generator/CodeBuilder.java index e4e92294fb..43ccde35c6 100644 --- a/core/src/main/java/org/lflang/generator/CodeBuilder.java +++ b/core/src/main/java/org/lflang/generator/CodeBuilder.java @@ -68,7 +68,7 @@ public int length() { /** Add a new line. */ public void newLine() { - this.pr(""); + this.pr("\n"); } /** From c9b1a60495c8b36412918913321ee2988793a11f Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 8 Jun 2023 20:29:37 -0700 Subject: [PATCH 150/516] Update submodule. --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 032855f151..0bcec558d5 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 032855f15187e004caa620b90f51e046edcbc74a +Subproject commit 0bcec558d5bd8869beccd4780b392f831b3dc0e8 From 273133a956150ad8f1dd73407013460881e92ccb Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 9 Jun 2023 11:25:08 -0700 Subject: [PATCH 151/516] Fix the federated launch script (again). This fixes another way tests can pass when they should be failing. --- .../org/lflang/federated/launcher/FedLauncherGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java b/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java index b3be6acad0..c06ced57f3 100644 --- a/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java +++ b/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java @@ -203,7 +203,7 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { "# The errors are handled separately via trap.", "for pid in \"${pids[@]}\"", "do", - " wait $pid", + " wait $pid || exit $?", "done", "echo \"All done.\"", "EXITED_SUCCESSFULLY=true") From ec8513d9ed152097f649642066af559cd1631412 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 9 Jun 2023 16:57:33 -0700 Subject: [PATCH 152/516] Start implementing code generation of indexers. It is necessary to index into elements of a multiport. This is one way to do that. This change is still incomplete. --- .../federated/generator/FedASTUtils.java | 14 +++-- .../federated/generator/FedGenerator.java | 63 +++++++++++++++++++ .../federated/generator/FedMainEmitter.java | 7 ++- .../federated/generator/FederateInstance.java | 5 ++ 4 files changed, 83 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 2da640fa27..fde1a32eaf 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -206,6 +206,15 @@ private static Action createNetworkAction(FedConnectionInstance connection) { return action; } + /** Add a reactor definition with the given name to the given resource and return it. */ + public static Reactor addReactorDefinition(String name, Resource resource) { + var reactor = LfFactory.eINSTANCE.createReactor(); + reactor.setName(name); + EObject node = IteratorExtensions.findFirst(resource.getAllContents(), Model.class::isInstance); + ((Model) node).getReactors().add(reactor); + return reactor; + } + /** * Add a network receiver reactor for a given input port 'destination' to destination's parent * reactor. This reaction will react to a generated 'networkAction' (triggered asynchronously, @@ -230,7 +239,7 @@ private static void addNetworkReceiverReactor( VarRef instRef = factory.createVarRef(); // instantiation connection VarRef destRef = factory.createVarRef(); // destination connection - Reactor receiver = factory.createReactor(); + Reactor receiver = addReactorDefinition("NetworkReceiver_" + networkIDReceiver++, resource); Reaction networkReceiverReaction = factory.createReaction(); Output out = factory.createOutput(); @@ -247,9 +256,6 @@ private static void addNetworkReceiverReactor( receiver.getReactions().add(networkReceiverReaction); receiver.getOutputs().add(out); - EObject node = IteratorExtensions.findFirst(resource.getAllContents(), Model.class::isInstance); - ((Model) node).getReactors().add(receiver); - receiver.setName("NetworkReceiver_" + networkIDReceiver++); // networkReceiverReaction.setName("NetworkReceiverReaction_" + networkIDReceiver++); networkInstance.setReactorClass(receiver); diff --git a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java index 4b369dfbda..17839eaa4b 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java +++ b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java @@ -51,9 +51,11 @@ import org.lflang.generator.SendRange; import org.lflang.generator.SubContext; import org.lflang.lf.Expression; +import org.lflang.lf.Input; import org.lflang.lf.Instantiation; import org.lflang.lf.LfFactory; import org.lflang.lf.Reactor; +import org.lflang.lf.VarRef; import org.lflang.util.Averager; public class FedGenerator { @@ -484,6 +486,8 @@ private void replaceFederateConnectionsWithProxies(Reactor federation, Resource // that those contain. ReactorInstance mainInstance = new ReactorInstance(federation, errorReporter); + insertIndexers(mainInstance, resource); + for (ReactorInstance child : mainInstance.children) { for (PortInstance output : child.outputs) { replaceConnectionFromOutputPort(output, resource); @@ -498,6 +502,65 @@ private void replaceFederateConnectionsWithProxies(Reactor federation, Resource mainInstance.clearCaches(false); } + /** + * Insert reactors that split a multiport into many ports. This is necessary in order to index + * into specific entries of a multiport. + */ + private void insertIndexers(ReactorInstance mainInstance, Resource resource) { + for (ReactorInstance child : mainInstance.children) { + for (PortInstance input : child.inputs) { + var indexer = indexer(child, input, resource); + var count = 0; + for (FederateInstance federate : federatesByInstantiation.get(child.getDefinition())) { + var outerConnection = LfFactory.eINSTANCE.createConnection(); + var instantiation = LfFactory.eINSTANCE.createInstantiation(); + instantiation.setReactorClass(indexer); + instantiation.setName(indexer.getName() + count++); + federate.networkHelperInstantiations.add(instantiation); + outerConnection.getLeftPorts().add(varRefOf(instantiation, "port")); + outerConnection.getRightPorts().add(varRefOf(child.getDefinition(), input.getName())); + federate.networkConnections.add(outerConnection); + } + } + } + } + + /** + * Add an {@code indexer} to the model and return it. An indexer is a reactor that is an adapter + * from many ports to just one port + */ + private Reactor indexer(ReactorInstance reactorInstance, PortInstance input, Resource resource) { + var indexer = + FedASTUtils.addReactorDefinition( + "_" + reactorInstance.getName() + input.getName(), resource); + var output = LfFactory.eINSTANCE.createOutput(); + output.setName("port"); + indexer.getOutputs().add(output); + for (int i = 0; i < (input.isMultiport() ? input.getWidth() : 1); i++) { + var splitInput = LfFactory.eINSTANCE.createInput(); + splitInput.setName("port" + i); + indexer.getInputs().add(splitInput); + } + var innerConnection = LfFactory.eINSTANCE.createConnection(); + indexer.getInputs().stream() + .map(Input::getName) + .map(it -> FedGenerator.varRefOf(null, it)) + .forEach(innerConnection.getLeftPorts()::add); + innerConnection.getRightPorts().add(varRefOf(null, output.getName())); + indexer.getConnections().add(innerConnection); + return indexer; + } + + /** Return a {@code VarRef} with the given name. */ + private static VarRef varRefOf(Instantiation container, String name) { + var varRef = LfFactory.eINSTANCE.createVarRef(); + var variable = LfFactory.eINSTANCE.createVariable(); + variable.setName(name); + varRef.setVariable(variable); + varRef.setContainer(container); + return varRef; + } + /** * Replace the connections from the specified output port. * diff --git a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java index ecedc3b583..3fb1b87fd4 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java @@ -64,8 +64,11 @@ String generateMainReactor( .map(renderer) .collect(Collectors.joining("\n")), federate.networkReceiverInstantiations.stream() - .map(renderer) - .collect(Collectors.joining("\n")), + .map(renderer) + .collect(Collectors.joining("\n")), + federate.networkHelperInstantiations.stream() + .map(renderer) + .collect(Collectors.joining("\n")), federate.networkConnections.stream() .map(renderer) .collect(Collectors.joining("\n"))) diff --git a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java index ed6a4c7d42..5a9171c4e9 100644 --- a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java +++ b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java @@ -261,6 +261,11 @@ public Instantiation getInstantiation() { */ public List networkReceiverInstantiations = new ArrayList<>(); + /** + * List of generated instantiations that serve as helpers for forming the network connections. + */ + public List networkHelperInstantiations = new ArrayList<>(); + /** Parsed target config of the federate. */ public TargetConfig targetConfig; From de397f04d2a197ce9e0b8db008299ae55bfc3d00 Mon Sep 17 00:00:00 2001 From: Abhi Gundrala Date: Wed, 14 Jun 2023 13:54:16 -0700 Subject: [PATCH 153/516] for use in lf-pico --- core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index dcbe52521a..9c87d4047a 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -141,7 +141,7 @@ CodeBuilder generateCMakeCode( cMakeCode.pr(" set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})"); cMakeCode.pr("else()"); cMakeCode.pr(" message(\"Could not find SDK path in ENV\")"); - cMakeCode.pr(" set(PICO_SDK_PATH ./pico-sdk)"); + cMakeCode.pr(" set(PICO_SDK_PATH ./lib/pico-sdk)"); cMakeCode.pr("endif()"); cMakeCode.pr("message(PICO_SDK_PATH=\"${PICO_SDK_PATH}\")"); cMakeCode.newLine(); From 1dbe9402b95a57a9c4772975e5ba09138bc16c1b Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 14 Jun 2023 17:41:30 -0700 Subject: [PATCH 154/516] DistributedMultiport compiles and almost works. In the beginning, several messages are dropped. --- .../org/lflang/federated/generator/FedASTUtils.java | 12 ++++++++---- .../org/lflang/federated/generator/FedGenerator.java | 7 +++++++ .../lflang/federated/generator/FederateInstance.java | 4 +++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index fde1a32eaf..3b491a04ce 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -316,15 +316,19 @@ private static void addNetworkReceiverReactor( // Establish references to the involved ports. sourceRef.setContainer(connection.getSourcePortInstance().getParent().getDefinition()); sourceRef.setVariable(connection.getSourcePortInstance().getDefinition()); - destRef.setContainer(connection.getDestinationPortInstance().getParent().getDefinition()); - destRef.setVariable(connection.getDestinationPortInstance().getDefinition()); + destRef.setContainer( + connection + .getDstFederate() + .networkPortToIndexer + .get(connection.getDestinationPortInstance())); + var v = LfFactory.eINSTANCE.createVariable(); + v.setName("port" + connection.getDstChannel()); + destRef.setVariable(v); instRef.setContainer(networkInstance); instRef.setVariable(out); out.setName("msg"); out.setType(type); - out.setWidthSpec( - EcoreUtil.copy(connection.getDestinationPortInstance().getDefinition().getWidthSpec())); outRef.setVariable(out); // Add the output port at the receiver reactor as an effect diff --git a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java index 17839eaa4b..0ffa5e7b80 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java +++ b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java @@ -20,8 +20,10 @@ import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; + import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.xtext.generator.JavaIoFileSystemAccess; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.resource.XtextResourceSet; @@ -512,10 +514,12 @@ private void insertIndexers(ReactorInstance mainInstance, Resource resource) { var indexer = indexer(child, input, resource); var count = 0; for (FederateInstance federate : federatesByInstantiation.get(child.getDefinition())) { + federate.networkReactors.add(indexer); var outerConnection = LfFactory.eINSTANCE.createConnection(); var instantiation = LfFactory.eINSTANCE.createInstantiation(); instantiation.setReactorClass(indexer); instantiation.setName(indexer.getName() + count++); + federate.networkPortToIndexer.put(input, instantiation); federate.networkHelperInstantiations.add(instantiation); outerConnection.getLeftPorts().add(varRefOf(instantiation, "port")); outerConnection.getRightPorts().add(varRefOf(child.getDefinition(), input.getName())); @@ -534,11 +538,14 @@ private Reactor indexer(ReactorInstance reactorInstance, PortInstance input, Res FedASTUtils.addReactorDefinition( "_" + reactorInstance.getName() + input.getName(), resource); var output = LfFactory.eINSTANCE.createOutput(); + output.setWidthSpec(EcoreUtil.copy(input.getDefinition().getWidthSpec())); + output.setType(EcoreUtil.copy(input.getDefinition().getType())); output.setName("port"); indexer.getOutputs().add(output); for (int i = 0; i < (input.isMultiport() ? input.getWidth() : 1); i++) { var splitInput = LfFactory.eINSTANCE.createInput(); splitInput.setName("port" + i); + splitInput.setType(EcoreUtil.copy(input.getDefinition().getType())); indexer.getInputs().add(splitInput); } var innerConnection = LfFactory.eINSTANCE.createConnection(); diff --git a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java index 5a9171c4e9..7728ea536e 100644 --- a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java +++ b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java @@ -76,7 +76,7 @@ * @author Edward A. Lee * @author Soroush Bateni */ -public class FederateInstance { // why does this not extend ReactorInstance? +public class FederateInstance { /** * Construct a new instance with the specified instantiation of of a top-level reactor. The @@ -243,6 +243,8 @@ public Instantiation getInstantiation() { */ public Map networkPortToInstantiation = new HashMap<>(); + public Map networkPortToIndexer = new HashMap<>(); + /** * List of generated network connections (network input and outputs) that belong to this federate * instance. From 5fd6baba22983875bb9c299ee06603b668121ed0 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 14 Jun 2023 19:27:42 -0700 Subject: [PATCH 155/516] Pass DistributedMultiport. --- core/src/main/resources/lib/c/reactor-c | 2 +- test/C/src/federated/DistributedMultiport.lf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 0bcec558d5..936ff80d1b 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 0bcec558d5bd8869beccd4780b392f831b3dc0e8 +Subproject commit 936ff80d1b2536e52b050ff9484c4c0636438e80 diff --git a/test/C/src/federated/DistributedMultiport.lf b/test/C/src/federated/DistributedMultiport.lf index 7590710339..864f4e57d8 100644 --- a/test/C/src/federated/DistributedMultiport.lf +++ b/test/C/src/federated/DistributedMultiport.lf @@ -23,7 +23,7 @@ reactor Destination { reaction(in) {= for (int i = 0; i < in_width; i++) { if (in[i]->is_present) { - lf_print("Received %d.", in[i]->value); + lf_print("Received %d at %d.", in[i]->value, i); if (in[i]->value != self->count++) { lf_print_error_and_exit("Expected %d.", self->count - 1); } From 47fb539c3972519ddf76e5d5d66cc22bf6e30148 Mon Sep 17 00:00:00 2001 From: revol-xut Date: Thu, 15 Jun 2023 19:21:56 +0200 Subject: [PATCH 156/516] adjusting code generation for declarative port graphs --- .../cpp/CppAssembleMethodGenerator.kt | 45 ++++++------------- .../generator/cpp/CppConnectionGenerator.kt | 12 ++--- org.lflang/src/lib/cpp/reactor-cpp | 1 + 3 files changed, 21 insertions(+), 37 deletions(-) create mode 160000 org.lflang/src/lib/cpp/reactor-cpp diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt index 0bb63e24c3..93f70ef548 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt @@ -100,6 +100,12 @@ class CppAssembleMethodGenerator(private val reactor: Reactor) { } } + private val Connection.cppDelay: String + get() = if (delay != null) "${delay.toCppTime()}" else "0s" + + private val Connection.properties: String + get() = "ConnectionProperties{$cppType, $cppDelay, nullptr}" + 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 @@ -153,17 +159,9 @@ class CppAssembleMethodGenerator(private val reactor: Reactor) { val leftPort = c.leftPorts[0] val rightPort = c.rightPorts[0] - if (c.requiresConnectionClass) - """ - // connection $idx - ${c.name}.bind_upstream_port(&${leftPort.name}); - ${c.name}.bind_downstream_port(&${rightPort.name}); - """.trimIndent() - else - """ - // connection $idx - ${leftPort.name}.bind_to(&${rightPort.name}); - """.trimIndent() + """ + left.environment()->draw_connection(${leftPort.name}, ${rightPort.name}, ${c.properties}) + """.trimIndent() } /** @@ -199,26 +197,11 @@ class CppAssembleMethodGenerator(private val reactor: Reactor) { } private fun Connection.getConnectionLambda(portType: String): String { - return when { - isEnclaveConnection -> """ - [this]($portType left, $portType right, std::size_t idx) { - $name.push_back(std::make_unique<$cppType>( - "$name" + std::to_string(idx), right->environment()${if (delay != null) ", ${delay.toCppTime()}" else ""})); - $name.back()->bind_upstream_port(left); - $name.back()->bind_downstream_port(right); - } - """.trimIndent() - - requiresConnectionClass -> """ - [this]($portType left, $portType right, std::size_t idx) { - $name.push_back(std::make_unique<$cppType>("$name" + std::to_string(idx), this, ${delay.toCppTime()})); - $name.back()->bind_upstream_port(left); - $name.back()->bind_downstream_port(right); - } - """.trimIndent() - - else -> "[]($portType left, $portType right, [[maybe_unused]]std::size_t idx) { left->bind_to(right); }" - } + return """ + [this](const BasePort& left, const BasePort& right, std::size_t idx) { + left.environment()->draw_connection(left, right, $properties) + } + """.trimIndent() } private fun addAllPortsToVector(varRef: VarRef, vectorName: String): String = diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionGenerator.kt index c9af656291..a88cd20b3f 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionGenerator.kt @@ -28,14 +28,14 @@ class CppConnectionGenerator(private val reactor: Reactor) { val leftPort = leftPorts.first() return when { isEnclaveConnection -> when { - isPhysical -> "reactor::PhysicalEnclaveConnection<${leftPort.dataType}>" - delay != null -> "reactor::DelayedEnclaveConnection<${leftPort.dataType}>" - else -> "reactor::EnclaveConnection<${leftPort.dataType}>" + isPhysical -> "ConnectionType::PhysicalEnclaved" + delay != null -> "ConnectionType::DelayedEnclaved" + else -> "ConnectionType::Enclaved" } - isPhysical -> "reactor::PhysicalConnection<${leftPort.dataType}>" - delay != null -> "reactor::DelayedConnection<${leftPort.dataType}>" - else -> throw IllegalArgumentException("Unsupported connection type") + isPhysical -> "ConnectionType::Physical" + delay != null -> "ConnectionType::Delayed" + else -> "ConnectionType::Normal" } } diff --git a/org.lflang/src/lib/cpp/reactor-cpp b/org.lflang/src/lib/cpp/reactor-cpp new file mode 160000 index 0000000000..b607f1f640 --- /dev/null +++ b/org.lflang/src/lib/cpp/reactor-cpp @@ -0,0 +1 @@ +Subproject commit b607f1f64083ab531e7a676b29de75f076aa1cde From 679ad1822aebd0369d84642bc359d5577448e271 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 15 Jun 2023 17:40:14 -0700 Subject: [PATCH 157/516] Try to get more test output. --- core/src/testFixtures/java/org/lflang/tests/LFTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/testFixtures/java/org/lflang/tests/LFTest.java b/core/src/testFixtures/java/org/lflang/tests/LFTest.java index c38160150c..64e7b36dc3 100644 --- a/core/src/testFixtures/java/org/lflang/tests/LFTest.java +++ b/core/src/testFixtures/java/org/lflang/tests/LFTest.java @@ -258,8 +258,7 @@ private Thread recordStream(StringBuffer builder, InputStream inputStream) { char[] buf = new char[1024]; while ((len = reader.read(buf)) > 0) { builder.append(buf, 0, len); - if (Runtime.getRuntime().freeMemory() - < Runtime.getRuntime().totalMemory() * 3 / 4) { + if (Runtime.getRuntime().freeMemory() < Runtime.getRuntime().totalMemory() / 3) { builder.delete(0, builder.length() / 2); } } From c90c84bb6d034188be30f4e7c421924712970114 Mon Sep 17 00:00:00 2001 From: revol-xut Date: Fri, 16 Jun 2023 10:16:52 +0200 Subject: [PATCH 158/516] updating reactor-cpp --- 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 b607f1f640..f6551c8a12 160000 --- a/org.lflang/src/lib/cpp/reactor-cpp +++ b/org.lflang/src/lib/cpp/reactor-cpp @@ -1 +1 @@ -Subproject commit b607f1f64083ab531e7a676b29de75f076aa1cde +Subproject commit f6551c8a12dd8870e8bf065b192ba8f2ada35ef2 From 08896ddd2dac0997b382b79c5c062449f52d5779 Mon Sep 17 00:00:00 2001 From: revol-xut Date: Fri, 16 Jun 2023 10:55:26 +0200 Subject: [PATCH 159/516] updating reactor-cpp --- .gitmodules | 2 +- org.lflang/src/lib/cpp/reactor-cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 9cd89ffe1a..2bbfceff4a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,7 @@ url = https://github.com/lf-lang/reactor-c.git [submodule "org.lflang/src/lib/cpp/reactor-cpp"] path = core/src/main/resources/lib/cpp/reactor-cpp - url = https://github.com/lf-lang/reactor-cpp + url = https://github.com/lf-lang/reactor-cpp/connection-optimization [submodule "org.lflang/src/lib/rs/reactor-rs"] path = core/src/main/resources/lib/rs/reactor-rs url = https://github.com/lf-lang/reactor-rs diff --git a/org.lflang/src/lib/cpp/reactor-cpp b/org.lflang/src/lib/cpp/reactor-cpp index f6551c8a12..b607f1f640 160000 --- a/org.lflang/src/lib/cpp/reactor-cpp +++ b/org.lflang/src/lib/cpp/reactor-cpp @@ -1 +1 @@ -Subproject commit f6551c8a12dd8870e8bf065b192ba8f2ada35ef2 +Subproject commit b607f1f64083ab531e7a676b29de75f076aa1cde From 49c88443373ce537d1246b4f3bfd6376854b0fbb Mon Sep 17 00:00:00 2001 From: revol-xut Date: Fri, 16 Jun 2023 11:02:08 +0200 Subject: [PATCH 160/516] updating reactor-cpp --- .gitmodules | 3 ++- org.lflang/src/lib/cpp/reactor-cpp | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) delete mode 160000 org.lflang/src/lib/cpp/reactor-cpp diff --git a/.gitmodules b/.gitmodules index 2bbfceff4a..036edd9440 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,8 @@ url = https://github.com/lf-lang/reactor-c.git [submodule "org.lflang/src/lib/cpp/reactor-cpp"] path = core/src/main/resources/lib/cpp/reactor-cpp - url = https://github.com/lf-lang/reactor-cpp/connection-optimization + url = https://github.com/lf-lang/reactor-cpp.git + branch = "connection-optimization" [submodule "org.lflang/src/lib/rs/reactor-rs"] path = core/src/main/resources/lib/rs/reactor-rs url = https://github.com/lf-lang/reactor-rs diff --git a/org.lflang/src/lib/cpp/reactor-cpp b/org.lflang/src/lib/cpp/reactor-cpp deleted file mode 160000 index b607f1f640..0000000000 --- a/org.lflang/src/lib/cpp/reactor-cpp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b607f1f64083ab531e7a676b29de75f076aa1cde From 6b31083f3606c46b980e3682a9a39400abcdcd72 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 16 Jun 2023 13:31:32 -0700 Subject: [PATCH 161/516] Actually pass DistributedMultiport this time. --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 936ff80d1b..170208fed6 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 936ff80d1b2536e52b050ff9484c4c0636438e80 +Subproject commit 170208fed6c207d223527ccf1c8ca9c278f694a4 From 88312d5c01c2cc30aea41b632fcafa7875793495 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 17 Jun 2023 16:34:41 -0700 Subject: [PATCH 162/516] Get ChainWithDelay to pass locally. --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 170208fed6..00f28dd1ec 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 170208fed6c207d223527ccf1c8ca9c278f694a4 +Subproject commit 00f28dd1ecd2e05eb35a52462859026b1dd8d9ca From 4e8d1572b14a72c8bb57ce3552f8753da31fa2d5 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 17 Jun 2023 18:54:32 -0700 Subject: [PATCH 163/516] Get StopAtShutdown to pass locally. --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 00f28dd1ec..c1dd61ee09 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 00f28dd1ecd2e05eb35a52462859026b1dd8d9ca +Subproject commit c1dd61ee091bae697ad76530412fe0d2ed3e38ae From 3d0670bbc1ab601aef63b7d0760d59b9d0b2ab47 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Sun, 18 Jun 2023 16:59:17 +0200 Subject: [PATCH 164/516] use the antlr gradle plugin --- .../org.lflang.antlr-conventions.gradle | 11 ++++ core/build.gradle | 61 ++++++------------- .../org/lflang/dsl/antlr4 => antlr}/C.g4 | 0 .../lflang/dsl/antlr4 => antlr}/MTLLexer.g4 | 0 .../lflang/dsl/antlr4 => antlr}/MTLParser.g4 | 0 gradle.properties | 2 + 6 files changed, 33 insertions(+), 41 deletions(-) create mode 100644 buildSrc/src/main/groovy/org.lflang.antlr-conventions.gradle rename core/src/main/{java/org/lflang/dsl/antlr4 => antlr}/C.g4 (100%) rename core/src/main/{java/org/lflang/dsl/antlr4 => antlr}/MTLLexer.g4 (100%) rename core/src/main/{java/org/lflang/dsl/antlr4 => antlr}/MTLParser.g4 (100%) diff --git a/buildSrc/src/main/groovy/org.lflang.antlr-conventions.gradle b/buildSrc/src/main/groovy/org.lflang.antlr-conventions.gradle new file mode 100644 index 0000000000..634230b72f --- /dev/null +++ b/buildSrc/src/main/groovy/org.lflang.antlr-conventions.gradle @@ -0,0 +1,11 @@ +plugins { + id 'antlr' +} + +repositories { + mavenCentral() +} + +dependencies { + antlr "org.antlr:antlr4:${antlrVersion}" +} diff --git a/core/build.gradle b/core/build.gradle index 10dae91f90..c5ab6fbb02 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,41 +1,35 @@ plugins { id 'org.lflang.java-library-conventions' id 'org.lflang.kotlin-conventions' + id 'org.lflang.antlr-conventions' } sourceSets { main { java { - srcDirs = ['src/main/java', 'src-gen'] + srcDirs += ['src-gen'] } resources { - srcDirs = ['src/main/resources', 'src-gen'] + srcDirs += ['src-gen'] } } test { java { - srcDirs = ['src/test/java', 'test-gen'] + srcDirs += ['test-gen'] } } testFixtures { java { - srcDirs = ['src/testFixtures/java', 'test-gen'] + srcDirs +=['test-gen'] } } } -// Antlr4 -configurations { - antlr4 -} - dependencies { api "org.eclipse.xtext:org.eclipse.xtext:$xtextVersion" api "org.eclipse.xtext:org.eclipse.xtext.xbase.lib:$xtextVersion" api "org.eclipse.xtext:org.eclipse.xtext.ide:$xtextVersion" - implementation "org.eclipse.emf:org.eclipse.emf.mwe2.launch:$mwe2LaunchVersion" - implementation "com.fasterxml.jackson.core:jackson-core:$fasterxmlVersion" implementation "com.fasterxml.jackson.core:jackson-annotations:$fasterxmlVersion" implementation "com.fasterxml.jackson.core:jackson-databind:$fasterxmlVersion" @@ -49,9 +43,7 @@ dependencies { exclude group: 'org.eclipse.platform', module: 'org.eclipse.swt' } - antlr4 'org.antlr:antlr4:4.7.2' - implementation 'org.antlr:antlr4-runtime:4.7.2' - implementation 'org.json:json:20200518' // JSON + implementation "org.json:json:$jsonVersion" testImplementation "org.junit.jupiter:junit-jupiter-api:$jupiterVersion" testImplementation "org.junit.jupiter:junit-jupiter-engine:$jupiterVersion" @@ -63,15 +55,13 @@ dependencies { } configurations { - mwe2 { - extendsFrom implementation - } + mwe2 } dependencies { - mwe2 'org.eclipse.emf:org.eclipse.emf.mwe2.launch' - mwe2 "org.eclipse.xtext:org.eclipse.xtext.common.types:${xtextVersion}" - mwe2 "org.eclipse.xtext:org.eclipse.xtext.xtext.generator:${xtextVersion}" + mwe2 "org.eclipse.emf:org.eclipse.emf.mwe2.launch:$mwe2LaunchVersion" + mwe2 "org.eclipse.xtext:org.eclipse.xtext.common.types:$xtextVersion" + mwe2 "org.eclipse.xtext:org.eclipse.xtext.xtext.generator:$xtextVersion" } tasks.register('generateXtextLanguage', JavaExec) { @@ -88,31 +78,20 @@ tasks.register('generateXtextLanguage', JavaExec) { args += "rootPath=/${projectDir}/.." } -// Add Antlr4 for various DSLs, including MTL for verification. -tasks.register('runAntlr4', JavaExec) { - //see incremental task api, prevents rerun if nothing has changed. - inputs.dir "$projectDir/src/main/java/org/lflang/dsl/antlr4/" - outputs.dir "$projectDir/src/main/java/org/lflang/dsl/generated/antlr4/main/" - - classpath = configurations.antlr4 +compileJava.dependsOn(generateXtextLanguage) +compileKotlin.dependsOn(generateXtextLanguage) +processResources.dependsOn(generateXtextLanguage) +clean.dependsOn(cleanGenerateXtextLanguage) +spotlessJava.mustRunAfter(generateXtextLanguage) +rootProject.spotlessMisc.mustRunAfter(generateXtextLanguage) - main = "org.antlr.v4.Tool" - args = [ "-visitor", - "-o", "$projectDir/src/main/java/org/lflang/dsl/generated/antlr4/main/", - "-package", "org.lflang.dsl", - "$projectDir/src/main/java/org/lflang/dsl/antlr4/MTLLexer.g4", - "$projectDir/src/main/java/org/lflang/dsl/antlr4/MTLParser.g4", - "$projectDir/src/main/java/org/lflang/dsl/antlr4/C.g4"] +// antlr4 configuration +generateGrammarSource { + arguments += ['-visitor', '-package', 'org.lflang.dsl'] } +compileKotlin.dependsOn(generateGrammarSource) -compileJava.dependsOn(generateXtextLanguage, runAntlr4) -compileKotlin.dependsOn(generateXtextLanguage, runAntlr4) -processResources.dependsOn(generateXtextLanguage, runAntlr4) -clean.dependsOn(cleanGenerateXtextLanguage) -spotlessJava.mustRunAfter(generateXtextLanguage) -runAntlr4.mustRunAfter(spotlessJava) -rootProject.spotlessMisc.mustRunAfter(generateXtextLanguage, runAntlr4) tasks.register('getSubmoduleVersions', Exec) { description('Run a Git command to get the current status of submodules') diff --git a/core/src/main/java/org/lflang/dsl/antlr4/C.g4 b/core/src/main/antlr/C.g4 similarity index 100% rename from core/src/main/java/org/lflang/dsl/antlr4/C.g4 rename to core/src/main/antlr/C.g4 diff --git a/core/src/main/java/org/lflang/dsl/antlr4/MTLLexer.g4 b/core/src/main/antlr/MTLLexer.g4 similarity index 100% rename from core/src/main/java/org/lflang/dsl/antlr4/MTLLexer.g4 rename to core/src/main/antlr/MTLLexer.g4 diff --git a/core/src/main/java/org/lflang/dsl/antlr4/MTLParser.g4 b/core/src/main/antlr/MTLParser.g4 similarity index 100% rename from core/src/main/java/org/lflang/dsl/antlr4/MTLParser.g4 rename to core/src/main/antlr/MTLParser.g4 diff --git a/gradle.properties b/gradle.properties index 8d2599bdf6..db47ec6323 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,6 +16,8 @@ picocliVersion=4.7.0 resourcesVersion=3.16.0 xtextVersion=2.28.0 klighdVersion=2.2.1-SNAPSHOT +antlrVersion=4.7.2 +jsonVersion=20200518 [manifestPropertyNames] org.eclipse.xtext=xtextVersion From fd9a94cfaf73ab54d6630e951a6895f1e48d9152 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Sun, 18 Jun 2023 15:53:46 -0700 Subject: [PATCH 165/516] Update gradle.properties --- gradle.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 199752eda8..28cac25560 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,6 +10,7 @@ jacocoVersion=0.8.7 jsonVersion=20200518 jupiterVersion=5.8.2 jUnitPlatformVersion=1.8.2 +klighdVersion=2.3.0.v20230606 kotlinJvmTarget=17 lsp4jVersion=0.14.0 mwe2LaunchVersion=2.12.2 @@ -23,4 +24,4 @@ org.eclipse.xtext=xtextVersion org.eclipse.lsp4j=lsp4jVersion org.opentest4j=openTest4jVersion org.eclipse.core.resources=resourcesVersion -org.junit.jupiter=jupiterVersion \ No newline at end of file +org.junit.jupiter=jupiterVersion From fff61cf29ea4c5f76fce7f08604fce6bb9eaf184 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Mon, 19 Jun 2023 10:48:21 +0200 Subject: [PATCH 166/516] don't format generated java files --- buildSrc/src/main/groovy/org.lflang.java-conventions.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/groovy/org.lflang.java-conventions.gradle b/buildSrc/src/main/groovy/org.lflang.java-conventions.gradle index 4cbea8bf20..b5d922535b 100644 --- a/buildSrc/src/main/groovy/org.lflang.java-conventions.gradle +++ b/buildSrc/src/main/groovy/org.lflang.java-conventions.gradle @@ -9,7 +9,7 @@ repositories { spotless { java { - targetExclude 'src-gen/**', 'test-gen/**' + targetExclude 'src-gen/**', 'test-gen/**', 'build/**' // The following is quoted from https://github.com/google/google-java-format // "Note: There is no configurability as to the formatter's algorithm for formatting. // This is a deliberate design decision to unify our code formatting on a single format." From 56127752316d66de3d7411f5380cfe51948cf829 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Mon, 19 Jun 2023 14:51:11 +0200 Subject: [PATCH 167/516] fix kotlin antlr dependencies --- buildSrc/src/main/groovy/org.lflang.antlr-conventions.gradle | 5 +++++ core/build.gradle | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/groovy/org.lflang.antlr-conventions.gradle b/buildSrc/src/main/groovy/org.lflang.antlr-conventions.gradle index 634230b72f..291cba30ae 100644 --- a/buildSrc/src/main/groovy/org.lflang.antlr-conventions.gradle +++ b/buildSrc/src/main/groovy/org.lflang.antlr-conventions.gradle @@ -9,3 +9,8 @@ repositories { dependencies { antlr "org.antlr:antlr4:${antlrVersion}" } + +if (project.tasks.findByName('compileKotlin')) { + // make all kotlin compile tasks depent on the antl generation tasks + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).each(it -> it.dependsOn(tasks.withType(AntlrTask))) +} diff --git a/core/build.gradle b/core/build.gradle index b65f657370..afc272355c 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -93,8 +93,6 @@ rootProject.spotlessMisc.mustRunAfter(generateXtextLanguage) generateGrammarSource { arguments += ['-visitor', '-package', 'org.lflang.dsl'] } -compileKotlin.dependsOn(generateGrammarSource) - tasks.register('getSubmoduleVersions', Exec) { description('Run a Git command to get the current status of submodules') From 2d9d653c1b351ae00c5071d2a579304ab2bb4803 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Mon, 19 Jun 2023 14:53:50 +0200 Subject: [PATCH 168/516] clean up source sets --- core/build.gradle | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/core/build.gradle b/core/build.gradle index afc272355c..61fd39ce3a 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -13,19 +13,6 @@ sourceSets { srcDirs += ['src-gen'] } } - test { - java { - srcDirs = ['src/test/java'] - } - resources { - srcDirs = [ 'src/test/resources' ] - } - } - testFixtures { - java { - srcDirs = ['src/testFixtures/java'] - } - } } dependencies { From ea6f05a08cbb6b07bbd4a82203ecd23dae0fe1dc Mon Sep 17 00:00:00 2001 From: Abhi Gundrala Date: Mon, 19 Jun 2023 11:44:54 -0700 Subject: [PATCH 169/516] extra launch scripts --- .../lflang/generator/c/CCmakeGenerator.java | 26 +- .../org/lflang/generator/c/CGenerator.java | 12 + .../resources/lib/platform/pico/launch.json | 29 + .../pico/pico_extras_import_optional.cmake | 59 ++ .../lib/platform/pico/pico_sdk_import.cmake | 73 +++ .../resources/lib/platform/pico/pico_setup.sh | 549 ++++++++++++++++++ 6 files changed, 730 insertions(+), 18 deletions(-) create mode 100644 core/src/main/resources/lib/platform/pico/launch.json create mode 100644 core/src/main/resources/lib/platform/pico/pico_extras_import_optional.cmake create mode 100644 core/src/main/resources/lib/platform/pico/pico_sdk_import.cmake create mode 100755 core/src/main/resources/lib/platform/pico/pico_setup.sh diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 9c87d4047a..3daa04a924 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -131,24 +131,13 @@ CodeBuilder generateCMakeCode( cMakeCode.pr("# We recommend Zephyr v3.3.0 but we are compatible with older versions also"); cMakeCode.pr("find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE} 3.3.0)"); cMakeCode.newLine(); - } - - if (targetConfig.platformOptions.platform == Platform.PICO) { - // resolve pico-sdk path - cMakeCode.pr("# Recommended to place the pico-sdk path in the shell environment variable"); - cMakeCode.pr("if(DEFINED ENV{PICO_SDK_PATH})"); - cMakeCode.pr(" message(\"Found SDK path in ENV\")"); - cMakeCode.pr(" set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})"); - cMakeCode.pr("else()"); - cMakeCode.pr(" message(\"Could not find SDK path in ENV\")"); - cMakeCode.pr(" set(PICO_SDK_PATH ./lib/pico-sdk)"); - cMakeCode.pr("endif()"); - cMakeCode.pr("message(PICO_SDK_PATH=\"${PICO_SDK_PATH}\")"); - cMakeCode.newLine(); - // include cmake - cMakeCode.pr("include(\"${PICO_SDK_PATH}/pico_sdk_init.cmake\")"); + } else if (targetConfig.platformOptions.platform == Platform.PICO) { + cMakeCode.pr("message(\"Run ./pico_setup.sh for unix systems in a chosen directory.\")"); + cMakeCode.pr("message(\"The script will download all required dependencies in /pico.\")"); cMakeCode.newLine(); - // pico sdk uses asm cpp and c sources + // include cmake before project + cMakeCode.pr("include(pico_sdk_import.cmake)"); + cMakeCode.pr("include(pico_extras_import_optional.cmake)"); cMakeCode.pr("project(" + executableName + " LANGUAGES C CXX ASM)"); cMakeCode.newLine(); } else { @@ -435,8 +424,9 @@ private static String setUpMainTargetPico( var code = new CodeBuilder(); // FIXME: remove this and move to lingo build code.pr("add_compile_options(-Wall -Wextra -DLF_UNTHREADED)"); - // + // initialize sdk code.pr("pico_sdk_init()"); + code.newLine(); code.pr("add_subdirectory(core)"); code.pr("target_link_libraries(core PUBLIC pico_stdlib)"); code.pr("target_link_libraries(core PUBLIC pico_multicore)"); diff --git a/core/src/main/java/org/lflang/generator/c/CGenerator.java b/core/src/main/java/org/lflang/generator/c/CGenerator.java index 7bed894341..32cfd0dc41 100644 --- a/core/src/main/java/org/lflang/generator/c/CGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CGenerator.java @@ -967,6 +967,18 @@ protected void copyTargetFiles() throws IOException { FileUtil.copyFileFromClassPath( "/lib/platform/zephyr/Kconfig", fileConfig.getSrcGenPath(), true); } + + if (targetConfig.platformOptions.platform == Platform.PICO) { + Path vscodePath = Path.of(fileConfig.getSrcGenPath() + File.separator + ".vscode"); + FileUtil.copyFileFromClassPath( + "/lib/platform/pico/pico_setup.sh", fileConfig.getSrcGenPath(), true); + FileUtil.copyFileFromClassPath( + "/lib/platform/pico/pico_sdk_import.cmake", fileConfig.getSrcGenPath(), true); + FileUtil.copyFileFromClassPath( + "/lib/platform/pico/pico_extras_import_optional.cmake", fileConfig.getSrcGenPath(), true); + FileUtil.copyFileFromClassPath( + "/lib/platform/pico/launch.json", vscodePath, true); + } } //////////////////////////////////////////// diff --git a/core/src/main/resources/lib/platform/pico/launch.json b/core/src/main/resources/lib/platform/pico/launch.json new file mode 100644 index 0000000000..21eec34b84 --- /dev/null +++ b/core/src/main/resources/lib/platform/pico/launch.json @@ -0,0 +1,29 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug", + "cwd": "${workspaceRoot}", + "executable": "${command:cmake.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + // This may need to be "arm-none-eabi-gdb" for some previous builds + "gdbPath" : "gdb-multiarch", + "device": "RP2040", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/rp2040.cfg" + ], + "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd", + "runToEntryPoint": "main", + // Work around for stopping at main on restart + "postRestartCommands": [ + "break main", + "continue" + ], + "showDevDebugOutput": "raw", + "openOCDLaunchCommands": ["adapter speed 5000"] + } + ] +} diff --git a/core/src/main/resources/lib/platform/pico/pico_extras_import_optional.cmake b/core/src/main/resources/lib/platform/pico/pico_extras_import_optional.cmake new file mode 100644 index 0000000000..692e14ad9d --- /dev/null +++ b/core/src/main/resources/lib/platform/pico/pico_extras_import_optional.cmake @@ -0,0 +1,59 @@ +# This is a copy of /external/pico_extras_import.cmake + +# This can be dropped into an external project to help locate pico-extras +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_EXTRAS_PATH} AND (NOT PICO_EXTRAS_PATH)) + set(PICO_EXTRAS_PATH $ENV{PICO_EXTRAS_PATH}) + message("Using PICO_EXTRAS_PATH from environment ('${PICO_EXTRAS_PATH}')") +endif () + +if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT)) + set(PICO_EXTRAS_FETCH_FROM_GIT $ENV{PICO_EXTRAS_FETCH_FROM_GIT}) + message("Using PICO_EXTRAS_FETCH_FROM_GIT from environment ('${PICO_EXTRAS_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT_PATH)) + set(PICO_EXTRAS_FETCH_FROM_GIT_PATH $ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH}) + message("Using PICO_EXTRAS_FETCH_FROM_GIT_PATH from environment ('${PICO_EXTRAS_FETCH_FROM_GIT_PATH}')") +endif () + +if (NOT PICO_EXTRAS_PATH) + if (PICO_EXTRAS_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_EXTRAS_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_extras + GIT_REPOSITORY https://github.com/raspberrypi/pico-extras + GIT_TAG master + ) + if (NOT pico_extras) + message("Downloading Raspberry Pi Pico Extras") + FetchContent_Populate(pico_extras) + set(PICO_EXTRAS_PATH ${pico_extras_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + if (PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../pico-extras") + set(PICO_EXTRAS_PATH ${PICO_SDK_PATH}/../pico-extras) + message("Defaulting PICO_EXTRAS_PATH as sibling of PICO_SDK_PATH: ${PICO_EXTRAS_PATH}") + endif() + endif () +endif () + +if (PICO_EXTRAS_PATH) + set(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" CACHE PATH "Path to the PICO EXTRAS") + set(PICO_EXTRAS_FETCH_FROM_GIT "${PICO_EXTRAS_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO EXTRAS from git if not otherwise locatable") + set(PICO_EXTRAS_FETCH_FROM_GIT_PATH "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download EXTRAS") + + get_filename_component(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") + if (NOT EXISTS ${PICO_EXTRAS_PATH}) + message(FATAL_ERROR "Directory '${PICO_EXTRAS_PATH}' not found") + endif () + + set(PICO_EXTRAS_PATH ${PICO_EXTRAS_PATH} CACHE PATH "Path to the PICO EXTRAS" FORCE) + add_subdirectory(${PICO_EXTRAS_PATH} pico_extras) +endif() \ No newline at end of file diff --git a/core/src/main/resources/lib/platform/pico/pico_sdk_import.cmake b/core/src/main/resources/lib/platform/pico/pico_sdk_import.cmake new file mode 100644 index 0000000000..65f8a6f7db --- /dev/null +++ b/core/src/main/resources/lib/platform/pico/pico_sdk_import.cmake @@ -0,0 +1,73 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + GIT_SUBMODULES_RECURSE FALSE + ) + else () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + endif () + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/core/src/main/resources/lib/platform/pico/pico_setup.sh b/core/src/main/resources/lib/platform/pico/pico_setup.sh new file mode 100755 index 0000000000..853a08c185 --- /dev/null +++ b/core/src/main/resources/lib/platform/pico/pico_setup.sh @@ -0,0 +1,549 @@ +#!/usr/bin/env bash + +# Phase 0: Preflight check +# Verify baseline dependencies + +# Phase 1: Setup dev environment +# Install the software packages from APT or Homebrew +# Create a directory called pico +# Download the pico-sdk repository and submodules +# Define env variables: PICO_SDK_PATH +# On Raspberry Pi only: configure the UART for use with Raspberry Pi Pico + +# Phase 2: Setting up tutorial repos +# Download pico-examples, pico-extras, pico-playground repositories, and submodules +# Build the blink and hello_world examples + +# Phase 3: Recommended tools +# Download and build picotool (see Appendix B), and copy it to /usr/local/bin. +# Download and build picoprobe (see Appendix A) and OpenOCD +# Download and install Visual Studio Code and required extensions + + +# Exit on error +set -e + +# Trying to use a non-existent variable is an error +set -u + +# if printenv DEBUG >& /dev/null; then + # Show all commands + set -x + + env +# fi + +# Number of cores when running make +JNUM=4 + +# Where will the output go? +if printenv TARGET_DIR; then + echo "Using target dir from \$TARGET_DIR: ${TARGET_DIR}" +else + TARGET_DIR="$(pwd)/pico" + echo "Using target dir: ${TARGET_DIR}" +fi + +linux() { + # Returns true iff this is running on Linux + uname | grep -q "^Linux$" + return ${?} +} + +raspbian() { + # Returns true iff this is running on Raspbian or close derivative such as Raspberry Pi OS, but not necessarily on a Raspberry Pi computer + grep -q '^NAME="Raspbian GNU/Linux"$' /etc/os-release + return ${?} +} + +debian() { + # Returns true iff this is running on Debian + grep -q '^NAME="Debian GNU/Linux"$' /etc/os-release + return ${?} +} + +ubuntu() { + # Returns true iff this is running on Ubuntu + grep -q '^NAME="Ubuntu"$' /etc/os-release + return ${?} +} + +mac() { + # Returns true iff this is running on macOS and presumably Apple hardware + uname | grep -q "^Darwin$" + return ${?} +} + +raspberry_pi() { + # Returns true iff this is running on a Raspberry Pi computer, regardless of the OS + if [ -f /proc/cpuinfo ]; then + grep -q "^Model\s*: Raspberry Pi" /proc/cpuinfo + return ${?} + fi + return 1 +} + +sudo_wrapper() { + # Some platforms have different needs for invoking sudo. This wrapper encapsulates that complexity. + # The output of this function should be a string on stdout, which will be used as a command. Example: + # `$(sudo_wrapper) whoami` + # The above may equate to: + # `sudo -i whoami` + + if [ "${USER}" = root ]; then + # if we're already root, don't sudo at all. Relevant to some Docker images that don't have sudo but already run as root. + return + fi + + # EC2 AMIs tend to have the user password unset, so you can't sudo without -i. It will cd /root, so you have to be + # careful with relative paths in the command. + echo sudo -i +} + +phase_0() { + # Preflight check + # Checks the baseline dependencies. If you don't have these, this script won't work. + echo "Entering phase 0: Preflight check" + + if mac; then + echo "Running on macOS" + if which brew >> /dev/null; then + echo "Found brew" + brew update + else + echo -e 'This script requires Homebrew, the missing package manager for macOS. See https://docs.brew.sh/Installation. For quick install, run:\n/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"' + echo "Stopping." + exit 1 + fi + else + if linux; then + echo "Running on Linux" + else + echo "Platform $(uname) not recognized. Use at your own risk. Continuing as though this were Linux." + fi + + if which apt >> /dev/null; then + echo "Found apt" + $(sudo_wrapper) apt update + else + echo 'This script requires apt, the default package manager for Debian and Debian-derived distros such as Ubuntu and Raspberry Pi OS.' + echo "Stopping." + exit 1 + fi + fi +} + +fail() { + # Outputs a failure message and exits with the error code output by the previous call. + # All args are echoed as a failure message. + + R="${?}" + echo "Validation failed! :'-(" + if [ ${*} ]; then + echo "${*}" + fi + exit ${R} +} + +validate_git_repo() { + # tests that the given relative path exists and is a git repo + git -C ${TARGET_DIR}/${1} status >& /dev/null || fail +} + +validate_toolchain_linux() { + # test that the expected packages are installed + dpkg-query -s git cmake gcc-arm-none-eabi build-essential gdb-multiarch automake autoconf build-essential texinfo libtool libftdi-dev libusb-1.0-0-dev >& /dev/null || fail +} + +install_dev_env_deps_linux() { + # Install development environment dependencies for Linux + + # Avoid a certain dependency by installing ssh-client without recommends. See + # https://github.com/raspberrypi/pico-setup/pull/20#discussion_r608793993 for details. + $(sudo_wrapper) apt install -y --no-install-recommends ssh-client + + DEPS="autoconf automake build-essential cmake gcc-arm-none-eabi gdb-multiarch git libftdi-dev libtool libusb-1.0-0-dev minicom pkg-config python3 texinfo" + if debian || ubuntu; then + DEPS="${DEPS} libstdc++-arm-none-eabi-newlib" + fi + $(sudo_wrapper) apt install -y ${DEPS} +} + +brew_install_idempotent() { + # For some reason, brew install is not idempotent. This function succeeds even when the package is already installed. + brew list ${*} || brew install ${*} + return ${?} +} + +validate_toolchain_mac() { + # test that the expected packages are installed + brew list git cmake pkg-config libtool automake libusb wget pkg-config gcc texinfo arm-none-eabi-gcc >& /dev/null +} + +install_dev_env_deps_mac() { + # Install development environment dependencies for mac + + brew_install_idempotent ArmMbed/homebrew-formulae/arm-none-eabi-gcc automake cmake git libtool libusb gcc minicom pkg-config texinfo wget +} + +create_TARGET_DIR() { + # Creates ./pico directory if necessary + + mkdir -p "${TARGET_DIR}" +} + +clone_raspberrypi_repo() { + # Clones the given repo name from GitHub and inits any submodules + # $1 should be the full name of the repo, ex: pico-sdk + # $2 should be the branch name. Defaults to master. + # all other args are passed to git clone + REPO_NAME="${1}" + if shift && [ ${#} -gt 0 ]; then + BRANCH="${1}" + # Can't just say `shift` because `set -e` will think it's an error and terminate the script. + shift || true + else + BRANCH=master + fi + + REPO_URL="https://github.com/raspberrypi/${REPO_NAME}.git" + REPO_DIR="${TARGET_DIR}/${REPO_NAME}" + + if [ -d "${REPO_DIR}" ]; then + echo "${REPO_DIR} already exists. Updating." + git -C "${REPO_DIR}" pull --ff-only + else + echo "Cloning ${REPO_URL}" + if [ ${#} -gt 0 ]; then + git -C "${TARGET_DIR}" clone -b "${BRANCH}" "${REPO_URL}" ${*} + else + git -C "${TARGET_DIR}" clone -b "${BRANCH}" "${REPO_URL}" + fi + + # Any submodules + git -C "${REPO_DIR}" submodule update --init + fi +} + +warn_for_bashrc() { + # Earlier versions of this script set environment variables in .bashrc. The location has moved to .profile or + # .zprofile. If the user has a .bashrc defining any pico dev env variables, they could conflict with the settings + # in the other files. This function raises a warning for the user. + + REGEX="^\s*export\s+\"?PICO_SDK_PATH=" + if grep -q "${REGEX}" ~/.bashrc; then + echo "Your ~/.bashrc file contains the following line, which may conflict with this script's settings. We recommend removing it to prevent possible issues." + echo -n " "; grep "${REGEX}" ~/.bashrc + fi +} + +set_env() { + # Permanently sets an environment variable by adding it to the current user's profile script + # The form of the arguments should be `FOO foo`, which sets the environment variable `FOO=foo` + NAME="${1}" + VALUE="${2}" + EXPR="${NAME}=${VALUE}" + + # detect appropriate file for setting env vars + if echo "${SHELL}" | grep -q zsh; then + # zsh detected + FILE=~/.zprofile + else + # sh, bash and others + FILE=~/.profile + fi + + # ensure that appends go to a new line + if [ -f "${FILE}" ]; then + if ! ( tail -n 1 "${FILE}" | grep -q "^$" ); then + # FILE exists but has no trailing newline. Adding newline. + echo >> "${FILE}" + fi + fi + + # set for now + export "${EXPR}" + + # set for later + REGEX="^\s*export\s+\"?${NAME}=" + if grep -q "${REGEX}" "${FILE}"; then + # Warn the user + echo "Your ${FILE} already contains the following environment variable definition(s):" + grep "${REGEX}" "${FILE}" + echo "This script would normally set the following line. We're adding it, but commented out, so that you can choose which you want." + echo "export \"${EXPR}\"" + # Write to file + echo "# pico_setup.sh commented out the following line because it conflicts with another line in this file. You should choose one or the other." >> "${FILE}" + echo "# export \"${EXPR}\"" >> "${FILE}" + else + echo "Setting env variable ${EXPR} in ${FILE}" + echo "export \"${EXPR}\"" >> "${FILE}" + fi +} + +validate_pico_sdk() { + validate_git_repo pico-sdk + + # test that the SDK env var is set and correct + test "${PICO_SDK_PATH}" = "${TARGET_DIR}/pico-sdk" || fail +} + +setup_sdk() { + # Download the SDK + clone_raspberrypi_repo pico-sdk + + # Set env var PICO_SDK_PATH + set_env PICO_SDK_PATH "${TARGET_DIR}/pico-sdk" +} + +validate_uart() { + # test that the UART is configured. Only works on Raspberry Pi OS on Raspberry Pi hardware. + dpkg-query -s minicom >& /dev/null || fail + grep -q "enable_uart=1" /boot/config.txt || fail + # note that the test for console=serial0 tests for the absence of a string + grep -q "console=serial0" /boot/cmdline.txt && fail +} + +enable_uart() { + # Enable UART + echo "Disabling Linux serial console (UART) so we can use it for pico" + $(sudo_wrapper) raspi-config nonint do_serial 2 + echo "You must run sudo reboot to finish UART setup" +} + +phase_1() { + # Setup minimum dev environment + echo "Entering phase 1: Setup minimum dev environment" + + if mac; then + install_dev_env_deps_mac + validate_toolchain_mac + else + install_dev_env_deps_linux + validate_toolchain_linux + fi + + create_TARGET_DIR + setup_sdk + validate_pico_sdk + + if raspberry_pi && which raspi-config >> /dev/null; then + enable_uart + validate_uart + else + echo "Not configuring UART, because either this is not a Raspberry Pi computer, or raspi-config is not available." + fi +} + +build_examples() { + # Build a couple of examples + echo "Building selected examples" + + # Save the working directory + pushd "${TARGET_DIR}/pico-examples" >> /dev/null + + mkdir -p build + cd build + cmake ../ -DCMAKE_BUILD_TYPE=Debug + + for EXAMPLE in blink hello_world; do + echo "Building $EXAMPLE" + cd "$EXAMPLE" + make -j${JNUM} + cd .. + done + + # Restore the working directory + popd >> /dev/null +} + +validate_pico_extras() { + validate_git_repo pico-extras +} + +validate_pico_examples() { + validate_git_repo pico-examples + + # test that blink is built + test -f ${TARGET_DIR}/pico-examples/build/blink/blink.uf2 || fail + + # test that hello_serial is built + test -f ${TARGET_DIR}/pico-examples/build/hello_world/serial/hello_serial.uf2 || fail + + # test that hello_usb is built + test -f ${TARGET_DIR}/pico-examples/build/hello_world/usb/hello_usb.uf2 || fail +} + +validate_pico_playground() { + validate_git_repo pico-playground +} + +phase_2() { + # Setup tutorial repos + echo "Entering phase 2: Setting up tutorial repos" + + for REPO_NAME in pico-examples pico-extras pico-playground; do + clone_raspberrypi_repo "${REPO_NAME}" + done + + build_examples + + validate_pico_examples + validate_pico_extras + validate_pico_playground +} + +validate_picotool() { + validate_git_repo picotool + + # test that the binary is built + test -x ${TARGET_DIR}/picotool/build/picotool || fail + + # test that picotool is installed in the expected location + test -x /usr/local/bin/picotool || fail +} + +setup_picotool() { + # Downloads, builds, and installs picotool + echo "Setting up picotool" + + # Save the working directory + pushd "${TARGET_DIR}" >> /dev/null + + clone_raspberrypi_repo picotool + cd "${TARGET_DIR}/picotool" + mkdir -p build + cd build + cmake ../ + make -j${JNUM} + + echo "Installing picotool to /usr/local/bin/picotool" + $(sudo_wrapper) cp "${TARGET_DIR}/picotool/build/picotool" /usr/local/bin/ + + # Restore the working directory + popd >> /dev/null +} + +validate_openocd() { + validate_git_repo openocd + + # test that the binary is built + test -x ${TARGET_DIR}/openocd/src/openocd || fail +} + +setup_openocd() { + # Download, build, and install OpenOCD for picoprobe and bit-banging without picoprobe + echo "Setting up OpenOCD" + + # Save the working directory + pushd "${TARGET_DIR}" >> /dev/null + + clone_raspberrypi_repo openocd picoprobe --depth=1 + cd "${TARGET_DIR}/openocd" + ./bootstrap + OPTS="--enable-ftdi --enable-bcm2835gpio --enable-picoprobe" + if linux; then + # sysfsgpio is only available on linux + OPTS="${OPTS} --enable-sysfsgpio" + fi + ./configure ${OPTS} + make -j${JNUM} + $(sudo_wrapper) make -C "${TARGET_DIR}/openocd" install + + # Restore the working directory + popd >> /dev/null +} + +validate_picoprobe() { + validate_git_repo picoprobe || fail + + # test that the binary is built + test -f ${TARGET_DIR}/picoprobe/build/picoprobe.uf2 || fail +} + +setup_picoprobe() { + # Download and build picoprobe. Requires that OpenOCD is already setup + echo "Setting up picoprobe" + + # Save the working directory + pushd "${TARGET_DIR}" >> /dev/null + + clone_raspberrypi_repo picoprobe + cd "${TARGET_DIR}/picoprobe" + mkdir -p build + cd build + cmake .. + make -j${JNUM} + + # Restore the working directory + popd >> /dev/null +} + +validate_vscode_linux() { + dpkg-query -s code >& /dev/null || fail +} + +install_vscode_linux() { + # Install Visual Studio Code + + # VS Code is specially added to Raspberry Pi OS repos, but might not be present on Debian/Ubuntu. So we check first. + if ! apt-cache show code >& /dev/null; then + echo "It appears that your APT repos do not offer Visual Studio Code. Skipping." + return + fi + + echo "Installing Visual Studio Code" + + $(sudo_wrapper) apt install -y code + + # Get extensions + code --install-extension marus25.cortex-debug + code --install-extension ms-vscode.cmake-tools + code --install-extension ms-vscode.cpptools +} + +validate_vscode_mac() { + echo "Not yet implemented: testing Visual Studio Code on macOS" +} + +install_vscode_mac() { + echo "Not yet implemented: installing Visual Studio Code on macOS" +} + +phase_3() { + # Setup recommended tools + echo "Setting up recommended tools" + + setup_picotool + validate_picotool + + setup_openocd + validate_openocd + + setup_picoprobe + validate_picoprobe + + # Install Visual Studio Code + if mac; then + install_vscode_mac + validate_vscode_mac + else + if dpkg-query -s xserver-xorg >& /dev/null; then + install_vscode_linux + validate_vscode_linux + else + echo "Not installing Visual Studio Code because it looks like XWindows is not installed." + fi + fi +} + +main() { + phase_0 + phase_1 + phase_2 + phase_3 + + echo "Congratulations, installation is complete. :D" +} + +main From 15287b76c92530e3b766e493a9f0161ac491996f Mon Sep 17 00:00:00 2001 From: revol-xut Date: Wed, 21 Jun 2023 11:11:15 +0200 Subject: [PATCH 170/516] fixing code generation --- .../generator/cpp/CppAssembleMethodGenerator.kt | 8 ++++---- .../lflang/generator/cpp/CppConnectionGenerator.kt | 12 ++++++------ .../org/lflang/generator/cpp/CppReactorGenerator.kt | 5 ----- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt index 93f70ef548..460bdf8f9e 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt @@ -104,7 +104,7 @@ class CppAssembleMethodGenerator(private val reactor: Reactor) { get() = if (delay != null) "${delay.toCppTime()}" else "0s" private val Connection.properties: String - get() = "ConnectionProperties{$cppType, $cppDelay, nullptr}" + get() = "reactor::ConnectionProperties{$cppType, $cppDelay, nullptr}" private fun declareTrigger(reaction: Reaction, trigger: TriggerRef): String = if (trigger is VarRef && trigger.variable is Port) { @@ -160,7 +160,7 @@ class CppAssembleMethodGenerator(private val reactor: Reactor) { val rightPort = c.rightPorts[0] """ - left.environment()->draw_connection(${leftPort.name}, ${rightPort.name}, ${c.properties}) + this->environment()->draw_connection(${leftPort.name}, ${rightPort.name}, ${c.properties}); """.trimIndent() } @@ -183,7 +183,7 @@ class CppAssembleMethodGenerator(private val reactor: Reactor) { // if the connection is an interleaved connection, than we change the order on the right side and iterate over ports before banks. return with(PrependOperator) { """ - |// connection $idx + |// connection $idx REEEEEEEEE |std::vector<$portType> __lf_left_ports_$idx; ${" |"..c.leftPorts.joinWithLn { addAllPortsToVector(it, "__lf_left_ports_$idx") }} |std::vector<$portType> __lf_right_ports_$idx; @@ -199,7 +199,7 @@ class CppAssembleMethodGenerator(private val reactor: Reactor) { private fun Connection.getConnectionLambda(portType: String): String { return """ [this](const BasePort& left, const BasePort& right, std::size_t idx) { - left.environment()->draw_connection(left, right, $properties) + left.environment()->draw_connection(left, right, $properties); } """.trimIndent() } diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionGenerator.kt index a88cd20b3f..50d67ef097 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionGenerator.kt @@ -28,14 +28,14 @@ class CppConnectionGenerator(private val reactor: Reactor) { val leftPort = leftPorts.first() return when { isEnclaveConnection -> when { - isPhysical -> "ConnectionType::PhysicalEnclaved" - delay != null -> "ConnectionType::DelayedEnclaved" - else -> "ConnectionType::Enclaved" + isPhysical -> "reactor::ConnectionType::PhysicalEnclaved" + delay != null -> "reactor::ConnectionType::DelayedEnclaved" + else -> "reactor::ConnectionType::Enclaved" } - isPhysical -> "ConnectionType::Physical" - delay != null -> "ConnectionType::Delayed" - else -> "ConnectionType::Normal" + isPhysical -> "reactor::ConnectionType::Physical" + delay != null -> "reactor::ConnectionType::Delayed" + else -> "reactor::ConnectionType::Normal" } } diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppReactorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppReactorGenerator.kt index 78df4519f2..7408098047 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppReactorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppReactorGenerator.kt @@ -56,7 +56,6 @@ class CppReactorGenerator(private val reactor: Reactor, fileConfig: CppFileConfi private val ports = CppPortGenerator(reactor) private val reactions = CppReactionGenerator(reactor, ports, instances) private val assemble = CppAssembleMethodGenerator(reactor) - private val connections = CppConnectionGenerator(reactor) private fun publicPreamble() = reactor.preambles.filter { it.isPublic } @@ -122,9 +121,6 @@ class CppReactorGenerator(private val reactor: Reactor, fileConfig: CppFileConfi ${" | "..outerConstructorSignature(false)}; | | void assemble() override; - | - | private: - ${" | "..connections.generateDeclarations()} |}; | ${" |"..if (reactor.isGeneric) """#include "$implHeaderFile"""" else ""} @@ -188,7 +184,6 @@ class CppReactorGenerator(private val reactor: Reactor, fileConfig: CppFileConfi ${" | "..timers.generateInitializers()} ${" | "..actions.generateInitializers()} ${" | "..reactions.generateReactionViewInitializers()} - ${" | "..connections.generateInitializers()} |{ ${" | "..ports.generateConstructorInitializers()} ${" | "..instances.generateConstructorInitializers()} From 37fdbfeb58de11755232739acaef4916fd43037f Mon Sep 17 00:00:00 2001 From: revol-xut Date: Wed, 21 Jun 2023 11:12:13 +0200 Subject: [PATCH 171/516] updating reactor-cpp --- core/src/main/resources/lib/cpp/reactor-cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/cpp/reactor-cpp b/core/src/main/resources/lib/cpp/reactor-cpp index b607f1f640..f6551c8a12 160000 --- a/core/src/main/resources/lib/cpp/reactor-cpp +++ b/core/src/main/resources/lib/cpp/reactor-cpp @@ -1 +1 @@ -Subproject commit b607f1f64083ab531e7a676b29de75f076aa1cde +Subproject commit f6551c8a12dd8870e8bf065b192ba8f2ada35ef2 From 684e937068eaba45f56ad26b032cbb36346cb667 Mon Sep 17 00:00:00 2001 From: revol-xut Date: Wed, 21 Jun 2023 11:15:12 +0200 Subject: [PATCH 172/516] removing debug comment --- .../org/lflang/generator/cpp/CppAssembleMethodGenerator.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt index 460bdf8f9e..e28aa5206a 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt @@ -183,7 +183,7 @@ class CppAssembleMethodGenerator(private val reactor: Reactor) { // if the connection is an interleaved connection, than we change the order on the right side and iterate over ports before banks. return with(PrependOperator) { """ - |// connection $idx REEEEEEEEE + |// connection $idx |std::vector<$portType> __lf_left_ports_$idx; ${" |"..c.leftPorts.joinWithLn { addAllPortsToVector(it, "__lf_left_ports_$idx") }} |std::vector<$portType> __lf_right_ports_$idx; From be77b455770d7f669dfb96d3c234a486969fe6a7 Mon Sep 17 00:00:00 2001 From: "Shaokai (Jerry) Lin" Date: Thu, 22 Jun 2023 16:22:34 +0800 Subject: [PATCH 173/516] Apply suggestions from @petervdonovan Co-authored-by: Peter Donovan <33707478+petervdonovan@users.noreply.github.com> --- .github/workflows/only-c.yml | 1 - .../main/java/org/lflang/analyses/cast/AstUtils.java | 10 ++++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/only-c.yml b/.github/workflows/only-c.yml index bbbc48db3b..e3208bb3fe 100644 --- a/.github/workflows/only-c.yml +++ b/.github/workflows/only-c.yml @@ -44,5 +44,4 @@ jobs: # Run the Uclid-based LF Verifier tests. uclid: - if: ${{ inputs.all || github.event.pull_request.draft }} uses: ./.github/workflows/uclid-verifier-c-tests.yml diff --git a/core/src/main/java/org/lflang/analyses/cast/AstUtils.java b/core/src/main/java/org/lflang/analyses/cast/AstUtils.java index 1b87562cb4..bf6a6275d9 100644 --- a/core/src/main/java/org/lflang/analyses/cast/AstUtils.java +++ b/core/src/main/java/org/lflang/analyses/cast/AstUtils.java @@ -34,7 +34,7 @@ public static CAst.AstNode takeDisjunction(List conditions) { } else if (conditions.size() == 1) { return conditions.get(0); } else { - // Take the conjunction of all the conditions. + // Take the disjunction of all the conditions. CAst.LogicalOrNode top = new CAst.LogicalOrNode(); CAst.LogicalOrNode cur = top; for (int i = 0; i < conditions.size() - 1; i++) { @@ -50,9 +50,11 @@ public static CAst.AstNode takeDisjunction(List conditions) { } } - // A handy function for debugging ASTs. - // It prints the stack trace of the visitor functions - // and shows the text matched by the ANTLR rules. + /** + * A handy function for debugging ASTs. + * It prints the stack trace of the visitor functions + * and shows the text matched by the ANTLR rules. + */ public static void printStackTraceAndMatchedText(ParserRuleContext ctx) { System.out.println("========== AST DEBUG =========="); From d4ed4d5020ed159eefefcb736320c6bc06d8bd92 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 22 Jun 2023 16:24:29 +0800 Subject: [PATCH 174/516] Apply more suggestions from @petervdonovan --- .github/workflows/uclid-verifier-c-tests.yml | 2 +- .../java/org/lflang/analyses/cast/IfNormalFormAstVisitor.java | 1 - .../src/main/java/org/lflang/analyses/statespace/StateInfo.java | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/uclid-verifier-c-tests.yml b/.github/workflows/uclid-verifier-c-tests.yml index 9e21f3b9e5..e17e152408 100644 --- a/.github/workflows/uclid-verifier-c-tests.yml +++ b/.github/workflows/uclid-verifier-c-tests.yml @@ -24,7 +24,7 @@ jobs: - name: Check out lf-verifier-benchmarks repository uses: actions/checkout@v3 with: - repository: lsk567/lf-verifier-benchmarks + repository: lf-lang/lf-verifier-benchmarks ref: main path: lf-verifier-benchmarks - name: Check out Uclid5 repository diff --git a/core/src/main/java/org/lflang/analyses/cast/IfNormalFormAstVisitor.java b/core/src/main/java/org/lflang/analyses/cast/IfNormalFormAstVisitor.java index 180fe1f45f..a8ad9e8284 100644 --- a/core/src/main/java/org/lflang/analyses/cast/IfNormalFormAstVisitor.java +++ b/core/src/main/java/org/lflang/analyses/cast/IfNormalFormAstVisitor.java @@ -27,7 +27,6 @@ public class IfNormalFormAstVisitor extends CBaseAstVisitor { @Override public Void visitStatementSequenceNode( CAst.StatementSequenceNode node, List conditions) { - // Create a new StatementSequenceNode. for (CAst.AstNode child : node.children) { visit(child, conditions); } diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateInfo.java b/core/src/main/java/org/lflang/analyses/statespace/StateInfo.java index 8226d784d7..89c3bd19de 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/StateInfo.java +++ b/core/src/main/java/org/lflang/analyses/statespace/StateInfo.java @@ -1,9 +1,9 @@ -/** A class that represents information in a step in a counterexample trace */ package org.lflang.analyses.statespace; import java.util.ArrayList; import java.util.HashMap; +/** A class that represents information in a step in a counterexample trace */ public class StateInfo { public ArrayList reactions = new ArrayList<>(); From 6d92a9e103d21f09a0fbf9673c667796416e0c84 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 22 Jun 2023 16:29:13 +0800 Subject: [PATCH 175/516] Apply spotless --- core/src/main/java/org/lflang/analyses/cast/AstUtils.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/cast/AstUtils.java b/core/src/main/java/org/lflang/analyses/cast/AstUtils.java index bf6a6275d9..65f1c28852 100644 --- a/core/src/main/java/org/lflang/analyses/cast/AstUtils.java +++ b/core/src/main/java/org/lflang/analyses/cast/AstUtils.java @@ -51,9 +51,8 @@ public static CAst.AstNode takeDisjunction(List conditions) { } /** - * A handy function for debugging ASTs. - * It prints the stack trace of the visitor functions - * and shows the text matched by the ANTLR rules. + * A handy function for debugging ASTs. It prints the stack trace of the visitor functions and + * shows the text matched by the ANTLR rules. */ public static void printStackTraceAndMatchedText(ParserRuleContext ctx) { System.out.println("========== AST DEBUG =========="); From 02b2bc66670329f0d30da2b674f88f411791463e Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 22 Jun 2023 21:48:57 +0800 Subject: [PATCH 176/516] Apply spotless --- core/src/main/java/org/lflang/generator/LFGenerator.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/java/org/lflang/generator/LFGenerator.java b/core/src/main/java/org/lflang/generator/LFGenerator.java index db06945633..024ca94ff7 100644 --- a/core/src/main/java/org/lflang/generator/LFGenerator.java +++ b/core/src/main/java/org/lflang/generator/LFGenerator.java @@ -2,7 +2,6 @@ import com.google.inject.Inject; import com.google.inject.Injector; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; From 77b5aa16df3a08ab6d20542f4ce775da0343e43b Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 22 Jun 2023 22:13:38 +0800 Subject: [PATCH 177/516] Relocate doc comments --- .../main/java/org/lflang/analyses/statespace/Event.java | 2 +- .../java/org/lflang/analyses/statespace/EventQueue.java | 2 +- .../org/lflang/analyses/statespace/StateSpaceDiagram.java | 3 +-- .../lflang/analyses/statespace/StateSpaceExplorer.java | 2 +- .../org/lflang/analyses/statespace/StateSpaceNode.java | 2 +- .../src/main/java/org/lflang/analyses/statespace/Tag.java | 8 ++++---- .../main/java/org/lflang/analyses/uclid/UclidRunner.java | 2 +- 7 files changed, 10 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/statespace/Event.java b/core/src/main/java/org/lflang/analyses/statespace/Event.java index ce96c878aa..b04b9861a0 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/Event.java +++ b/core/src/main/java/org/lflang/analyses/statespace/Event.java @@ -1,8 +1,8 @@ -/** A node in the state space diagram representing a step in the execution of an LF program. */ package org.lflang.analyses.statespace; import org.lflang.generator.TriggerInstance; +/** A node in the state space diagram representing a step in the execution of an LF program. */ public class Event implements Comparable { public TriggerInstance trigger; diff --git a/core/src/main/java/org/lflang/analyses/statespace/EventQueue.java b/core/src/main/java/org/lflang/analyses/statespace/EventQueue.java index 67cc485f68..b04339d465 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/EventQueue.java +++ b/core/src/main/java/org/lflang/analyses/statespace/EventQueue.java @@ -1,8 +1,8 @@ -/** An event queue implementation that sorts events by time tag order */ package org.lflang.analyses.statespace; import java.util.PriorityQueue; +/** An event queue implementation that sorts events by time tag order */ public class EventQueue extends PriorityQueue { /** diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java index 82d46f0e4a..d4583a90ca 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java +++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java @@ -1,4 +1,3 @@ -/** A directed graph representing the state space of an LF program. */ package org.lflang.analyses.statespace; import java.util.List; @@ -9,7 +8,7 @@ import org.lflang.generator.ReactionInstance; import org.lflang.graph.DirectedGraph; -// FIXME: Use a linkedlist instead. +/** A directed graph representing the state space of an LF program. */ public class StateSpaceDiagram extends DirectedGraph { /** The first node of the state space diagram. */ diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java index 8e05b95cc2..ff0087fad6 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java +++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java @@ -1,4 +1,3 @@ -/** Explores the state space of an LF program. */ package org.lflang.analyses.statespace; import java.util.ArrayList; @@ -20,6 +19,7 @@ import org.lflang.lf.Time; import org.lflang.lf.Variable; +/** Explores the state space of an LF program. */ public class StateSpaceExplorer { // Instantiate an empty state space diagram. diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java index a2f0918b02..42fb1b043e 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java +++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java @@ -1,4 +1,3 @@ -/** A node in the state space diagram representing a step in the execution of an LF program. */ package org.lflang.analyses.statespace; import java.util.ArrayList; @@ -9,6 +8,7 @@ import org.lflang.generator.ReactionInstance; import org.lflang.generator.TriggerInstance; +/** A node in the state space diagram representing a step in the execution of an LF program. */ public class StateSpaceNode { public int index; // Set in StateSpaceDiagram.java diff --git a/core/src/main/java/org/lflang/analyses/statespace/Tag.java b/core/src/main/java/org/lflang/analyses/statespace/Tag.java index 718db3e6ad..ddbd15abb8 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/Tag.java +++ b/core/src/main/java/org/lflang/analyses/statespace/Tag.java @@ -1,11 +1,11 @@ -/** - * Class representing a logical time tag, which is a pair that consists of a timestamp (type long) - * and a microstep (type long). - */ package org.lflang.analyses.statespace; import org.lflang.TimeValue; +/** + * Class representing a logical time tag, which is a pair that consists of a timestamp (type long) + * and a microstep (type long). + */ public class Tag implements Comparable { public long timestamp; diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidRunner.java b/core/src/main/java/org/lflang/analyses/uclid/UclidRunner.java index 4b34fd9cfa..b2fe3ecc71 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/UclidRunner.java +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidRunner.java @@ -1,4 +1,3 @@ -/** (EXPERIMENTAL) Runner for Uclid5 models. */ package org.lflang.analyses.uclid; import java.io.IOException; @@ -19,6 +18,7 @@ import org.lflang.generator.GeneratorCommandFactory; import org.lflang.util.LFCommand; +/** (EXPERIMENTAL) Runner for Uclid5 models. */ public class UclidRunner { /** A list of paths to the generated files */ From baa5061c6297b989a6d26beb728cd368b20b36c0 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 22 Jun 2023 22:30:41 +0800 Subject: [PATCH 178/516] Remove some inconsistent comments --- .../org/lflang/analyses/cast/BuildAstParseTreeVisitor.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java b/core/src/main/java/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java index 97e1bd40be..fe9fbea1f1 100644 --- a/core/src/main/java/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java +++ b/core/src/main/java/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java @@ -261,13 +261,10 @@ public CAst.AstNode visitPostfixExpression(PostfixExpressionContext ctx) { } CAst.VariableNode varNode = (CAst.VariableNode) primaryExprNode; if (varNode.name.equals("self")) { - // return a state variable node. return new CAst.StateVarNode(ctx.Identifier().get(0).getText()); } else if (ctx.Identifier().get(0).getText().equals("value")) { - // return a trigger present node. return new CAst.TriggerValueNode(varNode.name); } else if (ctx.Identifier().get(0).getText().equals("is_present")) { - // return a trigger value node. return new CAst.TriggerIsPresentNode(varNode.name); } else { // Generic pointer dereference, unanalyzable. From d2407d57a9264fac3847d0d45ace6fbc4427c8f0 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 22 Jun 2023 16:18:53 -0700 Subject: [PATCH 179/516] Pass DistributedCountPhysical. --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index c1dd61ee09..0412c1d6c6 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit c1dd61ee091bae697ad76530412fe0d2ed3e38ae +Subproject commit 0412c1d6c6723f2a3f6044c4a0588a69f27f31c8 From ae6614f979758ea734ed5e34cba77044bfdab1e5 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 22 Jun 2023 16:24:01 -0700 Subject: [PATCH 180/516] Format. --- .../java/org/lflang/federated/generator/FedGenerator.java | 1 - .../org/lflang/federated/generator/FedMainEmitter.java | 8 ++++---- .../org/lflang/federated/generator/FederateInstance.java | 4 +--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java index 0ffa5e7b80..32101f4969 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java +++ b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java @@ -20,7 +20,6 @@ import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; - import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EcoreUtil; diff --git a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java index 3fb1b87fd4..53b8b7dc84 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java @@ -64,11 +64,11 @@ String generateMainReactor( .map(renderer) .collect(Collectors.joining("\n")), federate.networkReceiverInstantiations.stream() - .map(renderer) - .collect(Collectors.joining("\n")), + .map(renderer) + .collect(Collectors.joining("\n")), federate.networkHelperInstantiations.stream() - .map(renderer) - .collect(Collectors.joining("\n")), + .map(renderer) + .collect(Collectors.joining("\n")), federate.networkConnections.stream() .map(renderer) .collect(Collectors.joining("\n"))) diff --git a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java index 7728ea536e..f7e81115c3 100644 --- a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java +++ b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java @@ -263,9 +263,7 @@ public Instantiation getInstantiation() { */ public List networkReceiverInstantiations = new ArrayList<>(); - /** - * List of generated instantiations that serve as helpers for forming the network connections. - */ + /** List of generated instantiations that serve as helpers for forming the network connections. */ public List networkHelperInstantiations = new ArrayList<>(); /** Parsed target config of the federate. */ From 1499431d554b42c2de9594f20bc18d639d97d594 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 22 Jun 2023 20:04:51 -0700 Subject: [PATCH 181/516] Pass DistributedCountDecentralized locally. --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 0412c1d6c6..90888a96bd 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 0412c1d6c6723f2a3f6044c4a0588a69f27f31c8 +Subproject commit 90888a96bd9aaf726718890bd2c9774a2bd674f1 From df25bc0f55a039102e05f537b0d6411c9f5b4e03 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 23 Jun 2023 11:22:12 +0800 Subject: [PATCH 182/516] Throw exceptions instead of printf --- .../lflang/analyses/cast/BuildAstParseTreeVisitor.java | 8 ++------ .../lflang/analyses/cast/VariablePrecedenceVisitor.java | 2 +- .../lflang/analyses/statespace/StateSpaceExplorer.java | 4 +--- .../main/java/org/lflang/analyses/uclid/MTLVisitor.java | 2 +- .../java/org/lflang/analyses/uclid/UclidGenerator.java | 6 +++--- 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java b/core/src/main/java/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java index fe9fbea1f1..3b72539d14 100644 --- a/core/src/main/java/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java +++ b/core/src/main/java/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java @@ -255,9 +255,7 @@ public CAst.AstNode visitPostfixExpression(PostfixExpressionContext ctx) { && ctx.Arrow().size() == 1) { CAst.AstNode primaryExprNode = visitPrimaryExpression(ctx.primaryExpression()); if (primaryExprNode instanceof CAst.LiteralNode) { - // Unreachable. - System.out.println("Unreachable!"); - return new CAst.OpaqueNode(); // FIXME: Throw an exception instead. + throw new AssertionError("unreachable"); } CAst.VariableNode varNode = (CAst.VariableNode) primaryExprNode; if (varNode.name.equals("self")) { @@ -286,9 +284,7 @@ public CAst.AstNode visitPostfixExpression(PostfixExpressionContext ctx) { List params = ctx.argumentExpressionList().get(0).assignmentExpression(); if (primaryExprNode instanceof CAst.LiteralNode) { - // Unreachable. - System.out.println("Unreachable!"); - return new CAst.OpaqueNode(); // FIXME: Throw an exception instead. + throw new AssertionError("unreachable"); } CAst.VariableNode varNode = (CAst.VariableNode) primaryExprNode; if (varNode.name.equals("lf_set")) { diff --git a/core/src/main/java/org/lflang/analyses/cast/VariablePrecedenceVisitor.java b/core/src/main/java/org/lflang/analyses/cast/VariablePrecedenceVisitor.java index e6124bc516..944245d6ef 100644 --- a/core/src/main/java/org/lflang/analyses/cast/VariablePrecedenceVisitor.java +++ b/core/src/main/java/org/lflang/analyses/cast/VariablePrecedenceVisitor.java @@ -21,7 +21,7 @@ public Void visitAssignmentNode(AssignmentNode node) { } } } else { - System.out.println("Unreachable!"); // FIXME: Throw exception. + throw new AssertionError("unreachable"); } return null; } diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java index ff0087fad6..b1b37e1333 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java +++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java @@ -4,7 +4,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Set; -import org.eclipse.xtext.xbase.lib.Exceptions; import org.lflang.TimeUnit; import org.lflang.TimeValue; import org.lflang.generator.ActionInstance; @@ -281,8 +280,7 @@ else if (previousTag != null && currentTag.timestamp == previousTag.timestamp) { // Update the eventQ snapshot. currentNode.eventQ = new ArrayList(eventQ); } else { - // Unreachable - Exceptions.sneakyThrow(new Exception("Reached an unreachable part.")); + throw new AssertionError("unreachable"); } // Update the current tag for the next iteration. diff --git a/core/src/main/java/org/lflang/analyses/uclid/MTLVisitor.java b/core/src/main/java/org/lflang/analyses/uclid/MTLVisitor.java index b83c3849a6..b8f4981eea 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/MTLVisitor.java +++ b/core/src/main/java/org/lflang/analyses/uclid/MTLVisitor.java @@ -22,7 +22,6 @@ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************/ -/** (EXPERIMENTAL) Transpiler from an MTL specification to a Uclid axiom. */ package org.lflang.analyses.uclid; import org.lflang.TimeUnit; @@ -31,6 +30,7 @@ import org.lflang.dsl.MTLParserBaseVisitor; import org.lflang.generator.CodeBuilder; +/** (EXPERIMENTAL) Transpiler from an MTL specification to a Uclid axiom. */ public class MTLVisitor extends MTLParserBaseVisitor { //////////////////////////////////////////// diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java index f8289fb2c9..6f52cf9a2e 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java @@ -22,7 +22,6 @@ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************/ -/** (EXPERIMENTAL) Generator for Uclid5 models. */ package org.lflang.analyses.uclid; import java.io.IOException; @@ -80,6 +79,7 @@ import org.lflang.lf.Time; import org.lflang.util.StringUtil; +/** (EXPERIMENTAL) Generator for Uclid5 models. */ public class UclidGenerator extends GeneratorBase { //////////////////////////////////////////// @@ -1356,7 +1356,7 @@ protected void generateReactionAxioms() { + " == " + "false"); } else { - System.out.println("Unreachable!"); + throw new AssertionError("unreachable"); } code.pr("))"); } @@ -1730,6 +1730,6 @@ public Target getTarget() { @Override public TargetTypes getTargetTypes() { - throw new UnsupportedOperationException("TODO: auto-generated method stub"); + throw new UnsupportedOperationException("This method is not applicable for this generator since Uclid5 is not an LF target."); } } From b1308b057a64d56288535bb66ee2bd3035c3a3fe Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 23 Jun 2023 11:30:39 +0800 Subject: [PATCH 183/516] Apply suggestions from @petervdonovan --- .../lflang/analyses/uclid/UclidGenerator.java | 28 ++++--------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java index 6f52cf9a2e..42285ac028 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java @@ -986,21 +986,7 @@ protected void generateTriggersAndReactions() { if (this.timerInstances.size() > 0) { code.pr(String.join("\n", "/**********", " * Timers *", " **********/")); - /** - * The timer axioms take the following form: - * - *

// An initial firing at {offset, 0} axiom( ((g(END)._1 >= 500000000) ==> ( finite_exists - * (j : integer) in indices :: (j > START && j <= END) && Timer_t_is_present(t(j)) && - * tag_same(g(j), {500000000, 0}) )) && ((g(END)._1 < 500000000) ==> ( finite_forall (i : - * integer) in indices :: (i > START && i <= END) ==> (!isNULL(i)) )) ); // Schedule - * subsequent firings. axiom( finite_forall (i : integer) in indices :: (i >= START && i <= - * END) ==> ( Timer_t_is_present(t(i)) ==> ( ( finite_exists (j : integer) in indices :: (j >= - * START && j > i) && Timer_t_is_present(t(j)) && (g(j) == tag_schedule(g(i), 1000000000)) ) ) - * ) ); // All firings must be evenly spaced out. axiom( finite_forall (i : integer) in - * indices :: (i >= START && i <= END) ==> ( Timer_t_is_present(t(i)) ==> ( // Timestamp must - * be offset + n * period ( exists (n : integer) :: ( n >= 0 && g(i)._1 == 500000000 + n * - * 1000000000 ) ) // Microstep must be 0 && ( g(i)._2 == 0 ) ) ) ); - */ + for (var timer : this.timerInstances) { long offset = timer.getOffset().toNanoSeconds(); long period = timer.getPeriod().toNanoSeconds(); @@ -1285,13 +1271,10 @@ protected void generateReactionAxioms() { // FIXME: A more systematic check is needed // to ensure that the generated Uclid file // is valid. - try { - if (resetCondition.contains("null")) { - throw new Exception("Null detected in a reset condition. Stop."); - } - } catch (Exception e) { - Exceptions.sneakyThrow(e); + if (resetCondition.contains("null")) { + throw new IllegalStateException("Null detected in a reset condition. Stop."); } + code.pr("// Unused state variables and ports are reset by default."); code.pr("&& " + "(" + "(" + resetCondition + ")" + " ==> " + "("); if (key instanceof StateVariableInstance) { @@ -1730,6 +1713,7 @@ public Target getTarget() { @Override public TargetTypes getTargetTypes() { - throw new UnsupportedOperationException("This method is not applicable for this generator since Uclid5 is not an LF target."); + throw new UnsupportedOperationException( + "This method is not applicable for this generator since Uclid5 is not an LF target."); } } From a859a6654f4d9ce027c2aef374b43877762c06c4 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 23 Jun 2023 23:00:31 +0800 Subject: [PATCH 184/516] Do not check models by default, based on suggestions from @lhstrh --- cli/lfc/src/main/java/org/lflang/cli/Lfc.java | 10 ++-- .../main/java/org/lflang/TargetConfig.java | 8 ++-- .../main/java/org/lflang/TargetProperty.java | 8 ++-- .../org/lflang/generator/LFGenerator.java | 48 +++++++++++++------ .../lflang/generator/LFGeneratorContext.java | 2 +- 5 files changed, 47 insertions(+), 29 deletions(-) diff --git a/cli/lfc/src/main/java/org/lflang/cli/Lfc.java b/cli/lfc/src/main/java/org/lflang/cli/Lfc.java index cde6fabde1..f2cc27c726 100644 --- a/cli/lfc/src/main/java/org/lflang/cli/Lfc.java +++ b/cli/lfc/src/main/java/org/lflang/cli/Lfc.java @@ -80,10 +80,10 @@ public class Lfc extends CliBase { private boolean noCompile; @Option( - names = {"--no-verify"}, + names = {"--verify"}, arity = "0", - description = "Do not run the generated verification models.") - private boolean noVerify; + description = "Run the generated verification models.") + private boolean verify; @Option( names = {"--print-statistics"}, @@ -256,8 +256,8 @@ public Properties getGeneratorArgs() { props.setProperty(BuildParm.NO_COMPILE.getKey(), "true"); } - if (noVerify) { - props.setProperty(BuildParm.NO_VERIFY.getKey(), "true"); + if (verify) { + props.setProperty(BuildParm.VERIFY.getKey(), "true"); } if (targetCompiler != null) { diff --git a/core/src/main/java/org/lflang/TargetConfig.java b/core/src/main/java/org/lflang/TargetConfig.java index 8a01b019cd..0ce15abfe8 100644 --- a/core/src/main/java/org/lflang/TargetConfig.java +++ b/core/src/main/java/org/lflang/TargetConfig.java @@ -82,8 +82,8 @@ public TargetConfig(Properties cliArgs, TargetDecl target, MessageReporter messa if (cliArgs.containsKey("no-compile")) { this.noCompile = true; } - if (cliArgs.containsKey("no-verify")) { - this.noVerify = true; + if (cliArgs.containsKey("verify")) { + this.verify = true; } if (cliArgs.containsKey("docker")) { var arg = cliArgs.getProperty("docker"); @@ -235,8 +235,8 @@ public TargetConfig(Properties cliArgs, TargetDecl target, MessageReporter messa /** If true, do not perform runtime validation. The default is false. */ public boolean noRuntimeValidation = false; - /** If true, do not check the generated verification model. The default is false. */ - public boolean noVerify = false; + /** If true, check the generated verification model. The default is false. */ + public boolean verify = false; /** * Set the target platform config. This tells the build system what platform-specific support diff --git a/core/src/main/java/org/lflang/TargetProperty.java b/core/src/main/java/org/lflang/TargetProperty.java index cace618542..27fafee00d 100644 --- a/core/src/main/java/org/lflang/TargetProperty.java +++ b/core/src/main/java/org/lflang/TargetProperty.java @@ -411,13 +411,13 @@ public enum TargetProperty { }), /** Directive to not check the generated verification model. */ - NO_VERIFY( - "no-verify", + VERIFY( + "verify", PrimitiveType.BOOLEAN, Arrays.asList(Target.C), - (config) -> ASTUtils.toElement(config.noVerify), + (config) -> ASTUtils.toElement(config.verify), (config, value, err) -> { - config.noVerify = ASTUtils.toBoolean(value); + config.verify = ASTUtils.toBoolean(value); }), /** diff --git a/core/src/main/java/org/lflang/generator/LFGenerator.java b/core/src/main/java/org/lflang/generator/LFGenerator.java index 024ca94ff7..c0ec38cd9f 100644 --- a/core/src/main/java/org/lflang/generator/LFGenerator.java +++ b/core/src/main/java/org/lflang/generator/LFGenerator.java @@ -163,29 +163,47 @@ protected void cleanIfNeeded(LFGeneratorContext context) { */ private void runVerifierIfPropertiesDetected(Resource resource, LFGeneratorContext lfContext) { Reactor main = ASTUtils.getMainReactor(resource); + final MessageReporter messageReporter = lfContext.getErrorReporter(); List properties = AttributeUtils.getAttributes(main).stream() .filter(attr -> attr.getAttrName().equals("property")) .collect(Collectors.toList()); if (properties.size() > 0) { - System.out.println( - "*** WARNING: @property is an experimental feature. Use it with caution. ***"); - - // Check if Uclid5 and Z3 are installed. - if (execInstalled("uclid", "--help", "uclid 0.9.5") - && execInstalled("z3", "--version", "Z3 version")) { - UclidGenerator uclidGenerator = new UclidGenerator(lfContext, properties); - // Generate uclid files. - uclidGenerator.doGenerate(resource, lfContext); - if (!uclidGenerator.targetConfig.noVerify) { - // Invoke the generated uclid files. - uclidGenerator.runner.run(); - } else { - System.out.println("\"no-verify\" is set to true. Skip checking the verification model."); + // Provide a warning. + messageReporter + .nowhere() + .warning( + "Verification using \"@property\" and \"--verify\" is an experimental feature. Use" + + " with caution."); + + // Generate uclid files. + UclidGenerator uclidGenerator = new UclidGenerator(lfContext, properties); + uclidGenerator.doGenerate(resource, lfContext); + + // Check the generated uclid files. + if (uclidGenerator.targetConfig.verify) { + + // Check if Uclid5 and Z3 are installed. + if (!execInstalled("uclid", "--help", "uclid 0.9.5") + || !execInstalled("z3", "--version", "Z3 version")) { + messageReporter + .nowhere() + .error( + "Fail to check the generated verification models because Uclid5 or Z3 is not" + + " installed."); } + + // Run the Uclid tool. + uclidGenerator.runner.run(); + } else { - System.out.println("*** WARNING: Uclid5 or Z3 is not installed. @property is skipped. ***"); + messageReporter + .nowhere() + .warning( + "The \"verify\" target property is set to false. Skip checking the verification" + + " model. To check the generated verification models, set the \"verify\"" + + " target property to true or pass \"--verify\" to the lfc command"); } } } diff --git a/core/src/main/java/org/lflang/generator/LFGeneratorContext.java b/core/src/main/java/org/lflang/generator/LFGeneratorContext.java index ffc984312a..4353e3c46e 100644 --- a/core/src/main/java/org/lflang/generator/LFGeneratorContext.java +++ b/core/src/main/java/org/lflang/generator/LFGeneratorContext.java @@ -28,7 +28,7 @@ enum BuildParm { LOGGING("The logging level to use by the generated binary"), LINT("Enable or disable linting of generated code."), NO_COMPILE("Do not invoke target compiler."), - NO_VERIFY("Do not check the generated verification model."), + VERIFY("Check the generated verification model."), OUTPUT_PATH("Specify the root output directory."), PRINT_STATISTICS("Instruct the runtime to collect and print statistics."), QUIET("Suppress output of the target compiler and other commands"), From d75598cfdf409f61d36d8b025a83b4180c6323e6 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 24 Jun 2023 10:57:43 -0700 Subject: [PATCH 185/516] Make level the max of the levels depended on. This is the same as the lub/sup and makes the method match the spec. It is also a potential performance optimization because it exposes more parallelism with less synchronization in the case of parallel paths with different numbers of reactions. However it does mean that the number of reactions in the current level can change after we have already started executing the level. I think that we already had that problem due to some details surrounding federated execution, but I am not sure. --- .../main/java/org/lflang/generator/ReactionInstanceGraph.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java index a0dd1df962..f8f46dd5f0 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java @@ -304,7 +304,7 @@ protected void addNodesAndEdges(ReactorInstance reactor) { * *

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 the levels of the reactions it depends on. + * reaction is the maximum of the levels of the reactions it depends on. */ private void assignLevels() { List start = new ArrayList<>(rootNodes()); @@ -327,7 +327,7 @@ private void assignLevels() { toRemove.add(effect); // Update level of downstream node. - effect.level = origin.level + 1; + effect.level = Math.max(effect.level, origin.level); } // Remove visited edges. for (Runtime effect : toRemove) { From 4f9e205edd57839cea38755934fa4a85d6cc8dd8 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 24 Jun 2023 17:09:27 -0700 Subject: [PATCH 186/516] Revert "Make level the max of the levels depended on." This reverts commit d75598cfdf409f61d36d8b025a83b4180c6323e6. It seems like the reverted change is not compatible with our current scheduler implementations, for the reason described in the commit message. --- .../main/java/org/lflang/generator/ReactionInstanceGraph.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java index f8f46dd5f0..a0dd1df962 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java @@ -304,7 +304,7 @@ protected void addNodesAndEdges(ReactorInstance reactor) { * *

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 maximum of the levels of the reactions it depends on. + * reaction is the least upper bound of the levels of the reactions it depends on. */ private void assignLevels() { List start = new ArrayList<>(rootNodes()); @@ -327,7 +327,7 @@ private void assignLevels() { toRemove.add(effect); // Update level of downstream node. - effect.level = Math.max(effect.level, origin.level); + effect.level = origin.level + 1; } // Remove visited edges. for (Runtime effect : toRemove) { From b4a76297b444c4a317bc0aec03a2025ee6fe4538 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 24 Jun 2023 17:10:50 -0700 Subject: [PATCH 187/516] Add TPO annotations. This should correctly handle top-level banks and multiports, but I have not checked. --- .../federated/generator/FedASTUtils.java | 299 ++++++++++-------- .../federated/generator/FedGenerator.java | 3 + .../federated/generator/FedMainEmitter.java | 14 +- .../federated/generator/FederateInstance.java | 10 +- .../org/lflang/generator/PortInstance.java | 17 + .../lflang/generator/ReactionInstance.java | 15 +- .../generator/ReactionInstanceGraph.java | 125 +++++--- .../org/lflang/generator/ReactorInstance.java | 9 + .../org/lflang/validation/AttributeSpec.java | 11 +- 9 files changed, 311 insertions(+), 192 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 3b491a04ce..f2635079a9 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -258,6 +258,8 @@ private static void addNetworkReceiverReactor( receiver.getOutputs().add(out); // networkReceiverReaction.setName("NetworkReceiverReaction_" + networkIDReceiver++); + addLevelAttribute( + networkInstance, connection.getDestinationPortInstance(), connection.getSrcChannel()); networkInstance.setReactorClass(receiver); networkInstance.setName( ASTUtils.getUniqueIdentifier(top, "nr_" + connection.getDstFederate().name)); @@ -370,144 +372,172 @@ private static void addNetworkReceiverReactor( connection.dstFederate.networkActionToInstantiation.put(networkAction, networkInstance); // System.out.println(connection.getSourcePortInstance()); - if (!connection.getDefinition().isPhysical() - && - // Connections that are physical don't need control reactions - connection.getDefinition().getDelay() - == null // Connections that have delays don't need control reactions - ) { - // Add necessary dependency annotations to federate to ensure the level - // assigner has enough information to correctly assign levels without introducing deadlock - addRelativeDependencyAnnotation(connection, networkReceiverReaction, errorReporter); - } + // if (!connection.getDefinition().isPhysical() + // && + // // Connections that are physical don't need control reactions + // connection.getDefinition().getDelay() + // == null // Connections that have delays don't need control reactions + // ) { + // // Add necessary dependency annotations to federate to ensure the level + // // assigner has enough information to correctly assign levels without introducing + // deadlock + // addRelativeDependencyAnnotation(connection, networkReceiverReaction, errorReporter); + // } } - /** - * Add a network control reaction for a given input port 'destination' to destination's parent - * reactor. This reaction will block for any valid logical time until it is known whether the - * trigger for the action corresponding to the given port is present or absent. - * - * @param connection FIXME - * @param coordination FIXME - * @param errorReporter - * @note Used in federated execution - */ - private static void addNetworkInputControlReaction( - FedConnectionInstance connection, - CoordinationType coordination, - ErrorReporter errorReporter) { - - LfFactory factory = LfFactory.eINSTANCE; - Reaction reaction = factory.createReaction(); - VarRef destRef = factory.createVarRef(); - int receivingPortID = connection.dstFederate.networkMessageActions.size(); - - // If the sender or receiver is in a bank of reactors, then we want - // these reactions to appear only in the federate whose bank ID matches. - setReactionBankIndex(reaction, connection.getDstBank()); - - // FIXME: do not create a new extension every time it is used - FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) - .annotateReaction(reaction); - - // Create a new action that will be used to trigger the - // input control reactions. - Action newTriggerForControlReactionInput = factory.createAction(); - newTriggerForControlReactionInput.setOrigin(ActionOrigin.LOGICAL); - - // Set the container and variable according to the network port - destRef.setContainer(connection.getDestinationPortInstance().getParent().getDefinition()); - destRef.setVariable(connection.getDestinationPortInstance().getDefinition()); - - Reactor top = connection.getDestinationPortInstance().getParent().getParent().reactorDefinition; - - newTriggerForControlReactionInput.setName( - ASTUtils.getUniqueIdentifier(top, "inputControlReactionTrigger")); - - // Add the newly created Action to the action list of the federated reactor. - top.getActions().add(newTriggerForControlReactionInput); - - // Create the trigger for the reaction - VarRef newTriggerForControlReaction = factory.createVarRef(); - newTriggerForControlReaction.setVariable(newTriggerForControlReactionInput); - - // Add the appropriate triggers to the list of triggers of the reaction - reaction.getTriggers().add(newTriggerForControlReaction); - - // Add the destination port as an effect of the reaction - reaction.getEffects().add(destRef); - - // Generate code for the network input control reaction - reaction.setCode(factory.createCode()); - - TimeValue maxSTP = findMaxSTP(connection, coordination); - - reaction - .getCode() - .setBody( - FedTargetExtensionFactory.getExtension(connection.dstFederate.targetConfig.target) - .generateNetworkInputControlReactionBody(receivingPortID, maxSTP, coordination)); - - // Insert the reaction - top.getReactions().add(reaction); - - // Add the trigger for this reaction to the list of triggers, used to actually - // trigger the reaction at the beginning of each logical time. - connection.dstFederate.networkInputControlReactionsTriggers.add( - newTriggerForControlReactionInput); - - // Add the network input control reaction to the federate instance's list - // of network reactions - // connection.dstFederate.networkReactions.add(reaction); - - // Add necessary dependencies to reaction to ensure that it executes correctly - // relative to other network input control reactions in the federate. - // addRelativeDependency(connection, reaction, errorReporter); + private static void addLevelAttribute(Instantiation instantiation, PortInstance p, int index) { + var a = LfFactory.eINSTANCE.createAttribute(); + a.setAttrName("_tpoLevel"); + var e = LfFactory.eINSTANCE.createAttrParm(); + // If the port is an input, we can the port the maximum level possible without changing its + // ordering relative to a + // reaction. If it is an output, then either it does nothing or it sends to an input. The former + // case is fine; + // to handle the latter case, we decrement 1 to ensure that it precedes the level of the input + // that it sends to. + // This also does not change its ordering relative to any reaction that is upstream of the + // receiving port because + // our current level assignment algorithm increments the level between every reaction, so in the + // worst case this + // gives this port the same level as the sending reaction. + var ub = p.getLevelUpperBound(index); + e.setValue(String.valueOf(p.isInput() ? ub : ub - 1)); + a.getAttrParms().add(e); + instantiation.getAttributes().add(a); } - /** - * Add necessary dependency information to the signature of {@code networkInputReaction} so that - * it can execute in the correct order relative to other network reactions in the federate. - * - *

In particular, we want to avoid a deadlock if multiple network input control reactions in - * federate are in a zero-delay cycle through other federates in the federation. To avoid the - * deadlock, we encode the zero-delay cycle inside the federate by adding an artificial dependency - * from the output port of this federate that is involved in the cycle to the signature of {@code - * networkInputReaction} as a source. - */ - private static void addRelativeDependencyAnnotation( - FedConnectionInstance connection, - Reaction networkInputReaction, - ErrorReporter errorReporter) { - var upstreamOutputPortsInFederate = - findUpstreamPortsInFederate( - connection.dstFederate, - connection.getSourcePortInstance(), - new HashSet<>(), - new HashSet<>()); - - ModelInfo info = new ModelInfo(); - for (var port : upstreamOutputPortsInFederate) { - // VarRef sourceRef = ASTUtils.factory.createVarRef(); - connection.dstFederate.networkReactionDependencyPairs.add( - new Pair(connection.getDestinationPortInstance(), port)); - - // sourceRef.setContainer(port.getParent().getDefinition()); - // sourceRef.setVariable(port.getDefinition()); - // networkInputReaction.getSources().add(sourceRef); - - // // Remove the port if it introduces cycles - // info.update( - // (Model)networkInputReaction.eContainer().eContainer(), - // errorReporter - // ); - // if (!info.topologyCycles().isEmpty()) { - // networkInputReaction.getSources().remove(sourceRef); - // } - } - // System.out.println(connection.dstFederate.networkReactionDependencyPairs); - - } + // /** + // * Add a network control reaction for a given input port 'destination' to destination's parent + // * reactor. This reaction will block for any valid logical time until it is known whether the + // * trigger for the action corresponding to the given port is present or absent. + // * + // * @param connection FIXME + // * @param coordination FIXME + // * @param errorReporter + // * @note Used in federated execution + // */ + // private static void addNetworkInputControlReaction( + // FedConnectionInstance connection, + // CoordinationType coordination, + // ErrorReporter errorReporter) { + // + // LfFactory factory = LfFactory.eINSTANCE; + // Reaction reaction = factory.createReaction(); + // VarRef destRef = factory.createVarRef(); + // int receivingPortID = connection.dstFederate.networkMessageActions.size(); + // + // // If the sender or receiver is in a bank of reactors, then we want + // // these reactions to appear only in the federate whose bank ID matches. + // setReactionBankIndex(reaction, connection.getDstBank()); + // + // // FIXME: do not create a new extension every time it is used + // FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) + // .annotateReaction(reaction); + // + // // Create a new action that will be used to trigger the + // // input control reactions. + // Action newTriggerForControlReactionInput = factory.createAction(); + // newTriggerForControlReactionInput.setOrigin(ActionOrigin.LOGICAL); + // + // // Set the container and variable according to the network port + // destRef.setContainer(connection.getDestinationPortInstance().getParent().getDefinition()); + // destRef.setVariable(connection.getDestinationPortInstance().getDefinition()); + // + // Reactor top = + // connection.getDestinationPortInstance().getParent().getParent().reactorDefinition; + // + // newTriggerForControlReactionInput.setName( + // ASTUtils.getUniqueIdentifier(top, "inputControlReactionTrigger")); + // + // // Add the newly created Action to the action list of the federated reactor. + // top.getActions().add(newTriggerForControlReactionInput); + // + // // Create the trigger for the reaction + // VarRef newTriggerForControlReaction = factory.createVarRef(); + // newTriggerForControlReaction.setVariable(newTriggerForControlReactionInput); + // + // // Add the appropriate triggers to the list of triggers of the reaction + // reaction.getTriggers().add(newTriggerForControlReaction); + // + // // Add the destination port as an effect of the reaction + // reaction.getEffects().add(destRef); + // + // // Generate code for the network input control reaction + // reaction.setCode(factory.createCode()); + // + // TimeValue maxSTP = findMaxSTP(connection, coordination); + // + // reaction + // .getCode() + // .setBody( + // FedTargetExtensionFactory.getExtension(connection.dstFederate.targetConfig.target) + // .generateNetworkInputControlReactionBody(receivingPortID, maxSTP, + // coordination)); + // + // // Insert the reaction + // top.getReactions().add(reaction); + // + // // Add the trigger for this reaction to the list of triggers, used to actually + // // trigger the reaction at the beginning of each logical time. + // connection.dstFederate.networkInputControlReactionsTriggers.add( + // newTriggerForControlReactionInput); + // + // // Add the network input control reaction to the federate instance's list + // // of network reactions + // // connection.dstFederate.networkReactions.add(reaction); + // + // // Add necessary dependencies to reaction to ensure that it executes correctly + // // relative to other network input control reactions in the federate. + // // addRelativeDependency(connection, reaction, errorReporter); + // } + + // /** + // * Add necessary dependency information to the signature of {@code networkInputReaction} so + // that + // * it can execute in the correct order relative to other network reactions in the federate. + // * + // *

In particular, we want to avoid a deadlock if multiple network input control reactions + // in + // * federate are in a zero-delay cycle through other federates in the federation. To avoid the + // * deadlock, we encode the zero-delay cycle inside the federate by adding an artificial + // dependency + // * from the output port of this federate that is involved in the cycle to the signature of + // {@code + // * networkInputReaction} as a source. + // */ + // private static void addRelativeDependencyAnnotation( + // FedConnectionInstance connection, + // Reaction networkInputReaction, + // ErrorReporter errorReporter) { + // var upstreamOutputPortsInFederate = + // findUpstreamPortsInFederate( + // connection.dstFederate, + // connection.getSourcePortInstance(), + // new HashSet<>(), + // new HashSet<>()); + // + // ModelInfo info = new ModelInfo(); + // for (var port : upstreamOutputPortsInFederate) { + // // VarRef sourceRef = ASTUtils.factory.createVarRef(); + // connection.dstFederate.networkReactionDependencyPairs.add( + // new Pair(connection.getDestinationPortInstance(), port)); + // + // // sourceRef.setContainer(port.getParent().getDefinition()); + // // sourceRef.setVariable(port.getDefinition()); + // // networkInputReaction.getSources().add(sourceRef); + // + // // // Remove the port if it introduces cycles + // // info.update( + // // (Model)networkInputReaction.eContainer().eContainer(), + // // errorReporter + // // ); + // // if (!info.topologyCycles().isEmpty()) { + // // networkInputReaction.getSources().remove(sourceRef); + // // } + // } + // // System.out.println(connection.dstFederate.networkReactionDependencyPairs); + // + // } /** * Go upstream from input port {@code port} until we reach one or more output ports that belong to @@ -834,6 +864,7 @@ private static void addNetworkSenderReactor( networkInstance.setName( ASTUtils.getUniqueIdentifier(top, "ns_" + connection.getDstFederate().name)); top.getInstantiations().add(networkInstance); + addLevelAttribute(networkInstance, connection.getSourcePortInstance(), connection.srcChannel); Connection senderToReaction = factory.createConnection(); diff --git a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java index 32101f4969..410fd67a24 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java +++ b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java @@ -47,6 +47,7 @@ import org.lflang.generator.LFGeneratorContext.BuildParm; import org.lflang.generator.MixedRadixInt; import org.lflang.generator.PortInstance; +import org.lflang.generator.ReactionInstanceGraph; import org.lflang.generator.ReactorInstance; import org.lflang.generator.RuntimeRange; import org.lflang.generator.SendRange; @@ -487,6 +488,8 @@ private void replaceFederateConnectionsWithProxies(Reactor federation, Resource // that those contain. ReactorInstance mainInstance = new ReactorInstance(federation, errorReporter); + new ReactionInstanceGraph(mainInstance); // Constructor has side effects; its result is ignored + insertIndexers(mainInstance, resource); for (ReactorInstance child : mainInstance.children) { diff --git a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java index 53b8b7dc84..97aba1b432 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java @@ -116,19 +116,19 @@ private CharSequence generateMainSignature( .map(Variable::getName) .collect(Collectors.joining(",")); - List vals = new ArrayList<>(); - for (var pair : federate.networkReactionDependencyPairs) { - vals.add(getDependencyList(federate, pair)); - } +// List vals = new ArrayList<>(); +// for (var pair : federate.networkReactionDependencyPairs) { +// vals.add(getDependencyList(federate, pair)); +// } - String intraDependencies = String.join(";", vals); +// String intraDependencies = String.join(";", vals); return """ - @_fed_config(network_message_actions="%s", dependencyPairs="%s") + @_fed_config(network_message_actions="%s") main reactor %s { """ .formatted( networkMessageActionsListString, - intraDependencies, +// intraDependencies, paramList.equals("()") ? "" : paramList); } } diff --git a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java index f7e81115c3..894ef9728d 100644 --- a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java +++ b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java @@ -231,11 +231,11 @@ public Instantiation getInstantiation() { */ public List networkReactors = new ArrayList<>(); - /** - * List of relative dependencies between network input and output reactions belonging to the same - * federate that have zero logical delay between them. - */ - public List> networkReactionDependencyPairs = new ArrayList<>(); +// /** +// * List of relative dependencies between network input and output reactions belonging to the same +// * federate that have zero logical delay between them. +// */ +// public List> networkReactionDependencyPairs = new ArrayList<>(); /** * Mapping from a port instance of a connection to its associated network reaction. We populate diff --git a/core/src/main/java/org/lflang/generator/PortInstance.java b/core/src/main/java/org/lflang/generator/PortInstance.java index 60a904ef5b..f3aa439b19 100644 --- a/core/src/main/java/org/lflang/generator/PortInstance.java +++ b/core/src/main/java/org/lflang/generator/PortInstance.java @@ -26,6 +26,7 @@ package org.lflang.generator; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.PriorityQueue; @@ -204,6 +205,18 @@ public String toString() { return "PortInstance " + getFullName(); } + /** + * Record that the {@code index}th sub-port of this has a dependent reaction of level {@code + * level}. + */ + public void hasDependentReactionWithLevel(int index, int level) { + levelUpperBounds.set(index, Math.min(levelUpperBounds.get(index), level)); + } + + public int getLevelUpperBound(int index) { + return levelUpperBounds.get(index); + } + ////////////////////////////////////////////////////// //// Protected fields. @@ -437,4 +450,8 @@ private void setInitialWidth(ErrorReporter errorReporter) { /** Indicator that we are clearing the caches. */ private boolean clearingCaches = false; + + /** The levels of the sub-ports of this. */ + private final List levelUpperBounds = + new ArrayList<>(Collections.nCopies(width < 0 ? 1 : width, Integer.MAX_VALUE)); } diff --git a/core/src/main/java/org/lflang/generator/ReactionInstance.java b/core/src/main/java/org/lflang/generator/ReactionInstance.java index 5e8069728f..2feaf7367b 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstance.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstance.java @@ -371,7 +371,7 @@ public String getName() { * contained by the top level (the top-level reactor need not be included because its index is * always {@code 0}). * - *

The size of the returned array is the product of the widths of all of the container {@link + *

The size of the returned array is the product of the widths of all the container {@link * ReactorInstance} objects. If none of these is a bank, then the size will be {@code 1}. * *

This method creates this array the first time it is called, but then holds on to it. The @@ -385,8 +385,7 @@ public List getRuntimeInstances() { if (size < 0) size = 1; runtimeInstances = new ArrayList<>(size); for (int i = 0; i < size; i++) { - Runtime r = new Runtime(); - r.id = i; + Runtime r = new Runtime(i); if (declaredDeadline != null) { r.deadline = declaredDeadline.maxDelay; } @@ -498,6 +497,8 @@ public TimeValue assignLogicalExecutionTime() { /////////////////////////////////////////////////////////// //// Inner classes + public record SourcePort(PortInstance port, int index) {} + /** Inner class representing a runtime instance of a reaction. */ public class Runtime { public TimeValue deadline; @@ -506,10 +507,12 @@ public class Runtime { // point to that upstream reaction. public Runtime dominating; /** ID ranging from 0 to parent.getTotalWidth() - 1. */ - public int id; + public final int id; public int level; + public List sourcePorts = new ArrayList<>(); + public ReactionInstance getReaction() { return ReactionInstance.this; } @@ -527,9 +530,9 @@ public String toString() { return result; } - public Runtime() { + public Runtime(int id) { this.dominating = null; - this.id = 0; + this.id = id; this.level = 0; if (ReactionInstance.this.declaredDeadline != null) { this.deadline = ReactionInstance.this.declaredDeadline.maxDelay; diff --git a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java index a0dd1df962..9d1bc71a7f 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java @@ -100,43 +100,43 @@ public void rebuild() { } } - /** - * Adds manually a set of dependent network edges as needed to nudge the level assignment - * algorithm into creating a correct level assignment. - * - * @param main - */ - private void addDependentNetworkEdges(ReactorInstance main) { - // FIXME: I do not think this belongs here because it pertains to federated execution. Also, it - // seems to relate to a design that we do not intend to use? - Attribute attribute = - AttributeUtils.findAttributeByName(main.definition.getReactorClass(), "_fed_config"); - String actionsStr = - AttributeUtils.getAttributeParameter(attribute, AttributeSpec.DEPENDENCY_PAIRS); - if (actionsStr == null) - return; // No dependent network edges, the levels algorithm has enough information - List dependencies = List.of(actionsStr.split(";", -1)); - // Recursively add nodes and edges from contained reactors. - Map m = new HashMap<>(); - for (ReactorInstance child : main.children) { - m.put(child.getName(), child); - } - for (String dependency : dependencies) { - List dep = List.of(dependency.split(",", 2)); - ReactorInstance downStream = m.getOrDefault(dep.get(0), null); - ReactorInstance upStream = m.getOrDefault(dep.get(1), null); - if (downStream == null || upStream == null) { - System.out.println("Downstream or Upstream reaction pair is undefined. Continuing."); - continue; - } - ReactionInstance down = downStream.reactions.get(0); - Runtime downRuntime = down.getRuntimeInstances().get(0); - for (ReactionInstance up : upStream.reactions) { - Runtime upRuntime = up.getRuntimeInstances().get(0); - addEdge(downRuntime, upRuntime); - } - } - } +// /** +// * Adds manually a set of dependent network edges as needed to nudge the level assignment +// * algorithm into creating a correct level assignment. +// * +// * @param main +// */ +// private void addDependentNetworkEdges(ReactorInstance main) { +// // FIXME: I do not think this belongs here because it pertains to federated execution. Also, it +// // seems to relate to a design that we do not intend to use? +// Attribute attribute = +// AttributeUtils.findAttributeByName(main.definition.getReactorClass(), "_fed_config"); +//// String actionsStr = +//// AttributeUtils.getAttributeParameter(attribute, AttributeSpec.DEPENDENCY_PAIRS); +//// if (actionsStr == null) +//// return; // No dependent network edges, the levels algorithm has enough information +//// List dependencies = List.of(actionsStr.split(";", -1)); +// // Recursively add nodes and edges from contained reactors. +// Map m = new HashMap<>(); +// for (ReactorInstance child : main.children) { +// m.put(child.getName(), child); +// } +//// for (String dependency : dependencies) { +//// List dep = List.of(dependency.split(",", 2)); +//// ReactorInstance downStream = m.getOrDefault(dep.get(0), null); +//// ReactorInstance upStream = m.getOrDefault(dep.get(1), null); +//// if (downStream == null || upStream == null) { +//// System.out.println("Downstream or Upstream reaction pair is undefined. Continuing."); +//// continue; +//// } +//// ReactionInstance down = downStream.reactions.get(0); +//// Runtime downRuntime = down.getRuntimeInstances().get(0); +//// for (ReactionInstance up : upStream.reactions) { +//// Runtime upRuntime = up.getRuntimeInstances().get(0); +//// addEdge(downRuntime, upRuntime); +//// } +//// } +// } /** This function rebuilds the graph and propagates and assigns deadlines to all reactions. */ public void rebuildAndAssignDeadlines() { this.clear(); @@ -283,6 +283,7 @@ protected void addNodesAndEdges(ReactorInstance reactor) { for (ReactorInstance child : reactor.children) { addNodesAndEdges(child); } + registerPortInstances(reactor); } /////////////////////////////////////////////////////////// @@ -297,6 +298,46 @@ protected void addNodesAndEdges(ReactorInstance reactor) { /////////////////////////////////////////////////////////// //// Private methods + private void registerPortInstances(ReactorInstance reactor) { + var allPorts = new ArrayList(); + allPorts.addAll(reactor.inputs); + allPorts.addAll(reactor.outputs); + for (var port : allPorts) { + 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); + for (ReactionInstance dstReaction : dstRange.instance.dependentReactions) { + List dstRuntimes = dstReaction.getRuntimeInstances(); + Runtime dstRuntime = dstRuntimes.get(dstIndex); + dstRuntime.sourcePorts.add(new ReactionInstance.SourcePort(port, srcIndex)); + } + dstRangePosition.increment(); + sendRangePosition.increment(); + sendRangeCount++; + if (sendRangeCount >= sendRange.width) { + // Reset to multicast. + sendRangeCount = 0; + sendRangePosition = sendRange.startMR(); + } + } + } + } + } + } + /** * 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 @@ -341,12 +382,22 @@ private void assignLevels() { // Remove visited origin. removeNode(origin); + assignPortLevel(origin); // Update numReactionsPerLevel info adjustNumReactionsPerLevel(origin.level, 1); } } + /** + * Update the level of the source ports of {@code current} to be at most that of {@code current}. + */ + private void assignPortLevel(Runtime current) { + for (var sp : current.sourcePorts) { + sp.port().hasDependentReactionWithLevel(sp.index(), current.level); + } + } + /** * This function assigns inferred deadlines to all the reactions in the graph. It is modeled after * {@code assignLevels} but it starts at the leaf nodes and uses Kahns algorithm to build a diff --git a/core/src/main/java/org/lflang/generator/ReactorInstance.java b/core/src/main/java/org/lflang/generator/ReactorInstance.java index 644de11be7..10b082bc01 100644 --- a/core/src/main/java/org/lflang/generator/ReactorInstance.java +++ b/core/src/main/java/org/lflang/generator/ReactorInstance.java @@ -172,6 +172,8 @@ public ReactorInstance(Reactor reactor, ReactorInstance parent, ErrorReporter re public TypeParameterizedReactor tpr; + public final Integer tpoLevel; + ////////////////////////////////////////////////////// //// Public methods. @@ -791,6 +793,13 @@ public ReactorInstance( int desiredDepth, List reactors) { super(definition, parent); + this.tpoLevel = + definition.getAttributes().stream() + .filter(it -> it.getAttrName().equals("_tpoLevel")) + .map(it -> it.getAttrParms().stream().findAny().orElseThrow()) + .map(it -> Integer.parseInt(it.getValue())) + .findFirst() + .orElse(null); this.reporter = reporter; this.reactorDeclaration = definition.getReactorClass(); this.reactorDefinition = ASTUtils.toDefinition(reactorDeclaration); diff --git a/core/src/main/java/org/lflang/validation/AttributeSpec.java b/core/src/main/java/org/lflang/validation/AttributeSpec.java index 71ec56e53c..c0649057f8 100644 --- a/core/src/main/java/org/lflang/validation/AttributeSpec.java +++ b/core/src/main/java/org/lflang/validation/AttributeSpec.java @@ -49,7 +49,7 @@ public class AttributeSpec { public static final String VALUE_ATTR = "value"; public static final String NETWORK_MESSAGE_ACTIONS = "network_message_actions"; - public static final String DEPENDENCY_PAIRS = "dependencyPairs"; +// public static final String DEPENDENCY_PAIRS = "dependencyPairs"; public static final String EACH_ATTR = "each"; /** A map from a string to a supported AttributeSpec */ @@ -220,8 +220,13 @@ enum AttrParamType { new AttributeSpec( List.of( new AttrParamSpec( - AttributeSpec.NETWORK_MESSAGE_ACTIONS, AttrParamType.STRING, false), - new AttrParamSpec(AttributeSpec.DEPENDENCY_PAIRS, AttrParamType.STRING, false)))); + AttributeSpec.NETWORK_MESSAGE_ACTIONS, AttrParamType.STRING, false)//, +// new AttrParamSpec(AttributeSpec.DEPENDENCY_PAIRS, AttrParamType.STRING, false) + ))); ATTRIBUTE_SPECS_BY_NAME.put("_c_body", new AttributeSpec(null)); + ATTRIBUTE_SPECS_BY_NAME.put( + "_tpoLevel", + new AttributeSpec(List.of(new AttrParamSpec(VALUE_ATTR, AttrParamType.INT, false))) + ); } } From f75d05afc3b1d88d6da069845a2a411b73bcff66 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 24 Jun 2023 17:52:15 -0700 Subject: [PATCH 188/516] Respect TPO levels. This code is not tested. Bugs are expected. --- .../generator/ReactionInstanceGraph.java | 123 ++++++++++++------ 1 file changed, 81 insertions(+), 42 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java index 9d1bc71a7f..4f448df30d 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java @@ -27,19 +27,16 @@ package org.lflang.generator; import java.util.ArrayList; -import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; +import java.util.NavigableMap; import java.util.Set; +import java.util.TreeMap; import java.util.stream.Collectors; -import org.lflang.AttributeUtils; import org.lflang.generator.ReactionInstance.Runtime; import org.lflang.generator.c.CUtil; import org.lflang.graph.PrecedenceGraph; -import org.lflang.lf.Attribute; import org.lflang.lf.Variable; -import org.lflang.validation.AttributeSpec; /** * This class analyzes the dependencies between reaction runtime instances. For each @@ -84,6 +81,7 @@ public ReactionInstanceGraph(ReactorInstance main) { public void rebuild() { this.clear(); addNodesAndEdges(main); + addEdgesForTpoLevels(main); // FIXME: Use {@link TargetProperty#EXPORT_DEPENDENCY_GRAPH}. // System.out.println(toDOT()); @@ -100,43 +98,44 @@ public void rebuild() { } } -// /** -// * Adds manually a set of dependent network edges as needed to nudge the level assignment -// * algorithm into creating a correct level assignment. -// * -// * @param main -// */ -// private void addDependentNetworkEdges(ReactorInstance main) { -// // FIXME: I do not think this belongs here because it pertains to federated execution. Also, it -// // seems to relate to a design that we do not intend to use? -// Attribute attribute = -// AttributeUtils.findAttributeByName(main.definition.getReactorClass(), "_fed_config"); -//// String actionsStr = -//// AttributeUtils.getAttributeParameter(attribute, AttributeSpec.DEPENDENCY_PAIRS); -//// if (actionsStr == null) -//// return; // No dependent network edges, the levels algorithm has enough information -//// List dependencies = List.of(actionsStr.split(";", -1)); -// // Recursively add nodes and edges from contained reactors. -// Map m = new HashMap<>(); -// for (ReactorInstance child : main.children) { -// m.put(child.getName(), child); -// } -//// for (String dependency : dependencies) { -//// List dep = List.of(dependency.split(",", 2)); -//// ReactorInstance downStream = m.getOrDefault(dep.get(0), null); -//// ReactorInstance upStream = m.getOrDefault(dep.get(1), null); -//// if (downStream == null || upStream == null) { -//// System.out.println("Downstream or Upstream reaction pair is undefined. Continuing."); -//// continue; -//// } -//// ReactionInstance down = downStream.reactions.get(0); -//// Runtime downRuntime = down.getRuntimeInstances().get(0); -//// for (ReactionInstance up : upStream.reactions) { -//// Runtime upRuntime = up.getRuntimeInstances().get(0); -//// addEdge(downRuntime, upRuntime); -//// } -//// } -// } + // /** + // * Adds manually a set of dependent network edges as needed to nudge the level assignment + // * algorithm into creating a correct level assignment. + // * + // * @param main + // */ + // private void addDependentNetworkEdges(ReactorInstance main) { + // // FIXME: I do not think this belongs here because it pertains to federated execution. Also, + // it + // // seems to relate to a design that we do not intend to use? + // Attribute attribute = + // AttributeUtils.findAttributeByName(main.definition.getReactorClass(), "_fed_config"); + //// String actionsStr = + //// AttributeUtils.getAttributeParameter(attribute, AttributeSpec.DEPENDENCY_PAIRS); + //// if (actionsStr == null) + //// return; // No dependent network edges, the levels algorithm has enough information + //// List dependencies = List.of(actionsStr.split(";", -1)); + // // Recursively add nodes and edges from contained reactors. + // Map m = new HashMap<>(); + // for (ReactorInstance child : main.children) { + // m.put(child.getName(), child); + // } + //// for (String dependency : dependencies) { + //// List dep = List.of(dependency.split(",", 2)); + //// ReactorInstance downStream = m.getOrDefault(dep.get(0), null); + //// ReactorInstance upStream = m.getOrDefault(dep.get(1), null); + //// if (downStream == null || upStream == null) { + //// System.out.println("Downstream or Upstream reaction pair is undefined. Continuing."); + //// continue; + //// } + //// ReactionInstance down = downStream.reactions.get(0); + //// Runtime downRuntime = down.getRuntimeInstances().get(0); + //// for (ReactionInstance up : upStream.reactions) { + //// Runtime upRuntime = up.getRuntimeInstances().get(0); + //// addEdge(downRuntime, upRuntime); + //// } + //// } + // } /** This function rebuilds the graph and propagates and assigns deadlines to all reactions. */ public void rebuildAndAssignDeadlines() { this.clear(); @@ -286,6 +285,46 @@ protected void addNodesAndEdges(ReactorInstance reactor) { registerPortInstances(reactor); } + /** Add edges that encode the precedence relations induced by the TPO levels. */ + private void addEdgesForTpoLevels(ReactorInstance main) { + var constrainedReactions = getConstrainedReactions(main); + for (var i : constrainedReactions.keySet()) { + var nextKey = constrainedReactions.higherKey(i); + if (nextKey == null) continue; + for (var r : constrainedReactions.get(i)) { + for (var rr : constrainedReactions.get(nextKey)) { + addEdge(r, rr); + } + } + } + } + + /** + * Get those reactions contained directly or transitively by the children of {@code main} whose TPO levels are + * specified. + * @return A map from TPO levels to reactions that are constrained to have the TPO levels. + */ + private NavigableMap> getConstrainedReactions(ReactorInstance main) { + NavigableMap> constrainedReactions = new TreeMap<>(); + for (var child : main.children) { + if (child.tpoLevel != null) { + if (!constrainedReactions.containsKey(child.tpoLevel)) { + constrainedReactions.put(child.tpoLevel, new ArrayList<>()); + } + getAllContainedReactions(constrainedReactions.get(child.tpoLevel), child); + } + } + return constrainedReactions; + } + + /** Add all reactions contained directly or transitively by {@code r}. */ + private void getAllContainedReactions(List runtimeReactions, ReactorInstance r) { + for (var reaction : r.reactions) { + runtimeReactions.addAll(reaction.getRuntimeInstances()); + } + for (var child : r.children) getAllContainedReactions(runtimeReactions, child); + } + /////////////////////////////////////////////////////////// //// Private fields From a2624041a612e5a62a4659bcabd3604d9ebd637b Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 24 Jun 2023 19:39:47 -0700 Subject: [PATCH 189/516] Fix (?) IndexOutOfBoundsException. This works for StarvationThreaded. --- core/src/main/java/org/lflang/generator/PortInstance.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/generator/PortInstance.java b/core/src/main/java/org/lflang/generator/PortInstance.java index f3aa439b19..45c706febe 100644 --- a/core/src/main/java/org/lflang/generator/PortInstance.java +++ b/core/src/main/java/org/lflang/generator/PortInstance.java @@ -453,5 +453,7 @@ private void setInitialWidth(ErrorReporter errorReporter) { /** The levels of the sub-ports of this. */ private final List levelUpperBounds = - new ArrayList<>(Collections.nCopies(width < 0 ? 1 : width, Integer.MAX_VALUE)); + new ArrayList<>( + Collections.nCopies( + (width < 0 ? 1 : width) * (parent.width < 0 ? 1 : parent.width), Integer.MAX_VALUE)); } From 6e4b862e7a1c4c870be8e88dc363aab7a9a89e99 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 24 Jun 2023 19:52:09 -0700 Subject: [PATCH 190/516] Order of source and sink was reversed. --- .../main/java/org/lflang/generator/ReactionInstanceGraph.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java index 4f448df30d..aba5836f17 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java @@ -293,7 +293,7 @@ private void addEdgesForTpoLevels(ReactorInstance main) { if (nextKey == null) continue; for (var r : constrainedReactions.get(i)) { for (var rr : constrainedReactions.get(nextKey)) { - addEdge(r, rr); + addEdge(rr, r); } } } From 8d14cb345e328e24499739d7d81834b11fe1e50f Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 24 Jun 2023 20:02:05 -0700 Subject: [PATCH 191/516] Add another little hack to get strict inequality. --- .../lflang/federated/generator/FedASTUtils.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index f2635079a9..f4d7c8462c 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -389,19 +389,12 @@ private static void addLevelAttribute(Instantiation instantiation, PortInstance var a = LfFactory.eINSTANCE.createAttribute(); a.setAttrName("_tpoLevel"); var e = LfFactory.eINSTANCE.createAttrParm(); - // If the port is an input, we can the port the maximum level possible without changing its - // ordering relative to a - // reaction. If it is an output, then either it does nothing or it sends to an input. The former - // case is fine; - // to handle the latter case, we decrement 1 to ensure that it precedes the level of the input - // that it sends to. - // This also does not change its ordering relative to any reaction that is upstream of the - // receiving port because - // our current level assignment algorithm increments the level between every reaction, so in the - // worst case this - // gives this port the same level as the sending reaction. + // preserve relative orderings according to the downstream reaction, but also ensure that the + // output and the input + // that it is connected to, which both have the same downstream reaction, have the correct + // ordering wrt each other. var ub = p.getLevelUpperBound(index); - e.setValue(String.valueOf(p.isInput() ? ub : ub - 1)); + e.setValue(String.valueOf(p.isInput() ? 2 * ub : 2 * ub - 1)); a.getAttrParms().add(e); instantiation.getAttributes().add(a); } From 48c019f9066eda1b121439371f88415bc2e5482f Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 24 Jun 2023 23:16:01 -0700 Subject: [PATCH 192/516] Do something bad to see if tests pass. Do not worry, this is temporary! --- core/src/main/java/org/lflang/generator/PortInstance.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/generator/PortInstance.java b/core/src/main/java/org/lflang/generator/PortInstance.java index 45c706febe..d28b8720ad 100644 --- a/core/src/main/java/org/lflang/generator/PortInstance.java +++ b/core/src/main/java/org/lflang/generator/PortInstance.java @@ -210,7 +210,12 @@ public String toString() { * level}. */ public void hasDependentReactionWithLevel(int index, int level) { - levelUpperBounds.set(index, Math.min(levelUpperBounds.get(index), level)); + // FIXME: index is ignored. This is a real bug that will keep this from working properly in some + // cases! We should + // fix it before merging into master. The precondition for fixing it is to compute the + // sendRange for the current + // port or top-level port sending into the dependent reaction, not the earliest port possible. + levelUpperBounds.replaceAll(a -> Math.min(a, level)); } public int getLevelUpperBound(int index) { From 6508f7506c53c0d3861ad7ef77a61b27d34f471b Mon Sep 17 00:00:00 2001 From: Abhi Gundrala Date: Mon, 26 Jun 2023 11:38:36 -0700 Subject: [PATCH 193/516] bump reactor-cpp --- core/src/main/resources/lib/cpp/reactor-cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/cpp/reactor-cpp b/core/src/main/resources/lib/cpp/reactor-cpp index e3564782f0..b607f1f640 160000 --- a/core/src/main/resources/lib/cpp/reactor-cpp +++ b/core/src/main/resources/lib/cpp/reactor-cpp @@ -1 +1 @@ -Subproject commit e3564782f0e1a2bc427e5932a88ff8f87dacd50c +Subproject commit b607f1f64083ab531e7a676b29de75f076aa1cde From d59235a20e453f8e3a0363aa20a0302d1c0fc131 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 26 Jun 2023 13:41:14 -0700 Subject: [PATCH 194/516] Update submodule. --- .../java/org/lflang/federated/extensions/CExtension.java | 8 ++++---- core/src/main/resources/lib/c/reactor-c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index 5a41efbbd0..0bbd9049de 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -548,16 +548,16 @@ protected String makePreamble( int numOfNetworkReactions = federate.networkReceiverReactions.size(); code.pr( """ - reaction_t* networkInputReactions[%1$s]; - size_t numNetworkInputReactions = %1$s; + reaction_t* network_input_reactions[%1$s]; + size_t num_network_input_reactions = %1$s; """ .formatted(numOfNetworkReactions)); int numOfNetworkSenderControlReactions = federate.networkSenderControlReactions.size(); code.pr( """ - reaction_t* portAbsentReaction[%1$s]; - size_t numSenderReactions = %1$s; + reaction_t* port_absent_reaction[%1$s]; + size_t num_sender_reactions = %1$s; """ .formatted(numOfNetworkSenderControlReactions)); diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 90888a96bd..d14c83ff43 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 90888a96bd9aaf726718890bd2c9774a2bd674f1 +Subproject commit d14c83ff43322b5c0905b693f3a4def75d3659a7 From 35778b54344df901609553306a2567b4c0038896 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 26 Jun 2023 21:07:56 -0700 Subject: [PATCH 195/516] Get BroadcastFeedback to pass locally. --- core/src/main/java/org/lflang/ast/ToLf.java | 2 +- .../federated/extensions/CExtension.java | 9 +- .../federated/extensions/CExtensionUtils.java | 60 +++++---- .../federated/generator/FedASTUtils.java | 116 +++++++++++++----- .../federated/generator/FederateInstance.java | 2 + .../generator/c/CReactionGenerator.java | 25 ++-- core/src/main/resources/lib/c/reactor-c | 2 +- 7 files changed, 135 insertions(+), 81 deletions(-) diff --git a/core/src/main/java/org/lflang/ast/ToLf.java b/core/src/main/java/org/lflang/ast/ToLf.java index 9505c57478..2dfe99c390 100644 --- a/core/src/main/java/org/lflang/ast/ToLf.java +++ b/core/src/main/java/org/lflang/ast/ToLf.java @@ -610,7 +610,7 @@ public MalleableString caseReaction(Reaction object) { } else { msb.append("reaction"); } - msb.append(list(true, object.getTriggers())); + msb.append(list(false, object.getTriggers())); msb.append(list(", ", " ", "", true, false, object.getSources())); if (!object.getEffects().isEmpty()) { List allModes = ASTUtils.allModes(ASTUtils.getEnclosingReactor(object)); diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index 0bbd9049de..8c93ae69fc 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -462,11 +462,10 @@ public String generateNetworkOutputControlReactionBody( "\n", "// If the output port has not been lf_set for the current logical time,", "// send an ABSENT message to the receiving federate ", - "LF_PRINT_LOG(\"Contemplating whether to send port \"", - " \"absent for port %d to federate %d.\", ", - " " + receivingPortID + ", " + connection.getDstFederate().id + ");", + "LF_PRINT_LOG(\"Executing port absent reaction for port %d to federate %d at time %lld.\", ", + " " + receivingPortID + ", " + connection.getDstFederate().id + ", (long long) lf_time_logical_elapsed());", "if (" + sendRef + " == NULL || !" + sendRef + "->is_present) {", - " // The output port is NULL or it is not present.", + "LF_PRINT_LOG(\"The output port is NULL or it is not present.\");", " send_port_absent_to_federate(" + additionalDelayString + ", " @@ -556,7 +555,7 @@ protected String makePreamble( int numOfNetworkSenderControlReactions = federate.networkSenderControlReactions.size(); code.pr( """ - reaction_t* port_absent_reaction[%1$s]; + reaction_t* port_absent_reaction[%1$s] = { 0 }; size_t num_sender_reactions = %1$s; """ .formatted(numOfNetworkSenderControlReactions)); diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java b/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java index 1f9fbc2454..1f72cb23a1 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java @@ -116,8 +116,6 @@ public static String initializeTriggersForNetworkActions( * if it isn't known. * * @param federate The federate. - * @param main The main reactor that contains the federate (used to lookup references). - * @return */ public static String stpStructs(FederateInstance federate, ErrorReporter errorReporter) { CodeBuilder code = new CodeBuilder(); @@ -699,33 +697,33 @@ public static CharSequence upstreamPortReactions( return code.getCode(); } - public static CharSequence downstreamControlPortReactions( - FederateInstance federate, ReactorInstance main) { - CodeBuilder code = new CodeBuilder(); - if (!federate.networkSenderControlReactions.isEmpty()) { - // Create a static array of trigger_t pointers. - // networkMessageActions is a list of Actions, but we - // need a list of trigger struct names for ActionInstances. - // There should be exactly one ActionInstance in the - // main reactor for each Action. - var reactions = new LinkedList(); - for (int i = 0; i < federate.networkSenderControlReactions.size(); ++i) { - // Find the corresponding ActionInstance. - var reaction = federate.networkSenderControlReactions.get(i); - var reactor = main.lookupReactorInstance(federate.networkSenderInstantiations.get(i)); - var reactionInstance = reactor.lookupReactionInstance(reaction); - reactions.add(CUtil.reactionRef(reactionInstance)); - } - var tableCount = 0; - for (String react : reactions) { - code.pr( - "downstreamControlPortReactions[" - + (tableCount++) - + "] = (reaction_t*)&" - + react - + "; \\"); - } - } - return code.getCode(); - } +// public static CharSequence downstreamControlPortReactions( +// FederateInstance federate, ReactorInstance main) { +// CodeBuilder code = new CodeBuilder(); +// if (!federate.networkSenderControlReactions.isEmpty()) { +// // Create a static array of trigger_t pointers. +// // networkMessageActions is a list of Actions, but we +// // need a list of trigger struct names for ActionInstances. +// // There should be exactly one ActionInstance in the +// // main reactor for each Action. +// var reactions = new LinkedList(); +// for (int i = 0; i < federate.networkSenderControlReactions.size(); ++i) { +// // Find the corresponding ActionInstance. +// var reaction = federate.networkSenderControlReactions.get(i); +// var reactor = main.lookupReactorInstance(federate.networkSenderInstantiations.get(i)); +// var reactionInstance = reactor.lookupReactionInstance(reaction); +// reactions.add(CUtil.reactionRef(reactionInstance)); +// } +// var tableCount = 0; +// for (String react : reactions) { +// code.pr( +// "downstreamControlPortReactions[" +// + (tableCount++) +// + "] = (reaction_t*)&" +// + react +// + "; \\"); +// } +// } +// return code.getCode(); +// } } diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index f4d7c8462c..f0e604e12d 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -45,7 +45,6 @@ import org.eclipse.xtext.xbase.lib.IteratorExtensions; import org.lflang.ErrorReporter; import org.lflang.InferredType; -import org.lflang.ModelInfo; import org.lflang.TargetProperty.CoordinationType; import org.lflang.TimeValue; import org.lflang.ast.ASTUtils; @@ -55,6 +54,8 @@ import org.lflang.generator.ReactionInstance; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; +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; @@ -69,7 +70,6 @@ import org.lflang.lf.Type; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; -import org.lflang.util.Pair; /** * A helper class for AST transformations needed for federated execution. @@ -748,7 +748,6 @@ public static List safe(List list) { return list == null ? Collections.emptyList() : list; } - public static int networkIDSender = 0; public static int networkIDReceiver = 0; private static Map networkSenderReactors = new HashMap<>(); @@ -762,21 +761,43 @@ private static Reactor getNetworkSenderReactor( LfFactory factory = LfFactory.eINSTANCE; Type type = EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getType()); - // Initialize Reactor and Reaction AST Nodes - Reactor sender = factory.createReactor(); - Reaction networkSenderReaction = factory.createReaction(); - VarRef inRef = factory.createVarRef(); // in port to network reaction VarRef destRef = factory.createVarRef(); // destination fed + // Initialize Reactor and Reaction AST Nodes + Reactor sender = factory.createReactor(); + Input in = factory.createInput(); + in.setName("msg"); + in.setType(type); + in.setWidthSpec( + EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getWidthSpec())); + inRef.setVariable(in); + destRef.setContainer(connection.getDestinationPortInstance().getParent().getDefinition()); + destRef.setVariable(connection.getDestinationPortInstance().getDefinition()); + + Reaction networkSenderReaction = getNetworkSenderReaction(inRef, destRef, connection, coordination, type, errorReporter); + + var senderIndexParameter = LfFactory.eINSTANCE.createParameter(); + var senderIndexParameterType = LfFactory.eINSTANCE.createType(); + senderIndexParameter.setName("sender_index"); + senderIndexParameterType.setId("int"); + senderIndexParameter.setType(senderIndexParameterType); + var senderIndexParameterInit = LfFactory.eINSTANCE.createInitializer(); + var senderIndexParameterInitExpr = LfFactory.eINSTANCE.createLiteral(); + senderIndexParameterInitExpr.setLiteral("0"); + senderIndexParameterInit.getExprs().add(senderIndexParameterInitExpr); + senderIndexParameter.setInit(senderIndexParameterInit); + sender.getParameters().add(senderIndexParameter); + + sender.getReactions().add(getInitializationReaction()); sender.getReactions().add(networkSenderReaction); sender.getInputs().add(in); EObject node = IteratorExtensions.findFirst(resource.getAllContents(), Model.class::isInstance); ((Model) node).getReactors().add(sender); - sender.setName("NetworkSender_" + networkIDSender++); + sender.setName("NetworkSender_" + connection.getSrcFederate().networkIdSender); // networkSenderReaction.setName("NetworkSenderReaction_" + networkIDSender++); // FIXME: do not create a new extension every time it is used @@ -787,18 +808,25 @@ private static Reactor getNetworkSenderReactor( // these reactions to appear only in the federate whose bank ID matches. setReactionBankIndex(networkSenderReaction, connection.getSrcBank()); - in.setName("msg"); - in.setType(type); - in.setWidthSpec( - EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getWidthSpec())); - inRef.setVariable(in); + // Add the network sender reaction to the federate instance's list + // of network reactions + connection.srcFederate.networkSenderReactions.add(networkSenderReaction); + connection.srcFederate.networkReactors.add(sender); - destRef.setContainer(connection.getDestinationPortInstance().getParent().getDefinition()); - destRef.setVariable(connection.getDestinationPortInstance().getDefinition()); + networkSenderReactors.put(connection, sender); + return sender; + } - // Configure the sending reaction. + private static Reaction getNetworkSenderReaction( + VarRef inRef, + VarRef destRef, + FedConnectionInstance connection, + CoordinationType coordination, + Type type, + ErrorReporter errorReporter) { + var networkSenderReaction = LfFactory.eINSTANCE.createReaction(); networkSenderReaction.getTriggers().add(inRef); - networkSenderReaction.setCode(factory.createCode()); + networkSenderReaction.setCode(LfFactory.eINSTANCE.createCode()); networkSenderReaction .getCode() .setBody( @@ -810,14 +838,25 @@ private static Reactor getNetworkSenderReactor( InferredType.fromAST(type), coordination, errorReporter)); + return networkSenderReaction; + } - // Add the network sender reaction to the federate instance's list - // of network reactions - connection.srcFederate.networkSenderReactions.add(networkSenderReaction); - connection.srcFederate.networkReactors.add(sender); - - networkSenderReactors.put(connection, sender); - return sender; + private static Reaction getInitializationReaction() { + var initializationReaction = LfFactory.eINSTANCE.createReaction(); + var startup = LfFactory.eINSTANCE.createBuiltinTriggerRef(); + startup.setType(BuiltinTrigger.STARTUP); + initializationReaction.getTriggers().add(startup); + var code = LfFactory.eINSTANCE.createCode(); + code.setBody(""" + extern reaction_t* port_absent_reaction[]; + void enqueue_network_output_control_reactions(); + LF_PRINT_DEBUG("Adding network output control reaction to table."); + port_absent_reaction[self->sender_index] = &self->_lf__reaction_2; + LF_PRINT_DEBUG("Added network output control reaction to table. Enqueueing it..."); + enqueue_network_output_control_reactions(); + """); + initializationReaction.setCode(code); + return initializationReaction; } /** @@ -857,6 +896,7 @@ private static void addNetworkSenderReactor( networkInstance.setName( ASTUtils.getUniqueIdentifier(top, "ns_" + connection.getDstFederate().name)); top.getInstantiations().add(networkInstance); + networkInstance.getParameters().add(getSenderIndex(connection.getSrcFederate().networkIdSender++)); addLevelAttribute(networkInstance, connection.getSourcePortInstance(), connection.srcChannel); Connection senderToReaction = factory.createConnection(); @@ -888,6 +928,20 @@ private static void addNetworkSenderReactor( connection.getSourcePortInstance(), networkInstance); } + private static Assignment getSenderIndex(int networkIDSender) { + var senderIndex = LfFactory.eINSTANCE.createAssignment(); + var senderIndexParameter = LfFactory.eINSTANCE.createParameter(); + senderIndexParameter.setName("sender_index"); + senderIndex.setLhs(senderIndexParameter); + var senderIndexInitializer = LfFactory.eINSTANCE.createInitializer(); + senderIndexInitializer.setAssign(true); + var senderIndexInitializerExpression = LfFactory.eINSTANCE.createLiteral(); + senderIndexInitializerExpression.setLiteral(String.valueOf(networkIDSender)); + senderIndexInitializer.getExprs().add(senderIndexInitializerExpression); + senderIndex.setRhs(senderIndexInitializer); + return senderIndex; + } + /** * Add a network control reaction for a given output port 'source' to source's parent reactor. * This reaction will send a port absent message if the status of the output port is absent. @@ -933,10 +987,10 @@ private static void addNetworkOutputControlReaction(FedConnectionInstance connec // If no trigger with the name "outputControlReactionTrigger" is // already added to the reactor definition, we need to create it // for the first time. The trigger is a logical action. - Action newTriggerForControlReactionVariable = factory.createAction(); - newTriggerForControlReactionVariable.setName("outputControlReactionTrigger"); - newTriggerForControlReactionVariable.setOrigin(ActionOrigin.LOGICAL); - top.getActions().add(newTriggerForControlReactionVariable); + // Action newTriggerForControlReactionVariable = factory.createAction(); + // newTriggerForControlReactionVariable.setName("outputControlReactionTrigger"); + // newTriggerForControlReactionVariable.setOrigin(ActionOrigin.LOGICAL); + // top.getActions().add(newTriggerForControlReactionVariable); // // Now that the variable is created, store it in the federate instance // connection.srcFederate.networkOutputControlReactionsTrigger @@ -951,9 +1005,9 @@ private static void addNetworkOutputControlReaction(FedConnectionInstance connec // } // Add the trigger for all output control reactions to the list of triggers - VarRef triggerRef = factory.createVarRef(); - triggerRef.setVariable(newTriggerForControlReactionVariable); - reaction.getTriggers().add(triggerRef); + // VarRef triggerRef = factory.createVarRef(); + // triggerRef.setVariable(newTriggerForControlReactionVariable); + // reaction.getTriggers().add(triggerRef); // int val = networkIDSender-1; // reaction.setName("NetworkSenderControlReaction_" + val); diff --git a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java index 894ef9728d..91b8166f79 100644 --- a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java +++ b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java @@ -136,6 +136,8 @@ public Instantiation getInstantiation() { /** A list of individual connections between federates */ public Set connections = new HashSet<>(); + public int networkIdSender = 0; + /** * 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 diff --git a/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java b/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java index 824dc97ee6..411b0c1632 100644 --- a/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java @@ -136,18 +136,19 @@ public static String generateInitializationForReaction( for (Input input : tpr.reactor().getInputs()) { reactionInitialization.pr(generateInputVariablesInReaction(input, tpr, types)); } - } - // Define argument for non-triggering inputs. - for (VarRef src : ASTUtils.convertToEmptyListIfNull(reaction.getSources())) { - if (src.getVariable() instanceof Port) { - generatePortVariablesInReaction( - reactionInitialization, fieldsForStructsForContainedReactors, src, tpr, 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(), tpr, types)); - actionsAsTriggers.add((Action) src.getVariable()); + } else { + // Define argument for non-triggering inputs. + for (VarRef src : ASTUtils.convertToEmptyListIfNull(reaction.getSources())) { + if (src.getVariable() instanceof Port) { + generatePortVariablesInReaction( + reactionInitialization, fieldsForStructsForContainedReactors, src, tpr, 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(), tpr, types)); + actionsAsTriggers.add((Action) src.getVariable()); + } } } diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index d14c83ff43..bef58d3a2b 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit d14c83ff43322b5c0905b693f3a4def75d3659a7 +Subproject commit bef58d3a2b9c05dfb733bc057480f66a033936e4 From ff001ddca6ee8ebc61b53639176891f825b7d0e8 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 26 Jun 2023 21:39:32 -0700 Subject: [PATCH 196/516] Another quick temporary hack. Just to see how many tests pass. --- .../main/java/org/lflang/federated/generator/FedASTUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index f0e604e12d..0dedf9e47f 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -259,7 +259,7 @@ private static void addNetworkReceiverReactor( // networkReceiverReaction.setName("NetworkReceiverReaction_" + networkIDReceiver++); addLevelAttribute( - networkInstance, connection.getDestinationPortInstance(), connection.getSrcChannel()); + networkInstance, connection.getDestinationPortInstance(), 0/*connection.getSrcChannel()*/); networkInstance.setReactorClass(receiver); networkInstance.setName( ASTUtils.getUniqueIdentifier(top, "nr_" + connection.getDstFederate().name)); @@ -897,7 +897,7 @@ private static void addNetworkSenderReactor( ASTUtils.getUniqueIdentifier(top, "ns_" + connection.getDstFederate().name)); top.getInstantiations().add(networkInstance); networkInstance.getParameters().add(getSenderIndex(connection.getSrcFederate().networkIdSender++)); - addLevelAttribute(networkInstance, connection.getSourcePortInstance(), connection.srcChannel); + addLevelAttribute(networkInstance, connection.getSourcePortInstance(), 0/*connection.srcChannel*/); Connection senderToReaction = factory.createConnection(); From 826f7e01b1cd932dd29f4d7266ea9757aca07312 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 27 Jun 2023 20:34:19 -0700 Subject: [PATCH 197/516] Delete dead code; address warnings. --- .../federated/extensions/CExtension.java | 258 +++++++++--------- .../federated/extensions/CExtensionUtils.java | 247 ++--------------- .../federated/generator/FedASTUtils.java | 230 +--------------- 3 files changed, 151 insertions(+), 584 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index 8c93ae69fc..f7889eacbb 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -97,8 +97,7 @@ public void initializeTargetConfig( federate.targetConfig.setByUser.add(TargetProperty.THREADING); // Include the fed setup file for this federate in the target property - String relPath = getPreamblePath(federate); - federate.targetConfig.fedSetupPreamble = relPath; + federate.targetConfig.fedSetupPreamble = getPreamblePath(federate); federate.targetConfig.setByUser.add(TargetProperty.FED_SETUP); } @@ -115,10 +114,9 @@ protected void generateCMakeInclude(FederateInstance federate, FedFileConfig fil * @param action The action. * @param sendingPort The output port providing the data to send. * @param receivingPort The ID of the destination port. - * @param connection FIXME - * @param type FIXME + * @param connection The federated connection being lowered. + * @param type The type of the data conveyed by the port. * @param coordinationType The coordination type - * @param errorReporter */ public String generateNetworkReceiverBody( Action action, @@ -183,49 +181,42 @@ protected void deserialize( } var value = ""; switch (connection.getSerializer()) { - case NATIVE: - { - // NOTE: Docs say that malloc'd char* is freed on conclusion of the time step. - // So passing it downstream should be OK. - value = action.getName() + "->value"; - if (CUtil.isTokenType(type, types)) { - result.pr("lf_set_token(" + receiveRef + ", " + action.getName() + "->token);"); - } else { - result.pr("lf_set(" + receiveRef + ", " + value + ");"); - } - break; + case NATIVE -> { + // NOTE: Docs say that malloc'd char* is freed on conclusion of the time step. + // So passing it downstream should be OK. + value = action.getName() + "->value"; + if (CUtil.isTokenType(type, types)) { + result.pr("lf_set_token(" + receiveRef + ", " + action.getName() + "->token);"); + } else { + result.pr("lf_set(" + receiveRef + ", " + value + ");"); } - case PROTO: - { - throw new UnsupportedOperationException("Protobuf serialization is not supported yet."); - } - case ROS2: - { - var portType = ASTUtils.getInferredType(((Port) receivingPort.getVariable())); - var portTypeStr = types.getTargetType(portType); - if (CUtil.isTokenType(portType, types)) { - throw new UnsupportedOperationException( - "Cannot handle ROS serialization when ports are pointers."); - } else if (CExtensionUtils.isSharedPtrType(portType, types)) { - var matcher = CExtensionUtils.sharedPointerVariable.matcher(portTypeStr); - if (matcher.find()) { - portTypeStr = matcher.group("type"); - } - } - var ROSDeserializer = new FedROS2CPPSerialization(); - value = FedROS2CPPSerialization.deserializedVarName; - result.pr( - ROSDeserializer.generateNetworkDeserializerCode( - "self->_lf__" + action.getName(), portTypeStr)); - if (CExtensionUtils.isSharedPtrType(portType, types)) { - result.pr( - "auto msg_shared_ptr = std::make_shared<" + portTypeStr + ">(" + value + ");"); - result.pr("lf_set(" + receiveRef + ", msg_shared_ptr);"); - } else { - result.pr("lf_set(" + receiveRef + ", std::move(" + value + "));"); + } + case PROTO -> throw new UnsupportedOperationException( + "Protobuf serialization is not supported yet."); + case ROS2 -> { + var portType = ASTUtils.getInferredType(((Port) receivingPort.getVariable())); + var portTypeStr = types.getTargetType(portType); + if (CUtil.isTokenType(portType, types)) { + throw new UnsupportedOperationException( + "Cannot handle ROS serialization when ports are pointers."); + } else if (CExtensionUtils.isSharedPtrType(portType, types)) { + var matcher = CExtensionUtils.sharedPointerVariable.matcher(portTypeStr); + if (matcher.find()) { + portTypeStr = matcher.group("type"); } - break; } + var ROSDeserializer = new FedROS2CPPSerialization(); + value = FedROS2CPPSerialization.deserializedVarName; + result.pr( + ROSDeserializer.generateNetworkDeserializerCode( + "self->_lf__" + action.getName(), portTypeStr)); + if (CExtensionUtils.isSharedPtrType(portType, types)) { + result.pr("auto msg_shared_ptr = std::make_shared<" + portTypeStr + ">(" + value + ");"); + result.pr("lf_set(" + receiveRef + ", msg_shared_ptr);"); + } else { + result.pr("lf_set(" + receiveRef + ", std::move(" + value + "));"); + } + } } } @@ -235,10 +226,9 @@ protected void deserialize( * * @param sendingPort The output port providing the data to send. * @param receivingPort The variable reference to the destination port. - * @param connection - * @param type - * @param coordinationType - * @param errorReporter FIXME + * @param connection The federated connection being lowered. + * @param type The type of the data conveyed by the connection. + * @param coordinationType Centralized or decentralized. */ public String generateNetworkSenderBody( VarRef sendingPort, @@ -327,15 +317,15 @@ public String generateNetworkSenderBody( } /** - * FIXME + * Generate code for serializing data and sending it over the given connection. * - * @param connection - * @param type - * @param sendRef - * @param result - * @param sendingFunction - * @param commonArgs - * @param errorReporter + * @param connection A federated connection. + * @param type The type of the data sent on the connection. + * @param sendRef C code representing a reference to the data to be sent. + * @param result An accumulator of the generated code. + * @param sendingFunction The name of the function that sends the serialized data. + * @param commonArgs Arguments passed to {@code sendingFunction} regardless of serialization + * method. */ protected void serializeAndSend( FedConnectionInstance connection, @@ -349,67 +339,60 @@ protected void serializeAndSend( var lengthExpression = ""; var pointerExpression = ""; switch (connection.getSerializer()) { - case NATIVE: - { - // Handle native types. - if (CUtil.isTokenType(type, types)) { - // NOTE: Transporting token types this way is likely to only work if the sender and - // receiver - // both have the same endianness. Otherwise, you have to use protobufs or some other - // serialization scheme. - result.pr( - "size_t message_length = " - + sendRef - + "->token->length * " - + sendRef - + "->token->type->element_size;"); - result.pr( - sendingFunction + "(" + commonArgs + ", (unsigned char*) " + sendRef + "->value);"); - } else { - // string types need to be dealt with specially because they are hidden pointers. - // void type is odd, but it avoids generating non-standard expression sizeof(void), - // which some compilers reject. - lengthExpression = "sizeof(" + types.getTargetType(type) + ")"; - pointerExpression = "(unsigned char*)&" + sendRef + "->value"; - var targetType = types.getTargetType(type); - if (targetType.equals("string")) { - lengthExpression = "strlen(" + sendRef + "->value) + 1"; - pointerExpression = "(unsigned char*) " + sendRef + "->value"; - } else if (targetType.equals("void")) { - lengthExpression = "0"; - } - result.pr("size_t message_length = " + lengthExpression + ";"); - result.pr(sendingFunction + "(" + commonArgs + ", " + pointerExpression + ");"); - } - break; - } - case PROTO: - { - throw new UnsupportedOperationException("Protobuf serialization is not supported yet."); - } - case ROS2: - { - var variableToSerialize = sendRef; - var typeStr = types.getTargetType(type); - if (CUtil.isTokenType(type, types)) { - throw new UnsupportedOperationException( - "Cannot handle ROS serialization when ports are pointers."); - } else if (CExtensionUtils.isSharedPtrType(type, types)) { - var matcher = CExtensionUtils.sharedPointerVariable.matcher(typeStr); - if (matcher.find()) { - typeStr = matcher.group("type"); - } - } - var ROSSerializer = new FedROS2CPPSerialization(); - lengthExpression = ROSSerializer.serializedBufferLength(); - pointerExpression = ROSSerializer.seializedBufferVar(); + case NATIVE -> { + // Handle native types. + if (CUtil.isTokenType(type, types)) { + // NOTE: Transporting token types this way is likely to only work if the sender and + // receiver + // both have the same endianness. Otherwise, you have to use protobufs or some other + // serialization scheme. + result.pr( + "size_t message_length = " + + sendRef + + "->token->length * " + + sendRef + + "->token->type->element_size;"); result.pr( - ROSSerializer.generateNetworkSerializerCode( - variableToSerialize, typeStr, CExtensionUtils.isSharedPtrType(type, types))); + sendingFunction + "(" + commonArgs + ", (unsigned char*) " + sendRef + "->value);"); + } else { + // string types need to be dealt with specially because they are hidden pointers. + // void type is odd, but it avoids generating non-standard expression sizeof(void), + // which some compilers reject. + lengthExpression = "sizeof(" + types.getTargetType(type) + ")"; + pointerExpression = "(unsigned char*)&" + sendRef + "->value"; + var targetType = types.getTargetType(type); + if (targetType.equals("string")) { + lengthExpression = "strlen(" + sendRef + "->value) + 1"; + pointerExpression = "(unsigned char*) " + sendRef + "->value"; + } else if (targetType.equals("void")) { + lengthExpression = "0"; + } result.pr("size_t message_length = " + lengthExpression + ";"); result.pr(sendingFunction + "(" + commonArgs + ", " + pointerExpression + ");"); - break; } + } + case PROTO -> throw new UnsupportedOperationException( + "Protobuf serialization is not supported yet."); + case ROS2 -> { + var typeStr = types.getTargetType(type); + if (CUtil.isTokenType(type, types)) { + throw new UnsupportedOperationException( + "Cannot handle ROS serialization when ports are pointers."); + } else if (CExtensionUtils.isSharedPtrType(type, types)) { + var matcher = CExtensionUtils.sharedPointerVariable.matcher(typeStr); + if (matcher.find()) { + typeStr = matcher.group("type"); + } + } + var ROSSerializer = new FedROS2CPPSerialization(); + lengthExpression = ROSSerializer.serializedBufferLength(); + pointerExpression = ROSSerializer.seializedBufferVar(); + result.pr( + ROSSerializer.generateNetworkSerializerCode( + sendRef, typeStr, CExtensionUtils.isSharedPtrType(type, types))); + result.pr("size_t message_length = " + lengthExpression + ";"); + result.pr(sendingFunction + "(" + commonArgs + ", " + pointerExpression + ");"); + } } } @@ -442,8 +425,8 @@ public String generateNetworkInputControlReactionBody( * Generate code for the body of a reaction that sends a port status message for the given port if * it is absent. * - * @oaram srcOutputPort FIXME - * @param connection FIXME + * @param srcOutputPort A reference to the port that the sender reaction reads from. + * @param connection The federated connection being lowered. */ public String generateNetworkOutputControlReactionBody( VarRef srcOutputPort, FedConnectionInstance connection) { @@ -462,8 +445,13 @@ public String generateNetworkOutputControlReactionBody( "\n", "// If the output port has not been lf_set for the current logical time,", "// send an ABSENT message to the receiving federate ", - "LF_PRINT_LOG(\"Executing port absent reaction for port %d to federate %d at time %lld.\", ", - " " + receivingPortID + ", " + connection.getDstFederate().id + ", (long long) lf_time_logical_elapsed());", + "LF_PRINT_LOG(\"Executing port absent reaction for port %d to federate %d at time" + + " %lld.\", ", + " " + + receivingPortID + + ", " + + connection.getDstFederate().id + + ", (long long) lf_time_logical_elapsed());", "if (" + sendRef + " == NULL || !" + sendRef + "->is_present) {", "LF_PRINT_LOG(\"The output port is NULL or it is not present.\");", " send_port_absent_to_federate(" @@ -493,7 +481,7 @@ public String generatePreamble( ErrorReporter errorReporter) throws IOException { // Put the C preamble in a {@code include/_federate.name + _preamble.h} file - String cPreamble = makePreamble(federate, fileConfig, rtiConfig, errorReporter); + String cPreamble = makePreamble(federate, rtiConfig, errorReporter); String relPath = getPreamblePath(federate); Path fedPreamblePath = fileConfig.getSrcPath().resolve(relPath); Files.createDirectories(fedPreamblePath.getParent()); @@ -502,7 +490,11 @@ public String generatePreamble( } var includes = new CodeBuilder(); if (federate.targetConfig.target != Target.Python) { - includes.pr("#ifdef __cplusplus\n" + "extern \"C\" {\n" + "#endif"); + includes.pr( + """ + #ifdef __cplusplus + extern "C" { + #endif"""); includes.pr("#include \"core/federated/federate.h\""); includes.pr("#include \"core/federated/net_common.h\""); includes.pr("#include \"core/federated/net_util.h\""); @@ -510,7 +502,10 @@ public String generatePreamble( 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"); + includes.pr(""" + #ifdef __cplusplus + } + #endif"""); includes.pr(generateSerializationIncludes(federate, fileConfig)); } @@ -519,10 +514,7 @@ public String generatePreamble( /** Generate the preamble to setup federated execution in C. */ protected String makePreamble( - FederateInstance federate, - FedFileConfig fileConfig, - RtiConfig rtiConfig, - ErrorReporter errorReporter) { + FederateInstance federate, RtiConfig rtiConfig, ErrorReporter errorReporter) { var code = new CodeBuilder(); @@ -571,7 +563,7 @@ protected String makePreamble( code.pr(generateExecutablePreamble(federate, rtiConfig, errorReporter)); - code.pr(generateSTAAInitialization(federate, rtiConfig, errorReporter)); + code.pr(generateSTAAInitialization(federate)); code.pr(generateInitializeTriggers(federate, errorReporter)); @@ -604,12 +596,8 @@ private String generateInitializeTriggers( var oldFederatedReactorName = federatedReactor.getName(); federatedReactor.setName(federate.name); var main = new ReactorInstance(federatedReactor, errorReporter, -1); - code.pr(CExtensionUtils.initializeTriggersForNetworkActions(federate, main, errorReporter)); + code.pr(CExtensionUtils.initializeTriggersForNetworkActions(federate, main)); code.pr("staa_initialization(); \\"); - // code.pr(CExtensionUtils.initializeTriggersForControlReactions(federate, main, - // errorReporter)); - // code.pr(CExtensionUtils.networkInputReactions(federate, main)); - // code.pr(CExtensionUtils.portAbsentReaction(federate, main)); federatedReactor.setName(oldFederatedReactorName); return """ @@ -642,12 +630,10 @@ void _lf_executable_preamble() { } /** Generate code for an executed preamble. */ - private String generateSTAAInitialization( - FederateInstance federate, RtiConfig rtiConfig, ErrorReporter errorReporter) { + private String generateSTAAInitialization(FederateInstance federate) { CodeBuilder code = new CodeBuilder(); code.pr( - CExtensionUtils.surroundWithIfFederatedDecentralized( - CExtensionUtils.stpStructs(federate, errorReporter))); + CExtensionUtils.surroundWithIfFederatedDecentralized(CExtensionUtils.stpStructs(federate))); // code.pr(CExtensionUtils.allocateTriggersForFederate(federate)); @@ -662,7 +648,7 @@ void staa_initialization() { /** * Generate code to initialize the {@code federate}. * - * @param rtiConfig + * @param rtiConfig Information about the RTI's deployment. * @return The generated code */ private String generateCodeToInitializeFederate(FederateInstance federate, RtiConfig rtiConfig) { diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java b/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java index 1f72cb23a1..3a010d48ea 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java @@ -4,11 +4,9 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.regex.Pattern; -import org.lflang.ErrorReporter; import org.lflang.InferredType; import org.lflang.TargetConfig.ClockSyncOptions; import org.lflang.TargetProperty; @@ -35,39 +33,6 @@ public class CExtensionUtils { static final Pattern sharedPointerVariable = Pattern.compile("^(/\\*.*?\\*/)?std::shared_ptr<(?((/\\*.*?\\*/)?(\\S+))+)>$"); - /** - * Generate C code that allocates sufficient memory for the following two critical data structures - * that support network control reactions: - * - *

    - *
  • {@code triggers_for_network_input_control_reactions}: These are triggers that are used at - * runtime to insert network input control reactions into the reaction queue. - *
  • {@code trigger_for_network_output_control_reactions}: Triggers for network output control - * reactions, which are unique per each output port. There could be multiple network output - * control reactions for each network output port if it is connected to multiple downstream - * federates. - *
- * - * @param federate The top-level federate instance - * @return A string that allocates memory for the aforementioned three structures. - */ - public static String allocateTriggersForFederate(FederateInstance federate) { - - CodeBuilder builder = new CodeBuilder(); - if (federate.networkInputControlReactionsTriggers.size() > 0) { - // Proliferate the network input control reaction trigger array - builder.pr( - """ - // Initialize the array of pointers to network input port triggers - _fed.triggers_for_network_input_control_reactions_size = %s; - _fed.triggers_for_network_input_control_reactions = (trigger_t**)malloc( - _fed.triggers_for_network_input_control_reactions_size * sizeof(trigger_t*)); - """ - .formatted(federate.networkInputControlReactionsTriggers.size())); - } - return builder.getCode(); - } - /** * Generate C code that initializes network actions. * @@ -76,10 +41,9 @@ public static String allocateTriggersForFederate(FederateInstance federate) { * * @param federate The federate. * @param main The main reactor that contains the federate (used to lookup references). - * @return */ public static String initializeTriggersForNetworkActions( - FederateInstance federate, ReactorInstance main, ErrorReporter errorReporter) { + FederateInstance federate, ReactorInstance main) { CodeBuilder code = new CodeBuilder(); if (federate.networkMessageActions.size() > 0) { // Create a static array of trigger_t pointers. @@ -117,13 +81,9 @@ public static String initializeTriggersForNetworkActions( * * @param federate The federate. */ - public static String stpStructs(FederateInstance federate, ErrorReporter errorReporter) { + public static String stpStructs(FederateInstance federate) { CodeBuilder code = new CodeBuilder(); - Collections.sort( - federate.stpOffsets, - (d1, d2) -> { - return (int) (d1.time - d2.time); - }); + federate.stpOffsets.sort((d1, d2) -> (int) (d1.time - d2.time)); if (!federate.stpOffsets.isEmpty()) { // Create a static array of trigger_t pointers. // networkMessageActions is a list of Actions, but we @@ -165,90 +125,6 @@ public static String stpStructs(FederateInstance federate, ErrorReporter errorRe return code.getCode(); } - /** - * Generate C code that initializes three critical structures that support network control - * reactions: - triggers_for_network_input_control_reactions: These are triggers that are used at - * runtime to insert network input control reactions into the reaction queue. There could be - * multiple network input control reactions for one network input at multiple levels in the - * hierarchy. - trigger_for_network_output_control_reactions: Triggers for network output control - * reactions, which are unique per each output port. There could be multiple network output - * control reactions for each network output port if it is connected to multiple downstream - * federates. - * - * @param instance The reactor instance that is at any level of the hierarchy within the federate. - * @param errorReporter The top-level federate - * @return A string that initializes the aforementioned three structures. - */ - // public static String initializeTriggersForControlReactions( - // FederateInstance instance, - // ReactorInstance main, - // ErrorReporter errorReporter - // ) { - // CodeBuilder builder = new CodeBuilder(); - - // if (federate.networkSenderControlReactions.size() > 0) { - // // Create a static array of trigger_t pointers. - // // networkMessageActions is a list of Actions, but we - // // need a list of trigger struct names for ActionInstances. - // // There should be exactly one ActionInstance in the - // // main reactor for each Action. - // var triggers = new LinkedList(); - // for (int i = 0; i < federate.networkSenderControlReactions.size(); ++i) { - // // Find the corresponding ActionInstance. - // Action action = federate.networkMessageActions.get(i); - // var reactor = - // main.lookupReactorInstance(federate.networkReceiverInstantiations.get(i)); - // var actionInstance = reactor.lookupActionInstance(action); - // triggers.add(CUtil.actionRef(actionInstance, null)); - // } - // var actionTableCount = 0; - // for (String trigger : triggers) { - // code.pr("_lf_action_table[" + (actionTableCount++) + "] = (lf_action_base_t*)&" - // + trigger + "; \\"); - // } - // } - - // ReactorDecl reactorClass = instance.getDefinition().getReactorClass(); - // Reactor reactor = ASTUtils.toDefinition(reactorClass); - // String nameOfSelfStruct = CUtil.reactorRef(instance); - - // // Initialize triggers for network input control reactions - // for (Action trigger : errorReporter.networkInputControlReactionsTriggers) { - // // Check if the trigger belongs to this reactor instance - // if (ASTUtils.allReactions(reactor).stream().anyMatch(r -> { - // return r.getTriggers().stream().anyMatch(t -> { - // if (t instanceof VarRef) { - // return ((VarRef) t).getVariable().equals(trigger); - // } else { - // return false; - // } - // }); - // })) { - // // Initialize the triggers_for_network_input_control_reactions for the input - // builder.pr( - // String.join("\n", - // "/* Add trigger " + nameOfSelfStruct + "->_lf__"+trigger.getName()+" to the - // global list of network input ports. */ \\", - // - // "_fed.triggers_for_network_input_control_reactions["+errorReporter.networkInputControlReactionsTriggers.indexOf(trigger)+"]= \\", - // " &"+nameOfSelfStruct+"->_lf__"+trigger.getName()+"; \\" - // ) - // ); - // } - // } - - // nameOfSelfStruct = CUtil.reactorRef(instance); - - // // Initialize the trigger for network output control reactions if it doesn't exist. - // if (errorReporter.networkOutputControlReactionsTrigger != null) { - // builder.pr("_fed.trigger_for_network_output_control_reactions=&" - // + nameOfSelfStruct - // + "->_lf__outputControlReactionTrigger; \\"); - // } - - // return builder.getCode(); - // } - /** * Create a port status field variable for a network input port "input" in the self struct of a * reactor. @@ -258,23 +134,19 @@ public static String stpStructs(FederateInstance federate, ErrorReporter errorRe */ public static String createPortStatusFieldForInput(Input input) { StringBuilder builder = new StringBuilder(); - // Check if the port is a multiport + // If it is not a multiport, then we could re-use the port trigger, and nothing needs to be done if (ASTUtils.isMultiport(input)) { // If it is a multiport, then create an auxiliary list of port // triggers for each channel of // the multiport to keep track of the status of each channel // individually - builder.append("trigger_t* _lf__" + input.getName() + "_network_port_status;\n"); - } else { - // If it is not a multiport, then we could re-use the port trigger, - // and nothing needs to be - // done + builder.append("trigger_t* _lf__").append(input.getName()).append("_network_port_status;\n"); } return builder.toString(); } /** - * Given a connection 'delay' predicate, return a string that represents the interval_t value of + * Given a connection 'delay' expression, return a string that represents the interval_t value of * the additional delay that needs to be applied to the outgoing message. * *

The returned additional delay in absence of after on network connection (i.e., if delay is @@ -285,8 +157,7 @@ public static String createPortStatusFieldForInput(Input input) { * to the network connection (that can be zero) either as a time value (e.g., 200 msec) or as a * literal (e.g., a parameter), that delay in nsec will be returned. * - * @param delay - * @return + * @param delay The delay associated with a connection. */ public static String getNetworkDelayLiteral(Expression delay) { String additionalDelayString = "NEVER"; @@ -577,17 +448,6 @@ public static String generateFederateNeighborStructure(FederateInstance federate return code.toString(); } - public static List getFederatedFiles() { - return List.of( - "federated/net_util.c", - "federated/net_util.h", - "federated/net_common.h", - "federated/federate.c", - "federated/federate.h", - "federated/clock-sync.h", - "federated/clock-sync.c"); - } - /** * Surround {@code code} with blocks to ensure that code only executes if the program is * federated. @@ -633,18 +493,13 @@ public static String generateSerializationIncludes( CodeBuilder code = new CodeBuilder(); for (SupportedSerializers serializer : federate.enabledSerializers) { switch (serializer) { - case NATIVE: - case PROTO: - { - // No need to do anything at this point. - break; - } - case ROS2: - { - var ROSSerializer = new FedROS2CPPSerialization(); - code.pr(ROSSerializer.generatePreambleForSupport().toString()); - break; - } + case NATIVE, PROTO -> { + // No need to do anything at this point. + } + case ROS2 -> { + var ROSSerializer = new FedROS2CPPSerialization(); + code.pr(ROSSerializer.generatePreambleForSupport().toString()); + } } } return code.getCode(); @@ -655,75 +510,15 @@ public static String generateSerializationCMakeExtension(FederateInstance federa CodeBuilder code = new CodeBuilder(); for (SupportedSerializers serializer : federate.enabledSerializers) { switch (serializer) { - case NATIVE: - case PROTO: - { - // No CMake code is needed for now - break; - } - case ROS2: - { - var ROSSerializer = new FedROS2CPPSerialization(); - code.pr(ROSSerializer.generateCompilerExtensionForSupport()); - break; - } - } - } - return code.getCode(); - } - - public static CharSequence upstreamPortReactions( - FederateInstance federate, ReactorInstance main) { - CodeBuilder code = new CodeBuilder(); - if (!federate.networkMessageActions.isEmpty()) { - // Create a static array of trigger_t pointers. - // networkMessageActions is a list of Actions, but we - // need a list of trigger struct names for ActionInstances. - // There should be exactly one ActionInstance in the - // main reactor for each Action. - var reactions = new LinkedList(); - for (int i = 0; i < federate.networkMessageActions.size(); ++i) { - // Find the corresponding ActionInstance. - var reaction = federate.networkReceiverReactions.get(i); - var reactor = main.lookupReactorInstance(federate.networkReceiverInstantiations.get(i)); - var reactionInstance = reactor.lookupReactionInstance(reaction); - reactions.add(CUtil.reactionRef(reactionInstance)); - } - var tableCount = 0; - for (String react : reactions) { - code.pr("upstreamPortReactions[" + (tableCount++) + "] = (reaction_t*)&" + react + "; \\"); + case NATIVE, PROTO -> { + // No CMake code is needed for now + } + case ROS2 -> { + var ROSSerializer = new FedROS2CPPSerialization(); + code.pr(ROSSerializer.generateCompilerExtensionForSupport()); + } } } return code.getCode(); } - -// public static CharSequence downstreamControlPortReactions( -// FederateInstance federate, ReactorInstance main) { -// CodeBuilder code = new CodeBuilder(); -// if (!federate.networkSenderControlReactions.isEmpty()) { -// // Create a static array of trigger_t pointers. -// // networkMessageActions is a list of Actions, but we -// // need a list of trigger struct names for ActionInstances. -// // There should be exactly one ActionInstance in the -// // main reactor for each Action. -// var reactions = new LinkedList(); -// for (int i = 0; i < federate.networkSenderControlReactions.size(); ++i) { -// // Find the corresponding ActionInstance. -// var reaction = federate.networkSenderControlReactions.get(i); -// var reactor = main.lookupReactorInstance(federate.networkSenderInstantiations.get(i)); -// var reactionInstance = reactor.lookupReactionInstance(reaction); -// reactions.add(CUtil.reactionRef(reactionInstance)); -// } -// var tableCount = 0; -// for (String react : reactions) { -// code.pr( -// "downstreamControlPortReactions[" -// + (tableCount++) -// + "] = (reaction_t*)&" -// + react -// + "; \\"); -// } -// } -// return code.getCode(); -// } } diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 0dedf9e47f..58fb659cb1 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -128,7 +128,7 @@ public static Reactor findFederatedReactor(Resource resource) { * Replace the specified connection with communication between federates. * * @param connection Network connection between two federates. - * @param resource + * @param resource The resource from which the ECore model was derived. * @param coordination One of CoordinationType.DECENTRALIZED or CoordinationType.CENTRALIZED. * @param errorReporter Used to report errors encountered. */ @@ -138,24 +138,10 @@ public static void makeCommunication( CoordinationType coordination, ErrorReporter errorReporter) { - // Add the sender reactor. addNetworkSenderReactor(connection, coordination, resource, errorReporter); - // Next, generate control reactions - // if ( - // !connection.getDefinition().isPhysical() && - // // Connections that are physical don't need control reactions - // connection.getDefinition().getDelay() - // == null // Connections that have delays don't need control reactions - // ) { - // Add the network output control reaction to the parent FedASTUtils.addNetworkOutputControlReaction(connection); - // Add the network input control reaction to the parent - // FedASTUtils.addNetworkInputControlReaction(connection, coordination, errorReporter); - // } - - // Add the network receiver reactor in the destinationFederate addNetworkReceiverReactor(connection, coordination, resource, errorReporter); } @@ -222,10 +208,9 @@ public static Reactor addReactorDefinition(String name, Resource resource) { * sender in 'action->value'. This value is forwarded to 'destination' in the network receiver * reaction. * - * @param connection FIXME + * @param connection A description of the federated connection that is being replaced. * @param coordination One of CoordinationType.DECENTRALIZED or CoordinationType.CENTRALIZED. - * @param resource - * @note: Used in federated execution + * @param resource The resource from which the ECore model was derived. */ private static void addNetworkReceiverReactor( FedConnectionInstance connection, @@ -256,7 +241,6 @@ private static void addNetworkReceiverReactor( receiver.getReactions().add(networkReceiverReaction); receiver.getOutputs().add(out); - // networkReceiverReaction.setName("NetworkReceiverReaction_" + networkIDReceiver++); addLevelAttribute( networkInstance, connection.getDestinationPortInstance(), 0/*connection.getSrcChannel()*/); @@ -370,21 +354,9 @@ private static void addNetworkReceiverReactor( connection.dstFederate.networkPortToInstantiation.put( connection.getDestinationPortInstance(), networkInstance); connection.dstFederate.networkActionToInstantiation.put(networkAction, networkInstance); - // System.out.println(connection.getSourcePortInstance()); - - // if (!connection.getDefinition().isPhysical() - // && - // // Connections that are physical don't need control reactions - // connection.getDefinition().getDelay() - // == null // Connections that have delays don't need control reactions - // ) { - // // Add necessary dependency annotations to federate to ensure the level - // // assigner has enough information to correctly assign levels without introducing - // deadlock - // addRelativeDependencyAnnotation(connection, networkReceiverReaction, errorReporter); - // } } + /** Add a level annotation to the instantiation of a network reactor. */ private static void addLevelAttribute(Instantiation instantiation, PortInstance p, int index) { var a = LfFactory.eINSTANCE.createAttribute(); a.setAttrName("_tpoLevel"); @@ -399,139 +371,6 @@ private static void addLevelAttribute(Instantiation instantiation, PortInstance instantiation.getAttributes().add(a); } - // /** - // * Add a network control reaction for a given input port 'destination' to destination's parent - // * reactor. This reaction will block for any valid logical time until it is known whether the - // * trigger for the action corresponding to the given port is present or absent. - // * - // * @param connection FIXME - // * @param coordination FIXME - // * @param errorReporter - // * @note Used in federated execution - // */ - // private static void addNetworkInputControlReaction( - // FedConnectionInstance connection, - // CoordinationType coordination, - // ErrorReporter errorReporter) { - // - // LfFactory factory = LfFactory.eINSTANCE; - // Reaction reaction = factory.createReaction(); - // VarRef destRef = factory.createVarRef(); - // int receivingPortID = connection.dstFederate.networkMessageActions.size(); - // - // // If the sender or receiver is in a bank of reactors, then we want - // // these reactions to appear only in the federate whose bank ID matches. - // setReactionBankIndex(reaction, connection.getDstBank()); - // - // // FIXME: do not create a new extension every time it is used - // FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) - // .annotateReaction(reaction); - // - // // Create a new action that will be used to trigger the - // // input control reactions. - // Action newTriggerForControlReactionInput = factory.createAction(); - // newTriggerForControlReactionInput.setOrigin(ActionOrigin.LOGICAL); - // - // // Set the container and variable according to the network port - // destRef.setContainer(connection.getDestinationPortInstance().getParent().getDefinition()); - // destRef.setVariable(connection.getDestinationPortInstance().getDefinition()); - // - // Reactor top = - // connection.getDestinationPortInstance().getParent().getParent().reactorDefinition; - // - // newTriggerForControlReactionInput.setName( - // ASTUtils.getUniqueIdentifier(top, "inputControlReactionTrigger")); - // - // // Add the newly created Action to the action list of the federated reactor. - // top.getActions().add(newTriggerForControlReactionInput); - // - // // Create the trigger for the reaction - // VarRef newTriggerForControlReaction = factory.createVarRef(); - // newTriggerForControlReaction.setVariable(newTriggerForControlReactionInput); - // - // // Add the appropriate triggers to the list of triggers of the reaction - // reaction.getTriggers().add(newTriggerForControlReaction); - // - // // Add the destination port as an effect of the reaction - // reaction.getEffects().add(destRef); - // - // // Generate code for the network input control reaction - // reaction.setCode(factory.createCode()); - // - // TimeValue maxSTP = findMaxSTP(connection, coordination); - // - // reaction - // .getCode() - // .setBody( - // FedTargetExtensionFactory.getExtension(connection.dstFederate.targetConfig.target) - // .generateNetworkInputControlReactionBody(receivingPortID, maxSTP, - // coordination)); - // - // // Insert the reaction - // top.getReactions().add(reaction); - // - // // Add the trigger for this reaction to the list of triggers, used to actually - // // trigger the reaction at the beginning of each logical time. - // connection.dstFederate.networkInputControlReactionsTriggers.add( - // newTriggerForControlReactionInput); - // - // // Add the network input control reaction to the federate instance's list - // // of network reactions - // // connection.dstFederate.networkReactions.add(reaction); - // - // // Add necessary dependencies to reaction to ensure that it executes correctly - // // relative to other network input control reactions in the federate. - // // addRelativeDependency(connection, reaction, errorReporter); - // } - - // /** - // * Add necessary dependency information to the signature of {@code networkInputReaction} so - // that - // * it can execute in the correct order relative to other network reactions in the federate. - // * - // *

In particular, we want to avoid a deadlock if multiple network input control reactions - // in - // * federate are in a zero-delay cycle through other federates in the federation. To avoid the - // * deadlock, we encode the zero-delay cycle inside the federate by adding an artificial - // dependency - // * from the output port of this federate that is involved in the cycle to the signature of - // {@code - // * networkInputReaction} as a source. - // */ - // private static void addRelativeDependencyAnnotation( - // FedConnectionInstance connection, - // Reaction networkInputReaction, - // ErrorReporter errorReporter) { - // var upstreamOutputPortsInFederate = - // findUpstreamPortsInFederate( - // connection.dstFederate, - // connection.getSourcePortInstance(), - // new HashSet<>(), - // new HashSet<>()); - // - // ModelInfo info = new ModelInfo(); - // for (var port : upstreamOutputPortsInFederate) { - // // VarRef sourceRef = ASTUtils.factory.createVarRef(); - // connection.dstFederate.networkReactionDependencyPairs.add( - // new Pair(connection.getDestinationPortInstance(), port)); - // - // // sourceRef.setContainer(port.getParent().getDefinition()); - // // sourceRef.setVariable(port.getDefinition()); - // // networkInputReaction.getSources().add(sourceRef); - // - // // // Remove the port if it introduces cycles - // // info.update( - // // (Model)networkInputReaction.eContainer().eContainer(), - // // errorReporter - // // ); - // // if (!info.topologyCycles().isEmpty()) { - // // networkInputReaction.getSources().remove(sourceRef); - // // } - // } - // // System.out.println(connection.dstFederate.networkReactionDependencyPairs); - // - // } - /** * Go upstream from input port {@code port} until we reach one or more output ports that belong to * the same federate. @@ -561,9 +400,7 @@ else if (federate.contains(port.getParent())) { // Follow depends on reactions port.getDependsOnReactions() .forEach( - reaction -> { - followReactionUpstream(federate, visitedPorts, toReturn, reaction, reactionVisited); - }); + reaction -> followReactionUpstream(federate, visitedPorts, toReturn, reaction, reactionVisited)); // Follow depends on ports port.getDependsOnPorts() .forEach( @@ -750,8 +587,7 @@ public static List safe(List list) { public static int networkIDReceiver = 0; - private static Map networkSenderReactors = new HashMap<>(); - private static Map networkSenderInstantiations = new HashMap<>(); + private static final Map networkSenderReactors = new HashMap<>(); // FIXME: static mutable objects are bad private static Reactor getNetworkSenderReactor( FedConnectionInstance connection, @@ -866,9 +702,7 @@ private static Reaction getInitializationReaction() { * * @param connection Network connection between two federates. * @param coordination One of CoordinationType.DECENTRALIZED or CoordinationType.CENTRALIZED. - * @param resource - * @param errorReporter FIXME - * @note Used in federated execution + * @param resource The resource from which the ECore model was derived. */ private static void addNetworkSenderReactor( FedConnectionInstance connection, @@ -946,8 +780,7 @@ private static Assignment getSenderIndex(int networkIDSender) { * Add a network control reaction for a given output port 'source' to source's parent reactor. * This reaction will send a port absent message if the status of the output port is absent. * - * @param connection FIXME - * @note Used in federated execution + * @param connection The federated connection being replaced. */ private static void addNetworkOutputControlReaction(FedConnectionInstance connection) { LfFactory factory = LfFactory.eINSTANCE; @@ -968,49 +801,6 @@ private static void addNetworkOutputControlReaction(FedConnectionInstance connec FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) .annotateReaction(reaction); - // We use an action at the top-level to manually - // trigger output control reactions. That action is created once - // and recorded in the federate instance. - // Check whether the action already has been created. - // if (connection.srcFederate.networkOutputControlReactionsTrigger == null) { - // // The port has not been created. - // String triggerName = "outputControlReactionTrigger"; - - // // Find the trigger definition in the reactor definition, which could have been - // // generated for another federate instance if there are multiple instances - // // of the same reactor that are each distinct federates. - // Optional optTriggerInput - // = top.getActions().stream().filter( - // I -> I.getName().equals(triggerName)).findFirst(); - - // if (optTriggerInput.isEmpty()) { - // If no trigger with the name "outputControlReactionTrigger" is - // already added to the reactor definition, we need to create it - // for the first time. The trigger is a logical action. - // Action newTriggerForControlReactionVariable = factory.createAction(); - // newTriggerForControlReactionVariable.setName("outputControlReactionTrigger"); - // newTriggerForControlReactionVariable.setOrigin(ActionOrigin.LOGICAL); - // top.getActions().add(newTriggerForControlReactionVariable); - - // // Now that the variable is created, store it in the federate instance - // connection.srcFederate.networkOutputControlReactionsTrigger - // = newTriggerForControlReactionVariable; - // } else { - // If the "outputControlReactionTrigger" trigger is already - // there, we can re-use it for this new reaction since a single trigger - // will trigger - // // all network output control reactions. - // connection.srcFederate.networkOutputControlReactionsTrigger = optTriggerInput.get(); - // } - // } - - // Add the trigger for all output control reactions to the list of triggers - // VarRef triggerRef = factory.createVarRef(); - // triggerRef.setVariable(newTriggerForControlReactionVariable); - // reaction.getTriggers().add(triggerRef); - // int val = networkIDSender-1; - // reaction.setName("NetworkSenderControlReaction_" + val); - // Generate the code reaction.setCode(factory.createCode()); @@ -1029,9 +819,5 @@ private static void addNetworkOutputControlReaction(FedConnectionInstance connec connection.srcFederate.networkSenderReactions.add(reaction); connection.srcFederate.networkSenderControlReactions.add(reaction); - // connection.srcFederate.networkPortToControlReaction.put(connection.getSourcePortInstance(), - // reaction); - // connection.srcFederate.networkOutputControlReactionsTriggers.add(newTriggerForControlReactionVariable); - } } From 4cf334b79c78af192fb2d1fb6eaee3e2b6240334 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 27 Jun 2023 20:57:28 -0700 Subject: [PATCH 198/516] Address warnings and format. --- .../federated/extensions/CExtension.java | 2 +- .../federated/extensions/PythonExtension.java | 6 +-- .../federated/generator/FedASTUtils.java | 25 ++++++---- .../federated/generator/FedMainEmitter.java | 14 +++--- .../federated/generator/FederateInstance.java | 47 +++---------------- .../generator/ReactionInstanceGraph.java | 5 +- .../generator/c/CReactionGenerator.java | 4 +- .../org/lflang/validation/AttributeSpec.java | 14 +++--- 8 files changed, 44 insertions(+), 73 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index f7889eacbb..3c3c671641 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -547,7 +547,7 @@ protected String makePreamble( int numOfNetworkSenderControlReactions = federate.networkSenderControlReactions.size(); code.pr( """ - reaction_t* port_absent_reaction[%1$s] = { 0 }; + reaction_t* port_absent_reaction[%1$s]; // initialize to null pointers; see C99 6.7.8.10 size_t num_sender_reactions = %1$s; """ .formatted(numOfNetworkSenderControlReactions)); diff --git a/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java b/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java index ee598d8e26..9cca139255 100644 --- a/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java @@ -26,7 +26,6 @@ package org.lflang.federated.extensions; -import java.io.IOException; import org.lflang.ErrorReporter; import org.lflang.InferredType; import org.lflang.TargetProperty.CoordinationType; @@ -51,8 +50,7 @@ public class PythonExtension extends CExtension { @Override - protected void generateCMakeInclude(FederateInstance federate, FedFileConfig fileConfig) - throws IOException {} + protected void generateCMakeInclude(FederateInstance federate, FedFileConfig fileConfig) {} @Override protected String generateSerializationIncludes( @@ -168,7 +166,7 @@ protected void serializeAndSend( } case PROTO: { - throw new UnsupportedOperationException("Protbuf serialization is not supported yet."); + throw new UnsupportedOperationException("Protobuf serialization is not supported yet."); } case ROS2: { diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 58fb659cb1..a3532e5cd1 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -243,7 +243,7 @@ private static void addNetworkReceiverReactor( receiver.getOutputs().add(out); addLevelAttribute( - networkInstance, connection.getDestinationPortInstance(), 0/*connection.getSrcChannel()*/); + networkInstance, connection.getDestinationPortInstance(), 0 /*connection.getSrcChannel()*/); networkInstance.setReactorClass(receiver); networkInstance.setName( ASTUtils.getUniqueIdentifier(top, "nr_" + connection.getDstFederate().name)); @@ -400,7 +400,9 @@ else if (federate.contains(port.getParent())) { // Follow depends on reactions port.getDependsOnReactions() .forEach( - reaction -> followReactionUpstream(federate, visitedPorts, toReturn, reaction, reactionVisited)); + reaction -> + followReactionUpstream( + federate, visitedPorts, toReturn, reaction, reactionVisited)); // Follow depends on ports port.getDependsOnPorts() .forEach( @@ -587,7 +589,8 @@ public static List safe(List list) { public static int networkIDReceiver = 0; - private static final Map networkSenderReactors = new HashMap<>(); // FIXME: static mutable objects are bad + private static final Map networkSenderReactors = + new HashMap<>(); // FIXME: static mutable objects are bad private static Reactor getNetworkSenderReactor( FedConnectionInstance connection, @@ -607,13 +610,14 @@ private static Reactor getNetworkSenderReactor( in.setName("msg"); in.setType(type); in.setWidthSpec( - EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getWidthSpec())); + EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getWidthSpec())); inRef.setVariable(in); destRef.setContainer(connection.getDestinationPortInstance().getParent().getDefinition()); destRef.setVariable(connection.getDestinationPortInstance().getDefinition()); - Reaction networkSenderReaction = getNetworkSenderReaction(inRef, destRef, connection, coordination, type, errorReporter); + Reaction networkSenderReaction = + getNetworkSenderReaction(inRef, destRef, connection, coordination, type, errorReporter); var senderIndexParameter = LfFactory.eINSTANCE.createParameter(); var senderIndexParameterType = LfFactory.eINSTANCE.createType(); @@ -683,7 +687,8 @@ private static Reaction getInitializationReaction() { startup.setType(BuiltinTrigger.STARTUP); initializationReaction.getTriggers().add(startup); var code = LfFactory.eINSTANCE.createCode(); - code.setBody(""" + code.setBody( + """ extern reaction_t* port_absent_reaction[]; void enqueue_network_output_control_reactions(); LF_PRINT_DEBUG("Adding network output control reaction to table."); @@ -730,8 +735,11 @@ private static void addNetworkSenderReactor( networkInstance.setName( ASTUtils.getUniqueIdentifier(top, "ns_" + connection.getDstFederate().name)); top.getInstantiations().add(networkInstance); - networkInstance.getParameters().add(getSenderIndex(connection.getSrcFederate().networkIdSender++)); - addLevelAttribute(networkInstance, connection.getSourcePortInstance(), 0/*connection.srcChannel*/); + networkInstance + .getParameters() + .add(getSenderIndex(connection.getSrcFederate().networkIdSender++)); + addLevelAttribute( + networkInstance, connection.getSourcePortInstance(), 0 /*connection.srcChannel*/); Connection senderToReaction = factory.createConnection(); @@ -818,6 +826,5 @@ private static void addNetworkOutputControlReaction(FedConnectionInstance connec // of network reactions connection.srcFederate.networkSenderReactions.add(reaction); connection.srcFederate.networkSenderControlReactions.add(reaction); - } } diff --git a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java index 97aba1b432..6c097f790c 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java @@ -1,7 +1,5 @@ package org.lflang.federated.generator; -import java.util.ArrayList; -import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; import org.eclipse.emf.ecore.EObject; @@ -116,19 +114,19 @@ private CharSequence generateMainSignature( .map(Variable::getName) .collect(Collectors.joining(",")); -// List vals = new ArrayList<>(); -// for (var pair : federate.networkReactionDependencyPairs) { -// vals.add(getDependencyList(federate, pair)); -// } + // List vals = new ArrayList<>(); + // for (var pair : federate.networkReactionDependencyPairs) { + // vals.add(getDependencyList(federate, pair)); + // } -// String intraDependencies = String.join(";", vals); + // String intraDependencies = String.join(";", vals); return """ @_fed_config(network_message_actions="%s") main reactor %s { """ .formatted( networkMessageActionsListString, -// intraDependencies, + // intraDependencies, paramList.equals("()") ? "" : paramList); } } diff --git a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java index 91b8166f79..520c73b1fa 100644 --- a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java +++ b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java @@ -66,7 +66,6 @@ import org.lflang.lf.TriggerRef; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; -import org.lflang.util.Pair; /** * Instance of a federate, or marker that no federation has been defined (if isSingleton() returns @@ -118,7 +117,7 @@ public FederateInstance( * 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; + public int bankIndex; /** A list of outputs that can be triggered directly or indirectly by physical actions. */ public Set outputsConnectedToPhysicalActions = new LinkedHashSet<>(); @@ -162,7 +161,7 @@ public Instantiation getInstantiation() { public String user = null; /** The integer ID of this federate. */ - public int id = 0; + public int id; /** * The name of this federate instance. This will be the instantiation name, possibly appended with @@ -194,22 +193,6 @@ public Instantiation getInstantiation() { */ 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 triggers that trigger the output control reactions 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 List networkOutputControlReactionsTriggers = new ArrayList<>(); - /** Indicates whether the federate is remote or local */ public boolean isRemote = false; @@ -233,12 +216,6 @@ public Instantiation getInstantiation() { */ public List networkReactors = new ArrayList<>(); -// /** -// * List of relative dependencies between network input and output reactions belonging to the same -// * federate that have zero logical delay between them. -// */ -// public List> networkReactionDependencyPairs = new ArrayList<>(); - /** * Mapping from a port instance of a connection to its associated network reaction. We populate * this map as we process connections as a means of annotating intra-federate dependencies @@ -357,7 +334,7 @@ private boolean contains(Import imp) { * @param param The parameter */ private boolean contains(Parameter param) { - boolean returnValue = false; + boolean returnValue; // Check if param is referenced in this federate's instantiation returnValue = instantiation.getParameters().stream() @@ -524,7 +501,7 @@ public boolean contains(ReactorInstance instance) { * @param federatedReactor The top-level federated reactor */ private void indexExcludedTopLevelReactions(Reactor federatedReactor) { - boolean inFederate = false; + boolean inFederate; if (excludeReactions != null) { throw new IllegalStateException( "The index for excluded reactions at the top level is already built."); @@ -541,11 +518,12 @@ private void indexExcludedTopLevelReactions(Reactor federatedReactor) { .filter(it -> !networkSenderReactions.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 + // Create a collection of all the VarRefs (i.e., triggers, sources, and effects) in the + // reaction // 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 + // reaction 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 @@ -614,17 +592,6 @@ public LinkedHashMap findOutputsConnectedToPhysicalActions( return physicalActionToOutputMinDelay; } - /** - * Return a list of federates that are upstream of this federate and have a zero-delay (direct) - * connection to this federate. - */ - public List getZeroDelayImmediateUpstreamFederates() { - return this.dependsOn.entrySet().stream() - .filter(e -> e.getValue().contains(null)) - .map(Map.Entry::getKey) - .toList(); - } - @Override public String toString() { return "Federate " diff --git a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java index aba5836f17..f81e7d776e 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java @@ -300,8 +300,9 @@ private void addEdgesForTpoLevels(ReactorInstance main) { } /** - * Get those reactions contained directly or transitively by the children of {@code main} whose TPO levels are - * specified. + * Get those reactions contained directly or transitively by the children of {@code main} whose + * TPO levels are specified. + * * @return A map from TPO levels to reactions that are constrained to have the TPO levels. */ private NavigableMap> getConstrainedReactions(ReactorInstance main) { diff --git a/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java b/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java index 411b0c1632..7efc26712b 100644 --- a/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java @@ -141,12 +141,12 @@ public static String generateInitializationForReaction( for (VarRef src : ASTUtils.convertToEmptyListIfNull(reaction.getSources())) { if (src.getVariable() instanceof Port) { generatePortVariablesInReaction( - reactionInitialization, fieldsForStructsForContainedReactors, src, tpr, types); + reactionInitialization, fieldsForStructsForContainedReactors, src, tpr, 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(), tpr, types)); + generateActionVariablesInReaction((Action) src.getVariable(), tpr, types)); actionsAsTriggers.add((Action) src.getVariable()); } } diff --git a/core/src/main/java/org/lflang/validation/AttributeSpec.java b/core/src/main/java/org/lflang/validation/AttributeSpec.java index c0649057f8..8f060a6d2e 100644 --- a/core/src/main/java/org/lflang/validation/AttributeSpec.java +++ b/core/src/main/java/org/lflang/validation/AttributeSpec.java @@ -49,7 +49,7 @@ public class AttributeSpec { public static final String VALUE_ATTR = "value"; public static final String NETWORK_MESSAGE_ACTIONS = "network_message_actions"; -// public static final String DEPENDENCY_PAIRS = "dependencyPairs"; + // public static final String DEPENDENCY_PAIRS = "dependencyPairs"; public static final String EACH_ATTR = "each"; /** A map from a string to a supported AttributeSpec */ @@ -220,13 +220,13 @@ enum AttrParamType { new AttributeSpec( List.of( new AttrParamSpec( - AttributeSpec.NETWORK_MESSAGE_ACTIONS, AttrParamType.STRING, false)//, -// new AttrParamSpec(AttributeSpec.DEPENDENCY_PAIRS, AttrParamType.STRING, false) - ))); + AttributeSpec.NETWORK_MESSAGE_ACTIONS, AttrParamType.STRING, false) // , + // new AttrParamSpec(AttributeSpec.DEPENDENCY_PAIRS, + // AttrParamType.STRING, false) + ))); ATTRIBUTE_SPECS_BY_NAME.put("_c_body", new AttributeSpec(null)); ATTRIBUTE_SPECS_BY_NAME.put( - "_tpoLevel", - new AttributeSpec(List.of(new AttrParamSpec(VALUE_ATTR, AttrParamType.INT, false))) - ); + "_tpoLevel", + new AttributeSpec(List.of(new AttrParamSpec(VALUE_ATTR, AttrParamType.INT, false)))); } } From d3d3f25c98036f7e7337b47f8d7f46038fbdaec9 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 27 Jun 2023 21:30:12 -0700 Subject: [PATCH 199/516] Minor cleanups. --- .../org/lflang/generator/PortInstance.java | 18 +++++++----------- .../org/lflang/generator/ReactionInstance.java | 14 -------------- 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/PortInstance.java b/core/src/main/java/org/lflang/generator/PortInstance.java index d28b8720ad..01c0c2ea98 100644 --- a/core/src/main/java/org/lflang/generator/PortInstance.java +++ b/core/src/main/java/org/lflang/generator/PortInstance.java @@ -27,7 +27,6 @@ import java.util.ArrayList; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.PriorityQueue; import org.lflang.ErrorReporter; @@ -234,14 +233,14 @@ public int getLevelUpperBound(int index) { * checked by the validator). Each channel of this port will be broadcast to N recipients (or, if * there are no connections to zero recipients). */ - List dependentPorts = new ArrayList(); + List dependentPorts = new ArrayList<>(); /** * Upstream ports that are connected directly to this port, if there are any. For an ordinary * port, this set will have size 0 or 1. For a multiport, it can have a larger size. This * initially has capacity 1 because that is by far the most common case. */ - List> dependsOnPorts = new ArrayList>(1); + List> dependsOnPorts = new ArrayList<>(1); /** Indicator of whether this is a multiport. */ boolean isMultiport = false; @@ -269,8 +268,8 @@ private static List eventualDestinations(RuntimeRange s // a queue of ranges that may overlap, then we split those ranges // and consolidate their destinations. - List result = new ArrayList(); - PriorityQueue queue = new PriorityQueue(); + List result = new ArrayList<>(); + PriorityQueue queue = new PriorityQueue<>(); PortInstance srcPort = srcRange.instance; // Start with, if this port has dependent reactions, then add it to @@ -290,10 +289,7 @@ private static List eventualDestinations(RuntimeRange s } // Need to find send ranges that overlap with this srcRange. - Iterator sendRanges = srcPort.dependentPorts.iterator(); - while (sendRanges.hasNext()) { - - SendRange wSendRange = sendRanges.next(); + for (SendRange wSendRange : srcPort.dependentPorts) { if (wSendRange.connection != null && wSendRange.connection.getDelay() != null) { continue; @@ -359,7 +355,7 @@ private static List eventualDestinations(RuntimeRange s // Ranges overlap. Can use a truncated candidate and make its // truncated version the new candidate. result.add(candidate.head(next.start)); - candidate = (SendRange) candidate.tail(next.start); + candidate = candidate.tail(next.start); } } } @@ -383,7 +379,7 @@ private static List eventualDestinations(RuntimeRange s private List> eventualSources(RuntimeRange range) { if (eventualSourceRanges == null) { // Cached result has not been created. - eventualSourceRanges = new ArrayList>(); + eventualSourceRanges = new ArrayList<>(); if (!dependsOnReactions.isEmpty()) { eventualSourceRanges.add(new RuntimeRange.Port(this)); diff --git a/core/src/main/java/org/lflang/generator/ReactionInstance.java b/core/src/main/java/org/lflang/generator/ReactionInstance.java index 2feaf7367b..67d5351d3f 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstance.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstance.java @@ -394,20 +394,6 @@ public List getRuntimeInstances() { return runtimeInstances; } - /** - * Purge 'portInstance' from this reaction, removing it from the list of triggers, sources, - * effects, and reads. Note that this leaves the runtime instances intact, including their level - * information. - */ - public void removePortInstance(PortInstance portInstance) { - this.triggers.remove(portInstance); - this.sources.remove(portInstance); - this.effects.remove(portInstance); - this.reads.remove(portInstance); - clearCaches(false); - portInstance.clearCaches(); - } - /** Return a descriptive string. */ @Override public String toString() { From f9b6eaefdb80e5942d40675b97840ba66ef70109 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 27 Jun 2023 21:30:24 -0700 Subject: [PATCH 200/516] Revert "Do something bad to see if tests pass." This reverts commit 48c019f9066eda1b121439371f88415bc2e5482f. --- core/src/main/java/org/lflang/generator/PortInstance.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/PortInstance.java b/core/src/main/java/org/lflang/generator/PortInstance.java index 01c0c2ea98..06a408f540 100644 --- a/core/src/main/java/org/lflang/generator/PortInstance.java +++ b/core/src/main/java/org/lflang/generator/PortInstance.java @@ -209,12 +209,7 @@ public String toString() { * level}. */ public void hasDependentReactionWithLevel(int index, int level) { - // FIXME: index is ignored. This is a real bug that will keep this from working properly in some - // cases! We should - // fix it before merging into master. The precondition for fixing it is to compute the - // sendRange for the current - // port or top-level port sending into the dependent reaction, not the earliest port possible. - levelUpperBounds.replaceAll(a -> Math.min(a, level)); + levelUpperBounds.set(index, Math.min(levelUpperBounds.get(index), level)); } public int getLevelUpperBound(int index) { From cd93364b525fa333f7e7dee8c36a5d3593235962 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 28 Jun 2023 12:07:41 -0700 Subject: [PATCH 201/516] Revert "Another quick temporary hack." This reverts commit ff001ddca6ee8ebc61b53639176891f825b7d0e8. --- .../org/lflang/federated/generator/FedASTUtils.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index a3532e5cd1..77cfe77f7c 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -243,7 +243,7 @@ private static void addNetworkReceiverReactor( receiver.getOutputs().add(out); addLevelAttribute( - networkInstance, connection.getDestinationPortInstance(), 0 /*connection.getSrcChannel()*/); + networkInstance, connection.getDestinationPortInstance(), connection.getSrcChannel()); networkInstance.setReactorClass(receiver); networkInstance.setName( ASTUtils.getUniqueIdentifier(top, "nr_" + connection.getDstFederate().name)); @@ -317,9 +317,6 @@ private static void addNetworkReceiverReactor( out.setType(type); outRef.setVariable(out); - // Add the output port at the receiver reactor as an effect - // networkReceiverReaction.getEffects().add(outRef); - VarRef triggerRef = factory.createVarRef(); // Establish references to the action. triggerRef.setVariable(networkAction); @@ -342,9 +339,6 @@ private static void addNetworkReceiverReactor( coordination, errorReporter)); - // Add the receiver reaction to the parent - // parent.getReactions().add(networkReceiverReaction); - // Add the network receiver reaction to the federate instance's list // of network reactions connection.dstFederate.networkReceiverReactions.add(networkReceiverReaction); @@ -738,8 +732,7 @@ private static void addNetworkSenderReactor( networkInstance .getParameters() .add(getSenderIndex(connection.getSrcFederate().networkIdSender++)); - addLevelAttribute( - networkInstance, connection.getSourcePortInstance(), 0 /*connection.srcChannel*/); + addLevelAttribute(networkInstance, connection.getSourcePortInstance(), connection.srcChannel); Connection senderToReaction = factory.createConnection(); From 73837b86b89f179909f396a9a1f48a62b4835bd9 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 28 Jun 2023 13:42:36 -0700 Subject: [PATCH 202/516] First attempt to handle banks/multiports correctly --- .../federated/generator/FedASTUtils.java | 26 ++++++++++++++++--- .../org/lflang/generator/MixedRadixInt.java | 26 ++++++++++++++++--- .../org/lflang/generator/PortInstance.java | 14 +++++----- .../lflang/generator/ReactionInstance.java | 2 +- .../generator/ReactionInstanceGraph.java | 5 ++-- .../org/lflang/generator/ReactorInstance.java | 1 - .../org/lflang/generator/c/CGenerator.java | 3 ++- 7 files changed, 59 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 77cfe77f7c..302d1bb298 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -50,8 +50,10 @@ import org.lflang.ast.ASTUtils; import org.lflang.federated.extensions.FedTargetExtensionFactory; import org.lflang.federated.serialization.SupportedSerializers; +import org.lflang.generator.MixedRadixInt; import org.lflang.generator.PortInstance; import org.lflang.generator.ReactionInstance; +import org.lflang.generator.ReactorInstance; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; import org.lflang.lf.Assignment; @@ -243,7 +245,7 @@ private static void addNetworkReceiverReactor( receiver.getOutputs().add(out); addLevelAttribute( - networkInstance, connection.getDestinationPortInstance(), connection.getSrcChannel()); + networkInstance, connection.getDestinationPortInstance(), getSrcIndex(connection, resource, errorReporter)); networkInstance.setReactorClass(receiver); networkInstance.setName( ASTUtils.getUniqueIdentifier(top, "nr_" + connection.getDstFederate().name)); @@ -350,8 +352,26 @@ private static void addNetworkReceiverReactor( connection.dstFederate.networkActionToInstantiation.put(networkAction, networkInstance); } + private static MixedRadixInt getSrcIndex(FedConnectionInstance connection, Resource resource, ErrorReporter errorReporter) { + var main = new ReactorInstance( + findFederatedReactor(resource), + errorReporter, List.of()); + var federateReactorInstance = new ReactorInstance(connection.srcFederate.instantiation, main, errorReporter, 1, List.of()); + var widths = List.of(connection.srcRange.instance.getWidth(), federateReactorInstance.getWidth()); + var digits = List.of(connection.getSrcChannel(), connection.getSrcBank()); + return new MixedRadixInt(digits, widths, List.of(0, 1)); + } + + private static MixedRadixInt getDstIndex(FedConnectionInstance connection, Resource resource, ErrorReporter errorReporter) { + var main = new ReactorInstance(findFederatedReactor(resource), errorReporter, List.of()); + var federateReactorInstance = new ReactorInstance(connection.dstFederate.instantiation, main, errorReporter, 1, List.of()); + var widths = List.of(connection.dstRange.instance.getWidth(), federateReactorInstance.getWidth()); + var digits = List.of(connection.getDstChannel(), connection.getDstBank()); + return new MixedRadixInt(digits, widths, List.of(0, 1)); + } + /** Add a level annotation to the instantiation of a network reactor. */ - private static void addLevelAttribute(Instantiation instantiation, PortInstance p, int index) { + private static void addLevelAttribute(Instantiation instantiation, PortInstance p, MixedRadixInt index) { var a = LfFactory.eINSTANCE.createAttribute(); a.setAttrName("_tpoLevel"); var e = LfFactory.eINSTANCE.createAttrParm(); @@ -732,7 +752,7 @@ private static void addNetworkSenderReactor( networkInstance .getParameters() .add(getSenderIndex(connection.getSrcFederate().networkIdSender++)); - addLevelAttribute(networkInstance, connection.getSourcePortInstance(), connection.srcChannel); + addLevelAttribute(networkInstance, connection.getSourcePortInstance(), getDstIndex(connection, resource, errorReporter)); Connection senderToReaction = factory.createConnection(); diff --git a/core/src/main/java/org/lflang/generator/MixedRadixInt.java b/core/src/main/java/org/lflang/generator/MixedRadixInt.java index c75ebd5864..ab15082287 100644 --- a/core/src/main/java/org/lflang/generator/MixedRadixInt.java +++ b/core/src/main/java/org/lflang/generator/MixedRadixInt.java @@ -26,6 +26,8 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.generator; +import com.google.common.collect.ImmutableList; + import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; @@ -73,7 +75,7 @@ public MixedRadixInt(List digits, List radixes, List || radixes.contains(0)) { throw new IllegalArgumentException("Invalid constructor arguments."); } - this.radixes = radixes; + this.radixes = ImmutableList.copyOf(radixes); if (digits != null) { this.digits = digits; } else { @@ -247,10 +249,28 @@ public String toString() { return String.join(", ", pieces); } + @Override + public int hashCode() { + int sum = 0; + for (var radix : radixes) sum = sum * 31 + radix; + for (var digit : digits) sum = sum * 31 + digit; + for (var p : permutation) sum = sum * 31 + p; + return sum; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof MixedRadixInt mri && radixes.equals(mri.radixes) && digits.equals(mri.digits) && permutation.equals(mri.permutation); + } + + public MixedRadixInt copy() { + return new MixedRadixInt(List.copyOf(digits), List.copyOf(radixes), List.copyOf(permutation)); + } + ////////////////////////////////////////////////////////// //// Private variables - private List radixes; - private List digits; + private final ImmutableList radixes; + private final List digits; private List permutation; } diff --git a/core/src/main/java/org/lflang/generator/PortInstance.java b/core/src/main/java/org/lflang/generator/PortInstance.java index 06a408f540..01f6e45bfb 100644 --- a/core/src/main/java/org/lflang/generator/PortInstance.java +++ b/core/src/main/java/org/lflang/generator/PortInstance.java @@ -27,7 +27,9 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.PriorityQueue; import org.lflang.ErrorReporter; import org.lflang.lf.Input; @@ -208,11 +210,12 @@ public String toString() { * Record that the {@code index}th sub-port of this has a dependent reaction of level {@code * level}. */ - public void hasDependentReactionWithLevel(int index, int level) { - levelUpperBounds.set(index, Math.min(levelUpperBounds.get(index), level)); + public void hasDependentReactionWithLevel(MixedRadixInt index, int level) { + levelUpperBounds.put( + index, Math.min(levelUpperBounds.getOrDefault(index, Integer.MAX_VALUE), level)); } - public int getLevelUpperBound(int index) { + public int getLevelUpperBound(MixedRadixInt index) { return levelUpperBounds.get(index); } @@ -448,8 +451,5 @@ private void setInitialWidth(ErrorReporter errorReporter) { private boolean clearingCaches = false; /** The levels of the sub-ports of this. */ - private final List levelUpperBounds = - new ArrayList<>( - Collections.nCopies( - (width < 0 ? 1 : width) * (parent.width < 0 ? 1 : parent.width), Integer.MAX_VALUE)); + private final Map levelUpperBounds = new HashMap<>(); } diff --git a/core/src/main/java/org/lflang/generator/ReactionInstance.java b/core/src/main/java/org/lflang/generator/ReactionInstance.java index 67d5351d3f..edaab12e80 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstance.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstance.java @@ -497,7 +497,7 @@ public class Runtime { public int level; - public List sourcePorts = new ArrayList<>(); + public List sourcePorts = new ArrayList<>(); public ReactionInstance getReaction() { return ReactionInstance.this; diff --git a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java index f81e7d776e..95a249bd33 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java @@ -338,6 +338,8 @@ private void getAllContainedReactions(List runtimeReactions, ReactorIns /////////////////////////////////////////////////////////// //// Private methods + public record MriPortPair(MixedRadixInt index, PortInstance port) {} + private void registerPortInstances(ReactorInstance reactor) { var allPorts = new ArrayList(); allPorts.addAll(reactor.inputs); @@ -357,12 +359,11 @@ private void registerPortInstances(ReactorInstance reactor) { int sendRangeCount = 0; while (dstRangeCount++ < dstRange.width) { - int srcIndex = sendRangePosition.get(srcDepth); int dstIndex = dstRangePosition.get(dstDepth); for (ReactionInstance dstReaction : dstRange.instance.dependentReactions) { List dstRuntimes = dstReaction.getRuntimeInstances(); Runtime dstRuntime = dstRuntimes.get(dstIndex); - dstRuntime.sourcePorts.add(new ReactionInstance.SourcePort(port, srcIndex)); + dstRuntime.sourcePorts.add(new MriPortPair(sendRangePosition.copy(), port)); } dstRangePosition.increment(); sendRangePosition.increment(); diff --git a/core/src/main/java/org/lflang/generator/ReactorInstance.java b/core/src/main/java/org/lflang/generator/ReactorInstance.java index 10b082bc01..0b0a746ddb 100644 --- a/core/src/main/java/org/lflang/generator/ReactorInstance.java +++ b/core/src/main/java/org/lflang/generator/ReactorInstance.java @@ -90,7 +90,6 @@ public class ReactorInstance extends NamedInstance { */ public ReactorInstance(Reactor reactor, ErrorReporter reporter, List reactors) { this(ASTUtils.createInstantiation(reactor), null, reporter, -1, reactors); - assert !reactors.isEmpty(); } /** diff --git a/core/src/main/java/org/lflang/generator/c/CGenerator.java b/core/src/main/java/org/lflang/generator/c/CGenerator.java index 7bed894341..a9eae3ec9f 100644 --- a/core/src/main/java/org/lflang/generator/c/CGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CGenerator.java @@ -601,7 +601,8 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { GeneratorUtils.refreshProject(resource, context.getMode()); } - private void generateCodeFor(String lfModuleName) throws IOException { + private void + generateCodeFor(String lfModuleName) throws IOException { startTimeStepIsPresentCount = 0; code.pr(generateDirectives()); code.pr(new CMainFunctionGenerator(targetConfig).generateCode()); From 68837875e83504393ee2e8b85ec328496baaac42 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 28 Jun 2023 14:40:09 -0700 Subject: [PATCH 203/516] Try again to get correct bank width. --- .../federated/generator/FedASTUtils.java | 18 ++++++------------ .../federated/generator/FedGenerator.java | 3 ++- .../federated/generator/FederateInstance.java | 8 ++++++++ .../org/lflang/generator/ReactorInstance.java | 1 + 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 302d1bb298..eb68cdf726 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -245,7 +245,7 @@ private static void addNetworkReceiverReactor( receiver.getOutputs().add(out); addLevelAttribute( - networkInstance, connection.getDestinationPortInstance(), getSrcIndex(connection, resource, errorReporter)); + networkInstance, connection.getDestinationPortInstance(), getDstIndex(connection)); networkInstance.setReactorClass(receiver); networkInstance.setName( ASTUtils.getUniqueIdentifier(top, "nr_" + connection.getDstFederate().name)); @@ -352,20 +352,14 @@ private static void addNetworkReceiverReactor( connection.dstFederate.networkActionToInstantiation.put(networkAction, networkInstance); } - private static MixedRadixInt getSrcIndex(FedConnectionInstance connection, Resource resource, ErrorReporter errorReporter) { - var main = new ReactorInstance( - findFederatedReactor(resource), - errorReporter, List.of()); - var federateReactorInstance = new ReactorInstance(connection.srcFederate.instantiation, main, errorReporter, 1, List.of()); - var widths = List.of(connection.srcRange.instance.getWidth(), federateReactorInstance.getWidth()); + private static MixedRadixInt getSrcIndex(FedConnectionInstance connection) { + var widths = List.of(connection.srcRange.instance.getWidth(), connection.srcFederate.bankWidth); var digits = List.of(connection.getSrcChannel(), connection.getSrcBank()); return new MixedRadixInt(digits, widths, List.of(0, 1)); } - private static MixedRadixInt getDstIndex(FedConnectionInstance connection, Resource resource, ErrorReporter errorReporter) { - var main = new ReactorInstance(findFederatedReactor(resource), errorReporter, List.of()); - var federateReactorInstance = new ReactorInstance(connection.dstFederate.instantiation, main, errorReporter, 1, List.of()); - var widths = List.of(connection.dstRange.instance.getWidth(), federateReactorInstance.getWidth()); + private static MixedRadixInt getDstIndex(FedConnectionInstance connection) { + var widths = List.of(connection.dstRange.instance.getWidth(), connection.dstFederate.bankWidth); var digits = List.of(connection.getDstChannel(), connection.getDstBank()); return new MixedRadixInt(digits, widths, List.of(0, 1)); } @@ -752,7 +746,7 @@ private static void addNetworkSenderReactor( networkInstance .getParameters() .add(getSenderIndex(connection.getSrcFederate().networkIdSender++)); - addLevelAttribute(networkInstance, connection.getSourcePortInstance(), getDstIndex(connection, resource, errorReporter)); + addLevelAttribute(networkInstance, connection.getSourcePortInstance(), getSrcIndex(connection)); Connection senderToReaction = factory.createConnection(); diff --git a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java index 410fd67a24..75bb9f13e3 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java +++ b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java @@ -449,7 +449,8 @@ private List getFederateInstances( var resource = instantiation.getReactorClass().eResource(); var federateTargetConfig = new FedTargetConfig(context, resource); FederateInstance federateInstance = - new FederateInstance(instantiation, federateID, i, federateTargetConfig, errorReporter); + new FederateInstance( + instantiation, federateID, i, bankWidth, federateTargetConfig, errorReporter); federates.add(federateInstance); federateInstances.add(federateInstance); diff --git a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java index 520c73b1fa..0e0239283b 100644 --- a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java +++ b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java @@ -91,11 +91,13 @@ public FederateInstance( Instantiation instantiation, int id, int bankIndex, + int bankWidth, TargetConfig targetConfig, ErrorReporter errorReporter) { this.instantiation = instantiation; this.id = id; this.bankIndex = bankIndex; + this.bankWidth = bankWidth; this.errorReporter = errorReporter; this.targetConfig = targetConfig; @@ -119,6 +121,12 @@ public FederateInstance( */ public int bankIndex; + /** + * The width of the bank in which this federate was instantiated. This is 1 if the instantiation + * is not a bank of reactors. + */ + public int bankWidth; + /** A list of outputs that can be triggered directly or indirectly by physical actions. */ public Set outputsConnectedToPhysicalActions = new LinkedHashSet<>(); diff --git a/core/src/main/java/org/lflang/generator/ReactorInstance.java b/core/src/main/java/org/lflang/generator/ReactorInstance.java index 0b0a746ddb..10b082bc01 100644 --- a/core/src/main/java/org/lflang/generator/ReactorInstance.java +++ b/core/src/main/java/org/lflang/generator/ReactorInstance.java @@ -90,6 +90,7 @@ public class ReactorInstance extends NamedInstance { */ public ReactorInstance(Reactor reactor, ErrorReporter reporter, List reactors) { this(ASTUtils.createInstantiation(reactor), null, reporter, -1, reactors); + assert !reactors.isEmpty(); } /** From cae1820decaed1f18c59098cc7b16593a431c159 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 28 Jun 2023 15:36:13 -0700 Subject: [PATCH 204/516] Fix NPE resulting from nonzero-delay connections. --- .../main/java/org/lflang/generator/PortInstance.java | 12 ++++++++---- .../org/lflang/generator/ReactionInstanceGraph.java | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/PortInstance.java b/core/src/main/java/org/lflang/generator/PortInstance.java index 01f6e45bfb..f995342a7b 100644 --- a/core/src/main/java/org/lflang/generator/PortInstance.java +++ b/core/src/main/java/org/lflang/generator/PortInstance.java @@ -147,10 +147,14 @@ public List eventualDestinations() { // Construct the full range for this port. RuntimeRange range = new RuntimeRange.Port(this); - eventualDestinationRanges = eventualDestinations(range); + eventualDestinationRanges = eventualDestinations(range, true); return eventualDestinationRanges; } + public List eventualDestinationsNonzeroDelayOk() { + return eventualDestinations(new RuntimeRange.Port(this), false); + } + /** * Return a list of ranges of ports that send data to this port. If this port is directly written * to by one more more reactions, then it is its own eventual source and only this port will be @@ -255,7 +259,7 @@ public int getLevelUpperBound(MixedRadixInt index) { * * @param srcRange The source range. */ - private static List eventualDestinations(RuntimeRange srcRange) { + private static List eventualDestinations(RuntimeRange srcRange, boolean zeroDelayRequired) { // Getting the destinations is more complex than getting the sources // because of multicast, where there is more than one connection statement @@ -289,7 +293,7 @@ private static List eventualDestinations(RuntimeRange s // Need to find send ranges that overlap with this srcRange. for (SendRange wSendRange : srcPort.dependentPorts) { - if (wSendRange.connection != null && wSendRange.connection.getDelay() != null) { + if (zeroDelayRequired && wSendRange.connection != null && wSendRange.connection.getDelay() != null) { continue; } @@ -300,7 +304,7 @@ private static List eventualDestinations(RuntimeRange s } for (RuntimeRange dstRange : wSendRange.destinations) { // Recursively get the send ranges of that destination port. - List dstSendRanges = eventualDestinations(dstRange); + List dstSendRanges = eventualDestinations(dstRange, zeroDelayRequired); int sendRangeStart = 0; for (SendRange dstSend : dstSendRanges) { queue.add(dstSend.newSendRange(wSendRange, sendRangeStart)); diff --git a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java index 95a249bd33..8060d4d9ed 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java @@ -345,7 +345,7 @@ private void registerPortInstances(ReactorInstance reactor) { allPorts.addAll(reactor.inputs); allPorts.addAll(reactor.outputs); for (var port : allPorts) { - List eventualDestinations = port.eventualDestinations(); + List eventualDestinations = port.eventualDestinationsNonzeroDelayOk(); int srcDepth = (port.isInput()) ? 2 : 1; for (SendRange sendRange : eventualDestinations) { From 262e6221a3b504d98dd51e8ccd54f0da3f631fca Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 28 Jun 2023 15:51:57 -0700 Subject: [PATCH 205/516] Revert "Fix NPE resulting from nonzero-delay connections." This reverts commit cae1820decaed1f18c59098cc7b16593a431c159. --- .../main/java/org/lflang/generator/PortInstance.java | 12 ++++-------- .../org/lflang/generator/ReactionInstanceGraph.java | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/PortInstance.java b/core/src/main/java/org/lflang/generator/PortInstance.java index f995342a7b..01f6e45bfb 100644 --- a/core/src/main/java/org/lflang/generator/PortInstance.java +++ b/core/src/main/java/org/lflang/generator/PortInstance.java @@ -147,14 +147,10 @@ public List eventualDestinations() { // Construct the full range for this port. RuntimeRange range = new RuntimeRange.Port(this); - eventualDestinationRanges = eventualDestinations(range, true); + eventualDestinationRanges = eventualDestinations(range); return eventualDestinationRanges; } - public List eventualDestinationsNonzeroDelayOk() { - return eventualDestinations(new RuntimeRange.Port(this), false); - } - /** * Return a list of ranges of ports that send data to this port. If this port is directly written * to by one more more reactions, then it is its own eventual source and only this port will be @@ -259,7 +255,7 @@ public int getLevelUpperBound(MixedRadixInt index) { * * @param srcRange The source range. */ - private static List eventualDestinations(RuntimeRange srcRange, boolean zeroDelayRequired) { + private static List eventualDestinations(RuntimeRange srcRange) { // Getting the destinations is more complex than getting the sources // because of multicast, where there is more than one connection statement @@ -293,7 +289,7 @@ private static List eventualDestinations(RuntimeRange s // Need to find send ranges that overlap with this srcRange. for (SendRange wSendRange : srcPort.dependentPorts) { - if (zeroDelayRequired && wSendRange.connection != null && wSendRange.connection.getDelay() != null) { + if (wSendRange.connection != null && wSendRange.connection.getDelay() != null) { continue; } @@ -304,7 +300,7 @@ private static List eventualDestinations(RuntimeRange s } for (RuntimeRange dstRange : wSendRange.destinations) { // Recursively get the send ranges of that destination port. - List dstSendRanges = eventualDestinations(dstRange, zeroDelayRequired); + List dstSendRanges = eventualDestinations(dstRange); int sendRangeStart = 0; for (SendRange dstSend : dstSendRanges) { queue.add(dstSend.newSendRange(wSendRange, sendRangeStart)); diff --git a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java index 8060d4d9ed..95a249bd33 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java @@ -345,7 +345,7 @@ private void registerPortInstances(ReactorInstance reactor) { allPorts.addAll(reactor.inputs); allPorts.addAll(reactor.outputs); for (var port : allPorts) { - List eventualDestinations = port.eventualDestinationsNonzeroDelayOk(); + List eventualDestinations = port.eventualDestinations(); int srcDepth = (port.isInput()) ? 2 : 1; for (SendRange sendRange : eventualDestinations) { From 94680c2f8b2d5ca0c9f772c249eb033faa12bcb9 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 28 Jun 2023 16:03:51 -0700 Subject: [PATCH 206/516] Pass FeedbackDelaySimple. --- .../java/org/lflang/federated/generator/FedASTUtils.java | 8 ++++---- .../main/java/org/lflang/generator/ReactionInstance.java | 2 -- .../java/org/lflang/generator/ReactionInstanceGraph.java | 1 - 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index eb68cdf726..dcee6069f8 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -53,7 +53,6 @@ import org.lflang.generator.MixedRadixInt; import org.lflang.generator.PortInstance; import org.lflang.generator.ReactionInstance; -import org.lflang.generator.ReactorInstance; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; import org.lflang.lf.Assignment; @@ -245,7 +244,7 @@ private static void addNetworkReceiverReactor( receiver.getOutputs().add(out); addLevelAttribute( - networkInstance, connection.getDestinationPortInstance(), getDstIndex(connection)); + networkInstance, connection.getDestinationPortInstance(), getDstIndex(connection), connection); networkInstance.setReactorClass(receiver); networkInstance.setName( ASTUtils.getUniqueIdentifier(top, "nr_" + connection.getDstFederate().name)); @@ -365,7 +364,8 @@ private static MixedRadixInt getDstIndex(FedConnectionInstance connection) { } /** Add a level annotation to the instantiation of a network reactor. */ - private static void addLevelAttribute(Instantiation instantiation, PortInstance p, MixedRadixInt index) { + private static void addLevelAttribute(Instantiation instantiation, PortInstance p, MixedRadixInt index, FedConnectionInstance connection) { + if (connection.getDefinition().getDelay() != null) return; var a = LfFactory.eINSTANCE.createAttribute(); a.setAttrName("_tpoLevel"); var e = LfFactory.eINSTANCE.createAttrParm(); @@ -746,7 +746,7 @@ private static void addNetworkSenderReactor( networkInstance .getParameters() .add(getSenderIndex(connection.getSrcFederate().networkIdSender++)); - addLevelAttribute(networkInstance, connection.getSourcePortInstance(), getSrcIndex(connection)); + addLevelAttribute(networkInstance, connection.getSourcePortInstance(), getSrcIndex(connection), connection); Connection senderToReaction = factory.createConnection(); diff --git a/core/src/main/java/org/lflang/generator/ReactionInstance.java b/core/src/main/java/org/lflang/generator/ReactionInstance.java index edaab12e80..2ed89c8b75 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstance.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstance.java @@ -1,5 +1,3 @@ -/** Representation of a runtime instance of a reaction. */ - /************* * Copyright (c) 2019-2022, The University of California at Berkeley. * diff --git a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java index 95a249bd33..bfeb70c914 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java @@ -346,7 +346,6 @@ private void registerPortInstances(ReactorInstance reactor) { allPorts.addAll(reactor.outputs); for (var port : allPorts) { List eventualDestinations = port.eventualDestinations(); - int srcDepth = (port.isInput()) ? 2 : 1; for (SendRange sendRange : eventualDestinations) { for (RuntimeRange dstRange : sendRange.destinations) { From 8f339df391bf61d347fca58f188c922767d3eec9 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 28 Jun 2023 16:09:13 -0700 Subject: [PATCH 207/516] Pass SimpleFederated. --- core/src/main/java/org/lflang/generator/PortInstance.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/generator/PortInstance.java b/core/src/main/java/org/lflang/generator/PortInstance.java index 01f6e45bfb..8e8afc3a99 100644 --- a/core/src/main/java/org/lflang/generator/PortInstance.java +++ b/core/src/main/java/org/lflang/generator/PortInstance.java @@ -216,7 +216,9 @@ public void hasDependentReactionWithLevel(MixedRadixInt index, int level) { } public int getLevelUpperBound(MixedRadixInt index) { - return levelUpperBounds.get(index); + // It should be uncommon for Integer.MAX_VALUE to be used and using it can mask bugs. + // It makes sense when there is no downstream reaction. + return levelUpperBounds.getOrDefault(index, Integer.MAX_VALUE); } ////////////////////////////////////////////////////// From 04ede283d85c238b0ced156b986e490c6f25a025 Mon Sep 17 00:00:00 2001 From: revol-xut Date: Wed, 28 Jun 2023 11:47:36 +0200 Subject: [PATCH 208/516] integrated with tests --- .../cpp/CppAssembleMethodGenerator.kt | 6 +- core/src/main/resources/lib/cpp/lfutil.hh | 82 +++++++------ .../src/main/resources/lib/cpp/time_parser.hh | 112 +++++++++--------- 3 files changed, 104 insertions(+), 96 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt index e28aa5206a..c8ffec605b 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt @@ -160,7 +160,7 @@ class CppAssembleMethodGenerator(private val reactor: Reactor) { val rightPort = c.rightPorts[0] """ - this->environment()->draw_connection(${leftPort.name}, ${rightPort.name}, ${c.properties}); + this->environment()->draw_connection(&${leftPort.name}, &${rightPort.name}, ${c.properties}); """.trimIndent() } @@ -198,8 +198,8 @@ class CppAssembleMethodGenerator(private val reactor: Reactor) { private fun Connection.getConnectionLambda(portType: String): String { return """ - [this](const BasePort& left, const BasePort& right, std::size_t idx) { - left.environment()->draw_connection(left, right, $properties); + [this]($portType left, $portType right) { + left->environment()->draw_connection(left, right, $properties); } """.trimIndent() } diff --git a/core/src/main/resources/lib/cpp/lfutil.hh b/core/src/main/resources/lib/cpp/lfutil.hh index 77116dcbe1..967f42b730 100644 --- a/core/src/main/resources/lib/cpp/lfutil.hh +++ b/core/src/main/resources/lib/cpp/lfutil.hh @@ -1,7 +1,8 @@ /* * Copyright (c) 2020, TU Dresden. - * Redistribution and use in source and binary forms, with or without modification, + * 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, @@ -11,36 +12,43 @@ * 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, + * 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 + * 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. */ #pragma once -#include #include +#include namespace lfutil { -template -void after_delay(reactor::Action* action, const reactor::Port* port) { - if constexpr(std::is_void::value) { +template +void after_delay(reactor::Action *action, const reactor::Port *port) { + if constexpr (std::is_void::value) { action->schedule(); } else { action->schedule(std::move(port->get())); } } -template -void after_forward(const reactor::Action* action, reactor::Port* port) { - if constexpr(std::is_void::value) { +template +void after_forward(const reactor::Action *action, reactor::Port *port) { + if constexpr (std::is_void::value) { port->set(); } else { port->set(std::move(action->get())); @@ -48,28 +56,34 @@ void after_forward(const reactor::Action* action, reactor::Port* port) { } class LFScope { - private: - reactor::Reactor* reactor; - public: - LFScope(reactor::Reactor* reactor) : reactor(reactor) {} +private: + reactor::Reactor *reactor; - reactor::TimePoint get_physical_time() const { return reactor->get_physical_time(); } +public: + LFScope(reactor::Reactor *reactor) : reactor(reactor) {} + + reactor::TimePoint get_physical_time() const { + return reactor->get_physical_time(); + } reactor::Tag get_tag() const { return reactor->get_tag(); } - reactor::TimePoint get_logical_time() const { return reactor->get_logical_time(); } + reactor::TimePoint get_logical_time() const { + return reactor->get_logical_time(); + } reactor::mstep_t get_microstep() const { return reactor->get_microstep(); } - reactor::Duration get_elapsed_logical_time() const { return reactor->get_elapsed_logical_time(); } - reactor::Duration get_elapsed_physical_time() const { return reactor->get_elapsed_physical_time(); } - reactor::Environment* environment() const { return reactor->environment(); } + reactor::Duration get_elapsed_logical_time() const { + return reactor->get_elapsed_logical_time(); + } + reactor::Duration get_elapsed_physical_time() const { + return reactor->get_elapsed_physical_time(); + } + reactor::Environment *environment() const { return reactor->environment(); } void request_stop() const { return environment()->sync_shutdown(); } }; -template -void bind_multiple_ports( - std::vector& left_ports, - std::vector& right_ports, - bool repeat_left, - std::function connect) -{ +template +void bind_multiple_ports(std::vector &left_ports, + std::vector &right_ports, bool repeat_left, + std::function connect) { if (repeat_left) { auto l_size = left_ports.size(); auto r_size = right_ports.size(); @@ -93,15 +107,13 @@ void bind_multiple_ports( << "Not all ports will be connected!"; } - std::size_t idx{0}; while (left_it != left_ports.end() && right_it != right_ports.end()) { auto left = *left_it; auto right = *right_it; - connect(left, right, idx); + connect(left, right); left_it++; right_it++; - idx++; } } -} +} // namespace lfutil diff --git a/core/src/main/resources/lib/cpp/time_parser.hh b/core/src/main/resources/lib/cpp/time_parser.hh index b34b8029fd..58c067b796 100644 --- a/core/src/main/resources/lib/cpp/time_parser.hh +++ b/core/src/main/resources/lib/cpp/time_parser.hh @@ -1,7 +1,8 @@ /* * Copyright (c) 2020, TU Dresden. - * Redistribution and use in source and binary forms, with or without modification, + * 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, @@ -11,14 +12,21 @@ * 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, + * 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 + * 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. */ @@ -27,43 +35,36 @@ #include "reactor-cpp/reactor-cpp.hh" #include -std::stringstream &operator>>(std::stringstream& in, reactor::Duration& dur); +std::stringstream &operator>>(std::stringstream &in, reactor::Duration &dur); #include "CLI/cxxopts.hpp" +#include #include #include -#include #include -#include +#include -bool iequals(const std::string& a, const std::string& b) -{ - return std::equal(a.begin(), a.end(), - b.begin(), b.end(), - [](char a, char b) { - return tolower(a) == tolower(b); - }); +bool iequals(const std::string &a, const std::string &b) { + return std::equal(a.begin(), a.end(), b.begin(), b.end(), + [](char a, char b) { return tolower(a) == tolower(b); }); } -class argument_incorrect_type_with_reason : public cxxopts::OptionParseException -{ - public: - explicit argument_incorrect_type_with_reason( - const std::string& arg, - const std::string& reason) - : cxxopts::OptionParseException( - "Argument " + cxxopts::LQUOTE + arg + cxxopts::RQUOTE + " failed to parse (" + reason + ")" - ) - {} +class argument_incorrect_type_with_reason + : public cxxopts::OptionParseException { +public: + explicit argument_incorrect_type_with_reason(const std::string &arg, + const std::string &reason) + : cxxopts::OptionParseException("Argument " + cxxopts::LQUOTE + arg + + cxxopts::RQUOTE + " failed to parse (" + + reason + ")") {} }; - -std::string validate_time_string(const std::string& time); +std::string validate_time_string(const std::string &time); /** * converts a reactor::Duration to a string with ns as unit */ -std::string time_to_string(const reactor::Duration& dur) { +std::string time_to_string(const reactor::Duration &dur) { if (dur == reactor::Duration::max()) { return "forever"; } @@ -73,14 +74,13 @@ std::string time_to_string(const reactor::Duration& dur) { return ss.str(); } -template -std::string any_to_string(const T val){ - std::stringstream ss; - ss << val; - return ss.str(); +template std::string any_to_string(const T val) { + std::stringstream ss; + ss << val; + return ss.str(); } -std::stringstream &operator>>(std::stringstream& in, reactor::Duration& dur) { +std::stringstream &operator>>(std::stringstream &in, reactor::Duration &dur) { double value; std::string unit; @@ -107,7 +107,7 @@ std::stringstream &operator>>(std::stringstream& in, reactor::Duration& dur) { } } else { in >> unit; - if (unit == "nsec" || unit == "nsecs" || unit == "ns" ) { + if (unit == "nsec" || unit == "nsecs" || unit == "ns") { std::chrono::duration tmp{value}; dur = std::chrono::duration_cast(tmp); } else if (unit == "usec" || unit == "usecs" || unit == "us") { @@ -116,22 +116,22 @@ std::stringstream &operator>>(std::stringstream& in, reactor::Duration& dur) { } else if (unit == "msec" || unit == "msecs" || unit == "ms") { std::chrono::duration tmp{value}; dur = std::chrono::duration_cast(tmp); - } else if (unit == "sec" || unit == "secs" || - unit == "second" || unit == "seconds" || unit == "s") { + } else if (unit == "sec" || unit == "secs" || unit == "second" || + unit == "seconds" || unit == "s") { std::chrono::duration> tmp{value}; dur = std::chrono::duration_cast(tmp); - } else if (unit == "min" || unit == "mins" || - unit == "minute" || unit == "minutes" || unit == "m") { + } else if (unit == "min" || unit == "mins" || unit == "minute" || + unit == "minutes" || unit == "m") { std::chrono::duration> tmp{value}; dur = std::chrono::duration_cast(tmp); } else if (unit == "hour" || unit == "hours" || unit == "h") { std::chrono::duration> tmp{value}; dur = std::chrono::duration_cast(tmp); } else if (unit == "day" || unit == "days" || unit == "d") { - std::chrono::duration> tmp{value}; + std::chrono::duration> tmp{value}; dur = std::chrono::duration_cast(tmp); } else if (unit == "week" || unit == "weeks" || unit == "w") { - std::chrono::duration> tmp{value}; + std::chrono::duration> tmp{value}; dur = std::chrono::duration_cast(tmp); } else { // mark as error @@ -143,9 +143,9 @@ std::stringstream &operator>>(std::stringstream& in, reactor::Duration& dur) { } /** -* Tests for correct syntax in unit usage for time strings -**/ -std::string validate_time_string(const std::string& time) { + * Tests for correct syntax in unit usage for time strings + **/ +std::string validate_time_string(const std::string &time) { auto trimmed = std::regex_replace(time, std::regex("^ +| +$|( ) +"), "$1"); if (trimmed.size() == 0) { return "The empty string is not a valid time!"; @@ -161,16 +161,14 @@ std::string validate_time_string(const std::string& time) { return "No unit given!"; } else { auto unit = trimmed.substr(pos); - if (unit == "nsec" || unit == "nsecs" || unit == "ns" || - unit == "usec" || unit == "usecs" || unit == "us" || - unit == "msec" || unit == "msecs" || unit == "ms" || - unit == "sec" || unit == "secs" || + if (unit == "nsec" || unit == "nsecs" || unit == "ns" || unit == "usec" || + unit == "usecs" || unit == "us" || unit == "msec" || + unit == "msecs" || unit == "ms" || unit == "sec" || unit == "secs" || unit == "second" || unit == "seconds" || unit == "s" || - unit == "min" || unit == "mins" || - unit == "minute" || unit == "minutes" || unit == "m" || - unit == "hour" || unit == "hours" || unit == "h" || - unit == "day" || unit == "days" || unit == "d" || - unit == "week" || unit == "weeks" || unit == "w") { + unit == "min" || unit == "mins" || unit == "minute" || + unit == "minutes" || unit == "m" || unit == "hour" || + unit == "hours" || unit == "h" || unit == "day" || unit == "days" || + unit == "d" || unit == "week" || unit == "weeks" || unit == "w") { return ""; } else { std::stringstream ss; @@ -181,5 +179,3 @@ std::string validate_time_string(const std::string& time) { } return "Unexpected error!"; } - - From 380577132f18c4371561f16d40d49afb0606176d Mon Sep 17 00:00:00 2001 From: revol-xut Date: Thu, 29 Jun 2023 16:05:44 +0200 Subject: [PATCH 209/516] upgrading reactor-cpp --- core/src/main/resources/lib/cpp/reactor-cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/cpp/reactor-cpp b/core/src/main/resources/lib/cpp/reactor-cpp index e3564782f0..909a32205f 160000 --- a/core/src/main/resources/lib/cpp/reactor-cpp +++ b/core/src/main/resources/lib/cpp/reactor-cpp @@ -1 +1 @@ -Subproject commit e3564782f0e1a2bc427e5932a88ff8f87dacd50c +Subproject commit 909a32205f7048d77a0cd6894f78f6595d92be70 From ade0f724047f04d8e0f93e1c7f514950583f8392 Mon Sep 17 00:00:00 2001 From: revol-xut Date: Thu, 29 Jun 2023 16:59:57 +0200 Subject: [PATCH 210/516] upgrading reactor-cpp --- core/src/main/resources/lib/cpp/reactor-cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/cpp/reactor-cpp b/core/src/main/resources/lib/cpp/reactor-cpp index 909a32205f..4d1d18fca6 160000 --- a/core/src/main/resources/lib/cpp/reactor-cpp +++ b/core/src/main/resources/lib/cpp/reactor-cpp @@ -1 +1 @@ -Subproject commit 909a32205f7048d77a0cd6894f78f6595d92be70 +Subproject commit 4d1d18fca678fa89242944c560fb42bff6eb8652 From 56e762bce373d794e080a5cbbc452c255e9e23b9 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 29 Jun 2023 13:09:25 -0700 Subject: [PATCH 211/516] Pass DecentralizedP2PComm. --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index bef58d3a2b..9e68085774 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit bef58d3a2b9c05dfb733bc057480f66a033936e4 +Subproject commit 9e680857747bb9640692140e0d595e22a752f64f From 589c61ccdbcedcbc5a92d7c3850508746f48ba66 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 29 Jun 2023 15:29:39 -0700 Subject: [PATCH 212/516] Update submodule. --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 9e68085774..779b97f0bb 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 9e680857747bb9640692140e0d595e22a752f64f +Subproject commit 779b97f0bb032bdf1e21d9b39ba9654c3b537e35 From 587233dd81b9391bda8b2024a3d4b30122ed1366 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 29 Jun 2023 16:15:54 -0700 Subject: [PATCH 213/516] Format. --- .../lflang/federated/generator/FedASTUtils.java | 14 +++++++++++--- .../java/org/lflang/generator/PortInstance.java | 1 - .../lflang/generator/ReactionInstanceGraph.java | 2 +- .../java/org/lflang/generator/c/CGenerator.java | 3 +-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index dcee6069f8..8f0579cb77 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -244,7 +244,10 @@ private static void addNetworkReceiverReactor( receiver.getOutputs().add(out); addLevelAttribute( - networkInstance, connection.getDestinationPortInstance(), getDstIndex(connection), connection); + networkInstance, + connection.getDestinationPortInstance(), + getDstIndex(connection), + connection); networkInstance.setReactorClass(receiver); networkInstance.setName( ASTUtils.getUniqueIdentifier(top, "nr_" + connection.getDstFederate().name)); @@ -364,7 +367,11 @@ private static MixedRadixInt getDstIndex(FedConnectionInstance connection) { } /** Add a level annotation to the instantiation of a network reactor. */ - private static void addLevelAttribute(Instantiation instantiation, PortInstance p, MixedRadixInt index, FedConnectionInstance connection) { + private static void addLevelAttribute( + Instantiation instantiation, + PortInstance p, + MixedRadixInt index, + FedConnectionInstance connection) { if (connection.getDefinition().getDelay() != null) return; var a = LfFactory.eINSTANCE.createAttribute(); a.setAttrName("_tpoLevel"); @@ -746,7 +753,8 @@ private static void addNetworkSenderReactor( networkInstance .getParameters() .add(getSenderIndex(connection.getSrcFederate().networkIdSender++)); - addLevelAttribute(networkInstance, connection.getSourcePortInstance(), getSrcIndex(connection), connection); + addLevelAttribute( + networkInstance, connection.getSourcePortInstance(), getSrcIndex(connection), connection); Connection senderToReaction = factory.createConnection(); diff --git a/core/src/main/java/org/lflang/generator/PortInstance.java b/core/src/main/java/org/lflang/generator/PortInstance.java index 8e8afc3a99..fffcd70d0f 100644 --- a/core/src/main/java/org/lflang/generator/PortInstance.java +++ b/core/src/main/java/org/lflang/generator/PortInstance.java @@ -26,7 +26,6 @@ package org.lflang.generator; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java index bfeb70c914..de7c3b6567 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java @@ -338,7 +338,7 @@ private void getAllContainedReactions(List runtimeReactions, ReactorIns /////////////////////////////////////////////////////////// //// Private methods - public record MriPortPair(MixedRadixInt index, PortInstance port) {} + public record MriPortPair(MixedRadixInt index, PortInstance port) {} private void registerPortInstances(ReactorInstance reactor) { var allPorts = new ArrayList(); diff --git a/core/src/main/java/org/lflang/generator/c/CGenerator.java b/core/src/main/java/org/lflang/generator/c/CGenerator.java index a9eae3ec9f..7bed894341 100644 --- a/core/src/main/java/org/lflang/generator/c/CGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CGenerator.java @@ -601,8 +601,7 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { GeneratorUtils.refreshProject(resource, context.getMode()); } - private void - generateCodeFor(String lfModuleName) throws IOException { + private void generateCodeFor(String lfModuleName) throws IOException { startTimeStepIsPresentCount = 0; code.pr(generateDirectives()); code.pr(new CMainFunctionGenerator(targetConfig).generateCode()); From eba231f2ebdf64b1405dd0014d3518ae1b0d8a0d Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 29 Jun 2023 16:16:06 -0700 Subject: [PATCH 214/516] Make questionable change to MixedRadixInt. This might not make sense. It should be revisited. --- .../org/lflang/generator/MixedRadixInt.java | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/MixedRadixInt.java b/core/src/main/java/org/lflang/generator/MixedRadixInt.java index ab15082287..aee756ec66 100644 --- a/core/src/main/java/org/lflang/generator/MixedRadixInt.java +++ b/core/src/main/java/org/lflang/generator/MixedRadixInt.java @@ -27,7 +27,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.generator; import com.google.common.collect.ImmutableList; - import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; @@ -71,20 +70,19 @@ public class MixedRadixInt { public MixedRadixInt(List digits, List radixes, List permutation) { if (radixes == null || (digits != null && digits.size() > radixes.size()) - || (permutation != null && permutation.size() != radixes.size()) - || radixes.contains(0)) { + || (permutation != null && permutation.size() != radixes.size())) { throw new IllegalArgumentException("Invalid constructor arguments."); } this.radixes = ImmutableList.copyOf(radixes); if (digits != null) { this.digits = digits; } else { - this.digits = new ArrayList(1); + this.digits = new ArrayList<>(1); this.digits.add(0); } if (permutation != null) { // Check the permutation matrix. - Set indices = new HashSet(); + Set indices = new HashSet<>(); for (int p : permutation) { if (p < 0 || p >= radixes.size() || indices.contains(p)) { throw new IllegalArgumentException( @@ -136,7 +134,7 @@ public List getDigits() { public List getPermutation() { if (permutation == null) { // Construct a default permutation. - permutation = new ArrayList(radixes.size()); + permutation = new ArrayList<>(radixes.size()); for (int i = 0; i < radixes.size(); i++) { permutation.add(i); } @@ -201,16 +199,18 @@ public int numDigits() { * @param v The ordinary integer value of this number. */ public void set(int v) { + // it does not make sense to call set int temp = v; int count = 0; for (int radix : radixes) { + var digit = radix == 0 ? 0 : temp % radix; if (count >= digits.size()) { - digits.add(temp % radix); + digits.add(digit); } else { - digits.set(count, temp % radix); + digits.set(count, digit); } count++; - temp = temp / radix; + temp = temp == 0 ? temp : temp / radix; } } @@ -226,8 +226,13 @@ public void setMagnitude(int v) { for (int i = 0; i < radixes.size(); i++) { int p = getPermutation().get(i); while (digits.size() < p + 1) digits.add(0); - digits.set(p, temp % radixes.get(p)); - temp = temp / radixes.get(p); + var r = radixes.get(p); + if (r == 0 && v == 0) { + digits.set(p, 0); // zero does not make sense here, but we have to put something. + } else { + digits.set(p, temp % r); + temp = temp / r; + } } } @@ -237,7 +242,7 @@ public void setMagnitude(int v) { */ @Override public String toString() { - List pieces = new LinkedList(); + List pieces = new LinkedList<>(); Iterator radixIterator = radixes.iterator(); for (int digit : digits) { if (!radixIterator.hasNext()) { @@ -260,7 +265,10 @@ public int hashCode() { @Override public boolean equals(Object obj) { - return obj instanceof MixedRadixInt mri && radixes.equals(mri.radixes) && digits.equals(mri.digits) && permutation.equals(mri.permutation); + return obj instanceof MixedRadixInt mri + && radixes.equals(mri.radixes) + && digits.equals(mri.digits) + && permutation.equals(mri.permutation); } public MixedRadixInt copy() { From bb77945ab8033286dcd9bfed2bb800574c8456f8 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 29 Jun 2023 16:40:41 -0700 Subject: [PATCH 215/516] Pass DistributedCountDecentralized. --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 779b97f0bb..40d24c320e 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 779b97f0bb032bdf1e21d9b39ba9654c3b537e35 +Subproject commit 40d24c320eb53520372653a1cd7fa338fc7237e7 From ceb444f141ae7088da31a9c1582343611055e8d4 Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Fri, 30 Jun 2023 13:59:07 +0900 Subject: [PATCH 216/516] Make TS federated codes can be compiled --- .../java/org/lflang/federated/extensions/TSExtension.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java index 14df96afb5..68f91853cc 100644 --- a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java @@ -48,12 +48,11 @@ public String generateNetworkReceiverBody( return """ // generateNetworkReceiverBody if (%1$s !== undefined) { - %2$s.%3$s = %1$s; + %2$s = %1$s; } """ .formatted( action.getName(), - receivingPort.getContainer().getName(), receivingPort.getVariable().getName()); } @@ -66,12 +65,11 @@ public String generateNetworkSenderBody( CoordinationType coordinationType, ErrorReporter errorReporter) { return """ - if (%1$s.%2$s !== undefined) { - this.util.sendRTITimedMessage(%1$s.%2$s, %3$s, %4$s, %5$s); + if (%1$s !== undefined) { + this.util.sendRTITimedMessage(%1$s, %2$s, %3$s, %4$s); } """ .formatted( - sendingPort.getContainer().getName(), sendingPort.getVariable().getName(), connection.getDstFederate().id, connection.getDstFederate().networkMessageActions.size(), From ec0198cf94e6346a6bbd02355dabc46164a25bff Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 30 Jun 2023 15:13:17 +0800 Subject: [PATCH 217/516] Factor out createMainInstantiation() --- .../lflang/analyses/uclid/UclidGenerator.java | 31 ++------------ .../main/java/org/lflang/ast/ASTUtils.java | 41 +++++++++++++++++++ .../org/lflang/generator/c/CGenerator.java | 37 ++--------------- 3 files changed, 47 insertions(+), 62 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java index 42285ac028..573ba7a3ea 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java @@ -156,16 +156,14 @@ public UclidGenerator(LFGeneratorContext context, List properties) { //// Public methods public void doGenerate(Resource resource, LFGeneratorContext context) { - // FIXME: How much of doGenerate() from GeneratorBase is needed? + // Reuse parts of doGenerate() from GeneratorBase. super.printInfo(context.getMode()); ASTUtils.setMainName(context.getFileConfig().resource, context.getFileConfig().name); - // FIXME: Perform an analysis on the property and remove unrelevant components. super.createMainInstantiation(); - //////////////////////////////////////// - // Create the main reactor instance if there is a main reactor. - createMainReactorInstance(); + this.main = ASTUtils.createMainReactorInstance( + mainDef, reactors, hasDeadlines, messageReporter, targetConfig); // Extract information from the named instances. populateDataStructures(); @@ -1492,29 +1490,6 @@ protected void generateControlBlock() { //////////////////////////////////////////////////////////// //// 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 is done once because - // it is the same for all federates. - this.main = - new ReactorInstance(ASTUtils.toDefinition(mainDef.getReactorClass()), messageReporter); - var reactionInstanceGraph = this.main.assignLevels(); - if (reactionInstanceGraph.nodeCount() > 0) { - messageReporter - .nowhere() - .error("Main reactor has causality cycles. Skipping code generation."); - return; - } - } - } - } - private void setupDirectories() { // Make sure the target directory exists. Path modelGenDir = context.getFileConfig().getModelGenPath(); diff --git a/core/src/main/java/org/lflang/ast/ASTUtils.java b/core/src/main/java/org/lflang/ast/ASTUtils.java index 1ecf7606de..402b7ddf6b 100644 --- a/core/src/main/java/org/lflang/ast/ASTUtils.java +++ b/core/src/main/java/org/lflang/ast/ASTUtils.java @@ -58,7 +58,9 @@ import org.eclipse.xtext.xbase.lib.IteratorExtensions; import org.eclipse.xtext.xbase.lib.StringExtensions; import org.lflang.InferredType; +import org.lflang.MessageReporter; import org.lflang.Target; +import org.lflang.TargetConfig; import org.lflang.TimeUnit; import org.lflang.TimeValue; import org.lflang.generator.CodeMap; @@ -585,6 +587,45 @@ public static List collectElements( return result; } + /** + * 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. + */ + public static ReactorInstance createMainReactorInstance( + Instantiation mainDef, + List reactors, + boolean hasDeadlines, + MessageReporter messageReporter, + TargetConfig targetConfig + ) { + if (mainDef != null) { + // Recursively build instances. + ReactorInstance main = + new ReactorInstance(toDefinition(mainDef.getReactorClass()), messageReporter, reactors); + var reactionInstanceGraph = main.assignLevels(); + if (reactionInstanceGraph.nodeCount() > 0) { + messageReporter + .nowhere() + .error("Main reactor has causality cycles. Skipping code generation."); + return null; + } + if (hasDeadlines) { + main.assignDeadlines(); + } + // Inform the run-time of the breadth/parallelism of the reaction graph + var breadth = reactionInstanceGraph.getBreadth(); + if (breadth == 0) { + messageReporter.nowhere().warning("The program has no reactions"); + } else { + targetConfig.compileDefinitions.put( + "LF_REACTION_GRAPH_BREADTH", String.valueOf(reactionInstanceGraph.getBreadth())); + } + return main; + } + return null; + } + /** * Adds the elements into the given list at a location matching to their textual position. * diff --git a/core/src/main/java/org/lflang/generator/c/CGenerator.java b/core/src/main/java/org/lflang/generator/c/CGenerator.java index 1099b4e7ca..0929295f54 100644 --- a/core/src/main/java/org/lflang/generator/c/CGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CGenerator.java @@ -1957,7 +1957,9 @@ protected void setUpGeneralParameters() { "LOG_LEVEL", String.valueOf(targetConfig.logLevel.ordinal())); targetConfig.compileAdditionalSources.addAll(CCoreFilesUtils.getCTargetSrc()); // Create the main reactor instance if there is a main reactor. - createMainReactorInstance(); + // FIXME: is `hasDeadlines` here always false? That does not look right. + this.main = ASTUtils.createMainReactorInstance( + mainDef, reactors, hasDeadlines, messageReporter, targetConfig); if (hasModalReactors) { // So that each separate compile knows about modal reactors, do this: targetConfig.compileDefinitions.put("MODAL_REACTORS", "TRUE"); @@ -2104,39 +2106,6 @@ public Target getTarget() { //////////////////////////////////////////////////////////// //// 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()), messageReporter, reactors); - var reactionInstanceGraph = this.main.assignLevels(); - if (reactionInstanceGraph.nodeCount() > 0) { - messageReporter - .nowhere() - .error("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) { - messageReporter.nowhere().warning("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. * From f7d94f59dafa9a9307aba279b024f4132e93e3df Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 30 Jun 2023 15:57:14 +0800 Subject: [PATCH 218/516] Factor generateTriggersAndReactions() into multiple smaller functions. Change tactic to an enum. --- .../org/lflang/analyses/uclid/MTLVisitor.java | 11 ++-- .../lflang/analyses/uclid/UclidGenerator.java | 66 ++++++++++++++----- 2 files changed, 56 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/uclid/MTLVisitor.java b/core/src/main/java/org/lflang/analyses/uclid/MTLVisitor.java index b8f4981eea..0a60b08d5c 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/MTLVisitor.java +++ b/core/src/main/java/org/lflang/analyses/uclid/MTLVisitor.java @@ -26,6 +26,7 @@ import org.lflang.TimeUnit; import org.lflang.TimeValue; +import org.lflang.analyses.uclid.UclidGenerator.Tactic; import org.lflang.dsl.MTLParser; import org.lflang.dsl.MTLParserBaseVisitor; import org.lflang.generator.CodeBuilder; @@ -40,13 +41,13 @@ public class MTLVisitor extends MTLParserBaseVisitor { protected CodeBuilder code = new CodeBuilder(); /** Tactic to be used to prove the property. */ - protected String tactic; + protected Tactic tactic; /** Time horizon (in nanoseconds) of the property */ protected long horizon = 0; // Constructor - public MTLVisitor(String tactic) { + public MTLVisitor(Tactic tactic) { this.tactic = tactic; } @@ -178,7 +179,7 @@ public String visitUntil( } String end; - if (this.tactic.equals("induction")) { + if (this.tactic == Tactic.INDUCTION) { end = "(" + prefixIdx + " + CT)"; } else { end = "END"; @@ -286,7 +287,7 @@ public String visitGlobally( long horizon) { String end; - if (this.tactic.equals("induction")) { + if (this.tactic == Tactic.INDUCTION) { end = "(" + prefixIdx + " + CT)"; } else { end = "END"; @@ -345,7 +346,7 @@ public String visitFinally( long horizon) { String end; - if (this.tactic.equals("induction")) { + if (this.tactic == Tactic.INDUCTION) { end = "(" + prefixIdx + " + CT)"; } else { end = "END"; diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java index 573ba7a3ea..35e27269b9 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java @@ -84,44 +84,49 @@ public class UclidGenerator extends GeneratorBase { //////////////////////////////////////////// //// Public fields - // Data structures for storing info about the runtime instances. + /** Data structures for storing info about the runtime instances. */ public List reactorInstances = new ArrayList(); public List reactionInstances = new ArrayList(); - // State variables in the system + /** State variables in the system */ public List stateVariables = new ArrayList(); - // Triggers in the system + /** Triggers in the system */ public List actionInstances = new ArrayList(); public List inputInstances = new ArrayList(); public List outputInstances = new ArrayList(); public List portInstances = new ArrayList(); public List timerInstances = new ArrayList(); - // Joint lists of the lists above. + /** Joint lists of the lists above. */ public List triggerInstances; // Triggers = ports + actions + timers public List namedInstances; // Named instances = triggers + state variables - // A list of paths to the uclid files generated + /** A list of paths to the uclid files generated */ public List generatedFiles = new ArrayList<>(); public Map expectations = new HashMap<>(); - // The directory where the generated files are placed + /** The directory where the generated files are placed */ public Path outputDir; - // A runner for the generated Uclid files + /** A runner for the generated Uclid files */ public UclidRunner runner; - // If true, use logical time-based semantics; - // otherwise, use event-based semantics, - // as described in Sirjani et. al (2020). + /** + * If true, use logical time-based semantics; + * otherwise, use event-based semantics, + * as described in Sirjani et. al (2020). + * This is currently always false and serves + * as a placeholder for a future version that + * supports logical time-based semantics. + */ public boolean logicalTimeBased = false; //////////////////////////////////////////// //// Protected fields - // A list of MTL properties represented in Attributes. + /** A list of MTL properties represented in Attributes. */ protected List properties; /** The main place to put generated code. */ @@ -130,8 +135,19 @@ public class UclidGenerator extends GeneratorBase { /** Strings from the property attribute */ protected String name; - protected String tactic; - protected String spec; // SMTL + /** + * A tactic used to verify properties. + * Currently, only BMC (bounded model checking) is functional. + * FIXME: For a future version that supports multiple tactics, + * the tactics should be stored in a list. + */ + enum Tactic { BMC, INDUCTION } + protected Tactic tactic; + + /** Safety MTL property to be verified */ + protected String spec; + + /** A property's ground truth value, for debugging the verifier */ protected String expect; /** @@ -180,13 +196,14 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { .findFirst() .get() .getValue()); - this.tactic = + String tacticStr = StringUtil.removeQuotes( prop.getAttrParms().stream() .filter(attr -> attr.getName().equals("tactic")) .findFirst() .get() .getValue()); + if (tacticStr.equals("bmc")) this.tactic = Tactic.BMC; this.spec = StringUtil.removeQuotes( prop.getAttrParms().stream() @@ -825,8 +842,16 @@ protected void generateReactorSemantics() { } } - /** Axioms for the trigger mechanism */ + /** Axioms for all triggers */ protected void generateTriggersAndReactions() { + generateConnectionAxioms(); + generateActionAxioms(); + generateTimerAxioms(); + generateReactionTriggerAxioms(); + } + + /** Generate axiomatic semantics for connections */ + protected void generateConnectionAxioms() { code.pr(String.join("\n", "/***************", " * Connections *", " ***************/")); // FIXME: Support banks and multiports. Figure out how to work with ranges. // Iterate over all the ports. Generate an axiom for each connection @@ -923,7 +948,10 @@ protected void generateTriggersAndReactions() { } } } + } + /** Generate axiomatic semantics for actions */ + protected void generateActionAxioms() { if (this.actionInstances.size() > 0) { code.pr(String.join("\n", "/***********", " * Actions *", " ***********/")); for (var action : this.actionInstances) { @@ -981,7 +1009,10 @@ protected void generateTriggersAndReactions() { "));")); } } + } + /** Generate axiomatic semantics for timers */ + protected void generateTimerAxioms() { if (this.timerInstances.size() > 0) { code.pr(String.join("\n", "/**********", " * Timers *", " **********/")); @@ -1051,7 +1082,10 @@ protected void generateTriggersAndReactions() { ");")); } } + } + /** Axioms for encoding how reactions are triggered. */ + protected void generateReactionTriggerAxioms() { code.pr( String.join( "\n", @@ -1457,7 +1491,7 @@ protected void generateProperty() { code.pr(this.FOLSpec + ";"); code.unindent(); - if (this.tactic.equals("bmc")) { + if (this.tactic == Tactic.BMC) { code.pr( String.join( "\n", From f422a2adeb04de351661b2917572ae42be00452a Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 30 Jun 2023 16:13:45 +0800 Subject: [PATCH 219/516] Separate public from private variables --- .../lflang/analyses/uclid/UclidGenerator.java | 79 +++++++++++-------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java index 35e27269b9..a9ac774dd9 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java @@ -84,20 +84,11 @@ public class UclidGenerator extends GeneratorBase { //////////////////////////////////////////// //// Public fields - /** Data structures for storing info about the runtime instances. */ - public List reactorInstances = new ArrayList(); - public List reactionInstances = - new ArrayList(); + /** A list of reaction runtime instances. */ + public List reactionInstances = new ArrayList(); - /** State variables in the system */ - public List stateVariables = new ArrayList(); - - /** Triggers in the system */ + /** A list of action instances */ public List actionInstances = new ArrayList(); - public List inputInstances = new ArrayList(); - public List outputInstances = new ArrayList(); - public List portInstances = new ArrayList(); - public List timerInstances = new ArrayList(); /** Joint lists of the lists above. */ public List triggerInstances; // Triggers = ports + actions + timers @@ -113,27 +104,37 @@ public class UclidGenerator extends GeneratorBase { /** A runner for the generated Uclid files */ public UclidRunner runner; - /** - * If true, use logical time-based semantics; - * otherwise, use event-based semantics, - * as described in Sirjani et. al (2020). - * This is currently always false and serves - * as a placeholder for a future version that - * supports logical time-based semantics. - */ - public boolean logicalTimeBased = false; + /** Completeness threshold */ + public int CT = 0; //////////////////////////////////////////// - //// Protected fields + //// Private fields + /** A list of reactor runtime instances. */ + private List reactorInstances = new ArrayList(); + + /** State variables in the system */ + private List stateVariables = new ArrayList(); + + /** A list of input port instances */ + private List inputInstances = new ArrayList(); + + /** A list of output port instances */ + private List outputInstances = new ArrayList(); + + /** A list of input AND output port instances */ + private List portInstances = new ArrayList(); + + /** A list of timer instances */ + private List timerInstances = new ArrayList(); /** A list of MTL properties represented in Attributes. */ - protected List properties; + private List properties; /** The main place to put generated code. */ - protected CodeBuilder code = new CodeBuilder(); + private CodeBuilder code = new CodeBuilder(); /** Strings from the property attribute */ - protected String name; + private String name; /** * A tactic used to verify properties. @@ -142,26 +143,38 @@ public class UclidGenerator extends GeneratorBase { * the tactics should be stored in a list. */ enum Tactic { BMC, INDUCTION } - protected Tactic tactic; + private Tactic tactic; /** Safety MTL property to be verified */ - protected String spec; + private String spec; /** A property's ground truth value, for debugging the verifier */ - protected String expect; + private String expect; /** * The horizon (the total time interval required for evaluating an MTL property, which is derived * from the MTL spec), the completeness threshold (CT) (the number of transitions required for * evaluating the FOL spec in the trace), and the transpiled FOL spec. */ - protected long horizon = 0; // in nanoseconds + private long horizon = 0; // in nanoseconds + + /** First-Order Logic formula matching the Safety MTL property */ + private String FOLSpec = ""; - protected String FOLSpec = ""; - protected int CT = 0; - protected static final int CT_MAX_SUPPORTED = 100; + /** Maximum CT supported. This is a hardcoded value. */ + private static final int CT_MAX_SUPPORTED = 100; + + /** + * If true, use logical time-based semantics; + * otherwise, use event-based semantics, + * as described in Sirjani et. al (2020). + * This is currently always false and serves + * as a placeholder for a future version that + * supports logical time-based semantics. + */ + private boolean logicalTimeBased = false; - // Constructor + /** Constructor */ public UclidGenerator(LFGeneratorContext context, List properties) { super(context); this.properties = properties; From 877ce36687f386ac80fb6e386a36e6722438cd3a Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 30 Jun 2023 16:25:58 +0800 Subject: [PATCH 220/516] Improve pattern matching and apply spotless --- .../org/lflang/analyses/uclid/MTLVisitor.java | 27 +++++++-------- .../lflang/analyses/uclid/UclidGenerator.java | 34 +++++++++++-------- .../main/java/org/lflang/ast/ASTUtils.java | 11 +++--- .../org/lflang/generator/c/CGenerator.java | 5 +-- 4 files changed, 39 insertions(+), 38 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/uclid/MTLVisitor.java b/core/src/main/java/org/lflang/analyses/uclid/MTLVisitor.java index 0a60b08d5c..1aef6fe554 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/MTLVisitor.java +++ b/core/src/main/java/org/lflang/analyses/uclid/MTLVisitor.java @@ -147,23 +147,20 @@ public String _visitUnaryOp( long horizon) { // FIXME: Is there a more "antlr" way to do dispatch here? - if (ctx instanceof MTLParser.NoUnaryOpContext) { - return visitNoUnaryOp( - (MTLParser.NoUnaryOpContext) ctx, prefixIdx, QFIdx, prevPrefixIdx, horizon); + if (ctx instanceof MTLParser.NoUnaryOpContext _ctx) { + return visitNoUnaryOp(_ctx, prefixIdx, QFIdx, prevPrefixIdx, horizon); } - if (ctx instanceof MTLParser.NegationContext) { - return visitNegation( - (MTLParser.NegationContext) ctx, prefixIdx, QFIdx, prevPrefixIdx, horizon); + if (ctx instanceof MTLParser.NegationContext _ctx) { + return visitNegation(_ctx, prefixIdx, QFIdx, prevPrefixIdx, horizon); } - if (ctx instanceof MTLParser.NextContext) { - return visitNext((MTLParser.NextContext) ctx, prefixIdx, QFIdx, prevPrefixIdx, horizon); + if (ctx instanceof MTLParser.NextContext _ctx) { + return visitNext(_ctx, prefixIdx, QFIdx, prevPrefixIdx, horizon); } - if (ctx instanceof MTLParser.GloballyContext) { - return visitGlobally( - (MTLParser.GloballyContext) ctx, prefixIdx, QFIdx, prevPrefixIdx, horizon); + if (ctx instanceof MTLParser.GloballyContext _ctx) { + return visitGlobally(_ctx, prefixIdx, QFIdx, prevPrefixIdx, horizon); } - if (ctx instanceof MTLParser.FinallyContext) { - return visitFinally((MTLParser.FinallyContext) ctx, prefixIdx, QFIdx, prevPrefixIdx, horizon); + if (ctx instanceof MTLParser.FinallyContext _ctx) { + return visitFinally(_ctx, prefixIdx, QFIdx, prevPrefixIdx, horizon); } // FIXME: Throw an exception. @@ -565,8 +562,8 @@ private long getNanoSecFromIntervalContext(MTLParser.IntervalContext ctx, boolea * Generate a time predicate from a range. * * @param ctx - * @param lowerBoundNanoSec - * @param upperBoundNanoSec + * @param lowerBoundNanoSec The lowerbound of the time interval (in nanoseconds) in an MTL formula + * @param upperBoundNanoSec The upperbound of the time interval (in nanoseconds) in an MTL formula * @return */ private String generateTimePredicate( diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java index a9ac774dd9..ac84b7dcc2 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java @@ -85,17 +85,20 @@ public class UclidGenerator extends GeneratorBase { //////////////////////////////////////////// //// Public fields /** A list of reaction runtime instances. */ - public List reactionInstances = new ArrayList(); + public List reactionInstances = + new ArrayList(); /** A list of action instances */ public List actionInstances = new ArrayList(); /** Joint lists of the lists above. */ public List triggerInstances; // Triggers = ports + actions + timers + public List namedInstances; // Named instances = triggers + state variables /** A list of paths to the uclid files generated */ public List generatedFiles = new ArrayList<>(); + public Map expectations = new HashMap<>(); /** The directory where the generated files are placed */ @@ -136,13 +139,16 @@ public class UclidGenerator extends GeneratorBase { /** Strings from the property attribute */ private String name; - /** - * A tactic used to verify properties. - * Currently, only BMC (bounded model checking) is functional. - * FIXME: For a future version that supports multiple tactics, - * the tactics should be stored in a list. + /** + * A tactic used to verify properties. Currently, only BMC (bounded model checking) is functional. + * FIXME: For a future version that supports multiple tactics, the tactics should be stored in a + * list. */ - enum Tactic { BMC, INDUCTION } + enum Tactic { + BMC, + INDUCTION + } + private Tactic tactic; /** Safety MTL property to be verified */ @@ -165,12 +171,9 @@ enum Tactic { BMC, INDUCTION } private static final int CT_MAX_SUPPORTED = 100; /** - * If true, use logical time-based semantics; - * otherwise, use event-based semantics, - * as described in Sirjani et. al (2020). - * This is currently always false and serves - * as a placeholder for a future version that - * supports logical time-based semantics. + * If true, use logical time-based semantics; otherwise, use event-based semantics, as described + * in Sirjani et. al (2020). This is currently always false and serves as a placeholder for a + * future version that supports logical time-based semantics. */ private boolean logicalTimeBased = false; @@ -191,8 +194,9 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { super.createMainInstantiation(); // Create the main reactor instance if there is a main reactor. - this.main = ASTUtils.createMainReactorInstance( - mainDef, reactors, hasDeadlines, messageReporter, targetConfig); + this.main = + ASTUtils.createMainReactorInstance( + mainDef, reactors, hasDeadlines, messageReporter, targetConfig); // Extract information from the named instances. populateDataStructures(); diff --git a/core/src/main/java/org/lflang/ast/ASTUtils.java b/core/src/main/java/org/lflang/ast/ASTUtils.java index 402b7ddf6b..7e686dd190 100644 --- a/core/src/main/java/org/lflang/ast/ASTUtils.java +++ b/core/src/main/java/org/lflang/ast/ASTUtils.java @@ -593,12 +593,11 @@ public static List collectElements( * transformation to disconnect connections between federates. */ public static ReactorInstance createMainReactorInstance( - Instantiation mainDef, - List reactors, - boolean hasDeadlines, - MessageReporter messageReporter, - TargetConfig targetConfig - ) { + Instantiation mainDef, + List reactors, + boolean hasDeadlines, + MessageReporter messageReporter, + TargetConfig targetConfig) { if (mainDef != null) { // Recursively build instances. ReactorInstance main = diff --git a/core/src/main/java/org/lflang/generator/c/CGenerator.java b/core/src/main/java/org/lflang/generator/c/CGenerator.java index 0929295f54..adf6f341b8 100644 --- a/core/src/main/java/org/lflang/generator/c/CGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CGenerator.java @@ -1958,8 +1958,9 @@ protected void setUpGeneralParameters() { targetConfig.compileAdditionalSources.addAll(CCoreFilesUtils.getCTargetSrc()); // Create the main reactor instance if there is a main reactor. // FIXME: is `hasDeadlines` here always false? That does not look right. - this.main = ASTUtils.createMainReactorInstance( - mainDef, reactors, hasDeadlines, messageReporter, targetConfig); + this.main = + ASTUtils.createMainReactorInstance( + mainDef, reactors, hasDeadlines, messageReporter, targetConfig); if (hasModalReactors) { // So that each separate compile knows about modal reactors, do this: targetConfig.compileDefinitions.put("MODAL_REACTORS", "TRUE"); From b1d35c0efd20048ae6406540feb2b88530465a5a Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sat, 1 Jul 2023 16:18:55 +0800 Subject: [PATCH 221/516] Use more getters and setters, and mark certain variables final --- .../org/lflang/analyses/statespace/Event.java | 18 +++++--- .../statespace/StateSpaceDiagram.java | 46 +++++++++---------- .../statespace/StateSpaceExplorer.java | 22 ++++----- .../analyses/statespace/StateSpaceNode.java | 44 ++++++++++++++---- .../org/lflang/analyses/statespace/Tag.java | 6 +-- .../lflang/analyses/uclid/UclidGenerator.java | 16 +++---- 6 files changed, 92 insertions(+), 60 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/statespace/Event.java b/core/src/main/java/org/lflang/analyses/statespace/Event.java index b04b9861a0..8cffd89336 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/Event.java +++ b/core/src/main/java/org/lflang/analyses/statespace/Event.java @@ -5,22 +5,18 @@ /** A node in the state space diagram representing a step in the execution of an LF program. */ public class Event implements Comparable { - public TriggerInstance trigger; - public Tag tag; + private TriggerInstance trigger; + private Tag tag; public Event(TriggerInstance trigger, Tag tag) { this.trigger = trigger; this.tag = tag; } - public TriggerInstance getTrigger() { - return this.trigger; - } - @Override public int compareTo(Event e) { // Compare tags first. - int ret = this.tag.compareTo(e.tag); + int ret = this.tag.compareTo(e.getTag()); // If tags match, compare trigger names. if (ret == 0) ret = this.trigger.getFullName().compareTo(e.trigger.getFullName()); return ret; @@ -41,4 +37,12 @@ public boolean equals(Object o) { public String toString() { return "(" + trigger.getFullName() + ", " + tag + ")"; } + + public Tag getTag() { + return tag; + } + + public TriggerInstance getTrigger() { + return trigger; + } } diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java index d4583a90ca..05cbfd0e31 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java +++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java @@ -41,7 +41,7 @@ public class StateSpaceDiagram extends DirectedGraph { /** Before adding the node, assign it an index. */ @Override public void addNode(StateSpaceNode node) { - node.index = this.nodeCount(); + node.setIndex(this.nodeCount()); super.addNode(node); } @@ -64,32 +64,32 @@ public void display() { return; } while (node != this.tail) { - System.out.print("* State " + node.index + ": "); + System.out.print("* State " + node.getIndex() + ": "); node.display(); // Store the tag of the prior step. - timestamp = node.tag.timestamp; + timestamp = node.getTag().timestamp; // Assume a unique next state. node = getDownstreamNode(node); // Compute time difference if (node != null) { - TimeValue tsDiff = TimeValue.fromNanoSeconds(node.tag.timestamp - timestamp); + TimeValue tsDiff = TimeValue.fromNanoSeconds(node.getTag().timestamp - timestamp); System.out.println("* => Advance time by " + tsDiff); } } // Print tail node - System.out.print("* (Tail) state " + node.index + ": "); + System.out.print("* (Tail) state " + node.getIndex() + ": "); node.display(); if (this.loopNode != null) { // Compute time difference - TimeValue tsDiff = TimeValue.fromNanoSeconds(loopNodeNext.tag.timestamp - tail.tag.timestamp); + TimeValue tsDiff = TimeValue.fromNanoSeconds(loopNodeNext.getTag().timestamp - tail.getTag().timestamp); System.out.println("* => Advance time by " + tsDiff); - System.out.println("* Goes back to loop node: state " + this.loopNode.index); + System.out.println("* Goes back to loop node: state " + this.loopNode.getIndex()); System.out.print("* Loop node reached 2nd time: "); this.loopNodeNext.display(); } @@ -123,39 +123,39 @@ public CodeBuilder generateDot() { for (StateSpaceNode n : this.nodes()) { dot.pr( "S" - + n.index + + n.getIndex() + " [" + "label = \" {" + "S" - + n.index + + n.getIndex() + " | " - + n.reactionsInvoked.size() + + n.getReactionsInvoked().size() + " | " - + n.eventQ.size() + + n.getEventQ().size() + "}" + " | " - + n.tag + + n.getTag() + "\"" + "]"); } } else { for (StateSpaceNode n : this.nodes()) { List reactions = - n.reactionsInvoked.stream() + n.getReactionsInvoked().stream() .map(ReactionInstance::getFullName) .collect(Collectors.toList()); String reactionsStr = String.join("\\n", reactions); - List events = n.eventQ.stream().map(Event::toString).collect(Collectors.toList()); + List events = n.getEventQ().stream().map(Event::toString).collect(Collectors.toList()); String eventsStr = String.join("\\n", events); dot.pr( "S" - + n.index + + n.getIndex() + " [" + "label = \"" + "S" - + n.index + + n.getIndex() + " | " - + n.tag + + n.getTag() + " | " + "Reactions invoked:\\n" + reactionsStr @@ -170,13 +170,13 @@ public CodeBuilder generateDot() { StateSpaceNode current = this.head; StateSpaceNode next = getDownstreamNode(this.head); while (current != null && next != null && current != this.tail) { - TimeValue tsDiff = TimeValue.fromNanoSeconds(next.tag.timestamp - current.tag.timestamp); + TimeValue tsDiff = TimeValue.fromNanoSeconds(next.getTag().timestamp - current.getTag().timestamp); dot.pr( "S" - + current.index + + current.getIndex() + " -> " + "S" - + next.index + + next.getIndex() + " [label = " + "\"" + "+" @@ -189,14 +189,14 @@ public CodeBuilder generateDot() { if (loopNode != null) { TimeValue tsDiff = - TimeValue.fromNanoSeconds(loopNodeNext.tag.timestamp - tail.tag.timestamp); + TimeValue.fromNanoSeconds(loopNodeNext.getTag().timestamp - tail.getTag().timestamp); TimeValue period = TimeValue.fromNanoSeconds(loopPeriod); dot.pr( "S" - + current.index + + current.getIndex() + " -> " + "S" - + next.index + + next.getIndex() + " [label = " + "\"" + "+" diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java index b1b37e1333..9a82de5e04 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java +++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java @@ -87,7 +87,7 @@ public void explore(Tag horizon, boolean findLoop) { boolean stop = true; if (this.eventQ.size() > 0) { stop = false; - currentTag = (eventQ.peek()).tag; + currentTag = (eventQ.peek()).getTag(); } // A list of reactions invoked at the current logical tag @@ -100,7 +100,7 @@ public void explore(Tag horizon, boolean findLoop) { // Pop the events from the earliest tag off the event queue. ArrayList currentEvents = new ArrayList(); // FIXME: Use stream methods here? - while (eventQ.size() > 0 && eventQ.peek().tag.compareTo(currentTag) == 0) { + while (eventQ.size() > 0 && eventQ.peek().getTag().compareTo(currentTag) == 0) { Event e = eventQ.poll(); // System.out.println("DEBUG: Popped an event: " + e); currentEvents.add(e); @@ -114,19 +114,19 @@ public void explore(Tag horizon, boolean findLoop) { // and we do not want to record duplicate reaction invocations. reactionsTemp = new HashSet(); for (Event e : currentEvents) { - Set dependentReactions = e.trigger.getDependentReactions(); + Set dependentReactions = e.getTrigger().getDependentReactions(); reactionsTemp.addAll(dependentReactions); // System.out.println("DEBUG: dependentReactions: " + dependentReactions); // System.out.println("DEBUG: ReactionTemp: " + reactionsTemp); // If the event is a timer firing, enqueue the next firing. - if (e.trigger instanceof TimerInstance) { - TimerInstance timer = (TimerInstance) e.trigger; + if (e.getTrigger() instanceof TimerInstance) { + TimerInstance timer = (TimerInstance) e.getTrigger(); eventQ.add( new Event( timer, new Tag( - e.tag.timestamp + timer.getPeriod().toNanoSeconds(), + e.getTag().timestamp + timer.getPeriod().toNanoSeconds(), 0, // A time advancement resets microstep to 0. false))); } @@ -227,7 +227,7 @@ else if (previousTag != null && currentTag.timestamp > previousTag.timestamp) { // Loop period is the time difference between the 1st time // the node is reached and the 2nd time the node is reached. this.diagram.loopPeriod = - this.diagram.loopNodeNext.tag.timestamp - this.diagram.loopNode.tag.timestamp; + this.diagram.loopNodeNext.getTag().timestamp - this.diagram.loopNode.getTag().timestamp; this.diagram.addEdge(this.diagram.loopNode, this.diagram.tail); return; // Exit the while loop early. } @@ -276,9 +276,9 @@ else if (previousTag != null && currentTag.timestamp == previousTag.timestamp) { // System.out.println("DEBUG: Case 3"); // Add reactions explored in the current loop iteration // to the existing state space node. - currentNode.reactionsInvoked.addAll(reactionsTemp); + currentNode.getReactionsInvoked().addAll(reactionsTemp); // Update the eventQ snapshot. - currentNode.eventQ = new ArrayList(eventQ); + currentNode.setEventQ(new ArrayList(eventQ)); } else { throw new AssertionError("unreachable"); } @@ -286,7 +286,7 @@ else if (previousTag != null && currentTag.timestamp == previousTag.timestamp) { // Update the current tag for the next iteration. if (eventQ.size() > 0) { previousTag = currentTag; - currentTag = eventQ.peek().tag; + currentTag = eventQ.peek().getTag(); } // Stop if: @@ -309,7 +309,7 @@ else if (previousTag != null && currentTag.timestamp == previousTag.timestamp) { // or (previousTag != null // && currentTag.compareTo(previousTag) > 0) is true and then // the simulation ends, leaving a new node dangling. - if (previousNode == null || previousNode.tag.timestamp < currentNode.tag.timestamp) { + if (previousNode == null || previousNode.getTag().timestamp < currentNode.getTag().timestamp) { this.diagram.addNode(currentNode); this.diagram.tail = currentNode; // Update the current tail. if (previousNode != null) { diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java index 42fb1b043e..72b6acffde 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java +++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java @@ -11,11 +11,11 @@ /** A node in the state space diagram representing a step in the execution of an LF program. */ public class StateSpaceNode { - public int index; // Set in StateSpaceDiagram.java - public Tag tag; - public TimeValue time; // Readable representation of tag.timestamp - public Set reactionsInvoked; - public ArrayList eventQ; + private int index; // Set in StateSpaceDiagram.java + private Tag tag; + private TimeValue time; // Readable representation of tag.timestamp + private Set reactionsInvoked; + private ArrayList eventQ; public StateSpaceNode(Tag tag, Set reactionsInvoked, ArrayList eventQ) { this.tag = tag; @@ -31,8 +31,8 @@ public StateSpaceNode(Tag tag, Set reactionsInvoked, ArrayList private boolean equidistant(StateSpaceNode n1, StateSpaceNode n2) { if (n1.eventQ.size() != n2.eventQ.size()) return false; for (int i = 0; i < n1.eventQ.size(); i++) { - if (n1.eventQ.get(i).tag.timestamp - n1.tag.timestamp - != n2.eventQ.get(i).tag.timestamp - n2.tag.timestamp) { + if (n1.eventQ.get(i).getTag().timestamp - n1.getTag().timestamp + != n2.eventQ.get(i).getTag().timestamp - n2.getTag().timestamp) { return false; } } @@ -89,11 +89,39 @@ public int hash() { this.eventQ.stream() .map( e -> { - return e.tag.timestamp - this.tag.timestamp; + return e.getTag().timestamp - this.tag.timestamp; }) .collect(Collectors.toList()); result = 31 * result + timeDiff.hashCode(); return result; } + + public int getIndex() { + return index; + } + + public void setIndex(int i) { + index = i; + } + + public Tag getTag() { + return tag; + } + + public TimeValue getTime() { + return time; + } + + public Set getReactionsInvoked() { + return reactionsInvoked; + } + + public ArrayList getEventQ() { + return eventQ; + } + + public void setEventQ(ArrayList list) { + eventQ = list; + } } diff --git a/core/src/main/java/org/lflang/analyses/statespace/Tag.java b/core/src/main/java/org/lflang/analyses/statespace/Tag.java index ddbd15abb8..f62dde6c32 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/Tag.java +++ b/core/src/main/java/org/lflang/analyses/statespace/Tag.java @@ -8,9 +8,9 @@ */ public class Tag implements Comparable { - public long timestamp; - public long microstep; - public boolean forever; // Whether the tag is FOREVER into the future. + public final long timestamp; + public final long microstep; + public final boolean forever; // Whether the tag is FOREVER into the future. public Tag(long timestamp, long microstep, boolean forever) { this.timestamp = timestamp; diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java index ac84b7dcc2..be0d3fd60e 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java @@ -1632,10 +1632,10 @@ private void computeCT() { // a linkedlist implementation. We can go straight // to the next node. StateSpaceNode node = diagram.head; - this.CT = diagram.head.reactionsInvoked.size(); + this.CT = diagram.head.getReactionsInvoked().size(); while (node != diagram.tail) { node = diagram.getDownstreamNode(node); - this.CT += node.reactionsInvoked.size(); + this.CT += node.getReactionsInvoked().size(); } } } @@ -1643,7 +1643,7 @@ private void computeCT() { else { // Subtract the non-periodic logical time // interval from the total horizon. - long horizonRemained = Math.subtractExact(this.horizon, diagram.loopNode.tag.timestamp); + long horizonRemained = Math.subtractExact(this.horizon, diagram.loopNode.getTag().timestamp); // Check how many loop iteration is required // to check the remaining horizon. @@ -1670,8 +1670,8 @@ else if (diagram.loopPeriod == 0 && horizonRemained == 0) { An overflow-safe version of the line above */ - int t0 = Math.addExact(diagram.loopNode.index, 1); - int t1 = Math.subtractExact(diagram.tail.index, diagram.loopNode.index); + int t0 = Math.addExact(diagram.loopNode.getIndex(), 1); + int t1 = Math.subtractExact(diagram.tail.getIndex(), diagram.loopNode.getIndex()); int t2 = Math.addExact(t1, 1); int t3 = Math.multiplyExact(t2, loopIterations); this.CT = Math.addExact(t0, t3); @@ -1682,18 +1682,18 @@ else if (diagram.loopPeriod == 0 && horizonRemained == 0) { StateSpaceNode node = diagram.head; int numReactionInvocationsBeforeLoop = 0; while (node != diagram.loopNode) { - numReactionInvocationsBeforeLoop += node.reactionsInvoked.size(); + numReactionInvocationsBeforeLoop += node.getReactionsInvoked().size(); node = diagram.getDownstreamNode(node); } // Account for the loop node in numReactionInvocationsBeforeLoop. - numReactionInvocationsBeforeLoop += node.reactionsInvoked.size(); + numReactionInvocationsBeforeLoop += node.getReactionsInvoked().size(); // Count the events from the loop node until // loop node is reached again. int numReactionInvocationsInsideLoop = 0; do { node = diagram.getDownstreamNode(node); - numReactionInvocationsInsideLoop += node.reactionsInvoked.size(); + numReactionInvocationsInsideLoop += node.getReactionsInvoked().size(); } while (node != diagram.loopNode); /* From 2e687e66b067d8aadbeb015954790434feeb05b9 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sat, 1 Jul 2023 17:50:34 +0800 Subject: [PATCH 222/516] Remove sneakyThrow and apply spotless --- .../org/lflang/analyses/statespace/Event.java | 2 +- .../analyses/statespace/StateSpaceDiagram.java | 9 ++++++--- .../analyses/statespace/StateSpaceExplorer.java | 3 ++- .../lflang/analyses/uclid/UclidGenerator.java | 16 +++++++--------- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/statespace/Event.java b/core/src/main/java/org/lflang/analyses/statespace/Event.java index 8cffd89336..917857341e 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/Event.java +++ b/core/src/main/java/org/lflang/analyses/statespace/Event.java @@ -39,7 +39,7 @@ public String toString() { } public Tag getTag() { - return tag; + return tag; } public TriggerInstance getTrigger() { diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java index 05cbfd0e31..75275a6ff3 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java +++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java @@ -86,7 +86,8 @@ public void display() { if (this.loopNode != null) { // Compute time difference - TimeValue tsDiff = TimeValue.fromNanoSeconds(loopNodeNext.getTag().timestamp - tail.getTag().timestamp); + TimeValue tsDiff = + TimeValue.fromNanoSeconds(loopNodeNext.getTag().timestamp - tail.getTag().timestamp); System.out.println("* => Advance time by " + tsDiff); System.out.println("* Goes back to loop node: state " + this.loopNode.getIndex()); @@ -145,7 +146,8 @@ public CodeBuilder generateDot() { .map(ReactionInstance::getFullName) .collect(Collectors.toList()); String reactionsStr = String.join("\\n", reactions); - List events = n.getEventQ().stream().map(Event::toString).collect(Collectors.toList()); + List events = + n.getEventQ().stream().map(Event::toString).collect(Collectors.toList()); String eventsStr = String.join("\\n", events); dot.pr( "S" @@ -170,7 +172,8 @@ public CodeBuilder generateDot() { StateSpaceNode current = this.head; StateSpaceNode next = getDownstreamNode(this.head); while (current != null && next != null && current != this.tail) { - TimeValue tsDiff = TimeValue.fromNanoSeconds(next.getTag().timestamp - current.getTag().timestamp); + TimeValue tsDiff = + TimeValue.fromNanoSeconds(next.getTag().timestamp - current.getTag().timestamp); dot.pr( "S" + current.getIndex() diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java index 9a82de5e04..f827fa9ef6 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java +++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java @@ -227,7 +227,8 @@ else if (previousTag != null && currentTag.timestamp > previousTag.timestamp) { // Loop period is the time difference between the 1st time // the node is reached and the 2nd time the node is reached. this.diagram.loopPeriod = - this.diagram.loopNodeNext.getTag().timestamp - this.diagram.loopNode.getTag().timestamp; + this.diagram.loopNodeNext.getTag().timestamp + - this.diagram.loopNode.getTag().timestamp; this.diagram.addEdge(this.diagram.loopNode, this.diagram.tail); return; // Exit the while loop early. } diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java index be0d3fd60e..9db668d04d 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java @@ -37,7 +37,6 @@ import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.eclipse.emf.ecore.resource.Resource; -import org.eclipse.xtext.xbase.lib.Exceptions; import org.lflang.Target; import org.lflang.TimeUnit; import org.lflang.TimeValue; @@ -274,7 +273,7 @@ protected void generateUclidFile() { this.generatedFiles.add(file); if (this.expect != null) this.expectations.put(file, this.expect); } catch (IOException e) { - Exceptions.sneakyThrow(e); + throw new RuntimeException(e); } } @@ -1548,7 +1547,7 @@ private void setupDirectories() { try { Files.createDirectories(outputDir); } catch (IOException e) { - Exceptions.sneakyThrow(e); + throw new RuntimeException(e); } System.out.println("The models will be located in: " + outputDir); } @@ -1621,7 +1620,7 @@ private void computeCT() { String filename = file.toString(); dot.writeToFile(filename); } catch (IOException e) { - Exceptions.sneakyThrow(e); + throw new RuntimeException(e); } //// Compute CT @@ -1649,13 +1648,12 @@ private void computeCT() { // to check the remaining horizon. int loopIterations = 0; if (diagram.loopPeriod == 0 && horizonRemained != 0) - Exceptions.sneakyThrow( - new Exception( - "ERROR: Zeno behavior detected while the horizon is non-zero. The program has no" - + " finite CT.")); + throw new RuntimeException( + "ERROR: Zeno behavior detected while the horizon is non-zero. The program has no" + + " finite CT."); else if (diagram.loopPeriod == 0 && horizonRemained == 0) { // Handle this edge case. - Exceptions.sneakyThrow(new Exception("Unhandled case: both the horizon and period are 0!")); + throw new RuntimeException("Unhandled case: both the horizon and period are 0!"); } else { loopIterations = (int) Math.ceil((double) horizonRemained / diagram.loopPeriod); } From 7c74e08e3e8bb6f778b30e92d746e6577787da0a Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sat, 1 Jul 2023 18:32:54 +0800 Subject: [PATCH 223/516] Report warnings using messageReporter --- .../cast/BuildAstParseTreeVisitor.java | 454 ++++++++++-------- .../lflang/analyses/uclid/UclidGenerator.java | 4 +- 2 files changed, 264 insertions(+), 194 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java b/core/src/main/java/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java index 3b72539d14..aa9fd1917d 100644 --- a/core/src/main/java/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java +++ b/core/src/main/java/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java @@ -3,11 +3,22 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.lflang.MessageReporter; import org.lflang.dsl.CBaseVisitor; import org.lflang.dsl.CParser.*; +/** This visitor class builds an AST from the parse tree of a C program */ public class BuildAstParseTreeVisitor extends CBaseVisitor { + /** Message reporter for reporting warnings and errors */ + MessageReporter messageReporter; + + /** Constructor */ + public BuildAstParseTreeVisitor(MessageReporter messageReporter) { + super(); + this.messageReporter = messageReporter; + } + @Override public CAst.AstNode visitBlockItemList(BlockItemListContext ctx) { CAst.StatementSequenceNode stmtSeq = new CAst.StatementSequenceNode(); @@ -34,26 +45,30 @@ public CAst.AstNode visitDeclaration(DeclarationContext ctx) { // Cannot handle more than 1 specifiers, e.g. static const int. // We can augment the analytical capability later. if (declSpecList.size() > 1) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "the analyzer cannot handle more than 1 specifiers,", - "e.g. static const int.", - "Marking the declaration as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "the analyzer cannot handle more than 1 specifiers,", + "e.g. static const int.", + "Marking the declaration as opaque.")); return new CAst.OpaqueNode(); } // Check if the declaration specifier is a type specifier: e.g. int or long. DeclarationSpecifierContext declSpec = declSpecList.get(0); if (declSpec.typeSpecifier() == null) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "only type specifiers are supported.", - "e.g. \"static const int\" is not analyzable.", - "Marking the declaration as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "only type specifiers are supported.", + "e.g. \"static const int\" is not analyzable.", + "Marking the declaration as opaque.")); return new CAst.OpaqueNode(); } @@ -68,13 +83,15 @@ public CAst.AstNode visitDeclaration(DeclarationContext ctx) { else if (declSpec.typeSpecifier().Bool() != null) type = CAst.VariableNode.Type.BOOLEAN; // Mark the declaration unanalyzable if the type is unsupported. else { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "unsupported type detected at " + declSpec.typeSpecifier(), - "Only " + supportedTypes + " are supported.", - "Marking the declaration as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "unsupported type detected at " + declSpec.typeSpecifier(), + "Only " + supportedTypes + " are supported.", + "Marking the declaration as opaque.")); return new CAst.OpaqueNode(); } @@ -84,46 +101,54 @@ public CAst.AstNode visitDeclaration(DeclarationContext ctx) { // Cannot handle more than 1 initDeclarator: e.g. x = 1, y = 2; // We can augment the analytical capability later. if (initDeclList.size() > 1) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "more than 1 declarators are detected on a single line,", - "e.g. \"int x = 1, y = 2;\" is not yet analyzable.", - "Marking the declaration as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "more than 1 declarators are detected on a single line,", + "e.g. \"int x = 1, y = 2;\" is not yet analyzable.", + "Marking the declaration as opaque.")); return new CAst.OpaqueNode(); } // Get the variable name from the declarator. DeclaratorContext decl = initDeclList.get(0).declarator(); if (decl.pointer() != null) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "pointers are currently not supported,", - "e.g. \"int *x;\" is not yet analyzable.", - "Marking the declaration as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "pointers are currently not supported,", + "e.g. \"int *x;\" is not yet analyzable.", + "Marking the declaration as opaque.")); return new CAst.OpaqueNode(); } if (decl.gccDeclaratorExtension().size() > 0) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "GCC declarator extensions are currently not supported,", - "e.g. \"__asm\" and \"__attribute__\" are not yet analyzable.", - "Marking the declaration as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "GCC declarator extensions are currently not supported,", + "e.g. \"__asm\" and \"__attribute__\" are not yet analyzable.", + "Marking the declaration as opaque.")); return new CAst.OpaqueNode(); } DirectDeclaratorContext directDecl = decl.directDeclarator(); if (directDecl.Identifier() == null) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "the variable identifier is missing.", - "Marking the declaration as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "the variable identifier is missing.", + "Marking the declaration as opaque.")); return new CAst.OpaqueNode(); } @@ -137,13 +162,15 @@ public CAst.AstNode visitDeclaration(DeclarationContext ctx) { // Make sure that there is an initializer. InitDeclaratorContext initDecl = initDeclList.get(0); if (initDecl.initializer() == null) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "the initializer is missing,", - "e.g. \"int x;\" is not yet analyzable.", - "Marking the declaration as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "the initializer is missing,", + "e.g. \"int x;\" is not yet analyzable.", + "Marking the declaration as opaque.")); return new CAst.OpaqueNode(); // FIXME: Use UninitCAst.VariableNode to perform special encoding. @@ -153,12 +180,14 @@ public CAst.AstNode visitDeclaration(DeclarationContext ctx) { // Extract the primaryExpression from the initializer. if (initDecl.initializer().assignmentExpression() == null || initDecl.initializer().assignmentExpression().conditionalExpression() == null) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "assignmentExpression or conditionalExpression is missing.", - "Marking the declaration as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "assignmentExpression or conditionalExpression is missing.", + "Marking the declaration as opaque.")); return new CAst.OpaqueNode(); } @@ -206,12 +235,14 @@ public CAst.AstNode visitExpression(ExpressionContext ctx) { if (ctx.assignmentExpression().size() == 1) { return visitAssignmentExpression(ctx.assignmentExpression().get(0)); } - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "only one assignmentExpression in an expression is currently supported.", - "Marking the statement as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "only one assignmentExpression in an expression is currently supported.", + "Marking the statement as opaque.")); return new CAst.OpaqueNode(); } @@ -224,12 +255,14 @@ public CAst.AstNode visitPrimaryExpression(PrimaryExpressionContext ctx) { } else if (ctx.expression() != null) { return visitExpression(ctx.expression()); } - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "only identifier, constant, and expressions are supported in a primary expression.", - "Marking the declaration as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "only identifier, constant, and expressions are supported in a primary expression.", + "Marking the declaration as opaque.")); return new CAst.OpaqueNode(); } @@ -241,12 +274,14 @@ public CAst.AstNode visitPostfixExpression(PostfixExpressionContext ctx) { || ctx.MinusMinus().size() > 0 || ctx.Dot().size() > 0 || (ctx.LeftBracket().size() > 0 && ctx.RightBracket().size() > 0)) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Postfix '++', '--', '.', '[]' are currently not supported.", - "Marking the statement as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Postfix '++', '--', '.', '[]' are currently not supported.", + "Marking the statement as opaque.")); return new CAst.OpaqueNode(); } // State variables on the self struct, ports and actions. @@ -266,12 +301,14 @@ public CAst.AstNode visitPostfixExpression(PostfixExpressionContext ctx) { return new CAst.TriggerIsPresentNode(varNode.name); } else { // Generic pointer dereference, unanalyzable. - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Generic pointer dereference is not supported in a postfix expression.", - "Marking the declaration as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Generic pointer dereference is not supported in a postfix expression.", + "Marking the declaration as opaque.")); return new CAst.OpaqueNode(); } } @@ -290,12 +327,15 @@ public CAst.AstNode visitPostfixExpression(PostfixExpressionContext ctx) { if (varNode.name.equals("lf_set")) { // return a set port node. if (params.size() != 2) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "lf_set must have 2 arguments. Detected " + ctx.argumentExpressionList().size(), - "Marking the function call as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "lf_set must have 2 arguments. Detected " + + ctx.argumentExpressionList().size(), + "Marking the function call as opaque.")); return new CAst.OpaqueNode(); } CAst.SetPortNode node = new CAst.SetPortNode(); @@ -305,13 +345,15 @@ public CAst.AstNode visitPostfixExpression(PostfixExpressionContext ctx) { } else if (varNode.name.equals("lf_schedule")) { // return a set port node. if (params.size() != 2) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "lf_schedule must have two arguments. Detected " - + ctx.argumentExpressionList().size(), - "Marking the function call as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "lf_schedule must have two arguments. Detected " + + ctx.argumentExpressionList().size(), + "Marking the function call as opaque.")); return new CAst.OpaqueNode(); } CAst.ScheduleActionNode node = new CAst.ScheduleActionNode(); @@ -322,13 +364,15 @@ public CAst.AstNode visitPostfixExpression(PostfixExpressionContext ctx) { } else if (varNode.name.equals("lf_schedule_int")) { // return a set port node. if (params.size() != 3) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "lf_schedule_int must have three arguments. Detected " - + ctx.argumentExpressionList().size(), - "Marking the function call as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "lf_schedule_int must have three arguments. Detected " + + ctx.argumentExpressionList().size(), + "Marking the function call as opaque.")); return new CAst.OpaqueNode(); } CAst.ScheduleActionIntNode node = new CAst.ScheduleActionIntNode(); @@ -338,13 +382,15 @@ public CAst.AstNode visitPostfixExpression(PostfixExpressionContext ctx) { return node; } else { // Generic pointer dereference, unanalyzable. - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Generic pointer dereference and function calls are not supported in a postfix" - + " expression.", - "Marking the declaration as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Generic pointer dereference and function calls are not supported in a postfix" + + " expression.", + "Marking the declaration as opaque.")); return new CAst.OpaqueNode(); } } @@ -352,13 +398,15 @@ public CAst.AstNode visitPostfixExpression(PostfixExpressionContext ctx) { if (ctx.primaryExpression() != null) { return visitPrimaryExpression(ctx.primaryExpression()); } - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "only an identifier, constant, state variable, port, and action are supported in a" - + " primary expression.", - "Marking the declaration as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "only an identifier, constant, state variable, port, and action are supported in a" + + " primary expression.", + "Marking the declaration as opaque.")); return new CAst.OpaqueNode(); } @@ -366,12 +414,14 @@ public CAst.AstNode visitPostfixExpression(PostfixExpressionContext ctx) { public CAst.AstNode visitUnaryExpression(UnaryExpressionContext ctx) { // Check for prefixes and mark them as opaque (unsupported for now). if (ctx.PlusPlus().size() > 0 || ctx.MinusMinus().size() > 0 || ctx.Sizeof().size() > 0) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Prefix '++', '--', and 'sizeof' are currently not supported.", - "Marking the statement as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Prefix '++', '--', and 'sizeof' are currently not supported.", + "Marking the statement as opaque.")); AstUtils.printStackTraceAndMatchedText(ctx); return new CAst.OpaqueNode(); } @@ -410,12 +460,14 @@ public CAst.AstNode visitUnaryExpression(UnaryExpressionContext ctx) { } // Mark all the remaining cases as opaque. - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "only postfixExpression and '!' in a unaryExpression is currently supported.", - "Marking the statement as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "only postfixExpression and '!' in a unaryExpression is currently supported.", + "Marking the statement as opaque.")); AstUtils.printStackTraceAndMatchedText(ctx); return new CAst.OpaqueNode(); } @@ -425,12 +477,14 @@ public CAst.AstNode visitCastExpression(CastExpressionContext ctx) { if (ctx.unaryExpression() != null) { return visitUnaryExpression(ctx.unaryExpression()); } - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "only unaryExpression in a castExpression is currently supported.", - "Marking the statement as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "only unaryExpression in a castExpression is currently supported.", + "Marking the statement as opaque.")); return new CAst.OpaqueNode(); } @@ -443,12 +497,14 @@ public CAst.AstNode visitMultiplicativeExpression(MultiplicativeExpressionContex } else if (ctx.Div().size() > 0) { node = new CAst.DivisionNode(); } else if (ctx.Mod().size() > 0) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Mod expression '%' is currently unsupported.", - "Marking the expression as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Mod expression '%' is currently unsupported.", + "Marking the expression as opaque.")); return new CAst.OpaqueNode(); } else { node = new CAst.AstNodeBinary(); @@ -481,12 +537,14 @@ public CAst.AstNode visitAdditiveExpression(AdditiveExpressionContext ctx) { @Override public CAst.AstNode visitShiftExpression(ShiftExpressionContext ctx) { if (ctx.additiveExpression().size() > 1) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Shift expression '<<' or '>>' is currently unsupported.", - "Marking the expression as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Shift expression '<<' or '>>' is currently unsupported.", + "Marking the expression as opaque.")); return new CAst.OpaqueNode(); } return visitAdditiveExpression(ctx.additiveExpression().get(0)); @@ -535,12 +593,14 @@ public CAst.AstNode visitEqualityExpression(EqualityExpressionContext ctx) { @Override public CAst.AstNode visitAndExpression(AndExpressionContext ctx) { if (ctx.equalityExpression().size() > 1) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "And expression '&' is currently unsupported.", - "Marking the expression as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "And expression '&' is currently unsupported.", + "Marking the expression as opaque.")); return new CAst.OpaqueNode(); } return visitEqualityExpression(ctx.equalityExpression().get(0)); @@ -549,12 +609,14 @@ public CAst.AstNode visitAndExpression(AndExpressionContext ctx) { @Override public CAst.AstNode visitExclusiveOrExpression(ExclusiveOrExpressionContext ctx) { if (ctx.andExpression().size() > 1) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Exclusive Or '^' is currently unsupported.", - "Marking the expression as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Exclusive Or '^' is currently unsupported.", + "Marking the expression as opaque.")); return new CAst.OpaqueNode(); } return visitAndExpression(ctx.andExpression().get(0)); @@ -563,12 +625,14 @@ public CAst.AstNode visitExclusiveOrExpression(ExclusiveOrExpressionContext ctx) @Override public CAst.AstNode visitInclusiveOrExpression(InclusiveOrExpressionContext ctx) { if (ctx.exclusiveOrExpression().size() > 1) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Inclusive Or '|' is currently unsupported.", - "Marking the expression as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Inclusive Or '|' is currently unsupported.", + "Marking the expression as opaque.")); return new CAst.OpaqueNode(); } return visitExclusiveOrExpression(ctx.exclusiveOrExpression().get(0)); @@ -599,12 +663,14 @@ public CAst.AstNode visitLogicalOrExpression(LogicalOrExpressionContext ctx) { @Override public CAst.AstNode visitConditionalExpression(ConditionalExpressionContext ctx) { if (ctx.expression() != null) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Currently do not support inline conditional expression.", - "Marking the expression as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Currently do not support inline conditional expression.", + "Marking the expression as opaque.")); return new CAst.OpaqueNode(); } return visitLogicalOrExpression(ctx.logicalOrExpression()); @@ -641,34 +707,40 @@ public CAst.AstNode visitAssignmentExpression(AssignmentExpressionContext ctx) { subnode.right = visitAssignmentExpression(ctx.assignmentExpression()); assignmentNode.right = subnode; } else { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Only '=', '+=', '-=', '*=', '/=' assignment operators are supported.", - "Marking the expression as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Only '=', '+=', '-=', '*=', '/=' assignment operators are supported.", + "Marking the expression as opaque.")); return new CAst.OpaqueNode(); } return assignmentNode; } - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "DigitSequence in an assignmentExpression is currently not supported.", - "Marking the expression as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "DigitSequence in an assignmentExpression is currently not supported.", + "Marking the expression as opaque.")); return new CAst.OpaqueNode(); } @Override public CAst.AstNode visitSelectionStatement(SelectionStatementContext ctx) { if (ctx.Switch() != null) { - System.out.println( - String.join( - " ", - "Warning (line " + ctx.getStart().getLine() + "):", - "Switch case statement is currently not supported.", - "Marking the expression as opaque.")); + messageReporter + .nowhere() + .warning( + String.join( + " ", + "Warning (line " + ctx.getStart().getLine() + "):", + "Switch case statement is currently not supported.", + "Marking the expression as opaque.")); return new CAst.OpaqueNode(); } CAst.IfBlockNode ifBlockNode = new CAst.IfBlockNode(); diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java index 9db668d04d..d188d83ae4 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java @@ -1215,8 +1215,6 @@ protected void generateReactionAxioms() { for (ReactionInstance.Runtime reaction : this.reactionInstances) { String body = reaction.getReaction().getDefinition().getCode().getBody(); - // System.out.println("DEBUG: Printing reaction body of " + reaction); - // System.out.println(body); // Generate a parse tree. CLexer lexer = new CLexer(CharStreams.fromString(body)); @@ -1225,7 +1223,7 @@ protected void generateReactionAxioms() { BlockItemListContext parseTree = parser.blockItemList(); // Build an AST. - BuildAstParseTreeVisitor buildAstVisitor = new BuildAstParseTreeVisitor(); + BuildAstParseTreeVisitor buildAstVisitor = new BuildAstParseTreeVisitor(messageReporter); CAst.AstNode ast = buildAstVisitor.visitBlockItemList(parseTree); // VariablePrecedenceVisitor From 0256af655b7d50bb39800555a190ab9389889655 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sat, 1 Jul 2023 18:58:26 +0800 Subject: [PATCH 224/516] Apply more suggestions from @petervdonovan --- .../java/org/lflang/analyses/cast/CAst.java | 7 ++- .../lflang/analyses/cast/CToUclidVisitor.java | 50 +++++++++---------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/cast/CAst.java b/core/src/main/java/org/lflang/analyses/cast/CAst.java index ea04d45426..2126902da9 100644 --- a/core/src/main/java/org/lflang/analyses/cast/CAst.java +++ b/core/src/main/java/org/lflang/analyses/cast/CAst.java @@ -46,6 +46,7 @@ public T accept(AstVisitor visitor, List nodeList) { } } + /** An AST node class that can have a list of child nodes with arbitrary length */ public static class AstNodeDynamic extends AstNode implements Visitable { public ArrayList children = new ArrayList<>(); @@ -391,7 +392,11 @@ public T accept(AstVisitor visitor, List nodeList) { } } - /** Handle state variables appearing as self-> */ + /** + * Handle state variables appearing as self->. If the state variable appears on both sides + * of an assignment, such as `self-> = self-> + 1`, then `self->` on the RHS is + * marked as a "previous state" with `prev` set to true. + */ public static class StateVarNode extends AstNode implements Visitable { public String name; public boolean prev = false; // By default, this is not a previous state. diff --git a/core/src/main/java/org/lflang/analyses/cast/CToUclidVisitor.java b/core/src/main/java/org/lflang/analyses/cast/CToUclidVisitor.java index 231f0c5ae3..03b533861f 100644 --- a/core/src/main/java/org/lflang/analyses/cast/CToUclidVisitor.java +++ b/core/src/main/java/org/lflang/analyses/cast/CToUclidVisitor.java @@ -14,31 +14,35 @@ public class CToUclidVisitor extends CBaseAstVisitor { - // The Uclid generator instance - protected UclidGenerator generator; + /** The Uclid generator instance */ + private UclidGenerator generator; - // The reaction instance for the generated axiom - protected ReactionInstance.Runtime reaction; + /** The reaction instance for the generated axiom */ + private ReactionInstance.Runtime reaction; - // The reactor that contains the reaction - protected ReactorInstance reactor; + /** The reactor that contains the reaction */ + private ReactorInstance reactor; - // A list of all the named instances - protected List instances = new ArrayList(); + /** A list of all the named instances */ + private List instances = new ArrayList(); - // Quantified variable - protected String qv = "i"; - protected String qv2 = "j"; + /** Quantified variable */ + private final String qv = "i"; - // Unchanged variables and triggers - protected List unchangedStates; - protected List unchangedTriggers; + private final String qv2 = "j"; + + /** Unchanged variables and triggers */ + private List unchangedStates; + + private List unchangedTriggers; // FIXME: Make this more flexible and infer value from program. - // Default reset value - String defaultValue = "0"; - String defaultPresence = "false"; + /** Default reset value */ + private final String defaultValue = "0"; + + private final String defaultPresence = "false"; + /** Constructor */ public CToUclidVisitor(UclidGenerator generator, ReactionInstance.Runtime reaction) { this.generator = generator; this.reaction = reaction; @@ -347,8 +351,7 @@ public String visitStateVarNode(StateVarNode node) { + ")" + ")"; } - // FIXME: Throw exception - return ""; + throw new RuntimeException("Instance not found in CToUclidVisitor"); } @Override @@ -382,8 +385,7 @@ public String visitTriggerIsPresentNode(TriggerIsPresentNode node) { + ")" + ")"; } - // FIXME: Throw exception - return ""; + throw new RuntimeException("Instance not found in CToUclidVisitor"); } @Override @@ -393,8 +395,7 @@ public String visitTriggerValueNode(TriggerValueNode node) { if (instance != null) { return instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; } - // FIXME: Throw exception - return ""; + throw new RuntimeException("Instance not found in CToUclidVisitor"); } @Override @@ -403,8 +404,7 @@ public String visitVariableNode(VariableNode node) { if (instance != null) { return instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; } - // FIXME: Throw exception - return ""; + throw new RuntimeException("Instance not found in CToUclidVisitor"); } ///////////////////////////// From 6df5d0887cfcf537942cace1f54ce9ad84c318d7 Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Sun, 2 Jul 2023 15:54:58 +0900 Subject: [PATCH 225/516] Merge branch 'present-unknown' into 'ts-level-assignment' manually to sync with the master of reactor-ts --- core/src/main/java/org/lflang/Target.java | 1 - core/src/main/java/org/lflang/generator/TargetTypes.java | 2 +- core/src/main/java/org/lflang/generator/ts/TSTypes.java | 2 +- .../kotlin/org/lflang/generator/ts/TSDelayBodyGenerator.kt | 6 +++--- .../src/main/kotlin/org/lflang/generator/ts/TSExtensions.kt | 6 +++--- .../org/lflang/generator/ts/TSImportPreambleGenerator.kt | 2 +- .../kotlin/org/lflang/generator/ts/TSReactionGenerator.kt | 4 ++-- .../main/kotlin/org/lflang/generator/ts/TSTimerGenerator.kt | 2 -- 8 files changed, 11 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/lflang/Target.java b/core/src/main/java/org/lflang/Target.java index 8f08e89fcc..4a6012f7a8 100644 --- a/core/src/main/java/org/lflang/Target.java +++ b/core/src/main/java/org/lflang/Target.java @@ -268,7 +268,6 @@ public enum Target { // underscores) "TimeUnit", "TimeValue", - "Present", "Sched", "Read", "Write", diff --git a/core/src/main/java/org/lflang/generator/TargetTypes.java b/core/src/main/java/org/lflang/generator/TargetTypes.java index eda5cffd11..968a641ed3 100644 --- a/core/src/main/java/org/lflang/generator/TargetTypes.java +++ b/core/src/main/java/org/lflang/generator/TargetTypes.java @@ -60,7 +60,7 @@ default String getTargetBracedListExpr(BracedListExpression expr, InferredType t .collect(Collectors.joining(",", "{", "}")); } - /** Return an "undefined" type which is used as a default when a type cannot be inferred. */ + /** Return an "unknown" type which is used as a default when a type cannot be inferred. */ String getTargetUndefinedType(); /** diff --git a/core/src/main/java/org/lflang/generator/ts/TSTypes.java b/core/src/main/java/org/lflang/generator/ts/TSTypes.java index 0fe4fc8a5e..cca3736c66 100644 --- a/core/src/main/java/org/lflang/generator/ts/TSTypes.java +++ b/core/src/main/java/org/lflang/generator/ts/TSTypes.java @@ -40,7 +40,7 @@ public String getTargetTagType() { @Override public String getTargetUndefinedType() { - return "Present"; + return "unknown"; } public String getTargetTimeExpr(TimeValue value) { diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSDelayBodyGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSDelayBodyGenerator.kt index e561696524..ecdcf762a2 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSDelayBodyGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSDelayBodyGenerator.kt @@ -9,7 +9,7 @@ object TSDelayBodyGenerator : DelayBodyGenerator { /** * Return a TS type for the specified action. * If the type has not been specified, return - * "Present" which is the base type for Actions. + * `unknown`. * @param action The action * @return The TS type. */ @@ -17,7 +17,7 @@ object TSDelayBodyGenerator : DelayBodyGenerator { return if (action.type != null) { TSTypes.getInstance().getTargetType(action.type) } else { - "Present" + "unknown" } } @@ -30,7 +30,7 @@ object TSDelayBodyGenerator : DelayBodyGenerator { } override fun generateDelayGeneric(): String { - return "T extends Present" + return "T" } override fun generateAfterDelaysWithVariableWidth() = false diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSExtensions.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSExtensions.kt index a28e462921..6c304a5a8d 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSExtensions.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSExtensions.kt @@ -35,11 +35,11 @@ fun WidthSpec.toTSCode(): String = terms.joinToString(" + ") { /** * Return a TS type for the specified port. * If the type has not been specified, return - * "Present" which is the base type for ports. + * `unknown`. * @return The TS type. */ val Port.tsPortType: String - get() = type?.let { TSTypes.getInstance().getTargetType(it) } ?: "Present" + get() = type?.let { TSTypes.getInstance().getTargetType(it) } ?: "unknown" /** * 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.getInstance().getTargetType(it) } ?: "Present" + get() = type?.let { TSTypes.getInstance().getTargetType(it) } ?: "unknown" fun Expression.toTsTime(): String = TSTypes.getInstance().getTargetTimeExpr(this) fun TimeValue.toTsTime(): String = TSTypes.getInstance().getTargetTimeExpr(this) diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSImportPreambleGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSImportPreambleGenerator.kt index 6621794852..c7f780f8ee 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSImportPreambleGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSImportPreambleGenerator.kt @@ -63,7 +63,7 @@ class TSImportPreambleGenerator( import {Reaction as __Reaction} from '@lf-lang/reactor-ts' import {State as __State} from '@lf-lang/reactor-ts' import {TimeUnit, TimeValue, Tag as __Tag, Origin as __Origin} from '@lf-lang/reactor-ts' - import {Args as __Args, Variable as __Variable, Triggers as __Triggers, Present, Read, Write, ReadWrite, MultiReadWrite, Sched} from '@lf-lang/reactor-ts' + import {Variable as __Variable, Read, Write, ReadWrite, MultiReadWrite, Sched} from '@lf-lang/reactor-ts' import {Log} from '@lf-lang/reactor-ts' import {ProcessedCommandLineArgs as __ProcessedCommandLineArgs, CommandLineOptionDefs as __CommandLineOptionDefs, CommandLineUsageDefs as __CommandLineUsageDefs, CommandLineOptionSpec as __CommandLineOptionSpec, unitBasedTimeValueCLAType as __unitBasedTimeValueCLAType, booleanCLAType as __booleanCLAType} from '@lf-lang/reactor-ts' """ diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSReactionGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSReactionGenerator.kt index b82e1d20af..672d923d2b 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSReactionGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSReactionGenerator.kt @@ -105,8 +105,8 @@ class TSReactionGenerator( """ | |this.add${if (reaction.isMutation) "Mutation" else "Reaction"}( - | new __Triggers($reactionTriggers), - | new __Args($reactFuncArgs), + | [$reactionTriggers], + | [$reactFuncArgs], | function ($reactSignature) { | // =============== START react prologue ${" | "..reactPrologue} diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSTimerGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSTimerGenerator.kt index 2cb1f37894..411249d7ca 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSTimerGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSTimerGenerator.kt @@ -1,8 +1,6 @@ package org.lflang.generator.ts -import org.lflang.generator.getTargetTimeExpr import org.lflang.generator.orZero -import org.lflang.lf.Expression import org.lflang.lf.Timer import java.util.* From 16650fe43ee13213abed47a828435530fec2d578 Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Sun, 2 Jul 2023 17:01:55 +0900 Subject: [PATCH 226/516] Add fixme for the initialization reaction of TS-targeted federates --- .../federated/generator/FedASTUtils.java | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 8f0579cb77..9edc4cb115 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -45,6 +45,7 @@ import org.eclipse.xtext.xbase.lib.IteratorExtensions; import org.lflang.ErrorReporter; import org.lflang.InferredType; +import org.lflang.Target; import org.lflang.TargetProperty.CoordinationType; import org.lflang.TimeValue; import org.lflang.ast.ASTUtils; @@ -637,7 +638,11 @@ private static Reactor getNetworkSenderReactor( var senderIndexParameter = LfFactory.eINSTANCE.createParameter(); var senderIndexParameterType = LfFactory.eINSTANCE.createType(); senderIndexParameter.setName("sender_index"); - senderIndexParameterType.setId("int"); + if (connection.srcFederate.targetConfig.target != Target.TS) { + senderIndexParameterType.setId("int"); + } else { + senderIndexParameterType.setId("Number"); + } senderIndexParameter.setType(senderIndexParameterType); var senderIndexParameterInit = LfFactory.eINSTANCE.createInitializer(); var senderIndexParameterInitExpr = LfFactory.eINSTANCE.createLiteral(); @@ -696,21 +701,28 @@ private static Reaction getNetworkSenderReaction( return networkSenderReaction; } - private static Reaction getInitializationReaction() { + private static Reaction getInitializationReaction(FedConnectionInstance connection) { var initializationReaction = LfFactory.eINSTANCE.createReaction(); var startup = LfFactory.eINSTANCE.createBuiltinTriggerRef(); startup.setType(BuiltinTrigger.STARTUP); initializationReaction.getTriggers().add(startup); var code = LfFactory.eINSTANCE.createCode(); - code.setBody( - """ - extern reaction_t* port_absent_reaction[]; - void enqueue_network_output_control_reactions(); - LF_PRINT_DEBUG("Adding network output control reaction to table."); - port_absent_reaction[self->sender_index] = &self->_lf__reaction_2; - LF_PRINT_DEBUG("Added network output control reaction to table. Enqueueing it..."); - enqueue_network_output_control_reactions(); - """); + if (connection.srcFederate.targetConfig.target != Target.TS) { + code.setBody( + """ + extern reaction_t* port_absent_reaction[]; + void enqueue_network_output_control_reactions(); + LF_PRINT_DEBUG("Adding network output control reaction to table."); + port_absent_reaction[self->sender_index] = &self->_lf__reaction_2; + LF_PRINT_DEBUG("Added network output control reaction to table. Enqueueing it..."); + enqueue_network_output_control_reactions(); + """); + } else { + code.setBody( + """ + // TODO: Figure out what to do for initialization reaction + """); + } initializationReaction.setCode(code); return initializationReaction; } From f11a9c580e52e3f2cb10b5e01e61ab240c8ce123 Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Sun, 2 Jul 2023 17:11:25 +0900 Subject: [PATCH 227/516] Pass a parameter --- .../main/java/org/lflang/federated/generator/FedASTUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 9edc4cb115..94dc0b0e6a 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -651,7 +651,7 @@ private static Reactor getNetworkSenderReactor( senderIndexParameter.setInit(senderIndexParameterInit); sender.getParameters().add(senderIndexParameter); - sender.getReactions().add(getInitializationReaction()); + sender.getReactions().add(getInitializationReaction(connection)); sender.getReactions().add(networkSenderReaction); sender.getInputs().add(in); From dd6e19564ad6b64bc068ae78b1d927c922234302 Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Mon, 3 Jul 2023 09:50:51 +0900 Subject: [PATCH 228/516] Register appropriate federate ports --- .../generator/ts/TSConstructorGenerator.kt | 2 +- .../lflang/generator/ts/TSReactorGenerator.kt | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt index a1ec96c9ac..7c357d0c6e 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt @@ -92,7 +92,7 @@ class TSConstructorGenerator( actions: TSActionGenerator, ports: TSPortGenerator, isFederate: Boolean, - networkMessageActions: List + networkMessageActions: MutableList ): String { val connections = TSConnectionGenerator(reactor.connections, errorReporter) val reactions = TSReactionGenerator(errorReporter, reactor) diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt index 4d537f5a19..23448c9441 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt @@ -102,10 +102,23 @@ ${" |"..preamble.code.toText()} }.trimMargin() } - private fun getNetworkMessagActions(reactor: Reactor): List { + private fun getNetworkMessageActions(reactor: Reactor): MutableList { val attribute = AttributeUtils.findAttributeByName(reactor, "_fed_config") val actionsStr = AttributeUtils.getAttributeParameter(attribute, AttributeSpec.NETWORK_MESSAGE_ACTIONS) - return actionsStr?.split(",")?.filter { it.isNotEmpty()} ?: emptyList() + var actionsList = actionsStr?.split(",")?.filter { it.isNotEmpty()} ?: emptyList() + actionsList = actionsList.toMutableList() + + val childReactors = reactor.instantiations + var actionsListCount = 0 + for (childReactor in childReactors) { + if (childReactor.reactorClass.name.take(15) == "NetworkReceiver") { + // FIXME: Assume that the order of childReactor and attribute list are identical. + // This assumption might bring some erros + actionsList[actionsListCount] = childReactor.name + "." + actionsList[actionsListCount] + actionsListCount++ + } + } + return actionsList } fun generateReactor(reactor: Reactor): String { @@ -116,7 +129,7 @@ ${" |"..preamble.code.toText()} } val isFederate = AttributeUtils.isFederate(reactor) - val networkMessageActions = getNetworkMessagActions(reactor) + val networkMessageActions = getNetworkMessageActions(reactor) // NOTE: type parameters that are referenced in ports or actions must extend // Present in order for the program to type check. From b4540bac32377df381c8861b596ca4d21b011ef3 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sat, 1 Jul 2023 19:09:05 +0800 Subject: [PATCH 229/516] Throw error when looking up named instances --- .../lflang/analyses/cast/CToUclidVisitor.java | 52 +++++++------------ 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/cast/CToUclidVisitor.java b/core/src/main/java/org/lflang/analyses/cast/CToUclidVisitor.java index 03b533861f..634045a10e 100644 --- a/core/src/main/java/org/lflang/analyses/cast/CToUclidVisitor.java +++ b/core/src/main/java/org/lflang/analyses/cast/CToUclidVisitor.java @@ -341,17 +341,14 @@ public String visitSetPortNode(SetPortNode node) { @Override public String visitStateVarNode(StateVarNode node) { NamedInstance instance = getInstanceByName(node.name); - if (instance != null) { - return instance.getFullNameWithJoiner("_") - + "(" - + "s" - + "(" - + this.qv - + (node.prev ? "-1" : "") - + ")" - + ")"; - } - throw new RuntimeException("Instance not found in CToUclidVisitor"); + return instance.getFullNameWithJoiner("_") + + "(" + + "s" + + "(" + + this.qv + + (node.prev ? "-1" : "") + + ")" + + ")"; } @Override @@ -375,41 +372,33 @@ public String visitSubtractionNode(SubtractionNode node) { public String visitTriggerIsPresentNode(TriggerIsPresentNode node) { // Find the trigger instance by name. NamedInstance instance = getInstanceByName(node.name); - if (instance != null) { - return instance.getFullNameWithJoiner("_") - + "_is_present" - + "(" - + "t" - + "(" - + this.qv - + ")" - + ")"; - } - throw new RuntimeException("Instance not found in CToUclidVisitor"); + return instance.getFullNameWithJoiner("_") + + "_is_present" + + "(" + + "t" + + "(" + + this.qv + + ")" + + ")"; } @Override public String visitTriggerValueNode(TriggerValueNode node) { // Find the trigger instance by name. NamedInstance instance = getInstanceByName(node.name); - if (instance != null) { - return instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; - } - throw new RuntimeException("Instance not found in CToUclidVisitor"); + return instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; } @Override public String visitVariableNode(VariableNode node) { NamedInstance instance = getInstanceByName(node.name); - if (instance != null) { - return instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; - } - throw new RuntimeException("Instance not found in CToUclidVisitor"); + return instance.getFullNameWithJoiner("_") + "(" + "s" + "(" + this.qv + ")" + ")"; } ///////////////////////////// //// Private functions + /** Look up an instance by name. This function throws an error if an instance is not found. */ private NamedInstance getInstanceByName(String name) { for (NamedInstance i : this.instances) { if (i instanceof ActionInstance) { @@ -426,7 +415,6 @@ private NamedInstance getInstanceByName(String name) { } } } - System.out.println("Named instance" + "not found."); - return null; + throw new RuntimeException("NamedInstance not found!"); } } From 5043e30171866747b8cda09897e844341d96c841 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Tue, 4 Jul 2023 17:02:42 +0200 Subject: [PATCH 230/516] Handle the equals() issue --- .../org/lflang/analyses/statespace/Event.java | 9 ++- .../analyses/statespace/EventQueue.java | 5 +- .../statespace/StateSpaceDiagram.java | 4 +- .../statespace/StateSpaceExplorer.java | 2 +- .../analyses/statespace/StateSpaceNode.java | 67 +++++++++++++------ 5 files changed, 58 insertions(+), 29 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/statespace/Event.java b/core/src/main/java/org/lflang/analyses/statespace/Event.java index 917857341e..d8613e5138 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/Event.java +++ b/core/src/main/java/org/lflang/analyses/statespace/Event.java @@ -13,6 +13,10 @@ public Event(TriggerInstance trigger, Tag tag) { this.tag = tag; } + /** + * Compare two events first by tags and, if tags are equal, by trigger names in lexical order. + * This is useful for enforcing a unique order of events in a priority queue of Event instances. + */ @Override public int compareTo(Event e) { // Compare tags first. @@ -22,9 +26,8 @@ public int compareTo(Event e) { return ret; } - /** This equals() method does NOT compare tags, only compares triggers. */ - @Override - public boolean equals(Object o) { + /** This method checks if two events have the same triggers. */ + public boolean hasSameTriggers(Object o) { if (o == null) return false; if (o instanceof Event) { Event e = (Event) o; diff --git a/core/src/main/java/org/lflang/analyses/statespace/EventQueue.java b/core/src/main/java/org/lflang/analyses/statespace/EventQueue.java index b04339d465..7c04206da7 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/EventQueue.java +++ b/core/src/main/java/org/lflang/analyses/statespace/EventQueue.java @@ -2,7 +2,10 @@ import java.util.PriorityQueue; -/** An event queue implementation that sorts events by time tag order */ +/** + * An event queue implementation that sorts events in the order of _time tags_ and _trigger names_ + * based on the implementation of compareTo() in the Event class. + */ public class EventQueue extends PriorityQueue { /** diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java index 75275a6ff3..e2aa66737d 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java +++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java @@ -132,7 +132,7 @@ public CodeBuilder generateDot() { + " | " + n.getReactionsInvoked().size() + " | " - + n.getEventQ().size() + + n.getEventQcopy().size() + "}" + " | " + n.getTag() @@ -147,7 +147,7 @@ public CodeBuilder generateDot() { .collect(Collectors.toList()); String reactionsStr = String.join("\\n", reactions); List events = - n.getEventQ().stream().map(Event::toString).collect(Collectors.toList()); + n.getEventQcopy().stream().map(Event::toString).collect(Collectors.toList()); String eventsStr = String.join("\\n", events); dot.pr( "S" diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java index f827fa9ef6..3d22bc7fbd 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java +++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java @@ -279,7 +279,7 @@ else if (previousTag != null && currentTag.timestamp == previousTag.timestamp) { // to the existing state space node. currentNode.getReactionsInvoked().addAll(reactionsTemp); // Update the eventQ snapshot. - currentNode.setEventQ(new ArrayList(eventQ)); + currentNode.setEventQcopy(new ArrayList(eventQ)); } else { throw new AssertionError("unreachable"); } diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java index 72b6acffde..af8d5f1e4e 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java +++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java @@ -1,5 +1,6 @@ package org.lflang.analyses.statespace; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -15,51 +16,73 @@ public class StateSpaceNode { private Tag tag; private TimeValue time; // Readable representation of tag.timestamp private Set reactionsInvoked; - private ArrayList eventQ; + private ArrayList eventQcopy; // A snapshot of the eventQ represented as an ArrayList - public StateSpaceNode(Tag tag, Set reactionsInvoked, ArrayList eventQ) { + public StateSpaceNode( + Tag tag, Set reactionsInvoked, ArrayList eventQcopy) { this.tag = tag; - this.eventQ = eventQ; + this.eventQcopy = eventQcopy; this.reactionsInvoked = reactionsInvoked; this.time = TimeValue.fromNanoSeconds(tag.timestamp); } /** - * Assuming both eventQs have the same length, for each pair of events in eventQ1 and eventQ2, - * check if the time distances between the node's tag and the two events' tags are equal. + * Check if two state space nodes have the same time distance from their respective future events. + * Given eventQs from both nodes have the same length, check if the time distances between the two + * nodes' tags and the tags of a pair of events are equal, for all pairs of events (one from n1's + * eventQ and the other from n2's eventQ),. */ - private boolean equidistant(StateSpaceNode n1, StateSpaceNode n2) { - if (n1.eventQ.size() != n2.eventQ.size()) return false; - for (int i = 0; i < n1.eventQ.size(); i++) { - if (n1.eventQ.get(i).getTag().timestamp - n1.getTag().timestamp - != n2.eventQ.get(i).getTag().timestamp - n2.getTag().timestamp) { + private boolean equidistantNodes(StateSpaceNode n1, StateSpaceNode n2) { + if (n1.eventQcopy.size() != n2.eventQcopy.size()) return false; + for (int i = 0; i < n1.eventQcopy.size(); i++) { + if (n1.eventQcopy.get(i).getTag().timestamp - n1.getTag().timestamp + != n2.eventQcopy.get(i).getTag().timestamp - n2.getTag().timestamp) { return false; } } return true; } + /** + * Check if two event queues are analogous, meaning that 1) the two event queues have the same + * size, and 2) each pair of events has the same triggers. + */ + private boolean analogousEventQs(ArrayList q1, ArrayList q2) { + if (q1.size() != q2.size()) return false; + for (int i = 0; i < q1.size(); i++) { + if (!q1.get(i).hasSameTriggers(q2.get(i))) return false; + } + return true; + } + /** Two methods for pretty printing */ public void display() { - System.out.println("(" + this.time + ", " + reactionsInvoked + ", " + eventQ + ")"); + System.out.println("(" + this.time + ", " + reactionsInvoked + ", " + eventQcopy + ")"); } public String toString() { - return "(" + this.time + ", " + reactionsInvoked + ", " + eventQ + ")"; + return "(" + this.time + ", " + reactionsInvoked + ", " + eventQcopy + ")"; } /** - * This equals method does NOT compare tags, only compares reactionsInvoked, eventQ, and whether - * future events are equally distant. + * This equals method does NOT compare tags, only compares reactionsInvoked, eventQcopy, and + * whether future events are equally distant. + * + *

FIXME: Where is this used? This is not used!!! */ @Override public boolean equals(Object o) { + try { + throw new IOException(); + } catch (IOException e) { + e.printStackTrace(); + } if (o == null) return false; if (o instanceof StateSpaceNode) { StateSpaceNode node = (StateSpaceNode) o; if (this.reactionsInvoked.equals(node.reactionsInvoked) - && this.eventQ.equals(node.eventQ) - && equidistant(this, node)) return true; + && analogousEventQs(this.eventQcopy, node.eventQcopy) + && equidistantNodes(this, node)) return true; } return false; } @@ -78,7 +101,7 @@ public int hash() { // Generate hash for the triggers in the queued events. List eventNames = - this.eventQ.stream() + this.eventQcopy.stream() .map(Event::getTrigger) .map(TriggerInstance::getFullName) .collect(Collectors.toList()); @@ -86,7 +109,7 @@ public int hash() { // Generate hash for the time differences. List timeDiff = - this.eventQ.stream() + this.eventQcopy.stream() .map( e -> { return e.getTag().timestamp - this.tag.timestamp; @@ -117,11 +140,11 @@ public Set getReactionsInvoked() { return reactionsInvoked; } - public ArrayList getEventQ() { - return eventQ; + public ArrayList getEventQcopy() { + return eventQcopy; } - public void setEventQ(ArrayList list) { - eventQ = list; + public void setEventQcopy(ArrayList list) { + eventQcopy = list; } } From 55583e15a9f77eb7ea72ac2f4df31bfb145b5cb1 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Tue, 4 Jul 2023 18:12:47 +0200 Subject: [PATCH 231/516] Prune unused code --- .../analyses/statespace/StateSpaceNode.java | 65 +++---------------- 1 file changed, 8 insertions(+), 57 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java index af8d5f1e4e..0061853157 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java +++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java @@ -1,6 +1,5 @@ package org.lflang.analyses.statespace; -import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -26,35 +25,6 @@ public StateSpaceNode( this.time = TimeValue.fromNanoSeconds(tag.timestamp); } - /** - * Check if two state space nodes have the same time distance from their respective future events. - * Given eventQs from both nodes have the same length, check if the time distances between the two - * nodes' tags and the tags of a pair of events are equal, for all pairs of events (one from n1's - * eventQ and the other from n2's eventQ),. - */ - private boolean equidistantNodes(StateSpaceNode n1, StateSpaceNode n2) { - if (n1.eventQcopy.size() != n2.eventQcopy.size()) return false; - for (int i = 0; i < n1.eventQcopy.size(); i++) { - if (n1.eventQcopy.get(i).getTag().timestamp - n1.getTag().timestamp - != n2.eventQcopy.get(i).getTag().timestamp - n2.getTag().timestamp) { - return false; - } - } - return true; - } - - /** - * Check if two event queues are analogous, meaning that 1) the two event queues have the same - * size, and 2) each pair of events has the same triggers. - */ - private boolean analogousEventQs(ArrayList q1, ArrayList q2) { - if (q1.size() != q2.size()) return false; - for (int i = 0; i < q1.size(); i++) { - if (!q1.get(i).hasSameTriggers(q2.get(i))) return false; - } - return true; - } - /** Two methods for pretty printing */ public void display() { System.out.println("(" + this.time + ", " + reactionsInvoked + ", " + eventQcopy + ")"); @@ -65,32 +35,12 @@ public String toString() { } /** - * This equals method does NOT compare tags, only compares reactionsInvoked, eventQcopy, and - * whether future events are equally distant. - * - *

FIXME: Where is this used? This is not used!!! - */ - @Override - public boolean equals(Object o) { - try { - throw new IOException(); - } catch (IOException e) { - e.printStackTrace(); - } - if (o == null) return false; - if (o instanceof StateSpaceNode) { - StateSpaceNode node = (StateSpaceNode) o; - if (this.reactionsInvoked.equals(node.reactionsInvoked) - && analogousEventQs(this.eventQcopy, node.eventQcopy) - && equidistantNodes(this, node)) return true; - } - return false; - } - - /** - * Generate hash for the node. This hash function is used for checking the uniqueness of nodes. It - * is not meant to be used as a hashCode() because doing so interferes with node insertion in the - * state space diagram. + * Generate hash for the node. This hash function is used for checking whether two nodes are + * analogous, meaning that 1) they have the same reactions invoked at their tags, 2) the queued + * events have the same triggers, and 3) the time offsets between future events' tags of one node + * and its tag are the same as the time offsets between future events' tags of the other node and + * the other node's tag. The hash() method is not meant to replace the hashCode() method because + * doing so changes the way nodes are inserted in the state space diagram. */ public int hash() { // Initial value @@ -107,7 +57,8 @@ public int hash() { .collect(Collectors.toList()); result = 31 * result + eventNames.hashCode(); - // Generate hash for the time differences. + // Generate hash for a list of time differences between future events' tags and + // the current tag. List timeDiff = this.eventQcopy.stream() .map( From 8b21944c26e6e9d9a61579527241e9ba180a0393 Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Wed, 5 Jul 2023 15:49:18 +0900 Subject: [PATCH 232/516] Rename `TimeValue.NEVER()` to `TimeValue.never()` --- .../java/org/lflang/federated/extensions/TSExtension.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java index 68f91853cc..84d6e76edb 100644 --- a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java @@ -205,7 +205,7 @@ private List getUpstreamConnectionDelays(FederateInstance federate) { if (delays != null) { for (Expression delay : delays) { if (delay == null) { - element += "TimeValue.NEVER()"; + element += "TimeValue.never()"; } else { element += "TimeValue.nsec(" + getNetworkDelayLiteral(delay) + ")"; } @@ -215,7 +215,7 @@ private List getUpstreamConnectionDelays(FederateInstance federate) { } } } else { - element += "TimeValue.NEVER()"; + element += "TimeValue.never()"; } element += "]"; candidates.add(element); From 3f8a1f69b4846780d7c2e966f11d37089b6606a4 Mon Sep 17 00:00:00 2001 From: revol-xut Date: Wed, 5 Jul 2023 11:16:17 +0200 Subject: [PATCH 233/516] removing verbose null ptr check --- .../org/lflang/generator/cpp/CppAssembleMethodGenerator.kt | 2 +- core/src/main/resources/lib/cpp/reactor-cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt index c8ffec605b..bcce92a055 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt @@ -101,7 +101,7 @@ class CppAssembleMethodGenerator(private val reactor: Reactor) { } private val Connection.cppDelay: String - get() = if (delay != null) "${delay.toCppTime()}" else "0s" + get() = delay?.toCppTime() ?: "0s" private val Connection.properties: String get() = "reactor::ConnectionProperties{$cppType, $cppDelay, nullptr}" diff --git a/core/src/main/resources/lib/cpp/reactor-cpp b/core/src/main/resources/lib/cpp/reactor-cpp index 4d1d18fca6..251299abc4 160000 --- a/core/src/main/resources/lib/cpp/reactor-cpp +++ b/core/src/main/resources/lib/cpp/reactor-cpp @@ -1 +1 @@ -Subproject commit 4d1d18fca678fa89242944c560fb42bff6eb8652 +Subproject commit 251299abc425aff74d05fa203a95691986ee0ef4 From 460f1c824e7b52f92897b855ea8c5b096ab2ae15 Mon Sep 17 00:00:00 2001 From: revol-xut Date: Wed, 5 Jul 2023 12:49:23 +0200 Subject: [PATCH 234/516] updating reactor-cpp --- core/src/main/resources/lib/cpp/reactor-cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/cpp/reactor-cpp b/core/src/main/resources/lib/cpp/reactor-cpp index 251299abc4..5775399706 160000 --- a/core/src/main/resources/lib/cpp/reactor-cpp +++ b/core/src/main/resources/lib/cpp/reactor-cpp @@ -1 +1 @@ -Subproject commit 251299abc425aff74d05fa203a95691986ee0ef4 +Subproject commit 5775399706b217a775a81aba9a2c024998a0ca64 From 9bb39cf217310b93e95f464e2601ddeca93c234a Mon Sep 17 00:00:00 2001 From: "Shaokai (Jerry) Lin" Date: Wed, 5 Jul 2023 15:37:49 +0200 Subject: [PATCH 235/516] Apply suggestions from @lhstrh Co-authored-by: Marten Lohstroh --- buildSrc/src/main/groovy/org.lflang.antlr-conventions.gradle | 2 +- core/src/main/antlr/MTLLexer.g4 | 2 +- .../main/java/org/lflang/analyses/cast/AbstractAstVisitor.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/groovy/org.lflang.antlr-conventions.gradle b/buildSrc/src/main/groovy/org.lflang.antlr-conventions.gradle index 291cba30ae..096e0636b3 100644 --- a/buildSrc/src/main/groovy/org.lflang.antlr-conventions.gradle +++ b/buildSrc/src/main/groovy/org.lflang.antlr-conventions.gradle @@ -11,6 +11,6 @@ dependencies { } if (project.tasks.findByName('compileKotlin')) { - // make all kotlin compile tasks depent on the antl generation tasks + // make all kotlin compile tasks dependent on the antl generation tasks tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).each(it -> it.dependsOn(tasks.withType(AntlrTask))) } diff --git a/core/src/main/antlr/MTLLexer.g4 b/core/src/main/antlr/MTLLexer.g4 index b8fe9c118d..5d0b803ff8 100644 --- a/core/src/main/antlr/MTLLexer.g4 +++ b/core/src/main/antlr/MTLLexer.g4 @@ -114,4 +114,4 @@ INTEGER ID : ([a-zA-Z0-9]|'_')+ - ; \ No newline at end of file + ; diff --git a/core/src/main/java/org/lflang/analyses/cast/AbstractAstVisitor.java b/core/src/main/java/org/lflang/analyses/cast/AbstractAstVisitor.java index c780a9c682..732dad8fc6 100644 --- a/core/src/main/java/org/lflang/analyses/cast/AbstractAstVisitor.java +++ b/core/src/main/java/org/lflang/analyses/cast/AbstractAstVisitor.java @@ -2,7 +2,7 @@ import java.util.List; -/** Modeled after AbstractParseTreeVisitor.class */ +/** Modeled after {@link AbstractParseTreeVisitor}. */ public abstract class AbstractAstVisitor implements AstVisitor { @Override From 9b5a6f8ed5b2d3a8837b2e29cd9298d3cb37dc47 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 5 Jul 2023 16:10:43 +0200 Subject: [PATCH 236/516] Apply suggestions from @lhstrh --- core/src/main/java/org/lflang/FileConfig.java | 2 +- .../{cast => c}/AbstractAstVisitor.java | 2 +- .../lflang/analyses/{cast => c}/AstUtils.java | 2 +- .../analyses/{cast => c}/AstVisitor.java | 2 +- .../{cast => c}/BuildAstParseTreeVisitor.java | 2 +- .../org/lflang/analyses/{cast => c}/CAst.java | 2 +- .../analyses/{cast => c}/CAstVisitor.java | 2 +- .../analyses/{cast => c}/CBaseAstVisitor.java | 2 +- .../analyses/{cast => c}/CToUclidVisitor.java | 4 +-- .../{cast => c}/IfNormalFormAstVisitor.java | 2 +- .../VariablePrecedenceVisitor.java | 5 ++-- .../analyses/{cast => c}/Visitable.java | 2 +- .../statespace/StateSpaceExplorer.java | 25 ++++++------------- .../lflang/analyses/uclid/UclidGenerator.java | 12 ++++----- 14 files changed, 28 insertions(+), 38 deletions(-) rename core/src/main/java/org/lflang/analyses/{cast => c}/AbstractAstVisitor.java (91%) rename core/src/main/java/org/lflang/analyses/{cast => c}/AstUtils.java (98%) rename core/src/main/java/org/lflang/analyses/{cast => c}/AstVisitor.java (95%) rename core/src/main/java/org/lflang/analyses/{cast => c}/BuildAstParseTreeVisitor.java (99%) rename core/src/main/java/org/lflang/analyses/{cast => c}/CAst.java (99%) rename core/src/main/java/org/lflang/analyses/{cast => c}/CAstVisitor.java (99%) rename core/src/main/java/org/lflang/analyses/{cast => c}/CBaseAstVisitor.java (99%) rename core/src/main/java/org/lflang/analyses/{cast => c}/CToUclidVisitor.java (99%) rename core/src/main/java/org/lflang/analyses/{cast => c}/IfNormalFormAstVisitor.java (99%) rename core/src/main/java/org/lflang/analyses/{cast => c}/VariablePrecedenceVisitor.java (90%) rename core/src/main/java/org/lflang/analyses/{cast => c}/Visitable.java (90%) diff --git a/core/src/main/java/org/lflang/FileConfig.java b/core/src/main/java/org/lflang/FileConfig.java index 9012e0963d..eb2d53c655 100644 --- a/core/src/main/java/org/lflang/FileConfig.java +++ b/core/src/main/java/org/lflang/FileConfig.java @@ -33,7 +33,7 @@ public abstract class FileConfig { public static final String DEFAULT_SRC_GEN_DIR = "src-gen"; /** Default name of the directory to store generated verification models in. */ - public static final String DEFAULT_MODEL_GEN_DIR = "model-gen"; + public static final String DEFAULT_MODEL_GEN_DIR = "mod-gen"; // Public fields. diff --git a/core/src/main/java/org/lflang/analyses/cast/AbstractAstVisitor.java b/core/src/main/java/org/lflang/analyses/c/AbstractAstVisitor.java similarity index 91% rename from core/src/main/java/org/lflang/analyses/cast/AbstractAstVisitor.java rename to core/src/main/java/org/lflang/analyses/c/AbstractAstVisitor.java index 732dad8fc6..c279f962d6 100644 --- a/core/src/main/java/org/lflang/analyses/cast/AbstractAstVisitor.java +++ b/core/src/main/java/org/lflang/analyses/c/AbstractAstVisitor.java @@ -1,4 +1,4 @@ -package org.lflang.analyses.cast; +package org.lflang.analyses.c; import java.util.List; diff --git a/core/src/main/java/org/lflang/analyses/cast/AstUtils.java b/core/src/main/java/org/lflang/analyses/c/AstUtils.java similarity index 98% rename from core/src/main/java/org/lflang/analyses/cast/AstUtils.java rename to core/src/main/java/org/lflang/analyses/c/AstUtils.java index 65f1c28852..c3ac49b6fd 100644 --- a/core/src/main/java/org/lflang/analyses/cast/AstUtils.java +++ b/core/src/main/java/org/lflang/analyses/c/AstUtils.java @@ -1,4 +1,4 @@ -package org.lflang.analyses.cast; +package org.lflang.analyses.c; import java.util.List; import org.antlr.v4.runtime.ParserRuleContext; diff --git a/core/src/main/java/org/lflang/analyses/cast/AstVisitor.java b/core/src/main/java/org/lflang/analyses/c/AstVisitor.java similarity index 95% rename from core/src/main/java/org/lflang/analyses/cast/AstVisitor.java rename to core/src/main/java/org/lflang/analyses/c/AstVisitor.java index b19e5cbfc7..d25c9d0364 100644 --- a/core/src/main/java/org/lflang/analyses/cast/AstVisitor.java +++ b/core/src/main/java/org/lflang/analyses/c/AstVisitor.java @@ -1,4 +1,4 @@ -package org.lflang.analyses.cast; +package org.lflang.analyses.c; import java.util.List; diff --git a/core/src/main/java/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java b/core/src/main/java/org/lflang/analyses/c/BuildAstParseTreeVisitor.java similarity index 99% rename from core/src/main/java/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java rename to core/src/main/java/org/lflang/analyses/c/BuildAstParseTreeVisitor.java index aa9fd1917d..e8923a2ce4 100644 --- a/core/src/main/java/org/lflang/analyses/cast/BuildAstParseTreeVisitor.java +++ b/core/src/main/java/org/lflang/analyses/c/BuildAstParseTreeVisitor.java @@ -1,4 +1,4 @@ -package org.lflang.analyses.cast; +package org.lflang.analyses.c; import java.util.ArrayList; import java.util.Arrays; diff --git a/core/src/main/java/org/lflang/analyses/cast/CAst.java b/core/src/main/java/org/lflang/analyses/c/CAst.java similarity index 99% rename from core/src/main/java/org/lflang/analyses/cast/CAst.java rename to core/src/main/java/org/lflang/analyses/c/CAst.java index 2126902da9..4bb391be00 100644 --- a/core/src/main/java/org/lflang/analyses/cast/CAst.java +++ b/core/src/main/java/org/lflang/analyses/c/CAst.java @@ -1,4 +1,4 @@ -package org.lflang.analyses.cast; +package org.lflang.analyses.c; import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/org/lflang/analyses/cast/CAstVisitor.java b/core/src/main/java/org/lflang/analyses/c/CAstVisitor.java similarity index 99% rename from core/src/main/java/org/lflang/analyses/cast/CAstVisitor.java rename to core/src/main/java/org/lflang/analyses/c/CAstVisitor.java index 034a93449c..d3c8f5c28d 100644 --- a/core/src/main/java/org/lflang/analyses/cast/CAstVisitor.java +++ b/core/src/main/java/org/lflang/analyses/c/CAstVisitor.java @@ -1,4 +1,4 @@ -package org.lflang.analyses.cast; +package org.lflang.analyses.c; import java.util.List; diff --git a/core/src/main/java/org/lflang/analyses/cast/CBaseAstVisitor.java b/core/src/main/java/org/lflang/analyses/c/CBaseAstVisitor.java similarity index 99% rename from core/src/main/java/org/lflang/analyses/cast/CBaseAstVisitor.java rename to core/src/main/java/org/lflang/analyses/c/CBaseAstVisitor.java index ce212c53a7..11c2df3ef6 100644 --- a/core/src/main/java/org/lflang/analyses/cast/CBaseAstVisitor.java +++ b/core/src/main/java/org/lflang/analyses/c/CBaseAstVisitor.java @@ -1,4 +1,4 @@ -package org.lflang.analyses.cast; +package org.lflang.analyses.c; import java.util.List; diff --git a/core/src/main/java/org/lflang/analyses/cast/CToUclidVisitor.java b/core/src/main/java/org/lflang/analyses/c/CToUclidVisitor.java similarity index 99% rename from core/src/main/java/org/lflang/analyses/cast/CToUclidVisitor.java rename to core/src/main/java/org/lflang/analyses/c/CToUclidVisitor.java index 634045a10e..0608387abb 100644 --- a/core/src/main/java/org/lflang/analyses/cast/CToUclidVisitor.java +++ b/core/src/main/java/org/lflang/analyses/c/CToUclidVisitor.java @@ -1,8 +1,8 @@ -package org.lflang.analyses.cast; +package org.lflang.analyses.c; import java.util.ArrayList; import java.util.List; -import org.lflang.analyses.cast.CAst.*; +import org.lflang.analyses.c.CAst.*; import org.lflang.analyses.uclid.UclidGenerator; import org.lflang.generator.ActionInstance; import org.lflang.generator.NamedInstance; diff --git a/core/src/main/java/org/lflang/analyses/cast/IfNormalFormAstVisitor.java b/core/src/main/java/org/lflang/analyses/c/IfNormalFormAstVisitor.java similarity index 99% rename from core/src/main/java/org/lflang/analyses/cast/IfNormalFormAstVisitor.java rename to core/src/main/java/org/lflang/analyses/c/IfNormalFormAstVisitor.java index a8ad9e8284..d51bec6aad 100644 --- a/core/src/main/java/org/lflang/analyses/cast/IfNormalFormAstVisitor.java +++ b/core/src/main/java/org/lflang/analyses/c/IfNormalFormAstVisitor.java @@ -1,4 +1,4 @@ -package org.lflang.analyses.cast; +package org.lflang.analyses.c; import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/org/lflang/analyses/cast/VariablePrecedenceVisitor.java b/core/src/main/java/org/lflang/analyses/c/VariablePrecedenceVisitor.java similarity index 90% rename from core/src/main/java/org/lflang/analyses/cast/VariablePrecedenceVisitor.java rename to core/src/main/java/org/lflang/analyses/c/VariablePrecedenceVisitor.java index 944245d6ef..9354e36269 100644 --- a/core/src/main/java/org/lflang/analyses/cast/VariablePrecedenceVisitor.java +++ b/core/src/main/java/org/lflang/analyses/c/VariablePrecedenceVisitor.java @@ -1,6 +1,6 @@ -package org.lflang.analyses.cast; +package org.lflang.analyses.c; -import org.lflang.analyses.cast.CAst.*; +import org.lflang.analyses.c.CAst.*; /** This visitor marks certain variable node as "previous." */ public class VariablePrecedenceVisitor extends CBaseAstVisitor { @@ -9,7 +9,6 @@ public class VariablePrecedenceVisitor extends CBaseAstVisitor { // e.g., self->s = (self->s + 1) - (2 * 2). @Override public Void visitAssignmentNode(AssignmentNode node) { - // System.out.println("******* In assignment!!!"); if (node.left instanceof StateVarNode) { if (node.right instanceof AstNodeBinary) { AstNodeBinary n = (AstNodeBinary) node.right; diff --git a/core/src/main/java/org/lflang/analyses/cast/Visitable.java b/core/src/main/java/org/lflang/analyses/c/Visitable.java similarity index 90% rename from core/src/main/java/org/lflang/analyses/cast/Visitable.java rename to core/src/main/java/org/lflang/analyses/c/Visitable.java index 458d629bc2..cbdea446ca 100644 --- a/core/src/main/java/org/lflang/analyses/cast/Visitable.java +++ b/core/src/main/java/org/lflang/analyses/c/Visitable.java @@ -1,4 +1,4 @@ -package org.lflang.analyses.cast; +package org.lflang.analyses.c; import java.util.List; diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java index 3d22bc7fbd..48f407914f 100644 --- a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java +++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java @@ -18,7 +18,10 @@ import org.lflang.lf.Time; import org.lflang.lf.Variable; -/** Explores the state space of an LF program. */ +/** + * (EXPERIMENTAL) Explores the state space of an LF program. Use with caution since this is + * experimental code. + */ public class StateSpaceExplorer { // Instantiate an empty state space diagram. @@ -69,7 +72,9 @@ public void addInitialEvents(ReactorInstance reactor) { * space during exploration. If a loop is found (i.e. a previously encountered state is reached * again) during exploration, the function returns early. * - *

TODOs: 1. Handle action with 0 min delay. 2. Check if zero-delay connection works. + *

TODOs: 1. Handle action with 0 minimum delay. + * + *

Note: This is experimental code which is to be refactored in a future PR. Use with caution. */ public void explore(Tag horizon, boolean findLoop) { // Traverse the main reactor instance recursively to find @@ -77,7 +82,6 @@ public void explore(Tag horizon, boolean findLoop) { // FIXME: It seems that we need to handle shutdown triggers // separately, because they could break the back loop. addInitialEvents(this.main); - // System.out.println("DEBUG: Event queue: " + this.eventQ); Tag previousTag = null; // Tag in the previous loop ITERATION Tag currentTag = null; // Tag in the current loop ITERATION @@ -102,10 +106,8 @@ public void explore(Tag horizon, boolean findLoop) { // FIXME: Use stream methods here? while (eventQ.size() > 0 && eventQ.peek().getTag().compareTo(currentTag) == 0) { Event e = eventQ.poll(); - // System.out.println("DEBUG: Popped an event: " + e); currentEvents.add(e); } - // System.out.println("DEBUG: currentEvents: " + currentEvents); // Collect all the reactions invoked in this current LOOP ITERATION // triggered by the earliest events. @@ -116,8 +118,6 @@ public void explore(Tag horizon, boolean findLoop) { for (Event e : currentEvents) { Set dependentReactions = e.getTrigger().getDependentReactions(); reactionsTemp.addAll(dependentReactions); - // System.out.println("DEBUG: dependentReactions: " + dependentReactions); - // System.out.println("DEBUG: ReactionTemp: " + reactionsTemp); // If the event is a timer firing, enqueue the next firing. if (e.getTrigger() instanceof TimerInstance) { @@ -161,7 +161,6 @@ public void explore(Tag horizon, boolean findLoop) { // Create and enqueue a new event. Event e = new Event(downstreamPort, new Tag(currentTag.timestamp + delay, 0, false)); - // System.out.println("DEBUG: Added a port event: " + e); eventQ.add(e); } } @@ -175,7 +174,6 @@ public void explore(Tag horizon, boolean findLoop) { // Create and enqueue a new event. Event e = new Event(effect, new Tag(currentTag.timestamp + min_delay, microstep, false)); - // System.out.println("DEBUG: Added an action event: " + e); eventQ.add(e); } } @@ -184,7 +182,6 @@ public void explore(Tag horizon, boolean findLoop) { // We are at the first iteration. // Initialize currentNode. if (previousTag == null) { - // System.out.println("DEBUG: Case 1"); //// Now we are done with the node at the previous tag, //// work on the new node at the current timestamp. // Copy the reactions in reactionsTemp. @@ -212,7 +209,6 @@ public void explore(Tag horizon, boolean findLoop) { // at the timestamp-level, so that we don't have to // worry about microsteps. else if (previousTag != null && currentTag.timestamp > previousTag.timestamp) { - // System.out.println("DEBUG: Case 2"); // Whenever we finish a tag, check for loops fist. // If currentNode matches an existing node in uniqueNodes, // duplicate is set to the existing node. @@ -274,14 +270,13 @@ else if (previousTag != null && currentTag.timestamp > previousTag.timestamp) { // Timestamp does not advance because we are processing // connections with zero delay. else if (previousTag != null && currentTag.timestamp == previousTag.timestamp) { - // System.out.println("DEBUG: Case 3"); // Add reactions explored in the current loop iteration // to the existing state space node. currentNode.getReactionsInvoked().addAll(reactionsTemp); // Update the eventQ snapshot. currentNode.setEventQcopy(new ArrayList(eventQ)); } else { - throw new AssertionError("unreachable"); + throw new AssertionError("Unreachable"); } // Update the current tag for the next iteration. @@ -294,12 +289,8 @@ else if (previousTag != null && currentTag.timestamp == previousTag.timestamp) { // 1. the event queue is empty, or // 2. the horizon is reached. if (eventQ.size() == 0) { - // System.out.println("DEBUG: Stopping because eventQ is empty!"); stop = true; } else if (currentTag.timestamp > horizon.timestamp) { - // System.out.println("DEBUG: Stopping because horizon is reached! Horizon: " + horizon + - // "Current Tag: " + currentTag); - // System.out.println("DEBUG: EventQ: " + eventQ); stop = true; } } diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java index d188d83ae4..7421b8fd7b 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java @@ -40,12 +40,12 @@ import org.lflang.Target; import org.lflang.TimeUnit; import org.lflang.TimeValue; -import org.lflang.analyses.cast.AstUtils; -import org.lflang.analyses.cast.BuildAstParseTreeVisitor; -import org.lflang.analyses.cast.CAst; -import org.lflang.analyses.cast.CToUclidVisitor; -import org.lflang.analyses.cast.IfNormalFormAstVisitor; -import org.lflang.analyses.cast.VariablePrecedenceVisitor; +import org.lflang.analyses.c.AstUtils; +import org.lflang.analyses.c.BuildAstParseTreeVisitor; +import org.lflang.analyses.c.CAst; +import org.lflang.analyses.c.CToUclidVisitor; +import org.lflang.analyses.c.IfNormalFormAstVisitor; +import org.lflang.analyses.c.VariablePrecedenceVisitor; import org.lflang.analyses.statespace.StateSpaceDiagram; import org.lflang.analyses.statespace.StateSpaceExplorer; import org.lflang.analyses.statespace.StateSpaceNode; From eeedef705e81270a3f43502e451ade18aff1fd16 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 5 Jul 2023 16:12:21 +0200 Subject: [PATCH 237/516] Fix typo --- core/src/main/java/org/lflang/TargetProperty.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/TargetProperty.java b/core/src/main/java/org/lflang/TargetProperty.java index 27fafee00d..aa091af3cd 100644 --- a/core/src/main/java/org/lflang/TargetProperty.java +++ b/core/src/main/java/org/lflang/TargetProperty.java @@ -410,7 +410,7 @@ public enum TargetProperty { config.noRuntimeValidation = ASTUtils.toBoolean(value); }), - /** Directive to not check the generated verification model. */ + /** Directive to check the generated verification model. */ VERIFY( "verify", PrimitiveType.BOOLEAN, From 6a1109ed4d902958acb1e9f60cdada77f93500c1 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 5 Jul 2023 16:59:40 +0200 Subject: [PATCH 238/516] Remove the unused global variable --- .../main/java/org/lflang/analyses/uclid/UclidGenerator.java | 3 +-- core/src/main/java/org/lflang/ast/ASTUtils.java | 4 ---- core/src/main/java/org/lflang/generator/GeneratorBase.java | 6 ------ core/src/main/java/org/lflang/generator/c/CGenerator.java | 4 +--- 4 files changed, 2 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java index 7421b8fd7b..17d71da304 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java @@ -194,8 +194,7 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // Create the main reactor instance if there is a main reactor. this.main = - ASTUtils.createMainReactorInstance( - mainDef, reactors, hasDeadlines, messageReporter, targetConfig); + ASTUtils.createMainReactorInstance(mainDef, reactors, messageReporter, targetConfig); // Extract information from the named instances. populateDataStructures(); diff --git a/core/src/main/java/org/lflang/ast/ASTUtils.java b/core/src/main/java/org/lflang/ast/ASTUtils.java index 7e686dd190..1789c79ead 100644 --- a/core/src/main/java/org/lflang/ast/ASTUtils.java +++ b/core/src/main/java/org/lflang/ast/ASTUtils.java @@ -595,7 +595,6 @@ public static List collectElements( public static ReactorInstance createMainReactorInstance( Instantiation mainDef, List reactors, - boolean hasDeadlines, MessageReporter messageReporter, TargetConfig targetConfig) { if (mainDef != null) { @@ -609,9 +608,6 @@ public static ReactorInstance createMainReactorInstance( .error("Main reactor has causality cycles. Skipping code generation."); return null; } - if (hasDeadlines) { - main.assignDeadlines(); - } // Inform the run-time of the breadth/parallelism of the reaction graph var breadth = reactionInstanceGraph.getBreadth(); if (breadth == 0) { diff --git a/core/src/main/java/org/lflang/generator/GeneratorBase.java b/core/src/main/java/org/lflang/generator/GeneratorBase.java index 1983b3a145..dde30f5580 100644 --- a/core/src/main/java/org/lflang/generator/GeneratorBase.java +++ b/core/src/main/java/org/lflang/generator/GeneratorBase.java @@ -144,12 +144,6 @@ public Instantiation getMainDef() { /** 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; diff --git a/core/src/main/java/org/lflang/generator/c/CGenerator.java b/core/src/main/java/org/lflang/generator/c/CGenerator.java index adf6f341b8..5825d4043f 100644 --- a/core/src/main/java/org/lflang/generator/c/CGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CGenerator.java @@ -1957,10 +1957,8 @@ protected void setUpGeneralParameters() { "LOG_LEVEL", String.valueOf(targetConfig.logLevel.ordinal())); targetConfig.compileAdditionalSources.addAll(CCoreFilesUtils.getCTargetSrc()); // Create the main reactor instance if there is a main reactor. - // FIXME: is `hasDeadlines` here always false? That does not look right. this.main = - ASTUtils.createMainReactorInstance( - mainDef, reactors, hasDeadlines, messageReporter, targetConfig); + ASTUtils.createMainReactorInstance(mainDef, reactors, messageReporter, targetConfig); if (hasModalReactors) { // So that each separate compile knows about modal reactors, do this: targetConfig.compileDefinitions.put("MODAL_REACTORS", "TRUE"); From b5f5081f1616686cbe122af9cd042be3380ff2ce Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Thu, 6 Jul 2023 19:38:28 -0700 Subject: [PATCH 239/516] no warnings [temp] --- .../org/lflang/generator/c/CCmakeGenerator.java | 6 ++++++ core/src/main/resources/lib/c/reactor-c | 2 +- test/C/src/pico/BareThread.lf | 13 +++++++++++++ test/C/src/pico/FreeRTOSThread.lf | 12 ++++++++++++ test/C/src/pico/HelloPico.lf | 2 +- test/C/src/pico/Timer.lf | 11 +++++++++++ 6 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 test/C/src/pico/BareThread.lf create mode 100644 test/C/src/pico/FreeRTOSThread.lf create mode 100644 test/C/src/pico/Timer.lf diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index dd91294506..8141baf153 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -163,6 +163,7 @@ CodeBuilder generateCMakeCode( + " gcc\")"); cMakeCode.pr(" endif()"); cMakeCode.pr("endif()"); + cMakeCode.pr("set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -w\")"); cMakeCode.pr("# Require C11"); cMakeCode.pr("set(CMAKE_C_STANDARD 11)"); @@ -449,6 +450,11 @@ private static String setUpMainTargetPico( code.unindent(); code.pr(")"); code.newLine(); + code.pr("# Set pico-sdk default build configurations"); + code.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 0)"); + code.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 1)"); + code.pr("pico_add_extra_outputs(${LF_MAIN_TARGET})"); + return code.toString(); } diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 628e235172..2973e98f89 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 628e2351723b01dcef7977d1999426e4935d0272 +Subproject commit 2973e98f897efd774e42a3970f6d5423a868ba3f diff --git a/test/C/src/pico/BareThread.lf b/test/C/src/pico/BareThread.lf new file mode 100644 index 0000000000..1bde45fb9b --- /dev/null +++ b/test/C/src/pico/BareThread.lf @@ -0,0 +1,13 @@ +target C { + platform: { + name: "Pico", + threading: "BareIron" + } + threading: true, +} + +// check that only 2 threads can be created max +main reactor { + timer t(0, 1 sec); + +} diff --git a/test/C/src/pico/FreeRTOSThread.lf b/test/C/src/pico/FreeRTOSThread.lf new file mode 100644 index 0000000000..244b01ac7f --- /dev/null +++ b/test/C/src/pico/FreeRTOSThread.lf @@ -0,0 +1,12 @@ +target C { + platform: { + name: "Pico", + thread-lib: "FreeRTOS" + }, + threading: true, + workers: 4 +} + +// test creation of thread tasks using FreeRTOS support +main reactor {= +=} diff --git a/test/C/src/pico/HelloPico.lf b/test/C/src/pico/HelloPico.lf index ef25cdeba8..ba57fe7039 100644 --- a/test/C/src/pico/HelloPico.lf +++ b/test/C/src/pico/HelloPico.lf @@ -6,4 +6,4 @@ main reactor { reaction(startup) {= printf("Hello World!\n"); =} -} \ No newline at end of file +} diff --git a/test/C/src/pico/Timer.lf b/test/C/src/pico/Timer.lf new file mode 100644 index 0000000000..591d5d90e5 --- /dev/null +++ b/test/C/src/pico/Timer.lf @@ -0,0 +1,11 @@ +target C { + platform: "Pico" + timeout: 1 sec, +} + +main reactor { + timer t1(0, 100 msec); + reaction(t1) {= + printf("Logical Time: %d. \n", lf_time_logical()); + =} +} From 202c15c673cc309acee26259559634cf2da4f5e8 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sat, 8 Jul 2023 12:20:05 +0200 Subject: [PATCH 240/516] Fix get macro, SendToDownstream, and Correspondence axioms --- .../org/lflang/analyses/uclid/UclidGenerator.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java index 17d71da304..f3a0c7d230 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java @@ -512,7 +512,7 @@ protected void generateTraceDefinition() { } code.pr("// Helper macro that returns an element based on index."); code.pr("define get(tr : trace_t, i : step_t) : event_t ="); - code.pr("if (i >= START || i <= END_TRACE) then tr[i] else"); + code.pr("if (i >= START && i <= END_TRACE) then tr[i] else"); code.pr( "{ " + initialReactions @@ -916,7 +916,7 @@ protected void generateConnectionAxioms() { + " will be present.", "// with the same value after some fixed delay of " + delay + " nanoseconds.", "(" + source.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> ((", - " finite_exists (j : integer) in indices :: j > i && j <= END", + " finite_exists (j : integer) in indices :: j > i && j <= END_TRACE", " && " + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(j))", " && " + destination.getFullNameWithJoiner("_") @@ -940,6 +940,11 @@ protected void generateConnectionAxioms() { "&& (" + destination.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", " finite_exists (j : integer) in indices :: j >= START && j < i", " && " + source.getFullNameWithJoiner("_") + "_is_present" + "(t(j))", + " && " + + source.getFullNameWithJoiner("_") + + "(s(j)) == " + + destination.getFullNameWithJoiner("_") + + "(s(i))", connection.isPhysical() ? "" : " && g(i) == tag_delay(g(j), " + delay + ")", ")) // Closes the one-to-one relationship.", "));")); @@ -951,7 +956,7 @@ protected void generateConnectionAxioms() { "// If " + destination.getFullNameWithJoiner("_") + " is not present, then its value resets to 0.", - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END &&" + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END_TRACE &&" + " !isNULL(i)) ==> (", " (!" + destination.getFullNameWithJoiner("_") From 55435bdc6421a577ace3e3414ba3fb2926bb8857 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sat, 8 Jul 2023 13:01:52 +0200 Subject: [PATCH 241/516] Use END_TRACE in a few more places since it is the default --- .../java/org/lflang/analyses/uclid/UclidGenerator.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java index f3a0c7d230..8292a8f7ce 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java @@ -831,9 +831,9 @@ protected void generateReactorSemantics() { String.join( "\n", "// the same event can only trigger once in a logical instant", - "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END) ==>" + "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END_TRACE) ==>" + " (finite_forall (j : integer) in indices ::", - " (j >= START && j <= END) ==> ((rxn(i) == rxn(j) && i != j)", + " (j >= START && j <= END_TRACE) ==> ((rxn(i) == rxn(j) && i != j)", " ==> !tag_same(g(i), g(j)))));", "")); @@ -844,7 +844,7 @@ protected void generateReactorSemantics() { String.join( "\n", "// Only one reaction gets triggered at a time.", - "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END) ==> (", + "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END_TRACE) ==> (", " isNULL(i)")); code.indent(); for (int i = 0; i < this.reactionInstances.size(); i++) { @@ -1009,7 +1009,7 @@ protected void generateActionAxioms() { String.join( "\n", "// " + comment, - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END) ==> (" + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END_TRACE) ==> (" + " false", triggerStr, "));")); @@ -1021,7 +1021,7 @@ protected void generateActionAxioms() { "// If " + action.getFullNameWithJoiner("_") + " is not present, then its value resets to 0.", - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END &&" + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END_TRACE &&" + " !isNULL(i)) ==> (", " (!" + action.getFullNameWithJoiner("_") + "_is_present" + "(t(i)) ==> (", " " + action.getFullNameWithJoiner("_") + "(s(i)) == 0", From a9d44ffb231276c854c79db2f20d3ac4831e853c Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 10 Jul 2023 10:40:30 +0200 Subject: [PATCH 242/516] Rename workflow --- .github/workflows/only-c.yml | 4 ++-- ...d-verifier-c-tests.yml => uclid-verifier-c-benchmarks.yml} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename .github/workflows/{uclid-verifier-c-tests.yml => uclid-verifier-c-benchmarks.yml} (100%) diff --git a/.github/workflows/only-c.yml b/.github/workflows/only-c.yml index e3208bb3fe..1ae37cb18e 100644 --- a/.github/workflows/only-c.yml +++ b/.github/workflows/only-c.yml @@ -42,6 +42,6 @@ jobs: use-cpp: true all-platforms: ${{ !github.event.pull_request.draft }} - # Run the Uclid-based LF Verifier tests. + # Run the Uclid-based LF Verifier benchmarks. uclid: - uses: ./.github/workflows/uclid-verifier-c-tests.yml + uses: ./.github/workflows/uclid-verifier-c-benchmarks.yml diff --git a/.github/workflows/uclid-verifier-c-tests.yml b/.github/workflows/uclid-verifier-c-benchmarks.yml similarity index 100% rename from .github/workflows/uclid-verifier-c-tests.yml rename to .github/workflows/uclid-verifier-c-benchmarks.yml From 233a741b4bd677ac9fff5b2af17ee72b044312cd Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Mon, 10 Jul 2023 13:53:17 +0200 Subject: [PATCH 243/516] Apply spotless --- .../java/org/lflang/analyses/uclid/UclidGenerator.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java index 8292a8f7ce..2a07c3cba0 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java @@ -844,7 +844,8 @@ protected void generateReactorSemantics() { String.join( "\n", "// Only one reaction gets triggered at a time.", - "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END_TRACE) ==> (", + "axiom(finite_forall (i : integer) in indices :: (i >= START && i <= END_TRACE) ==>" + + " (", " isNULL(i)")); code.indent(); for (int i = 0; i < this.reactionInstances.size(); i++) { @@ -1009,8 +1010,8 @@ protected void generateActionAxioms() { String.join( "\n", "// " + comment, - "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END_TRACE) ==> (" - + " false", + "axiom(finite_forall (i : integer) in indices :: (i > START && i <= END_TRACE) ==>" + + " ( false", triggerStr, "));")); From b19e92eb0ce29dad34b504a088cc9662f5e4610f Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 6 Jul 2023 11:55:54 -0700 Subject: [PATCH 244/516] Only let zero-delay connections affect MLAA. --- .../main/java/org/lflang/ast/ASTUtils.java | 16 ++++++++ .../federated/extensions/CExtension.java | 4 +- .../federated/extensions/CExtensionUtils.java | 37 +++++++------------ .../federated/generator/FedASTUtils.java | 3 +- .../federated/generator/FederateInstance.java | 6 +++ core/src/main/resources/lib/c/reactor-c | 2 +- 6 files changed, 41 insertions(+), 27 deletions(-) diff --git a/core/src/main/java/org/lflang/ast/ASTUtils.java b/core/src/main/java/org/lflang/ast/ASTUtils.java index 60b5538372..9904920209 100644 --- a/core/src/main/java/org/lflang/ast/ASTUtils.java +++ b/core/src/main/java/org/lflang/ast/ASTUtils.java @@ -1392,6 +1392,22 @@ public static Integer initialValueInt(Parameter parameter, List i return result; } + public static Long getDelay(Expression delay) { + Long ret = null; + if (delay != null) { + TimeValue tv; + if (delay instanceof ParameterReference) { + // The parameter has to be parameter of the main reactor. + // And that value has to be a Time. + tv = ASTUtils.getDefaultAsTimeValue(((ParameterReference) delay).getParameter()); + } else { + tv = ASTUtils.getLiteralTimeValue(delay); + } + ret = tv == null ? null : tv.toNanoSeconds(); + } + return ret; + } + /** * 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 diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index 3c3c671641..5aad1f90da 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -533,8 +533,10 @@ protected String makePreamble( """ lf_action_base_t* _lf_action_table[%1$s]; size_t _lf_action_table_size = %1$s; + lf_action_base_t* _lf_zero_delay_action_table[%2$s]; + size_t _lf_zero_delay_action_table_size = %2$s; """ - .formatted(numOfNetworkActions)); + .formatted(numOfNetworkActions, federate.zeroDelayNetworkMessageActions.size())); int numOfNetworkReactions = federate.networkReceiverReactions.size(); code.pr( diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java b/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java index 3a010d48ea..6ffda6497f 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java @@ -11,7 +11,6 @@ import org.lflang.TargetConfig.ClockSyncOptions; import org.lflang.TargetProperty; import org.lflang.TargetProperty.ClockSyncMode; -import org.lflang.TimeValue; import org.lflang.ast.ASTUtils; import org.lflang.federated.generator.FedFileConfig; import org.lflang.federated.generator.FederateInstance; @@ -46,27 +45,28 @@ public static String initializeTriggersForNetworkActions( FederateInstance federate, ReactorInstance main) { CodeBuilder code = new CodeBuilder(); if (federate.networkMessageActions.size() > 0) { - // Create a static array of trigger_t pointers. - // networkMessageActions is a list of Actions, but we - // need a list of trigger struct names for ActionInstances. - // There should be exactly one ActionInstance in the - // main reactor for each Action. - var triggers = new LinkedList(); + var actionTableCount = 0; + var zeroDelayActionTableCount = 0; for (int i = 0; i < federate.networkMessageActions.size(); ++i) { // Find the corresponding ActionInstance. Action action = federate.networkMessageActions.get(i); var reactor = main.lookupReactorInstance(federate.networkReceiverInstantiations.get(i)); var actionInstance = reactor.lookupActionInstance(action); - triggers.add(CUtil.actionRef(actionInstance, null)); - } - var actionTableCount = 0; - for (String trigger : triggers) { + var trigger = CUtil.actionRef(actionInstance, null); code.pr( "_lf_action_table[" + (actionTableCount++) + "] = (lf_action_base_t*)&" + trigger + "; \\"); + if (federate.zeroDelayNetworkMessageActions.contains(action)) { + code.pr( + "_lf_zero_delay_action_table[" + + (zeroDelayActionTableCount++) + + "] = (lf_action_base_t*)&" + + trigger + + "; \\"); + } } } return code.getCode(); @@ -160,19 +160,8 @@ public static String createPortStatusFieldForInput(Input input) { * @param delay The delay associated with a connection. */ public static String getNetworkDelayLiteral(Expression delay) { - String additionalDelayString = "NEVER"; - if (delay != null) { - TimeValue tv; - if (delay instanceof ParameterReference) { - // The parameter has to be parameter of the main reactor. - // And that value has to be a Time. - tv = ASTUtils.getDefaultAsTimeValue(((ParameterReference) delay).getParameter()); - } else { - tv = ASTUtils.getLiteralTimeValue(delay); - } - additionalDelayString = Long.toString(tv.toNanoSeconds()); - } - return additionalDelayString; + var d = ASTUtils.getDelay(delay); + return d == null ? "NEVER" : Long.toString(d); } static boolean isSharedPtrType(InferredType type, CTypes types) { diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 8f0579cb77..099d770fb1 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -261,6 +261,7 @@ private static void addNetworkReceiverReactor( // Keep track of this action in the destination federate. connection.dstFederate.networkMessageActions.add(networkAction); + if (connection.getDefinition().getDelay() == null) connection.dstFederate.zeroDelayNetworkMessageActions.add(networkAction); TimeValue maxSTP = findMaxSTP(connection, coordination); @@ -703,7 +704,7 @@ private static Reaction getInitializationReaction() { initializationReaction.getTriggers().add(startup); var code = LfFactory.eINSTANCE.createCode(); code.setBody( - """ + """ extern reaction_t* port_absent_reaction[]; void enqueue_network_output_control_reactions(); LF_PRINT_DEBUG("Adding network output control reaction to table."); diff --git a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java index 0e0239283b..0f7069ed96 100644 --- a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java +++ b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java @@ -185,6 +185,12 @@ public Instantiation getInstantiation() { */ public List networkMessageActions = new ArrayList<>(); + /** + * List of networkMessage actions corresponding to zero-delay connections. This should be a subset + * of the networkMessageActions. + */ + public List zeroDelayNetworkMessageActions = 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. diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 40d24c320e..a97238470f 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 40d24c320eb53520372653a1cd7fa338fc7237e7 +Subproject commit a97238470fcf559d34794856abd36e96b8344e94 From 1d14809c0aaad077550c515719ffb334d5ee6831 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 10 Jul 2023 18:29:03 -0700 Subject: [PATCH 245/516] Simple cleanups in the federated package. There is also a change to ensure that the reactors in the generated LF code correspond to distinct reactor classes. If I am not mistaken this fixes a scalability issue. --- .../federated/extensions/CExtension.java | 27 +---- .../federated/extensions/CExtensionUtils.java | 14 +-- .../extensions/FedTargetExtension.java | 38 ++----- .../extensions/FedTargetExtensionFactory.java | 17 +-- .../federated/extensions/PythonExtension.java | 60 ++++------ .../federated/extensions/TSExtension.java | 14 +-- .../generator/FedConnectionInstance.java | 8 -- .../federated/generator/FedEmitter.java | 6 +- .../federated/generator/FedGenerator.java | 17 ++- .../federated/generator/FedImportEmitter.java | 2 +- .../federated/generator/FedMainEmitter.java | 29 +---- .../generator/FedReactorEmitter.java | 6 +- .../federated/generator/FederateInstance.java | 3 - .../federated/launcher/CBuildConfig.java | 2 +- .../launcher/FedLauncherGenerator.java | 88 +++++++-------- .../FedNativePythonSerialization.java | 28 ++--- .../FedROS2CPPSerialization.java | 104 +++++++++--------- 17 files changed, 170 insertions(+), 293 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index 5aad1f90da..57872c13a0 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -396,31 +396,6 @@ protected void serializeAndSend( } } - /** - * Generate code for the body of a reaction that decides whether the trigger for the given port is - * going to be present or absent for the current logical time. This reaction is put just before - * the first reaction that is triggered by the network input port "port" or has it in its sources. - * If there are only connections to contained reactors, in the top-level reactor. - * - * @param receivingPortID The port to generate the control reaction for - * @param maxSTP The maximum value of STP is assigned to reactions (if any) that have port as - * their trigger or source - */ - public String generateNetworkInputControlReactionBody( - int receivingPortID, TimeValue maxSTP, CoordinationType coordination) { - // Store the code - var result = new CodeBuilder(); - result.pr("interval_t max_STP = 0LL;"); - - // Find the maximum STP for decentralized coordination - if (coordination == CoordinationType.DECENTRALIZED) { - 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);"); - return result.toString(); - } - /** * Generate code for the body of a reaction that sends a port status message for the given port if * it is absent. @@ -577,7 +552,7 @@ protected String makePreamble( /** Generate preamble code needed for enabled serializers of the federate. */ protected String generateSerializationIncludes( FederateInstance federate, FedFileConfig fileConfig) { - return CExtensionUtils.generateSerializationIncludes(federate, fileConfig); + return CExtensionUtils.generateSerializationIncludes(federate); } /** diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java b/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java index 6ffda6497f..c7f887f0c8 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java +++ b/core/src/main/java/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.LinkedList; import java.util.List; import java.util.regex.Pattern; import org.lflang.InferredType; @@ -61,11 +60,11 @@ public static String initializeTriggersForNetworkActions( + "; \\"); if (federate.zeroDelayNetworkMessageActions.contains(action)) { code.pr( - "_lf_zero_delay_action_table[" - + (zeroDelayActionTableCount++) - + "] = (lf_action_base_t*)&" - + trigger - + "; \\"); + "_lf_zero_delay_action_table[" + + (zeroDelayActionTableCount++) + + "] = (lf_action_base_t*)&" + + trigger + + "; \\"); } } } @@ -477,8 +476,7 @@ public static String surroundWithIfFederatedDecentralized(String code) { } /** Generate preamble code needed for enabled serializers of the federate. */ - public static String generateSerializationIncludes( - FederateInstance federate, FedFileConfig fileConfig) { + public static String generateSerializationIncludes(FederateInstance federate) { CodeBuilder code = new CodeBuilder(); for (SupportedSerializers serializer : federate.enabledSerializers) { switch (serializer) { diff --git a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java index 17ac198781..8957dfe0f4 100644 --- a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java @@ -41,10 +41,9 @@ void initializeTargetConfig( * @param action The action. * @param sendingPort The output port providing the data to send. * @param receivingPort The ID of the destination port. - * @param connection FIXME - * @param type FIXME + * @param connection The federated connection being lowered. + * @param type The type of the data being sent over the connection. * @param coordinationType The coordination type - * @param errorReporter */ String generateNetworkReceiverBody( Action action, @@ -61,10 +60,9 @@ String generateNetworkReceiverBody( * * @param sendingPort The output port providing the data to send. * @param receivingPort The variable reference to the destination port. - * @param connection - * @param type - * @param coordinationType - * @param errorReporter FIXME + * @param connection The federated connection being lowered. + * @param type The type of the data being sent over the connection. + * @param coordinationType Whether the federated program is centralized or decentralized. */ String generateNetworkSenderBody( VarRef sendingPort, @@ -74,26 +72,12 @@ String generateNetworkSenderBody( CoordinationType coordinationType, ErrorReporter errorReporter); - /** - * Generate code for the body of a reaction that decides whether the trigger for the given port is - * going to be present or absent for the current logical time. This reaction is put just before - * the first reaction that is triggered by the network input port "port" or has it in its sources. - * If there are only connections to contained reactors, in the top-level reactor. - * - * @param receivingPortID The port to generate the control reaction for - * @param maxSTP The maximum value of STP is assigned to reactions (if any) that have port as - * their trigger or source - * @param coordination FIXME - */ - String generateNetworkInputControlReactionBody( - int receivingPortID, TimeValue maxSTP, CoordinationType coordination); - /** * Generate code for the body of a reaction that sends a port status message for the given port if * it is absent. * - * @oaram srcOutputPort FIXME - * @param connection FIXME + * @param srcOutputPort A reference to the output port of the federate instance. + * @param connection The federated connection being lowered. */ String generateNetworkOutputControlReactionBody( VarRef srcOutputPort, FedConnectionInstance connection); @@ -109,12 +93,10 @@ default void annotateReaction(Reaction reaction) {} String getNetworkBufferType(); /** - * Add necessary preamble to the source to set up federated execution. + * Add preamble to the source to set up federated execution. * - * @param federate - * @param rtiConfig - * @param errorReporter - * @return + * @param federate The federate to which the generated setup code will correspond. + * @param rtiConfig The settings of the RTI. */ String generatePreamble( FederateInstance federate, diff --git a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtensionFactory.java b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtensionFactory.java index ca16d1d445..612aa02f7c 100644 --- a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtensionFactory.java +++ b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtensionFactory.java @@ -11,16 +11,11 @@ public class FedTargetExtensionFactory { /** Given a target, return the appropriate extension. */ public static FedTargetExtension getExtension(Target target) { - switch (target) { - case CCPP: - case C: - return new CExtension(); - case Python: - return new PythonExtension(); - case TS: - return new TSExtension(); - default: - throw new RuntimeException("Target not supported"); - } + return switch (target) { + case CCPP, C -> new CExtension(); + case Python -> new PythonExtension(); + case TS -> new TSExtension(); + default -> throw new RuntimeException("Target not supported"); + }; } } diff --git a/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java b/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java index 9cca139255..b119716c8d 100644 --- a/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java @@ -120,24 +120,16 @@ protected void deserialize( String receiveRef, CodeBuilder result, ErrorReporter errorReporter) { - String value = ""; + String value; switch (connection.getSerializer()) { - case NATIVE: - { - value = action.getName(); - FedNativePythonSerialization pickler = new FedNativePythonSerialization(); - result.pr(pickler.generateNetworkDeserializerCode(value, null)); - result.pr("lf_set(" + receiveRef + ", " + FedSerialization.deserializedVarName + ");\n"); - break; - } - case PROTO: - { - throw new UnsupportedOperationException("Protobuf serialization is not supported yet."); - } - case ROS2: - { - throw new UnsupportedOperationException("ROS2 serialization is not supported yet."); - } + case NATIVE -> { + value = action.getName(); + FedNativePythonSerialization pickler = new FedNativePythonSerialization(); + result.pr(pickler.generateNetworkDeserializerCode(value, null)); + result.pr("lf_set(" + receiveRef + ", " + FedSerialization.deserializedVarName + ");\n"); + } + case PROTO -> throw new UnsupportedOperationException("Protobuf serialization is not supported yet."); + case ROS2 -> throw new UnsupportedOperationException("ROS2 serialization is not supported yet."); } } @@ -150,28 +142,20 @@ protected void serializeAndSend( String sendingFunction, String commonArgs, ErrorReporter errorReporter) { - String lengthExpression = ""; - String pointerExpression = ""; + String lengthExpression; + String pointerExpression; switch (connection.getSerializer()) { - case NATIVE: - { - var variableToSerialize = sendRef + "->value"; - FedNativePythonSerialization pickler = new FedNativePythonSerialization(); - lengthExpression = pickler.serializedBufferLength(); - pointerExpression = pickler.seializedBufferVar(); - result.pr(pickler.generateNetworkSerializerCode(variableToSerialize, null)); - result.pr("size_t message_length = " + lengthExpression + ";"); - result.pr(sendingFunction + "(" + commonArgs + ", " + pointerExpression + ");\n"); - break; - } - case PROTO: - { - throw new UnsupportedOperationException("Protobuf serialization is not supported yet."); - } - case ROS2: - { - throw new UnsupportedOperationException("ROS2 serialization is not supported yet."); - } + case NATIVE -> { + var variableToSerialize = sendRef + "->value"; + FedNativePythonSerialization pickler = new FedNativePythonSerialization(); + lengthExpression = pickler.serializedBufferLength(); + pointerExpression = pickler.seializedBufferVar(); + result.pr(pickler.generateNetworkSerializerCode(variableToSerialize, null)); + result.pr("size_t message_length = " + lengthExpression + ";"); + result.pr(sendingFunction + "(" + commonArgs + ", " + pointerExpression + ");\n"); + } + case PROTO -> throw new UnsupportedOperationException("Protobuf serialization is not supported yet."); + case ROS2 -> throw new UnsupportedOperationException("ROS2 serialization is not supported yet."); } } diff --git a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java index 14df96afb5..396c83863d 100644 --- a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java @@ -33,8 +33,7 @@ public void initializeTargetConfig( FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter, - RtiConfig rtiConfig) - throws IOException {} + RtiConfig rtiConfig) {} @Override public String generateNetworkReceiverBody( @@ -83,12 +82,6 @@ private String getNetworkDelayLiteral(Expression e) { return cLiteral.equals("NEVER") ? "0" : cLiteral; } - @Override - public String generateNetworkInputControlReactionBody( - int receivingPortID, TimeValue maxSTP, CoordinationType coordination) { - return "// TODO(hokeun): Figure out what to do for generateNetworkInputControlReactionBody"; - } - @Override public String generateNetworkOutputControlReactionBody( VarRef srcOutputPort, FedConnectionInstance connection) { @@ -100,11 +93,6 @@ public String getNetworkBufferType() { return ""; } - /** - * Add necessary preamble to the source to set up federated execution. - * - * @return - */ @Override public String generatePreamble( FederateInstance federate, diff --git a/core/src/main/java/org/lflang/federated/generator/FedConnectionInstance.java b/core/src/main/java/org/lflang/federated/generator/FedConnectionInstance.java index d08aa5f850..50bfd277af 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedConnectionInstance.java +++ b/core/src/main/java/org/lflang/federated/generator/FedConnectionInstance.java @@ -58,14 +58,6 @@ public FedConnectionInstance( this.dstFederate.connections.add(this); } - public SendRange getSrcRange() { - return srcRange; - } - - public RuntimeRange getDstRange() { - return dstRange; - } - public int getSrcChannel() { return srcChannel; } diff --git a/core/src/main/java/org/lflang/federated/generator/FedEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedEmitter.java index bdf912d0c8..03c2bb638b 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedEmitter.java @@ -30,11 +30,7 @@ public FedEmitter( this.rtiConfig = rtiConfig; } - /** - * Generate a .lf file for federate {@code federate}. - * - * @throws IOException - */ + /** Generate a .lf file for federate {@code federate}. */ Map generateFederate( LFGeneratorContext context, FederateInstance federate, int numOfFederates) throws IOException { diff --git a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java index 75bb9f13e3..56981aad1f 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java +++ b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java @@ -98,8 +98,6 @@ public class FedGenerator { /** * 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(); @@ -114,7 +112,6 @@ public FedGenerator(LFGeneratorContext context) { * @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; @@ -332,7 +329,9 @@ public TargetConfig getTargetConfig() { // Wait for all compile threads to finish (NOTE: Can block forever) try { - compileThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); + if (!compileThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)) { + context.getErrorReporter().reportError("Timed out while compiling."); + } } catch (Exception e) { context.getErrorReporter().reportError("Failure during code generation: " + e.getMessage()); e.printStackTrace(); @@ -362,7 +361,7 @@ private void setFederationRTIProperties(LFGeneratorContext context) { String rtiAddr = context.getArgs().getProperty("rti"); Pattern pattern = Pattern.compile( - "([a-zA-Z0-9]+@)?([a-zA-Z0-9]+\\.?[a-z]{2,}|[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+):?([0-9]+)?"); + "([a-zA-Z\\d]+@)?([a-zA-Z\\d]+\\.?[a-z]{2,}|\\d+\\.\\d+\\.\\d+\\.\\d+):?(\\d+)?"); Matcher matcher = pattern.matcher(rtiAddr); if (!matcher.find()) { @@ -478,7 +477,7 @@ private List getFederateInstances( * data. * * @param federation Reactor class of the federation. - * @param resource + * @param resource The file system resource from which the original program is derived. */ private void replaceFederateConnectionsWithProxies(Reactor federation, Resource resource) { // Each connection in the AST may represent more than one connection between @@ -575,7 +574,7 @@ private static VarRef varRefOf(Instantiation container, String name) { * Replace the connections from the specified output port. * * @param output The output port instance. - * @param resource + * @param resource The file system resource from which the original program is derived. */ private void replaceConnectionFromOutputPort(PortInstance output, Resource resource) { // Iterate through ranges of the output port @@ -599,7 +598,7 @@ private void replaceConnectionFromOutputPort(PortInstance output, Resource resou * * @param srcRange A range of an output port that sources data for this connection. * @param dstRange A range of input ports that receive the data. - * @param resource + * @param resource The file system resource from which the original program is derived. */ private void replaceOneToManyConnection( SendRange srcRange, RuntimeRange dstRange, Resource resource) { @@ -650,7 +649,7 @@ private void replaceOneToManyConnection( * Replace a one-to-one federated connection with proxies. * * @param connection A connection between two federates. - * @param resource + * @param resource The file system resource from which the original program is derived. */ private void replaceFedConnection(FedConnectionInstance connection, Resource resource) { if (!connection.getDefinition().isPhysical() diff --git a/core/src/main/java/org/lflang/federated/generator/FedImportEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedImportEmitter.java index baf13d6a0a..be7c203a0f 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedImportEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedImportEmitter.java @@ -17,7 +17,7 @@ */ public class FedImportEmitter { - private static Set visitedImports = new HashSet<>(); + private static final Set visitedImports = new HashSet<>(); /** Generate import statements for {@code federate}. */ String generateImports(FederateInstance federate, FedFileConfig fileConfig) { diff --git a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java index 6c097f790c..a9999fad7c 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java @@ -6,10 +6,8 @@ import org.lflang.ErrorReporter; import org.lflang.ast.ASTUtils; import org.lflang.ast.FormattingUtils; -import org.lflang.generator.PortInstance; import org.lflang.lf.Reactor; import org.lflang.lf.Variable; -import org.lflang.util.Pair; /** Helper class to generate a main reactor */ public class FedMainEmitter { @@ -17,7 +15,6 @@ public class FedMainEmitter { /** * Generate a main reactor for {@code federate}. * - * @param federate * @param originalMainReactor The original main reactor. * @param errorReporter Used to report errors. * @return The main reactor. @@ -27,7 +24,7 @@ String generateMainReactor( // FIXME: Handle modes at the top-level if (!ASTUtils.allModes(originalMainReactor).isEmpty()) { errorReporter.reportError( - ASTUtils.allModes(originalMainReactor).stream().findFirst().get(), + ASTUtils.allModes(originalMainReactor).stream().findFirst().orElseThrow(), "Modes at the top level are not supported under federated execution."); } var renderer = FormattingUtils.renderer(federate.targetConfig.target); @@ -75,24 +72,6 @@ String generateMainReactor( "}"); } - private static String getDependencyList( - FederateInstance federate, Pair p) { - // StringBuilder lst = new StringBuilder(); - var inputPort = p.getFirst(); - var outputPort = p.getSecond(); - var inputPortInstance = federate.networkPortToInstantiation.getOrDefault(inputPort, null); - var outputPortInstance = federate.networkPortToInstantiation.getOrDefault(outputPort, null); - // var outputPortControlReaction = federate.networkPortToInstantiation.getOrDefault(outputPort, - // null); - if (inputPortInstance == null) return ""; - // System.out.println("IP: " + inputPortReaction.getCode()); - if (outputPortInstance != null) { - // System.out.println("OP: " + outputPortReaction.toString()); - return inputPortInstance.getName() + "," + outputPortInstance.getName(); - } - return ""; - } - /** * Generate the signature of the main reactor. * @@ -114,12 +93,6 @@ private CharSequence generateMainSignature( .map(Variable::getName) .collect(Collectors.joining(",")); - // List vals = new ArrayList<>(); - // for (var pair : federate.networkReactionDependencyPairs) { - // vals.add(getDependencyList(federate, pair)); - // } - - // String intraDependencies = String.join(";", vals); return """ @_fed_config(network_message_actions="%s") main reactor %s { diff --git a/core/src/main/java/org/lflang/federated/generator/FedReactorEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedReactorEmitter.java index 69a6342486..8865f5709e 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedReactorEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedReactorEmitter.java @@ -8,13 +8,11 @@ public class FedReactorEmitter { public FedReactorEmitter() {} - /** - * @param federate - * @return - */ + /** Return textual representations of all reactor classes belonging to {@code federate}. */ String generateReactorDefinitions(FederateInstance federate) { return ((Model) federate.instantiation.eContainer().eContainer()) .getReactors().stream() + .distinct() .filter(federate::contains) .map(FormattingUtils.renderer(federate.targetConfig.target)) .collect(Collectors.joining("\n")); diff --git a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java index 0f7069ed96..16ddccd75d 100644 --- a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java +++ b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java @@ -127,9 +127,6 @@ public FederateInstance( */ public int bankWidth; - /** 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"; diff --git a/core/src/main/java/org/lflang/federated/launcher/CBuildConfig.java b/core/src/main/java/org/lflang/federated/launcher/CBuildConfig.java index d77ea3988c..f012403f6c 100644 --- a/core/src/main/java/org/lflang/federated/launcher/CBuildConfig.java +++ b/core/src/main/java/org/lflang/federated/launcher/CBuildConfig.java @@ -48,7 +48,7 @@ public CBuildConfig( @Override public String compileCommand() { - String commandToReturn = ""; + 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 = diff --git a/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java b/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java index c06ced57f3..ba5b4479c7 100644 --- a/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java +++ b/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java @@ -110,7 +110,7 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { // var outPath = binGenPath StringBuilder shCode = new StringBuilder(); StringBuilder distCode = new StringBuilder(); - shCode.append(getSetupCode() + "\n"); + shCode.append(getSetupCode()).append("\n"); String distHeader = getDistHeader(); String host = rtiConfig.getHost(); String target = host; @@ -120,18 +120,18 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { target = user + "@" + host; } - shCode.append("#### Host is " + host); + shCode.append("#### Host is ").append(host); // Launch the RTI in the foreground. if (host.equals("localhost") || host.equals("0.0.0.0")) { // FIXME: the paths below will not work on Windows - shCode.append(getLaunchCode(getRtiCommand(federates, false)) + "\n"); + shCode.append(getLaunchCode(getRtiCommand(federates, false))).append("\n"); } else { // Start the RTI on the remote machine. // FIXME: Should $FEDERATION_ID be used to ensure unique directories, executables, on the // remote host? // Copy the source code onto the remote machine and compile it there. - if (distCode.length() == 0) distCode.append(distHeader + "\n"); + if (distCode.length() == 0) distCode.append(distHeader).append("\n"); String logFileName = String.format("log/%s_RTI.log", fileConfig.name); @@ -149,8 +149,7 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { // The sleep at the end prevents screen from exiting before outgoing messages from // the federate have had time to go out to the RTI through the socket. - shCode.append( - getRemoteLaunchCode(host, target, logFileName, getRtiCommand(federates, true)) + "\n"); + shCode.append(getRemoteLaunchCode(host, target, logFileName, getRtiCommand(federates, true))).append("\n"); } // Index used for storing pids of federates @@ -160,32 +159,28 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { if (federate.isRemote) { Path fedRelSrcGenPath = fileConfig.getOutPath().relativize(fileConfig.getSrcGenPath()).resolve(federate.name); - if (distCode.length() == 0) distCode.append(distHeader + "\n"); + if (distCode.length() == 0) distCode.append(distHeader).append("\n"); String logFileName = String.format("log/%s_%s.log", fileConfig.name, federate.name); String compileCommand = buildConfig.compileCommand(); // FIXME: Should $FEDERATION_ID be used to ensure unique directories, executables, on the // remote host? - distCode.append( - getDistCode( - rtiConfig.getDirectory(), - federate, - fedRelSrcGenPath, - logFileName, - fileConfig.getSrcGenPath(), - compileCommand) - + "\n"); + distCode.append(getDistCode( + rtiConfig.getDirectory(), + federate, + fedRelSrcGenPath, + logFileName, + fileConfig.getSrcGenPath(), + compileCommand)).append("\n"); String executeCommand = buildConfig.remoteExecuteCommand(); - shCode.append( - getFedRemoteLaunchCode( - federate, - rtiConfig.getDirectory(), - logFileName, - executeCommand, - federateIndex++) - + "\n"); + shCode.append(getFedRemoteLaunchCode( + federate, + rtiConfig.getDirectory(), + logFileName, + executeCommand, + federateIndex++)).append("\n"); } else { String executeCommand = buildConfig.localExecuteCommand(); - shCode.append(getFedLocalLaunchCode(federate, executeCommand, federateIndex++) + "\n"); + shCode.append(getFedLocalLaunchCode(federate, executeCommand, federateIndex++)).append("\n"); } } if (host.equals("localhost") || host.equals("0.0.0.0")) { @@ -195,19 +190,17 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { shCode.append("fg %1" + "\n"); } // Wait for launched processes to finish - shCode.append( - String.join( - "\n", - "echo \"RTI has exited. Wait for federates to exit.\"", - "# Wait for launched processes to finish.", - "# The errors are handled separately via trap.", - "for pid in \"${pids[@]}\"", - "do", - " wait $pid || exit $?", - "done", - "echo \"All done.\"", - "EXITED_SUCCESSFULLY=true") - + "\n"); + shCode.append(String.join( + "\n", + "echo \"RTI has exited. Wait for federates to exit.\"", + "# Wait for launched processes to finish.", + "# The errors are handled separately via trap.", + "for pid in \"${pids[@]}\"", + "do", + " wait $pid || exit $?", + "done", + "echo \"All done.\"", + "EXITED_SUCCESSFULLY=true")).append("\n"); // Create bin directory for the script. if (!Files.exists(fileConfig.binPath)) { @@ -225,7 +218,7 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { // Delete file previously produced, if any. File file = fileConfig.binPath.resolve(fileConfig.name).toFile(); if (file.exists()) { - file.delete(); + if (!file.delete()) errorReporter.reportError("Failed to delete existing federated launch script \"" + file + "\""); } FileOutputStream fOut = null; @@ -234,11 +227,13 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { } 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 (fOut != null) { + try { + fOut.write(shCode.toString().getBytes()); + fOut.close(); + } catch (IOException e) { + errorReporter.reportError("Unable to write to file: " + file); + } } if (!file.setExecutable(true, false)) { @@ -249,7 +244,7 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { // Delete the file even if it does not get generated. file = fileConfig.binPath.resolve(fileConfig.name + "_distribute.sh").toFile(); if (file.exists()) { - file.delete(); + if (!file.delete()) errorReporter.reportError("Failed to delete existing federated distributor script \"" + file + "\""); } if (distCode.length() > 0) { try { @@ -498,8 +493,7 @@ private String getFedLocalLaunchCode( * * @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 + * @param errorReporter An error reporter to report problems */ private BuildConfig getBuildConfig( FederateInstance federate, FedFileConfig fileConfig, ErrorReporter errorReporter) { diff --git a/core/src/main/java/org/lflang/federated/serialization/FedNativePythonSerialization.java b/core/src/main/java/org/lflang/federated/serialization/FedNativePythonSerialization.java index 3947b1bd92..3f34673900 100644 --- a/core/src/main/java/org/lflang/federated/serialization/FedNativePythonSerialization.java +++ b/core/src/main/java/org/lflang/federated/serialization/FedNativePythonSerialization.java @@ -64,10 +64,11 @@ public StringBuilder generateNetworkSerializerCode(String varName, String origin "if (global_pickler == NULL) lf_print_error_and_exit(\"The pickle module is not" + " loaded.\");\n"); // Define the serialized PyObject - serializerCode.append( - "PyObject* serialized_pyobject = PyObject_CallMethod(global_pickler, \"dumps\", \"O\", " - + varName - + ");\n"); + serializerCode + .append( + "PyObject* serialized_pyobject = PyObject_CallMethod(global_pickler, \"dumps\", \"O\", ") + .append(varName) + .append(");\n"); // Error check serializerCode.append("if (serialized_pyobject == NULL) {\n"); @@ -98,13 +99,12 @@ public StringBuilder generateNetworkDeserializerCode(String varName, String targ StringBuilder deserializerCode = new StringBuilder(); // Convert the network message to a Python ByteArray - deserializerCode.append( - "PyObject* message_byte_array = " - + "PyBytes_FromStringAndSize((char*)" - + varName - + "->token->value, " - + varName - + "->token->length);\n"); + deserializerCode + .append("PyObject* message_byte_array = " + "PyBytes_FromStringAndSize((char*)") + .append(varName) + .append("->token->value, ") + .append(varName) + .append("->token->length);\n"); // Deserialize using Pickle deserializerCode.append("Py_XINCREF(message_byte_array);\n"); deserializerCode.append( @@ -118,7 +118,7 @@ public StringBuilder generateNetworkDeserializerCode(String varName, String targ " lf_print_error_and_exit(\"Could not deserialize " + deserializedVarName + ".\");\n"); deserializerCode.append("}\n"); - // Decrment the reference count + // Decrement the reference count deserializerCode.append("Py_XDECREF(message_byte_array);\n"); return deserializerCode; @@ -126,11 +126,11 @@ public StringBuilder generateNetworkDeserializerCode(String varName, String targ @Override public StringBuilder generatePreambleForSupport() { - return new StringBuilder(""); + return new StringBuilder(); } @Override public StringBuilder generateCompilerExtensionForSupport() { - return new StringBuilder(""); + return new StringBuilder(); } } diff --git a/core/src/main/java/org/lflang/federated/serialization/FedROS2CPPSerialization.java b/core/src/main/java/org/lflang/federated/serialization/FedROS2CPPSerialization.java index 1384d11517..4bec959a70 100644 --- a/core/src/main/java/org/lflang/federated/serialization/FedROS2CPPSerialization.java +++ b/core/src/main/java/org/lflang/federated/serialization/FedROS2CPPSerialization.java @@ -90,14 +90,14 @@ public StringBuilder generateNetworkSerializerCode(String varName, String origin serializerCode.append("rclcpp::SerializedMessage " + serializedVarName + "(0u);\n"); // Use the port type verbatim here, which can result // in compile error if it is not a valid ROS type - serializerCode.append("using MessageT = " + originalType + ";\n"); + serializerCode.append("using MessageT = ").append(originalType).append(";\n"); serializerCode.append("static rclcpp::Serialization _lf_serializer;\n"); - serializerCode.append( - "_lf_serializer.serialize_message(&" - + varName - + "->value , &" - + serializedVarName - + ");\n"); + serializerCode + .append("_lf_serializer.serialize_message(&") + .append(varName) + .append("->value , &") + .append(serializedVarName) + .append(");\n"); return serializerCode; } @@ -115,22 +115,22 @@ public StringBuilder generateNetworkSerializerCode( serializerCode.append("rclcpp::SerializedMessage " + serializedVarName + "(0u);\n"); // Use the port type verbatim here, which can result // in compile error if it is not a valid ROS type - serializerCode.append("using MessageT = " + originalType + ";\n"); + serializerCode.append("using MessageT = ").append(originalType).append(";\n"); serializerCode.append("static rclcpp::Serialization _lf_serializer;\n"); if (isSharedPtrType) { - serializerCode.append( - "_lf_serializer.serialize_message(" - + varName - + "->value.get() , &" - + serializedVarName - + ");\n"); + serializerCode + .append("_lf_serializer.serialize_message(") + .append(varName) + .append("->value.get() , &") + .append(serializedVarName) + .append(");\n"); } else { - serializerCode.append( - "_lf_serializer.serialize_message(&" - + varName - + "->value , &" - + serializedVarName - + ");\n"); + serializerCode + .append("_lf_serializer.serialize_message(&") + .append(varName) + .append("->value , &") + .append(serializedVarName) + .append(");\n"); } return serializerCode; @@ -149,25 +149,28 @@ public StringBuilder generateNetworkSerializerCode( public StringBuilder generateNetworkDeserializerCode(String varName, String targetType) { StringBuilder deserializerCode = new StringBuilder(); - deserializerCode.append( - "auto message = std::make_unique( rcl_serialized_message_t{\n" - + " .buffer = (uint8_t*)" - + varName - + ".tmplt.token->value,\n" - + " .buffer_length = " - + varName - + ".tmplt.token->length,\n" - + " .buffer_capacity = " - + varName - + ".tmplt.token->length,\n" - + " .allocator = rcl_get_default_allocator()\n" - + "});\n"); + deserializerCode + .append( + "auto message = std::make_unique( rcl_serialized_message_t{\n" + + " .buffer = (uint8_t*)") + .append(varName) + .append(".tmplt.token->value,\n") + .append(" .buffer_length = ") + .append(varName) + .append(".tmplt.token->length,\n") + .append(" .buffer_capacity = ") + .append(varName) + .append(".tmplt.token->length,\n") + .append(" .allocator = rcl_get_default_allocator()\n") + .append("});\n"); deserializerCode.append( "auto msg = std::make_unique(std::move(*message.get()));\n"); - deserializerCode.append(varName + ".tmplt.token->value = NULL; // Manually move the data\n"); + deserializerCode + .append(varName) + .append(".tmplt.token->value = NULL; // Manually move the data\n"); // Use the port type verbatim here, which can result // in compile error if it is not a valid ROS type - deserializerCode.append("using MessageT = " + targetType + ";\n"); + deserializerCode.append("using MessageT = ").append(targetType).append(";\n"); deserializerCode.append( "MessageT " + deserializedVarName @@ -189,10 +192,12 @@ public StringBuilder generatePreambleForSupport() { StringBuilder preamble = new StringBuilder(); preamble.append( - "#include \"rcutils/allocator.h\"\n" - + "#include \"rclcpp/rclcpp.hpp\"\n" - + "#include \"rclcpp/serialization.hpp\"\n" - + "#include \"rclcpp/serialized_message.hpp\"\n"); + """ + #include "rcutils/allocator.h" + #include "rclcpp/rclcpp.hpp" + #include "rclcpp/serialization.hpp" + #include "rclcpp/serialized_message.hpp" + """); return preamble; } @@ -206,16 +211,17 @@ public StringBuilder generateCompilerExtensionForSupport() { StringBuilder cMakeExtension = new StringBuilder(); cMakeExtension.append( - "enable_language(CXX)\n" - + "set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wno-write-strings -O2\")\n" - + "\n" - + "find_package(ament_cmake REQUIRED)\n" - + "find_package(rclcpp REQUIRED)\n" - + "find_package(rclcpp_components REQUIRED)\n" - + "find_package(rcutils)\n" - + "find_package(rmw REQUIRED)\n" - + "\n" - + "ament_target_dependencies( ${LF_MAIN_TARGET} rclcpp rmw)"); + """ + enable_language(CXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-write-strings -O2") + + find_package(ament_cmake REQUIRED) + find_package(rclcpp REQUIRED) + find_package(rclcpp_components REQUIRED) + find_package(rcutils) + find_package(rmw REQUIRED) + + ament_target_dependencies( ${LF_MAIN_TARGET} rclcpp rmw)"""); return cMakeExtension; } From e6f81b4355c71df4b7a01cf532cf0c2fe7aafade Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Tue, 11 Jul 2023 11:26:58 -0700 Subject: [PATCH 246/516] change to rp2040 target --- core/src/main/java/org/lflang/TargetProperty.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/TargetProperty.java b/core/src/main/java/org/lflang/TargetProperty.java index 17d29410a6..3c43ac4665 100644 --- a/core/src/main/java/org/lflang/TargetProperty.java +++ b/core/src/main/java/org/lflang/TargetProperty.java @@ -1726,10 +1726,10 @@ public enum Platform { AUTO, ARDUINO, NRF52("Nrf52"), + RP2040("Rp2040"), LINUX("Linux"), MAC("Darwin"), ZEPHYR("Zephyr"), - PICO("Pico"), WINDOWS("Windows"); String cMakeName; From 46087251b5883370d929ce79dc0f31047bf52356 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 11 Jul 2023 11:29:08 -0700 Subject: [PATCH 247/516] Reduce name collisions in fed-gen reaction bodies. Also format. --- .../federated/extensions/CExtension.java | 10 +-- .../extensions/FedTargetExtension.java | 1 - .../federated/extensions/PythonExtension.java | 12 ++- .../federated/extensions/TSExtension.java | 1 - .../federated/generator/FedASTUtils.java | 5 +- .../launcher/FedLauncherGenerator.java | 73 ++++++++++++------- .../FedNativePythonSerialization.java | 3 +- .../FedROS2CPPSerialization.java | 8 +- 8 files changed, 68 insertions(+), 45 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index 57872c13a0..94f09792b1 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -296,7 +296,7 @@ public String generateNetworkSenderBody( receivingPortID + "", connection.getDstFederate().id + "", next_destination_name, - "message_length"); + "_lf_message_length"); if (connection.getDefinition().isPhysical()) { // Messages going on a physical connection do not // carry a timestamp or require the delay; @@ -309,7 +309,7 @@ public String generateNetworkSenderBody( + connection.getDstFederate().id + ", " + next_destination_name - + ", message_length"; + + ", _lf_message_length"; } serializeAndSend(connection, type, sendRef, result, sendingFunction, commonArgs, errorReporter); @@ -347,7 +347,7 @@ protected void serializeAndSend( // both have the same endianness. Otherwise, you have to use protobufs or some other // serialization scheme. result.pr( - "size_t message_length = " + "size_t _lf_message_length = " + sendRef + "->token->length * " + sendRef @@ -367,7 +367,7 @@ protected void serializeAndSend( } else if (targetType.equals("void")) { lengthExpression = "0"; } - result.pr("size_t message_length = " + lengthExpression + ";"); + result.pr("size_t _lf_message_length = " + lengthExpression + ";"); result.pr(sendingFunction + "(" + commonArgs + ", " + pointerExpression + ");"); } } @@ -390,7 +390,7 @@ protected void serializeAndSend( result.pr( ROSSerializer.generateNetworkSerializerCode( sendRef, typeStr, CExtensionUtils.isSharedPtrType(type, types))); - result.pr("size_t message_length = " + lengthExpression + ";"); + result.pr("size_t _lf_message_length = " + lengthExpression + ";"); result.pr(sendingFunction + "(" + commonArgs + ", " + pointerExpression + ");"); } } diff --git a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java index 8957dfe0f4..8cbaf4e0f5 100644 --- a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java @@ -4,7 +4,6 @@ import org.lflang.ErrorReporter; import org.lflang.InferredType; 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; diff --git a/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java b/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java index b119716c8d..0706dc78c9 100644 --- a/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java @@ -128,8 +128,10 @@ protected void deserialize( result.pr(pickler.generateNetworkDeserializerCode(value, null)); result.pr("lf_set(" + receiveRef + ", " + FedSerialization.deserializedVarName + ");\n"); } - case PROTO -> throw new UnsupportedOperationException("Protobuf serialization is not supported yet."); - case ROS2 -> throw new UnsupportedOperationException("ROS2 serialization is not supported yet."); + case PROTO -> throw new UnsupportedOperationException( + "Protobuf serialization is not supported yet."); + case ROS2 -> throw new UnsupportedOperationException( + "ROS2 serialization is not supported yet."); } } @@ -154,8 +156,10 @@ protected void serializeAndSend( result.pr("size_t message_length = " + lengthExpression + ";"); result.pr(sendingFunction + "(" + commonArgs + ", " + pointerExpression + ");\n"); } - case PROTO -> throw new UnsupportedOperationException("Protobuf serialization is not supported yet."); - case ROS2 -> throw new UnsupportedOperationException("ROS2 serialization is not supported yet."); + case PROTO -> throw new UnsupportedOperationException( + "Protobuf serialization is not supported yet."); + case ROS2 -> throw new UnsupportedOperationException( + "ROS2 serialization is not supported yet."); } } diff --git a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java index 396c83863d..f59f986f14 100644 --- a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java @@ -2,7 +2,6 @@ import static org.lflang.util.StringUtil.addDoubleQuotes; -import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 099d770fb1..10e5152387 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -261,7 +261,8 @@ private static void addNetworkReceiverReactor( // Keep track of this action in the destination federate. connection.dstFederate.networkMessageActions.add(networkAction); - if (connection.getDefinition().getDelay() == null) connection.dstFederate.zeroDelayNetworkMessageActions.add(networkAction); + if (connection.getDefinition().getDelay() == null) + connection.dstFederate.zeroDelayNetworkMessageActions.add(networkAction); TimeValue maxSTP = findMaxSTP(connection, coordination); @@ -704,7 +705,7 @@ private static Reaction getInitializationReaction() { initializationReaction.getTriggers().add(startup); var code = LfFactory.eINSTANCE.createCode(); code.setBody( - """ + """ extern reaction_t* port_absent_reaction[]; void enqueue_network_output_control_reactions(); LF_PRINT_DEBUG("Adding network output control reaction to table."); diff --git a/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java b/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java index ba5b4479c7..2f56257255 100644 --- a/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java +++ b/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java @@ -149,7 +149,9 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { // The sleep at the end prevents screen from exiting before outgoing messages from // the federate have had time to go out to the RTI through the socket. - shCode.append(getRemoteLaunchCode(host, target, logFileName, getRtiCommand(federates, true))).append("\n"); + shCode + .append(getRemoteLaunchCode(host, target, logFileName, getRtiCommand(federates, true))) + .append("\n"); } // Index used for storing pids of federates @@ -164,23 +166,31 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { String compileCommand = buildConfig.compileCommand(); // FIXME: Should $FEDERATION_ID be used to ensure unique directories, executables, on the // remote host? - distCode.append(getDistCode( - rtiConfig.getDirectory(), - federate, - fedRelSrcGenPath, - logFileName, - fileConfig.getSrcGenPath(), - compileCommand)).append("\n"); + distCode + .append( + getDistCode( + rtiConfig.getDirectory(), + federate, + fedRelSrcGenPath, + logFileName, + fileConfig.getSrcGenPath(), + compileCommand)) + .append("\n"); String executeCommand = buildConfig.remoteExecuteCommand(); - shCode.append(getFedRemoteLaunchCode( - federate, - rtiConfig.getDirectory(), - logFileName, - executeCommand, - federateIndex++)).append("\n"); + shCode + .append( + getFedRemoteLaunchCode( + federate, + rtiConfig.getDirectory(), + logFileName, + executeCommand, + federateIndex++)) + .append("\n"); } else { String executeCommand = buildConfig.localExecuteCommand(); - shCode.append(getFedLocalLaunchCode(federate, executeCommand, federateIndex++)).append("\n"); + shCode + .append(getFedLocalLaunchCode(federate, executeCommand, federateIndex++)) + .append("\n"); } } if (host.equals("localhost") || host.equals("0.0.0.0")) { @@ -190,17 +200,20 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { shCode.append("fg %1" + "\n"); } // Wait for launched processes to finish - shCode.append(String.join( - "\n", - "echo \"RTI has exited. Wait for federates to exit.\"", - "# Wait for launched processes to finish.", - "# The errors are handled separately via trap.", - "for pid in \"${pids[@]}\"", - "do", - " wait $pid || exit $?", - "done", - "echo \"All done.\"", - "EXITED_SUCCESSFULLY=true")).append("\n"); + shCode + .append( + String.join( + "\n", + "echo \"RTI has exited. Wait for federates to exit.\"", + "# Wait for launched processes to finish.", + "# The errors are handled separately via trap.", + "for pid in \"${pids[@]}\"", + "do", + " wait $pid || exit $?", + "done", + "echo \"All done.\"", + "EXITED_SUCCESSFULLY=true")) + .append("\n"); // Create bin directory for the script. if (!Files.exists(fileConfig.binPath)) { @@ -218,7 +231,9 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { // Delete file previously produced, if any. File file = fileConfig.binPath.resolve(fileConfig.name).toFile(); if (file.exists()) { - if (!file.delete()) errorReporter.reportError("Failed to delete existing federated launch script \"" + file + "\""); + if (!file.delete()) + errorReporter.reportError( + "Failed to delete existing federated launch script \"" + file + "\""); } FileOutputStream fOut = null; @@ -244,7 +259,9 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { // Delete the file even if it does not get generated. file = fileConfig.binPath.resolve(fileConfig.name + "_distribute.sh").toFile(); if (file.exists()) { - if (!file.delete()) errorReporter.reportError("Failed to delete existing federated distributor script \"" + file + "\""); + if (!file.delete()) + errorReporter.reportError( + "Failed to delete existing federated distributor script \"" + file + "\""); } if (distCode.length() > 0) { try { diff --git a/core/src/main/java/org/lflang/federated/serialization/FedNativePythonSerialization.java b/core/src/main/java/org/lflang/federated/serialization/FedNativePythonSerialization.java index 3f34673900..98f78d9115 100644 --- a/core/src/main/java/org/lflang/federated/serialization/FedNativePythonSerialization.java +++ b/core/src/main/java/org/lflang/federated/serialization/FedNativePythonSerialization.java @@ -66,7 +66,8 @@ public StringBuilder generateNetworkSerializerCode(String varName, String origin // Define the serialized PyObject serializerCode .append( - "PyObject* serialized_pyobject = PyObject_CallMethod(global_pickler, \"dumps\", \"O\", ") + "PyObject* serialized_pyobject = PyObject_CallMethod(global_pickler, \"dumps\", \"O\"," + + " ") .append(varName) .append(");\n"); diff --git a/core/src/main/java/org/lflang/federated/serialization/FedROS2CPPSerialization.java b/core/src/main/java/org/lflang/federated/serialization/FedROS2CPPSerialization.java index 4bec959a70..44d2616a1b 100644 --- a/core/src/main/java/org/lflang/federated/serialization/FedROS2CPPSerialization.java +++ b/core/src/main/java/org/lflang/federated/serialization/FedROS2CPPSerialization.java @@ -151,7 +151,8 @@ public StringBuilder generateNetworkDeserializerCode(String varName, String targ deserializerCode .append( - "auto message = std::make_unique( rcl_serialized_message_t{\n" + "auto _lf_message = std::make_unique(" + + " rcl_serialized_message_t{\n" + " .buffer = (uint8_t*)") .append(varName) .append(".tmplt.token->value,\n") @@ -164,7 +165,8 @@ public StringBuilder generateNetworkDeserializerCode(String varName, String targ .append(" .allocator = rcl_get_default_allocator()\n") .append("});\n"); deserializerCode.append( - "auto msg = std::make_unique(std::move(*message.get()));\n"); + "auto _lf_msg =" + + " std::make_unique(std::move(*_lf_message.get()));\n"); deserializerCode .append(varName) .append(".tmplt.token->value = NULL; // Manually move the data\n"); @@ -176,7 +178,7 @@ public StringBuilder generateNetworkDeserializerCode(String varName, String targ + deserializedVarName + " = MessageT();\n" + "auto _lf_serializer = rclcpp::Serialization();\n" - + "_lf_serializer.deserialize_message(msg.get(), &" + + "_lf_serializer.deserialize_message(_lf_msg.get(), &" + deserializedVarName + ");\n"); From 80edc9219152eef9e6afdf565af8a2e4da5256fc Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Tue, 11 Jul 2023 12:07:11 -0700 Subject: [PATCH 248/516] remove resources and refactor --- .../pico/pico_extras_import_optional.cmake | 59 -- .../resources/lib/platform/pico/pico_setup.sh | 549 ------------------ .../lib/platform/{pico => rp2040}/launch.json | 0 .../{pico => rp2040}/pico_sdk_import.cmake | 0 4 files changed, 608 deletions(-) delete mode 100644 core/src/main/resources/lib/platform/pico/pico_extras_import_optional.cmake delete mode 100755 core/src/main/resources/lib/platform/pico/pico_setup.sh rename core/src/main/resources/lib/platform/{pico => rp2040}/launch.json (100%) rename core/src/main/resources/lib/platform/{pico => rp2040}/pico_sdk_import.cmake (100%) diff --git a/core/src/main/resources/lib/platform/pico/pico_extras_import_optional.cmake b/core/src/main/resources/lib/platform/pico/pico_extras_import_optional.cmake deleted file mode 100644 index 692e14ad9d..0000000000 --- a/core/src/main/resources/lib/platform/pico/pico_extras_import_optional.cmake +++ /dev/null @@ -1,59 +0,0 @@ -# This is a copy of /external/pico_extras_import.cmake - -# This can be dropped into an external project to help locate pico-extras -# It should be include()ed prior to project() - -if (DEFINED ENV{PICO_EXTRAS_PATH} AND (NOT PICO_EXTRAS_PATH)) - set(PICO_EXTRAS_PATH $ENV{PICO_EXTRAS_PATH}) - message("Using PICO_EXTRAS_PATH from environment ('${PICO_EXTRAS_PATH}')") -endif () - -if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT)) - set(PICO_EXTRAS_FETCH_FROM_GIT $ENV{PICO_EXTRAS_FETCH_FROM_GIT}) - message("Using PICO_EXTRAS_FETCH_FROM_GIT from environment ('${PICO_EXTRAS_FETCH_FROM_GIT}')") -endif () - -if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT_PATH)) - set(PICO_EXTRAS_FETCH_FROM_GIT_PATH $ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH}) - message("Using PICO_EXTRAS_FETCH_FROM_GIT_PATH from environment ('${PICO_EXTRAS_FETCH_FROM_GIT_PATH}')") -endif () - -if (NOT PICO_EXTRAS_PATH) - if (PICO_EXTRAS_FETCH_FROM_GIT) - include(FetchContent) - set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) - if (PICO_EXTRAS_FETCH_FROM_GIT_PATH) - get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") - endif () - FetchContent_Declare( - pico_extras - GIT_REPOSITORY https://github.com/raspberrypi/pico-extras - GIT_TAG master - ) - if (NOT pico_extras) - message("Downloading Raspberry Pi Pico Extras") - FetchContent_Populate(pico_extras) - set(PICO_EXTRAS_PATH ${pico_extras_SOURCE_DIR}) - endif () - set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) - else () - if (PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../pico-extras") - set(PICO_EXTRAS_PATH ${PICO_SDK_PATH}/../pico-extras) - message("Defaulting PICO_EXTRAS_PATH as sibling of PICO_SDK_PATH: ${PICO_EXTRAS_PATH}") - endif() - endif () -endif () - -if (PICO_EXTRAS_PATH) - set(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" CACHE PATH "Path to the PICO EXTRAS") - set(PICO_EXTRAS_FETCH_FROM_GIT "${PICO_EXTRAS_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO EXTRAS from git if not otherwise locatable") - set(PICO_EXTRAS_FETCH_FROM_GIT_PATH "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download EXTRAS") - - get_filename_component(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") - if (NOT EXISTS ${PICO_EXTRAS_PATH}) - message(FATAL_ERROR "Directory '${PICO_EXTRAS_PATH}' not found") - endif () - - set(PICO_EXTRAS_PATH ${PICO_EXTRAS_PATH} CACHE PATH "Path to the PICO EXTRAS" FORCE) - add_subdirectory(${PICO_EXTRAS_PATH} pico_extras) -endif() \ No newline at end of file diff --git a/core/src/main/resources/lib/platform/pico/pico_setup.sh b/core/src/main/resources/lib/platform/pico/pico_setup.sh deleted file mode 100755 index 853a08c185..0000000000 --- a/core/src/main/resources/lib/platform/pico/pico_setup.sh +++ /dev/null @@ -1,549 +0,0 @@ -#!/usr/bin/env bash - -# Phase 0: Preflight check -# Verify baseline dependencies - -# Phase 1: Setup dev environment -# Install the software packages from APT or Homebrew -# Create a directory called pico -# Download the pico-sdk repository and submodules -# Define env variables: PICO_SDK_PATH -# On Raspberry Pi only: configure the UART for use with Raspberry Pi Pico - -# Phase 2: Setting up tutorial repos -# Download pico-examples, pico-extras, pico-playground repositories, and submodules -# Build the blink and hello_world examples - -# Phase 3: Recommended tools -# Download and build picotool (see Appendix B), and copy it to /usr/local/bin. -# Download and build picoprobe (see Appendix A) and OpenOCD -# Download and install Visual Studio Code and required extensions - - -# Exit on error -set -e - -# Trying to use a non-existent variable is an error -set -u - -# if printenv DEBUG >& /dev/null; then - # Show all commands - set -x - - env -# fi - -# Number of cores when running make -JNUM=4 - -# Where will the output go? -if printenv TARGET_DIR; then - echo "Using target dir from \$TARGET_DIR: ${TARGET_DIR}" -else - TARGET_DIR="$(pwd)/pico" - echo "Using target dir: ${TARGET_DIR}" -fi - -linux() { - # Returns true iff this is running on Linux - uname | grep -q "^Linux$" - return ${?} -} - -raspbian() { - # Returns true iff this is running on Raspbian or close derivative such as Raspberry Pi OS, but not necessarily on a Raspberry Pi computer - grep -q '^NAME="Raspbian GNU/Linux"$' /etc/os-release - return ${?} -} - -debian() { - # Returns true iff this is running on Debian - grep -q '^NAME="Debian GNU/Linux"$' /etc/os-release - return ${?} -} - -ubuntu() { - # Returns true iff this is running on Ubuntu - grep -q '^NAME="Ubuntu"$' /etc/os-release - return ${?} -} - -mac() { - # Returns true iff this is running on macOS and presumably Apple hardware - uname | grep -q "^Darwin$" - return ${?} -} - -raspberry_pi() { - # Returns true iff this is running on a Raspberry Pi computer, regardless of the OS - if [ -f /proc/cpuinfo ]; then - grep -q "^Model\s*: Raspberry Pi" /proc/cpuinfo - return ${?} - fi - return 1 -} - -sudo_wrapper() { - # Some platforms have different needs for invoking sudo. This wrapper encapsulates that complexity. - # The output of this function should be a string on stdout, which will be used as a command. Example: - # `$(sudo_wrapper) whoami` - # The above may equate to: - # `sudo -i whoami` - - if [ "${USER}" = root ]; then - # if we're already root, don't sudo at all. Relevant to some Docker images that don't have sudo but already run as root. - return - fi - - # EC2 AMIs tend to have the user password unset, so you can't sudo without -i. It will cd /root, so you have to be - # careful with relative paths in the command. - echo sudo -i -} - -phase_0() { - # Preflight check - # Checks the baseline dependencies. If you don't have these, this script won't work. - echo "Entering phase 0: Preflight check" - - if mac; then - echo "Running on macOS" - if which brew >> /dev/null; then - echo "Found brew" - brew update - else - echo -e 'This script requires Homebrew, the missing package manager for macOS. See https://docs.brew.sh/Installation. For quick install, run:\n/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"' - echo "Stopping." - exit 1 - fi - else - if linux; then - echo "Running on Linux" - else - echo "Platform $(uname) not recognized. Use at your own risk. Continuing as though this were Linux." - fi - - if which apt >> /dev/null; then - echo "Found apt" - $(sudo_wrapper) apt update - else - echo 'This script requires apt, the default package manager for Debian and Debian-derived distros such as Ubuntu and Raspberry Pi OS.' - echo "Stopping." - exit 1 - fi - fi -} - -fail() { - # Outputs a failure message and exits with the error code output by the previous call. - # All args are echoed as a failure message. - - R="${?}" - echo "Validation failed! :'-(" - if [ ${*} ]; then - echo "${*}" - fi - exit ${R} -} - -validate_git_repo() { - # tests that the given relative path exists and is a git repo - git -C ${TARGET_DIR}/${1} status >& /dev/null || fail -} - -validate_toolchain_linux() { - # test that the expected packages are installed - dpkg-query -s git cmake gcc-arm-none-eabi build-essential gdb-multiarch automake autoconf build-essential texinfo libtool libftdi-dev libusb-1.0-0-dev >& /dev/null || fail -} - -install_dev_env_deps_linux() { - # Install development environment dependencies for Linux - - # Avoid a certain dependency by installing ssh-client without recommends. See - # https://github.com/raspberrypi/pico-setup/pull/20#discussion_r608793993 for details. - $(sudo_wrapper) apt install -y --no-install-recommends ssh-client - - DEPS="autoconf automake build-essential cmake gcc-arm-none-eabi gdb-multiarch git libftdi-dev libtool libusb-1.0-0-dev minicom pkg-config python3 texinfo" - if debian || ubuntu; then - DEPS="${DEPS} libstdc++-arm-none-eabi-newlib" - fi - $(sudo_wrapper) apt install -y ${DEPS} -} - -brew_install_idempotent() { - # For some reason, brew install is not idempotent. This function succeeds even when the package is already installed. - brew list ${*} || brew install ${*} - return ${?} -} - -validate_toolchain_mac() { - # test that the expected packages are installed - brew list git cmake pkg-config libtool automake libusb wget pkg-config gcc texinfo arm-none-eabi-gcc >& /dev/null -} - -install_dev_env_deps_mac() { - # Install development environment dependencies for mac - - brew_install_idempotent ArmMbed/homebrew-formulae/arm-none-eabi-gcc automake cmake git libtool libusb gcc minicom pkg-config texinfo wget -} - -create_TARGET_DIR() { - # Creates ./pico directory if necessary - - mkdir -p "${TARGET_DIR}" -} - -clone_raspberrypi_repo() { - # Clones the given repo name from GitHub and inits any submodules - # $1 should be the full name of the repo, ex: pico-sdk - # $2 should be the branch name. Defaults to master. - # all other args are passed to git clone - REPO_NAME="${1}" - if shift && [ ${#} -gt 0 ]; then - BRANCH="${1}" - # Can't just say `shift` because `set -e` will think it's an error and terminate the script. - shift || true - else - BRANCH=master - fi - - REPO_URL="https://github.com/raspberrypi/${REPO_NAME}.git" - REPO_DIR="${TARGET_DIR}/${REPO_NAME}" - - if [ -d "${REPO_DIR}" ]; then - echo "${REPO_DIR} already exists. Updating." - git -C "${REPO_DIR}" pull --ff-only - else - echo "Cloning ${REPO_URL}" - if [ ${#} -gt 0 ]; then - git -C "${TARGET_DIR}" clone -b "${BRANCH}" "${REPO_URL}" ${*} - else - git -C "${TARGET_DIR}" clone -b "${BRANCH}" "${REPO_URL}" - fi - - # Any submodules - git -C "${REPO_DIR}" submodule update --init - fi -} - -warn_for_bashrc() { - # Earlier versions of this script set environment variables in .bashrc. The location has moved to .profile or - # .zprofile. If the user has a .bashrc defining any pico dev env variables, they could conflict with the settings - # in the other files. This function raises a warning for the user. - - REGEX="^\s*export\s+\"?PICO_SDK_PATH=" - if grep -q "${REGEX}" ~/.bashrc; then - echo "Your ~/.bashrc file contains the following line, which may conflict with this script's settings. We recommend removing it to prevent possible issues." - echo -n " "; grep "${REGEX}" ~/.bashrc - fi -} - -set_env() { - # Permanently sets an environment variable by adding it to the current user's profile script - # The form of the arguments should be `FOO foo`, which sets the environment variable `FOO=foo` - NAME="${1}" - VALUE="${2}" - EXPR="${NAME}=${VALUE}" - - # detect appropriate file for setting env vars - if echo "${SHELL}" | grep -q zsh; then - # zsh detected - FILE=~/.zprofile - else - # sh, bash and others - FILE=~/.profile - fi - - # ensure that appends go to a new line - if [ -f "${FILE}" ]; then - if ! ( tail -n 1 "${FILE}" | grep -q "^$" ); then - # FILE exists but has no trailing newline. Adding newline. - echo >> "${FILE}" - fi - fi - - # set for now - export "${EXPR}" - - # set for later - REGEX="^\s*export\s+\"?${NAME}=" - if grep -q "${REGEX}" "${FILE}"; then - # Warn the user - echo "Your ${FILE} already contains the following environment variable definition(s):" - grep "${REGEX}" "${FILE}" - echo "This script would normally set the following line. We're adding it, but commented out, so that you can choose which you want." - echo "export \"${EXPR}\"" - # Write to file - echo "# pico_setup.sh commented out the following line because it conflicts with another line in this file. You should choose one or the other." >> "${FILE}" - echo "# export \"${EXPR}\"" >> "${FILE}" - else - echo "Setting env variable ${EXPR} in ${FILE}" - echo "export \"${EXPR}\"" >> "${FILE}" - fi -} - -validate_pico_sdk() { - validate_git_repo pico-sdk - - # test that the SDK env var is set and correct - test "${PICO_SDK_PATH}" = "${TARGET_DIR}/pico-sdk" || fail -} - -setup_sdk() { - # Download the SDK - clone_raspberrypi_repo pico-sdk - - # Set env var PICO_SDK_PATH - set_env PICO_SDK_PATH "${TARGET_DIR}/pico-sdk" -} - -validate_uart() { - # test that the UART is configured. Only works on Raspberry Pi OS on Raspberry Pi hardware. - dpkg-query -s minicom >& /dev/null || fail - grep -q "enable_uart=1" /boot/config.txt || fail - # note that the test for console=serial0 tests for the absence of a string - grep -q "console=serial0" /boot/cmdline.txt && fail -} - -enable_uart() { - # Enable UART - echo "Disabling Linux serial console (UART) so we can use it for pico" - $(sudo_wrapper) raspi-config nonint do_serial 2 - echo "You must run sudo reboot to finish UART setup" -} - -phase_1() { - # Setup minimum dev environment - echo "Entering phase 1: Setup minimum dev environment" - - if mac; then - install_dev_env_deps_mac - validate_toolchain_mac - else - install_dev_env_deps_linux - validate_toolchain_linux - fi - - create_TARGET_DIR - setup_sdk - validate_pico_sdk - - if raspberry_pi && which raspi-config >> /dev/null; then - enable_uart - validate_uart - else - echo "Not configuring UART, because either this is not a Raspberry Pi computer, or raspi-config is not available." - fi -} - -build_examples() { - # Build a couple of examples - echo "Building selected examples" - - # Save the working directory - pushd "${TARGET_DIR}/pico-examples" >> /dev/null - - mkdir -p build - cd build - cmake ../ -DCMAKE_BUILD_TYPE=Debug - - for EXAMPLE in blink hello_world; do - echo "Building $EXAMPLE" - cd "$EXAMPLE" - make -j${JNUM} - cd .. - done - - # Restore the working directory - popd >> /dev/null -} - -validate_pico_extras() { - validate_git_repo pico-extras -} - -validate_pico_examples() { - validate_git_repo pico-examples - - # test that blink is built - test -f ${TARGET_DIR}/pico-examples/build/blink/blink.uf2 || fail - - # test that hello_serial is built - test -f ${TARGET_DIR}/pico-examples/build/hello_world/serial/hello_serial.uf2 || fail - - # test that hello_usb is built - test -f ${TARGET_DIR}/pico-examples/build/hello_world/usb/hello_usb.uf2 || fail -} - -validate_pico_playground() { - validate_git_repo pico-playground -} - -phase_2() { - # Setup tutorial repos - echo "Entering phase 2: Setting up tutorial repos" - - for REPO_NAME in pico-examples pico-extras pico-playground; do - clone_raspberrypi_repo "${REPO_NAME}" - done - - build_examples - - validate_pico_examples - validate_pico_extras - validate_pico_playground -} - -validate_picotool() { - validate_git_repo picotool - - # test that the binary is built - test -x ${TARGET_DIR}/picotool/build/picotool || fail - - # test that picotool is installed in the expected location - test -x /usr/local/bin/picotool || fail -} - -setup_picotool() { - # Downloads, builds, and installs picotool - echo "Setting up picotool" - - # Save the working directory - pushd "${TARGET_DIR}" >> /dev/null - - clone_raspberrypi_repo picotool - cd "${TARGET_DIR}/picotool" - mkdir -p build - cd build - cmake ../ - make -j${JNUM} - - echo "Installing picotool to /usr/local/bin/picotool" - $(sudo_wrapper) cp "${TARGET_DIR}/picotool/build/picotool" /usr/local/bin/ - - # Restore the working directory - popd >> /dev/null -} - -validate_openocd() { - validate_git_repo openocd - - # test that the binary is built - test -x ${TARGET_DIR}/openocd/src/openocd || fail -} - -setup_openocd() { - # Download, build, and install OpenOCD for picoprobe and bit-banging without picoprobe - echo "Setting up OpenOCD" - - # Save the working directory - pushd "${TARGET_DIR}" >> /dev/null - - clone_raspberrypi_repo openocd picoprobe --depth=1 - cd "${TARGET_DIR}/openocd" - ./bootstrap - OPTS="--enable-ftdi --enable-bcm2835gpio --enable-picoprobe" - if linux; then - # sysfsgpio is only available on linux - OPTS="${OPTS} --enable-sysfsgpio" - fi - ./configure ${OPTS} - make -j${JNUM} - $(sudo_wrapper) make -C "${TARGET_DIR}/openocd" install - - # Restore the working directory - popd >> /dev/null -} - -validate_picoprobe() { - validate_git_repo picoprobe || fail - - # test that the binary is built - test -f ${TARGET_DIR}/picoprobe/build/picoprobe.uf2 || fail -} - -setup_picoprobe() { - # Download and build picoprobe. Requires that OpenOCD is already setup - echo "Setting up picoprobe" - - # Save the working directory - pushd "${TARGET_DIR}" >> /dev/null - - clone_raspberrypi_repo picoprobe - cd "${TARGET_DIR}/picoprobe" - mkdir -p build - cd build - cmake .. - make -j${JNUM} - - # Restore the working directory - popd >> /dev/null -} - -validate_vscode_linux() { - dpkg-query -s code >& /dev/null || fail -} - -install_vscode_linux() { - # Install Visual Studio Code - - # VS Code is specially added to Raspberry Pi OS repos, but might not be present on Debian/Ubuntu. So we check first. - if ! apt-cache show code >& /dev/null; then - echo "It appears that your APT repos do not offer Visual Studio Code. Skipping." - return - fi - - echo "Installing Visual Studio Code" - - $(sudo_wrapper) apt install -y code - - # Get extensions - code --install-extension marus25.cortex-debug - code --install-extension ms-vscode.cmake-tools - code --install-extension ms-vscode.cpptools -} - -validate_vscode_mac() { - echo "Not yet implemented: testing Visual Studio Code on macOS" -} - -install_vscode_mac() { - echo "Not yet implemented: installing Visual Studio Code on macOS" -} - -phase_3() { - # Setup recommended tools - echo "Setting up recommended tools" - - setup_picotool - validate_picotool - - setup_openocd - validate_openocd - - setup_picoprobe - validate_picoprobe - - # Install Visual Studio Code - if mac; then - install_vscode_mac - validate_vscode_mac - else - if dpkg-query -s xserver-xorg >& /dev/null; then - install_vscode_linux - validate_vscode_linux - else - echo "Not installing Visual Studio Code because it looks like XWindows is not installed." - fi - fi -} - -main() { - phase_0 - phase_1 - phase_2 - phase_3 - - echo "Congratulations, installation is complete. :D" -} - -main diff --git a/core/src/main/resources/lib/platform/pico/launch.json b/core/src/main/resources/lib/platform/rp2040/launch.json similarity index 100% rename from core/src/main/resources/lib/platform/pico/launch.json rename to core/src/main/resources/lib/platform/rp2040/launch.json diff --git a/core/src/main/resources/lib/platform/pico/pico_sdk_import.cmake b/core/src/main/resources/lib/platform/rp2040/pico_sdk_import.cmake similarity index 100% rename from core/src/main/resources/lib/platform/pico/pico_sdk_import.cmake rename to core/src/main/resources/lib/platform/rp2040/pico_sdk_import.cmake From 23051f10a3de81b8ccb563e6f36ded0c2c5f43ee Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Tue, 11 Jul 2023 12:27:38 -0700 Subject: [PATCH 249/516] generator changes: pico->rp2040 --- .../lflang/generator/c/CCmakeGenerator.java | 100 +++++++++--------- .../org/lflang/generator/c/CGenerator.java | 13 ++- .../generator/c/CMainFunctionGenerator.java | 2 +- 3 files changed, 59 insertions(+), 56 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 8141baf153..228dbc7ee5 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -117,34 +117,35 @@ CodeBuilder generateCMakeCode( cMakeCode.pr("cmake_minimum_required(VERSION " + MIN_CMAKE_VERSION + ")"); - if (targetConfig.platformOptions.platform == Platform.ZEPHYR) { - cMakeCode.pr("# Set default configuration file. To add custom configurations,"); - cMakeCode.pr("# pass -- -DOVERLAY_CONFIG=my_config.prj to either cmake or west"); - cMakeCode.pr("set(CONF_FILE prj_lf.conf)"); - if (targetConfig.platformOptions.board != null) { - cMakeCode.pr("# Selecting board specified in target property"); - cMakeCode.pr("set(BOARD " + targetConfig.platformOptions.board + ")"); - } else { - cMakeCode.pr("# Selecting default board"); - cMakeCode.pr("set(BOARD qemu_cortex_m3)"); - } - cMakeCode.pr("# We recommend Zephyr v3.3.0 but we are compatible with older versions also"); - cMakeCode.pr("find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE} 3.3.0)"); - cMakeCode.newLine(); - } else if (targetConfig.platformOptions.platform == Platform.PICO) { - cMakeCode.pr("message(\"Run ./pico_setup.sh for unix systems in a chosen directory.\")"); - cMakeCode.pr("message(\"The script will download all required dependencies in /pico.\")"); - cMakeCode.newLine(); - // include cmake before project - cMakeCode.pr("include(pico_sdk_import.cmake)"); - cMakeCode.pr("include(pico_extras_import_optional.cmake)"); - cMakeCode.pr("project(" + executableName + " LANGUAGES C CXX ASM)"); - cMakeCode.newLine(); - } else { - cMakeCode.pr("project(" + executableName + " LANGUAGES C)"); - cMakeCode.newLine(); + // Setup the project header for different platforms + switch (targetConfig.platformOptions.platform) { + case ZEPHYR: + cMakeCode.pr("# Set default configuration file. To add custom configurations,"); + cMakeCode.pr("# pass -- -DOVERLAY_CONFIG=my_config.prj to either cmake or west"); + cMakeCode.pr("set(CONF_FILE prj_lf.conf)"); + if (targetConfig.platformOptions.board != null) { + cMakeCode.pr("# Selecting board specified in target property"); + cMakeCode.pr("set(BOARD " + targetConfig.platformOptions.board + ")"); + } else { + cMakeCode.pr("# Selecting default board"); + cMakeCode.pr("set(BOARD qemu_cortex_m3)"); + } + cMakeCode.pr("# We recommend Zephyr v3.3.0 but we are compatible with older versions also"); + cMakeCode.pr("find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE} 3.3.0)"); + cMakeCode.newLine(); + cMakeCode.pr("project(" + executableName + " LANGUAGES C)"); + cMakeCode.newLine(); + break; + case RP2040: + cMakeCode.pr("include(pico_sdk_import.cmake)"); + cMakeCode.pr("project(" + executableName + " LANGUAGES C CXX ASM)"); + cMakeCode.newLine(); + default: + cMakeCode.pr("project(" + executableName + " LANGUAGES C)"); + cMakeCode.newLine(); } + // The Test build type is the Debug type plus coverage generation cMakeCode.pr("if(CMAKE_BUILD_TYPE STREQUAL \"Test\")"); cMakeCode.pr(" set(CMAKE_BUILD_TYPE \"Debug\")"); @@ -211,26 +212,30 @@ CodeBuilder generateCMakeCode( } cMakeCode.newLine(); - if (targetConfig.platformOptions.platform == Platform.ZEPHYR) { - cMakeCode.pr( - setUpMainTargetZephyr( - hasMain, - executableName, - Stream.concat(additionalSources.stream(), sources.stream()))); - } else if (targetConfig.platformOptions.platform == Platform.PICO) { - cMakeCode.pr( - setUpMainTargetPico( - hasMain, - executableName, - Stream.concat(additionalSources.stream(), sources.stream()))); - } else { - cMakeCode.pr( - setUpMainTarget.getCmakeCode( - hasMain, - executableName, - Stream.concat(additionalSources.stream(), sources.stream()))); + // Setup main target for different platforms + switch (targetConfig.platformOptions.platform) { + case ZEPHYR: + cMakeCode.pr( + setUpMainTargetZephyr( + hasMain, + executableName, + Stream.concat(additionalSources.stream(), sources.stream()))); + break; + case RP2040: + cMakeCode.pr( + setUpMainTargetRp2040( + hasMain, + executableName, + Stream.concat(additionalSources.stream(), sources.stream()))); + break; + default: + cMakeCode.pr( + setUpMainTarget.getCmakeCode( + hasMain, + executableName, + Stream.concat(additionalSources.stream(), sources.stream()))); } - + cMakeCode.pr("target_link_libraries(${LF_MAIN_TARGET} PRIVATE core)"); cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC .)"); @@ -258,8 +263,7 @@ CodeBuilder generateCMakeCode( } if (targetConfig.threading || targetConfig.tracing != null) { - // dont include thread library for pico platform - if (targetConfig.platformOptions.platform != Platform.PICO) { + if (targetConfig.platformOptions.platform != Platform.RP2040) { // If threaded computation is requested, add the threads option. cMakeCode.pr("# Find threads and link to it"); cMakeCode.pr("find_package(Threads REQUIRED)"); @@ -422,7 +426,7 @@ private static String setUpMainTargetZephyr( return code.toString(); } - private static String setUpMainTargetPico( + private static String setUpMainTargetRp2040( boolean hasMain, String executableName, Stream cSources) { var code = new CodeBuilder(); // FIXME: remove this and move to lingo build diff --git a/core/src/main/java/org/lflang/generator/c/CGenerator.java b/core/src/main/java/org/lflang/generator/c/CGenerator.java index e80dd0baf1..e966d4613c 100644 --- a/core/src/main/java/org/lflang/generator/c/CGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CGenerator.java @@ -931,16 +931,15 @@ protected void copyTargetFiles() throws IOException { "/lib/platform/zephyr/Kconfig", fileConfig.getSrcGenPath(), true); } - if (targetConfig.platformOptions.platform == Platform.PICO) { + // For the pico src-gen, copy over vscode configurations for debugging + if (targetConfig.platformOptions.platform == Platform.RP2040) { Path vscodePath = Path.of(fileConfig.getSrcGenPath() + File.separator + ".vscode"); + // If pico-sdk-path not defined, this can be used to pull the sdk into src-gen FileUtil.copyFileFromClassPath( - "/lib/platform/pico/pico_setup.sh", fileConfig.getSrcGenPath(), true); + "/lib/platform/rp2040/pico_sdk_import.cmake", fileConfig.getSrcGenPath(), true); + // VS Code configurations FileUtil.copyFileFromClassPath( - "/lib/platform/pico/pico_sdk_import.cmake", fileConfig.getSrcGenPath(), true); - FileUtil.copyFileFromClassPath( - "/lib/platform/pico/pico_extras_import_optional.cmake", fileConfig.getSrcGenPath(), true); - FileUtil.copyFileFromClassPath( - "/lib/platform/pico/launch.json", vscodePath, true); + "/lib/platform/rp2040/launch.json", vscodePath, true); } } diff --git a/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java b/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java index 3e9dcd5fe2..3ffea7e332 100644 --- a/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java @@ -62,7 +62,7 @@ private String generateMainFunction() { " int res = lf_reactor_c_main(0, NULL);", " exit(res);", "}"); - } else if (targetConfig.platformOptions.platform == Platform.PICO) { + } else if (targetConfig.platformOptions.platform == Platform.RP2040) { // Pico platform cannont use command line args. return String.join( "\n", From 485b4c61eda7844ace6c4bd3c6510c5a2482f106 Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Tue, 11 Jul 2023 12:28:37 -0700 Subject: [PATCH 250/516] test refactor --- test/C/src/pico/BareThread.lf | 13 ------------- test/C/src/pico/FreeRTOSThread.lf | 12 ------------ test/C/src/pico/HelloPico.lf | 9 --------- test/C/src/pico/Timer.lf | 11 ----------- test/C/src/rp2040/HelloPico.lf | 7 +++++++ test/C/src/rp2040/Thread.lf | 13 +++++++++++++ test/C/src/rp2040/Timer.lf | 9 +++++++++ 7 files changed, 29 insertions(+), 45 deletions(-) delete mode 100644 test/C/src/pico/BareThread.lf delete mode 100644 test/C/src/pico/FreeRTOSThread.lf delete mode 100644 test/C/src/pico/HelloPico.lf delete mode 100644 test/C/src/pico/Timer.lf create mode 100644 test/C/src/rp2040/HelloPico.lf create mode 100644 test/C/src/rp2040/Thread.lf create mode 100644 test/C/src/rp2040/Timer.lf diff --git a/test/C/src/pico/BareThread.lf b/test/C/src/pico/BareThread.lf deleted file mode 100644 index 1bde45fb9b..0000000000 --- a/test/C/src/pico/BareThread.lf +++ /dev/null @@ -1,13 +0,0 @@ -target C { - platform: { - name: "Pico", - threading: "BareIron" - } - threading: true, -} - -// check that only 2 threads can be created max -main reactor { - timer t(0, 1 sec); - -} diff --git a/test/C/src/pico/FreeRTOSThread.lf b/test/C/src/pico/FreeRTOSThread.lf deleted file mode 100644 index 244b01ac7f..0000000000 --- a/test/C/src/pico/FreeRTOSThread.lf +++ /dev/null @@ -1,12 +0,0 @@ -target C { - platform: { - name: "Pico", - thread-lib: "FreeRTOS" - }, - threading: true, - workers: 4 -} - -// test creation of thread tasks using FreeRTOS support -main reactor {= -=} diff --git a/test/C/src/pico/HelloPico.lf b/test/C/src/pico/HelloPico.lf deleted file mode 100644 index ba57fe7039..0000000000 --- a/test/C/src/pico/HelloPico.lf +++ /dev/null @@ -1,9 +0,0 @@ -target C { - platform: "Pico" -} - -main reactor { - reaction(startup) {= - printf("Hello World!\n"); - =} -} diff --git a/test/C/src/pico/Timer.lf b/test/C/src/pico/Timer.lf deleted file mode 100644 index 591d5d90e5..0000000000 --- a/test/C/src/pico/Timer.lf +++ /dev/null @@ -1,11 +0,0 @@ -target C { - platform: "Pico" - timeout: 1 sec, -} - -main reactor { - timer t1(0, 100 msec); - reaction(t1) {= - printf("Logical Time: %d. \n", lf_time_logical()); - =} -} diff --git a/test/C/src/rp2040/HelloPico.lf b/test/C/src/rp2040/HelloPico.lf new file mode 100644 index 0000000000..bf4a7c7c29 --- /dev/null +++ b/test/C/src/rp2040/HelloPico.lf @@ -0,0 +1,7 @@ +target C { + platform: "Rp2040" +} + +main reactor { + reaction(startup) {= printf("Hello World!\n"); =} +} diff --git a/test/C/src/rp2040/Thread.lf b/test/C/src/rp2040/Thread.lf new file mode 100644 index 0000000000..2cb0494d1d --- /dev/null +++ b/test/C/src/rp2040/Thread.lf @@ -0,0 +1,13 @@ +target C { + platform: { + name: "Rp2040" + }, + threading: true, + workers: 3 +} + +main reactor { + timer t(0, 1 sec) + + reaction(t) {= prinf("Hello, World!"); =} +} diff --git a/test/C/src/rp2040/Timer.lf b/test/C/src/rp2040/Timer.lf new file mode 100644 index 0000000000..5aefd01f49 --- /dev/null +++ b/test/C/src/rp2040/Timer.lf @@ -0,0 +1,9 @@ +target C { + platform: "Rp2040" +} + +main reactor { + timer t1(0, 100 msec) + + reaction(t1) {= printf("Logical Time: %d. \n", lf_time_logical()); =} +} From 73b8d8293d8dccb98b3d49248ab6ed766854b8c4 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 11 Jul 2023 14:49:58 -0700 Subject: [PATCH 251/516] Fix compilation errors from merge. --- .../lflang/federated/extensions/CExtension.java | 2 +- .../lflang/federated/generator/FedASTUtils.java | 15 ++++++++------- .../lflang/federated/generator/FedGenerator.java | 4 ++-- .../federated/generator/FedMainEmitter.java | 6 +++--- .../federated/generator/FedReactorEmitter.java | 2 +- .../federated/generator/FederateInstance.java | 5 ++++- .../federated/launcher/FedLauncherGenerator.java | 12 +++++++----- .../org/lflang/generator/ReactorInstance.java | 1 - core/src/main/resources/lib/c/reactor-c | 2 +- 9 files changed, 27 insertions(+), 22 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index f61ae51854..7370ad64fb 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -639,7 +639,7 @@ private String generateCodeToInitializeFederate(FederateInstance federate, RtiCo "\n", "// Initialize the socket mutex", "lf_mutex_init(&outbound_socket_mutex);", - "lf_cond_init(&port_status_changed, &mutex);", + "lf_cond_init(&port_status_changed, &env->mutex);", CExtensionUtils.surroundWithIfFederatedDecentralized( "lf_cond_init(&logical_time_changed, &env->mutex);"))); diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 6a1b8bc082..607993112a 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -44,6 +44,7 @@ import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.xtext.xbase.lib.IteratorExtensions; import org.lflang.InferredType; +import org.lflang.MessageReporter; import org.lflang.TargetProperty.CoordinationType; import org.lflang.TimeValue; import org.lflang.ast.ASTUtils; @@ -633,7 +634,7 @@ private static Reactor getNetworkSenderReactor( destRef.setVariable(connection.getDestinationPortInstance().getDefinition()); Reaction networkSenderReaction = - getNetworkSenderReaction(inRef, destRef, connection, coordination, type, errorReporter); + getNetworkSenderReaction(inRef, destRef, connection, coordination, type, messageReporter); var senderIndexParameter = LfFactory.eINSTANCE.createParameter(); var senderIndexParameterType = LfFactory.eINSTANCE.createType(); @@ -679,7 +680,7 @@ private static Reaction getNetworkSenderReaction( FedConnectionInstance connection, CoordinationType coordination, Type type, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { var networkSenderReaction = LfFactory.eINSTANCE.createReaction(); networkSenderReaction.getTriggers().add(inRef); networkSenderReaction.setCode(LfFactory.eINSTANCE.createCode()); @@ -693,7 +694,7 @@ private static Reaction getNetworkSenderReaction( connection, InferredType.fromAST(type), coordination, - errorReporter)); + messageReporter)); return networkSenderReaction; } @@ -706,11 +707,11 @@ private static Reaction getInitializationReaction() { code.setBody( """ extern reaction_t* port_absent_reaction[]; - void enqueue_network_output_control_reactions(); + void enqueue_network_output_control_reactions(environment_t*); LF_PRINT_DEBUG("Adding network output control reaction to table."); port_absent_reaction[self->sender_index] = &self->_lf__reaction_2; LF_PRINT_DEBUG("Added network output control reaction to table. Enqueueing it..."); - enqueue_network_output_control_reactions(); + enqueue_network_output_control_reactions(self->base.environment); """); initializationReaction.setCode(code); return initializationReaction; @@ -729,11 +730,11 @@ private static void addNetworkSenderReactor( FedConnectionInstance connection, CoordinationType coordination, Resource resource, - ErrorReporter errorReporter) { + MessageReporter messageReporter) { LfFactory factory = LfFactory.eINSTANCE; // Assume all the types are the same, so just use the first on the right. - Reactor sender = getNetworkSenderReactor(connection, coordination, resource, errorReporter); + Reactor sender = getNetworkSenderReactor(connection, coordination, resource, messageReporter); Instantiation networkInstance = factory.createInstantiation(); diff --git a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java index 93ae31869e..ea1d960e0a 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java +++ b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java @@ -57,8 +57,8 @@ import org.lflang.lf.Instantiation; import org.lflang.lf.LfFactory; import org.lflang.lf.Reactor; -import org.lflang.lf.VarRef; import org.lflang.lf.TargetDecl; +import org.lflang.lf.VarRef; import org.lflang.util.Averager; public class FedGenerator { @@ -339,7 +339,7 @@ public TargetConfig getTargetConfig() { // Wait for all compile threads to finish (NOTE: Can block forever) try { if (!compileThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)) { - context.getErrorReporter().reportError("Timed out while compiling."); + context.getErrorReporter().nowhere().error("Timed out while compiling."); } } catch (Exception e) { context diff --git a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java index 6198573d17..51e16c2450 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java @@ -23,9 +23,9 @@ String generateMainReactor( FederateInstance federate, Reactor originalMainReactor, MessageReporter messageReporter) { // FIXME: Handle modes at the top-level if (!ASTUtils.allModes(originalMainReactor).isEmpty()) { - messageReporter.reportError( - ASTUtils.allModes(originalMainReactor).stream().findFirst().orElseThrow(), - "Modes at the top level are not supported under federated execution."); + messageReporter + .at(ASTUtils.allModes(originalMainReactor).stream().findFirst().orElseThrow()) + .error("Modes at the top level are not supported under federated execution."); } var renderer = FormattingUtil.renderer(federate.targetConfig.target); diff --git a/core/src/main/java/org/lflang/federated/generator/FedReactorEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedReactorEmitter.java index 2147e48bb3..366a18c2fe 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedReactorEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedReactorEmitter.java @@ -14,7 +14,7 @@ String generateReactorDefinitions(FederateInstance federate) { .getReactors().stream() .distinct() .filter(federate::references) - .map(FormattingUtils.renderer(federate.targetConfig.target)) + .map(FormattingUtil.renderer(federate.targetConfig.target)) .collect(Collectors.joining("\n")); } } diff --git a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java index 458abab7ed..88bb6a0051 100644 --- a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java +++ b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java @@ -249,6 +249,9 @@ public Instantiation getInstantiation() { /** Keep a unique list of enabled serializers */ public HashSet enabledSerializers = new HashSet<>(); + /** Cached result of analysis of which reactions to exclude from main. */ + private Set excludeReactions = null; + /** Keep a unique list of enabled serializers */ public List stpOffsets = new ArrayList<>(); @@ -292,7 +295,7 @@ private boolean references(Instantiation instantiation, ReactorDecl declaration) } boolean instantiationsCheck = false; - if (networkReactors.contains(ASTUtils.toDefinition(reactor))) { + if (networkReactors.contains(ASTUtils.toDefinition(declaration))) { return true; } // For a federate, we don't need to look inside imported reactors. diff --git a/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java b/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java index ba8012eea0..6f2347d85c 100644 --- a/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java +++ b/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java @@ -233,8 +233,9 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { File file = fileConfig.binPath.resolve(fileConfig.name).toFile(); if (file.exists()) { if (!file.delete()) - errorReporter.reportError( - "Failed to delete existing federated launch script \"" + file + "\""); + messageReporter + .nowhere() + .error("Failed to delete existing federated launch script \"" + file + "\""); } FileOutputStream fOut = null; @@ -248,7 +249,7 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { fOut.write(shCode.toString().getBytes()); fOut.close(); } catch (IOException e) { - messageReporter.reportError("Unable to write to file: " + file); + messageReporter.nowhere().error("Unable to write to file: " + file); } } @@ -261,8 +262,9 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { file = fileConfig.binPath.resolve(fileConfig.name + "_distribute.sh").toFile(); if (file.exists()) { if (!file.delete()) - errorReporter.reportError( - "Failed to delete existing federated distributor script \"" + file + "\""); + messageReporter + .nowhere() + .error("Failed to delete existing federated distributor script \"" + file + "\""); } if (distCode.length() > 0) { try { diff --git a/core/src/main/java/org/lflang/generator/ReactorInstance.java b/core/src/main/java/org/lflang/generator/ReactorInstance.java index 4b664aa531..53ca96a667 100644 --- a/core/src/main/java/org/lflang/generator/ReactorInstance.java +++ b/core/src/main/java/org/lflang/generator/ReactorInstance.java @@ -36,7 +36,6 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import org.lflang.AttributeUtils; import org.lflang.MessageReporter; import org.lflang.TimeValue; import org.lflang.ast.ASTUtils; diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index f85dc17368..afd9bf9445 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit f85dc17368625ed99a28147138db0b1e7fbcc3de +Subproject commit afd9bf9445c26b511ea00611f67483901ddd6302 From 583b0d9231b945709ce9a9ded3554f39d7537630 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 11 Jul 2023 15:23:07 -0700 Subject: [PATCH 252/516] Minor cleanup in ReactorInstanceGraph. --- .../generator/ReactionInstanceGraph.java | 65 ++----------------- 1 file changed, 7 insertions(+), 58 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java index 2a86483d69..5e5c50a598 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java @@ -1,5 +1,3 @@ -/** A graph that represents causality cycles formed by reaction instances. */ - /************* * Copyright (c) 2021, The University of California at Berkeley. * @@ -88,52 +86,9 @@ public void rebuild() { // Assign a level to each reaction. // If there are cycles present in the graph, it will be detected here. assignLevels(); - if (nodeCount() != 0) { - // The graph has cycles. - // main.reporter.reportError("Reactions form a cycle! " + toString()); - // Do not throw an exception so that cycle visualization can proceed. - // throw new InvalidSourceException("Reactions form a cycle!"); - } + // Do not throw an exception when nodeCount != 0 so that cycle visualization can proceed. } - // /** - // * Adds manually a set of dependent network edges as needed to nudge the level assignment - // * algorithm into creating a correct level assignment. - // * - // * @param main - // */ - // private void addDependentNetworkEdges(ReactorInstance main) { - // // FIXME: I do not think this belongs here because it pertains to federated execution. Also, - // it - // // seems to relate to a design that we do not intend to use? - // Attribute attribute = - // AttributeUtils.findAttributeByName(main.definition.getReactorClass(), "_fed_config"); - //// String actionsStr = - //// AttributeUtils.getAttributeParameter(attribute, AttributeSpec.DEPENDENCY_PAIRS); - //// if (actionsStr == null) - //// return; // No dependent network edges, the levels algorithm has enough information - //// List dependencies = List.of(actionsStr.split(";", -1)); - // // Recursively add nodes and edges from contained reactors. - // Map m = new HashMap<>(); - // for (ReactorInstance child : main.children) { - // m.put(child.getName(), child); - // } - //// for (String dependency : dependencies) { - //// List dep = List.of(dependency.split(",", 2)); - //// ReactorInstance downStream = m.getOrDefault(dep.get(0), null); - //// ReactorInstance upStream = m.getOrDefault(dep.get(1), null); - //// if (downStream == null || upStream == null) { - //// System.out.println("Downstream or Upstream reaction pair is undefined. Continuing."); - //// continue; - //// } - //// ReactionInstance down = downStream.reactions.get(0); - //// Runtime downRuntime = down.getRuntimeInstances().get(0); - //// for (ReactionInstance up : upStream.reactions) { - //// Runtime upRuntime = up.getRuntimeInstances().get(0); - //// addEdge(downRuntime, upRuntime); - //// } - //// } - // } /** This function rebuilds the graph and propagates and assigns deadlines to all reactions. */ public void rebuildAndAssignDeadlines() { this.clear(); @@ -331,7 +286,7 @@ private void getAllContainedReactions(List runtimeReactions, ReactorIns * 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)); + private final List numReactionsPerLevel = new ArrayList<>(List.of(0)); /////////////////////////////////////////////////////////// //// Private methods @@ -423,7 +378,7 @@ private void assignLevels() { assignPortLevel(origin); // Update numReactionsPerLevel info - adjustNumReactionsPerLevel(origin.level, 1); + incrementNumReactionsPerLevel(origin.level); } } @@ -481,16 +436,15 @@ private void assignInferredDeadlines() { * If there is no previously recorded number for this level, then * create one with index level and value valueToAdd. * @param level The level. - * @param valueToAdd The value to add to the number of levels. */ - private void adjustNumReactionsPerLevel(int level, int valueToAdd) { + private void incrementNumReactionsPerLevel(int level) { if (numReactionsPerLevel.size() > level) { - numReactionsPerLevel.set(level, numReactionsPerLevel.get(level) + valueToAdd); + numReactionsPerLevel.set(level, numReactionsPerLevel.get(level) + 1); } else { while (numReactionsPerLevel.size() < level) { numReactionsPerLevel.add(0); } - numReactionsPerLevel.add(valueToAdd); + numReactionsPerLevel.add(1); } } @@ -538,12 +492,7 @@ public String toDOT() { CUtil.getName(downstreamNode.getReaction().getParent().tpr) + "." + downstreamNode.getReaction().getName(); - edges.append( - " node_" - + labelHashCode - + " -> node_" - + (downstreamLabel.hashCode() & 0xfffffff) - + ";\n"); + edges.append(" node_").append(labelHashCode).append(" -> node_").append(downstreamLabel.hashCode() & 0xfffffff).append(";\n"); } } // Close the subgraph From f8a21d6cd4b279c26288a6692feca02607033fb1 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 11 Jul 2023 16:20:21 -0700 Subject: [PATCH 253/516] Fix more compiler errors. --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index afd9bf9445..fe7270a916 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit afd9bf9445c26b511ea00611f67483901ddd6302 +Subproject commit fe7270a9167494733d7b3d7567b35af5fef30af6 From f8b29f444e20746cf76d244e81736725e60635da Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 11 Jul 2023 16:23:18 -0700 Subject: [PATCH 254/516] Remove networkMessageActions param of fed_config. --- .../org/lflang/federated/generator/FedMainEmitter.java | 10 +--------- .../main/java/org/lflang/validation/AttributeSpec.java | 10 +--------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java index 51e16c2450..7419728fec 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java @@ -84,20 +84,12 @@ private CharSequence generateMainSignature( .filter(federate::references) .map(renderer) .collect(Collectors.joining(",", "(", ")")); - // Empty "()" is currently not allowed by the syntax - - var networkMessageActionsListString = - federate.networkMessageActions.stream() - .map(Variable::getName) - .collect(Collectors.joining(",")); return """ - @_fed_config(network_message_actions="%s") + @_fed_config() main reactor %s { """ .formatted( - networkMessageActionsListString, - // intraDependencies, paramList.equals("()") ? "" : paramList); } } diff --git a/core/src/main/java/org/lflang/validation/AttributeSpec.java b/core/src/main/java/org/lflang/validation/AttributeSpec.java index 4534fc0c13..b89d766c6f 100644 --- a/core/src/main/java/org/lflang/validation/AttributeSpec.java +++ b/core/src/main/java/org/lflang/validation/AttributeSpec.java @@ -219,15 +219,7 @@ enum AttrParamType { "enclave", new AttributeSpec(List.of(new AttrParamSpec(EACH_ATTR, AttrParamType.BOOLEAN, true)))); - ATTRIBUTE_SPECS_BY_NAME.put( - "_fed_config", - new AttributeSpec( - List.of( - new AttrParamSpec( - AttributeSpec.NETWORK_MESSAGE_ACTIONS, AttrParamType.STRING, false) // , - // new AttrParamSpec(AttributeSpec.DEPENDENCY_PAIRS, - // AttrParamType.STRING, false) - ))); + ATTRIBUTE_SPECS_BY_NAME.put("_fed_config", new AttributeSpec(List.of())); ATTRIBUTE_SPECS_BY_NAME.put("_c_body", new AttributeSpec(null)); ATTRIBUTE_SPECS_BY_NAME.put( "_tpoLevel", From 709a705f5c9a99de3fb888eba68be8dfa7b16dee Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 11 Jul 2023 16:41:22 -0700 Subject: [PATCH 255/516] Get a smoke test to pass for Python federated. --- .../org/lflang/federated/extensions/PythonExtension.java | 4 ++-- .../java/org/lflang/federated/generator/FedASTUtils.java | 5 ++++- .../org/lflang/federated/generator/FedMainEmitter.java | 4 +--- .../java/org/lflang/generator/ReactionInstanceGraph.java | 7 ++++++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java b/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java index f39b71f5ba..fa50ae9c63 100644 --- a/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java @@ -167,13 +167,13 @@ protected void serializeAndSend( lengthExpression = pickler.serializedBufferLength(); pointerExpression = pickler.seializedBufferVar(); result.pr(pickler.generateNetworkSerializerCode(variableToSerialize, null)); - result.pr("size_t message_length = " + lengthExpression + ";"); + result.pr("size_t _lf_message_length = " + lengthExpression + ";"); result.pr(sendingFunction + "(" + commonArgs + ", " + pointerExpression + ");\n"); // Decrease the reference count for serialized_pyobject result.pr("Py_XDECREF(serialized_pyobject);\n"); } case PROTO -> throw new UnsupportedOperationException( - "Protbuf serialization is not supported yet."); + "Protobuf serialization is not supported yet."); case ROS2 -> throw new UnsupportedOperationException( "ROS2 serialization is not supported yet."); } diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 607993112a..f5cccc8038 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -165,7 +165,7 @@ private static Action createNetworkAction(FedConnectionInstance connection) { Action action = factory.createAction(); // Name the newly created action; set its delay and type. - action.setName("networkMessage_" + networkMessageActionID++); + action.setName("networkMessage"); if (connection.serializer == SupportedSerializers.NATIVE) { action.setType(EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getType())); } else { @@ -702,6 +702,9 @@ private static Reaction getInitializationReaction() { var initializationReaction = LfFactory.eINSTANCE.createReaction(); var startup = LfFactory.eINSTANCE.createBuiltinTriggerRef(); startup.setType(BuiltinTrigger.STARTUP); + var a = LfFactory.eINSTANCE.createAttribute(); + a.setAttrName("_c_body"); + initializationReaction.getAttributes().add(a); initializationReaction.getTriggers().add(startup); var code = LfFactory.eINSTANCE.createCode(); code.setBody( diff --git a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java index 7419728fec..7c78a10df7 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java @@ -7,7 +7,6 @@ import org.lflang.ast.ASTUtils; import org.lflang.ast.FormattingUtil; import org.lflang.lf.Reactor; -import org.lflang.lf.Variable; /** Helper class to generate a main reactor */ public class FedMainEmitter { @@ -89,7 +88,6 @@ private CharSequence generateMainSignature( @_fed_config() main reactor %s { """ - .formatted( - paramList.equals("()") ? "" : paramList); + .formatted(paramList.equals("()") ? "" : paramList); } } diff --git a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java index 5e5c50a598..3c91b03537 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java @@ -492,7 +492,12 @@ public String toDOT() { CUtil.getName(downstreamNode.getReaction().getParent().tpr) + "." + downstreamNode.getReaction().getName(); - edges.append(" node_").append(labelHashCode).append(" -> node_").append(downstreamLabel.hashCode() & 0xfffffff).append(";\n"); + edges + .append(" node_") + .append(labelHashCode) + .append(" -> node_") + .append(downstreamLabel.hashCode() & 0xfffffff) + .append(";\n"); } } // Close the subgraph From 74baa0b863e3b1c7de00f0e0b1fc7d1a6f960ebd Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Wed, 12 Jul 2023 10:18:22 +0900 Subject: [PATCH 256/516] Resolve conflicts --- .../federated/generator/FedASTUtils.java | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 107cb4c593..c13cb96406 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -44,11 +44,8 @@ import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.xtext.xbase.lib.IteratorExtensions; import org.lflang.InferredType; -<<<<<<< HEAD import org.lflang.Target; -======= import org.lflang.MessageReporter; ->>>>>>> level-assignment import org.lflang.TargetProperty.CoordinationType; import org.lflang.TimeValue; import org.lflang.ast.ASTUtils; @@ -715,25 +712,8 @@ private static Reaction getInitializationReaction(FedConnectionInstance connecti initializationReaction.getAttributes().add(a); initializationReaction.getTriggers().add(startup); var code = LfFactory.eINSTANCE.createCode(); -<<<<<<< HEAD if (connection.srcFederate.targetConfig.target != Target.TS) { code.setBody( - """ - extern reaction_t* port_absent_reaction[]; - void enqueue_network_output_control_reactions(); - LF_PRINT_DEBUG("Adding network output control reaction to table."); - port_absent_reaction[self->sender_index] = &self->_lf__reaction_2; - LF_PRINT_DEBUG("Added network output control reaction to table. Enqueueing it..."); - enqueue_network_output_control_reactions(); - """); - } else { - code.setBody( - """ - // TODO: Figure out what to do for initialization reaction - """); - } -======= - code.setBody( """ extern reaction_t* port_absent_reaction[]; void enqueue_network_output_control_reactions(environment_t*); @@ -742,7 +722,12 @@ private static Reaction getInitializationReaction(FedConnectionInstance connecti LF_PRINT_DEBUG("Added network output control reaction to table. Enqueueing it..."); enqueue_network_output_control_reactions(self->base.environment); """); ->>>>>>> level-assignment + } else { + code.setBody( + """ + // TODO: Figure out what to do for initialization reaction + """); + } initializationReaction.setCode(code); return initializationReaction; } From 50ef79a16698eceb04681fe970b21b8d53791bf3 Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Tue, 11 Jul 2023 19:38:05 -0700 Subject: [PATCH 257/516] formatter and update reactor-c threads --- .../lflang/generator/c/CCmakeGenerator.java | 86 +++++++++---------- .../org/lflang/generator/c/CGenerator.java | 3 +- .../generator/c/CMainFunctionGenerator.java | 8 +- core/src/main/resources/lib/c/reactor-c | 2 +- test/C/src/rp2040/Timer.lf | 1 - 5 files changed, 45 insertions(+), 55 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 228dbc7ee5..6c4f97c462 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -120,32 +120,31 @@ CodeBuilder generateCMakeCode( // Setup the project header for different platforms switch (targetConfig.platformOptions.platform) { case ZEPHYR: - cMakeCode.pr("# Set default configuration file. To add custom configurations,"); - cMakeCode.pr("# pass -- -DOVERLAY_CONFIG=my_config.prj to either cmake or west"); - cMakeCode.pr("set(CONF_FILE prj_lf.conf)"); - if (targetConfig.platformOptions.board != null) { - cMakeCode.pr("# Selecting board specified in target property"); - cMakeCode.pr("set(BOARD " + targetConfig.platformOptions.board + ")"); - } else { - cMakeCode.pr("# Selecting default board"); - cMakeCode.pr("set(BOARD qemu_cortex_m3)"); - } - cMakeCode.pr("# We recommend Zephyr v3.3.0 but we are compatible with older versions also"); - cMakeCode.pr("find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE} 3.3.0)"); - cMakeCode.newLine(); - cMakeCode.pr("project(" + executableName + " LANGUAGES C)"); - cMakeCode.newLine(); - break; + cMakeCode.pr("# Set default configuration file. To add custom configurations,"); + cMakeCode.pr("# pass -- -DOVERLAY_CONFIG=my_config.prj to either cmake or west"); + cMakeCode.pr("set(CONF_FILE prj_lf.conf)"); + if (targetConfig.platformOptions.board != null) { + cMakeCode.pr("# Selecting board specified in target property"); + cMakeCode.pr("set(BOARD " + targetConfig.platformOptions.board + ")"); + } else { + cMakeCode.pr("# Selecting default board"); + cMakeCode.pr("set(BOARD qemu_cortex_m3)"); + } + cMakeCode.pr("# We recommend Zephyr v3.3.0 but we are compatible with older versions also"); + cMakeCode.pr("find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE} 3.3.0)"); + cMakeCode.newLine(); + cMakeCode.pr("project(" + executableName + " LANGUAGES C)"); + cMakeCode.newLine(); + break; case RP2040: - cMakeCode.pr("include(pico_sdk_import.cmake)"); - cMakeCode.pr("project(" + executableName + " LANGUAGES C CXX ASM)"); - cMakeCode.newLine(); + cMakeCode.pr("include(pico_sdk_import.cmake)"); + cMakeCode.pr("project(" + executableName + " LANGUAGES C CXX ASM)"); + cMakeCode.newLine(); default: - cMakeCode.pr("project(" + executableName + " LANGUAGES C)"); - cMakeCode.newLine(); + cMakeCode.pr("project(" + executableName + " LANGUAGES C)"); + cMakeCode.newLine(); } - // The Test build type is the Debug type plus coverage generation cMakeCode.pr("if(CMAKE_BUILD_TYPE STREQUAL \"Test\")"); cMakeCode.pr(" set(CMAKE_BUILD_TYPE \"Debug\")"); @@ -214,28 +213,28 @@ CodeBuilder generateCMakeCode( // Setup main target for different platforms switch (targetConfig.platformOptions.platform) { - case ZEPHYR: - cMakeCode.pr( - setUpMainTargetZephyr( - hasMain, - executableName, - Stream.concat(additionalSources.stream(), sources.stream()))); - break; + case ZEPHYR: + cMakeCode.pr( + setUpMainTargetZephyr( + hasMain, + executableName, + Stream.concat(additionalSources.stream(), sources.stream()))); + break; case RP2040: - cMakeCode.pr( - setUpMainTargetRp2040( - hasMain, - executableName, - Stream.concat(additionalSources.stream(), sources.stream()))); - break; + cMakeCode.pr( + setUpMainTargetRp2040( + hasMain, + executableName, + Stream.concat(additionalSources.stream(), sources.stream()))); + break; default: - cMakeCode.pr( - setUpMainTarget.getCmakeCode( - hasMain, - executableName, - Stream.concat(additionalSources.stream(), sources.stream()))); + cMakeCode.pr( + setUpMainTarget.getCmakeCode( + hasMain, + executableName, + Stream.concat(additionalSources.stream(), sources.stream()))); } - + cMakeCode.pr("target_link_libraries(${LF_MAIN_TARGET} PRIVATE core)"); cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC .)"); @@ -431,7 +430,7 @@ private static String setUpMainTargetRp2040( var code = new CodeBuilder(); // FIXME: remove this and move to lingo build code.pr("add_compile_options(-Wall -Wextra -DLF_UNTHREADED)"); - // initialize sdk + // initialize sdk code.pr("pico_sdk_init()"); code.newLine(); code.pr("add_subdirectory(core)"); @@ -461,7 +460,4 @@ private static String setUpMainTargetRp2040( return code.toString(); } - } - - diff --git a/core/src/main/java/org/lflang/generator/c/CGenerator.java b/core/src/main/java/org/lflang/generator/c/CGenerator.java index e966d4613c..f34a57beed 100644 --- a/core/src/main/java/org/lflang/generator/c/CGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CGenerator.java @@ -938,8 +938,7 @@ protected void copyTargetFiles() throws IOException { FileUtil.copyFileFromClassPath( "/lib/platform/rp2040/pico_sdk_import.cmake", fileConfig.getSrcGenPath(), true); // VS Code configurations - FileUtil.copyFileFromClassPath( - "/lib/platform/rp2040/launch.json", vscodePath, true); + FileUtil.copyFileFromClassPath("/lib/platform/rp2040/launch.json", vscodePath, true); } } diff --git a/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java b/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java index 3ffea7e332..5d11e27e05 100644 --- a/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java @@ -63,12 +63,8 @@ private String generateMainFunction() { " exit(res);", "}"); } else if (targetConfig.platformOptions.platform == Platform.RP2040) { - // Pico platform cannont use command line args. - return String.join( - "\n", - "int main(void) {", - " return lf_reactor_c_main(0, NULL);", - "}"); + // Pico platform cannont use command line args. + return String.join("\n", "int main(void) {", " return lf_reactor_c_main(0, NULL);", "}"); } else { return String.join( "\n", diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index d43d241808..2207668f6a 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit d43d241808ffd54364280acded943b74393b9843 +Subproject commit 2207668f6a703b9ea22e3c81668579b36cdd18db diff --git a/test/C/src/rp2040/Timer.lf b/test/C/src/rp2040/Timer.lf index 5aefd01f49..ef437a46f3 100644 --- a/test/C/src/rp2040/Timer.lf +++ b/test/C/src/rp2040/Timer.lf @@ -4,6 +4,5 @@ target C { main reactor { timer t1(0, 100 msec) - reaction(t1) {= printf("Logical Time: %d. \n", lf_time_logical()); =} } From 97e2897a880233869fe487f3181e60a1fe008208 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 12 Jul 2023 14:30:39 +0200 Subject: [PATCH 258/516] Add verifier tests for CI --- test/C/src/verifier/ADASModel.lf | 86 +++++++++++++++++++++++++++++ test/C/src/verifier/AircraftDoor.lf | 43 +++++++++++++++ test/C/src/verifier/Alarm.lf | 35 ++++++++++++ test/C/src/verifier/CoopSchedule.lf | 35 ++++++++++++ test/C/src/verifier/Election2.lf | 69 +++++++++++++++++++++++ test/C/src/verifier/PingPong.lf | 67 ++++++++++++++++++++++ test/C/src/verifier/ProcessMsg.lf | 39 +++++++++++++ test/C/src/verifier/ProcessSync.lf | 17 ++++++ test/C/src/verifier/Ring.lf | 48 ++++++++++++++++ test/C/src/verifier/SafeSend.lf | 61 ++++++++++++++++++++ test/C/src/verifier/Thermostat.lf | 66 ++++++++++++++++++++++ test/C/src/verifier/TrainDoor.lf | 35 ++++++++++++ test/C/src/verifier/UnsafeSend.lf | 61 ++++++++++++++++++++ 13 files changed, 662 insertions(+) create mode 100644 test/C/src/verifier/ADASModel.lf create mode 100644 test/C/src/verifier/AircraftDoor.lf create mode 100644 test/C/src/verifier/Alarm.lf create mode 100644 test/C/src/verifier/CoopSchedule.lf create mode 100644 test/C/src/verifier/Election2.lf create mode 100644 test/C/src/verifier/PingPong.lf create mode 100644 test/C/src/verifier/ProcessMsg.lf create mode 100644 test/C/src/verifier/ProcessSync.lf create mode 100644 test/C/src/verifier/Ring.lf create mode 100644 test/C/src/verifier/SafeSend.lf create mode 100644 test/C/src/verifier/Thermostat.lf create mode 100644 test/C/src/verifier/TrainDoor.lf create mode 100644 test/C/src/verifier/UnsafeSend.lf diff --git a/test/C/src/verifier/ADASModel.lf b/test/C/src/verifier/ADASModel.lf new file mode 100644 index 0000000000..38426fac26 --- /dev/null +++ b/test/C/src/verifier/ADASModel.lf @@ -0,0 +1,86 @@ +target C; + +preamble {= + typedef int c_frame_t; + typedef int l_frame_t; +=} + +reactor Camera { + output out :{=c_frame_t=}; + state frame:{=c_frame_t=}; + timer t(0, 17 msec); // 60 fps + reaction(t) -> out {= + // Capture a frame. + self->frame = 1; + lf_set(out, self->frame); + =} +} + +reactor LiDAR { + output out :{=l_frame_t=}; + state frame:{=l_frame_t=}; + timer t(0, 34 msec); // 30 fps + reaction(t) -> out {= + // Capture a frame. + self->frame = 2; + lf_set(out, self->frame); + =} +} + +reactor Pedal { + output out:int; + physical action a; + reaction(a) -> out {= + lf_set(out, 1); =} +} + +reactor Brakes { + input inADAS:int; + input inPedal:int; + state brakesApplied:int(0); + reaction(inADAS, inPedal) {= + // Actuate brakes. + self->brakesApplied = 1; + =} deadline(10 msec) {= =} +} + +reactor ADASProcessor { + input in1:{=l_frame_t=}; + input in2:{=c_frame_t=}; + output out1:int; + output out2:int; + logical action a(50 msec); + state requestStop:int; + reaction(in1,in2) -> a {= + // ... Detect danger + // and request stop. + lf_schedule(a, 0); + self->requestStop = 1; + =} + reaction(a) -> out1, out2 {= + if (self->requestStop == 1) + lf_set(out1, 1); + =} deadline(20 msec) {= =} +} + +reactor Dashboard { + input in:int; + state received:int; + reaction(in) {= + self->received = 1; =} +} + +@property(name="responsive", tactic="bmc", spec="G[0, 10 ms]((ADASModel_l_reaction_0 && (F[0](ADASModel_p_requestStop == 1))) ==> (F[0, 55 ms]( ADASModel_b_brakesApplied == 1 )))", expect=true) +main reactor ADASModel { + c = new Camera(); + l = new LiDAR(); + p = new ADASProcessor(); + b = new Brakes(); + d = new Dashboard(); + // p = new Pedal(); + l.out -> p.in1; + c.out -> p.in2; + p.out2 -> d.in; + p.out1 -> b.inADAS after 5 msec; + // p.out -> b.inPedal; +} diff --git a/test/C/src/verifier/AircraftDoor.lf b/test/C/src/verifier/AircraftDoor.lf new file mode 100644 index 0000000000..c9fc6d3511 --- /dev/null +++ b/test/C/src/verifier/AircraftDoor.lf @@ -0,0 +1,43 @@ +target C; + +reactor Controller { + output out:int; + reaction(startup) -> out {= + lf_set(out, 1); + =} +} + +reactor Vision { + + input in:int; + output out:int; + state ramp:int(0); + + reaction(in) -> out {= + if (self->ramp == 1) { + lf_set(out, 0); // 0 = Do not open. + } else { + lf_set(out, 1); // 1 = Open. + } + =} +} + +reactor Door { + input in:int; + state doorOpen:int; + reaction(in) {= + if (in->value == 1) + self->doorOpen = 1; // Open + else if (in->value == 0) + self->doorOpen = 0; // Closed + =} +} + +@property(name="vision_works", tactic="bmc", spec="((AircraftDoor_vision_ramp == 0) ==> (G[0 sec](AircraftDoor_door_reaction_0 ==> (AircraftDoor_door_doorOpen == 1))))", expect=true) +main reactor AircraftDoor { + controller = new Controller(); + vision = new Vision(); + door = new Door(); + controller.out -> vision.in; + vision.out -> door.in; +} \ No newline at end of file diff --git a/test/C/src/verifier/Alarm.lf b/test/C/src/verifier/Alarm.lf new file mode 100644 index 0000000000..3aeb3ef264 --- /dev/null +++ b/test/C/src/verifier/Alarm.lf @@ -0,0 +1,35 @@ +target C; + +reactor Controller { + + output out:int; + output out2:int; + state fault:int; + + logical action turnOff(1 sec):int; + + @label("Computation") + reaction(startup) -> out, out2, turnOff {= + // ... Operation + self->fault = 1; // Fault occurs + + // Fault handling + if (self->fault == 1) { + lf_schedule(turnOff, 0); // Wrong action value. Should be 1. + lf_set(out, 5); + lf_set(out2, 10); + } + =} + + @label("Stop") + reaction(turnOff) {= + // Trigger the alarm and reset fault. + if (turnOff->value == 1) + self->fault = 0; + =} +} + +@property(name="machine_stops_within_1_sec", tactic="bmc", spec="G[0, 1 sec]((Alarm_c_reaction_0) ==> F(0, 1 sec](Alarm_c_reaction_1)))", expect=true) +main reactor { + c = new Controller(); +} diff --git a/test/C/src/verifier/CoopSchedule.lf b/test/C/src/verifier/CoopSchedule.lf new file mode 100644 index 0000000000..6cf88f3266 --- /dev/null +++ b/test/C/src/verifier/CoopSchedule.lf @@ -0,0 +1,35 @@ +target C + +reactor Trigger { + output out:int + timer t(0,1 usec) + + reaction(t) -> out {= + lf_set(out, 1); + =} +} + +reactor Task { + input cnt:int; + state counter:int(0); + + reaction(cnt) {= + self->counter += 2; // Should be 1. + =} +} + +@property(name="upperbound", tactic="bmc", spec="G[0, 1 usec]((CoopSchedule_task1_counter + CoopSchedule_task2_counter + CoopSchedule_task3_counter + CoopSchedule_task4_counter + CoopSchedule_task5_counter) < 15)", expect=false) +main reactor { + trigger = new Trigger() + // TODO: Put these reactors in a bank. + task1 = new Task() + task2 = new Task() + task3 = new Task() + task4 = new Task() + task5 = new Task() + trigger.out -> task1.cnt + trigger.out -> task2.cnt + trigger.out -> task3.cnt + trigger.out -> task4.cnt + trigger.out -> task5.cnt +} \ No newline at end of file diff --git a/test/C/src/verifier/Election2.lf b/test/C/src/verifier/Election2.lf new file mode 100644 index 0000000000..37540325de --- /dev/null +++ b/test/C/src/verifier/Election2.lf @@ -0,0 +1,69 @@ +target C + +reactor Node0 { + input in:int + output out:int + state id:int + state elected:int(0) + + reaction(startup) -> out {= + self->id = 0; + lf_set(out, self->id); + =} + reaction(in) -> out {= + if (in->value > self->id) { + lf_set(out, in->value); + } else if (in->value == self->id) { + self->elected = 1; + } + =} +} + +reactor Node1 { + input in:int + output out:int + state id:int + state elected:int(0) + + reaction(startup) -> out {= + self->id = 1; + lf_set(out, self->id); + =} + reaction(in) -> out {= + if (in->value > self->id) { + lf_set(out, in->value); + } else if (in->value == self->id) { + self->elected = 1; + } + =} +} + +reactor Node2 { + input in:int + output out:int + state id:int + state elected:int(0) + + reaction(startup) -> out {= + self->id = 2; + lf_set(out, self->id); + =} + reaction(in) -> out {= + if (in->value > self->id) { + lf_set(out, in->value); + } else if (in->value == self->id) { + self->elected = 1; + } + =} +} + +// This property being checked does not hold. A valid property is F[0, 30 msec]((Election2_i0_elected + Election2_i1_elected + Election2_i2_elected) == 1) +@property(name="exactly_one_elected", tactic="bmc", spec="F[0, 20 msec]((Election2_i0_elected + Election2_i1_elected + Election2_i2_elected) == 1)", expect=false) +main reactor { + i0 = new Node0() + i1 = new Node1() + i2 = new Node2() + i0.out -> i1.in after 10 msec + i1.out -> i2.in after 10 msec + i2.out -> i0.in after 10 msec +} \ No newline at end of file diff --git a/test/C/src/verifier/PingPong.lf b/test/C/src/verifier/PingPong.lf new file mode 100644 index 0000000000..23e5936cb7 --- /dev/null +++ b/test/C/src/verifier/PingPong.lf @@ -0,0 +1,67 @@ +/** + * Basic benchmark from the Savina benchmark suite that is intended to measure + * message-passing overhead. This is based on + * https://www.scala-lang.org/old/node/54 See + * https://shamsimam.github.io/papers/2014-agere-savina.pdf. + * + * Ping introduces a microstep delay using a logical action to break the + * causality loop. + * + * To get a sense, some (informal) results for 1,000,000 ping-pongs on my Mac: + * + * - Unthreaded: 97 msec + * - Threaded: 265 msec + * + * There is no parallelism in this application, so it does not benefit from + * being being threaded, just some additional overhead. + * + * These measurements are total execution time, including startup and shutdown. + * These are about an order of magnitude faster than anything reported in the + * paper. + */ +target C { + fast: true +} + +reactor Ping { + input receive: int + output send: int + state pingsLeft: int(10) + logical action serve(1 nsec) + + reaction(startup) -> serve {= + self->pingsLeft = 10; + lf_schedule(serve, 0); + =} + + reaction(serve) -> send {= + lf_set(send, self->pingsLeft); + self->pingsLeft -= 1; + =} + + reaction(receive) -> serve {= + if (self->pingsLeft > 0) { + lf_schedule(serve, 0); + } + =} +} + +reactor Pong { + input receive: int + output send: int + state count: int(0) + state expected: int(10) + + reaction(receive) -> send {= + self->count += 1; + lf_set(send, receive->value); + =} +} + +@property(name="no_two_consecutive_pings", tactic="bmc", spec="G[0, 4 nsec](PingPong_ping_reaction_1 ==> X(!PingPong_ping_reaction_1))", expect=false) +main reactor PingPong { + ping = new Ping() + pong = new Pong() + ping.send -> pong.receive + pong.send -> ping.receive +} \ No newline at end of file diff --git a/test/C/src/verifier/ProcessMsg.lf b/test/C/src/verifier/ProcessMsg.lf new file mode 100644 index 0000000000..b936b8909a --- /dev/null +++ b/test/C/src/verifier/ProcessMsg.lf @@ -0,0 +1,39 @@ +target C + +reactor Task { + input in:int + output out:int + + state messageSent:int + state counter:int(0) + state panic:int(0) + + timer t(0, 1 nsec) + + logical action updateMessage + + reaction(startup) {= + self->messageSent = 0; + =} + reaction(t) -> out {= + lf_set(out, self->messageSent); + =} + reaction(in) -> updateMessage {= + /* Check for invalid message. */ + if (in->value != self->messageSent) { + self->panic = 1; + } + lf_schedule(updateMessage, 0); + self->counter += 1; + =} + reaction(updateMessage) {= + /* Increment the last word of the 16-byte message. */ + self->messageSent += 1; + =} +} + +@property(name="panic_free", tactic="bmc", spec="G[5 nsec](ProcessMsg_task_panic != 1)", expect=true) +main reactor { + task = new Task() + task.out -> task.in +} \ No newline at end of file diff --git a/test/C/src/verifier/ProcessSync.lf b/test/C/src/verifier/ProcessSync.lf new file mode 100644 index 0000000000..20bc498b46 --- /dev/null +++ b/test/C/src/verifier/ProcessSync.lf @@ -0,0 +1,17 @@ +target C + +reactor Task { + /* Define the counters used in the demo application... */ + state tm_synchronization_processing_counter:int(0) + timer t(0,1 nsec) + + reaction(t) {= + /* Increment the counter. */ + self->tm_synchronization_processing_counter += 1; + =} +} + +@property(name="correctness", tactic="bmc", spec="G[2 nsec](ProcessSync_task_tm_synchronization_processing_counter == 3)", expect=true) +main reactor { + task = new Task() +} \ No newline at end of file diff --git a/test/C/src/verifier/Ring.lf b/test/C/src/verifier/Ring.lf new file mode 100644 index 0000000000..d2a3e6db32 --- /dev/null +++ b/test/C/src/verifier/Ring.lf @@ -0,0 +1,48 @@ +/** + * There seems to be some problem with infinite feedback loops. + */ +target C + +reactor Source { + input in:int + output out:int + logical action start(1 nsec) + state received:int + reaction(startup) -> start {= + self->received = 0; + lf_schedule(start, 0); + =} + reaction(start) -> out {= + lf_set(out, self->received); + =} + /* Uncomment the following to debug feedback loops. */ + // reaction(in) -> start {= + reaction(in) {= + self->received = in->value; + // lf_schedule(start, 0); + =} +} + +reactor Node { + input in:int + output out:int + reaction(in) -> out {= + lf_set(out, in->value + 1); + =} +} + +@property(name="full_circle", tactic="bmc", spec="F[0, 10 nsec](Ring_s_reaction_2)", expect=true) +main reactor { + s = new Source() + n1 = new Node() + n2 = new Node() + n3 = new Node() + n4 = new Node() + n5 = new Node() + s.out -> n1.in after 1 nsec + n1.out -> n2.in after 1 nsec + n2.out -> n3.in after 1 nsec + n3.out -> n4.in after 1 nsec + n4.out -> n5.in after 1 nsec + n5.out -> s.in after 1 nsec +} diff --git a/test/C/src/verifier/SafeSend.lf b/test/C/src/verifier/SafeSend.lf new file mode 100644 index 0000000000..cf1dde7bb1 --- /dev/null +++ b/test/C/src/verifier/SafeSend.lf @@ -0,0 +1,61 @@ +/** + * (Original) Description: + * This example illustrates the "Verify Absence-of-Errors" mode. + * The server expects a tuple `{REQUEST,PID-OF-SENDER}` + * but the main sends to it an atom instead of its pid, then + * generating an exception when the server tries to send back + * a response to what he assumes to be a pid. + * + * The verification step discovers a *genuine* counter-example. + * To inspect the error trace run bfc on the generated model + * and look at the trace alongside the dot model of the ACS. + */ + +target C; + +reactor Client { + input in:int; + output out:int; + state req:int(0); + + reaction(startup) -> out {= + // Sends a query + self->req = 1; // 1 is valid. 0 is invalid. + lf_set(out, self->req); + =} + + reaction(in) {= + // Should not be invoked. + self->req = 0; + =} +} + +reactor Server { + + input in:int; + output out:int; + state error:int; + logical action err; + + reaction(in) -> out, err {= + if (in->value == 0) { + lf_schedule(err, 0); + } + else { + lf_set(out, in->value); + } + =} + + // Error handling logic. + reaction(err) {= + self->error = 1; + =} +} + +@property(name="success", tactic="bmc", spec="(F[0, 1 sec](SafeSend_c_reaction_1))", expect=true) +main reactor { + c = new Client(); + s = new Server(); + c.out -> s.in after 1 nsec; + s.out -> c.in; +} diff --git a/test/C/src/verifier/Thermostat.lf b/test/C/src/verifier/Thermostat.lf new file mode 100644 index 0000000000..4141201902 --- /dev/null +++ b/test/C/src/verifier/Thermostat.lf @@ -0,0 +1,66 @@ +/** + * A thermostat model based on + * Lee & Seshia Chapter 3, Example 3.5. + */ +target C + +reactor Environment { + input heatOn:int + output temperature:int + timer t(1 nsec, 10 sec) + state _heatOn:int + state _temperature:int + reaction(startup) {= + self->_temperature = 19; + =} + reaction(t) -> temperature {= + if (self->_heatOn == 0) { + self->_temperature -= 1; + } + else { + self->_temperature += 1; + } + lf_set(temperature, self->_temperature); + =} + reaction(heatOn) {= + self->_heatOn = heatOn->value; + =} +} + +reactor _Thermostat { + input temperature:int + output heatOn:int // 0 = OFF, 1 = ON + state _mode:int // 0 = COOLING, 1 = HEATING + logical action changeMode; + reaction(startup) {= + self->_mode = 0; + =} + reaction(temperature) -> heatOn, changeMode {= + if (self->_mode == 0) { + if (temperature->value <= 18) { + lf_set(heatOn, 1); + lf_schedule(changeMode, 0); + } + } + else if (self->_mode == 0) { + if (temperature->value >= 22) { + lf_set(heatOn, 0); + lf_schedule(changeMode, 0); + } + } + =} + reaction(changeMode) {= + if (self->_mode == 0) + self->_mode = 1; + else + self->_mode = 0; + =} +} + +@property(name="correctness", tactic="bmc", spec="G[0, 20 sec](((Thermostat_t_temperature <= 18) ==> F[0](Thermostat_t__mode == 1)) && ((Thermostat_t_temperature >= 22) ==> F[0](Thermostat_t__mode == 0)))", expect=true) +main reactor { + e = new Environment() + t = new _Thermostat() + e.temperature -> t.temperature + t.heatOn -> e.heatOn +} \ No newline at end of file diff --git a/test/C/src/verifier/TrainDoor.lf b/test/C/src/verifier/TrainDoor.lf new file mode 100644 index 0000000000..88d3950aba --- /dev/null +++ b/test/C/src/verifier/TrainDoor.lf @@ -0,0 +1,35 @@ +target C; + +reactor Controller { + output out1:int; + output out2:int; + reaction(startup) -> out1, out2 {= + lf_set(out1, 1); + lf_set(out2, 2); + =} +} + +reactor Train { + input in:int; + state received:int; + reaction(in) {= + self->received = in->value; + =} +} + +reactor Door { + input in:int; + state received:int; + reaction(in) {= + self->received = in->value; + =} +} + +@property(name="train_does_not_move_until_door_closes", tactic="bmc", spec="(!TrainDoor_t_reaction_0)U[0, 1 sec](TrainDoor_d_reaction_0)", expect=false) +main reactor { + c = new Controller(); + t = new Train(); + d = new Door(); + c.out1 -> t.in after 1 sec; + c.out2 -> d.in after 1 sec; +} diff --git a/test/C/src/verifier/UnsafeSend.lf b/test/C/src/verifier/UnsafeSend.lf new file mode 100644 index 0000000000..7d03892069 --- /dev/null +++ b/test/C/src/verifier/UnsafeSend.lf @@ -0,0 +1,61 @@ +/** + * (Original) Description: + * This example illustrates the "Verify Absence-of-Errors" mode. + * The server expects a tuple `{REQUEST,PID-OF-SENDER}` + * but the main sends to it an atom instead of its pid, then + * generating an exception when the server tries to send back + * a response to what he assumes to be a pid. + * + * The verification step discovers a *genuine* counter-example. + * To inspect the error trace run bfc on the generated model + * and look at the trace alongside the dot model of the ACS. + */ + +target C; + +reactor Client { + input in:int; + output out:int; + state req:int(0); + + reaction(startup) -> out {= + // Sends a query + self->req = 0; // 1 is valid. 0 is invalid. + lf_set(out, self->req); + =} + + reaction(in) {= + // Should not be invoked. + self->req = 0; + =} +} + +reactor Server { + + input in:int; + output out:int; + state error:int; + logical action err(1 nsec); + + reaction(in) -> out, err {= + if (in->value == 0) { + lf_schedule(err, 0); + } + else { + lf_set(out, in->value); + } + =} + + // Error handling logic. + reaction(err) {= + self->error = 1; + =} +} + +@property(name="success", tactic="bmc", spec="(F[0, 5 nsec](UnsafeSend_c_reaction_1))", expect=false) +main reactor { + c = new Client(); + s = new Server(); + c.out -> s.in after 2 nsec; + s.out -> c.in after 2 nsec; +} From e588226f08b95480b2a9f45e1fa5a683afa99bc4 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 12 Jul 2023 14:33:51 +0200 Subject: [PATCH 259/516] Apply spotless --- test/C/src/verifier/ADASModel.lf | 131 +++++++++++++------------ test/C/src/verifier/AircraftDoor.lf | 67 +++++++------ test/C/src/verifier/Alarm.lf | 53 +++++----- test/C/src/verifier/CoopSchedule.lf | 49 +++++----- test/C/src/verifier/Election2.lf | 145 +++++++++++++++------------- test/C/src/verifier/PingPong.lf | 85 ++++++++-------- test/C/src/verifier/ProcessMsg.lf | 69 ++++++------- test/C/src/verifier/ProcessSync.lf | 24 +++-- test/C/src/verifier/Ring.lf | 78 +++++++-------- test/C/src/verifier/SafeSend.lf | 94 +++++++++--------- test/C/src/verifier/Thermostat.lf | 111 ++++++++++----------- test/C/src/verifier/TrainDoor.lf | 49 +++++----- test/C/src/verifier/UnsafeSend.lf | 94 +++++++++--------- 13 files changed, 538 insertions(+), 511 deletions(-) diff --git a/test/C/src/verifier/ADASModel.lf b/test/C/src/verifier/ADASModel.lf index 38426fac26..41f8e27ace 100644 --- a/test/C/src/verifier/ADASModel.lf +++ b/test/C/src/verifier/ADASModel.lf @@ -1,86 +1,93 @@ -target C; +target C preamble {= - typedef int c_frame_t; - typedef int l_frame_t; + typedef int c_frame_t; + typedef int l_frame_t; =} reactor Camera { - output out :{=c_frame_t=}; - state frame:{=c_frame_t=}; - timer t(0, 17 msec); // 60 fps - reaction(t) -> out {= - // Capture a frame. - self->frame = 1; - lf_set(out, self->frame); - =} + output out: {= c_frame_t =} + state frame: {= c_frame_t =} + timer t(0, 17 msec) // 60 fps + + reaction(t) -> out {= + // Capture a frame. + self->frame = 1; + lf_set(out, self->frame); + =} } reactor LiDAR { - output out :{=l_frame_t=}; - state frame:{=l_frame_t=}; - timer t(0, 34 msec); // 30 fps - reaction(t) -> out {= - // Capture a frame. - self->frame = 2; - lf_set(out, self->frame); - =} + output out: {= l_frame_t =} + state frame: {= l_frame_t =} + timer t(0, 34 msec) // 30 fps + + reaction(t) -> out {= + // Capture a frame. + self->frame = 2; + lf_set(out, self->frame); + =} } reactor Pedal { - output out:int; - physical action a; - reaction(a) -> out {= - lf_set(out, 1); =} + output out: int + physical action a + + reaction(a) -> out {= lf_set(out, 1); =} } reactor Brakes { - input inADAS:int; - input inPedal:int; - state brakesApplied:int(0); - reaction(inADAS, inPedal) {= - // Actuate brakes. - self->brakesApplied = 1; - =} deadline(10 msec) {= =} + input inADAS: int + input inPedal: int + state brakesApplied: int = 0 + + reaction(inADAS, inPedal) {= + // Actuate brakes. + self->brakesApplied = 1; + =} deadline(10 msec) {= =} } reactor ADASProcessor { - input in1:{=l_frame_t=}; - input in2:{=c_frame_t=}; - output out1:int; - output out2:int; - logical action a(50 msec); - state requestStop:int; - reaction(in1,in2) -> a {= - // ... Detect danger - // and request stop. - lf_schedule(a, 0); - self->requestStop = 1; - =} - reaction(a) -> out1, out2 {= - if (self->requestStop == 1) - lf_set(out1, 1); - =} deadline(20 msec) {= =} + input in1: {= l_frame_t =} + input in2: {= c_frame_t =} + output out1: int + output out2: int + logical action a(50 msec) + state requestStop: int + + reaction(in1, in2) -> a {= + // ... Detect danger + // and request stop. + lf_schedule(a, 0); + self->requestStop = 1; + =} + + reaction(a) -> out1, out2 {= + if (self->requestStop == 1) + lf_set(out1, 1); + =} deadline(20 msec) {= =} } reactor Dashboard { - input in:int; - state received:int; - reaction(in) {= - self->received = 1; =} + input in: int + state received: int + + reaction(in) {= self->received = 1; =} } -@property(name="responsive", tactic="bmc", spec="G[0, 10 ms]((ADASModel_l_reaction_0 && (F[0](ADASModel_p_requestStop == 1))) ==> (F[0, 55 ms]( ADASModel_b_brakesApplied == 1 )))", expect=true) +@property( + name = "responsive", + tactic = "bmc", + spec = "G[0, 10 ms]((ADASModel_l_reaction_0 && (F[0](ADASModel_p_requestStop == 1))) ==> (F[0, 55 ms]( ADASModel_b_brakesApplied == 1 )))", + expect = true) main reactor ADASModel { - c = new Camera(); - l = new LiDAR(); - p = new ADASProcessor(); - b = new Brakes(); - d = new Dashboard(); - // p = new Pedal(); - l.out -> p.in1; - c.out -> p.in2; - p.out2 -> d.in; - p.out1 -> b.inADAS after 5 msec; - // p.out -> b.inPedal; + c = new Camera() + l = new LiDAR() + p = new ADASProcessor() + b = new Brakes() + d = new Dashboard() + l.out -> p.in1 // p = new Pedal(); + c.out -> p.in2 + p.out2 -> d.in + p.out1 -> b.inADAS after 5 msec // p.out -> b.inPedal; } diff --git a/test/C/src/verifier/AircraftDoor.lf b/test/C/src/verifier/AircraftDoor.lf index c9fc6d3511..ab6390ffbe 100644 --- a/test/C/src/verifier/AircraftDoor.lf +++ b/test/C/src/verifier/AircraftDoor.lf @@ -1,43 +1,46 @@ -target C; +target C reactor Controller { - output out:int; - reaction(startup) -> out {= - lf_set(out, 1); - =} + output out: int + + reaction(startup) -> out {= lf_set(out, 1); =} } reactor Vision { - - input in:int; - output out:int; - state ramp:int(0); - - reaction(in) -> out {= - if (self->ramp == 1) { - lf_set(out, 0); // 0 = Do not open. - } else { - lf_set(out, 1); // 1 = Open. - } - =} + input in: int + output out: int + state ramp: int = 0 + + reaction(in) -> out {= + if (self->ramp == 1) { + lf_set(out, 0); // 0 = Do not open. + } else { + lf_set(out, 1); // 1 = Open. + } + =} } reactor Door { - input in:int; - state doorOpen:int; - reaction(in) {= - if (in->value == 1) - self->doorOpen = 1; // Open - else if (in->value == 0) - self->doorOpen = 0; // Closed - =} + input in: int + state doorOpen: int + + reaction(in) {= + if (in->value == 1) + self->doorOpen = 1; // Open + else if (in->value == 0) + self->doorOpen = 0; // Closed + =} } -@property(name="vision_works", tactic="bmc", spec="((AircraftDoor_vision_ramp == 0) ==> (G[0 sec](AircraftDoor_door_reaction_0 ==> (AircraftDoor_door_doorOpen == 1))))", expect=true) +@property( + name = "vision_works", + tactic = "bmc", + spec = "((AircraftDoor_vision_ramp == 0) ==> (G[0 sec](AircraftDoor_door_reaction_0 ==> (AircraftDoor_door_doorOpen == 1))))", + expect = true) main reactor AircraftDoor { - controller = new Controller(); - vision = new Vision(); - door = new Door(); - controller.out -> vision.in; - vision.out -> door.in; -} \ No newline at end of file + controller = new Controller() + vision = new Vision() + door = new Door() + controller.out -> vision.in + vision.out -> door.in +} diff --git a/test/C/src/verifier/Alarm.lf b/test/C/src/verifier/Alarm.lf index 3aeb3ef264..6449f8d626 100644 --- a/test/C/src/verifier/Alarm.lf +++ b/test/C/src/verifier/Alarm.lf @@ -1,35 +1,38 @@ -target C; +target C reactor Controller { - - output out:int; - output out2:int; - state fault:int; + output out: int + output out2: int + state fault: int - logical action turnOff(1 sec):int; + logical action turnOff(1 sec): int - @label("Computation") - reaction(startup) -> out, out2, turnOff {= - // ... Operation - self->fault = 1; // Fault occurs + @label("Computation") + reaction(startup) -> out, out2, turnOff {= + // ... Operation + self->fault = 1; // Fault occurs - // Fault handling - if (self->fault == 1) { - lf_schedule(turnOff, 0); // Wrong action value. Should be 1. - lf_set(out, 5); - lf_set(out2, 10); - } - =} + // Fault handling + if (self->fault == 1) { + lf_schedule(turnOff, 0); // Wrong action value. Should be 1. + lf_set(out, 5); + lf_set(out2, 10); + } + =} - @label("Stop") - reaction(turnOff) {= - // Trigger the alarm and reset fault. - if (turnOff->value == 1) - self->fault = 0; - =} + @label("Stop") + reaction(turnOff) {= + // Trigger the alarm and reset fault. + if (turnOff->value == 1) + self->fault = 0; + =} } -@property(name="machine_stops_within_1_sec", tactic="bmc", spec="G[0, 1 sec]((Alarm_c_reaction_0) ==> F(0, 1 sec](Alarm_c_reaction_1)))", expect=true) +@property( + name = "machine_stops_within_1_sec", + tactic = "bmc", + spec = "G[0, 1 sec]((Alarm_c_reaction_0) ==> F(0, 1 sec](Alarm_c_reaction_1)))", + expect = true) main reactor { - c = new Controller(); + c = new Controller() } diff --git a/test/C/src/verifier/CoopSchedule.lf b/test/C/src/verifier/CoopSchedule.lf index 6cf88f3266..c755ef7559 100644 --- a/test/C/src/verifier/CoopSchedule.lf +++ b/test/C/src/verifier/CoopSchedule.lf @@ -1,35 +1,36 @@ target C reactor Trigger { - output out:int - timer t(0,1 usec) + output out: int + timer t(0, 1 usec) - reaction(t) -> out {= - lf_set(out, 1); - =} + reaction(t) -> out {= lf_set(out, 1); =} } reactor Task { - input cnt:int; - state counter:int(0); + input cnt: int + state counter: int = 0 - reaction(cnt) {= - self->counter += 2; // Should be 1. - =} + reaction(cnt) {= + self->counter += 2; // Should be 1. + =} } -@property(name="upperbound", tactic="bmc", spec="G[0, 1 usec]((CoopSchedule_task1_counter + CoopSchedule_task2_counter + CoopSchedule_task3_counter + CoopSchedule_task4_counter + CoopSchedule_task5_counter) < 15)", expect=false) +@property( + name = "upperbound", + tactic = "bmc", + spec = "G[0, 1 usec]((CoopSchedule_task1_counter + CoopSchedule_task2_counter + CoopSchedule_task3_counter + CoopSchedule_task4_counter + CoopSchedule_task5_counter) < 15)", + expect = false) main reactor { - trigger = new Trigger() - // TODO: Put these reactors in a bank. - task1 = new Task() - task2 = new Task() - task3 = new Task() - task4 = new Task() - task5 = new Task() - trigger.out -> task1.cnt - trigger.out -> task2.cnt - trigger.out -> task3.cnt - trigger.out -> task4.cnt - trigger.out -> task5.cnt -} \ No newline at end of file + trigger = new Trigger() + task1 = new Task() // TODO: Put these reactors in a bank. + task2 = new Task() + task3 = new Task() + task4 = new Task() + task5 = new Task() + trigger.out -> task1.cnt + trigger.out -> task2.cnt + trigger.out -> task3.cnt + trigger.out -> task4.cnt + trigger.out -> task5.cnt +} diff --git a/test/C/src/verifier/Election2.lf b/test/C/src/verifier/Election2.lf index 37540325de..a94ac19429 100644 --- a/test/C/src/verifier/Election2.lf +++ b/test/C/src/verifier/Election2.lf @@ -1,69 +1,76 @@ -target C - -reactor Node0 { - input in:int - output out:int - state id:int - state elected:int(0) - - reaction(startup) -> out {= - self->id = 0; - lf_set(out, self->id); - =} - reaction(in) -> out {= - if (in->value > self->id) { - lf_set(out, in->value); - } else if (in->value == self->id) { - self->elected = 1; - } - =} -} - -reactor Node1 { - input in:int - output out:int - state id:int - state elected:int(0) - - reaction(startup) -> out {= - self->id = 1; - lf_set(out, self->id); - =} - reaction(in) -> out {= - if (in->value > self->id) { - lf_set(out, in->value); - } else if (in->value == self->id) { - self->elected = 1; - } - =} -} - -reactor Node2 { - input in:int - output out:int - state id:int - state elected:int(0) - - reaction(startup) -> out {= - self->id = 2; - lf_set(out, self->id); - =} - reaction(in) -> out {= - if (in->value > self->id) { - lf_set(out, in->value); - } else if (in->value == self->id) { - self->elected = 1; - } - =} -} - -// This property being checked does not hold. A valid property is F[0, 30 msec]((Election2_i0_elected + Election2_i1_elected + Election2_i2_elected) == 1) -@property(name="exactly_one_elected", tactic="bmc", spec="F[0, 20 msec]((Election2_i0_elected + Election2_i1_elected + Election2_i2_elected) == 1)", expect=false) -main reactor { - i0 = new Node0() - i1 = new Node1() - i2 = new Node2() - i0.out -> i1.in after 10 msec - i1.out -> i2.in after 10 msec - i2.out -> i0.in after 10 msec -} \ No newline at end of file +target C + +reactor Node0 { + input in: int + output out: int + state id: int + state elected: int = 0 + + reaction(startup) -> out {= + self->id = 0; + lf_set(out, self->id); + =} + + reaction(in) -> out {= + if (in->value > self->id) { + lf_set(out, in->value); + } else if (in->value == self->id) { + self->elected = 1; + } + =} +} + +reactor Node1 { + input in: int + output out: int + state id: int + state elected: int = 0 + + reaction(startup) -> out {= + self->id = 1; + lf_set(out, self->id); + =} + + reaction(in) -> out {= + if (in->value > self->id) { + lf_set(out, in->value); + } else if (in->value == self->id) { + self->elected = 1; + } + =} +} + +reactor Node2 { + input in: int + output out: int + state id: int + state elected: int = 0 + + reaction(startup) -> out {= + self->id = 2; + lf_set(out, self->id); + =} + + reaction(in) -> out {= + if (in->value > self->id) { + lf_set(out, in->value); + } else if (in->value == self->id) { + self->elected = 1; + } + =} +} + +// This property being checked does not hold. A valid property is F[0, 30 msec]((Election2_i0_elected + Election2_i1_elected + Election2_i2_elected) == 1) +@property( + name = "exactly_one_elected", + tactic = "bmc", + spec = "F[0, 20 msec]((Election2_i0_elected + Election2_i1_elected + Election2_i2_elected) == 1)", + expect = false) +main reactor { + i0 = new Node0() + i1 = new Node1() + i2 = new Node2() + i0.out -> i1.in after 10 msec + i1.out -> i2.in after 10 msec + i2.out -> i0.in after 10 msec +} diff --git a/test/C/src/verifier/PingPong.lf b/test/C/src/verifier/PingPong.lf index 23e5936cb7..c8f45b400b 100644 --- a/test/C/src/verifier/PingPong.lf +++ b/test/C/src/verifier/PingPong.lf @@ -1,67 +1,68 @@ /** - * Basic benchmark from the Savina benchmark suite that is intended to measure - * message-passing overhead. This is based on - * https://www.scala-lang.org/old/node/54 See + * Basic benchmark from the Savina benchmark suite that is intended to measure message-passing + * overhead. This is based on https://www.scala-lang.org/old/node/54 See * https://shamsimam.github.io/papers/2014-agere-savina.pdf. * - * Ping introduces a microstep delay using a logical action to break the - * causality loop. + * Ping introduces a microstep delay using a logical action to break the causality loop. * * To get a sense, some (informal) results for 1,000,000 ping-pongs on my Mac: * * - Unthreaded: 97 msec * - Threaded: 265 msec * - * There is no parallelism in this application, so it does not benefit from - * being being threaded, just some additional overhead. + * There is no parallelism in this application, so it does not benefit from being being threaded, + * just some additional overhead. * - * These measurements are total execution time, including startup and shutdown. - * These are about an order of magnitude faster than anything reported in the - * paper. + * These measurements are total execution time, including startup and shutdown. These are about an + * order of magnitude faster than anything reported in the paper. */ target C { - fast: true + fast: true } reactor Ping { - input receive: int - output send: int - state pingsLeft: int(10) - logical action serve(1 nsec) + input receive: int + output send: int + state pingsLeft: int = 10 + logical action serve(1 nsec) - reaction(startup) -> serve {= - self->pingsLeft = 10; - lf_schedule(serve, 0); - =} + reaction(startup) -> serve {= + self->pingsLeft = 10; + lf_schedule(serve, 0); + =} - reaction(serve) -> send {= - lf_set(send, self->pingsLeft); - self->pingsLeft -= 1; - =} + reaction(serve) -> send {= + lf_set(send, self->pingsLeft); + self->pingsLeft -= 1; + =} - reaction(receive) -> serve {= - if (self->pingsLeft > 0) { - lf_schedule(serve, 0); - } - =} + reaction(receive) -> serve {= + if (self->pingsLeft > 0) { + lf_schedule(serve, 0); + } + =} } reactor Pong { - input receive: int - output send: int - state count: int(0) - state expected: int(10) + input receive: int + output send: int + state count: int = 0 + state expected: int = 10 - reaction(receive) -> send {= - self->count += 1; - lf_set(send, receive->value); - =} + reaction(receive) -> send {= + self->count += 1; + lf_set(send, receive->value); + =} } -@property(name="no_two_consecutive_pings", tactic="bmc", spec="G[0, 4 nsec](PingPong_ping_reaction_1 ==> X(!PingPong_ping_reaction_1))", expect=false) +@property( + name = "no_two_consecutive_pings", + tactic = "bmc", + spec = "G[0, 4 nsec](PingPong_ping_reaction_1 ==> X(!PingPong_ping_reaction_1))", + expect = false) main reactor PingPong { - ping = new Ping() - pong = new Pong() - ping.send -> pong.receive - pong.send -> ping.receive -} \ No newline at end of file + ping = new Ping() + pong = new Pong() + ping.send -> pong.receive + pong.send -> ping.receive +} diff --git a/test/C/src/verifier/ProcessMsg.lf b/test/C/src/verifier/ProcessMsg.lf index b936b8909a..ce9a2090d7 100644 --- a/test/C/src/verifier/ProcessMsg.lf +++ b/test/C/src/verifier/ProcessMsg.lf @@ -1,39 +1,42 @@ target C reactor Task { - input in:int - output out:int - - state messageSent:int - state counter:int(0) - state panic:int(0) - - timer t(0, 1 nsec) - - logical action updateMessage - - reaction(startup) {= - self->messageSent = 0; - =} - reaction(t) -> out {= - lf_set(out, self->messageSent); - =} - reaction(in) -> updateMessage {= - /* Check for invalid message. */ - if (in->value != self->messageSent) { - self->panic = 1; - } - lf_schedule(updateMessage, 0); - self->counter += 1; - =} - reaction(updateMessage) {= - /* Increment the last word of the 16-byte message. */ - self->messageSent += 1; - =} + input in: int + output out: int + + state messageSent: int + state counter: int = 0 + state panic: int = 0 + + timer t(0, 1 nsec) + + logical action updateMessage + + reaction(startup) {= self->messageSent = 0; =} + + reaction(t) -> out {= lf_set(out, self->messageSent); =} + + reaction(in) -> updateMessage {= + /* Check for invalid message. */ + if (in->value != self->messageSent) { + self->panic = 1; + } + lf_schedule(updateMessage, 0); + self->counter += 1; + =} + + reaction(updateMessage) {= + /* Increment the last word of the 16-byte message. */ + self->messageSent += 1; + =} } -@property(name="panic_free", tactic="bmc", spec="G[5 nsec](ProcessMsg_task_panic != 1)", expect=true) +@property( + name = "panic_free", + tactic = "bmc", + spec = "G[5 nsec](ProcessMsg_task_panic != 1)", + expect = true) main reactor { - task = new Task() - task.out -> task.in -} \ No newline at end of file + task = new Task() + task.out -> task.in +} diff --git a/test/C/src/verifier/ProcessSync.lf b/test/C/src/verifier/ProcessSync.lf index 20bc498b46..cc045810ea 100644 --- a/test/C/src/verifier/ProcessSync.lf +++ b/test/C/src/verifier/ProcessSync.lf @@ -1,17 +1,21 @@ target C reactor Task { - /* Define the counters used in the demo application... */ - state tm_synchronization_processing_counter:int(0) - timer t(0,1 nsec) + /** Define the counters used in the demo application... */ + state tm_synchronization_processing_counter: int = 0 + timer t(0, 1 nsec) - reaction(t) {= - /* Increment the counter. */ - self->tm_synchronization_processing_counter += 1; - =} + reaction(t) {= + /* Increment the counter. */ + self->tm_synchronization_processing_counter += 1; + =} } -@property(name="correctness", tactic="bmc", spec="G[2 nsec](ProcessSync_task_tm_synchronization_processing_counter == 3)", expect=true) +@property( + name = "correctness", + tactic = "bmc", + spec = "G[2 nsec](ProcessSync_task_tm_synchronization_processing_counter == 3)", + expect = true) main reactor { - task = new Task() -} \ No newline at end of file + task = new Task() +} diff --git a/test/C/src/verifier/Ring.lf b/test/C/src/verifier/Ring.lf index d2a3e6db32..eb289e1f24 100644 --- a/test/C/src/verifier/Ring.lf +++ b/test/C/src/verifier/Ring.lf @@ -1,48 +1,50 @@ -/** - * There seems to be some problem with infinite feedback loops. - */ +/** There seems to be some problem with infinite feedback loops. */ target C reactor Source { - input in:int - output out:int - logical action start(1 nsec) - state received:int - reaction(startup) -> start {= - self->received = 0; - lf_schedule(start, 0); - =} - reaction(start) -> out {= - lf_set(out, self->received); - =} - /* Uncomment the following to debug feedback loops. */ - // reaction(in) -> start {= - reaction(in) {= - self->received = in->value; - // lf_schedule(start, 0); - =} + input in: int + output out: int + logical action start(1 nsec) + state received: int + + reaction(startup) -> start {= + self->received = 0; + lf_schedule(start, 0); + =} + + reaction(start) -> out {= lf_set(out, self->received); =} + + /** Uncomment the following to debug feedback loops. */ + // reaction(in) -> start {= + reaction(in) {= + self->received = in->value; + // lf_schedule(start, 0); + =} } reactor Node { - input in:int - output out:int - reaction(in) -> out {= - lf_set(out, in->value + 1); - =} + input in: int + output out: int + + reaction(in) -> out {= lf_set(out, in->value + 1); =} } -@property(name="full_circle", tactic="bmc", spec="F[0, 10 nsec](Ring_s_reaction_2)", expect=true) +@property( + name = "full_circle", + tactic = "bmc", + spec = "F[0, 10 nsec](Ring_s_reaction_2)", + expect = true) main reactor { - s = new Source() - n1 = new Node() - n2 = new Node() - n3 = new Node() - n4 = new Node() - n5 = new Node() - s.out -> n1.in after 1 nsec - n1.out -> n2.in after 1 nsec - n2.out -> n3.in after 1 nsec - n3.out -> n4.in after 1 nsec - n4.out -> n5.in after 1 nsec - n5.out -> s.in after 1 nsec + s = new Source() + n1 = new Node() + n2 = new Node() + n3 = new Node() + n4 = new Node() + n5 = new Node() + s.out -> n1.in after 1 nsec + n1.out -> n2.in after 1 nsec + n2.out -> n3.in after 1 nsec + n3.out -> n4.in after 1 nsec + n4.out -> n5.in after 1 nsec + n5.out -> s.in after 1 nsec } diff --git a/test/C/src/verifier/SafeSend.lf b/test/C/src/verifier/SafeSend.lf index cf1dde7bb1..da21ef589f 100644 --- a/test/C/src/verifier/SafeSend.lf +++ b/test/C/src/verifier/SafeSend.lf @@ -1,61 +1,57 @@ /** - * (Original) Description: - * This example illustrates the "Verify Absence-of-Errors" mode. - * The server expects a tuple `{REQUEST,PID-OF-SENDER}` - * but the main sends to it an atom instead of its pid, then - * generating an exception when the server tries to send back - * a response to what he assumes to be a pid. + * (Original) Description: This example illustrates the "Verify Absence-of-Errors" mode. The server + * expects a tuple `{REQUEST,PID-OF-SENDER}` but the main sends to it an atom instead of its pid, + * then generating an exception when the server tries to send back a response to what he assumes to + * be a pid. * - * The verification step discovers a *genuine* counter-example. - * To inspect the error trace run bfc on the generated model - * and look at the trace alongside the dot model of the ACS. + * The verification step discovers a *genuine* counter-example. To inspect the error trace run bfc + * on the generated model and look at the trace alongside the dot model of the ACS. */ - -target C; +target C reactor Client { - input in:int; - output out:int; - state req:int(0); - - reaction(startup) -> out {= - // Sends a query - self->req = 1; // 1 is valid. 0 is invalid. - lf_set(out, self->req); - =} - - reaction(in) {= - // Should not be invoked. - self->req = 0; - =} + input in: int + output out: int + state req: int = 0 + + reaction(startup) -> out {= + // Sends a query + self->req = 1; // 1 is valid. 0 is invalid. + lf_set(out, self->req); + =} + + reaction(in) {= + // Should not be invoked. + self->req = 0; + =} } reactor Server { - - input in:int; - output out:int; - state error:int; - logical action err; - - reaction(in) -> out, err {= - if (in->value == 0) { - lf_schedule(err, 0); - } - else { - lf_set(out, in->value); - } - =} - - // Error handling logic. - reaction(err) {= - self->error = 1; - =} + input in: int + output out: int + state error: int + logical action err + + reaction(in) -> out, err {= + if (in->value == 0) { + lf_schedule(err, 0); + } + else { + lf_set(out, in->value); + } + =} + + reaction(err) {= self->error = 1; =} // Error handling logic. } -@property(name="success", tactic="bmc", spec="(F[0, 1 sec](SafeSend_c_reaction_1))", expect=true) +@property( + name = "success", + tactic = "bmc", + spec = "(F[0, 1 sec](SafeSend_c_reaction_1))", + expect = true) main reactor { - c = new Client(); - s = new Server(); - c.out -> s.in after 1 nsec; - s.out -> c.in; + c = new Client() + s = new Server() + c.out -> s.in after 1 nsec + s.out -> c.in } diff --git a/test/C/src/verifier/Thermostat.lf b/test/C/src/verifier/Thermostat.lf index 4141201902..0b583d307d 100644 --- a/test/C/src/verifier/Thermostat.lf +++ b/test/C/src/verifier/Thermostat.lf @@ -1,66 +1,67 @@ -/** - * A thermostat model based on - * Lee & Seshia Chapter 3, Example 3.5. - */ +/** A thermostat model based on Lee & Seshia Chapter 3, Example 3.5. */ target C reactor Environment { - input heatOn:int - output temperature:int - timer t(1 nsec, 10 sec) - state _heatOn:int - state _temperature:int - reaction(startup) {= - self->_temperature = 19; - =} - reaction(t) -> temperature {= - if (self->_heatOn == 0) { - self->_temperature -= 1; - } - else { - self->_temperature += 1; - } - lf_set(temperature, self->_temperature); - =} - reaction(heatOn) {= - self->_heatOn = heatOn->value; - =} + input heatOn: int + output temperature: int + timer t(1 nsec, 10 sec) + state _heatOn: int + state _temperature: int + + reaction(startup) {= self->_temperature = 19; =} + + reaction(t) -> temperature {= + if (self->_heatOn == 0) { + self->_temperature -= 1; + } + else { + self->_temperature += 1; + } + lf_set(temperature, self->_temperature); + =} + + reaction(heatOn) {= self->_heatOn = heatOn->value; =} } reactor _Thermostat { - input temperature:int - output heatOn:int // 0 = OFF, 1 = ON - state _mode:int // 0 = COOLING, 1 = HEATING - logical action changeMode; - reaction(startup) {= - self->_mode = 0; - =} - reaction(temperature) -> heatOn, changeMode {= - if (self->_mode == 0) { - if (temperature->value <= 18) { - lf_set(heatOn, 1); - lf_schedule(changeMode, 0); - } + input temperature: int + output heatOn: int // 0 = OFF, 1 = ON + state _mode: int // 0 = COOLING, 1 = HEATING + logical action changeMode + + reaction(startup) {= self->_mode = 0; =} + + reaction(temperature) -> heatOn, changeMode {= + if (self->_mode == 0) { + if (temperature->value <= 18) { + lf_set(heatOn, 1); + lf_schedule(changeMode, 0); } - else if (self->_mode == 0) { - if (temperature->value >= 22) { - lf_set(heatOn, 0); - lf_schedule(changeMode, 0); - } + } + else if (self->_mode == 0) { + if (temperature->value >= 22) { + lf_set(heatOn, 0); + lf_schedule(changeMode, 0); } - =} - reaction(changeMode) {= - if (self->_mode == 0) - self->_mode = 1; - else - self->_mode = 0; - =} + } + =} + + reaction(changeMode) {= + if (self->_mode == 0) + self->_mode = 1; + else + self->_mode = 0; + =} } -@property(name="correctness", tactic="bmc", spec="G[0, 20 sec](((Thermostat_t_temperature <= 18) ==> F[0](Thermostat_t__mode == 1)) && ((Thermostat_t_temperature >= 22) ==> F[0](Thermostat_t__mode == 0)))", expect=true) +@property( + name = "correctness", + tactic = "bmc", + spec = "G[0, 20 sec](((Thermostat_t_temperature <= 18) ==> F[0](Thermostat_t__mode == 1)) && ((Thermostat_t_temperature >= 22) ==> F[0](Thermostat_t__mode == 0)))", + expect = true) main reactor { - e = new Environment() - t = new _Thermostat() - e.temperature -> t.temperature - t.heatOn -> e.heatOn -} \ No newline at end of file + e = new Environment() + t = new _Thermostat() + e.temperature -> t.temperature + t.heatOn -> e.heatOn +} diff --git a/test/C/src/verifier/TrainDoor.lf b/test/C/src/verifier/TrainDoor.lf index 88d3950aba..d39f473bea 100644 --- a/test/C/src/verifier/TrainDoor.lf +++ b/test/C/src/verifier/TrainDoor.lf @@ -1,35 +1,38 @@ -target C; +target C reactor Controller { - output out1:int; - output out2:int; - reaction(startup) -> out1, out2 {= - lf_set(out1, 1); - lf_set(out2, 2); - =} + output out1: int + output out2: int + + reaction(startup) -> out1, out2 {= + lf_set(out1, 1); + lf_set(out2, 2); + =} } reactor Train { - input in:int; - state received:int; - reaction(in) {= - self->received = in->value; - =} + input in: int + state received: int + + reaction(in) {= self->received = in->value; =} } reactor Door { - input in:int; - state received:int; - reaction(in) {= - self->received = in->value; - =} + input in: int + state received: int + + reaction(in) {= self->received = in->value; =} } -@property(name="train_does_not_move_until_door_closes", tactic="bmc", spec="(!TrainDoor_t_reaction_0)U[0, 1 sec](TrainDoor_d_reaction_0)", expect=false) +@property( + name = "train_does_not_move_until_door_closes", + tactic = "bmc", + spec = "(!TrainDoor_t_reaction_0)U[0, 1 sec](TrainDoor_d_reaction_0)", + expect = false) main reactor { - c = new Controller(); - t = new Train(); - d = new Door(); - c.out1 -> t.in after 1 sec; - c.out2 -> d.in after 1 sec; + c = new Controller() + t = new Train() + d = new Door() + c.out1 -> t.in after 1 sec + c.out2 -> d.in after 1 sec } diff --git a/test/C/src/verifier/UnsafeSend.lf b/test/C/src/verifier/UnsafeSend.lf index 7d03892069..73c3585885 100644 --- a/test/C/src/verifier/UnsafeSend.lf +++ b/test/C/src/verifier/UnsafeSend.lf @@ -1,61 +1,57 @@ /** - * (Original) Description: - * This example illustrates the "Verify Absence-of-Errors" mode. - * The server expects a tuple `{REQUEST,PID-OF-SENDER}` - * but the main sends to it an atom instead of its pid, then - * generating an exception when the server tries to send back - * a response to what he assumes to be a pid. + * (Original) Description: This example illustrates the "Verify Absence-of-Errors" mode. The server + * expects a tuple `{REQUEST,PID-OF-SENDER}` but the main sends to it an atom instead of its pid, + * then generating an exception when the server tries to send back a response to what he assumes to + * be a pid. * - * The verification step discovers a *genuine* counter-example. - * To inspect the error trace run bfc on the generated model - * and look at the trace alongside the dot model of the ACS. + * The verification step discovers a *genuine* counter-example. To inspect the error trace run bfc + * on the generated model and look at the trace alongside the dot model of the ACS. */ - -target C; +target C reactor Client { - input in:int; - output out:int; - state req:int(0); - - reaction(startup) -> out {= - // Sends a query - self->req = 0; // 1 is valid. 0 is invalid. - lf_set(out, self->req); - =} - - reaction(in) {= - // Should not be invoked. - self->req = 0; - =} + input in: int + output out: int + state req: int = 0 + + reaction(startup) -> out {= + // Sends a query + self->req = 0; // 1 is valid. 0 is invalid. + lf_set(out, self->req); + =} + + reaction(in) {= + // Should not be invoked. + self->req = 0; + =} } reactor Server { - - input in:int; - output out:int; - state error:int; - logical action err(1 nsec); - - reaction(in) -> out, err {= - if (in->value == 0) { - lf_schedule(err, 0); - } - else { - lf_set(out, in->value); - } - =} - - // Error handling logic. - reaction(err) {= - self->error = 1; - =} + input in: int + output out: int + state error: int + logical action err(1 nsec) + + reaction(in) -> out, err {= + if (in->value == 0) { + lf_schedule(err, 0); + } + else { + lf_set(out, in->value); + } + =} + + reaction(err) {= self->error = 1; =} // Error handling logic. } -@property(name="success", tactic="bmc", spec="(F[0, 5 nsec](UnsafeSend_c_reaction_1))", expect=false) +@property( + name = "success", + tactic = "bmc", + spec = "(F[0, 5 nsec](UnsafeSend_c_reaction_1))", + expect = false) main reactor { - c = new Client(); - s = new Server(); - c.out -> s.in after 2 nsec; - s.out -> c.in after 2 nsec; + c = new Client() + s = new Server() + c.out -> s.in after 2 nsec + s.out -> c.in after 2 nsec } From 197c323d9db72e20615461a34d4477107f7b9f6d Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Wed, 12 Jul 2023 16:55:37 +0200 Subject: [PATCH 260/516] rename ping pong test to make the name unique --- test/C/src/verifier/{PingPong.lf => PingPongVerifier.lf} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename test/C/src/verifier/{PingPong.lf => PingPongVerifier.lf} (92%) diff --git a/test/C/src/verifier/PingPong.lf b/test/C/src/verifier/PingPongVerifier.lf similarity index 92% rename from test/C/src/verifier/PingPong.lf rename to test/C/src/verifier/PingPongVerifier.lf index c8f45b400b..b69f99aa3a 100644 --- a/test/C/src/verifier/PingPong.lf +++ b/test/C/src/verifier/PingPongVerifier.lf @@ -58,9 +58,9 @@ reactor Pong { @property( name = "no_two_consecutive_pings", tactic = "bmc", - spec = "G[0, 4 nsec](PingPong_ping_reaction_1 ==> X(!PingPong_ping_reaction_1))", + spec = "G[0, 4 nsec](PingPongVerifier_ping_reaction_1 ==> X(!PingPongVerifier_ping_reaction_1))", expect = false) -main reactor PingPong { +main reactor PingPongVerifier { ping = new Ping() pong = new Pong() ping.send -> pong.receive From 00c04f78b14628fb100327695a51fd4bfaa80478 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Wed, 12 Jul 2023 16:56:13 +0200 Subject: [PATCH 261/516] add the verifier test category --- core/src/testFixtures/java/org/lflang/tests/TestRegistry.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java b/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java index 53dda29121..27e1d26d3f 100644 --- a/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java +++ b/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java @@ -334,6 +334,7 @@ public enum TestCategory { ARDUINO(false, "", TestLevel.BUILD), ZEPHYR_THREADED(false, "zephyr" + File.separator + "threaded", TestLevel.BUILD), ZEPHYR_UNTHREADED(false, "zephyr" + File.separator + "unthreaded", TestLevel.BUILD), + VERIFIER(false, "verifier", TestLevel.EXECUTION), TARGET(false, "", TestLevel.EXECUTION); /** Whether we should compare coverage against other targets. */ From 4b83bcf8a32fbaa11c74a3299df9595176897c1e Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Wed, 12 Jul 2023 17:09:05 +0200 Subject: [PATCH 262/516] add verifier test class --- .../lflang/tests/runtime/CVerifierTest.java | 30 +++++++++++++++++++ .../java/org/lflang/tests/TestBase.java | 1 + 2 files changed, 31 insertions(+) create mode 100644 core/src/integrationTest/java/org/lflang/tests/runtime/CVerifierTest.java diff --git a/core/src/integrationTest/java/org/lflang/tests/runtime/CVerifierTest.java b/core/src/integrationTest/java/org/lflang/tests/runtime/CVerifierTest.java new file mode 100644 index 0000000000..ac8dbc6e9c --- /dev/null +++ b/core/src/integrationTest/java/org/lflang/tests/runtime/CVerifierTest.java @@ -0,0 +1,30 @@ +package org.lflang.tests.runtime; + +import java.util.List; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; +import org.lflang.Target; +import org.lflang.tests.TestBase; +import org.lflang.tests.TestRegistry; + +public class CVerifierTest extends TestBase { + protected CVerifierTest() { + super(Target.C); + } + + @Test + public void runVerifierTests() { + Assumptions.assumeTrue(isLinux(), "Verifier tests only run on Linux"); + + super.runTestsFor( + List.of(Target.C), + Message.DESC_VERIFIER, + TestRegistry.TestCategory.VERIFIER::equals, + test -> { + test.getContext().getTargetConfig().verify = true; + return true; + }, + TestLevel.EXECUTION, + false); + } +} diff --git a/core/src/testFixtures/java/org/lflang/tests/TestBase.java b/core/src/testFixtures/java/org/lflang/tests/TestBase.java index b1b52b0c2e..92eeba5734 100644 --- a/core/src/testFixtures/java/org/lflang/tests/TestBase.java +++ b/core/src/testFixtures/java/org/lflang/tests/TestBase.java @@ -156,6 +156,7 @@ public static class Message { public static final String DESC_SCHED_SWAPPING = "Running with non-default runtime scheduler "; public static final String DESC_ROS2 = "Running tests using ROS2."; public static final String DESC_MODAL = "Run modal reactor tests."; + public static final String DESC_VERIFIER = "Run verifier tests."; /* Missing dependency messages */ public static final String MISSING_DOCKER = From 4db440452a955bd73f502f2712f71301f794787d Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Wed, 12 Jul 2023 17:32:42 +0200 Subject: [PATCH 263/516] exclude the verifier tests from zephyr and multithreaded --- .../integrationTest/java/org/lflang/tests/runtime/CCppTest.java | 1 + core/src/testFixtures/java/org/lflang/tests/Configurators.java | 1 + 2 files changed, 2 insertions(+) diff --git a/core/src/integrationTest/java/org/lflang/tests/runtime/CCppTest.java b/core/src/integrationTest/java/org/lflang/tests/runtime/CCppTest.java index 3142058c23..6324098963 100644 --- a/core/src/integrationTest/java/org/lflang/tests/runtime/CCppTest.java +++ b/core/src/integrationTest/java/org/lflang/tests/runtime/CCppTest.java @@ -46,6 +46,7 @@ private static boolean isExcludedFromCCpp(TestCategory category) { excluded |= category == TestCategory.ZEPHYR_THREADED; excluded |= category == TestCategory.ARDUINO; excluded |= category == TestCategory.NO_INLINING; + excluded |= category == TestCategory.VERIFIER; return !excluded; } } diff --git a/core/src/testFixtures/java/org/lflang/tests/Configurators.java b/core/src/testFixtures/java/org/lflang/tests/Configurators.java index 4fd80816e1..4191687278 100644 --- a/core/src/testFixtures/java/org/lflang/tests/Configurators.java +++ b/core/src/testFixtures/java/org/lflang/tests/Configurators.java @@ -113,6 +113,7 @@ public static boolean compatibleWithThreadingOff(TestCategory category) { || category == TestCategory.DOCKER_FEDERATED || category == TestCategory.DOCKER || category == TestCategory.ARDUINO + || category == TestCategory.VERIFIER || category == TestCategory.ZEPHYR_UNTHREADED || category == TestCategory.ZEPHYR_THREADED; From c9f80aa7cc1c4d2a9d12cb090d212685d219ba84 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Wed, 12 Jul 2023 17:33:15 +0200 Subject: [PATCH 264/516] update CI --- ...ier-c-benchmarks.yml => c-verifier-tests.yml} | 16 +++++++--------- .github/workflows/only-c.yml | 4 ++-- 2 files changed, 9 insertions(+), 11 deletions(-) rename .github/workflows/{uclid-verifier-c-benchmarks.yml => c-verifier-tests.yml} (80%) diff --git a/.github/workflows/uclid-verifier-c-benchmarks.yml b/.github/workflows/c-verifier-tests.yml similarity index 80% rename from .github/workflows/uclid-verifier-c-benchmarks.yml rename to .github/workflows/c-verifier-tests.yml index e17e152408..a2a7e13821 100644 --- a/.github/workflows/uclid-verifier-c-benchmarks.yml +++ b/.github/workflows/c-verifier-tests.yml @@ -21,12 +21,6 @@ jobs: path: lingua-franca submodules: true fetch-depth: 0 - - name: Check out lf-verifier-benchmarks repository - uses: actions/checkout@v3 - with: - repository: lf-lang/lf-verifier-benchmarks - ref: main - path: lf-verifier-benchmarks - name: Check out Uclid5 repository uses: actions/checkout@v3 with: @@ -64,6 +58,10 @@ jobs: unzip uclid-0.9.5.zip ./uclid-0.9.5/bin/uclid --help echo "$(pwd)/uclid-0.9.5/bin" >> $GITHUB_PATH - - name: Run the test script - working-directory: lf-verifier-benchmarks/ - run: ./scripts/test-lf-verifier + - name: Run verifier tests + run: ./gradlew core:integrationTest --tests org.lflang.tests.runtime.CVerifierTest.* core:integrationTestCodeCoverageReport + - name: Report to CodeCov + uses: ./.github/actions/report-code-coverage + with: + files: core/build/reports/jacoco/integrationTestCodeCoverageReport/integrationTestCodeCoverageReport.xml + if: ${{ github.repository == 'lf-lang/lingua-franca' }} diff --git a/.github/workflows/only-c.yml b/.github/workflows/only-c.yml index 1ae37cb18e..ae9016465c 100644 --- a/.github/workflows/only-c.yml +++ b/.github/workflows/only-c.yml @@ -43,5 +43,5 @@ jobs: all-platforms: ${{ !github.event.pull_request.draft }} # Run the Uclid-based LF Verifier benchmarks. - uclid: - uses: ./.github/workflows/uclid-verifier-c-benchmarks.yml + verifier: + uses: ./.github/workflows/c-verifier-tests.yml From 2747157c116ed87994856026315db84f7080b2af Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Wed, 12 Jul 2023 18:04:33 +0200 Subject: [PATCH 265/516] ensure correct directory --- .github/workflows/c-verifier-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/c-verifier-tests.yml b/.github/workflows/c-verifier-tests.yml index a2a7e13821..59883532da 100644 --- a/.github/workflows/c-verifier-tests.yml +++ b/.github/workflows/c-verifier-tests.yml @@ -58,6 +58,7 @@ jobs: unzip uclid-0.9.5.zip ./uclid-0.9.5/bin/uclid --help echo "$(pwd)/uclid-0.9.5/bin" >> $GITHUB_PATH + cd .. - name: Run verifier tests run: ./gradlew core:integrationTest --tests org.lflang.tests.runtime.CVerifierTest.* core:integrationTestCodeCoverageReport - name: Report to CodeCov From d07e029c8814b9b5b4329d7881b40e0b3f5130ff Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 12 Jul 2023 09:20:11 -0700 Subject: [PATCH 266/516] Output error message when dependencies are not installed. --- core/src/main/java/org/lflang/generator/LFGenerator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/LFGenerator.java b/core/src/main/java/org/lflang/generator/LFGenerator.java index c0ec38cd9f..9ee47427f8 100644 --- a/core/src/main/java/org/lflang/generator/LFGenerator.java +++ b/core/src/main/java/org/lflang/generator/LFGenerator.java @@ -192,11 +192,11 @@ private void runVerifierIfPropertiesDetected(Resource resource, LFGeneratorConte .error( "Fail to check the generated verification models because Uclid5 or Z3 is not" + " installed."); + } else { + // Run the Uclid tool. + uclidGenerator.runner.run(); } - // Run the Uclid tool. - uclidGenerator.runner.run(); - } else { messageReporter .nowhere() From 06a8944de01ad41cdd86578bba22204fab58bc1b Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Wed, 12 Jul 2023 18:29:49 +0200 Subject: [PATCH 267/516] actually ensure correct directory --- .github/workflows/c-verifier-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/c-verifier-tests.yml b/.github/workflows/c-verifier-tests.yml index 59883532da..9d9161e4af 100644 --- a/.github/workflows/c-verifier-tests.yml +++ b/.github/workflows/c-verifier-tests.yml @@ -58,7 +58,7 @@ jobs: unzip uclid-0.9.5.zip ./uclid-0.9.5/bin/uclid --help echo "$(pwd)/uclid-0.9.5/bin" >> $GITHUB_PATH - cd .. + cd ../.. - name: Run verifier tests run: ./gradlew core:integrationTest --tests org.lflang.tests.runtime.CVerifierTest.* core:integrationTestCodeCoverageReport - name: Report to CodeCov From b99931b88d8f4786861d9a56d1c5fa00d9a5f101 Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Wed, 12 Jul 2023 10:51:30 -0700 Subject: [PATCH 268/516] [refactor] break in switch --- core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java | 1 + core/src/main/resources/lib/c/reactor-c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 6c4f97c462..7c6b68908f 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -140,6 +140,7 @@ CodeBuilder generateCMakeCode( cMakeCode.pr("include(pico_sdk_import.cmake)"); cMakeCode.pr("project(" + executableName + " LANGUAGES C CXX ASM)"); cMakeCode.newLine(); + break; default: cMakeCode.pr("project(" + executableName + " LANGUAGES C)"); cMakeCode.newLine(); diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 2207668f6a..3daba0d3c6 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 2207668f6a703b9ea22e3c81668579b36cdd18db +Subproject commit 3daba0d3c697e75f811485786f82562416764889 From 4696ee02ca3e93e411a5a98001f9248e58fd456b Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Thu, 13 Jul 2023 10:03:54 +0200 Subject: [PATCH 269/516] bring workflow up to standard and add debug info --- .github/workflows/c-verifier-tests.yml | 39 +++++++++++++++----------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/.github/workflows/c-verifier-tests.yml b/.github/workflows/c-verifier-tests.yml index 9d9161e4af..8e2ec09915 100644 --- a/.github/workflows/c-verifier-tests.yml +++ b/.github/workflows/c-verifier-tests.yml @@ -3,10 +3,13 @@ name: Uclid5-based Verifier Tests on: # Trigger this workflow also on workflow_call events. workflow_call: - -env: - ACTIONS_STEP_DEBUG: TRUE - ACTIONS_RUNNER_DEBUG: TRUE + inputs: + compiler-ref: + required: false + type: string + runtime-ref: + required: false + type: string jobs: run: @@ -18,26 +21,25 @@ jobs: - name: Check out lingua-franca repository uses: actions/checkout@v3 with: - path: lingua-franca + repository: lf-lang/lingua-franca submodules: true + ref: ${{ inputs.compiler-ref }} fetch-depth: 0 + - name: Prepare build environment + uses: ./.github/actions/prepare-build-env + - name: Check out specific ref of reactor-c + uses: actions/checkout@v3 + with: + repository: lf-lang/reactor-c + path: core/src/main/resources/lib/c/reactor-c + ref: ${{ inputs.runtime-ref }} + if: ${{ inputs.runtime-ref }} - name: Check out Uclid5 repository uses: actions/checkout@v3 with: repository: uclid-org/uclid ref: 4fd5e566c5f87b052f92e9b23723a85e1c4d8c1c path: uclid - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - distribution: 'temurin' - java-version: 17 - - name: Install Lingua Franca - working-directory: lingua-franca/ - run: | - ./gradlew assemble - ./bin/lfc --version - echo "$(pwd)/bin" >> $GITHUB_PATH - name: Download Z3 working-directory: uclid/ if: steps.cache-z3.outputs.cache-hit != 'true' @@ -60,7 +62,10 @@ jobs: echo "$(pwd)/uclid-0.9.5/bin" >> $GITHUB_PATH cd ../.. - name: Run verifier tests - run: ./gradlew core:integrationTest --tests org.lflang.tests.runtime.CVerifierTest.* core:integrationTestCodeCoverageReport + run: | + echo "$pwd" + ls -la + ./gradlew core:integrationTest --tests org.lflang.tests.runtime.CVerifierTest.* core:integrationTestCodeCoverageReport - name: Report to CodeCov uses: ./.github/actions/report-code-coverage with: From ad4daeb1584df0aee700368fe4abca82e4948bf7 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 13 Jul 2023 17:32:16 +0200 Subject: [PATCH 270/516] Fix the assertion error. --- core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java index 2a07c3cba0..c563356edf 100644 --- a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java +++ b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java @@ -191,6 +191,7 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { super.printInfo(context.getMode()); ASTUtils.setMainName(context.getFileConfig().resource, context.getFileConfig().name); super.createMainInstantiation(); + super.setReactorsAndInstantiationGraph(context.getMode()); // Create the main reactor instance if there is a main reactor. this.main = From 67f37e3898d4ba3a23a979930f6f0f24cb58e77b Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 13 Jul 2023 11:35:40 -0700 Subject: [PATCH 271/516] Update submodule. --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index fe7270a916..e01d85a242 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit fe7270a9167494733d7b3d7567b35af5fef30af6 +Subproject commit e01d85a242f91d672f8cf660ea8a2a1df9e050d4 From 2e7cfb06f7a658e29a646ef54f9ae5926702be39 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 14 Jul 2023 11:10:17 +0200 Subject: [PATCH 272/516] Adjust platform and test level --- .../java/org/lflang/tests/runtime/CVerifierTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/integrationTest/java/org/lflang/tests/runtime/CVerifierTest.java b/core/src/integrationTest/java/org/lflang/tests/runtime/CVerifierTest.java index ac8dbc6e9c..d9bde8b3ed 100644 --- a/core/src/integrationTest/java/org/lflang/tests/runtime/CVerifierTest.java +++ b/core/src/integrationTest/java/org/lflang/tests/runtime/CVerifierTest.java @@ -14,7 +14,7 @@ protected CVerifierTest() { @Test public void runVerifierTests() { - Assumptions.assumeTrue(isLinux(), "Verifier tests only run on Linux"); + Assumptions.assumeTrue(isLinux() || isMac(), "Verifier tests only run on Linux or macOS"); super.runTestsFor( List.of(Target.C), @@ -24,7 +24,7 @@ public void runVerifierTests() { test.getContext().getTargetConfig().verify = true; return true; }, - TestLevel.EXECUTION, + TestLevel.BUILD, false); } } From 5df0f52fa3b5d0cd30027ffbf2e23691f345ed9e Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Fri, 14 Jul 2023 11:24:57 +0200 Subject: [PATCH 273/516] Fix typos --- test/README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/README.md b/test/README.md index 6725982dd0..2d494954d9 100644 --- a/test/README.md +++ b/test/README.md @@ -8,13 +8,19 @@ The simplest way to run integration tests for a specific target is the `targetTe ``` ./gradlew targetTest -Ptarget=Rust ``` -You can specify any valid target. If you run the task without specifying the target property `./gradlew tagetTest` it will produce an error message and list all available targets. +You can specify any valid target. If you run the task without specifying the target property `./gradlew targetTest` it will produce an error message and list all available targets. The `targetTest` task is essentially a convenient shortcut for the following: ``` -./gradew core:integrationTest --test org.lflang.tests.runtime.Test.* +./gradlew core:integrationTest --tests org.lflang.tests.runtime.Test.* ``` If you prefer have more control over which tests are executed, you can also use this more verbose version. +On Zsh (Z shell), which is the default shell for macOS, make sure to add +quotes if `*` is used to prevent Zsh from matching the test name against the +filesystem and returning a `zsh: no matches found` error. +``` +./gradlew core:integrationTest --tests "org.lflang.tests.runtime.Test.*" +``` It is also possible to run a subset of the tests. For example, the C tests are organized into the following categories: From d1a80e5af65dcdcddca1a9724c25cbf9b591a700 Mon Sep 17 00:00:00 2001 From: "Shaokai (Jerry) Lin" Date: Fri, 14 Jul 2023 11:27:47 +0200 Subject: [PATCH 274/516] Add a line break --- test/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/test/README.md b/test/README.md index 2d494954d9..be5833ae43 100644 --- a/test/README.md +++ b/test/README.md @@ -15,6 +15,7 @@ The `targetTest` task is essentially a convenient shortcut for the following: ./gradlew core:integrationTest --tests org.lflang.tests.runtime.Test.* ``` If you prefer have more control over which tests are executed, you can also use this more verbose version. + On Zsh (Z shell), which is the default shell for macOS, make sure to add quotes if `*` is used to prevent Zsh from matching the test name against the filesystem and returning a `zsh: no matches found` error. From 0a1c42109c6cff278eb0adc2fcc31aeb3555c01c Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Fri, 14 Jul 2023 15:20:02 -0700 Subject: [PATCH 275/516] Update test/README.md --- test/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/README.md b/test/README.md index be5833ae43..2292f25320 100644 --- a/test/README.md +++ b/test/README.md @@ -8,7 +8,7 @@ The simplest way to run integration tests for a specific target is the `targetTe ``` ./gradlew targetTest -Ptarget=Rust ``` -You can specify any valid target. If you run the task without specifying the target property `./gradlew targetTest` it will produce an error message and list all available targets. +You can specify any valid target. If you run the task without specifying the target property, `./gradlew targetTest` will produce an error message and list all available targets. The `targetTest` task is essentially a convenient shortcut for the following: ``` From cdb04131368ba8fc2090cc54650d688c9c71696b Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 14 Jul 2023 17:05:19 -0700 Subject: [PATCH 276/516] Mark SpuriousDependency as passing. It was necessary to make a modification to SpuriousDependency in order for it to pass because the original version had top-level reactions and state variables. We do not currently have a good way to get top-level reactions and state variables to work properly. The problem is that is we simply put the reactions where we think they "belong" and replicate the state variables, then the values of the state variables will not be the same across different federates. This is a can of worms that is outside the scope of this PR to fix. --- .../{failing => }/SpuriousDependency.lf | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) rename test/C/src/federated/{failing => }/SpuriousDependency.lf (74%) diff --git a/test/C/src/federated/failing/SpuriousDependency.lf b/test/C/src/federated/SpuriousDependency.lf similarity index 74% rename from test/C/src/federated/failing/SpuriousDependency.lf rename to test/C/src/federated/SpuriousDependency.lf index b509a4acae..6eda57264b 100644 --- a/test/C/src/federated/failing/SpuriousDependency.lf +++ b/test/C/src/federated/SpuriousDependency.lf @@ -6,15 +6,15 @@ * resolved the ambiguity in a way that does appear to result in deadlock. */ target C { - timeout: 2 sec + timeout: 1 sec } -reactor Passthrough { +reactor Passthrough(id: int = 0) { input in: int output out: int reaction(in) -> out {= - lf_print("Hello from passthrough"); + lf_print("Hello from passthrough %d", self->id); lf_set(out, in->value); =} } @@ -24,24 +24,22 @@ reactor Twisty { input in1: int output out0: int output out1: int - p0 = new Passthrough() - p1 = new Passthrough() + p0 = new Passthrough(id=0) + p1 = new Passthrough(id = 1) in0 -> p0.in p0.out -> out0 in1 -> p1.in p1.out -> out1 } -federated reactor { - t0 = new Twisty() - t1 = new Twisty() - t0.out1 -> t1.in0 - t1.out1 -> t0.in0 - state count: int = 0 +reactor Check { + input in: int - reaction(startup) -> t0.in1 {= lf_set(t0.in1, 0); =} + state count: int = 0 - reaction(t1.out0) {= self->count++; =} + reaction (in) {= + lf_print("count is now %d", ++self->count); + =} reaction(shutdown) {= lf_print("******* Shutdown invoked."); @@ -50,3 +48,16 @@ federated reactor { } =} } + +federated reactor { + t0 = new Twisty() + t1 = new Twisty() + check = new Check() + t0.out1 -> t1.in0 + t1.out1 -> t0.in0 + state count: int = 0 + + reaction(startup) -> t0.in1 {= lf_set(t0.in1, 0); =} + + t1.out0 -> check.in +} From 5a0bad3e1beb72d666113665c5975c146a7718bb Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 14 Jul 2023 23:19:24 -0700 Subject: [PATCH 277/516] Update reactor-c. --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index e01d85a242..0a77ffafff 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit e01d85a242f91d672f8cf660ea8a2a1df9e050d4 +Subproject commit 0a77ffaffff754bbe6a4635b1cda6ae712d2a236 From 2a039c85f4ffb8e62c4c44c7c0385825c5a11022 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 14 Jul 2023 23:31:02 -0700 Subject: [PATCH 278/516] Format. --- test/C/src/federated/SpuriousDependency.lf | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/test/C/src/federated/SpuriousDependency.lf b/test/C/src/federated/SpuriousDependency.lf index 6eda57264b..f7a8a36858 100644 --- a/test/C/src/federated/SpuriousDependency.lf +++ b/test/C/src/federated/SpuriousDependency.lf @@ -1,9 +1,8 @@ /** - * This checks that a federated program does not deadlock when it is ambiguous, - * given the structure of a federate, whether it is permissible to require - * certain network sender/receiver reactions to precede others in the execution - * of a given tag. The version of LFC that was in the master branch on 4/15/2023 - * resolved the ambiguity in a way that does appear to result in deadlock. + * This checks that a federated program does not deadlock when it is ambiguous, given the structure + * of a federate, whether it is permissible to require certain network sender/receiver reactions to + * precede others in the execution of a given tag. The version of LFC that was in the master branch + * on 4/15/2023 resolved the ambiguity in a way that does appear to result in deadlock. */ target C { timeout: 1 sec @@ -25,7 +24,7 @@ reactor Twisty { output out0: int output out1: int p0 = new Passthrough(id=0) - p1 = new Passthrough(id = 1) + p1 = new Passthrough(id=1) in0 -> p0.in p0.out -> out0 in1 -> p1.in @@ -37,9 +36,7 @@ reactor Check { state count: int = 0 - reaction (in) {= - lf_print("count is now %d", ++self->count); - =} + reaction(in) {= lf_print("count is now %d", ++self->count); =} reaction(shutdown) {= lf_print("******* Shutdown invoked."); @@ -57,7 +54,7 @@ federated reactor { t1.out1 -> t0.in0 state count: int = 0 - reaction(startup) -> t0.in1 {= lf_set(t0.in1, 0); =} - t1.out0 -> check.in + + reaction(startup) -> t0.in1 {= lf_set(t0.in1, 0); =} } From 2843752a27dfceb36f3da4dedfb29877c45d092f Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 14 Jul 2023 23:48:47 -0700 Subject: [PATCH 279/516] Move C-specific code to CExtension. --- .../federated/extensions/CExtension.java | 12 +++++++++++ .../extensions/FedTargetExtension.java | 3 +++ .../federated/extensions/TSExtension.java | 5 +++++ .../federated/generator/FedASTUtils.java | 21 +++++++------------ 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index 7370ad64fb..3cf1e67d39 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -220,6 +220,18 @@ protected void deserialize( } } + @Override + public String outputInitializationBody() { + return """ + extern reaction_t* port_absent_reaction[]; + void enqueue_network_output_control_reactions(environment_t*); + LF_PRINT_DEBUG("Adding network output control reaction to table."); + port_absent_reaction[self->sender_index] = &self->_lf__reaction_2; + LF_PRINT_DEBUG("Added network output control reaction to table. Enqueueing it..."); + enqueue_network_output_control_reactions(self->base.environment); + """; + } + /** * Generate code for the body of a reaction that handles an output that is to be sent over the * network. diff --git a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java index c228fbb92b..a22c7d37a7 100644 --- a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java @@ -53,6 +53,9 @@ String generateNetworkReceiverBody( CoordinationType coordinationType, MessageReporter messageReporter); + /** Generate code for initializing a network output reactor from its startup reaction. */ + String outputInitializationBody(); + /** * Generate code for the body of a reaction that handles an output that is to be sent over the * network. diff --git a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java index 1f50448151..05641105d9 100644 --- a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java @@ -55,6 +55,11 @@ public String generateNetworkReceiverBody( receivingPort.getVariable().getName()); } + @Override + public String outputInitializationBody() { + return ""; // TODO + } + @Override public String generateNetworkSenderBody( VarRef sendingPort, diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index f5cccc8038..c5223dffc2 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -45,6 +45,7 @@ import org.eclipse.xtext.xbase.lib.IteratorExtensions; import org.lflang.InferredType; import org.lflang.MessageReporter; +import org.lflang.Target; import org.lflang.TargetProperty.CoordinationType; import org.lflang.TimeValue; import org.lflang.ast.ASTUtils; @@ -146,8 +147,6 @@ public static void makeCommunication( addNetworkReceiverReactor(connection, coordination, resource, messageReporter); } - public static int networkMessageActionID = 0; - /** * Create a "network action" in the reactor that contains the given connection and return it. * @@ -648,7 +647,9 @@ private static Reactor getNetworkSenderReactor( senderIndexParameter.setInit(senderIndexParameterInit); sender.getParameters().add(senderIndexParameter); - sender.getReactions().add(getInitializationReaction()); + sender + .getReactions() + .add(getInitializationReaction(connection.srcFederate.targetConfig.target)); sender.getReactions().add(networkSenderReaction); sender.getInputs().add(in); @@ -698,24 +699,16 @@ private static Reaction getNetworkSenderReaction( return networkSenderReaction; } - private static Reaction getInitializationReaction() { + private static Reaction getInitializationReaction(Target target) { var initializationReaction = LfFactory.eINSTANCE.createReaction(); var startup = LfFactory.eINSTANCE.createBuiltinTriggerRef(); startup.setType(BuiltinTrigger.STARTUP); var a = LfFactory.eINSTANCE.createAttribute(); - a.setAttrName("_c_body"); + if (target == Target.C) a.setAttrName("_c_body"); initializationReaction.getAttributes().add(a); initializationReaction.getTriggers().add(startup); var code = LfFactory.eINSTANCE.createCode(); - code.setBody( - """ - extern reaction_t* port_absent_reaction[]; - void enqueue_network_output_control_reactions(environment_t*); - LF_PRINT_DEBUG("Adding network output control reaction to table."); - port_absent_reaction[self->sender_index] = &self->_lf__reaction_2; - LF_PRINT_DEBUG("Added network output control reaction to table. Enqueueing it..."); - enqueue_network_output_control_reactions(self->base.environment); - """); + code.setBody(FedTargetExtensionFactory.getExtension(target).outputInitializationBody()); initializationReaction.setCode(code); return initializationReaction; } From b9501fb01c1f33a2d5b202d83a86ec3f02cd05ea Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Sat, 15 Jul 2023 17:37:58 +0900 Subject: [PATCH 280/516] Remove the part using networkMessageActions param of fed_config --- .../lflang/generator/ts/TSActionGenerator.kt | 16 +++---- .../generator/ts/TSConstructorGenerator.kt | 11 +++-- .../lflang/generator/ts/TSReactorGenerator.kt | 42 +++++++++---------- 3 files changed, 35 insertions(+), 34 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt index e5dda9ee3d..ec81ba898b 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt @@ -10,7 +10,7 @@ import java.util.* */ class TSActionGenerator( private val actions: List, - private val networkMessageActions: List + // private val networkMessageActions: List ) { fun generateClassProperties(): String { @@ -45,13 +45,15 @@ class TSActionGenerator( ", " + action.minDelay.toTsTime() } } - if (action.name in networkMessageActions) { - actionInstantiations.add( - "this.${action.name} = new __FederatePortAction<${action.tsActionType}>($actionArgs);") - } else { - actionInstantiations.add( + // if (action.name in networkMessageActions) { + // actionInstantiations.add( + // "this.${action.name} = new __FederatePortAction<${action.tsActionType}>($actionArgs);") + // } else { + // actionInstantiations.add( + // "this.${action.name} = new __Action<${action.tsActionType}>($actionArgs);") + // } + actionInstantiations.add( "this.${action.name} = new __Action<${action.tsActionType}>($actionArgs);") - } } } return actionInstantiations.joinToString("\n") diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt index befb08cc82..b6624d797f 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt @@ -70,10 +70,10 @@ class TSConstructorGenerator( // If the app is federated, register its // networkMessageActions with the RTIClient - private fun generateFederatePortActionRegistrations(networkMessageActions: List): String = - networkMessageActions.withIndex().joinWithLn { (fedPortID, actionName) -> - "this.registerFederatePortAction($fedPortID, this.$actionName);" - } + // private fun generateFederatePortActionRegistrations(networkMessageActions: List): String = + // networkMessageActions.withIndex().joinWithLn { (fedPortID, actionName) -> + // "this.registerFederatePortAction($fedPortID, this.$actionName);" + // } // Generate code for setting target configurations. private fun generateTargetConfigurations(targetConfig: TargetConfig): String { @@ -92,7 +92,7 @@ class TSConstructorGenerator( actions: TSActionGenerator, ports: TSPortGenerator, isFederate: Boolean, - networkMessageActions: MutableList + // networkMessageActions: MutableList ): String { val connections = TSConnectionGenerator(reactor.connections, messageReporter) val reactions = TSReactionGenerator(messageReporter, reactor) @@ -111,7 +111,6 @@ class TSConstructorGenerator( ${" | "..actions.generateInstantiations()} ${" | "..ports.generateInstantiations()} ${" | "..connections.generateInstantiations()} - ${" | "..if (reactor.isMain && isFederate) generateFederatePortActionRegistrations(networkMessageActions) else ""} ${" | "..reactions.generateAllReactions()} |} """.trimMargin() diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt index 33f87374d3..e3be95f91c 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt @@ -90,24 +90,24 @@ ${" |"..preamble.code.toText()} }.trimMargin() } - private fun getNetworkMessageActions(reactor: Reactor): MutableList { - val attribute = AttributeUtils.findAttributeByName(reactor, "_fed_config") - val actionsStr = AttributeUtils.getAttributeParameter(attribute, AttributeSpec.NETWORK_MESSAGE_ACTIONS) - var actionsList = actionsStr?.split(",")?.filter { it.isNotEmpty()} ?: emptyList() - actionsList = actionsList.toMutableList() - - val childReactors = reactor.instantiations - var actionsListCount = 0 - for (childReactor in childReactors) { - if (childReactor.reactorClass.name.take(15) == "NetworkReceiver") { - // FIXME: Assume that the order of childReactor and attribute list are identical. - // This assumption might bring some erros - actionsList[actionsListCount] = childReactor.name + "." + actionsList[actionsListCount] - actionsListCount++ - } - } - return actionsList - } + // private fun getNetworkMessageActions(reactor: Reactor): MutableList { + // val attribute = AttributeUtils.findAttributeByName(reactor, "_fed_config") + // val actionsStr = AttributeUtils.getAttributeParameter(attribute, AttributeSpec.NETWORK_MESSAGE_ACTIONS) + // var actionsList = actionsStr?.split(",")?.filter { it.isNotEmpty()} ?: emptyList() + // actionsList = actionsList.toMutableList() + + // val childReactors = reactor.instantiations + // var actionsListCount = 0 + // for (childReactor in childReactors) { + // if (childReactor.reactorClass.name.take(15) == "NetworkReceiver") { + // // FIXME: Assume that the order of childReactor and attribute list are identical. + // // This assumption might bring some erros + // actionsList[actionsListCount] = childReactor.name + "." + actionsList[actionsListCount] + // actionsListCount++ + // } + // } + // return actionsList + // } fun generateReactor(reactor: Reactor): String { var reactorName = reactor.name @@ -117,7 +117,7 @@ ${" |"..preamble.code.toText()} } val isFederate = AttributeUtils.isFederate(reactor) - val networkMessageActions = getNetworkMessageActions(reactor) + // val networkMessageActions = getNetworkMessageActions(reactor) // NOTE: type parameters that are referenced in ports or actions must extend // Present in order for the program to type check. @@ -135,7 +135,7 @@ ${" |"..preamble.code.toText()} val timerGenerator = TSTimerGenerator(reactor.timers) val parameterGenerator = TSParameterGenerator(reactor.parameters) val stateGenerator = TSStateGenerator(reactor.stateVars) - val actionGenerator = TSActionGenerator(reactor.actions, networkMessageActions) + val actionGenerator = TSActionGenerator(reactor.actions) val portGenerator = TSPortGenerator(reactor.inputs, reactor.outputs) val constructorGenerator = TSConstructorGenerator(messageReporter, reactor) @@ -152,7 +152,7 @@ ${" |"..preamble.code.toText()} ${" | "..actionGenerator.generateClassProperties()} ${" | "..portGenerator.generateClassProperties()} ${" | "..constructorGenerator.generateConstructor(targetConfig, instanceGenerator, timerGenerator, parameterGenerator, - stateGenerator, actionGenerator, portGenerator, isFederate, networkMessageActions)} + stateGenerator, actionGenerator, portGenerator, isFederate)} |} |// =============== END reactor class ${reactor.name} | From eae0439e516e18fc91368e18a8ca4b799ecd87a6 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 15 Jul 2023 17:11:46 -0700 Subject: [PATCH 281/516] Bugfix in adding @_c_body. --- .../java/org/lflang/federated/generator/FedASTUtils.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index c5223dffc2..3f76df5d39 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -703,9 +703,11 @@ private static Reaction getInitializationReaction(Target target) { var initializationReaction = LfFactory.eINSTANCE.createReaction(); var startup = LfFactory.eINSTANCE.createBuiltinTriggerRef(); startup.setType(BuiltinTrigger.STARTUP); - var a = LfFactory.eINSTANCE.createAttribute(); - if (target == Target.C) a.setAttrName("_c_body"); - initializationReaction.getAttributes().add(a); + if (target == Target.Python) { + var a = LfFactory.eINSTANCE.createAttribute(); + a.setAttrName("_c_body"); + initializationReaction.getAttributes().add(a); + } initializationReaction.getTriggers().add(startup); var code = LfFactory.eINSTANCE.createCode(); code.setBody(FedTargetExtensionFactory.getExtension(target).outputInitializationBody()); From a2a38312ee65d9c3d56b067927973b5d59200618 Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun <78055940+byeong-gil@users.noreply.github.com> Date: Sun, 16 Jul 2023 09:58:32 +0900 Subject: [PATCH 282/516] Update package.json --- core/src/main/resources/lib/ts/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/ts/package.json b/core/src/main/resources/lib/ts/package.json index f619a1375f..4a28012180 100644 --- a/core/src/main/resources/lib/ts/package.json +++ b/core/src/main/resources/lib/ts/package.json @@ -2,7 +2,7 @@ "name": "LinguaFrancaDefault", "type": "commonjs", "dependencies": { - "@lf-lang/reactor-ts": "^0.4.0", + "@lf-lang/reactor-ts": "git://github.com/lf-lang/reactor-ts.git#ts-level-assignment", "command-line-args": "^5.1.1", "command-line-usage": "^6.1.3" }, From c44cfad8681c51a85f8f0f2376c333b8469e5a83 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 15 Jul 2023 18:21:24 -0700 Subject: [PATCH 283/516] Pass BroadcastFeedback with Python. --- .../federated/extensions/CExtension.java | 8 +++-- .../federated/generator/FedASTUtils.java | 35 ++++++------------- .../org/lflang/validation/LFValidator.java | 1 - 3 files changed, 16 insertions(+), 28 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index 3cf1e67d39..3ef2d5de90 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -226,7 +226,7 @@ public String outputInitializationBody() { extern reaction_t* port_absent_reaction[]; void enqueue_network_output_control_reactions(environment_t*); LF_PRINT_DEBUG("Adding network output control reaction to table."); - port_absent_reaction[self->sender_index] = &self->_lf__reaction_2; + port_absent_reaction[SENDERINDEXPARAMETER] = &self->_lf__reaction_2; LF_PRINT_DEBUG("Added network output control reaction to table. Enqueueing it..."); enqueue_network_output_control_reactions(self->base.environment); """; @@ -273,7 +273,11 @@ public String generateNetworkSenderBody( // channel or bank index of sendRef is present // ex. if a.out[i] is present, the entire output a.out is triggered. if (connection.getSrcBank() != -1 || connection.getSrcChannel() != -1) { - result.pr("if (!" + sendRef + "->is_present) return;"); + result.pr("if (!" + sendRef + "->is_present) {"); + if (connection.getSrcFederate().targetConfig.target == Target.Python) + result.pr("PyGILState_Release(gstate);"); + result.pr("return;"); + result.pr("}"); } // If the connection is physical and the receiving federate is remote, send it directly on a diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 3f76df5d39..8cf82c2ecf 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -56,7 +56,6 @@ import org.lflang.generator.ReactionInstance; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; -import org.lflang.lf.Assignment; import org.lflang.lf.BuiltinTrigger; import org.lflang.lf.BuiltinTriggerRef; import org.lflang.lf.Connection; @@ -635,17 +634,9 @@ private static Reactor getNetworkSenderReactor( Reaction networkSenderReaction = getNetworkSenderReaction(inRef, destRef, connection, coordination, type, messageReporter); - var senderIndexParameter = LfFactory.eINSTANCE.createParameter(); - var senderIndexParameterType = LfFactory.eINSTANCE.createType(); - senderIndexParameter.setName("sender_index"); - senderIndexParameterType.setId("int"); - senderIndexParameter.setType(senderIndexParameterType); - var senderIndexParameterInit = LfFactory.eINSTANCE.createInitializer(); - var senderIndexParameterInitExpr = LfFactory.eINSTANCE.createLiteral(); - senderIndexParameterInitExpr.setLiteral("0"); - senderIndexParameterInit.getExprs().add(senderIndexParameterInitExpr); - senderIndexParameter.setInit(senderIndexParameterInit); - sender.getParameters().add(senderIndexParameter); + var tp = LfFactory.eINSTANCE.createTypeParm(); + tp.setLiteral("SENDERINDEXPARAMETER"); + sender.getTypeParms().add(tp); sender .getReactions() @@ -751,7 +742,7 @@ private static void addNetworkSenderReactor( ASTUtils.getUniqueIdentifier(top, "ns_" + connection.getDstFederate().name)); top.getInstantiations().add(networkInstance); networkInstance - .getParameters() + .getTypeArgs() .add(getSenderIndex(connection.getSrcFederate().networkIdSender++)); addLevelAttribute( networkInstance, connection.getSourcePortInstance(), getSrcIndex(connection), connection); @@ -785,18 +776,12 @@ private static void addNetworkSenderReactor( connection.getSourcePortInstance(), networkInstance); } - private static Assignment getSenderIndex(int networkIDSender) { - var senderIndex = LfFactory.eINSTANCE.createAssignment(); - var senderIndexParameter = LfFactory.eINSTANCE.createParameter(); - senderIndexParameter.setName("sender_index"); - senderIndex.setLhs(senderIndexParameter); - var senderIndexInitializer = LfFactory.eINSTANCE.createInitializer(); - senderIndexInitializer.setAssign(true); - var senderIndexInitializerExpression = LfFactory.eINSTANCE.createLiteral(); - senderIndexInitializerExpression.setLiteral(String.valueOf(networkIDSender)); - senderIndexInitializer.getExprs().add(senderIndexInitializerExpression); - senderIndex.setRhs(senderIndexInitializer); - return senderIndex; + private static Type getSenderIndex(int networkIDSender) { + var senderIndexParameter = LfFactory.eINSTANCE.createType(); + var c = LfFactory.eINSTANCE.createCode(); + c.setBody(String.valueOf(networkIDSender)); + senderIndexParameter.setCode(c); + return senderIndexParameter; } /** diff --git a/core/src/main/java/org/lflang/validation/LFValidator.java b/core/src/main/java/org/lflang/validation/LFValidator.java index a602d54076..0a4062df84 100644 --- a/core/src/main/java/org/lflang/validation/LFValidator.java +++ b/core/src/main/java/org/lflang/validation/LFValidator.java @@ -1253,7 +1253,6 @@ public void checkTimer(Timer timer) { @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); From 0463c1c4c63a06b8409d00310b16c4111039a26c Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 15 Jul 2023 19:05:09 -0700 Subject: [PATCH 284/516] Try to make compatible with TypeScript. --- .../federated/extensions/CExtension.java | 19 +++++++++++ .../extensions/FedTargetExtension.java | 8 +++++ .../federated/extensions/TSExtension.java | 33 +++++++++++++++++++ .../federated/generator/FedASTUtils.java | 25 +++++--------- 4 files changed, 69 insertions(+), 16 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index 3ef2d5de90..2d0fcc304a 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -52,8 +52,11 @@ import org.lflang.generator.c.CTypes; import org.lflang.generator.c.CUtil; import org.lflang.lf.Action; +import org.lflang.lf.Instantiation; +import org.lflang.lf.LfFactory; import org.lflang.lf.Output; import org.lflang.lf.Port; +import org.lflang.lf.Reactor; import org.lflang.lf.VarRef; /** @@ -232,6 +235,22 @@ public String outputInitializationBody() { """; } + @Override + public void addSenderIndexParameter(Reactor sender) { + var tp = LfFactory.eINSTANCE.createTypeParm(); + tp.setLiteral("SENDERINDEXPARAMETER"); + sender.getTypeParms().add(tp); + } + + @Override + public void supplySenderIndexParameter(Instantiation inst, int idx) { + var senderIndexParameter = LfFactory.eINSTANCE.createType(); + var c = LfFactory.eINSTANCE.createCode(); + c.setBody(String.valueOf(idx)); + senderIndexParameter.setCode(c); + inst.getTypeArgs().add(senderIndexParameter); + } + /** * Generate code for the body of a reaction that handles an output that is to be sent over the * network. diff --git a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java index a22c7d37a7..c185c8ed3a 100644 --- a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java @@ -10,7 +10,9 @@ import org.lflang.federated.launcher.RtiConfig; import org.lflang.generator.LFGeneratorContext; import org.lflang.lf.Action; +import org.lflang.lf.Instantiation; import org.lflang.lf.Reaction; +import org.lflang.lf.Reactor; import org.lflang.lf.VarRef; public interface FedTargetExtension { @@ -56,6 +58,12 @@ String generateNetworkReceiverBody( /** Generate code for initializing a network output reactor from its startup reaction. */ String outputInitializationBody(); + /** Generate code for the parameter that specifies the sender index. */ + void addSenderIndexParameter(Reactor sender); + + /** Generate code for the sender index argument of {@code instantiation}. */ + void supplySenderIndexParameter(Instantiation inst, int idx); + /** * Generate code for the body of a reaction that handles an output that is to be sent over the * network. diff --git a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java index 05641105d9..46e773a286 100644 --- a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java @@ -20,7 +20,10 @@ import org.lflang.generator.ts.TSTypes; import org.lflang.lf.Action; import org.lflang.lf.Expression; +import org.lflang.lf.Instantiation; +import org.lflang.lf.LfFactory; import org.lflang.lf.Output; +import org.lflang.lf.Reactor; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; @@ -60,6 +63,36 @@ public String outputInitializationBody() { return ""; // TODO } + @Override + public void addSenderIndexParameter(Reactor sender) { + var senderIndexParameter = LfFactory.eINSTANCE.createParameter(); + var senderIndexParameterType = LfFactory.eINSTANCE.createType(); + senderIndexParameter.setName("sender_index"); + senderIndexParameterType.setId("Number"); + senderIndexParameter.setType(senderIndexParameterType); + var senderIndexParameterInit = LfFactory.eINSTANCE.createInitializer(); + var senderIndexParameterInitExpr = LfFactory.eINSTANCE.createLiteral(); + senderIndexParameterInitExpr.setLiteral("0"); + senderIndexParameterInit.getExprs().add(senderIndexParameterInitExpr); + senderIndexParameter.setInit(senderIndexParameterInit); + sender.getParameters().add(senderIndexParameter); + } + + @Override + public void supplySenderIndexParameter(Instantiation inst, int idx) { + var senderIndex = LfFactory.eINSTANCE.createAssignment(); + var senderIndexParameter = LfFactory.eINSTANCE.createParameter(); + senderIndexParameter.setName("sender_index"); + senderIndex.setLhs(senderIndexParameter); + var senderIndexInitializer = LfFactory.eINSTANCE.createInitializer(); + senderIndexInitializer.setAssign(true); + var senderIndexInitializerExpression = LfFactory.eINSTANCE.createLiteral(); + senderIndexInitializerExpression.setLiteral(String.valueOf(idx)); + senderIndexInitializer.getExprs().add(senderIndexInitializerExpression); + senderIndex.setRhs(senderIndexInitializer); + inst.getParameters().add(senderIndex); + } + @Override public String generateNetworkSenderBody( VarRef sendingPort, diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 8cf82c2ecf..0d0a873274 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -612,6 +612,8 @@ private static Reactor getNetworkSenderReactor( CoordinationType coordination, Resource resource, MessageReporter messageReporter) { + var extension = + FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target); LfFactory factory = LfFactory.eINSTANCE; Type type = EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getType()); @@ -634,9 +636,7 @@ private static Reactor getNetworkSenderReactor( Reaction networkSenderReaction = getNetworkSenderReaction(inRef, destRef, connection, coordination, type, messageReporter); - var tp = LfFactory.eINSTANCE.createTypeParm(); - tp.setLiteral("SENDERINDEXPARAMETER"); - sender.getTypeParms().add(tp); + extension.addSenderIndexParameter(sender); sender .getReactions() @@ -650,8 +650,7 @@ private static Reactor getNetworkSenderReactor( // networkSenderReaction.setName("NetworkSenderReaction_" + networkIDSender++); // FIXME: do not create a new extension every time it is used - FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) - .annotateReaction(networkSenderReaction); + extension.annotateReaction(networkSenderReaction); // If the sender or receiver is in a bank of reactors, then we want // these reactions to appear only in the federate whose bank ID matches. @@ -721,6 +720,8 @@ private static void addNetworkSenderReactor( Resource resource, MessageReporter messageReporter) { LfFactory factory = LfFactory.eINSTANCE; + var extension = + FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target); // Assume all the types are the same, so just use the first on the right. Reactor sender = getNetworkSenderReactor(connection, coordination, resource, messageReporter); @@ -741,9 +742,9 @@ private static void addNetworkSenderReactor( networkInstance.setName( ASTUtils.getUniqueIdentifier(top, "ns_" + connection.getDstFederate().name)); top.getInstantiations().add(networkInstance); - networkInstance - .getTypeArgs() - .add(getSenderIndex(connection.getSrcFederate().networkIdSender++)); + + extension.supplySenderIndexParameter( + networkInstance, connection.getSrcFederate().networkIdSender++); addLevelAttribute( networkInstance, connection.getSourcePortInstance(), getSrcIndex(connection), connection); @@ -776,14 +777,6 @@ private static void addNetworkSenderReactor( connection.getSourcePortInstance(), networkInstance); } - private static Type getSenderIndex(int networkIDSender) { - var senderIndexParameter = LfFactory.eINSTANCE.createType(); - var c = LfFactory.eINSTANCE.createCode(); - c.setBody(String.valueOf(networkIDSender)); - senderIndexParameter.setCode(c); - return senderIndexParameter; - } - /** * Add a network control reaction for a given output port 'source' to source's parent reactor. * This reaction will send a port absent message if the status of the output port is absent. From d2b5d0a054e1fc0d0f192c9249388a447df92545 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 15 Jul 2023 19:40:53 -0700 Subject: [PATCH 285/516] Attempt to address OutOfMemoryError in tests. --- core/src/testFixtures/java/org/lflang/tests/LFTest.java | 8 +++++--- .../DistributedCountDecentralizedLateHierarchy.lf | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/src/testFixtures/java/org/lflang/tests/LFTest.java b/core/src/testFixtures/java/org/lflang/tests/LFTest.java index 64e7b36dc3..9430f80bd5 100644 --- a/core/src/testFixtures/java/org/lflang/tests/LFTest.java +++ b/core/src/testFixtures/java/org/lflang/tests/LFTest.java @@ -257,10 +257,12 @@ private Thread recordStream(StringBuffer builder, InputStream inputStream) { int len; char[] buf = new char[1024]; while ((len = reader.read(buf)) > 0) { - builder.append(buf, 0, len); - if (Runtime.getRuntime().freeMemory() < Runtime.getRuntime().totalMemory() / 3) { - builder.delete(0, builder.length() / 2); + if (Runtime.getRuntime().freeMemory() < Runtime.getRuntime().totalMemory() / 2) { + Runtime.getRuntime().gc(); + if (Runtime.getRuntime().freeMemory() < Runtime.getRuntime().totalMemory() / 2) + builder.delete(0, builder.length() / 2); } + builder.append(buf, 0, len); } } catch (IOException e) { throw new RuntimeIOException(e); diff --git a/test/Python/src/federated/DistributedCountDecentralizedLateHierarchy.lf b/test/Python/src/federated/DistributedCountDecentralizedLateHierarchy.lf index ab02b16187..646cdff5c8 100644 --- a/test/Python/src/federated/DistributedCountDecentralizedLateHierarchy.lf +++ b/test/Python/src/federated/DistributedCountDecentralizedLateHierarchy.lf @@ -19,7 +19,7 @@ reactor ImportantActuator { input inp state success = 0 # Count messages that arrive without STP violation. state success_stp_violation = 0 - timer t(0, 10 usec) # Force a timer to be invoked periodically + timer t(0, 10 msec) # Force a timer to be invoked periodically state c = 0 # to ensure logical time will advance in the absence of incoming messages. reaction(inp) {= From a54fe61851f7c3f90d71ff10a8b8349c524bae7a Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 15 Jul 2023 22:46:15 -0700 Subject: [PATCH 286/516] Add JavaDoc etc. --- .../main/java/org/lflang/ast/ASTUtils.java | 4 ++++ .../federated/generator/FedASTUtils.java | 15 ++++++++++++- .../federated/generator/FederateInstance.java | 6 ++++++ .../org/lflang/generator/PortInstance.java | 1 + .../lflang/generator/ReactionInstance.java | 1 + .../generator/ReactionInstanceGraph.java | 6 ++++++ .../org/lflang/generator/ReactorInstance.java | 4 ++++ core/src/main/java/org/lflang/util/Pair.java | 21 ++----------------- .../org/lflang/validation/AttributeSpec.java | 1 - test/C/src/federated/SpuriousDependency.lf | 3 +-- 10 files changed, 39 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/org/lflang/ast/ASTUtils.java b/core/src/main/java/org/lflang/ast/ASTUtils.java index 036fbd4dc9..4868fded46 100644 --- a/core/src/main/java/org/lflang/ast/ASTUtils.java +++ b/core/src/main/java/org/lflang/ast/ASTUtils.java @@ -1372,6 +1372,10 @@ public static Integer initialValueInt(Parameter parameter, List i return result; } + /** + * Return the delay (in nanoseconds) denoted by {@code delay}, or {@code null} if the delay cannot + * be determined. + */ public static Long getDelay(Expression delay) { Long ret = null; if (delay != null) { diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 0d0a873274..0d16ddcd60 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -604,9 +604,19 @@ public static List safe(List list) { public static int networkIDReceiver = 0; + /** The network sender reactors created for each connection. */ private static final Map networkSenderReactors = new HashMap<>(); // FIXME: static mutable objects are bad + /** + * Generate and return the EObject representing the reactor definition of a network sender. + * + * @param connection The connection the communication over which is handled by an instance of the + * reactor to be returned. + * @param coordination Centralized or decentralized. + * @param resource The resource of the ECore model to which the new reactor definition should be + * added. + */ private static Reactor getNetworkSenderReactor( FedConnectionInstance connection, CoordinationType coordination, @@ -647,7 +657,6 @@ private static Reactor getNetworkSenderReactor( EObject node = IteratorExtensions.findFirst(resource.getAllContents(), Model.class::isInstance); ((Model) node).getReactors().add(sender); sender.setName("NetworkSender_" + connection.getSrcFederate().networkIdSender); - // networkSenderReaction.setName("NetworkSenderReaction_" + networkIDSender++); // FIXME: do not create a new extension every time it is used extension.annotateReaction(networkSenderReaction); @@ -665,6 +674,7 @@ private static Reactor getNetworkSenderReactor( return sender; } + /** Return the reaction that sends messages when its corresponding port is present. */ private static Reaction getNetworkSenderReaction( VarRef inRef, VarRef destRef, @@ -689,6 +699,9 @@ private static Reaction getNetworkSenderReaction( return networkSenderReaction; } + /** + * Return the reaction that initializes the containing network sender reactor on {@code startup}. + */ private static Reaction getInitializationReaction(Target target) { var initializationReaction = LfFactory.eINSTANCE.createReaction(); var startup = LfFactory.eINSTANCE.createBuiltinTriggerRef(); diff --git a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java index 88bb6a0051..56df7ae6a1 100644 --- a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java +++ b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java @@ -130,6 +130,7 @@ public Instantiation getInstantiation() { /** A list of individual connections between federates */ public Set connections = new HashSet<>(); + /** The counter used to assign IDs to network senders. */ public int networkIdSender = 0; /** @@ -220,6 +221,10 @@ public Instantiation getInstantiation() { */ public Map networkPortToInstantiation = new HashMap<>(); + /** + * The mapping from network multiports of the federate to indexer reactors that split the + * multiport into individual ports. + */ public Map networkPortToIndexer = new HashMap<>(); /** @@ -255,6 +260,7 @@ public Instantiation getInstantiation() { /** Keep a unique list of enabled serializers */ public List stpOffsets = new ArrayList<>(); + /** The STP offsets that have been recorded in {@code stpOffsets thus far. */ public Set currentSTPOffsets = new HashSet<>(); /** Keep a map of STP values to a list of network actions */ diff --git a/core/src/main/java/org/lflang/generator/PortInstance.java b/core/src/main/java/org/lflang/generator/PortInstance.java index 016bda4556..3a227fb84c 100644 --- a/core/src/main/java/org/lflang/generator/PortInstance.java +++ b/core/src/main/java/org/lflang/generator/PortInstance.java @@ -214,6 +214,7 @@ public void hasDependentReactionWithLevel(MixedRadixInt index, int level) { index, Math.min(levelUpperBounds.getOrDefault(index, Integer.MAX_VALUE), level)); } + /** Return the minimum of the levels of the reactions that are downstream of this port. */ public int getLevelUpperBound(MixedRadixInt index) { // It should be uncommon for Integer.MAX_VALUE to be used and using it can mask bugs. // It makes sense when there is no downstream reaction. diff --git a/core/src/main/java/org/lflang/generator/ReactionInstance.java b/core/src/main/java/org/lflang/generator/ReactionInstance.java index 2ed89c8b75..f0a7513796 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstance.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstance.java @@ -495,6 +495,7 @@ public class Runtime { public int level; + /** The ports that directly or transitively send to this reaction. */ public List sourcePorts = new ArrayList<>(); public ReactionInstance getReaction() { diff --git a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java index 3c91b03537..9dd7548d6c 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java @@ -291,8 +291,14 @@ private void getAllContainedReactions(List runtimeReactions, ReactorIns /////////////////////////////////////////////////////////// //// Private methods + /** A port and an index of a reaction relative to the port. */ public record MriPortPair(MixedRadixInt index, PortInstance port) {} + /** + * For each port in {@code reactor}, add that port to its downstream reactions, together with the + * {@code MixedRadixInt} that is the index of the downstream reaction relative to the port and the + * intervening ports. + */ private void registerPortInstances(ReactorInstance reactor) { var allPorts = new ArrayList(); allPorts.addAll(reactor.inputs); diff --git a/core/src/main/java/org/lflang/generator/ReactorInstance.java b/core/src/main/java/org/lflang/generator/ReactorInstance.java index 53ca96a667..cf571fbb64 100644 --- a/core/src/main/java/org/lflang/generator/ReactorInstance.java +++ b/core/src/main/java/org/lflang/generator/ReactorInstance.java @@ -175,6 +175,10 @@ public ReactorInstance(Reactor reactor, ReactorInstance parent, MessageReporter public EnclaveInfo enclaveInfo = null; public TypeParameterizedReactor tpr; + /** + * The TPO level with which {@code this} was annotated, or {@code null} if there is no TPO + * annotation. + */ public final Integer tpoLevel; ////////////////////////////////////////////////////// diff --git a/core/src/main/java/org/lflang/util/Pair.java b/core/src/main/java/org/lflang/util/Pair.java index a1fb07ac7b..bae17a76d3 100644 --- a/core/src/main/java/org/lflang/util/Pair.java +++ b/core/src/main/java/org/lflang/util/Pair.java @@ -1,21 +1,4 @@ package org.lflang.util; -public class Pair { - private final F first; - private final S second; - - public Pair(F first, S second) { - this.first = first; - this.second = second; - } - - public F getFirst() { - return first; - } - - public S getSecond() { - return second; - } -} - -// TimeValue maxSTP = findMaxSTP(connection, coordination); +/** A pair whose first element has type {@code F} and whose second element has type {@code S}. */ +public record Pair(F first, S second) {} diff --git a/core/src/main/java/org/lflang/validation/AttributeSpec.java b/core/src/main/java/org/lflang/validation/AttributeSpec.java index b89d766c6f..aab6faca9a 100644 --- a/core/src/main/java/org/lflang/validation/AttributeSpec.java +++ b/core/src/main/java/org/lflang/validation/AttributeSpec.java @@ -49,7 +49,6 @@ public class AttributeSpec { public static final String VALUE_ATTR = "value"; public static final String NETWORK_MESSAGE_ACTIONS = "network_message_actions"; - // public static final String DEPENDENCY_PAIRS = "dependencyPairs"; public static final String EACH_ATTR = "each"; /** A map from a string to a supported AttributeSpec */ diff --git a/test/C/src/federated/SpuriousDependency.lf b/test/C/src/federated/SpuriousDependency.lf index f7a8a36858..cf0deb6380 100644 --- a/test/C/src/federated/SpuriousDependency.lf +++ b/test/C/src/federated/SpuriousDependency.lf @@ -1,8 +1,7 @@ /** * This checks that a federated program does not deadlock when it is ambiguous, given the structure * of a federate, whether it is permissible to require certain network sender/receiver reactions to - * precede others in the execution of a given tag. The version of LFC that was in the master branch - * on 4/15/2023 resolved the ambiguity in a way that does appear to result in deadlock. + * precede others in the execution of a given tag. */ target C { timeout: 1 sec From bcdafebd440565a91bd69bcff45ecf71fba9fa55 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sun, 16 Jul 2023 07:10:21 -0400 Subject: [PATCH 287/516] Reduce volume of DEBUG output and changed printf to lf_print --- .../federated/DistributedCountDecentralizedLate.lf | 11 +++++------ .../DistributedCountDecentralizedLateHierarchy.lf | 10 +++++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/test/C/src/federated/DistributedCountDecentralizedLate.lf b/test/C/src/federated/DistributedCountDecentralizedLate.lf index 56d64004c6..02eb582b0e 100644 --- a/test/C/src/federated/DistributedCountDecentralizedLate.lf +++ b/test/C/src/federated/DistributedCountDecentralizedLate.lf @@ -18,12 +18,12 @@ reactor Print { 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) + timer t(0, 100 msec) state c: int = 0 reaction(in) {= tag_t current_tag = lf_tag(); - printf("At tag (%lld, %u) received %d. Intended tag is (%lld, %u).\n", + lf_print("At tag (%lld, %u) received %d. Intended tag is (%lld, %u).", lf_time_logical_elapsed(), lf_tag().microstep, in->value, @@ -36,7 +36,7 @@ reactor Print { self->c++; =} STP(0) {= tag_t current_tag = lf_tag(); - printf("At tag (%lld, %u), message has violated the STP offset by (%lld, %u).\n", + lf_print("At tag (%lld, %u), message has violated the STP offset by (%lld, %u).", current_tag.time - lf_time_start(), current_tag.microstep, current_tag.time - in->intended_tag.time, current_tag.microstep - in->intended_tag.microstep); @@ -50,10 +50,9 @@ reactor Print { reaction(shutdown) {= if ((self->success + self->success_stp_violation) != 5) { - fprintf(stderr, "Failed to detect STP violations in messages.\n"); - exit(1); + lf_print_error_and_exit("Failed to detect STP violations in messages."); } else { - printf("Successfully detected STP violation (%d violations, %d on-time).\n", self->success_stp_violation, self->success); + lf_print("Successfully detected STP violation (%d violations, %d on-time).", self->success_stp_violation, self->success); } =} } diff --git a/test/C/src/federated/DistributedCountDecentralizedLateHierarchy.lf b/test/C/src/federated/DistributedCountDecentralizedLateHierarchy.lf index cd9f8097aa..cfbd27de15 100644 --- a/test/C/src/federated/DistributedCountDecentralizedLateHierarchy.lf +++ b/test/C/src/federated/DistributedCountDecentralizedLateHierarchy.lf @@ -18,12 +18,12 @@ reactor ImportantActuator { input in: int state success: int = 0 state success_stp_violation: int = 0 - timer t(0, 10 msec) // Force a timer to be invoke periodically + timer t(0, 100 msec) // Force a timer to be invoke periodically state c: int = 0 // to ensure logical time will advance in the absence of incoming messages. reaction(in) {= tag_t current_tag = lf_tag(); - printf("At tag (%lld, %u) received %d. Intended tag is (%lld, %u).\n", + lf_print("At tag (%lld, %u) ImportantActuator received %d. Intended tag is (%lld, %u).", lf_time_logical_elapsed(), lf_tag().microstep, in->value, @@ -36,7 +36,7 @@ reactor ImportantActuator { self->c++; =} STP(0) {= tag_t current_tag = lf_tag(); - printf("Message violated STP offset by (%lld, %u).\n", + lf_print("Message violated STP offset by (%lld, %u).", current_tag.time - in->intended_tag.time, current_tag.microstep - in->intended_tag.microstep); self->success_stp_violation++; @@ -62,7 +62,7 @@ reactor Print { reaction(in) {= tag_t current_tag = lf_tag(); - printf("At tag (%lld, %u) received %d. Intended tag is (%lld, %u).\n", + lf_print("At tag (%lld, %u) Print received %d. Intended tag is (%lld, %u).", current_tag.time - lf_time_start(), current_tag.microstep, in->value, @@ -73,7 +73,7 @@ reactor Print { reactor Receiver { input in: int - timer t(0, 10 msec) // Force a timer to be invoke periodically + timer t(0, 100 msec) // Force a timer to be invoke periodically state c: int = 0 // to ensure logical time will advance in the absence of incoming messages. p = new Print() a = new ImportantActuator() From 64e7629cedd2ea7343af4182837d0ea266a33d9f Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sun, 16 Jul 2023 07:37:05 -0400 Subject: [PATCH 288/516] Reduce DEBUG output volume --- .../Python/src/federated/DistributedCountDecentralizedLate.lf | 2 +- .../federated/DistributedCountDecentralizedLateDownstream.lf | 2 +- .../federated/DistributedCountDecentralizedLateHierarchy.lf | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/Python/src/federated/DistributedCountDecentralizedLate.lf b/test/Python/src/federated/DistributedCountDecentralizedLate.lf index 1a0f47febd..87583d9277 100644 --- a/test/Python/src/federated/DistributedCountDecentralizedLate.lf +++ b/test/Python/src/federated/DistributedCountDecentralizedLate.lf @@ -17,7 +17,7 @@ reactor Print { input in_ # STP () state success = 0 # STP(in, 30 msec); state success_stp_violation = 0 - timer t(0, 1 msec) # Force a timer to be invoke periodically + timer t(0, 100 msec) # Force a timer to be invoke periodically state c = 0 # to ensure logical time will advance in the absence of incoming messages. reaction(in_) {= diff --git a/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf b/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf index 6c1ec4828e..dca44c16f7 100644 --- a/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf +++ b/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf @@ -72,7 +72,7 @@ reactor Print { reactor Receiver { input inp - timer t(0, 10 msec) # Force a timer to be invoke periodically + timer t(0, 100 msec) # Force a timer to be invoke periodically state c = 0 # to ensure logical time will advance in the absence of incoming messages. p = new Print() a = new ImportantActuator() diff --git a/test/Python/src/federated/DistributedCountDecentralizedLateHierarchy.lf b/test/Python/src/federated/DistributedCountDecentralizedLateHierarchy.lf index ab02b16187..c681805ca8 100644 --- a/test/Python/src/federated/DistributedCountDecentralizedLateHierarchy.lf +++ b/test/Python/src/federated/DistributedCountDecentralizedLateHierarchy.lf @@ -19,7 +19,7 @@ reactor ImportantActuator { input inp state success = 0 # Count messages that arrive without STP violation. state success_stp_violation = 0 - timer t(0, 10 usec) # Force a timer to be invoked periodically + timer t(0, 100 msec) # Force a timer to be invoked periodically state c = 0 # to ensure logical time will advance in the absence of incoming messages. reaction(inp) {= @@ -55,7 +55,7 @@ reactor ImportantActuator { reactor Receiver { input inp - timer t(0, 10 msec) # Force a timer to be invoke periodically + timer t(0, 100 msec) # Force a timer to be invoke periodically state c = 0 # to ensure logical time will advance in the absence of incoming messages. p = new Print() a = new ImportantActuator() From 570915fb6d3809a95db7e360c577e8f782999d6c Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sun, 16 Jul 2023 11:47:50 -0400 Subject: [PATCH 289/516] Format --- .../federated/DistributedCountDecentralizedLateHierarchy.lf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/C/src/federated/DistributedCountDecentralizedLateHierarchy.lf b/test/C/src/federated/DistributedCountDecentralizedLateHierarchy.lf index cfbd27de15..fd6f719af2 100644 --- a/test/C/src/federated/DistributedCountDecentralizedLateHierarchy.lf +++ b/test/C/src/federated/DistributedCountDecentralizedLateHierarchy.lf @@ -19,7 +19,7 @@ reactor ImportantActuator { state success: int = 0 state success_stp_violation: int = 0 timer t(0, 100 msec) // Force a timer to be invoke periodically - state c: int = 0 // to ensure logical time will advance in the absence of incoming messages. + state c: int = 0 // to ensure logical time will advance in the absence of incoming messages. reaction(in) {= tag_t current_tag = lf_tag(); @@ -74,7 +74,7 @@ reactor Print { reactor Receiver { input in: int timer t(0, 100 msec) // Force a timer to be invoke periodically - state c: int = 0 // to ensure logical time will advance in the absence of incoming messages. + state c: int = 0 // to ensure logical time will advance in the absence of incoming messages. p = new Print() a = new ImportantActuator() in -> p.in From bee3771de8fd989fc984fc92db5d278247d26292 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sun, 16 Jul 2023 11:49:15 -0400 Subject: [PATCH 290/516] Format --- .../src/federated/DistributedCountDecentralizedLate.lf | 6 +++--- .../DistributedCountDecentralizedLateDownstream.lf | 2 +- .../federated/DistributedCountDecentralizedLateHierarchy.lf | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/Python/src/federated/DistributedCountDecentralizedLate.lf b/test/Python/src/federated/DistributedCountDecentralizedLate.lf index 87583d9277..f58d5fd93e 100644 --- a/test/Python/src/federated/DistributedCountDecentralizedLate.lf +++ b/test/Python/src/federated/DistributedCountDecentralizedLate.lf @@ -14,11 +14,11 @@ import Count from "../lib/Count.lf" reactor Print { preamble {= import sys =} - input in_ # STP () - state success = 0 # STP(in, 30 msec); + input in_ # STP () + state success = 0 # STP(in, 30 msec); state success_stp_violation = 0 timer t(0, 100 msec) # Force a timer to be invoke periodically - state c = 0 # to ensure logical time will advance in the absence of incoming messages. + state c = 0 # to ensure logical time will advance in the absence of incoming messages. reaction(in_) {= current_tag = lf.tag() diff --git a/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf b/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf index dca44c16f7..983c5c0940 100644 --- a/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf +++ b/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf @@ -73,7 +73,7 @@ reactor Print { reactor Receiver { input inp timer t(0, 100 msec) # Force a timer to be invoke periodically - state c = 0 # to ensure logical time will advance in the absence of incoming messages. + state c = 0 # to ensure logical time will advance in the absence of incoming messages. p = new Print() a = new ImportantActuator() diff --git a/test/Python/src/federated/DistributedCountDecentralizedLateHierarchy.lf b/test/Python/src/federated/DistributedCountDecentralizedLateHierarchy.lf index c681805ca8..0603de1a1e 100644 --- a/test/Python/src/federated/DistributedCountDecentralizedLateHierarchy.lf +++ b/test/Python/src/federated/DistributedCountDecentralizedLateHierarchy.lf @@ -17,10 +17,10 @@ import Print from "DistributedCountDecentralizedLateDownstream.lf" reactor ImportantActuator { input inp - state success = 0 # Count messages that arrive without STP violation. + state success = 0 # Count messages that arrive without STP violation. state success_stp_violation = 0 timer t(0, 100 msec) # Force a timer to be invoked periodically - state c = 0 # to ensure logical time will advance in the absence of incoming messages. + state c = 0 # to ensure logical time will advance in the absence of incoming messages. reaction(inp) {= current_tag = lf.tag() @@ -56,7 +56,7 @@ reactor ImportantActuator { reactor Receiver { input inp timer t(0, 100 msec) # Force a timer to be invoke periodically - state c = 0 # to ensure logical time will advance in the absence of incoming messages. + state c = 0 # to ensure logical time will advance in the absence of incoming messages. p = new Print() a = new ImportantActuator() inp -> p.inp From ae0e4eb839acc9bb68d90ecf0c39d4843a69c0c7 Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Sun, 16 Jul 2023 23:25:38 -0500 Subject: [PATCH 291/516] [refactor] add reactor-c changes --- core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java | 3 +-- core/src/main/resources/lib/c/reactor-c | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 7c6b68908f..5eabc818a0 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -429,8 +429,6 @@ private static String setUpMainTargetZephyr( private static String setUpMainTargetRp2040( boolean hasMain, String executableName, Stream cSources) { var code = new CodeBuilder(); - // FIXME: remove this and move to lingo build - code.pr("add_compile_options(-Wall -Wextra -DLF_UNTHREADED)"); // initialize sdk code.pr("pico_sdk_init()"); code.newLine(); @@ -458,6 +456,7 @@ private static String setUpMainTargetRp2040( code.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 0)"); code.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 1)"); code.pr("pico_add_extra_outputs(${LF_MAIN_TARGET})"); + code.newLine(); return code.toString(); } diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 3daba0d3c6..7433c9414d 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 3daba0d3c697e75f811485786f82562416764889 +Subproject commit 7433c9414d4c72a6ee2c8cf3942cd2d4c58c0295 From c41feed94587f2ddf71eaf70589e9d3096b06fa5 Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Sun, 16 Jul 2023 23:30:13 -0500 Subject: [PATCH 292/516] [threads] cond_var changes --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 7433c9414d..bc458330d5 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 7433c9414d4c72a6ee2c8cf3942cd2d4c58c0295 +Subproject commit bc458330d5c6e9821569e2812095160bf159da74 From c014196d3643600c3121b67d3fb936affff11a65 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 17 Jul 2023 03:16:56 -0400 Subject: [PATCH 293/516] Aligned with pico-support branch of reactor-c --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index bc458330d5..bd704bece4 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit bc458330d5c6e9821569e2812095160bf159da74 +Subproject commit bd704bece43c10c1a17d985250968b04d7feed7e From 1074f8403892445c7cb067dc7a7aca14e0769a9f Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Mon, 17 Jul 2023 02:27:44 -0500 Subject: [PATCH 294/516] [threaded] compilation check from threaded --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index bc458330d5..bd704bece4 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit bc458330d5c6e9821569e2812095160bf159da74 +Subproject commit bd704bece43c10c1a17d985250968b04d7feed7e From f71a01721fe3fe71ec72949e1168cdd1b7204027 Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Tue, 18 Jul 2023 11:18:32 +0900 Subject: [PATCH 295/516] Add the test SpuriousDependency.lf for TypeScript federated execution --- .../federated/failing/SpuriousDependency.lf | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 test/TypeScript/src/federated/failing/SpuriousDependency.lf diff --git a/test/TypeScript/src/federated/failing/SpuriousDependency.lf b/test/TypeScript/src/federated/failing/SpuriousDependency.lf new file mode 100644 index 0000000000..dc16319a70 --- /dev/null +++ b/test/TypeScript/src/federated/failing/SpuriousDependency.lf @@ -0,0 +1,66 @@ +/** + * This checks that a federated program does not deadlock when it is ambiguous, given the structure + * of a federate, whether it is permissible to require certain network sender/receiver reactions to + * precede others inp the execution of a given tag. The version of LFC that was inp the master branch + * on 4/15/2023 resolved the ambiguity inp a way that does appear to result inp deadlock. + */ +target TypeScript { + timeout: 1 sec +} + +reactor Passthrough(id: number = 0) { + input inp: number + output out: number + + reaction(inp) -> out {= + //lf_print("Hello from passthrough %d", self->id); + console.log("Hello from passthrough " + id); + //lf_set(out, inp->value); + out = inp; + =} +} + +reactor Twisty { + input in0: number + input in1: number + output out0: number + output out1: number + p0 = new Passthrough(id=0) + p1 = new Passthrough(id=1) + in0 -> p0.inp + p0.out -> out0 + in1 -> p1.inp + p1.out -> out1 +} + +reactor Check { + input inp: number + + state count: number = 0 + + reaction(inp) {= console.log("count is now " + ++count); =} + + reaction(shutdown) {= + // lf_print("******* Shutdown invoked."); + console.log("******* Shutdown invoked."); + // if (self->count != 1) { + // lf_print_error_and_exit("Failed to receive expected input."); + // } + if (count != 1) { + util.reportError("Failed to receieve expected input."); + } + =} +} + +federated reactor { + t0 = new Twisty() + t1 = new Twisty() + check = new Check() + t0.out1 -> t1.in0 + t1.out1 -> t0.in0 + state count: number = 0 + + t1.out0 -> check.inp + + reaction(startup) -> t0.in1 {= t0.in1 = 0; =} +} From 81b130a50ff0bdbefc7de12f0f7539353d88ac80 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 18 Jul 2023 10:11:33 -0700 Subject: [PATCH 296/516] Improve error message. --- test/Python/src/federated/DistributedCountDecentralizedLate.lf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Python/src/federated/DistributedCountDecentralizedLate.lf b/test/Python/src/federated/DistributedCountDecentralizedLate.lf index 1a0f47febd..1807f906e5 100644 --- a/test/Python/src/federated/DistributedCountDecentralizedLate.lf +++ b/test/Python/src/federated/DistributedCountDecentralizedLate.lf @@ -51,7 +51,7 @@ reactor Print { reaction(shutdown) {= if self.success + self.success_stp_violation != 5: - self.sys.stderr.write("Failed to detect STP violations in messages.\n") + self.sys.stderr.write(f"FATAL ERROR: Failed to detect STP violations in messages. Expected 4 STP violations but got {self.success_stp_violation}.\n") self.sys.exit(1) else: print("Successfully detected STP violation ({} violations, {} on-time).".format(self.success_stp_violation, self.success)) From aa18d29ba11a08e9a1861274745808970efebaa3 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 18 Jul 2023 11:00:36 -0700 Subject: [PATCH 297/516] Don't try to invoke STP handler of input reactions --- .../federated/extensions/CExtension.java | 5 +++++ .../extensions/FedTargetExtension.java | 7 +++++-- .../federated/extensions/TSExtension.java | 5 +++++ .../federated/generator/FedASTUtils.java | 21 +++++++++---------- core/src/main/resources/lib/c/reactor-c | 2 +- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index 2d0fcc304a..3541ff0813 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -235,6 +235,11 @@ public String outputInitializationBody() { """; } + @Override + public String inputInitializationBody() { + return "self->_lf__reaction_1.is_an_input_reaction = true;\n"; + } + @Override public void addSenderIndexParameter(Reactor sender) { var tp = LfFactory.eINSTANCE.createTypeParm(); diff --git a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java index c185c8ed3a..b3d816f23a 100644 --- a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java @@ -20,8 +20,8 @@ public interface FedTargetExtension { /** * Perform necessary actions to initialize the target config. * - * @param context - * @param numOfFederates + * @param context The context of the original code generation process. + * @param numOfFederates The number of federates in the program. * @param federate The federate instance. * @param fileConfig An instance of {@code FedFileConfig}. * @param messageReporter Used to report errors. @@ -58,6 +58,9 @@ String generateNetworkReceiverBody( /** Generate code for initializing a network output reactor from its startup reaction. */ String outputInitializationBody(); + /** Generate code for initializing a network input reactor from its startup reaction. */ + String inputInitializationBody(); + /** Generate code for the parameter that specifies the sender index. */ void addSenderIndexParameter(Reactor sender); diff --git a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java index 46e773a286..75930c7180 100644 --- a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java @@ -63,6 +63,11 @@ public String outputInitializationBody() { return ""; // TODO } + @Override + public String inputInitializationBody() { + return ""; + } + @Override public void addSenderIndexParameter(Reactor sender) { var senderIndexParameter = LfFactory.eINSTANCE.createParameter(); diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 0d16ddcd60..70fe164012 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -45,10 +45,10 @@ import org.eclipse.xtext.xbase.lib.IteratorExtensions; import org.lflang.InferredType; import org.lflang.MessageReporter; -import org.lflang.Target; import org.lflang.TargetProperty.CoordinationType; import org.lflang.TimeValue; import org.lflang.ast.ASTUtils; +import org.lflang.federated.extensions.FedTargetExtension; import org.lflang.federated.extensions.FedTargetExtensionFactory; import org.lflang.federated.serialization.SupportedSerializers; import org.lflang.generator.MixedRadixInt; @@ -217,6 +217,8 @@ private static void addNetworkReceiverReactor( Resource resource, MessageReporter messageReporter) { LfFactory factory = LfFactory.eINSTANCE; + var extension = + FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target); Type type = EcoreUtil.copy(connection.getDestinationPortInstance().getDefinition().getType()); VarRef sourceRef = factory.createVarRef(); // source fed @@ -238,6 +240,8 @@ private static void addNetworkReceiverReactor( .getParent() .reactorDefinition; // Top-level reactor. + receiver.getReactions().add(getInitializationReaction(extension, extension.inputInitializationBody())); + receiver.getReactions().add(networkReceiverReaction); receiver.getOutputs().add(out); @@ -288,8 +292,7 @@ private static void addNetworkReceiverReactor( setReactionBankIndex(networkReceiverReaction, connection.getDstBank()); // FIXME: do not create a new extension every time it is used - FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) - .annotateReaction(networkReceiverReaction); + extension.annotateReaction(networkReceiverReaction); // The connection is 'physical' if it uses the ~> notation. if (connection.getDefinition().isPhysical()) { @@ -650,7 +653,7 @@ private static Reactor getNetworkSenderReactor( sender .getReactions() - .add(getInitializationReaction(connection.srcFederate.targetConfig.target)); + .add(getInitializationReaction(extension, extension.outputInitializationBody())); sender.getReactions().add(networkSenderReaction); sender.getInputs().add(in); @@ -702,18 +705,14 @@ private static Reaction getNetworkSenderReaction( /** * Return the reaction that initializes the containing network sender reactor on {@code startup}. */ - private static Reaction getInitializationReaction(Target target) { + private static Reaction getInitializationReaction(FedTargetExtension extension, String body) { var initializationReaction = LfFactory.eINSTANCE.createReaction(); var startup = LfFactory.eINSTANCE.createBuiltinTriggerRef(); startup.setType(BuiltinTrigger.STARTUP); - if (target == Target.Python) { - var a = LfFactory.eINSTANCE.createAttribute(); - a.setAttrName("_c_body"); - initializationReaction.getAttributes().add(a); - } + extension.annotateReaction(initializationReaction); initializationReaction.getTriggers().add(startup); var code = LfFactory.eINSTANCE.createCode(); - code.setBody(FedTargetExtensionFactory.getExtension(target).outputInitializationBody()); + code.setBody(body); initializationReaction.setCode(code); return initializationReaction; } diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 0a77ffafff..793f350161 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 0a77ffaffff754bbe6a4635b1cda6ae712d2a236 +Subproject commit 793f35016127702889fd12690530fa39856b4d75 From caf9d776e83b05e747ae243cfceaf6aa70f0a432 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 18 Jul 2023 12:32:03 -0700 Subject: [PATCH 298/516] Reduce volume of test output in DistributedCountDecentralizedLateDownstream --- .../federated/DistributedCountDecentralizedLateDownstream.lf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf b/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf index 983c5c0940..4a8aa0af0e 100644 --- a/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf +++ b/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf @@ -26,7 +26,7 @@ reactor ImportantActuator { input inp state success = 0 # Count messages that arrive without STP violation. state success_stp_violation = 0 - timer t(0, 10 usec) # Force a timer to be invoked periodically + timer t(0, 100 msec) # Force a timer to be invoked periodically state c = 0 # to ensure logical time will advance in the absence of incoming messages. reaction(inp) {= From 259da76a739b559027b90024d946b4a004579e6a Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 18 Jul 2023 12:55:07 -0700 Subject: [PATCH 299/516] Bugfix in DistributedStopZero. --- test/Python/src/federated/DistributedStopZero.lf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Python/src/federated/DistributedStopZero.lf b/test/Python/src/federated/DistributedStopZero.lf index d933b9ce68..845f099698 100644 --- a/test/Python/src/federated/DistributedStopZero.lf +++ b/test/Python/src/federated/DistributedStopZero.lf @@ -32,7 +32,7 @@ reactor Sender { reaction(shutdown) {= tag = lf.tag() - if tag.time != USEC(0) or tag.microstep != 1: + if tag.time != self.startup_logical_time or tag.microstep != 1: sys.stderr.write("ERROR: Sender failed to stop the federation in time. Stopping at ({}, {}).\n".format( tag.time, tag.microstep)) @@ -69,7 +69,7 @@ reactor Receiver { # Therefore, the shutdown events must occur at (1000, 0) on the # receiver. tag = lf.tag() - if tag.time != USEC(0) or tag.microstep != 1: + if tag.time != self.startup_logical_time or tag.microstep != 1: sys.stderr.write("ERROR: Receiver failed to stop the federation in time. Stopping at ({}, {}).\n".format( tag.time, tag.microstep)) From 9516d24d696641978612bb4b4b556d5a4b9ddd56 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 18 Jul 2023 13:36:07 -0700 Subject: [PATCH 300/516] Fix mistake in DistributedCountDecentralizedLate.lf This is another instance of confusing absolute time and elapsed time. Apparently it is non-reproducible whether the first iteration is an STP violation or not? Locally it is always an STP violation for me, but apparently it is different in CI. --- .../main/java/org/lflang/federated/generator/FedASTUtils.java | 4 +++- .../Python/src/federated/DistributedCountDecentralizedLate.lf | 4 ++-- .../federated/DistributedCountDecentralizedLateDownstream.lf | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 70fe164012..7da95b6c60 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -240,7 +240,9 @@ private static void addNetworkReceiverReactor( .getParent() .reactorDefinition; // Top-level reactor. - receiver.getReactions().add(getInitializationReaction(extension, extension.inputInitializationBody())); + receiver + .getReactions() + .add(getInitializationReaction(extension, extension.inputInitializationBody())); receiver.getReactions().add(networkReceiverReaction); receiver.getOutputs().add(out); diff --git a/test/Python/src/federated/DistributedCountDecentralizedLate.lf b/test/Python/src/federated/DistributedCountDecentralizedLate.lf index d2175121fc..fe52eec1b7 100644 --- a/test/Python/src/federated/DistributedCountDecentralizedLate.lf +++ b/test/Python/src/federated/DistributedCountDecentralizedLate.lf @@ -30,7 +30,7 @@ reactor Print { in_.intended_tag.microstep ) ) - if (lf.tag() == Tag(SEC(1), 0)): + if (lf.tag() == Tag(lf.time.start() + SEC(1), 0)): self.success += 1 # Message was on-time self.c += 1 =} STP(0) {= @@ -51,7 +51,7 @@ reactor Print { reaction(shutdown) {= if self.success + self.success_stp_violation != 5: - self.sys.stderr.write(f"FATAL ERROR: Failed to detect STP violations in messages. Expected 4 STP violations but got {self.success_stp_violation}.\n") + print(f"FATAL ERROR: Failed to detect STP violations in messages. Expected at most 1 initial success and 4 or 5 STP violations depending on whether the first was a success, but got {self.success} initial successes and {self.success_stp_violation} STP violations.\n") self.sys.exit(1) else: print("Successfully detected STP violation ({} violations, {} on-time).".format(self.success_stp_violation, self.success)) diff --git a/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf b/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf index 4a8aa0af0e..3ba50bc770 100644 --- a/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf +++ b/test/Python/src/federated/DistributedCountDecentralizedLateDownstream.lf @@ -24,10 +24,10 @@ import Count from "../lib/Count.lf" reactor ImportantActuator { input inp - state success = 0 # Count messages that arrive without STP violation. + state success = 0 # Count messages that arrive without STP violation. state success_stp_violation = 0 timer t(0, 100 msec) # Force a timer to be invoked periodically - state c = 0 # to ensure logical time will advance in the absence of incoming messages. + state c = 0 # to ensure logical time will advance in the absence of incoming messages. reaction(inp) {= current_tag = lf.tag() From d87763fd013bd6d31f73b34941d8d274c8978d35 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Tue, 18 Jul 2023 13:53:12 -0700 Subject: [PATCH 301/516] Update core/src/main/java/org/lflang/generator/LFGenerator.java --- core/src/main/java/org/lflang/generator/LFGenerator.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/java/org/lflang/generator/LFGenerator.java b/core/src/main/java/org/lflang/generator/LFGenerator.java index 9ee47427f8..0b1c0d7418 100644 --- a/core/src/main/java/org/lflang/generator/LFGenerator.java +++ b/core/src/main/java/org/lflang/generator/LFGenerator.java @@ -144,7 +144,6 @@ public boolean errorsOccurred() { /** * Check if a clean was requested from the standalone compiler and perform the clean step. * - *

FIXME: the signature can be reduced to only take context. */ protected void cleanIfNeeded(LFGeneratorContext context) { if (context.getArgs().containsKey("clean")) { From dab235fe57c00601a36f7df083b4b96c539c56a6 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 18 Jul 2023 14:27:08 -0700 Subject: [PATCH 302/516] Try to reduce volume of output. This is an attempt to address timeouts in CI. --- test/Python/src/federated/DistributedLoopedAction.lf | 2 +- test/Python/src/federated/DistributedLoopedPhysicalAction.lf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Python/src/federated/DistributedLoopedAction.lf b/test/Python/src/federated/DistributedLoopedAction.lf index 550284dfac..81394d62d0 100644 --- a/test/Python/src/federated/DistributedLoopedAction.lf +++ b/test/Python/src/federated/DistributedLoopedAction.lf @@ -15,7 +15,7 @@ reactor Receiver(take_a_break_after=10, break_interval = 400 msec) { state received_messages = 0 state total_received_messages = 0 state breaks = 0 - timer t(0, 1 msec) # This will impact the performance + timer t(0, 50 msec) # This will impact the performance # but forces the logical time to advance Comment this line for a more sensible log output. reaction(in_) {= diff --git a/test/Python/src/federated/DistributedLoopedPhysicalAction.lf b/test/Python/src/federated/DistributedLoopedPhysicalAction.lf index 51d73727dc..fa88bb3700 100644 --- a/test/Python/src/federated/DistributedLoopedPhysicalAction.lf +++ b/test/Python/src/federated/DistributedLoopedPhysicalAction.lf @@ -38,7 +38,7 @@ reactor Receiver(take_a_break_after=10, break_interval = 550 msec) { state total_received_messages = 0 state breaks = 0 # This will impact the performance - timer t(0, 1 msec) + timer t(0, 50 msec) # but forces the logical time to advance Comment this line for a more sensible log output. state base_logical_time From b7ba921efbe6e38409fda1d6a090465b94e7fa65 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 18 Jul 2023 16:51:42 -0700 Subject: [PATCH 303/516] Address failing LSP tests. --- .../java/org/lflang/federated/extensions/TSExtension.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java index 75930c7180..45f58bc3b4 100644 --- a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java @@ -107,12 +107,12 @@ public String generateNetworkSenderBody( CoordinationType coordinationType, MessageReporter messageReporter) { return """ - if (%1$s.%2$s !== undefined) { - this.util.sendRTITimedMessage(%1$s.%2$s, %3$s, %4$s, %5$s); + if (%1$s%2$s !== undefined) { + this.util.sendRTITimedMessage(%1$s%2$s, %3$s, %4$s, %5$s); } """ .formatted( - sendingPort.getContainer().getName(), + sendingPort.getContainer() == null ? "" : sendingPort.getContainer().getName() + ".", sendingPort.getVariable().getName(), connection.getDstFederate().id, connection.getDstFederate().networkMessageActions.size(), From 96991e0d813f630e2532cfd196fdff963b0800cf Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 18 Jul 2023 17:27:11 -0700 Subject: [PATCH 304/516] Another fix for the LSP tests. --- .../java/org/lflang/federated/extensions/TSExtension.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java index 45f58bc3b4..9887d7b0b5 100644 --- a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java @@ -49,12 +49,12 @@ public String generateNetworkReceiverBody( return """ // generateNetworkReceiverBody if (%1$s !== undefined) { - %2$s.%3$s = %1$s; + %2$s%3$s = %1$s; } """ .formatted( action.getName(), - receivingPort.getContainer().getName(), + receivingPort.getContainer() == null ? "" : receivingPort.getContainer().getName() + ".", receivingPort.getVariable().getName()); } From e0e83d422821b07813ec390f42281054acebfbc5 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 18 Jul 2023 17:43:28 -0700 Subject: [PATCH 305/516] Format. --- .../java/org/lflang/federated/extensions/TSExtension.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java index 9887d7b0b5..80d3933ce0 100644 --- a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java @@ -54,7 +54,9 @@ public String generateNetworkReceiverBody( """ .formatted( action.getName(), - receivingPort.getContainer() == null ? "" : receivingPort.getContainer().getName() + ".", + receivingPort.getContainer() == null + ? "" + : receivingPort.getContainer().getName() + ".", receivingPort.getVariable().getName()); } From 35f08874e70b6aad9398c93990c6455d6d7e0312 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 19 Jul 2023 11:15:57 +0200 Subject: [PATCH 306/516] Apply spotless --- core/src/main/java/org/lflang/generator/LFGenerator.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/LFGenerator.java b/core/src/main/java/org/lflang/generator/LFGenerator.java index 0b1c0d7418..410051f835 100644 --- a/core/src/main/java/org/lflang/generator/LFGenerator.java +++ b/core/src/main/java/org/lflang/generator/LFGenerator.java @@ -141,10 +141,7 @@ public boolean errorsOccurred() { return generatorErrorsOccurred; } - /** - * Check if a clean was requested from the standalone compiler and perform the clean step. - * - */ + /** 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 { From 054d4663c596cbe588383c174ef52b995ad6d89f Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 20 Jul 2023 13:11:53 -0700 Subject: [PATCH 307/516] Disable Python gc. --- .../federated/extensions/CExtension.java | 59 ++++++++++--------- .../federated/extensions/PythonExtension.java | 18 ++++++ 2 files changed, 50 insertions(+), 27 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index 3541ff0813..2da5ee1821 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -487,18 +487,13 @@ public String getNetworkBufferType() { return "uint8_t*"; } - /** - * 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 - public String generatePreamble( + /** Put the C preamble in a {@code include/_federate.name + _preamble.h} file. */ + protected final void writePreambleFile( FederateInstance federate, FedFileConfig fileConfig, RtiConfig rtiConfig, MessageReporter messageReporter) throws IOException { - // Put the C preamble in a {@code include/_federate.name + _preamble.h} file String cPreamble = makePreamble(federate, rtiConfig, messageReporter); String relPath = getPreamblePath(federate); Path fedPreamblePath = fileConfig.getSrcPath().resolve(relPath); @@ -506,27 +501,37 @@ public String generatePreamble( try (var writer = Files.newBufferedWriter(fedPreamblePath)) { writer.write(cPreamble); } - var includes = new CodeBuilder(); - if (federate.targetConfig.target != Target.Python) { - includes.pr( - """ - #ifdef __cplusplus - extern "C" { - #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/federated/clock-sync.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 - } - #endif"""); - includes.pr(generateSerializationIncludes(federate, fileConfig)); - } + } + /** + * 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 + public String generatePreamble( + FederateInstance federate, + FedFileConfig fileConfig, + RtiConfig rtiConfig, + MessageReporter messageReporter) + throws IOException { + writePreambleFile(federate, fileConfig, rtiConfig, messageReporter); + var includes = new CodeBuilder(); + includes.pr(""" + #ifdef __cplusplus + extern "C" { + #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/federated/clock-sync.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 + } + #endif"""); + includes.pr(generateSerializationIncludes(federate, fileConfig)); return includes.toString(); } diff --git a/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java b/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java index fa50ae9c63..9b85a0358c 100644 --- a/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java @@ -26,6 +26,7 @@ package org.lflang.federated.extensions; +import java.io.IOException; import org.lflang.InferredType; import org.lflang.MessageReporter; import org.lflang.TargetProperty.CoordinationType; @@ -33,6 +34,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.FedNativePythonSerialization; import org.lflang.federated.serialization.FedSerialization; import org.lflang.federated.serialization.SupportedSerializers; @@ -183,4 +185,20 @@ protected void serializeAndSend( public void annotateReaction(Reaction reaction) { ASTUtils.addReactionAttribute(reaction, "_c_body"); } + + @Override + public String generatePreamble( + FederateInstance federate, + FedFileConfig fileConfig, + RtiConfig rtiConfig, + MessageReporter messageReporter) + throws IOException { + writePreambleFile(federate, fileConfig, rtiConfig, messageReporter); + return """ + import gc + import atexit + gc.disable() + atexit.register(os._exit, 0) + """; + } } From 88e673f4fea9952d0d5c12e5dabb9b79c256baed Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 20 Jul 2023 21:18:16 -0700 Subject: [PATCH 308/516] Test clock synchronization. --- core/src/main/resources/lib/c/reactor-c | 2 +- test/C/src/federated/DistributedCountDecentralized.lf | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index ac3f64993d..907ba0e86a 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit ac3f64993d40d8db2ee5e3af3fb10cbb8035b7c1 +Subproject commit 907ba0e86a3cf7956e96257ab51c2d89c3c71069 diff --git a/test/C/src/federated/DistributedCountDecentralized.lf b/test/C/src/federated/DistributedCountDecentralized.lf index 78eb8f2435..1a45795fad 100644 --- a/test/C/src/federated/DistributedCountDecentralized.lf +++ b/test/C/src/federated/DistributedCountDecentralized.lf @@ -6,7 +6,11 @@ */ target C { timeout: 5 sec, - coordination: decentralized + coordination: decentralized, + clock-sync: on, + clock-sync-options: { + local-federates-on: true, + }, } import Count from "../lib/Count.lf" From f88d452633f4e09f7506005fa2205cfd8164ad47 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Thu, 20 Jul 2023 21:41:40 -0700 Subject: [PATCH 309/516] Apply formatter --- test/C/src/federated/DistributedCountDecentralized.lf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/C/src/federated/DistributedCountDecentralized.lf b/test/C/src/federated/DistributedCountDecentralized.lf index 1a45795fad..b06f56fb9b 100644 --- a/test/C/src/federated/DistributedCountDecentralized.lf +++ b/test/C/src/federated/DistributedCountDecentralized.lf @@ -9,8 +9,8 @@ target C { coordination: decentralized, clock-sync: on, clock-sync-options: { - local-federates-on: true, - }, + local-federates-on: true + } } import Count from "../lib/Count.lf" From 890a1e89523a511b17f662eeed257335c85a29e0 Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Fri, 21 Jul 2023 12:26:03 -0500 Subject: [PATCH 310/516] [feature] add board property with usb/uart switch --- .../main/java/org/lflang/TargetConfig.java | 5 ++++ .../lflang/generator/c/CCmakeGenerator.java | 29 ++++++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/lflang/TargetConfig.java b/core/src/main/java/org/lflang/TargetConfig.java index 86ac029131..6fb0dac8ae 100644 --- a/core/src/main/java/org/lflang/TargetConfig.java +++ b/core/src/main/java/org/lflang/TargetConfig.java @@ -384,6 +384,11 @@ public static class PlatformOptions { * The string value used to determine what type of embedded board we work with and can be used * to simplify the build process. For example, when we want to flash to an Arduino Nano 33 BLE * board, we can use the string arduino:mbed_nano:nano33ble + * TODO: move to lingo + * Can also be used to specify uart output setting on rp2040 boards + * where arduino_nano_rp2040_connect:uart + * or arduino_nano_rp2040_connect:usb (usb) + * fully specifies the board and uart output */ public String board = null; diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index dfd31a494c..4e942386f7 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -139,6 +139,11 @@ CodeBuilder generateCMakeCode( cMakeCode.pr("include(pico_sdk_import.cmake)"); cMakeCode.pr("project(" + executableName + " LANGUAGES C CXX ASM)"); cMakeCode.newLine(); + // board type for rp2040 based boards + if (targetConfig.platformOptions.board != null) { + String[] bProps = targetConfig.platformOptions.board.split(":"); + cMakeCode.pr("set(PICO_BOARD \"" + bProps[0] + "\")"); + } break; default: cMakeCode.pr("project(" + executableName + " LANGUAGES C)"); @@ -163,6 +168,7 @@ CodeBuilder generateCMakeCode( + " gcc\")"); cMakeCode.pr(" endif()"); cMakeCode.pr("endif()"); + // remove warnings for making building easier cMakeCode.pr("set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -w\")"); cMakeCode.pr("# Require C11"); @@ -251,6 +257,25 @@ CodeBuilder generateCMakeCode( cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/core/modal_models)"); cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/core/utils)"); + + // post target definition board configurations + if (targetConfig.platformOptions.board != null) { + switch(targetConfig.platformOptions.platform) { + case RP2040: + String[] bProps = targetConfig.platformOptions.board.split(":"); + cMakeCode.pr("# Set pico-sdk default build configurations"); + // uart ouput option provided + if (bProps.length > 1 && bProps[1].equals("uart")) { + cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 0)"); + cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 1)"); + } else if (bProps.length > 1 && bProps[1].equals("usb")) { + cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 1)"); + cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 0)"); + } + break; + } + } + if (targetConfig.auth) { // If security is requested, add the auth option. var osName = System.getProperty("os.name").toLowerCase(); @@ -446,12 +471,8 @@ private static String setUpMainTargetRp2040( code.unindent(); code.pr(")"); code.newLine(); - code.pr("# Set pico-sdk default build configurations"); - code.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 0)"); - code.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 1)"); code.pr("pico_add_extra_outputs(${LF_MAIN_TARGET})"); code.newLine(); - return code.toString(); } } From 9103508c24e2bf3029bccd4c9b5506f40d9e3bf6 Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Fri, 21 Jul 2023 13:33:58 -0500 Subject: [PATCH 311/516] [refactor] remove tests, set to basic reactor-c branch --- core/src/main/resources/lib/c/reactor-c | 2 +- test/C/src/rp2040/HelloPico.lf | 7 ------- test/C/src/rp2040/Thread.lf | 13 ------------- test/C/src/rp2040/Timer.lf | 8 -------- 4 files changed, 1 insertion(+), 29 deletions(-) delete mode 100644 test/C/src/rp2040/HelloPico.lf delete mode 100644 test/C/src/rp2040/Thread.lf delete mode 100644 test/C/src/rp2040/Timer.lf diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index ffc5b07355..13f00e8b7e 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit ffc5b073556f5dea7e9d4f107a6fa5ce5272c692 +Subproject commit 13f00e8b7e4caa9f6172a64a6a2473d97096e392 diff --git a/test/C/src/rp2040/HelloPico.lf b/test/C/src/rp2040/HelloPico.lf deleted file mode 100644 index bf4a7c7c29..0000000000 --- a/test/C/src/rp2040/HelloPico.lf +++ /dev/null @@ -1,7 +0,0 @@ -target C { - platform: "Rp2040" -} - -main reactor { - reaction(startup) {= printf("Hello World!\n"); =} -} diff --git a/test/C/src/rp2040/Thread.lf b/test/C/src/rp2040/Thread.lf deleted file mode 100644 index 2cb0494d1d..0000000000 --- a/test/C/src/rp2040/Thread.lf +++ /dev/null @@ -1,13 +0,0 @@ -target C { - platform: { - name: "Rp2040" - }, - threading: true, - workers: 3 -} - -main reactor { - timer t(0, 1 sec) - - reaction(t) {= prinf("Hello, World!"); =} -} diff --git a/test/C/src/rp2040/Timer.lf b/test/C/src/rp2040/Timer.lf deleted file mode 100644 index ef437a46f3..0000000000 --- a/test/C/src/rp2040/Timer.lf +++ /dev/null @@ -1,8 +0,0 @@ -target C { - platform: "Rp2040" -} - -main reactor { - timer t1(0, 100 msec) - reaction(t1) {= printf("Logical Time: %d. \n", lf_time_logical()); =} -} From bb70407b93e3ebd75c659435b950e8c98947500c Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Fri, 21 Jul 2023 13:38:06 -0500 Subject: [PATCH 312/516] [format] apply formatter --- .../main/java/org/lflang/TargetConfig.java | 9 ++--- .../lflang/generator/c/CCmakeGenerator.java | 35 +++++++++---------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/core/src/main/java/org/lflang/TargetConfig.java b/core/src/main/java/org/lflang/TargetConfig.java index 6fb0dac8ae..a8c9d6599e 100644 --- a/core/src/main/java/org/lflang/TargetConfig.java +++ b/core/src/main/java/org/lflang/TargetConfig.java @@ -383,12 +383,9 @@ public static class PlatformOptions { /** * The string value used to determine what type of embedded board we work with and can be used * to simplify the build process. For example, when we want to flash to an Arduino Nano 33 BLE - * board, we can use the string arduino:mbed_nano:nano33ble - * TODO: move to lingo - * Can also be used to specify uart output setting on rp2040 boards - * where arduino_nano_rp2040_connect:uart - * or arduino_nano_rp2040_connect:usb (usb) - * fully specifies the board and uart output + * board, we can use the string arduino:mbed_nano:nano33ble TODO: move to lingo Can also be used + * to specify uart output setting on rp2040 boards where arduino_nano_rp2040_connect:uart or + * arduino_nano_rp2040_connect:usb (usb) fully specifies the board and uart output */ public String board = null; diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 4e942386f7..28970b768c 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -141,8 +141,8 @@ CodeBuilder generateCMakeCode( cMakeCode.newLine(); // board type for rp2040 based boards if (targetConfig.platformOptions.board != null) { - String[] bProps = targetConfig.platformOptions.board.split(":"); - cMakeCode.pr("set(PICO_BOARD \"" + bProps[0] + "\")"); + String[] bProps = targetConfig.platformOptions.board.split(":"); + cMakeCode.pr("set(PICO_BOARD \"" + bProps[0] + "\")"); } break; default: @@ -257,23 +257,22 @@ CodeBuilder generateCMakeCode( cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/core/modal_models)"); cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/core/utils)"); - - // post target definition board configurations + // post target definition board configurations if (targetConfig.platformOptions.board != null) { - switch(targetConfig.platformOptions.platform) { - case RP2040: - String[] bProps = targetConfig.platformOptions.board.split(":"); - cMakeCode.pr("# Set pico-sdk default build configurations"); - // uart ouput option provided - if (bProps.length > 1 && bProps[1].equals("uart")) { - cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 0)"); - cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 1)"); - } else if (bProps.length > 1 && bProps[1].equals("usb")) { - cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 1)"); - cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 0)"); - } - break; - } + switch (targetConfig.platformOptions.platform) { + case RP2040: + String[] bProps = targetConfig.platformOptions.board.split(":"); + cMakeCode.pr("# Set pico-sdk default build configurations"); + // uart ouput option provided + if (bProps.length > 1 && bProps[1].equals("uart")) { + cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 0)"); + cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 1)"); + } else if (bProps.length > 1 && bProps[1].equals("usb")) { + cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 1)"); + cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 0)"); + } + break; + } } if (targetConfig.auth) { From 49a2003bdba95e08bb25716b925e5dbff0c00fcd Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Fri, 21 Jul 2023 15:49:19 -0500 Subject: [PATCH 313/516] default board stdio --- core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 28970b768c..3bbf89918f 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -270,6 +270,9 @@ CodeBuilder generateCMakeCode( } else if (bProps.length > 1 && bProps[1].equals("usb")) { cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 1)"); cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 0)"); + } else { + cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 1)"); + cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 1)"); } break; } From b77d48cede6ee6180c2d5973dffb1ca221020166 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 21 Jul 2023 22:51:59 -0700 Subject: [PATCH 314/516] Fix deadlock in StopAtShutdown for Python. See commit message in reactor-c for details. --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 793f350161..74b4eec482 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 793f35016127702889fd12690530fa39856b4d75 +Subproject commit 74b4eec482a519740f2120df7397f7155b38857d From 07cb011aee9fa41fbc7938a22d1abae21ceaa33f Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Sat, 22 Jul 2023 18:35:47 -0500 Subject: [PATCH 315/516] resolve comments --- .../main/java/org/lflang/TargetConfig.java | 5 ++-- .../org/lflang/generator/CodeBuilder.java | 6 +++-- .../lflang/generator/c/CCmakeGenerator.java | 24 +++++++++++++++---- .../org/lflang/generator/c/CGenerator.java | 4 ++-- core/src/main/resources/lib/c/reactor-c | 2 +- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/lflang/TargetConfig.java b/core/src/main/java/org/lflang/TargetConfig.java index a8c9d6599e..7d3f4c18eb 100644 --- a/core/src/main/java/org/lflang/TargetConfig.java +++ b/core/src/main/java/org/lflang/TargetConfig.java @@ -383,8 +383,9 @@ public static class PlatformOptions { /** * The string value used to determine what type of embedded board we work with and can be used * to simplify the build process. For example, when we want to flash to an Arduino Nano 33 BLE - * board, we can use the string arduino:mbed_nano:nano33ble TODO: move to lingo Can also be used - * to specify uart output setting on rp2040 boards where arduino_nano_rp2040_connect:uart or + * board, we can use the string arduino:mbed_nano:nano33ble + * Can also be used to specify uart output setting on + * rp2040 boards where arduino_nano_rp2040_connect:uart or * arduino_nano_rp2040_connect:usb (usb) fully specifies the board and uart output */ public String board = null; diff --git a/core/src/main/java/org/lflang/generator/CodeBuilder.java b/core/src/main/java/org/lflang/generator/CodeBuilder.java index 43ccde35c6..6231bf2b55 100644 --- a/core/src/main/java/org/lflang/generator/CodeBuilder.java +++ b/core/src/main/java/org/lflang/generator/CodeBuilder.java @@ -68,9 +68,9 @@ public int length() { /** Add a new line. */ public void newLine() { - this.pr("\n"); + this.pr(""); } - + /** * Append the specified text plus a final newline. * @@ -84,6 +84,8 @@ public void pr(String format, Object... args) { /** Append the given text to the code buffer at the current indentation level. */ public void pr(CharSequence text) { + // append newline on empty input string + if (text.toString().equals("")) code.append("\n"); for (String line : (Iterable) () -> text.toString().lines().iterator()) { code.append(indentation).append(line).append("\n"); } diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 3bbf89918f..42b9258db2 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -141,8 +141,19 @@ CodeBuilder generateCMakeCode( cMakeCode.newLine(); // board type for rp2040 based boards if (targetConfig.platformOptions.board != null) { - String[] bProps = targetConfig.platformOptions.board.split(":"); - cMakeCode.pr("set(PICO_BOARD \"" + bProps[0] + "\")"); + // board syntax : + // ignore whitespace + String[] bProps = targetConfig.platformOptions.board. + trim(). + split(":"); + for (int i = 0; i < bProps.length; i++) { + bProps[i] = bProps[i].trim(); + } + if (bProps.length < 1 || bProps[0].equals("")) { + cMakeCode.pr("set(PICO_BOARD pico)"); + } else { + cMakeCode.pr("set(PICO_BOARD \"" + bProps[0] + "\")"); + } } break; default: @@ -261,8 +272,12 @@ CodeBuilder generateCMakeCode( if (targetConfig.platformOptions.board != null) { switch (targetConfig.platformOptions.platform) { case RP2040: - String[] bProps = targetConfig.platformOptions.board.split(":"); - cMakeCode.pr("# Set pico-sdk default build configurations"); + String[] bProps = targetConfig.platformOptions.board. + trim(). + split(":"); + for (int i = 0; i < bProps.length; i++) { + bProps[i] = bProps[i].trim(); + } // uart ouput option provided if (bProps.length > 1 && bProps[1].equals("uart")) { cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 0)"); @@ -271,6 +286,7 @@ CodeBuilder generateCMakeCode( cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 1)"); cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 0)"); } else { + cMakeCode.pr("# Enable both usb and uart stdio"); cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 1)"); cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 1)"); } diff --git a/core/src/main/java/org/lflang/generator/c/CGenerator.java b/core/src/main/java/org/lflang/generator/c/CGenerator.java index ce238b6246..d34e5b05af 100644 --- a/core/src/main/java/org/lflang/generator/c/CGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CGenerator.java @@ -520,7 +520,7 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { .map(key -> "\"-D" + key + "=" + targetConfig.compileDefinitions.get(key) + "\"") .collect(Collectors.joining(",\n")); String settings = "{\n" + "\"cmake.configureArgs\": [\n" + compileDefs + "\n]\n}\n"; - Path vscodePath = Path.of(fileConfig.getSrcGenPath() + File.separator + ".vscode"); + Path vscodePath = fileConfig.getSrcGenPath().resolve(".vscode"); if (!Files.exists(vscodePath)) Files.createDirectory(vscodePath); FileUtil.writeToFile( settings, @@ -933,7 +933,7 @@ protected void copyTargetFiles() throws IOException { // For the pico src-gen, copy over vscode configurations for debugging if (targetConfig.platformOptions.platform == Platform.RP2040) { - Path vscodePath = Path.of(fileConfig.getSrcGenPath() + File.separator + ".vscode"); + Path vscodePath = fileConfig.getSrcGenPath().resolve(".vscode"); // If pico-sdk-path not defined, this can be used to pull the sdk into src-gen FileUtil.copyFileFromClassPath( "/lib/platform/rp2040/pico_sdk_import.cmake", fileConfig.getSrcGenPath(), true); diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 13f00e8b7e..e27c747186 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 13f00e8b7e4caa9f6172a64a6a2473d97096e392 +Subproject commit e27c7471869b4d721d8c7e79dda69ccc4b51c189 From 53481669aa9a0adcd27fb449cb19ffb157562cc7 Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Sat, 22 Jul 2023 18:49:39 -0500 Subject: [PATCH 316/516] cmake warnings and format --- .../src/main/java/org/lflang/TargetConfig.java | 5 ++--- .../java/org/lflang/generator/CodeBuilder.java | 2 +- .../lflang/generator/c/CCmakeGenerator.java | 18 +++++++----------- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/org/lflang/TargetConfig.java b/core/src/main/java/org/lflang/TargetConfig.java index 7d3f4c18eb..5772e7e169 100644 --- a/core/src/main/java/org/lflang/TargetConfig.java +++ b/core/src/main/java/org/lflang/TargetConfig.java @@ -383,9 +383,8 @@ public static class PlatformOptions { /** * The string value used to determine what type of embedded board we work with and can be used * to simplify the build process. For example, when we want to flash to an Arduino Nano 33 BLE - * board, we can use the string arduino:mbed_nano:nano33ble - * Can also be used to specify uart output setting on - * rp2040 boards where arduino_nano_rp2040_connect:uart or + * board, we can use the string arduino:mbed_nano:nano33ble Can also be used to specify uart + * output setting on rp2040 boards where arduino_nano_rp2040_connect:uart or * arduino_nano_rp2040_connect:usb (usb) fully specifies the board and uart output */ public String board = null; diff --git a/core/src/main/java/org/lflang/generator/CodeBuilder.java b/core/src/main/java/org/lflang/generator/CodeBuilder.java index 6231bf2b55..c868d2026e 100644 --- a/core/src/main/java/org/lflang/generator/CodeBuilder.java +++ b/core/src/main/java/org/lflang/generator/CodeBuilder.java @@ -70,7 +70,7 @@ public int length() { public void newLine() { this.pr(""); } - + /** * Append the specified text plus a final newline. * diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 42b9258db2..d1d03a5181 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -141,13 +141,11 @@ CodeBuilder generateCMakeCode( cMakeCode.newLine(); // board type for rp2040 based boards if (targetConfig.platformOptions.board != null) { - // board syntax : + // board syntax : // ignore whitespace - String[] bProps = targetConfig.platformOptions.board. - trim(). - split(":"); + String[] bProps = targetConfig.platformOptions.board.trim().split(":"); for (int i = 0; i < bProps.length; i++) { - bProps[i] = bProps[i].trim(); + bProps[i] = bProps[i].trim(); } if (bProps.length < 1 || bProps[0].equals("")) { cMakeCode.pr("set(PICO_BOARD pico)"); @@ -155,6 +153,8 @@ CodeBuilder generateCMakeCode( cMakeCode.pr("set(PICO_BOARD \"" + bProps[0] + "\")"); } } + // remove warnings for rp2040 only to make debug easier + cMakeCode.pr("set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -w\")"); break; default: cMakeCode.pr("project(" + executableName + " LANGUAGES C)"); @@ -179,8 +179,6 @@ CodeBuilder generateCMakeCode( + " gcc\")"); cMakeCode.pr(" endif()"); cMakeCode.pr("endif()"); - // remove warnings for making building easier - cMakeCode.pr("set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -w\")"); cMakeCode.pr("# Require C11"); cMakeCode.pr("set(CMAKE_C_STANDARD 11)"); @@ -272,11 +270,9 @@ CodeBuilder generateCMakeCode( if (targetConfig.platformOptions.board != null) { switch (targetConfig.platformOptions.platform) { case RP2040: - String[] bProps = targetConfig.platformOptions.board. - trim(). - split(":"); + String[] bProps = targetConfig.platformOptions.board.trim().split(":"); for (int i = 0; i < bProps.length; i++) { - bProps[i] = bProps[i].trim(); + bProps[i] = bProps[i].trim(); } // uart ouput option provided if (bProps.length > 1 && bProps[1].equals("uart")) { From 928db8e672b0c71c3e1f52fd76636fa00ff342b1 Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Sat, 22 Jul 2023 18:53:22 -0500 Subject: [PATCH 317/516] [submodule] merge main --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index e27c747186..e15597ebb2 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit e27c7471869b4d721d8c7e79dda69ccc4b51c189 +Subproject commit e15597ebb2749dff1fba72af12ec6ad56eb99b7e From c6c947f91486f54e2b3500b4bcbe28b516620577 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 22 Jul 2023 17:25:55 -0700 Subject: [PATCH 318/516] "Fix" some test failures. We use tests that have nondeterministic behavior wrt the model of a machine that has nondeterministic execution times. These changes may make the nondeterminism less likely to be a problem. --- core/src/main/resources/lib/c/reactor-c | 2 +- .../federated/LoopDistributedCentralizedPhysicalAction.lf | 6 +++--- test/C/src/federated/LoopDistributedDouble.lf | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 74b4eec482..6a0ab07d91 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 74b4eec482a519740f2120df7397f7155b38857d +Subproject commit 6a0ab07d9180d0911e40275e4dde2fa839a1fcdb diff --git a/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf b/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf index 2f0da4e3de..7c31b0c55b 100644 --- a/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf +++ b/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf @@ -9,7 +9,7 @@ target C { coordination-options: { advance-message-interval: 100 msec }, - timeout: 5 sec + timeout: 6 sec } preamble {= @@ -26,7 +26,7 @@ reactor Looper(incr: int = 1, delay: time = 0 msec) { while(!stop) { lf_print("Scheduling action."); lf_schedule(actionref, 0); - sleep(1); + sleep(2); } return NULL; } @@ -59,7 +59,7 @@ reactor Looper(incr: int = 1, delay: time = 0 msec) { lf_print("******* Shutdown invoked."); // Stop the thread that is scheduling actions. stop = true; - if (self->count != 5 * self->incr) { + if (self->count != 3 * self->incr) { lf_print_error_and_exit("Failed to receive all five expected inputs."); } =} diff --git a/test/C/src/federated/LoopDistributedDouble.lf b/test/C/src/federated/LoopDistributedDouble.lf index 45d3c207ac..a2b19f92f8 100644 --- a/test/C/src/federated/LoopDistributedDouble.lf +++ b/test/C/src/federated/LoopDistributedDouble.lf @@ -9,7 +9,7 @@ target C { coordination-options: { advance-message-interval: 100 msec }, - timeout: 5 sec + timeout: 6 sec } preamble {= @@ -26,7 +26,7 @@ reactor Looper(incr: int = 1, delay: time = 0 msec) { while(!stop) { lf_print("Scheduling action."); lf_schedule(actionref, 0); - sleep(1); + sleep(2); } return NULL; } @@ -37,7 +37,7 @@ reactor Looper(incr: int = 1, delay: time = 0 msec) { output out2: int physical action a(delay) state count: int = 0 - timer t(0, 1 sec) + timer t(0, 2 sec) reaction(startup) -> a {= // Start the thread that listens for Enter or Return. @@ -82,7 +82,7 @@ reactor Looper(incr: int = 1, delay: time = 0 msec) { lf_print("******* Shutdown invoked."); // Stop the thread that is scheduling actions. stop = true; - if (self->count != 5 * self->incr) { + if (self->count != 3 * self->incr) { lf_print_error_and_exit("Failed to receive all five expected inputs."); } =} From 1062ce14a5b21138b25b845f6277e8709b80964b Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Mon, 24 Jul 2023 11:21:52 +0900 Subject: [PATCH 319/516] Use the class 'NetworkReactor' for network sender and receiver reactors --- .../org/lflang/generator/ts/TSActionGenerator.kt | 11 +++++++++-- .../org/lflang/generator/ts/TSConstructorGenerator.kt | 9 ++++++++- .../lflang/generator/ts/TSImportPreambleGenerator.kt | 2 +- .../org/lflang/generator/ts/TSInstanceGenerator.kt | 11 +++++++++++ .../org/lflang/generator/ts/TSReactorGenerator.kt | 7 ++++++- 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt index ec81ba898b..227404e12f 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt @@ -52,8 +52,15 @@ class TSActionGenerator( // actionInstantiations.add( // "this.${action.name} = new __Action<${action.tsActionType}>($actionArgs);") // } - actionInstantiations.add( - "this.${action.name} = new __Action<${action.tsActionType}>($actionArgs);") + if (action.name.take(7) == "network") { + actionInstantiations.add( + "this.${action.name} = new __FederatePortAction<${action.tsActionType}>($actionArgs);") + actionInstantiations.add( + "this.registerNetworkInputAction<${action.tsActionType}>(this.${action.name})") + } else { + actionInstantiations.add( + "this.${action.name} = new __Action<${action.tsActionType}>($actionArgs);") + } } } return actionInstantiations.joinToString("\n") diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt index b6624d797f..4ebbaa7d47 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt @@ -34,6 +34,9 @@ class TSConstructorGenerator( arguments.add("federationID: string = 'Unidentified Federation'") } else { arguments.add("parent: __Reactor") + if (reactor.name.take(15) == "NetworkReceiver") { + arguments.add("portID: number") + } } // For TS, parameters are arguments of the class constructor. @@ -65,7 +68,11 @@ class TSConstructorGenerator( "super(timeout, keepAlive, fast, success, fail);" } } else { - "super(parent);" + if (reactor.name.take(15) == "NetworkReceiver") { + "super(parent, portID);" + } else { + "super(parent);" + } } // If the app is federated, register its diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSImportPreambleGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSImportPreambleGenerator.kt index c7f780f8ee..d71d39a2e8 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSImportPreambleGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSImportPreambleGenerator.kt @@ -57,7 +57,7 @@ class TSImportPreambleGenerator( import {Parameter as __Parameter, Timer as __Timer, Reactor as __Reactor, App as __App} from '@lf-lang/reactor-ts' import {Action as __Action, Startup as __Startup, FederatePortAction as __FederatePortAction} from '@lf-lang/reactor-ts' import {Bank as __Bank} from '@lf-lang/reactor-ts' - import {FederatedApp as __FederatedApp, FederateConfig as __FederateConfig} from '@lf-lang/reactor-ts' + import {NetworkReactor as __NetworkReactor, FederatedApp as __FederatedApp, FederateConfig as __FederateConfig} from '@lf-lang/reactor-ts' import {InPort as __InPort, OutPort as __OutPort, Port as __Port, WritablePort as __WritablePort, WritableMultiPort as __WritableMultiPort} from '@lf-lang/reactor-ts' import {InMultiPort as __InMultiPort, OutMultiPort as __OutMultiPort} from '@lf-lang/reactor-ts' import {Reaction as __Reaction} from '@lf-lang/reactor-ts' diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt index 97e753f99b..1bf8ac006f 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt @@ -48,6 +48,9 @@ class TSInstanceGenerator( for (childReactor in childReactors) { val childReactorArguments = StringJoiner(", ") childReactorArguments.add("this") + if (childReactor.reactorClass.name.take(15) == "NetworkReceiver") { + childReactorArguments.add(childReactor.reactorClass.name.takeLast(1)) + } for (parameter in childReactor.reactorClass.toDefinition().parameters) { childReactorArguments.add(TSTypes.getInstance().getTargetInitializer(parameter, childReactor)) @@ -64,6 +67,14 @@ class TSInstanceGenerator( childReactorInstantiations.add( "this.${childReactor.name} = " + "new ${childReactor.reactorClass.name}($childReactorArguments)") + if (childReactor.reactorClass.name.take(15) == "NetworkReceiver") { + childReactorInstantiations.add( + "this.registerNetworkReciever(this.${childReactor.name})") + } + if (childReactor.reactorClass.name.take(13) == "NetworkSender") { + childReactorInstantiations.add( + "this.registerNetworkSender(this.${childReactor.name})") + } } } return childReactorInstantiations.joinToString("\n") diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt index e3be95f91c..54cc76b915 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt @@ -117,6 +117,7 @@ ${" |"..preamble.code.toText()} } val isFederate = AttributeUtils.isFederate(reactor) + val isNetworkReactor = reactorName.take(7) == "Network" // val networkMessageActions = getNetworkMessageActions(reactor) // NOTE: type parameters that are referenced in ports or actions must extend @@ -128,7 +129,11 @@ ${" |"..preamble.code.toText()} "class $reactorName extends __App {" } } else { - "export class $reactorName extends __Reactor {" + if (isNetworkReactor) { + "export class $reactorName extends __NetworkReactor {" + } else { + "export class $reactorName extends __Reactor {" + } } val instanceGenerator = TSInstanceGenerator(reactor) From 9632a8f0616ed3de05b3773d6b852697a0b42a5e Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Mon, 24 Jul 2023 15:33:12 +0900 Subject: [PATCH 320/516] Instantiate network receivers with a proper portID --- .../kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt index 1bf8ac006f..2df5dfa80f 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt @@ -45,11 +45,14 @@ class TSInstanceGenerator( fun generateInstantiations(): String { val childReactorInstantiations = LinkedList() + var portID = 0 for (childReactor in childReactors) { val childReactorArguments = StringJoiner(", ") childReactorArguments.add("this") if (childReactor.reactorClass.name.take(15) == "NetworkReceiver") { - childReactorArguments.add(childReactor.reactorClass.name.takeLast(1)) + // Assume that network receiver reactors are sorted by portID + childReactorArguments.add(portID.toString()) + portID++ } for (parameter in childReactor.reactorClass.toDefinition().parameters) { From 307902578e4d18175b81f336d4296f306a10f70d Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Mon, 24 Jul 2023 17:34:55 +0900 Subject: [PATCH 321/516] Fill the port absent reactor body --- .../federated/extensions/TSExtension.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java index 80d3933ce0..32f9bd7f56 100644 --- a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java @@ -123,13 +123,30 @@ public String generateNetworkSenderBody( private String getNetworkDelayLiteral(Expression e) { var cLiteral = CExtensionUtils.getNetworkDelayLiteral(e); - return cLiteral.equals("NEVER") ? "0" : cLiteral; + return cLiteral.equals("NEVER") ? "TimeValue.never()" : cLiteral; } @Override public String generateNetworkOutputControlReactionBody( VarRef srcOutputPort, FedConnectionInstance connection) { - return "// TODO(hokeun): Figure out what to do for generateNetworkOutputControlReactionBody"; + // The ID of the receiving port (rightPort) is the position + // of the networkAction (see below) in this list. + int receivingPortID = connection.getDstFederate().networkMessageActions.size(); + var additionalDelayString = getNetworkDelayLiteral(connection.getDefinition().getDelay()); + return """ + // If the output port has not been set for the current logical time, + // send an ABSENT message to the receiving federate + if (%1$s%2$s === undefined) { + this.util.sendRTIPortAbsent(%3$s, %4$d, %5$d); + } + """ + .formatted( + srcOutputPort.getContainer() == null ? "" : srcOutputPort.getContainer().getName() + ".", + srcOutputPort.getVariable().getName(), + additionalDelayString, + connection.getDstFederate().id, + receivingPortID + ); } @Override From 6a9c6bf2b499e18b208b574f5d6e686281ba086f Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Mon, 24 Jul 2023 17:49:47 +0900 Subject: [PATCH 322/516] Minor fixes --- .../federated/extensions/TSExtension.java | 23 ++++++++++--------- .../federated/generator/FedASTUtils.java | 1 - 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java index 32f9bd7f56..332eeaeee9 100644 --- a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java @@ -129,24 +129,25 @@ private String getNetworkDelayLiteral(Expression e) { @Override public String generateNetworkOutputControlReactionBody( VarRef srcOutputPort, FedConnectionInstance connection) { - // The ID of the receiving port (rightPort) is the position + // The ID of the receiving port (rightPort) is the position // of the networkAction (see below) in this list. int receivingPortID = connection.getDstFederate().networkMessageActions.size(); var additionalDelayString = getNetworkDelayLiteral(connection.getDefinition().getDelay()); return """ - // If the output port has not been set for the current logical time, - // send an ABSENT message to the receiving federate - if (%1$s%2$s === undefined) { - this.util.sendRTIPortAbsent(%3$s, %4$d, %5$d); - } - """ - .formatted( - srcOutputPort.getContainer() == null ? "" : srcOutputPort.getContainer().getName() + ".", + // If the output port has not been set for the current logical time, + // send an ABSENT message to the receiving federate + if (%1$s%2$s === undefined) { + this.util.sendRTIPortAbsent(%3$s, %4$d, %5$d); + } + """ + .formatted( + srcOutputPort.getContainer() == null + ? "" + : srcOutputPort.getContainer().getName() + ".", srcOutputPort.getVariable().getName(), additionalDelayString, connection.getDstFederate().id, - receivingPortID - ); + receivingPortID); } @Override diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 81497f2776..7da95b6c60 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -44,7 +44,6 @@ import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.xtext.xbase.lib.IteratorExtensions; import org.lflang.InferredType; -import org.lflang.Target; import org.lflang.MessageReporter; import org.lflang.TargetProperty.CoordinationType; import org.lflang.TimeValue; From bf56d36098cc437f2b2d5f2c6c1351c3c0e6f77a Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Mon, 24 Jul 2023 19:22:45 +0900 Subject: [PATCH 323/516] Use a proper foramt of TimeValue --- .../java/org/lflang/federated/extensions/TSExtension.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java index 332eeaeee9..956823a1bf 100644 --- a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java @@ -123,7 +123,7 @@ public String generateNetworkSenderBody( private String getNetworkDelayLiteral(Expression e) { var cLiteral = CExtensionUtils.getNetworkDelayLiteral(e); - return cLiteral.equals("NEVER") ? "TimeValue.never()" : cLiteral; + return cLiteral.equals("NEVER") ? "TimeValue.never()" : "TimeValue.nsec(" + cLiteral + ")"; } @Override @@ -259,7 +259,7 @@ private List getUpstreamConnectionDelays(FederateInstance federate) { if (delay == null) { element += "TimeValue.never()"; } else { - element += "TimeValue.nsec(" + getNetworkDelayLiteral(delay) + ")"; + element += getNetworkDelayLiteral(delay); } cnt++; if (cnt != delays.size()) { From e69890732601bda43aad74d96c9e1dc04dce7958 Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Mon, 24 Jul 2023 12:45:49 -0700 Subject: [PATCH 324/516] [cmake] add defaults for null board --- .../lflang/generator/c/CCmakeGenerator.java | 20 +++++++++---------- core/src/main/resources/lib/c/reactor-c | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index d1d03a5181..63262147e7 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -267,27 +267,27 @@ CodeBuilder generateCMakeCode( cMakeCode.pr("target_include_directories(${LF_MAIN_TARGET} PUBLIC include/core/utils)"); // post target definition board configurations - if (targetConfig.platformOptions.board != null) { - switch (targetConfig.platformOptions.platform) { - case RP2040: + switch (targetConfig.platformOptions.platform) { + case RP2040: + if (targetConfig.platformOptions.board != null) { String[] bProps = targetConfig.platformOptions.board.trim().split(":"); for (int i = 0; i < bProps.length; i++) { bProps[i] = bProps[i].trim(); } - // uart ouput option provided if (bProps.length > 1 && bProps[1].equals("uart")) { cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 0)"); cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 1)"); } else if (bProps.length > 1 && bProps[1].equals("usb")) { cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 1)"); cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 0)"); - } else { - cMakeCode.pr("# Enable both usb and uart stdio"); - cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 1)"); - cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 1)"); } - break; - } + } else { + // default + cMakeCode.pr("# Enable both usb and uart stdio"); + cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 1)"); + cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 1)"); + } + break; } if (targetConfig.auth) { diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index e15597ebb2..77ce2c42c9 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit e15597ebb2749dff1fba72af12ec6ad56eb99b7e +Subproject commit 77ce2c42c900f1aeaeadaddec5efd53ceca69bae From bba51a2a5562f07ffafb88ee456869459c1b63c0 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 24 Jul 2023 12:58:59 -0700 Subject: [PATCH 325/516] Revert ""Fix" some test failures." This reverts commit c6c947f91486f54e2b3500b4bcbe28b516620577. --- core/src/main/resources/lib/c/reactor-c | 2 +- .../federated/LoopDistributedCentralizedPhysicalAction.lf | 6 +++--- test/C/src/federated/LoopDistributedDouble.lf | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 6a0ab07d91..74b4eec482 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 6a0ab07d9180d0911e40275e4dde2fa839a1fcdb +Subproject commit 74b4eec482a519740f2120df7397f7155b38857d diff --git a/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf b/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf index 7c31b0c55b..2f0da4e3de 100644 --- a/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf +++ b/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf @@ -9,7 +9,7 @@ target C { coordination-options: { advance-message-interval: 100 msec }, - timeout: 6 sec + timeout: 5 sec } preamble {= @@ -26,7 +26,7 @@ reactor Looper(incr: int = 1, delay: time = 0 msec) { while(!stop) { lf_print("Scheduling action."); lf_schedule(actionref, 0); - sleep(2); + sleep(1); } return NULL; } @@ -59,7 +59,7 @@ reactor Looper(incr: int = 1, delay: time = 0 msec) { lf_print("******* Shutdown invoked."); // Stop the thread that is scheduling actions. stop = true; - if (self->count != 3 * self->incr) { + if (self->count != 5 * self->incr) { lf_print_error_and_exit("Failed to receive all five expected inputs."); } =} diff --git a/test/C/src/federated/LoopDistributedDouble.lf b/test/C/src/federated/LoopDistributedDouble.lf index a2b19f92f8..45d3c207ac 100644 --- a/test/C/src/federated/LoopDistributedDouble.lf +++ b/test/C/src/federated/LoopDistributedDouble.lf @@ -9,7 +9,7 @@ target C { coordination-options: { advance-message-interval: 100 msec }, - timeout: 6 sec + timeout: 5 sec } preamble {= @@ -26,7 +26,7 @@ reactor Looper(incr: int = 1, delay: time = 0 msec) { while(!stop) { lf_print("Scheduling action."); lf_schedule(actionref, 0); - sleep(2); + sleep(1); } return NULL; } @@ -37,7 +37,7 @@ reactor Looper(incr: int = 1, delay: time = 0 msec) { output out2: int physical action a(delay) state count: int = 0 - timer t(0, 2 sec) + timer t(0, 1 sec) reaction(startup) -> a {= // Start the thread that listens for Enter or Return. @@ -82,7 +82,7 @@ reactor Looper(incr: int = 1, delay: time = 0 msec) { lf_print("******* Shutdown invoked."); // Stop the thread that is scheduling actions. stop = true; - if (self->count != 3 * self->incr) { + if (self->count != 5 * self->incr) { lf_print_error_and_exit("Failed to receive all five expected inputs."); } =} From d49ef82f16df4d1f798cf4d9ae99f743fabbcae0 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 24 Jul 2023 13:41:51 -0700 Subject: [PATCH 326/516] Try again to work around the test failures. I noticed that the test that fails most often had an out of memory error in CI at one point. Usually the problem was something else -- undetermined because the debug output was being truncated. However this is an indication that the debug output is excessive. We have spent a lot of time struggling with excessive debug output in CI and so I am resorting to this most radical solution of allowing to disable it for specific tests. --- .../java/org/lflang/tests/runtime/PythonTest.java | 5 +++-- .../org/lflang/tests/serialization/SerializationTest.java | 5 +++-- core/src/testFixtures/java/org/lflang/tests/TestBase.java | 7 ++++--- .../federated/LoopDistributedCentralizedPhysicalAction.lf | 3 ++- test/C/src/federated/LoopDistributedDouble.lf | 3 ++- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/core/src/integrationTest/java/org/lflang/tests/runtime/PythonTest.java b/core/src/integrationTest/java/org/lflang/tests/runtime/PythonTest.java index e1ffbd57b8..5b72c406d9 100644 --- a/core/src/integrationTest/java/org/lflang/tests/runtime/PythonTest.java +++ b/core/src/integrationTest/java/org/lflang/tests/runtime/PythonTest.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.lflang.Target; +import org.lflang.TargetConfig; import org.lflang.tests.RuntimeTest; /** @@ -47,8 +48,8 @@ public PythonTest() { } @Override - protected void addExtraLfcArgs(Properties args) { - super.addExtraLfcArgs(args); + protected void addExtraLfcArgs(Properties args, TargetConfig targetConfig) { + super.addExtraLfcArgs(args, targetConfig); if (System.getProperty("os.name").startsWith("Windows")) { // Use the RelWithDebInfo build type on Windows as the Debug/Test build type produces linker // Errors in CI diff --git a/core/src/integrationTest/java/org/lflang/tests/serialization/SerializationTest.java b/core/src/integrationTest/java/org/lflang/tests/serialization/SerializationTest.java index 26f3ec37af..8f92c63437 100644 --- a/core/src/integrationTest/java/org/lflang/tests/serialization/SerializationTest.java +++ b/core/src/integrationTest/java/org/lflang/tests/serialization/SerializationTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; import org.lflang.Target; +import org.lflang.TargetConfig; import org.lflang.tests.Configurators; import org.lflang.tests.TestBase; import org.lflang.tests.TestRegistry.TestCategory; @@ -15,8 +16,8 @@ protected SerializationTest() { } @Override - protected void addExtraLfcArgs(Properties args) { - super.addExtraLfcArgs(args); + protected void addExtraLfcArgs(Properties args, TargetConfig targetConfig) { + super.addExtraLfcArgs(args, targetConfig); // Use the Debug build type as coverage generation does not work for the serialization tests args.setProperty("build-type", "Debug"); } diff --git a/core/src/testFixtures/java/org/lflang/tests/TestBase.java b/core/src/testFixtures/java/org/lflang/tests/TestBase.java index b1b52b0c2e..9911314a01 100644 --- a/core/src/testFixtures/java/org/lflang/tests/TestBase.java +++ b/core/src/testFixtures/java/org/lflang/tests/TestBase.java @@ -40,6 +40,7 @@ import org.lflang.LFRuntimeModule; import org.lflang.LFStandaloneSetup; import org.lflang.Target; +import org.lflang.TargetConfig; import org.lflang.generator.GeneratorResult; import org.lflang.generator.LFGenerator; import org.lflang.generator.LFGeneratorContext; @@ -386,7 +387,6 @@ private void configure(LFTest test, Configurator configurator, TestLevel level) throws IOException, TestError { var props = new Properties(); props.setProperty("hierarchical-bin", "true"); - addExtraLfcArgs(props); var sysProps = System.getProperties(); // Set the external-runtime-path property if it was specified. @@ -426,6 +426,7 @@ private void configure(LFTest test, Configurator configurator, TestLevel level) r, fileAccess, fileConfig -> new DefaultMessageReporter()); + addExtraLfcArgs(props, context.getTargetConfig()); test.configure(context); @@ -469,9 +470,9 @@ private void validate(LFTest test) throws TestError { } /** Override to add some LFC arguments to all runs of this test class. */ - protected void addExtraLfcArgs(Properties args) { + protected void addExtraLfcArgs(Properties args, TargetConfig targetConfig) { args.setProperty("build-type", "Test"); - args.setProperty("logging", "Debug"); + if (targetConfig.logLevel == null) args.setProperty("logging", "Debug"); } /** diff --git a/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf b/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf index 2f0da4e3de..0d5f6b7591 100644 --- a/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf +++ b/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf @@ -9,7 +9,8 @@ target C { coordination-options: { advance-message-interval: 100 msec }, - timeout: 5 sec + timeout: 5 sec, + logging: log } preamble {= diff --git a/test/C/src/federated/LoopDistributedDouble.lf b/test/C/src/federated/LoopDistributedDouble.lf index 45d3c207ac..187db3df6f 100644 --- a/test/C/src/federated/LoopDistributedDouble.lf +++ b/test/C/src/federated/LoopDistributedDouble.lf @@ -9,7 +9,8 @@ target C { coordination-options: { advance-message-interval: 100 msec }, - timeout: 5 sec + timeout: 5 sec, + logging: log } preamble {= From e6c4b63c48181e8c8df35065a9961eaad3878e3d Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Mon, 24 Jul 2023 15:55:32 -0700 Subject: [PATCH 327/516] exception for null target property string --- core/src/main/java/org/lflang/TargetProperty.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/src/main/java/org/lflang/TargetProperty.java b/core/src/main/java/org/lflang/TargetProperty.java index 3c43ac4665..19e9175434 100644 --- a/core/src/main/java/org/lflang/TargetProperty.java +++ b/core/src/main/java/org/lflang/TargetProperty.java @@ -455,6 +455,13 @@ public enum TargetProperty { config.platformOptions = new PlatformOptions(); config.platformOptions.platform = (Platform) UnionType.PLATFORM_UNION.forName(ASTUtils.elementToSingleString(value)); + if (config.platformOptions.platform == null) { + String s = + "Unidentified Platform Type, LF supports the following platform types: " + + Arrays.asList(Platform.values()).toString(); + err.at(value).error(s); + throw new AssertionError(s); + } } else { config.platformOptions = new PlatformOptions(); for (KeyValuePair entry : value.getKeyvalue().getPairs()) { From e057e0a6f3a0a31843f415251e93d91126791fa6 Mon Sep 17 00:00:00 2001 From: Abhinav Gundrala Date: Mon, 24 Jul 2023 16:07:15 -0700 Subject: [PATCH 328/516] [format] spotless apply --- core/src/main/java/org/lflang/TargetProperty.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/lflang/TargetProperty.java b/core/src/main/java/org/lflang/TargetProperty.java index 19e9175434..16cb142640 100644 --- a/core/src/main/java/org/lflang/TargetProperty.java +++ b/core/src/main/java/org/lflang/TargetProperty.java @@ -457,8 +457,8 @@ public enum TargetProperty { (Platform) UnionType.PLATFORM_UNION.forName(ASTUtils.elementToSingleString(value)); if (config.platformOptions.platform == null) { String s = - "Unidentified Platform Type, LF supports the following platform types: " - + Arrays.asList(Platform.values()).toString(); + "Unidentified Platform Type, LF supports the following platform types: " + + Arrays.asList(Platform.values()).toString(); err.at(value).error(s); throw new AssertionError(s); } From 2fa5bbe24c8a9777b9ea342b8cc63cf589facb6b Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Tue, 25 Jul 2023 10:25:33 +0900 Subject: [PATCH 329/516] Add the generic type of `NetworkReactor` --- .../main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt | 2 +- .../main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt | 3 ++- .../main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt index 227404e12f..896fd51ee8 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt @@ -56,7 +56,7 @@ class TSActionGenerator( actionInstantiations.add( "this.${action.name} = new __FederatePortAction<${action.tsActionType}>($actionArgs);") actionInstantiations.add( - "this.registerNetworkInputAction<${action.tsActionType}>(this.${action.name})") + "this.registerNetworkInputAction(this.${action.name})") } else { actionInstantiations.add( "this.${action.name} = new __Action<${action.tsActionType}>($actionArgs);") diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt index 2df5dfa80f..632febf920 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt @@ -72,7 +72,8 @@ class TSInstanceGenerator( "new ${childReactor.reactorClass.name}($childReactorArguments)") if (childReactor.reactorClass.name.take(15) == "NetworkReceiver") { childReactorInstantiations.add( - "this.registerNetworkReciever(this.${childReactor.name})") + "this.registerNetworkReceiver(\n" + + "\tthis.${childReactor.name} as __NetworkReactor\n)") } if (childReactor.reactorClass.name.take(13) == "NetworkSender") { childReactorInstantiations.add( diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt index 54cc76b915..4e81a1f967 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt @@ -130,7 +130,8 @@ ${" |"..preamble.code.toText()} } } else { if (isNetworkReactor) { - "export class $reactorName extends __NetworkReactor {" + val networkInputType = if (reactor.actions.size == 0) "unknown" else reactor.actions[0].tsActionType + "export class $reactorName extends __NetworkReactor<$networkInputType> {" } else { "export class $reactorName extends __Reactor {" } From 7cf28194e285ab54d33000ba4db691a30e4d78a8 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 24 Jul 2023 16:23:10 -0700 Subject: [PATCH 330/516] Try again to work around the test failures. --- core/src/main/resources/lib/c/reactor-c | 2 +- .../C/src/federated/LoopDistributedCentralizedPhysicalAction.lf | 2 +- test/C/src/federated/LoopDistributedDouble.lf | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 74b4eec482..6a0ab07d91 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 74b4eec482a519740f2120df7397f7155b38857d +Subproject commit 6a0ab07d9180d0911e40275e4dde2fa839a1fcdb diff --git a/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf b/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf index 0d5f6b7591..ac783f07cc 100644 --- a/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf +++ b/test/C/src/federated/LoopDistributedCentralizedPhysicalAction.lf @@ -10,7 +10,7 @@ target C { advance-message-interval: 100 msec }, timeout: 5 sec, - logging: log + logging: warn } preamble {= diff --git a/test/C/src/federated/LoopDistributedDouble.lf b/test/C/src/federated/LoopDistributedDouble.lf index 187db3df6f..94c19bebbc 100644 --- a/test/C/src/federated/LoopDistributedDouble.lf +++ b/test/C/src/federated/LoopDistributedDouble.lf @@ -10,7 +10,7 @@ target C { advance-message-interval: 100 msec }, timeout: 5 sec, - logging: log + logging: warn } preamble {= From 45d316916d95a2554dc4c16a6f173f6d28a3bd46 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 24 Jul 2023 17:49:52 -0700 Subject: [PATCH 331/516] Fix STP violation that was reported on master. Thanks to Jacky Kwok for finding this. --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 6a0ab07d91..9428f3d380 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 6a0ab07d9180d0911e40275e4dde2fa839a1fcdb +Subproject commit 9428f3d380d7fe9044eb4664e49201ce013bafef From 9acb408821453057fa1dedd652cce0c8ddf5fa9e Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Tue, 25 Jul 2023 15:20:44 +0900 Subject: [PATCH 332/516] Clean up commented-out code --- .../lflang/generator/ts/TSActionGenerator.kt | 7 ------- .../generator/ts/TSConstructorGenerator.kt | 10 +--------- .../lflang/generator/ts/TSReactorGenerator.kt | 20 ------------------- 3 files changed, 1 insertion(+), 36 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt index 896fd51ee8..1ec5e9847a 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt @@ -45,13 +45,6 @@ class TSActionGenerator( ", " + action.minDelay.toTsTime() } } - // if (action.name in networkMessageActions) { - // actionInstantiations.add( - // "this.${action.name} = new __FederatePortAction<${action.tsActionType}>($actionArgs);") - // } else { - // actionInstantiations.add( - // "this.${action.name} = new __Action<${action.tsActionType}>($actionArgs);") - // } if (action.name.take(7) == "network") { actionInstantiations.add( "this.${action.name} = new __FederatePortAction<${action.tsActionType}>($actionArgs);") diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt index 4ebbaa7d47..d044fc8910 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt @@ -75,13 +75,6 @@ class TSConstructorGenerator( } } - // If the app is federated, register its - // networkMessageActions with the RTIClient - // private fun generateFederatePortActionRegistrations(networkMessageActions: List): String = - // networkMessageActions.withIndex().joinWithLn { (fedPortID, actionName) -> - // "this.registerFederatePortAction($fedPortID, this.$actionName);" - // } - // Generate code for setting target configurations. private fun generateTargetConfigurations(targetConfig: TargetConfig): String { val interval = targetConfig.coordinationOptions.advance_message_interval @@ -98,8 +91,7 @@ class TSConstructorGenerator( states: TSStateGenerator, actions: TSActionGenerator, ports: TSPortGenerator, - isFederate: Boolean, - // networkMessageActions: MutableList + isFederate: Boolean ): String { val connections = TSConnectionGenerator(reactor.connections, messageReporter) val reactions = TSReactionGenerator(messageReporter, reactor) diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt index 4e81a1f967..67f5f8ba6c 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt @@ -90,25 +90,6 @@ ${" |"..preamble.code.toText()} }.trimMargin() } - // private fun getNetworkMessageActions(reactor: Reactor): MutableList { - // val attribute = AttributeUtils.findAttributeByName(reactor, "_fed_config") - // val actionsStr = AttributeUtils.getAttributeParameter(attribute, AttributeSpec.NETWORK_MESSAGE_ACTIONS) - // var actionsList = actionsStr?.split(",")?.filter { it.isNotEmpty()} ?: emptyList() - // actionsList = actionsList.toMutableList() - - // val childReactors = reactor.instantiations - // var actionsListCount = 0 - // for (childReactor in childReactors) { - // if (childReactor.reactorClass.name.take(15) == "NetworkReceiver") { - // // FIXME: Assume that the order of childReactor and attribute list are identical. - // // This assumption might bring some erros - // actionsList[actionsListCount] = childReactor.name + "." + actionsList[actionsListCount] - // actionsListCount++ - // } - // } - // return actionsList - // } - fun generateReactor(reactor: Reactor): String { var reactorName = reactor.name if (!reactor.typeParms.isEmpty()) { @@ -118,7 +99,6 @@ ${" |"..preamble.code.toText()} val isFederate = AttributeUtils.isFederate(reactor) val isNetworkReactor = reactorName.take(7) == "Network" - // val networkMessageActions = getNetworkMessageActions(reactor) // NOTE: type parameters that are referenced in ports or actions must extend // Present in order for the program to type check. From c770be9f93b9485920744d691526f527ab55c00c Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun <78055940+byeong-gil@users.noreply.github.com> Date: Tue, 25 Jul 2023 15:23:47 +0900 Subject: [PATCH 333/516] Update TSActionGenerator.kt --- .../main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt index 1ec5e9847a..5009f88a71 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt @@ -9,8 +9,7 @@ import java.util.* * Generator for actions in TypeScript target. */ class TSActionGenerator( - private val actions: List, - // private val networkMessageActions: List + private val actions: List ) { fun generateClassProperties(): String { From c31aa8f3302fc5995b61e1d7917858756e826057 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Tue, 25 Jul 2023 10:35:29 +0200 Subject: [PATCH 334/516] install executable script for python programs --- .../generator/python/PythonGenerator.java | 32 ++++++++---- .../generator/python/PythonInfoGenerator.java | 52 ------------------- 2 files changed, 22 insertions(+), 62 deletions(-) delete mode 100644 core/src/main/java/org/lflang/generator/python/PythonInfoGenerator.java diff --git a/core/src/main/java/org/lflang/generator/python/PythonGenerator.java b/core/src/main/java/org/lflang/generator/python/PythonGenerator.java index b8b3a74282..70a00db411 100644 --- a/core/src/main/java/org/lflang/generator/python/PythonGenerator.java +++ b/core/src/main/java/org/lflang/generator/python/PythonGenerator.java @@ -37,6 +37,7 @@ import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.xbase.lib.Exceptions; import org.lflang.AttributeUtils; +import org.lflang.FileConfig; import org.lflang.Target; import org.lflang.TargetProperty; import org.lflang.ast.ASTUtils; @@ -100,8 +101,7 @@ public PythonGenerator(LFGeneratorContext context) { "lib/python_time.c", "lib/pythontarget.c"), PythonGenerator::setUpMainTarget, - "install(TARGETS)" // No-op - )); + generateCmakeInstall(context.getFileConfig()))); } private PythonGenerator( @@ -396,15 +396,10 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { codeMaps.putAll(codeMapsForFederate); copyTargetFiles(); new PythonValidator(fileConfig, messageReporter, codeMaps, protoNames).doValidate(context); - if (targetConfig.noCompile) { - messageReporter.nowhere().info(PythonInfoGenerator.generateSetupInfo(fileConfig)); - } } catch (Exception e) { //noinspection ConstantConditions throw Exceptions.sneakyThrow(e); } - - messageReporter.nowhere().info(PythonInfoGenerator.generateRunInfo(fileConfig, lfModuleName)); } if (messageReporter.getErrorsOccurred()) { @@ -578,7 +573,7 @@ private static String setUpMainTarget( add_subdirectory(core) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}) set(LF_MAIN_TARGET ) - find_package(Python 3.7.0...<3.11.0 COMPONENTS Interpreter Development) + find_package(Python 3.7.0...<3.12.0 REQUIRED COMPONENTS Interpreter Development) Python_add_library( ${LF_MAIN_TARGET} MODULE @@ -598,11 +593,28 @@ private static String setUpMainTarget( target_link_libraries(${LF_MAIN_TARGET} PRIVATE ${Python_LIBRARIES}) target_compile_definitions(${LF_MAIN_TARGET} PUBLIC MODULE_NAME=) """) - .replace("", generatePythonModuleName(executableName)) - .replace("executableName", executableName); + .replace("", generatePythonModuleName(executableName)); // The use of fileConfig.name will break federated execution, but that's fine } + private static String generateCmakeInstall(FileConfig fileConfig) { + return """ + file(GENERATE OUTPUT CONTENT + "#!/bin/sh\n\\ + ${Python_EXECUTABLE} " + ) + install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/ DESTINATION ${CMAKE_INSTALL_BINDIR}) + """ + .replace("", fileConfig.name) + .replace( + "", + fileConfig + .getSrcGenPath() + .resolve(fileConfig.name + ".py") + .toAbsolutePath() + .toString()); + } + /** * Generate a ({@code key}, {@code val}) tuple pair for the {@code define_macros} field of the * Extension class constructor from setuptools. diff --git a/core/src/main/java/org/lflang/generator/python/PythonInfoGenerator.java b/core/src/main/java/org/lflang/generator/python/PythonInfoGenerator.java deleted file mode 100644 index 01cadbcc5c..0000000000 --- a/core/src/main/java/org/lflang/generator/python/PythonInfoGenerator.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.lflang.generator.python; - -import java.io.File; -import org.lflang.FileConfig; - -public class PythonInfoGenerator { - /** - * Print information about necessary steps to install the supporting Python C extension for the - * generated program. - * - * @note Only needed if no-compile is set to true - */ - public static String generateSetupInfo(FileConfig fileConfig) { - return String.join( - "\n", - "", - "#####################################", - "To compile and install the generated code, do:", - " ", - " cd " + fileConfig.getSrcGenPath() + File.separator, - " python3 -m pip install --force-reinstall .", - ""); - } - - /** Print information on how to execute the generated program. */ - public static String generateRunInfo(FileConfig fileConfig, String lfModuleName) { - return String.join( - "\n", - "", - "#####################################", - "To run the generated program, use:", - " ", - " python3 " + fileConfig.getSrcGenPath() + File.separator + lfModuleName + ".py", - "", - "#####################################", - ""); - } - - /** Print information on how to execute the generated federation. */ - public static String generateFedRunInfo(FileConfig fileConfig) { - return String.join( - "\n", - "", - "#####################################", - "To run the generated program, run:", - " ", - " bash " + fileConfig.binPath + "/" + fileConfig.name, - "", - "#####################################", - ""); - } -} From e1b7e0a36f13fd67af14d9d56e1b6d221e3c90d2 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Tue, 25 Jul 2023 10:39:48 +0200 Subject: [PATCH 335/516] also install batch script on Windows --- .../generator/python/PythonGenerator.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/python/PythonGenerator.java b/core/src/main/java/org/lflang/generator/python/PythonGenerator.java index 70a00db411..fcd86ed2a1 100644 --- a/core/src/main/java/org/lflang/generator/python/PythonGenerator.java +++ b/core/src/main/java/org/lflang/generator/python/PythonGenerator.java @@ -599,12 +599,21 @@ private static String setUpMainTarget( private static String generateCmakeInstall(FileConfig fileConfig) { return """ - file(GENERATE OUTPUT CONTENT - "#!/bin/sh\n\\ - ${Python_EXECUTABLE} " - ) - install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/ DESTINATION ${CMAKE_INSTALL_BINDIR}) - """ + if(WIN32) + file(GENERATE OUTPUT .bat CONTENT + "@echo off\n\\ + ${Python_EXECUTABLE} \n\\ + pause" + ) + install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/.bat DESTINATION ${CMAKE_INSTALL_BINDIR}) + else() + file(GENERATE OUTPUT CONTENT + "#!/bin/sh\n\\ + ${Python_EXECUTABLE} " + ) + install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/ DESTINATION ${CMAKE_INSTALL_BINDIR}) + endif() + """ .replace("", fileConfig.name) .replace( "", From df0635b8ca29ab6c5a7a3adffb22641f152ea40d Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Tue, 25 Jul 2023 18:15:10 +0900 Subject: [PATCH 336/516] Add an attribute named `_NetworkReactor` to classify network reaceiver and sender reactors The attribute has a parameter either `Receiver` or `Sender`. If the action is in a network receiver, it is `FederatePortAction`. --- .../federated/generator/FedASTUtils.java | 17 +++++++++++++++++ .../org/lflang/validation/AttributeSpec.java | 4 +++- .../lflang/generator/ts/TSActionGenerator.kt | 4 ++-- .../generator/ts/TSConstructorGenerator.kt | 17 +++++++++-------- .../lflang/generator/ts/TSInstanceGenerator.kt | 14 +++++++++++--- .../lflang/generator/ts/TSReactorGenerator.kt | 18 ++++++++++++------ 6 files changed, 54 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 7da95b6c60..7e1df41b58 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -240,6 +240,14 @@ private static void addNetworkReceiverReactor( .getParent() .reactorDefinition; // Top-level reactor. + // Add the attribute "_NetworkReactor" for the network receiver. + var a = factory.createAttribute(); + a.setAttrName("_NetworkReactor"); + var e = factory.createAttrParm(); + e.setValue("Receiver"); + a.getAttrParms().add(e); + receiver.getAttributes().add(a); + receiver .getReactions() .add(getInitializationReaction(extension, extension.inputInitializationBody())); @@ -638,6 +646,14 @@ private static Reactor getNetworkSenderReactor( // Initialize Reactor and Reaction AST Nodes Reactor sender = factory.createReactor(); + // Add the attribute "_NetworkReactor" for the network sender. + var a = factory.createAttribute(); + a.setAttrName("_NetworkReactor"); + var e = factory.createAttrParm(); + e.setValue("Sender"); + a.getAttrParms().add(e); + sender.getAttributes().add(a); + Input in = factory.createInput(); in.setName("msg"); in.setType(type); @@ -676,6 +692,7 @@ private static Reactor getNetworkSenderReactor( connection.srcFederate.networkReactors.add(sender); networkSenderReactors.put(connection, sender); + return sender; } diff --git a/core/src/main/java/org/lflang/validation/AttributeSpec.java b/core/src/main/java/org/lflang/validation/AttributeSpec.java index aab6faca9a..a5db6aca65 100644 --- a/core/src/main/java/org/lflang/validation/AttributeSpec.java +++ b/core/src/main/java/org/lflang/validation/AttributeSpec.java @@ -48,7 +48,6 @@ public class AttributeSpec { private final Map paramSpecByName; public static final String VALUE_ATTR = "value"; - public static final String NETWORK_MESSAGE_ACTIONS = "network_message_actions"; public static final String EACH_ATTR = "each"; /** A map from a string to a supported AttributeSpec */ @@ -223,5 +222,8 @@ enum AttrParamType { ATTRIBUTE_SPECS_BY_NAME.put( "_tpoLevel", new AttributeSpec(List.of(new AttrParamSpec(VALUE_ATTR, AttrParamType.INT, false)))); + ATTRIBUTE_SPECS_BY_NAME.put( + "_NetworkReactor", + new AttributeSpec(List.of(new AttrParamSpec(VALUE_ATTR, AttrParamType.STRING, false)))); } } diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt index 5009f88a71..5220ddcda7 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSActionGenerator.kt @@ -26,7 +26,7 @@ class TSActionGenerator( return stateClassProperties.joinToString("\n") } - fun generateInstantiations(): String { + fun generateInstantiations(isFederatePortAction: Boolean): String { val actionInstantiations = LinkedList() for (action in actions) { // Shutdown actions are handled internally by the @@ -44,7 +44,7 @@ class TSActionGenerator( ", " + action.minDelay.toTsTime() } } - if (action.name.take(7) == "network") { + if (isFederatePortAction) { actionInstantiations.add( "this.${action.name} = new __FederatePortAction<${action.tsActionType}>($actionArgs);") actionInstantiations.add( diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt index d044fc8910..e079e8db77 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt @@ -25,7 +25,7 @@ class TSConstructorGenerator( private fun initializeParameter(p: Parameter): String = "${p.name}: ${TSTypes.getInstance().getTargetType(p)} = ${TSTypes.getInstance().getTargetInitializer(p)}" - private fun generateConstructorArguments(reactor: Reactor): String { + private fun generateConstructorArguments(reactor: Reactor, isNetworkReceiver: Boolean): String { val arguments = StringJoiner(", \n") if (reactor.isMain || reactor.isFederated) { arguments.add("timeout: TimeValue | undefined = undefined") @@ -34,7 +34,7 @@ class TSConstructorGenerator( arguments.add("federationID: string = 'Unidentified Federation'") } else { arguments.add("parent: __Reactor") - if (reactor.name.take(15) == "NetworkReceiver") { + if (isNetworkReceiver) { arguments.add("portID: number") } } @@ -52,7 +52,7 @@ class TSConstructorGenerator( return arguments.toString() } - private fun generateSuperConstructorCall(reactor: Reactor, isFederate: Boolean): String = + private fun generateSuperConstructorCall(reactor: Reactor, isFederate: Boolean, isNetworkReceiver: Boolean): String = if (reactor.isMain) { if (isFederate) { """ @@ -68,7 +68,7 @@ class TSConstructorGenerator( "super(timeout, keepAlive, fast, success, fail);" } } else { - if (reactor.name.take(15) == "NetworkReceiver") { + if (isNetworkReceiver) { "super(parent, portID);" } else { "super(parent);" @@ -91,7 +91,8 @@ class TSConstructorGenerator( states: TSStateGenerator, actions: TSActionGenerator, ports: TSPortGenerator, - isFederate: Boolean + isFederate: Boolean, + isNetworkReceiver: Boolean ): String { val connections = TSConnectionGenerator(reactor.connections, messageReporter) val reactions = TSReactionGenerator(messageReporter, reactor) @@ -99,15 +100,15 @@ class TSConstructorGenerator( return with(PrependOperator) { """ |constructor ( - ${" | "..generateConstructorArguments(reactor)} + ${" | "..generateConstructorArguments(reactor, isNetworkReceiver)} |) { - ${" | "..generateSuperConstructorCall(reactor, isFederate)} + ${" | "..generateSuperConstructorCall(reactor, isFederate, isNetworkReceiver)} ${" | "..generateTargetConfigurations(targetConfig)} ${" | "..instances.generateInstantiations()} ${" | "..timers.generateInstantiations()} ${" | "..parameters.generateInstantiations()} ${" | "..states.generateInstantiations()} - ${" | "..actions.generateInstantiations()} + ${" | "..actions.generateInstantiations(isNetworkReceiver)} ${" | "..ports.generateInstantiations()} ${" | "..connections.generateInstantiations()} ${" | "..reactions.generateAllReactions()} diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt index 632febf920..1eb5d1a285 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt @@ -1,5 +1,6 @@ package org.lflang.generator.ts +import org.lflang.AttributeUtils import org.lflang.generator.getTargetInitializer import org.lflang.isBank import org.lflang.joinWithLn @@ -47,9 +48,16 @@ class TSInstanceGenerator( val childReactorInstantiations = LinkedList() var portID = 0 for (childReactor in childReactors) { + var isNetworkSender = false + var isNetworkReceiver = false + val networkReactorAttribute = AttributeUtils.findAttributeByName(childReactor.reactorClass, "_NetworkReactor") + if (networkReactorAttribute != null) { + isNetworkSender = networkReactorAttribute.getAttrParms().get(0).getName() == "Sender" + isNetworkReceiver = networkReactorAttribute.getAttrParms().get(0).getName() == "Receiver" + } val childReactorArguments = StringJoiner(", ") childReactorArguments.add("this") - if (childReactor.reactorClass.name.take(15) == "NetworkReceiver") { + if (isNetworkReceiver) { // Assume that network receiver reactors are sorted by portID childReactorArguments.add(portID.toString()) portID++ @@ -70,12 +78,12 @@ class TSInstanceGenerator( childReactorInstantiations.add( "this.${childReactor.name} = " + "new ${childReactor.reactorClass.name}($childReactorArguments)") - if (childReactor.reactorClass.name.take(15) == "NetworkReceiver") { + if (isNetworkReceiver) { childReactorInstantiations.add( "this.registerNetworkReceiver(\n" + "\tthis.${childReactor.name} as __NetworkReactor\n)") } - if (childReactor.reactorClass.name.take(13) == "NetworkSender") { + if (isNetworkSender) { childReactorInstantiations.add( "this.registerNetworkSender(this.${childReactor.name})") } diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt index 67f5f8ba6c..71d86c2ca5 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt @@ -3,7 +3,6 @@ package org.lflang.generator.ts import org.lflang.* import org.lflang.generator.PrependOperator import org.lflang.lf.* -import org.lflang.validation.AttributeSpec import java.util.* /** @@ -98,7 +97,13 @@ ${" |"..preamble.code.toText()} } val isFederate = AttributeUtils.isFederate(reactor) - val isNetworkReactor = reactorName.take(7) == "Network" + val networkReactorAttribute = AttributeUtils.findAttributeByName(reactor, "_NetworkReactor") + var isNetworkSender = false + var isNetworkReceiver = false + if (networkReactorAttribute != null) { + isNetworkSender = networkReactorAttribute.getAttrParms().get(0).getName() == "Sender" + isNetworkReceiver = networkReactorAttribute.getAttrParms().get(0).getName() == "Receiver" + } // NOTE: type parameters that are referenced in ports or actions must extend // Present in order for the program to type check. @@ -109,9 +114,10 @@ ${" |"..preamble.code.toText()} "class $reactorName extends __App {" } } else { - if (isNetworkReactor) { - val networkInputType = if (reactor.actions.size == 0) "unknown" else reactor.actions[0].tsActionType - "export class $reactorName extends __NetworkReactor<$networkInputType> {" + if (isNetworkSender) { + "export class $reactorName extends __NetworkReactor {" + } else if (isNetworkReceiver) { + "export class $reactorName extends __NetworkReactor<${reactor.actions[0].tsActionType}> {" } else { "export class $reactorName extends __Reactor {" } @@ -138,7 +144,7 @@ ${" |"..preamble.code.toText()} ${" | "..actionGenerator.generateClassProperties()} ${" | "..portGenerator.generateClassProperties()} ${" | "..constructorGenerator.generateConstructor(targetConfig, instanceGenerator, timerGenerator, parameterGenerator, - stateGenerator, actionGenerator, portGenerator, isFederate)} + stateGenerator, actionGenerator, portGenerator, isFederate, isNetworkReceiver)} |} |// =============== END reactor class ${reactor.name} | From da74101e35ed14c595ef965c757c30b5b21788bd Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Tue, 25 Jul 2023 10:59:28 +0200 Subject: [PATCH 337/516] fix escapes --- .../org/lflang/generator/python/PythonGenerator.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/python/PythonGenerator.java b/core/src/main/java/org/lflang/generator/python/PythonGenerator.java index fcd86ed2a1..60a687691a 100644 --- a/core/src/main/java/org/lflang/generator/python/PythonGenerator.java +++ b/core/src/main/java/org/lflang/generator/python/PythonGenerator.java @@ -601,15 +601,14 @@ private static String generateCmakeInstall(FileConfig fileConfig) { return """ if(WIN32) file(GENERATE OUTPUT .bat CONTENT - "@echo off\n\\ - ${Python_EXECUTABLE} \n\\ - pause" + "@echo off\n\ + ${Python_EXECUTABLE} " ) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/.bat DESTINATION ${CMAKE_INSTALL_BINDIR}) else() file(GENERATE OUTPUT CONTENT - "#!/bin/sh\n\\ - ${Python_EXECUTABLE} " + "#!/bin/sh\\n\\ + ${Python_EXECUTABLE} " ) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/ DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() @@ -621,7 +620,8 @@ private static String generateCmakeInstall(FileConfig fileConfig) { .getSrcGenPath() .resolve(fileConfig.name + ".py") .toAbsolutePath() - .toString()); + .toString() + .replace("\\", "\\\\")); } /** From 83853af76e6624ac49cd05dd64df8f149f0926a0 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Tue, 25 Jul 2023 11:26:06 +0200 Subject: [PATCH 338/516] require Python 3.10 --- .../main/java/org/lflang/generator/python/PythonGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/generator/python/PythonGenerator.java b/core/src/main/java/org/lflang/generator/python/PythonGenerator.java index 60a687691a..34241798f6 100644 --- a/core/src/main/java/org/lflang/generator/python/PythonGenerator.java +++ b/core/src/main/java/org/lflang/generator/python/PythonGenerator.java @@ -573,7 +573,7 @@ private static String setUpMainTarget( add_subdirectory(core) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}) set(LF_MAIN_TARGET ) - find_package(Python 3.7.0...<3.12.0 REQUIRED COMPONENTS Interpreter Development) + find_package(Python 3.10.0...<3.11.0 REQUIRED COMPONENTS Interpreter Development) Python_add_library( ${LF_MAIN_TARGET} MODULE From f3b7df445fb0dc0c5b99377a8f9851e678a48853 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Tue, 25 Jul 2023 12:46:41 +0200 Subject: [PATCH 339/516] use the script in bin for testing --- .../lflang/generator/python/PyFileConfig.java | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/python/PyFileConfig.java b/core/src/main/java/org/lflang/generator/python/PyFileConfig.java index 7c67b92493..3927b6d02e 100644 --- a/core/src/main/java/org/lflang/generator/python/PyFileConfig.java +++ b/core/src/main/java/org/lflang/generator/python/PyFileConfig.java @@ -2,10 +2,9 @@ import java.io.IOException; import java.nio.file.Path; -import java.util.List; import org.eclipse.emf.ecore.resource.Resource; +import org.lflang.generator.GeneratorUtils; import org.lflang.generator.c.CFileConfig; -import org.lflang.util.LFCommand; public class PyFileConfig extends CFileConfig { public PyFileConfig(Resource resource, Path srcGenBasePath, boolean useHierarchicalBin) @@ -13,19 +12,8 @@ public PyFileConfig(Resource resource, Path srcGenBasePath, boolean useHierarchi super(resource, srcGenBasePath, useHierarchicalBin); } - @Override - public LFCommand getCommand() { - return LFCommand.get( - "python3", List.of(srcPkgPath.relativize(getExecutable()).toString()), true, srcPkgPath); - } - - @Override - public Path getExecutable() { - return srcGenPath.resolve(name + getExecutableExtension()); - } - @Override protected String getExecutableExtension() { - return ".py"; + return GeneratorUtils.isHostWindows() ? ".bat" : ""; } } From e132dda561d3b0f19a5014456d18347cb6492575 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Tue, 25 Jul 2023 12:47:14 +0200 Subject: [PATCH 340/516] fix regex to also work on Windows --- .../integrationTest/java/org/lflang/tests/RunSingleTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/integrationTest/java/org/lflang/tests/RunSingleTest.java b/core/src/integrationTest/java/org/lflang/tests/RunSingleTest.java index 424568109a..29e62e835b 100644 --- a/core/src/integrationTest/java/org/lflang/tests/RunSingleTest.java +++ b/core/src/integrationTest/java/org/lflang/tests/RunSingleTest.java @@ -49,7 +49,7 @@ public class RunSingleTest { private static final Pattern TEST_FILE_PATTERN = - Pattern.compile("(test/(\\w+))/src/([^/]++/)*(\\w+.lf)"); + Pattern.compile("(test\\W(\\w+))\\Wsrc\\W(\\w++\\W)*(\\w+.lf)"); @Test public void runSingleTest() throws FileNotFoundException { From 4e64dbe3601d618a0dcc19f9785c3155fc43e25e Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Tue, 25 Jul 2023 12:59:14 +0200 Subject: [PATCH 341/516] also use bin scripts in federated launch script --- .../federated/launcher/PyBuildConfig.java | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/launcher/PyBuildConfig.java b/core/src/main/java/org/lflang/federated/launcher/PyBuildConfig.java index 09f7fac34f..98f1c16ad6 100644 --- a/core/src/main/java/org/lflang/federated/launcher/PyBuildConfig.java +++ b/core/src/main/java/org/lflang/federated/launcher/PyBuildConfig.java @@ -12,26 +12,12 @@ public PyBuildConfig( } @Override - public String localExecuteCommand() { - return "python3 " - + fileConfig.getSrcGenPath() - + "/" - + federate.name - + "/" - + federate.name - + ".py -i $FEDERATION_ID"; + public String remoteExecuteCommand() { + return "bin/" + fileConfig.name + "_" + federate.name + " -i '$FEDERATION_ID'"; } @Override - public String remoteExecuteCommand() { - return "python3 src-gen/" - + fileConfig.name - + "/" - + federate.name - + "/" - + fileConfig.name - + "_" - + federate.name - + " -i '$FEDERATION_ID'"; + public String localExecuteCommand() { + return fileConfig.getFedBinPath().resolve(federate.name) + " -i $FEDERATION_ID"; } } From c226c66252ee5b09f5a02d950bf28d02f21d4dc2 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Tue, 25 Jul 2023 15:58:46 +0200 Subject: [PATCH 342/516] forward all arguments to the Python script --- .../generator/python/PythonGenerator.java | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/python/PythonGenerator.java b/core/src/main/java/org/lflang/generator/python/PythonGenerator.java index 34241798f6..16615bb59e 100644 --- a/core/src/main/java/org/lflang/generator/python/PythonGenerator.java +++ b/core/src/main/java/org/lflang/generator/python/PythonGenerator.java @@ -598,30 +598,27 @@ private static String setUpMainTarget( } private static String generateCmakeInstall(FileConfig fileConfig) { + final var pyMainPath = + fileConfig.getSrcGenPath().resolve(fileConfig.name + ".py").toAbsolutePath(); + // need to replace '\' with '\\' on Windwos for proper escaping in cmake + final var pyMainName = pyMainPath.toString().replace("\\", "\\\\"); return """ if(WIN32) file(GENERATE OUTPUT .bat CONTENT "@echo off\n\ - ${Python_EXECUTABLE} " + ${Python_EXECUTABLE} %*" ) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/.bat DESTINATION ${CMAKE_INSTALL_BINDIR}) else() file(GENERATE OUTPUT CONTENT "#!/bin/sh\\n\\ - ${Python_EXECUTABLE} " + ${Python_EXECUTABLE} \\\"$@\\\"" ) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/ DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() """ .replace("", fileConfig.name) - .replace( - "", - fileConfig - .getSrcGenPath() - .resolve(fileConfig.name + ".py") - .toAbsolutePath() - .toString() - .replace("\\", "\\\\")); + .replace("", pyMainName); } /** From 794dba0f559ec67d56a62d102161cf34b4374ab2 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Tue, 25 Jul 2023 19:17:36 +0200 Subject: [PATCH 343/516] Fix error when there is no main reactor --- core/src/main/java/org/lflang/ast/ASTUtils.java | 6 +++--- core/src/main/java/org/lflang/generator/LFGenerator.java | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/lflang/ast/ASTUtils.java b/core/src/main/java/org/lflang/ast/ASTUtils.java index 93c689b810..f89922f59b 100644 --- a/core/src/main/java/org/lflang/ast/ASTUtils.java +++ b/core/src/main/java/org/lflang/ast/ASTUtils.java @@ -35,6 +35,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.Predicate; import java.util.regex.Matcher; @@ -154,14 +155,13 @@ public static List getAllReactors(Resource resource) { * @param resource the resource to extract reactors from * @return An iterable over all reactors found in the resource */ - public static Reactor getMainReactor(Resource resource) { + public static Optional getMainReactor(Resource resource) { return StreamSupport.stream( IteratorExtensions.toIterable(resource.getAllContents()).spliterator(), false) .filter(Reactor.class::isInstance) .map(Reactor.class::cast) .filter(it -> it.isMain()) - .findFirst() - .get(); + .findFirst(); } /** diff --git a/core/src/main/java/org/lflang/generator/LFGenerator.java b/core/src/main/java/org/lflang/generator/LFGenerator.java index 410051f835..84a13f6fc8 100644 --- a/core/src/main/java/org/lflang/generator/LFGenerator.java +++ b/core/src/main/java/org/lflang/generator/LFGenerator.java @@ -8,6 +8,7 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.generator.AbstractGenerator; @@ -158,7 +159,9 @@ protected void cleanIfNeeded(LFGeneratorContext context) { * connections, etc.). */ private void runVerifierIfPropertiesDetected(Resource resource, LFGeneratorContext lfContext) { - Reactor main = ASTUtils.getMainReactor(resource); + Optional mainOpt = ASTUtils.getMainReactor(resource); + if (mainOpt.isEmpty()) return; + Reactor main = mainOpt.get(); final MessageReporter messageReporter = lfContext.getErrorReporter(); List properties = AttributeUtils.getAttributes(main).stream() From 2b41ad2c108eeedce07622366952c3cd1db1e5cc Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 25 Jul 2023 11:16:57 -0700 Subject: [PATCH 344/516] Rename output control -> port absent. --- .../org/lflang/federated/extensions/CExtension.java | 13 ++++--------- .../federated/extensions/FedTargetExtension.java | 2 +- .../federated/extensions/PythonExtension.java | 2 +- .../lflang/federated/extensions/TSExtension.java | 2 +- .../org/lflang/federated/generator/FedASTUtils.java | 8 ++++---- .../federated/generator/FederateInstance.java | 2 +- .../serialization/FedNativePythonSerialization.java | 2 +- .../serialization/FedROS2CPPSerialization.java | 2 +- .../federated/serialization/FedSerialization.java | 2 +- core/src/main/resources/lib/c/reactor-c | 2 +- 10 files changed, 16 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index 2da5ee1821..f2048a6ab7 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -428,7 +428,7 @@ protected void serializeAndSend( } var ROSSerializer = new FedROS2CPPSerialization(); lengthExpression = ROSSerializer.serializedBufferLength(); - pointerExpression = ROSSerializer.seializedBufferVar(); + pointerExpression = ROSSerializer.serializedBufferVar(); result.pr( ROSSerializer.generateNetworkSerializerCode( sendRef, typeStr, CExtensionUtils.isSharedPtrType(type, types))); @@ -445,7 +445,7 @@ protected void serializeAndSend( * @param srcOutputPort A reference to the port that the sender reaction reads from. * @param connection The federated connection being lowered. */ - public String generateNetworkOutputControlReactionBody( + public String generatePortAbsentReactionBody( VarRef srcOutputPort, FedConnectionInstance connection) { // Store the code var result = new CodeBuilder(); @@ -569,13 +569,13 @@ protected String makePreamble( """ .formatted(numOfNetworkReactions)); - int numOfNetworkSenderControlReactions = federate.networkSenderControlReactions.size(); + int numOfPortAbsentReactions = federate.portAbsentReactions.size(); code.pr( """ reaction_t* port_absent_reaction[%1$s]; // initialize to null pointers; see C99 6.7.8.10 size_t num_sender_reactions = %1$s; """ - .formatted(numOfNetworkSenderControlReactions)); + .formatted(numOfPortAbsentReactions)); int numOfSTAAOffsets = federate.stpOffsets.size(); code.pr( @@ -643,9 +643,6 @@ private String generateExecutablePreamble( code.pr(generateCodeForPhysicalActions(federate, messageReporter)); code.pr(generateCodeToInitializeFederate(federate, rtiConfig)); - - // code.pr(CExtensionUtils.allocateTriggersForFederate(federate)); - return """ void _lf_executable_preamble(environment_t* env) { %s @@ -660,8 +657,6 @@ private String generateSTAAInitialization(FederateInstance federate) { code.pr( CExtensionUtils.surroundWithIfFederatedDecentralized(CExtensionUtils.stpStructs(federate))); - // code.pr(CExtensionUtils.allocateTriggersForFederate(federate)); - return """ void staa_initialization() { %s diff --git a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java index b3d816f23a..6ac077f2ca 100644 --- a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java @@ -92,7 +92,7 @@ String generateNetworkSenderBody( * @param srcOutputPort A reference to the output port of the federate instance. * @param connection The federated connection being lowered. */ - String generateNetworkOutputControlReactionBody( + String generatePortAbsentReactionBody( VarRef srcOutputPort, FedConnectionInstance connection); /** Optionally apply additional annotations to the reaction. */ diff --git a/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java b/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java index 9b85a0358c..3d84d78562 100644 --- a/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/PythonExtension.java @@ -167,7 +167,7 @@ protected void serializeAndSend( var variableToSerialize = sendRef + "->value"; FedNativePythonSerialization pickler = new FedNativePythonSerialization(); lengthExpression = pickler.serializedBufferLength(); - pointerExpression = pickler.seializedBufferVar(); + pointerExpression = pickler.serializedBufferVar(); result.pr(pickler.generateNetworkSerializerCode(variableToSerialize, null)); result.pr("size_t _lf_message_length = " + lengthExpression + ";"); result.pr(sendingFunction + "(" + commonArgs + ", " + pointerExpression + ");\n"); diff --git a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java index 956823a1bf..b572dc8a0b 100644 --- a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java @@ -127,7 +127,7 @@ private String getNetworkDelayLiteral(Expression e) { } @Override - public String generateNetworkOutputControlReactionBody( + public String generatePortAbsentReactionBody( VarRef srcOutputPort, FedConnectionInstance connection) { // The ID of the receiving port (rightPort) is the position // of the networkAction (see below) in this list. diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 7e1df41b58..d38237c71b 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -141,7 +141,7 @@ public static void makeCommunication( addNetworkSenderReactor(connection, coordination, resource, messageReporter); - FedASTUtils.addNetworkOutputControlReaction(connection); + FedASTUtils.addPortAbsentReaction(connection); addNetworkReceiverReactor(connection, coordination, resource, messageReporter); } @@ -814,7 +814,7 @@ private static void addNetworkSenderReactor( * * @param connection The federated connection being replaced. */ - private static void addNetworkOutputControlReaction(FedConnectionInstance connection) { + private static void addPortAbsentReaction(FedConnectionInstance connection) { LfFactory factory = LfFactory.eINSTANCE; Reaction reaction = factory.createReaction(); Reactor top = networkSenderReactors.getOrDefault(connection, null); @@ -840,7 +840,7 @@ private static void addNetworkOutputControlReaction(FedConnectionInstance connec .getCode() .setBody( FedTargetExtensionFactory.getExtension(connection.srcFederate.targetConfig.target) - .generateNetworkOutputControlReactionBody(newPortRef, connection)); + .generatePortAbsentReactionBody(newPortRef, connection)); // Insert the newly generated reaction after the generated sender and // receiver top-level reactions. @@ -849,6 +849,6 @@ private static void addNetworkOutputControlReaction(FedConnectionInstance connec // Add the network output control reaction to the federate instance's list // of network reactions connection.srcFederate.networkSenderReactions.add(reaction); - connection.srcFederate.networkSenderControlReactions.add(reaction); + connection.srcFederate.portAbsentReactions.add(reaction); } } diff --git a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java index 56df7ae6a1..1b3f9f5669 100644 --- a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java +++ b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java @@ -207,7 +207,7 @@ public Instantiation getInstantiation() { * List of generated network control reactions (network sender) that belong to this federate * instance. */ - public List networkSenderControlReactions = new ArrayList<>(); + public List portAbsentReactions = new ArrayList<>(); /** * List of generated network reactors (network input and outputs) that belong to this federate diff --git a/core/src/main/java/org/lflang/federated/serialization/FedNativePythonSerialization.java b/core/src/main/java/org/lflang/federated/serialization/FedNativePythonSerialization.java index 7eae1450e0..7e6b7b2869 100644 --- a/core/src/main/java/org/lflang/federated/serialization/FedNativePythonSerialization.java +++ b/core/src/main/java/org/lflang/federated/serialization/FedNativePythonSerialization.java @@ -51,7 +51,7 @@ public String serializedBufferLength() { } @Override - public String seializedBufferVar() { + public String serializedBufferVar() { return serializedVarName + ".buf"; } diff --git a/core/src/main/java/org/lflang/federated/serialization/FedROS2CPPSerialization.java b/core/src/main/java/org/lflang/federated/serialization/FedROS2CPPSerialization.java index 1bf6bde698..4807f42fbc 100644 --- a/core/src/main/java/org/lflang/federated/serialization/FedROS2CPPSerialization.java +++ b/core/src/main/java/org/lflang/federated/serialization/FedROS2CPPSerialization.java @@ -74,7 +74,7 @@ public String serializedBufferLength() { * @return Expression in target language that is the buffer variable itself. */ @Override - public String seializedBufferVar() { + public String serializedBufferVar() { return serializedVarName + ".get_rcl_serialized_message().buffer"; } diff --git a/core/src/main/java/org/lflang/federated/serialization/FedSerialization.java b/core/src/main/java/org/lflang/federated/serialization/FedSerialization.java index fab7201c21..7f3dc1b935 100644 --- a/core/src/main/java/org/lflang/federated/serialization/FedSerialization.java +++ b/core/src/main/java/org/lflang/federated/serialization/FedSerialization.java @@ -32,7 +32,7 @@ public interface FedSerialization { /** * @return Expression in target language that is the buffer variable itself. */ - public String seializedBufferVar(); + public String serializedBufferVar(); /** * Generate code in target language that serializes 'varName'. This code will convert the data in diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 9428f3d380..d16b3b72b1 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 9428f3d380d7fe9044eb4664e49201ce013bafef +Subproject commit d16b3b72b17a8cfba33e51ab25b2631f43fa1495 From eeb7d34e271987dab70303f8137aa295c0088f37 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 25 Jul 2023 12:02:07 -0700 Subject: [PATCH 345/516] Update submodule. --- .../org/lflang/federated/extensions/FedTargetExtension.java | 3 +-- core/src/main/resources/lib/c/reactor-c | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java index 6ac077f2ca..f3a9c48d51 100644 --- a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java @@ -92,8 +92,7 @@ String generateNetworkSenderBody( * @param srcOutputPort A reference to the output port of the federate instance. * @param connection The federated connection being lowered. */ - String generatePortAbsentReactionBody( - VarRef srcOutputPort, FedConnectionInstance connection); + String generatePortAbsentReactionBody(VarRef srcOutputPort, FedConnectionInstance connection); /** Optionally apply additional annotations to the reaction. */ default void annotateReaction(Reaction reaction) {} diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index d16b3b72b1..bd530d52e1 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit d16b3b72b17a8cfba33e51ab25b2631f43fa1495 +Subproject commit bd530d52e1af14e2c1a22726e60006b7fa568c74 From 34dc7ac40c83b8023a34c63868e40300053ad211 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 25 Jul 2023 12:16:00 -0700 Subject: [PATCH 346/516] Remove WORKERS_NEEDED_FOR_FEDERATE. The ability to do this is the initial motivation for this PR. --- .../federated/extensions/CExtensionUtils.java | 15 --------------- core/src/main/resources/lib/c/reactor-c | 2 +- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java b/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java index 5cefbbe339..0903d2d90d 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java @@ -183,27 +183,12 @@ public static void handleCompileDefinitions( federate.targetConfig.compileDefinitions.put( "NUMBER_OF_FEDERATES", String.valueOf(numOfFederates)); federate.targetConfig.compileDefinitions.put("EXECUTABLE_PREAMBLE", ""); - federate.targetConfig.compileDefinitions.put( - "WORKERS_NEEDED_FOR_FEDERATE", String.valueOf(minThreadsToHandleInputPorts(federate))); handleAdvanceMessageInterval(federate); initializeClockSynchronization(federate, rtiConfig, messageReporter); } - /** - * The number of threads needs to be at least one larger than the input ports to allow the - * federate to wait on all input ports while allowing an additional worker thread to process - * incoming messages. - * - * @return The minimum number of threads needed. - */ - public static int minThreadsToHandleInputPorts(FederateInstance federate) { - int nthreads = 1; - nthreads = Math.max(nthreads, federate.networkMessageActions.size() + 1); - return nthreads; - } - private static void handleAdvanceMessageInterval(FederateInstance federate) { var advanceMessageInterval = federate.targetConfig.coordinationOptions.advance_message_interval; federate.targetConfig.setByUser.remove(TargetProperty.COORDINATION_OPTIONS); diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index bd530d52e1..cfc545b85c 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit bd530d52e1af14e2c1a22726e60006b7fa568c74 +Subproject commit cfc545b85c2301e21bd083dccbcffd29833becd2 From b493d4193f8a4510fe8589d6d9efde2393f1d2bb Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 25 Jul 2023 13:20:57 -0700 Subject: [PATCH 347/516] Patch lingering terminology mismatch. --- .../lflang/federated/extensions/CExtension.java | 15 ++++----------- .../java/org/lflang/tests/TestBase.java | 2 +- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index f2048a6ab7..3829c72617 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -60,14 +60,7 @@ import org.lflang.lf.VarRef; /** - * An extension class to the CGenerator that enables certain federated functionalities. Currently, - * this class offers the following features: - * - *

    - *
  • Allocating and initializing C structures for federated communication - *
  • Creating status field for network input ports that help the receiver logic in federate.c - * communicate the status of a network input port with network input control reactions. - *
+ * An extension class to the CGenerator that enables certain federated functionalities. * * @author {Soroush Bateni } * @author {Hou Seng Wong } @@ -227,11 +220,11 @@ protected void deserialize( public String outputInitializationBody() { return """ extern reaction_t* port_absent_reaction[]; - void enqueue_network_output_control_reactions(environment_t*); - LF_PRINT_DEBUG("Adding network output control reaction to table."); + void enqueue_port_absent_reactions(environment_t*); + LF_PRINT_DEBUG("Adding network port absent reaction to table."); port_absent_reaction[SENDERINDEXPARAMETER] = &self->_lf__reaction_2; LF_PRINT_DEBUG("Added network output control reaction to table. Enqueueing it..."); - enqueue_network_output_control_reactions(self->base.environment); + enqueue_port_absent_reactions(self->base.environment); """; } diff --git a/core/src/testFixtures/java/org/lflang/tests/TestBase.java b/core/src/testFixtures/java/org/lflang/tests/TestBase.java index 23d1a83341..401c7e93d5 100644 --- a/core/src/testFixtures/java/org/lflang/tests/TestBase.java +++ b/core/src/testFixtures/java/org/lflang/tests/TestBase.java @@ -74,7 +74,7 @@ public abstract class TestBase extends LfInjectedTestBase { private static final PrintStream err = System.err; /** Execution timeout enforced for all tests. */ - private static final long MAX_EXECUTION_TIME_SECONDS = 180; + private static final long MAX_EXECUTION_TIME_SECONDS = 20; /** Content separator used in test output, 78 characters wide. */ public static final String THIN_LINE = From c284f60ba40c316b98b2ae904e58229254f91b3b Mon Sep 17 00:00:00 2001 From: revol-xut Date: Wed, 26 Jul 2023 13:18:21 +0200 Subject: [PATCH 348/516] implementing new draw_connection interface --- .gitmodules | 1 - .../lflang/generator/cpp/CppAssembleMethodGenerator.kt | 10 ++++++++-- core/src/main/resources/lib/cpp/reactor-cpp | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.gitmodules b/.gitmodules index 036edd9440..09e6ea5240 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,7 +4,6 @@ [submodule "org.lflang/src/lib/cpp/reactor-cpp"] path = core/src/main/resources/lib/cpp/reactor-cpp url = https://github.com/lf-lang/reactor-cpp.git - branch = "connection-optimization" [submodule "org.lflang/src/lib/rs/reactor-rs"] path = core/src/main/resources/lib/rs/reactor-rs url = https://github.com/lf-lang/reactor-rs diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt index bcce92a055..1d73946cf7 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt @@ -101,7 +101,14 @@ class CppAssembleMethodGenerator(private val reactor: Reactor) { } private val Connection.cppDelay: String - get() = delay?.toCppTime() ?: "0s" + get() { + val d = delay; + return if (d is ParameterReference) { + "__lf_inner.${d.parameter.name}" + } else { + d.toCppTime() + } + } private val Connection.properties: String get() = "reactor::ConnectionProperties{$cppType, $cppDelay, nullptr}" @@ -188,7 +195,6 @@ class CppAssembleMethodGenerator(private val reactor: Reactor) { ${" |"..c.leftPorts.joinWithLn { addAllPortsToVector(it, "__lf_left_ports_$idx") }} |std::vector<$portType> __lf_right_ports_$idx; ${" |"..c.rightPorts.joinWithLn { addAllPortsToVector(it, "__lf_right_ports_$idx") }} - ${" |"..if (c.requiresConnectionClass) "${c.name}.reserve(std::max(__lf_left_ports_$idx.size(), __lf_right_ports_$idx.size()));" else ""} |lfutil::bind_multiple_ports<$portType>(__lf_left_ports_$idx, __lf_right_ports_$idx, ${c.isIterated}, ${" |"..c.getConnectionLambda(portType)} |); diff --git a/core/src/main/resources/lib/cpp/reactor-cpp b/core/src/main/resources/lib/cpp/reactor-cpp index 5775399706..3c6d8ec833 160000 --- a/core/src/main/resources/lib/cpp/reactor-cpp +++ b/core/src/main/resources/lib/cpp/reactor-cpp @@ -1 +1 @@ -Subproject commit 5775399706b217a775a81aba9a2c024998a0ca64 +Subproject commit 3c6d8ec833bb3319b8f979f43bcc1416d2635d85 From 0cb3792237cf20c9885dc158d2050fa4b05f3670 Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Wed, 26 Jul 2023 21:50:11 +0900 Subject: [PATCH 349/516] Using `NetworkReceiver` and `NetworkSender` classes instead of the common class `NetworkReactor` Those two classes have no common method or variable. However, it does when we want to apply the TPO level concept to reactor-ts. The common parent class `NetworkReactor` will be added for that in future work. --- .../org/lflang/generator/ts/TSImportPreambleGenerator.kt | 2 +- .../kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt | 2 +- .../main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSImportPreambleGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSImportPreambleGenerator.kt index d71d39a2e8..fcf22b486c 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSImportPreambleGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSImportPreambleGenerator.kt @@ -57,7 +57,7 @@ class TSImportPreambleGenerator( import {Parameter as __Parameter, Timer as __Timer, Reactor as __Reactor, App as __App} from '@lf-lang/reactor-ts' import {Action as __Action, Startup as __Startup, FederatePortAction as __FederatePortAction} from '@lf-lang/reactor-ts' import {Bank as __Bank} from '@lf-lang/reactor-ts' - import {NetworkReactor as __NetworkReactor, FederatedApp as __FederatedApp, FederateConfig as __FederateConfig} from '@lf-lang/reactor-ts' + import {NetworkSender as __NetworkSender, NetworkReceiver as __NetworkReceiver, FederatedApp as __FederatedApp, FederateConfig as __FederateConfig} from '@lf-lang/reactor-ts' import {InPort as __InPort, OutPort as __OutPort, Port as __Port, WritablePort as __WritablePort, WritableMultiPort as __WritableMultiPort} from '@lf-lang/reactor-ts' import {InMultiPort as __InMultiPort, OutMultiPort as __OutMultiPort} from '@lf-lang/reactor-ts' import {Reaction as __Reaction} from '@lf-lang/reactor-ts' diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt index 1eb5d1a285..8d04f93dd5 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt @@ -81,7 +81,7 @@ class TSInstanceGenerator( if (isNetworkReceiver) { childReactorInstantiations.add( "this.registerNetworkReceiver(\n" - + "\tthis.${childReactor.name} as __NetworkReactor\n)") + + "\tthis.${childReactor.name} as __NetworkReceiver\n)") } if (isNetworkSender) { childReactorInstantiations.add( diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt index 71d86c2ca5..e84f012480 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt @@ -115,9 +115,9 @@ ${" |"..preamble.code.toText()} } } else { if (isNetworkSender) { - "export class $reactorName extends __NetworkReactor {" + "export class $reactorName extends __NetworkSender {" } else if (isNetworkReceiver) { - "export class $reactorName extends __NetworkReactor<${reactor.actions[0].tsActionType}> {" + "export class $reactorName extends __NetworkReceiver<${reactor.actions[0].tsActionType}> {" } else { "export class $reactorName extends __Reactor {" } From 3f99fc3426c7a37c4b09e67c6848dda726ab2609 Mon Sep 17 00:00:00 2001 From: revol-xut Date: Wed, 26 Jul 2023 15:09:45 +0200 Subject: [PATCH 350/516] clang-format for c++ files --- core/src/main/resources/lib/cpp/.clang-format | 8 ++++ core/src/main/resources/lib/cpp/lfutil.hh | 32 +++++--------- .../src/main/resources/lib/cpp/time_parser.hh | 43 ++++++++----------- 3 files changed, 36 insertions(+), 47 deletions(-) create mode 100644 core/src/main/resources/lib/cpp/.clang-format diff --git a/core/src/main/resources/lib/cpp/.clang-format b/core/src/main/resources/lib/cpp/.clang-format new file mode 100644 index 0000000000..ac94ae098b --- /dev/null +++ b/core/src/main/resources/lib/cpp/.clang-format @@ -0,0 +1,8 @@ +--- +BasedOnStyle: LLVM +Language: Cpp +IndentWidth: 2 +BreakConstructorInitializersBeforeComma: 'true' +AllowShortFunctionsOnASingleLine: All +PointerAlignment: Left +ColumnLimit: 120 \ No newline at end of file diff --git a/core/src/main/resources/lib/cpp/lfutil.hh b/core/src/main/resources/lib/cpp/lfutil.hh index 967f42b730..959d4218df 100644 --- a/core/src/main/resources/lib/cpp/lfutil.hh +++ b/core/src/main/resources/lib/cpp/lfutil.hh @@ -37,8 +37,7 @@ namespace lfutil { -template -void after_delay(reactor::Action *action, const reactor::Port *port) { +template void after_delay(reactor::Action* action, const reactor::Port* port) { if constexpr (std::is_void::value) { action->schedule(); } else { @@ -46,8 +45,7 @@ void after_delay(reactor::Action *action, const reactor::Port *port) { } } -template -void after_forward(const reactor::Action *action, reactor::Port *port) { +template void after_forward(const reactor::Action* action, reactor::Port* port) { if constexpr (std::is_void::value) { port->set(); } else { @@ -57,32 +55,24 @@ void after_forward(const reactor::Action *action, reactor::Port *port) { class LFScope { private: - reactor::Reactor *reactor; + reactor::Reactor* reactor; public: - LFScope(reactor::Reactor *reactor) : reactor(reactor) {} + LFScope(reactor::Reactor* reactor) + : reactor(reactor) {} - reactor::TimePoint get_physical_time() const { - return reactor->get_physical_time(); - } + reactor::TimePoint get_physical_time() const { return reactor->get_physical_time(); } reactor::Tag get_tag() const { return reactor->get_tag(); } - reactor::TimePoint get_logical_time() const { - return reactor->get_logical_time(); - } + reactor::TimePoint get_logical_time() const { return reactor->get_logical_time(); } reactor::mstep_t get_microstep() const { return reactor->get_microstep(); } - reactor::Duration get_elapsed_logical_time() const { - return reactor->get_elapsed_logical_time(); - } - reactor::Duration get_elapsed_physical_time() const { - return reactor->get_elapsed_physical_time(); - } - reactor::Environment *environment() const { return reactor->environment(); } + reactor::Duration get_elapsed_logical_time() const { return reactor->get_elapsed_logical_time(); } + reactor::Duration get_elapsed_physical_time() const { return reactor->get_elapsed_physical_time(); } + reactor::Environment* environment() const { return reactor->environment(); } void request_stop() const { return environment()->sync_shutdown(); } }; template -void bind_multiple_ports(std::vector &left_ports, - std::vector &right_ports, bool repeat_left, +void bind_multiple_ports(std::vector& left_ports, std::vector& right_ports, bool repeat_left, std::function connect) { if (repeat_left) { auto l_size = left_ports.size(); diff --git a/core/src/main/resources/lib/cpp/time_parser.hh b/core/src/main/resources/lib/cpp/time_parser.hh index 58c067b796..999e33ca84 100644 --- a/core/src/main/resources/lib/cpp/time_parser.hh +++ b/core/src/main/resources/lib/cpp/time_parser.hh @@ -35,7 +35,7 @@ #include "reactor-cpp/reactor-cpp.hh" #include -std::stringstream &operator>>(std::stringstream &in, reactor::Duration &dur); +std::stringstream& operator>>(std::stringstream& in, reactor::Duration& dur); #include "CLI/cxxopts.hpp" #include @@ -44,27 +44,23 @@ std::stringstream &operator>>(std::stringstream &in, reactor::Duration &dur); #include #include -bool iequals(const std::string &a, const std::string &b) { - return std::equal(a.begin(), a.end(), b.begin(), b.end(), - [](char a, char b) { return tolower(a) == tolower(b); }); +bool iequals(const std::string& a, const std::string& b) { + return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { return tolower(a) == tolower(b); }); } -class argument_incorrect_type_with_reason - : public cxxopts::OptionParseException { +class argument_incorrect_type_with_reason : public cxxopts::OptionParseException { public: - explicit argument_incorrect_type_with_reason(const std::string &arg, - const std::string &reason) - : cxxopts::OptionParseException("Argument " + cxxopts::LQUOTE + arg + - cxxopts::RQUOTE + " failed to parse (" + + explicit argument_incorrect_type_with_reason(const std::string& arg, const std::string& reason) + : cxxopts::OptionParseException("Argument " + cxxopts::LQUOTE + arg + cxxopts::RQUOTE + " failed to parse (" + reason + ")") {} }; -std::string validate_time_string(const std::string &time); +std::string validate_time_string(const std::string& time); /** * converts a reactor::Duration to a string with ns as unit */ -std::string time_to_string(const reactor::Duration &dur) { +std::string time_to_string(const reactor::Duration& dur) { if (dur == reactor::Duration::max()) { return "forever"; } @@ -80,7 +76,7 @@ template std::string any_to_string(const T val) { return ss.str(); } -std::stringstream &operator>>(std::stringstream &in, reactor::Duration &dur) { +std::stringstream& operator>>(std::stringstream& in, reactor::Duration& dur) { double value; std::string unit; @@ -116,12 +112,10 @@ std::stringstream &operator>>(std::stringstream &in, reactor::Duration &dur) { } else if (unit == "msec" || unit == "msecs" || unit == "ms") { std::chrono::duration tmp{value}; dur = std::chrono::duration_cast(tmp); - } else if (unit == "sec" || unit == "secs" || unit == "second" || - unit == "seconds" || unit == "s") { + } else if (unit == "sec" || unit == "secs" || unit == "second" || unit == "seconds" || unit == "s") { std::chrono::duration> tmp{value}; dur = std::chrono::duration_cast(tmp); - } else if (unit == "min" || unit == "mins" || unit == "minute" || - unit == "minutes" || unit == "m") { + } else if (unit == "min" || unit == "mins" || unit == "minute" || unit == "minutes" || unit == "m") { std::chrono::duration> tmp{value}; dur = std::chrono::duration_cast(tmp); } else if (unit == "hour" || unit == "hours" || unit == "h") { @@ -145,7 +139,7 @@ std::stringstream &operator>>(std::stringstream &in, reactor::Duration &dur) { /** * Tests for correct syntax in unit usage for time strings **/ -std::string validate_time_string(const std::string &time) { +std::string validate_time_string(const std::string& time) { auto trimmed = std::regex_replace(time, std::regex("^ +| +$|( ) +"), "$1"); if (trimmed.size() == 0) { return "The empty string is not a valid time!"; @@ -161,14 +155,11 @@ std::string validate_time_string(const std::string &time) { return "No unit given!"; } else { auto unit = trimmed.substr(pos); - if (unit == "nsec" || unit == "nsecs" || unit == "ns" || unit == "usec" || - unit == "usecs" || unit == "us" || unit == "msec" || - unit == "msecs" || unit == "ms" || unit == "sec" || unit == "secs" || - unit == "second" || unit == "seconds" || unit == "s" || - unit == "min" || unit == "mins" || unit == "minute" || - unit == "minutes" || unit == "m" || unit == "hour" || - unit == "hours" || unit == "h" || unit == "day" || unit == "days" || - unit == "d" || unit == "week" || unit == "weeks" || unit == "w") { + if (unit == "nsec" || unit == "nsecs" || unit == "ns" || unit == "usec" || unit == "usecs" || unit == "us" || + unit == "msec" || unit == "msecs" || unit == "ms" || unit == "sec" || unit == "secs" || unit == "second" || + unit == "seconds" || unit == "s" || unit == "min" || unit == "mins" || unit == "minute" || + unit == "minutes" || unit == "m" || unit == "hour" || unit == "hours" || unit == "h" || unit == "day" || + unit == "days" || unit == "d" || unit == "week" || unit == "weeks" || unit == "w") { return ""; } else { std::stringstream ss; From 92b74e36fe58d3e7dbb41e42ab9cf40a8c0ede48 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Wed, 26 Jul 2023 15:10:23 +0200 Subject: [PATCH 351/516] cleaup unused code --- .../cpp/CppAssembleMethodGenerator.kt | 5 +--- .../generator/cpp/CppConnectionGenerator.kt | 30 ------------------- 2 files changed, 1 insertion(+), 34 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt index 1d73946cf7..7239bd8879 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt @@ -165,10 +165,7 @@ class CppAssembleMethodGenerator(private val reactor: Reactor) { } else { val leftPort = c.leftPorts[0] val rightPort = c.rightPorts[0] - - """ - this->environment()->draw_connection(&${leftPort.name}, &${rightPort.name}, ${c.properties}); - """.trimIndent() + "this->environment()->draw_connection(&${leftPort.name}, &${rightPort.name}, ${c.properties});" } /** diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionGenerator.kt index 50d67ef097..48ed28e0b0 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionGenerator.kt @@ -48,36 +48,6 @@ class CppConnectionGenerator(private val reactor: Reactor) { } return false } - - val Connection.requiresConnectionClass: Boolean get() = isPhysical || delay != null || isEnclaveConnection; - } - - fun generateDeclarations() = - reactor.connections.mapNotNull { generateDecleration(it) } - .joinToString("\n", "// connections \n", postfix = "\n") - - fun generateInitializers() = - reactor.connections.mapNotNull { generateConstructorInitializer(it) }.joinLn() - - private fun generateDecleration(connection: Connection): String? = - with(connection) { - if (requiresConnectionClass) { - if (hasMultipleConnections) { - "std::vector> ${connection.name};" - } else { - "${connection.cppType} ${connection.name};" - } - } else null - } - - private fun generateConstructorInitializer(connection: Connection): String? = with(connection) { - if (requiresConnectionClass && !hasMultipleConnections) { - when { - isEnclaveConnection && delay == null -> """, $name{"$name", ${rightPorts[0].container.name}->__lf_env.get()}""" - isEnclaveConnection && delay != null -> """, $name{"$name", ${rightPorts[0].container.name}->__lf_env.get(), ${delay.toCppTime()}}""" - else -> """, $name{"$name", this, ${delay.toCppTime()}}""" - } - } else null } } From 529438ff3a12b35ea186face1eca9dc954fe5608 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Wed, 26 Jul 2023 15:15:28 +0200 Subject: [PATCH 352/516] remove connection generator class --- .../cpp/CppAssembleMethodGenerator.kt | 4 -- .../generator/cpp/CppConnectionExtensions.kt | 41 ++++++++++++++ .../generator/cpp/CppConnectionGenerator.kt | 53 ------------------- 3 files changed, 41 insertions(+), 57 deletions(-) create mode 100644 core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionExtensions.kt delete mode 100644 core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionGenerator.kt diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt index 7239bd8879..1a12f6424a 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt @@ -26,10 +26,6 @@ package org.lflang.generator.cpp import org.lflang.* import org.lflang.generator.PrependOperator -import org.lflang.generator.cpp.CppConnectionGenerator.Companion.cppType -import org.lflang.generator.cpp.CppConnectionGenerator.Companion.isEnclaveConnection -import org.lflang.generator.cpp.CppConnectionGenerator.Companion.name -import org.lflang.generator.cpp.CppConnectionGenerator.Companion.requiresConnectionClass import org.lflang.generator.cpp.CppInstanceGenerator.Companion.isEnclave import org.lflang.generator.cpp.CppPortGenerator.Companion.dataType import org.lflang.lf.Action diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionExtensions.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionExtensions.kt new file mode 100644 index 0000000000..e0615afbe9 --- /dev/null +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionExtensions.kt @@ -0,0 +1,41 @@ +package org.lflang.generator.cpp + +import org.lflang.generator.cpp.CppInstanceGenerator.Companion.isEnclave +import org.lflang.lf.Connection +import org.lflang.lf.Reactor + + +val Connection.name: String + get() = + "connection_" + leftPorts.joinToString("__") { + if (it.container == null) { + it.variable.name + } else { + it.container.name + "_" + it.variable.name + } + } + +val Connection.cppType: String + get() { + return when { + isEnclaveConnection -> when { + isPhysical -> "reactor::ConnectionType::PhysicalEnclaved" + delay != null -> "reactor::ConnectionType::DelayedEnclaved" + else -> "reactor::ConnectionType::Enclaved" + } + + isPhysical -> "reactor::ConnectionType::Physical" + delay != null -> "reactor::ConnectionType::Delayed" + else -> "reactor::ConnectionType::Normal" + } + } + +val Connection.isEnclaveConnection: Boolean + get() { + for (port in leftPorts + rightPorts) { + if (port.container?.isEnclave == true) { + return true + } + } + return false + } diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionGenerator.kt deleted file mode 100644 index 48ed28e0b0..0000000000 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppConnectionGenerator.kt +++ /dev/null @@ -1,53 +0,0 @@ -package org.lflang.generator.cpp - -import org.lflang.* -import org.lflang.generator.cpp.CppConnectionGenerator.Companion.cppType -import org.lflang.generator.cpp.CppConnectionGenerator.Companion.isEnclaveConnection -import org.lflang.generator.cpp.CppInstanceGenerator.Companion.isEnclave -import org.lflang.generator.cpp.CppPortGenerator.Companion.dataType -import org.lflang.lf.Connection -import org.lflang.lf.Port -import org.lflang.lf.Reactor -import org.lflang.lf.VarRef - -class CppConnectionGenerator(private val reactor: Reactor) { - - companion object { - val Connection.name: String - get() = - "connection_" + leftPorts.joinToString("__") { - if (it.container == null) { - it.variable.name - } else { - it.container.name + "_" + it.variable.name - } - } - - val Connection.cppType: String - get() { - val leftPort = leftPorts.first() - return when { - isEnclaveConnection -> when { - isPhysical -> "reactor::ConnectionType::PhysicalEnclaved" - delay != null -> "reactor::ConnectionType::DelayedEnclaved" - else -> "reactor::ConnectionType::Enclaved" - } - - isPhysical -> "reactor::ConnectionType::Physical" - delay != null -> "reactor::ConnectionType::Delayed" - else -> "reactor::ConnectionType::Normal" - } - } - - val Connection.isEnclaveConnection: Boolean - get() { - for (port in leftPorts + rightPorts) { - if (port.container?.isEnclave == true) { - return true - } - } - return false - } - } - -} From 155c6ea62775881fe984b940f5ebce9876008894 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Wed, 26 Jul 2023 15:22:41 +0200 Subject: [PATCH 353/516] update reacor-cpp --- core/src/main/resources/lib/cpp/reactor-cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/cpp/reactor-cpp b/core/src/main/resources/lib/cpp/reactor-cpp index 3c6d8ec833..4d8423d736 160000 --- a/core/src/main/resources/lib/cpp/reactor-cpp +++ b/core/src/main/resources/lib/cpp/reactor-cpp @@ -1 +1 @@ -Subproject commit 3c6d8ec833bb3319b8f979f43bcc1416d2635d85 +Subproject commit 4d8423d73681cc734c3017595996bf3c39d0dc6a From 57ed5bae5e077c7c476b799034f54affbdbdc63e Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Wed, 26 Jul 2023 15:26:55 +0200 Subject: [PATCH 354/516] fix file headers --- core/src/main/resources/lib/cpp/lfutil.hh | 32 +++++++------------ .../src/main/resources/lib/cpp/time_parser.hh | 32 +++++++------------ 2 files changed, 24 insertions(+), 40 deletions(-) diff --git a/core/src/main/resources/lib/cpp/lfutil.hh b/core/src/main/resources/lib/cpp/lfutil.hh index 959d4218df..ef7880f301 100644 --- a/core/src/main/resources/lib/cpp/lfutil.hh +++ b/core/src/main/resources/lib/cpp/lfutil.hh @@ -1,32 +1,24 @@ /* * Copyright (c) 2020, TU Dresden. - - * Redistribution and use in source and binary forms, with or without - modification, + * + * 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, + * + * 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 + * 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. */ diff --git a/core/src/main/resources/lib/cpp/time_parser.hh b/core/src/main/resources/lib/cpp/time_parser.hh index 999e33ca84..3693715f34 100644 --- a/core/src/main/resources/lib/cpp/time_parser.hh +++ b/core/src/main/resources/lib/cpp/time_parser.hh @@ -1,32 +1,24 @@ /* * Copyright (c) 2020, TU Dresden. - - * Redistribution and use in source and binary forms, with or without - modification, + * + * 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, + * + * 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 + * 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. */ From d19cd05aeb71a352a6240571a6d827fd954f23e0 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Wed, 26 Jul 2023 15:28:30 +0200 Subject: [PATCH 355/516] undo change in .gitmodules --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 09e6ea5240..9cd89ffe1a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,7 @@ url = https://github.com/lf-lang/reactor-c.git [submodule "org.lflang/src/lib/cpp/reactor-cpp"] path = core/src/main/resources/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 = core/src/main/resources/lib/rs/reactor-rs url = https://github.com/lf-lang/reactor-rs From e381113c93db3938cc60d619259f483f669b4793 Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Thu, 27 Jul 2023 13:17:31 +0900 Subject: [PATCH 356/516] Register network receiver classes with a portID In reactor-ts, from now on, network receivers are stored in a map consists of portID and network receivers. Thus, portID should be passed when network receivers are registered to the map. --- .../generator/ts/TSConstructorGenerator.kt | 17 +++++------------ .../lflang/generator/ts/TSInstanceGenerator.kt | 7 ++----- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt index e079e8db77..b8c01a0ee7 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt @@ -25,7 +25,7 @@ class TSConstructorGenerator( private fun initializeParameter(p: Parameter): String = "${p.name}: ${TSTypes.getInstance().getTargetType(p)} = ${TSTypes.getInstance().getTargetInitializer(p)}" - private fun generateConstructorArguments(reactor: Reactor, isNetworkReceiver: Boolean): String { + private fun generateConstructorArguments(reactor: Reactor): String { val arguments = StringJoiner(", \n") if (reactor.isMain || reactor.isFederated) { arguments.add("timeout: TimeValue | undefined = undefined") @@ -34,9 +34,6 @@ class TSConstructorGenerator( arguments.add("federationID: string = 'Unidentified Federation'") } else { arguments.add("parent: __Reactor") - if (isNetworkReceiver) { - arguments.add("portID: number") - } } // For TS, parameters are arguments of the class constructor. @@ -52,7 +49,7 @@ class TSConstructorGenerator( return arguments.toString() } - private fun generateSuperConstructorCall(reactor: Reactor, isFederate: Boolean, isNetworkReceiver: Boolean): String = + private fun generateSuperConstructorCall(reactor: Reactor, isFederate: Boolean): String = if (reactor.isMain) { if (isFederate) { """ @@ -68,11 +65,7 @@ class TSConstructorGenerator( "super(timeout, keepAlive, fast, success, fail);" } } else { - if (isNetworkReceiver) { - "super(parent, portID);" - } else { - "super(parent);" - } + "super(parent);" } // Generate code for setting target configurations. @@ -100,9 +93,9 @@ class TSConstructorGenerator( return with(PrependOperator) { """ |constructor ( - ${" | "..generateConstructorArguments(reactor, isNetworkReceiver)} + ${" | "..generateConstructorArguments(reactor)} |) { - ${" | "..generateSuperConstructorCall(reactor, isFederate, isNetworkReceiver)} + ${" | "..generateSuperConstructorCall(reactor, isFederate)} ${" | "..generateTargetConfigurations(targetConfig)} ${" | "..instances.generateInstantiations()} ${" | "..timers.generateInstantiations()} diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt index 8d04f93dd5..6cb6aea34f 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt @@ -57,11 +57,6 @@ class TSInstanceGenerator( } val childReactorArguments = StringJoiner(", ") childReactorArguments.add("this") - if (isNetworkReceiver) { - // Assume that network receiver reactors are sorted by portID - childReactorArguments.add(portID.toString()) - portID++ - } for (parameter in childReactor.reactorClass.toDefinition().parameters) { childReactorArguments.add(TSTypes.getInstance().getTargetInitializer(parameter, childReactor)) @@ -79,8 +74,10 @@ class TSInstanceGenerator( "this.${childReactor.name} = " + "new ${childReactor.reactorClass.name}($childReactorArguments)") if (isNetworkReceiver) { + // Assume that network receiver reactors are sorted by portID childReactorInstantiations.add( "this.registerNetworkReceiver(\n" + + "\t${portID},\n" + "\tthis.${childReactor.name} as __NetworkReceiver\n)") } if (isNetworkSender) { From ea2bf26b25d39e2c697d529bf50aee3f23ab4f8a Mon Sep 17 00:00:00 2001 From: Abhi Gundrala Date: Wed, 26 Jul 2023 23:51:48 -0700 Subject: [PATCH 357/516] update reactor-c --- core/src/main/resources/lib/c/reactor-c | 2 +- core/src/main/resources/lib/cpp/reactor-cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 2207668f6a..586a98f887 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 2207668f6a703b9ea22e3c81668579b36cdd18db +Subproject commit 586a98f887824955e3bb19869de279c026a48846 diff --git a/core/src/main/resources/lib/cpp/reactor-cpp b/core/src/main/resources/lib/cpp/reactor-cpp index e3564782f0..b607f1f640 160000 --- a/core/src/main/resources/lib/cpp/reactor-cpp +++ b/core/src/main/resources/lib/cpp/reactor-cpp @@ -1 +1 @@ -Subproject commit e3564782f0e1a2bc427e5932a88ff8f87dacd50c +Subproject commit b607f1f64083ab531e7a676b29de75f076aa1cde From 0c970056e56b2be9edbab05c41d881c0dcb7497a Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Thu, 27 Jul 2023 16:27:34 +0900 Subject: [PATCH 358/516] Use `undefined` to represent 'no delay' in TypeScript --- .../java/org/lflang/federated/extensions/TSExtension.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java index b572dc8a0b..e62d1c29d1 100644 --- a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java @@ -123,7 +123,7 @@ public String generateNetworkSenderBody( private String getNetworkDelayLiteral(Expression e) { var cLiteral = CExtensionUtils.getNetworkDelayLiteral(e); - return cLiteral.equals("NEVER") ? "TimeValue.never()" : "TimeValue.nsec(" + cLiteral + ")"; + return cLiteral.equals("NEVER") ? "undefined" : "TimeValue.nsec(" + cLiteral + ")"; } @Override @@ -137,7 +137,7 @@ public String generatePortAbsentReactionBody( // If the output port has not been set for the current logical time, // send an ABSENT message to the receiving federate if (%1$s%2$s === undefined) { - this.util.sendRTIPortAbsent(%3$s, %4$d, %5$d); + this.util.sendRTIPortAbsent(%3$d, %4$d, %5$s); } """ .formatted( @@ -145,9 +145,9 @@ public String generatePortAbsentReactionBody( ? "" : srcOutputPort.getContainer().getName() + ".", srcOutputPort.getVariable().getName(), - additionalDelayString, connection.getDstFederate().id, - receivingPortID); + receivingPortID, + additionalDelayString); } @Override From bb42523ba54381bcbf8e02051b7a12d28f93cf06 Mon Sep 17 00:00:00 2001 From: revol-xut Date: Wed, 26 Jul 2023 16:29:23 +0200 Subject: [PATCH 359/516] update reactor-cpp updating reactor-cpp updating reactor-cpp update reactor-cpp --- core/src/main/resources/lib/cpp/reactor-cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/cpp/reactor-cpp b/core/src/main/resources/lib/cpp/reactor-cpp index 4d8423d736..521f1d852b 160000 --- a/core/src/main/resources/lib/cpp/reactor-cpp +++ b/core/src/main/resources/lib/cpp/reactor-cpp @@ -1 +1 @@ -Subproject commit 4d8423d73681cc734c3017595996bf3c39d0dc6a +Subproject commit 521f1d852b6b3dc5850d3dcfc669ea812296db94 From 9b800f077158064fd13b11b467cf4a5c2533bd84 Mon Sep 17 00:00:00 2001 From: Abhi Gundrala Date: Thu, 27 Jul 2023 08:36:13 -0700 Subject: [PATCH 360/516] [submodule] update reactor-c to main --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 907ba0e86a..586a98f887 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 907ba0e86a3cf7956e96257ab51c2d89c3c71069 +Subproject commit 586a98f887824955e3bb19869de279c026a48846 From 2791e065d9f09f79e18d374e47e6445e4b5f594b Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 27 Jul 2023 18:27:14 -0700 Subject: [PATCH 361/516] Fix vscode-lingua-franca#133. --- core/src/main/java/org/lflang/ast/ToLf.java | 38 +++++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/lflang/ast/ToLf.java b/core/src/main/java/org/lflang/ast/ToLf.java index 3cb9b375f5..929dbf6d54 100644 --- a/core/src/main/java/org/lflang/ast/ToLf.java +++ b/core/src/main/java/org/lflang/ast/ToLf.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Comparator; import java.util.HashSet; import java.util.List; @@ -117,14 +116,19 @@ public MalleableString doSwitch(EObject eObject) { Predicate doesNotBelongToPrevious = doesNotBelongToAncestor.and( previous == null ? n -> true : ASTUtils.sameLine(previous).negate()); - Stream precedingComments = - ASTUtils.getPrecedingComments(node, doesNotBelongToPrevious).map(String::strip); - Collection allComments = new ArrayList<>(); - precedingComments.forEachOrdered(allComments::add); - getContainedComments(node).stream() - .filter(doesNotBelongToAncestor) - .map(INode::getText) - .forEachOrdered(allComments::add); + var allComments = new ArrayList(); + if (eObject.eContents().isEmpty() && !(eObject instanceof Code)) { + getContainedComments(node).stream() + .filter(doesNotBelongToAncestor) + .filter(doesNotBelongToPrevious) + .map(INode::getText) + .forEach(allComments::add); + } else { + Stream precedingComments = + ASTUtils.getPrecedingComments(node, doesNotBelongToPrevious).map(String::strip); + precedingComments.forEachOrdered(allComments::add); + getContainedCodeComments(node).stream().map(INode::getText).forEach(allComments::add); + } allComments.addAll(followingComments); if (allComments.stream().anyMatch(s -> KEEP_FORMAT_COMMENT.matcher(s).matches())) { return MalleableString.anyOf(StringUtil.trimCodeBlock(node.getText(), 0)) @@ -140,7 +144,7 @@ static Set getAncestorComments(INode node) { for (ICompositeNode ancestor = node.getParent(); ancestor != null; ancestor = ancestor.getParent()) { - ancestorComments.addAll(getContainedComments(ancestor)); + ancestorComments.addAll(getContainedCodeComments(ancestor)); ASTUtils.getPrecedingCommentNodes(ancestor, u -> true).forEachOrdered(ancestorComments::add); } return ancestorComments; @@ -192,7 +196,7 @@ private static Stream getFollowingComments( * Return comments contained by {@code node} that logically belong to this node (and not to any of * its children). */ - private static List getContainedComments(INode node) { + private static List getContainedCodeComments(INode node) { ArrayList ret = new ArrayList<>(); boolean inSemanticallyInsignificantLeadingRubbish = true; for (INode child : node.getAsTreeIterable()) { @@ -212,6 +216,18 @@ private static List getContainedComments(INode node) { return ret; } + /** + * Return all comments that are part of {@code node}, regardless of where they appear relative to + * the main content of the node. + */ + private static List getContainedComments(INode node) { + var ret = new ArrayList(); + for (INode child : node.getAsTreeIterable()) { + if (ASTUtils.isComment(child)) ret.add(child); + } + return ret; + } + @Override public MalleableString caseCodeExpr(CodeExpr object) { return caseCode(object.getCode()); From 42c1f53c5f6c3fbfd1df5b84497fb0a1b38c893b Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Fri, 28 Jul 2023 13:27:01 +0900 Subject: [PATCH 362/516] Update package.json --- core/src/main/resources/lib/ts/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/ts/package.json b/core/src/main/resources/lib/ts/package.json index 4a28012180..4797fd56ed 100644 --- a/core/src/main/resources/lib/ts/package.json +++ b/core/src/main/resources/lib/ts/package.json @@ -2,7 +2,7 @@ "name": "LinguaFrancaDefault", "type": "commonjs", "dependencies": { - "@lf-lang/reactor-ts": "git://github.com/lf-lang/reactor-ts.git#ts-level-assignment", + "@lf-lang/reactor-ts": "git://github.com/lf-lang/reactor-ts.git#ts-cyclic-dependencies", "command-line-args": "^5.1.1", "command-line-usage": "^6.1.3" }, From 5fe79f3f78efafb1a40506591d09fe7b36359271 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Fri, 28 Jul 2023 15:04:04 -0700 Subject: [PATCH 363/516] Add `extends` keyword to tokens allowed in reaction bodies --- core/src/main/java/org/lflang/LinguaFranca.xtext | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/org/lflang/LinguaFranca.xtext b/core/src/main/java/org/lflang/LinguaFranca.xtext index e414ff6b0e..b00456ce07 100644 --- a/core/src/main/java/org/lflang/LinguaFranca.xtext +++ b/core/src/main/java/org/lflang/LinguaFranca.xtext @@ -513,6 +513,7 @@ Token: 'startup' | 'shutdown' | 'after' | 'deadline' | 'mutation' | 'preamble' | 'new' | 'federated' | 'at' | 'as' | 'from' | 'widthof' | 'const' | 'method' | 'interleaved' | 'mode' | 'initial' | 'reset' | 'history' | 'watchdog' | + 'extends' | // Other terminals NEGINT | TRUE | FALSE | From 5f7ca6edbcb71a7d2d050f0d4dea546506919e7a Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun <78055940+byeong-gil@users.noreply.github.com> Date: Sat, 29 Jul 2023 14:19:09 +0900 Subject: [PATCH 364/516] Update package.json --- core/src/main/resources/lib/ts/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/ts/package.json b/core/src/main/resources/lib/ts/package.json index 4a28012180..fc063d791e 100644 --- a/core/src/main/resources/lib/ts/package.json +++ b/core/src/main/resources/lib/ts/package.json @@ -2,7 +2,7 @@ "name": "LinguaFrancaDefault", "type": "commonjs", "dependencies": { - "@lf-lang/reactor-ts": "git://github.com/lf-lang/reactor-ts.git#ts-level-assignment", + "@lf-lang/reactor-ts": "git://github.com/lf-lang/reactor-ts.git#master", "command-line-args": "^5.1.1", "command-line-usage": "^6.1.3" }, From 050729ab02bd63ba7ebd57857a19d172e58b8a63 Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Sat, 29 Jul 2023 16:49:14 +0900 Subject: [PATCH 365/516] Pass the TPO level info to network reactors --- .../federated/generator/FedASTUtils.java | 13 +++++++------ .../org/lflang/validation/AttributeSpec.java | 2 +- .../generator/ts/TSConstructorGenerator.kt | 19 ++++++++++++++----- .../generator/ts/TSInstanceGenerator.kt | 15 ++++++++------- .../lflang/generator/ts/TSReactorGenerator.kt | 13 +++++-------- 5 files changed, 35 insertions(+), 27 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index d38237c71b..5b4492942d 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -43,6 +43,7 @@ import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.xtext.xbase.lib.IteratorExtensions; +import org.lflang.AttributeUtils; import org.lflang.InferredType; import org.lflang.MessageReporter; import org.lflang.TargetProperty.CoordinationType; @@ -240,11 +241,11 @@ private static void addNetworkReceiverReactor( .getParent() .reactorDefinition; // Top-level reactor. - // Add the attribute "_NetworkReactor" for the network receiver. + // Add the attribute "_networkReactor" for the network receiver. var a = factory.createAttribute(); - a.setAttrName("_NetworkReactor"); + a.setAttrName("_networkReactor"); var e = factory.createAttrParm(); - e.setValue("Receiver"); + e.setValue("\"receiver\""); a.getAttrParms().add(e); receiver.getAttributes().add(a); @@ -646,11 +647,11 @@ private static Reactor getNetworkSenderReactor( // Initialize Reactor and Reaction AST Nodes Reactor sender = factory.createReactor(); - // Add the attribute "_NetworkReactor" for the network sender. + // Add the attribute "_networkReactor" for the network sender. var a = factory.createAttribute(); - a.setAttrName("_NetworkReactor"); + a.setAttrName("_networkReactor"); var e = factory.createAttrParm(); - e.setValue("Sender"); + e.setValue("\"sender\""); a.getAttrParms().add(e); sender.getAttributes().add(a); diff --git a/core/src/main/java/org/lflang/validation/AttributeSpec.java b/core/src/main/java/org/lflang/validation/AttributeSpec.java index 9e69a28f1f..57405d2983 100644 --- a/core/src/main/java/org/lflang/validation/AttributeSpec.java +++ b/core/src/main/java/org/lflang/validation/AttributeSpec.java @@ -233,7 +233,7 @@ enum AttrParamType { "_tpoLevel", new AttributeSpec(List.of(new AttrParamSpec(VALUE_ATTR, AttrParamType.INT, false)))); ATTRIBUTE_SPECS_BY_NAME.put( - "_NetworkReactor", + "_networkReactor", new AttributeSpec(List.of(new AttrParamSpec(VALUE_ATTR, AttrParamType.STRING, false)))); } } diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt index b8c01a0ee7..0644319f4d 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt @@ -25,7 +25,7 @@ class TSConstructorGenerator( private fun initializeParameter(p: Parameter): String = "${p.name}: ${TSTypes.getInstance().getTargetType(p)} = ${TSTypes.getInstance().getTargetInitializer(p)}" - private fun generateConstructorArguments(reactor: Reactor): String { + private fun generateConstructorArguments(reactor: Reactor, isNetworkReactor: Boolean): String { val arguments = StringJoiner(", \n") if (reactor.isMain || reactor.isFederated) { arguments.add("timeout: TimeValue | undefined = undefined") @@ -46,10 +46,14 @@ class TSConstructorGenerator( arguments.add("fail?: () => void") } + if (isNetworkReactor) { + arguments.add("tpoLevel: number") + } + return arguments.toString() } - private fun generateSuperConstructorCall(reactor: Reactor, isFederate: Boolean): String = + private fun generateSuperConstructorCall(reactor: Reactor, isFederate: Boolean, isNetworkReactor: Boolean): String = if (reactor.isMain) { if (isFederate) { """ @@ -65,7 +69,11 @@ class TSConstructorGenerator( "super(timeout, keepAlive, fast, success, fail);" } } else { - "super(parent);" + if (isNetworkReactor) { + "super(parent, tpoLevel)" + } else { + "super(parent);" + } } // Generate code for setting target configurations. @@ -85,6 +93,7 @@ class TSConstructorGenerator( actions: TSActionGenerator, ports: TSPortGenerator, isFederate: Boolean, + isNetworkReactor: Boolean, isNetworkReceiver: Boolean ): String { val connections = TSConnectionGenerator(reactor.connections, messageReporter) @@ -93,9 +102,9 @@ class TSConstructorGenerator( return with(PrependOperator) { """ |constructor ( - ${" | "..generateConstructorArguments(reactor)} + ${" | "..generateConstructorArguments(reactor, isNetworkReactor)} |) { - ${" | "..generateSuperConstructorCall(reactor, isFederate)} + ${" | "..generateSuperConstructorCall(reactor, isFederate, isNetworkReactor)} ${" | "..generateTargetConfigurations(targetConfig)} ${" | "..instances.generateInstantiations()} ${" | "..timers.generateInstantiations()} diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt index 6cb6aea34f..2911503159 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt @@ -48,19 +48,20 @@ class TSInstanceGenerator( val childReactorInstantiations = LinkedList() var portID = 0 for (childReactor in childReactors) { - var isNetworkSender = false - var isNetworkReceiver = false - val networkReactorAttribute = AttributeUtils.findAttributeByName(childReactor.reactorClass, "_NetworkReactor") - if (networkReactorAttribute != null) { - isNetworkSender = networkReactorAttribute.getAttrParms().get(0).getName() == "Sender" - isNetworkReceiver = networkReactorAttribute.getAttrParms().get(0).getName() == "Receiver" - } + var tpoLevel = AttributeUtils.getFirstArgumentValue(AttributeUtils.findAttributeByName(childReactor, "_tpoLevel")); + val networkReactorAttributeValue = AttributeUtils.getFirstArgumentValue(AttributeUtils.findAttributeByName(childReactor.reactorClass, "_networkReactor")) + var isNetworkReceiver = networkReactorAttributeValue == "receiver" + var isNetworkSender = networkReactorAttributeValue == "sender" + val childReactorArguments = StringJoiner(", ") childReactorArguments.add("this") for (parameter in childReactor.reactorClass.toDefinition().parameters) { childReactorArguments.add(TSTypes.getInstance().getTargetInitializer(parameter, childReactor)) } + if (tpoLevel != null) { + childReactorArguments.add(tpoLevel.toString()); + } if (childReactor.isBank) { childReactorInstantiations.add( "this.${childReactor.name} = " + diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt index e84f012480..73da56bf4c 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSReactorGenerator.kt @@ -97,13 +97,10 @@ ${" |"..preamble.code.toText()} } val isFederate = AttributeUtils.isFederate(reactor) - val networkReactorAttribute = AttributeUtils.findAttributeByName(reactor, "_NetworkReactor") - var isNetworkSender = false - var isNetworkReceiver = false - if (networkReactorAttribute != null) { - isNetworkSender = networkReactorAttribute.getAttrParms().get(0).getName() == "Sender" - isNetworkReceiver = networkReactorAttribute.getAttrParms().get(0).getName() == "Receiver" - } + val networkReactorAttributeValue = AttributeUtils.getFirstArgumentValue(AttributeUtils.findAttributeByName(reactor, "_networkReactor")) + var isNetworkReactor = networkReactorAttributeValue != null + var isNetworkReceiver = networkReactorAttributeValue == "receiver" + var isNetworkSender = networkReactorAttributeValue == "sender" // NOTE: type parameters that are referenced in ports or actions must extend // Present in order for the program to type check. @@ -144,7 +141,7 @@ ${" |"..preamble.code.toText()} ${" | "..actionGenerator.generateClassProperties()} ${" | "..portGenerator.generateClassProperties()} ${" | "..constructorGenerator.generateConstructor(targetConfig, instanceGenerator, timerGenerator, parameterGenerator, - stateGenerator, actionGenerator, portGenerator, isFederate, isNetworkReceiver)} + stateGenerator, actionGenerator, portGenerator, isFederate, isNetworkReactor, isNetworkReceiver)} |} |// =============== END reactor class ${reactor.name} | From 3b6a39f72ac9d360133ae20e991bb48d2426d3be Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun <78055940+byeong-gil@users.noreply.github.com> Date: Sat, 29 Jul 2023 16:50:34 +0900 Subject: [PATCH 366/516] Update FedASTUtils.java --- .../main/java/org/lflang/federated/generator/FedASTUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index 5b4492942d..dda752ff93 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -43,7 +43,6 @@ import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.xtext.xbase.lib.IteratorExtensions; -import org.lflang.AttributeUtils; import org.lflang.InferredType; import org.lflang.MessageReporter; import org.lflang.TargetProperty.CoordinationType; From d1669459824f9b8eca2a8399155ad0525f93e3cd Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sun, 30 Jul 2023 08:17:06 -0400 Subject: [PATCH 367/516] Attempt to deal with comment in #1831 --- core/src/main/java/org/lflang/TargetConfig.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/lflang/TargetConfig.java b/core/src/main/java/org/lflang/TargetConfig.java index 8eb12b6096..12096f9d6c 100644 --- a/core/src/main/java/org/lflang/TargetConfig.java +++ b/core/src/main/java/org/lflang/TargetConfig.java @@ -388,10 +388,9 @@ public static class PlatformOptions { /** * The string value used to determine what type of embedded board we work with and can be used - * to simplify the build process. For example, when we want to flash to an Arduino Nano 33 BLE - * board, we can use the string arduino:mbed_nano:nano33ble Can also be used to specify uart - * output setting on rp2040 boards where arduino_nano_rp2040_connect:uart or - * arduino_nano_rp2040_connect:usb (usb) fully specifies the board and uart output + * to simplify the build process. This string has the form "board_name[:option]*" (zero or more + * options separated by colons). For example, "pico:usb" specifies a Raspberry Pi Pico where + * stdin and stdout go through a USB serial port. */ public String board = null; From 6cec307b5b0246c498d2c3a548ea8025f4b07003 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Sun, 30 Jul 2023 06:45:57 -0700 Subject: [PATCH 368/516] Update core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java Co-authored-by: Edward A. Lee --- .../java/org/lflang/generator/c/CMainFunctionGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java b/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java index 5d11e27e05..5f0657c322 100644 --- a/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java @@ -63,7 +63,7 @@ private String generateMainFunction() { " exit(res);", "}"); } else if (targetConfig.platformOptions.platform == Platform.RP2040) { - // Pico platform cannont use command line args. + // Pico platform cannot use command line args. return String.join("\n", "int main(void) {", " return lf_reactor_c_main(0, NULL);", "}"); } else { return String.join( From 75ebf6d1b0c45b32568097b9020f10c8bb92639f Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sun, 30 Jul 2023 07:32:28 -0400 Subject: [PATCH 369/516] Attempt to find pico-sdk path relative to package root --- .../org/lflang/generator/c/CCmakeGenerator.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 63262147e7..19df4d44e7 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -25,6 +25,7 @@ package org.lflang.generator.c; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -136,6 +137,22 @@ CodeBuilder generateCMakeCode( cMakeCode.newLine(); break; case RP2040: + // Attempt to set PICO_SDK_PATH if it is not already set. + if (System.getenv("PICO_SDK_PATH") == null) { + Path picoSDKPath = fileConfig.getOutPath().resolve("pico-sdk"); + if (Files.isDirectory(picoSDKPath)) { + messageReporter.nowhere().info("pico-sdk library found at " + + picoSDKPath.toString() + + ". You can override this by setting PICO_SDK_PATH."); + cMakeCode.pr("# Define the root of the pico-sdk library."); + cMakeCode.pr("set(PICO_SDK_PATH " + fileConfig.getOutPath() + "/pico-sdk)"); + } else { + messageReporter.nowhere().warning( + "No PICO_SDK_PATH environment variable and no pico-sdk directory " + + "at the package root directory. Pico SDK will not be found." + ); + } + } cMakeCode.pr("include(pico_sdk_import.cmake)"); cMakeCode.pr("project(" + executableName + " LANGUAGES C CXX ASM)"); cMakeCode.newLine(); From 45fb5cbdbc662408e2b44b75ddb02692393a2a3f Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sun, 30 Jul 2023 13:19:22 -0400 Subject: [PATCH 370/516] Align reactor-c to main --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 586a98f887..ccf230f3d1 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 586a98f887824955e3bb19869de279c026a48846 +Subproject commit ccf230f3d1027ee39987066b9e1f3211560e9f6e From 6ded11576c52cc62eecedba059d9cb9d77eb44b5 Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Mon, 31 Jul 2023 14:27:03 +0900 Subject: [PATCH 371/516] Handle network reactions without a TPO level --- .../kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt index 0644319f4d..fc8b942a2f 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSConstructorGenerator.kt @@ -47,7 +47,7 @@ class TSConstructorGenerator( } if (isNetworkReactor) { - arguments.add("tpoLevel: number") + arguments.add("tpoLevel?: number") } return arguments.toString() From 1027c0f365ed90f5f36ecd62f1584cab2b06deea Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Mon, 31 Jul 2023 02:32:53 -0700 Subject: [PATCH 372/516] Use package published on npm --- core/src/main/resources/lib/ts/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/ts/package.json b/core/src/main/resources/lib/ts/package.json index fc063d791e..1ed0dd7c54 100644 --- a/core/src/main/resources/lib/ts/package.json +++ b/core/src/main/resources/lib/ts/package.json @@ -2,7 +2,7 @@ "name": "LinguaFrancaDefault", "type": "commonjs", "dependencies": { - "@lf-lang/reactor-ts": "git://github.com/lf-lang/reactor-ts.git#master", + "@lf-lang/reactor-ts": "^0.5.0", "command-line-args": "^5.1.1", "command-line-usage": "^6.1.3" }, From bb83f62ebc74e2fc3050136450ef7df89e15f89c Mon Sep 17 00:00:00 2001 From: Abhi Gundrala Date: Mon, 31 Jul 2023 09:18:25 -0700 Subject: [PATCH 373/516] [format] apply spotless --- .../org/lflang/generator/c/CCmakeGenerator.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 19df4d44e7..5f40318ca1 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -141,16 +141,20 @@ CodeBuilder generateCMakeCode( if (System.getenv("PICO_SDK_PATH") == null) { Path picoSDKPath = fileConfig.getOutPath().resolve("pico-sdk"); if (Files.isDirectory(picoSDKPath)) { - messageReporter.nowhere().info("pico-sdk library found at " - + picoSDKPath.toString() - + ". You can override this by setting PICO_SDK_PATH."); + messageReporter + .nowhere() + .info( + "pico-sdk library found at " + + picoSDKPath.toString() + + ". You can override this by setting PICO_SDK_PATH."); cMakeCode.pr("# Define the root of the pico-sdk library."); cMakeCode.pr("set(PICO_SDK_PATH " + fileConfig.getOutPath() + "/pico-sdk)"); } else { - messageReporter.nowhere().warning( + messageReporter + .nowhere() + .warning( "No PICO_SDK_PATH environment variable and no pico-sdk directory " - + "at the package root directory. Pico SDK will not be found." - ); + + "at the package root directory. Pico SDK will not be found."); } } cMakeCode.pr("include(pico_sdk_import.cmake)"); From d04d038a58f46852224c6d56522b847531f9fead Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 31 Jul 2023 09:25:58 -0700 Subject: [PATCH 374/516] Fix whitespace-related bug. --- .../main/java/org/lflang/federated/extensions/CExtension.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index 3829c72617..1ad0f306fd 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -614,7 +614,8 @@ private String generateInitializeTriggers( var oldFederatedReactorName = federatedReactor.getName(); federatedReactor.setName(federate.name); var main = new ReactorInstance(federatedReactor, messageReporter, -1); - code.pr(CExtensionUtils.initializeTriggersForNetworkActions(federate, main)); + var initializeTriggersForNetworkActions = CExtensionUtils.initializeTriggersForNetworkActions(federate, main); + if (!initializeTriggersForNetworkActions.isBlank()) code.pr(initializeTriggersForNetworkActions); code.pr("staa_initialization(); \\"); federatedReactor.setName(oldFederatedReactorName); From 246c143c2aca8e8f730282e6f375766ec853a0a9 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Mon, 31 Jul 2023 09:46:36 -0700 Subject: [PATCH 375/516] Apply formatter --- .../java/org/lflang/federated/extensions/CExtension.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index 1ad0f306fd..d6493f96c0 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -614,8 +614,10 @@ private String generateInitializeTriggers( var oldFederatedReactorName = federatedReactor.getName(); federatedReactor.setName(federate.name); var main = new ReactorInstance(federatedReactor, messageReporter, -1); - var initializeTriggersForNetworkActions = CExtensionUtils.initializeTriggersForNetworkActions(federate, main); - if (!initializeTriggersForNetworkActions.isBlank()) code.pr(initializeTriggersForNetworkActions); + var initializeTriggersForNetworkActions = + CExtensionUtils.initializeTriggersForNetworkActions(federate, main); + if (!initializeTriggersForNetworkActions.isBlank()) + code.pr(initializeTriggersForNetworkActions); code.pr("staa_initialization(); \\"); federatedReactor.setName(oldFederatedReactorName); From ef92075fb1b4e69f4b39d71033ba393ff0b9823e Mon Sep 17 00:00:00 2001 From: Abhi Gundrala Date: Mon, 31 Jul 2023 10:24:57 -0700 Subject: [PATCH 376/516] [refactor] resolve cmake comments --- .../federated/extensions/CExtension.java | 6 ++- .../lflang/generator/c/CCmakeGenerator.java | 49 +++++++++---------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index 1ad0f306fd..d6493f96c0 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -614,8 +614,10 @@ private String generateInitializeTriggers( var oldFederatedReactorName = federatedReactor.getName(); federatedReactor.setName(federate.name); var main = new ReactorInstance(federatedReactor, messageReporter, -1); - var initializeTriggersForNetworkActions = CExtensionUtils.initializeTriggersForNetworkActions(federate, main); - if (!initializeTriggersForNetworkActions.isBlank()) code.pr(initializeTriggersForNetworkActions); + var initializeTriggersForNetworkActions = + CExtensionUtils.initializeTriggersForNetworkActions(federate, main); + if (!initializeTriggersForNetworkActions.isBlank()) + code.pr(initializeTriggersForNetworkActions); code.pr("staa_initialization(); \\"); federatedReactor.setName(oldFederatedReactorName); diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 5f40318ca1..38dda90dbd 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -112,6 +112,20 @@ CodeBuilder generateCMakeCode( .relativize(fileConfig.getSrcGenPath().resolve(Paths.get(file))); additionalSources.add(FileUtil.toUnixString(relativePath)); } + // Parse board option of the platform target property + // Specified as a series of colon spaced options + // Board syntax + // rp2040 : + // arduino + String[] boardProperties = null; + if (targetConfig.platformOptions.board != null) { + boardProperties = targetConfig.platformOptions.board.trim().split(":"); + // Ignore whitespace + for (int i = 0; i < boardProperties.length; i++) { + boardProperties[i] = boardProperties[i].trim(); + } + } + additionalSources.addAll(this.additionalSources); cMakeCode.newLine(); @@ -162,16 +176,10 @@ CodeBuilder generateCMakeCode( cMakeCode.newLine(); // board type for rp2040 based boards if (targetConfig.platformOptions.board != null) { - // board syntax : - // ignore whitespace - String[] bProps = targetConfig.platformOptions.board.trim().split(":"); - for (int i = 0; i < bProps.length; i++) { - bProps[i] = bProps[i].trim(); - } - if (bProps.length < 1 || bProps[0].equals("")) { + if (boardProperties.length < 1 || boardProperties[0].equals("")) { cMakeCode.pr("set(PICO_BOARD pico)"); } else { - cMakeCode.pr("set(PICO_BOARD \"" + bProps[0] + "\")"); + cMakeCode.pr("set(PICO_BOARD \"" + boardProperties[0] + "\")"); } } // remove warnings for rp2040 only to make debug easier @@ -290,24 +298,15 @@ CodeBuilder generateCMakeCode( // post target definition board configurations switch (targetConfig.platformOptions.platform) { case RP2040: - if (targetConfig.platformOptions.board != null) { - String[] bProps = targetConfig.platformOptions.board.trim().split(":"); - for (int i = 0; i < bProps.length; i++) { - bProps[i] = bProps[i].trim(); - } - if (bProps.length > 1 && bProps[1].equals("uart")) { - cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 0)"); - cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 1)"); - } else if (bProps.length > 1 && bProps[1].equals("usb")) { - cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 1)"); - cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 0)"); - } - } else { - // default - cMakeCode.pr("# Enable both usb and uart stdio"); - cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} 1)"); - cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} 1)"); + // set stdio output + boolean usb = true; + boolean uart = true; + if (targetConfig.platformOptions.board != null && boardProperties.length > 1) { + uart = !boardProperties[1].equals("usb"); + usb = !boardProperties[1].equals("uart"); } + cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET}" + (usb ? 1 : 0) + ")"); + cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET}" + (uart ? 0 : 0) + ")"); break; } From 0eae94eb80d38c7672666730d1e66a13d0f95f78 Mon Sep 17 00:00:00 2001 From: Abhi Gundrala Date: Mon, 31 Jul 2023 12:01:01 -0700 Subject: [PATCH 377/516] [refactor] whitespace --- .../main/java/org/lflang/generator/c/CCmakeGenerator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 38dda90dbd..381dadcce5 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -117,7 +117,7 @@ CodeBuilder generateCMakeCode( // Board syntax // rp2040 : // arduino - String[] boardProperties = null; + String[] boardProperties = {}; if (targetConfig.platformOptions.board != null) { boardProperties = targetConfig.platformOptions.board.trim().split(":"); // Ignore whitespace @@ -305,8 +305,8 @@ CodeBuilder generateCMakeCode( uart = !boardProperties[1].equals("usb"); usb = !boardProperties[1].equals("uart"); } - cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET}" + (usb ? 1 : 0) + ")"); - cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET}" + (uart ? 0 : 0) + ")"); + cMakeCode.pr("pico_enable_stdio_usb(${LF_MAIN_TARGET} " + (usb ? 1 : 0) + ")"); + cMakeCode.pr("pico_enable_stdio_uart(${LF_MAIN_TARGET} " + (uart ? 1 : 0) + ")"); break; } From 0c0171c9b53e40481e68ec8042b5c204168e95d1 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 31 Jul 2023 11:43:26 -0700 Subject: [PATCH 378/516] Fix comment duplication bug. The change on line 130 (filter out ancestor comments) is the relevant one. This is just a matter of accidentally deleting one tiny bit of code that was in master. --- core/src/main/java/org/lflang/ast/ToLf.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/lflang/ast/ToLf.java b/core/src/main/java/org/lflang/ast/ToLf.java index 929dbf6d54..9646bea79c 100644 --- a/core/src/main/java/org/lflang/ast/ToLf.java +++ b/core/src/main/java/org/lflang/ast/ToLf.java @@ -125,9 +125,13 @@ public MalleableString doSwitch(EObject eObject) { .forEach(allComments::add); } else { Stream precedingComments = - ASTUtils.getPrecedingComments(node, doesNotBelongToPrevious).map(String::strip); + ASTUtils.getPrecedingComments(node, doesNotBelongToPrevious.and(doesNotBelongToAncestor)) + .map(String::strip); precedingComments.forEachOrdered(allComments::add); - getContainedCodeComments(node).stream().map(INode::getText).forEach(allComments::add); + getContainedCodeComments(node).stream() + .filter(doesNotBelongToAncestor) + .map(INode::getText) + .forEach(allComments::add); } allComments.addAll(followingComments); if (allComments.stream().anyMatch(s -> KEEP_FORMAT_COMMENT.matcher(s).matches())) { @@ -146,6 +150,7 @@ static Set getAncestorComments(INode node) { ancestor = ancestor.getParent()) { ancestorComments.addAll(getContainedCodeComments(ancestor)); ASTUtils.getPrecedingCommentNodes(ancestor, u -> true).forEachOrdered(ancestorComments::add); + ancestorComments.addAll(getContainedCodeComments(ancestor)); } return ancestorComments; } From 6c3916d6aa79fe56b0a40c7a70b2f8df22a29f1f Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Wed, 2 Aug 2023 09:58:53 +0900 Subject: [PATCH 379/516] Restore failing TS tests for testing --- .../src/federated/{failing => }/LoopDistributedCentralized.lf | 0 .../src/federated/{failing => }/LoopDistributedDouble.lf | 0 .../TypeScript/src/federated/{failing => }/PingPongDistributed.lf | 0 .../src/federated/{failing => }/PingPongDistributedPhysical.lf | 0 test/TypeScript/src/federated/{failing => }/SimpleFederated.lf | 0 test/TypeScript/src/federated/{failing => }/SpuriousDependency.lf | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename test/TypeScript/src/federated/{failing => }/LoopDistributedCentralized.lf (100%) rename test/TypeScript/src/federated/{failing => }/LoopDistributedDouble.lf (100%) rename test/TypeScript/src/federated/{failing => }/PingPongDistributed.lf (100%) rename test/TypeScript/src/federated/{failing => }/PingPongDistributedPhysical.lf (100%) rename test/TypeScript/src/federated/{failing => }/SimpleFederated.lf (100%) rename test/TypeScript/src/federated/{failing => }/SpuriousDependency.lf (100%) diff --git a/test/TypeScript/src/federated/failing/LoopDistributedCentralized.lf b/test/TypeScript/src/federated/LoopDistributedCentralized.lf similarity index 100% rename from test/TypeScript/src/federated/failing/LoopDistributedCentralized.lf rename to test/TypeScript/src/federated/LoopDistributedCentralized.lf diff --git a/test/TypeScript/src/federated/failing/LoopDistributedDouble.lf b/test/TypeScript/src/federated/LoopDistributedDouble.lf similarity index 100% rename from test/TypeScript/src/federated/failing/LoopDistributedDouble.lf rename to test/TypeScript/src/federated/LoopDistributedDouble.lf diff --git a/test/TypeScript/src/federated/failing/PingPongDistributed.lf b/test/TypeScript/src/federated/PingPongDistributed.lf similarity index 100% rename from test/TypeScript/src/federated/failing/PingPongDistributed.lf rename to test/TypeScript/src/federated/PingPongDistributed.lf diff --git a/test/TypeScript/src/federated/failing/PingPongDistributedPhysical.lf b/test/TypeScript/src/federated/PingPongDistributedPhysical.lf similarity index 100% rename from test/TypeScript/src/federated/failing/PingPongDistributedPhysical.lf rename to test/TypeScript/src/federated/PingPongDistributedPhysical.lf diff --git a/test/TypeScript/src/federated/failing/SimpleFederated.lf b/test/TypeScript/src/federated/SimpleFederated.lf similarity index 100% rename from test/TypeScript/src/federated/failing/SimpleFederated.lf rename to test/TypeScript/src/federated/SimpleFederated.lf diff --git a/test/TypeScript/src/federated/failing/SpuriousDependency.lf b/test/TypeScript/src/federated/SpuriousDependency.lf similarity index 100% rename from test/TypeScript/src/federated/failing/SpuriousDependency.lf rename to test/TypeScript/src/federated/SpuriousDependency.lf From 9e18ee58bf1dcbac83037b9bc83cc1b4b32a7ef1 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Wed, 2 Aug 2023 23:59:27 +0200 Subject: [PATCH 380/516] Look for Pico SDK in package root, not output path --- .../src/main/java/org/lflang/generator/c/CCmakeGenerator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 381dadcce5..790e00e9f8 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -153,7 +153,7 @@ CodeBuilder generateCMakeCode( case RP2040: // Attempt to set PICO_SDK_PATH if it is not already set. if (System.getenv("PICO_SDK_PATH") == null) { - Path picoSDKPath = fileConfig.getOutPath().resolve("pico-sdk"); + Path picoSDKPath = fileConfig.srcPkgPath.resolve("pico-sdk"); if (Files.isDirectory(picoSDKPath)) { messageReporter .nowhere() @@ -162,7 +162,7 @@ CodeBuilder generateCMakeCode( + picoSDKPath.toString() + ". You can override this by setting PICO_SDK_PATH."); cMakeCode.pr("# Define the root of the pico-sdk library."); - cMakeCode.pr("set(PICO_SDK_PATH " + fileConfig.getOutPath() + "/pico-sdk)"); + cMakeCode.pr("set(PICO_SDK_PATH " + picoSDKPath + ")"); } else { messageReporter .nowhere() From c813b2472f15a4806313ff4dfb39c909be869075 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Thu, 3 Aug 2023 15:33:29 +0200 Subject: [PATCH 381/516] Do checks in validator. This could be done more elegantly but requires refactoring. --- .../main/java/org/lflang/TargetProperty.java | 35 ++++++---- .../lflang/generator/c/CCmakeGenerator.java | 10 ++- .../org/lflang/validation/LFValidator.java | 64 +++++++++++++------ 3 files changed, 71 insertions(+), 38 deletions(-) diff --git a/core/src/main/java/org/lflang/TargetProperty.java b/core/src/main/java/org/lflang/TargetProperty.java index e9664e98e8..7df857d02f 100644 --- a/core/src/main/java/org/lflang/TargetProperty.java +++ b/core/src/main/java/org/lflang/TargetProperty.java @@ -512,6 +512,10 @@ public enum TargetProperty { } } } + // If the platform does not support threading, disable it. + if (!config.platformOptions.platform.isMultiThreaded()) { + config.threading = false; + } }), /** Directive to instruct the runtime to collect and print execution statistics. */ @@ -1481,8 +1485,8 @@ public interface TargetPropertyType { * @param v A reference to the validator to report errors to. */ public static void produceError(String name, String description, LFValidator v) { - v.getTargetPropertyErrors() - .add("Target property '" + name + "' is required to be " + description + "."); + + v.reportTargetPropertyError("Target property '" + name + "' is required to be " + description + "."); } } @@ -1742,21 +1746,22 @@ public String toString() { public enum Platform { AUTO, ARDUINO, - NRF52("Nrf52"), - RP2040("Rp2040"), - LINUX("Linux"), - MAC("Darwin"), - ZEPHYR("Zephyr"), - WINDOWS("Windows"); + NRF52("Nrf52", true), + RP2040("Rp2040", false), + LINUX("Linux", true), + MAC("Darwin", true), + ZEPHYR("Zephyr", true), + WINDOWS("Windows", true); String cMakeName; - Platform() { - this.cMakeName = this.toString(); - } + private boolean multiThreaded = true; - Platform(String cMakeName) { - this.cMakeName = cMakeName; + Platform() { this.cMakeName = this.toString(); } + + Platform(String cMakeName, boolean isMultiThreaded) { + this.cMakeName = cMakeName; + this.multiThreaded = isMultiThreaded; } /** Return the name in lower case. */ @@ -1769,6 +1774,10 @@ public String toString() { public String getcMakeName() { return this.cMakeName; } + + public boolean isMultiThreaded() { + return this.multiThreaded; + } } /** diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 790e00e9f8..810fde6350 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -326,16 +326,14 @@ CodeBuilder generateCMakeCode( cMakeCode.newLine(); } - if (targetConfig.threading || targetConfig.tracing != null) { - if (targetConfig.platformOptions.platform != Platform.RP2040) { - // If threaded computation is requested, add the threads option. + if (targetConfig.threading) { + // If threaded computation is requested, add the threads option. cMakeCode.pr("# Find threads and link to it"); cMakeCode.pr("find_package(Threads REQUIRED)"); cMakeCode.pr("target_link_libraries(${LF_MAIN_TARGET} PRIVATE Threads::Threads)"); cMakeCode.newLine(); - } - // If the LF program itself is threaded or if tracing is enabled, we need to define - // NUMBER_OF_WORKERS so that platform-specific C files will contain the appropriate functions + // If the LF program itself is threaded, we need to define NUMBER_OF_WORKERS so that + // platform-specific C files will contain the appropriate functions cMakeCode.pr("# Set the number of workers to enable threading/tracing"); cMakeCode.pr( "target_compile_definitions(${LF_MAIN_TARGET} PUBLIC NUMBER_OF_WORKERS=" diff --git a/core/src/main/java/org/lflang/validation/LFValidator.java b/core/src/main/java/org/lflang/validation/LFValidator.java index 0a4062df84..487082c85c 100644 --- a/core/src/main/java/org/lflang/validation/LFValidator.java +++ b/core/src/main/java/org/lflang/validation/LFValidator.java @@ -60,6 +60,8 @@ import org.lflang.ModelInfo; import org.lflang.Target; import org.lflang.TargetProperty; +import org.lflang.TargetProperty.Platform; +import org.lflang.TargetProperty.TargetPropertyType; import org.lflang.TimeValue; import org.lflang.ast.ASTUtils; import org.lflang.federated.serialization.SupportedSerializers; @@ -119,7 +121,7 @@ /** * Custom validation checks for Lingua Franca programs. * - *

Also see: https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#validation + *

Also see: ... * * @author Edward A. Lee * @author Marten Lohstroh @@ -130,19 +132,11 @@ */ 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. + // The methods annotated with @Check are automatically invoked on AST nodes matching the types of + // their arguments. CheckType.FAST ensures that these checks run whenever a file is modified; + // when CheckType.NORMAL is used, the check is run upon saving. + // NOTE: please list methods in alphabetical order, and follow a naming convention checkClass, + // where Class is the AST class. @Check(CheckType.FAST) public void checkAction(Action action) { @@ -565,10 +559,12 @@ public void checkKeyValuePair(KeyValuePair param) { Literals.KEY_VALUE_PAIR__NAME); } - // Report problem with the assigned value. + // Run checks on the property. After running the check, errors/warnings + // are retrievable from the targetPropertyErrors collection. prop.type.check(param.getValue(), param.getName(), this); } + // Retrieve the errors that resulted from the check. for (String it : targetPropertyErrors) { error(it, Literals.KEY_VALUE_PAIR__VALUE); } @@ -1117,13 +1113,14 @@ public void checkTargetDecl(TargetDecl target) throws IOException { * * @param targetProperties The target properties defined in the current Lingua Franca program. */ - @Check(CheckType.EXPENSIVE) + @Check(CheckType.NORMAL) public void checkTargetProperties(KeyValuePairs targetProperties) { validateFastTargetProperty(targetProperties); validateClockSyncTargetProperties(targetProperties); validateSchedulerTargetProperties(targetProperties); validateRos2TargetProperties(targetProperties); validateKeepalive(targetProperties); + validateThreading(targetProperties); } private KeyValuePair getKeyValuePair(KeyValuePairs targetProperties, TargetProperty property) { @@ -1232,6 +1229,35 @@ private void validateKeepalive(KeyValuePairs targetProperties) { } } + private void validateThreading(KeyValuePairs targetProperties) { + var threadingP = getKeyValuePair(targetProperties, TargetProperty.THREADING); + var tracingP = getKeyValuePair(targetProperties, TargetProperty.TRACING); + var platformP = getKeyValuePair(targetProperties, TargetProperty.PLATFORM); + if (threadingP != null) { + if (tracingP != null) { + if (!ASTUtils.toBoolean(threadingP.getValue()) && !tracingP.getValue().toString().equalsIgnoreCase("false")) { + error("Cannot disable treading support because tracing is enabled", threadingP, Literals.KEY_VALUE_PAIR__NAME); + error("Cannot enable tracing because threading support is disabled", tracingP, Literals.KEY_VALUE_PAIR__NAME); + } + } + if (platformP != null && ASTUtils.toBoolean(threadingP.getValue())) { + var lit = ASTUtils.elementToSingleString(platformP.getValue()); + var dic = platformP.getValue().getKeyvalue(); + if (lit != null && lit.equalsIgnoreCase(Platform.RP2040.toString())) { + error("Platform " + Platform.RP2040 + " does not support threading", platformP, Literals.KEY_VALUE_PAIR__VALUE); + } + if (dic != null) { + var rp = dic.getPairs().stream().filter( + kv -> kv.getName().equalsIgnoreCase("name") && + ASTUtils.elementToSingleString(kv.getValue()).equalsIgnoreCase(Platform.RP2040.toString())).findFirst(); + if (rp.isPresent()) { + error("Platform " + Platform.RP2040 + " does not support threading", rp.get(), Literals.KEY_VALUE_PAIR__VALUE); + } + } + } + } + } + private void validateRos2TargetProperties(KeyValuePairs targetProperties) { KeyValuePair ros2 = getKeyValuePair(targetProperties, TargetProperty.ROS2); KeyValuePair ros2Dependencies = @@ -1656,9 +1682,9 @@ public ValidationMessageAcceptor getMessageAcceptor() { return messageAcceptor == null ? this : messageAcceptor; } - /** Return a list of error messages for the target declaration. */ - public List getTargetPropertyErrors() { - return this.targetPropertyErrors; + /** Report an error on the value of a target property */ + public void reportTargetPropertyError(String message) { + this.targetPropertyErrors.add(message); } ////////////////////////////////////////////////////////////// From b018184e6ab300ea482d2995e7599ec868e9698e Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Thu, 3 Aug 2023 15:40:06 +0200 Subject: [PATCH 382/516] Apply formatter --- .../main/java/org/lflang/TargetProperty.java | 15 +++++--- .../lflang/generator/c/CCmakeGenerator.java | 8 ++-- .../org/lflang/validation/LFValidator.java | 38 ++++++++++++++----- 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/org/lflang/TargetProperty.java b/core/src/main/java/org/lflang/TargetProperty.java index 7df857d02f..d494e1d760 100644 --- a/core/src/main/java/org/lflang/TargetProperty.java +++ b/core/src/main/java/org/lflang/TargetProperty.java @@ -514,7 +514,7 @@ public enum TargetProperty { } // If the platform does not support threading, disable it. if (!config.platformOptions.platform.isMultiThreaded()) { - config.threading = false; + config.threading = false; } }), @@ -1486,7 +1486,8 @@ public interface TargetPropertyType { */ public static void produceError(String name, String description, LFValidator v) { - v.reportTargetPropertyError("Target property '" + name + "' is required to be " + description + "."); + v.reportTargetPropertyError( + "Target property '" + name + "' is required to be " + description + "."); } } @@ -1757,11 +1758,13 @@ public enum Platform { private boolean multiThreaded = true; - Platform() { this.cMakeName = this.toString(); } + Platform() { + this.cMakeName = this.toString(); + } Platform(String cMakeName, boolean isMultiThreaded) { - this.cMakeName = cMakeName; - this.multiThreaded = isMultiThreaded; + this.cMakeName = cMakeName; + this.multiThreaded = isMultiThreaded; } /** Return the name in lower case. */ @@ -1776,7 +1779,7 @@ public String getcMakeName() { } public boolean isMultiThreaded() { - return this.multiThreaded; + return this.multiThreaded; } } diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 810fde6350..0ee24fc39a 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -328,10 +328,10 @@ CodeBuilder generateCMakeCode( if (targetConfig.threading) { // If threaded computation is requested, add the threads option. - cMakeCode.pr("# Find threads and link to it"); - cMakeCode.pr("find_package(Threads REQUIRED)"); - cMakeCode.pr("target_link_libraries(${LF_MAIN_TARGET} PRIVATE Threads::Threads)"); - cMakeCode.newLine(); + cMakeCode.pr("# Find threads and link to it"); + cMakeCode.pr("find_package(Threads REQUIRED)"); + cMakeCode.pr("target_link_libraries(${LF_MAIN_TARGET} PRIVATE Threads::Threads)"); + cMakeCode.newLine(); // If the LF program itself is threaded, we need to define NUMBER_OF_WORKERS so that // platform-specific C files will contain the appropriate functions cMakeCode.pr("# Set the number of workers to enable threading/tracing"); diff --git a/core/src/main/java/org/lflang/validation/LFValidator.java b/core/src/main/java/org/lflang/validation/LFValidator.java index 487082c85c..7dcf00d107 100644 --- a/core/src/main/java/org/lflang/validation/LFValidator.java +++ b/core/src/main/java/org/lflang/validation/LFValidator.java @@ -61,7 +61,6 @@ import org.lflang.Target; import org.lflang.TargetProperty; import org.lflang.TargetProperty.Platform; -import org.lflang.TargetProperty.TargetPropertyType; import org.lflang.TimeValue; import org.lflang.ast.ASTUtils; import org.lflang.federated.serialization.SupportedSerializers; @@ -121,7 +120,8 @@ /** * Custom validation checks for Lingua Franca programs. * - *

Also see: ... + *

Also see: ... * * @author Edward A. Lee * @author Marten Lohstroh @@ -1235,23 +1235,41 @@ private void validateThreading(KeyValuePairs targetProperties) { var platformP = getKeyValuePair(targetProperties, TargetProperty.PLATFORM); if (threadingP != null) { if (tracingP != null) { - if (!ASTUtils.toBoolean(threadingP.getValue()) && !tracingP.getValue().toString().equalsIgnoreCase("false")) { - error("Cannot disable treading support because tracing is enabled", threadingP, Literals.KEY_VALUE_PAIR__NAME); - error("Cannot enable tracing because threading support is disabled", tracingP, Literals.KEY_VALUE_PAIR__NAME); + if (!ASTUtils.toBoolean(threadingP.getValue()) + && !tracingP.getValue().toString().equalsIgnoreCase("false")) { + error( + "Cannot disable treading support because tracing is enabled", + threadingP, + Literals.KEY_VALUE_PAIR__NAME); + error( + "Cannot enable tracing because threading support is disabled", + tracingP, + Literals.KEY_VALUE_PAIR__NAME); } } if (platformP != null && ASTUtils.toBoolean(threadingP.getValue())) { var lit = ASTUtils.elementToSingleString(platformP.getValue()); var dic = platformP.getValue().getKeyvalue(); if (lit != null && lit.equalsIgnoreCase(Platform.RP2040.toString())) { - error("Platform " + Platform.RP2040 + " does not support threading", platformP, Literals.KEY_VALUE_PAIR__VALUE); + error( + "Platform " + Platform.RP2040 + " does not support threading", + platformP, + Literals.KEY_VALUE_PAIR__VALUE); } if (dic != null) { - var rp = dic.getPairs().stream().filter( - kv -> kv.getName().equalsIgnoreCase("name") && - ASTUtils.elementToSingleString(kv.getValue()).equalsIgnoreCase(Platform.RP2040.toString())).findFirst(); + var rp = + dic.getPairs().stream() + .filter( + kv -> + kv.getName().equalsIgnoreCase("name") + && ASTUtils.elementToSingleString(kv.getValue()) + .equalsIgnoreCase(Platform.RP2040.toString())) + .findFirst(); if (rp.isPresent()) { - error("Platform " + Platform.RP2040 + " does not support threading", rp.get(), Literals.KEY_VALUE_PAIR__VALUE); + error( + "Platform " + Platform.RP2040 + " does not support threading", + rp.get(), + Literals.KEY_VALUE_PAIR__VALUE); } } } From 71c4fe2a802b9d90f1933908cd70fd54635c8c22 Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Fri, 4 Aug 2023 10:56:39 +0900 Subject: [PATCH 383/516] Apply spotless --- .../DistributedCountPhysicalAfterDelay.lf | 34 +++++------ .../federated/LoopDistributedCentralized.lf | 48 ++++++++-------- .../src/federated/LoopDistributedDouble.lf | 56 +++++++++---------- .../src/federated/PingPongDistributed.lf | 42 +++++++------- .../federated/PingPongDistributedPhysical.lf | 38 ++++++------- .../src/federated/SpuriousDependency.lf | 9 +-- 6 files changed, 110 insertions(+), 117 deletions(-) rename test/TypeScript/src/federated/{failing => }/DistributedCountPhysicalAfterDelay.lf (50%) diff --git a/test/TypeScript/src/federated/failing/DistributedCountPhysicalAfterDelay.lf b/test/TypeScript/src/federated/DistributedCountPhysicalAfterDelay.lf similarity index 50% rename from test/TypeScript/src/federated/failing/DistributedCountPhysicalAfterDelay.lf rename to test/TypeScript/src/federated/DistributedCountPhysicalAfterDelay.lf index 0ea0befda7..c6ab1190de 100644 --- a/test/TypeScript/src/federated/failing/DistributedCountPhysicalAfterDelay.lf +++ b/test/TypeScript/src/federated/DistributedCountPhysicalAfterDelay.lf @@ -19,30 +19,30 @@ reactor Print { 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(); + 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."); - } + 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) + c = new Count(offset = 200 msec, period=0) p = new Print() c.out ~> p.inp after 400 msec // Indicating a 'physical' connection with a 400 msec after delay. } diff --git a/test/TypeScript/src/federated/LoopDistributedCentralized.lf b/test/TypeScript/src/federated/LoopDistributedCentralized.lf index aebc87d619..15dafae904 100644 --- a/test/TypeScript/src/federated/LoopDistributedCentralized.lf +++ b/test/TypeScript/src/federated/LoopDistributedCentralized.lf @@ -15,50 +15,50 @@ reactor Looper(incr: number = 1, delay: time = 0 msec) { state count: number = 0 preamble {= - let stop = false; - // Function to trigger an action once every second. - function ping(act: any) { - if (!stop) { - console.log("Scheduling action."); - act.schedule(0, null); - setTimeout(ping, 1000, act); + let stop = false; + // Function to trigger an action once every second. + function ping(act: any) { + if (!stop) { + console.log("Scheduling action."); + act.schedule(0, null); + setTimeout(ping, 1000, act); + } } - } =} reaction(startup) -> a {= - // Start the ping function for triggering an action every second. - console.log("Starting ping function."); - ping(actions.a); + // Start the ping function for triggering an action every second. + console.log("Starting ping function."); + ping(actions.a); =} reaction(a) -> out {= - out = count; - count += incr; + out = count; + count += incr; =} reaction(inp) {= - let logical = util.getCurrentLogicalTime(); - let physical = util.getCurrentPhysicalTime(); + let logical = util.getCurrentLogicalTime(); + let physical = util.getCurrentPhysicalTime(); - let time_lag = physical.subtract(logical); + let time_lag = physical.subtract(logical); - console.log("Received " + inp + ". Logical time is behind physical time by " + time_lag + "."); + console.log("Received " + inp + ". Logical time is behind physical time by " + time_lag + "."); =} reaction(shutdown) {= - console.log("******* Shutdown invoked."); - // Stop the ping function that is scheduling actions. - stop = true; - if (count != 5 * incr) { - util.requestErrorStop("Failed to receive all five expected inputs."); - } + console.log("******* Shutdown invoked."); + // Stop the ping function that is scheduling actions. + stop = true; + if (count != 5 * incr) { + util.requestErrorStop("Failed to receive all five expected inputs."); + } =} } federated reactor LoopDistributedCentralized(delay: time = 0) { left = new Looper() - right = new Looper(incr = -1) + right = new Looper(incr=-1) left.out -> right.inp right.out -> left.inp } diff --git a/test/TypeScript/src/federated/LoopDistributedDouble.lf b/test/TypeScript/src/federated/LoopDistributedDouble.lf index 081c070048..f6b5b39d50 100644 --- a/test/TypeScript/src/federated/LoopDistributedDouble.lf +++ b/test/TypeScript/src/federated/LoopDistributedDouble.lf @@ -8,7 +8,7 @@ target TypeScript { timeout: 5 sec, keepAlive: true, coordination-options: { - advance-message-interval: 100 msec + advance-message-interval: 100 msec } } @@ -22,57 +22,57 @@ reactor Looper(incr: number = 1, delay: time = 0 msec) { timer t(0, 1 sec) preamble {= - let stop = false; - // Function to trigger an action once every second. - function ping(act: any) { - if (!stop) { - console.log("Scheduling action."); - act.schedule(0, null); - setTimeout(ping, 1000, act); + let stop = false; + // Function to trigger an action once every second. + function ping(act: any) { + if (!stop) { + console.log("Scheduling action."); + act.schedule(0, null); + setTimeout(ping, 1000, act); + } } - } =} reaction(startup) -> a {= - // Start the ping function for triggering an action every second. - console.log("Starting ping function."); - ping(actions.a); + // Start the ping function for triggering an action every second. + console.log("Starting ping function."); + ping(actions.a); =} reaction(a) -> out, out2 {= - if (count % 2 == 0) { - out = count; - } else { - out2 = count; - } - count += incr; + if (count % 2 == 0) { + out = count; + } else { + out2 = count; + } + count += incr; =} reaction(inp) {= - console.log("Received " + inp + " on inp at logical time " + util.getElapsedLogicalTime() + "."); + console.log("Received " + inp + " on inp at logical time " + util.getElapsedLogicalTime() + "."); =} reaction(inp2) {= - console.log("Received " + inp2 + " on inp2 at logical time " + util.getElapsedLogicalTime() + "."); + console.log("Received " + inp2 + " on inp2 at logical time " + util.getElapsedLogicalTime() + "."); =} reaction(t) {= - console.log("Timer triggered at logical time " + util.getElapsedLogicalTime() + "."); + console.log("Timer triggered at logical time " + util.getElapsedLogicalTime() + "."); =} reaction(shutdown) {= - console.log("******* Shutdown invoked."); - // Stop the ping function that is scheduling actions. - stop = true; - if (count != 5 * incr) { - util.requestErrorStop("Failed to receive all five expected inputs."); - } + console.log("******* Shutdown invoked."); + // Stop the ping function that is scheduling actions. + stop = true; + if (count != 5 * incr) { + util.requestErrorStop("Failed to receive all five expected inputs."); + } =} } federated reactor(delay: time = 0) { left = new Looper() - right = new Looper(incr = -1) + right = new Looper(incr=-1) left.out -> right.inp right.out -> left.inp right.out2 -> left.inp2 diff --git a/test/TypeScript/src/federated/PingPongDistributed.lf b/test/TypeScript/src/federated/PingPongDistributed.lf index 42cceae73c..e189bd44e0 100644 --- a/test/TypeScript/src/federated/PingPongDistributed.lf +++ b/test/TypeScript/src/federated/PingPongDistributed.lf @@ -8,18 +8,18 @@ reactor Ping(count: number = 10) { logical action serve reaction(startup, serve) -> send {= - console.log(`At logical time ${util.getElapsedLogicalTime()}, Ping sending ${pingsLeft}`); - send = pingsLeft; - pingsLeft = pingsLeft - 1; + console.log(`At logical time ${util.getElapsedLogicalTime()}, Ping sending ${pingsLeft}`); + send = pingsLeft; + pingsLeft = pingsLeft - 1; =} reaction(receive) -> serve {= - if (pingsLeft > 0){ - actions.serve.schedule(0, null); - } - else{ - util.requestStop(); - } + if (pingsLeft > 0){ + actions.serve.schedule(0, null); + } + else{ + util.requestStop(); + } =} } @@ -29,25 +29,25 @@ reactor Pong(expected: number = 10) { state count: number = 0 reaction(receive) -> send {= - count += 1; - console.log(`At logical time ${util.getElapsedLogicalTime()}, Pong received ${receive}`) - send = receive; - if (count == expected){ - util.requestStop(); - } + count += 1; + console.log(`At logical time ${util.getElapsedLogicalTime()}, Pong received ${receive}`) + send = receive; + if (count == expected){ + util.requestStop(); + } =} reaction(shutdown) {= - if (count != expected){ - util.requestErrorStop(`Pong expected to receive ${expected} inputs, but it received ${count}`); - } - console.log(`Pong received ${count} pings.`); + if (count != expected){ + util.requestErrorStop(`Pong expected to receive ${expected} inputs, but it received ${count}`); + } + console.log(`Pong received ${count} pings.`); =} } federated reactor PingPongDistributed(count: number = 10) { - ping = new Ping(count = count) - pong = new Pong(expected = count) + ping = new Ping(count=count) + pong = new Pong(expected=count) ping.send -> pong.receive pong.send -> ping.receive } diff --git a/test/TypeScript/src/federated/PingPongDistributedPhysical.lf b/test/TypeScript/src/federated/PingPongDistributedPhysical.lf index 298a7298d4..ef773e2446 100644 --- a/test/TypeScript/src/federated/PingPongDistributedPhysical.lf +++ b/test/TypeScript/src/federated/PingPongDistributedPhysical.lf @@ -32,16 +32,16 @@ reactor Ping(count: number = 10) { logical action serve reaction(startup, serve) -> send {= - console.log(`At logical time ${util.getElapsedLogicalTime()}, Ping sending ${pingsLeft}.`); - send = pingsLeft--; + console.log(`At logical time ${util.getElapsedLogicalTime()}, Ping sending ${pingsLeft}.`); + send = pingsLeft--; =} reaction(receive) -> serve {= - if (pingsLeft > 0) { - actions.serve.schedule(0, null); - } else { - util.requestStop(); - } + if (pingsLeft > 0) { + actions.serve.schedule(0, null); + } else { + util.requestStop(); + } =} } @@ -51,25 +51,25 @@ reactor Pong(expected: number = 10) { state count: number = 0 reaction(receive) -> send {= - count++; - console.log(`At logical time ${util.getElapsedLogicalTime()}, Pong receivedd ${receive}.`); - send = receive; - if (count === expected) { - util.requestStop(); - } + count++; + console.log(`At logical time ${util.getElapsedLogicalTime()}, Pong receivedd ${receive}.`); + send = receive; + if (count === expected) { + util.requestStop(); + } =} reaction(shutdown) {= - if (count !== expected) { - util.requestErrorStop(`Pong expected to received ${expected} inputs, but it received ${count}.`); - } - console.log(`Pong received ${count} pings.`); + if (count !== expected) { + util.requestErrorStop(`Pong expected to received ${expected} inputs, but it received ${count}.`); + } + console.log(`Pong received ${count} pings.`); =} } federated reactor(count: number = 10) { - ping = new Ping(count = count) - pong = new Pong(expected = count) + ping = new Ping(count=count) + pong = new Pong(expected=count) ping.send ~> pong.receive pong.send ~> ping.receive } diff --git a/test/TypeScript/src/federated/SpuriousDependency.lf b/test/TypeScript/src/federated/SpuriousDependency.lf index dc16319a70..39da8f0ea5 100644 --- a/test/TypeScript/src/federated/SpuriousDependency.lf +++ b/test/TypeScript/src/federated/SpuriousDependency.lf @@ -1,8 +1,7 @@ /** * This checks that a federated program does not deadlock when it is ambiguous, given the structure * of a federate, whether it is permissible to require certain network sender/receiver reactions to - * precede others inp the execution of a given tag. The version of LFC that was inp the master branch - * on 4/15/2023 resolved the ambiguity inp a way that does appear to result inp deadlock. + * precede others in the execution of a given tag. */ target TypeScript { timeout: 1 sec @@ -13,9 +12,7 @@ reactor Passthrough(id: number = 0) { output out: number reaction(inp) -> out {= - //lf_print("Hello from passthrough %d", self->id); console.log("Hello from passthrough " + id); - //lf_set(out, inp->value); out = inp; =} } @@ -41,11 +38,7 @@ reactor Check { reaction(inp) {= console.log("count is now " + ++count); =} reaction(shutdown) {= - // lf_print("******* Shutdown invoked."); console.log("******* Shutdown invoked."); - // if (self->count != 1) { - // lf_print_error_and_exit("Failed to receive expected input."); - // } if (count != 1) { util.reportError("Failed to receieve expected input."); } From 021166557349e38776a91bcfc53a57d7d961b361 Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Fri, 4 Aug 2023 11:19:24 +0900 Subject: [PATCH 384/516] Increment the port ID to match messages properly --- .../kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt | 2 +- .../src/federated/DistributedCountPhysicalAfterDelay.lf | 5 +---- test/TypeScript/src/federated/LoopDistributedDouble.lf | 1 - 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt b/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt index 2911503159..3e01cdccbd 100644 --- a/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/ts/TSInstanceGenerator.kt @@ -78,7 +78,7 @@ class TSInstanceGenerator( // Assume that network receiver reactors are sorted by portID childReactorInstantiations.add( "this.registerNetworkReceiver(\n" - + "\t${portID},\n" + + "\t${portID++},\n" + "\tthis.${childReactor.name} as __NetworkReceiver\n)") } if (isNetworkSender) { diff --git a/test/TypeScript/src/federated/DistributedCountPhysicalAfterDelay.lf b/test/TypeScript/src/federated/DistributedCountPhysicalAfterDelay.lf index c6ab1190de..107cd847cb 100644 --- a/test/TypeScript/src/federated/DistributedCountPhysicalAfterDelay.lf +++ b/test/TypeScript/src/federated/DistributedCountPhysicalAfterDelay.lf @@ -7,10 +7,7 @@ * @author Soroush Bateni * @author Byeong-gil Jun */ -target TypeScript { - logging: debug, - keepalive: true -} +target TypeScript import Count from "../lib/Count.lf" diff --git a/test/TypeScript/src/federated/LoopDistributedDouble.lf b/test/TypeScript/src/federated/LoopDistributedDouble.lf index f6b5b39d50..0facc3c6dc 100644 --- a/test/TypeScript/src/federated/LoopDistributedDouble.lf +++ b/test/TypeScript/src/federated/LoopDistributedDouble.lf @@ -6,7 +6,6 @@ */ target TypeScript { timeout: 5 sec, - keepAlive: true, coordination-options: { advance-message-interval: 100 msec } From e5ed19d75d5b1ff538e0583058472c338450d4d9 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 4 Aug 2023 11:11:24 -0700 Subject: [PATCH 385/516] Fix IllegalArgumentException in diagram synthesis. This is a runtime type error -- this is my bad, as such things should not happen in well written Java code. This commit modifies FormattingUtil to make it slightly more type-safe. It also changes the diagram synthesis to bypass the regular formatting logic -- the formatting logic seems more heavyweight than necessary since in diagrams we should not usually have to worry about how to represent comments and where to break lines (comment representation is the reason why the target was needed by the FormattingUtil.render function). --- cli/lff/src/main/java/org/lflang/cli/Lff.java | 3 ++- core/src/main/java/org/lflang/ast/FormattingUtil.java | 4 ++-- .../org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java | 6 +++--- core/src/main/java/org/lflang/formatting2/LFFormatter.java | 3 ++- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/cli/lff/src/main/java/org/lflang/cli/Lff.java b/cli/lff/src/main/java/org/lflang/cli/Lff.java index fa534b15d0..cabf5f1515 100644 --- a/cli/lff/src/main/java/org/lflang/cli/Lff.java +++ b/cli/lff/src/main/java/org/lflang/cli/Lff.java @@ -12,6 +12,7 @@ import org.lflang.ast.FormattingUtil; import org.lflang.ast.IsEqual; import org.lflang.ast.LfParsingHelper; +import org.lflang.lf.Model; import org.lflang.util.FileUtil; import picocli.CommandLine.Command; import picocli.CommandLine.Option; @@ -175,7 +176,7 @@ private void formatSingleFile(Path path, Path inputRoot, Path outputRoot) { } final String formattedFileContents = - FormattingUtil.render(resource.getContents().get(0), lineLength); + FormattingUtil.render((Model) resource.getContents().get(0), lineLength); if (!new IsEqual(resource.getContents().get(0)) .doSwitch( new LfParsingHelper() diff --git a/core/src/main/java/org/lflang/ast/FormattingUtil.java b/core/src/main/java/org/lflang/ast/FormattingUtil.java index 27b19dabb2..19754b5f10 100644 --- a/core/src/main/java/org/lflang/ast/FormattingUtil.java +++ b/core/src/main/java/org/lflang/ast/FormattingUtil.java @@ -42,7 +42,7 @@ public class FormattingUtil { static final long BADNESS_PER_NEWLINE = 1; /** Return a String representation of {@code object}, with lines wrapped at {@code lineLength}. */ - public static String render(EObject object, int lineLength) { + public static String render(Model object, int lineLength) { return render(object, lineLength, inferTarget(object), false); } @@ -89,7 +89,7 @@ private static Target inferTarget(EObject object) { } /** Return a String representation of {@code object} using a reasonable default line length. */ - public static String render(EObject object) { + public static String render(Model object) { return render(object, DEFAULT_LINE_LENGTH); } diff --git a/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java b/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java index 272b483607..7aaa6482b9 100644 --- a/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java +++ b/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java @@ -98,7 +98,7 @@ import org.lflang.AttributeUtils; import org.lflang.InferredType; import org.lflang.ast.ASTUtils; -import org.lflang.ast.FormattingUtil; +import org.lflang.ast.ToLf; import org.lflang.diagram.synthesis.action.CollapseAllReactorsAction; import org.lflang.diagram.synthesis.action.ExpandAllReactorsAction; import org.lflang.diagram.synthesis.action.FilterCycleAction; @@ -1481,7 +1481,7 @@ private String createParameterLabel(ParameterInstance param) { if (param.getOverride() != null) { b.append(" = "); var init = param.getActualValue(); - b.append(FormattingUtil.render(init)); + b.append(ToLf.instance.doSwitch(init)); } return b.toString(); } @@ -1512,7 +1512,7 @@ private String createStateVariableLabel(StateVar variable) { b.append(":").append(t.toOriginalText()); } if (variable.getInit() != null) { - b.append(FormattingUtil.render(variable.getInit())); + b.append(ToLf.instance.doSwitch(variable.getInit())); } return b.toString(); } diff --git a/core/src/main/java/org/lflang/formatting2/LFFormatter.java b/core/src/main/java/org/lflang/formatting2/LFFormatter.java index e2ea020af1..fb95140fa1 100644 --- a/core/src/main/java/org/lflang/formatting2/LFFormatter.java +++ b/core/src/main/java/org/lflang/formatting2/LFFormatter.java @@ -17,6 +17,7 @@ import org.eclipse.xtext.validation.CheckMode; import org.eclipse.xtext.validation.IResourceValidator; import org.lflang.ast.FormattingUtil; +import org.lflang.lf.Model; public class LFFormatter implements IFormatter2 { @@ -40,6 +41,6 @@ public List format(FormatterRequest request) { request.getTextRegionAccess(), documentRegion.getOffset(), documentRegion.getLength(), - FormattingUtil.render(documentContents.get(0)))); + FormattingUtil.render((Model) documentContents.get(0)))); } } From 736abe7cf79d1142c815cca9f7e70e2664b3ef69 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Tue, 8 Aug 2023 18:08:15 +0200 Subject: [PATCH 386/516] support named reactions --- core/src/main/kotlin/org/lflang/generator/cpp/CppExtensions.kt | 2 +- .../kotlin/org/lflang/generator/cpp/CppReactionGenerator.kt | 3 +-- .../kotlin/org/lflang/generator/cpp/CppReactorGenerator.kt | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppExtensions.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppExtensions.kt index 9c37bcd8ab..e9e3f9fd8a 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppExtensions.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppExtensions.kt @@ -47,7 +47,7 @@ import org.lflang.lf.WidthSpec /** Get the "name" a reaction is represented with in target code.*/ val Reaction.codeName - get(): String = "r$indexInContainer" + get(): String = name ?: "r$indexInContainer" /* ********************************************************************************************** * C++ specific extensions shared across classes diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppReactionGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppReactionGenerator.kt index 7d4dbdb13f..1b894378f3 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppReactionGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppReactionGenerator.kt @@ -45,8 +45,7 @@ import org.lflang.toText /** A C++ code generator for reactions and their function bodies */ class CppReactionGenerator( private val reactor: Reactor, - private val portGenerator: CppPortGenerator, - private val instanceGenerator: CppInstanceGenerator + private val portGenerator: CppPortGenerator ) { private val reactionsWithDeadlines = reactor.reactions.filter { it.deadline != null } diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppReactorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppReactorGenerator.kt index af65a4a790..a7d8f0e4ae 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppReactorGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppReactorGenerator.kt @@ -54,7 +54,7 @@ class CppReactorGenerator(private val reactor: Reactor, fileConfig: CppFileConfi private val timers = CppTimerGenerator(reactor) private val actions = CppActionGenerator(reactor, messageReporter) private val ports = CppPortGenerator(reactor) - private val reactions = CppReactionGenerator(reactor, ports, instances) + private val reactions = CppReactionGenerator(reactor, ports) private val assemble = CppAssembleMethodGenerator(reactor) private fun publicPreamble() = From 250e3abe330ae69131ece423dbb24363b5f7b73f Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Tue, 8 Aug 2023 18:44:02 +0200 Subject: [PATCH 387/516] enable bodyless reactions in C++ --- core/src/main/java/org/lflang/Target.java | 2 +- .../org/lflang/generator/cpp/CppReactionGenerator.kt | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/lflang/Target.java b/core/src/main/java/org/lflang/Target.java index f95f991875..096788484e 100644 --- a/core/src/main/java/org/lflang/Target.java +++ b/core/src/main/java/org/lflang/Target.java @@ -494,7 +494,7 @@ public boolean supportsParameterizedWidths() { * this target. */ public boolean supportsReactionDeclarations() { - if (this.equals(Target.C)) return true; + if (this.equals(Target.C) || this.equals(Target.CPP)) return true; else return false; } diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppReactionGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppReactionGenerator.kt index 1b894378f3..489dd8c2ae 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppReactionGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppReactionGenerator.kt @@ -148,8 +148,9 @@ class CppReactionGenerator( } } - private fun generateBodyDefinition(reaction: Reaction): String { - return with(PrependOperator) { + private fun generateBodyDefinition(reaction: Reaction): String? { + return if (reaction.code == null) null + else with(PrependOperator) { """ |// reaction ${reaction.label} |${reactor.templateLine} @@ -252,7 +253,7 @@ class CppReactionGenerator( /** Get all definitions of reaction bodies. */ fun generateBodyDefinitions() = - reactor.reactions.joinToString(separator = "\n", postfix = "\n") { generateBodyDefinition(it) } + reactor.reactions.mapNotNull { generateBodyDefinition(it) }.joinToString(separator = "\n", postfix = "\n") /** Get all declarations of deadline handlers. */ fun generateDeadlineHandlerDeclarations(): String = From fac6624f84448fc6021684eab3fa8b08c4585cea Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Tue, 8 Aug 2023 18:44:15 +0200 Subject: [PATCH 388/516] add testcase --- test/Cpp/src/HelloBodylessWorld.lf | 7 +++++++ test/Cpp/src/hello_bodyless_world.cc | 5 +++++ test/Cpp/src/hello_bodyless_world.cmake | 3 +++ 3 files changed, 15 insertions(+) create mode 100644 test/Cpp/src/HelloBodylessWorld.lf create mode 100644 test/Cpp/src/hello_bodyless_world.cc create mode 100644 test/Cpp/src/hello_bodyless_world.cmake diff --git a/test/Cpp/src/HelloBodylessWorld.lf b/test/Cpp/src/HelloBodylessWorld.lf new file mode 100644 index 0000000000..3825e1077f --- /dev/null +++ b/test/Cpp/src/HelloBodylessWorld.lf @@ -0,0 +1,7 @@ +target Cpp { + cmake-include: hello_bodyless_world.cmake +} + +main reactor { + reaction hello(startup) +} diff --git a/test/Cpp/src/hello_bodyless_world.cc b/test/Cpp/src/hello_bodyless_world.cc new file mode 100644 index 0000000000..782aeefc53 --- /dev/null +++ b/test/Cpp/src/hello_bodyless_world.cc @@ -0,0 +1,5 @@ +#include "HelloBodylessWorld/HelloBodylessWorld.hh" + +void HelloBodylessWorld::Inner::hello_body([[maybe_unused]] const reactor::StartupTrigger& startup) { + std::cout << "Hello World." << std::endl; +} diff --git a/test/Cpp/src/hello_bodyless_world.cmake b/test/Cpp/src/hello_bodyless_world.cmake new file mode 100644 index 0000000000..09e0a5cf3f --- /dev/null +++ b/test/Cpp/src/hello_bodyless_world.cmake @@ -0,0 +1,3 @@ +target_sources(${LF_MAIN_TARGET} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/hello_bodyless_world.cc) + + From 6f7e8140f9f55cfd461dc7b743163181488f1439 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Tue, 8 Aug 2023 18:57:06 +0200 Subject: [PATCH 389/516] also base the reaction name (string) on the name in LF code --- core/src/main/kotlin/org/lflang/ast/AstExtensions.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/ast/AstExtensions.kt b/core/src/main/kotlin/org/lflang/ast/AstExtensions.kt index 26678a2273..1f4f99a2c4 100644 --- a/core/src/main/kotlin/org/lflang/ast/AstExtensions.kt +++ b/core/src/main/kotlin/org/lflang/ast/AstExtensions.kt @@ -263,10 +263,11 @@ val Resource.model: Model get() = this.allContents.asSequence().filterIsInstance /** Get a label representing the receiving reaction. * - * If the reaction is annotated with a label, then the label is returned. Otherwise, a reaction name - * is generated based on its priority. + * If the reaction is named, then the name is returned. + * If it is not named but annotated with a label, then the label is returned. + * Otherwise, a reaction name is generated based on its priority. */ -val Reaction.label get(): String = AttributeUtils.getLabel(this) ?: "reaction_$priority" +val Reaction.label get(): String = name ?: AttributeUtils.getLabel(this) ?: "reaction_$priority" /** Get the priority of a receiving reaction */ val Reaction.priority From bf01321831341136e4ae033ef63df7e4abf88d23 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 8 Aug 2023 11:28:22 -0700 Subject: [PATCH 390/516] Update submodule. --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index ccf230f3d1..809c454db0 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit ccf230f3d1027ee39987066b9e1f3211560e9f6e +Subproject commit 809c454db07c1b5e03a789a4863137107b817da6 From c24d4c09a0e98c398b46944c782a7973d5a5d454 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 11 Aug 2023 12:04:19 -0700 Subject: [PATCH 391/516] Record execution times. This is in response to a timeout. I would like to get an idea of the range of normal variability in the execution times of these tests in GH actions. If a test fails with a timeout but finishes quickly when re-run, then that is an indication that the timeout might be a real deadlock. --- .../java/org/lflang/tests/LFTest.java | 18 +++++++++++++++++- .../java/org/lflang/tests/TestBase.java | 16 ++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/core/src/testFixtures/java/org/lflang/tests/LFTest.java b/core/src/testFixtures/java/org/lflang/tests/LFTest.java index 9430f80bd5..ee67dcbb96 100644 --- a/core/src/testFixtures/java/org/lflang/tests/LFTest.java +++ b/core/src/testFixtures/java/org/lflang/tests/LFTest.java @@ -47,6 +47,8 @@ public class LFTest implements Comparable { /** The target of the test program. */ private final Target target; + private long executionTimeNanoseconds; + /** * Create a new test. * @@ -143,7 +145,10 @@ public void reportErrors() { if (this.hasFailed()) { System.out.println( "+---------------------------------------------------------------------------+"); - System.out.println("Failed: " + this); + System.out.println( + "Failed: " + + this + + String.format(" in %.2f seconds\n", getExecutionTimeNanoseconds() / 1.0e9)); System.out.println( "-----------------------------------------------------------------------------"); System.out.println("Reason: " + this.result.message); @@ -295,4 +300,15 @@ public Thread recordStdOut(Process process) { public Thread recordStdErr(Process process) { return execLog.recordStdErr(process); } + + /** Record the execution time of this test in nanoseconds. */ + public void setExecutionTimeNanoseconds(long time) { + assert executionTimeNanoseconds == 0; // it should only be set once + executionTimeNanoseconds = time; + } + + /** Return the execution time of this test in nanoseconds. */ + public long getExecutionTimeNanoseconds() { + return executionTimeNanoseconds; + } } diff --git a/core/src/testFixtures/java/org/lflang/tests/TestBase.java b/core/src/testFixtures/java/org/lflang/tests/TestBase.java index 401c7e93d5..5e345e5e8e 100644 --- a/core/src/testFixtures/java/org/lflang/tests/TestBase.java +++ b/core/src/testFixtures/java/org/lflang/tests/TestBase.java @@ -358,14 +358,20 @@ protected static void printTestHeader(Target target, String description) { * @param tests The tests to inspect the results of. */ private static void checkAndReportFailures(Set tests) { - var passed = tests.stream().filter(it -> it.hasPassed()).collect(Collectors.toList()); + var passed = tests.stream().filter(LFTest::hasPassed).toList(); var s = new StringBuffer(); s.append(THIN_LINE); - s.append("Passing: " + passed.size() + "/" + tests.size() + "\n"); + s.append("Passing: ").append(passed.size()).append("/").append(tests.size()).append("\n"); s.append(THIN_LINE); - passed.forEach(test -> s.append("Passed: ").append(test).append("\n")); + passed.forEach( + test -> + s.append("Passed: ") + .append(test) + .append( + String.format( + " in %.2f seconds\n", test.getExecutionTimeNanoseconds() / 1.0e9))); s.append(THIN_LINE); - System.out.print(s.toString()); + System.out.print(s); for (var test : tests) { test.reportErrors(); @@ -516,7 +522,9 @@ private void execute(LFTest test) throws TestError { stderr.start(); stdout.start(); + long t0 = System.nanoTime(); var timeout = !p.waitFor(MAX_EXECUTION_TIME_SECONDS, TimeUnit.SECONDS); + test.setExecutionTimeNanoseconds(System.nanoTime() - t0); stdout.interrupt(); stderr.interrupt(); if (timeout) { From 9f8d959a29d364d4cdfab8f5f4dd53c430895ada Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 14 Aug 2023 11:52:52 -0700 Subject: [PATCH 392/516] Revert accidental change to the test timeout. --- core/src/testFixtures/java/org/lflang/tests/TestBase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/testFixtures/java/org/lflang/tests/TestBase.java b/core/src/testFixtures/java/org/lflang/tests/TestBase.java index 5e345e5e8e..9fb8b93dc0 100644 --- a/core/src/testFixtures/java/org/lflang/tests/TestBase.java +++ b/core/src/testFixtures/java/org/lflang/tests/TestBase.java @@ -74,7 +74,7 @@ public abstract class TestBase extends LfInjectedTestBase { private static final PrintStream err = System.err; /** Execution timeout enforced for all tests. */ - private static final long MAX_EXECUTION_TIME_SECONDS = 20; + private static final long MAX_EXECUTION_TIME_SECONDS = 300; /** Content separator used in test output, 78 characters wide. */ public static final String THIN_LINE = From 5ad4321114c2878c2a767eb940da8fb642929244 Mon Sep 17 00:00:00 2001 From: Erling Rennemo Jellum Date: Tue, 15 Aug 2023 18:12:53 +0200 Subject: [PATCH 393/516] Remove tracing related utils --- util/tracing/.gitignore | 2 - util/tracing/README.md | 26 -- util/tracing/influxdb.h | 447 -------------------- util/tracing/makefile | 35 -- util/tracing/trace_to_chrome.c | 452 --------------------- util/tracing/trace_to_csv.c | 436 -------------------- util/tracing/trace_to_influxdb.c | 281 ------------- util/tracing/trace_util.c | 295 -------------- util/tracing/trace_util.h | 134 ------ util/tracing/visualization/.gitignore | 1 - util/tracing/visualization/README.md | 35 -- util/tracing/visualization/fedsd.py | 331 --------------- util/tracing/visualization/fedsd_helper.py | 255 ------------ 13 files changed, 2730 deletions(-) delete mode 100644 util/tracing/.gitignore delete mode 100644 util/tracing/README.md delete mode 100644 util/tracing/influxdb.h delete mode 100644 util/tracing/makefile delete mode 100644 util/tracing/trace_to_chrome.c delete mode 100644 util/tracing/trace_to_csv.c delete mode 100644 util/tracing/trace_to_influxdb.c delete mode 100644 util/tracing/trace_util.c delete mode 100644 util/tracing/trace_util.h delete mode 100644 util/tracing/visualization/.gitignore delete mode 100644 util/tracing/visualization/README.md delete mode 100644 util/tracing/visualization/fedsd.py delete mode 100644 util/tracing/visualization/fedsd_helper.py diff --git a/util/tracing/.gitignore b/util/tracing/.gitignore deleted file mode 100644 index 5fe4f5283d..0000000000 --- a/util/tracing/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.o -trace_to_csv diff --git a/util/tracing/README.md b/util/tracing/README.md deleted file mode 100644 index 2e6f0cee4a..0000000000 --- a/util/tracing/README.md +++ /dev/null @@ -1,26 +0,0 @@ -## util/tracing - -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). - -* fedsd: A utility that converts trace files from a federate into sequence diagrams - showing the interactions between federates and the RTI. - -## Installing - -``` - make install -``` -This puts the executables in lingua-franca/bin. diff --git a/util/tracing/influxdb.h b/util/tracing/influxdb.h deleted file mode 100644 index 5d423d6332..0000000000 --- a/util/tracing/influxdb.h +++ /dev/null @@ -1,447 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - Usage: - send_udp/post_http(c, - INFLUX_MEAS("foo"), - INFLUX_TAG("k", "v"), INFLUX_TAG("k2", "v2"), - INFLUX_F_STR("s", "string"), INFLUX_F_FLT("f", 28.39, 2), - - INFLUX_MEAS("bar"), - INFLUX_F_INT("i", 1048576), INFLUX_F_BOL("b", 1), - INFLUX_TS(1512722735522840439), - - INFLUX_END); - - **NOTICE**: For best performance you should sort tags by key before sending them to the database. - The sort should match the results from the [Go bytes.Compare function](https://golang.org/pkg/bytes/#Compare). - */ - -#define INFLUX_MEAS(m) IF_TYPE_MEAS, (m) -#define INFLUX_TAG(k, v) IF_TYPE_TAG, (k), (v) -#define INFLUX_F_STR(k, v) IF_TYPE_FIELD_STRING, (k), (v) -#define INFLUX_F_FLT(k, v, p) IF_TYPE_FIELD_FLOAT, (k), (double)(v), (int)(p) -#define INFLUX_F_INT(k, v) IF_TYPE_FIELD_INTEGER, (k), (long long)(v) -#define INFLUX_F_BOL(k, v) IF_TYPE_FIELD_BOOLEAN, (k), ((v) ? 1 : 0) -#define INFLUX_TS(ts) IF_TYPE_TIMESTAMP, (long long)(ts) -#define INFLUX_END IF_TYPE_ARG_END - -typedef struct _influx_client_t -{ - char* host; - int port; - char* db; // http only - char* usr; // http only [optional for auth] - char* pwd; // http only [optional for auth] - char* token; // http only -} influx_client_t; - -typedef struct _influx_v2_client_t -{ - char* host; - int port; - char* org; - char* bucket; - char* precision; - char* usr; // http only [optional for auth] - char* pwd; // http only [optional for auth] - char* token; // http only -} influx_v2_client_t; - -int format_line(char **buf, int *len, size_t used, ...); -int post_http(influx_client_t* c, ...); -int send_udp(influx_client_t* c, ...); -int post_curl(influx_v2_client_t* c, ...); - -#define IF_TYPE_ARG_END 0 -#define IF_TYPE_MEAS 1 -#define IF_TYPE_TAG 2 -#define IF_TYPE_FIELD_STRING 3 -#define IF_TYPE_FIELD_FLOAT 4 -#define IF_TYPE_FIELD_INTEGER 5 -#define IF_TYPE_FIELD_BOOLEAN 6 -#define IF_TYPE_TIMESTAMP 7 - -int _escaped_append(char** dest, size_t* len, size_t* used, const char* src, const char* escape_seq); -int _begin_line(char **buf); -int _format_line(char** buf, va_list ap); -int _format_line2(char** buf, va_list ap, size_t *, size_t); -int post_http_send_line(influx_client_t *c, char *buf, int len); -int send_udp_line(influx_client_t* c, char *line, int len); - -int post_http_send_line(influx_client_t *c, char *buf, int len) -{ - int sock = -1 , ret_code = 0, content_length = 0; - struct sockaddr_in addr; - struct iovec iv[2]; - char ch; - - iv[1].iov_base = buf; - iv[1].iov_len = len; - - if(!(iv[0].iov_base = (char*)malloc(len = 0x800))) { - free(iv[1].iov_base); - return -2; - } - - for(;;) { - iv[0].iov_len = snprintf((char*)iv[0].iov_base, len, - "POST /write?db=%s&u=%s&p=%s HTTP/1.1\r\n" - "Host: %s\r\n" - "Accept: application/json\r\n" - "Content-type: text/plain\r\n" - "Authorization: Token %s\r\n" - "Content-Length: %zd\r\n" - "\r\n", // Final blank line is needed. - c->db, c->usr ? c->usr : "", c->pwd ? c->pwd : "", c->host, c->token ? c->token : "", iv[1].iov_len); - if((int)iv[0].iov_len >= len && !(iv[0].iov_base = (char*)realloc(iv[0].iov_base, len *= 2))) { - free(iv[1].iov_base); - free(iv[0].iov_base); - return -3; - } - else - break; - } - - fprintf(stderr, "influxdb-c::post_http: iv[0] = '%s'\n", (char *)iv[0].iov_base); - fprintf(stderr, "influxdb-c::post_http: iv[1] = '%s'\n", (char *)iv[1].iov_base); - - addr.sin_family = AF_INET; - addr.sin_port = htons(c->port); - // EAL: Rather than just an IP address, allow a hostname, like "localhost" - struct hostent* resolved_host = gethostbyname(c->host); - if (!resolved_host) { - free(iv[1].iov_base); - free(iv[0].iov_base); - return -4; - } - memcpy(&addr.sin_addr, resolved_host->h_addr_list[0], resolved_host->h_length); - /* - if((addr.sin_addr.s_addr = inet_addr(resolved_host->h_addr)) == INADDR_NONE) { - free(iv[1].iov_base); - free(iv[0].iov_base); - return -4; - } - */ - - if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { - free(iv[1].iov_base); - free(iv[0].iov_base); - return -5; - } - - if(connect(sock, (struct sockaddr*)(&addr), sizeof(addr)) < 0) { - ret_code = -6; - goto END; - } - - if(writev(sock, iv, 2) < (int)(iv[0].iov_len + iv[1].iov_len)) { - ret_code = -7; - goto END; - } - iv[0].iov_len = len; - -#define _GET_NEXT_CHAR() (ch = (len >= (int)iv[0].iov_len && \ - (iv[0].iov_len = recv(sock, iv[0].iov_base, iv[0].iov_len, len = 0)) == (size_t)(-1) ? \ - 0 : *((char*)iv[0].iov_base + len++))) -#define _LOOP_NEXT(statement) for(;;) { if(!(_GET_NEXT_CHAR())) { ret_code = -8; goto END; } statement } -#define _UNTIL(c) _LOOP_NEXT( if(ch == c) break; ) -#define _GET_NUMBER(n) _LOOP_NEXT( if(ch >= '0' && ch <= '9') n = n * 10 + (ch - '0'); else break; ) -#define _(c) if((_GET_NEXT_CHAR()) != c) break; - - _UNTIL(' ')_GET_NUMBER(ret_code) - for(;;) { - _UNTIL('\n') - switch(_GET_NEXT_CHAR()) { - case 'C':_('o')_('n')_('t')_('e')_('n')_('t')_('-') - _('L')_('e')_('n')_('g')_('t')_('h')_(':')_(' ') - _GET_NUMBER(content_length) - break; - case '\r':_('\n') - while(content_length-- > 0 && _GET_NEXT_CHAR());// printf("%c", ch); - goto END; - } - if(!ch) { - ret_code = -10; - goto END; - } - } - ret_code = -11; -END: - close(sock); - free(iv[0].iov_base); - free(iv[1].iov_base); - return ret_code / 100 == 2 ? 0 : ret_code; -} -#undef _GET_NEXT_CHAR -#undef _LOOP_NEXT -#undef _UNTIL -#undef _GET_NUMBER -#undef _ - -int post_http(influx_client_t* c, ...) -{ - va_list ap; - char *line = NULL; - int ret_code = 0, len = 0; - - va_start(ap, c); - len = _format_line((char**)&line, ap); - va_end(ap); - if(len < 0) - return -1; - - ret_code = post_http_send_line(c, line, len); - - return ret_code; -} - -int send_udp_line(influx_client_t* c, char *line, int len) -{ - int sock = -1, ret = 0; - struct sockaddr_in addr; - - addr.sin_family = AF_INET; - addr.sin_port = htons(c->port); - if((addr.sin_addr.s_addr = inet_addr(c->host)) == INADDR_NONE) { - ret = -2; - goto END; - } - - if((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { - ret = -3; - goto END; - } - - if(sendto(sock, line, len, 0, (struct sockaddr *)&addr, sizeof(addr)) < len) - ret = -4; - -END: - if (sock >= 0) { - close(sock); - } - return ret; -} - -int send_udp(influx_client_t* c, ...) -{ - int ret = 0, len; - va_list ap; - char* line = NULL; - - va_start(ap, c); - len = _format_line(&line, ap); - va_end(ap); - if(len < 0) - return -1; - - ret = send_udp_line(c, line, len); - - free(line); - return ret; -} - -int post_curl(influx_v2_client_t* c, ...) -{ - va_list ap; - char *data = NULL; - int len = 0; - va_start(ap, c); - len = _format_line((char**)&data, ap); - va_end(ap); - - CURL *curl; - - /* In windows, this will init the winsock stuff */ - curl_global_init(CURL_GLOBAL_ALL); - CURLcode res; - - /* get a curl handle */ - curl = curl_easy_init(); - if(!curl) { - return CURLE_FAILED_INIT; - } - - char* url_string = (char*)malloc(len); - snprintf(url_string, len, - "http://%s:%d/api/v2/write?org=%s&bucket=%s&precision=%s", - c->host ? c->host: "localhost", c->port ? c->port : 8086, c->org, c->bucket, c->precision ? c->precision : "ns"); - - curl_easy_setopt(curl, CURLOPT_URL, url_string); - free(url_string); - - char* token_string = (char*)malloc(120*sizeof(char)); - sprintf(token_string, "Authorization: Token %s", c->token); - - struct curl_slist *list = NULL; - list = curl_slist_append(list, token_string); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); - free(token_string); - - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); - curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0"); - - /* Perform the request, res will get the return code */ - res = curl_easy_perform(curl); - /* Check for errors */ - if(res != CURLE_OK){ - fprintf(stderr, "curl_easy_perform() failed: %s\n", - curl_easy_strerror(res)); - } - - free(data); - curl_easy_cleanup(curl); - curl_global_cleanup(); - return res; -} - -int format_line(char **buf, int *len, size_t used, ...) -{ - va_list ap; - va_start(ap, used); - used = _format_line2(buf, ap, (size_t *)len, used); - va_end(ap); - if(*len < 0) - return -1; - else - return used; -} - -int _begin_line(char **buf) -{ - int len = 0x100; - if(!(*buf = (char*)malloc(len))) - return -1; - return len; -} - -int _format_line(char** buf, va_list ap) -{ - size_t len = 0; - *buf = NULL; - return _format_line2(buf, ap, &len, 0); -} - -int _format_line2(char** buf, va_list ap, size_t *_len, size_t used) -{ -#define _APPEND(fmter...) \ - for(;;) {\ - if((written = snprintf(*buf + used, len - used, ##fmter)) < 0)\ - goto FAIL;\ - if(used + written >= len && !(*buf = (char*)realloc(*buf, len *= 2)))\ - return -1;\ - else {\ - used += written;\ - break;\ - }\ - } - - size_t len = *_len; - int written = 0, type = 0, last_type = 0; - unsigned long long i = 0; - double d = 0.0; - - if (*buf == NULL) { - len = _begin_line(buf); - used = 0; - } - - type = va_arg(ap, int); - while(type != IF_TYPE_ARG_END) { - if(type >= IF_TYPE_TAG && type <= IF_TYPE_FIELD_BOOLEAN) { - if(last_type < IF_TYPE_MEAS || last_type > (type == IF_TYPE_TAG ? IF_TYPE_TAG : IF_TYPE_FIELD_BOOLEAN)) - goto FAIL; - _APPEND("%c", (last_type <= IF_TYPE_TAG && type > IF_TYPE_TAG) ? ' ' : ','); - if(_escaped_append(buf, &len, &used, va_arg(ap, char*), ",= ")) - return -2; - _APPEND("="); - } - switch(type) { - case IF_TYPE_MEAS: - if(last_type) - _APPEND("\n"); - if(last_type && last_type <= IF_TYPE_TAG) - goto FAIL; - if(_escaped_append(buf, &len, &used, va_arg(ap, char*), ", ")) - return -3; - break; - case IF_TYPE_TAG: - if(_escaped_append(buf, &len, &used, va_arg(ap, char*), ",= ")) - return -4; - break; - case IF_TYPE_FIELD_STRING: - _APPEND("\""); - if(_escaped_append(buf, &len, &used, va_arg(ap, char*), "\"")) - return -5; - _APPEND("\""); - break; - case IF_TYPE_FIELD_FLOAT: - d = va_arg(ap, double); - i = va_arg(ap, int); - _APPEND("%.*lf", (int)i, d); - break; - case IF_TYPE_FIELD_INTEGER: - i = va_arg(ap, long long); - _APPEND("%lldi", i); - break; - case IF_TYPE_FIELD_BOOLEAN: - i = va_arg(ap, int); - _APPEND("%c", i ? 't' : 'f'); - break; - case IF_TYPE_TIMESTAMP: - if(last_type < IF_TYPE_FIELD_STRING || last_type > IF_TYPE_FIELD_BOOLEAN) - goto FAIL; - i = va_arg(ap, long long); - _APPEND(" %lld", i); - break; - default: - goto FAIL; - } - last_type = type; - type = va_arg(ap, int); - } - _APPEND("\n"); - if(last_type <= IF_TYPE_TAG) - goto FAIL; - *_len = len; - return used; -FAIL: - free(*buf); - *buf = NULL; - return -1; -} -#undef _APPEND - -int _escaped_append(char** dest, size_t* len, size_t* used, const char* src, const char* escape_seq) -{ - size_t i = 0; - - for(;;) { - if((i = strcspn(src, escape_seq)) > 0) { - if(*used + i > *len && !(*dest = (char*)realloc(*dest, (*len) *= 2))) - return -1; - strncpy(*dest + *used, src, i); - *used += i; - src += i; - } - if(*src) { - if(*used + 2 > *len && !(*dest = (char*)realloc(*dest, (*len) *= 2))) - return -2; - (*dest)[(*used)++] = '\\'; - (*dest)[(*used)++] = *src++; - } - else - return 0; - } - return 0; -} diff --git a/util/tracing/makefile b/util/tracing/makefile deleted file mode 100644 index f414f05ef3..0000000000 --- a/util/tracing/makefile +++ /dev/null @@ -1,35 +0,0 @@ -# Makefile for utilities that convert Lingua Franca trace files -# into other formats. -# @author: Edward A. Lee - -CC=gcc -CFLAGS=-I../../core/src/main/resources/lib/c/reactor-c/include/core/ \ - -I../../core/src/main/resources/lib/c/reactor-c/include/core/modal_models \ - -I../../core/src/main/resources/lib/c/reactor-c/include/core/platform \ - -I../../core/src/main/resources/lib/c/reactor-c/include/core/utils \ - -DLF_UNTHREADED=1 \ - -Wall -DEPS= -LIBS=-lcurl - -%.o: %.c $(DEPS) - $(CC) -c -o $@ $< $(CFLAGS) - -trace_to_csv: trace_to_csv.o trace_util.o - $(CC) -o trace_to_csv trace_to_csv.o trace_util.o - -trace_to_chrome: trace_to_chrome.o trace_util.o - $(CC) -o trace_to_chrome trace_to_chrome.o trace_util.o - -trace_to_influxdb: trace_to_influxdb.o trace_util.o - $(CC) -o trace_to_influxdb trace_to_influxdb.o trace_util.o $(LIBS) - -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 ../util/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 deleted file mode 100644 index 8e0c29dba8..0000000000 --- a/util/tracing/trace_to_chrome.c +++ /dev/null @@ -1,452 +0,0 @@ -/** - * @file - * @author Edward A. Lee - * - * @section LICENSE -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. - - * @section DESCRIPTION - * Standalone program to convert a Lingua Franca trace file to a JSON file suitable - * for viewing in Chrome's event visualizer. To visualize the resulting file, - * point your chrome browser to chrome://tracing/ and the load the .json file. - */ -#define LF_TRACE -#include -#include -#include "reactor.h" -#include "trace.h" -#include "trace_util.h" - -#define PID_FOR_USER_EVENT 1000000 // Assumes no more than a million reactors. -#define PID_FOR_WORKER_WAIT 0 // Use 1000001 to show in separate trace. -#define PID_FOR_WORKER_ADVANCING_TIME 0 // Use 1000002 to show in separate trace. -#define PID_FOR_UNKNOWN_EVENT 2000000 - -/** 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. - */ -void usage() { - printf("\nUsage: trace_to_chrome [options] trace_file (with or without .lft extension)\n"); - printf("Options: \n"); - printf(" -p, --physical\n"); - printf(" Use only physical time, not logical time, for all horizontal axes.\n"); - printf("\n"); -} - -/** Maximum reaction number encountered. */ -int max_reaction_number = 0; - -/** Indicator to plot vs. physical time only. */ -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(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\""; - - // 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].dst_id); - } - // printf("DEBUG: Reactor's self struct pointer: %p\n", trace[i].pointer); - int reactor_index; - char* reactor_name = get_object_description(trace[i].pointer, &reactor_index); - 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 - || trace[i].event_type == scheduler_advancing_time_starts) { - reactor_name = "ADVANCE TIME"; - } else { - reactor_name = "NO REACTOR"; - } - } - // Default name is the reactor name. - char* name = reactor_name; - - int trigger_index; - char* trigger_name = get_trigger_name(trace[i].trigger, &trigger_index); - if (trigger_name == NULL) { - trigger_name = "NONE"; - } - // By default, the timestamp used in the trace is the elapsed - // physical time in microseconds. But for schedule_called events, - // it will instead be the logical time at which the action or timer - // is to be scheduled. - interval_t elapsed_physical_time = (trace[i].physical_time - start_time)/1000; - interval_t timestamp = elapsed_physical_time; - interval_t elapsed_logical_time = (trace[i].logical_time - start_time)/1000; - - if (elapsed_physical_time < 0) { - fprintf(stderr, "WARNING: Negative elapsed physical time %lld. Skipping trace entry.\n", elapsed_physical_time); - continue; - } - if (elapsed_logical_time < 0) { - fprintf(stderr, "WARNING: Negative elapsed logical time %lld. Skipping trace entry.\n", elapsed_logical_time); - continue; - } - - // Default thread id is the worker number. - int thread_id = trace[i].src_id; - - char* args; - asprintf(&args, "{" - "\"reaction\": %s," // reaction number. - "\"logical time\": %lld," // logical time. - "\"physical time\": %lld," // physical time. - "\"microstep\": %d" // microstep. - "}", - reaction_name, - elapsed_logical_time, - elapsed_physical_time, - trace[i].microstep - ); - char* phase; - int pid; - switch(trace[i].event_type) { - case reaction_starts: - phase = "B"; - pid = 0; // Process 0 will be named "Execution" - break; - case reaction_ends: - phase = "E"; - pid = 0; // Process 0 will be named "Execution" - break; - case schedule_called: - phase = "i"; - pid = reactor_index + 1; // One pid per reactor. - if (!physical_time_only) { - timestamp = elapsed_logical_time + trace[i].extra_delay/1000; - } - thread_id = trigger_index; - name = trigger_name; - break; - case user_event: - pid = PID_FOR_USER_EVENT; - phase= "i"; - if (!physical_time_only) { - timestamp = elapsed_logical_time; - } - thread_id = reactor_index; - break; - case user_value: - pid = PID_FOR_USER_EVENT; - phase= "C"; - if (!physical_time_only) { - timestamp = elapsed_logical_time; - } - thread_id = reactor_index; - free(args); - asprintf(&args, "{\"value\": %lld}", trace[i].extra_delay); - break; - case worker_wait_starts: - pid = PID_FOR_WORKER_WAIT; - phase = "B"; - break; - case worker_wait_ends: - pid = PID_FOR_WORKER_WAIT; - phase = "E"; - break; - case scheduler_advancing_time_starts: - pid = PID_FOR_WORKER_ADVANCING_TIME; - phase = "B"; - break; - case scheduler_advancing_time_ends: - pid = PID_FOR_WORKER_ADVANCING_TIME; - phase = "E"; - break; - default: - 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"; - } - fprintf(output_file, "{" - "\"name\": \"%s\", " // name is the reactor or trigger name. - "\"cat\": \"%s\", " // category is the type of event. - "\"ph\": \"%s\", " // phase is "B" (begin), "E" (end), or "X" (complete). - "\"tid\": %d, " // thread ID. - "\"pid\": %d, " // process ID is required. - "\"ts\": %lld, " // timestamp in microseconds - "\"args\": %s" // additional arguments from above. - "},\n", - name, - trace_event_names[trace[i].event_type], - phase, - thread_id, - pid, - timestamp, - args - ); - free(args); - - 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 - // event to be shown in the reactor's section, along with timers and actions. - if (trace[i].event_type == reaction_starts && !physical_time_only) { - phase = "i"; - pid = reactor_index + 1; - reaction_name = (char*)malloc(4); - char name[13]; - 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].dst_id; - if (trace[i].dst_id > max_reaction_number) { - max_reaction_number = trace[i].dst_id; - } - - fprintf(output_file, "{" - "\"name\": \"%s\", " // name is the reactor or trigger name. - "\"cat\": \"%s\", " // category is the type of event. - "\"ph\": \"%s\", " // phase is "B" (begin), "E" (end), or "X" (complete). - "\"tid\": %d, " // thread ID. - "\"pid\": %d, " // process ID is required. - "\"ts\": %lld, " // timestamp in microseconds - "\"args\": {" - "\"microstep\": %d, " // microstep. - "\"physical time\": %lld" // physical time. - "}},\n", - name, - "Reaction", - phase, - thread_id, - pid, - elapsed_logical_time, - trace[i].microstep, - elapsed_physical_time - ); - } - } - return trace_length; -} - -/** - * Write metadata events, which provide names in the renderer. - * @param output_file An open output .json file. - */ -void write_metadata_events(FILE* output_file) { - // Thread 0 is the main thread. - fprintf(output_file, "{" - "\"name\": \"thread_name\", " - "\"ph\": \"M\", " // mark as metadata. - "\"pid\": 0, " - "\"tid\": 0, " - "\"args\": {" - "\"name\": \"Main thread\"" - "}},\n" - ); - - // Name the worker threads. - for (int i = 1; i <= max_thread_id; i++) { - fprintf(output_file, "{" - "\"name\": \"thread_name\", " - "\"ph\": \"M\", " // mark as metadata. - "\"pid\": 0, " - "\"tid\": %d, " - "\"args\": {" - "\"name\": \"Worker %d\"" - "}},\n", - i, i - ); - fprintf(output_file, "{" - "\"name\": \"thread_name\", " - "\"ph\": \"M\", " // mark as metadata. - "\"pid\": %d, " - "\"tid\": %d, " - "\"args\": {" - "\"name\": \"Worker %d\"" - "}},\n", - PID_FOR_WORKER_WAIT, i, i - ); - fprintf(output_file, "{" - "\"name\": \"thread_name\", " - "\"ph\": \"M\", " // mark as metadata. - "\"pid\": %d, " - "\"tid\": %d, " - "\"args\": {" - "\"name\": \"Worker %d\"" - "}},\n", - PID_FOR_WORKER_ADVANCING_TIME, i, i - ); - } - - // Name reactions for each reactor. - for (int reactor_index = 1; reactor_index <= object_table_size; reactor_index++) { - for (int reaction_number = 0; reaction_number <= max_reaction_number; reaction_number++) { - fprintf(output_file, "{" - "\"name\": \"thread_name\", " - "\"ph\": \"M\", " // mark as metadata. - "\"pid\": %d, " - "\"tid\": %d, " - "\"args\": {" - "\"name\": \"Reaction %d\"" - "}},\n", - reactor_index, reaction_number + 1024, reaction_number - ); - } - } - - // Write the reactor names for the logical timelines. - for (int i = 0; i < object_table_size; i++) { - if (object_table[i].type == trace_trigger) { - // We need the reactor index (not the name) to set the pid. - int reactor_index; - get_object_description(object_table[i].pointer, &reactor_index); - fprintf(output_file, "{" - "\"name\": \"thread_name\", " // metadata for thread name. - "\"ph\": \"M\", " // mark as metadata. - "\"pid\": %d, " // the "process" to identify by reactor. - "\"tid\": %d," // The "thread" to label with action or timer name. - "\"args\": {" - "\"name\": \"Trigger %s\"" - "}},\n", - reactor_index + 1, // Offset of 1 prevents collision with Execution. - i, - object_table[i].description); - } else if (object_table[i].type == trace_reactor) { - fprintf(output_file, "{" - "\"name\": \"process_name\", " // metadata for process name. - "\"ph\": \"M\", " // mark as metadata. - "\"pid\": %d, " // the "process" to label as reactor. - "\"args\": {" - "\"name\": \"Reactor %s reactions, actions, and timers in logical time\"" - "}},\n", - i + 1, // Offset of 1 prevents collision with Execution. - object_table[i].description); - } else if (object_table[i].type == trace_user) { - fprintf(output_file, "{" - "\"name\": \"thread_name\", " // metadata for thread name. - "\"ph\": \"M\", " // mark as metadata. - "\"pid\": %d, " // the "process" to label as reactor. - "\"tid\": %d," // The "thread" to label with action or timer name. - "\"args\": {" - "\"name\": \"%s\"" - "}},\n", - PID_FOR_USER_EVENT, - i, // This is the index in the object table. - object_table[i].description); - } - } - // Name the "process" for "Execution" - fprintf(output_file, "{" - "\"name\": \"process_name\", " // metadata for process name. - "\"ph\": \"M\", " // mark as metadata. - "\"pid\": 0, " // the "process" to label "Execution". - "\"args\": {" - "\"name\": \"Execution of %s\"" - "}},\n", - top_level); - // Name the "process" for "Worker Waiting" if the PID is not the main execution one. - if (PID_FOR_WORKER_WAIT > 0) { - fprintf(output_file, "{" - "\"name\": \"process_name\", " // metadata for process name. - "\"ph\": \"M\", " // mark as metadata. - "\"pid\": %d, " // the "process" to label "Workers waiting for reaction queue". - "\"args\": {" - "\"name\": \"Workers waiting for reaction queue\"" - "}},\n", - PID_FOR_WORKER_WAIT); - } - // Name the "process" for "Worker advancing time" if the PID is not the main execution one. - if (PID_FOR_WORKER_ADVANCING_TIME > 0) { - fprintf(output_file, "{" - "\"name\": \"process_name\", " // metadata for process name. - "\"ph\": \"M\", " // mark as metadata. - "\"pid\": %d, " // the "process" to label "Workers waiting for reaction queue". - "\"args\": {" - "\"name\": \"Workers advancing time\"" - "}},\n", - PID_FOR_WORKER_ADVANCING_TIME); - } - // Name the "process" for "User Events" - // Last metadata entry lacks a comma. - fprintf(output_file, "{" - "\"name\": \"process_name\", " // metadata for process name. - "\"ph\": \"M\", " // mark as metadata. - "\"pid\": %d, " // the "process" to label "User events". - "\"args\": {" - "\"name\": \"User events in %s, shown in physical time:\"" - "}}\n", - PID_FOR_USER_EVENT, top_level); -} - -int main(int argc, char* argv[]) { - char* filename = NULL; - for (int i = 1; i < argc; i++) { - if (strncmp(argv[i], "-p", 2) == 0 || strncmp(argv[i], "--physical", 10) == 0) { - physical_time_only = true; - } else if (argv[i][0] == '-') { - usage(); - return(1); - } else { - filename = argv[i]; - } - } - if (filename == NULL) { - usage(); - exit(0); - } - - // 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(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 deleted file mode 100644 index 1abf08c082..0000000000 --- a/util/tracing/trace_to_csv.c +++ /dev/null @@ -1,436 +0,0 @@ -/** - * @file - * @author Edward A. Lee - * - * @section LICENSE -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. - - * @section DESCRIPTION - * Standalone program to convert a Lingua Franca trace file to a comma-separated values - * text file. - */ -#define LF_TRACE -#include -#include -#include "reactor.h" -#include "trace.h" -#include "trace_util.h" - -#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; - -/** - * Print a usage message. - */ -void usage() { - printf("\nUsage: trace_to_csv [options] trace_file_root (without .lft extension)\n\n"); - /* No options yet: - printf("\nOptions: \n\n"); - printf(" -f, --fast [true | false]\n"); - printf(" Whether to wait for physical time to match logical time.\n\n"); - printf("\n\n"); - */ -} - -/** - * Struct for collecting summary statistics for reaction invocations. - */ -typedef struct reaction_stats_t { - int occurrences; - instant_t latest_start_time; - interval_t total_exec_time; - interval_t max_exec_time; - interval_t min_exec_time; -} reaction_stats_t; - -/** - * Struct for collecting summary statistics. - */ -typedef struct summary_stats_t { - trace_event_t event_type; // Use reaction_ends for reactions. - 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]; -} 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. - */ -summary_stats_t** summary_stats; - -/** Largest timestamp seen. */ -instant_t latest_time = 0LL; - -/** - * Read a trace in the specified file and write it to the specified CSV file. - * @return The number of records read or 0 upon seeing an EOF. - */ -size_t read_and_write_trace() { - int trace_length = read_trace(); - if (trace_length == 0) return 0; - // Write each line. - for (int i = 0; i < trace_length; i++) { - // 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); - if (reactor_name == NULL) { - reactor_name = "NO REACTOR"; - } - int trigger_instance = -1; - char* trigger_name = get_trigger_name(trace[i].trigger, &trigger_instance); - if (trigger_name == NULL) { - trigger_name = "NO TRIGGER"; - } - fprintf(output_file, "%s, %s, %d, %d, %lld, %d, %lld, %s, %lld\n", - trace_event_names[trace[i].event_type], - reactor_name, - trace[i].src_id, - trace[i].dst_id, - trace[i].logical_time - start_time, - trace[i].microstep, - trace[i].physical_time - start_time, - trigger_name, - trace[i].extra_delay - ); - // Update summary statistics. - if (trace[i].physical_time > latest_time) { - latest_time = trace[i].physical_time; - } - 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[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; - 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].dst_id >= MAX_NUM_REACTIONS) { - fprintf(stderr, "WARNING: Too many reactions. Not all will be shown in summary file.\n"); - continue; - } - stats = summary_stats[NUM_EVENT_TYPES + object_instance]; - stats->description = reactor_name; - if (trace[i].dst_id >= stats->num_reactions_seen) { - stats->num_reactions_seen = trace[i].dst_id + 1; - } - rstats = &stats->reactions[trace[i].dst_id]; - if (trace[i].event_type == reaction_starts) { - rstats->latest_start_time = trace[i].physical_time; - } else { - rstats->occurrences++; - exec_time = trace[i].physical_time - rstats->latest_start_time; - rstats->latest_start_time = 0LL; - rstats->total_exec_time += exec_time; - if (exec_time > rstats->max_exec_time) { - rstats->max_exec_time = exec_time; - } - if (exec_time < rstats->min_exec_time || rstats->min_exec_time == 0LL) { - rstats->min_exec_time = exec_time; - } - } - break; - case schedule_called: - if (trigger_instance < 0) { - // No trigger. Do not report. - continue; - } - 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[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[NUM_EVENT_TYPES + object_instance]; - stats->description = reactor_name; - 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; - rstats->total_exec_time += value; - if (value > rstats->max_exec_time) { - rstats->max_exec_time = value; - } - if (value < rstats->min_exec_time || rstats->min_exec_time == 0LL) { - rstats->min_exec_time = value; - } - break; - case worker_wait_starts: - case worker_wait_ends: - case scheduler_advancing_time_starts: - case scheduler_advancing_time_ends: - // 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].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 - || trace[i].event_type == scheduler_advancing_time_ends) { - index++; - } - if (object_table_size + index >= table_size) { - fprintf(stderr, "WARNING: Too many workers. Not all will be shown in summary file.\n"); - continue; - } - 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[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. - if (index >= stats->num_reactions_seen) { - stats->num_reactions_seen = index; - } - rstats = &stats->reactions[index]; - if (trace[i].event_type == worker_wait_starts - || trace[i].event_type == scheduler_advancing_time_starts - ) { - rstats->latest_start_time = trace[i].physical_time; - } else { - rstats->occurrences++; - exec_time = trace[i].physical_time - rstats->latest_start_time; - rstats->latest_start_time = 0LL; - rstats->total_exec_time += exec_time; - if (exec_time > rstats->max_exec_time) { - rstats->max_exec_time = exec_time; - } - if (exec_time < rstats->min_exec_time || rstats->min_exec_time == 0LL) { - rstats->min_exec_time = exec_time; - } - } - break; - default: - // No special summary statistics for the rest. - break; - } - // Common stats across event types. - if (stats != NULL) { - stats->occurrences++; - stats->event_type = trace[i].event_type; - } - } - return trace_length; -} - -/** - * Write the summary file. - */ -void write_summary_file() { - // Overall stats. - fprintf(summary_file, "Start time:, %lld\n", start_time); - 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 = 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++) { - reaction_stats_t* rstats = &stats->reactions[j]; - if (rstats->occurrences > 0) { - fprintf(summary_file, "%s, %d, %d, %lld, %f, %lld, %lld, %lld\n", - stats->description, - j, // Reaction number. - rstats->occurrences, - rstats->total_exec_time, - rstats->total_exec_time * 100.0 / (latest_time - start_time), - rstats->total_exec_time / rstats->occurrences, - rstats->max_exec_time, - rstats->min_exec_time - ); - } - } - } - } - - // Next pass looks for calls to schedule. - bool first = true; - 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) { - first = false; - fprintf(summary_file, "\nSchedule calls\n"); - fprintf(summary_file, "Trigger, Occurrences\n"); - } - fprintf(summary_file, "%s, %d\n", stats->description, stats->occurrences); - } - } - - // Next pass looks for user-defined events. - first = true; - 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) - && stats->occurrences > 0) { - if (first) { - first = false; - fprintf(summary_file, "\nUser events\n"); - fprintf(summary_file, "Description, Occurrences, Total Value, Avg Value, Max Value, Min Value\n"); - } - fprintf(summary_file, "%s, %d", stats->description, stats->occurrences); - if (stats->event_type == user_value && stats->reactions[0].occurrences > 0) { - // This assumes that the first "reactions" entry has been comandeered for this data. - fprintf(summary_file, ", %lld, %lld, %lld, %lld\n", - stats->reactions[0].total_exec_time, - stats->reactions[0].total_exec_time / stats->reactions[0].occurrences, - stats->reactions[0].max_exec_time, - stats->reactions[0].min_exec_time - ); - } else { - fprintf(summary_file, "\n"); - } - } - } - - // Next pass looks for wait events. - first = true; - 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 - || stats->event_type == scheduler_advancing_time_ends) - ) { - if (first) { - first = false; - fprintf(summary_file, "\nWorkers Waiting\n"); - fprintf(summary_file, "Worker, Waiting On, Occurrences, Total Time, Pct Total Time, Avg Time, Max Time, Min Time\n"); - } - char* waitee = "reaction queue"; - if (stats->event_type == scheduler_advancing_time_ends - || stats->event_type == scheduler_advancing_time_starts) { - waitee = "advancing time"; - } - for (int j = 0; j <= stats->num_reactions_seen; j++) { - reaction_stats_t* rstats = &stats->reactions[j]; - if (rstats->occurrences > 0) { - fprintf(summary_file, "%d, %s, %d, %lld, %f, %lld, %lld, %lld\n", - j / 2, - waitee, - rstats->occurrences, - rstats->total_exec_time, - rstats->total_exec_time * 100.0 / (latest_time - start_time), - rstats->total_exec_time / rstats->occurrences, - rstats->max_exec_time, - rstats->min_exec_time - ); - } - } - } - } -} - -int main(int argc, char* argv[]) { - if (argc != 2) { - usage(); - exit(0); - } - // 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 = 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, Source, Destination, Elapsed Logical Time, Microstep, Elapsed Physical Time, Trigger, Extra Delay\n"); - while (read_and_write_trace() != 0) {}; - - write_summary_file(); - - // File closing is handled by termination function. - } -} diff --git a/util/tracing/trace_to_influxdb.c b/util/tracing/trace_to_influxdb.c deleted file mode 100644 index aa50911e5e..0000000000 --- a/util/tracing/trace_to_influxdb.c +++ /dev/null @@ -1,281 +0,0 @@ -/** - * @file - * @author Edward A. Lee - * @author Ravi Akella - * - * @section LICENSE -Copyright (c) 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. - - * @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 - * ``` - * 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/ - * ps aux | grep nflux - * ``` - * The second command will report any InfluxDB processes that are running. Kill them with - * ```shell - * 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 - * ``` - * The browser will walk you through the process of creating a user, password, organization, and initial bucket. E.g.: - * ``` - * User: eal superSecretPassword - * Organization: iCyPhy - * Bucket: test - * ``` - * 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 { - * tracing: true - * }; - * ``` - * 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: - * ``` - * N1mK4b7z29YuWrWG_rBRJF3owaXjPA6gBVOgGG3eStS_zbESHTYJgfJWHB2JA_y3-BMYlMPVa05ccLVA1S770A== - * ``` - * Then, invoke the conversion program as follows: - * ```shell - * trace_to_influxdb Filename.lft \ - * --token N1mK4b7z29YuWrWG_rBRJF3owaXjPA6gBVOgGG3eStS_zbESHTYJgfJWHB2JA_y3-BMYlMPVa05ccLVA1S770A== - * ``` - * 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" -#include "influxdb.h" - -#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; -/** - * Print a usage message. - */ -void usage() { - printf("\nUsage: trace_to_influxdb [options] trace_file [options]\n\n"); - printf("\nOptions: \n\n"); - printf(" -t, --token TOKEN\n"); - printf(" The token for access to InfluxDB (required argument).\n\n"); - printf(" -h, --host HOSTNAME\n"); - printf(" The host name for access to InfluxDB (default is 'localhost').\n\n"); - printf(" -p, --port PORT\n"); - printf(" The port for access to InfluxDB (default is 8086).\n\n"); - printf(" -o, --ort ORGANIZATION\n"); - printf(" The organization for access to InfluxDB (default is 'iCyPhy').\n\n"); - printf(" -b, --bucket BUCKET\n"); - printf(" The bucket into which to put the data (default is 'test').\n\n"); - printf("\n\n"); -} - -/** Largest timestamp seen. */ -instant_t latest_time = 0LL; - -/** - * Read a trace in the specified file and write it to the specified CSV file. - * @return The number of records read or 0 upon seeing an EOF. - */ -size_t read_and_write_trace() { - int trace_length = read_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].dst_id >= 0) { - reaction_name = (char*)malloc(4); - snprintf(reaction_name, 4, "%d", trace[i].dst_id); - } - // 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); - if (reactor_name == NULL) { - reactor_name = "NO REACTOR"; - } - int trigger_instance = -1; - char* trigger_name = get_trigger_name(trace[i].trigger, &trigger_instance); - if (trigger_name == NULL) { - trigger_name = "NO TRIGGER"; - } - // FIXME: Treating physical time as the timestamp. - // Do we want this to optionally be logical time? - // FIXME: What is the difference between a TAG and F_STR (presumably, Field String)? - // Presumably, the HTTP post is formatted as a "line protocol" command. See: - // https://docs.influxdata.com/influxdb/v2.0/reference/syntax/line-protocol/ - int response_code = post_curl(&influx_v2_client, - 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].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), - INFLUX_F_INT("Extra Delay", trace[i].extra_delay), - INFLUX_TS(trace[i].physical_time), - INFLUX_END - ); - if (response_code != 0) { - fprintf(stderr, "****** response code: %d\n", response_code); - return 0; - } - } - return trace_length; -} - -int main(int argc, char* argv[]) { - if (argc < 2) { - usage(); - exit(0); - } - // Defaults. - influx_v2_client.token = NULL; - influx_v2_client.host = "localhost"; - influx_v2_client.port = 8086; - influx_v2_client.org = "iCyPhy"; - influx_v2_client.bucket = "test"; - - char* filename = NULL; - - for (int i = 1; i < argc; i++) { - if (strcmp("-t", argv[i]) == 0 || strcmp("--token", argv[i]) == 0) { - if (i++ == argc - 1) { - usage(); - fprintf(stderr, "No token specified.\n"); - exit(1); - } - influx_v2_client.token = argv[i]; - } else if (strcmp("-h", argv[i]) == 0 || strcmp("--host", argv[i]) == 0) { - if (i++ == argc - 1) { - usage(); - fprintf(stderr, "No host specified.\n"); - exit(1); - } - influx_v2_client.host = argv[i]; - } else if (strcmp("-p", argv[i]) == 0 || strcmp("--port", argv[i]) == 0) { - if (i++ == argc - 1) { - usage(); - fprintf(stderr, "No port specified.\n"); - exit(1); - } - influx_v2_client.port = atoi(argv[i]); - if (influx_v2_client.port == 0) { - fprintf(stderr, "Invalid port: %s.\n", argv[i]); - } - } else if (strcmp("-o", argv[i]) == 0 || strcmp("--org", argv[i]) == 0) { - if (i++ == argc - 1) { - usage(); - fprintf(stderr, "No organization specified.\n"); - exit(1); - } - influx_v2_client.org = argv[i]; - } else if (strcmp("-b", argv[i]) == 0 || strcmp("--bucket", argv[i]) == 0) { - if (i++ == argc - 1) { - usage(); - fprintf(stderr, "No bucket specified.\n"); - exit(1); - } - influx_v2_client.bucket = argv[i]; - } else { - // Must be the filename. - filename = argv[i]; - } - } - if (influx_v2_client.token == NULL) { - fprintf(stderr, "No token specified.\n"); - exit(1); - } - if (filename == NULL) { - fprintf(stderr, "No trace file specified.\n"); - exit(1); - } - - // Open the trace file. - trace_file = open_file(filename, "r"); - - if (read_header() >= 0) { - size_t num_records = 0, result; - while ((result = read_and_write_trace()) != 0) { - num_records = result; - }; - printf("***** %zu records written to InfluxDB.\n", num_records); - // File closing is handled by termination function. - } -} diff --git a/util/tracing/trace_util.c b/util/tracing/trace_util.c deleted file mode 100644 index 551ec94473..0000000000 --- a/util/tracing/trace_util.c +++ /dev/null @@ -1,295 +0,0 @@ -/** - * @file - * @author Edward A. Lee - * - * @section LICENSE -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. - - * @section DESCRIPTION - * Standalone program to convert a Lingua Franca trace file to a comma-separated values - * text file. - */ -#define LF_TRACE -#include -#include -#include -#include "reactor.h" -#include "trace.h" -#include "trace_util.h" - -/** Buffer for reading object descriptions. Size limit is BUFFER_SIZE bytes. */ -char buffer[BUFFER_SIZE]; - -/** Buffer for reading trace records. */ -trace_record_t trace[TRACE_BUFFER_CAPACITY]; - -/** The start time read from the trace file. */ -instant_t start_time; - -/** Name of the top-level reactor (first entry in symbol table). */ -char* top_level = NULL; - -/** Table of pointers to the self struct of a reactor. */ -// FIXME: Replace with hash table implementation. -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. - */ -void termination() { - // Free memory in object description table. - for (int i = 0; i < object_table_size; i++) { - free(object_table[i].description); - } - while (_open_files != NULL) { - fclose(_open_files->file); - open_file_t* tmp = _open_files->next; - free(_open_files); - _open_files = tmp; - } - printf("Done!\n"); -} - -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_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; -} - -/** - * Get the description of the object pointed to by the specified pointer. - * For example, this can be the name of a reactor (pointer points to - * the self struct) or a user-define string. - * If there is no such pointer in the symbol table, return NULL. - * If the index argument is non-null, then put the index - * of the entry in the table into the int pointed to - * or -1 if none was found. - * @param pointer The pointer to to an object, e.g. a self struct. - * @param index An optional pointer into which to write the index. - */ -char* get_object_description(void* pointer, int* index) { - // FIXME: Replace with a hash table implementation. - for (int i = 0; i < object_table_size; i++) { - if (object_table[i].pointer == pointer) { - if (index != NULL) { - *index = i; - } - return object_table[i].description; - } - } - if (index != NULL) { - *index = 0; - } - return NULL; -} - -/** - * Get the trigger name for the specified pointer. - * If there is no such trigger, return NULL. - * If the index argument is non-null, then put the index - * of the trigger in the table into the int pointed to - * or -1 if none was found. - * @param reactor The pointer to a self struct. - * @param index An optional pointer into which to write the index. - */ -char* get_trigger_name(void* trigger, int* index) { - // FIXME: Replace with a hash table implementation. - for (int i = 0; i < object_table_size; i++) { - if (object_table[i].trigger == trigger && object_table[i].type == trace_trigger) { - if (index != NULL) { - *index = i; - } - return object_table[i].description; - } - } - if (index != NULL) { - *index = 0; - } - return NULL; -} - -/** - * Print the object to description table. - */ -void print_table() { - printf("------- objects traced:\n"); - for (int i = 0; i < object_table_size; i++) { - char* type; - if (object_table[i].type == trace_reactor) { - type = "reactor"; - } else if (object_table[i].type == trace_trigger) { - type = "trigger"; - } else if (object_table[i].type == trace_user) { - type = "user-defined"; - } else { - type = "unknown type"; - } - printf("pointer = %p, trigger = %p, type = %s: %s\n", - object_table[i].pointer, - object_table[i].trigger, - type, - object_table[i].description); - } - printf("-------\n"); -} - -/** - * Read header information. - * @return The number of objects in the object table or -1 for failure. - */ -size_t read_header() { - // Read the start time. - int items_read = fread(&start_time, sizeof(instant_t), 1, trace_file); - if (items_read != 1) _LF_TRACE_FAILURE(trace_file); - - printf("Start time is %lld.\n", start_time); - - // Read the table mapping pointers to descriptions. - // First read its length. - items_read = fread(&object_table_size, sizeof(int), 1, trace_file); - if (items_read != 1) _LF_TRACE_FAILURE(trace_file); - - printf("There are %d objects traced.\n", object_table_size); - - object_table = calloc(object_table_size, sizeof(trace_record_t)); - if (object_table == NULL) { - fprintf(stderr, "ERROR: Memory allocation failure %d.\n", errno); - return -1; - } - - // Next, read each table entry. - for (int i = 0; i < object_table_size; i++) { - void* reactor; - items_read = fread(&reactor, sizeof(void*), 1, trace_file); - if (items_read != 1) _LF_TRACE_FAILURE(trace_file); - object_table[i].pointer = reactor; - - void* trigger; - items_read = fread(&trigger, sizeof(trigger_t*), 1, trace_file); - if (items_read != 1) _LF_TRACE_FAILURE(trace_file); - object_table[i].trigger = trigger; - - // Next, read the type. - _lf_trace_object_t trace_type; - items_read = fread(&trace_type, sizeof(_lf_trace_object_t), 1, trace_file); - if (items_read != 1) _LF_TRACE_FAILURE(trace_file); - object_table[i].type = trace_type; - - // Next, read the string description into the buffer. - int description_length = 0; - char character; - items_read = fread(&character, sizeof(char), 1, trace_file); - if (items_read != 1) _LF_TRACE_FAILURE(trace_file); - while(character != 0 && description_length < BUFFER_SIZE - 1) { - buffer[description_length++] = character; - items_read = fread(&character, sizeof(char), 1, trace_file); - if (items_read != 1) _LF_TRACE_FAILURE(trace_file); - } - // Terminate with null. - buffer[description_length++] = 0; - - // Allocate memory to store the description. - object_table[i].description = malloc(description_length); - strcpy(object_table[i].description, buffer); - - if (top_level == NULL) { - top_level = object_table[i].description; - } - } - print_table(); - return object_table_size; -} - -/** - * Read the trace from the specified file and put it in the trace global - * variable. Return the length of the trace. - * @return The number of trace record read or 0 upon seeing an EOF. - */ -int read_trace() { - // Read first the int giving the length of the trace. - int trace_length; - int items_read = fread(&trace_length, sizeof(int), 1, trace_file); - if (items_read != 1) { - if (feof(trace_file)) return 0; - fprintf(stderr, "Failed to read trace length.\n"); - exit(3); - } - if (trace_length > TRACE_BUFFER_CAPACITY) { - fprintf(stderr, "ERROR: Trace length %d exceeds capacity. File is garbled.\n", trace_length); - exit(4); - } - // printf("DEBUG: Trace of length %d being converted.\n", trace_length); - - items_read = fread(&trace, sizeof(trace_record_t), trace_length, trace_file); - if (items_read != trace_length) { - fprintf(stderr, "Failed to read trace of length %d.\n", trace_length); - exit(5); - } - return trace_length; -} diff --git a/util/tracing/trace_util.h b/util/tracing/trace_util.h deleted file mode 100644 index dab1f5e989..0000000000 --- a/util/tracing/trace_util.h +++ /dev/null @@ -1,134 +0,0 @@ -/** - * @file - * @author Edward A. Lee - * - * @section LICENSE -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. - - * @section DESCRIPTION - * Header file for common utilities used in converting Lingua Franca trace files - * into other formats. - */ -#define LF_TRACE -#include "reactor.h" -#include "trace.h" - -/** Macro to use when access to trace file fails. */ -#define _LF_TRACE_FAILURE(trace_file) \ - do { \ - fprintf(stderr, "ERROR: Access to trace file failed.\n"); \ - fclose(trace_file); \ - trace_file = NULL; \ - exit(1); \ - } while(0) - -/** Buffer for reading object descriptions. Size limit is BUFFER_SIZE bytes. */ -#define BUFFER_SIZE 1024 - -/** Buffer for reading trace records. */ -extern trace_record_t trace[]; - -/** File containing the trace binary data. */ -extern FILE* trace_file; - -/** File for writing the output data. */ -extern FILE* output_file; - -/** File for writing summary statistics. */ -extern FILE* summary_file; - -/** - * Print a usage message. - */ -void usage(); - -/** The start time read from the trace file. */ -extern instant_t start_time; - -/** Table of pointers to a description of the object. */ -extern object_description_t* object_table; -extern int object_table_size; - -/** Name of the top-level reactor (first entry in symbol table). */ -extern char* top_level; - -/** - * @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. - */ -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. - * For example, this can be the name of a reactor (pointer points to - * the self struct) or a user-defined string. - * If there is no such pointer in the symbol table, return NULL. - * If the index argument is non-null, then put the index - * of the entry in the table into the int pointed to - * or -1 if none was found. - * @param pointer The pointer to to an object, e.g. a self struct. - * @param index An optional pointer into which to write the index. - */ -char* get_object_description(void* reactor, int* index); - -/** - * Get the trigger name for the specified pointer. - * If there is no such trigger, return NULL. - * If the index argument is non-null, then put the index - * of the trigger in the table into the int pointed to - * or -1 if none was found. - * @param reactor The pointer to a self struct. - * @param index An optional pointer into which to write the index. - */ -char* get_trigger_name(void* trigger, int* index); - -/** - * Print the object to description table. - */ -void print_table(); - -/** - * Read header information. - * @return The number of objects in the object table or -1 for failure. - */ -size_t read_header(); - -/** - * Read the trace from the specified file and put it in the trace global - * variable. Return the length of the trace. - * @return The number of trace record read or 0 upon seeing an EOF. - */ -int read_trace(); diff --git a/util/tracing/visualization/.gitignore b/util/tracing/visualization/.gitignore deleted file mode 100644 index ba0430d26c..0000000000 --- a/util/tracing/visualization/.gitignore +++ /dev/null @@ -1 +0,0 @@ -__pycache__/ \ No newline at end of file diff --git a/util/tracing/visualization/README.md b/util/tracing/visualization/README.md deleted file mode 100644 index 4437540a6a..0000000000 --- a/util/tracing/visualization/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# Trace sequence diagram visualizer - -`fedsd` is a utility that reports the interactions (exchanged messages) -between federates and the RTI in a sequence-diagram-like format. - -To use `fedsd`, you need to first obtain an execution trace. To do this, enable the tracing mechanism in your Lingua Franca program by setting the `tracing` target property to `true` and then compile and run the program. - -This utility starts by transforming each `.lft` file into a `.csv` file, by -internally running `trace_to_csv`. It then aggregates the data from all `.csv` -files to do the matching and draw the sequence diagram. - -# Installing - -To build `fedsd`, change directory to `./util/tracing` relative to the root of the `lingua-franca` repository and run `make install`. - -# Running - -In case the federation is launched using the `bash` script under `bin`, an `.lft` trace -file will be generated for each of the federates, in addition to `rti.lft`. The latter -contains the RTI trace. - -If, however, the federation is launched manually, then running the `RTI` command should be passed the `-t` flag in order to make sure that it, too, has tracing enabled: -``` -$ RTI -n -t -``` - -It is most convenient to launch the RTI and all federates from the same working directory so that they will all write their trace file to that directory. - -Once the federation stopped executing, run `fedsd` on all generated `.lft` files: -``` -$ fedsd *.lft -``` - -The output is an html file named `trace_svg.html` (in the current directory) that contains the sequence of interactions -between the federates and the RTI. diff --git a/util/tracing/visualization/fedsd.py b/util/tracing/visualization/fedsd.py deleted file mode 100644 index 3480d5afb1..0000000000 --- a/util/tracing/visualization/fedsd.py +++ /dev/null @@ -1,331 +0,0 @@ -''' -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.') - -# Events matching at the sender and receiver ends depend on whether they are tagged -# (the elapsed logical time and microstep have to be the same) or not. -# Set of tagged events (messages) -non_tagged_messages = {'FED_ID', 'ACK', 'REJECT', 'ADR_RQ', 'ADR_AD', 'MSG', 'P2P_MSG'} - -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] - - # Fix the parameters of the event 'Scheduler advancing time ends' - # We rely on the fact that the first row of the csv file cannot be the end of advancing time - id = df.iloc[-1]['self_id'] - df['self_id'] = id - 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() - - # 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 - - # Set the RTI x coordinate - x_coor[-1] = padding * 2 - actors.append(-1) - actors_names[-1] = "RTI" - - trace_df = pd.DataFrame() - - ############################################################################ - #### 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 - try: - fed_df = load_and_process_csv_file(fed_trace) - except Exception as e: - print(f"Warning: Problem processing trace file {fed_trace}: `{e}`") - continue - - 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] - trace_df = pd.concat([trace_df, fed_df]) - fed_df = fed_df[0:0] - - - ############################################################################ - #### RTI trace processing, if any - ############################################################################ - if (exists(args.rti)): - rti_df = load_and_process_csv_file(args.rti) - rti_df['x1'] = x_coor[-1] - else: - # If there is no RTI, derive one. - # This is particularly useful for tracing enclaves - # FIXME: Currently, `fedsd` is used either for federates OR enclaves. - # As soon as there is a consensus on how to visualize federations where - # a federate has several enclves, the utility will be updated. - rti_df = trace_df[['event', 'self_id', 'partner_id', 'logical_time', 'microstep', 'physical_time', 'inout']].copy() - rti_df = rti_df[rti_df['event'].str.contains('AdvLT') == False] - rti_df.columns = ['event', 'partner_id', 'self_id', 'logical_time', 'microstep', 'physical_time', 'inout'] - rti_df['inout'] = rti_df['inout'].apply(lambda e: 'in' if 'out' in e else 'out') - rti_df['x1'] = rti_df['self_id'].apply(lambda e: x_coor[int(e)]) - - trace_df = pd.concat([trace_df, rti_df]) - - # 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) - - # 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 - # Depends on whether the event is tagged or not - if (trace_df.at[index,'event'] not in non_tagged_messages): - 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) \ - ] - else : - 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) - ] - - if (matching_df.empty) : - # If no matching receiver, than set the arrow to 'dot', - # meaning that only a dot will be rendered - trace_df.at[index, 'arrow'] = 'dot' - else: - # If there is one or more matching rows, then consider - # the first one - matching_index = matching_df.index[0] - matching_row = matching_df.loc[matching_index] - if (inout == 'out'): - trace_df.at[index, 'x2'] = matching_row['x1'] - trace_df.at[index, 'y2'] = matching_row['y1'] - else: - 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'] - 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'])) - if (row['inout'] in 'in'): - f.write(fhlp.svg_string_draw_side_label(row['x2'], row['y2'], physical_time, anchor)) - else: - 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 deleted file mode 100644 index 561429a184..0000000000 --- a/util/tracing/visualization/fedsd_helper.py +++ /dev/null @@ -1,255 +0,0 @@ -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 - ''' - - if (y2 != y1): - rotation = - math.ceil(math.atan((x2-x1)/(y2-y1)) * 180 / 3.14) - 90 - else: - if (x1 > x2): - rotation = 0 - else: - rotation = - 180 - - 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. - if (y2 != y1): - rotation = - math.ceil(math.atan((x2-x1)/(y2-y1)) * 180 / 3.14) - 90 - else: - rotation = 0 - - str_line = '\t'+label+'\n' - else : - # Right-going arrow. - if (y2 != y1): - rotation = - math.ceil(math.atan((x1-x2)/(y1-y2)) * 180 / 3.14) + 90 - else: - rotation = 0 - 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 793cebb6e42ba983355b2993d834e8322da18114 Mon Sep 17 00:00:00 2001 From: Erling Rennemo Jellum Date: Tue, 15 Aug 2023 18:49:44 +0200 Subject: [PATCH 394/516] Move zphyr run script and fedsd from util --- .../scripts/run-zephyr-tests.sh | 0 .github/workflows/c-zephyr-tests.yml | 6 +- util/scripts/launch-fedsd.sh | 102 ------------------ 3 files changed, 3 insertions(+), 105 deletions(-) rename util/RunZephyrTests.sh => .github/scripts/run-zephyr-tests.sh (100%) delete mode 100755 util/scripts/launch-fedsd.sh diff --git a/util/RunZephyrTests.sh b/.github/scripts/run-zephyr-tests.sh similarity index 100% rename from util/RunZephyrTests.sh rename to .github/scripts/run-zephyr-tests.sh diff --git a/.github/workflows/c-zephyr-tests.yml b/.github/workflows/c-zephyr-tests.yml index 0375c408a8..49c073d03b 100644 --- a/.github/workflows/c-zephyr-tests.yml +++ b/.github/workflows/c-zephyr-tests.yml @@ -42,17 +42,17 @@ jobs: - name: Run Zephyr smoke tests run: | ./gradlew core:integrationTest --tests org.lflang.tests.runtime.CZephyrTest.buildZephyr* core:integrationTestCodeCoverageReport - util/RunZephyrTests.sh test/C/src-gen + ./.github/scripts/run-zephyr-tests.sh test/C/src-gen rm -r test/C/src-gen - name: Run basic tests run: | ./gradlew core:integrationTest --tests org.lflang.tests.runtime.CZephyrTest.buildBasic* core:integrationTestCodeCoverageReport - util/RunZephyrTests.sh test/C/src-gen + ./.github/scripts/run-zephyr-tests.sh test/C/src-gen rm -r test/C/src-gen - name: Run concurrent tests run: | ./gradlew core:integrationTest --tests org.lflang.tests.runtime.CZephyrTest.buildConcurrent* core:integrationTestCodeCoverageReport - util/RunZephyrTests.sh test/C/src-gen + ./.github/scripts/run-zephyr-tests.sh test/C/src-gen rm -r test/C/src-gen - name: Report to CodeCov uses: ./.github/actions/report-code-coverage diff --git a/util/scripts/launch-fedsd.sh b/util/scripts/launch-fedsd.sh deleted file mode 100755 index 341e673324..0000000000 --- a/util/scripts/launch-fedsd.sh +++ /dev/null @@ -1,102 +0,0 @@ -#!/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 - ${base}/bin/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 [ ! -z $rti_csv_file ] -then - 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 From f5a58d7d417cc19018fc52bf14769d3c680f48f3 Mon Sep 17 00:00:00 2001 From: Erling Rennemo Jellum Date: Tue, 15 Aug 2023 18:49:59 +0200 Subject: [PATCH 395/516] Bump reactor-c --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 809c454db0..24b21d3451 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 809c454db07c1b5e03a789a4863137107b817da6 +Subproject commit 24b21d3451677a0229d0a068c8764537166179b8 From 3891436dbe002ed49fdb3efa4bfee063bbee256f Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun <78055940+byeong-gil@users.noreply.github.com> Date: Wed, 16 Aug 2023 10:58:00 +0900 Subject: [PATCH 396/516] Update package.json --- core/src/main/resources/lib/ts/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/ts/package.json b/core/src/main/resources/lib/ts/package.json index 4797fd56ed..fc063d791e 100644 --- a/core/src/main/resources/lib/ts/package.json +++ b/core/src/main/resources/lib/ts/package.json @@ -2,7 +2,7 @@ "name": "LinguaFrancaDefault", "type": "commonjs", "dependencies": { - "@lf-lang/reactor-ts": "git://github.com/lf-lang/reactor-ts.git#ts-cyclic-dependencies", + "@lf-lang/reactor-ts": "git://github.com/lf-lang/reactor-ts.git#master", "command-line-args": "^5.1.1", "command-line-usage": "^6.1.3" }, From 9e898ec3ced32771fd55550278c52ce07c4214d4 Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Wed, 16 Aug 2023 16:26:05 +0200 Subject: [PATCH 397/516] First prototype @layout annotation. --- .../main/java/org/lflang/AttributeUtils.java | 47 +++++++++++++++++++ .../synthesis/LinguaFrancaSynthesis.java | 26 +++++++++- .../org/lflang/validation/AttributeSpec.java | 5 ++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/AttributeUtils.java b/core/src/main/java/org/lflang/AttributeUtils.java index f611e92cd7..c10258abe8 100644 --- a/core/src/main/java/org/lflang/AttributeUtils.java +++ b/core/src/main/java/org/lflang/AttributeUtils.java @@ -27,8 +27,11 @@ import static org.lflang.ast.ASTUtils.factory; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; + import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.nodemodel.ICompositeNode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; @@ -101,6 +104,24 @@ public static Attribute findAttributeByName(EObject node, String name) { .orElse(null); } + /** + * Return the attributes with the given name. + * + * @throws IllegalArgumentException If the node cannot have attributes + */ + public static List findAttributesByName(EObject node, String name) { + List attrs = getAttributes(node); + if (!attrs.isEmpty()) { + System.out.println("Fun"); + } + return attrs.stream() + .filter( + it -> + it.getAttrName() + .equalsIgnoreCase(name)) // case-insensitive search (more user-friendly) + .toList(); + } + /** * Return the first argument specified for the attribute. * @@ -133,6 +154,24 @@ public static String getAttributeValue(EObject node, String attrName) { return value; } + /** + * Search for an attribute with the given name on the given AST node and return its first argument + * as a String. + * + *

This should only be used on attributes that are expected to have a single argument. + * + *

Returns null if the attribute is not found or if it does not have any arguments. + */ + public static Map getAttributeValues(EObject node, String attrName) { + final List attrs = findAttributesByName(node, attrName); + HashMap layoutOptions = new HashMap<>(); + for (Attribute attribute : attrs) { + layoutOptions.put(StringUtil.removeQuotes(attribute.getAttrParms().get(0).getValue()), + StringUtil.removeQuotes(attribute.getAttrParms().get(1).getValue())); + } + return layoutOptions; + } + /** * Retrieve a specific annotation in a comment associated with the given model element in the AST. * @@ -241,6 +280,14 @@ public static String getPortSide(EObject node) { return getAttributeValue(node, "side"); } + /** + * Return the {@code layout} annotation for the given element or null if there is + * no such annotation. + */ + public static Map getLayoutOption(EObject node) { + return getAttributeValues(node, "layout"); + } + /** * Return the {@code @enclave} attribute annotated on the given node. * diff --git a/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java b/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java index 272b483607..0d0b5b93ad 100644 --- a/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java +++ b/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java @@ -32,6 +32,7 @@ import de.cau.cs.kieler.klighd.DisplayedActionData; import de.cau.cs.kieler.klighd.Klighd; import de.cau.cs.kieler.klighd.SynthesisOption; +import de.cau.cs.kieler.klighd.kgraph.EMapPropertyHolder; import de.cau.cs.kieler.klighd.kgraph.KEdge; import de.cau.cs.kieler.klighd.kgraph.KLabel; import de.cau.cs.kieler.klighd.kgraph.KNode; @@ -68,12 +69,16 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; + import javax.inject.Inject; + import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.elk.alg.layered.options.LayerConstraint; import org.eclipse.elk.alg.layered.options.LayeredOptions; import org.eclipse.elk.alg.layered.options.NodePlacementStrategy; +import org.eclipse.elk.core.data.LayoutMetaDataService; +import org.eclipse.elk.core.data.LayoutOptionData; import org.eclipse.elk.core.math.ElkMargin; import org.eclipse.elk.core.math.ElkPadding; import org.eclipse.elk.core.math.KVector; @@ -159,6 +164,9 @@ public class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { // ------------------------------------------------------------------------- + /** Service class for accessing layout options by name */ + private static final LayoutMetaDataService LAYOUT_OPTIONS_SERVICE = LayoutMetaDataService.getInstance(); + public static final String ID = "org.lflang.diagram.synthesis.LinguaFrancaSynthesis"; // -- INTERNAL -- @@ -732,7 +740,7 @@ private Collection createReactorNode( nodes.add(errNode); } } - + setAnnotatedLayoutOptions(reactor, node); return nodes; } @@ -1722,4 +1730,20 @@ private Iterable createUserComments(EObject element, KNode targetNode) { } return List.of(); } + + /** + * Searches the "@layout" annotations and applies them to the corresponding element. + * + * @param kgraphElement The view model element to apply the layout options to, e.g. a KNode. + * @param modelElement The model element that has the annotations, e.g. a reactor. + */ + private void setAnnotatedLayoutOptions(EObject modelElement, EMapPropertyHolder kgraphElement) { + Map options = AttributeUtils.getLayoutOption(modelElement); + for (String key : options.keySet()) { + LayoutOptionData data = LAYOUT_OPTIONS_SERVICE.getOptionDataBySuffix(key); + if (data != null) { + kgraphElement.setProperty(data, data.parseValue(options.get(key))); + } + } + } } diff --git a/core/src/main/java/org/lflang/validation/AttributeSpec.java b/core/src/main/java/org/lflang/validation/AttributeSpec.java index 90e7f9cf6d..1df9a0c6ef 100644 --- a/core/src/main/java/org/lflang/validation/AttributeSpec.java +++ b/core/src/main/java/org/lflang/validation/AttributeSpec.java @@ -213,6 +213,11 @@ enum AttrParamType { ATTRIBUTE_SPECS_BY_NAME.put( "side", new AttributeSpec(List.of(new AttrParamSpec(VALUE_ATTR, AttrParamType.STRING, false)))); + // @layout("string", "any") e.g. @layout("port.side", "WEST") + ATTRIBUTE_SPECS_BY_NAME.put( + "layout", + new AttributeSpec(List.of(new AttrParamSpec(VALUE_ATTR, AttrParamType.STRING, false), + new AttrParamSpec(VALUE_ATTR, AttrParamType.STRING, false)))); // @enclave(each=boolean) ATTRIBUTE_SPECS_BY_NAME.put( "enclave", From 6de9d85b425e050e26bc4005c9aaaa09334f27e5 Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Wed, 16 Aug 2023 17:18:25 +0200 Subject: [PATCH 398/516] Added annotation for all annotatable elements. --- .../lflang/diagram/synthesis/LinguaFrancaSynthesis.java | 9 +++++++-- .../main/java/org/lflang/validation/AttributeSpec.java | 5 +++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java b/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java index 0d0b5b93ad..d10597f44a 100644 --- a/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java +++ b/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java @@ -491,6 +491,7 @@ private Collection createReactorNode( Iterables.addAll(nodes, createUserComments(reactor, node)); configureReactorNodeLayout(node, true); _layoutPostProcessing.configureMainReactor(node); + setAnnotatedLayoutOptions(reactor, node); } else { ReactorInstance instance = reactorInstance; @@ -731,6 +732,7 @@ private Collection createReactorNode( } configureReactorNodeLayout(node, false); _layoutPostProcessing.configureReactor(node); + setAnnotatedLayoutOptions(reactor, node); } // Find and annotate cycles @@ -740,7 +742,6 @@ private Collection createReactorNode( nodes.add(errNode); } } - setAnnotatedLayoutOptions(reactor, node); return nodes; } @@ -1043,6 +1044,7 @@ private Collection transformReactorNetwork( timerNodes.put(timer, node); _linguaFrancaShapeExtensions.addTimerFigure(node, timer); _layoutPostProcessing.configureTimer(node); + setAnnotatedLayoutOptions(timer.getDefinition(), node); } // Create reactions @@ -1057,6 +1059,7 @@ private Collection transformReactorNetwork( setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); _layoutPostProcessing.configureReaction(node); + setAnnotatedLayoutOptions(reaction.getDefinition(), node); setLayoutOption( node, LayeredOptions.POSITION, @@ -1210,6 +1213,7 @@ private Collection transformReactorNetwork( Iterables.addAll(nodes, createUserComments(action.getDefinition(), node)); setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); _layoutPostProcessing.configureAction(node); + setAnnotatedLayoutOptions(action.getDefinition(), node); Pair ports = _linguaFrancaShapeExtensions.addActionFigureAndPorts( node, action.isPhysical() ? "P" : "L"); @@ -1666,6 +1670,7 @@ private KPort addIOPort( } } associateWith(_kLabelExtensions.addOutsidePortLabel(port, label, 8), lfPort.getDefinition()); + setAnnotatedLayoutOptions(lfPort.getDefinition(), port); return port; } @@ -1737,7 +1742,7 @@ private Iterable createUserComments(EObject element, KNode targetNode) { * @param kgraphElement The view model element to apply the layout options to, e.g. a KNode. * @param modelElement The model element that has the annotations, e.g. a reactor. */ - private void setAnnotatedLayoutOptions(EObject modelElement, EMapPropertyHolder kgraphElement) { + public void setAnnotatedLayoutOptions(EObject modelElement, EMapPropertyHolder kgraphElement) { Map options = AttributeUtils.getLayoutOption(modelElement); for (String key : options.keySet()) { LayoutOptionData data = LAYOUT_OPTIONS_SERVICE.getOptionDataBySuffix(key); diff --git a/core/src/main/java/org/lflang/validation/AttributeSpec.java b/core/src/main/java/org/lflang/validation/AttributeSpec.java index 1df9a0c6ef..6d1da3e0a3 100644 --- a/core/src/main/java/org/lflang/validation/AttributeSpec.java +++ b/core/src/main/java/org/lflang/validation/AttributeSpec.java @@ -50,6 +50,7 @@ public class AttributeSpec { public static final String VALUE_ATTR = "value"; public static final String NETWORK_MESSAGE_ACTIONS = "network_message_actions"; public static final String EACH_ATTR = "each"; + public static final String OPTION_ATTR = "option"; /** A map from a string to a supported AttributeSpec */ public static final Map ATTRIBUTE_SPECS_BY_NAME = new HashMap<>(); @@ -213,10 +214,10 @@ enum AttrParamType { ATTRIBUTE_SPECS_BY_NAME.put( "side", new AttributeSpec(List.of(new AttrParamSpec(VALUE_ATTR, AttrParamType.STRING, false)))); - // @layout("string", "any") e.g. @layout("port.side", "WEST") + // @layout(option="string", value="any") e.g. @layout(option="port.side", value="WEST") ATTRIBUTE_SPECS_BY_NAME.put( "layout", - new AttributeSpec(List.of(new AttrParamSpec(VALUE_ATTR, AttrParamType.STRING, false), + new AttributeSpec(List.of(new AttrParamSpec(OPTION_ATTR, AttrParamType.STRING, false), new AttrParamSpec(VALUE_ATTR, AttrParamType.STRING, false)))); // @enclave(each=boolean) ATTRIBUTE_SPECS_BY_NAME.put( From 6ddb1c7c2e0efc32f3ea4ce2a926fe6b7a4dc350 Mon Sep 17 00:00:00 2001 From: Erling Rennemo Jellum Date: Wed, 16 Aug 2023 17:32:00 +0200 Subject: [PATCH 399/516] Move CI testing of trace tools to reactor-c --- .github/workflows/all-misc.yml | 8 ------ .github/workflows/build-trace-tools.yml | 26 ------------------- .../non-target-specific-extended.yml | 7 ----- core/src/main/resources/lib/c/reactor-c | 2 +- 4 files changed, 1 insertion(+), 42 deletions(-) delete mode 100644 .github/workflows/build-trace-tools.yml diff --git a/.github/workflows/all-misc.yml b/.github/workflows/all-misc.yml index 0cbdd2a21d..90ff0eebfd 100644 --- a/.github/workflows/all-misc.yml +++ b/.github/workflows/all-misc.yml @@ -30,14 +30,6 @@ jobs: all-platforms: ${{ !github.event.pull_request.draft }} if: ${{ needs.check-diff.outputs.run_build == 'true' }} - # Build the tools used for processing execution traces - tracing: - needs: check-diff - if: ${{ needs.check-diff.outputs.run_tracing == 'true' }} - uses: ./.github/workflows/build-trace-tools.yml - with: - all-platforms: ${{ !github.event.pull_request.draft }} - # Run tests for the standalone compiler. cli: if: ${{ needs.check-diff.outputs.run_misc == 'true' }} diff --git a/.github/workflows/build-trace-tools.yml b/.github/workflows/build-trace-tools.yml deleted file mode 100644 index d451b3786c..0000000000 --- a/.github/workflows/build-trace-tools.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Build trace tools - -on: - workflow_call: - inputs: - all-platforms: - required: false - default: true - type: boolean - -jobs: - build-trace-tools: - strategy: - matrix: - platform: ${{ (inputs.all-platforms && fromJSON('["ubuntu-latest", "macos-latest", "windows-latest"]')) || fromJSON('["ubuntu-latest"]') }} - runs-on: ${{ matrix.platform }} - steps: - - name: Check out lingua-franca repository - uses: actions/checkout@v3 - with: - fetch-depth: 0 - submodules: recursive - - name: Run make - working-directory: ./util/tracing - run: make - shell: bash diff --git a/.github/workflows/non-target-specific-extended.yml b/.github/workflows/non-target-specific-extended.yml index 465681d18d..216e667364 100644 --- a/.github/workflows/non-target-specific-extended.yml +++ b/.github/workflows/non-target-specific-extended.yml @@ -24,13 +24,6 @@ jobs: with: all-platforms: ${{ !github.event.pull_request.draft }} - # Build the tools used for processing execution traces - tracing: - if: ${{ inputs.all || github.event.pull_request.draft }} - uses: ./.github/workflows/build-trace-tools.yml - with: - all-platforms: ${{ !github.event.pull_request.draft }} - # Run tests for the standalone compiler. cli: if: ${{ inputs.all || github.event.pull_request.draft }} diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 24b21d3451..4e47b354bc 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 24b21d3451677a0229d0a068c8764537166179b8 +Subproject commit 4e47b354bce833034faaad0567a65d5b60e60ccc From b2e9b91759a51dc79c8dda76c9361150ef09bef7 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 17 Aug 2023 13:50:39 -0700 Subject: [PATCH 400/516] Closes #1942. --- .../org/lflang/federated/generator/FedASTUtils.java | 11 +++++++++-- .../org/lflang/federated/generator/FedGenerator.java | 6 +++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java index d38237c71b..5dfcc74368 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java +++ b/core/src/main/java/org/lflang/federated/generator/FedASTUtils.java @@ -657,8 +657,15 @@ private static Reactor getNetworkSenderReactor( Input in = factory.createInput(); in.setName("msg"); in.setType(type); - in.setWidthSpec( - EcoreUtil.copy(connection.getSourcePortInstance().getDefinition().getWidthSpec())); + var width = + ASTUtils.width( + connection.getSourcePortInstance().getDefinition().getWidthSpec(), + List.of(connection.getSrcFederate().instantiation)); + var widthSpec = factory.createWidthSpec(); + var widthTerm = factory.createWidthTerm(); + widthTerm.setWidth(width); + widthSpec.getTerms().add(widthTerm); + in.setWidthSpec(widthSpec); inRef.setVariable(in); destRef.setContainer(connection.getDestinationPortInstance().getParent().getDefinition()); diff --git a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java index ea1d960e0a..f885daabdf 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java +++ b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java @@ -553,7 +553,11 @@ private Reactor indexer(ReactorInstance reactorInstance, PortInstance input, Res FedASTUtils.addReactorDefinition( "_" + reactorInstance.getName() + input.getName(), resource); var output = LfFactory.eINSTANCE.createOutput(); - output.setWidthSpec(EcoreUtil.copy(input.getDefinition().getWidthSpec())); + var widthSpec = LfFactory.eINSTANCE.createWidthSpec(); + var widthTerm = LfFactory.eINSTANCE.createWidthTerm(); + widthTerm.setWidth(input.getWidth()); + widthSpec.getTerms().add(widthTerm); + output.setWidthSpec(widthSpec); output.setType(EcoreUtil.copy(input.getDefinition().getType())); output.setName("port"); indexer.getOutputs().add(output); From 6eba8bb56a5faf5a419ac768a28f2289dda41302 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 17 Aug 2023 14:56:44 -0700 Subject: [PATCH 401/516] Fix after delays' access to user's declarations. --- .../ast/DelayedConnectionTransformation.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/lflang/ast/DelayedConnectionTransformation.java b/core/src/main/java/org/lflang/ast/DelayedConnectionTransformation.java index 882019ac44..4576be6ff5 100644 --- a/core/src/main/java/org/lflang/ast/DelayedConnectionTransformation.java +++ b/core/src/main/java/org/lflang/ast/DelayedConnectionTransformation.java @@ -102,7 +102,11 @@ private void insertGeneratedDelays(List reactors) { EObject parent = connection.eContainer(); // Assume all the types are the same, so just use the first on the right. Type type = ((Port) connection.getRightPorts().get(0).getVariable()).getType(); - Reactor delayClass = getDelayClass(type, connection.isPhysical()); + var resource = + connection.getLeftPorts().size() > 0 + ? connection.getLeftPorts().get(0).getContainer().getReactorClass().eResource() + : mainResource; + Reactor delayClass = getDelayClass(resource, type, connection.isPhysical()); String generic = targetTypes.supportsGenerics() ? targetTypes.getTargetType(type) : null; Instantiation delayInstance = @@ -274,7 +278,7 @@ private static Instantiation getDelayInstance( * @param type The type the delay class must be compatible with. * @param isPhysical Is this delay reactor using a physical action. */ - private Reactor getDelayClass(Type type, boolean isPhysical) { + private Reactor getDelayClass(Resource resource, Type type, boolean isPhysical) { String className; if (targetTypes.supportsGenerics()) { className = DelayBodyGenerator.GEN_DELAY_CLASS_NAME; @@ -381,7 +385,7 @@ private Reactor getDelayClass(Type type, boolean isPhysical) { delayClass.getInputs().add(input); delayClass.getOutputs().add(output); delayClass.getParameters().add(delayParameter); - addDelayClass(delayClass); + addDelayClass(resource, delayClass); return delayClass; } @@ -389,12 +393,11 @@ private Reactor getDelayClass(Type type, boolean isPhysical) { * Store the given reactor in the collection of generated delay classes and insert it in the AST * under the top-level reactor's node. */ - private void addDelayClass(Reactor generatedDelay) { + private void addDelayClass(Resource resource, Reactor generatedDelay) { // Record this class, so it can be reused. delayClasses.add(generatedDelay); // And hook it into the AST. - EObject node = - IteratorExtensions.findFirst(mainResource.getAllContents(), Model.class::isInstance); + EObject node = IteratorExtensions.findFirst(resource.getAllContents(), Model.class::isInstance); ((Model) node).getReactors().add(generatedDelay); } From e7f5284c5e26c67b398db14fa8ead0d755a3ac97 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 17 Aug 2023 15:23:39 -0700 Subject: [PATCH 402/516] Fix unused multiports bug (#1957). --- .../org/lflang/generator/c/CTriggerObjectsGenerator.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java b/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java index 4d1124a3b9..77e7eee22e 100644 --- a/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java @@ -785,10 +785,13 @@ private static String deferredOutputNumDestinations(ReactorInstance reactor) { if (output.eventualDestinations().size() == 0) { // Dangling output. Still set the source reactor code.pr( - CUtil.portRef(output) + "for (int index486184027c8990b = 0; index486184027c8990b < " + + output.getWidth() + + "; index486184027c8990b++) { " + + CUtil.portRef(output, false, true, null, null, "index486184027c8990b") + "._base.source_reactor = (self_base_t*)" + CUtil.reactorRef(reactor) - + ";"); + + "; }"); } } return code.toString(); From a95f66a547557c15db0f4d4d81dbe1cc054e5854 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 17 Aug 2023 15:28:28 -0700 Subject: [PATCH 403/516] Fix NPE. --- .../java/org/lflang/ast/DelayedConnectionTransformation.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/org/lflang/ast/DelayedConnectionTransformation.java b/core/src/main/java/org/lflang/ast/DelayedConnectionTransformation.java index 4576be6ff5..dba3fdeaf7 100644 --- a/core/src/main/java/org/lflang/ast/DelayedConnectionTransformation.java +++ b/core/src/main/java/org/lflang/ast/DelayedConnectionTransformation.java @@ -104,6 +104,7 @@ private void insertGeneratedDelays(List reactors) { Type type = ((Port) connection.getRightPorts().get(0).getVariable()).getType(); var resource = connection.getLeftPorts().size() > 0 + && connection.getLeftPorts().get(0).getContainer() != null ? connection.getLeftPorts().get(0).getContainer().getReactorClass().eResource() : mainResource; Reactor delayClass = getDelayClass(resource, type, connection.isPhysical()); From 9f04e2464ee9e9ab6ceea14526ddbcc2229ee119 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 17 Aug 2023 15:40:11 -0700 Subject: [PATCH 404/516] Add test. --- test/C/src/GenDelayTest.lf | 13 +++++++++++++ test/C/src/lib/GenDelay.lf | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 test/C/src/GenDelayTest.lf create mode 100644 test/C/src/lib/GenDelay.lf diff --git a/test/C/src/GenDelayTest.lf b/test/C/src/GenDelayTest.lf new file mode 100644 index 0000000000..ecd7f095ab --- /dev/null +++ b/test/C/src/GenDelayTest.lf @@ -0,0 +1,13 @@ +/** + * Test that types used in delayed connections do not need to be in scope for + * the parent reactor in order to compile. + */ + +target C +import Source, Sink from "lib/GenDelay.lf" + +main reactor { + source = new Source() + sink = new Sink() + source.out -> sink.in after 10 ms +} diff --git a/test/C/src/lib/GenDelay.lf b/test/C/src/lib/GenDelay.lf new file mode 100644 index 0000000000..dfe7195de4 --- /dev/null +++ b/test/C/src/lib/GenDelay.lf @@ -0,0 +1,19 @@ +target C + +preamble {= + typedef int message_t; +=} + +reactor Source { + output out: message_t + reaction(startup) -> out {= + lf_set(out, 42); + =} +} + +reactor Sink { + input in: message_t + reaction(in) {= + lf_print("Received %d at time %lld", in->value, lf_time_logical_elapsed()); + =} +} From 49fae5a9cca4da41d173743fb50452694188ab52 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 17 Aug 2023 15:42:26 -0700 Subject: [PATCH 405/516] Make test more comprehensive. --- test/C/src/federated/DistributedMultiport.lf | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/C/src/federated/DistributedMultiport.lf b/test/C/src/federated/DistributedMultiport.lf index fcd570d890..018a286360 100644 --- a/test/C/src/federated/DistributedMultiport.lf +++ b/test/C/src/federated/DistributedMultiport.lf @@ -4,8 +4,8 @@ target C { coordination: centralized } -reactor Source { - output[4] out: int +reactor Source(width: int = 2) { + output[width] out: int timer t(0, 100 msec) state count: int = 0 @@ -16,8 +16,8 @@ reactor Source { =} } -reactor Destination { - input[4] in: int +reactor Destination(width: int = 3) { + input[width] in: int state count: int = 0 reaction(in) {= @@ -39,7 +39,7 @@ reactor Destination { } federated reactor DistributedMultiport { - s = new Source() - d = new Destination() + s = new Source(width = 4) + d = new Destination(width = 4) s.out -> d.in } From 71e419a1ab716f0eb7310fbb8fc0d5048e8ae56a Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 18 Aug 2023 15:38:36 -0700 Subject: [PATCH 406/516] Format tests. --- test/C/src/GenDelayTest.lf | 6 +++--- test/C/src/lib/GenDelay.lf | 14 +++++--------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/test/C/src/GenDelayTest.lf b/test/C/src/GenDelayTest.lf index ecd7f095ab..6572aef829 100644 --- a/test/C/src/GenDelayTest.lf +++ b/test/C/src/GenDelayTest.lf @@ -1,9 +1,9 @@ /** - * Test that types used in delayed connections do not need to be in scope for - * the parent reactor in order to compile. + * Test that types used in delayed connections do not need to be in scope for the parent reactor in + * order to compile. */ - target C + import Source, Sink from "lib/GenDelay.lf" main reactor { diff --git a/test/C/src/lib/GenDelay.lf b/test/C/src/lib/GenDelay.lf index dfe7195de4..a9d607db75 100644 --- a/test/C/src/lib/GenDelay.lf +++ b/test/C/src/lib/GenDelay.lf @@ -1,19 +1,15 @@ target C -preamble {= - typedef int message_t; -=} +preamble {= typedef int message_t; =} reactor Source { output out: message_t - reaction(startup) -> out {= - lf_set(out, 42); - =} + + reaction(startup) -> out {= lf_set(out, 42); =} } reactor Sink { input in: message_t - reaction(in) {= - lf_print("Received %d at time %lld", in->value, lf_time_logical_elapsed()); - =} + + reaction(in) {= lf_print("Received %d at time %lld", in->value, lf_time_logical_elapsed()); =} } From 9e8fc2d8ced0c212fe05a345a80905dd64268926 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 18 Aug 2023 15:40:50 -0700 Subject: [PATCH 407/516] Format. --- test/C/src/federated/DistributedMultiport.lf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/C/src/federated/DistributedMultiport.lf b/test/C/src/federated/DistributedMultiport.lf index 018a286360..0d786e8357 100644 --- a/test/C/src/federated/DistributedMultiport.lf +++ b/test/C/src/federated/DistributedMultiport.lf @@ -39,7 +39,7 @@ reactor Destination(width: int = 3) { } federated reactor DistributedMultiport { - s = new Source(width = 4) - d = new Destination(width = 4) + s = new Source(width=4) + d = new Destination(width=4) s.out -> d.in } From d357aae6fdcb311387d63c3a70bd58008726874f Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 18 Aug 2023 18:18:48 -0700 Subject: [PATCH 408/516] Address width mismatch warning. --- .../src/docker/federated/DistributedMultiportContainerized.lf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/C/src/docker/federated/DistributedMultiportContainerized.lf b/test/C/src/docker/federated/DistributedMultiportContainerized.lf index ec27e0dea3..6250c105c4 100644 --- a/test/C/src/docker/federated/DistributedMultiportContainerized.lf +++ b/test/C/src/docker/federated/DistributedMultiportContainerized.lf @@ -8,7 +8,7 @@ target C { import Source, Destination from "../../federated/DistributedMultiport.lf" federated reactor DistributedMultiportContainerized at rti { - s = new Source() - d = new Destination() + s = new Source(width=4) + d = new Destination(width=4) s.out -> d.in } From 432ac5e19ebb27d1d98e86b9ddda1604d4f1241b Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Tue, 22 Aug 2023 15:13:13 -0700 Subject: [PATCH 409/516] Apply suggestions from code review --- core/src/main/resources/lib/ts/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/ts/package.json b/core/src/main/resources/lib/ts/package.json index fc063d791e..04978d7322 100644 --- a/core/src/main/resources/lib/ts/package.json +++ b/core/src/main/resources/lib/ts/package.json @@ -2,7 +2,7 @@ "name": "LinguaFrancaDefault", "type": "commonjs", "dependencies": { - "@lf-lang/reactor-ts": "git://github.com/lf-lang/reactor-ts.git#master", + "@lf-lang/reactor-ts": "^0.6.0", "command-line-args": "^5.1.1", "command-line-usage": "^6.1.3" }, From b28c61959a4053ba44b7cd06f3a5ed37cbbc3627 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 22 Aug 2023 20:32:32 -0700 Subject: [PATCH 410/516] Bugfix for TypeScript. --- .../java/org/lflang/federated/extensions/TSExtension.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java index e62d1c29d1..f0000b7435 100644 --- a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java @@ -109,8 +109,8 @@ public String generateNetworkSenderBody( CoordinationType coordinationType, MessageReporter messageReporter) { return """ - if (%1$s%2$s !== undefined) { - this.util.sendRTITimedMessage(%1$s%2$s, %3$s, %4$s, %5$s); + if (%1$s%2$s[sender_index as number] !== undefined) { + this.util.sendRTITimedMessage(%1$s%2$s[sender_index as number], %3$s, %4$s, %5$s); } """ .formatted( From 24ed790df18681b1da39c33dcd23772ada1bba8f Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 22 Aug 2023 20:50:43 -0700 Subject: [PATCH 411/516] Generate one federate per federate. Fixes #1961. --- .../java/org/lflang/federated/generator/FedMainEmitter.java | 5 ++++- test/C/src/federated/DistributedBank.lf | 5 ++++- test/TypeScript/src/federated/DistributedCount.lf | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java index 7c78a10df7..f6e8f6e9c8 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java @@ -3,6 +3,7 @@ import java.util.function.Function; import java.util.stream.Collectors; import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.util.EcoreUtil; import org.lflang.MessageReporter; import org.lflang.ast.ASTUtils; import org.lflang.ast.FormattingUtil; @@ -27,13 +28,15 @@ String generateMainReactor( .error("Modes at the top level are not supported under federated execution."); } var renderer = FormattingUtil.renderer(federate.targetConfig.target); + var instantiation = EcoreUtil.copy(federate.instantiation); + instantiation.setWidthSpec(null); return String.join( "\n", generateMainSignature(federate, originalMainReactor, renderer), String.join( "\n", - renderer.apply(federate.instantiation), + renderer.apply(instantiation), ASTUtils.allStateVars(originalMainReactor).stream() .map(renderer) .collect(Collectors.joining("\n")), diff --git a/test/C/src/federated/DistributedBank.lf b/test/C/src/federated/DistributedBank.lf index 130ae95961..bb23df81d6 100644 --- a/test/C/src/federated/DistributedBank.lf +++ b/test/C/src/federated/DistributedBank.lf @@ -4,13 +4,16 @@ target C { coordination: centralized } -reactor Node { +reactor Node(bank_index: int = 0) { timer t(0, 100 msec) state count: int = 0 reaction(t) {= lf_print("Hello world %d.", self->count++); =} reaction(shutdown) {= + if (self->bank_index) { + lf_print_error_and_exit("The only bank index should be zero because there should be only one bank member per federate."); + } if (self->count == 0) { lf_print_error_and_exit("Timer reactions did not execute."); } diff --git a/test/TypeScript/src/federated/DistributedCount.lf b/test/TypeScript/src/federated/DistributedCount.lf index 49ccfafc09..d57e96d024 100644 --- a/test/TypeScript/src/federated/DistributedCount.lf +++ b/test/TypeScript/src/federated/DistributedCount.lf @@ -19,7 +19,7 @@ reactor Print { const elapsedTime = util.getElapsedLogicalTime(); console.log("At time " + elapsedTime + ", received " + inp); if (inp !== c) { - util.requestErrorStop("Expected to receive " + c + "."); + util.requestErrorStop("Expected to receive " + JSON.stringify(c) + " but received " + JSON.stringify(inp) + "."); } if (!elapsedTime.isEqualTo(TimeValue.msec(200).add(TimeValue.sec(c - 1)))) { util.requestErrorStop("Expected received time to be " + TimeValue.msec(200).add(TimeValue.sec(c - 1)) + "."); From 8baa059bebb9469e2f8d89bed77b3fa943806e5e Mon Sep 17 00:00:00 2001 From: Alexander Schulz-Rosengarten Date: Wed, 23 Aug 2023 18:22:27 +0200 Subject: [PATCH 412/516] Fixed compilation error in code for reset state variables with time type Fixes #1938 --- .../lflang/generator/c/CStateGenerator.java | 3 +- .../ResetStateVariableOfTypeTime.lf | 45 +++++++++++++++++++ ...esetStateVariableWithParameterizedValue.lf | 45 +++++++++++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 test/C/src/modal_models/ResetStateVariableOfTypeTime.lf create mode 100644 test/C/src/modal_models/ResetStateVariableWithParameterizedValue.lf diff --git a/core/src/main/java/org/lflang/generator/c/CStateGenerator.java b/core/src/main/java/org/lflang/generator/c/CStateGenerator.java index 225320bfe7..c8686173b6 100644 --- a/core/src/main/java/org/lflang/generator/c/CStateGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CStateGenerator.java @@ -88,8 +88,7 @@ private static String generateModalReset( + "]"; var type = types.getTargetType(instance.tpr.resolveType(ASTUtils.getInferredType(stateVar))); - if (ASTUtils.isOfTimeType(stateVar) - || ASTUtils.isParameterized(stateVar) && !stateVar.getInit().getExprs().isEmpty()) { + if (ASTUtils.isParameterized(stateVar) && !stateVar.getInit().getExprs().isEmpty()) { return CModesGenerator.generateStateResetStructure( instance, modeRef, selfRef, stateVar.getName(), initExpr, type); } else { diff --git a/test/C/src/modal_models/ResetStateVariableOfTypeTime.lf b/test/C/src/modal_models/ResetStateVariableOfTypeTime.lf new file mode 100644 index 0000000000..72e2075d2c --- /dev/null +++ b/test/C/src/modal_models/ResetStateVariableOfTypeTime.lf @@ -0,0 +1,45 @@ +/** + * Modal Reactor Test. + * Tests state variable with type time. + * https://github.com/lf-lang/lingua-franca/issues/1938 + * Model by Edward Lee. + */ +target C { + timeout: 3s, + fast: true +} + +reactor C { + input trigger:bool + reset state t:time = 0s + + reaction(trigger) {= + lf_print("t = %ld", self->t); + if (self->t != SEC(0)) { + lf_print("Error: Missing reset"); + } + + self->t = lf_time_logical(); + =} +} + +main reactor { + timer t(0, 1s) + + initial mode A { + c = new C() + + reaction(t) -> reset(B), c.trigger {= + lf_print("In A"); + lf_set(c.trigger, true); + lf_set_mode(B); + =} + } + + mode B { + reaction(t) -> reset(A) {= + lf_print("In B"); + lf_set_mode(A); + =} + } +} \ No newline at end of file diff --git a/test/C/src/modal_models/ResetStateVariableWithParameterizedValue.lf b/test/C/src/modal_models/ResetStateVariableWithParameterizedValue.lf new file mode 100644 index 0000000000..f7daaa8950 --- /dev/null +++ b/test/C/src/modal_models/ResetStateVariableWithParameterizedValue.lf @@ -0,0 +1,45 @@ +/** + * Modal Reactor Test. + * Tests state variable initialized via parameter. + * https://github.com/lf-lang/lingua-franca/issues/1938 + * Model by Edward Lee. + */ +target C { + timeout: 3s, + fast: true +} + +reactor C (init:int = 0) { + input trigger:bool + reset state i:int = init + + reaction(trigger) {= + lf_print("i = %d", self->i); + if (self->i != -1) { + lf_print("Error: Missing reset"); + } + + self->i += 10; + =} +} + +main reactor { + timer t(0, 1s) + + initial mode A { + c = new C(init = -1) + + reaction(t) -> reset(B), c.trigger {= + lf_print("In A"); + lf_set(c.trigger, true); + lf_set_mode(B); + =} + } + + mode B { + reaction(t) -> reset(A) {= + lf_print("In B"); + lf_set_mode(A); + =} + } +} \ No newline at end of file From abda76c8353aeb14300ea13c0bed52581cd10c9e Mon Sep 17 00:00:00 2001 From: Alexander Schulz-Rosengarten Date: Thu, 24 Aug 2023 09:29:16 +0200 Subject: [PATCH 413/516] Applied LF formatter --- .../ResetStateVariableOfTypeTime.lf | 27 +++++++--------- ...esetStateVariableWithParameterizedValue.lf | 31 +++++++++---------- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/test/C/src/modal_models/ResetStateVariableOfTypeTime.lf b/test/C/src/modal_models/ResetStateVariableOfTypeTime.lf index 72e2075d2c..f7d25c97d6 100644 --- a/test/C/src/modal_models/ResetStateVariableOfTypeTime.lf +++ b/test/C/src/modal_models/ResetStateVariableOfTypeTime.lf @@ -1,45 +1,42 @@ -/** - * Modal Reactor Test. - * Tests state variable with type time. +/** + * Modal Reactor Test. Tests state variable with type time. Model by Edward Lee. * https://github.com/lf-lang/lingua-franca/issues/1938 - * Model by Edward Lee. */ target C { - timeout: 3s, - fast: true + timeout: 3 s, + fast: true } reactor C { - input trigger:bool - reset state t:time = 0s - + input trigger: bool + reset state t: time = 0 s + reaction(trigger) {= lf_print("t = %ld", self->t); if (self->t != SEC(0)) { lf_print("Error: Missing reset"); } - + self->t = lf_time_logical(); =} } main reactor { - timer t(0, 1s) - + timer t(0, 1 s) + initial mode A { c = new C() - reaction(t) -> reset(B), c.trigger {= lf_print("In A"); lf_set(c.trigger, true); lf_set_mode(B); =} } - + mode B { reaction(t) -> reset(A) {= lf_print("In B"); lf_set_mode(A); =} } -} \ No newline at end of file +} diff --git a/test/C/src/modal_models/ResetStateVariableWithParameterizedValue.lf b/test/C/src/modal_models/ResetStateVariableWithParameterizedValue.lf index f7daaa8950..3e0096e5e0 100644 --- a/test/C/src/modal_models/ResetStateVariableWithParameterizedValue.lf +++ b/test/C/src/modal_models/ResetStateVariableWithParameterizedValue.lf @@ -1,45 +1,42 @@ -/** - * Modal Reactor Test. - * Tests state variable initialized via parameter. +/** + * Modal Reactor Test. Tests state variable initialized via parameter. Model by Edward Lee. * https://github.com/lf-lang/lingua-franca/issues/1938 - * Model by Edward Lee. */ target C { - timeout: 3s, - fast: true + timeout: 3 s, + fast: true } -reactor C (init:int = 0) { - input trigger:bool - reset state i:int = init - +reactor C(init: int = 0) { + input trigger: bool + reset state i: int = init + reaction(trigger) {= lf_print("i = %d", self->i); if (self->i != -1) { lf_print("Error: Missing reset"); } - + self->i += 10; =} } main reactor { - timer t(0, 1s) - + timer t(0, 1 s) + initial mode A { - c = new C(init = -1) - + c = new C(init=-1) reaction(t) -> reset(B), c.trigger {= lf_print("In A"); lf_set(c.trigger, true); lf_set_mode(B); =} } - + mode B { reaction(t) -> reset(A) {= lf_print("In B"); lf_set_mode(A); =} } -} \ No newline at end of file +} From 7e7554e644e904f8208bb7ef2cc51effe45d1d9a Mon Sep 17 00:00:00 2001 From: Alexander Schulz-Rosengarten Date: Thu, 24 Aug 2023 09:31:33 +0200 Subject: [PATCH 414/516] Added newline --- test/C/src/modal_models/ResetStateVariableOfTypeTime.lf | 1 + .../src/modal_models/ResetStateVariableWithParameterizedValue.lf | 1 + 2 files changed, 2 insertions(+) diff --git a/test/C/src/modal_models/ResetStateVariableOfTypeTime.lf b/test/C/src/modal_models/ResetStateVariableOfTypeTime.lf index f7d25c97d6..0b5f40df23 100644 --- a/test/C/src/modal_models/ResetStateVariableOfTypeTime.lf +++ b/test/C/src/modal_models/ResetStateVariableOfTypeTime.lf @@ -40,3 +40,4 @@ main reactor { =} } } + diff --git a/test/C/src/modal_models/ResetStateVariableWithParameterizedValue.lf b/test/C/src/modal_models/ResetStateVariableWithParameterizedValue.lf index 3e0096e5e0..e3081a4d0a 100644 --- a/test/C/src/modal_models/ResetStateVariableWithParameterizedValue.lf +++ b/test/C/src/modal_models/ResetStateVariableWithParameterizedValue.lf @@ -40,3 +40,4 @@ main reactor { =} } } + From 9ec756ddae63de7b43c0444e929d8379183de5b0 Mon Sep 17 00:00:00 2001 From: Alexander Schulz-Rosengarten Date: Thu, 24 Aug 2023 11:09:30 +0200 Subject: [PATCH 415/516] Revert "Added newline" This reverts commit 7e7554e644e904f8208bb7ef2cc51effe45d1d9a. --- test/C/src/modal_models/ResetStateVariableOfTypeTime.lf | 1 - .../src/modal_models/ResetStateVariableWithParameterizedValue.lf | 1 - 2 files changed, 2 deletions(-) diff --git a/test/C/src/modal_models/ResetStateVariableOfTypeTime.lf b/test/C/src/modal_models/ResetStateVariableOfTypeTime.lf index 0b5f40df23..f7d25c97d6 100644 --- a/test/C/src/modal_models/ResetStateVariableOfTypeTime.lf +++ b/test/C/src/modal_models/ResetStateVariableOfTypeTime.lf @@ -40,4 +40,3 @@ main reactor { =} } } - diff --git a/test/C/src/modal_models/ResetStateVariableWithParameterizedValue.lf b/test/C/src/modal_models/ResetStateVariableWithParameterizedValue.lf index e3081a4d0a..3e0096e5e0 100644 --- a/test/C/src/modal_models/ResetStateVariableWithParameterizedValue.lf +++ b/test/C/src/modal_models/ResetStateVariableWithParameterizedValue.lf @@ -40,4 +40,3 @@ main reactor { =} } } - From b33ce99267ba4c845789b5a0691708a3253d29ab Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Thu, 24 Aug 2023 19:47:06 +0900 Subject: [PATCH 416/516] Index 0 should be used to check the output's status --- .../java/org/lflang/federated/extensions/TSExtension.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java index f0000b7435..9ef81c6a23 100644 --- a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java @@ -109,8 +109,8 @@ public String generateNetworkSenderBody( CoordinationType coordinationType, MessageReporter messageReporter) { return """ - if (%1$s%2$s[sender_index as number] !== undefined) { - this.util.sendRTITimedMessage(%1$s%2$s[sender_index as number], %3$s, %4$s, %5$s); + if (%1$s%2$s[0] !== undefined) { + this.util.sendRTITimedMessage(%1$s%2$s[0], %3$s, %4$s, %5$s); } """ .formatted( @@ -136,7 +136,7 @@ public String generatePortAbsentReactionBody( return """ // If the output port has not been set for the current logical time, // send an ABSENT message to the receiving federate - if (%1$s%2$s === undefined) { + if (%1$s%2$s[0] === undefined) { this.util.sendRTIPortAbsent(%3$d, %4$d, %5$s); } """ From 294b32f85239b9b0b1579a50452443c8bfed28e7 Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Mon, 28 Aug 2023 08:21:48 +0200 Subject: [PATCH 417/516] Ran spotlessJavaApply. --- core/src/main/java/org/lflang/AttributeUtils.java | 8 ++++---- .../lflang/diagram/synthesis/LinguaFrancaSynthesis.java | 5 ++--- .../main/java/org/lflang/validation/AttributeSpec.java | 6 ++++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/lflang/AttributeUtils.java b/core/src/main/java/org/lflang/AttributeUtils.java index c10258abe8..ee9666f0b8 100644 --- a/core/src/main/java/org/lflang/AttributeUtils.java +++ b/core/src/main/java/org/lflang/AttributeUtils.java @@ -31,7 +31,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; - import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.nodemodel.ICompositeNode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; @@ -166,7 +165,8 @@ public static Map getAttributeValues(EObject node, String attrNa final List attrs = findAttributesByName(node, attrName); HashMap layoutOptions = new HashMap<>(); for (Attribute attribute : attrs) { - layoutOptions.put(StringUtil.removeQuotes(attribute.getAttrParms().get(0).getValue()), + layoutOptions.put( + StringUtil.removeQuotes(attribute.getAttrParms().get(0).getValue()), StringUtil.removeQuotes(attribute.getAttrParms().get(1).getValue())); } return layoutOptions; @@ -281,8 +281,8 @@ public static String getPortSide(EObject node) { } /** - * Return the {@code layout} annotation for the given element or null if there is - * no such annotation. + * Return the {@code layout} annotation for the given element or null if there is no such + * annotation. */ public static Map getLayoutOption(EObject node) { return getAttributeValues(node, "layout"); diff --git a/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java b/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java index d10597f44a..a0f73dbabf 100644 --- a/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java +++ b/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java @@ -69,9 +69,7 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; - import javax.inject.Inject; - import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.elk.alg.layered.options.LayerConstraint; @@ -165,7 +163,8 @@ public class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { // ------------------------------------------------------------------------- /** Service class for accessing layout options by name */ - private static final LayoutMetaDataService LAYOUT_OPTIONS_SERVICE = LayoutMetaDataService.getInstance(); + private static final LayoutMetaDataService LAYOUT_OPTIONS_SERVICE = + LayoutMetaDataService.getInstance(); public static final String ID = "org.lflang.diagram.synthesis.LinguaFrancaSynthesis"; diff --git a/core/src/main/java/org/lflang/validation/AttributeSpec.java b/core/src/main/java/org/lflang/validation/AttributeSpec.java index 6d1da3e0a3..5b9d6dc51b 100644 --- a/core/src/main/java/org/lflang/validation/AttributeSpec.java +++ b/core/src/main/java/org/lflang/validation/AttributeSpec.java @@ -217,8 +217,10 @@ enum AttrParamType { // @layout(option="string", value="any") e.g. @layout(option="port.side", value="WEST") ATTRIBUTE_SPECS_BY_NAME.put( "layout", - new AttributeSpec(List.of(new AttrParamSpec(OPTION_ATTR, AttrParamType.STRING, false), - new AttrParamSpec(VALUE_ATTR, AttrParamType.STRING, false)))); + new AttributeSpec( + List.of( + new AttrParamSpec(OPTION_ATTR, AttrParamType.STRING, false), + new AttrParamSpec(VALUE_ATTR, AttrParamType.STRING, false)))); // @enclave(each=boolean) ATTRIBUTE_SPECS_BY_NAME.put( "enclave", From b1f5cf617033a6bbd71f2638a8ee105edc089d11 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Mon, 28 Aug 2023 12:10:20 +0200 Subject: [PATCH 418/516] avoid the _body postfix for reaction functions --- .../org/lflang/generator/cpp/CppExtensions.kt | 2 +- .../generator/cpp/CppReactionGenerator.kt | 24 ++++++++++--------- test/Cpp/src/hello_bodyless_world.cc | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppExtensions.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppExtensions.kt index e9e3f9fd8a..45a39eb68a 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppExtensions.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppExtensions.kt @@ -47,7 +47,7 @@ import org.lflang.lf.WidthSpec /** Get the "name" a reaction is represented with in target code.*/ val Reaction.codeName - get(): String = name ?: "r$indexInContainer" + get(): String = name ?: "reaction_$priority" /* ********************************************************************************************** * C++ specific extensions shared across classes diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppReactionGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppReactionGenerator.kt index 489dd8c2ae..e0dc0c143d 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppReactionGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppReactionGenerator.kt @@ -102,7 +102,7 @@ class CppReactionGenerator( allUncontainedSources.map { it.name } + allUncontainedEffects.map { it.name } + allReferencedContainers.map { getViewInstanceName(it) } - val body = "void ${codeName}_body() { __lf_inner.${codeName}_body(${parameters.joinToString(", ")}); }" + val body = "void ${codeName}_body() { __lf_inner.${codeName}(${parameters.joinToString(", ")}); }" val deadlineHandler = "void ${codeName}_deadline_handler() { __lf_inner.${codeName}_deadline_handler(${parameters.joinToString(", ")}); }" @@ -120,28 +120,30 @@ class CppReactionGenerator( } } - private fun generateFunctionDeclaration(reaction: Reaction, postfix: String): String { + private fun generateFunctionDeclaration(reaction: Reaction, postfix: String?): String { val params = reaction.getBodyParameters() + val reactionName = reaction.codeName + if(postfix != null) "_$postfix" else "" return when (params.size) { - 0 -> "void ${reaction.codeName}_$postfix();" - 1 -> "void ${reaction.codeName}_$postfix(${params[0]});" + 0 -> "void $reactionName();" + 1 -> "void $reactionName(${params[0]});" else -> with(PrependOperator) { """ - |void ${reaction.codeName}_$postfix( + |void $reactionName( ${" | "..params.joinToString(",\n")}); """.trimMargin() } } } - private fun getFunctionDefinitionSignature(reaction: Reaction, postfix: String): String { + private fun getFunctionDefinitionSignature(reaction: Reaction, postfix: String?): String { val params = reaction.getBodyParameters() + val reactionName = "${reactor.templateName}::Inner::${reaction.codeName}" + if(postfix != null) "_$postfix" else "" return when (params.size) { - 0 -> "void ${reactor.templateName}::Inner::${reaction.codeName}_$postfix()" - 1 -> "void ${reactor.templateName}::Inner::${reaction.codeName}_$postfix(${params[0]})" + 0 -> "void $reactionName()" + 1 -> "void $reactionName(${params[0]})" else -> with(PrependOperator) { """ - |void ${reactor.templateName}::Inner::${reaction.codeName}_$postfix( + |void $reactionName( ${" | "..params.joinToString(",\n")}) """.trimMargin() } @@ -154,7 +156,7 @@ class CppReactionGenerator( """ |// reaction ${reaction.label} |${reactor.templateLine} - ${" |"..getFunctionDefinitionSignature(reaction, "body")} { + ${" |"..getFunctionDefinitionSignature(reaction, null)} { ${" | "..reaction.code.toText()} |} | @@ -248,7 +250,7 @@ class CppReactionGenerator( /** Get all declarations of reaction bodies. */ fun generateBodyDeclarations() = reactor.reactions.joinToString("\n", "// reaction bodies\n", "\n") { - generateFunctionDeclaration(it, "body") + generateFunctionDeclaration(it, null) } /** Get all definitions of reaction bodies. */ diff --git a/test/Cpp/src/hello_bodyless_world.cc b/test/Cpp/src/hello_bodyless_world.cc index 782aeefc53..278c8dbe5d 100644 --- a/test/Cpp/src/hello_bodyless_world.cc +++ b/test/Cpp/src/hello_bodyless_world.cc @@ -1,5 +1,5 @@ #include "HelloBodylessWorld/HelloBodylessWorld.hh" -void HelloBodylessWorld::Inner::hello_body([[maybe_unused]] const reactor::StartupTrigger& startup) { +void HelloBodylessWorld::Inner::hello([[maybe_unused]] const reactor::StartupTrigger& startup) { std::cout << "Hello World." << std::endl; } From 62968d444596ba1a36745e116ada63618d868010 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Mon, 28 Aug 2023 13:01:29 -0700 Subject: [PATCH 419/516] Simplify build environment setup and make caching more robust --- .github/actions/prepare-build-env/action.yml | 38 +++----------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/.github/actions/prepare-build-env/action.yml b/.github/actions/prepare-build-env/action.yml index 2d0c82c78f..71ca8a50ee 100644 --- a/.github/actions/prepare-build-env/action.yml +++ b/.github/actions/prepare-build-env/action.yml @@ -1,5 +1,5 @@ -name: Prepare build environment (Linux only) -description: Set up Java, Maven, Gradle, etc. +name: Set up build environment +description: Set up Java and Gradle (including caching). runs: using: "composite" steps: @@ -8,35 +8,7 @@ runs: echo "$JAVA_HOME_17_X64/bin" >> $GITHUB_PATH echo "org.gradle.java.home=${JAVA_HOME_17_X64//\\/\/}" >> gradle.properties echo "JAVA_HOME=$JAVA_HOME_17_X64" >> $GITHUB_ENV + echo "$GRADLE_USER_HOME" shell: bash - - name: Check settings - run: | - echo $(which java) - cat gradle.properties - echo $JAVA_HOME - shell: bash - - 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 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - 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' }} + - name: Gradle Build Action + uses: gradle/gradle-build-action@v2.8.0 From aca30ce98de3e6ac0fd316582a197938e8283238 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Mon, 28 Aug 2023 13:03:39 -0700 Subject: [PATCH 420/516] More standard way of setting up Java --- .github/actions/prepare-build-env/action.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/actions/prepare-build-env/action.yml b/.github/actions/prepare-build-env/action.yml index 71ca8a50ee..71013d2fbe 100644 --- a/.github/actions/prepare-build-env/action.yml +++ b/.github/actions/prepare-build-env/action.yml @@ -3,12 +3,9 @@ description: Set up Java and Gradle (including caching). runs: using: "composite" steps: - - name: Set up Java 17 - run: | - echo "$JAVA_HOME_17_X64/bin" >> $GITHUB_PATH - echo "org.gradle.java.home=${JAVA_HOME_17_X64//\\/\/}" >> gradle.properties - echo "JAVA_HOME=$JAVA_HOME_17_X64" >> $GITHUB_ENV - echo "$GRADLE_USER_HOME" - shell: bash + - uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 17 - name: Gradle Build Action uses: gradle/gradle-build-action@v2.8.0 From ccdd8b966030f1acae9483f214d0810fb4054dd4 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Mon, 28 Aug 2023 22:44:52 -0700 Subject: [PATCH 421/516] Update .github/actions/prepare-build-env/action.yml --- .github/actions/prepare-build-env/action.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/actions/prepare-build-env/action.yml b/.github/actions/prepare-build-env/action.yml index 71013d2fbe..167e00bf62 100644 --- a/.github/actions/prepare-build-env/action.yml +++ b/.github/actions/prepare-build-env/action.yml @@ -9,3 +9,5 @@ runs: java-version: 17 - name: Gradle Build Action uses: gradle/gradle-build-action@v2.8.0 + with: + - cache-read-only: false From 2a76fe862b2ccd003620c4a32699876d268778e2 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Mon, 28 Aug 2023 22:49:05 -0700 Subject: [PATCH 422/516] Update action.yml --- .github/actions/prepare-build-env/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/prepare-build-env/action.yml b/.github/actions/prepare-build-env/action.yml index 167e00bf62..682be37dbb 100644 --- a/.github/actions/prepare-build-env/action.yml +++ b/.github/actions/prepare-build-env/action.yml @@ -10,4 +10,4 @@ runs: - name: Gradle Build Action uses: gradle/gradle-build-action@v2.8.0 with: - - cache-read-only: false + cache-read-only: false From d99f0a7f5c4f07defcb2dfff834677291ba335e4 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Tue, 29 Aug 2023 12:54:22 -0700 Subject: [PATCH 423/516] Build Epoch in CI --- .github/workflows/build.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e84f9fce42..7c8be826e0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,3 +30,9 @@ jobs: uses: ./.github/actions/report-code-coverage with: files: core/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml,cli/base/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml,cli/lfc/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml,cli/lfd/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml,cli/lff/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml + + build-epoch: + uses: lf-lang/epoch/.github/workflows/build.yml@main + with: + lingua-franca-ref: ${{ github.head_ref || github.ref_name }} + upload-artifacts: false From 58972c0d7c93243a0b1dca593673d7f401484dcf Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Tue, 29 Aug 2023 13:32:50 -0700 Subject: [PATCH 424/516] Rename job --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7c8be826e0..6d7edf2145 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,7 +31,7 @@ jobs: with: files: core/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml,cli/base/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml,cli/lfc/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml,cli/lfd/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml,cli/lff/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml - build-epoch: + epoch: uses: lf-lang/epoch/.github/workflows/build.yml@main with: lingua-franca-ref: ${{ github.head_ref || github.ref_name }} From 2583b77f414c119b1aa7eb9300fcea7b49661359 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Tue, 29 Aug 2023 14:30:18 -0700 Subject: [PATCH 425/516] Add actions for setting up francabot --- .github/actions/setup-francabot/action.yml | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/actions/setup-francabot/action.yml diff --git a/.github/actions/setup-francabot/action.yml b/.github/actions/setup-francabot/action.yml new file mode 100644 index 0000000000..7f2e2b86ab --- /dev/null +++ b/.github/actions/setup-francabot/action.yml @@ -0,0 +1,34 @@ +name: Load global configuration settings for francabot +description: Set up author information and GPG signature +author: Marten Lohstroh + +inputs: + gpg-key: + description: 'francabot GPG key' + required: true + gpg-passphrase: + description: 'francabot GPG passphrase' + required: true + +runs: + using: composite + steps: + - name: Set environment variables + run: | + echo "username=lingua-franca[bot]" >> "$GITHUB_ENV" + echo "email=97201490+francabot@users.noreply.github.com" >> "$GITHUB_ENV" + echo "user-and-email=lingua-franca[bot] <97201490+francabot@users.noreply.github.com>" >> "$GITHUB_ENV" + shell: bash + - name: Configure git username and email + run: | + git config --global user.name '${{ env.username }}' + git config --global user.email '${{ env.email }}' + shell: bash + - name: Import GPG key + uses: crazy-max/ghaction-import-gpg@v5 + with: + gpg_private_key: ${{ inputs.gpg-key }} + passphrase: ${{ inputs.gpg-passphrase }} + git_config_global: true + git_user_signingkey: true + git_commit_gpgsign: true From 18b5547394059304ed10329278ba4c3e5fb25b43 Mon Sep 17 00:00:00 2001 From: "lingua-franca[bot]" <97201490+francabot@users.noreply.github.com> Date: Wed, 30 Aug 2023 05:22:39 +0000 Subject: [PATCH 426/516] Update CHANGELOG.md --- CHANGELOG.md | 236 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fd8bbdb56..947b70314b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,240 @@ # Changelog + +## [v0.5.0](https://github.com/lf-lang/lingua-franca/tree/v0.5.0) (2023-08-30) + +**Highlights** + +This release introduces new syntax for initializers, includes a renovation of the C backend to let it generate more modular code, and brings new features like a watchdog construct, support for generics in C, support for SMT-solver-based formal verification using UCLID-5, and bare-iron support for the Raspberry Pi Pico platform. + +**🚀 New Features** + +- Types allowed in reactor type args [\#1639](https://github.com/lf-lang/lingua-franca/pull/1639) ([@oowekyala](https://github.com/oowekyala)) +- Tracing of federate interactions [\#1632](https://github.com/lf-lang/lingua-franca/pull/1632) ([@edwardalee](https://github.com/edwardalee)) +- Equals initializer syntax [\#1580](https://github.com/lf-lang/lingua-franca/pull/1580) ([@oowekyala](https://github.com/oowekyala)) +- `--json` and `--json-file` CLI args add to `lfc` [\#1686](https://github.com/lf-lang/lingua-franca/pull/1686) ([@patilatharva](https://github.com/patilatharva)) +- Generated structs exposed in header files, reactions linkable from separate source files, and updated C target preamble visibility [\#1599](https://github.com/lf-lang/lingua-franca/pull/1599) ([@petervdonovan](https://github.com/petervdonovan)) +- Preprocessor definition for `LF_PACKAGE_DIRECTORY` [\#1720](https://github.com/lf-lang/lingua-franca/pull/1720) ([@edwardalee](https://github.com/edwardalee)) +- Mechanism for printing execution statistics [\#1743](https://github.com/lf-lang/lingua-franca/pull/1743) ([@cmnrd](https://github.com/cmnrd)) +- Watchdog support for the C target [\#1730](https://github.com/lf-lang/lingua-franca/pull/1730) ([@Benichiwa](https://github.com/Benichiwa)) +- Automatically generated .vscode/settings.json file [\#1759](https://github.com/lf-lang/lingua-franca/pull/1759) ([@edwardalee](https://github.com/edwardalee)) +- C Generics [\#1681](https://github.com/lf-lang/lingua-franca/pull/1681) ([@mkhubaibumer](https://github.com/mkhubaibumer)) +- `USER_THREADS` specifiable in platform options [\#1721](https://github.com/lf-lang/lingua-franca/pull/1721) ([@siljesu](https://github.com/siljesu)) +- Generic params allowed as generic arguments in C target [\#1804](https://github.com/lf-lang/lingua-franca/pull/1804) ([@petervdonovan](https://github.com/petervdonovan)) +- New `--check` flag for `lff` [\#1822](https://github.com/lf-lang/lingua-franca/pull/1822) ([@cmnrd](https://github.com/cmnrd)) +- Environments in the C target [\#1772](https://github.com/lf-lang/lingua-franca/pull/1772) ([@erlingrj](https://github.com/erlingrj)) +- `lfd` binary for generating diagrams from the command line [\#1713](https://github.com/lf-lang/lingua-franca/pull/1713) ([@cmnrd](https://github.com/cmnrd)) +- `fedsd` utility updated to make the RTI optional and support enclaves visualization [\#1870](https://github.com/lf-lang/lingua-franca/pull/1870) ([@ChadliaJerad](https://github.com/ChadliaJerad)) +- C math lib always linked for C target [\#1894](https://github.com/lf-lang/lingua-franca/pull/1894) ([@cmnrd](https://github.com/cmnrd)) +- Critical sections enabled outside of the context of a reactor [\#1901](https://github.com/lf-lang/lingua-franca/pull/1901) ([@edwardalee](https://github.com/edwardalee)) +- Uclid5-based LF Verifier [\#1271](https://github.com/lf-lang/lingua-franca/pull/1271) ([@lsk567](https://github.com/lsk567)) +- Python launch scripts [\#1914](https://github.com/lf-lang/lingua-franca/pull/1914) ([@cmnrd](https://github.com/cmnrd)) +- Raspberry Pi Pico target support [\#1831](https://github.com/lf-lang/lingua-franca/pull/1831) ([@gundralaa](https://github.com/gundralaa)) +- Handling cyclic dependencies for TypeScript federated execution [\#1925](https://github.com/lf-lang/lingua-franca/pull/1925) ([@byeong-gil](https://github.com/byeong-gil)) + +**✨ Enhancements** + +- Keeplive natively inferred in the C++ runtime [\#1630](https://github.com/lf-lang/lingua-franca/pull/1630) ([@cmnrd](https://github.com/cmnrd)) +- File access [\#1715](https://github.com/lf-lang/lingua-franca/pull/1715) ([@edwardalee](https://github.com/edwardalee)) +- Faster building of TypeScript [\#1611](https://github.com/lf-lang/lingua-franca/pull/1611) ([@lhstrh](https://github.com/lhstrh)) +- Revised mechanism for handling the `files` target property [\#1700](https://github.com/lf-lang/lingua-franca/pull/1700) ([@lhstrh](https://github.com/lhstrh)) +- Validator rules to check if target supports federation or inheritance [\#1726](https://github.com/lf-lang/lingua-franca/pull/1726) ([@cmnrd](https://github.com/cmnrd)) +- Optimized Gradle configuration for faster building [\#1774](https://github.com/lf-lang/lingua-franca/pull/1774) ([@axmmisaka](https://github.com/axmmisaka)) +- Adjustable port sides for reactors [\#1807](https://github.com/lf-lang/lingua-franca/pull/1807) ([@a-sr](https://github.com/a-sr)) +- No more space inserted after `interleaved` keyword by formatter [\#1846](https://github.com/lf-lang/lingua-franca/pull/1846) ([@cmnrd](https://github.com/cmnrd)) +- More natural syntax for reaction declarations [\#1853](https://github.com/lf-lang/lingua-franca/pull/1853) ([@lhstrh](https://github.com/lhstrh)) +- Fix for extra whitespace around info messages [\#1879](https://github.com/lf-lang/lingua-franca/pull/1879) ([@oowekyala](https://github.com/oowekyala)) +- TypeScript runtime bumped to `v0.5.0` [\#1927](https://github.com/lf-lang/lingua-franca/pull/1927) ([@byeong-gil](https://github.com/byeong-gil)) +- Support for named and bodyless reactions in C++ [\#1933](https://github.com/lf-lang/lingua-franca/pull/1933) ([@cmnrd](https://github.com/cmnrd)) +- Added @layout annotation to add arbitrary layout options an elements [\#1951](https://github.com/lf-lang/lingua-franca/pull/1951) ([@soerendomroes](https://github.com/soerendomroes)) + +**🔧 Fixes** + +- Physical connections implemented as AST transformation [\#1596](https://github.com/lf-lang/lingua-franca/pull/1596) ([@erlingrj](https://github.com/erlingrj)) +- Fix for passing of command line options from lfc to the generator [\#1631](https://github.com/lf-lang/lingua-franca/pull/1631) ([@cmnrd](https://github.com/cmnrd)) +- Fix for validation of target properties [\#1629](https://github.com/lf-lang/lingua-franca/pull/1629) ([@cmnrd](https://github.com/cmnrd)) +- Fix in validation so that warnings are not reported as errors [\#1643](https://github.com/lf-lang/lingua-franca/pull/1643) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix for NPE in lfc error reporting [\#1655](https://github.com/lf-lang/lingua-franca/pull/1655) ([@cmnrd](https://github.com/cmnrd)) +- Upstream connection delays now properly handled in the TypeScript federates [\#1607](https://github.com/lf-lang/lingua-franca/pull/1607) ([@byeong-gil](https://github.com/byeong-gil)) +- Fix for authenticated federation [\#1698](https://github.com/lf-lang/lingua-franca/pull/1698) ([@Jakio815](https://github.com/Jakio815)) +- Bugfixes in handling of `files` target property [\#1725](https://github.com/lf-lang/lingua-franca/pull/1725) ([@lhstrh](https://github.com/lhstrh)) +- Preambles properly inherited from superclasses [\#1732](https://github.com/lf-lang/lingua-franca/pull/1732) ([@edwardalee](https://github.com/edwardalee)) +- Reactor classes in the same file with the same name, up to case differences, are prohibited [\#1741](https://github.com/lf-lang/lingua-franca/pull/1741) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix for ROS serialization [\#1755](https://github.com/lf-lang/lingua-franca/pull/1755) ([@petervdonovan](https://github.com/petervdonovan)) +- Improved line adjustment logic for federated programs [\#1760](https://github.com/lf-lang/lingua-franca/pull/1760) ([@petervdonovan](https://github.com/petervdonovan)) +- Fixed race condition in C++ enclave coordination [\#1778](https://github.com/lf-lang/lingua-franca/pull/1778) ([@cmnrd](https://github.com/cmnrd)) +- Fix for error reporting bug [\#1787](https://github.com/lf-lang/lingua-franca/pull/1787) ([@lhstrh](https://github.com/lhstrh)) +- Fix warnings reported on CliBase. [\#1793](https://github.com/lf-lang/lingua-franca/pull/1793) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix to allow CLI args to be passed to federates [\#1798](https://github.com/lf-lang/lingua-franca/pull/1798) ([@petervdonovan](https://github.com/petervdonovan)) +- Multiple fixes for federated programs with TypeScript target [\#1752](https://github.com/lf-lang/lingua-franca/pull/1752) ([@byeong-gil](https://github.com/byeong-gil)) +- Fix for race condition in `uniqueName` [\#1815](https://github.com/lf-lang/lingua-franca/pull/1815) ([@petervdonovan](https://github.com/petervdonovan)) +- Fedsd compatibility with pandas 2.0 [\#1836](https://github.com/lf-lang/lingua-franca/pull/1836) ([@ChadliaJerad](https://github.com/ChadliaJerad)) +- Formatter fixes [\#1840](https://github.com/lf-lang/lingua-franca/pull/1840) ([@petervdonovan](https://github.com/petervdonovan)) +- Adjustments for running the LF compiler in Epoch [\#1844](https://github.com/lf-lang/lingua-franca/pull/1844) ([@a-sr](https://github.com/a-sr)) +- More formatter fixes [\#1850](https://github.com/lf-lang/lingua-franca/pull/1850) ([@petervdonovan](https://github.com/petervdonovan)) +- More formatter fixes [\#1851](https://github.com/lf-lang/lingua-franca/pull/1851) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix for naming collision when generic reactor is instantiated with different parameters [\#1864](https://github.com/lf-lang/lingua-franca/pull/1864) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix for inheritance problem exposed in examples [\#1891](https://github.com/lf-lang/lingua-franca/pull/1891) ([@lhstrh](https://github.com/lhstrh)) +- Fix verifier error when there is no main reactor [\#1916](https://github.com/lf-lang/lingua-franca/pull/1916) ([@lsk567](https://github.com/lsk567)) +- No more use of unordered reactions in federated programs; fix for deadlocks in some federated programs [\#1684](https://github.com/lf-lang/lingua-franca/pull/1684) ([@arengarajan99](https://github.com/arengarajan99)) +- Keyword `extends` added to tokens allowed in reaction bodies [\#1926](https://github.com/lf-lang/lingua-franca/pull/1926) ([@lhstrh](https://github.com/lhstrh)) +- Fix for edge case in which comments are dropped [\#1924](https://github.com/lf-lang/lingua-franca/pull/1924) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix for IllegalArgumentException in diagram synthesis [\#1932](https://github.com/lf-lang/lingua-franca/pull/1932) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix for STP violation [\#1935](https://github.com/lf-lang/lingua-franca/pull/1935) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix for after delays that use user-provided declarations [\#1959](https://github.com/lf-lang/lingua-franca/pull/1959) ([@petervdonovan](https://github.com/petervdonovan)) +- Bugfix for when top-level multiport width in federation depends on parameter [\#1956](https://github.com/lf-lang/lingua-franca/pull/1956) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix compilation error in code for reset state variables with time type [\#1964](https://github.com/lf-lang/lingua-franca/pull/1964) ([@a-sr](https://github.com/a-sr)) + +**🚧 Maintenance and Refactoring** + +- Migration of Epoch into separate repository [\#1482](https://github.com/lf-lang/lingua-franca/pull/1482) ([@a-sr](https://github.com/a-sr)) +- Improved CLI argument handling using `picocli` [\#1534](https://github.com/lf-lang/lingua-franca/pull/1534) ([@patilatharva](https://github.com/patilatharva)) +- Compile definitions no longer hardcoded in generated CMakeLists.txt [\#1622](https://github.com/lf-lang/lingua-franca/pull/1622) ([@petervdonovan](https://github.com/petervdonovan)) +- Remove unchecked compilation warnings [\#1638](https://github.com/lf-lang/lingua-franca/pull/1638) ([@oowekyala](https://github.com/oowekyala)) +- Refactoring of part of the federated package [\#1663](https://github.com/lf-lang/lingua-franca/pull/1663) ([@lhstrh](https://github.com/lhstrh)) +- Removal of the use of non-API global variables in tests [\#1696](https://github.com/lf-lang/lingua-franca/pull/1696) ([@edwardalee](https://github.com/edwardalee)) +- Gradle bumped to version 8 [\#1691](https://github.com/lf-lang/lingua-franca/pull/1691) ([@lhstrh](https://github.com/lhstrh)) +- Removal of deprecated `build-lfc` script and `buildLfc` Gradle task [\#1714](https://github.com/lf-lang/lingua-franca/pull/1714) ([@cmnrd](https://github.com/cmnrd)) +- Delete unnecessary complexity from `build-lf-cli` [\#1745](https://github.com/lf-lang/lingua-franca/pull/1745) ([@petervdonovan](https://github.com/petervdonovan)) +- C files for Python support retrieved from reactor-c repo [\#1757](https://github.com/lf-lang/lingua-franca/pull/1757) ([@lhstrh](https://github.com/lhstrh)) +- Google autoformatter applied to all files [\#1766](https://github.com/lf-lang/lingua-franca/pull/1766) ([@lhstrh](https://github.com/lhstrh)) +- Struct refactoring for actions and ports [\#1756](https://github.com/lf-lang/lingua-franca/pull/1756) ([@edwardalee](https://github.com/edwardalee)) +- Redesign of the repository layout and gradle configuration [\#1779](https://github.com/lf-lang/lingua-franca/pull/1779) ([@cmnrd](https://github.com/cmnrd)) +- Removal of odd mechanism for loading target generators dynamically [\#1813](https://github.com/lf-lang/lingua-franca/pull/1813) ([@cmnrd](https://github.com/cmnrd)) +- Default line length set to 100 for LF files [\#1389](https://github.com/lf-lang/lingua-franca/pull/1389) ([@petervdonovan](https://github.com/petervdonovan)) +- KlighD bumped to `2.3.0` and now retrieved from Maven Central [\#1823](https://github.com/lf-lang/lingua-franca/pull/1823) ([@cmnrd](https://github.com/cmnrd)) +- Refactor error reporter [\#1527](https://github.com/lf-lang/lingua-franca/pull/1527) ([@oowekyala](https://github.com/oowekyala)) +- Unknown port types handled with `unknown` instead of `Present` [\#1857](https://github.com/lf-lang/lingua-franca/pull/1857) ([@lhstrh](https://github.com/lhstrh)) +- TS code generator adjusted to appease `eslint` [\#1878](https://github.com/lf-lang/lingua-franca/pull/1878) ([@lhstrh](https://github.com/lhstrh)) +- Minor fixes for the README in the test directory [\#1903](https://github.com/lf-lang/lingua-franca/pull/1903) ([@lsk567](https://github.com/lsk567)) +- Declarative Port Graph in C++ Runtime [\#1848](https://github.com/lf-lang/lingua-franca/pull/1848) ([@revol-xut](https://github.com/revol-xut)) +- No more use of unordered reactions in federated programs; fix for deadlocks in some federated programs [\#1684](https://github.com/lf-lang/lingua-franca/pull/1684) ([@arengarajan99](https://github.com/arengarajan99)) +- Tracing utils and Zephyr run scripts moved from `util` folder [\#1948](https://github.com/lf-lang/lingua-franca/pull/1948) ([@erlingrj](https://github.com/erlingrj)) + +**📖 Documentation** + +- Documentation about LSP tests in `README.md` [\#1587](https://github.com/lf-lang/lingua-franca/pull/1587) ([@petervdonovan](https://github.com/petervdonovan)) + +**🧪 Tests** + +- TypeScript tests for federates with physical connections [\#1623](https://github.com/lf-lang/lingua-franca/pull/1623) ([@byeong-gil](https://github.com/byeong-gil)) +- Zephyr tests pinned to particular version of docker image [\#1648](https://github.com/lf-lang/lingua-franca/pull/1648) ([@erlingrj](https://github.com/erlingrj)) +- Test for the support of delayed physical connections in the TypeScript target [\#1676](https://github.com/lf-lang/lingua-franca/pull/1676) ([@byeong-gil](https://github.com/byeong-gil)) +- Test for parsing CLI arguments in `lfc` [\#1668](https://github.com/lf-lang/lingua-franca/pull/1668) ([@patilatharva](https://github.com/patilatharva)) +- Zephyr regression tests executed on QEMU [\#1678](https://github.com/lf-lang/lingua-franca/pull/1678) ([@erlingrj](https://github.com/erlingrj)) +- CodeCov reporting for CLI tests [\#1688](https://github.com/lf-lang/lingua-franca/pull/1688) ([@lhstrh](https://github.com/lhstrh)) +- Test to help ensure that level-based scheduling does not cause deadlock [\#1703](https://github.com/lf-lang/lingua-franca/pull/1703) ([@edwardalee](https://github.com/edwardalee)) +- Flaky tests adjusted [\#1764](https://github.com/lf-lang/lingua-franca/pull/1764) ([@edwardalee](https://github.com/edwardalee)) +- Spurious dependency example [\#1707](https://github.com/lf-lang/lingua-franca/pull/1707) ([@petervdonovan](https://github.com/petervdonovan)) +- Added test of documented STP_offset parameter [\#1786](https://github.com/lf-lang/lingua-franca/pull/1786) ([@edwardalee](https://github.com/edwardalee)) +- `SimpleFederatedAuthenticated.lf` test passing [\#1776](https://github.com/lf-lang/lingua-franca/pull/1776) ([@Jakio815](https://github.com/Jakio815)) +- CI updates [\#1814](https://github.com/lf-lang/lingua-franca/pull/1814) ([@petervdonovan](https://github.com/petervdonovan)) +- Parallel execution of round trip tests [\#1845](https://github.com/lf-lang/lingua-franca/pull/1845) ([@oowekyala](https://github.com/oowekyala)) +- Tests for `lf_request_stop` with enclaves and federates [\#1871](https://github.com/lf-lang/lingua-franca/pull/1871) ([@edwardalee](https://github.com/edwardalee)) +- Fixed code coverage aggregation and reporting [\#1868](https://github.com/lf-lang/lingua-franca/pull/1868) ([@cmnrd](https://github.com/cmnrd)) +- New job for building `epoch` in CI [\#1974](https://github.com/lf-lang/lingua-franca/pull/1974) ([@lhstrh](https://github.com/lhstrh)) + +**⬆️ Updated Dependencies** + +- Update to Zephyr v3.3.0 and SDK v0.16.1 [\#1825](https://github.com/lf-lang/lingua-franca/pull/1825) ([@erlingrj](https://github.com/erlingrj)) +- Reactor-ts bumped to v0.4.0 [\#1749](https://github.com/lf-lang/lingua-franca/pull/1749) ([@lhstrh](https://github.com/lhstrh)) +- Gradle Wrapper bumped to `8.1.1` [\#1890](https://github.com/lf-lang/lingua-franca/pull/1890) ([@lhstrh](https://github.com/lhstrh)) +- Eclipse-related dependencies bumped to latest releases [\#1889](https://github.com/lf-lang/lingua-franca/pull/1889) ([@a-sr](https://github.com/a-sr)) +- TypeScript runtime bumped to `v0.5.0` [\#1927](https://github.com/lf-lang/lingua-franca/pull/1927) ([@byeong-gil](https://github.com/byeong-gil)) + + +### Submodule [lf-lang/reactor-c](http://github.com/lf-lang/reactor-c) + +**🚀 New Features** + +- New tracepoint for deadline misses [\#169](https://github.com/lf-lang/reactor-c/pull/169) ([@erlingrj](https://github.com/erlingrj)) +- Compile definitions for federated programs [\#179](https://github.com/lf-lang/reactor-c/pull/179) ([@petervdonovan](https://github.com/petervdonovan)) +- Tracing federate interactions [\#178](https://github.com/lf-lang/reactor-c/pull/178) ([@edwardalee](https://github.com/edwardalee)) +- CMake definition to find `FEDERATED_AUTHENTICATED` [\#196](https://github.com/lf-lang/reactor-c/pull/196) ([@Jakio815](https://github.com/Jakio815)) +- Memory reporting [\#201](https://github.com/lf-lang/reactor-c/pull/201) ([@edwardalee](https://github.com/edwardalee)) +- Added `LF_PACKAGE_DIRECTORY` [\#204](https://github.com/lf-lang/reactor-c/pull/204) ([@edwardalee](https://github.com/edwardalee)) +- Runtime support for watchdogs [\#177](https://github.com/lf-lang/reactor-c/pull/177) ([@Benichiwa](https://github.com/Benichiwa)) +- Environments [\#212](https://github.com/lf-lang/reactor-c/pull/212) ([@erlingrj](https://github.com/erlingrj)) +- Enclave request stop [\#244](https://github.com/lf-lang/reactor-c/pull/244) ([@edwardalee](https://github.com/edwardalee)) +- Critical sections enabled outside of the context of a reactor [\#249](https://github.com/lf-lang/reactor-c/pull/249) ([@edwardalee](https://github.com/edwardalee)) +- Rp2040 Target Support [\#253](https://github.com/lf-lang/reactor-c/pull/253) ([@gundralaa](https://github.com/gundralaa)) +- Platform support for Raspberry Pi Pico [\#233](https://github.com/lf-lang/reactor-c/pull/233) ([@gundralaa](https://github.com/gundralaa)) + +**✨ Enhancements** + +- Removal of unnecessary TAG messages [\#175](https://github.com/lf-lang/reactor-c/pull/175) ([@byeong-gil](https://github.com/byeong-gil)) +- Cleaner namespace [\#189](https://github.com/lf-lang/reactor-c/pull/189) ([@petervdonovan](https://github.com/petervdonovan)) +- File access and doc fixes [\#198](https://github.com/lf-lang/reactor-c/pull/198) ([@edwardalee](https://github.com/edwardalee)) +- Improvements of support for watchdogs [\#209](https://github.com/lf-lang/reactor-c/pull/209) ([@edwardalee](https://github.com/edwardalee)) +- Switch to more general thread creation in Zephyr support [\#194](https://github.com/lf-lang/reactor-c/pull/194) ([@siljesu](https://github.com/siljesu)) +- Minor improvements to Zephyr platform [\#187](https://github.com/lf-lang/reactor-c/pull/187) ([@erlingrj](https://github.com/erlingrj)) +- Output error when trying to use --auth (-a) for RTI built without -DAUTH=ON [\#222](https://github.com/lf-lang/reactor-c/pull/222) ([@hokeun](https://github.com/hokeun)) +- Change nanosleep to lf_sleep in federate and RTI code [\#219](https://github.com/lf-lang/reactor-c/pull/219) ([@siljesu](https://github.com/siljesu)) +- RTI exit while saving the trace file [\#228](https://github.com/lf-lang/reactor-c/pull/228) ([@ChadliaJerad](https://github.com/ChadliaJerad)) +- Less namespace pollution [\#240](https://github.com/lf-lang/reactor-c/pull/240) ([@erlingrj](https://github.com/erlingrj)) +- Enclaves tuning [\#243](https://github.com/lf-lang/reactor-c/pull/243) ([@edwardalee](https://github.com/edwardalee)) +- If clock sync is on, link math [\#252](https://github.com/lf-lang/reactor-c/pull/252) ([@petervdonovan](https://github.com/petervdonovan)) +- No more use of unordered reactions in federated programs [\#191](https://github.com/lf-lang/reactor-c/pull/191) ([@arengarajan99](https://github.com/arengarajan99)) + +**🔧 Fixes** + +- Fix for definition of `LF_TIME_BUFFER_LENGTH` [\#197](https://github.com/lf-lang/reactor-c/pull/197) ([@edwardalee](https://github.com/edwardalee)) +- Scheduler leak fix [\#200](https://github.com/lf-lang/reactor-c/pull/200) ([@edwardalee](https://github.com/edwardalee)) +- Fix for Arduino to avoid duplicate definition of `timespec` [\#195](https://github.com/lf-lang/reactor-c/pull/195) ([@arengarajan99](https://github.com/arengarajan99)) +- Suppression of "no symbols" warnings emitted by ranlib [\#214](https://github.com/lf-lang/reactor-c/pull/214) ([@petervdonovan](https://github.com/petervdonovan)) +- Segfault fix [\#218](https://github.com/lf-lang/reactor-c/pull/218) ([@petervdonovan](https://github.com/petervdonovan)) +- Zephyr fixes on thread creation and deletion [\#223](https://github.com/lf-lang/reactor-c/pull/223) ([@erlingrj](https://github.com/erlingrj)) +- Minor fix of the federate id in the tracepoint [\#245](https://github.com/lf-lang/reactor-c/pull/245) ([@ChadliaJerad](https://github.com/ChadliaJerad)) +- Fix protocol of HMAC authentication to start from federate. [\#231](https://github.com/lf-lang/reactor-c/pull/231) ([@Jakio815](https://github.com/Jakio815)) +- Use of correct federate ID in tracing of absent messages [\#248](https://github.com/lf-lang/reactor-c/pull/248) ([@ChadliaJerad](https://github.com/ChadliaJerad)) +- Memory leak in Python target fixed [\#246](https://github.com/lf-lang/reactor-c/pull/246) ([@jackykwok2024](https://github.com/jackykwok2024)) +- Fix for fatal error raised during shutdown when decrementing a tag barrier that is zero [\#251](https://github.com/lf-lang/reactor-c/pull/251) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix for STP violation [\#257](https://github.com/lf-lang/reactor-c/pull/257) ([@petervdonovan](https://github.com/petervdonovan)) + +**🚧 Maintenance and Refactoring** + +- Functions of rti.c moved to rti_lib.c to enable reuse [\#172](https://github.com/lf-lang/reactor-c/pull/172) ([@Jakio815](https://github.com/Jakio815)) +- Code in `rti.c` made available as library [\#174](https://github.com/lf-lang/reactor-c/pull/174) ([@Jakio815](https://github.com/Jakio815)) +- Documentation and code cleanup [\#193](https://github.com/lf-lang/reactor-c/pull/193) ([@edwardalee](https://github.com/edwardalee)) +- Platform abstraction layer for the RTI [\#213](https://github.com/lf-lang/reactor-c/pull/213) ([@siljesu](https://github.com/siljesu)) +- C files from reactor-c-py moved back into the reactor-c repo [\#217](https://github.com/lf-lang/reactor-c/pull/217) ([@lhstrh](https://github.com/lhstrh)) +- Struct refactoring for actions and ports [\#216](https://github.com/lf-lang/reactor-c/pull/216) ([@edwardalee](https://github.com/edwardalee)) +- Refactoring of the RTI implementation [\#224](https://github.com/lf-lang/reactor-c/pull/224) ([@ChadliaJerad](https://github.com/ChadliaJerad)) +- `_lf_count_token_allocations` made `extern` instead of `static` [\#236](https://github.com/lf-lang/reactor-c/pull/236) ([@erlingrj](https://github.com/erlingrj)) +- Refactoring of obsolete `gethostbyname()` in `connect_to_rti()` [\#220](https://github.com/lf-lang/reactor-c/pull/220) ([@siljesu](https://github.com/siljesu)) +- No more use of unordered reactions in federated programs [\#191](https://github.com/lf-lang/reactor-c/pull/191) ([@arengarajan99](https://github.com/arengarajan99)) +- Fewer warnings [\#258](https://github.com/lf-lang/reactor-c/pull/258) ([@edwardalee](https://github.com/edwardalee)) + +**📖 Documentation** + +- Documentation and code cleanup [\#193](https://github.com/lf-lang/reactor-c/pull/193) ([@edwardalee](https://github.com/edwardalee)) + + +### Submodule [lf-lang/reactor-cpp](http://github.com/lf-lang/reactor-cpp) + +**🚀 New Features** + +- Enclave connections and enclave coordination [\#44](https://github.com/lf-lang/reactor-cpp/pull/44) ([@cmnrd](https://github.com/cmnrd)) +- Simple mechanism for collecting statistics during execution [\#47](https://github.com/lf-lang/reactor-cpp/pull/47) ([@cmnrd](https://github.com/cmnrd)) +- Port graph [\#51](https://github.com/lf-lang/reactor-cpp/pull/51) ([@revol-xut](https://github.com/revol-xut)) + +**✨ Enhancements** + +- Keep track of input actions in the environment [\#42](https://github.com/lf-lang/reactor-cpp/pull/42) ([@cmnrd](https://github.com/cmnrd)) +- Factored event queue into its own class and fixed race condition between multiple starting enclaves [\#45](https://github.com/lf-lang/reactor-cpp/pull/45) ([@cmnrd](https://github.com/cmnrd)) + +**🔧 Fixes** + +- Fix race condition in time barriers [\#49](https://github.com/lf-lang/reactor-cpp/pull/49) ([@cmnrd](https://github.com/cmnrd)) +- Fix validate method and fix incorrect phase checks [\#50](https://github.com/lf-lang/reactor-cpp/pull/50) ([@cmnrd](https://github.com/cmnrd)) + + +### Submodule [lf-lang/reactor-rs](http://github.com/lf-lang/reactor-rs) + +- Use Cargo workspaces to directly include vecmap dependency [\#40](https://github.com/lf-lang/reactor-rs/pull/40) ([@jhaye](https://github.com/jhaye)) +- Fixes for current Clippy version [\#45](https://github.com/lf-lang/reactor-rs/pull/45) ([@jhaye](https://github.com/jhaye)) +- chore: Bump dependencies to avoid vulnerability in smallvec [\#44](https://github.com/lf-lang/reactor-rs/pull/44) ([@oowekyala](https://github.com/oowekyala)) +- Fix use of multiport as reaction source [\#43](https://github.com/lf-lang/reactor-rs/pull/43) ([@oowekyala](https://github.com/oowekyala)) + ## [v0.4.0](https://github.com/lf-lang/lingua-franca/tree/v0.4.0) (2023-03-01) From 313e0b2f34fe2e66a1f61a9ca003aa090e641eb2 Mon Sep 17 00:00:00 2001 From: "lingua-franca[bot]" <97201490+francabot@users.noreply.github.com> Date: Wed, 30 Aug 2023 05:24:55 +0000 Subject: [PATCH 427/516] Bump version to 0.5.0 --- CHANGELOG.md | 2 +- core/src/main/resources/org/lflang/StringsBundle.properties | 2 +- gradle.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 947b70314b..5dd1e426ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # Changelog - + ## [v0.5.0](https://github.com/lf-lang/lingua-franca/tree/v0.5.0) (2023-08-30) **Highlights** diff --git a/core/src/main/resources/org/lflang/StringsBundle.properties b/core/src/main/resources/org/lflang/StringsBundle.properties index 29b191399c..b58621d407 100644 --- a/core/src/main/resources/org/lflang/StringsBundle.properties +++ b/core/src/main/resources/org/lflang/StringsBundle.properties @@ -1 +1 @@ -VERSION = 0.4.1-SNAPSHOT +VERSION = 0.5.0 diff --git a/gradle.properties b/gradle.properties index fd082fc31f..ce77106367 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ [header] group=org.lflang -version=0.4.1-SNAPSHOT +version=0.5.0 [versions] antlrVersion=4.7.2 From 15b514408c912f71910abae3036845e679bb2637 Mon Sep 17 00:00:00 2001 From: revol-xut Date: Thu, 31 Aug 2023 13:13:10 +0200 Subject: [PATCH 428/516] fixing using int for iterators --- test/Cpp/src/ArrayScale.lf | 2 +- test/Cpp/src/enclave/EnclaveMultiportToPort.lf | 2 +- test/Cpp/src/enclave/EnclaveMultiportToPort2.lf | 2 +- test/Cpp/src/multiport/MultiportFromBankHierarchyAfter.lf | 2 +- test/Cpp/src/multiport/MultiportFromHierarchy.lf | 2 +- test/Cpp/src/multiport/MultiportIn.lf | 2 +- test/Cpp/src/multiport/MultiportToMultiportArray.lf | 2 +- test/Cpp/src/multiport/MultiportToPort.lf | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/Cpp/src/ArrayScale.lf b/test/Cpp/src/ArrayScale.lf index 9d0b3cf350..751a36c5a2 100644 --- a/test/Cpp/src/ArrayScale.lf +++ b/test/Cpp/src/ArrayScale.lf @@ -14,7 +14,7 @@ reactor Scale(scale: int = 2) { auto array = in.get().get_mutable_copy(); // NOTE: Ideally, no copy copy would be made here, as there is only // one recipient for the value, but this is not supported yet by the C++ runtime. - for(int i = 0; i < array->size(); i++) { + for(auto i = 0; i < array->size(); i++) { (*array)[i] = (*array)[i] * scale; } out.set(std::move(array)); diff --git a/test/Cpp/src/enclave/EnclaveMultiportToPort.lf b/test/Cpp/src/enclave/EnclaveMultiportToPort.lf index f20ad09633..53f5e8bfdb 100644 --- a/test/Cpp/src/enclave/EnclaveMultiportToPort.lf +++ b/test/Cpp/src/enclave/EnclaveMultiportToPort.lf @@ -8,7 +8,7 @@ reactor Source { output[2] out: int reaction(startup) -> out {= - for(int i = 0; i < out.size(); i++) { + for(auto i = 0; i < out.size(); i++) { std::cout << "Source sending " << i << ".\n"; out[i].set(i); } diff --git a/test/Cpp/src/enclave/EnclaveMultiportToPort2.lf b/test/Cpp/src/enclave/EnclaveMultiportToPort2.lf index 7e40a09623..257374001d 100644 --- a/test/Cpp/src/enclave/EnclaveMultiportToPort2.lf +++ b/test/Cpp/src/enclave/EnclaveMultiportToPort2.lf @@ -8,7 +8,7 @@ reactor Source { output[2] out: int reaction(startup) -> out {= - for(int i = 0; i < out.size(); i++) { + for(auto i = 0; i < out.size(); i++) { std::cout << "Source sending " << i << ".\n"; out[i].set(i); } diff --git a/test/Cpp/src/multiport/MultiportFromBankHierarchyAfter.lf b/test/Cpp/src/multiport/MultiportFromBankHierarchyAfter.lf index cb3eeaa0c4..bbd25fa5ce 100644 --- a/test/Cpp/src/multiport/MultiportFromBankHierarchyAfter.lf +++ b/test/Cpp/src/multiport/MultiportFromBankHierarchyAfter.lf @@ -22,7 +22,7 @@ reactor Destination { state received: bool = false reaction(in) {= - for (int i = 0; i < in.size(); i++) { + for (auto i = 0; i < in.size(); i++) { int value = *in[i].get(); std::cout << "Destination channel " << i << " received " << value << '\n'; if (i != value) { diff --git a/test/Cpp/src/multiport/MultiportFromHierarchy.lf b/test/Cpp/src/multiport/MultiportFromHierarchy.lf index 0615286dfc..1531af59a6 100644 --- a/test/Cpp/src/multiport/MultiportFromHierarchy.lf +++ b/test/Cpp/src/multiport/MultiportFromHierarchy.lf @@ -10,7 +10,7 @@ reactor Source { state s: int = 0 reaction(t) -> out {= - for(int i = 0; i < 4; i++) { + for(auto i = 0; i < 4; i++) { out[i].set(s++); } =} diff --git a/test/Cpp/src/multiport/MultiportIn.lf b/test/Cpp/src/multiport/MultiportIn.lf index 6f4f63f74b..862549a7a0 100644 --- a/test/Cpp/src/multiport/MultiportIn.lf +++ b/test/Cpp/src/multiport/MultiportIn.lf @@ -26,7 +26,7 @@ reactor Destination { reaction(in) {= int sum = 0; - for (int i = 0; i < in.size(); i++) { + for (auto i = 0; i < in.size(); i++) { sum += *in[i].get(); } std::cout << "Sum of received: " << sum << ".\n"; diff --git a/test/Cpp/src/multiport/MultiportToMultiportArray.lf b/test/Cpp/src/multiport/MultiportToMultiportArray.lf index 3f95cd2c21..9ab9b4fba9 100644 --- a/test/Cpp/src/multiport/MultiportToMultiportArray.lf +++ b/test/Cpp/src/multiport/MultiportToMultiportArray.lf @@ -31,7 +31,7 @@ reactor Destination { int sum = 0; for (auto i : in.present_indices_unsorted()) { const auto& a = *in[i].get(); - for (int j = 0; j < a.size(); j++) { + for (auto j = 0; j < a.size(); j++) { sum += a[j]; } } diff --git a/test/Cpp/src/multiport/MultiportToPort.lf b/test/Cpp/src/multiport/MultiportToPort.lf index 31354d320c..5d32c6ee45 100644 --- a/test/Cpp/src/multiport/MultiportToPort.lf +++ b/test/Cpp/src/multiport/MultiportToPort.lf @@ -8,7 +8,7 @@ reactor Source { output[2] out: int reaction(startup) -> out {= - for(int i = 0; i < out.size(); i++) { + for(auto i = 0; i < out.size(); i++) { std::cout << "Source sending " << i << ".\n"; out[i].set(i); } From 4e1ca21a497a3ba6892e569f8523e7a9ab231526 Mon Sep 17 00:00:00 2001 From: i love smoked beef brisket Date: Thu, 31 Aug 2023 21:44:24 -0700 Subject: [PATCH 429/516] Do not use gradlew to run dev version of lf cli tools --- util/scripts/launch.ps1 | 8 ++------ util/scripts/launch.sh | 7 ++----- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/util/scripts/launch.ps1 b/util/scripts/launch.ps1 index dda258cc44..bb14204f5c 100644 --- a/util/scripts/launch.ps1 +++ b/util/scripts/launch.ps1 @@ -29,9 +29,5 @@ $base="$PSScriptRoot\..\..\" $gradlew="${base}/gradlew.bat" # invoke script -if ($args.Length -eq 0) { - & "${gradlew}" -p "${base}" "cli:${tool}:run" -} else { - $argsString = $args -join " " - & "${gradlew}" -p "${base}" "cli:${tool}:run" --args="${argsString}" -} \ No newline at end of file +& "${gradlew}" assemble +& "${base}/build/install/lf-cli/bin/${tool}" @args \ No newline at end of file diff --git a/util/scripts/launch.sh b/util/scripts/launch.sh index 36a342bd25..22a272f81f 100755 --- a/util/scripts/launch.sh +++ b/util/scripts/launch.sh @@ -72,8 +72,5 @@ fi gradlew="${base}/gradlew" # Launch the tool. -if [ $# -eq 0 ]; then - "${gradlew}" -p "${base}" "cli:${tool}:run" -else - "${gradlew}" -p "${base}" "cli:${tool}:run" --args="$*" -fi +"${gradlew}" assemble +"${base}/build/install/lf-cli/bin/${tool}" "$@" \ No newline at end of file From 3edc610040b396e0b44c95605b22fdd4479fb3dc Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Thu, 31 Aug 2023 23:05:29 -0700 Subject: [PATCH 430/516] Update reactor-c --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 4e47b354bc..09b75edfdb 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 4e47b354bce833034faaad0567a65d5b60e60ccc +Subproject commit 09b75edfdbfcd0206a89199fcb65b1678398a0c7 From 4997ead45327df2148038ef9168842032be2a1f3 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Thu, 31 Aug 2023 23:59:28 -0700 Subject: [PATCH 431/516] Let the context be equal to the directory of the container --- core/src/main/java/org/lflang/generator/DockerGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/generator/DockerGenerator.java b/core/src/main/java/org/lflang/generator/DockerGenerator.java index d7d842d15e..4148eb42d1 100644 --- a/core/src/main/java/org/lflang/generator/DockerGenerator.java +++ b/core/src/main/java/org/lflang/generator/DockerGenerator.java @@ -38,7 +38,7 @@ public DockerData generateDockerData() { var dockerFilePath = context.getFileConfig().getSrcGenPath().resolve("Dockerfile"); var dockerFileContent = generateDockerFileContent(); - return new DockerData(name.replace("_", ""), dockerFilePath, dockerFileContent, context); + return new DockerData(name, dockerFilePath, dockerFileContent, context); } public static DockerGenerator dockerGeneratorFactory(LFGeneratorContext context) { From 18abbab2db12cecafc0439814fba2cf2ffa424a1 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Fri, 1 Sep 2023 10:54:08 -0400 Subject: [PATCH 432/516] Test lf_set_array --- test/C/src/ArraySet.lf | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 test/C/src/ArraySet.lf diff --git a/test/C/src/ArraySet.lf b/test/C/src/ArraySet.lf new file mode 100644 index 0000000000..c35bbb9750 --- /dev/null +++ b/test/C/src/ArraySet.lf @@ -0,0 +1,38 @@ +target C + +reactor Source { + output out: int[] + + reaction(startup) -> out {= + // Dynamically allocate an output array of length 3. + int* array = (int*)malloc(3 * sizeof(int)); + // Populate the array. + array[0] = 0; + array[1] = 1; + array[2] = 2; + // Set the output, specifying the array length. + lf_set_array(out, array, 3); + =} +} + +reactor Print { + input in: int[] + + reaction(in) {= + printf("Received: ["); + for (int i = 0; i < in->length; i++) { + if (i > 0) printf(", "); + printf("%d", in->value[i]); + if (in->value[i] != i) { + lf_print_error_and_exit("Expected %d.", i); + } + } + printf("]\n"); + =} +} + +main reactor { + s = new Source() + p = new Print() + s.out -> p.in +} From 8119a55a6c06fdf4470e8acae8838c2b66b58d4e Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Fri, 1 Sep 2023 11:24:41 -0400 Subject: [PATCH 433/516] Added test of persistent input to match docs --- test/C/src/PersistentInputs.lf | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 test/C/src/PersistentInputs.lf diff --git a/test/C/src/PersistentInputs.lf b/test/C/src/PersistentInputs.lf new file mode 100644 index 0000000000..55aa74540d --- /dev/null +++ b/test/C/src/PersistentInputs.lf @@ -0,0 +1,33 @@ +target C { + timeout: 400 ms, + fast: true +} +reactor Source { + output out: int + timer t(100 ms, 200 ms) + state count: int = 1 + reaction(t) -> out {= + lf_set(out, self->count++); + =} +} +reactor Sink { + input in: int + timer t(0, 100 ms) + // For testing, emulate the count variable of Source. + state count: int = 0 + timer t2(100 ms, 200 ms) + reaction(t2) {= + self->count++; + =} + reaction(t) in {= + printf("Value of the input is %d at time %lld\n", in->value, lf_time_logical_elapsed()); + if (in->value != self->count) { + lf_print_error_and_exit("Expected %d.", self->count); + } + =} +} +main reactor { + source = new Source() + sink = new Sink() + source.out -> sink.in +} \ No newline at end of file From 5ee6fec21557ccc2303a2d458091085ca5370f4d Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Fri, 1 Sep 2023 12:32:37 -0400 Subject: [PATCH 434/516] Formated --- test/C/src/PersistentInputs.lf | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/test/C/src/PersistentInputs.lf b/test/C/src/PersistentInputs.lf index 55aa74540d..8ff14e41b2 100644 --- a/test/C/src/PersistentInputs.lf +++ b/test/C/src/PersistentInputs.lf @@ -2,23 +2,23 @@ target C { timeout: 400 ms, fast: true } + reactor Source { output out: int timer t(100 ms, 200 ms) state count: int = 1 - reaction(t) -> out {= - lf_set(out, self->count++); - =} + + reaction(t) -> out {= lf_set(out, self->count++); =} } + reactor Sink { input in: int timer t(0, 100 ms) - // For testing, emulate the count variable of Source. - state count: int = 0 + state count: int = 0 // For testing, emulate the count variable of Source. timer t2(100 ms, 200 ms) - reaction(t2) {= - self->count++; - =} + + reaction(t2) {= self->count++; =} + reaction(t) in {= printf("Value of the input is %d at time %lld\n", in->value, lf_time_logical_elapsed()); if (in->value != self->count) { @@ -26,8 +26,9 @@ reactor Sink { } =} } + main reactor { source = new Source() sink = new Sink() source.out -> sink.in -} \ No newline at end of file +} From 3912e812bf0ce896cb822458d3938566b619356a Mon Sep 17 00:00:00 2001 From: "lingua-franca[bot]" <97201490+francabot@users.noreply.github.com> Date: Fri, 1 Sep 2023 22:58:58 +0000 Subject: [PATCH 435/516] Bump version to 0.5.1-SNAPSHOT --- core/src/main/resources/org/lflang/StringsBundle.properties | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/resources/org/lflang/StringsBundle.properties b/core/src/main/resources/org/lflang/StringsBundle.properties index 29b191399c..dc4f99684f 100644 --- a/core/src/main/resources/org/lflang/StringsBundle.properties +++ b/core/src/main/resources/org/lflang/StringsBundle.properties @@ -1 +1 @@ -VERSION = 0.4.1-SNAPSHOT +VERSION = 0.5.1-SNAPSHOT diff --git a/gradle.properties b/gradle.properties index fd082fc31f..245b5cb0ba 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ [header] group=org.lflang -version=0.4.1-SNAPSHOT +version=0.5.1-SNAPSHOT [versions] antlrVersion=4.7.2 From e9657310fb6a6d41ae018d7a39283204037b2d81 Mon Sep 17 00:00:00 2001 From: "lingua-franca[bot]" <97201490+francabot@users.noreply.github.com> Date: Fri, 1 Sep 2023 18:32:58 -0700 Subject: [PATCH 436/516] Update CHANGELOG.md --- CHANGELOG.md | 240 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fd8bbdb56..c469ed6334 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,244 @@ # Changelog + +## [v0.5.0](https://github.com/lf-lang/lingua-franca/tree/v0.5.0) (2023-09-01) + +**Highlights** + +This release introduces new syntax for initializers, includes a renovation of the C backend to let it generate more modular code, and brings new features like a watchdog construct, support for generics in C, support for SMT-solver-based formal verification using UCLID-5, and bare-iron support for the Raspberry Pi Pico platform. + +**🚀 New Features** + +- Types allowed in reactor type args [\#1639](https://github.com/lf-lang/lingua-franca/pull/1639) ([@oowekyala](https://github.com/oowekyala)) +- Equals initializer syntax [\#1580](https://github.com/lf-lang/lingua-franca/pull/1580) ([@oowekyala](https://github.com/oowekyala)) +- `--json` and `--json-file` CLI args add to `lfc` [\#1686](https://github.com/lf-lang/lingua-franca/pull/1686) ([@patilatharva](https://github.com/patilatharva)) +- Generated structs exposed in header files, reactions linkable from separate source files, and updated C target preamble visibility [\#1599](https://github.com/lf-lang/lingua-franca/pull/1599) ([@petervdonovan](https://github.com/petervdonovan)) +- Preprocessor definition for `LF_PACKAGE_DIRECTORY` [\#1720](https://github.com/lf-lang/lingua-franca/pull/1720) ([@edwardalee](https://github.com/edwardalee)) +- Enclave connections and enclave coordination in the C++ target [\#1665](https://github.com/lf-lang/lingua-franca/pull/1665) ([@cmnrd](https://github.com/cmnrd)) +- Mechanism for printing execution statistics [\#1743](https://github.com/lf-lang/lingua-franca/pull/1743) ([@cmnrd](https://github.com/cmnrd)) +- Watchdog support for the C target [\#1730](https://github.com/lf-lang/lingua-franca/pull/1730) ([@Benichiwa](https://github.com/Benichiwa)) +- Automatically generated .vscode/settings.json file [\#1759](https://github.com/lf-lang/lingua-franca/pull/1759) ([@edwardalee](https://github.com/edwardalee)) +- C Generics [\#1681](https://github.com/lf-lang/lingua-franca/pull/1681) ([@mkhubaibumer](https://github.com/mkhubaibumer)) +- Generic params allowed as generic arguments in C target [\#1804](https://github.com/lf-lang/lingua-franca/pull/1804) ([@petervdonovan](https://github.com/petervdonovan)) +- New `--check` flag for `lff` [\#1822](https://github.com/lf-lang/lingua-franca/pull/1822) ([@cmnrd](https://github.com/cmnrd)) +- Environments in the C target [\#1772](https://github.com/lf-lang/lingua-franca/pull/1772) ([@erlingrj](https://github.com/erlingrj)) +- `lfd` binary for generating diagrams from the command line [\#1713](https://github.com/lf-lang/lingua-franca/pull/1713) ([@cmnrd](https://github.com/cmnrd)) +- `fedsd` utility updated to make the RTI optional and support enclaves visualization [\#1870](https://github.com/lf-lang/lingua-franca/pull/1870) ([@ChadliaJerad](https://github.com/ChadliaJerad)) +- C math lib always linked for C target [\#1894](https://github.com/lf-lang/lingua-franca/pull/1894) ([@cmnrd](https://github.com/cmnrd)) +- Critical sections enabled outside of the context of a reactor [\#1901](https://github.com/lf-lang/lingua-franca/pull/1901) ([@edwardalee](https://github.com/edwardalee)) +- Uclid5-based LF Verifier [\#1271](https://github.com/lf-lang/lingua-franca/pull/1271) ([@lsk567](https://github.com/lsk567)) +- Python launch scripts [\#1914](https://github.com/lf-lang/lingua-franca/pull/1914) ([@cmnrd](https://github.com/cmnrd)) +- Raspberry Pi Pico target support [\#1831](https://github.com/lf-lang/lingua-franca/pull/1831) ([@gundralaa](https://github.com/gundralaa)) +- Handling cyclic dependencies for TypeScript federated execution [\#1925](https://github.com/lf-lang/lingua-franca/pull/1925) ([@byeong-gil](https://github.com/byeong-gil)) + +**✨ Enhancements** + +- Keeplive natively inferred in the C++ runtime [\#1630](https://github.com/lf-lang/lingua-franca/pull/1630) ([@cmnrd](https://github.com/cmnrd)) +- File access [\#1715](https://github.com/lf-lang/lingua-franca/pull/1715) ([@edwardalee](https://github.com/edwardalee)) +- Validator rules to check if target supports federation or inheritance [\#1726](https://github.com/lf-lang/lingua-franca/pull/1726) ([@cmnrd](https://github.com/cmnrd)) +- No more space inserted after `interleaved` keyword by formatter [\#1846](https://github.com/lf-lang/lingua-franca/pull/1846) ([@cmnrd](https://github.com/cmnrd)) +- More natural syntax for reaction declarations [\#1853](https://github.com/lf-lang/lingua-franca/pull/1853) ([@lhstrh](https://github.com/lhstrh)) +- Fix for extra whitespace around info messages [\#1879](https://github.com/lf-lang/lingua-franca/pull/1879) ([@oowekyala](https://github.com/oowekyala)) +- TypeScript runtime bumped to `v0.5.0` [\#1927](https://github.com/lf-lang/lingua-franca/pull/1927) ([@byeong-gil](https://github.com/byeong-gil)) +- Support for named and bodyless reactions in C++ [\#1933](https://github.com/lf-lang/lingua-franca/pull/1933) ([@cmnrd](https://github.com/cmnrd)) +- Added @layout annotation to add arbitrary layout options an elements [\#1951](https://github.com/lf-lang/lingua-franca/pull/1951) ([@soerendomroes](https://github.com/soerendomroes)) + +**🔧 Fixes** + +- Physical connections implemented as AST transformation [\#1596](https://github.com/lf-lang/lingua-franca/pull/1596) ([@erlingrj](https://github.com/erlingrj)) +- Fix for language server "Build and Run" command [\#1619](https://github.com/lf-lang/lingua-franca/pull/1619) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix for passing of command line options from lfc to the generator [\#1631](https://github.com/lf-lang/lingua-franca/pull/1631) ([@cmnrd](https://github.com/cmnrd)) +- Fix for validation of target properties [\#1629](https://github.com/lf-lang/lingua-franca/pull/1629) ([@cmnrd](https://github.com/cmnrd)) +- Fix in validation so that warnings are not reported as errors [\#1643](https://github.com/lf-lang/lingua-franca/pull/1643) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix for NPE in lfc error reporting [\#1655](https://github.com/lf-lang/lingua-franca/pull/1655) ([@cmnrd](https://github.com/cmnrd)) +- Upstream connection delays now properly handled in the TypeScript federates [\#1607](https://github.com/lf-lang/lingua-franca/pull/1607) ([@byeong-gil](https://github.com/byeong-gil)) +- Fix for authenticated federation [\#1698](https://github.com/lf-lang/lingua-franca/pull/1698) ([@Jakio815](https://github.com/Jakio815)) +- Bugfixes in handling of `files` target property [\#1725](https://github.com/lf-lang/lingua-franca/pull/1725) ([@lhstrh](https://github.com/lhstrh)) +- Preambles properly inherited from superclasses [\#1732](https://github.com/lf-lang/lingua-franca/pull/1732) ([@edwardalee](https://github.com/edwardalee)) +- Clean `Correspondence` tags out of generated C code [\#1737](https://github.com/lf-lang/lingua-franca/pull/1737) ([@petervdonovan](https://github.com/petervdonovan)) +- Reactor classes in the same file with the same name, up to case differences, are prohibited [\#1741](https://github.com/lf-lang/lingua-franca/pull/1741) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix for ROS serialization [\#1755](https://github.com/lf-lang/lingua-franca/pull/1755) ([@petervdonovan](https://github.com/petervdonovan)) +- Improved line adjustment logic for federated programs [\#1760](https://github.com/lf-lang/lingua-franca/pull/1760) ([@petervdonovan](https://github.com/petervdonovan)) +- Multiple fixes for federated programs with TypeScript target [\#1752](https://github.com/lf-lang/lingua-franca/pull/1752) ([@byeong-gil](https://github.com/byeong-gil)) +- Fix for race condition in `uniqueName` [\#1815](https://github.com/lf-lang/lingua-franca/pull/1815) ([@petervdonovan](https://github.com/petervdonovan)) +- Bugfix in integration tests and fixed ROS2 tests [\#1841](https://github.com/lf-lang/lingua-franca/pull/1841) ([@cmnrd](https://github.com/cmnrd)) +- Formatter fixes [\#1840](https://github.com/lf-lang/lingua-franca/pull/1840) ([@petervdonovan](https://github.com/petervdonovan)) +- Adjustments for running the LF compiler in Epoch [\#1844](https://github.com/lf-lang/lingua-franca/pull/1844) ([@a-sr](https://github.com/a-sr)) +- More formatter fixes [\#1850](https://github.com/lf-lang/lingua-franca/pull/1850) ([@petervdonovan](https://github.com/petervdonovan)) +- More formatter fixes [\#1851](https://github.com/lf-lang/lingua-franca/pull/1851) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix for naming collision when generic reactor is instantiated with different parameters [\#1864](https://github.com/lf-lang/lingua-franca/pull/1864) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix for inheritance problem exposed in examples [\#1891](https://github.com/lf-lang/lingua-franca/pull/1891) ([@lhstrh](https://github.com/lhstrh)) +- Fix verifier error when there is no main reactor [\#1916](https://github.com/lf-lang/lingua-franca/pull/1916) ([@lsk567](https://github.com/lsk567)) +- No more use of unordered reactions in federated programs; fix for deadlocks in some federated programs [\#1684](https://github.com/lf-lang/lingua-franca/pull/1684) ([@arengarajan99](https://github.com/arengarajan99)) +- Keyword `extends` added to tokens allowed in reaction bodies [\#1926](https://github.com/lf-lang/lingua-franca/pull/1926) ([@lhstrh](https://github.com/lhstrh)) +- Fix for edge case in which comments are dropped [\#1924](https://github.com/lf-lang/lingua-franca/pull/1924) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix for IllegalArgumentException in diagram synthesis [\#1932](https://github.com/lf-lang/lingua-franca/pull/1932) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix for STP violation [\#1935](https://github.com/lf-lang/lingua-franca/pull/1935) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix for after delays that use user-provided declarations [\#1959](https://github.com/lf-lang/lingua-franca/pull/1959) ([@petervdonovan](https://github.com/petervdonovan)) +- Bugfix for when top-level multiport width in federation depends on parameter [\#1956](https://github.com/lf-lang/lingua-franca/pull/1956) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix compilation error in code for reset state variables with time type [\#1964](https://github.com/lf-lang/lingua-franca/pull/1964) ([@a-sr](https://github.com/a-sr)) +- Fixed path issue in generated `docker-compose.yml` for federations [\#1983](https://github.com/lf-lang/lingua-franca/pull/1983) ([@lhstrh](https://github.com/lhstrh)) + +**🚧 Maintenance and Refactoring** + +- Migration of Epoch into separate repository [\#1482](https://github.com/lf-lang/lingua-franca/pull/1482) ([@a-sr](https://github.com/a-sr)) +- Improved CLI argument handling using `picocli` [\#1534](https://github.com/lf-lang/lingua-franca/pull/1534) ([@patilatharva](https://github.com/patilatharva)) +- Compile definitions no longer hardcoded in generated CMakeLists.txt [\#1622](https://github.com/lf-lang/lingua-franca/pull/1622) ([@petervdonovan](https://github.com/petervdonovan)) +- Remove unchecked compilation warnings [\#1638](https://github.com/lf-lang/lingua-franca/pull/1638) ([@oowekyala](https://github.com/oowekyala)) +- Refactoring of part of the federated package [\#1663](https://github.com/lf-lang/lingua-franca/pull/1663) ([@lhstrh](https://github.com/lhstrh)) +- Removal of the use of non-API global variables in tests [\#1696](https://github.com/lf-lang/lingua-franca/pull/1696) ([@edwardalee](https://github.com/edwardalee)) +- Gradle bumped to version 8 [\#1691](https://github.com/lf-lang/lingua-franca/pull/1691) ([@lhstrh](https://github.com/lhstrh)) +- Removal of deprecated `build-lfc` script and `buildLfc` Gradle task [\#1714](https://github.com/lf-lang/lingua-franca/pull/1714) ([@cmnrd](https://github.com/cmnrd)) +- Delete unnecessary complexity from `build-lf-cli` [\#1745](https://github.com/lf-lang/lingua-franca/pull/1745) ([@petervdonovan](https://github.com/petervdonovan)) +- C files for Python support retrieved from reactor-c repo [\#1757](https://github.com/lf-lang/lingua-franca/pull/1757) ([@lhstrh](https://github.com/lhstrh)) +- Google autoformatter applied to all files [\#1766](https://github.com/lf-lang/lingua-franca/pull/1766) ([@lhstrh](https://github.com/lhstrh)) +- Redesign of the repository layout and gradle configuration [\#1779](https://github.com/lf-lang/lingua-franca/pull/1779) ([@cmnrd](https://github.com/cmnrd)) +- Removal of odd mechanism for loading target generators dynamically [\#1813](https://github.com/lf-lang/lingua-franca/pull/1813) ([@cmnrd](https://github.com/cmnrd)) +- Default line length set to 100 for LF files [\#1389](https://github.com/lf-lang/lingua-franca/pull/1389) ([@petervdonovan](https://github.com/petervdonovan)) +- KlighD bumped to `2.3.0` and now retrieved from Maven Central [\#1823](https://github.com/lf-lang/lingua-franca/pull/1823) ([@cmnrd](https://github.com/cmnrd)) +- Refactor error reporter [\#1527](https://github.com/lf-lang/lingua-franca/pull/1527) ([@oowekyala](https://github.com/oowekyala)) +- Unknown port types handled with `unknown` instead of `Present` [\#1857](https://github.com/lf-lang/lingua-franca/pull/1857) ([@lhstrh](https://github.com/lhstrh)) +- TS code generator adjusted to appease `eslint` [\#1878](https://github.com/lf-lang/lingua-franca/pull/1878) ([@lhstrh](https://github.com/lhstrh)) +- Minor fixes for the README in the test directory [\#1903](https://github.com/lf-lang/lingua-franca/pull/1903) ([@lsk567](https://github.com/lsk567)) +- Declarative Port Graph in C++ Runtime [\#1848](https://github.com/lf-lang/lingua-franca/pull/1848) ([@revol-xut](https://github.com/revol-xut)) +- No more use of unordered reactions in federated programs; fix for deadlocks in some federated programs [\#1684](https://github.com/lf-lang/lingua-franca/pull/1684) ([@arengarajan99](https://github.com/arengarajan99)) +- Tracing utils and Zephyr run scripts moved from `util` folder [\#1948](https://github.com/lf-lang/lingua-franca/pull/1948) ([@erlingrj](https://github.com/erlingrj)) + +**📖 Documentation** + +- Documentation about LSP tests in `README.md` [\#1587](https://github.com/lf-lang/lingua-franca/pull/1587) ([@petervdonovan](https://github.com/petervdonovan)) +- Updated README for the federate trace visualizer [\#1735](https://github.com/lf-lang/lingua-franca/pull/1735) ([@ChadliaJerad](https://github.com/ChadliaJerad)) + +**🧪 Tests** + +- TypeScript tests for federates with physical connections [\#1623](https://github.com/lf-lang/lingua-franca/pull/1623) ([@byeong-gil](https://github.com/byeong-gil)) +- Zephyr tests pinned to particular version of docker image [\#1648](https://github.com/lf-lang/lingua-franca/pull/1648) ([@erlingrj](https://github.com/erlingrj)) +- Test for the support of delayed physical connections in the TypeScript target [\#1676](https://github.com/lf-lang/lingua-franca/pull/1676) ([@byeong-gil](https://github.com/byeong-gil)) +- Test for parsing CLI arguments in `lfc` [\#1668](https://github.com/lf-lang/lingua-franca/pull/1668) ([@patilatharva](https://github.com/patilatharva)) +- Zephyr regression tests executed on QEMU [\#1678](https://github.com/lf-lang/lingua-franca/pull/1678) ([@erlingrj](https://github.com/erlingrj)) +- CodeCov reporting for CLI tests [\#1688](https://github.com/lf-lang/lingua-franca/pull/1688) ([@lhstrh](https://github.com/lhstrh)) +- Test to help ensure that level-based scheduling does not cause deadlock [\#1703](https://github.com/lf-lang/lingua-franca/pull/1703) ([@edwardalee](https://github.com/edwardalee)) +- Flaky tests adjusted [\#1764](https://github.com/lf-lang/lingua-franca/pull/1764) ([@edwardalee](https://github.com/edwardalee)) +- `SimpleFederatedAuthenticated.lf` test passing [\#1776](https://github.com/lf-lang/lingua-franca/pull/1776) ([@Jakio815](https://github.com/Jakio815)) +- CI updates [\#1814](https://github.com/lf-lang/lingua-franca/pull/1814) ([@petervdonovan](https://github.com/petervdonovan)) +- Parallel execution of round trip tests [\#1845](https://github.com/lf-lang/lingua-franca/pull/1845) ([@oowekyala](https://github.com/oowekyala)) +- Tests for `lf_request_stop` with enclaves and federates [\#1871](https://github.com/lf-lang/lingua-franca/pull/1871) ([@edwardalee](https://github.com/edwardalee)) +- Fixed code coverage aggregation and reporting [\#1868](https://github.com/lf-lang/lingua-franca/pull/1868) ([@cmnrd](https://github.com/cmnrd)) +- New job for building `epoch` in CI [\#1974](https://github.com/lf-lang/lingua-franca/pull/1974) ([@lhstrh](https://github.com/lhstrh)) + +**⬆️ Updated Dependencies** + +- Update to Zephyr v3.3.0 and SDK v0.16.1 [\#1825](https://github.com/lf-lang/lingua-franca/pull/1825) ([@erlingrj](https://github.com/erlingrj)) +- Reactor-ts bumped to v0.4.0 [\#1749](https://github.com/lf-lang/lingua-franca/pull/1749) ([@lhstrh](https://github.com/lhstrh)) +- Gradle Wrapper bumped to `8.1.1` [\#1890](https://github.com/lf-lang/lingua-franca/pull/1890) ([@lhstrh](https://github.com/lhstrh)) +- Eclipse-related dependencies bumped to latest releases [\#1889](https://github.com/lf-lang/lingua-franca/pull/1889) ([@a-sr](https://github.com/a-sr)) +- TypeScript runtime bumped to `v0.5.0` [\#1927](https://github.com/lf-lang/lingua-franca/pull/1927) ([@byeong-gil](https://github.com/byeong-gil)) + + +### Submodule [lf-lang/reactor-c](http://github.com/lf-lang/reactor-c) + +**🚀 New Features** + +- New tracepoint for deadline misses [\#169](https://github.com/lf-lang/reactor-c/pull/169) ([@erlingrj](https://github.com/erlingrj)) +- Compile definitions for federated programs [\#179](https://github.com/lf-lang/reactor-c/pull/179) ([@petervdonovan](https://github.com/petervdonovan)) +- Tracing federate interactions [\#178](https://github.com/lf-lang/reactor-c/pull/178) ([@edwardalee](https://github.com/edwardalee)) +- CMake definition to find `FEDERATED_AUTHENTICATED` [\#196](https://github.com/lf-lang/reactor-c/pull/196) ([@Jakio815](https://github.com/Jakio815)) +- Memory reporting [\#201](https://github.com/lf-lang/reactor-c/pull/201) ([@edwardalee](https://github.com/edwardalee)) +- Added `LF_PACKAGE_DIRECTORY` [\#204](https://github.com/lf-lang/reactor-c/pull/204) ([@edwardalee](https://github.com/edwardalee)) +- Runtime support for watchdogs [\#177](https://github.com/lf-lang/reactor-c/pull/177) ([@Benichiwa](https://github.com/Benichiwa)) +- Environments [\#212](https://github.com/lf-lang/reactor-c/pull/212) ([@erlingrj](https://github.com/erlingrj)) +- Enclave request stop [\#244](https://github.com/lf-lang/reactor-c/pull/244) ([@edwardalee](https://github.com/edwardalee)) +- Critical sections enabled outside of the context of a reactor [\#249](https://github.com/lf-lang/reactor-c/pull/249) ([@edwardalee](https://github.com/edwardalee)) +- Rp2040 Target Support [\#253](https://github.com/lf-lang/reactor-c/pull/253) ([@gundralaa](https://github.com/gundralaa)) +- Platform support for Raspberry Pi Pico [\#233](https://github.com/lf-lang/reactor-c/pull/233) ([@gundralaa](https://github.com/gundralaa)) + +**✨ Enhancements** + +- Removal of unnecessary TAG messages [\#175](https://github.com/lf-lang/reactor-c/pull/175) ([@byeong-gil](https://github.com/byeong-gil)) +- Cleaner namespace [\#189](https://github.com/lf-lang/reactor-c/pull/189) ([@petervdonovan](https://github.com/petervdonovan)) +- File access and doc fixes [\#198](https://github.com/lf-lang/reactor-c/pull/198) ([@edwardalee](https://github.com/edwardalee)) +- Improvements of support for watchdogs [\#209](https://github.com/lf-lang/reactor-c/pull/209) ([@edwardalee](https://github.com/edwardalee)) +- Switch to more general thread creation in Zephyr support [\#194](https://github.com/lf-lang/reactor-c/pull/194) ([@siljesu](https://github.com/siljesu)) +- Minor improvements to Zephyr platform [\#187](https://github.com/lf-lang/reactor-c/pull/187) ([@erlingrj](https://github.com/erlingrj)) +- Output error when trying to use --auth (-a) for RTI built without -DAUTH=ON [\#222](https://github.com/lf-lang/reactor-c/pull/222) ([@hokeun](https://github.com/hokeun)) +- Change nanosleep to lf_sleep in federate and RTI code [\#219](https://github.com/lf-lang/reactor-c/pull/219) ([@siljesu](https://github.com/siljesu)) +- [C-Generics] Helper Macros [\#190](https://github.com/lf-lang/reactor-c/pull/190) ([@mkhubaibumer](https://github.com/mkhubaibumer)) +- RTI exit while saving the trace file [\#228](https://github.com/lf-lang/reactor-c/pull/228) ([@ChadliaJerad](https://github.com/ChadliaJerad)) +- Less namespace pollution [\#240](https://github.com/lf-lang/reactor-c/pull/240) ([@erlingrj](https://github.com/erlingrj)) +- Enclaves tuning [\#243](https://github.com/lf-lang/reactor-c/pull/243) ([@edwardalee](https://github.com/edwardalee)) +- If clock sync is on, link math [\#252](https://github.com/lf-lang/reactor-c/pull/252) ([@petervdonovan](https://github.com/petervdonovan)) +- No more use of unordered reactions in federated programs [\#191](https://github.com/lf-lang/reactor-c/pull/191) ([@arengarajan99](https://github.com/arengarajan99)) + +**🔧 Fixes** + +- Fix for definition of `LF_TIME_BUFFER_LENGTH` [\#197](https://github.com/lf-lang/reactor-c/pull/197) ([@edwardalee](https://github.com/edwardalee)) +- Scheduler leak fix [\#200](https://github.com/lf-lang/reactor-c/pull/200) ([@edwardalee](https://github.com/edwardalee)) +- Fix for Arduino to avoid duplicate definition of `timespec` [\#195](https://github.com/lf-lang/reactor-c/pull/195) ([@arengarajan99](https://github.com/arengarajan99)) +- Suppression of "no symbols" warnings emitted by ranlib [\#214](https://github.com/lf-lang/reactor-c/pull/214) ([@petervdonovan](https://github.com/petervdonovan)) +- Segfault fix [\#218](https://github.com/lf-lang/reactor-c/pull/218) ([@petervdonovan](https://github.com/petervdonovan)) +- Zephyr fixes on thread creation and deletion [\#223](https://github.com/lf-lang/reactor-c/pull/223) ([@erlingrj](https://github.com/erlingrj)) +- Minor fix of the federate id in the tracepoint [\#245](https://github.com/lf-lang/reactor-c/pull/245) ([@ChadliaJerad](https://github.com/ChadliaJerad)) +- Fix protocol of HMAC authentication to start from federate. [\#231](https://github.com/lf-lang/reactor-c/pull/231) ([@Jakio815](https://github.com/Jakio815)) +- Use of correct federate ID in tracing of absent messages [\#248](https://github.com/lf-lang/reactor-c/pull/248) ([@ChadliaJerad](https://github.com/ChadliaJerad)) +- Memory leak in Python target fixed [\#246](https://github.com/lf-lang/reactor-c/pull/246) ([@jackykwok2024](https://github.com/jackykwok2024)) +- Fix for fatal error raised during shutdown when decrementing a tag barrier that is zero [\#251](https://github.com/lf-lang/reactor-c/pull/251) ([@petervdonovan](https://github.com/petervdonovan)) +- Fix for STP violation [\#257](https://github.com/lf-lang/reactor-c/pull/257) ([@petervdonovan](https://github.com/petervdonovan)) +- Updated Makefile and docs for fedsd utility [\#262](https://github.com/lf-lang/reactor-c/pull/262) ([@ChadliaJerad](https://github.com/ChadliaJerad)) + +**🚧 Maintenance and Refactoring** + +- Functions of rti.c moved to rti_lib.c to enable reuse [\#172](https://github.com/lf-lang/reactor-c/pull/172) ([@Jakio815](https://github.com/Jakio815)) +- Code in `rti.c` made available as library [\#174](https://github.com/lf-lang/reactor-c/pull/174) ([@Jakio815](https://github.com/Jakio815)) +- Documentation and code cleanup [\#193](https://github.com/lf-lang/reactor-c/pull/193) ([@edwardalee](https://github.com/edwardalee)) +- Platform abstraction layer for the RTI [\#213](https://github.com/lf-lang/reactor-c/pull/213) ([@siljesu](https://github.com/siljesu)) +- C files from reactor-c-py moved back into the reactor-c repo [\#217](https://github.com/lf-lang/reactor-c/pull/217) ([@lhstrh](https://github.com/lhstrh)) +- Struct refactoring for actions and ports [\#216](https://github.com/lf-lang/reactor-c/pull/216) ([@edwardalee](https://github.com/edwardalee)) +- Refactoring of the RTI implementation [\#224](https://github.com/lf-lang/reactor-c/pull/224) ([@ChadliaJerad](https://github.com/ChadliaJerad)) +- `_lf_count_token_allocations` made `extern` instead of `static` [\#236](https://github.com/lf-lang/reactor-c/pull/236) ([@erlingrj](https://github.com/erlingrj)) +- Refactoring of obsolete `gethostbyname()` in `connect_to_rti()` [\#220](https://github.com/lf-lang/reactor-c/pull/220) ([@siljesu](https://github.com/siljesu)) +- No more use of unordered reactions in federated programs [\#191](https://github.com/lf-lang/reactor-c/pull/191) ([@arengarajan99](https://github.com/arengarajan99)) +- Fewer warnings [\#258](https://github.com/lf-lang/reactor-c/pull/258) ([@edwardalee](https://github.com/edwardalee)) +- Tracing utils moved into reactor-c [\#259](https://github.com/lf-lang/reactor-c/pull/259) ([@erlingrj](https://github.com/erlingrj)) + +**📖 Documentation** + +- Documentation and code cleanup [\#193](https://github.com/lf-lang/reactor-c/pull/193) ([@edwardalee](https://github.com/edwardalee)) + + +### Submodule [lf-lang/reactor-cpp](http://github.com/lf-lang/reactor-cpp) + +**🚀 New Features** + +- Enclave connections and enclave coordination [\#44](https://github.com/lf-lang/reactor-cpp/pull/44) ([@cmnrd](https://github.com/cmnrd)) +- Simple mechanism for collecting statistics during execution [\#47](https://github.com/lf-lang/reactor-cpp/pull/47) ([@cmnrd](https://github.com/cmnrd)) +- Port graph [\#51](https://github.com/lf-lang/reactor-cpp/pull/51) ([@revol-xut](https://github.com/revol-xut)) + +**✨ Enhancements** + +- Keep track of input actions in the environment [\#42](https://github.com/lf-lang/reactor-cpp/pull/42) ([@cmnrd](https://github.com/cmnrd)) +- Factored event queue into its own class and fixed race condition between multiple starting enclaves [\#45](https://github.com/lf-lang/reactor-cpp/pull/45) ([@cmnrd](https://github.com/cmnrd)) + +**🔧 Fixes** + +- Fix race condition in time barriers [\#49](https://github.com/lf-lang/reactor-cpp/pull/49) ([@cmnrd](https://github.com/cmnrd)) +- Fix validate method and fix incorrect phase checks [\#50](https://github.com/lf-lang/reactor-cpp/pull/50) ([@cmnrd](https://github.com/cmnrd)) + + +### Submodule [lf-lang/reactor-rs](http://github.com/lf-lang/reactor-rs) + +**🔧 Fixes** + +- Fix use of multiport as reaction source [\#43](https://github.com/lf-lang/reactor-rs/pull/43) ([@oowekyala](https://github.com/oowekyala)) + +**🚧 Maintenance and Refactoring** + +- Use Cargo workspaces to directly include vecmap dependency [\#40](https://github.com/lf-lang/reactor-rs/pull/40) ([@jhaye](https://github.com/jhaye)) +- Fixes for current Clippy version [\#45](https://github.com/lf-lang/reactor-rs/pull/45) ([@jhaye](https://github.com/jhaye)) + +**⬆️ Updated Dependencies** + +- Dependencies bumped to avoid vulnerability in smallvec [\#44](https://github.com/lf-lang/reactor-rs/pull/44) ([@oowekyala](https://github.com/oowekyala)) + + ## [v0.4.0](https://github.com/lf-lang/lingua-franca/tree/v0.4.0) (2023-03-01) From 5d6d8843b4aadb4826872c4b009805a179450720 Mon Sep 17 00:00:00 2001 From: "lingua-franca[bot]" <97201490+francabot@users.noreply.github.com> Date: Fri, 1 Sep 2023 18:35:07 -0700 Subject: [PATCH 437/516] Bump version to 0.5.0 --- CHANGELOG.md | 2 +- core/src/main/resources/org/lflang/StringsBundle.properties | 2 +- gradle.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c469ed6334..f916e8496d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # Changelog - + ## [v0.5.0](https://github.com/lf-lang/lingua-franca/tree/v0.5.0) (2023-09-01) **Highlights** diff --git a/core/src/main/resources/org/lflang/StringsBundle.properties b/core/src/main/resources/org/lflang/StringsBundle.properties index 29b191399c..b58621d407 100644 --- a/core/src/main/resources/org/lflang/StringsBundle.properties +++ b/core/src/main/resources/org/lflang/StringsBundle.properties @@ -1 +1 @@ -VERSION = 0.4.1-SNAPSHOT +VERSION = 0.5.0 diff --git a/gradle.properties b/gradle.properties index fd082fc31f..ce77106367 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ [header] group=org.lflang -version=0.4.1-SNAPSHOT +version=0.5.0 [versions] antlrVersion=4.7.2 From fbaa670090e3704f38883047fc1a4c2cf06f12ba Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 1 Sep 2023 21:00:09 -0700 Subject: [PATCH 438/516] Do not squeeze reaction bodies onto one line. This adds state, but it is a private non-static member and the formatter is not parallelized, so it should be OK. The technique employed here is I think the most reasonable way to account for context-sensitive formatting rules. --- core/src/main/java/org/lflang/ast/ToLf.java | 17 ++++++++++ test/C/src/ActionDelay.lf | 8 +++-- test/C/src/ActionWithNoReaction.lf | 4 ++- test/C/src/After.lf | 8 +++-- test/C/src/AfterCycles.lf | 8 +++-- test/C/src/AfterZero.lf | 8 +++-- test/C/src/Alignment.lf | 4 ++- test/C/src/CompositionGain.lf | 4 ++- test/C/src/DanglingOutput.lf | 4 ++- test/C/src/DeadlineAnytime.lf | 8 +++-- test/C/src/DeadlineInherited.lf | 12 +++++-- test/C/src/DeadlinePriority.lf | 12 +++++-- test/C/src/DeadlineWithAfterDelay.lf | 8 +++-- test/C/src/DeadlineWithBanks.lf | 8 +++-- test/C/src/DeadlineZero.lf | 8 +++-- test/C/src/DelayArray.lf | 8 +++-- test/C/src/DelayInt.lf | 8 +++-- test/C/src/DelayString.lf | 8 +++-- test/C/src/DelayedAction.lf | 4 ++- test/C/src/DelayedReaction.lf | 4 ++- test/C/src/Determinism.lf | 8 +++-- test/C/src/DoublePort.lf | 12 +++++-- test/C/src/Gain.lf | 8 +++-- test/C/src/GetMicroStep.lf | 4 ++- test/C/src/Hierarchy.lf | 4 ++- test/C/src/Hierarchy2.lf | 4 ++- test/C/src/Import.lf | 4 ++- test/C/src/ImportComposition.lf | 4 ++- test/C/src/ImportRenamed.lf | 4 ++- test/C/src/InheritanceAction.lf | 8 +++-- test/C/src/ManualDelayedReaction.lf | 9 ++++-- test/C/src/Methods.lf | 8 +++-- test/C/src/MethodsRecursive.lf | 4 ++- test/C/src/MethodsSameName.lf | 8 +++-- test/C/src/Microsteps.lf | 4 ++- test/C/src/Minimal.lf | 4 ++- test/C/src/MultipleContained.lf | 4 ++- test/C/src/NestedTriggeredReactions.lf | 12 +++++-- test/C/src/ParameterizedState.lf | 4 ++- test/C/src/PhysicalConnection.lf | 4 ++- test/C/src/PingPong.lf | 4 ++- test/C/src/PreambleInherited.lf | 4 ++- test/C/src/ReadOutputOfContainedReactor.lf | 4 ++- test/C/src/RepeatedInheritance.lf | 4 ++- test/C/src/Schedule.lf | 8 +++-- test/C/src/ScheduleLogicalAction.lf | 8 +++-- test/C/src/SelfLoop.lf | 4 ++- test/C/src/SendingInside2.lf | 4 ++- test/C/src/SendsPointerTest.lf | 4 ++- test/C/src/SetToken.lf | 8 +++-- test/C/src/SlowingClock.lf | 4 ++- test/C/src/StartupOutFromInside.lf | 4 ++- test/C/src/TimeLimit.lf | 4 ++- test/C/src/TimeState.lf | 4 ++- test/C/src/UnconnectedInput.lf | 4 ++- test/C/src/Wcet.lf | 4 ++- test/C/src/arduino/Blink.lf | 12 +++++-- test/C/src/arduino/BlinkAttemptThreading.lf | 12 +++++-- test/C/src/arduino/BlinkMBED.lf | 12 +++++-- test/C/src/arduino/DigitalReadSerial.lf | 4 ++- test/C/src/arduino/Fade.lf | 4 ++- test/C/src/arduino/ReadAnalogVoltage.lf | 4 ++- test/C/src/concurrent/DelayIntThreaded.lf | 8 +++-- test/C/src/concurrent/DeterminismThreaded.lf | 8 +++-- test/C/src/concurrent/GainThreaded.lf | 8 +++-- test/C/src/concurrent/ImportThreaded.lf | 4 ++- test/C/src/concurrent/MinimalThreaded.lf | 4 ++- test/C/src/concurrent/PingPongThreaded.lf | 4 ++- test/C/src/concurrent/TimeLimitThreaded.lf | 4 ++- test/C/src/enclave/EnclaveRequestStop.lf | 4 ++- test/C/src/federated/BroadcastFeedback.lf | 4 ++- .../BroadcastFeedbackWithHierarchy.lf | 4 ++- test/C/src/federated/CycleDetection.lf | 12 +++++-- test/C/src/federated/DecentralizedP2PComm.lf | 4 ++- .../DecentralizedP2PUnbalancedTimeout.lf | 4 ++- ...centralizedP2PUnbalancedTimeoutPhysical.lf | 4 ++- test/C/src/federated/DistributedBank.lf | 4 ++- test/C/src/federated/DistributedDoublePort.lf | 12 +++++-- .../DistributedPhysicalActionUpstream.lf | 4 ++- .../DistributedPhysicalActionUpstreamLong.lf | 4 ++- .../federated/EnclaveFederatedRequestStop.lf | 4 ++- test/C/src/federated/FeedbackDelay.lf | 8 +++-- test/C/src/federated/FeedbackDelaySimple.lf | 4 ++- test/C/src/federated/HelloDistributed.lf | 8 +++-- test/C/src/federated/InheritanceFederated.lf | 4 ++- .../federated/InheritanceFederatedImport.lf | 4 ++- test/C/src/federated/LevelPattern.lf | 12 +++++-- ...stributedCentralizedPrecedenceHierarchy.lf | 8 +++-- test/C/src/federated/SpuriousDependency.lf | 8 +++-- test/C/src/federated/StopAtShutdown.lf | 20 +++++++++--- test/C/src/federated/TopLevelArtifacts.lf | 8 +++-- test/C/src/generics/ParameterAsArgument.lf | 4 ++- test/C/src/generics/TypeCheck.lf | 4 ++- test/C/src/lib/Count.lf | 4 ++- test/C/src/lib/FileLevelPreamble.lf | 4 ++- test/C/src/lib/GenDelay.lf | 12 +++++-- test/C/src/lib/Imported.lf | 4 ++- test/C/src/lib/ImportedComposition.lf | 4 ++- test/C/src/lib/InternalDelay.lf | 8 +++-- test/C/src/lib/PassThrough.lf | 4 ++- .../modal_models/BanksCount3ModesComplex.lf | 8 +++-- test/C/src/modal_models/ConvertCaseTest.lf | 28 ++++++++++++---- test/C/src/modal_models/Count3Modes.lf | 5 ++- test/C/src/modal_models/MixedReactions.lf | 16 +++++++--- test/C/src/modal_models/ModalActions.lf | 5 ++- test/C/src/modal_models/ModalAfter.lf | 5 ++- test/C/src/modal_models/ModalCycleBreaker.lf | 13 ++++++-- .../src/modal_models/ModalNestedReactions.lf | 13 ++++++-- .../src/modal_models/ModalStartupShutdown.lf | 5 ++- test/C/src/modal_models/ModalStateReset.lf | 13 ++++++-- .../C/src/modal_models/ModalStateResetAuto.lf | 5 ++- test/C/src/modal_models/ModalTimers.lf | 5 ++- .../MultipleOutputFeeder_2Connections.lf | 22 ++++++++++--- ...ultipleOutputFeeder_ReactionConnections.lf | 26 +++++++++++---- test/C/src/modal_models/util/TraceTesting.lf | 4 ++- test/C/src/multiport/BankGangedConnections.lf | 4 ++- test/C/src/multiport/BankIndexInitializer.lf | 12 +++++-- test/C/src/multiport/BankSelfBroadcast.lf | 4 ++- test/C/src/multiport/BankToMultiport.lf | 4 ++- test/C/src/multiport/Broadcast.lf | 4 ++- test/C/src/multiport/BroadcastAfter.lf | 4 ++- .../C/src/multiport/BroadcastMultipleAfter.lf | 4 ++- test/C/src/multiport/MultiportFromBank.lf | 4 ++- test/C/src/multiport/MultiportIn.lf | 4 ++- .../src/multiport/MultiportInParameterized.lf | 4 ++- test/C/src/multiport/PipelineAfter.lf | 8 +++-- test/C/src/multiport/ReactionsToNested.lf | 8 +++-- test/C/src/no_inlining/Count.lf | 4 ++- test/C/src/no_inlining/CountHierarchy.lf | 8 +++-- test/C/src/target/FederatedFiles.lf | 4 ++- test/C/src/target/Math.lf | 4 ++- test/C/src/target/Platform.lf | 4 ++- test/C/src/token/lib/Token.lf | 8 +++-- test/C/src/verifier/ADASModel.lf | 32 ++++++++++++++----- test/C/src/verifier/AircraftDoor.lf | 4 ++- test/C/src/verifier/CoopSchedule.lf | 4 ++- test/C/src/verifier/ProcessMsg.lf | 8 +++-- test/C/src/verifier/Ring.lf | 8 +++-- test/C/src/verifier/SafeSend.lf | 5 ++- test/C/src/verifier/Thermostat.lf | 12 +++++-- test/C/src/verifier/TrainDoor.lf | 8 +++-- test/C/src/verifier/UnsafeSend.lf | 5 ++- test/C/src/zephyr/unthreaded/HelloZephyr.lf | 4 ++- test/C/src/zephyr/unthreaded/Timer.lf | 4 ++- test/Cpp/src/ActionDelay.lf | 8 +++-- test/Cpp/src/ActionWithNoReaction.lf | 4 ++- test/Cpp/src/After.lf | 4 ++- test/Cpp/src/AfterZero.lf | 4 ++- test/Cpp/src/Alignment.lf | 4 ++- test/Cpp/src/CompositionGain.lf | 4 ++- test/Cpp/src/DanglingOutput.lf | 4 ++- test/Cpp/src/DelayInt.lf | 12 +++++-- test/Cpp/src/DelayedAction.lf | 4 ++- test/Cpp/src/DelayedReaction.lf | 4 ++- test/Cpp/src/Determinism.lf | 8 +++-- test/Cpp/src/DoublePort.lf | 4 ++- test/Cpp/src/Gain.lf | 8 +++-- test/Cpp/src/GetMicroStep.lf | 8 +++-- test/Cpp/src/Hello.lf | 8 +++-- test/Cpp/src/HelloWorld.lf | 4 ++- test/Cpp/src/Hierarchy.lf | 8 +++-- test/Cpp/src/Hierarchy2.lf | 4 ++- test/Cpp/src/Import.lf | 4 ++- test/Cpp/src/ImportComposition.lf | 4 ++- test/Cpp/src/ImportRenamed.lf | 4 ++- test/Cpp/src/ManualDelayedReaction.lf | 13 ++++++-- test/Cpp/src/Methods.lf | 8 +++-- test/Cpp/src/Microsteps.lf | 4 ++- test/Cpp/src/Minimal.lf | 4 ++- test/Cpp/src/MultipleContained.lf | 4 ++- test/Cpp/src/NativeListsAndTimes.lf | 9 ++++-- test/Cpp/src/NestedTriggeredReactions.lf | 12 +++++-- test/Cpp/src/PeriodicDesugared.lf | 4 ++- test/Cpp/src/PhysicalConnection.lf | 4 ++- test/Cpp/src/Pipeline.lf | 4 ++- test/Cpp/src/PreambleTest.lf | 4 ++- test/Cpp/src/ReadOutputOfContainedReactor.lf | 4 ++- test/Cpp/src/Schedule.lf | 8 +++-- test/Cpp/src/ScheduleLogicalAction.lf | 8 +++-- test/Cpp/src/SelfLoop.lf | 4 ++- test/Cpp/src/SendingInside2.lf | 4 ++- test/Cpp/src/SlowingClock.lf | 4 ++- test/Cpp/src/StartupOutFromInside.lf | 4 ++- test/Cpp/src/Stride.lf | 4 ++- test/Cpp/src/StructPrint.lf | 4 ++- test/Cpp/src/TimeLimit.lf | 4 ++- test/Cpp/src/TimeState.lf | 4 ++- test/Cpp/src/concurrent/AsyncCallback.lf | 4 ++- test/Cpp/src/concurrent/DelayIntThreaded.lf | 12 +++++-- .../Cpp/src/concurrent/DeterminismThreaded.lf | 8 +++-- test/Cpp/src/concurrent/GainThreaded.lf | 8 +++-- test/Cpp/src/concurrent/HelloThreaded.lf | 12 +++++-- test/Cpp/src/concurrent/ImportThreaded.lf | 4 ++- test/Cpp/src/concurrent/MinimalThreaded.lf | 4 ++- test/Cpp/src/concurrent/TimeLimitThreaded.lf | 4 ++- test/Cpp/src/enclave/EnclaveBankEach.lf | 8 +++-- test/Cpp/src/enclave/EnclaveBroadcast.lf | 4 ++- test/Cpp/src/enclave/EnclaveCommunication.lf | 4 ++- test/Cpp/src/enclave/EnclaveCommunication2.lf | 4 ++- .../enclave/EnclaveCommunicationDelayed.lf | 4 ++- .../enclave/EnclaveCommunicationDelayed2.lf | 4 ++- .../EnclaveCommunicationDelayedLocalEvents.lf | 8 +++-- .../EnclaveCommunicationLocalEvents.lf | 8 +++-- .../EnclaveCommunicationMultiportToBank.lf | 4 ++- ...laveCommunicationMultiportToBankDelayed.lf | 4 ++- ...EnclaveCommunicationMultiportToBankEach.lf | 4 ++- ...CommunicationMultiportToBankEachDelayed.lf | 4 ++- ...ommunicationMultiportToBankEachPhysical.lf | 4 ++- ...aveCommunicationMultiportToBankPhysical.lf | 4 ++- .../enclave/EnclaveCommunicationPhysical.lf | 4 ++- ...EnclaveCommunicationPhysicalLocalEvents.lf | 8 +++-- test/Cpp/src/enclave/EnclaveCycle.lf | 4 ++- test/Cpp/src/enclave/EnclaveCycleTwoTimers.lf | 8 +++-- test/Cpp/src/enclave/EnclaveHelloWorld.lf | 4 ++- test/Cpp/src/enclave/EnclaveShutdown.lf | 8 +++-- .../enclave/EnclaveSparseUpstreamEvents.lf | 8 +++-- .../EnclaveSparseUpstreamEventsDelayed.lf | 8 +++-- .../EnclaveSparseUpstreamEventsPhysical.lf | 8 +++-- test/Cpp/src/enclave/EnclaveTimeout.lf | 4 ++- .../enclave/EnclaveUpstreamPhysicalAction.lf | 4 ++- .../EnclaveUpstreamPhysicalActionDelayed.lf | 4 ++- test/Cpp/src/enclave/FastAndSlow.lf | 4 ++- test/Cpp/src/lib/Imported.lf | 4 ++- test/Cpp/src/lib/ImportedComposition.lf | 4 ++- test/Cpp/src/multiport/BankSelfBroadcast.lf | 4 ++- test/Cpp/src/multiport/BankToMultiport.lf | 4 ++- test/Cpp/src/multiport/Broadcast.lf | 4 ++- test/Cpp/src/multiport/BroadcastAfter.lf | 4 ++- .../src/multiport/BroadcastMultipleAfter.lf | 4 ++- .../src/multiport/IndexIntoMultiportInput.lf | 12 +++++-- test/Cpp/src/multiport/MultiportFromBank.lf | 4 ++- .../multiport/MultiportFromBankHierarchy.lf | 4 ++- .../MultiportFromBankHierarchyAfter.lf | 4 ++- test/Cpp/src/multiport/MultiportIn.lf | 8 +++-- test/Cpp/src/multiport/PipelineAfter.lf | 8 +++-- .../multiport/ReadOutputOfContainedBank.lf | 4 ++- test/Cpp/src/multiport/WidthGivenByCode.lf | 12 +++++-- test/Cpp/src/properties/Fast.lf | 4 ++- test/Cpp/src/target/AfterVoid.lf | 4 ++- .../src/target/CliParserGenericArguments.lf | 24 ++++++++++---- test/Cpp/src/target/CombinedTypeNames.lf | 18 ++++++++--- test/Cpp/src/target/GenericAfter.lf | 12 +++++-- test/Cpp/src/target/GenericDelay.lf | 12 +++++-- .../src/target/MultipleContainedGeneric.lf | 4 ++- test/Cpp/src/target/PointerParameters.lf | 4 ++- test/Python/src/ActionDelay.lf | 8 +++-- test/Python/src/ActionWithNoReaction.lf | 4 ++- test/Python/src/After.lf | 8 +++-- test/Python/src/AfterCycles.lf | 8 +++-- test/Python/src/CompareTags.lf | 4 ++- test/Python/src/CompositionGain.lf | 4 ++- test/Python/src/DanglingOutput.lf | 4 ++- test/Python/src/Deadline.lf | 4 ++- test/Python/src/DeadlineHandledAbove.lf | 4 ++- test/Python/src/DelayArray.lf | 4 ++- test/Python/src/DelayInt.lf | 8 +++-- test/Python/src/DelayString.lf | 12 +++++-- test/Python/src/DelayStruct.lf | 12 +++++-- test/Python/src/DelayStructWithAfter.lf | 8 +++-- .../src/DelayStructWithAfterOverlapped.lf | 4 ++- test/Python/src/DelayedAction.lf | 4 ++- test/Python/src/DelayedReaction.lf | 4 ++- test/Python/src/Determinism.lf | 8 +++-- test/Python/src/Gain.lf | 8 +++-- test/Python/src/GetMicroStep.lf | 8 +++-- test/Python/src/Hierarchy.lf | 4 ++- test/Python/src/Hierarchy2.lf | 4 ++- test/Python/src/Import.lf | 4 ++- test/Python/src/ImportComposition.lf | 4 ++- test/Python/src/ImportRenamed.lf | 4 ++- test/Python/src/ManualDelayedReaction.lf | 9 ++++-- test/Python/src/Methods.lf | 8 +++-- test/Python/src/MethodsRecursive.lf | 4 ++- test/Python/src/MethodsSameName.lf | 8 +++-- test/Python/src/Microsteps.lf | 4 ++- test/Python/src/Minimal.lf | 4 ++- test/Python/src/MultipleContained.lf | 4 ++- test/Python/src/ParameterizedState.lf | 4 ++- .../src/ReadOutputOfContainedReactor.lf | 4 ++- test/Python/src/Schedule.lf | 8 +++-- test/Python/src/ScheduleLogicalAction.lf | 8 +++-- test/Python/src/SelfLoop.lf | 4 ++- test/Python/src/SendingInside2.lf | 4 ++- test/Python/src/SetArray.lf | 4 ++- test/Python/src/SimpleDeadline.lf | 4 ++- test/Python/src/SlowingClock.lf | 4 ++- test/Python/src/StartupOutFromInside.lf | 4 ++- test/Python/src/StructAsType.lf | 4 ++- test/Python/src/StructAsTypeDirect.lf | 4 ++- test/Python/src/StructParallel.lf | 4 ++- test/Python/src/StructPrint.lf | 8 +++-- test/Python/src/StructScale.lf | 8 +++-- test/Python/src/TestForPreviousOutput.lf | 4 ++- test/Python/src/TimeLimit.lf | 4 ++- test/Python/src/TimeState.lf | 4 ++- test/Python/src/Timers.lf | 8 +++-- .../src/TriggerDownstreamOnlyIfPresent2.lf | 4 ++- test/Python/src/Wcet.lf | 4 ++- .../src/docker/FilesPropertyContainerized.lf | 4 ++- .../Python/src/federated/BroadcastFeedback.lf | 4 ++- .../BroadcastFeedbackWithHierarchy.lf | 8 +++-- test/Python/src/federated/CycleDetection.lf | 16 +++++++--- .../src/federated/DecentralizedP2PComm.lf | 4 ++- .../DecentralizedP2PUnbalancedTimeout.lf | 12 +++++-- ...centralizedP2PUnbalancedTimeoutPhysical.lf | 8 +++-- test/Python/src/federated/DistributedBank.lf | 4 ++- .../federated/DistributedBankToMultiport.lf | 4 ++- test/Python/src/federated/DistributedCount.lf | 4 ++- .../DistributedCountDecentralized.lf | 4 ++- .../DistributedCountDecentralizedLate.lf | 4 ++- .../src/federated/DistributedCountPhysical.lf | 4 ++- .../DistributedCountPhysicalAfterDelay.lf | 4 ++- .../DistributedCountPhysicalDecentralized.lf | 4 ++- .../src/federated/DistributedDoublePort.lf | 12 +++++-- .../src/federated/DistributedLoopedAction.lf | 4 ++- .../DistributedLoopedPhysicalAction.lf | 8 +++-- .../src/federated/DistributedMultiport.lf | 4 ++- .../federated/DistributedMultiportToBank.lf | 4 ++- .../src/federated/DistributedNoReact.lf | 4 ++- .../src/federated/DistributedSendClass.lf | 8 +++-- test/Python/src/federated/DistributedStop.lf | 4 ++- .../src/federated/DistributedStopZero.lf | 12 +++++-- .../src/federated/DistributedStructAsType.lf | 4 ++- .../DistributedStructAsTypeDirect.lf | 4 ++- .../federated/DistributedStructParallel.lf | 4 ++- .../src/federated/DistributedStructPrint.lf | 4 ++- .../src/federated/DistributedStructScale.lf | 4 ++- test/Python/src/federated/HelloDistributed.lf | 4 ++- ...stributedCentralizedPrecedenceHierarchy.lf | 8 +++-- test/Python/src/federated/PhysicalSTP.lf | 4 ++- .../src/federated/PingPongDistributed.lf | 4 ++- test/Python/src/federated/StopAtShutdown.lf | 20 +++++++++--- test/Python/src/lib/Imported.lf | 4 ++- test/Python/src/lib/ImportedComposition.lf | 4 ++- test/Python/src/lib/InternalDelay.lf | 8 +++-- test/Python/src/lib/TestCount.lf | 4 ++- test/Python/src/lib/TestCountMultiport.lf | 4 ++- .../modal_models/BanksCount3ModesComplex.lf | 8 +++-- .../src/modal_models/ConvertCaseTest.lf | 32 ++++++++++++++----- test/Python/src/modal_models/Count3Modes.lf | 5 ++- test/Python/src/modal_models/ModalActions.lf | 5 ++- test/Python/src/modal_models/ModalAfter.lf | 5 ++- .../src/modal_models/ModalCycleBreaker.lf | 9 ++++-- .../src/modal_models/ModalNestedReactions.lf | 13 ++++++-- .../src/modal_models/ModalStartupShutdown.lf | 5 ++- .../src/modal_models/ModalStateReset.lf | 13 ++++++-- .../src/modal_models/ModalStateResetAuto.lf | 5 ++- test/Python/src/modal_models/ModalTimers.lf | 5 ++- .../MultipleOutputFeeder_2Connections.lf | 18 ++++++++--- ...ultipleOutputFeeder_ReactionConnections.lf | 22 ++++++++++--- .../src/modal_models/util/TraceTesting.lf | 4 ++- .../src/multiport/BankIndexInitializer.lf | 8 +++-- test/Python/src/multiport/BankToMultiport.lf | 4 ++- test/Python/src/multiport/Broadcast.lf | 4 ++- .../Python/src/multiport/MultiportFromBank.lf | 4 ++- .../multiport/MultiportFromBankHierarchy.lf | 4 ++- test/Python/src/multiport/MultiportIn.lf | 4 ++- .../src/multiport/MultiportInParameterized.lf | 4 ++- test/Python/src/multiport/MultiportOut.lf | 4 ++- test/Python/src/multiport/PipelineAfter.lf | 8 +++-- .../Python/src/multiport/ReactionsToNested.lf | 8 +++-- test/Python/src/target/AfterNoTypes.lf | 8 +++-- test/Rust/src/ActionImplicitDelay.lf | 4 ++- test/Rust/src/ActionValuesCleanup.lf | 4 ++- .../src/CompositionInitializationOrder.lf | 12 +++++-- test/Rust/src/CompositionWithPorts.lf | 4 ++- test/Rust/src/DependencyOnChildPort.lf | 8 +++-- test/Rust/src/DependencyThroughChildPort.lf | 4 ++- test/Rust/src/DependencyUseAccessible.lf | 13 ++++++-- test/Rust/src/DependencyUseNonTrigger.lf | 12 +++++-- test/Rust/src/Import.lf | 4 ++- test/Rust/src/ImportPreambleItem.lf | 4 ++- test/Rust/src/MainReactorParam.lf | 4 ++- test/Rust/src/Minimal.lf | 4 ++- test/Rust/src/MovingAverage.lf | 8 +++-- test/Rust/src/NativeListsAndTimes.lf | 5 ++- test/Rust/src/PortConnectionInSelfOutSelf.lf | 4 ++- .../Rust/src/PortConnectionOutChildOutSelf.lf | 8 +++-- test/Rust/src/PortValueCleanup.lf | 4 ++- test/Rust/src/Preamble.lf | 4 ++- test/Rust/src/ReactionLabels.lf | 5 ++- test/Rust/src/SingleFileGeneration.lf | 4 ++- test/Rust/src/StopNoEvent.lf | 4 ++- test/Rust/src/StructAsType.lf | 4 ++- test/Rust/src/TimeState.lf | 4 ++- test/Rust/src/Timers.lf | 8 +++-- test/Rust/src/concurrent/AsyncCallback.lf | 4 ++- test/Rust/src/generics/CtorParamGeneric.lf | 9 ++++-- .../Rust/src/generics/CtorParamGenericInst.lf | 14 +++++--- test/Rust/src/generics/GenericComplexType.lf | 4 ++- test/Rust/src/generics/GenericReactor.lf | 12 +++++-- test/Rust/src/lib/Imported.lf | 4 ++- .../src/multiport/ConnectionToSelfBank.lf | 4 ++- test/Rust/src/multiport/CycledLhs_SelfLoop.lf | 8 +++-- test/Rust/src/multiport/CycledLhs_Single.lf | 8 +++-- test/Rust/src/multiport/FullyConnected.lf | 4 ++- test/Rust/src/multiport/MultiportFromBank.lf | 4 ++- .../multiport/ReadOutputOfContainedBank.lf | 4 ++- test/Rust/src/multiport/WidthWithParameter.lf | 4 ++- .../multiport/WriteInputOfContainedBank.lf | 4 ++- .../src/target/CargoDependencyOnRuntime.lf | 4 ++- test/Rust/src/target/CliFeature.lf | 4 ++- test/TypeScript/src/ActionDelay.lf | 8 +++-- test/TypeScript/src/ActionWithNoReaction.lf | 4 ++- test/TypeScript/src/After.lf | 8 +++-- test/TypeScript/src/ArrayAsParameter.lf | 4 ++- test/TypeScript/src/ArrayAsType.lf | 8 +++-- test/TypeScript/src/ArrayPrint.lf | 8 +++-- test/TypeScript/src/ArrayScale.lf | 8 +++-- test/TypeScript/src/DanglingOutput.lf | 4 ++- test/TypeScript/src/DelayInt.lf | 8 +++-- test/TypeScript/src/DelayedAction.lf | 4 ++- test/TypeScript/src/DelayedReaction.lf | 4 ++- test/TypeScript/src/Determinism.lf | 8 +++-- test/TypeScript/src/Gain.lf | 8 +++-- test/TypeScript/src/HelloWorld.lf | 4 ++- test/TypeScript/src/Hierarchy2.lf | 4 ++- test/TypeScript/src/Import.lf | 4 ++- test/TypeScript/src/Microsteps.lf | 4 ++- test/TypeScript/src/Minimal.lf | 4 ++- test/TypeScript/src/MovingAverage.lf | 4 ++- test/TypeScript/src/MultipleContained.lf | 4 ++- test/TypeScript/src/ParameterizedState.lf | 4 ++- test/TypeScript/src/PhysicalConnection.lf | 4 ++- .../src/ReadOutputOfContainedReactor.lf | 4 ++- test/TypeScript/src/Schedule.lf | 8 +++-- test/TypeScript/src/ScheduleLogicalAction.lf | 8 +++-- test/TypeScript/src/SendingInside2.lf | 4 ++- test/TypeScript/src/SendsPointerTest.lf | 12 +++++-- test/TypeScript/src/SlowingClock.lf | 4 ++- test/TypeScript/src/Stride.lf | 4 ++- test/TypeScript/src/TimeLimit.lf | 4 ++- test/TypeScript/src/TimeState.lf | 4 ++- test/TypeScript/src/Wcet.lf | 4 ++- .../src/federated/DistributedDoublePort.lf | 12 +++++-- .../src/federated/HelloDistributed.lf | 8 +++-- .../src/federated/SpuriousDependency.lf | 8 +++-- .../src/federated/StopAtShutdown.lf | 20 +++++++++--- .../src/federated/TopLevelArtifacts.lf | 8 +++-- test/TypeScript/src/lib/Count.lf | 4 ++- test/TypeScript/src/lib/Imported.lf | 4 ++- test/TypeScript/src/lib/InternalDelay.lf | 8 +++-- .../src/multiport/BankSelfBroadcast.lf | 4 ++- .../src/multiport/BankToMultiport.lf | 4 ++- test/TypeScript/src/multiport/Broadcast.lf | 4 ++- .../src/multiport/BroadcastAfter.lf | 4 ++- .../src/multiport/BroadcastMultipleAfter.lf | 4 ++- .../src/multiport/MultiportFromBank.lf | 4 ++- test/TypeScript/src/multiport/MultiportIn.lf | 8 +++-- .../src/multiport/MultiportInParameterized.lf | 4 ++- .../multiport/MultiportMutableInputArray.lf | 16 +++++++--- .../multiport/MultiportToMultiportArray.lf | 8 +++-- .../TypeScript/src/multiport/PipelineAfter.lf | 8 +++-- .../src/multiport/ReactionsToNested.lf | 8 +++-- 454 files changed, 2181 insertions(+), 712 deletions(-) diff --git a/core/src/main/java/org/lflang/ast/ToLf.java b/core/src/main/java/org/lflang/ast/ToLf.java index 9646bea79c..50ffb442ea 100644 --- a/core/src/main/java/org/lflang/ast/ToLf.java +++ b/core/src/main/java/org/lflang/ast/ToLf.java @@ -1,5 +1,6 @@ package org.lflang.ast; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -91,6 +92,12 @@ public class ToLf extends LfSwitch { /// public instance initialized when loading the class public static final ToLf instance = new ToLf(); + /** + * The eObjects in the syntax tree on the path from the root up to and including the current + * eObject. + */ + private final ArrayDeque callStack = new ArrayDeque<>(); + // private constructor private ToLf() { super(); @@ -104,6 +111,13 @@ public MalleableString caseArraySpec(ArraySpec spec) { @Override public MalleableString doSwitch(EObject eObject) { + callStack.push(eObject); + var ret = doSwitchHelper(eObject); + callStack.pop(); + return ret; + } + + private MalleableString doSwitchHelper(EObject eObject) { ICompositeNode node = NodeModelUtils.findActualNodeFor(eObject); if (node == null) return super.doSwitch(eObject); var ancestorComments = getAncestorComments(node); @@ -257,6 +271,9 @@ public MalleableString caseCode(Code code) { if (content.lines().count() > 1 || content.contains("#") || content.contains("//")) { return multilineRepresentation; } + if (callStack.stream().anyMatch(it -> it instanceof Code) && !content.isBlank()) { + return MalleableString.anyOf(multilineRepresentation); + } return MalleableString.anyOf(singleLineRepresentation, multilineRepresentation); } diff --git a/test/C/src/ActionDelay.lf b/test/C/src/ActionDelay.lf index 90847eb9d5..d1f94cb3e5 100644 --- a/test/C/src/ActionDelay.lf +++ b/test/C/src/ActionDelay.lf @@ -12,13 +12,17 @@ reactor GeneratedDelay { lf_schedule(act, MSEC(0)); =} - reaction(act) -> y_out {= lf_set(y_out, self->y_state); =} + reaction(act) -> y_out {= + lf_set(y_out, self->y_state); + =} } reactor Source { output out: int - reaction(startup) -> out {= lf_set(out, 1); =} + reaction(startup) -> out {= + lf_set(out, 1); + =} } reactor Sink { diff --git a/test/C/src/ActionWithNoReaction.lf b/test/C/src/ActionWithNoReaction.lf index 0143d67cce..e1f19c7ffe 100644 --- a/test/C/src/ActionWithNoReaction.lf +++ b/test/C/src/ActionWithNoReaction.lf @@ -33,5 +33,7 @@ main reactor ActionWithNoReaction { timer t(0, 1 sec) f.y -> p.x after 10 msec - reaction(t) -> f.x {= lf_set(f.x, 42); =} + reaction(t) -> f.x {= + lf_set(f.x, 42); + =} } diff --git a/test/C/src/After.lf b/test/C/src/After.lf index f21c6b7f76..60816c7f78 100644 --- a/test/C/src/After.lf +++ b/test/C/src/After.lf @@ -8,7 +8,9 @@ reactor foo { input x: int output y: int - reaction(x) -> y {= lf_set(y, 2*x->value); =} + reaction(x) -> y {= + lf_set(y, 2*x->value); + =} } reactor print { @@ -47,5 +49,7 @@ main reactor After { timer t(0, 1 sec) f.y -> p.x after 10 msec - reaction(t) -> f.x {= lf_set(f.x, 42); =} + reaction(t) -> f.x {= + lf_set(f.x, 42); + =} } diff --git a/test/C/src/AfterCycles.lf b/test/C/src/AfterCycles.lf index cc2eab638e..3e75f8fded 100644 --- a/test/C/src/AfterCycles.lf +++ b/test/C/src/AfterCycles.lf @@ -5,14 +5,18 @@ target C reactor Source { output out: unsigned - reaction(startup) -> out {= lf_set(out, 1); =} + reaction(startup) -> out {= + lf_set(out, 1); + =} } reactor Work { input in: unsigned output out: unsigned - reaction(in) -> out {= lf_set(out, in->value); =} + reaction(in) -> out {= + lf_set(out, in->value); + =} } main reactor AfterCycles { diff --git a/test/C/src/AfterZero.lf b/test/C/src/AfterZero.lf index 10fc9d7a8b..ce6cedbe90 100644 --- a/test/C/src/AfterZero.lf +++ b/test/C/src/AfterZero.lf @@ -8,7 +8,9 @@ reactor foo { input x: int output y: int - reaction(x) -> y {= SET(y, 2*x->value); =} + reaction(x) -> y {= + SET(y, 2*x->value); + =} } reactor print { @@ -52,5 +54,7 @@ main reactor { timer t(0, 1 sec) f.y -> p.x after 0 - reaction(t) -> f.x {= SET(f.x, 42); =} + reaction(t) -> f.x {= + SET(f.x, 42); + =} } diff --git a/test/C/src/Alignment.lf b/test/C/src/Alignment.lf index c1cba1b948..5884126d58 100644 --- a/test/C/src/Alignment.lf +++ b/test/C/src/Alignment.lf @@ -9,7 +9,9 @@ reactor Source { state count: int = 1 timer t(0, 100 msec) - reaction(t) -> out {= lf_set(out, self->count++); =} + reaction(t) -> out {= + lf_set(out, self->count++); + =} } reactor Sieve { diff --git a/test/C/src/CompositionGain.lf b/test/C/src/CompositionGain.lf index a34c95c10c..43220614f4 100644 --- a/test/C/src/CompositionGain.lf +++ b/test/C/src/CompositionGain.lf @@ -22,7 +22,9 @@ reactor Wrapper { main reactor CompositionGain { wrapper = new Wrapper() - reaction(startup) -> wrapper.x {= lf_set(wrapper.x, 42); =} + reaction(startup) -> wrapper.x {= + lf_set(wrapper.x, 42); + =} reaction(wrapper.y) {= printf("Received %d\n", wrapper.y->value); diff --git a/test/C/src/DanglingOutput.lf b/test/C/src/DanglingOutput.lf index 8c39ad5612..2167d3556f 100644 --- a/test/C/src/DanglingOutput.lf +++ b/test/C/src/DanglingOutput.lf @@ -6,7 +6,9 @@ reactor Source { output out: int timer t - reaction(t) -> out {= lf_set(out, 1); =} + reaction(t) -> out {= + lf_set(out, 1); + =} } reactor Gain { diff --git a/test/C/src/DeadlineAnytime.lf b/test/C/src/DeadlineAnytime.lf index 5ba39e36e0..fcb84e7a4e 100644 --- a/test/C/src/DeadlineAnytime.lf +++ b/test/C/src/DeadlineAnytime.lf @@ -8,9 +8,13 @@ reactor A { reaction(startup) -> a {= self->i = 0; while (!lf_check_deadline(self, true)); - =} deadline(10 msec) {= lf_schedule(a, 0); =} + =} deadline(10 msec) {= + lf_schedule(a, 0); + =} - reaction(a) {= self->i = 42; =} + reaction(a) {= + self->i = 42; + =} reaction(shutdown) {= if (self->i == 42) { diff --git a/test/C/src/DeadlineInherited.lf b/test/C/src/DeadlineInherited.lf index 5c99bd56a6..785087b18b 100644 --- a/test/C/src/DeadlineInherited.lf +++ b/test/C/src/DeadlineInherited.lf @@ -4,13 +4,19 @@ target C { threading: false } -preamble {= extern int global_cnt; =} +preamble {= + extern int global_cnt; +=} reactor NoDeadline { - preamble {= int global_cnt = 0; =} + preamble {= + int global_cnt = 0; + =} timer t(0 msec, 100 msec) - reaction(t) {= global_cnt++; =} + reaction(t) {= + global_cnt++; + =} } reactor WithDeadline { diff --git a/test/C/src/DeadlinePriority.lf b/test/C/src/DeadlinePriority.lf index 58761975ac..95db38a259 100644 --- a/test/C/src/DeadlinePriority.lf +++ b/test/C/src/DeadlinePriority.lf @@ -4,13 +4,19 @@ target C { threading: false } -preamble {= extern int global_cnt; =} +preamble {= + extern int global_cnt; +=} reactor NoDeadline { - preamble {= int global_cnt = 0; =} + preamble {= + int global_cnt = 0; + =} timer t(0 msec, 100 msec) - reaction(t) {= global_cnt++; =} + reaction(t) {= + global_cnt++; + =} } reactor WithDeadline { diff --git a/test/C/src/DeadlineWithAfterDelay.lf b/test/C/src/DeadlineWithAfterDelay.lf index 9478d8e00f..12bd80425b 100644 --- a/test/C/src/DeadlineWithAfterDelay.lf +++ b/test/C/src/DeadlineWithAfterDelay.lf @@ -4,10 +4,14 @@ target C { threading: false } -preamble {= extern int global_cnt; =} +preamble {= + extern int global_cnt; +=} reactor Source { - preamble {= int global_cnt = 0; =} + 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 471d22682e..7d8c06b8fd 100644 --- a/test/C/src/DeadlineWithBanks.lf +++ b/test/C/src/DeadlineWithBanks.lf @@ -8,10 +8,14 @@ target C { build-type: Debug } -preamble {= extern volatile int global_cnt; =} +preamble {= + extern volatile int global_cnt; +=} reactor Bank(bank_index: int = 0) { - preamble {= volatile int global_cnt = 0; =} + preamble {= + volatile int global_cnt = 0; + =} timer t(0, 100 msec) output out: int diff --git a/test/C/src/DeadlineZero.lf b/test/C/src/DeadlineZero.lf index 26f556a800..e4408d3917 100644 --- a/test/C/src/DeadlineZero.lf +++ b/test/C/src/DeadlineZero.lf @@ -10,14 +10,18 @@ reactor Detector { reaction(trigger) {= printf("ERROR: failed to detect zero-duration deadline at iteration %d.\n", self->cnt); exit(1); - =} deadline(0 msec) {= self->cnt++; =} + =} deadline(0 msec) {= + self->cnt++; + =} } reactor Generator { output pulse: int timer t(0, 100 msec) - reaction(t) -> pulse {= lf_set(pulse, 0); =} + reaction(t) -> pulse {= + lf_set(pulse, 0); + =} } main reactor { diff --git a/test/C/src/DelayArray.lf b/test/C/src/DelayArray.lf index 6f4bd6a754..7e024b1133 100644 --- a/test/C/src/DelayArray.lf +++ b/test/C/src/DelayArray.lf @@ -8,9 +8,13 @@ reactor DelayPointer(delay: time = 100 msec) { output out: int[] logical action a: int[] - reaction(in) -> a {= lf_schedule_token(a, self->delay, in->token); =} + reaction(in) -> a {= + lf_schedule_token(a, self->delay, in->token); + =} - reaction(a) -> out {= lf_set_token(out, a->token); =} + reaction(a) -> out {= + lf_set_token(out, a->token); + =} } reactor Source { diff --git a/test/C/src/DelayInt.lf b/test/C/src/DelayInt.lf index 58e74b2b8a..f3e1501414 100644 --- a/test/C/src/DelayInt.lf +++ b/test/C/src/DelayInt.lf @@ -6,7 +6,9 @@ reactor Delay(delay: time = 100 msec) { output out: int logical action a: int - reaction(a) -> out {= if (a->has_value && a->is_present) lf_set(out, a->value); =} + reaction(a) -> out {= + if (a->has_value && a->is_present) lf_set(out, a->value); + =} reaction(in) -> a {= // Use specialized form of schedule for integer payloads. @@ -55,5 +57,7 @@ main reactor DelayInt { t = new Test() d.out -> t.in - reaction(startup) -> d.in {= lf_set(d.in, 42); =} + reaction(startup) -> d.in {= + lf_set(d.in, 42); + =} } diff --git a/test/C/src/DelayString.lf b/test/C/src/DelayString.lf index f49cd270de..052e304ac3 100644 --- a/test/C/src/DelayString.lf +++ b/test/C/src/DelayString.lf @@ -16,7 +16,9 @@ reactor DelayString2(delay: time = 100 msec) { output out: string logical action a: string - reaction(a) -> out {= lf_set(out, a->value); =} + reaction(a) -> out {= + lf_set(out, a->value); + =} reaction(in) -> a {= // The following copies the char*, not the string. @@ -48,5 +50,7 @@ main reactor { t = new Test() d.out -> t.in - reaction(startup) -> d.in {= lf_set(d.in, "Hello"); =} + reaction(startup) -> d.in {= + lf_set(d.in, "Hello"); + =} } diff --git a/test/C/src/DelayedAction.lf b/test/C/src/DelayedAction.lf index 529f92badf..3b3b3a9f7a 100644 --- a/test/C/src/DelayedAction.lf +++ b/test/C/src/DelayedAction.lf @@ -8,7 +8,9 @@ main reactor DelayedAction { logical action a state count: int = 0 - reaction(t) -> a {= lf_schedule(a, MSEC(100)); =} + reaction(t) -> a {= + lf_schedule(a, MSEC(100)); + =} reaction(a) {= interval_t elapsed = lf_time_logical_elapsed(); diff --git a/test/C/src/DelayedReaction.lf b/test/C/src/DelayedReaction.lf index 41cb9f3052..2b9bf0a275 100644 --- a/test/C/src/DelayedReaction.lf +++ b/test/C/src/DelayedReaction.lf @@ -5,7 +5,9 @@ reactor Source { output out: int timer t - reaction(t) -> out {= lf_set(out, 1); =} + reaction(t) -> out {= + lf_set(out, 1); + =} } reactor Sink { diff --git a/test/C/src/Determinism.lf b/test/C/src/Determinism.lf index 6ccc7dde34..c6e66c521c 100644 --- a/test/C/src/Determinism.lf +++ b/test/C/src/Determinism.lf @@ -4,7 +4,9 @@ reactor Source { output y: int timer t - reaction(t) -> y {= lf_set(y, 1); =} + reaction(t) -> y {= + lf_set(y, 1); + =} } reactor Destination { @@ -31,7 +33,9 @@ reactor Pass { input x: int output y: int - reaction(x) -> y {= lf_set(y, x->value); =} + reaction(x) -> y {= + lf_set(y, x->value); + =} } main reactor Determinism { diff --git a/test/C/src/DoublePort.lf b/test/C/src/DoublePort.lf index 046a4f951f..b17bf6d71b 100644 --- a/test/C/src/DoublePort.lf +++ b/test/C/src/DoublePort.lf @@ -17,9 +17,13 @@ reactor CountMicrostep { logical action act: int timer t(0, 1 sec) - reaction(t) -> act {= lf_schedule_int(act, 0, self->count++); =} + reaction(t) -> act {= + lf_schedule_int(act, 0, self->count++); + =} - reaction(act) -> out {= lf_set(out, act->value); =} + reaction(act) -> out {= + lf_set(out, act->value); + =} } reactor Print { @@ -35,7 +39,9 @@ reactor Print { } =} - reaction(shutdown) {= printf("SUCCESS: messages were at least one microstep apart.\n"); =} + reaction(shutdown) {= + printf("SUCCESS: messages were at least one microstep apart.\n"); + =} } main reactor DoublePort { diff --git a/test/C/src/Gain.lf b/test/C/src/Gain.lf index bc096cf77c..d2ea2e4893 100644 --- a/test/C/src/Gain.lf +++ b/test/C/src/Gain.lf @@ -5,7 +5,9 @@ reactor Scale(scale: int = 2) { input x: int output y: int - reaction(x) -> y {= lf_set(y, x->value * self->scale); =} + reaction(x) -> y {= + lf_set(y, x->value * self->scale); + =} } reactor Test { @@ -35,5 +37,7 @@ main reactor Gain { d = new Test() g.y -> d.x - reaction(startup) -> g.x {= lf_set(g.x, 1); =} + reaction(startup) -> g.x {= + lf_set(g.x, 1); + =} } diff --git a/test/C/src/GetMicroStep.lf b/test/C/src/GetMicroStep.lf index 4f0fda5e9f..eb5e3d0a49 100644 --- a/test/C/src/GetMicroStep.lf +++ b/test/C/src/GetMicroStep.lf @@ -6,7 +6,9 @@ main reactor GetMicroStep { logical action l - reaction(startup) -> l {= lf_schedule(l, 0); =} + reaction(startup) -> l {= + lf_schedule(l, 0); + =} reaction(l) -> l {= microstep_t microstep = lf_tag().microstep; diff --git a/test/C/src/Hierarchy.lf b/test/C/src/Hierarchy.lf index 47efcf6b65..8049f6b755 100644 --- a/test/C/src/Hierarchy.lf +++ b/test/C/src/Hierarchy.lf @@ -5,7 +5,9 @@ reactor Source { output out: int timer t - reaction(t) -> out {= lf_set(out, 1); =} + reaction(t) -> out {= + lf_set(out, 1); + =} } reactor Gain { diff --git a/test/C/src/Hierarchy2.lf b/test/C/src/Hierarchy2.lf index 7e361dc580..bb565c72ae 100644 --- a/test/C/src/Hierarchy2.lf +++ b/test/C/src/Hierarchy2.lf @@ -8,7 +8,9 @@ reactor Source { output out: int timer t(0, 1 sec) - reaction(t) -> out {= lf_set(out, 1); =} + reaction(t) -> out {= + lf_set(out, 1); + =} } reactor Count { diff --git a/test/C/src/Import.lf b/test/C/src/Import.lf index 812f14edaa..afcd6e585b 100644 --- a/test/C/src/Import.lf +++ b/test/C/src/Import.lf @@ -7,5 +7,7 @@ main reactor Import { timer t a = new Imported() - reaction(t) -> a.x {= lf_set(a.x, 42); =} + reaction(t) -> a.x {= + lf_set(a.x, 42); + =} } diff --git a/test/C/src/ImportComposition.lf b/test/C/src/ImportComposition.lf index 41e493f8d9..654a1d9283 100644 --- a/test/C/src/ImportComposition.lf +++ b/test/C/src/ImportComposition.lf @@ -7,7 +7,9 @@ main reactor ImportComposition { a = new ImportedComposition() state received: bool = false - reaction(startup) -> a.x {= lf_set(a.x, 42); =} + reaction(startup) -> a.x {= + lf_set(a.x, 42); + =} reaction(a.y) {= interval_t receive_time = lf_time_logical_elapsed(); diff --git a/test/C/src/ImportRenamed.lf b/test/C/src/ImportRenamed.lf index 6ddee69dfa..8158f74da7 100644 --- a/test/C/src/ImportRenamed.lf +++ b/test/C/src/ImportRenamed.lf @@ -11,5 +11,7 @@ main reactor { b = new Y() c = new Z() - reaction(t) -> a.x {= lf_set(a.x, 42); =} + reaction(t) -> a.x {= + lf_set(a.x, 42); + =} } diff --git a/test/C/src/InheritanceAction.lf b/test/C/src/InheritanceAction.lf index e2b5857ff7..7e60ea23c3 100644 --- a/test/C/src/InheritanceAction.lf +++ b/test/C/src/InheritanceAction.lf @@ -7,11 +7,15 @@ reactor Source { logical action foo: int output y: int - reaction(foo) -> y {= lf_set(y, foo->value); =} + reaction(foo) -> y {= + lf_set(y, foo->value); + =} } reactor SourceExtended extends Source { - reaction(startup) -> foo {= lf_schedule_int(foo, 0, 42); =} + reaction(startup) -> foo {= + lf_schedule_int(foo, 0, 42); + =} } reactor Test { diff --git a/test/C/src/ManualDelayedReaction.lf b/test/C/src/ManualDelayedReaction.lf index 24f5cc3d23..c81dabb590 100644 --- a/test/C/src/ManualDelayedReaction.lf +++ b/test/C/src/ManualDelayedReaction.lf @@ -18,14 +18,19 @@ reactor GeneratedDelay { lf_schedule(act, MSEC(100)); =} - reaction(act) -> y_out {= lf_set(y_out, self->y_state); =} + reaction(act) -> y_out {= + lf_set(y_out, self->y_state); + =} } reactor Source { output out: int timer t - reaction(t) -> out {= lf_set(out, 1); =} // reaction(t) -> out after 100 msec + // reaction(t) -> out after 100 msec + reaction(t) -> out {= + lf_set(out, 1); + =} } reactor Sink { diff --git a/test/C/src/Methods.lf b/test/C/src/Methods.lf index 2d69d5c77d..6409cb1a27 100644 --- a/test/C/src/Methods.lf +++ b/test/C/src/Methods.lf @@ -3,9 +3,13 @@ target C main reactor { state foo: int = 2 - method getFoo(): int {= return self->foo; =} + method getFoo(): int {= + return self->foo; + =} - method add(x: int) {= self->foo += x; =} + method add(x: int) {= + self->foo += x; + =} reaction(startup) {= lf_print("Foo is initialized to %d", getFoo()); diff --git a/test/C/src/MethodsRecursive.lf b/test/C/src/MethodsRecursive.lf index 4e04a0a901..09761cc279 100644 --- a/test/C/src/MethodsRecursive.lf +++ b/test/C/src/MethodsRecursive.lf @@ -10,7 +10,9 @@ main reactor { return add(fib(n-1), fib(n-2)); =} - method add(x: int, y: int): int {= return x + y; =} + method add(x: int, y: int): int {= + return x + y; + =} reaction(startup) {= for (int n = 1; n < 10; n++) { diff --git a/test/C/src/MethodsSameName.lf b/test/C/src/MethodsSameName.lf index db07a5cd75..e4683d8f7f 100644 --- a/test/C/src/MethodsSameName.lf +++ b/test/C/src/MethodsSameName.lf @@ -4,7 +4,9 @@ target C reactor Foo { state foo: int = 2 - method add(x: int) {= self->foo += x; =} + method add(x: int) {= + self->foo += x; + =} reaction(startup) {= add(40); @@ -20,7 +22,9 @@ main reactor { a = new Foo() - method add(x: int) {= self->foo += x; =} + method add(x: int) {= + self->foo += x; + =} reaction(startup) {= add(40); diff --git a/test/C/src/Microsteps.lf b/test/C/src/Microsteps.lf index 6a2915b53f..12515731e3 100644 --- a/test/C/src/Microsteps.lf +++ b/test/C/src/Microsteps.lf @@ -37,5 +37,7 @@ main reactor Microsteps { lf_schedule(repeat, 0); =} - reaction(repeat) -> d.y {= lf_set(d.y, 1); =} + reaction(repeat) -> d.y {= + lf_set(d.y, 1); + =} } diff --git a/test/C/src/Minimal.lf b/test/C/src/Minimal.lf index b2534ab993..4b38e6a61e 100644 --- a/test/C/src/Minimal.lf +++ b/test/C/src/Minimal.lf @@ -2,5 +2,7 @@ target C main reactor Minimal { - reaction(startup) {= printf("Hello World.\n"); =} + reaction(startup) {= + printf("Hello World.\n"); + =} } diff --git a/test/C/src/MultipleContained.lf b/test/C/src/MultipleContained.lf index 1e98bff1fb..6354101b93 100644 --- a/test/C/src/MultipleContained.lf +++ b/test/C/src/MultipleContained.lf @@ -7,7 +7,9 @@ reactor Contained { input in2: int state count: int = 0 - reaction(startup) -> trigger {= lf_set(trigger, 42); =} + reaction(startup) -> trigger {= + lf_set(trigger, 42); + =} reaction(in1) {= printf("in1 received %d.\n", in1->value); diff --git a/test/C/src/NestedTriggeredReactions.lf b/test/C/src/NestedTriggeredReactions.lf index ca9ce7476c..59364c2b22 100644 --- a/test/C/src/NestedTriggeredReactions.lf +++ b/test/C/src/NestedTriggeredReactions.lf @@ -9,7 +9,9 @@ reactor Container { in -> contained.in - reaction(in) {= self->triggered = true; =} + reaction(in) {= + self->triggered = true; + =} reaction(shutdown) {= if (!self->triggered) { @@ -23,7 +25,9 @@ reactor Contained { state triggered: bool = false - reaction(in) {= self->triggered = true; =} + reaction(in) {= + self->triggered = true; + =} reaction(shutdown) {= if (!self->triggered) { @@ -35,5 +39,7 @@ reactor Contained { main reactor { container = new Container() - reaction(startup) -> container.in {= lf_set(container.in, true); =} + reaction(startup) -> container.in {= + lf_set(container.in, true); + =} } diff --git a/test/C/src/ParameterizedState.lf b/test/C/src/ParameterizedState.lf index 5b601a740b..78ace1c297 100644 --- a/test/C/src/ParameterizedState.lf +++ b/test/C/src/ParameterizedState.lf @@ -3,7 +3,9 @@ target C reactor Foo(bar: int = 42) { state baz = bar - reaction(startup) {= printf("Baz: %d\n", self->baz); =} + reaction(startup) {= + printf("Baz: %d\n", self->baz); + =} } main reactor { diff --git a/test/C/src/PhysicalConnection.lf b/test/C/src/PhysicalConnection.lf index af478e0425..4a7959a947 100644 --- a/test/C/src/PhysicalConnection.lf +++ b/test/C/src/PhysicalConnection.lf @@ -4,7 +4,9 @@ target C reactor Source { output out: int - reaction(startup) -> out {= lf_set(out, 42); =} + reaction(startup) -> out {= + lf_set(out, 42); + =} } reactor Destination { diff --git a/test/C/src/PingPong.lf b/test/C/src/PingPong.lf index c38bd36683..404fd9c798 100644 --- a/test/C/src/PingPong.lf +++ b/test/C/src/PingPong.lf @@ -28,7 +28,9 @@ reactor Ping(count: int = 10) { state pingsLeft: int = count logical action serve - reaction(startup, serve) -> send {= lf_set(send, self->pingsLeft--); =} + reaction(startup, serve) -> send {= + lf_set(send, self->pingsLeft--); + =} reaction(receive) -> serve {= if (self->pingsLeft > 0) { diff --git a/test/C/src/PreambleInherited.lf b/test/C/src/PreambleInherited.lf index 83f4a59d44..771185bdbc 100644 --- a/test/C/src/PreambleInherited.lf +++ b/test/C/src/PreambleInherited.lf @@ -6,7 +6,9 @@ reactor A { #define FOO 2 =} - reaction(startup) {= printf("FOO: %d\n", FOO); =} + reaction(startup) {= + printf("FOO: %d\n", FOO); + =} } reactor B extends A { diff --git a/test/C/src/ReadOutputOfContainedReactor.lf b/test/C/src/ReadOutputOfContainedReactor.lf index 0625d9ccf3..4ae3ae44f9 100644 --- a/test/C/src/ReadOutputOfContainedReactor.lf +++ b/test/C/src/ReadOutputOfContainedReactor.lf @@ -4,7 +4,9 @@ target C reactor Contained { output out: int - reaction(startup) -> out {= lf_set(out, 42); =} + reaction(startup) -> out {= + lf_set(out, 42); + =} } main reactor ReadOutputOfContainedReactor { diff --git a/test/C/src/RepeatedInheritance.lf b/test/C/src/RepeatedInheritance.lf index 7de84864cd..b6ff4cbc34 100644 --- a/test/C/src/RepeatedInheritance.lf +++ b/test/C/src/RepeatedInheritance.lf @@ -27,7 +27,9 @@ reactor A extends B, C { input a: int output out: int - reaction(a, b, c, d) -> out {= lf_set(out, a->value + b->value + c->value + d->value); =} + reaction(a, b, c, d) -> out {= + lf_set(out, a->value + b->value + c->value + d->value); + =} } main reactor { diff --git a/test/C/src/Schedule.lf b/test/C/src/Schedule.lf index 09f9de5bd9..e0d1db3fcc 100644 --- a/test/C/src/Schedule.lf +++ b/test/C/src/Schedule.lf @@ -5,7 +5,9 @@ reactor Schedule2 { input x: int logical action a - reaction(x) -> a {= lf_schedule(a, MSEC(200)); =} + reaction(x) -> a {= + lf_schedule(a, MSEC(200)); + =} reaction(a) {= interval_t elapsed_time = lf_time_logical_elapsed(); @@ -21,5 +23,7 @@ main reactor { a = new Schedule2() timer t - reaction(t) -> a.x {= lf_set(a.x, 1); =} + reaction(t) -> a.x {= + lf_set(a.x, 1); + =} } diff --git a/test/C/src/ScheduleLogicalAction.lf b/test/C/src/ScheduleLogicalAction.lf index 72835f8a96..9b85f443b7 100644 --- a/test/C/src/ScheduleLogicalAction.lf +++ b/test/C/src/ScheduleLogicalAction.lf @@ -16,7 +16,9 @@ reactor foo { lf_schedule(a, MSEC(500)); =} - reaction(a) -> y {= lf_set(y, -42); =} + reaction(a) -> y {= + lf_set(y, -42); + =} } reactor print { @@ -42,5 +44,7 @@ main reactor { timer t(0, 1 sec) f.y -> p.x - reaction(t) -> f.x {= lf_set(f.x, 42); =} + reaction(t) -> f.x {= + lf_set(f.x, 42); + =} } diff --git a/test/C/src/SelfLoop.lf b/test/C/src/SelfLoop.lf index 204a5e856c..f6671dc225 100644 --- a/test/C/src/SelfLoop.lf +++ b/test/C/src/SelfLoop.lf @@ -24,7 +24,9 @@ reactor Self { lf_schedule_int(a, MSEC(100), x->value); =} - reaction(startup) -> a {= lf_schedule_int(a, 0, 42); =} + reaction(startup) -> a {= + lf_schedule_int(a, 0, 42); + =} reaction(shutdown) {= if (self->expected <= 43) { diff --git a/test/C/src/SendingInside2.lf b/test/C/src/SendingInside2.lf index ab15278044..ea016a6307 100644 --- a/test/C/src/SendingInside2.lf +++ b/test/C/src/SendingInside2.lf @@ -16,5 +16,7 @@ main reactor SendingInside2 { timer t p = new Printer() - reaction(t) -> p.x {= lf_set(p.x, 1); =} + reaction(t) -> p.x {= + lf_set(p.x, 1); + =} } diff --git a/test/C/src/SendsPointerTest.lf b/test/C/src/SendsPointerTest.lf index f05bec4934..d4478e75ed 100644 --- a/test/C/src/SendsPointerTest.lf +++ b/test/C/src/SendsPointerTest.lf @@ -2,7 +2,9 @@ // ensures that the struct is freed. target C -preamble {= typedef int* int_pointer; =} +preamble {= + typedef int* int_pointer; +=} reactor SendsPointer { output out: int_pointer diff --git a/test/C/src/SetToken.lf b/test/C/src/SetToken.lf index 6b589c6e57..6dc7badada 100644 --- a/test/C/src/SetToken.lf +++ b/test/C/src/SetToken.lf @@ -5,9 +5,13 @@ reactor Source { output out: int* logical action a: int - reaction(startup) -> a {= lf_schedule_int(a, MSEC(200), 42); =} + reaction(startup) -> a {= + lf_schedule_int(a, MSEC(200), 42); + =} - reaction(a) -> out {= lf_set_token(out, a->token); =} + reaction(a) -> out {= + lf_set_token(out, a->token); + =} } // expected parameter is for testing. diff --git a/test/C/src/SlowingClock.lf b/test/C/src/SlowingClock.lf index 36cb07c553..24d832149f 100644 --- a/test/C/src/SlowingClock.lf +++ b/test/C/src/SlowingClock.lf @@ -13,7 +13,9 @@ main reactor SlowingClock { state interval: time = 100 msec state expected_time: time = 100 msec - reaction(startup) -> a {= lf_schedule(a, 0); =} + reaction(startup) -> a {= + lf_schedule(a, 0); + =} reaction(a) -> a {= instant_t elapsed_logical_time = lf_time_logical_elapsed(); diff --git a/test/C/src/StartupOutFromInside.lf b/test/C/src/StartupOutFromInside.lf index 6233e0968f..b23ac76a55 100644 --- a/test/C/src/StartupOutFromInside.lf +++ b/test/C/src/StartupOutFromInside.lf @@ -3,7 +3,9 @@ target C reactor Bar { output out: int - reaction(startup) -> out {= lf_set(out, 42); =} + reaction(startup) -> out {= + lf_set(out, 42); + =} } main reactor StartupOutFromInside { diff --git a/test/C/src/TimeLimit.lf b/test/C/src/TimeLimit.lf index 4b1ea4f248..340cfff7f2 100644 --- a/test/C/src/TimeLimit.lf +++ b/test/C/src/TimeLimit.lf @@ -44,5 +44,7 @@ main reactor TimeLimit(period: time = 1 sec) { d = new Destination() c.y -> d.x - reaction(stop) {= lf_request_stop(); =} + reaction(stop) {= + lf_request_stop(); + =} } diff --git a/test/C/src/TimeState.lf b/test/C/src/TimeState.lf index d3aaf2029a..18447e4769 100644 --- a/test/C/src/TimeState.lf +++ b/test/C/src/TimeState.lf @@ -3,7 +3,9 @@ target C reactor Foo(bar: int = 42) { state baz: time = 500 msec - reaction(startup) {= printf("Baz: %lld\n", self->baz); =} + reaction(startup) {= + printf("Baz: %lld\n", self->baz); + =} } main reactor { diff --git a/test/C/src/UnconnectedInput.lf b/test/C/src/UnconnectedInput.lf index 71a141f158..87ea298959 100644 --- a/test/C/src/UnconnectedInput.lf +++ b/test/C/src/UnconnectedInput.lf @@ -9,7 +9,9 @@ reactor Source { timer t(0, 1 sec) state s: int = 1 - reaction(t) -> out {= lf_set(out, self->s++); =} + reaction(t) -> out {= + lf_set(out, self->s++); + =} } reactor Add { diff --git a/test/C/src/Wcet.lf b/test/C/src/Wcet.lf index 9e6f84be6a..d1aa88ae1b 100644 --- a/test/C/src/Wcet.lf +++ b/test/C/src/Wcet.lf @@ -31,7 +31,9 @@ reactor Work { reactor Print { input in: int - reaction(in) {= printf("Received: %d\n", in->value); =} + reaction(in) {= + printf("Received: %d\n", in->value); + =} } main reactor Wcet { diff --git a/test/C/src/arduino/Blink.lf b/test/C/src/arduino/Blink.lf index 2612e72a62..8fa855c03a 100644 --- a/test/C/src/arduino/Blink.lf +++ b/test/C/src/arduino/Blink.lf @@ -13,9 +13,15 @@ main reactor Blink { timer t1(0, 1 sec) timer t2(500 msec, 1 sec) - reaction(startup) {= pinMode(LED_BUILTIN, OUTPUT); =} + reaction(startup) {= + pinMode(LED_BUILTIN, OUTPUT); + =} - reaction(t1) {= digitalWrite(LED_BUILTIN, HIGH); =} + reaction(t1) {= + digitalWrite(LED_BUILTIN, HIGH); + =} - reaction(t2) {= digitalWrite(LED_BUILTIN, LOW); =} + reaction(t2) {= + digitalWrite(LED_BUILTIN, LOW); + =} } diff --git a/test/C/src/arduino/BlinkAttemptThreading.lf b/test/C/src/arduino/BlinkAttemptThreading.lf index 4c4dac927c..fb08214d26 100644 --- a/test/C/src/arduino/BlinkAttemptThreading.lf +++ b/test/C/src/arduino/BlinkAttemptThreading.lf @@ -15,9 +15,15 @@ main reactor BlinkAttemptThreading { timer t1(0, 1 sec) timer t2(500 msec, 1 sec) - reaction(startup) {= pinMode(LED_BUILTIN, OUTPUT); =} + reaction(startup) {= + pinMode(LED_BUILTIN, OUTPUT); + =} - reaction(t1) {= digitalWrite(LED_BUILTIN, HIGH); =} + reaction(t1) {= + digitalWrite(LED_BUILTIN, HIGH); + =} - reaction(t2) {= digitalWrite(LED_BUILTIN, LOW); =} + reaction(t2) {= + digitalWrite(LED_BUILTIN, LOW); + =} } diff --git a/test/C/src/arduino/BlinkMBED.lf b/test/C/src/arduino/BlinkMBED.lf index 3b2c84427e..2d7e4e329c 100644 --- a/test/C/src/arduino/BlinkMBED.lf +++ b/test/C/src/arduino/BlinkMBED.lf @@ -13,9 +13,15 @@ main reactor BlinkMBED { timer t1(0, 1 sec) timer t2(500 msec, 1 sec) - reaction(startup) {= pinMode(LED_BUILTIN, OUTPUT); =} + reaction(startup) {= + pinMode(LED_BUILTIN, OUTPUT); + =} - reaction(t1) {= digitalWrite(LED_BUILTIN, HIGH); =} + reaction(t1) {= + digitalWrite(LED_BUILTIN, HIGH); + =} - reaction(t2) {= digitalWrite(LED_BUILTIN, LOW); =} + reaction(t2) {= + digitalWrite(LED_BUILTIN, LOW); + =} } diff --git a/test/C/src/arduino/DigitalReadSerial.lf b/test/C/src/arduino/DigitalReadSerial.lf index e521585a5d..54a9af1e85 100644 --- a/test/C/src/arduino/DigitalReadSerial.lf +++ b/test/C/src/arduino/DigitalReadSerial.lf @@ -10,7 +10,9 @@ main reactor DigitalReadSerial { timer t1(0, 1 msec) state pushButton: int = 2 - reaction(startup) {= pinMode(self->pushButton, INPUT); =} + reaction(startup) {= + pinMode(self->pushButton, INPUT); + =} reaction(t1) {= int buttonState = digitalRead(self->pushButton); diff --git a/test/C/src/arduino/Fade.lf b/test/C/src/arduino/Fade.lf index 2684ef2dd7..4c74a3dca1 100644 --- a/test/C/src/arduino/Fade.lf +++ b/test/C/src/arduino/Fade.lf @@ -17,7 +17,9 @@ main reactor Fade { state brightness: int = 9 state fadeAmount: int = 5 - reaction(startup) {= pinMode(self->led, OUTPUT); =} + reaction(startup) {= + pinMode(self->led, OUTPUT); + =} reaction(t1) {= analogWrite(self->led, self->brightness); diff --git a/test/C/src/arduino/ReadAnalogVoltage.lf b/test/C/src/arduino/ReadAnalogVoltage.lf index 4dbffbbb83..0f48633939 100644 --- a/test/C/src/arduino/ReadAnalogVoltage.lf +++ b/test/C/src/arduino/ReadAnalogVoltage.lf @@ -14,7 +14,9 @@ target C { main reactor ReadAnalogVoltage { logical action a - reaction(startup) -> a {= lf_schedule(a, 0); =} + reaction(startup) -> a {= + lf_schedule(a, 0); + =} reaction(a) -> a {= int sensorValue = analogRead(A0); diff --git a/test/C/src/concurrent/DelayIntThreaded.lf b/test/C/src/concurrent/DelayIntThreaded.lf index d328954454..a75b82ab6e 100644 --- a/test/C/src/concurrent/DelayIntThreaded.lf +++ b/test/C/src/concurrent/DelayIntThreaded.lf @@ -6,7 +6,9 @@ reactor Delay(delay: time = 100 msec) { output out: int logical action a: int - reaction(a) -> out {= lf_set(out, a->value); =} + reaction(a) -> out {= + lf_set(out, a->value); + =} reaction(in) -> a {= // Use specialized form of schedule for integer payloads. @@ -55,5 +57,7 @@ main reactor { t = new Test() d.out -> t.in - reaction(startup) -> d.in {= lf_set(d.in, 42); =} + reaction(startup) -> d.in {= + lf_set(d.in, 42); + =} } diff --git a/test/C/src/concurrent/DeterminismThreaded.lf b/test/C/src/concurrent/DeterminismThreaded.lf index 813405f25f..2f73e3beaf 100644 --- a/test/C/src/concurrent/DeterminismThreaded.lf +++ b/test/C/src/concurrent/DeterminismThreaded.lf @@ -4,7 +4,9 @@ reactor Source { output y: int timer t - reaction(t) -> y {= lf_set(y, 1); =} + reaction(t) -> y {= + lf_set(y, 1); + =} } reactor Destination { @@ -31,7 +33,9 @@ reactor Pass { input x: int output y: int - reaction(x) -> y {= lf_set(y, x->value); =} + reaction(x) -> y {= + lf_set(y, x->value); + =} } main reactor { diff --git a/test/C/src/concurrent/GainThreaded.lf b/test/C/src/concurrent/GainThreaded.lf index 688a36eabd..d501f9c108 100644 --- a/test/C/src/concurrent/GainThreaded.lf +++ b/test/C/src/concurrent/GainThreaded.lf @@ -5,7 +5,9 @@ reactor Scale(scale: int = 2) { input x: int output y: int - reaction(x) -> y {= lf_set(y, x->value * self->scale); =} + reaction(x) -> y {= + lf_set(y, x->value * self->scale); + =} } reactor Test { @@ -35,5 +37,7 @@ main reactor { d = new Test() g.y -> d.x - reaction(startup) -> g.x {= lf_set(g.x, 1); =} + reaction(startup) -> g.x {= + lf_set(g.x, 1); + =} } diff --git a/test/C/src/concurrent/ImportThreaded.lf b/test/C/src/concurrent/ImportThreaded.lf index a40fd11fb7..cb4a0cad6b 100644 --- a/test/C/src/concurrent/ImportThreaded.lf +++ b/test/C/src/concurrent/ImportThreaded.lf @@ -7,5 +7,7 @@ main reactor ImportThreaded { timer t a = new Imported() - reaction(t) -> a.x {= lf_set(a.x, 42); =} + reaction(t) -> a.x {= + lf_set(a.x, 42); + =} } diff --git a/test/C/src/concurrent/MinimalThreaded.lf b/test/C/src/concurrent/MinimalThreaded.lf index dacafdcb73..8782c28ff6 100644 --- a/test/C/src/concurrent/MinimalThreaded.lf +++ b/test/C/src/concurrent/MinimalThreaded.lf @@ -4,5 +4,7 @@ target C main reactor MinimalThreaded { timer t - reaction(t) {= printf("Hello World.\n"); =} + reaction(t) {= + printf("Hello World.\n"); + =} } diff --git a/test/C/src/concurrent/PingPongThreaded.lf b/test/C/src/concurrent/PingPongThreaded.lf index 69d7b512a2..59de2bb7bc 100644 --- a/test/C/src/concurrent/PingPongThreaded.lf +++ b/test/C/src/concurrent/PingPongThreaded.lf @@ -27,7 +27,9 @@ reactor Ping(count: int = 10) { state pingsLeft: int = count logical action serve - reaction(startup, serve) -> send {= lf_set(send, self->pingsLeft--); =} + reaction(startup, serve) -> send {= + lf_set(send, self->pingsLeft--); + =} reaction(receive) -> serve {= if (self->pingsLeft > 0) { diff --git a/test/C/src/concurrent/TimeLimitThreaded.lf b/test/C/src/concurrent/TimeLimitThreaded.lf index 8ada5f42f5..f2b7be9559 100644 --- a/test/C/src/concurrent/TimeLimitThreaded.lf +++ b/test/C/src/concurrent/TimeLimitThreaded.lf @@ -44,5 +44,7 @@ main reactor(period: time = 1 sec) { d = new Destination() c.y -> d.x - reaction(stop) {= lf_request_stop(); =} + reaction(stop) {= + lf_request_stop(); + =} } diff --git a/test/C/src/enclave/EnclaveRequestStop.lf b/test/C/src/enclave/EnclaveRequestStop.lf index 781fb2811a..aea05a874d 100644 --- a/test/C/src/enclave/EnclaveRequestStop.lf +++ b/test/C/src/enclave/EnclaveRequestStop.lf @@ -9,7 +9,9 @@ reactor Stop(stop_time: time = 5 s) { =} timer t(stop_time) - reaction(t) {= lf_request_stop(); =} + reaction(t) {= + lf_request_stop(); + =} reaction(shutdown) {= lf_print("Stopped at tag (" PRINTF_TIME ", %d)", lf_time_logical_elapsed(), lf_tag().microstep); diff --git a/test/C/src/federated/BroadcastFeedback.lf b/test/C/src/federated/BroadcastFeedback.lf index 179d49edc7..66a93c275b 100644 --- a/test/C/src/federated/BroadcastFeedback.lf +++ b/test/C/src/federated/BroadcastFeedback.lf @@ -9,7 +9,9 @@ reactor SenderAndReceiver { input[2] in: int state received: bool = false - reaction(startup) -> out {= lf_set(out, 42); =} + reaction(startup) -> out {= + lf_set(out, 42); + =} 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/BroadcastFeedbackWithHierarchy.lf b/test/C/src/federated/BroadcastFeedbackWithHierarchy.lf index 9896c1fdf8..114e42cfd7 100644 --- a/test/C/src/federated/BroadcastFeedbackWithHierarchy.lf +++ b/test/C/src/federated/BroadcastFeedbackWithHierarchy.lf @@ -11,7 +11,9 @@ reactor SenderAndReceiver { r = new Receiver() in -> r.in - reaction(startup) -> out {= lf_set(out, 42); =} + reaction(startup) -> out {= + lf_set(out, 42); + =} } reactor Receiver { diff --git a/test/C/src/federated/CycleDetection.lf b/test/C/src/federated/CycleDetection.lf index 6d7c818882..8128147bfe 100644 --- a/test/C/src/federated/CycleDetection.lf +++ b/test/C/src/federated/CycleDetection.lf @@ -23,14 +23,18 @@ reactor CAReplica { } =} - reaction(query) -> response {= lf_set(response, self->balance); =} + reaction(query) -> response {= + lf_set(response, self->balance); + =} } reactor UserInput { input balance: int output deposit: int - reaction(startup) -> deposit {= lf_set(deposit, 100); =} + reaction(startup) -> deposit {= + lf_set(deposit, 100); + =} reaction(balance) {= if (balance->value != 200) { @@ -45,7 +49,9 @@ reactor UserInput { lf_request_stop(); =} - reaction(shutdown) {= lf_print("Shutdown reaction invoked."); =} + reaction(shutdown) {= + lf_print("Shutdown reaction invoked."); + =} } federated reactor { diff --git a/test/C/src/federated/DecentralizedP2PComm.lf b/test/C/src/federated/DecentralizedP2PComm.lf index 63163bd98b..4cf42ce5e8 100644 --- a/test/C/src/federated/DecentralizedP2PComm.lf +++ b/test/C/src/federated/DecentralizedP2PComm.lf @@ -12,7 +12,9 @@ reactor Platform(start: int = 0, expected_start: int = 0, stp_offset_param: time state count: int = start state expected: int = expected_start - reaction(t) -> out {= lf_set(out, self->count++); =} + reaction(t) -> out {= + lf_set(out, self->count++); + =} reaction(in) {= lf_print("Received %d.", in->value); diff --git a/test/C/src/federated/DecentralizedP2PUnbalancedTimeout.lf b/test/C/src/federated/DecentralizedP2PUnbalancedTimeout.lf index 0ebed39189..6c45870898 100644 --- a/test/C/src/federated/DecentralizedP2PUnbalancedTimeout.lf +++ b/test/C/src/federated/DecentralizedP2PUnbalancedTimeout.lf @@ -21,7 +21,9 @@ reactor Clock(offset: time = 0, period: time = 1 sec) { lf_set(y, self->count); =} - reaction(shutdown) {= lf_print("SUCCESS: the source exited successfully."); =} + reaction(shutdown) {= + lf_print("SUCCESS: the source exited successfully."); + =} } reactor Destination { diff --git a/test/C/src/federated/DecentralizedP2PUnbalancedTimeoutPhysical.lf b/test/C/src/federated/DecentralizedP2PUnbalancedTimeoutPhysical.lf index 356222a266..ce7f42a1f5 100644 --- a/test/C/src/federated/DecentralizedP2PUnbalancedTimeoutPhysical.lf +++ b/test/C/src/federated/DecentralizedP2PUnbalancedTimeoutPhysical.lf @@ -22,7 +22,9 @@ reactor Clock(offset: time = 0, period: time = 1 sec) { lf_set(y, self->count); =} - reaction(shutdown) {= lf_print("SUCCESS: the source exited successfully."); =} + reaction(shutdown) {= + lf_print("SUCCESS: the source exited successfully."); + =} } reactor Destination { diff --git a/test/C/src/federated/DistributedBank.lf b/test/C/src/federated/DistributedBank.lf index bb23df81d6..f934bd7361 100644 --- a/test/C/src/federated/DistributedBank.lf +++ b/test/C/src/federated/DistributedBank.lf @@ -8,7 +8,9 @@ reactor Node(bank_index: int = 0) { timer t(0, 100 msec) state count: int = 0 - reaction(t) {= lf_print("Hello world %d.", self->count++); =} + reaction(t) {= + lf_print("Hello world %d.", self->count++); + =} reaction(shutdown) {= if (self->bank_index) { diff --git a/test/C/src/federated/DistributedDoublePort.lf b/test/C/src/federated/DistributedDoublePort.lf index 5885d20a7e..ced1663dba 100644 --- a/test/C/src/federated/DistributedDoublePort.lf +++ b/test/C/src/federated/DistributedDoublePort.lf @@ -18,9 +18,13 @@ reactor CountMicrostep { logical action act: int timer t(0, 1 sec) - reaction(t) -> act {= lf_schedule_int(act, 0, self->count++); =} + reaction(t) -> act {= + lf_schedule_int(act, 0, self->count++); + =} - reaction(act) -> out {= lf_set(out, act->value); =} + reaction(act) -> out {= + lf_set(out, act->value); + =} } reactor Print { @@ -35,7 +39,9 @@ reactor Print { } =} - reaction(shutdown) {= lf_print("SUCCESS: messages were at least one microstep apart."); =} + reaction(shutdown) {= + lf_print("SUCCESS: messages were at least one microstep apart."); + =} } federated reactor DistributedDoublePort { diff --git a/test/C/src/federated/DistributedPhysicalActionUpstream.lf b/test/C/src/federated/DistributedPhysicalActionUpstream.lf index 847e9547ed..3a85c9b3d1 100644 --- a/test/C/src/federated/DistributedPhysicalActionUpstream.lf +++ b/test/C/src/federated/DistributedPhysicalActionUpstream.lf @@ -44,7 +44,9 @@ reactor WithPhysicalAction { lf_thread_create(&self->thread_id, &take_time, act); =} - reaction(act) -> out {= lf_set(out, act->value); =} + reaction(act) -> out {= + lf_set(out, act->value); + =} } federated reactor { diff --git a/test/C/src/federated/DistributedPhysicalActionUpstreamLong.lf b/test/C/src/federated/DistributedPhysicalActionUpstreamLong.lf index 180e05be47..99154b1ad2 100644 --- a/test/C/src/federated/DistributedPhysicalActionUpstreamLong.lf +++ b/test/C/src/federated/DistributedPhysicalActionUpstreamLong.lf @@ -43,7 +43,9 @@ reactor WithPhysicalAction { lf_thread_create(&self->thread_id, &take_time, act); =} - reaction(act) -> out {= lf_set(out, act->value); =} + reaction(act) -> out {= + lf_set(out, act->value); + =} } federated reactor { diff --git a/test/C/src/federated/EnclaveFederatedRequestStop.lf b/test/C/src/federated/EnclaveFederatedRequestStop.lf index cb92f4f20d..1a3a590308 100644 --- a/test/C/src/federated/EnclaveFederatedRequestStop.lf +++ b/test/C/src/federated/EnclaveFederatedRequestStop.lf @@ -12,7 +12,9 @@ reactor Stop(stop_time: time = 5 s) { =} timer t(stop_time) - reaction(t) {= lf_request_stop(); =} + reaction(t) {= + lf_request_stop(); + =} reaction(shutdown) {= lf_print("Stopped at tag (" PRINTF_TIME ", %d)", lf_time_logical_elapsed(), lf_tag().microstep); diff --git a/test/C/src/federated/FeedbackDelay.lf b/test/C/src/federated/FeedbackDelay.lf index ed458a9749..8bec54dc9f 100644 --- a/test/C/src/federated/FeedbackDelay.lf +++ b/test/C/src/federated/FeedbackDelay.lf @@ -20,7 +20,9 @@ reactor PhysicalPlant { instant_t control_time = lf_time_physical(); lf_print("Latency %lld.", control_time - self->previous_sensor_time); lf_print("Logical time: %lld.", lf_time_logical_elapsed()); - =} STP(33 msec) {= lf_print_warning("STP violation."); =} + =} STP(33 msec) {= + lf_print_warning("STP violation."); + =} } reactor Controller { @@ -33,7 +35,9 @@ reactor Controller { output request_for_planning: double input planning: double - reaction(planning) {= self->latest_control = planning->value; =} + reaction(planning) {= + self->latest_control = planning->value; + =} reaction(sensor) -> control, request_for_planning {= if (!self->first) { diff --git a/test/C/src/federated/FeedbackDelaySimple.lf b/test/C/src/federated/FeedbackDelaySimple.lf index ece831bf11..655fbe0762 100644 --- a/test/C/src/federated/FeedbackDelaySimple.lf +++ b/test/C/src/federated/FeedbackDelaySimple.lf @@ -20,7 +20,9 @@ reactor Loop { self->count++; =} - reaction(t) -> out {= lf_set(out, self->count); =} + reaction(t) -> out {= + lf_set(out, self->count); + =} reaction(shutdown) {= if (self->count != 11) { diff --git a/test/C/src/federated/HelloDistributed.lf b/test/C/src/federated/HelloDistributed.lf index 4b06399fa4..8fa47c22c9 100644 --- a/test/C/src/federated/HelloDistributed.lf +++ b/test/C/src/federated/HelloDistributed.lf @@ -24,7 +24,9 @@ reactor Destination { input in: string state received: bool = false - reaction(startup) {= lf_print("Destination started."); =} + reaction(startup) {= + lf_print("Destination started."); + =} reaction(in) {= lf_print("At logical time %lld, destination received: %s", lf_time_logical_elapsed(), in->value); @@ -48,5 +50,7 @@ federated reactor HelloDistributed at localhost { d = new Destination() // Reactor d is in federate Destination s.out -> d.in // This version preserves the timestamp. - reaction(startup) {= lf_print("Printing something in top-level federated reactor."); =} + reaction(startup) {= + lf_print("Printing something in top-level federated reactor."); + =} } diff --git a/test/C/src/federated/InheritanceFederated.lf b/test/C/src/federated/InheritanceFederated.lf index 70f429dbc8..90098b29bb 100644 --- a/test/C/src/federated/InheritanceFederated.lf +++ b/test/C/src/federated/InheritanceFederated.lf @@ -6,7 +6,9 @@ target C { } reactor A { - reaction(startup) {= printf("Hello\n"); =} + reaction(startup) {= + printf("Hello\n"); + =} } reactor B { diff --git a/test/C/src/federated/InheritanceFederatedImport.lf b/test/C/src/federated/InheritanceFederatedImport.lf index 0918e6809b..37da485956 100644 --- a/test/C/src/federated/InheritanceFederatedImport.lf +++ b/test/C/src/federated/InheritanceFederatedImport.lf @@ -7,7 +7,9 @@ target C { import HelloWorld2 from "../HelloWorld.lf" reactor Print extends HelloWorld2 { - reaction(startup) {= printf("Foo\n"); =} + reaction(startup) {= + printf("Foo\n"); + =} } federated reactor { diff --git a/test/C/src/federated/LevelPattern.lf b/test/C/src/federated/LevelPattern.lf index da47325ba1..1721861b14 100644 --- a/test/C/src/federated/LevelPattern.lf +++ b/test/C/src/federated/LevelPattern.lf @@ -15,7 +15,9 @@ reactor Through { input in: int output out: int - reaction(in) -> out {= lf_set(out, in->value); =} + reaction(in) -> out {= + lf_set(out, in->value); + =} } reactor A { @@ -30,9 +32,13 @@ reactor A { i2 = new Through() i2.out -> out2 - reaction(in1) -> i1.in {= lf_set(i1.in, in1->value); =} + reaction(in1) -> i1.in {= + lf_set(i1.in, in1->value); + =} - reaction(in2) -> i2.in {= lf_set(i2.in, in2->value); =} + reaction(in2) -> i2.in {= + lf_set(i2.in, in2->value); + =} } federated reactor { diff --git a/test/C/src/federated/LoopDistributedCentralizedPrecedenceHierarchy.lf b/test/C/src/federated/LoopDistributedCentralizedPrecedenceHierarchy.lf index 9e8fb02059..82adfca699 100644 --- a/test/C/src/federated/LoopDistributedCentralizedPrecedenceHierarchy.lf +++ b/test/C/src/federated/LoopDistributedCentralizedPrecedenceHierarchy.lf @@ -20,9 +20,13 @@ reactor Contained(incr: int = 1) { state count: int = 0 state received_count: int = 0 - reaction(t) {= self->count += self->incr; =} + reaction(t) {= + self->count += self->incr; + =} - reaction(in) {= self->received_count = self->count; =} + reaction(in) {= + self->received_count = self->count; + =} reaction(t) {= if (self->received_count != self->count) { diff --git a/test/C/src/federated/SpuriousDependency.lf b/test/C/src/federated/SpuriousDependency.lf index cf0deb6380..b810d5288f 100644 --- a/test/C/src/federated/SpuriousDependency.lf +++ b/test/C/src/federated/SpuriousDependency.lf @@ -35,7 +35,9 @@ reactor Check { state count: int = 0 - reaction(in) {= lf_print("count is now %d", ++self->count); =} + reaction(in) {= + lf_print("count is now %d", ++self->count); + =} reaction(shutdown) {= lf_print("******* Shutdown invoked."); @@ -55,5 +57,7 @@ federated reactor { t1.out0 -> check.in - reaction(startup) -> t0.in1 {= lf_set(t0.in1, 0); =} + reaction(startup) -> t0.in1 {= + lf_set(t0.in1, 0); + =} } diff --git a/test/C/src/federated/StopAtShutdown.lf b/test/C/src/federated/StopAtShutdown.lf index 2ac97d6805..2fad7db3d0 100644 --- a/test/C/src/federated/StopAtShutdown.lf +++ b/test/C/src/federated/StopAtShutdown.lf @@ -12,20 +12,30 @@ target C { reactor A { input in: int - reaction(startup) {= lf_print("Hello World!"); =} + reaction(startup) {= + lf_print("Hello World!"); + =} - reaction(in) {= lf_print("Got it"); =} + reaction(in) {= + lf_print("Got it"); + =} - reaction(shutdown) {= lf_request_stop(); =} + reaction(shutdown) {= + lf_request_stop(); + =} } reactor B { output out: int timer t(1 sec) - reaction(t) -> out {= lf_set(out, 1); =} + reaction(t) -> out {= + lf_set(out, 1); + =} - reaction(shutdown) {= lf_request_stop(); =} + reaction(shutdown) {= + lf_request_stop(); + =} } federated reactor { diff --git a/test/C/src/federated/TopLevelArtifacts.lf b/test/C/src/federated/TopLevelArtifacts.lf index b2e486b31d..d73ea35967 100644 --- a/test/C/src/federated/TopLevelArtifacts.lf +++ b/test/C/src/federated/TopLevelArtifacts.lf @@ -22,14 +22,18 @@ federated reactor { tc = new TestCount() c.out -> tc.in - reaction(startup) {= self->successes++; =} + reaction(startup) {= + self->successes++; + =} reaction(t) -> act {= self->successes++; lf_schedule(act, 0); =} - reaction(act) {= self->successes++; =} + reaction(act) {= + self->successes++; + =} reaction(shutdown) {= if (self->successes != 3) { diff --git a/test/C/src/generics/ParameterAsArgument.lf b/test/C/src/generics/ParameterAsArgument.lf index f6d460279e..47b3c3c903 100644 --- a/test/C/src/generics/ParameterAsArgument.lf +++ b/test/C/src/generics/ParameterAsArgument.lf @@ -23,5 +23,7 @@ reactor Super { main reactor { cc = new Super() - reaction(startup) -> cc.in {= lf_set(cc.in, 42); =} + reaction(startup) -> cc.in {= + lf_set(cc.in, 42); + =} } diff --git a/test/C/src/generics/TypeCheck.lf b/test/C/src/generics/TypeCheck.lf index db3524798e..d04e2dfa87 100644 --- a/test/C/src/generics/TypeCheck.lf +++ b/test/C/src/generics/TypeCheck.lf @@ -9,7 +9,9 @@ reactor Count(offset: time = 0, period: time = 1 sec) { output out: int timer t(offset, period) - reaction(t) -> out {= lf_set(out, self->count++); =} + reaction(t) -> out {= + lf_set(out, self->count++); + =} } reactor TestCount(start: int = 1, num_inputs: int = 1) { diff --git a/test/C/src/lib/Count.lf b/test/C/src/lib/Count.lf index 5b8d7b5844..ee3953b021 100644 --- a/test/C/src/lib/Count.lf +++ b/test/C/src/lib/Count.lf @@ -5,5 +5,7 @@ reactor Count(offset: time = 0, period: time = 1 sec) { output out: int timer t(offset, period) - reaction(t) -> out {= lf_set(out, self->count++); =} + reaction(t) -> out {= + lf_set(out, self->count++); + =} } diff --git a/test/C/src/lib/FileLevelPreamble.lf b/test/C/src/lib/FileLevelPreamble.lf index f11186979d..11067d5e63 100644 --- a/test/C/src/lib/FileLevelPreamble.lf +++ b/test/C/src/lib/FileLevelPreamble.lf @@ -6,5 +6,7 @@ preamble {= =} reactor FileLevelPreamble { - reaction(startup) {= printf("FOO: %d\n", FOO); =} + reaction(startup) {= + printf("FOO: %d\n", FOO); + =} } diff --git a/test/C/src/lib/GenDelay.lf b/test/C/src/lib/GenDelay.lf index a9d607db75..8f21c3de1b 100644 --- a/test/C/src/lib/GenDelay.lf +++ b/test/C/src/lib/GenDelay.lf @@ -1,15 +1,21 @@ target C -preamble {= typedef int message_t; =} +preamble {= + typedef int message_t; +=} reactor Source { output out: message_t - reaction(startup) -> out {= lf_set(out, 42); =} + reaction(startup) -> out {= + lf_set(out, 42); + =} } reactor Sink { input in: message_t - reaction(in) {= lf_print("Received %d at time %lld", in->value, lf_time_logical_elapsed()); =} + reaction(in) {= + lf_print("Received %d at time %lld", in->value, lf_time_logical_elapsed()); + =} } diff --git a/test/C/src/lib/Imported.lf b/test/C/src/lib/Imported.lf index 30e17cd94e..85d0a2b493 100644 --- a/test/C/src/lib/Imported.lf +++ b/test/C/src/lib/Imported.lf @@ -8,5 +8,7 @@ reactor Imported { input x: int a = new ImportedAgain() - reaction(x) -> a.x {= lf_set(a.x, x->value); =} + reaction(x) -> a.x {= + lf_set(a.x, x->value); + =} } diff --git a/test/C/src/lib/ImportedComposition.lf b/test/C/src/lib/ImportedComposition.lf index d434f4eb6a..e5524f3d22 100644 --- a/test/C/src/lib/ImportedComposition.lf +++ b/test/C/src/lib/ImportedComposition.lf @@ -6,7 +6,9 @@ reactor Gain { input x: int output y: int - reaction(x) -> y {= lf_set(y, x->value * 2); =} + reaction(x) -> y {= + lf_set(y, x->value * 2); + =} } reactor ImportedComposition { diff --git a/test/C/src/lib/InternalDelay.lf b/test/C/src/lib/InternalDelay.lf index 33ccf9e230..fb7124a4ec 100644 --- a/test/C/src/lib/InternalDelay.lf +++ b/test/C/src/lib/InternalDelay.lf @@ -5,7 +5,11 @@ reactor InternalDelay(delay: time = 10 msec) { output out: int logical action d: int - reaction(in) -> d {= lf_schedule_int(d, self->delay, in->value); =} + reaction(in) -> d {= + lf_schedule_int(d, self->delay, in->value); + =} - reaction(d) -> out {= lf_set(out, d->value); =} + reaction(d) -> out {= + lf_set(out, d->value); + =} } diff --git a/test/C/src/lib/PassThrough.lf b/test/C/src/lib/PassThrough.lf index 90d9138c09..389905489a 100644 --- a/test/C/src/lib/PassThrough.lf +++ b/test/C/src/lib/PassThrough.lf @@ -5,5 +5,7 @@ reactor PassThrough { input in: int output out: int - reaction(in) -> out {= lf_set(out, in->value); =} + reaction(in) -> out {= + lf_set(out, in->value); + =} } diff --git a/test/C/src/modal_models/BanksCount3ModesComplex.lf b/test/C/src/modal_models/BanksCount3ModesComplex.lf index e548d28cef..48774e0b80 100644 --- a/test/C/src/modal_models/BanksCount3ModesComplex.lf +++ b/test/C/src/modal_models/BanksCount3ModesComplex.lf @@ -25,7 +25,9 @@ reactor MetaCounter { mode1_counters.count -> mode1 timer t1(500 msec, 250 msec) - reaction(t1) -> reset(Two) {= lf_set_mode(Two); =} + reaction(t1) -> reset(Two) {= + lf_set_mode(Two); + =} } mode Two { @@ -35,7 +37,9 @@ reactor MetaCounter { mode2_counters.count -> mode2 timer t2(500 msec, 250 msec) - reaction(t2) -> history(One) {= lf_set_mode(One); =} + reaction(t2) -> history(One) {= + lf_set_mode(One); + =} } mode Three { diff --git a/test/C/src/modal_models/ConvertCaseTest.lf b/test/C/src/modal_models/ConvertCaseTest.lf index 09cb159f2d..eeab275996 100644 --- a/test/C/src/modal_models/ConvertCaseTest.lf +++ b/test/C/src/modal_models/ConvertCaseTest.lf @@ -15,13 +15,19 @@ reactor ResetProcessor { converter = new Converter() character -> converter.raw converter.converted -> converted - reaction(discard) -> reset(Discarding) {= lf_set_mode(Discarding); =} + reaction(discard) -> reset(Discarding) {= + lf_set_mode(Discarding); + =} } mode Discarding { - reaction(character) -> converted {= lf_set(converted, '_'); =} + reaction(character) -> converted {= + lf_set(converted, '_'); + =} - reaction(character) -> reset(Converting) {= lf_set_mode(Converting); =} + reaction(character) -> reset(Converting) {= + lf_set_mode(Converting); + =} } } @@ -34,13 +40,19 @@ reactor HistoryProcessor { converter = new Converter() character -> converter.raw converter.converted -> converted - reaction(discard) -> reset(Discarding) {= lf_set_mode(Discarding); =} + reaction(discard) -> reset(Discarding) {= + lf_set_mode(Discarding); + =} } mode Discarding { - reaction(character) -> converted {= lf_set(converted, '_'); =} + reaction(character) -> converted {= + lf_set(converted, '_'); + =} - reaction(character) -> history(Converting) {= lf_set_mode(Converting); =} + reaction(character) -> history(Converting) {= + lf_set_mode(Converting); + =} } } @@ -128,7 +140,9 @@ main reactor { lf_set(history_processor.discard, true); =} - reaction(reset_processor.converted) {= printf("Reset: %c\n", reset_processor.converted->value); =} + reaction(reset_processor.converted) {= + printf("Reset: %c\n", reset_processor.converted->value); + =} reaction(history_processor.converted) {= printf("History: %c\n", history_processor.converted->value); diff --git a/test/C/src/modal_models/Count3Modes.lf b/test/C/src/modal_models/Count3Modes.lf index 3c2b419a00..232482e009 100644 --- a/test/C/src/modal_models/Count3Modes.lf +++ b/test/C/src/modal_models/Count3Modes.lf @@ -36,7 +36,10 @@ main reactor { state expected_value: int = 1 - reaction(stepper) -> counter.next {= lf_set(counter.next, true); =} // Trigger + // Trigger + reaction(stepper) -> counter.next {= + lf_set(counter.next, true); + =} // Check reaction(stepper) counter.count {= diff --git a/test/C/src/modal_models/MixedReactions.lf b/test/C/src/modal_models/MixedReactions.lf index ed9ae422f3..a67d470b91 100644 --- a/test/C/src/modal_models/MixedReactions.lf +++ b/test/C/src/modal_models/MixedReactions.lf @@ -13,9 +13,13 @@ main reactor { timer t(0, 100 msec) - reaction(t) {= self->x = self->x * 10 + 1; =} + reaction(t) {= + self->x = self->x * 10 + 1; + =} - reaction(t) {= self->x = self->x * 10 + 2; =} + reaction(t) {= + self->x = self->x * 10 + 2; + =} initial mode A { reaction(t) -> reset(B) {= @@ -24,10 +28,14 @@ main reactor { =} } - reaction(t) {= self->x = self->x * 10 + 4; =} + reaction(t) {= + self->x = self->x * 10 + 4; + =} mode B { - reaction(t) {= self->x = self->x * 10 + 5; =} + reaction(t) {= + self->x = self->x * 10 + 5; + =} } reaction(t) {= diff --git a/test/C/src/modal_models/ModalActions.lf b/test/C/src/modal_models/ModalActions.lf index cf473cd125..3cebd07b30 100644 --- a/test/C/src/modal_models/ModalActions.lf +++ b/test/C/src/modal_models/ModalActions.lf @@ -89,5 +89,8 @@ main reactor { modal.action2_sched, modal.action2_exec -> test.events - reaction(stepper) -> modal.next {= lf_set(modal.next, true); =} // Trigger mode change + // Trigger mode change + reaction(stepper) -> modal.next {= + lf_set(modal.next, true); + =} } diff --git a/test/C/src/modal_models/ModalAfter.lf b/test/C/src/modal_models/ModalAfter.lf index 0b0c7251fc..fb2b36d71e 100644 --- a/test/C/src/modal_models/ModalAfter.lf +++ b/test/C/src/modal_models/ModalAfter.lf @@ -91,5 +91,8 @@ main reactor { modal.mode_switch, modal.produced1, modal.consumed1, modal.produced2, modal.consumed2 -> test.events - reaction(stepper) -> modal.next {= lf_set(modal.next, true); =} // Trigger mode change + // Trigger mode change + reaction(stepper) -> modal.next {= + lf_set(modal.next, true); + =} } diff --git a/test/C/src/modal_models/ModalCycleBreaker.lf b/test/C/src/modal_models/ModalCycleBreaker.lf index 428555e165..0f90275bee 100644 --- a/test/C/src/modal_models/ModalCycleBreaker.lf +++ b/test/C/src/modal_models/ModalCycleBreaker.lf @@ -30,7 +30,9 @@ reactor Modal { } initial mode One { - reaction(in1) -> out {= lf_set(out, in1->value); =} + reaction(in1) -> out {= + lf_set(out, in1->value); + =} reaction(in1) -> reset(Two) {= if (in1->value % 5 == 4) { @@ -47,7 +49,9 @@ reactor Counter(period: time = 1 sec) { timer t(0, period) state curval: int = 0 - reaction(t) -> value {= lf_set(value, self->curval++); =} + reaction(t) -> value {= + lf_set(value, self->curval++); + =} } main reactor { @@ -70,5 +74,8 @@ main reactor { modal.out -> test.events - reaction(modal.out) {= printf("%d\n", modal.out->value); =} // Print + // Print + reaction(modal.out) {= + printf("%d\n", modal.out->value); + =} } diff --git a/test/C/src/modal_models/ModalNestedReactions.lf b/test/C/src/modal_models/ModalNestedReactions.lf index e796c3df31..88bb61c8bb 100644 --- a/test/C/src/modal_models/ModalNestedReactions.lf +++ b/test/C/src/modal_models/ModalNestedReactions.lf @@ -29,7 +29,9 @@ reactor CounterCycle { } mode Three { - reaction(next) -> never {= lf_set(never, true); =} + reaction(next) -> never {= + lf_set(never, true); + =} } } @@ -37,14 +39,19 @@ reactor Forward { input in: bool output out: bool - reaction(in) -> out {= lf_set(out, in->value); =} + reaction(in) -> out {= + lf_set(out, in->value); + =} } main reactor { timer stepper(0, 250 msec) counter = new CounterCycle() - reaction(stepper) -> counter.next {= lf_set(counter.next, true); =} // Trigger + // Trigger + reaction(stepper) -> counter.next {= + lf_set(counter.next, true); + =} // Check reaction(stepper) counter.count, counter.only_in_two {= diff --git a/test/C/src/modal_models/ModalStartupShutdown.lf b/test/C/src/modal_models/ModalStartupShutdown.lf index d26a743ec7..2c0e974c67 100644 --- a/test/C/src/modal_models/ModalStartupShutdown.lf +++ b/test/C/src/modal_models/ModalStartupShutdown.lf @@ -137,5 +137,8 @@ main reactor { modal.reset5, modal.shutdown5 -> test.events - reaction(stepper) -> modal.next {= lf_set(modal.next, true); =} // Trigger mode change + // Trigger mode change + reaction(stepper) -> modal.next {= + lf_set(modal.next, true); + =} } diff --git a/test/C/src/modal_models/ModalStateReset.lf b/test/C/src/modal_models/ModalStateReset.lf index 181a8b1189..c8cf41d351 100644 --- a/test/C/src/modal_models/ModalStateReset.lf +++ b/test/C/src/modal_models/ModalStateReset.lf @@ -24,7 +24,9 @@ reactor Modal { initial mode One { state counter1: int = 0 timer T1(0 msec, 250 msec) - reaction(reset) {= self->counter1 = 0; =} + reaction(reset) {= + self->counter1 = 0; + =} reaction(T1) -> count1 {= printf("Counter1: %d\n", self->counter1); @@ -41,7 +43,9 @@ reactor Modal { mode Two { state counter2: int = -2 timer T2(0 msec, 250 msec) - reaction(reset) {= self->counter2 = -2; =} + reaction(reset) {= + self->counter2 = -2; + =} reaction(T2) -> count2 {= printf("Counter2: %d\n", self->counter2); @@ -84,5 +88,8 @@ main reactor { modal.mode_switch, modal.count0, modal.count1, modal.count2 -> test.events - reaction(stepper) -> modal.next {= lf_set(modal.next, true); =} // Trigger mode change + // Trigger mode change + reaction(stepper) -> modal.next {= + lf_set(modal.next, true); + =} } diff --git a/test/C/src/modal_models/ModalStateResetAuto.lf b/test/C/src/modal_models/ModalStateResetAuto.lf index 82832da72d..3fd6ec739d 100644 --- a/test/C/src/modal_models/ModalStateResetAuto.lf +++ b/test/C/src/modal_models/ModalStateResetAuto.lf @@ -80,5 +80,8 @@ main reactor { modal.mode_switch, modal.count0, modal.count1, modal.count2 -> test.events - reaction(stepper) -> modal.next {= lf_set(modal.next, true); =} // Trigger mode change + // Trigger mode change + reaction(stepper) -> modal.next {= + lf_set(modal.next, true); + =} } diff --git a/test/C/src/modal_models/ModalTimers.lf b/test/C/src/modal_models/ModalTimers.lf index 8e67b6aa75..89287c0819 100644 --- a/test/C/src/modal_models/ModalTimers.lf +++ b/test/C/src/modal_models/ModalTimers.lf @@ -62,5 +62,8 @@ main reactor { modal.mode_switch, modal.timer1, modal.timer2 -> test.events - reaction(stepper) -> modal.next {= lf_set(modal.next, true); =} // Trigger mode change + // Trigger mode change + reaction(stepper) -> modal.next {= + lf_set(modal.next, true); + =} } diff --git a/test/C/src/modal_models/MultipleOutputFeeder_2Connections.lf b/test/C/src/modal_models/MultipleOutputFeeder_2Connections.lf index 637f546498..b84184619b 100644 --- a/test/C/src/modal_models/MultipleOutputFeeder_2Connections.lf +++ b/test/C/src/modal_models/MultipleOutputFeeder_2Connections.lf @@ -18,13 +18,17 @@ reactor Modal { initial mode One { counter1 = new Counter(period = 250 msec) counter1.value -> count - reaction(next) -> reset(Two) {= lf_set_mode(Two); =} + reaction(next) -> reset(Two) {= + lf_set_mode(Two); + =} } mode Two { counter2 = new Counter(period = 100 msec) counter2.value -> count - reaction(next) -> history(One) {= lf_set_mode(One); =} + reaction(next) -> history(One) {= + lf_set_mode(One); + =} } } @@ -34,7 +38,9 @@ reactor Counter(period: time = 1 sec) { timer t(0, period) reset state curval: int = 0 - reaction(t) -> value {= lf_set(value, self->curval++); =} + reaction(t) -> value {= + lf_set(value, self->curval++); + =} } main reactor { @@ -63,7 +69,13 @@ main reactor { modal.count -> test.events - reaction(stepper) -> modal.next {= lf_set(modal.next, true); =} // Trigger mode change + // Trigger mode change + reaction(stepper) -> modal.next {= + lf_set(modal.next, true); + =} - reaction(modal.count) {= printf("%d\n", modal.count->value); =} // Print + // Print + reaction(modal.count) {= + printf("%d\n", modal.count->value); + =} } diff --git a/test/C/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf b/test/C/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf index 1281b3f490..90615541f4 100644 --- a/test/C/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf +++ b/test/C/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf @@ -18,14 +18,20 @@ reactor Modal { initial mode One { counter1 = new Counter(period = 250 msec) counter1.value -> count - reaction(next) -> reset(Two) {= lf_set_mode(Two); =} + reaction(next) -> reset(Two) {= + lf_set_mode(Two); + =} } mode Two { counter2 = new Counter(period = 100 msec) - reaction(counter2.value) -> count {= lf_set(count, counter2.value->value * 10); =} + reaction(counter2.value) -> count {= + lf_set(count, counter2.value->value * 10); + =} - reaction(next) -> history(One) {= lf_set_mode(One); =} + reaction(next) -> history(One) {= + lf_set_mode(One); + =} } } @@ -35,7 +41,9 @@ reactor Counter(period: time = 1 sec) { timer t(0, period) reset state curval: int = 0 - reaction(t) -> value {= lf_set(value, self->curval++); =} + reaction(t) -> value {= + lf_set(value, self->curval++); + =} } main reactor { @@ -64,7 +72,13 @@ main reactor { modal.count -> test.events - reaction(stepper) -> modal.next {= lf_set(modal.next, true); =} // Trigger mode change + // Trigger mode change + reaction(stepper) -> modal.next {= + lf_set(modal.next, true); + =} - reaction(modal.count) {= printf("%d\n", modal.count->value); =} // Print + // Print + reaction(modal.count) {= + printf("%d\n", modal.count->value); + =} } diff --git a/test/C/src/modal_models/util/TraceTesting.lf b/test/C/src/modal_models/util/TraceTesting.lf index 86908ad2e5..f317c3579d 100644 --- a/test/C/src/modal_models/util/TraceTesting.lf +++ b/test/C/src/modal_models/util/TraceTesting.lf @@ -13,7 +13,9 @@ reactor TraceTesting( state recorded_events: int* = 0 state recorded_events_next: int = 0 - reaction(startup) {= self->last_reaction_time = lf_time_logical(); =} + reaction(startup) {= + self->last_reaction_time = lf_time_logical(); + =} reaction(events) {= // Time passed since last reaction diff --git a/test/C/src/multiport/BankGangedConnections.lf b/test/C/src/multiport/BankGangedConnections.lf index 231014c55e..e2e20c78ad 100644 --- a/test/C/src/multiport/BankGangedConnections.lf +++ b/test/C/src/multiport/BankGangedConnections.lf @@ -9,7 +9,9 @@ reactor Through { input in: int output out: int - reaction(in) -> out {= lf_set(out, in->value); =} + reaction(in) -> out {= + lf_set(out, in->value); + =} } reactor Bank { diff --git a/test/C/src/multiport/BankIndexInitializer.lf b/test/C/src/multiport/BankIndexInitializer.lf index 434e59104a..ef8c181e90 100644 --- a/test/C/src/multiport/BankIndexInitializer.lf +++ b/test/C/src/multiport/BankIndexInitializer.lf @@ -1,14 +1,20 @@ // Test bank of reactors to multiport input with id parameter in the bank. target C -preamble {= extern int table[4]; =} +preamble {= + extern int table[4]; +=} reactor Source(bank_index: int = 0, value: int = 0) { - preamble {= int table[] = {4, 3, 2, 1}; =} + preamble {= + int table[] = {4, 3, 2, 1}; + =} output out: int - reaction(startup) -> out {= lf_set(out, self->value); =} + reaction(startup) -> out {= + lf_set(out, self->value); + =} } reactor Sink(width: int = 4) { diff --git a/test/C/src/multiport/BankSelfBroadcast.lf b/test/C/src/multiport/BankSelfBroadcast.lf index f9a52af044..07c4492f4e 100644 --- a/test/C/src/multiport/BankSelfBroadcast.lf +++ b/test/C/src/multiport/BankSelfBroadcast.lf @@ -12,7 +12,9 @@ reactor A(bank_index: int = 0) { output out: int state received: bool = false - reaction(startup) -> out {= lf_set(out, self->bank_index); =} + reaction(startup) -> out {= + lf_set(out, self->bank_index); + =} reaction(in) {= for (int i = 0; i < in_width; i++) { diff --git a/test/C/src/multiport/BankToMultiport.lf b/test/C/src/multiport/BankToMultiport.lf index 4506cee0c1..c6be3c0adc 100644 --- a/test/C/src/multiport/BankToMultiport.lf +++ b/test/C/src/multiport/BankToMultiport.lf @@ -4,7 +4,9 @@ target C reactor Source(bank_index: int = 0) { output out: int - reaction(startup) -> out {= lf_set(out, self->bank_index); =} + reaction(startup) -> out {= + lf_set(out, self->bank_index); + =} } reactor Sink(width: int = 4) { diff --git a/test/C/src/multiport/Broadcast.lf b/test/C/src/multiport/Broadcast.lf index 27092a4366..3750ec347d 100644 --- a/test/C/src/multiport/Broadcast.lf +++ b/test/C/src/multiport/Broadcast.lf @@ -6,7 +6,9 @@ target C { reactor Source { output out: int - reaction(startup) -> out {= lf_set(out, 42); =} + reaction(startup) -> out {= + lf_set(out, 42); + =} } reactor Destination(bank_index: int = 0) { diff --git a/test/C/src/multiport/BroadcastAfter.lf b/test/C/src/multiport/BroadcastAfter.lf index 48b5850b7c..b7e4114046 100644 --- a/test/C/src/multiport/BroadcastAfter.lf +++ b/test/C/src/multiport/BroadcastAfter.lf @@ -6,7 +6,9 @@ target C { reactor Source { output out: int - reaction(startup) -> out {= lf_set(out, 42); =} + reaction(startup) -> out {= + lf_set(out, 42); + =} } reactor Destination(bank_index: int = 0) { diff --git a/test/C/src/multiport/BroadcastMultipleAfter.lf b/test/C/src/multiport/BroadcastMultipleAfter.lf index 809c03448c..3f32a51263 100644 --- a/test/C/src/multiport/BroadcastMultipleAfter.lf +++ b/test/C/src/multiport/BroadcastMultipleAfter.lf @@ -6,7 +6,9 @@ target C { reactor Source(value: int = 42) { output out: int - reaction(startup) -> out {= lf_set(out, self->value); =} + reaction(startup) -> out {= + lf_set(out, self->value); + =} } reactor Destination(bank_index: int = 0) { diff --git a/test/C/src/multiport/MultiportFromBank.lf b/test/C/src/multiport/MultiportFromBank.lf index 3969da111d..77482f6730 100644 --- a/test/C/src/multiport/MultiportFromBank.lf +++ b/test/C/src/multiport/MultiportFromBank.lf @@ -8,7 +8,9 @@ target C { reactor Source(check_override: int = 0, bank_index: int = 0) { output out: int - reaction(startup) -> out {= lf_set(out, self->bank_index * self->check_override); =} + reaction(startup) -> out {= + lf_set(out, self->bank_index * self->check_override); + =} } reactor Destination(port_width: int = 3) { diff --git a/test/C/src/multiport/MultiportIn.lf b/test/C/src/multiport/MultiportIn.lf index caea737d76..53e18b52ef 100644 --- a/test/C/src/multiport/MultiportIn.lf +++ b/test/C/src/multiport/MultiportIn.lf @@ -20,7 +20,9 @@ reactor Computation { input in: int output out: int - reaction(in) -> out {= lf_set(out, in->value); =} + reaction(in) -> out {= + lf_set(out, in->value); + =} } reactor Destination { diff --git a/test/C/src/multiport/MultiportInParameterized.lf b/test/C/src/multiport/MultiportInParameterized.lf index fbf1b4b6f1..172d0f15ec 100644 --- a/test/C/src/multiport/MultiportInParameterized.lf +++ b/test/C/src/multiport/MultiportInParameterized.lf @@ -20,7 +20,9 @@ reactor Computation { input in: int output out: int - reaction(in) -> out {= lf_set(out, in->value); =} + reaction(in) -> out {= + lf_set(out, in->value); + =} } reactor Destination(width: int = 1) { diff --git a/test/C/src/multiport/PipelineAfter.lf b/test/C/src/multiport/PipelineAfter.lf index bbcbaebc49..2f5e2a7c0c 100644 --- a/test/C/src/multiport/PipelineAfter.lf +++ b/test/C/src/multiport/PipelineAfter.lf @@ -3,14 +3,18 @@ target C reactor Source { output out: unsigned - reaction(startup) -> out {= lf_set(out, 40); =} + reaction(startup) -> out {= + lf_set(out, 40); + =} } reactor Compute { input in: unsigned output out: unsigned - reaction(in) -> out {= lf_set(out, in->value + 2); =} + reaction(in) -> out {= + lf_set(out, in->value + 2); + =} } reactor Sink { diff --git a/test/C/src/multiport/ReactionsToNested.lf b/test/C/src/multiport/ReactionsToNested.lf index 2b07e948de..276c91fce3 100644 --- a/test/C/src/multiport/ReactionsToNested.lf +++ b/test/C/src/multiport/ReactionsToNested.lf @@ -32,7 +32,11 @@ reactor D { main reactor { d = new D() - reaction(startup) -> d.y {= lf_set(d.y[0], 42); =} + reaction(startup) -> d.y {= + lf_set(d.y[0], 42); + =} - reaction(startup) -> d.y {= lf_set(d.y[1], 43); =} + reaction(startup) -> d.y {= + lf_set(d.y[1], 43); + =} } diff --git a/test/C/src/no_inlining/Count.lf b/test/C/src/no_inlining/Count.lf index 49ceb04482..86155385c3 100644 --- a/test/C/src/no_inlining/Count.lf +++ b/test/C/src/no_inlining/Count.lf @@ -12,5 +12,7 @@ main reactor Count { reaction check_done(t) - reaction(shutdown) {= printf("%s", "shutting down\n"); =} + 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 index 42d7eb2bd7..5ac6726001 100644 --- a/test/C/src/no_inlining/CountHierarchy.lf +++ b/test/C/src/no_inlining/CountHierarchy.lf @@ -7,7 +7,9 @@ reactor Timer(m: time = 0, n: time = 0) { output out: int timer t(m, n) - reaction(t) -> out {= lf_set(out, 0); =} + reaction(t) -> out {= + lf_set(out, 0); + =} } main reactor { @@ -19,5 +21,7 @@ main reactor { reaction check_done(t.out) - reaction(shutdown) {= printf("%s", "shutting down\n"); =} + reaction(shutdown) {= + printf("%s", "shutting down\n"); + =} } diff --git a/test/C/src/target/FederatedFiles.lf b/test/C/src/target/FederatedFiles.lf index 9fb4440a02..ef606bff05 100644 --- a/test/C/src/target/FederatedFiles.lf +++ b/test/C/src/target/FederatedFiles.lf @@ -6,7 +6,9 @@ target C { } reactor Foo { - reaction(startup) {= lf_print("Hello World"); =} + reaction(startup) {= + lf_print("Hello World"); + =} } federated reactor { diff --git a/test/C/src/target/Math.lf b/test/C/src/target/Math.lf index 68a4a13b6f..4185a19f6a 100644 --- a/test/C/src/target/Math.lf +++ b/test/C/src/target/Math.lf @@ -6,5 +6,7 @@ preamble {= =} main reactor { - reaction(startup) {= lf_print("Maximum of 42.0 and 75 is %.2f", fmax(4.20, 75)); =} + reaction(startup) {= + lf_print("Maximum of 42.0 and 75 is %.2f", fmax(4.20, 75)); + =} } diff --git a/test/C/src/target/Platform.lf b/test/C/src/target/Platform.lf index 00895a749f..68a444c9f0 100644 --- a/test/C/src/target/Platform.lf +++ b/test/C/src/target/Platform.lf @@ -4,5 +4,7 @@ target C { } main reactor { - reaction(startup) {= lf_print("Hello, Mac!"); =} + reaction(startup) {= + lf_print("Hello, Mac!"); + =} } diff --git a/test/C/src/token/lib/Token.lf b/test/C/src/token/lib/Token.lf index 3bfabd9b1c..3ef7003836 100644 --- a/test/C/src/token/lib/Token.lf +++ b/test/C/src/token/lib/Token.lf @@ -95,7 +95,11 @@ reactor TokenDelay { output out: int_array_t* logical action a: int_array_t* - reaction(a) -> out {= lf_set_token(out, a->token); =} + reaction(a) -> out {= + lf_set_token(out, a->token); + =} - reaction(in) -> a {= lf_schedule_token(a, MSEC(1), in->token); =} + reaction(in) -> a {= + lf_schedule_token(a, MSEC(1), in->token); + =} } diff --git a/test/C/src/verifier/ADASModel.lf b/test/C/src/verifier/ADASModel.lf index 41f8e27ace..b127c1d1a8 100644 --- a/test/C/src/verifier/ADASModel.lf +++ b/test/C/src/verifier/ADASModel.lf @@ -6,8 +6,12 @@ preamble {= =} reactor Camera { - output out: {= c_frame_t =} - state frame: {= c_frame_t =} + output out: {= + c_frame_t + =} + state frame: {= + c_frame_t + =} timer t(0, 17 msec) // 60 fps reaction(t) -> out {= @@ -18,8 +22,12 @@ reactor Camera { } reactor LiDAR { - output out: {= l_frame_t =} - state frame: {= l_frame_t =} + output out: {= + l_frame_t + =} + state frame: {= + l_frame_t + =} timer t(0, 34 msec) // 30 fps reaction(t) -> out {= @@ -33,7 +41,9 @@ reactor Pedal { output out: int physical action a - reaction(a) -> out {= lf_set(out, 1); =} + reaction(a) -> out {= + lf_set(out, 1); + =} } reactor Brakes { @@ -48,8 +58,12 @@ reactor Brakes { } reactor ADASProcessor { - input in1: {= l_frame_t =} - input in2: {= c_frame_t =} + input in1: {= + l_frame_t + =} + input in2: {= + c_frame_t + =} output out1: int output out2: int logical action a(50 msec) @@ -72,7 +86,9 @@ reactor Dashboard { input in: int state received: int - reaction(in) {= self->received = 1; =} + reaction(in) {= + self->received = 1; + =} } @property( diff --git a/test/C/src/verifier/AircraftDoor.lf b/test/C/src/verifier/AircraftDoor.lf index ab6390ffbe..168bcca437 100644 --- a/test/C/src/verifier/AircraftDoor.lf +++ b/test/C/src/verifier/AircraftDoor.lf @@ -3,7 +3,9 @@ target C reactor Controller { output out: int - reaction(startup) -> out {= lf_set(out, 1); =} + reaction(startup) -> out {= + lf_set(out, 1); + =} } reactor Vision { diff --git a/test/C/src/verifier/CoopSchedule.lf b/test/C/src/verifier/CoopSchedule.lf index c755ef7559..8634143158 100644 --- a/test/C/src/verifier/CoopSchedule.lf +++ b/test/C/src/verifier/CoopSchedule.lf @@ -4,7 +4,9 @@ reactor Trigger { output out: int timer t(0, 1 usec) - reaction(t) -> out {= lf_set(out, 1); =} + reaction(t) -> out {= + lf_set(out, 1); + =} } reactor Task { diff --git a/test/C/src/verifier/ProcessMsg.lf b/test/C/src/verifier/ProcessMsg.lf index ce9a2090d7..22b06d2276 100644 --- a/test/C/src/verifier/ProcessMsg.lf +++ b/test/C/src/verifier/ProcessMsg.lf @@ -12,9 +12,13 @@ reactor Task { logical action updateMessage - reaction(startup) {= self->messageSent = 0; =} + reaction(startup) {= + self->messageSent = 0; + =} - reaction(t) -> out {= lf_set(out, self->messageSent); =} + reaction(t) -> out {= + lf_set(out, self->messageSent); + =} reaction(in) -> updateMessage {= /* Check for invalid message. */ diff --git a/test/C/src/verifier/Ring.lf b/test/C/src/verifier/Ring.lf index eb289e1f24..ac0c03907f 100644 --- a/test/C/src/verifier/Ring.lf +++ b/test/C/src/verifier/Ring.lf @@ -12,7 +12,9 @@ reactor Source { lf_schedule(start, 0); =} - reaction(start) -> out {= lf_set(out, self->received); =} + reaction(start) -> out {= + lf_set(out, self->received); + =} /** Uncomment the following to debug feedback loops. */ // reaction(in) -> start {= @@ -26,7 +28,9 @@ reactor Node { input in: int output out: int - reaction(in) -> out {= lf_set(out, in->value + 1); =} + reaction(in) -> out {= + lf_set(out, in->value + 1); + =} } @property( diff --git a/test/C/src/verifier/SafeSend.lf b/test/C/src/verifier/SafeSend.lf index da21ef589f..29b77dd43c 100644 --- a/test/C/src/verifier/SafeSend.lf +++ b/test/C/src/verifier/SafeSend.lf @@ -41,7 +41,10 @@ reactor Server { } =} - reaction(err) {= self->error = 1; =} // Error handling logic. + // Error handling logic. + reaction(err) {= + self->error = 1; + =} } @property( diff --git a/test/C/src/verifier/Thermostat.lf b/test/C/src/verifier/Thermostat.lf index 0b583d307d..17c08f256f 100644 --- a/test/C/src/verifier/Thermostat.lf +++ b/test/C/src/verifier/Thermostat.lf @@ -8,7 +8,9 @@ reactor Environment { state _heatOn: int state _temperature: int - reaction(startup) {= self->_temperature = 19; =} + reaction(startup) {= + self->_temperature = 19; + =} reaction(t) -> temperature {= if (self->_heatOn == 0) { @@ -20,7 +22,9 @@ reactor Environment { lf_set(temperature, self->_temperature); =} - reaction(heatOn) {= self->_heatOn = heatOn->value; =} + reaction(heatOn) {= + self->_heatOn = heatOn->value; + =} } reactor _Thermostat { @@ -29,7 +33,9 @@ reactor _Thermostat { state _mode: int // 0 = COOLING, 1 = HEATING logical action changeMode - reaction(startup) {= self->_mode = 0; =} + reaction(startup) {= + self->_mode = 0; + =} reaction(temperature) -> heatOn, changeMode {= if (self->_mode == 0) { diff --git a/test/C/src/verifier/TrainDoor.lf b/test/C/src/verifier/TrainDoor.lf index d39f473bea..176f83cbf9 100644 --- a/test/C/src/verifier/TrainDoor.lf +++ b/test/C/src/verifier/TrainDoor.lf @@ -14,14 +14,18 @@ reactor Train { input in: int state received: int - reaction(in) {= self->received = in->value; =} + reaction(in) {= + self->received = in->value; + =} } reactor Door { input in: int state received: int - reaction(in) {= self->received = in->value; =} + reaction(in) {= + self->received = in->value; + =} } @property( diff --git a/test/C/src/verifier/UnsafeSend.lf b/test/C/src/verifier/UnsafeSend.lf index 73c3585885..2033888ae4 100644 --- a/test/C/src/verifier/UnsafeSend.lf +++ b/test/C/src/verifier/UnsafeSend.lf @@ -41,7 +41,10 @@ reactor Server { } =} - reaction(err) {= self->error = 1; =} // Error handling logic. + // Error handling logic. + reaction(err) {= + self->error = 1; + =} } @property( diff --git a/test/C/src/zephyr/unthreaded/HelloZephyr.lf b/test/C/src/zephyr/unthreaded/HelloZephyr.lf index aae4f62252..9d73982db6 100644 --- a/test/C/src/zephyr/unthreaded/HelloZephyr.lf +++ b/test/C/src/zephyr/unthreaded/HelloZephyr.lf @@ -3,5 +3,7 @@ target C { } main reactor { - reaction(startup) {= printf("Hello World!\n"); =} + reaction(startup) {= + printf("Hello World!\n"); + =} } diff --git a/test/C/src/zephyr/unthreaded/Timer.lf b/test/C/src/zephyr/unthreaded/Timer.lf index 17ca51b842..2d5a22d775 100644 --- a/test/C/src/zephyr/unthreaded/Timer.lf +++ b/test/C/src/zephyr/unthreaded/Timer.lf @@ -7,5 +7,7 @@ target C { main reactor { timer t(0, 1 sec) - reaction(t) {= printf("Hello\n"); =} + reaction(t) {= + printf("Hello\n"); + =} } diff --git a/test/Cpp/src/ActionDelay.lf b/test/Cpp/src/ActionDelay.lf index 98cbd11790..b953d8f522 100644 --- a/test/Cpp/src/ActionDelay.lf +++ b/test/Cpp/src/ActionDelay.lf @@ -12,13 +12,17 @@ reactor GeneratedDelay { act.schedule(); =} - reaction(act) -> y_out {= y_out.set(y_state); =} + reaction(act) -> y_out {= + y_out.set(y_state); + =} } reactor Source { output out: int - reaction(startup) -> out {= out.set(1); =} + reaction(startup) -> out {= + out.set(1); + =} } reactor Sink { diff --git a/test/Cpp/src/ActionWithNoReaction.lf b/test/Cpp/src/ActionWithNoReaction.lf index 78f1011544..3dd6e3f236 100644 --- a/test/Cpp/src/ActionWithNoReaction.lf +++ b/test/Cpp/src/ActionWithNoReaction.lf @@ -32,5 +32,7 @@ main reactor { timer t(0, 1 sec) f.y -> p.x - reaction(t) -> f.x {= f.x.set(42); =} + reaction(t) -> f.x {= + f.x.set(42); + =} } diff --git a/test/Cpp/src/After.lf b/test/Cpp/src/After.lf index 49ef7fbe6e..4308ed156d 100644 --- a/test/Cpp/src/After.lf +++ b/test/Cpp/src/After.lf @@ -8,7 +8,9 @@ reactor foo { input x: int output y: int - reaction(x) -> y {= y.set(2*(*x.get())); =} + reaction(x) -> y {= + y.set(2*(*x.get())); + =} } reactor print { diff --git a/test/Cpp/src/AfterZero.lf b/test/Cpp/src/AfterZero.lf index ce3c6dd5c7..f80ffe01bf 100644 --- a/test/Cpp/src/AfterZero.lf +++ b/test/Cpp/src/AfterZero.lf @@ -8,7 +8,9 @@ reactor foo { input x: int output y: int - reaction(x) -> y {= y.set(2*(*x.get())); =} + reaction(x) -> y {= + y.set(2*(*x.get())); + =} } reactor print { diff --git a/test/Cpp/src/Alignment.lf b/test/Cpp/src/Alignment.lf index 00f4ce0936..7eaa596379 100644 --- a/test/Cpp/src/Alignment.lf +++ b/test/Cpp/src/Alignment.lf @@ -78,7 +78,9 @@ reactor Sieve { reactor Destination { input ok: bool input in: int - state last_invoked: {= reactor::TimePoint =} + state last_invoked: {= + reactor::TimePoint + =} reaction(ok, in) {= if (ok.is_present() && in.is_present()) { diff --git a/test/Cpp/src/CompositionGain.lf b/test/Cpp/src/CompositionGain.lf index db02a4ad55..fc8182bab1 100644 --- a/test/Cpp/src/CompositionGain.lf +++ b/test/Cpp/src/CompositionGain.lf @@ -22,7 +22,9 @@ reactor Wrapper { main reactor CompositionGain { wrapper = new Wrapper() - reaction(startup) -> wrapper.x {= wrapper.x.set(42); =} + reaction(startup) -> wrapper.x {= + wrapper.x.set(42); + =} reaction(wrapper.y) {= reactor::log::Info() << "Received " << *wrapper.y.get(); diff --git a/test/Cpp/src/DanglingOutput.lf b/test/Cpp/src/DanglingOutput.lf index 537eff1cde..fe392b0a90 100644 --- a/test/Cpp/src/DanglingOutput.lf +++ b/test/Cpp/src/DanglingOutput.lf @@ -6,7 +6,9 @@ reactor Source { output out: int timer t - reaction(t) -> out {= out.set(1); =} + reaction(t) -> out {= + out.set(1); + =} } reactor Gain { diff --git a/test/Cpp/src/DelayInt.lf b/test/Cpp/src/DelayInt.lf index 757890db20..5111e26ee6 100644 --- a/test/Cpp/src/DelayInt.lf +++ b/test/Cpp/src/DelayInt.lf @@ -6,7 +6,9 @@ reactor Delay(delay: time = 100 msec) { output out: int logical action d: int - reaction(in) -> d {= d.schedule(in.get(), delay); =} + reaction(in) -> d {= + d.schedule(in.get(), delay); + =} reaction(d) -> out {= if (d.is_present()) { @@ -17,7 +19,9 @@ reactor Delay(delay: time = 100 msec) { reactor Test { input in: int - state start_time: {= reactor::TimePoint =} + state start_time: {= + reactor::TimePoint + =} timer start reaction(start) {= @@ -50,5 +54,7 @@ main reactor DelayInt { test = new Test() d.out -> test.in - reaction(t) -> d.in {= d.in.set(42); =} + reaction(t) -> d.in {= + d.in.set(42); + =} } diff --git a/test/Cpp/src/DelayedAction.lf b/test/Cpp/src/DelayedAction.lf index deb3745a97..3ee931d484 100644 --- a/test/Cpp/src/DelayedAction.lf +++ b/test/Cpp/src/DelayedAction.lf @@ -8,7 +8,9 @@ main reactor DelayedAction { logical action a: void state count: int = 0 - reaction(t) -> a {= a.schedule(100ms); =} + reaction(t) -> a {= + a.schedule(100ms); + =} reaction(a) {= auto elapsed = get_elapsed_logical_time(); diff --git a/test/Cpp/src/DelayedReaction.lf b/test/Cpp/src/DelayedReaction.lf index c975246d23..df7214a9eb 100644 --- a/test/Cpp/src/DelayedReaction.lf +++ b/test/Cpp/src/DelayedReaction.lf @@ -4,7 +4,9 @@ target Cpp reactor Source { output out: void - reaction(startup) -> out {= out.set(); =} + reaction(startup) -> out {= + out.set(); + =} } reactor Sink { diff --git a/test/Cpp/src/Determinism.lf b/test/Cpp/src/Determinism.lf index 43577397e9..1cb77d70a3 100644 --- a/test/Cpp/src/Determinism.lf +++ b/test/Cpp/src/Determinism.lf @@ -4,7 +4,9 @@ reactor Source { output y: int timer t - reaction(t) -> y {= y.set(1); =} + reaction(t) -> y {= + y.set(1); + =} } reactor Destination { @@ -31,7 +33,9 @@ reactor Pass { input x: int output y: int - reaction(x) -> y {= y.set(x.get()); =} + reaction(x) -> y {= + y.set(x.get()); + =} } main reactor Determinism { diff --git a/test/Cpp/src/DoublePort.lf b/test/Cpp/src/DoublePort.lf index fed174dbe5..73eecfa24e 100644 --- a/test/Cpp/src/DoublePort.lf +++ b/test/Cpp/src/DoublePort.lf @@ -22,7 +22,9 @@ reactor CountMicrostep { count++; =} - reaction(act) -> out {= out.set(act.get()); =} + reaction(act) -> out {= + out.set(act.get()); + =} } reactor Print { diff --git a/test/Cpp/src/Gain.lf b/test/Cpp/src/Gain.lf index 6bc9794951..ea38301191 100644 --- a/test/Cpp/src/Gain.lf +++ b/test/Cpp/src/Gain.lf @@ -5,7 +5,9 @@ reactor Scale(scale: int = 2) { input x: int output y: int - reaction(x) -> y {= y.set(*x.get() * scale); =} + reaction(x) -> y {= + y.set(*x.get() * scale); + =} } reactor Test { @@ -27,5 +29,7 @@ main reactor Gain { g.y -> t.x timer tim - reaction(tim) -> g.x {= g.x.set(1); =} + reaction(tim) -> g.x {= + g.x.set(1); + =} } diff --git a/test/Cpp/src/GetMicroStep.lf b/test/Cpp/src/GetMicroStep.lf index 5552d57ef9..bc541db26a 100644 --- a/test/Cpp/src/GetMicroStep.lf +++ b/test/Cpp/src/GetMicroStep.lf @@ -2,11 +2,15 @@ target Cpp main reactor GetMicroStep { - state s: {= reactor::mstep_t =} = 1 + state s: {= + reactor::mstep_t + =} = 1 logical action l - reaction(startup) -> l {= l.schedule(reactor::Duration::zero()); =} + reaction(startup) -> l {= + l.schedule(reactor::Duration::zero()); + =} reaction(l) -> l {= auto microstep = get_microstep(); diff --git a/test/Cpp/src/Hello.lf b/test/Cpp/src/Hello.lf index 022b9a7383..483d15b166 100644 --- a/test/Cpp/src/Hello.lf +++ b/test/Cpp/src/Hello.lf @@ -7,9 +7,13 @@ target Cpp { fast: true } -reactor HelloCpp(period: time = 2 sec, message: {= std::string =} = "Hello C++") { +reactor HelloCpp(period: time = 2 sec, message: {= + std::string +=} = "Hello C++") { state count: int = 0 - state previous_time: {= reactor::TimePoint =} + state previous_time: {= + reactor::TimePoint + =} timer t(1 sec, period) logical action a: void diff --git a/test/Cpp/src/HelloWorld.lf b/test/Cpp/src/HelloWorld.lf index 071cc5c968..b0a5ec1109 100644 --- a/test/Cpp/src/HelloWorld.lf +++ b/test/Cpp/src/HelloWorld.lf @@ -3,7 +3,9 @@ target Cpp reactor HelloWorld2 { timer t - reaction(t) {= std::cout << "Hello World." << std::endl; =} + reaction(t) {= + std::cout << "Hello World." << std::endl; + =} } main reactor { diff --git a/test/Cpp/src/Hierarchy.lf b/test/Cpp/src/Hierarchy.lf index ba92775c1c..090962b379 100644 --- a/test/Cpp/src/Hierarchy.lf +++ b/test/Cpp/src/Hierarchy.lf @@ -5,14 +5,18 @@ reactor Source { output out: int timer t - reaction(t) -> out {= out.set(1); =} + reaction(t) -> out {= + out.set(1); + =} } reactor Gain { input in: int output out: int - reaction(in) -> out {= out.set((*in.get()) * 2); =} + reaction(in) -> out {= + out.set((*in.get()) * 2); + =} } reactor Print { diff --git a/test/Cpp/src/Hierarchy2.lf b/test/Cpp/src/Hierarchy2.lf index e21a966c54..e94929486a 100644 --- a/test/Cpp/src/Hierarchy2.lf +++ b/test/Cpp/src/Hierarchy2.lf @@ -8,7 +8,9 @@ reactor Source { output out: int timer t(0, 1 sec) - reaction(t) -> out {= out.set(1); =} + reaction(t) -> out {= + out.set(1); + =} } reactor Count { diff --git a/test/Cpp/src/Import.lf b/test/Cpp/src/Import.lf index c77f8382c5..046af870d4 100644 --- a/test/Cpp/src/Import.lf +++ b/test/Cpp/src/Import.lf @@ -7,5 +7,7 @@ main reactor Import { timer t a = new Imported() - reaction(t) -> a.x {= a.x.set(42); =} + reaction(t) -> a.x {= + a.x.set(42); + =} } diff --git a/test/Cpp/src/ImportComposition.lf b/test/Cpp/src/ImportComposition.lf index 390f02bee3..8d8f13ae9b 100644 --- a/test/Cpp/src/ImportComposition.lf +++ b/test/Cpp/src/ImportComposition.lf @@ -17,7 +17,9 @@ main reactor ImportComposition { imp_comp = new ImportedComposition() state received: bool = false - reaction(startup) -> imp_comp.x {= imp_comp.x.set(42); =} + reaction(startup) -> imp_comp.x {= + imp_comp.x.set(42); + =} reaction(imp_comp.y) {= auto receive_time = get_elapsed_logical_time(); diff --git a/test/Cpp/src/ImportRenamed.lf b/test/Cpp/src/ImportRenamed.lf index 4ec5dc132e..0f6f447e7e 100644 --- a/test/Cpp/src/ImportRenamed.lf +++ b/test/Cpp/src/ImportRenamed.lf @@ -17,5 +17,7 @@ main reactor { b = new Y() c = new Z() - reaction(t) -> a.x {= a.x.set(42); =} + reaction(t) -> a.x {= + a.x.set(42); + =} } diff --git a/test/Cpp/src/ManualDelayedReaction.lf b/test/Cpp/src/ManualDelayedReaction.lf index e7bb1190ba..2f01d4c753 100644 --- a/test/Cpp/src/ManualDelayedReaction.lf +++ b/test/Cpp/src/ManualDelayedReaction.lf @@ -8,16 +8,23 @@ reactor GeneratedDelay { logical action act(100 msec): int - reaction(y_in) -> act {= act.schedule(y_in.get()); =} + reaction(y_in) -> act {= + act.schedule(y_in.get()); + =} - reaction(act) -> y_out {= y_out.set(act.get()); =} + reaction(act) -> y_out {= + y_out.set(act.get()); + =} } reactor Source { output out: int timer t - reaction(t) -> out {= out.set(1); =} // reaction(t) -> out after 100 msec {= + // reaction(t) -> out after 100 msec {= + reaction(t) -> out {= + out.set(1); + =} } reactor Sink { diff --git a/test/Cpp/src/Methods.lf b/test/Cpp/src/Methods.lf index c39b0dff4d..79720b555a 100644 --- a/test/Cpp/src/Methods.lf +++ b/test/Cpp/src/Methods.lf @@ -3,9 +3,13 @@ target Cpp main reactor { state foo: int = 2 - const method getFoo(): int {= return foo; =} + const method getFoo(): int {= + return foo; + =} - method add(x: int) {= foo += x; =} + method add(x: int) {= + foo += x; + =} reaction(startup) {= std::cout << "Foo is initialized to " << getFoo() << '\n'; diff --git a/test/Cpp/src/Microsteps.lf b/test/Cpp/src/Microsteps.lf index 51a61e508a..43a5f374aa 100644 --- a/test/Cpp/src/Microsteps.lf +++ b/test/Cpp/src/Microsteps.lf @@ -39,5 +39,7 @@ main reactor Microsteps { repeat.schedule(); =} - reaction(repeat) -> d.y {= d.y.set(1); =} + reaction(repeat) -> d.y {= + d.y.set(1); + =} } diff --git a/test/Cpp/src/Minimal.lf b/test/Cpp/src/Minimal.lf index d49bea2620..2706976fe8 100644 --- a/test/Cpp/src/Minimal.lf +++ b/test/Cpp/src/Minimal.lf @@ -2,5 +2,7 @@ target Cpp main reactor Minimal { - reaction(startup) {= std::cout << "Hello World!\n"; =} + reaction(startup) {= + std::cout << "Hello World!\n"; + =} } diff --git a/test/Cpp/src/MultipleContained.lf b/test/Cpp/src/MultipleContained.lf index df168ca394..ecc38ae22b 100644 --- a/test/Cpp/src/MultipleContained.lf +++ b/test/Cpp/src/MultipleContained.lf @@ -6,7 +6,9 @@ reactor Contained { input in1: int input in2: int - reaction(startup) -> trigger {= trigger.set(42); =} + reaction(startup) -> trigger {= + trigger.set(42); + =} reaction(in1) {= std::cout << "in1 received " << *in1.get() << '\n'; diff --git a/test/Cpp/src/NativeListsAndTimes.lf b/test/Cpp/src/NativeListsAndTimes.lf index b6e8550c50..b786edc7a5 100644 --- a/test/Cpp/src/NativeListsAndTimes.lf +++ b/test/Cpp/src/NativeListsAndTimes.lf @@ -6,7 +6,9 @@ reactor Foo( 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: {= std::vector =}{1 msec, 2 msec, 3 msec}, + q: {= + std::vector + =}{1 msec, 2 msec, 3 msec}, g: time[]{1 msec, 2 msec}, // List of time values g2: int[] = {}) { state s: time = y // Reference to explicitly typed time parameter @@ -18,7 +20,10 @@ reactor Foo( timer toe(z) // Implicit type time state baz = p // Implicit type int[] state period = z // Implicit type time - state times: std::vector>{q, g} // a list of lists + // a list of lists + state times: std::vector>{q, g} state empty_list: int[] = {} reaction(tick) {= diff --git a/test/Cpp/src/NestedTriggeredReactions.lf b/test/Cpp/src/NestedTriggeredReactions.lf index 7cd16fad2e..5b95df380d 100644 --- a/test/Cpp/src/NestedTriggeredReactions.lf +++ b/test/Cpp/src/NestedTriggeredReactions.lf @@ -9,7 +9,9 @@ reactor Container { in -> contained.in - reaction(in) {= triggered = true; =} + reaction(in) {= + triggered = true; + =} reaction(shutdown) {= if (!triggered) { @@ -24,7 +26,9 @@ reactor Contained { state triggered: bool = false - reaction(in) {= triggered = true; =} + reaction(in) {= + triggered = true; + =} reaction(shutdown) {= if (!triggered) { @@ -37,5 +41,7 @@ reactor Contained { main reactor { container = new Container() - reaction(startup) -> container.in {= container.in.set(); =} + reaction(startup) -> container.in {= + container.in.set(); + =} } diff --git a/test/Cpp/src/PeriodicDesugared.lf b/test/Cpp/src/PeriodicDesugared.lf index 1e5dad1b7d..868c14bad1 100644 --- a/test/Cpp/src/PeriodicDesugared.lf +++ b/test/Cpp/src/PeriodicDesugared.lf @@ -13,7 +13,9 @@ main reactor(offset: time = 50 msec, period: time = 500 msec) { init.schedule(); =} - reaction(init) -> recur {= recur.schedule(); =} + reaction(init) -> recur {= + recur.schedule(); + =} reaction(init, recur) -> recur {= std::cout << "Periodic trigger!\n"; diff --git a/test/Cpp/src/PhysicalConnection.lf b/test/Cpp/src/PhysicalConnection.lf index 7957b5460f..6e28e8e3da 100644 --- a/test/Cpp/src/PhysicalConnection.lf +++ b/test/Cpp/src/PhysicalConnection.lf @@ -4,7 +4,9 @@ target Cpp reactor Source { output out: int - reaction(startup) -> out {= out.set(42); =} + reaction(startup) -> out {= + out.set(42); + =} } reactor Destination { diff --git a/test/Cpp/src/Pipeline.lf b/test/Cpp/src/Pipeline.lf index 00e9e3f48e..b4adbfd826 100644 --- a/test/Cpp/src/Pipeline.lf +++ b/test/Cpp/src/Pipeline.lf @@ -40,5 +40,7 @@ main reactor Pipeline { c3.out -> c4.in after 200 msec c4.out -> p.in - reaction(t) -> c1.in {= c1.in.set(count++); =} + reaction(t) -> c1.in {= + c1.in.set(count++); + =} } diff --git a/test/Cpp/src/PreambleTest.lf b/test/Cpp/src/PreambleTest.lf index b82e393a8e..9039a1ef1b 100644 --- a/test/Cpp/src/PreambleTest.lf +++ b/test/Cpp/src/PreambleTest.lf @@ -20,7 +20,9 @@ main reactor { =} logical action a: MyStruct - reaction(startup) -> a {= a.schedule({add_42(42), "baz"}); =} + reaction(startup) -> a {= + a.schedule({add_42(42), "baz"}); + =} reaction(a) {= auto& value = *a.get(); diff --git a/test/Cpp/src/ReadOutputOfContainedReactor.lf b/test/Cpp/src/ReadOutputOfContainedReactor.lf index 885fbad734..ee542683ba 100644 --- a/test/Cpp/src/ReadOutputOfContainedReactor.lf +++ b/test/Cpp/src/ReadOutputOfContainedReactor.lf @@ -4,7 +4,9 @@ target Cpp reactor Contained { output out: int - reaction(startup) -> out {= out.set(42); =} + reaction(startup) -> out {= + out.set(42); + =} } main reactor ReadOutputOfContainedReactor { diff --git a/test/Cpp/src/Schedule.lf b/test/Cpp/src/Schedule.lf index c62b1bf701..320cf02baf 100644 --- a/test/Cpp/src/Schedule.lf +++ b/test/Cpp/src/Schedule.lf @@ -5,7 +5,9 @@ reactor ScheduleTest { input x: int logical action a: void - reaction(x) -> a {= a.schedule(200ms); =} + reaction(x) -> a {= + a.schedule(200ms); + =} reaction(a) {= auto elapsed_time = get_elapsed_logical_time(); @@ -23,5 +25,7 @@ main reactor Schedule { a = new ScheduleTest() timer t - reaction(t) -> a.x {= a.x.set(1); =} + reaction(t) -> a.x {= + a.x.set(1); + =} } diff --git a/test/Cpp/src/ScheduleLogicalAction.lf b/test/Cpp/src/ScheduleLogicalAction.lf index f8092b3f09..b47a199b79 100644 --- a/test/Cpp/src/ScheduleLogicalAction.lf +++ b/test/Cpp/src/ScheduleLogicalAction.lf @@ -22,7 +22,9 @@ reactor foo { a.schedule(500ms); =} - reaction(a) -> y {= y.set(-42); =} + reaction(a) -> y {= + y.set(-42); + =} } reactor print { @@ -48,5 +50,7 @@ main reactor { timer t(0, 1 sec) f.y -> p.x - reaction(t) -> f.x {= f.x.set(42); =} + reaction(t) -> f.x {= + f.x.set(42); + =} } diff --git a/test/Cpp/src/SelfLoop.lf b/test/Cpp/src/SelfLoop.lf index 0fd8fe4d6b..48c180a156 100644 --- a/test/Cpp/src/SelfLoop.lf +++ b/test/Cpp/src/SelfLoop.lf @@ -29,7 +29,9 @@ reactor Self { a.schedule(x.get(), 100ms); =} - reaction(startup) -> a {= a.schedule(42, 0ns); =} + reaction(startup) -> a {= + a.schedule(42, 0ns); + =} reaction(shutdown) {= if(expected <= 43) { diff --git a/test/Cpp/src/SendingInside2.lf b/test/Cpp/src/SendingInside2.lf index d2b62982b2..a083443b94 100644 --- a/test/Cpp/src/SendingInside2.lf +++ b/test/Cpp/src/SendingInside2.lf @@ -16,5 +16,7 @@ main reactor SendingInside2 { timer t p = new Printer() - reaction(t) -> p.x {= p.x.set(1); =} + reaction(t) -> p.x {= + p.x.set(1); + =} } diff --git a/test/Cpp/src/SlowingClock.lf b/test/Cpp/src/SlowingClock.lf index 913768dada..cb79c80b75 100644 --- a/test/Cpp/src/SlowingClock.lf +++ b/test/Cpp/src/SlowingClock.lf @@ -17,7 +17,9 @@ main reactor SlowingClock { state interval: time = 100 msec state expected_time: time = 100 msec - reaction(startup) -> a {= a.schedule(0ns); =} + reaction(startup) -> a {= + a.schedule(0ns); + =} reaction(a) -> a {= auto elapsed_logical_time = get_elapsed_logical_time(); diff --git a/test/Cpp/src/StartupOutFromInside.lf b/test/Cpp/src/StartupOutFromInside.lf index 0d3502c8c5..d36441934b 100644 --- a/test/Cpp/src/StartupOutFromInside.lf +++ b/test/Cpp/src/StartupOutFromInside.lf @@ -8,7 +8,9 @@ target Cpp reactor Bar { output out: int - reaction(startup) -> out {= out.set(42); =} + reaction(startup) -> out {= + out.set(42); + =} } main reactor StartupOutFromInside { diff --git a/test/Cpp/src/Stride.lf b/test/Cpp/src/Stride.lf index 85b6c1ef0d..9ebb61c91f 100644 --- a/test/Cpp/src/Stride.lf +++ b/test/Cpp/src/Stride.lf @@ -19,7 +19,9 @@ reactor Count(stride: int = 1) { reactor Display { input x: int - reaction(x) {= std::cout << "Received " << *x.get() << std::endl; =} + reaction(x) {= + std::cout << "Received " << *x.get() << std::endl; + =} } main reactor Stride { diff --git a/test/Cpp/src/StructPrint.lf b/test/Cpp/src/StructPrint.lf index 807dba8e85..6e0fba1199 100644 --- a/test/Cpp/src/StructPrint.lf +++ b/test/Cpp/src/StructPrint.lf @@ -18,7 +18,9 @@ reactor Source { =} } -reactor Print(expected_value: int = 42, expected_name: {= std::string =} = "Earth") { +reactor Print(expected_value: int = 42, expected_name: {= + std::string +=} = "Earth") { input in: Hello reaction(in) {= diff --git a/test/Cpp/src/TimeLimit.lf b/test/Cpp/src/TimeLimit.lf index 0249285e6b..1d11882740 100644 --- a/test/Cpp/src/TimeLimit.lf +++ b/test/Cpp/src/TimeLimit.lf @@ -44,5 +44,7 @@ main reactor TimeLimit(period: time = 1 sec) { d = new Destination() c.y -> d.x - reaction(stop) {= environment()->sync_shutdown(); =} + reaction(stop) {= + environment()->sync_shutdown(); + =} } diff --git a/test/Cpp/src/TimeState.lf b/test/Cpp/src/TimeState.lf index 01f28e7f85..c08f081ba7 100644 --- a/test/Cpp/src/TimeState.lf +++ b/test/Cpp/src/TimeState.lf @@ -3,7 +3,9 @@ target Cpp reactor Foo(bar: time = 42 msec) { state baz = bar - reaction(startup) {= std::cout << "Baz: " << baz << std::endl; =} + reaction(startup) {= + std::cout << "Baz: " << baz << std::endl; + =} } main reactor { diff --git a/test/Cpp/src/concurrent/AsyncCallback.lf b/test/Cpp/src/concurrent/AsyncCallback.lf index b9d655ca75..07eb85ca3a 100644 --- a/test/Cpp/src/concurrent/AsyncCallback.lf +++ b/test/Cpp/src/concurrent/AsyncCallback.lf @@ -10,7 +10,9 @@ main reactor AsyncCallback { =} timer t(0, 200 msec) - state thread: {= std::thread =} + state thread: {= + std::thread + =} state expected_time: time = 100 msec state toggle: bool = false diff --git a/test/Cpp/src/concurrent/DelayIntThreaded.lf b/test/Cpp/src/concurrent/DelayIntThreaded.lf index 33db1411de..e090af0b89 100644 --- a/test/Cpp/src/concurrent/DelayIntThreaded.lf +++ b/test/Cpp/src/concurrent/DelayIntThreaded.lf @@ -6,7 +6,9 @@ reactor Delay(delay: time = 100 msec) { output out: int logical action d: int - reaction(in) -> d {= d.schedule(in.get(), delay); =} + reaction(in) -> d {= + d.schedule(in.get(), delay); + =} reaction(d) -> out {= if (d.is_present()) { @@ -17,7 +19,9 @@ reactor Delay(delay: time = 100 msec) { reactor Test { input in: int - state start_time: {= reactor::TimePoint =} + state start_time: {= + reactor::TimePoint + =} timer start reaction(start) {= @@ -50,5 +54,7 @@ main reactor { test = new Test() d.out -> test.in - reaction(t) -> d.in {= d.in.set(42); =} + reaction(t) -> d.in {= + d.in.set(42); + =} } diff --git a/test/Cpp/src/concurrent/DeterminismThreaded.lf b/test/Cpp/src/concurrent/DeterminismThreaded.lf index c0de1858c1..4fc1e5ba5b 100644 --- a/test/Cpp/src/concurrent/DeterminismThreaded.lf +++ b/test/Cpp/src/concurrent/DeterminismThreaded.lf @@ -4,7 +4,9 @@ reactor Source { output y: int timer t - reaction(t) -> y {= y.set(1); =} + reaction(t) -> y {= + y.set(1); + =} } reactor Destination { @@ -31,7 +33,9 @@ reactor Pass { input x: int output y: int - reaction(x) -> y {= y.set(x.get()); =} + reaction(x) -> y {= + y.set(x.get()); + =} } main reactor { diff --git a/test/Cpp/src/concurrent/GainThreaded.lf b/test/Cpp/src/concurrent/GainThreaded.lf index 112f97632b..379a441ff1 100644 --- a/test/Cpp/src/concurrent/GainThreaded.lf +++ b/test/Cpp/src/concurrent/GainThreaded.lf @@ -5,7 +5,9 @@ reactor Scale(scale: int = 2) { input x: int output y: int - reaction(x) -> y {= y.set(*x.get() * scale); =} + reaction(x) -> y {= + y.set(*x.get() * scale); + =} } reactor Test { @@ -27,5 +29,7 @@ main reactor { g.y -> t.x timer tim - reaction(tim) -> g.x {= g.x.set(1); =} + reaction(tim) -> g.x {= + g.x.set(1); + =} } diff --git a/test/Cpp/src/concurrent/HelloThreaded.lf b/test/Cpp/src/concurrent/HelloThreaded.lf index 35e2261048..ca08070d87 100644 --- a/test/Cpp/src/concurrent/HelloThreaded.lf +++ b/test/Cpp/src/concurrent/HelloThreaded.lf @@ -7,9 +7,13 @@ target Cpp { fast: true } -reactor HelloCpp(period: time = 2 sec, message: {= std::string =} = "Hello C++") { +reactor HelloCpp(period: time = 2 sec, message: {= + std::string +=} = "Hello C++") { state count: int = 0 - state previous_time: {= reactor::TimePoint =} + state previous_time: {= + reactor::TimePoint + =} timer t(1 sec, period) logical action a: void @@ -35,7 +39,9 @@ reactor HelloCpp(period: time = 2 sec, message: {= std::string =} = "Hello C++") =} } -reactor Inside(period: time = 1 sec, message: {= std::string =} = "Composite default message.") { +reactor Inside(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/ImportThreaded.lf b/test/Cpp/src/concurrent/ImportThreaded.lf index 791291fc10..73dac5911c 100644 --- a/test/Cpp/src/concurrent/ImportThreaded.lf +++ b/test/Cpp/src/concurrent/ImportThreaded.lf @@ -7,5 +7,7 @@ main reactor { timer t a = new Imported() - reaction(t) -> a.x {= a.x.set(42); =} + reaction(t) -> a.x {= + a.x.set(42); + =} } diff --git a/test/Cpp/src/concurrent/MinimalThreaded.lf b/test/Cpp/src/concurrent/MinimalThreaded.lf index a0184d5261..421eb45d57 100644 --- a/test/Cpp/src/concurrent/MinimalThreaded.lf +++ b/test/Cpp/src/concurrent/MinimalThreaded.lf @@ -2,5 +2,7 @@ target Cpp main reactor { - reaction(startup) {= std::cout << "Hello World!\n"; =} + reaction(startup) {= + std::cout << "Hello World!\n"; + =} } diff --git a/test/Cpp/src/concurrent/TimeLimitThreaded.lf b/test/Cpp/src/concurrent/TimeLimitThreaded.lf index ecff6ecfc9..6d561cca38 100644 --- a/test/Cpp/src/concurrent/TimeLimitThreaded.lf +++ b/test/Cpp/src/concurrent/TimeLimitThreaded.lf @@ -45,5 +45,7 @@ main reactor(period: time = 1 sec) { d = new Destination() c.y -> d.x - reaction(stop) {= environment()->sync_shutdown(); =} + reaction(stop) {= + environment()->sync_shutdown(); + =} } diff --git a/test/Cpp/src/enclave/EnclaveBankEach.lf b/test/Cpp/src/enclave/EnclaveBankEach.lf index d1b94748ba..c220a082c5 100644 --- a/test/Cpp/src/enclave/EnclaveBankEach.lf +++ b/test/Cpp/src/enclave/EnclaveBankEach.lf @@ -6,8 +6,12 @@ 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 =}) { + period: {= + reactor::Duration + =} = {= 100ms * (bank_index+1) =}, + duration: {= + reactor::Duration + =} = {= 50ms + 100ms * bank_index =}) { logical action a: void reaction(startup, a) -> a {= diff --git a/test/Cpp/src/enclave/EnclaveBroadcast.lf b/test/Cpp/src/enclave/EnclaveBroadcast.lf index a54452b2ea..389179429e 100644 --- a/test/Cpp/src/enclave/EnclaveBroadcast.lf +++ b/test/Cpp/src/enclave/EnclaveBroadcast.lf @@ -5,7 +5,9 @@ target Cpp { reactor Source { output out: unsigned - reaction(startup) -> out {= out.set(42); =} + reaction(startup) -> out {= + out.set(42); + =} } reactor Sink(bank_index: size_t = 0) { diff --git a/test/Cpp/src/enclave/EnclaveCommunication.lf b/test/Cpp/src/enclave/EnclaveCommunication.lf index 9bf2af20cc..338be8a66c 100644 --- a/test/Cpp/src/enclave/EnclaveCommunication.lf +++ b/test/Cpp/src/enclave/EnclaveCommunication.lf @@ -8,7 +8,9 @@ reactor Src { output out: int state counter: int = 0 - reaction(t) -> out {= out.set(counter++); =} + reaction(t) -> out {= + out.set(counter++); + =} } reactor Sink { diff --git a/test/Cpp/src/enclave/EnclaveCommunication2.lf b/test/Cpp/src/enclave/EnclaveCommunication2.lf index bb60949645..dae4722da8 100644 --- a/test/Cpp/src/enclave/EnclaveCommunication2.lf +++ b/test/Cpp/src/enclave/EnclaveCommunication2.lf @@ -8,7 +8,9 @@ reactor Src { output out: int state counter: int = 0 - reaction(t) -> out {= out.set(counter++); =} + reaction(t) -> out {= + out.set(counter++); + =} } reactor Sink { diff --git a/test/Cpp/src/enclave/EnclaveCommunicationDelayed.lf b/test/Cpp/src/enclave/EnclaveCommunicationDelayed.lf index 9adad03ebb..d9cea724bf 100644 --- a/test/Cpp/src/enclave/EnclaveCommunicationDelayed.lf +++ b/test/Cpp/src/enclave/EnclaveCommunicationDelayed.lf @@ -8,7 +8,9 @@ reactor Src { output out: int state counter: int = 0 - reaction(t) -> out {= out.set(counter++); =} + reaction(t) -> out {= + out.set(counter++); + =} } reactor Sink { diff --git a/test/Cpp/src/enclave/EnclaveCommunicationDelayed2.lf b/test/Cpp/src/enclave/EnclaveCommunicationDelayed2.lf index 6272a7aa00..89467ae0a5 100644 --- a/test/Cpp/src/enclave/EnclaveCommunicationDelayed2.lf +++ b/test/Cpp/src/enclave/EnclaveCommunicationDelayed2.lf @@ -8,7 +8,9 @@ reactor Src { output out: int state counter: int = 0 - reaction(t) -> out {= out.set(counter++); =} + reaction(t) -> out {= + out.set(counter++); + =} } reactor Sink { diff --git a/test/Cpp/src/enclave/EnclaveCommunicationDelayedLocalEvents.lf b/test/Cpp/src/enclave/EnclaveCommunicationDelayedLocalEvents.lf index 31939cf447..ae2b28da08 100644 --- a/test/Cpp/src/enclave/EnclaveCommunicationDelayedLocalEvents.lf +++ b/test/Cpp/src/enclave/EnclaveCommunicationDelayedLocalEvents.lf @@ -8,7 +8,9 @@ reactor Src { output out: int state counter: int = 0 - reaction(t) -> out {= out.set(counter++); =} + reaction(t) -> out {= + out.set(counter++); + =} } reactor Sink { @@ -34,7 +36,9 @@ reactor Sink { } =} - reaction(t) {= reactor::log::Info() << "Tick"; =} + reaction(t) {= + reactor::log::Info() << "Tick"; + =} } main reactor { diff --git a/test/Cpp/src/enclave/EnclaveCommunicationLocalEvents.lf b/test/Cpp/src/enclave/EnclaveCommunicationLocalEvents.lf index 342a6e7eaf..ac830a86c0 100644 --- a/test/Cpp/src/enclave/EnclaveCommunicationLocalEvents.lf +++ b/test/Cpp/src/enclave/EnclaveCommunicationLocalEvents.lf @@ -8,7 +8,9 @@ reactor Src { output out: int state counter: int = 0 - reaction(t) -> out {= out.set(counter++); =} + reaction(t) -> out {= + out.set(counter++); + =} } reactor Sink { @@ -34,7 +36,9 @@ reactor Sink { } =} - reaction(t) {= reactor::log::Info() << "Tick"; =} + reaction(t) {= + reactor::log::Info() << "Tick"; + =} } main reactor { diff --git a/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBank.lf b/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBank.lf index e315632425..134a53b9ef 100644 --- a/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBank.lf +++ b/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBank.lf @@ -44,7 +44,9 @@ reactor Sink(bank_index: std::size_t = 0) { } =} - reaction(t) {= reactor::log::Info() << "Tick"; =} + reaction(t) {= + reactor::log::Info() << "Tick"; + =} } main reactor { diff --git a/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankDelayed.lf b/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankDelayed.lf index 4eda00fd38..60776a4db0 100644 --- a/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankDelayed.lf +++ b/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankDelayed.lf @@ -44,7 +44,9 @@ reactor Sink(bank_index: std::size_t = 0) { } =} - reaction(t) {= reactor::log::Info() << "Tick"; =} + reaction(t) {= + reactor::log::Info() << "Tick"; + =} } main reactor { diff --git a/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankEach.lf b/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankEach.lf index 1016beffce..4acce71e86 100644 --- a/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankEach.lf +++ b/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankEach.lf @@ -44,7 +44,9 @@ reactor Sink(bank_index: std::size_t = 0) { } =} - reaction(t) {= reactor::log::Info() << "Tick"; =} + reaction(t) {= + reactor::log::Info() << "Tick"; + =} } main reactor { diff --git a/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankEachDelayed.lf b/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankEachDelayed.lf index 97ad53108a..4eb175dc6c 100644 --- a/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankEachDelayed.lf +++ b/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankEachDelayed.lf @@ -44,7 +44,9 @@ reactor Sink(bank_index: std::size_t = 0) { } =} - reaction(t) {= reactor::log::Info() << "Tick"; =} + reaction(t) {= + reactor::log::Info() << "Tick"; + =} } main reactor { diff --git a/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankEachPhysical.lf b/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankEachPhysical.lf index e7b9ab3288..3a8e7d2dfd 100644 --- a/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankEachPhysical.lf +++ b/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankEachPhysical.lf @@ -44,7 +44,9 @@ reactor Sink(bank_index: std::size_t = 0) { } =} - reaction(t) {= reactor::log::Info() << "Tick"; =} + reaction(t) {= + reactor::log::Info() << "Tick"; + =} } main reactor { diff --git a/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankPhysical.lf b/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankPhysical.lf index ece73aab30..257ce163f4 100644 --- a/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankPhysical.lf +++ b/test/Cpp/src/enclave/EnclaveCommunicationMultiportToBankPhysical.lf @@ -44,7 +44,9 @@ reactor Sink(bank_index: std::size_t = 0) { } =} - reaction(t) {= reactor::log::Info() << "Tick"; =} + reaction(t) {= + reactor::log::Info() << "Tick"; + =} } main reactor { diff --git a/test/Cpp/src/enclave/EnclaveCommunicationPhysical.lf b/test/Cpp/src/enclave/EnclaveCommunicationPhysical.lf index 65a2b44825..9abb687b07 100644 --- a/test/Cpp/src/enclave/EnclaveCommunicationPhysical.lf +++ b/test/Cpp/src/enclave/EnclaveCommunicationPhysical.lf @@ -8,7 +8,9 @@ reactor Src { output out: int state counter: int = 0 - reaction(t) -> out {= out.set(counter++); =} + reaction(t) -> out {= + out.set(counter++); + =} } reactor Sink { diff --git a/test/Cpp/src/enclave/EnclaveCommunicationPhysicalLocalEvents.lf b/test/Cpp/src/enclave/EnclaveCommunicationPhysicalLocalEvents.lf index c413345f16..1bdb3e8323 100644 --- a/test/Cpp/src/enclave/EnclaveCommunicationPhysicalLocalEvents.lf +++ b/test/Cpp/src/enclave/EnclaveCommunicationPhysicalLocalEvents.lf @@ -8,7 +8,9 @@ reactor Src { output out: int state counter: int = 0 - reaction(t) -> out {= out.set(counter++); =} + reaction(t) -> out {= + out.set(counter++); + =} } reactor Sink { @@ -34,7 +36,9 @@ reactor Sink { } =} - reaction(t) {= reactor::log::Info() << "Tick"; =} + reaction(t) {= + reactor::log::Info() << "Tick"; + =} } main reactor { diff --git a/test/Cpp/src/enclave/EnclaveCycle.lf b/test/Cpp/src/enclave/EnclaveCycle.lf index 15d68b99cc..960866e59f 100644 --- a/test/Cpp/src/enclave/EnclaveCycle.lf +++ b/test/Cpp/src/enclave/EnclaveCycle.lf @@ -10,7 +10,9 @@ reactor Ping { state counter: int = 0 state received: bool = false - reaction(t) -> out {= out.set(counter++); =} + reaction(t) -> out {= + out.set(counter++); + =} reaction(in) {= received = true; diff --git a/test/Cpp/src/enclave/EnclaveCycleTwoTimers.lf b/test/Cpp/src/enclave/EnclaveCycleTwoTimers.lf index 8c4ce316a3..5ba4ea4916 100644 --- a/test/Cpp/src/enclave/EnclaveCycleTwoTimers.lf +++ b/test/Cpp/src/enclave/EnclaveCycleTwoTimers.lf @@ -10,7 +10,9 @@ reactor Ping { state counter: int = 0 state received: bool = false - reaction(t) -> out {= out.set(counter++); =} + reaction(t) -> out {= + out.set(counter++); + =} reaction(in) {= received = true; @@ -38,7 +40,9 @@ reactor Pong { state counter: int = 0 state received: bool = false - reaction(t) -> out {= out.set(counter++); =} + reaction(t) -> out {= + out.set(counter++); + =} reaction(in) {= received = true; diff --git a/test/Cpp/src/enclave/EnclaveHelloWorld.lf b/test/Cpp/src/enclave/EnclaveHelloWorld.lf index 21b7374a09..e33c56ed93 100644 --- a/test/Cpp/src/enclave/EnclaveHelloWorld.lf +++ b/test/Cpp/src/enclave/EnclaveHelloWorld.lf @@ -1,7 +1,9 @@ target Cpp reactor Hello(msg: std::string = "World") { - reaction(startup) {= reactor::log::Info() << "Hello " << msg << '!'; =} + reaction(startup) {= + reactor::log::Info() << "Hello " << msg << '!'; + =} } main reactor(msg1: std::string = "World", msg2: std::string = "Enclave") { diff --git a/test/Cpp/src/enclave/EnclaveShutdown.lf b/test/Cpp/src/enclave/EnclaveShutdown.lf index ab56d58b08..a949cc88be 100644 --- a/test/Cpp/src/enclave/EnclaveShutdown.lf +++ b/test/Cpp/src/enclave/EnclaveShutdown.lf @@ -4,9 +4,13 @@ reactor Node(message: std::string = "Hello", period: time = 1 sec, stop: time = timer t(0, period) timer s(stop) - reaction(t) {= reactor::log::Info() << message; =} + reaction(t) {= + reactor::log::Info() << message; + =} - reaction(s) {= request_stop(); =} + reaction(s) {= + request_stop(); + =} reaction(shutdown) {= reactor::log::Info() << "Goodbye!"; diff --git a/test/Cpp/src/enclave/EnclaveSparseUpstreamEvents.lf b/test/Cpp/src/enclave/EnclaveSparseUpstreamEvents.lf index 047910fd91..33d0c8e0e6 100644 --- a/test/Cpp/src/enclave/EnclaveSparseUpstreamEvents.lf +++ b/test/Cpp/src/enclave/EnclaveSparseUpstreamEvents.lf @@ -10,7 +10,9 @@ reactor Src { output out: int state counter: int = 0 - reaction(t) -> out {= out.set(counter++); =} + reaction(t) -> out {= + out.set(counter++); + =} } reactor Sink { @@ -36,7 +38,9 @@ reactor Sink { } =} - reaction(t) {= reactor::log::Info() << "Tick"; =} deadline(2 s) {= + reaction(t) {= + reactor::log::Info() << "Tick"; + =} deadline(2 s) {= reactor::log::Error() << "Deadline violated."; exit(2); =} diff --git a/test/Cpp/src/enclave/EnclaveSparseUpstreamEventsDelayed.lf b/test/Cpp/src/enclave/EnclaveSparseUpstreamEventsDelayed.lf index 486655ddd6..1a02db98a5 100644 --- a/test/Cpp/src/enclave/EnclaveSparseUpstreamEventsDelayed.lf +++ b/test/Cpp/src/enclave/EnclaveSparseUpstreamEventsDelayed.lf @@ -10,7 +10,9 @@ reactor Src { output out: int state counter: int = 0 - reaction(t) -> out {= out.set(counter++); =} + reaction(t) -> out {= + out.set(counter++); + =} } reactor Sink { @@ -36,7 +38,9 @@ reactor Sink { } =} - reaction(t) {= reactor::log::Info() << "Tick"; =} deadline(2 s) {= + reaction(t) {= + reactor::log::Info() << "Tick"; + =} deadline(2 s) {= reactor::log::Error() << "Deadline violated."; exit(2); =} diff --git a/test/Cpp/src/enclave/EnclaveSparseUpstreamEventsPhysical.lf b/test/Cpp/src/enclave/EnclaveSparseUpstreamEventsPhysical.lf index d2f0a78e3f..b73152954b 100644 --- a/test/Cpp/src/enclave/EnclaveSparseUpstreamEventsPhysical.lf +++ b/test/Cpp/src/enclave/EnclaveSparseUpstreamEventsPhysical.lf @@ -10,7 +10,9 @@ reactor Src { output out: int state counter: int = 0 - reaction(t) -> out {= out.set(counter++); =} + reaction(t) -> out {= + out.set(counter++); + =} } reactor Sink { @@ -36,7 +38,9 @@ reactor Sink { } =} - reaction(t) {= reactor::log::Info() << "Tick"; =} deadline(2 s) {= + reaction(t) {= + reactor::log::Info() << "Tick"; + =} deadline(2 s) {= reactor::log::Error() << "Deadline violated."; exit(2); =} diff --git a/test/Cpp/src/enclave/EnclaveTimeout.lf b/test/Cpp/src/enclave/EnclaveTimeout.lf index b05e6b7b1e..2ba4a59464 100644 --- a/test/Cpp/src/enclave/EnclaveTimeout.lf +++ b/test/Cpp/src/enclave/EnclaveTimeout.lf @@ -5,7 +5,9 @@ target Cpp { reactor Node(message: std::string = "Hello", period: time = 1 sec) { timer t(0, period) - reaction(t) {= reactor::log::Info() << message; =} + reaction(t) {= + reactor::log::Info() << message; + =} reaction(shutdown) {= reactor::log::Info() << "Goodbye!"; diff --git a/test/Cpp/src/enclave/EnclaveUpstreamPhysicalAction.lf b/test/Cpp/src/enclave/EnclaveUpstreamPhysicalAction.lf index 16d0d55586..0097a1e4ae 100644 --- a/test/Cpp/src/enclave/EnclaveUpstreamPhysicalAction.lf +++ b/test/Cpp/src/enclave/EnclaveUpstreamPhysicalAction.lf @@ -25,7 +25,9 @@ reactor Src { }); =} - reaction(a) -> out {= out.set(a.get()); =} + reaction(a) -> out {= + out.set(a.get()); + =} reaction(shutdown) {= // make sure to join the thread before shutting down diff --git a/test/Cpp/src/enclave/EnclaveUpstreamPhysicalActionDelayed.lf b/test/Cpp/src/enclave/EnclaveUpstreamPhysicalActionDelayed.lf index 4e7b138c90..936e692a71 100644 --- a/test/Cpp/src/enclave/EnclaveUpstreamPhysicalActionDelayed.lf +++ b/test/Cpp/src/enclave/EnclaveUpstreamPhysicalActionDelayed.lf @@ -25,7 +25,9 @@ reactor Src { }); =} - reaction(a) -> out {= out.set(a.get()); =} + reaction(a) -> out {= + out.set(a.get()); + =} reaction(shutdown) {= // make sure to join the thread before shutting down diff --git a/test/Cpp/src/enclave/FastAndSlow.lf b/test/Cpp/src/enclave/FastAndSlow.lf index 3d421223e6..554f07073b 100644 --- a/test/Cpp/src/enclave/FastAndSlow.lf +++ b/test/Cpp/src/enclave/FastAndSlow.lf @@ -20,7 +20,9 @@ reactor Slow { reactor Fast { timer t(0 msec, 100 msec) - reaction(t) {= reactor::log::Info() << "Fast reaction executes"; =} deadline(200 msec) {= + reaction(t) {= + reactor::log::Info() << "Fast reaction executes"; + =} deadline(200 msec) {= reactor::log::Error() << "Fast deadline was violated!"; exit(2); =} diff --git a/test/Cpp/src/lib/Imported.lf b/test/Cpp/src/lib/Imported.lf index abf2464b96..f1ce5ae3aa 100644 --- a/test/Cpp/src/lib/Imported.lf +++ b/test/Cpp/src/lib/Imported.lf @@ -8,5 +8,7 @@ reactor Imported { input x: int a = new ImportedAgain() - reaction(x) -> a.x {= a.x.set(x.get()); =} + reaction(x) -> a.x {= + a.x.set(x.get()); + =} } diff --git a/test/Cpp/src/lib/ImportedComposition.lf b/test/Cpp/src/lib/ImportedComposition.lf index cc3ccba591..eec583e8a6 100644 --- a/test/Cpp/src/lib/ImportedComposition.lf +++ b/test/Cpp/src/lib/ImportedComposition.lf @@ -12,7 +12,9 @@ reactor Gain { input x: int output y: int - reaction(x) -> y {= y.set(*x.get() * 2); =} + reaction(x) -> y {= + y.set(*x.get() * 2); + =} } reactor ImportedComposition { diff --git a/test/Cpp/src/multiport/BankSelfBroadcast.lf b/test/Cpp/src/multiport/BankSelfBroadcast.lf index 2961e3a55c..a7f81fcff9 100644 --- a/test/Cpp/src/multiport/BankSelfBroadcast.lf +++ b/test/Cpp/src/multiport/BankSelfBroadcast.lf @@ -13,7 +13,9 @@ reactor A(bank_index: size_t = 0) { output out: size_t state received: bool = false - reaction(startup) -> out {= out.set(bank_index); =} + reaction(startup) -> out {= + out.set(bank_index); + =} reaction(in) {= for (size_t i = 0; i < in.size(); i++) { diff --git a/test/Cpp/src/multiport/BankToMultiport.lf b/test/Cpp/src/multiport/BankToMultiport.lf index 93b476c235..bf283164de 100644 --- a/test/Cpp/src/multiport/BankToMultiport.lf +++ b/test/Cpp/src/multiport/BankToMultiport.lf @@ -4,7 +4,9 @@ target Cpp reactor Source(bank_index: size_t = 0) { output out: unsigned - reaction(startup) -> out {= out.set(bank_index); =} + reaction(startup) -> out {= + out.set(bank_index); + =} } reactor Sink { diff --git a/test/Cpp/src/multiport/Broadcast.lf b/test/Cpp/src/multiport/Broadcast.lf index 0ae960053b..2289cbb596 100644 --- a/test/Cpp/src/multiport/Broadcast.lf +++ b/test/Cpp/src/multiport/Broadcast.lf @@ -3,7 +3,9 @@ target Cpp reactor Source { output out: unsigned - reaction(startup) -> out {= out.set(42); =} + reaction(startup) -> out {= + out.set(42); + =} } reactor Sink(bank_index: size_t = 0) { diff --git a/test/Cpp/src/multiport/BroadcastAfter.lf b/test/Cpp/src/multiport/BroadcastAfter.lf index 50b8d5bb59..bdf5274229 100644 --- a/test/Cpp/src/multiport/BroadcastAfter.lf +++ b/test/Cpp/src/multiport/BroadcastAfter.lf @@ -5,7 +5,9 @@ target Cpp { reactor Source { output out: unsigned - reaction(startup) -> out {= out.set(42); =} + reaction(startup) -> out {= + out.set(42); + =} } reactor Sink(bank_index: size_t = 0) { diff --git a/test/Cpp/src/multiport/BroadcastMultipleAfter.lf b/test/Cpp/src/multiport/BroadcastMultipleAfter.lf index da2af4e9cf..6d37fedc72 100644 --- a/test/Cpp/src/multiport/BroadcastMultipleAfter.lf +++ b/test/Cpp/src/multiport/BroadcastMultipleAfter.lf @@ -5,7 +5,9 @@ target Cpp { reactor Source(value: unsigned = 42) { output out: unsigned - reaction(startup) -> out {= out.set(value); =} + reaction(startup) -> out {= + out.set(value); + =} } reactor Sink(bank_index: size_t = 0) { diff --git a/test/Cpp/src/multiport/IndexIntoMultiportInput.lf b/test/Cpp/src/multiport/IndexIntoMultiportInput.lf index 39baee6b6b..a167edf8df 100644 --- a/test/Cpp/src/multiport/IndexIntoMultiportInput.lf +++ b/test/Cpp/src/multiport/IndexIntoMultiportInput.lf @@ -45,9 +45,15 @@ main reactor IndexIntoMultiportInput { splitter.out -> receiver.in - reaction(startup) -> splitter.in0 {= splitter.in0.set(0); =} + reaction(startup) -> splitter.in0 {= + splitter.in0.set(0); + =} - reaction(startup) -> splitter.in1 {= splitter.in1.set(1); =} + reaction(startup) -> splitter.in1 {= + splitter.in1.set(1); + =} - reaction(startup) -> splitter.in2 {= splitter.in2.set(2); =} + reaction(startup) -> splitter.in2 {= + splitter.in2.set(2); + =} } diff --git a/test/Cpp/src/multiport/MultiportFromBank.lf b/test/Cpp/src/multiport/MultiportFromBank.lf index e3f6a23b30..6d952ccbbf 100644 --- a/test/Cpp/src/multiport/MultiportFromBank.lf +++ b/test/Cpp/src/multiport/MultiportFromBank.lf @@ -8,7 +8,9 @@ target Cpp { reactor Source(bank_index: size_t = 0) { output out: unsigned - reaction(startup) -> out {= out.set(bank_index); =} + reaction(startup) -> out {= + out.set(bank_index); + =} } reactor Destination(port_width: size_t = 2) { diff --git a/test/Cpp/src/multiport/MultiportFromBankHierarchy.lf b/test/Cpp/src/multiport/MultiportFromBankHierarchy.lf index c6ffb52819..bd4a5ab263 100644 --- a/test/Cpp/src/multiport/MultiportFromBankHierarchy.lf +++ b/test/Cpp/src/multiport/MultiportFromBankHierarchy.lf @@ -8,7 +8,9 @@ target Cpp { reactor Source(bank_index: size_t = 0) { output out: unsigned - reaction(startup) -> out {= out.set(bank_index); =} + reaction(startup) -> out {= + out.set(bank_index); + =} } reactor Container { diff --git a/test/Cpp/src/multiport/MultiportFromBankHierarchyAfter.lf b/test/Cpp/src/multiport/MultiportFromBankHierarchyAfter.lf index cb3eeaa0c4..e3d76d4e51 100644 --- a/test/Cpp/src/multiport/MultiportFromBankHierarchyAfter.lf +++ b/test/Cpp/src/multiport/MultiportFromBankHierarchyAfter.lf @@ -8,7 +8,9 @@ target Cpp { reactor Source(bank_index: size_t = 0) { output out: int - reaction(startup) -> out {= out.set(bank_index); =} + reaction(startup) -> out {= + out.set(bank_index); + =} } reactor Container { diff --git a/test/Cpp/src/multiport/MultiportIn.lf b/test/Cpp/src/multiport/MultiportIn.lf index 6f4f63f74b..8b01ff6712 100644 --- a/test/Cpp/src/multiport/MultiportIn.lf +++ b/test/Cpp/src/multiport/MultiportIn.lf @@ -10,14 +10,18 @@ reactor Source { output out: int state s: int = 0 - reaction(t) -> out {= out.set(s++); =} + reaction(t) -> out {= + out.set(s++); + =} } reactor Computation { input in: int output out: int - reaction(in) -> out {= out.set(*in.get()); =} + reaction(in) -> out {= + out.set(*in.get()); + =} } reactor Destination { diff --git a/test/Cpp/src/multiport/PipelineAfter.lf b/test/Cpp/src/multiport/PipelineAfter.lf index 8fb6418e65..e164210de2 100644 --- a/test/Cpp/src/multiport/PipelineAfter.lf +++ b/test/Cpp/src/multiport/PipelineAfter.lf @@ -3,14 +3,18 @@ target Cpp reactor Source { output out: unsigned - reaction(startup) -> out {= out.set(40); =} + reaction(startup) -> out {= + out.set(40); + =} } reactor Compute { input in: unsigned output out: unsigned - reaction(in) -> out {= out.set(*in.get() + 2); =} + reaction(in) -> out {= + out.set(*in.get() + 2); + =} } reactor Sink { diff --git a/test/Cpp/src/multiport/ReadOutputOfContainedBank.lf b/test/Cpp/src/multiport/ReadOutputOfContainedBank.lf index c0fb6d59ee..02293cc44e 100644 --- a/test/Cpp/src/multiport/ReadOutputOfContainedBank.lf +++ b/test/Cpp/src/multiport/ReadOutputOfContainedBank.lf @@ -4,7 +4,9 @@ target Cpp reactor Contained(bank_index: size_t = 0) { output out: unsigned - reaction(startup) -> out {= out.set(42 * bank_index); =} + reaction(startup) -> out {= + out.set(42 * bank_index); + =} } main reactor { diff --git a/test/Cpp/src/multiport/WidthGivenByCode.lf b/test/Cpp/src/multiport/WidthGivenByCode.lf index 5e9ed80eb3..3580a9696c 100644 --- a/test/Cpp/src/multiport/WidthGivenByCode.lf +++ b/test/Cpp/src/multiport/WidthGivenByCode.lf @@ -1,8 +1,12 @@ target Cpp reactor Foo(a: size_t = 8, b: size_t = 2) { - input[{= a*b =}] in: size_t - output[{= a/b =}] out: size_t + input[{= + a*b + =}] in: size_t + output[{= + a/b + =}] out: size_t reaction(startup) in -> out {= if (in.size() != a*b) { @@ -20,7 +24,9 @@ main reactor { foo1 = new Foo() foo2 = new Foo(a=10, b=3) foo3 = new Foo(a=9, b=9) - foo_bank = new[{= 42 =}] Foo() + foo_bank = new[{= + 42 + =}] Foo() reaction(startup) foo_bank.out {= if (foo_bank.size() != 42) { diff --git a/test/Cpp/src/properties/Fast.lf b/test/Cpp/src/properties/Fast.lf index a44d1fb7e5..0f7e5e8e15 100644 --- a/test/Cpp/src/properties/Fast.lf +++ b/test/Cpp/src/properties/Fast.lf @@ -7,7 +7,9 @@ main reactor { state triggered: bool = false - reaction(startup) -> a {= a.schedule(2s); =} + reaction(startup) -> a {= + a.schedule(2s); + =} reaction(a) {= triggered = true; diff --git a/test/Cpp/src/target/AfterVoid.lf b/test/Cpp/src/target/AfterVoid.lf index 5b208b4008..cacc98e892 100644 --- a/test/Cpp/src/target/AfterVoid.lf +++ b/test/Cpp/src/target/AfterVoid.lf @@ -8,7 +8,9 @@ reactor foo { timer t(0, 1 sec) output y: void - reaction(t) -> y {= y.set(); =} + reaction(t) -> y {= + y.set(); + =} } reactor print { diff --git a/test/Cpp/src/target/CliParserGenericArguments.lf b/test/Cpp/src/target/CliParserGenericArguments.lf index 8028776e07..42c8bd6c2a 100644 --- a/test/Cpp/src/target/CliParserGenericArguments.lf +++ b/test/Cpp/src/target/CliParserGenericArguments.lf @@ -47,15 +47,27 @@ main reactor CliParserGenericArguments( 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, + 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, + 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"; =} + 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 f64116eb4a..5caa22f70a 100644 --- a/test/Cpp/src/target/CombinedTypeNames.lf +++ b/test/Cpp/src/target/CombinedTypeNames.lf @@ -2,9 +2,17 @@ // int`) can be used correctly in LF code. target Cpp -reactor Foo(bar: {= unsigned int =} = 0, baz: {= const unsigned int* =} = {= nullptr =}) { - state s_bar: {= unsigned int =} = bar - state s_baz: {= const unsigned int* =} = baz +reactor Foo(bar: {= + unsigned int +=} = 0, baz: {= + const unsigned int* +=} = {= nullptr =}) { + 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) { @@ -14,6 +22,8 @@ reactor Foo(bar: {= unsigned int =} = 0, baz: {= const unsigned int* =} = {= nul =} } -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/GenericAfter.lf b/test/Cpp/src/target/GenericAfter.lf index 3de5c1dba8..9957ea6e16 100644 --- a/test/Cpp/src/target/GenericAfter.lf +++ b/test/Cpp/src/target/GenericAfter.lf @@ -7,9 +7,13 @@ reactor Delay(delay: time(0)) { input in: T logical action a(delay): T - reaction(a) -> out {= out.set(a.get()); =} + reaction(a) -> out {= + out.set(a.get()); + =} - reaction(in) -> a {= a.schedule(in.get()); =} + reaction(in) -> a {= + a.schedule(in.get()); + =} } main reactor { @@ -17,5 +21,7 @@ main reactor { test = new Test() d.out -> test.in after 50 msec - reaction(startup) -> d.in {= d.in.set(42); =} + reaction(startup) -> d.in {= + d.in.set(42); + =} } diff --git a/test/Cpp/src/target/GenericDelay.lf b/test/Cpp/src/target/GenericDelay.lf index 5134f8cde6..0c7a63b8ab 100644 --- a/test/Cpp/src/target/GenericDelay.lf +++ b/test/Cpp/src/target/GenericDelay.lf @@ -7,9 +7,13 @@ reactor Delay(delay: time = 0) { input in: T logical action a(delay): T - reaction(a) -> out {= out.set(a.get()); =} + reaction(a) -> out {= + out.set(a.get()); + =} - reaction(in) -> a {= a.schedule(in.get()); =} + reaction(in) -> a {= + a.schedule(in.get()); + =} } main reactor { @@ -17,5 +21,7 @@ main reactor { test = new Test() d.out -> test.in - reaction(startup) -> d.in {= d.in.set(42); =} + reaction(startup) -> d.in {= + d.in.set(42); + =} } diff --git a/test/Cpp/src/target/MultipleContainedGeneric.lf b/test/Cpp/src/target/MultipleContainedGeneric.lf index da4e28081c..8a7ad42cd3 100644 --- a/test/Cpp/src/target/MultipleContainedGeneric.lf +++ b/test/Cpp/src/target/MultipleContainedGeneric.lf @@ -6,7 +6,9 @@ reactor Contained { input in1: T input in2: T - reaction(startup) -> trigger {= trigger.set(42); =} + reaction(startup) -> trigger {= + trigger.set(42); + =} reaction(in1) {= std::cout << "in1 received " << *in1.get() << '\n'; diff --git a/test/Cpp/src/target/PointerParameters.lf b/test/Cpp/src/target/PointerParameters.lf index 007fb1a816..fcd7454d52 100644 --- a/test/Cpp/src/target/PointerParameters.lf +++ b/test/Cpp/src/target/PointerParameters.lf @@ -12,7 +12,9 @@ reactor Foo(ptr: int* = {= nullptr =}) { } main reactor { - private preamble {= int a{42}; =} + private preamble {= + int a{42}; + =} foo = new Foo(ptr = {= &a =}) } diff --git a/test/Python/src/ActionDelay.lf b/test/Python/src/ActionDelay.lf index 0c063f6f1d..93d8ceea81 100644 --- a/test/Python/src/ActionDelay.lf +++ b/test/Python/src/ActionDelay.lf @@ -12,13 +12,17 @@ reactor GeneratedDelay { act.schedule(MSEC(0)) =} - reaction(act) -> y_out {= y_out.set(self.y_state) =} + reaction(act) -> y_out {= + y_out.set(self.y_state) + =} } reactor Source { output out - reaction(startup) -> out {= out.set(1) =} + reaction(startup) -> out {= + out.set(1) + =} } reactor Sink { diff --git a/test/Python/src/ActionWithNoReaction.lf b/test/Python/src/ActionWithNoReaction.lf index 5e9456356d..d4ebf49fa7 100644 --- a/test/Python/src/ActionWithNoReaction.lf +++ b/test/Python/src/ActionWithNoReaction.lf @@ -33,5 +33,7 @@ main reactor { timer t(0, 1 sec) f.y -> p.x after 10 msec - reaction(t) -> f.x {= f.x.set(42) =} + reaction(t) -> f.x {= + f.x.set(42) + =} } diff --git a/test/Python/src/After.lf b/test/Python/src/After.lf index 765f9ef990..3fba861dbf 100644 --- a/test/Python/src/After.lf +++ b/test/Python/src/After.lf @@ -8,7 +8,9 @@ reactor foo { input x output y - reaction(x) -> y {= y.set(2*x.value) =} + reaction(x) -> y {= + y.set(2*x.value) + =} } reactor print { @@ -45,5 +47,7 @@ main reactor { timer t(0, 1 sec) f.y -> p.x after 10 msec - reaction(t) -> f.x {= f.x.set(42) =} + reaction(t) -> f.x {= + f.x.set(42) + =} } diff --git a/test/Python/src/AfterCycles.lf b/test/Python/src/AfterCycles.lf index 4aa2ed0d39..8e1c72032d 100644 --- a/test/Python/src/AfterCycles.lf +++ b/test/Python/src/AfterCycles.lf @@ -5,14 +5,18 @@ target Python reactor Source { output out - reaction(startup) -> out {= out.set(1) =} + reaction(startup) -> out {= + out.set(1) + =} } reactor Work { input _in output out - reaction(_in) -> out {= out.set(_in.value) =} + reaction(_in) -> out {= + out.set(_in.value) + =} } main reactor AfterCycles { diff --git a/test/Python/src/CompareTags.lf b/test/Python/src/CompareTags.lf index 38366d2742..2283b68722 100644 --- a/test/Python/src/CompareTags.lf +++ b/test/Python/src/CompareTags.lf @@ -5,7 +5,9 @@ target Python { } main reactor CompareTags { - preamble {= import sys =} + preamble {= + import sys + =} timer t(0, 1 msec) logical action l diff --git a/test/Python/src/CompositionGain.lf b/test/Python/src/CompositionGain.lf index 067d662bdf..540cffc258 100644 --- a/test/Python/src/CompositionGain.lf +++ b/test/Python/src/CompositionGain.lf @@ -22,7 +22,9 @@ reactor Wrapper { main reactor { wrapper = new Wrapper() - reaction(startup) -> wrapper.x {= wrapper_x.set(42) =} + reaction(startup) -> wrapper.x {= + wrapper_x.set(42) + =} reaction(wrapper.y) {= print("Received " + str(wrapper_y.value)) diff --git a/test/Python/src/DanglingOutput.lf b/test/Python/src/DanglingOutput.lf index f6fe938765..97011c7f90 100644 --- a/test/Python/src/DanglingOutput.lf +++ b/test/Python/src/DanglingOutput.lf @@ -6,7 +6,9 @@ reactor Source { output out timer t - reaction(t) -> out {= out.set(1); =} + reaction(t) -> out {= + out.set(1); + =} } reactor Gain { diff --git a/test/Python/src/Deadline.lf b/test/Python/src/Deadline.lf index 7dfde79a35..7e1772e622 100644 --- a/test/Python/src/Deadline.lf +++ b/test/Python/src/Deadline.lf @@ -4,7 +4,9 @@ target Python { timeout: 6 sec } -preamble {= import time =} +preamble {= + import time +=} reactor Source(period = 3 sec) { output y diff --git a/test/Python/src/DeadlineHandledAbove.lf b/test/Python/src/DeadlineHandledAbove.lf index 80a5f847f6..08f97eb25c 100644 --- a/test/Python/src/DeadlineHandledAbove.lf +++ b/test/Python/src/DeadlineHandledAbove.lf @@ -2,7 +2,9 @@ # output. target Python -preamble {= import time =} +preamble {= + import time +=} reactor Deadline(threshold = 100 msec) { input x diff --git a/test/Python/src/DelayArray.lf b/test/Python/src/DelayArray.lf index aa7e66cb6e..a4adeb717b 100644 --- a/test/Python/src/DelayArray.lf +++ b/test/Python/src/DelayArray.lf @@ -12,7 +12,9 @@ reactor DelayPointer(delay = 100 msec) { a.schedule(self.delay, _in.value); =} - reaction(a) -> out {= out.set(a.value); =} + reaction(a) -> out {= + out.set(a.value); + =} } reactor Source { diff --git a/test/Python/src/DelayInt.lf b/test/Python/src/DelayInt.lf index 0432a5c18a..c58e2eef6f 100644 --- a/test/Python/src/DelayInt.lf +++ b/test/Python/src/DelayInt.lf @@ -11,7 +11,9 @@ reactor Delay(delay = 100 msec) { out.set(a.value) =} - reaction(_in) -> a {= a.schedule(self.delay, _in.value) =} + reaction(_in) -> a {= + a.schedule(self.delay, _in.value) + =} } reactor Test { @@ -52,5 +54,7 @@ main reactor DelayInt { t = new Test() d.out -> t._in - reaction(startup) -> d._in {= d._in.set(42) =} + reaction(startup) -> d._in {= + d._in.set(42) + =} } diff --git a/test/Python/src/DelayString.lf b/test/Python/src/DelayString.lf index 1faf80e31f..0873220fcf 100644 --- a/test/Python/src/DelayString.lf +++ b/test/Python/src/DelayString.lf @@ -6,9 +6,13 @@ reactor DelayString2(delay = 100 msec) { output out logical action a - reaction(a) -> out {= out.set(a.value) =} + reaction(a) -> out {= + out.set(a.value) + =} - reaction(_in) -> a {= a.schedule(self.delay, _in.value) =} + reaction(_in) -> a {= + a.schedule(self.delay, _in.value) + =} } reactor Test { @@ -34,5 +38,7 @@ main reactor { t = new Test() d.out -> t._in - reaction(startup) -> d._in {= d._in.set("Hello") =} + reaction(startup) -> d._in {= + d._in.set("Hello") + =} } diff --git a/test/Python/src/DelayStruct.lf b/test/Python/src/DelayStruct.lf index dcf1a8be7c..7a3ae51ba8 100644 --- a/test/Python/src/DelayStruct.lf +++ b/test/Python/src/DelayStruct.lf @@ -3,14 +3,18 @@ target Python { files: ["include/hello.py"] } -preamble {= import hello =} +preamble {= + import hello +=} reactor DelayPointer(delay = 100 msec) { input _in output out logical action a - reaction(a) -> out {= out.set(a.value); =} + reaction(a) -> out {= + out.set(a.value); + =} reaction(_in) -> a {= # Schedule the actual token from the input rather than @@ -22,7 +26,9 @@ reactor DelayPointer(delay = 100 msec) { reactor Source { output out - reaction(startup) -> out {= out.set(hello.hello("Earth", 42)) =} + reaction(startup) -> out {= + out.set(hello.hello("Earth", 42)) + =} } # expected parameter is for testing. diff --git a/test/Python/src/DelayStructWithAfter.lf b/test/Python/src/DelayStructWithAfter.lf index c6f27a0775..feb2c471f2 100644 --- a/test/Python/src/DelayStructWithAfter.lf +++ b/test/Python/src/DelayStructWithAfter.lf @@ -3,12 +3,16 @@ target Python { files: include/hello.py } -preamble {= import hello =} +preamble {= + import hello +=} reactor Source { output out - reaction(startup) -> out {= out.set(hello.hello("Earth", 42)) =} + reaction(startup) -> out {= + out.set(hello.hello("Earth", 42)) + =} } # expected parameter is for testing. diff --git a/test/Python/src/DelayStructWithAfterOverlapped.lf b/test/Python/src/DelayStructWithAfterOverlapped.lf index 73fd60c504..e11572dbc6 100644 --- a/test/Python/src/DelayStructWithAfterOverlapped.lf +++ b/test/Python/src/DelayStructWithAfterOverlapped.lf @@ -5,7 +5,9 @@ target Python { files: ["include/hello.py"] } -preamble {= import hello =} +preamble {= + import hello +=} reactor Source { output out diff --git a/test/Python/src/DelayedAction.lf b/test/Python/src/DelayedAction.lf index 1a461eccff..d95ceb6a99 100644 --- a/test/Python/src/DelayedAction.lf +++ b/test/Python/src/DelayedAction.lf @@ -8,7 +8,9 @@ main reactor DelayedAction { logical action a state count = 0 - reaction(t) -> a {= a.schedule(MSEC(100)) =} + reaction(t) -> a {= + a.schedule(MSEC(100)) + =} reaction(a) {= elapsed = lf.time.logical_elapsed() diff --git a/test/Python/src/DelayedReaction.lf b/test/Python/src/DelayedReaction.lf index 2ebde05b5a..d4af80394c 100644 --- a/test/Python/src/DelayedReaction.lf +++ b/test/Python/src/DelayedReaction.lf @@ -5,7 +5,9 @@ reactor Source { output out timer t - reaction(t) -> out {= out.set(1) =} + reaction(t) -> out {= + out.set(1) + =} } reactor Sink { diff --git a/test/Python/src/Determinism.lf b/test/Python/src/Determinism.lf index f200c141f5..17b0f3922d 100644 --- a/test/Python/src/Determinism.lf +++ b/test/Python/src/Determinism.lf @@ -4,7 +4,9 @@ reactor Source { output y timer t - reaction(t) -> y {= y.set(1) =} + reaction(t) -> y {= + y.set(1) + =} } reactor Destination { @@ -28,7 +30,9 @@ reactor Pass { input x output y - reaction(x) -> y {= y.set(x.value) =} + reaction(x) -> y {= + y.set(x.value) + =} } main reactor Determinism { diff --git a/test/Python/src/Gain.lf b/test/Python/src/Gain.lf index 34e9229748..baab5d1cb1 100644 --- a/test/Python/src/Gain.lf +++ b/test/Python/src/Gain.lf @@ -5,7 +5,9 @@ reactor Scale(scale=2) { input x output y - reaction(x) -> y {= y.set(x.value * self.scale) =} + reaction(x) -> y {= + y.set(x.value * self.scale) + =} } reactor Test { @@ -33,5 +35,7 @@ main reactor Gain { d = new Test() g.y -> d.x - reaction(startup) -> g.x {= g.x.set(1) =} + reaction(startup) -> g.x {= + g.x.set(1) + =} } diff --git a/test/Python/src/GetMicroStep.lf b/test/Python/src/GetMicroStep.lf index b137b8cd94..c82c3f6399 100644 --- a/test/Python/src/GetMicroStep.lf +++ b/test/Python/src/GetMicroStep.lf @@ -4,12 +4,16 @@ target Python { } main reactor GetMicroStep { - preamble {= import sys =} + preamble {= + import sys + =} state s = 1 logical action l # timer t(0, 1 msec); - reaction(startup) -> l {= l.schedule(0); =} + reaction(startup) -> l {= + l.schedule(0); + =} reaction(l) -> l {= microstep = lf.tag().microstep diff --git a/test/Python/src/Hierarchy.lf b/test/Python/src/Hierarchy.lf index 4d92568592..4686842131 100644 --- a/test/Python/src/Hierarchy.lf +++ b/test/Python/src/Hierarchy.lf @@ -5,7 +5,9 @@ reactor Source { output out timer t - reaction(t) -> out {= out.set(1) =} + reaction(t) -> out {= + out.set(1) + =} } reactor Gain { diff --git a/test/Python/src/Hierarchy2.lf b/test/Python/src/Hierarchy2.lf index 2172396617..49ed0d54dd 100644 --- a/test/Python/src/Hierarchy2.lf +++ b/test/Python/src/Hierarchy2.lf @@ -8,7 +8,9 @@ reactor Source { output out timer t(0, 1 sec) - reaction(t) -> out {= out.set(1) =} + reaction(t) -> out {= + out.set(1) + =} } reactor Count { diff --git a/test/Python/src/Import.lf b/test/Python/src/Import.lf index fc225f1cb0..990453efa5 100644 --- a/test/Python/src/Import.lf +++ b/test/Python/src/Import.lf @@ -7,5 +7,7 @@ main reactor Import { timer t a = new Imported() - reaction(t) -> a.x {= a.x.set(42) =} + reaction(t) -> a.x {= + a.x.set(42) + =} } diff --git a/test/Python/src/ImportComposition.lf b/test/Python/src/ImportComposition.lf index cbdeeb5c6c..15149283e8 100644 --- a/test/Python/src/ImportComposition.lf +++ b/test/Python/src/ImportComposition.lf @@ -7,7 +7,9 @@ main reactor ImportComposition { a = new ImportedComposition() state received = False - reaction(startup) -> a.x {= a.x.set(42) =} + reaction(startup) -> a.x {= + a.x.set(42) + =} reaction(a.y) {= receive_time = lf.time.logical_elapsed() diff --git a/test/Python/src/ImportRenamed.lf b/test/Python/src/ImportRenamed.lf index 0d7433028f..d032dd8fcb 100644 --- a/test/Python/src/ImportRenamed.lf +++ b/test/Python/src/ImportRenamed.lf @@ -11,5 +11,7 @@ main reactor { b = new Y() c = new Z() - reaction(t) -> a.x {= a.x.set(42) =} + reaction(t) -> a.x {= + a.x.set(42) + =} } diff --git a/test/Python/src/ManualDelayedReaction.lf b/test/Python/src/ManualDelayedReaction.lf index a9863589ff..79d3398a08 100644 --- a/test/Python/src/ManualDelayedReaction.lf +++ b/test/Python/src/ManualDelayedReaction.lf @@ -18,14 +18,19 @@ reactor GeneratedDelay { act.schedule(MSEC(100)) =} - reaction(act) -> y_out {= y_out.set(self.y_state) =} + reaction(act) -> y_out {= + y_out.set(self.y_state) + =} } reactor Source { output out timer t - reaction(t) -> out {= out.set(1) =} # reaction(t) -> out after 100 msec + # reaction(t) -> out after 100 msec + reaction(t) -> out {= + out.set(1) + =} } reactor Sink { diff --git a/test/Python/src/Methods.lf b/test/Python/src/Methods.lf index ad158e72b3..3d678b7ed1 100644 --- a/test/Python/src/Methods.lf +++ b/test/Python/src/Methods.lf @@ -4,9 +4,13 @@ target Python main reactor { state foo = 2 - method getFoo() {= return self.foo =} + method getFoo() {= + return self.foo + =} - method add(x) {= self.foo += x =} + method add(x) {= + self.foo += x + =} reaction(startup) {= print(f"Foo is initialized to {self.getFoo()}") diff --git a/test/Python/src/MethodsRecursive.lf b/test/Python/src/MethodsRecursive.lf index cc6a9ccb85..59686a522c 100644 --- a/test/Python/src/MethodsRecursive.lf +++ b/test/Python/src/MethodsRecursive.lf @@ -11,7 +11,9 @@ main reactor { return self.add(self.fib(n-1), self.fib(n-2)) =} - method add(x, y) {= return x + y =} + method add(x, y) {= + return x + y + =} reaction(startup) {= for n in range(1, 10): diff --git a/test/Python/src/MethodsSameName.lf b/test/Python/src/MethodsSameName.lf index e842912e84..c3c0178eac 100644 --- a/test/Python/src/MethodsSameName.lf +++ b/test/Python/src/MethodsSameName.lf @@ -4,7 +4,9 @@ target Python reactor Foo { state foo = 2 - method add(x) {= self.foo += x =} + method add(x) {= + self.foo += x + =} reaction(startup) {= self.add(40) @@ -20,7 +22,9 @@ main reactor { a = new Foo() - method add(x) {= self.foo += x =} + method add(x) {= + self.foo += x + =} reaction(startup) {= self.add(40) diff --git a/test/Python/src/Microsteps.lf b/test/Python/src/Microsteps.lf index 871ad321bc..bcb8003cd8 100644 --- a/test/Python/src/Microsteps.lf +++ b/test/Python/src/Microsteps.lf @@ -33,5 +33,7 @@ main reactor Microsteps { repeat.schedule(0) =} - reaction(repeat) -> d.y {= d.y.set(1) =} + reaction(repeat) -> d.y {= + d.y.set(1) + =} } diff --git a/test/Python/src/Minimal.lf b/test/Python/src/Minimal.lf index c0f3f9f6fa..3cc2aeaf8f 100644 --- a/test/Python/src/Minimal.lf +++ b/test/Python/src/Minimal.lf @@ -2,5 +2,7 @@ target Python main reactor Minimal { - reaction(startup) {= print("Hello World.") =} + reaction(startup) {= + print("Hello World.") + =} } diff --git a/test/Python/src/MultipleContained.lf b/test/Python/src/MultipleContained.lf index 8d0afa0a12..c15339e4ae 100644 --- a/test/Python/src/MultipleContained.lf +++ b/test/Python/src/MultipleContained.lf @@ -7,7 +7,9 @@ reactor Contained { input in1 input in2 - reaction(startup) -> trigger {= trigger.set(42) =} + reaction(startup) -> trigger {= + trigger.set(42) + =} reaction(in1) {= print("in1 received ", in1.value); diff --git a/test/Python/src/ParameterizedState.lf b/test/Python/src/ParameterizedState.lf index 2553cfcfd2..75f1a4fd15 100644 --- a/test/Python/src/ParameterizedState.lf +++ b/test/Python/src/ParameterizedState.lf @@ -3,7 +3,9 @@ target Python reactor Foo(bar=42) { state baz = bar - reaction(startup) {= print("Baz: ", self.baz) =} + reaction(startup) {= + print("Baz: ", self.baz) + =} } main reactor { diff --git a/test/Python/src/ReadOutputOfContainedReactor.lf b/test/Python/src/ReadOutputOfContainedReactor.lf index 0a086c9514..02a70b34f1 100644 --- a/test/Python/src/ReadOutputOfContainedReactor.lf +++ b/test/Python/src/ReadOutputOfContainedReactor.lf @@ -4,7 +4,9 @@ target Python reactor Contained { output out - reaction(startup) -> out {= out.set(42) =} + reaction(startup) -> out {= + out.set(42) + =} } main reactor ReadOutputOfContainedReactor { diff --git a/test/Python/src/Schedule.lf b/test/Python/src/Schedule.lf index 5d42888c2e..3738b63e92 100644 --- a/test/Python/src/Schedule.lf +++ b/test/Python/src/Schedule.lf @@ -5,7 +5,9 @@ reactor Schedule2 { input x logical action a - reaction(x) -> a {= a.schedule(MSEC(200)) =} + reaction(x) -> a {= + a.schedule(MSEC(200)) + =} reaction(a) {= elapsed_time = lf.time.logical_elapsed() @@ -20,5 +22,7 @@ main reactor { a = new Schedule2() timer t - reaction(t) -> a.x {= a.x.set(1) =} + reaction(t) -> a.x {= + a.x.set(1) + =} } diff --git a/test/Python/src/ScheduleLogicalAction.lf b/test/Python/src/ScheduleLogicalAction.lf index 37eeb8ab6d..31b2151da7 100644 --- a/test/Python/src/ScheduleLogicalAction.lf +++ b/test/Python/src/ScheduleLogicalAction.lf @@ -16,7 +16,9 @@ reactor foo { a.schedule(MSEC(500)) =} - reaction(a) -> y {= y.set(-42) =} + reaction(a) -> y {= + y.set(-42) + =} } reactor print { @@ -41,5 +43,7 @@ main reactor { timer t(0, 1 sec) f.y -> p.x - reaction(t) -> f.x {= f.x.set(42) =} + reaction(t) -> f.x {= + f.x.set(42) + =} } diff --git a/test/Python/src/SelfLoop.lf b/test/Python/src/SelfLoop.lf index 6362c77373..5d79493ec5 100644 --- a/test/Python/src/SelfLoop.lf +++ b/test/Python/src/SelfLoop.lf @@ -23,7 +23,9 @@ reactor Self { a.schedule(MSEC(100), x.value) =} - reaction(startup) -> a {= a.schedule(0, 42) =} + reaction(startup) -> a {= + a.schedule(0, 42) + =} reaction(shutdown) {= if self.expected <= 43: diff --git a/test/Python/src/SendingInside2.lf b/test/Python/src/SendingInside2.lf index 8ceb1ba5cc..3ddb25102c 100644 --- a/test/Python/src/SendingInside2.lf +++ b/test/Python/src/SendingInside2.lf @@ -15,5 +15,7 @@ main reactor SendingInside2 { timer t p = new Printer() - reaction(t) -> p.x {= p.x.set(1) =} + reaction(t) -> p.x {= + p.x.set(1) + =} } diff --git a/test/Python/src/SetArray.lf b/test/Python/src/SetArray.lf index 931260ab9b..73f80cf380 100644 --- a/test/Python/src/SetArray.lf +++ b/test/Python/src/SetArray.lf @@ -5,7 +5,9 @@ target Python reactor Source { output out - reaction(startup) -> out {= out.set([0,1,2]) =} + reaction(startup) -> out {= + out.set([0,1,2]) + =} } # The scale parameter is just for testing. diff --git a/test/Python/src/SimpleDeadline.lf b/test/Python/src/SimpleDeadline.lf index 2b6adbf942..c405774390 100644 --- a/test/Python/src/SimpleDeadline.lf +++ b/test/Python/src/SimpleDeadline.lf @@ -29,7 +29,9 @@ main reactor SimpleDeadline { d = new Deadline(threshold = 10 msec) p = new Print() d.deadlineViolation -> p._in - preamble {= import time =} + preamble {= + import time + =} reaction(start) -> d.x {= self.time.sleep(0.02) diff --git a/test/Python/src/SlowingClock.lf b/test/Python/src/SlowingClock.lf index 8298135c62..7d156b9be5 100644 --- a/test/Python/src/SlowingClock.lf +++ b/test/Python/src/SlowingClock.lf @@ -13,7 +13,9 @@ main reactor SlowingClock { state interval = 100 msec state expected_time = 100 msec - reaction(startup) -> a {= a.schedule(0) =} + reaction(startup) -> a {= + a.schedule(0) + =} reaction(a) -> a {= elapsed_logical_time = lf.time.logical_elapsed() diff --git a/test/Python/src/StartupOutFromInside.lf b/test/Python/src/StartupOutFromInside.lf index ce2025278f..c20a48773e 100644 --- a/test/Python/src/StartupOutFromInside.lf +++ b/test/Python/src/StartupOutFromInside.lf @@ -3,7 +3,9 @@ target Python reactor Bar { output out - reaction(startup) -> out {= out.set(42) =} + reaction(startup) -> out {= + out.set(42) + =} } main reactor StartupOutFromInside { diff --git a/test/Python/src/StructAsType.lf b/test/Python/src/StructAsType.lf index e1b089df5b..e20c3e4f8e 100644 --- a/test/Python/src/StructAsType.lf +++ b/test/Python/src/StructAsType.lf @@ -2,7 +2,9 @@ target Python { files: include/hello.py } -preamble {= import hello =} +preamble {= + import hello +=} reactor Source { output out diff --git a/test/Python/src/StructAsTypeDirect.lf b/test/Python/src/StructAsTypeDirect.lf index 132ef78948..c60241c914 100644 --- a/test/Python/src/StructAsTypeDirect.lf +++ b/test/Python/src/StructAsTypeDirect.lf @@ -2,7 +2,9 @@ target Python { files: include/hello.py } -preamble {= import hello =} +preamble {= + import hello +=} reactor Source { output out diff --git a/test/Python/src/StructParallel.lf b/test/Python/src/StructParallel.lf index 4e2024d99e..46514a2154 100644 --- a/test/Python/src/StructParallel.lf +++ b/test/Python/src/StructParallel.lf @@ -6,7 +6,9 @@ target Python { import Source from "StructScale.lf" -preamble {= import hello =} +preamble {= + import hello +=} reactor Check(expected=42) { input _in diff --git a/test/Python/src/StructPrint.lf b/test/Python/src/StructPrint.lf index 968a3e9d27..ebd6c82122 100644 --- a/test/Python/src/StructPrint.lf +++ b/test/Python/src/StructPrint.lf @@ -4,12 +4,16 @@ target Python { files: ["include/hello.py"] } -preamble {= import hello =} +preamble {= + import hello +=} reactor Print { output out - reaction(startup) -> out {= out.set(hello.hello("Earth", 42)) =} + reaction(startup) -> out {= + out.set(hello.hello("Earth", 42)) + =} } # expected parameter is for testing. diff --git a/test/Python/src/StructScale.lf b/test/Python/src/StructScale.lf index f2ec9162d4..d5400e3750 100644 --- a/test/Python/src/StructScale.lf +++ b/test/Python/src/StructScale.lf @@ -4,12 +4,16 @@ target Python { files: ["include/hello.py"] } -preamble {= import hello =} +preamble {= + import hello +=} reactor Source { output out - reaction(startup) -> out {= out.set(hello.hello("Earth", 42)) =} + reaction(startup) -> out {= + out.set(hello.hello("Earth", 42)) + =} } # expected parameter is for testing. diff --git a/test/Python/src/TestForPreviousOutput.lf b/test/Python/src/TestForPreviousOutput.lf index 7f10c13482..eaa0593a58 100644 --- a/test/Python/src/TestForPreviousOutput.lf +++ b/test/Python/src/TestForPreviousOutput.lf @@ -4,7 +4,9 @@ target Python reactor Source { output out - preamble {= import random =} + preamble {= + import random + =} reaction(startup) -> out {= # Set a seed for random number generation based on the current time. diff --git a/test/Python/src/TimeLimit.lf b/test/Python/src/TimeLimit.lf index 64c5fa6a71..4f3f1b0306 100644 --- a/test/Python/src/TimeLimit.lf +++ b/test/Python/src/TimeLimit.lf @@ -42,5 +42,7 @@ main reactor TimeLimit(period = 1 sec) { d = new Destination() c.y -> d.x - reaction(stop) {= request_stop() =} + reaction(stop) {= + request_stop() + =} } diff --git a/test/Python/src/TimeState.lf b/test/Python/src/TimeState.lf index ab6d435633..86e9946f17 100644 --- a/test/Python/src/TimeState.lf +++ b/test/Python/src/TimeState.lf @@ -3,7 +3,9 @@ target Python reactor Foo(bar=42) { state baz = 500 msec - reaction(startup) {= print("Baz: ", self.baz) =} + reaction(startup) {= + print("Baz: ", self.baz) + =} } main reactor { diff --git a/test/Python/src/Timers.lf b/test/Python/src/Timers.lf index 88a10bb367..1ba75599ab 100644 --- a/test/Python/src/Timers.lf +++ b/test/Python/src/Timers.lf @@ -8,9 +8,13 @@ main reactor { timer t2(0, 2 sec) state counter = 0 - reaction(t2) {= self.counter += 2 =} + reaction(t2) {= + self.counter += 2 + =} - reaction(t) {= self.counter -= 1 =} + reaction(t) {= + self.counter -= 1 + =} reaction(shutdown) {= if self.counter != 1: diff --git a/test/Python/src/TriggerDownstreamOnlyIfPresent2.lf b/test/Python/src/TriggerDownstreamOnlyIfPresent2.lf index 4bcba7d298..2fddd52aa2 100644 --- a/test/Python/src/TriggerDownstreamOnlyIfPresent2.lf +++ b/test/Python/src/TriggerDownstreamOnlyIfPresent2.lf @@ -27,7 +27,9 @@ reactor Destination { exit(1) =} - reaction(shutdown) {= print("SUCCESS.") =} + reaction(shutdown) {= + print("SUCCESS.") + =} } main reactor TriggerDownstreamOnlyIfPresent2 { diff --git a/test/Python/src/Wcet.lf b/test/Python/src/Wcet.lf index d1a3e41e18..4d6637ce13 100644 --- a/test/Python/src/Wcet.lf +++ b/test/Python/src/Wcet.lf @@ -30,7 +30,9 @@ reactor Work { reactor Print { input p_in - reaction(p_in) {= print("Received: ", p_in.value) =} + reaction(p_in) {= + print("Received: ", p_in.value) + =} } main reactor Wcet { diff --git a/test/Python/src/docker/FilesPropertyContainerized.lf b/test/Python/src/docker/FilesPropertyContainerized.lf index afbf76f8a8..139f1b682a 100644 --- a/test/Python/src/docker/FilesPropertyContainerized.lf +++ b/test/Python/src/docker/FilesPropertyContainerized.lf @@ -20,7 +20,9 @@ main reactor { state passed = False timer t(1 msec) - reaction(t) {= self.passed = True =} + reaction(t) {= + self.passed = True + =} reaction(shutdown) {= if not self.passed: diff --git a/test/Python/src/federated/BroadcastFeedback.lf b/test/Python/src/federated/BroadcastFeedback.lf index facc896800..dc5c88ccf6 100644 --- a/test/Python/src/federated/BroadcastFeedback.lf +++ b/test/Python/src/federated/BroadcastFeedback.lf @@ -8,7 +8,9 @@ reactor SenderAndReceiver { input[2] inp state received = False - reaction(startup) -> out {= out.set(42) =} + reaction(startup) -> out {= + out.set(42) + =} reaction(inp) {= if inp[0].is_present and inp[1].is_present and inp[0].value == 42 and inp[1].value == 42: diff --git a/test/Python/src/federated/BroadcastFeedbackWithHierarchy.lf b/test/Python/src/federated/BroadcastFeedbackWithHierarchy.lf index c0cc71ab5b..8c6339170a 100644 --- a/test/Python/src/federated/BroadcastFeedbackWithHierarchy.lf +++ b/test/Python/src/federated/BroadcastFeedbackWithHierarchy.lf @@ -11,11 +11,15 @@ reactor SenderAndReceiver { r = new Receiver() in_ -> r.in_ - reaction(startup) -> out {= out.set(42) =} + reaction(startup) -> out {= + out.set(42) + =} } reactor Receiver { - preamble {= import sys =} + preamble {= + import sys + =} input[2] in_ state received = False diff --git a/test/Python/src/federated/CycleDetection.lf b/test/Python/src/federated/CycleDetection.lf index c4eaac6904..cf6dd7f661 100644 --- a/test/Python/src/federated/CycleDetection.lf +++ b/test/Python/src/federated/CycleDetection.lf @@ -21,15 +21,21 @@ reactor CAReplica { self.balance += remote_update.value =} - reaction(query) -> response {= response.set(self.balance) =} + reaction(query) -> response {= + response.set(self.balance) + =} } reactor UserInput { - preamble {= import sys =} + preamble {= + import sys + =} input balance output deposit - reaction(startup) -> deposit {= deposit.set(100) =} + reaction(startup) -> deposit {= + deposit.set(100) + =} reaction(balance) {= if balance.value != 200: @@ -39,7 +45,9 @@ reactor UserInput { request_stop() =} - reaction(shutdown) {= print("Test passed!") =} + reaction(shutdown) {= + print("Test passed!") + =} } federated reactor { diff --git a/test/Python/src/federated/DecentralizedP2PComm.lf b/test/Python/src/federated/DecentralizedP2PComm.lf index 5bc2cc61a2..b3804c23b4 100644 --- a/test/Python/src/federated/DecentralizedP2PComm.lf +++ b/test/Python/src/federated/DecentralizedP2PComm.lf @@ -6,7 +6,9 @@ target Python { } reactor Platform(start=0, expected_start=0, stp_offset_param=0) { - preamble {= import sys =} + preamble {= + import sys + =} input in_ output out timer t(0, 100 msec) diff --git a/test/Python/src/federated/DecentralizedP2PUnbalancedTimeout.lf b/test/Python/src/federated/DecentralizedP2PUnbalancedTimeout.lf index 9b4883e730..088c71c086 100644 --- a/test/Python/src/federated/DecentralizedP2PUnbalancedTimeout.lf +++ b/test/Python/src/federated/DecentralizedP2PUnbalancedTimeout.lf @@ -22,16 +22,22 @@ reactor Clock(offset=0, period = 1 sec) { y.set(self.count) =} - reaction(shutdown) {= print("SUCCESS: the source exited successfully.") =} + reaction(shutdown) {= + print("SUCCESS: the source exited successfully.") + =} } reactor Destination { - preamble {= import sys =} + preamble {= + import sys + =} input x state s = 1 state startup_logical_time - reaction(startup) {= self.startup_logical_time = lf.time.logical() =} + reaction(startup) {= + self.startup_logical_time = lf.time.logical() + =} reaction(x) {= print("Received {}".format(x.value)) diff --git a/test/Python/src/federated/DecentralizedP2PUnbalancedTimeoutPhysical.lf b/test/Python/src/federated/DecentralizedP2PUnbalancedTimeoutPhysical.lf index a47d362140..86c2dffdd4 100644 --- a/test/Python/src/federated/DecentralizedP2PUnbalancedTimeoutPhysical.lf +++ b/test/Python/src/federated/DecentralizedP2PUnbalancedTimeoutPhysical.lf @@ -21,11 +21,15 @@ reactor Clock(offset=0, period = 1 sec) { y.set(self.count) =} - reaction(shutdown) {= print("SUCCESS: the source exited successfully.") =} + reaction(shutdown) {= + print("SUCCESS: the source exited successfully.") + =} } reactor Destination { - preamble {= import sys =} + preamble {= + import sys + =} input x state s = 1 diff --git a/test/Python/src/federated/DistributedBank.lf b/test/Python/src/federated/DistributedBank.lf index 9fb4720d59..3dfd8d7c32 100644 --- a/test/Python/src/federated/DistributedBank.lf +++ b/test/Python/src/federated/DistributedBank.lf @@ -5,7 +5,9 @@ target Python { } reactor Node { - preamble {= import sys =} + preamble {= + import sys + =} timer t(0, 100 msec) state count = 0 diff --git a/test/Python/src/federated/DistributedBankToMultiport.lf b/test/Python/src/federated/DistributedBankToMultiport.lf index 1171c1b1b4..9f65f2f065 100644 --- a/test/Python/src/federated/DistributedBankToMultiport.lf +++ b/test/Python/src/federated/DistributedBankToMultiport.lf @@ -6,7 +6,9 @@ target Python { import Count from "../lib/Count.lf" reactor Destination { - preamble {= import sys =} + preamble {= + import sys + =} input[2] in_ state count = 1 diff --git a/test/Python/src/federated/DistributedCount.lf b/test/Python/src/federated/DistributedCount.lf index 6b08b7e7fe..7a9bedf97c 100644 --- a/test/Python/src/federated/DistributedCount.lf +++ b/test/Python/src/federated/DistributedCount.lf @@ -12,7 +12,9 @@ target Python { import Count from "../lib/Count.lf" reactor Print { - preamble {= import sys =} + preamble {= + import sys + =} input in_ state c = 1 diff --git a/test/Python/src/federated/DistributedCountDecentralized.lf b/test/Python/src/federated/DistributedCountDecentralized.lf index 62f2921282..7c95336e1c 100644 --- a/test/Python/src/federated/DistributedCountDecentralized.lf +++ b/test/Python/src/federated/DistributedCountDecentralized.lf @@ -13,7 +13,9 @@ target Python { import Count from "../lib/Count.lf" reactor Print { - preamble {= import sys =} + preamble {= + import sys + =} input in_ state c = 1 diff --git a/test/Python/src/federated/DistributedCountDecentralizedLate.lf b/test/Python/src/federated/DistributedCountDecentralizedLate.lf index fe52eec1b7..c03c38600f 100644 --- a/test/Python/src/federated/DistributedCountDecentralizedLate.lf +++ b/test/Python/src/federated/DistributedCountDecentralizedLate.lf @@ -13,7 +13,9 @@ target Python { import Count from "../lib/Count.lf" reactor Print { - preamble {= import sys =} + preamble {= + import sys + =} input in_ # STP () state success = 0 # STP(in, 30 msec); state success_stp_violation = 0 diff --git a/test/Python/src/federated/DistributedCountPhysical.lf b/test/Python/src/federated/DistributedCountPhysical.lf index a55dcf06dd..acfb512897 100644 --- a/test/Python/src/federated/DistributedCountPhysical.lf +++ b/test/Python/src/federated/DistributedCountPhysical.lf @@ -23,7 +23,9 @@ reactor Count { } reactor Print { - preamble {= import sys =} + preamble {= + import sys + =} input in_ state c = 0 diff --git a/test/Python/src/federated/DistributedCountPhysicalAfterDelay.lf b/test/Python/src/federated/DistributedCountPhysicalAfterDelay.lf index f18f5dc997..7c3c17ec35 100644 --- a/test/Python/src/federated/DistributedCountPhysicalAfterDelay.lf +++ b/test/Python/src/federated/DistributedCountPhysicalAfterDelay.lf @@ -23,7 +23,9 @@ reactor Count { } reactor Print { - preamble {= import sys =} + preamble {= + import sys + =} input in_ state c = 0 diff --git a/test/Python/src/federated/DistributedCountPhysicalDecentralized.lf b/test/Python/src/federated/DistributedCountPhysicalDecentralized.lf index 71048dfa38..5eafa49e5d 100644 --- a/test/Python/src/federated/DistributedCountPhysicalDecentralized.lf +++ b/test/Python/src/federated/DistributedCountPhysicalDecentralized.lf @@ -24,7 +24,9 @@ reactor Count { } reactor Print { - preamble {= import sys =} + preamble {= + import sys + =} input in_ state c = 0 diff --git a/test/Python/src/federated/DistributedDoublePort.lf b/test/Python/src/federated/DistributedDoublePort.lf index 3c09373ad1..a90ec7b40f 100644 --- a/test/Python/src/federated/DistributedDoublePort.lf +++ b/test/Python/src/federated/DistributedDoublePort.lf @@ -23,11 +23,15 @@ reactor CountMicrostep { self.count += 1 =} - reaction(act) -> out {= out.set(act.value) =} + reaction(act) -> out {= + out.set(act.value) + =} } reactor Print { - preamble {= import sys =} + preamble {= + import sys + =} input in_ input in2 @@ -39,7 +43,9 @@ reactor Print { self.sys.exit(1) =} - reaction(shutdown) {= print("SUCCESS: messages were at least one microstep apart.") =} + reaction(shutdown) {= + print("SUCCESS: messages were at least one microstep apart.") + =} } federated reactor DistributedDoublePort { diff --git a/test/Python/src/federated/DistributedLoopedAction.lf b/test/Python/src/federated/DistributedLoopedAction.lf index 81394d62d0..37551a36d8 100644 --- a/test/Python/src/federated/DistributedLoopedAction.lf +++ b/test/Python/src/federated/DistributedLoopedAction.lf @@ -10,7 +10,9 @@ target Python { import Sender from "../lib/LoopedActionSender.lf" reactor Receiver(take_a_break_after=10, break_interval = 400 msec) { - preamble {= import sys =} + preamble {= + import sys + =} input in_ state received_messages = 0 state total_received_messages = 0 diff --git a/test/Python/src/federated/DistributedLoopedPhysicalAction.lf b/test/Python/src/federated/DistributedLoopedPhysicalAction.lf index fa88bb3700..79a3be24ec 100644 --- a/test/Python/src/federated/DistributedLoopedPhysicalAction.lf +++ b/test/Python/src/federated/DistributedLoopedPhysicalAction.lf @@ -32,7 +32,9 @@ reactor Sender(take_a_break_after=10, break_interval = 550 msec) { } reactor Receiver(take_a_break_after=10, break_interval = 550 msec) { - preamble {= import sys =} + preamble {= + import sys + =} input in_ state received_messages = 0 state total_received_messages = 0 @@ -42,7 +44,9 @@ reactor Receiver(take_a_break_after=10, break_interval = 550 msec) { # but forces the logical time to advance Comment this line for a more sensible log output. state base_logical_time - reaction(startup) {= self.base_logical_time = lf.time.logical() =} + reaction(startup) {= + self.base_logical_time = lf.time.logical() + =} reaction(in_) {= current_tag = lf.tag() diff --git a/test/Python/src/federated/DistributedMultiport.lf b/test/Python/src/federated/DistributedMultiport.lf index 6c67e0e90f..952021df01 100644 --- a/test/Python/src/federated/DistributedMultiport.lf +++ b/test/Python/src/federated/DistributedMultiport.lf @@ -17,7 +17,9 @@ reactor Source { } reactor Destination { - preamble {= import sys =} + preamble {= + import sys + =} input[4] in_ state count = 0 diff --git a/test/Python/src/federated/DistributedMultiportToBank.lf b/test/Python/src/federated/DistributedMultiportToBank.lf index c94e4ec49b..93e1daf076 100644 --- a/test/Python/src/federated/DistributedMultiportToBank.lf +++ b/test/Python/src/federated/DistributedMultiportToBank.lf @@ -16,7 +16,9 @@ reactor Source { } reactor Destination { - preamble {= import sys =} + preamble {= + import sys + =} input in_ state count = 0 diff --git a/test/Python/src/federated/DistributedNoReact.lf b/test/Python/src/federated/DistributedNoReact.lf index e28f5ae253..afbcd3aeed 100644 --- a/test/Python/src/federated/DistributedNoReact.lf +++ b/test/Python/src/federated/DistributedNoReact.lf @@ -15,7 +15,9 @@ reactor A { reactor B { output o - reaction(startup) -> o {= o.set(C()) =} + reaction(startup) -> o {= + o.set(C()) + =} } federated reactor { diff --git a/test/Python/src/federated/DistributedSendClass.lf b/test/Python/src/federated/DistributedSendClass.lf index 9f77259b37..6cdb4e013d 100644 --- a/test/Python/src/federated/DistributedSendClass.lf +++ b/test/Python/src/federated/DistributedSendClass.lf @@ -9,13 +9,17 @@ preamble {= reactor A { input o - reaction(o) {= request_stop() =} + reaction(o) {= + request_stop() + =} } reactor B { output o - reaction(startup) -> o {= o.set(C()) =} + reaction(startup) -> o {= + o.set(C()) + =} } federated reactor { diff --git a/test/Python/src/federated/DistributedStop.lf b/test/Python/src/federated/DistributedStop.lf index 0171a2bbfb..6aea789049 100644 --- a/test/Python/src/federated/DistributedStop.lf +++ b/test/Python/src/federated/DistributedStop.lf @@ -5,7 +5,9 @@ */ target Python -preamble {= import sys =} +preamble {= + import sys +=} reactor Sender { output out diff --git a/test/Python/src/federated/DistributedStopZero.lf b/test/Python/src/federated/DistributedStopZero.lf index 845f099698..ad4820eb03 100644 --- a/test/Python/src/federated/DistributedStopZero.lf +++ b/test/Python/src/federated/DistributedStopZero.lf @@ -6,14 +6,18 @@ # reason for failing: lf_tag().microstep and lf.tag_compare() are not not supported in python target target Python -preamble {= import sys =} +preamble {= + import sys +=} reactor Sender { output out timer t(0, 1 usec) state startup_logical_time - reaction(startup) {= self.startup_logical_time = lf.time.logical() =} + reaction(startup) {= + self.startup_logical_time = lf.time.logical() + =} reaction(t) -> out {= tag = lf.tag() @@ -47,7 +51,9 @@ reactor Receiver { input in_ state startup_logical_time - reaction(startup) {= self.startup_logical_time = lf.time.logical() =} + reaction(startup) {= + self.startup_logical_time = lf.time.logical() + =} reaction(in_) {= tag = lf.tag() diff --git a/test/Python/src/federated/DistributedStructAsType.lf b/test/Python/src/federated/DistributedStructAsType.lf index 36a3151289..b3a83cc600 100644 --- a/test/Python/src/federated/DistributedStructAsType.lf +++ b/test/Python/src/federated/DistributedStructAsType.lf @@ -5,7 +5,9 @@ target Python { import Source, Print from "../StructAsType.lf" -preamble {= import hello =} +preamble {= + import hello +=} federated reactor { s = new Source() diff --git a/test/Python/src/federated/DistributedStructAsTypeDirect.lf b/test/Python/src/federated/DistributedStructAsTypeDirect.lf index b74ba275b5..7463672366 100644 --- a/test/Python/src/federated/DistributedStructAsTypeDirect.lf +++ b/test/Python/src/federated/DistributedStructAsTypeDirect.lf @@ -5,7 +5,9 @@ target Python { import Source, Print from "../StructAsTypeDirect.lf" -preamble {= import hello =} +preamble {= + import hello +=} federated reactor { s = new Source() diff --git a/test/Python/src/federated/DistributedStructParallel.lf b/test/Python/src/federated/DistributedStructParallel.lf index e20bb21030..9577231955 100644 --- a/test/Python/src/federated/DistributedStructParallel.lf +++ b/test/Python/src/federated/DistributedStructParallel.lf @@ -8,7 +8,9 @@ target Python { import Source from "../StructScale.lf" import Check, Print from "../StructParallel.lf" -preamble {= import hello =} +preamble {= + import hello +=} federated reactor { s = new Source() diff --git a/test/Python/src/federated/DistributedStructPrint.lf b/test/Python/src/federated/DistributedStructPrint.lf index 829b298b28..d3dbfe398b 100644 --- a/test/Python/src/federated/DistributedStructPrint.lf +++ b/test/Python/src/federated/DistributedStructPrint.lf @@ -7,7 +7,9 @@ target Python { import Print, Check from "../StructPrint.lf" -preamble {= import hello =} +preamble {= + import hello +=} federated reactor { s = new Print() diff --git a/test/Python/src/federated/DistributedStructScale.lf b/test/Python/src/federated/DistributedStructScale.lf index 1551035c29..34a4977d05 100644 --- a/test/Python/src/federated/DistributedStructScale.lf +++ b/test/Python/src/federated/DistributedStructScale.lf @@ -7,7 +7,9 @@ target Python { import Source, TestInput, Print from "../StructScale.lf" -preamble {= import hello =} +preamble {= + import hello +=} federated reactor { s = new Source() diff --git a/test/Python/src/federated/HelloDistributed.lf b/test/Python/src/federated/HelloDistributed.lf index 7bbfa49368..300af9c78a 100644 --- a/test/Python/src/federated/HelloDistributed.lf +++ b/test/Python/src/federated/HelloDistributed.lf @@ -20,7 +20,9 @@ reactor Destination { input _in state received = False - reaction(startup) {= print("Destination started.") =} + reaction(startup) {= + print("Destination started.") + =} reaction(_in) {= print(f"At logical time {lf.time.logical_elapsed()}, destination received {_in.value}") diff --git a/test/Python/src/federated/LoopDistributedCentralizedPrecedenceHierarchy.lf b/test/Python/src/federated/LoopDistributedCentralizedPrecedenceHierarchy.lf index 3d4564fc45..1d996147a7 100644 --- a/test/Python/src/federated/LoopDistributedCentralizedPrecedenceHierarchy.lf +++ b/test/Python/src/federated/LoopDistributedCentralizedPrecedenceHierarchy.lf @@ -19,9 +19,13 @@ reactor Contained(incr=1) { state count = 0 state received_count = 0 - reaction(t) {= self.count += self.incr =} + reaction(t) {= + self.count += self.incr + =} - reaction(inp) {= self.received_count = self.count =} + reaction(inp) {= + self.received_count = self.count + =} reaction(t) {= if self.received_count != self.count: diff --git a/test/Python/src/federated/PhysicalSTP.lf b/test/Python/src/federated/PhysicalSTP.lf index 20d61df25b..e00fda3f36 100644 --- a/test/Python/src/federated/PhysicalSTP.lf +++ b/test/Python/src/federated/PhysicalSTP.lf @@ -7,7 +7,9 @@ target Python { import Count from "../lib/Count.lf" reactor Print(STP_offset=0) { - preamble {= import sys =} + preamble {= + import sys + =} input in_ state c = 1 diff --git a/test/Python/src/federated/PingPongDistributed.lf b/test/Python/src/federated/PingPongDistributed.lf index 4bd0cde285..a2c334ab57 100644 --- a/test/Python/src/federated/PingPongDistributed.lf +++ b/test/Python/src/federated/PingPongDistributed.lf @@ -40,7 +40,9 @@ reactor Ping(count=10) { } reactor Pong(expected=10) { - preamble {= import sys =} + preamble {= + import sys + =} input receive output send diff --git a/test/Python/src/federated/StopAtShutdown.lf b/test/Python/src/federated/StopAtShutdown.lf index 55708d038e..f5a784185d 100644 --- a/test/Python/src/federated/StopAtShutdown.lf +++ b/test/Python/src/federated/StopAtShutdown.lf @@ -12,20 +12,30 @@ target Python { reactor A { input in_ - reaction(startup) {= print("Hello World!") =} + reaction(startup) {= + print("Hello World!") + =} - reaction(in_) {= print("Got it") =} + reaction(in_) {= + print("Got it") + =} - reaction(shutdown) {= request_stop() =} + reaction(shutdown) {= + request_stop() + =} } reactor B { output out timer t(1 sec) - reaction(t) -> out {= out.set(1) =} + reaction(t) -> out {= + out.set(1) + =} - reaction(shutdown) {= request_stop() =} + reaction(shutdown) {= + request_stop() + =} } federated reactor { diff --git a/test/Python/src/lib/Imported.lf b/test/Python/src/lib/Imported.lf index e23b300991..e98af204a3 100644 --- a/test/Python/src/lib/Imported.lf +++ b/test/Python/src/lib/Imported.lf @@ -8,5 +8,7 @@ reactor Imported { input x a = new ImportedAgain() - reaction(x) -> a.x {= a.x.set(x.value) =} + reaction(x) -> a.x {= + a.x.set(x.value) + =} } diff --git a/test/Python/src/lib/ImportedComposition.lf b/test/Python/src/lib/ImportedComposition.lf index 5eeb39420f..9d55449db1 100644 --- a/test/Python/src/lib/ImportedComposition.lf +++ b/test/Python/src/lib/ImportedComposition.lf @@ -6,7 +6,9 @@ reactor Gain { input x output y - reaction(x) -> y {= y.set(x.value * 2) =} + reaction(x) -> y {= + y.set(x.value * 2) + =} } reactor ImportedComposition { diff --git a/test/Python/src/lib/InternalDelay.lf b/test/Python/src/lib/InternalDelay.lf index 964ec6b5a3..0438e1d877 100644 --- a/test/Python/src/lib/InternalDelay.lf +++ b/test/Python/src/lib/InternalDelay.lf @@ -5,7 +5,11 @@ reactor InternalDelay(delay = 10 msec) { output out logical action d - reaction(in_) -> d {= d.schedule(self.delay, in_.value) =} + reaction(in_) -> d {= + d.schedule(self.delay, in_.value) + =} - reaction(d) -> out {= out.set(d.value) =} + reaction(d) -> out {= + out.set(d.value) + =} } diff --git a/test/Python/src/lib/TestCount.lf b/test/Python/src/lib/TestCount.lf index 28dcf74ba0..75d9c96d9e 100644 --- a/test/Python/src/lib/TestCount.lf +++ b/test/Python/src/lib/TestCount.lf @@ -9,7 +9,9 @@ target Python reactor TestCount(start=1, stride=1, num_inputs=1) { - preamble {= import sys =} + preamble {= + import sys + =} state count = start state inputs_received = 0 input in_ diff --git a/test/Python/src/lib/TestCountMultiport.lf b/test/Python/src/lib/TestCountMultiport.lf index 0514bc2472..38bedbdbfd 100644 --- a/test/Python/src/lib/TestCountMultiport.lf +++ b/test/Python/src/lib/TestCountMultiport.lf @@ -11,7 +11,9 @@ target Python reactor TestCountMultiport(start=1, stride=1, num_inputs=1, width=2) { - preamble {= import sys =} + preamble {= + import sys + =} state count = start state inputs_received = 0 input[width] inp diff --git a/test/Python/src/modal_models/BanksCount3ModesComplex.lf b/test/Python/src/modal_models/BanksCount3ModesComplex.lf index 757331dd7d..3ea6d112ee 100644 --- a/test/Python/src/modal_models/BanksCount3ModesComplex.lf +++ b/test/Python/src/modal_models/BanksCount3ModesComplex.lf @@ -25,7 +25,9 @@ reactor MetaCounter { mode1_counters.count -> mode1 timer t1(500 msec, 250 msec) - reaction(t1) -> reset(Two) {= Two.set() =} + reaction(t1) -> reset(Two) {= + Two.set() + =} } mode Two { @@ -35,7 +37,9 @@ reactor MetaCounter { mode2_counters.count -> mode2 timer t2(500 msec, 250 msec) - reaction(t2) -> history(One) {= One.set() =} + reaction(t2) -> history(One) {= + One.set() + =} } mode Three { diff --git a/test/Python/src/modal_models/ConvertCaseTest.lf b/test/Python/src/modal_models/ConvertCaseTest.lf index f063da7e34..70eec263f5 100644 --- a/test/Python/src/modal_models/ConvertCaseTest.lf +++ b/test/Python/src/modal_models/ConvertCaseTest.lf @@ -15,13 +15,19 @@ reactor ResetProcessor { converter = new Converter() character -> converter.raw converter.converted -> converted - reaction(discard) -> reset(Discarding) {= Discarding.set() =} + reaction(discard) -> reset(Discarding) {= + Discarding.set() + =} } mode Discarding { - reaction(character) -> converted {= converted.set('_') =} + reaction(character) -> converted {= + converted.set('_') + =} - reaction(character) -> reset(Converting) {= Converting.set() =} + reaction(character) -> reset(Converting) {= + Converting.set() + =} } } @@ -34,13 +40,19 @@ reactor HistoryProcessor { converter = new Converter() character -> converter.raw converter.converted -> converted - reaction(discard) -> reset(Discarding) {= Discarding.set() =} + reaction(discard) -> reset(Discarding) {= + Discarding.set() + =} } mode Discarding { - reaction(character) -> converted {= converted.set('_') =} + reaction(character) -> converted {= + converted.set('_') + =} - reaction(character) -> history(Converting) {= Converting.set() =} + reaction(character) -> history(Converting) {= + Converting.set() + =} } } @@ -113,7 +125,11 @@ main reactor { history_processor.discard.set(True) =} - reaction(reset_processor.converted) {= print(f"Reset: {reset_processor.converted.value}") =} + reaction(reset_processor.converted) {= + print(f"Reset: {reset_processor.converted.value}") + =} - reaction(history_processor.converted) {= print(f"History: {history_processor.converted.value}") =} + reaction(history_processor.converted) {= + print(f"History: {history_processor.converted.value}") + =} } diff --git a/test/Python/src/modal_models/Count3Modes.lf b/test/Python/src/modal_models/Count3Modes.lf index 8d67270300..6f19128ca3 100644 --- a/test/Python/src/modal_models/Count3Modes.lf +++ b/test/Python/src/modal_models/Count3Modes.lf @@ -36,7 +36,10 @@ main reactor { state expected_value = 1 - reaction(stepper) -> counter.next {= counter.next.set(True) =} # Trigger + # Trigger + reaction(stepper) -> counter.next {= + counter.next.set(True) + =} # Check reaction(stepper) counter.count {= diff --git a/test/Python/src/modal_models/ModalActions.lf b/test/Python/src/modal_models/ModalActions.lf index 5218a1428e..5938061a51 100644 --- a/test/Python/src/modal_models/ModalActions.lf +++ b/test/Python/src/modal_models/ModalActions.lf @@ -89,5 +89,8 @@ main reactor { modal.action2_sched, modal.action2_exec -> test.events - reaction(stepper) -> modal.next {= modal.next.set(True) =} # Trigger mode change + # Trigger mode change + reaction(stepper) -> modal.next {= + modal.next.set(True) + =} } diff --git a/test/Python/src/modal_models/ModalAfter.lf b/test/Python/src/modal_models/ModalAfter.lf index f5e8141e00..a437052dcc 100644 --- a/test/Python/src/modal_models/ModalAfter.lf +++ b/test/Python/src/modal_models/ModalAfter.lf @@ -91,5 +91,8 @@ main reactor { modal.mode_switch, modal.produced1, modal.consumed1, modal.produced2, modal.consumed2 -> test.events - reaction(stepper) -> modal.next {= modal.next.set(True) =} # Trigger mode change + # Trigger mode change + reaction(stepper) -> modal.next {= + modal.next.set(True) + =} } diff --git a/test/Python/src/modal_models/ModalCycleBreaker.lf b/test/Python/src/modal_models/ModalCycleBreaker.lf index 2475366927..61ce4c60d7 100644 --- a/test/Python/src/modal_models/ModalCycleBreaker.lf +++ b/test/Python/src/modal_models/ModalCycleBreaker.lf @@ -28,7 +28,9 @@ reactor Modal { } initial mode One { - reaction(in1) -> out {= out.set(in1.value) =} + reaction(in1) -> out {= + out.set(in1.value) + =} reaction(in1) -> reset(Two) {= if in1.value % 5 == 4: @@ -70,5 +72,8 @@ main reactor { modal.out -> test.events - reaction(modal.out) {= print(modal.out.value) =} # Print + # Print + reaction(modal.out) {= + print(modal.out.value) + =} } diff --git a/test/Python/src/modal_models/ModalNestedReactions.lf b/test/Python/src/modal_models/ModalNestedReactions.lf index 5f30da93b8..a3b89c997a 100644 --- a/test/Python/src/modal_models/ModalNestedReactions.lf +++ b/test/Python/src/modal_models/ModalNestedReactions.lf @@ -29,7 +29,9 @@ reactor CounterCycle { } mode Three { - reaction(next) -> never {= never.set(True) =} + reaction(next) -> never {= + never.set(True) + =} } } @@ -37,14 +39,19 @@ reactor Forward { input inp output out - reaction(inp) -> out {= out.set(inp.value) =} + reaction(inp) -> out {= + out.set(inp.value) + =} } main reactor { timer stepper(0, 250 msec) counter = new CounterCycle() - reaction(stepper) -> counter.next {= counter.next.set(True) =} # Trigger + # Trigger + reaction(stepper) -> counter.next {= + counter.next.set(True) + =} # Check reaction(stepper) counter.count, counter.only_in_two {= diff --git a/test/Python/src/modal_models/ModalStartupShutdown.lf b/test/Python/src/modal_models/ModalStartupShutdown.lf index 8dc7eb8dd1..cd77eb2b1f 100644 --- a/test/Python/src/modal_models/ModalStartupShutdown.lf +++ b/test/Python/src/modal_models/ModalStartupShutdown.lf @@ -137,5 +137,8 @@ main reactor { modal.reset5, modal.shutdown5 -> test.events - reaction(stepper) -> modal.next {= modal.next.set(True) =} # Trigger mode change + # Trigger mode change + reaction(stepper) -> modal.next {= + modal.next.set(True) + =} } diff --git a/test/Python/src/modal_models/ModalStateReset.lf b/test/Python/src/modal_models/ModalStateReset.lf index 5de6b61e35..7ae20ad095 100644 --- a/test/Python/src/modal_models/ModalStateReset.lf +++ b/test/Python/src/modal_models/ModalStateReset.lf @@ -25,7 +25,9 @@ reactor Modal { initial mode One { state counter1 = 0 timer T1(0 msec, 250 msec) - reaction(reset) {= self.counter1 = 0 =} + reaction(reset) {= + self.counter1 = 0 + =} reaction(T1) -> count1 {= print(f"Counter1: {self.counter1}") @@ -43,7 +45,9 @@ reactor Modal { mode Two { state counter2 = -2 timer T2(0 msec, 250 msec) - reaction(reset) {= self.counter2 = -2 =} + reaction(reset) {= + self.counter2 = -2 + =} reaction(T2) -> count2 {= print(f"Counter2: {self.counter2}") @@ -87,5 +91,8 @@ main reactor { modal.mode_switch, modal.count0, modal.count1, modal.count2 -> test.events - reaction(stepper) -> modal.next {= modal.next.set(True) =} # Trigger mode change + # Trigger mode change + reaction(stepper) -> modal.next {= + modal.next.set(True) + =} } diff --git a/test/Python/src/modal_models/ModalStateResetAuto.lf b/test/Python/src/modal_models/ModalStateResetAuto.lf index 492d02f234..947fc4e924 100644 --- a/test/Python/src/modal_models/ModalStateResetAuto.lf +++ b/test/Python/src/modal_models/ModalStateResetAuto.lf @@ -83,5 +83,8 @@ main reactor { modal.mode_switch, modal.count0, modal.count1, modal.count2 -> test.events - reaction(stepper) -> modal.next {= modal.next.set(True) =} # Trigger mode change + # Trigger mode change + reaction(stepper) -> modal.next {= + modal.next.set(True) + =} } diff --git a/test/Python/src/modal_models/ModalTimers.lf b/test/Python/src/modal_models/ModalTimers.lf index f752e025d8..7d2d6c5707 100644 --- a/test/Python/src/modal_models/ModalTimers.lf +++ b/test/Python/src/modal_models/ModalTimers.lf @@ -62,5 +62,8 @@ main reactor { modal.mode_switch, modal.timer1, modal.timer2 -> test.events - reaction(stepper) -> modal.next {= modal.next.set(True) =} # Trigger mode change + # Trigger mode change + reaction(stepper) -> modal.next {= + modal.next.set(True) + =} } diff --git a/test/Python/src/modal_models/MultipleOutputFeeder_2Connections.lf b/test/Python/src/modal_models/MultipleOutputFeeder_2Connections.lf index 47e9c0b783..573d35f432 100644 --- a/test/Python/src/modal_models/MultipleOutputFeeder_2Connections.lf +++ b/test/Python/src/modal_models/MultipleOutputFeeder_2Connections.lf @@ -18,13 +18,17 @@ reactor Modal { initial mode One { counter1 = new Counter(period = 250 msec) counter1.value -> count - reaction(next) -> reset(Two) {= Two.set() =} + reaction(next) -> reset(Two) {= + Two.set() + =} } mode Two { counter2 = new Counter(period = 100 msec) counter2.value -> count - reaction(next) -> history(One) {= One.set() =} + reaction(next) -> history(One) {= + One.set() + =} } } @@ -66,7 +70,13 @@ main reactor { modal.count -> test.events - reaction(stepper) -> modal.next {= modal.next.set(True) =} # Trigger mode change + # Trigger mode change + reaction(stepper) -> modal.next {= + modal.next.set(True) + =} - reaction(modal.count) {= print(modal.count.value) =} # Print + # Print + reaction(modal.count) {= + print(modal.count.value) + =} } diff --git a/test/Python/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf b/test/Python/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf index 5d77127f86..213db4693b 100644 --- a/test/Python/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf +++ b/test/Python/src/modal_models/MultipleOutputFeeder_ReactionConnections.lf @@ -18,14 +18,20 @@ reactor Modal { initial mode One { counter1 = new Counter(period = 250 msec) counter1.value -> count - reaction(next) -> reset(Two) {= Two.set() =} + reaction(next) -> reset(Two) {= + Two.set() + =} } mode Two { counter2 = new Counter(period = 100 msec) - reaction(counter2.value) -> count {= count.set(counter2.value.value * 10) =} + reaction(counter2.value) -> count {= + count.set(counter2.value.value * 10) + =} - reaction(next) -> history(One) {= One.set() =} + reaction(next) -> history(One) {= + One.set() + =} } } @@ -67,7 +73,13 @@ main reactor { modal.count -> test.events - reaction(stepper) -> modal.next {= modal.next.set(True) =} # Trigger mode change + # Trigger mode change + reaction(stepper) -> modal.next {= + modal.next.set(True) + =} - reaction(modal.count) {= print(modal.count.value) =} # Print + # Print + reaction(modal.count) {= + print(modal.count.value) + =} } diff --git a/test/Python/src/modal_models/util/TraceTesting.lf b/test/Python/src/modal_models/util/TraceTesting.lf index 833ffa0ca6..0d5053b844 100644 --- a/test/Python/src/modal_models/util/TraceTesting.lf +++ b/test/Python/src/modal_models/util/TraceTesting.lf @@ -9,7 +9,9 @@ reactor TraceTesting(events_size=0, trace = {= [] =}, training=False) { state recorded_events = {= [] =} state recorded_events_next = 0 - reaction(startup) {= self.last_reaction_time = lf.time.logical() =} + reaction(startup) {= + self.last_reaction_time = lf.time.logical() + =} reaction(events) {= # Time passed since last reaction diff --git a/test/Python/src/multiport/BankIndexInitializer.lf b/test/Python/src/multiport/BankIndexInitializer.lf index babef36a7c..2e02c9f498 100644 --- a/test/Python/src/multiport/BankIndexInitializer.lf +++ b/test/Python/src/multiport/BankIndexInitializer.lf @@ -1,12 +1,16 @@ # Test bank of reactors to multiport input with id parameter in the bank. target Python -preamble {= table = [4, 3, 2, 1] =} +preamble {= + table = [4, 3, 2, 1] +=} reactor Source(bank_index=0, value=0) { output out - reaction(startup) -> out {= out.set(self.value) =} + reaction(startup) -> out {= + out.set(self.value) + =} } reactor Sink(width=4) { diff --git a/test/Python/src/multiport/BankToMultiport.lf b/test/Python/src/multiport/BankToMultiport.lf index 7be7e38e21..bd276a2aa8 100644 --- a/test/Python/src/multiport/BankToMultiport.lf +++ b/test/Python/src/multiport/BankToMultiport.lf @@ -4,7 +4,9 @@ target Python reactor Source(bank_index=0) { output out - reaction(startup) -> out {= out.set(self.bank_index) =} + reaction(startup) -> out {= + out.set(self.bank_index) + =} } reactor Sink(width=4) { diff --git a/test/Python/src/multiport/Broadcast.lf b/test/Python/src/multiport/Broadcast.lf index ad10435c7b..f54f8f4730 100644 --- a/test/Python/src/multiport/Broadcast.lf +++ b/test/Python/src/multiport/Broadcast.lf @@ -6,7 +6,9 @@ target Python { reactor Source(value=42) { output out - reaction(startup) -> out {= out.set(self.value) =} + reaction(startup) -> out {= + out.set(self.value) + =} } reactor Destination(bank_index=0, delay=0) { diff --git a/test/Python/src/multiport/MultiportFromBank.lf b/test/Python/src/multiport/MultiportFromBank.lf index 447f10154a..d41d247fc7 100644 --- a/test/Python/src/multiport/MultiportFromBank.lf +++ b/test/Python/src/multiport/MultiportFromBank.lf @@ -8,7 +8,9 @@ target Python { reactor Source(check_override=0, bank_index=0) { output out - reaction(startup) -> out {= out.set(self.bank_index * self.check_override) =} + reaction(startup) -> out {= + out.set(self.bank_index * self.check_override) + =} } reactor Destination { diff --git a/test/Python/src/multiport/MultiportFromBankHierarchy.lf b/test/Python/src/multiport/MultiportFromBankHierarchy.lf index 253de78c1a..20a66f2e1f 100644 --- a/test/Python/src/multiport/MultiportFromBankHierarchy.lf +++ b/test/Python/src/multiport/MultiportFromBankHierarchy.lf @@ -10,7 +10,9 @@ import Destination from "MultiportFromBank.lf" reactor Source(bank_index=0) { output out - reaction(startup) -> out {= out.set(self.bank_index) =} + reaction(startup) -> out {= + out.set(self.bank_index) + =} } reactor Container { diff --git a/test/Python/src/multiport/MultiportIn.lf b/test/Python/src/multiport/MultiportIn.lf index e72b037bab..3fd77500ff 100644 --- a/test/Python/src/multiport/MultiportIn.lf +++ b/test/Python/src/multiport/MultiportIn.lf @@ -20,7 +20,9 @@ reactor Computation { input _in output out - reaction(_in) -> out {= out.set(_in.value) =} + reaction(_in) -> out {= + out.set(_in.value) + =} } reactor Destination { diff --git a/test/Python/src/multiport/MultiportInParameterized.lf b/test/Python/src/multiport/MultiportInParameterized.lf index d9df047b5a..ece6b51b2a 100644 --- a/test/Python/src/multiport/MultiportInParameterized.lf +++ b/test/Python/src/multiport/MultiportInParameterized.lf @@ -20,7 +20,9 @@ reactor Computation { input _in output out - reaction(_in) -> out {= out.set(_in.value) =} + reaction(_in) -> out {= + out.set(_in.value) + =} } reactor Destination(width=1) { diff --git a/test/Python/src/multiport/MultiportOut.lf b/test/Python/src/multiport/MultiportOut.lf index b84f97989c..29a5816e90 100644 --- a/test/Python/src/multiport/MultiportOut.lf +++ b/test/Python/src/multiport/MultiportOut.lf @@ -21,7 +21,9 @@ reactor Computation { input _in output out - reaction(_in) -> out {= out.set(_in.value) =} + reaction(_in) -> out {= + out.set(_in.value) + =} } reactor Destination { diff --git a/test/Python/src/multiport/PipelineAfter.lf b/test/Python/src/multiport/PipelineAfter.lf index 307a4122cd..b16d20b7a4 100644 --- a/test/Python/src/multiport/PipelineAfter.lf +++ b/test/Python/src/multiport/PipelineAfter.lf @@ -3,14 +3,18 @@ target Python reactor Source { output out - reaction(startup) -> out {= out.set(40) =} + reaction(startup) -> out {= + out.set(40) + =} } reactor Compute { input inp output out - reaction(inp) -> out {= out.set(inp.value + 2) =} + reaction(inp) -> out {= + out.set(inp.value + 2) + =} } reactor Sink { diff --git a/test/Python/src/multiport/ReactionsToNested.lf b/test/Python/src/multiport/ReactionsToNested.lf index b298a85a95..f02fc91bc4 100644 --- a/test/Python/src/multiport/ReactionsToNested.lf +++ b/test/Python/src/multiport/ReactionsToNested.lf @@ -32,7 +32,11 @@ reactor D { main reactor { d = new D() - reaction(startup) -> d.y {= d.y[0].set(42) =} + reaction(startup) -> d.y {= + d.y[0].set(42) + =} - reaction(startup) -> d.y {= d.y[1].set(43) =} + reaction(startup) -> d.y {= + d.y[1].set(43) + =} } diff --git a/test/Python/src/target/AfterNoTypes.lf b/test/Python/src/target/AfterNoTypes.lf index c2be99ef2c..8a00ea94c7 100644 --- a/test/Python/src/target/AfterNoTypes.lf +++ b/test/Python/src/target/AfterNoTypes.lf @@ -9,7 +9,9 @@ reactor Foo { input x output y - reaction(x) -> y {= y.set(2*x.value); =} + reaction(x) -> y {= + y.set(2*x.value); + =} } reactor Print { @@ -46,5 +48,7 @@ main reactor { timer t(0, 1 sec) f.y -> p.x after 10 msec - reaction(t) -> f.x {= f.x.set(42) =} + reaction(t) -> f.x {= + f.x.set(42) + =} } diff --git a/test/Rust/src/ActionImplicitDelay.lf b/test/Rust/src/ActionImplicitDelay.lf index 5ae8557a57..ea33042ea1 100644 --- a/test/Rust/src/ActionImplicitDelay.lf +++ b/test/Rust/src/ActionImplicitDelay.lf @@ -5,7 +5,9 @@ main reactor ActionImplicitDelay { logical action act(40 msec) state count: u64 = 1 - reaction(startup) -> act {= ctx.schedule(act, Asap); =} + reaction(startup) -> act {= + ctx.schedule(act, Asap); + =} reaction(act) -> act {= assert_tag_is!(ctx, T0 + (40 * self.count) ms); diff --git a/test/Rust/src/ActionValuesCleanup.lf b/test/Rust/src/ActionValuesCleanup.lf index 1db2759ad6..407424b218 100644 --- a/test/Rust/src/ActionValuesCleanup.lf +++ b/test/Rust/src/ActionValuesCleanup.lf @@ -22,7 +22,9 @@ main reactor ActionValuesCleanup { logical action act: FooDrop state count: u32 = 0 - reaction(startup) -> act {= ctx.schedule_with_v(act, Some(FooDrop { }), Asap) =} + reaction(startup) -> act {= + ctx.schedule_with_v(act, Some(FooDrop { }), Asap) + =} reaction(act) -> act {= ctx.use_ref(act, |v| println!("{:?}", v)); diff --git a/test/Rust/src/CompositionInitializationOrder.lf b/test/Rust/src/CompositionInitializationOrder.lf index 5bb11f7d9c..1d1106dc58 100644 --- a/test/Rust/src/CompositionInitializationOrder.lf +++ b/test/Rust/src/CompositionInitializationOrder.lf @@ -5,13 +5,19 @@ main reactor CompositionInitializationOrder { c1 = new Component1() c2 = new Component2() - reaction(startup) {= println!("parent woke up"); =} + reaction(startup) {= + println!("parent woke up"); + =} } reactor Component2 { - reaction(startup) {= println!("c2 woke up"); =} + reaction(startup) {= + println!("c2 woke up"); + =} } reactor Component1 { - reaction(startup) {= println!("c1 woke up"); =} + reaction(startup) {= + println!("c1 woke up"); + =} } diff --git a/test/Rust/src/CompositionWithPorts.lf b/test/Rust/src/CompositionWithPorts.lf index 802f8cac21..17715fa5d9 100644 --- a/test/Rust/src/CompositionWithPorts.lf +++ b/test/Rust/src/CompositionWithPorts.lf @@ -3,7 +3,9 @@ target Rust reactor Source { output out: i32 - reaction(startup) -> out {= ctx.set(out, 76600) =} + reaction(startup) -> out {= + ctx.set(out, 76600) + =} } reactor Sink { diff --git a/test/Rust/src/DependencyOnChildPort.lf b/test/Rust/src/DependencyOnChildPort.lf index 2758af8604..0e6dbbda68 100644 --- a/test/Rust/src/DependencyOnChildPort.lf +++ b/test/Rust/src/DependencyOnChildPort.lf @@ -16,9 +16,13 @@ main reactor { box0.out -> box1.inp - reaction(startup) -> box0.inp {= ctx.set(box0__inp, 444); =} + reaction(startup) -> box0.inp {= + ctx.set(box0__inp, 444); + =} - reaction(box1.out) {= assert!(ctx.get_elapsed_logical_time().is_zero()); self.done = true; =} + reaction(box1.out) {= + assert!(ctx.get_elapsed_logical_time().is_zero()); self.done = true; + =} reaction(shutdown) {= assert!(self.done, "reaction was not executed"); diff --git a/test/Rust/src/DependencyThroughChildPort.lf b/test/Rust/src/DependencyThroughChildPort.lf index c8e8bf1592..48b6314363 100644 --- a/test/Rust/src/DependencyThroughChildPort.lf +++ b/test/Rust/src/DependencyThroughChildPort.lf @@ -40,5 +40,7 @@ main reactor { ctx.schedule(repeat, Asap); =} - reaction(repeat) -> d.y {= ctx.set(d__y, 1); =} + reaction(repeat) -> d.y {= + ctx.set(d__y, 1); + =} } diff --git a/test/Rust/src/DependencyUseAccessible.lf b/test/Rust/src/DependencyUseAccessible.lf index 0654d25f60..9a5866d6bf 100644 --- a/test/Rust/src/DependencyUseAccessible.lf +++ b/test/Rust/src/DependencyUseAccessible.lf @@ -8,11 +8,18 @@ reactor Source { timer t1(35 msec) timer t2(70 msec) - reaction(startup) -> clock {= ctx.set(clock, 0); =} + reaction(startup) -> clock {= + ctx.set(clock, 0); + =} - reaction(t1) -> clock, o1 {= ctx.set(clock, 1); ctx.set(o1, 10) =} + reaction(t1) -> clock, o1 {= + ctx.set(clock, 1); ctx.set(o1, 10) + =} - reaction(t2) -> clock, o2 {= ctx.set(clock, 2); =} // has a dependency but doesn't use it + // has a dependency but doesn't use it + reaction(t2) -> clock, o2 {= + ctx.set(clock, 2); + =} } reactor Sink { diff --git a/test/Rust/src/DependencyUseNonTrigger.lf b/test/Rust/src/DependencyUseNonTrigger.lf index 656ad1f768..11da73eb54 100644 --- a/test/Rust/src/DependencyUseNonTrigger.lf +++ b/test/Rust/src/DependencyUseNonTrigger.lf @@ -4,16 +4,22 @@ target Rust reactor Source { output clock: u32 - reaction(startup) -> clock {= ctx.set(clock, 0); =} + reaction(startup) -> clock {= + ctx.set(clock, 0); + =} } reactor Sink { input clock: u32 input bogus: u32 - reaction(bogus) clock {= panic!("Should not be executed") =} + reaction(bogus) clock {= + panic!("Should not be executed") + =} - reaction(shutdown) {= println!("Success") =} + reaction(shutdown) {= + println!("Success") + =} } main reactor { diff --git a/test/Rust/src/Import.lf b/test/Rust/src/Import.lf index c14618ba6e..f2c5549abc 100644 --- a/test/Rust/src/Import.lf +++ b/test/Rust/src/Import.lf @@ -7,5 +7,7 @@ main reactor Import { timer t a = new Imported() - reaction(t) -> a.x {= ctx.set(a__x, 42); =} + reaction(t) -> a.x {= + ctx.set(a__x, 42); + =} } diff --git a/test/Rust/src/ImportPreambleItem.lf b/test/Rust/src/ImportPreambleItem.lf index 922ea74b1a..dbbe1706e6 100644 --- a/test/Rust/src/ImportPreambleItem.lf +++ b/test/Rust/src/ImportPreambleItem.lf @@ -6,5 +6,7 @@ import SomethingWithAPreamble from "lib/SomethingWithAPreamble.lf" main reactor { r = new SomethingWithAPreamble() - reaction(startup) -> r.a {= ctx.set(r__a, super::something_with_a_preamble::some_fun()); =} + reaction(startup) -> r.a {= + ctx.set(r__a, super::something_with_a_preamble::some_fun()); + =} } diff --git a/test/Rust/src/MainReactorParam.lf b/test/Rust/src/MainReactorParam.lf index 2081466a37..90c10f4ea2 100644 --- a/test/Rust/src/MainReactorParam.lf +++ b/test/Rust/src/MainReactorParam.lf @@ -4,5 +4,7 @@ main reactor(one: u64 = 1152921504606846976, two: u64 = {= 1 << 60 =}) { state one = one state two = two - reaction(startup) {= assert_eq!(self.one, self.two); =} + reaction(startup) {= + assert_eq!(self.one, self.two); + =} } diff --git a/test/Rust/src/Minimal.lf b/test/Rust/src/Minimal.lf index 02fd0d3eb3..754d7bb1f8 100644 --- a/test/Rust/src/Minimal.lf +++ b/test/Rust/src/Minimal.lf @@ -2,5 +2,7 @@ target Rust main reactor Minimal { - reaction(startup) {= println!("Hello World."); =} + reaction(startup) {= + println!("Hello World."); + =} } diff --git a/test/Rust/src/MovingAverage.lf b/test/Rust/src/MovingAverage.lf index 22d10cabe0..0c5957b21c 100644 --- a/test/Rust/src/MovingAverage.lf +++ b/test/Rust/src/MovingAverage.lf @@ -17,7 +17,9 @@ reactor Source { } reactor MovingAverageImpl { - state delay_line: {= [f64 ; 4] =} = {= [ 0.0 ; 4 ] =} + state delay_line: {= + [f64 ; 4] + =} = {= [ 0.0 ; 4 ] =} state index: usize = 0 input in_: f64 output out: f64 @@ -40,7 +42,9 @@ reactor Print { input in_: f64 state count: usize = 0 - preamble {= const EXPECTED: [ f64 ; 6 ] = [0.0, 0.25, 0.75, 1.5, 2.5, 3.5]; =} + preamble {= + const EXPECTED: [ f64 ; 6 ] = [0.0, 0.25, 0.75, 1.5, 2.5, 3.5]; + =} reaction(in_) {= let in_ = ctx.get(in_).unwrap(); diff --git a/test/Rust/src/NativeListsAndTimes.lf b/test/Rust/src/NativeListsAndTimes.lf index 5401d6d1b9..d96e0649ba 100644 --- a/test/Rust/src/NativeListsAndTimes.lf +++ b/test/Rust/src/NativeListsAndTimes.lf @@ -30,7 +30,10 @@ reactor Foo( // state baz(p); // Implicit type i32[] fixme this interplays badly with syntax for array init // Implicit type time state period = z - state times: Vec>(q, g) // a list of lists + // a list of lists + state times: Vec>(q, g) /** * reactor Foo (p: i32[](1, 2)) { state baz(p); // Implicit type i32[] state baz({=p=}); // diff --git a/test/Rust/src/PortConnectionInSelfOutSelf.lf b/test/Rust/src/PortConnectionInSelfOutSelf.lf index d398e89f0f..578255d43f 100644 --- a/test/Rust/src/PortConnectionInSelfOutSelf.lf +++ b/test/Rust/src/PortConnectionInSelfOutSelf.lf @@ -26,7 +26,9 @@ reactor Sink { self.done = true; =} - reaction(shutdown) {= assert!(self.done, "reaction was not executed") =} + reaction(shutdown) {= + assert!(self.done, "reaction was not executed") + =} } main reactor { diff --git a/test/Rust/src/PortConnectionOutChildOutSelf.lf b/test/Rust/src/PortConnectionOutChildOutSelf.lf index 6935eb4256..0aef3570f4 100644 --- a/test/Rust/src/PortConnectionOutChildOutSelf.lf +++ b/test/Rust/src/PortConnectionOutChildOutSelf.lf @@ -27,7 +27,9 @@ reactor Sink { self.done = true; =} - reaction(shutdown) {= assert!(self.done, "reaction was not executed") =} + reaction(shutdown) {= + assert!(self.done, "reaction was not executed") + =} } main reactor { @@ -41,5 +43,7 @@ main reactor { self.done = true; =} - reaction(shutdown) {= assert!(self.done, "reaction was not executed") =} + reaction(shutdown) {= + assert!(self.done, "reaction was not executed") + =} } diff --git a/test/Rust/src/PortValueCleanup.lf b/test/Rust/src/PortValueCleanup.lf index ea0c9f50a0..13c35ac80c 100644 --- a/test/Rust/src/PortValueCleanup.lf +++ b/test/Rust/src/PortValueCleanup.lf @@ -4,7 +4,9 @@ target Rust reactor Source { output out: u32 - reaction(startup) -> out {= ctx.set(out, 150); =} + reaction(startup) -> out {= + ctx.set(out, 150); + =} } reactor Sink { diff --git a/test/Rust/src/Preamble.lf b/test/Rust/src/Preamble.lf index 3f195808d7..01fc117db3 100644 --- a/test/Rust/src/Preamble.lf +++ b/test/Rust/src/Preamble.lf @@ -7,5 +7,7 @@ main reactor Preamble { } =} - reaction(startup) {= println!("42 plus 42 is {}.\n", add_42(42)); =} + reaction(startup) {= + println!("42 plus 42 is {}.\n", add_42(42)); + =} } diff --git a/test/Rust/src/ReactionLabels.lf b/test/Rust/src/ReactionLabels.lf index 41c7ec57dd..0b3937bf6a 100644 --- a/test/Rust/src/ReactionLabels.lf +++ b/test/Rust/src/ReactionLabels.lf @@ -5,5 +5,8 @@ target Rust main reactor { timer t(0) - reaction(t) {= println!("success"); =} // @label foo + // @label foo + reaction(t) {= + println!("success"); + =} } diff --git a/test/Rust/src/SingleFileGeneration.lf b/test/Rust/src/SingleFileGeneration.lf index c88a4bdcbd..1fb32780a4 100644 --- a/test/Rust/src/SingleFileGeneration.lf +++ b/test/Rust/src/SingleFileGeneration.lf @@ -6,7 +6,9 @@ target Rust { reactor Source { output out: i32 - reaction(startup) -> out {= ctx.set(out, 76600) =} + reaction(startup) -> out {= + ctx.set(out, 76600) + =} } reactor Sink { diff --git a/test/Rust/src/StopNoEvent.lf b/test/Rust/src/StopNoEvent.lf index 9dc96d3533..3c93d5d205 100644 --- a/test/Rust/src/StopNoEvent.lf +++ b/test/Rust/src/StopNoEvent.lf @@ -2,5 +2,7 @@ target Rust main reactor StopNoEvent { - reaction(shutdown) {= println!("success"); =} + reaction(shutdown) {= + println!("success"); + =} } diff --git a/test/Rust/src/StructAsType.lf b/test/Rust/src/StructAsType.lf index 867651e8de..893a1ed8a8 100644 --- a/test/Rust/src/StructAsType.lf +++ b/test/Rust/src/StructAsType.lf @@ -20,7 +20,9 @@ reactor Source { // expected parameter is for testing. reactor Print(expected: i32 = 42) { - input inp: {= super::source::Hello =} + input inp: {= + super::source::Hello + =} state expected: i32 = expected reaction(inp) {= diff --git a/test/Rust/src/TimeState.lf b/test/Rust/src/TimeState.lf index 3de9934e28..d6195c8d10 100644 --- a/test/Rust/src/TimeState.lf +++ b/test/Rust/src/TimeState.lf @@ -3,7 +3,9 @@ target Rust reactor Foo { state baz: time = 500 msec - reaction(startup) {= assert_eq!(500, self.baz.as_millis()); =} + reaction(startup) {= + assert_eq!(500, self.baz.as_millis()); + =} } main reactor TimeState { diff --git a/test/Rust/src/Timers.lf b/test/Rust/src/Timers.lf index 16b359966c..05ba51003c 100644 --- a/test/Rust/src/Timers.lf +++ b/test/Rust/src/Timers.lf @@ -8,9 +8,13 @@ main reactor Timers { timer t2(0, 2 sec) state counter: i32 = 0 - reaction(t2) {= self.counter += 2; =} + reaction(t2) {= + self.counter += 2; + =} - reaction(t) {= self.counter -= 1; =} + reaction(t) {= + self.counter -= 1; + =} reaction(shutdown) {= assert_eq!(1, self.counter); diff --git a/test/Rust/src/concurrent/AsyncCallback.lf b/test/Rust/src/concurrent/AsyncCallback.lf index c6e42c3909..33bc8b9254 100644 --- a/test/Rust/src/concurrent/AsyncCallback.lf +++ b/test/Rust/src/concurrent/AsyncCallback.lf @@ -5,7 +5,9 @@ target Rust { } main reactor AsyncCallback(period: time = 10 msec) { - preamble {= use std::thread; =} + preamble {= + use std::thread; + =} timer t(0, period) state thread: Option> diff --git a/test/Rust/src/generics/CtorParamGeneric.lf b/test/Rust/src/generics/CtorParamGeneric.lf index 2104da5f54..fe9d2a4d90 100644 --- a/test/Rust/src/generics/CtorParamGeneric.lf +++ b/test/Rust/src/generics/CtorParamGeneric.lf @@ -1,8 +1,9 @@ // tests that ctor parameters may refer to type parameters. target Rust -reactor Generic<{= T: Default + Eq + Sync + std::fmt::Debug =}>( - value: T = {= Default::default() =}) { +reactor Generic<{= + T: Default + Eq + Sync + std::fmt::Debug +=}>(value: T = {= Default::default() =}) { input in: T state v: T = value @@ -17,5 +18,7 @@ reactor Generic<{= T: Default + Eq + Sync + std::fmt::Debug =}>( main reactor { p = new Generic(value=23) - reaction(startup) -> p.in {= ctx.set(p__in, 23); =} + reaction(startup) -> p.in {= + ctx.set(p__in, 23); + =} } diff --git a/test/Rust/src/generics/CtorParamGenericInst.lf b/test/Rust/src/generics/CtorParamGenericInst.lf index 1471178a17..de02ca156e 100644 --- a/test/Rust/src/generics/CtorParamGenericInst.lf +++ b/test/Rust/src/generics/CtorParamGenericInst.lf @@ -2,8 +2,9 @@ // argument list of a further child instance. target Rust -reactor Generic2<{= T: Default + Eq + Sync + std::fmt::Debug + Send + 'static =}>( - value: T = {= Default::default() =}) { +reactor Generic2<{= + T: Default + Eq + Sync + std::fmt::Debug + Send + 'static +=}>(value: T = {= Default::default() =}) { input in: T state v: T = value @@ -15,8 +16,9 @@ reactor Generic2<{= T: Default + Eq + Sync + std::fmt::Debug + Send + 'static =} =} } -reactor Generic<{= T: Default + Eq + Sync + std::fmt::Debug + Copy + Send + 'static =}>( - value: T = {= Default::default() =}) { +reactor Generic<{= + T: Default + Eq + Sync + std::fmt::Debug + Copy + Send + 'static +=}>(value: T = {= Default::default() =}) { input in: T inner = new Generic2(value=value) @@ -27,5 +29,7 @@ reactor Generic<{= T: Default + Eq + Sync + std::fmt::Debug + Copy + Send + 'sta main reactor { p = new Generic(value=23) - reaction(startup) -> p.in {= ctx.set(p__in, 23); =} + reaction(startup) -> p.in {= + ctx.set(p__in, 23); + =} } diff --git a/test/Rust/src/generics/GenericComplexType.lf b/test/Rust/src/generics/GenericComplexType.lf index d427f65b25..6e534117c3 100644 --- a/test/Rust/src/generics/GenericComplexType.lf +++ b/test/Rust/src/generics/GenericComplexType.lf @@ -18,5 +18,7 @@ reactor R { main reactor { p = new R() - reaction(startup) -> p.in {= ctx.set(p__in, vec![delay!(20 ms)]); =} + reaction(startup) -> p.in {= + ctx.set(p__in, vec![delay!(20 ms)]); + =} } diff --git a/test/Rust/src/generics/GenericReactor.lf b/test/Rust/src/generics/GenericReactor.lf index eaff12a4c9..251fefcc69 100644 --- a/test/Rust/src/generics/GenericReactor.lf +++ b/test/Rust/src/generics/GenericReactor.lf @@ -1,7 +1,9 @@ // Tests a port connection between (input of self -> input of child) target Rust -reactor Box<{= T: Sync =}> { +reactor Box<{= + T: Sync +=}> { input inp: T output out: T @@ -16,9 +18,13 @@ main reactor { box0.out -> box1.inp - reaction(startup) -> box0.inp {= ctx.set(box0__inp, 444); =} + reaction(startup) -> box0.inp {= + ctx.set(box0__inp, 444); + =} - reaction(box1.out) {= assert!(ctx.get_elapsed_logical_time().is_zero()); self.done = true; =} + reaction(box1.out) {= + assert!(ctx.get_elapsed_logical_time().is_zero()); self.done = true; + =} reaction(shutdown) {= assert!(self.done, "reaction was not executed"); diff --git a/test/Rust/src/lib/Imported.lf b/test/Rust/src/lib/Imported.lf index b72cfdf533..bf42bce770 100644 --- a/test/Rust/src/lib/Imported.lf +++ b/test/Rust/src/lib/Imported.lf @@ -8,5 +8,7 @@ reactor Imported { input x: u32 a = new ImportedAgain() - reaction(x) -> a.x {= ctx.set(a__x, ctx.get(x).unwrap()); =} + reaction(x) -> a.x {= + ctx.set(a__x, ctx.get(x).unwrap()); + =} } diff --git a/test/Rust/src/multiport/ConnectionToSelfBank.lf b/test/Rust/src/multiport/ConnectionToSelfBank.lf index c396bcb5e6..d4b883de8c 100644 --- a/test/Rust/src/multiport/ConnectionToSelfBank.lf +++ b/test/Rust/src/multiport/ConnectionToSelfBank.lf @@ -6,7 +6,9 @@ reactor Node(bank_index: usize = 0, num_nodes: usize = 4) { state bank_index = bank_index state num_nodes = num_nodes - reaction(startup) -> out {= ctx.set(out, self.bank_index); =} + reaction(startup) -> out {= + ctx.set(out, self.bank_index); + =} reaction(in) {= let count = r#in.iterate_set().count(); diff --git a/test/Rust/src/multiport/CycledLhs_SelfLoop.lf b/test/Rust/src/multiport/CycledLhs_SelfLoop.lf index efa3e13c4e..7d41b8ea3e 100644 --- a/test/Rust/src/multiport/CycledLhs_SelfLoop.lf +++ b/test/Rust/src/multiport/CycledLhs_SelfLoop.lf @@ -10,9 +10,13 @@ reactor Test { logical action act: u32 state last: u32 = 1 - reaction(startup) -> act {= ctx.schedule_with_v(act, Some(1), after!(1 us)); =} + reaction(startup) -> act {= + ctx.schedule_with_v(act, Some(1), after!(1 us)); + =} - reaction(act) -> out {= ctx.set_opt(out, ctx.get(act)); =} + reaction(act) -> out {= + ctx.set_opt(out, ctx.get(act)); + =} reaction(in) -> act {= let sum: u32 = r#in.iterate_values().sum(); diff --git a/test/Rust/src/multiport/CycledLhs_Single.lf b/test/Rust/src/multiport/CycledLhs_Single.lf index 32c406b692..4d8931e9d7 100644 --- a/test/Rust/src/multiport/CycledLhs_Single.lf +++ b/test/Rust/src/multiport/CycledLhs_Single.lf @@ -7,10 +7,14 @@ target Rust { reactor Test { output[2] out: u32 input[4] in: u32 - logical action act: {= (u32, u32) =} + logical action act: {= + (u32, u32) + =} state last: u32 = 1 - reaction(startup) -> act {= ctx.schedule_with_v(act, Some((0, 1)), after!(1 us)); =} + reaction(startup) -> act {= + ctx.schedule_with_v(act, Some((0, 1)), after!(1 us)); + =} reaction(act) -> out {= let (a, b) = ctx.get(act).unwrap(); diff --git a/test/Rust/src/multiport/FullyConnected.lf b/test/Rust/src/multiport/FullyConnected.lf index e83a0cac3d..50fea84f18 100644 --- a/test/Rust/src/multiport/FullyConnected.lf +++ b/test/Rust/src/multiport/FullyConnected.lf @@ -5,7 +5,9 @@ reactor Left(bank_index: usize = 0) { output out: usize state bank_index = bank_index - reaction(startup) -> out {= ctx.set(out, self.bank_index); =} + reaction(startup) -> out {= + ctx.set(out, self.bank_index); + =} } reactor Right(bank_index: usize = 0, num_nodes: usize = 4) { diff --git a/test/Rust/src/multiport/MultiportFromBank.lf b/test/Rust/src/multiport/MultiportFromBank.lf index cf66939562..3e0afdaedd 100644 --- a/test/Rust/src/multiport/MultiportFromBank.lf +++ b/test/Rust/src/multiport/MultiportFromBank.lf @@ -7,7 +7,9 @@ reactor Source(bank_index: usize = 0) { output out: usize state bank_index = bank_index - reaction(startup) -> out {= ctx.set(out, self.bank_index); =} + reaction(startup) -> out {= + ctx.set(out, self.bank_index); + =} } reactor Destination(port_width: usize = 2) { diff --git a/test/Rust/src/multiport/ReadOutputOfContainedBank.lf b/test/Rust/src/multiport/ReadOutputOfContainedBank.lf index 2d97ad74d7..4b47f97d3f 100644 --- a/test/Rust/src/multiport/ReadOutputOfContainedBank.lf +++ b/test/Rust/src/multiport/ReadOutputOfContainedBank.lf @@ -6,7 +6,9 @@ reactor Contained(bank_index: usize = 0) { output out: usize - reaction(startup) -> out {= ctx.set(out, 42 * self.bank_index); =} + reaction(startup) -> out {= + ctx.set(out, 42 * self.bank_index); + =} } main reactor { diff --git a/test/Rust/src/multiport/WidthWithParameter.lf b/test/Rust/src/multiport/WidthWithParameter.lf index 572da15222..4152e585fc 100644 --- a/test/Rust/src/multiport/WidthWithParameter.lf +++ b/test/Rust/src/multiport/WidthWithParameter.lf @@ -13,5 +13,7 @@ reactor Some(value: usize = 30) { main reactor { some = new Some(value=20) - reaction(some.finished) {= println!("success"); =} + reaction(some.finished) {= + println!("success"); + =} } diff --git a/test/Rust/src/multiport/WriteInputOfContainedBank.lf b/test/Rust/src/multiport/WriteInputOfContainedBank.lf index f44914bd5d..01a96e02bc 100644 --- a/test/Rust/src/multiport/WriteInputOfContainedBank.lf +++ b/test/Rust/src/multiport/WriteInputOfContainedBank.lf @@ -14,7 +14,9 @@ reactor Contained(bank_index: usize = 0) { self.count += 1; =} - reaction(shutdown) {= assert_eq!(self.count, 1, "One of the reactions failed to trigger"); =} + reaction(shutdown) {= + assert_eq!(self.count, 1, "One of the reactions failed to trigger"); + =} } main reactor { diff --git a/test/Rust/src/target/CargoDependencyOnRuntime.lf b/test/Rust/src/target/CargoDependencyOnRuntime.lf index 26aa2c5eb3..946ba7ab04 100644 --- a/test/Rust/src/target/CargoDependencyOnRuntime.lf +++ b/test/Rust/src/target/CargoDependencyOnRuntime.lf @@ -8,5 +8,7 @@ target Rust { } main reactor { - reaction(startup) {= println!("success") =} + reaction(startup) {= + println!("success") + =} } diff --git a/test/Rust/src/target/CliFeature.lf b/test/Rust/src/target/CliFeature.lf index a28f8d2e3d..d54f09c9cf 100644 --- a/test/Rust/src/target/CliFeature.lf +++ b/test/Rust/src/target/CliFeature.lf @@ -5,5 +5,7 @@ target Rust { // todo allow test framework to pass CLI arguments. main reactor CliFeature(size: u32 = 4, t: time = 4 sec) { - reaction(startup) {= println!("success"); =} + reaction(startup) {= + println!("success"); + =} } diff --git a/test/TypeScript/src/ActionDelay.lf b/test/TypeScript/src/ActionDelay.lf index 18949417d3..f2c723b601 100644 --- a/test/TypeScript/src/ActionDelay.lf +++ b/test/TypeScript/src/ActionDelay.lf @@ -12,13 +12,17 @@ reactor GeneratedDelay { actions.act.schedule(0, null); =} - reaction(act) -> y_out {= y_out = y_state; =} + reaction(act) -> y_out {= + y_out = y_state; + =} } reactor Source { output out: number - reaction(startup) -> out {= out = 1; =} + reaction(startup) -> out {= + out = 1; + =} } reactor Sink { diff --git a/test/TypeScript/src/ActionWithNoReaction.lf b/test/TypeScript/src/ActionWithNoReaction.lf index 14d6aa8801..c8055713db 100644 --- a/test/TypeScript/src/ActionWithNoReaction.lf +++ b/test/TypeScript/src/ActionWithNoReaction.lf @@ -32,5 +32,7 @@ main reactor { timer t(0, 1 sec) f.y -> p.x after 10 msec - reaction(t) -> f.x {= f.x = 42; =} + reaction(t) -> f.x {= + f.x = 42; + =} } diff --git a/test/TypeScript/src/After.lf b/test/TypeScript/src/After.lf index 30edbdb554..f65ab6bbb3 100644 --- a/test/TypeScript/src/After.lf +++ b/test/TypeScript/src/After.lf @@ -8,7 +8,9 @@ reactor Foo { input x: number output y: number - reaction(x) -> y {= y = 2 * (x as number); =} + reaction(x) -> y {= + y = 2 * (x as number); + =} } reactor Print { @@ -33,5 +35,7 @@ main reactor { timer t(0, 1 sec) f.y -> p.x after 10 msec - reaction(t) -> f.x {= f.x = 42; =} + reaction(t) -> f.x {= + f.x = 42; + =} } diff --git a/test/TypeScript/src/ArrayAsParameter.lf b/test/TypeScript/src/ArrayAsParameter.lf index ef9ba9f414..150213d47c 100644 --- a/test/TypeScript/src/ArrayAsParameter.lf +++ b/test/TypeScript/src/ArrayAsParameter.lf @@ -1,7 +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 logical action next diff --git a/test/TypeScript/src/ArrayAsType.lf b/test/TypeScript/src/ArrayAsType.lf index c26e3fe02e..f40d6d91e0 100644 --- a/test/TypeScript/src/ArrayAsType.lf +++ b/test/TypeScript/src/ArrayAsType.lf @@ -3,7 +3,9 @@ target TypeScript reactor Source { - output out: {= Array =} + output out: {= + Array + =} reaction(startup) -> out {= let toSend = []; @@ -16,7 +18,9 @@ reactor Source { // The scale parameter is just for testing. reactor Print(scale: number = 1) { - input x: {= Array =} + input x: {= + Array + =} reaction(x) {= let count = 0; // For testing. diff --git a/test/TypeScript/src/ArrayPrint.lf b/test/TypeScript/src/ArrayPrint.lf index 3c94b00b34..7967efb290 100644 --- a/test/TypeScript/src/ArrayPrint.lf +++ b/test/TypeScript/src/ArrayPrint.lf @@ -3,7 +3,9 @@ target TypeScript reactor Source { - output out: {= Array =} + output out: {= + Array + =} reaction(startup) -> out {= let toSend = new Array(); @@ -16,7 +18,9 @@ reactor Source { // The scale parameter is just for testing. reactor Print(scale: number = 1) { - input x: {= Array =} + input x: {= + Array + =} reaction(x) {= let count = 0; // For testing. diff --git a/test/TypeScript/src/ArrayScale.lf b/test/TypeScript/src/ArrayScale.lf index 2ddfa951f3..45c982f493 100644 --- a/test/TypeScript/src/ArrayScale.lf +++ b/test/TypeScript/src/ArrayScale.lf @@ -6,8 +6,12 @@ target TypeScript import Source, Print from "ArrayPrint.lf" reactor Scale(scale: number = 2) { - mutable input x: {= Array =} - output out: {= Array =} + mutable input x: {= + Array + =} + output out: {= + Array + =} reaction(x) -> out {= x = x as Array; diff --git a/test/TypeScript/src/DanglingOutput.lf b/test/TypeScript/src/DanglingOutput.lf index 8c9f884dd0..a55312ca40 100644 --- a/test/TypeScript/src/DanglingOutput.lf +++ b/test/TypeScript/src/DanglingOutput.lf @@ -6,7 +6,9 @@ reactor Source { output out: number timer t - reaction(t) -> out {= out = 1; =} + reaction(t) -> out {= + out = 1; + =} } reactor Gain { diff --git a/test/TypeScript/src/DelayInt.lf b/test/TypeScript/src/DelayInt.lf index 4cf2b5a8cc..1ff1df7871 100644 --- a/test/TypeScript/src/DelayInt.lf +++ b/test/TypeScript/src/DelayInt.lf @@ -7,7 +7,9 @@ reactor Delay(delay: time = 100 msec) { output out: number logical action a: number - reaction(x) -> a {= actions.a.schedule( delay, x as number); =} + reaction(x) -> a {= + actions.a.schedule( delay, x as number); + =} reaction(a) -> out {= if (a !== null){ @@ -54,5 +56,7 @@ main reactor DelayInt { t = new Test() d.out -> t.x - reaction(startup) -> d.x {= d.x = 42; =} + reaction(startup) -> d.x {= + d.x = 42; + =} } diff --git a/test/TypeScript/src/DelayedAction.lf b/test/TypeScript/src/DelayedAction.lf index 1979003351..de433faf58 100644 --- a/test/TypeScript/src/DelayedAction.lf +++ b/test/TypeScript/src/DelayedAction.lf @@ -7,7 +7,9 @@ main reactor DelayedAction { logical action a state count: number = 0 - reaction(t) -> a {= actions.a.schedule(TimeValue.msec(100), null); =} + reaction(t) -> a {= + actions.a.schedule(TimeValue.msec(100), null); + =} reaction(a) {= let elapsedLogical = util.getElapsedLogicalTime(); diff --git a/test/TypeScript/src/DelayedReaction.lf b/test/TypeScript/src/DelayedReaction.lf index 66a721069b..9a1e31585e 100644 --- a/test/TypeScript/src/DelayedReaction.lf +++ b/test/TypeScript/src/DelayedReaction.lf @@ -5,7 +5,9 @@ reactor Source { output out: number timer t - reaction(t) -> out {= out = 1; =} + reaction(t) -> out {= + out = 1; + =} } reactor Sink { diff --git a/test/TypeScript/src/Determinism.lf b/test/TypeScript/src/Determinism.lf index 5b9d6de201..ed110b0334 100644 --- a/test/TypeScript/src/Determinism.lf +++ b/test/TypeScript/src/Determinism.lf @@ -4,7 +4,9 @@ reactor Source { output y: number timer t - reaction(t) -> y {= y = 1; =} + reaction(t) -> y {= + y = 1; + =} } reactor Destination { @@ -30,7 +32,9 @@ reactor Pass { input x: number output y: number - reaction(x) -> y {= y = x as number; =} + reaction(x) -> y {= + y = x as number; + =} } main reactor Determinism { diff --git a/test/TypeScript/src/Gain.lf b/test/TypeScript/src/Gain.lf index bcb17bd4eb..c7b77cd31e 100644 --- a/test/TypeScript/src/Gain.lf +++ b/test/TypeScript/src/Gain.lf @@ -5,7 +5,9 @@ reactor Scale(scale: number = 2) { input x: number output y: number - reaction(x) -> y {= y = (x as number) * scale; =} + reaction(x) -> y {= + y = (x as number) * scale; + =} } reactor Test { @@ -34,5 +36,7 @@ main reactor Gain { d = new Test() g.y -> d.x - reaction(startup) -> g.x {= g.x = 1; =} + reaction(startup) -> g.x {= + g.x = 1; + =} } diff --git a/test/TypeScript/src/HelloWorld.lf b/test/TypeScript/src/HelloWorld.lf index 2fe705929b..2862c01297 100644 --- a/test/TypeScript/src/HelloWorld.lf +++ b/test/TypeScript/src/HelloWorld.lf @@ -3,7 +3,9 @@ target TypeScript reactor HelloWorldInside { timer t - reaction(t) {= console.log("Hello World."); =} + reaction(t) {= + console.log("Hello World."); + =} } main reactor HelloWorld { diff --git a/test/TypeScript/src/Hierarchy2.lf b/test/TypeScript/src/Hierarchy2.lf index 017c507b73..b401baf186 100644 --- a/test/TypeScript/src/Hierarchy2.lf +++ b/test/TypeScript/src/Hierarchy2.lf @@ -8,7 +8,9 @@ reactor Source { output out: number timer t(0, 1 sec) - reaction(t) -> out {= out = 1; =} + reaction(t) -> out {= + out = 1; + =} } reactor Count { diff --git a/test/TypeScript/src/Import.lf b/test/TypeScript/src/Import.lf index aecf259f71..4fc1e28e93 100644 --- a/test/TypeScript/src/Import.lf +++ b/test/TypeScript/src/Import.lf @@ -9,5 +9,7 @@ main reactor Import { timer t a = new Imported() - reaction(t) -> a.x {= a.x = 42; =} + reaction(t) -> a.x {= + a.x = 42; + =} } diff --git a/test/TypeScript/src/Microsteps.lf b/test/TypeScript/src/Microsteps.lf index c5412a6b06..96ce4da40d 100644 --- a/test/TypeScript/src/Microsteps.lf +++ b/test/TypeScript/src/Microsteps.lf @@ -35,5 +35,7 @@ main reactor Microsteps { actions.repeat.schedule(0, null); =} - reaction(repeat) -> d.y {= d.y = 1; =} + reaction(repeat) -> d.y {= + d.y = 1; + =} } diff --git a/test/TypeScript/src/Minimal.lf b/test/TypeScript/src/Minimal.lf index 54a1aa7ae9..b0f75adccc 100644 --- a/test/TypeScript/src/Minimal.lf +++ b/test/TypeScript/src/Minimal.lf @@ -4,5 +4,7 @@ target TypeScript main reactor Minimal { timer t - reaction(t) {= console.log("Hello World."); =} + reaction(t) {= + console.log("Hello World."); + =} } diff --git a/test/TypeScript/src/MovingAverage.lf b/test/TypeScript/src/MovingAverage.lf index f99e436ab7..38ddde1504 100644 --- a/test/TypeScript/src/MovingAverage.lf +++ b/test/TypeScript/src/MovingAverage.lf @@ -17,7 +17,9 @@ reactor Source { } reactor MovingAverageImpl { - state delay_line: {= Array =} = {= [0.0, 0.0, 0.0] =} + state delay_line: {= + Array + =} = {= [0.0, 0.0, 0.0] =} state index: number = 0 input x: number output out: number diff --git a/test/TypeScript/src/MultipleContained.lf b/test/TypeScript/src/MultipleContained.lf index a3ae63b2cf..cabacf59ea 100644 --- a/test/TypeScript/src/MultipleContained.lf +++ b/test/TypeScript/src/MultipleContained.lf @@ -6,7 +6,9 @@ reactor Contained { input in1: number input in2: number - reaction(startup) -> trigger {= trigger = 42; =} + reaction(startup) -> trigger {= + trigger = 42; + =} reaction(in1) {= in1 = in1 as number; diff --git a/test/TypeScript/src/ParameterizedState.lf b/test/TypeScript/src/ParameterizedState.lf index 2904b30106..f7ef0dd4d2 100644 --- a/test/TypeScript/src/ParameterizedState.lf +++ b/test/TypeScript/src/ParameterizedState.lf @@ -3,7 +3,9 @@ target TypeScript reactor Foo(bar: number = 42) { state baz = bar - reaction(startup) {= console.log("Baz: " + baz); =} + reaction(startup) {= + console.log("Baz: " + baz); + =} } main reactor { diff --git a/test/TypeScript/src/PhysicalConnection.lf b/test/TypeScript/src/PhysicalConnection.lf index 495d7bc0e0..8767075ce7 100644 --- a/test/TypeScript/src/PhysicalConnection.lf +++ b/test/TypeScript/src/PhysicalConnection.lf @@ -4,7 +4,9 @@ target TypeScript reactor Source { output y: number - reaction(startup) -> y {= y = 42 =} + reaction(startup) -> y {= + y = 42 + =} } reactor Destination { diff --git a/test/TypeScript/src/ReadOutputOfContainedReactor.lf b/test/TypeScript/src/ReadOutputOfContainedReactor.lf index 9009141181..11c8ebd2e4 100644 --- a/test/TypeScript/src/ReadOutputOfContainedReactor.lf +++ b/test/TypeScript/src/ReadOutputOfContainedReactor.lf @@ -4,7 +4,9 @@ target TypeScript reactor Contained { output out: number - reaction(startup) -> out {= out = 42; =} + reaction(startup) -> out {= + out = 42; + =} } main reactor ReadOutputOfContainedReactor { diff --git a/test/TypeScript/src/Schedule.lf b/test/TypeScript/src/Schedule.lf index a6b48a14fb..cd4f7e85e7 100644 --- a/test/TypeScript/src/Schedule.lf +++ b/test/TypeScript/src/Schedule.lf @@ -5,7 +5,9 @@ reactor ScheduleLogicalAction { input x: number logical action a - reaction(x) -> a {= actions.a.schedule(TimeValue.msec(200), null) =} + reaction(x) -> a {= + actions.a.schedule(TimeValue.msec(200), null) + =} reaction(a) {= let elapsedTime = util.getElapsedLogicalTime(); @@ -22,5 +24,7 @@ main reactor { a = new ScheduleLogicalAction() timer t - reaction(t) -> a.x {= a.x = 1; =} + reaction(t) -> a.x {= + a.x = 1; + =} } diff --git a/test/TypeScript/src/ScheduleLogicalAction.lf b/test/TypeScript/src/ScheduleLogicalAction.lf index b165588a1b..5340847280 100644 --- a/test/TypeScript/src/ScheduleLogicalAction.lf +++ b/test/TypeScript/src/ScheduleLogicalAction.lf @@ -17,7 +17,9 @@ reactor foo { actions.a.schedule(TimeValue.msec(500), null); =} - reaction(a) -> y {= y = -42; =} + reaction(a) -> y {= + y = -42; + =} } reactor print { @@ -42,5 +44,7 @@ main reactor { timer t(0, 1 sec) f.y -> p.x - reaction(t) -> f.x {= f.x = 42; =} + reaction(t) -> f.x {= + f.x = 42; + =} } diff --git a/test/TypeScript/src/SendingInside2.lf b/test/TypeScript/src/SendingInside2.lf index 0271112da6..e1e8f9dadc 100644 --- a/test/TypeScript/src/SendingInside2.lf +++ b/test/TypeScript/src/SendingInside2.lf @@ -15,5 +15,7 @@ main reactor SendingInside2 { timer t p = new Printer() - reaction(t) -> p.x {= p.x = 1; =} + reaction(t) -> p.x {= + p.x = 1; + =} } diff --git a/test/TypeScript/src/SendsPointerTest.lf b/test/TypeScript/src/SendsPointerTest.lf index 2808d2a87b..68cf14b4cd 100644 --- a/test/TypeScript/src/SendsPointerTest.lf +++ b/test/TypeScript/src/SendsPointerTest.lf @@ -3,7 +3,9 @@ target TypeScript reactor SendsPointer { - output out: {= {value: number} =} + output out: {= + {value: number} + =} reaction(startup) -> out {= let my_object = { value: 42 }; @@ -12,8 +14,12 @@ reactor SendsPointer { } // expected parameter is for testing. -reactor Print(expected: {= {value: number} =} = {= { value: 42 } =}) { - input x: {= {value: number} =} +reactor Print(expected: {= + {value: number} +=} = {= { value: 42 } =}) { + input x: {= + {value: number} + =} reaction(x) {= x = x as {value: number}; diff --git a/test/TypeScript/src/SlowingClock.lf b/test/TypeScript/src/SlowingClock.lf index 522778f2d7..0d0fb70a1e 100644 --- a/test/TypeScript/src/SlowingClock.lf +++ b/test/TypeScript/src/SlowingClock.lf @@ -8,7 +8,9 @@ main reactor SlowingClock { state interval: time = 100 msec state expected_time: time = 100 msec - reaction(startup) -> a {= actions.a.schedule(0, null); =} + reaction(startup) -> a {= + actions.a.schedule(0, null); + =} reaction(a) -> a {= let elapsed_logical_time : TimeValue = util.getElapsedLogicalTime(); diff --git a/test/TypeScript/src/Stride.lf b/test/TypeScript/src/Stride.lf index a8413a6aee..03b14b71e2 100644 --- a/test/TypeScript/src/Stride.lf +++ b/test/TypeScript/src/Stride.lf @@ -19,7 +19,9 @@ reactor Count(stride: number = 1) { reactor Display { input x: number - reaction(x) {= console.log("Received: " + x); =} + reaction(x) {= + console.log("Received: " + x); + =} } main reactor Stride { diff --git a/test/TypeScript/src/TimeLimit.lf b/test/TypeScript/src/TimeLimit.lf index 42ec3391ae..0b6985d985 100644 --- a/test/TypeScript/src/TimeLimit.lf +++ b/test/TypeScript/src/TimeLimit.lf @@ -37,5 +37,7 @@ main reactor TimeLimit(period: time = 1 msec) { d = new Destination() c.y -> d.x - reaction(stop) {= util.requestStop() =} + reaction(stop) {= + util.requestStop() + =} } diff --git a/test/TypeScript/src/TimeState.lf b/test/TypeScript/src/TimeState.lf index f71374fd59..1566c891bd 100644 --- a/test/TypeScript/src/TimeState.lf +++ b/test/TypeScript/src/TimeState.lf @@ -3,7 +3,9 @@ target TypeScript reactor Foo(bar: number = 42) { state baz: time = 500 msec - reaction(startup) {= console.log("Baz: " + baz); =} + reaction(startup) {= + console.log("Baz: " + baz); + =} } main reactor { diff --git a/test/TypeScript/src/Wcet.lf b/test/TypeScript/src/Wcet.lf index b715dfd9a4..6c517a9d78 100644 --- a/test/TypeScript/src/Wcet.lf +++ b/test/TypeScript/src/Wcet.lf @@ -31,7 +31,9 @@ reactor Work { reactor Print { input x: number - reaction(x) {= console.log("Received: " + x); =} + reaction(x) {= + console.log("Received: " + x); + =} } main reactor Wcet { diff --git a/test/TypeScript/src/federated/DistributedDoublePort.lf b/test/TypeScript/src/federated/DistributedDoublePort.lf index a6bde59ed5..90365e1c50 100644 --- a/test/TypeScript/src/federated/DistributedDoublePort.lf +++ b/test/TypeScript/src/federated/DistributedDoublePort.lf @@ -21,9 +21,13 @@ reactor CountMicrostep { logical action act: number timer t(0, 1 sec) - reaction(t) -> act {= actions.act.schedule(0, count++); =} + reaction(t) -> act {= + actions.act.schedule(0, count++); + =} - reaction(act) -> out {= out = act; =} + reaction(act) -> out {= + out = act; + =} } reactor Print { @@ -39,7 +43,9 @@ reactor Print { } =} - reaction(shutdown) {= console.log("SUCCESS: messages were at least one microstep apart."); =} + reaction(shutdown) {= + console.log("SUCCESS: messages were at least one microstep apart."); + =} } federated reactor DistributedDoublePort { diff --git a/test/TypeScript/src/federated/HelloDistributed.lf b/test/TypeScript/src/federated/HelloDistributed.lf index 4936cfae42..84b8b895b2 100644 --- a/test/TypeScript/src/federated/HelloDistributed.lf +++ b/test/TypeScript/src/federated/HelloDistributed.lf @@ -21,7 +21,9 @@ reactor Destination { input inp: string state received: boolean = false - reaction(startup) {= console.log("Destination started."); =} + reaction(startup) {= + console.log("Destination started."); + =} reaction(inp) {= console.log(`At logical time ${util.getElapsedLogicalTime()}, destination received: ` + inp); @@ -44,5 +46,7 @@ federated reactor HelloDistributed at localhost { d = new Destination() // Reactor d is in federate Destination s.out -> d.inp // This version preserves the timestamp. - reaction(startup) {= console.log("Printing something in top-level federated reactor."); =} + reaction(startup) {= + console.log("Printing something in top-level federated reactor."); + =} } diff --git a/test/TypeScript/src/federated/SpuriousDependency.lf b/test/TypeScript/src/federated/SpuriousDependency.lf index 39da8f0ea5..15e2b527c7 100644 --- a/test/TypeScript/src/federated/SpuriousDependency.lf +++ b/test/TypeScript/src/federated/SpuriousDependency.lf @@ -35,7 +35,9 @@ reactor Check { state count: number = 0 - reaction(inp) {= console.log("count is now " + ++count); =} + reaction(inp) {= + console.log("count is now " + ++count); + =} reaction(shutdown) {= console.log("******* Shutdown invoked."); @@ -55,5 +57,7 @@ federated reactor { t1.out0 -> check.inp - reaction(startup) -> t0.in1 {= t0.in1 = 0; =} + reaction(startup) -> t0.in1 {= + t0.in1 = 0; + =} } diff --git a/test/TypeScript/src/federated/StopAtShutdown.lf b/test/TypeScript/src/federated/StopAtShutdown.lf index 200e773e82..c8550147b6 100644 --- a/test/TypeScript/src/federated/StopAtShutdown.lf +++ b/test/TypeScript/src/federated/StopAtShutdown.lf @@ -10,20 +10,30 @@ target TypeScript { reactor A { input inp: number - reaction(startup) {= console.log("Hello World!"); =} + reaction(startup) {= + console.log("Hello World!"); + =} - reaction(inp) {= console.log("Got it"); =} + reaction(inp) {= + console.log("Got it"); + =} - reaction(shutdown) {= util.requestStop(); =} + reaction(shutdown) {= + util.requestStop(); + =} } reactor B { output out: number timer t(1 sec) - reaction(t) -> out {= out = 1; =} + reaction(t) -> out {= + out = 1; + =} - reaction(shutdown) {= util.requestStop(); =} + reaction(shutdown) {= + util.requestStop(); + =} } federated reactor { diff --git a/test/TypeScript/src/federated/TopLevelArtifacts.lf b/test/TypeScript/src/federated/TopLevelArtifacts.lf index b7d59ae4b4..df1edae247 100644 --- a/test/TypeScript/src/federated/TopLevelArtifacts.lf +++ b/test/TypeScript/src/federated/TopLevelArtifacts.lf @@ -23,14 +23,18 @@ federated reactor { tc = new TestCount() c.out -> tc.inp - reaction(startup) {= successes++; =} + reaction(startup) {= + successes++; + =} reaction(t) -> act {= successes++; actions.act.schedule(0, null); =} - reaction(act) {= successes++; =} + reaction(act) {= + successes++; + =} reaction(shutdown) {= if (successes != 3) { diff --git a/test/TypeScript/src/lib/Count.lf b/test/TypeScript/src/lib/Count.lf index 88326db3f1..de9cf6b98a 100644 --- a/test/TypeScript/src/lib/Count.lf +++ b/test/TypeScript/src/lib/Count.lf @@ -5,5 +5,7 @@ reactor Count(offset: time = 0, period: time = 1 sec) { timer t(offset, period) state count: number = 1 - reaction(t) -> out {= out = count++; =} + reaction(t) -> out {= + out = count++; + =} } diff --git a/test/TypeScript/src/lib/Imported.lf b/test/TypeScript/src/lib/Imported.lf index 9b8506f39b..cb6072a7c2 100644 --- a/test/TypeScript/src/lib/Imported.lf +++ b/test/TypeScript/src/lib/Imported.lf @@ -10,5 +10,7 @@ reactor Imported { input x: number a = new ImportedAgain() - reaction(x) -> a.x {= a.x = (x as number); =} + reaction(x) -> a.x {= + a.x = (x as number); + =} } diff --git a/test/TypeScript/src/lib/InternalDelay.lf b/test/TypeScript/src/lib/InternalDelay.lf index 7b734baf31..5ee717b2bc 100644 --- a/test/TypeScript/src/lib/InternalDelay.lf +++ b/test/TypeScript/src/lib/InternalDelay.lf @@ -6,7 +6,11 @@ reactor InternalDelay(delay: TimeValue = 10 msec) { output out: number logical action d: number - reaction(inp) -> d {= actions.d.schedule(delay, inp as number); =} + reaction(inp) -> d {= + actions.d.schedule(delay, inp as number); + =} - reaction(d) -> out {= out = d; =} + reaction(d) -> out {= + out = d; + =} } diff --git a/test/TypeScript/src/multiport/BankSelfBroadcast.lf b/test/TypeScript/src/multiport/BankSelfBroadcast.lf index 956de7d5a2..3e97b88952 100644 --- a/test/TypeScript/src/multiport/BankSelfBroadcast.lf +++ b/test/TypeScript/src/multiport/BankSelfBroadcast.lf @@ -14,7 +14,9 @@ reactor A { output out: number state received: boolean = false - reaction(startup) -> out {= out = this.getBankIndex(); =} + reaction(startup) -> out {= + out = this.getBankIndex(); + =} reaction(inp) {= for (let i = 0; i < inp.length; i++) { diff --git a/test/TypeScript/src/multiport/BankToMultiport.lf b/test/TypeScript/src/multiport/BankToMultiport.lf index b1ea107ca9..044c5b24b5 100644 --- a/test/TypeScript/src/multiport/BankToMultiport.lf +++ b/test/TypeScript/src/multiport/BankToMultiport.lf @@ -4,7 +4,9 @@ target TypeScript reactor Source { output out: number - reaction(startup) -> out {= out = this.getBankIndex(); =} + reaction(startup) -> out {= + out = this.getBankIndex(); + =} } reactor Sink { diff --git a/test/TypeScript/src/multiport/Broadcast.lf b/test/TypeScript/src/multiport/Broadcast.lf index 8ec6e21742..54f52694dd 100644 --- a/test/TypeScript/src/multiport/Broadcast.lf +++ b/test/TypeScript/src/multiport/Broadcast.lf @@ -3,7 +3,9 @@ target TypeScript reactor Source { output out: number - reaction(startup) -> out {= out = 42; =} + reaction(startup) -> out {= + out = 42; + =} } reactor Sink { diff --git a/test/TypeScript/src/multiport/BroadcastAfter.lf b/test/TypeScript/src/multiport/BroadcastAfter.lf index 5d8165b983..ac394fdf0d 100644 --- a/test/TypeScript/src/multiport/BroadcastAfter.lf +++ b/test/TypeScript/src/multiport/BroadcastAfter.lf @@ -5,7 +5,9 @@ target TypeScript { reactor Source { output out: number - reaction(startup) -> out {= out = 42; =} + reaction(startup) -> out {= + out = 42; + =} } reactor Destination { diff --git a/test/TypeScript/src/multiport/BroadcastMultipleAfter.lf b/test/TypeScript/src/multiport/BroadcastMultipleAfter.lf index cd9bcd726a..f3f1fc2270 100644 --- a/test/TypeScript/src/multiport/BroadcastMultipleAfter.lf +++ b/test/TypeScript/src/multiport/BroadcastMultipleAfter.lf @@ -5,7 +5,9 @@ target TypeScript { reactor Source(value: number = 42) { output out: number - reaction(startup) -> out {= out = value; =} + reaction(startup) -> out {= + out = value; + =} } reactor Destination { diff --git a/test/TypeScript/src/multiport/MultiportFromBank.lf b/test/TypeScript/src/multiport/MultiportFromBank.lf index 63708bfb76..f1629755ee 100644 --- a/test/TypeScript/src/multiport/MultiportFromBank.lf +++ b/test/TypeScript/src/multiport/MultiportFromBank.lf @@ -7,7 +7,9 @@ target TypeScript { reactor Source { output out: number - reaction(startup) -> out {= out = this.getBankIndex(); =} + reaction(startup) -> out {= + out = this.getBankIndex(); + =} } reactor Destination(portWidth: number = 3) { diff --git a/test/TypeScript/src/multiport/MultiportIn.lf b/test/TypeScript/src/multiport/MultiportIn.lf index 8e786a4fdd..1ea4501a3a 100644 --- a/test/TypeScript/src/multiport/MultiportIn.lf +++ b/test/TypeScript/src/multiport/MultiportIn.lf @@ -9,14 +9,18 @@ reactor Source { output out: number state s: number = 0 - reaction(t) -> out {= out = s++; =} + reaction(t) -> out {= + out = s++; + =} } reactor Computation { input inp: number output out: number - reaction(inp) -> out {= out = inp; =} + reaction(inp) -> out {= + out = inp; + =} } reactor Destination { diff --git a/test/TypeScript/src/multiport/MultiportInParameterized.lf b/test/TypeScript/src/multiport/MultiportInParameterized.lf index 1f60d19bf0..31662b9de1 100644 --- a/test/TypeScript/src/multiport/MultiportInParameterized.lf +++ b/test/TypeScript/src/multiport/MultiportInParameterized.lf @@ -19,7 +19,9 @@ reactor Computation { input inp: number output out: number - reaction(inp) -> out {= out = inp; =} + reaction(inp) -> out {= + out = inp; + =} } reactor Destination(width: number = 1) { diff --git a/test/TypeScript/src/multiport/MultiportMutableInputArray.lf b/test/TypeScript/src/multiport/MultiportMutableInputArray.lf index b097927288..440f939733 100644 --- a/test/TypeScript/src/multiport/MultiportMutableInputArray.lf +++ b/test/TypeScript/src/multiport/MultiportMutableInputArray.lf @@ -4,7 +4,9 @@ target TypeScript reactor Source { - output[2] out: {= Array =} + output[2] out: {= + Array + =} reaction(startup) -> out {= // Dynamically allocate an output array of length 3. @@ -27,7 +29,9 @@ reactor Source { // The scale parameter is just for testing. reactor Print(scale: number = 1) { - input[2] inp: {= Array =} + input[2] inp: {= + Array + =} reaction(inp) {= let count = 0; // For testing. @@ -56,8 +60,12 @@ reactor Print(scale: number = 1) { } reactor Scale(scale: number = 2) { - mutable input[2] inp: {= Array =} - output[2] out: {= Array =} + mutable input[2] inp: {= + Array + =} + output[2] out: {= + Array + =} reaction(inp) -> out {= for (let j = 0; j < inp.length; j++) { diff --git a/test/TypeScript/src/multiport/MultiportToMultiportArray.lf b/test/TypeScript/src/multiport/MultiportToMultiportArray.lf index 018d8dd587..eb108317e6 100644 --- a/test/TypeScript/src/multiport/MultiportToMultiportArray.lf +++ b/test/TypeScript/src/multiport/MultiportToMultiportArray.lf @@ -5,7 +5,9 @@ target TypeScript { reactor Source { timer t(0, 200 msec) - output[2] out: {= Array =} + output[2] out: {= + Array + =} state s: number = 0 reaction(t) -> out {= @@ -24,7 +26,9 @@ reactor Source { reactor Destination { state s: number = 15 - input[2] inp: {= Array =} + input[2] inp: {= + Array + =} reaction(inp) {= let sum = 0; diff --git a/test/TypeScript/src/multiport/PipelineAfter.lf b/test/TypeScript/src/multiport/PipelineAfter.lf index 9db397c0e4..cf46072bb2 100644 --- a/test/TypeScript/src/multiport/PipelineAfter.lf +++ b/test/TypeScript/src/multiport/PipelineAfter.lf @@ -3,14 +3,18 @@ target TypeScript reactor Source { output out: number - reaction(startup) -> out {= out = 40; =} + reaction(startup) -> out {= + out = 40; + =} } reactor Compute { input inp: number output out: number - reaction(inp) -> out {= out = (inp as number) + 2; =} + reaction(inp) -> out {= + out = (inp as number) + 2; + =} } reactor Sink { diff --git a/test/TypeScript/src/multiport/ReactionsToNested.lf b/test/TypeScript/src/multiport/ReactionsToNested.lf index a6baf12135..c1ce7c104d 100644 --- a/test/TypeScript/src/multiport/ReactionsToNested.lf +++ b/test/TypeScript/src/multiport/ReactionsToNested.lf @@ -32,7 +32,11 @@ reactor D { main reactor { d = new D() - reaction(startup) -> d.y {= d.y[0] = 42; =} + reaction(startup) -> d.y {= + d.y[0] = 42; + =} - reaction(startup) -> d.y {= d.y[1] = 43; =} + reaction(startup) -> d.y {= + d.y[1] = 43; + =} } From f3a3c1eac8e1afc0da12369516189e62f35ea9cf Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 1 Sep 2023 21:18:56 -0700 Subject: [PATCH 439/516] Do not make ToLf a singleton. The comment about thread-safety in the previous commit message was incorrect because I forget that we were using the singleton pattern on ToLf. To my knowledge there was no good reason for us to do that. --- .../main/java/org/lflang/ast/FormattingUtil.java | 2 +- core/src/main/java/org/lflang/ast/ToLf.java | 8 -------- core/src/main/java/org/lflang/ast/ToText.java | 16 ++++++++-------- .../diagram/synthesis/LinguaFrancaSynthesis.java | 4 ++-- 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/org/lflang/ast/FormattingUtil.java b/core/src/main/java/org/lflang/ast/FormattingUtil.java index 19754b5f10..232d05b190 100644 --- a/core/src/main/java/org/lflang/ast/FormattingUtil.java +++ b/core/src/main/java/org/lflang/ast/FormattingUtil.java @@ -56,7 +56,7 @@ public static Function renderer(Target target) { * with the assumption that the target language is {@code target}. */ public static String render(EObject object, int lineLength, Target target, boolean codeMapTags) { - MalleableString ms = ToLf.instance.doSwitch(object); + MalleableString ms = new ToLf().doSwitch(object); String singleLineCommentPrefix = target.getSingleLineCommentPrefix(); ms.findBestRepresentation( () -> ms.render(INDENTATION, singleLineCommentPrefix, codeMapTags, null), diff --git a/core/src/main/java/org/lflang/ast/ToLf.java b/core/src/main/java/org/lflang/ast/ToLf.java index 50ffb442ea..b70c6f735b 100644 --- a/core/src/main/java/org/lflang/ast/ToLf.java +++ b/core/src/main/java/org/lflang/ast/ToLf.java @@ -89,20 +89,12 @@ public class ToLf extends LfSwitch { private static final Pattern KEEP_FORMAT_COMMENT = Pattern.compile("\\s*(//|#)\\s*keep-format\\s*"); - /// public instance initialized when loading the class - public static final ToLf instance = new ToLf(); - /** * The eObjects in the syntax tree on the path from the root up to and including the current * eObject. */ private final ArrayDeque callStack = new ArrayDeque<>(); - // private constructor - private ToLf() { - super(); - } - @Override public MalleableString caseArraySpec(ArraySpec spec) { if (spec.isOfVariableLength()) return MalleableString.anyOf("[]"); diff --git a/core/src/main/java/org/lflang/ast/ToText.java b/core/src/main/java/org/lflang/ast/ToText.java index c677f0730b..6845fde4e7 100644 --- a/core/src/main/java/org/lflang/ast/ToText.java +++ b/core/src/main/java/org/lflang/ast/ToText.java @@ -34,7 +34,7 @@ private ToText() { @Override public String caseArraySpec(ArraySpec spec) { - return ToLf.instance.doSwitch(spec).toString(); + return new ToLf().doSwitch(spec).toString(); } @Override @@ -77,27 +77,27 @@ public String caseCode(Code code) { @Override public String caseBracedListExpression(BracedListExpression object) { - return ToLf.instance.caseBracedListExpression(object).toString(); + return new ToLf().caseBracedListExpression(object).toString(); } @Override public String caseHost(Host host) { - return ToLf.instance.caseHost(host).toString(); + return new ToLf().caseHost(host).toString(); } @Override public String caseLiteral(Literal l) { - return ToLf.instance.caseLiteral(l).toString(); + return new ToLf().caseLiteral(l).toString(); } @Override public String caseParameterReference(ParameterReference p) { - return ToLf.instance.caseParameterReference(p).toString(); + return new ToLf().caseParameterReference(p).toString(); } @Override public String caseTime(Time t) { - return ToLf.instance.caseTime(t).toString(); + return new ToLf().caseTime(t).toString(); } @Override @@ -105,13 +105,13 @@ public String caseType(Type type) { if (type.getCode() != null) { return caseCode(type.getCode()); } - return ToLf.instance.caseType(type).toString(); + return new ToLf().caseType(type).toString(); } @Override public String caseTypeParm(TypeParm t) { if (t.getCode() != null) return doSwitch(t.getCode()); - return ToLf.instance.caseTypeParm(t).toString(); + return new ToLf().caseTypeParm(t).toString(); } @Override diff --git a/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java b/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java index 6496bb5dcc..b51822c9a5 100644 --- a/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java +++ b/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java @@ -1492,7 +1492,7 @@ private String createParameterLabel(ParameterInstance param) { if (param.getOverride() != null) { b.append(" = "); var init = param.getActualValue(); - b.append(ToLf.instance.doSwitch(init)); + b.append(new ToLf().doSwitch(init)); } return b.toString(); } @@ -1523,7 +1523,7 @@ private String createStateVariableLabel(StateVar variable) { b.append(":").append(t.toOriginalText()); } if (variable.getInit() != null) { - b.append(ToLf.instance.doSwitch(variable.getInit())); + b.append(new ToLf().doSwitch(variable.getInit())); } return b.toString(); } From 69ebb0f188b60092bf7de579d0f4977faa81e47b Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 1 Sep 2023 22:40:28 -0700 Subject: [PATCH 440/516] Update formatter expect test. --- cli/lff/src/test/java/org/lflang/cli/LffCliTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cli/lff/src/test/java/org/lflang/cli/LffCliTest.java b/cli/lff/src/test/java/org/lflang/cli/LffCliTest.java index 5f5f09d977..6b66c7107e 100644 --- a/cli/lff/src/test/java/org/lflang/cli/LffCliTest.java +++ b/cli/lff/src/test/java/org/lflang/cli/LffCliTest.java @@ -83,7 +83,9 @@ public class LffCliTest { /** moo */ // this is a humbug reaction - reaction(a) -> humbug {= /* it reacts like this*/ react react =} + reaction(a) -> humbug {= + /* it reacts like this*/ react react + =} } """), List.of( From 3531fce531a579ecc04a75d5c2d5161170f7a6a1 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 2 Sep 2023 11:54:59 -0700 Subject: [PATCH 441/516] Delete preamble that does nothing. This preamble was not even appearing in the generated code, which causes LSP test to fail. --- test/Python/src/federated/DistributedStructParallel.lf | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/Python/src/federated/DistributedStructParallel.lf b/test/Python/src/federated/DistributedStructParallel.lf index 9577231955..9a27ec6e8e 100644 --- a/test/Python/src/federated/DistributedStructParallel.lf +++ b/test/Python/src/federated/DistributedStructParallel.lf @@ -8,10 +8,6 @@ target Python { import Source from "../StructScale.lf" import Check, Print from "../StructParallel.lf" -preamble {= - import hello -=} - federated reactor { s = new Source() c1 = new Print() From 62af2e173ec829ac1189d0103430ba4f48f6359e Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 2 Sep 2023 12:09:29 -0700 Subject: [PATCH 442/516] Update condition for forcing code to be multiline. Reactions, preambles, and methods typically consist of multiple statements appearing on distinct lines, and if they consist of only a single statement, they commonly evolve into multiple statements. Therefore these three constructs should all use multiline code blocks. --- core/src/main/java/org/lflang/ast/ToLf.java | 2 +- test/C/src/verifier/ADASModel.lf | 24 +++++-------------- test/Cpp/src/Alignment.lf | 4 +--- test/Cpp/src/DelayInt.lf | 4 +--- test/Cpp/src/GetMicroStep.lf | 4 +--- test/Cpp/src/Hello.lf | 8 ++----- test/Cpp/src/NativeListsAndTimes.lf | 9 ++----- test/Cpp/src/StructPrint.lf | 4 +--- test/Cpp/src/concurrent/AsyncCallback.lf | 4 +--- test/Cpp/src/concurrent/DelayIntThreaded.lf | 4 +--- test/Cpp/src/concurrent/HelloThreaded.lf | 12 +++------- test/Cpp/src/enclave/EnclaveBankEach.lf | 8 ++----- test/Cpp/src/multiport/WidthGivenByCode.lf | 12 +++------- .../src/target/CliParserGenericArguments.lf | 20 ++++------------ test/Cpp/src/target/CombinedTypeNames.lf | 18 ++++---------- test/Rust/src/MovingAverage.lf | 4 +--- test/Rust/src/NativeListsAndTimes.lf | 5 +--- test/Rust/src/StructAsType.lf | 4 +--- test/Rust/src/generics/CtorParamGeneric.lf | 5 ++-- .../Rust/src/generics/CtorParamGenericInst.lf | 10 ++++---- test/Rust/src/generics/GenericReactor.lf | 4 +--- test/Rust/src/multiport/CycledLhs_Single.lf | 4 +--- test/TypeScript/src/ArrayAsParameter.lf | 4 +--- test/TypeScript/src/ArrayAsType.lf | 8 ++----- test/TypeScript/src/ArrayPrint.lf | 8 ++----- test/TypeScript/src/ArrayScale.lf | 8 ++----- test/TypeScript/src/MovingAverage.lf | 4 +--- test/TypeScript/src/SendsPointerTest.lf | 12 +++------- .../multiport/MultiportMutableInputArray.lf | 16 ++++--------- .../multiport/MultiportToMultiportArray.lf | 8 ++----- 30 files changed, 62 insertions(+), 179 deletions(-) diff --git a/core/src/main/java/org/lflang/ast/ToLf.java b/core/src/main/java/org/lflang/ast/ToLf.java index b70c6f735b..0ebc942b26 100644 --- a/core/src/main/java/org/lflang/ast/ToLf.java +++ b/core/src/main/java/org/lflang/ast/ToLf.java @@ -263,7 +263,7 @@ public MalleableString caseCode(Code code) { if (content.lines().count() > 1 || content.contains("#") || content.contains("//")) { return multilineRepresentation; } - if (callStack.stream().anyMatch(it -> it instanceof Code) && !content.isBlank()) { + if (callStack.stream().anyMatch(it -> it instanceof Reaction || it instanceof Preamble || it instanceof Method) && !content.isBlank()) { return MalleableString.anyOf(multilineRepresentation); } return MalleableString.anyOf(singleLineRepresentation, multilineRepresentation); diff --git a/test/C/src/verifier/ADASModel.lf b/test/C/src/verifier/ADASModel.lf index b127c1d1a8..e0e960a894 100644 --- a/test/C/src/verifier/ADASModel.lf +++ b/test/C/src/verifier/ADASModel.lf @@ -6,12 +6,8 @@ preamble {= =} reactor Camera { - output out: {= - c_frame_t - =} - state frame: {= - c_frame_t - =} + output out: {= c_frame_t =} + state frame: {= c_frame_t =} timer t(0, 17 msec) // 60 fps reaction(t) -> out {= @@ -22,12 +18,8 @@ reactor Camera { } reactor LiDAR { - output out: {= - l_frame_t - =} - state frame: {= - l_frame_t - =} + output out: {= l_frame_t =} + state frame: {= l_frame_t =} timer t(0, 34 msec) // 30 fps reaction(t) -> out {= @@ -58,12 +50,8 @@ reactor Brakes { } reactor ADASProcessor { - input in1: {= - l_frame_t - =} - input in2: {= - c_frame_t - =} + input in1: {= l_frame_t =} + input in2: {= c_frame_t =} output out1: int output out2: int logical action a(50 msec) diff --git a/test/Cpp/src/Alignment.lf b/test/Cpp/src/Alignment.lf index 7eaa596379..00f4ce0936 100644 --- a/test/Cpp/src/Alignment.lf +++ b/test/Cpp/src/Alignment.lf @@ -78,9 +78,7 @@ reactor Sieve { reactor Destination { input ok: bool input in: int - state last_invoked: {= - reactor::TimePoint - =} + state last_invoked: {= reactor::TimePoint =} reaction(ok, in) {= if (ok.is_present() && in.is_present()) { diff --git a/test/Cpp/src/DelayInt.lf b/test/Cpp/src/DelayInt.lf index 5111e26ee6..1b02dcba61 100644 --- a/test/Cpp/src/DelayInt.lf +++ b/test/Cpp/src/DelayInt.lf @@ -19,9 +19,7 @@ reactor Delay(delay: time = 100 msec) { reactor Test { input in: int - state start_time: {= - reactor::TimePoint - =} + state start_time: {= reactor::TimePoint =} timer start reaction(start) {= diff --git a/test/Cpp/src/GetMicroStep.lf b/test/Cpp/src/GetMicroStep.lf index bc541db26a..d09ebcdb32 100644 --- a/test/Cpp/src/GetMicroStep.lf +++ b/test/Cpp/src/GetMicroStep.lf @@ -2,9 +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 483d15b166..022b9a7383 100644 --- a/test/Cpp/src/Hello.lf +++ b/test/Cpp/src/Hello.lf @@ -7,13 +7,9 @@ target Cpp { fast: true } -reactor HelloCpp(period: time = 2 sec, message: {= - std::string -=} = "Hello C++") { +reactor HelloCpp(period: time = 2 sec, message: {= std::string =} = "Hello C++") { state count: int = 0 - state previous_time: {= - reactor::TimePoint - =} + state previous_time: {= reactor::TimePoint =} timer t(1 sec, period) logical action a: void diff --git a/test/Cpp/src/NativeListsAndTimes.lf b/test/Cpp/src/NativeListsAndTimes.lf index b786edc7a5..b6e8550c50 100644 --- a/test/Cpp/src/NativeListsAndTimes.lf +++ b/test/Cpp/src/NativeListsAndTimes.lf @@ -6,9 +6,7 @@ reactor Foo( 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: {= - std::vector - =}{1 msec, 2 msec, 3 msec}, + q: {= std::vector =}{1 msec, 2 msec, 3 msec}, g: time[]{1 msec, 2 msec}, // List of time values g2: int[] = {}) { state s: time = y // Reference to explicitly typed time parameter @@ -20,10 +18,7 @@ reactor Foo( timer toe(z) // Implicit type time state baz = p // Implicit type int[] state period = z // Implicit type time - // a list of lists - state times: std::vector>{q, g} + state times: std::vector>{q, g} // a list of lists state empty_list: int[] = {} reaction(tick) {= diff --git a/test/Cpp/src/StructPrint.lf b/test/Cpp/src/StructPrint.lf index 6e0fba1199..807dba8e85 100644 --- a/test/Cpp/src/StructPrint.lf +++ b/test/Cpp/src/StructPrint.lf @@ -18,9 +18,7 @@ reactor Source { =} } -reactor Print(expected_value: int = 42, expected_name: {= - std::string -=} = "Earth") { +reactor Print(expected_value: int = 42, expected_name: {= std::string =} = "Earth") { input in: Hello reaction(in) {= diff --git a/test/Cpp/src/concurrent/AsyncCallback.lf b/test/Cpp/src/concurrent/AsyncCallback.lf index 07eb85ca3a..b9d655ca75 100644 --- a/test/Cpp/src/concurrent/AsyncCallback.lf +++ b/test/Cpp/src/concurrent/AsyncCallback.lf @@ -10,9 +10,7 @@ main reactor AsyncCallback { =} timer t(0, 200 msec) - state thread: {= - std::thread - =} + state thread: {= std::thread =} state expected_time: time = 100 msec state toggle: bool = false diff --git a/test/Cpp/src/concurrent/DelayIntThreaded.lf b/test/Cpp/src/concurrent/DelayIntThreaded.lf index e090af0b89..5a181e6770 100644 --- a/test/Cpp/src/concurrent/DelayIntThreaded.lf +++ b/test/Cpp/src/concurrent/DelayIntThreaded.lf @@ -19,9 +19,7 @@ reactor Delay(delay: time = 100 msec) { reactor Test { input in: int - state start_time: {= - reactor::TimePoint - =} + state start_time: {= reactor::TimePoint =} timer start reaction(start) {= diff --git a/test/Cpp/src/concurrent/HelloThreaded.lf b/test/Cpp/src/concurrent/HelloThreaded.lf index ca08070d87..35e2261048 100644 --- a/test/Cpp/src/concurrent/HelloThreaded.lf +++ b/test/Cpp/src/concurrent/HelloThreaded.lf @@ -7,13 +7,9 @@ target Cpp { fast: true } -reactor HelloCpp(period: time = 2 sec, message: {= - std::string -=} = "Hello C++") { +reactor HelloCpp(period: time = 2 sec, message: {= std::string =} = "Hello C++") { state count: int = 0 - state previous_time: {= - reactor::TimePoint - =} + state previous_time: {= reactor::TimePoint =} timer t(1 sec, period) logical action a: void @@ -39,9 +35,7 @@ reactor HelloCpp(period: time = 2 sec, message: {= =} } -reactor Inside(period: time = 1 sec, message: {= - std::string -=} = "Composite default message.") { +reactor Inside(period: time = 1 sec, message: {= std::string =} = "Composite default message.") { third_instance = new HelloCpp(period=period, message=message) } diff --git a/test/Cpp/src/enclave/EnclaveBankEach.lf b/test/Cpp/src/enclave/EnclaveBankEach.lf index c220a082c5..d1b94748ba 100644 --- a/test/Cpp/src/enclave/EnclaveBankEach.lf +++ b/test/Cpp/src/enclave/EnclaveBankEach.lf @@ -6,12 +6,8 @@ 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 =}) { + period: {= reactor::Duration =} = {= 100ms * (bank_index+1) =}, + duration: {= reactor::Duration =} = {= 50ms + 100ms * bank_index =}) { logical action a: void reaction(startup, a) -> a {= diff --git a/test/Cpp/src/multiport/WidthGivenByCode.lf b/test/Cpp/src/multiport/WidthGivenByCode.lf index 3580a9696c..5e9ed80eb3 100644 --- a/test/Cpp/src/multiport/WidthGivenByCode.lf +++ b/test/Cpp/src/multiport/WidthGivenByCode.lf @@ -1,12 +1,8 @@ target Cpp reactor Foo(a: size_t = 8, b: size_t = 2) { - input[{= - a*b - =}] in: size_t - output[{= - a/b - =}] out: size_t + input[{= a*b =}] in: size_t + output[{= a/b =}] out: size_t reaction(startup) in -> out {= if (in.size() != a*b) { @@ -24,9 +20,7 @@ main reactor { foo1 = new Foo() foo2 = new Foo(a=10, b=3) foo3 = new Foo(a=9, b=9) - foo_bank = new[{= - 42 - =}] Foo() + foo_bank = new[{= 42 =}] Foo() reaction(startup) foo_bank.out {= if (foo_bank.size() != 42) { diff --git a/test/Cpp/src/target/CliParserGenericArguments.lf b/test/Cpp/src/target/CliParserGenericArguments.lf index 42c8bd6c2a..fc1862b411 100644 --- a/test/Cpp/src/target/CliParserGenericArguments.lf +++ b/test/Cpp/src/target/CliParserGenericArguments.lf @@ -47,26 +47,16 @@ main reactor CliParserGenericArguments( 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, + 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, + long_double_value: {= long_double =} = 4.2, float_value: float = 10.5, string_value: string = "This is a testvalue", - custom_class_value: {= - CustomClass - =}("Peter")) { + 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 5caa22f70a..f64116eb4a 100644 --- a/test/Cpp/src/target/CombinedTypeNames.lf +++ b/test/Cpp/src/target/CombinedTypeNames.lf @@ -2,17 +2,9 @@ // int`) can be used correctly in LF code. target Cpp -reactor Foo(bar: {= - unsigned int -=} = 0, baz: {= - const unsigned int* -=} = {= nullptr =}) { - state s_bar: {= - unsigned int - =} = bar - state s_baz: {= - const unsigned int* - =} = baz +reactor Foo(bar: {= unsigned int =} = 0, baz: {= const unsigned int* =} = {= nullptr =}) { + 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) { @@ -22,8 +14,6 @@ reactor Foo(bar: {= =} } -main reactor(bar: {= - unsigned int -=} = 42) { +main reactor(bar: {= unsigned int =} = 42) { foo = new Foo(bar=bar, baz = {= &bar =}) } diff --git a/test/Rust/src/MovingAverage.lf b/test/Rust/src/MovingAverage.lf index 0c5957b21c..69f973cede 100644 --- a/test/Rust/src/MovingAverage.lf +++ b/test/Rust/src/MovingAverage.lf @@ -17,9 +17,7 @@ reactor Source { } reactor MovingAverageImpl { - state delay_line: {= - [f64 ; 4] - =} = {= [ 0.0 ; 4 ] =} + state delay_line: {= [f64 ; 4] =} = {= [ 0.0 ; 4 ] =} state index: usize = 0 input in_: f64 output out: f64 diff --git a/test/Rust/src/NativeListsAndTimes.lf b/test/Rust/src/NativeListsAndTimes.lf index d96e0649ba..5401d6d1b9 100644 --- a/test/Rust/src/NativeListsAndTimes.lf +++ b/test/Rust/src/NativeListsAndTimes.lf @@ -30,10 +30,7 @@ reactor Foo( // state baz(p); // Implicit type i32[] fixme this interplays badly with syntax for array init // Implicit type time state period = z - // a list of lists - state times: Vec>(q, g) + state times: Vec>(q, g) // a list of lists /** * reactor Foo (p: i32[](1, 2)) { state baz(p); // Implicit type i32[] state baz({=p=}); // diff --git a/test/Rust/src/StructAsType.lf b/test/Rust/src/StructAsType.lf index 893a1ed8a8..867651e8de 100644 --- a/test/Rust/src/StructAsType.lf +++ b/test/Rust/src/StructAsType.lf @@ -20,9 +20,7 @@ reactor Source { // expected parameter is for testing. reactor Print(expected: i32 = 42) { - input inp: {= - super::source::Hello - =} + input inp: {= super::source::Hello =} state expected: i32 = expected reaction(inp) {= diff --git a/test/Rust/src/generics/CtorParamGeneric.lf b/test/Rust/src/generics/CtorParamGeneric.lf index fe9d2a4d90..e5a6db3f0e 100644 --- a/test/Rust/src/generics/CtorParamGeneric.lf +++ b/test/Rust/src/generics/CtorParamGeneric.lf @@ -1,9 +1,8 @@ // tests that ctor parameters may refer to type parameters. target Rust -reactor Generic<{= - T: Default + Eq + Sync + std::fmt::Debug -=}>(value: T = {= Default::default() =}) { +reactor Generic<{= T: Default + Eq + Sync + std::fmt::Debug =}>( + value: T = {= Default::default() =}) { input in: T state v: T = value diff --git a/test/Rust/src/generics/CtorParamGenericInst.lf b/test/Rust/src/generics/CtorParamGenericInst.lf index de02ca156e..c76e0e1dcb 100644 --- a/test/Rust/src/generics/CtorParamGenericInst.lf +++ b/test/Rust/src/generics/CtorParamGenericInst.lf @@ -2,9 +2,8 @@ // argument list of a further child instance. target Rust -reactor Generic2<{= - T: Default + Eq + Sync + std::fmt::Debug + Send + 'static -=}>(value: T = {= Default::default() =}) { +reactor Generic2<{= T: Default + Eq + Sync + std::fmt::Debug + Send + 'static =}>( + value: T = {= Default::default() =}) { input in: T state v: T = value @@ -16,9 +15,8 @@ reactor Generic2<{= =} } -reactor Generic<{= - T: Default + Eq + Sync + std::fmt::Debug + Copy + Send + 'static -=}>(value: T = {= Default::default() =}) { +reactor Generic<{= T: Default + Eq + Sync + std::fmt::Debug + Copy + Send + 'static =}>( + value: T = {= Default::default() =}) { input in: T inner = new Generic2(value=value) diff --git a/test/Rust/src/generics/GenericReactor.lf b/test/Rust/src/generics/GenericReactor.lf index 251fefcc69..b87119bb86 100644 --- a/test/Rust/src/generics/GenericReactor.lf +++ b/test/Rust/src/generics/GenericReactor.lf @@ -1,9 +1,7 @@ // Tests a port connection between (input of self -> input of child) target Rust -reactor Box<{= - T: Sync -=}> { +reactor Box<{= T: Sync =}> { input inp: T output out: T diff --git a/test/Rust/src/multiport/CycledLhs_Single.lf b/test/Rust/src/multiport/CycledLhs_Single.lf index 4d8931e9d7..2ab73f49ad 100644 --- a/test/Rust/src/multiport/CycledLhs_Single.lf +++ b/test/Rust/src/multiport/CycledLhs_Single.lf @@ -7,9 +7,7 @@ target Rust { reactor Test { output[2] out: u32 input[4] in: u32 - logical action act: {= - (u32, u32) - =} + logical action act: {= (u32, u32) =} state last: u32 = 1 reaction(startup) -> act {= diff --git a/test/TypeScript/src/ArrayAsParameter.lf b/test/TypeScript/src/ArrayAsParameter.lf index 150213d47c..ef9ba9f414 100644 --- a/test/TypeScript/src/ArrayAsParameter.lf +++ b/test/TypeScript/src/ArrayAsParameter.lf @@ -1,9 +1,7 @@ // 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 logical action next diff --git a/test/TypeScript/src/ArrayAsType.lf b/test/TypeScript/src/ArrayAsType.lf index f40d6d91e0..c26e3fe02e 100644 --- a/test/TypeScript/src/ArrayAsType.lf +++ b/test/TypeScript/src/ArrayAsType.lf @@ -3,9 +3,7 @@ target TypeScript reactor Source { - output out: {= - Array - =} + output out: {= Array =} reaction(startup) -> out {= let toSend = []; @@ -18,9 +16,7 @@ reactor Source { // The scale parameter is just for testing. reactor Print(scale: number = 1) { - input x: {= - Array - =} + input x: {= Array =} reaction(x) {= let count = 0; // For testing. diff --git a/test/TypeScript/src/ArrayPrint.lf b/test/TypeScript/src/ArrayPrint.lf index 7967efb290..3c94b00b34 100644 --- a/test/TypeScript/src/ArrayPrint.lf +++ b/test/TypeScript/src/ArrayPrint.lf @@ -3,9 +3,7 @@ target TypeScript reactor Source { - output out: {= - Array - =} + output out: {= Array =} reaction(startup) -> out {= let toSend = new Array(); @@ -18,9 +16,7 @@ reactor Source { // The scale parameter is just for testing. reactor Print(scale: number = 1) { - input x: {= - Array - =} + input x: {= Array =} reaction(x) {= let count = 0; // For testing. diff --git a/test/TypeScript/src/ArrayScale.lf b/test/TypeScript/src/ArrayScale.lf index 45c982f493..2ddfa951f3 100644 --- a/test/TypeScript/src/ArrayScale.lf +++ b/test/TypeScript/src/ArrayScale.lf @@ -6,12 +6,8 @@ target TypeScript import Source, Print from "ArrayPrint.lf" reactor Scale(scale: number = 2) { - mutable input x: {= - Array - =} - output out: {= - Array - =} + mutable input x: {= Array =} + output out: {= Array =} reaction(x) -> out {= x = x as Array; diff --git a/test/TypeScript/src/MovingAverage.lf b/test/TypeScript/src/MovingAverage.lf index 38ddde1504..f99e436ab7 100644 --- a/test/TypeScript/src/MovingAverage.lf +++ b/test/TypeScript/src/MovingAverage.lf @@ -17,9 +17,7 @@ reactor Source { } reactor MovingAverageImpl { - state delay_line: {= - Array - =} = {= [0.0, 0.0, 0.0] =} + state delay_line: {= Array =} = {= [0.0, 0.0, 0.0] =} state index: number = 0 input x: number output out: number diff --git a/test/TypeScript/src/SendsPointerTest.lf b/test/TypeScript/src/SendsPointerTest.lf index 68cf14b4cd..2808d2a87b 100644 --- a/test/TypeScript/src/SendsPointerTest.lf +++ b/test/TypeScript/src/SendsPointerTest.lf @@ -3,9 +3,7 @@ target TypeScript reactor SendsPointer { - output out: {= - {value: number} - =} + output out: {= {value: number} =} reaction(startup) -> out {= let my_object = { value: 42 }; @@ -14,12 +12,8 @@ reactor SendsPointer { } // expected parameter is for testing. -reactor Print(expected: {= - {value: number} -=} = {= { value: 42 } =}) { - input x: {= - {value: number} - =} +reactor Print(expected: {= {value: number} =} = {= { value: 42 } =}) { + input x: {= {value: number} =} reaction(x) {= x = x as {value: number}; diff --git a/test/TypeScript/src/multiport/MultiportMutableInputArray.lf b/test/TypeScript/src/multiport/MultiportMutableInputArray.lf index 440f939733..b097927288 100644 --- a/test/TypeScript/src/multiport/MultiportMutableInputArray.lf +++ b/test/TypeScript/src/multiport/MultiportMutableInputArray.lf @@ -4,9 +4,7 @@ target TypeScript reactor Source { - output[2] out: {= - Array - =} + output[2] out: {= Array =} reaction(startup) -> out {= // Dynamically allocate an output array of length 3. @@ -29,9 +27,7 @@ reactor Source { // The scale parameter is just for testing. reactor Print(scale: number = 1) { - input[2] inp: {= - Array - =} + input[2] inp: {= Array =} reaction(inp) {= let count = 0; // For testing. @@ -60,12 +56,8 @@ reactor Print(scale: number = 1) { } reactor Scale(scale: number = 2) { - mutable input[2] inp: {= - Array - =} - output[2] out: {= - Array - =} + mutable input[2] inp: {= Array =} + output[2] out: {= Array =} reaction(inp) -> out {= for (let j = 0; j < inp.length; j++) { diff --git a/test/TypeScript/src/multiport/MultiportToMultiportArray.lf b/test/TypeScript/src/multiport/MultiportToMultiportArray.lf index eb108317e6..018d8dd587 100644 --- a/test/TypeScript/src/multiport/MultiportToMultiportArray.lf +++ b/test/TypeScript/src/multiport/MultiportToMultiportArray.lf @@ -5,9 +5,7 @@ target TypeScript { reactor Source { timer t(0, 200 msec) - output[2] out: {= - Array - =} + output[2] out: {= Array =} state s: number = 0 reaction(t) -> out {= @@ -26,9 +24,7 @@ reactor Source { reactor Destination { state s: number = 15 - input[2] inp: {= - Array - =} + input[2] inp: {= Array =} reaction(inp) {= let sum = 0; From 1cf93687e1a92f5a220345d20e7ff06da5b884f1 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 2 Sep 2023 22:27:26 -0700 Subject: [PATCH 443/516] Format. --- core/src/main/java/org/lflang/ast/ToLf.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/ast/ToLf.java b/core/src/main/java/org/lflang/ast/ToLf.java index 0ebc942b26..740dcb074d 100644 --- a/core/src/main/java/org/lflang/ast/ToLf.java +++ b/core/src/main/java/org/lflang/ast/ToLf.java @@ -263,7 +263,10 @@ public MalleableString caseCode(Code code) { if (content.lines().count() > 1 || content.contains("#") || content.contains("//")) { return multilineRepresentation; } - if (callStack.stream().anyMatch(it -> it instanceof Reaction || it instanceof Preamble || it instanceof Method) && !content.isBlank()) { + if (callStack.stream() + .anyMatch( + it -> it instanceof Reaction || it instanceof Preamble || it instanceof Method) + && !content.isBlank()) { return MalleableString.anyOf(multilineRepresentation); } return MalleableString.anyOf(singleLineRepresentation, multilineRepresentation); From 4b92a340c5b8f647926e607d8cd7e388be598100 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 2 Sep 2023 22:45:09 -0700 Subject: [PATCH 444/516] Delete another unused preamble. --- test/Python/src/federated/DistributedStructAsType.lf | 4 ---- test/Python/src/federated/DistributedStructAsTypeDirect.lf | 4 ---- test/Python/src/federated/DistributedStructPrint.lf | 4 ---- test/Python/src/federated/DistributedStructScale.lf | 4 ---- 4 files changed, 16 deletions(-) diff --git a/test/Python/src/federated/DistributedStructAsType.lf b/test/Python/src/federated/DistributedStructAsType.lf index b3a83cc600..8179a295a7 100644 --- a/test/Python/src/federated/DistributedStructAsType.lf +++ b/test/Python/src/federated/DistributedStructAsType.lf @@ -5,10 +5,6 @@ target Python { import Source, Print from "../StructAsType.lf" -preamble {= - import hello -=} - federated reactor { s = new Source() p = new Print() diff --git a/test/Python/src/federated/DistributedStructAsTypeDirect.lf b/test/Python/src/federated/DistributedStructAsTypeDirect.lf index 7463672366..80eac47772 100644 --- a/test/Python/src/federated/DistributedStructAsTypeDirect.lf +++ b/test/Python/src/federated/DistributedStructAsTypeDirect.lf @@ -5,10 +5,6 @@ target Python { import Source, Print from "../StructAsTypeDirect.lf" -preamble {= - import hello -=} - federated reactor { s = new Source() p = new Print() diff --git a/test/Python/src/federated/DistributedStructPrint.lf b/test/Python/src/federated/DistributedStructPrint.lf index d3dbfe398b..b447fc61cd 100644 --- a/test/Python/src/federated/DistributedStructPrint.lf +++ b/test/Python/src/federated/DistributedStructPrint.lf @@ -7,10 +7,6 @@ target Python { import Print, Check from "../StructPrint.lf" -preamble {= - import hello -=} - federated reactor { s = new Print() p = new Check() diff --git a/test/Python/src/federated/DistributedStructScale.lf b/test/Python/src/federated/DistributedStructScale.lf index 34a4977d05..0e7d614448 100644 --- a/test/Python/src/federated/DistributedStructScale.lf +++ b/test/Python/src/federated/DistributedStructScale.lf @@ -7,10 +7,6 @@ target Python { import Source, TestInput, Print from "../StructScale.lf" -preamble {= - import hello -=} - federated reactor { s = new Source() c = new Print() From ba2fa9c5b5d3366022b6dfb3f66e406c27ff0d5b Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sun, 3 Sep 2023 10:52:08 -0700 Subject: [PATCH 445/516] Format. --- test/C/src/PersistentInputs.lf | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/C/src/PersistentInputs.lf b/test/C/src/PersistentInputs.lf index 8ff14e41b2..822810fd90 100644 --- a/test/C/src/PersistentInputs.lf +++ b/test/C/src/PersistentInputs.lf @@ -8,7 +8,9 @@ reactor Source { timer t(100 ms, 200 ms) state count: int = 1 - reaction(t) -> out {= lf_set(out, self->count++); =} + reaction(t) -> out {= + lf_set(out, self->count++); + =} } reactor Sink { @@ -17,7 +19,9 @@ reactor Sink { state count: int = 0 // For testing, emulate the count variable of Source. timer t2(100 ms, 200 ms) - reaction(t2) {= self->count++; =} + reaction(t2) {= + self->count++; + =} reaction(t) in {= printf("Value of the input is %d at time %lld\n", in->value, lf_time_logical_elapsed()); From 14fac8c050168f8c9e175024e602f1e67aad52b7 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 1 Sep 2023 22:31:46 -0700 Subject: [PATCH 446/516] Set the bank index of top-level federates. Fixes #1962. --- .../java/org/lflang/ast/FormattingUtil.java | 4 ++- core/src/main/java/org/lflang/ast/ToLf.java | 21 +++++++++++-- .../federated/generator/FedMainEmitter.java | 13 ++++++++ .../generator/c/CParameterGenerator.java | 2 +- test/C/src/federated/BankIndex.lf | 30 +++++++++++++++++++ 5 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 test/C/src/federated/BankIndex.lf diff --git a/core/src/main/java/org/lflang/ast/FormattingUtil.java b/core/src/main/java/org/lflang/ast/FormattingUtil.java index 232d05b190..113bcb27c8 100644 --- a/core/src/main/java/org/lflang/ast/FormattingUtil.java +++ b/core/src/main/java/org/lflang/ast/FormattingUtil.java @@ -56,7 +56,9 @@ public static Function renderer(Target target) { * with the assumption that the target language is {@code target}. */ public static String render(EObject object, int lineLength, Target target, boolean codeMapTags) { - MalleableString ms = new ToLf().doSwitch(object); + var toLf = new ToLf(); + toLf.setTarget(target); + MalleableString ms = toLf.doSwitch(object); String singleLineCommentPrefix = target.getSingleLineCommentPrefix(); ms.findBestRepresentation( () -> ms.render(INDENTATION, singleLineCommentPrefix, codeMapTags, null), diff --git a/core/src/main/java/org/lflang/ast/ToLf.java b/core/src/main/java/org/lflang/ast/ToLf.java index 740dcb074d..b91ea5499c 100644 --- a/core/src/main/java/org/lflang/ast/ToLf.java +++ b/core/src/main/java/org/lflang/ast/ToLf.java @@ -95,6 +95,23 @@ public class ToLf extends LfSwitch { */ private final ArrayDeque callStack = new ArrayDeque<>(); + /** The target language. This is only needed when the complete program is not available. */ + private Target optionalTarget; + + /** Return the target language of the LF being generated. */ + private Target getTarget() { + if (callStack.getFirst() instanceof Model model) return ASTUtils.getTarget(model); + else return optionalTarget; + } + + /** + * Set the target language of the LF being generated. This has no effect unless the target spec is + * unavailable. + */ + public void setTarget(Target target) { + optionalTarget = target; + } + @Override public MalleableString caseArraySpec(ArraySpec spec) { if (spec.isOfVariableLength()) return MalleableString.anyOf("[]"); @@ -931,7 +948,7 @@ public MalleableString caseAssignment(Assignment object) { */ private boolean shouldOutputAsAssignment(Initializer init) { return init.isAssign() - || init.getExprs().size() == 1 && ASTUtils.getTarget(init).mandatesEqualsInitializers(); + || init.getExprs().size() == 1 && getTarget().mandatesEqualsInitializers(); } @Override @@ -945,7 +962,7 @@ public MalleableString caseInitializer(Initializer init) { Objects.requireNonNull(expr); return builder.append(doSwitch(expr)).get(); } - if (ASTUtils.getTarget(init) == Target.C) { + if (getTarget() == Target.C) { // This turns C array initializers into a braced expression. // C++ variants are not converted. return builder.append(bracedListExpression(init.getExprs())).get(); diff --git a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java index f6e8f6e9c8..9d4817c20d 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedMainEmitter.java @@ -7,6 +7,7 @@ import org.lflang.MessageReporter; import org.lflang.ast.ASTUtils; import org.lflang.ast.FormattingUtil; +import org.lflang.lf.LfFactory; import org.lflang.lf.Reactor; /** Helper class to generate a main reactor */ @@ -30,6 +31,18 @@ String generateMainReactor( var renderer = FormattingUtil.renderer(federate.targetConfig.target); var instantiation = EcoreUtil.copy(federate.instantiation); instantiation.setWidthSpec(null); + if (federate.bankWidth > 1) { + var assignment = LfFactory.eINSTANCE.createAssignment(); + var parameter = LfFactory.eINSTANCE.createParameter(); + parameter.setName("bank_index"); + assignment.setLhs(parameter); + var initializer = LfFactory.eINSTANCE.createInitializer(); + var expression = LfFactory.eINSTANCE.createLiteral(); + expression.setLiteral(String.valueOf(federate.bankIndex)); + initializer.getExprs().add(expression); + assignment.setRhs(initializer); + instantiation.getParameters().add(assignment); + } return String.join( "\n", diff --git a/core/src/main/java/org/lflang/generator/c/CParameterGenerator.java b/core/src/main/java/org/lflang/generator/c/CParameterGenerator.java index caaa375056..21a4803a5f 100644 --- a/core/src/main/java/org/lflang/generator/c/CParameterGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CParameterGenerator.java @@ -21,7 +21,7 @@ public class CParameterGenerator { */ public static String getInitializer(ParameterInstance p) { // Handle the bank_index parameter. - if (p.getName().equals("bank_index")) { + if (p.getName().equals("bank_index") && p.getOverride() == null) { return CUtil.bankIndex(p.getParent()); } diff --git a/test/C/src/federated/BankIndex.lf b/test/C/src/federated/BankIndex.lf new file mode 100644 index 0000000000..ff7341608f --- /dev/null +++ b/test/C/src/federated/BankIndex.lf @@ -0,0 +1,30 @@ +target C + +reactor Node(bank_index: int = 0, num_nodes: int = 3) { + input[num_nodes] in: int + output out: int + + reaction(startup) -> out {= + lf_set(out, self->bank_index); + =} +} + +reactor Check(num_nodes: int = 3) { + input[num_nodes] in: int + + reaction(in) {= + for (int i = 0; i < self->num_nodes; i++) { + if (in[i]->value != i) { + lf_print_error_and_exit("Expected %d, not %d.", i, in[i]->value); + } + } + lf_print("Success."); + lf_request_stop(); + =} +} + +federated reactor { + b = new[4] Node(num_nodes=4) + c = new Check(num_nodes=4) + b.out -> c.in +} From 7e8d3f3022871dabfc99a0b9b1b62000afc39f1f Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 2 Sep 2023 22:22:44 -0700 Subject: [PATCH 447/516] Remove check that bank index is zero. --- test/C/src/federated/DistributedBank.lf | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/C/src/federated/DistributedBank.lf b/test/C/src/federated/DistributedBank.lf index f934bd7361..65a6f871c2 100644 --- a/test/C/src/federated/DistributedBank.lf +++ b/test/C/src/federated/DistributedBank.lf @@ -13,9 +13,6 @@ reactor Node(bank_index: int = 0) { =} reaction(shutdown) {= - if (self->bank_index) { - lf_print_error_and_exit("The only bank index should be zero because there should be only one bank member per federate."); - } if (self->count == 0) { lf_print_error_and_exit("Timer reactions did not execute."); } From 02a8557b41ac5539303bfad6f2078616eb7428ed Mon Sep 17 00:00:00 2001 From: i love smoked beef brisket Date: Tue, 5 Sep 2023 19:50:12 -0700 Subject: [PATCH 448/516] Optimise gradle per @lhstrh suggestions, add --quiet flag --- util/scripts/launch.ps1 | 2 +- util/scripts/launch.sh | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/util/scripts/launch.ps1 b/util/scripts/launch.ps1 index bb14204f5c..40cd25ba55 100644 --- a/util/scripts/launch.ps1 +++ b/util/scripts/launch.ps1 @@ -29,5 +29,5 @@ $base="$PSScriptRoot\..\..\" $gradlew="${base}/gradlew.bat" # invoke script -& "${gradlew}" assemble +& "${gradlew}" --quiet assemble ":cli:${tool}:assemble" & "${base}/build/install/lf-cli/bin/${tool}" @args \ No newline at end of file diff --git a/util/scripts/launch.sh b/util/scripts/launch.sh index 22a272f81f..5233cb55fb 100755 --- a/util/scripts/launch.sh +++ b/util/scripts/launch.sh @@ -18,6 +18,8 @@ # 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. +set -euo pipefail + find_dir() ( start_dir=$PWD cd "$(dirname "$1")" @@ -72,5 +74,5 @@ fi gradlew="${base}/gradlew" # Launch the tool. -"${gradlew}" assemble +"${gradlew}" --quiet assemble ":cli:${tool}:assemble" "${base}/build/install/lf-cli/bin/${tool}" "$@" \ No newline at end of file From 4012718f9bfa137186ee05a5ec036e9fb57f4848 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Wed, 6 Sep 2023 17:02:54 -0700 Subject: [PATCH 449/516] Update latest-release workflow --- .github/actions/latest-release/action.yml | 26 +++++++++++++++++ .github/workflows/latest-release.yml | 34 ++++++----------------- 2 files changed, 34 insertions(+), 26 deletions(-) create mode 100644 .github/actions/latest-release/action.yml diff --git a/.github/actions/latest-release/action.yml b/.github/actions/latest-release/action.yml new file mode 100644 index 0000000000..e283e8ed01 --- /dev/null +++ b/.github/actions/latest-release/action.yml @@ -0,0 +1,26 @@ +name: Latest release +description: Report the latest release of the current repo +runs: + using: "composite" + steps: + - name: Install semver-tool + run: | + wget -O /usr/local/bin/semver https://raw.githubusercontent.com/fsaintjacques/semver-tool/master/src/semver + chmod +x /usr/local/bin/semver + semver --version + - name: Fetch all tags + run: git fetch --all --tags + - name: Fetch latest-release script + run: | + wget https://raw.githubusercontent.com/lf-lang/lingua-franca/master/.github/scripts/latest-release.sh + chmod +x latest-release.sh + - name: Find the latest release + id: find + run: | + export tag=$(./latest-release.sh) + echo "ref=${tag}" >> $GITHUB_OUTPUT + shopt -s extglob + export ver="${tag##v}" + echo "ver=${ver}" >> $GITHUB_OUTPUT + echo "Latest release tag: ${tag}" + echo "Without a leading 'v': ${ver}" diff --git a/.github/workflows/latest-release.yml b/.github/workflows/latest-release.yml index d327f8f722..900df0f843 100644 --- a/.github/workflows/latest-release.yml +++ b/.github/workflows/latest-release.yml @@ -10,10 +10,10 @@ on: outputs: ref: description: "The tag of the latest release" - value: ${{ jobs.run.outputs.ref }} + value: ${{ jobs.get-latest-release.outputs.ref }} ver: description: "The semver of the latest release (without a leading 'v')" - value: ${{ jobs.run.outputs.ver }} + value: ${{ jobs.get-latest-release.outputs.ver }} # Also allow trigging the workflow manually. workflow_dispatch: @@ -21,31 +21,13 @@ jobs: get-latest-release: runs-on: ubuntu-latest outputs: - ref: ${{ steps.find.outputs.ref }} - ver: ${{ steps.find.outputs.ver }} + ref: ${{ steps.semver.outputs.ref }} + ver: ${{ steps.semver.outputs.ver }} steps: - name: Check out repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: lf-lang/${{ inputs.repo }} - - name: Install semver-tool - run: | - wget -O /usr/local/bin/semver https://raw.githubusercontent.com/fsaintjacques/semver-tool/master/src/semver - chmod +x /usr/local/bin/semver - semver --version - - name: Fetch all tags - run: git fetch --all --tags - - name: Fetch latest-release script - run: | - wget https://raw.githubusercontent.com/lf-lang/lingua-franca/master/.github/scripts/latest-release.sh - chmod +x latest-release.sh - - name: Find the latest release - id: find - run: | - export tag=$(./latest-release.sh) - echo "{ref}={${tag}}" >> $GITHUB_OUTPUT - shopt -s extglob - export ver="${tag##v}" - echo "{ver}={${ver}}" >> $GITHUB_OUTPUT - echo "Latest release tag: ${tag}" - echo "Without a leading 'v': ${ver}" + - id: semver + uses: ./.github/actions/latest-release@master + \ No newline at end of file From fa9e2e5e13e85d2d74072a25665a8d689deab153 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Wed, 6 Sep 2023 17:05:13 -0700 Subject: [PATCH 450/516] Get input from user with invoked manually --- .github/workflows/latest-release.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/latest-release.yml b/.github/workflows/latest-release.yml index 900df0f843..7882968990 100644 --- a/.github/workflows/latest-release.yml +++ b/.github/workflows/latest-release.yml @@ -16,6 +16,11 @@ on: value: ${{ jobs.get-latest-release.outputs.ver }} # Also allow trigging the workflow manually. workflow_dispatch: + inputs: + repo: + type: string + description: Repo to find the latest release of + default: lingua-franca jobs: get-latest-release: @@ -27,7 +32,7 @@ jobs: - name: Check out repository uses: actions/checkout@v3 with: - repository: lf-lang/${{ inputs.repo }} + repository: lf-lang/${{ github.event.inputs.repo || inputs.repo }} - id: semver uses: ./.github/actions/latest-release@master \ No newline at end of file From 35f74a6297ac4b966633137f54e8061b2fb851a3 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Wed, 6 Sep 2023 17:36:16 -0700 Subject: [PATCH 451/516] Add option to specify different repo owner --- .github/actions/latest-release/action.yml | 4 ++++ .github/workflows/latest-release.yml | 15 +++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/actions/latest-release/action.yml b/.github/actions/latest-release/action.yml index e283e8ed01..98c51bb900 100644 --- a/.github/actions/latest-release/action.yml +++ b/.github/actions/latest-release/action.yml @@ -8,12 +8,15 @@ runs: wget -O /usr/local/bin/semver https://raw.githubusercontent.com/fsaintjacques/semver-tool/master/src/semver chmod +x /usr/local/bin/semver semver --version + shell: bash - name: Fetch all tags run: git fetch --all --tags + shell: bash - name: Fetch latest-release script run: | wget https://raw.githubusercontent.com/lf-lang/lingua-franca/master/.github/scripts/latest-release.sh chmod +x latest-release.sh + shell: bash - name: Find the latest release id: find run: | @@ -24,3 +27,4 @@ runs: echo "ver=${ver}" >> $GITHUB_OUTPUT echo "Latest release tag: ${tag}" echo "Without a leading 'v': ${ver}" + shell: bash diff --git a/.github/workflows/latest-release.yml b/.github/workflows/latest-release.yml index 7882968990..cdd65f59f1 100644 --- a/.github/workflows/latest-release.yml +++ b/.github/workflows/latest-release.yml @@ -3,6 +3,10 @@ name: Latest release on: workflow_call: inputs: + owner: + type: string + description: Owner of the repo + default: lf-lang repo: type: string description: Repo to find the latest release of @@ -14,9 +18,13 @@ on: ver: description: "The semver of the latest release (without a leading 'v')" value: ${{ jobs.get-latest-release.outputs.ver }} - # Also allow trigging the workflow manually. + workflow_dispatch: inputs: + owner: + type: string + description: Owner of the repo + default: lf-lang repo: type: string description: Repo to find the latest release of @@ -32,7 +40,6 @@ jobs: - name: Check out repository uses: actions/checkout@v3 with: - repository: lf-lang/${{ github.event.inputs.repo || inputs.repo }} + repository: ${{ github.event.inputs.owner || inputs.owner }}/${{ github.event.inputs.repo || inputs.repo }} - id: semver - uses: ./.github/actions/latest-release@master - \ No newline at end of file + uses: lf-lang/lingua-franca/.github/actions/latest-release@ci-stuff From 963baefc4876fdcd3c906fa13fe1402947826ac8 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Wed, 6 Sep 2023 18:05:14 -0700 Subject: [PATCH 452/516] Rename executables in bin --- bin/{lfc => lfc-dev} | 0 bin/{lfc.ps1 => lfc-dev.ps1} | 0 bin/{lfd => lfd-dev} | 0 bin/{lfd.ps1 => lfd-dev.ps1} | 0 bin/{lff => lff-dev} | 0 bin/{lff.ps1 => lff-dev.ps1} | 0 util/scripts/launch.sh | 7 +++---- 7 files changed, 3 insertions(+), 4 deletions(-) rename bin/{lfc => lfc-dev} (100%) rename bin/{lfc.ps1 => lfc-dev.ps1} (100%) rename bin/{lfd => lfd-dev} (100%) rename bin/{lfd.ps1 => lfd-dev.ps1} (100%) rename bin/{lff => lff-dev} (100%) rename bin/{lff.ps1 => lff-dev.ps1} (100%) diff --git a/bin/lfc b/bin/lfc-dev similarity index 100% rename from bin/lfc rename to bin/lfc-dev diff --git a/bin/lfc.ps1 b/bin/lfc-dev.ps1 similarity index 100% rename from bin/lfc.ps1 rename to bin/lfc-dev.ps1 diff --git a/bin/lfd b/bin/lfd-dev similarity index 100% rename from bin/lfd rename to bin/lfd-dev diff --git a/bin/lfd.ps1 b/bin/lfd-dev.ps1 similarity index 100% rename from bin/lfd.ps1 rename to bin/lfd-dev.ps1 diff --git a/bin/lff b/bin/lff-dev similarity index 100% rename from bin/lff rename to bin/lff-dev diff --git a/bin/lff.ps1 b/bin/lff-dev.ps1 similarity index 100% rename from bin/lff.ps1 rename to bin/lff-dev.ps1 diff --git a/util/scripts/launch.sh b/util/scripts/launch.sh index 5233cb55fb..b5e0f8c03e 100755 --- a/util/scripts/launch.sh +++ b/util/scripts/launch.sh @@ -54,12 +54,11 @@ else fi #============================================================================ - -if [[ "$0" == *lfc ]]; then +if [[ "${0%%-dev}" == *lfc ]]; then tool="lfc" -elif [[ "$0" == *lff ]]; then +elif [[ "${0%%-dev}" == *lff ]]; then tool="lff" -elif [[ "$0" == *lfd ]]; then +elif [[ "${0%%-dev}" == *lfd ]]; then tool="lfd" else known_commands="[lfc, lff, lfd]" From 805c149cf8b1d531a885b7e80c024fa13d37223b Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Wed, 6 Sep 2023 18:15:17 -0700 Subject: [PATCH 453/516] Add trailing newline --- util/scripts/launch.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/scripts/launch.sh b/util/scripts/launch.sh index b5e0f8c03e..5e52051c60 100755 --- a/util/scripts/launch.sh +++ b/util/scripts/launch.sh @@ -74,4 +74,4 @@ gradlew="${base}/gradlew" # Launch the tool. "${gradlew}" --quiet assemble ":cli:${tool}:assemble" -"${base}/build/install/lf-cli/bin/${tool}" "$@" \ No newline at end of file +"${base}/build/install/lf-cli/bin/${tool}" "$@" From 313db97f79e18edced156060f968cb48f87014fd Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Wed, 6 Sep 2023 18:35:55 -0700 Subject: [PATCH 454/516] Update CLI tests --- .github/scripts/test-lfc.sh | 6 +++--- .github/scripts/test-lfd.sh | 6 +++--- .github/scripts/test-lff.sh | 16 ++++++++-------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/scripts/test-lfc.sh b/.github/scripts/test-lfc.sh index fe9b6e3757..bc43342e44 100755 --- a/.github/scripts/test-lfc.sh +++ b/.github/scripts/test-lfc.sh @@ -14,7 +14,7 @@ function test_with_links() { foo/bar/baz/link-${1} --help } -bin/lfc test/C/src/Minimal.lf +bin/lfc-dev test/C/src/Minimal.lf -# Ensure that lfc is robust to symbolic links. -test_with_links "lfc" +# Ensure that lfc can be invoked via symbolic links. +test_with_links "lfc-dev" diff --git a/.github/scripts/test-lfd.sh b/.github/scripts/test-lfd.sh index 041daf7029..0f04fcc5b1 100755 --- a/.github/scripts/test-lfd.sh +++ b/.github/scripts/test-lfd.sh @@ -14,7 +14,7 @@ function test_with_links() { foo/bar/baz/link-${1} --help } -bin/lfd test/C/src/Minimal.lf +bin/lfd-dev test/C/src/Minimal.lf -# Ensure that lfd is robust to symbolic links. -test_with_links "lfd" +# Ensure that lfd can be invoked via symbolic links. +test_with_links "lfd-dev" diff --git a/.github/scripts/test-lff.sh b/.github/scripts/test-lff.sh index d98578a39b..273c26b429 100755 --- a/.github/scripts/test-lff.sh +++ b/.github/scripts/test-lff.sh @@ -15,14 +15,14 @@ function test_with_links() { } # just a couple of smoke tests -bin/lff --help -bin/lff --version +bin/lff-dev --help +bin/lff-dev --version -bin/lff -d test/C/src/Minimal.lf -bin/lff --dry-run test/Cpp/src/Minimal.lf +bin/lff-dev -d test/C/src/Minimal.lf +bin/lff-dev --dry-run test/Cpp/src/Minimal.lf -bin/lff -d test/C/src/Minimal.lf -bin/lff --dry-run test/Cpp/src/Minimal.lf +bin/lff-dev -d test/C/src/Minimal.lf +bin/lff-dev --dry-run test/Cpp/src/Minimal.lf -# Ensure that lff is robust to symbolic links. -test_with_links "lff" +# Ensure that lff can be invoked via symbolic links. +test_with_links "lff-dev" From 6b49fa464a44f4cbc7a7edfd6764a525df40452e Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Wed, 6 Sep 2023 20:09:56 -0700 Subject: [PATCH 455/516] Fix more filenames --- .github/workflows/cli-tests.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cli-tests.yml b/.github/workflows/cli-tests.yml index 2b718a774d..5312aef33c 100644 --- a/.github/workflows/cli-tests.yml +++ b/.github/workflows/cli-tests.yml @@ -44,24 +44,24 @@ jobs: if: ${{ runner.os == 'macOS' || runner.os == 'Linux' }} - name: Test lfc PowerShell script (Windows only) run: | - bin/lfc.ps1 --version - bin/lfc.ps1 test/C/src/Minimal.lf + bin/lfc-dev.ps1 --version + bin/lfc-dev.ps1 test/C/src/Minimal.lf ./gradlew assemble ./build/install/lf-cli/bin/lfc.bat --version ./build/install/lf-cli/bin/lfc.bat test/C/src/Minimal.lf if: ${{ runner.os == 'Windows' }} - name: Test lff PowerShell script (Windows only) run: | - bin/lff.ps1 --version - bin/lff.ps1 test/C/src/Minimal.lf + bin/lff-dev-dev.ps1 --version + bin/lff-dev-dev.ps1 test/C/src/Minimal.lf ./gradlew assemble ./build/install/lf-cli/bin/lff.bat --version ./build/install/lf-cli/bin/lff.bat test/C/src/Minimal.lf if: ${{ runner.os == 'Windows' }} - name: Test lfd PowerShell script (Windows only) run: | - bin/lfd.ps1 --version - bin/lfd.ps1 test/C/src/Minimal.lf + bin/lfd-dev.ps1 --version + bin/lfd-dev.ps1 test/C/src/Minimal.lf ./gradlew assemble ./build/install/lf-cli/bin/lfd.bat --version ./build/install/lf-cli/bin/lfd.bat test/C/src/Minimal.lf From d310df13bdd7fb7da9d18712669a5a083139dec4 Mon Sep 17 00:00:00 2001 From: i love smoked beef brisket Date: Wed, 6 Sep 2023 20:42:00 -0700 Subject: [PATCH 456/516] Strip out '-dev', and add trailing space --- util/scripts/launch.ps1 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/util/scripts/launch.ps1 b/util/scripts/launch.ps1 index 40cd25ba55..9100c75434 100644 --- a/util/scripts/launch.ps1 +++ b/util/scripts/launch.ps1 @@ -13,7 +13,8 @@ $invokerName = [System.IO.Path]::GetFileNameWithoutExtension("$(Split-Path -Path $mainClassTable = @{"lfc" = "org.lflang.cli.Lfc"; "lff" = "org.lflang.cli.Lff"; "lfd" = "org.lflang.cli.Lfd"} $tool = $null foreach ($k in $mainClassTable.Keys) { - if ($invokerName.EndsWith($k)) { + # This replacement is not ideal, but it is kinda analogous to its counterpart in launch.sh. + if ($invokerName.Contains(($k -replace "-dev", ""))) { $tool = $k break } @@ -30,4 +31,4 @@ $gradlew="${base}/gradlew.bat" # invoke script & "${gradlew}" --quiet assemble ":cli:${tool}:assemble" -& "${base}/build/install/lf-cli/bin/${tool}" @args \ No newline at end of file +& "${base}/build/install/lf-cli/bin/${tool}" @args From 3f42c83a51b037abaabc49c91d568dd50c39040b Mon Sep 17 00:00:00 2001 From: i love smoked beef brisket Date: Wed, 6 Sep 2023 20:53:18 -0700 Subject: [PATCH 457/516] Fix broken script names --- .github/workflows/cli-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cli-tests.yml b/.github/workflows/cli-tests.yml index 5312aef33c..edcc31b6f8 100644 --- a/.github/workflows/cli-tests.yml +++ b/.github/workflows/cli-tests.yml @@ -52,8 +52,8 @@ jobs: if: ${{ runner.os == 'Windows' }} - name: Test lff PowerShell script (Windows only) run: | - bin/lff-dev-dev.ps1 --version - bin/lff-dev-dev.ps1 test/C/src/Minimal.lf + bin/lff-dev.ps1 --version + bin/lff-dev.ps1 test/C/src/Minimal.lf ./gradlew assemble ./build/install/lf-cli/bin/lff.bat --version ./build/install/lf-cli/bin/lff.bat test/C/src/Minimal.lf From cfa80a298adaa50fe4ab9b49b76ca8d428d1c6dc Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Wed, 6 Sep 2023 22:02:48 -0700 Subject: [PATCH 458/516] Set outputs accordingly --- .github/actions/latest-release/action.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/actions/latest-release/action.yml b/.github/actions/latest-release/action.yml index 98c51bb900..8d78884008 100644 --- a/.github/actions/latest-release/action.yml +++ b/.github/actions/latest-release/action.yml @@ -1,5 +1,14 @@ name: Latest release description: Report the latest release of the current repo +outputs: + outputs: + ref: + value: ${{ steps.find.outputs.ref }} + description: The latest semver tag + ver: + value: ${{ steps.find.outputs.ver }} + description: The semver corresponding to the latest semver tag + runs: using: "composite" steps: From c99a0f9a88bb21828eb3b1264be79756d9d9baaa Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Wed, 6 Sep 2023 22:14:27 -0700 Subject: [PATCH 459/516] Yaml is not a programming language --- .github/actions/latest-release/action.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/actions/latest-release/action.yml b/.github/actions/latest-release/action.yml index 8d78884008..910da91c03 100644 --- a/.github/actions/latest-release/action.yml +++ b/.github/actions/latest-release/action.yml @@ -1,7 +1,6 @@ name: Latest release description: Report the latest release of the current repo outputs: - outputs: ref: value: ${{ steps.find.outputs.ref }} description: The latest semver tag From b14e8378e4b9ea42c0d5093e9e3b94aa42aca977 Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Thu, 7 Sep 2023 20:49:26 +0900 Subject: [PATCH 460/516] Use "rti" as the hostname of the RTI when it comes to a dockerized federation --- .../java/org/lflang/federated/generator/FedGenerator.java | 8 ++++++++ .../src/docker/federated/DistributedCountContainerized.lf | 2 +- .../federated/DistributedDoublePortContainerized.lf | 2 +- .../docker/federated/DistributedMultiportContainerized.lf | 2 +- .../DistributedStopDecentralizedContainerized.lf | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java index f885daabdf..8c7f190e4a 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java +++ b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java @@ -416,6 +416,14 @@ private void analyzeFederates(Reactor federation, LFGeneratorContext context) { && !federation.getHost().getAddr().equals("localhost")) { rtiConfig.setHost(federation.getHost().getAddr()); } + // If the federation is dockerized, use "rti" as the hostname. + // FIXME: Do we have to check the condition rtiConfig.getHost().equals("localhost")? + // In other words, do we have to preserve the user-specified IP address of the RTI + // when the program is dockerized? + if (rtiConfig.getHost().equals("localhost") + && targetConfig.dockerOptions != null) { + rtiConfig.setHost("rti"); + } // Since federates are always within the main (federated) reactor, // create a list containing just that one containing instantiation. diff --git a/test/C/src/docker/federated/DistributedCountContainerized.lf b/test/C/src/docker/federated/DistributedCountContainerized.lf index d7f5fc2cdb..0c9ae24594 100644 --- a/test/C/src/docker/federated/DistributedCountContainerized.lf +++ b/test/C/src/docker/federated/DistributedCountContainerized.lf @@ -14,7 +14,7 @@ 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) { c = new Count() p = new Print() c.out -> p.in after offset diff --git a/test/C/src/docker/federated/DistributedDoublePortContainerized.lf b/test/C/src/docker/federated/DistributedDoublePortContainerized.lf index c9273862c5..adad1f72b1 100644 --- a/test/C/src/docker/federated/DistributedDoublePortContainerized.lf +++ b/test/C/src/docker/federated/DistributedDoublePortContainerized.lf @@ -15,7 +15,7 @@ import Count from "../../lib/Count.lf" import CountMicrostep from "../../federated/DistributedDoublePort.lf" import Print from "../../federated/DistributedDoublePort.lf" -federated reactor DistributedDoublePortContainerized at rti { +federated reactor DistributedDoublePortContainerized { c = new Count() cm = new CountMicrostep() p = new Print() diff --git a/test/C/src/docker/federated/DistributedMultiportContainerized.lf b/test/C/src/docker/federated/DistributedMultiportContainerized.lf index 6250c105c4..026b6f4f17 100644 --- a/test/C/src/docker/federated/DistributedMultiportContainerized.lf +++ b/test/C/src/docker/federated/DistributedMultiportContainerized.lf @@ -7,7 +7,7 @@ target C { import Source, Destination from "../../federated/DistributedMultiport.lf" -federated reactor DistributedMultiportContainerized at rti { +federated reactor DistributedMultiportContainerized { s = new Source(width=4) d = new Destination(width=4) s.out -> d.in diff --git a/test/C/src/docker/federated/DistributedStopDecentralizedContainerized.lf b/test/C/src/docker/federated/DistributedStopDecentralizedContainerized.lf index 5480a6f06c..0c29c1aec9 100644 --- a/test/C/src/docker/federated/DistributedStopDecentralizedContainerized.lf +++ b/test/C/src/docker/federated/DistributedStopDecentralizedContainerized.lf @@ -10,7 +10,7 @@ target C { import Sender, Receiver from "../../federated/DistributedStop.lf" -federated reactor DistributedStopDecentralizedContainerized at rti { +federated reactor DistributedStopDecentralizedContainerized { sender = new Sender() receiver = new Receiver() sender.out -> receiver.in From 73e660cfc54ed06274e916ebaa1b61a19f438301 Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun Date: Thu, 7 Sep 2023 21:00:03 +0900 Subject: [PATCH 461/516] Formatting comments --- .../org/lflang/federated/generator/FedGenerator.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java index 8c7f190e4a..ed9969202c 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java +++ b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java @@ -417,11 +417,10 @@ private void analyzeFederates(Reactor federation, LFGeneratorContext context) { rtiConfig.setHost(federation.getHost().getAddr()); } // If the federation is dockerized, use "rti" as the hostname. - // FIXME: Do we have to check the condition rtiConfig.getHost().equals("localhost")? - // In other words, do we have to preserve the user-specified IP address of the RTI - // when the program is dockerized? - if (rtiConfig.getHost().equals("localhost") - && targetConfig.dockerOptions != null) { + // FIXME: Do we have to check the condition rtiConfig.getHost().equals("localhost")? + // In other words, do we have to preserve the user-specified IP address of the RTI + // when the program is dockerized? + if (rtiConfig.getHost().equals("localhost") && targetConfig.dockerOptions != null) { rtiConfig.setHost("rti"); } From af12fb4c8b4b55b5cfcd7c53cd9684f6ec3be2a4 Mon Sep 17 00:00:00 2001 From: Byeong-gil Jun <78055940+byeong-gil@users.noreply.github.com> Date: Fri, 8 Sep 2023 08:26:44 +0900 Subject: [PATCH 462/516] Update FedGenerator.java --- .../main/java/org/lflang/federated/generator/FedGenerator.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java index ed9969202c..8aca1fec8f 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java +++ b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java @@ -417,9 +417,6 @@ private void analyzeFederates(Reactor federation, LFGeneratorContext context) { rtiConfig.setHost(federation.getHost().getAddr()); } // If the federation is dockerized, use "rti" as the hostname. - // FIXME: Do we have to check the condition rtiConfig.getHost().equals("localhost")? - // In other words, do we have to preserve the user-specified IP address of the RTI - // when the program is dockerized? if (rtiConfig.getHost().equals("localhost") && targetConfig.dockerOptions != null) { rtiConfig.setHost("rti"); } From c3e2d1c9ce656546d262085175da9b4e70a26df0 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Fri, 8 Sep 2023 00:06:07 -0700 Subject: [PATCH 463/516] Update latest-release.yml --- .github/workflows/latest-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/latest-release.yml b/.github/workflows/latest-release.yml index cdd65f59f1..1169ff30ee 100644 --- a/.github/workflows/latest-release.yml +++ b/.github/workflows/latest-release.yml @@ -42,4 +42,4 @@ jobs: with: repository: ${{ github.event.inputs.owner || inputs.owner }}/${{ github.event.inputs.repo || inputs.repo }} - id: semver - uses: lf-lang/lingua-franca/.github/actions/latest-release@ci-stuff + uses: lf-lang/lingua-franca/.github/actions/latest-release@master From 98ece714c8854a4fd64dd3d2aa24fc99e4bcea34 Mon Sep 17 00:00:00 2001 From: Omer Majeed Date: Thu, 17 Aug 2023 18:15:57 +0500 Subject: [PATCH 464/516] unconnected multi-port, and bank reactor bug --- .../org/lflang/generator/c/CTriggerObjectsGenerator.java | 7 +++++++ core/src/main/resources/lib/c/reactor-c | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java b/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java index 77e7eee22e..8072e514c7 100644 --- a/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java @@ -783,6 +783,9 @@ private static String deferredOutputNumDestinations(ReactorInstance reactor) { } if (output.eventualDestinations().size() == 0) { + // Iteration for unconnected case when + // output port is a multiport + code.startChannelIteration(output); // Dangling output. Still set the source reactor code.pr( "for (int index486184027c8990b = 0; index486184027c8990b < " @@ -814,6 +817,9 @@ private static String deferredInitializeNonNested( CTypes types) { var code = new CodeBuilder(); code.pr("// **** Start non-nested deferred initialize for " + reactor.getFullName()); + // Initialization within a for loop iterating + // over bank members of reactor + code.startScopedBlock(reactor); // Initialize the num_destinations fields of port structs on the self struct. // This needs to be outside the above scoped block because it performs // its own iteration over ranges. @@ -830,6 +836,7 @@ private static String deferredInitializeNonNested( for (ReactorInstance child : reactor.children) { code.pr(deferredInitializeNonNested(child, main, child.reactions, types)); } + code.endScopedBlock(); code.pr("// **** End of non-nested deferred initialize for " + reactor.getFullName()); return code.toString(); } diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 09b75edfdb..809c454db0 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 09b75edfdbfcd0206a89199fcb65b1678398a0c7 +Subproject commit 809c454db07c1b5e03a789a4863137107b817da6 From 8057121c751344a0ab446346a2c78627740005e8 Mon Sep 17 00:00:00 2001 From: Omer Majeed Date: Fri, 18 Aug 2023 01:52:39 +0500 Subject: [PATCH 465/516] test file to test multi-port, multi-bank bug --- test/C/src/MultiPort_MultiBankTest.lf | 113 ++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 test/C/src/MultiPort_MultiBankTest.lf diff --git a/test/C/src/MultiPort_MultiBankTest.lf b/test/C/src/MultiPort_MultiBankTest.lf new file mode 100644 index 0000000000..39f5aa3031 --- /dev/null +++ b/test/C/src/MultiPort_MultiBankTest.lf @@ -0,0 +1,113 @@ +target C { + keepalive: true, + workers: 1 +}; + +preamble {= + #define P_FILE FILE * +=} + +reactor adder (bank_index:int(0), n_ports:int(1), log_file:FILE*({=stderr=})) { + input[n_ports] add_request:entry_T; + output[n_ports] add_response:entry_T; + + input[n_ports] result_request:entry_T; + output[n_ports] result_response:entry_T; + + state sum:entry_T(0); + + reaction (startup) {= + fprintf (self->log_file, "(%lld, %u) physical_time:%lld " + "adder_%d ports:%d " + "startup\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, self->n_ports + ); + =} + + reaction (add_request) -> add_response {= + for (int i = 0; i < add_request_width; ++i) { + if (add_request[i]->is_present) { + int req = add_request[i]->value; + fprintf (self->log_file, "(%lld, %u) physical_time:%lld " + "adder_%d port:%d " + "received add request:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, i, req + ); + self->sum += req; + lf_set (add_response[i], self->sum); + } + } + =} + + reaction (result_request) -> result_response {= + for (int i = 0; i < result_request_width; ++i) { + if (result_request[i]->is_present) { + int req = result_request[i]->value; + fprintf (self->log_file, "(%lld, %u) physical_time:%lld " + "adder_%d port:%d " + "received query request:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, i, req + ); + self->sum += req; + lf_set (result_response[i], self->sum); + } + } + =} +} + +reactor testing_adder (bank_index:int(0), iterations:uint32_t(20), log_file:FILE*({=stderr=})) { + output add_req:int; + output result_req:int; + + input add_resp:int; + input result_resp:int; + + state seed:uint32_t(0); + state request_counter:uint32_t(0); + state response_counter:uint32_t(0); + + timer t(0, 2 sec); + + reaction (startup) {= + fprintf (self->log_file, "(%lld, %u) physical_time:%lld " + "testing_adder_%d startup\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index + ); + =} + + reaction (t) -> add_req {= + int number = rand_r(&self->seed) % 100; + fprintf (self->log_file, "(%lld, %u) physical_time:%lld " + "testing_adder_%d sending add request[%u] number:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, self->request_counter, number + ); + lf_set (add_req, number); + ++self->request_counter; + =} + + reaction (add_resp) {= + int rsp = add_resp->value; + fprintf (self->log_file, "(%lld, %u) physical_time:%lld " + "testing_adder_%d response[%u] sum:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, self->response_counter, rsp); + + ++self->response_counter; + if (self->response_counter == self->iterations) { + lf_request_stop(); + } + =} +} + +main reactor MultiPort_MultiBankTest { + test = new [2] testing_adder(iterations = 20); + a = new [2] adder (); + + test.add_req -> a.add_request; + a.add_response -> test.add_resp; +} \ No newline at end of file From 04b9fc3d4e06a7a8687ca493e35ce159596af6f1 Mon Sep 17 00:00:00 2001 From: Omer Majeed Date: Fri, 18 Aug 2023 01:58:55 +0500 Subject: [PATCH 466/516] removed unnecessary preamble --- test/C/src/MultiPort_MultiBankTest.lf | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/C/src/MultiPort_MultiBankTest.lf b/test/C/src/MultiPort_MultiBankTest.lf index 39f5aa3031..558d3ae1da 100644 --- a/test/C/src/MultiPort_MultiBankTest.lf +++ b/test/C/src/MultiPort_MultiBankTest.lf @@ -3,10 +3,6 @@ target C { workers: 1 }; -preamble {= - #define P_FILE FILE * -=} - reactor adder (bank_index:int(0), n_ports:int(1), log_file:FILE*({=stderr=})) { input[n_ports] add_request:entry_T; output[n_ports] add_response:entry_T; From 8b4c5ce609d2dcfdd2b645455b033d73511dfb0e Mon Sep 17 00:00:00 2001 From: Omer Majeed Date: Wed, 23 Aug 2023 23:43:39 +0500 Subject: [PATCH 467/516] added multi-port and multi-bank tests for unconnected output port --- ...iBankToMultiPort_UnconnectedOutput_Test.lf | 86 ++++++++++++++++++ .../C/src/MultiBank_UnconnectedOutput_Test.lf | 82 +++++++++++++++++ ...t_MultiBankTest_UnconnectedOutput_Test.lf} | 2 +- .../C/src/MultiPort_UnconnectedOutput_Test.lf | 91 +++++++++++++++++++ 4 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 test/C/src/MultiBankToMultiPort_UnconnectedOutput_Test.lf create mode 100644 test/C/src/MultiBank_UnconnectedOutput_Test.lf rename test/C/src/{MultiPort_MultiBankTest.lf => MultiPort_MultiBankTest_UnconnectedOutput_Test.lf} (98%) create mode 100644 test/C/src/MultiPort_UnconnectedOutput_Test.lf diff --git a/test/C/src/MultiBankToMultiPort_UnconnectedOutput_Test.lf b/test/C/src/MultiBankToMultiPort_UnconnectedOutput_Test.lf new file mode 100644 index 0000000000..b3c9ccf679 --- /dev/null +++ b/test/C/src/MultiBankToMultiPort_UnconnectedOutput_Test.lf @@ -0,0 +1,86 @@ +target C { + keepalive: true, + workers: 1 +}; + +reactor echo (n_ports:int(1)) { + input[n_ports] request:entry_T; + output[n_ports] response:entry_T; + + output[n_ports] unconnected:entry_T; + + reaction (startup) {= + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "echo ports:%d" + "startup\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->n_ports + ); + =} + + reaction (request) -> response {= + for (int i = 0; i < request_width; ++i) { + if (request[i]->is_present) { + int req = request[i]->value; + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "echo port:%d " + "received echo request:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + i, req + ); + lf_set (response[i], req + i); + } + } + =} +} + +reactor testing_echo (bank_index:int(0), iterations:uint32_t(20), log_file:FILE*({=stderr=})) { + output req:int; + input resp:int; + + state seed:uint32_t(0); + state request_counter:uint32_t(0); + state response_counter:uint32_t(0); + + timer t(0, 2 sec); + + reaction (startup) {= + fprintf (self->log_file, "(%lld, %u) physical_time:%lld " + "testing_echo_%d startup\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index + ); + =} + + reaction (t) -> req {= + int number = rand_r(&self->seed); + fprintf (self->log_file, "(%lld, %u) physical_time:%lld " + "testing_echo_%d sending echo request[%u] number:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, self->request_counter, number + ); + lf_set (req, number + self->bank_index); + ++self->request_counter; + =} + + reaction (resp) {= + int rsp = resp->value; + fprintf (self->log_file, "(%lld, %u) physical_time:%lld " + "testing_echo_%d echo response[%u] number:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, self->response_counter, rsp); + + ++self->response_counter; + if (self->response_counter == self->iterations) { + lf_request_stop(); + } + =} +} + +main reactor MultiBankToMultiPort_UnconnectedOutput_Test { + test = new [2] testing_echo(iterations = 20); + e = new echo (n_ports = 2); + + test.req -> e.request; + e.response -> test.resp; +} \ No newline at end of file diff --git a/test/C/src/MultiBank_UnconnectedOutput_Test.lf b/test/C/src/MultiBank_UnconnectedOutput_Test.lf new file mode 100644 index 0000000000..6170299514 --- /dev/null +++ b/test/C/src/MultiBank_UnconnectedOutput_Test.lf @@ -0,0 +1,82 @@ +target C { + keepalive: true, + workers: 1 +}; + +reactor echo (bank_index:int(0)) { + input request:entry_T; + output response:entry_T; + + output unconnected:entry_T; + + reaction (startup) {= + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "echo_%d " + "startup\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index + ); + =} + + reaction (request) -> response {= + int req = request->value; + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "echo_%d " + "received echo request:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, req + ); + lf_set (response, req + self->bank_index); + =} +} + +reactor testing_echo (bank_index:int(0), iterations:uint32_t(20)) { + output req:int; + input resp:int; + + state seed:uint32_t(0); + state request_counter:uint32_t(0); + state response_counter:uint32_t(0); + + timer t(0, 2 sec); + + reaction (startup) {= + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "testing_echo_%d startup\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index + ); + =} + + reaction (t) -> req {= + int number = rand_r(&self->seed); + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "testing_echo_%d sending echo request[%u] number:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, self->request_counter, number + ); + lf_set (req, number + self->bank_index); + ++self->request_counter; + =} + + reaction (resp) {= + int rsp = resp->value; + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "testing_echo_%d echo response[%u] number:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, self->response_counter, rsp); + + ++self->response_counter; + if (self->response_counter == self->iterations) { + lf_request_stop(); + } + =} +} + +main reactor MultiBank_UnconnectedOutput_Test { + test = new [2] testing_echo(iterations = 20); + e = new [2] echo (); + + test.req -> e.request; + e.response -> test.resp; +} \ No newline at end of file diff --git a/test/C/src/MultiPort_MultiBankTest.lf b/test/C/src/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf similarity index 98% rename from test/C/src/MultiPort_MultiBankTest.lf rename to test/C/src/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf index 558d3ae1da..74813d23af 100644 --- a/test/C/src/MultiPort_MultiBankTest.lf +++ b/test/C/src/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf @@ -100,7 +100,7 @@ reactor testing_adder (bank_index:int(0), iterations:uint32_t(20), log_file:FILE =} } -main reactor MultiPort_MultiBankTest { +main reactor MultiPort_MultiBankTest_UnconnectedOutput_Test { test = new [2] testing_adder(iterations = 20); a = new [2] adder (); diff --git a/test/C/src/MultiPort_UnconnectedOutput_Test.lf b/test/C/src/MultiPort_UnconnectedOutput_Test.lf new file mode 100644 index 0000000000..a189ba29a8 --- /dev/null +++ b/test/C/src/MultiPort_UnconnectedOutput_Test.lf @@ -0,0 +1,91 @@ +target C { + keepalive: true, + workers: 1 +}; + +reactor echo (n_ports:int(1)) { + input[n_ports] request:entry_T; + output[n_ports] response:entry_T; + + output[n_ports] unconnected:entry_T; + + reaction (startup) {= + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "echo ports:%d" + "startup\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->n_ports + ); + =} + + reaction (request) -> response {= + for (int i = 0; i < request_width; ++i) { + if (request[i]->is_present) { + int req = request[i]->value; + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "echo port:%d " + "received echo request:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + i, req + ); + lf_set (response[i], req + i); + } + } + =} +} + +reactor testing_echo (n_ports:int(1), iterations:uint32_t(20), log_file:FILE*({=stderr=})) { + output[n_ports] req:int; + input[n_ports] resp:int; + + state seed:uint32_t(0); + state request_counter:uint32_t(0); + state response_counter:uint32_t(0); + + timer t(0, 2 sec); + + reaction (startup) {= + fprintf (self->log_file, "(%lld, %u) physical_time:%lld " + "testing_echo ports:%d startup\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->n_ports + ); + =} + + reaction (t) -> req {= + int number = rand_r(&self->seed); + for (int i = 0; i < req_width; ++i) { + fprintf (self->log_file, "(%lld, %u) physical_time:%lld " + "testing_echo port:%d sending echo request[%u] number:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + i, self->request_counter, number + i + ); + lf_set (req[i], number + i); + } + ++self->request_counter; + if (self->request_counter == self->iterations) { + lf_request_stop(); + } + =} + + reaction (resp) {= + for (int i = 0; i < resp_width; ++i) { + if (resp[i]->is_present) { + int rsp = resp[i]->value; + fprintf (self->log_file, "(%lld, %u) physical_time:%lld " + "testing_echo port:%d echo response[%u] new_number:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + i, self->response_counter, rsp); + } + } + ++self->response_counter; + =} +} + +main reactor MultiPort_UnconnectedOutput_Test { + test = new testing_echo(n_ports = 2, iterations = 20); + e = new echo (n_ports = 2); + + test.req -> e.request; + e.response -> test.resp; +} \ No newline at end of file From 5e6b7234f93d5073d76268e9f80365fb4ac43dfb Mon Sep 17 00:00:00 2001 From: Omer Majeed Date: Thu, 7 Sep 2023 21:01:20 +0500 Subject: [PATCH 468/516] keep the master changes for this multi-port fix --- .../java/org/lflang/generator/c/CTriggerObjectsGenerator.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java b/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java index 8072e514c7..98aec56d80 100644 --- a/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java @@ -783,9 +783,6 @@ private static String deferredOutputNumDestinations(ReactorInstance reactor) { } if (output.eventualDestinations().size() == 0) { - // Iteration for unconnected case when - // output port is a multiport - code.startChannelIteration(output); // Dangling output. Still set the source reactor code.pr( "for (int index486184027c8990b = 0; index486184027c8990b < " From 9b9d734cdbcc1ca41af12db1c6c5e5a97f9b719f Mon Sep 17 00:00:00 2001 From: Omer Majeed Date: Thu, 7 Sep 2023 21:58:01 +0500 Subject: [PATCH 469/516] test case fixes for PR to go into master --- ...iBankToMultiPort_UnconnectedOutput_Test.lf | 86 ------------------ .../C/src/MultiBank_UnconnectedOutput_Test.lf | 33 ++----- ...rt_MultiBankTest_UnconnectedOutput_Test.lf | 82 +++++------------ .../C/src/MultiPort_UnconnectedOutput_Test.lf | 91 ------------------- 4 files changed, 32 insertions(+), 260 deletions(-) delete mode 100644 test/C/src/MultiBankToMultiPort_UnconnectedOutput_Test.lf delete mode 100644 test/C/src/MultiPort_UnconnectedOutput_Test.lf diff --git a/test/C/src/MultiBankToMultiPort_UnconnectedOutput_Test.lf b/test/C/src/MultiBankToMultiPort_UnconnectedOutput_Test.lf deleted file mode 100644 index b3c9ccf679..0000000000 --- a/test/C/src/MultiBankToMultiPort_UnconnectedOutput_Test.lf +++ /dev/null @@ -1,86 +0,0 @@ -target C { - keepalive: true, - workers: 1 -}; - -reactor echo (n_ports:int(1)) { - input[n_ports] request:entry_T; - output[n_ports] response:entry_T; - - output[n_ports] unconnected:entry_T; - - reaction (startup) {= - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "echo ports:%d" - "startup\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->n_ports - ); - =} - - reaction (request) -> response {= - for (int i = 0; i < request_width; ++i) { - if (request[i]->is_present) { - int req = request[i]->value; - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "echo port:%d " - "received echo request:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - i, req - ); - lf_set (response[i], req + i); - } - } - =} -} - -reactor testing_echo (bank_index:int(0), iterations:uint32_t(20), log_file:FILE*({=stderr=})) { - output req:int; - input resp:int; - - state seed:uint32_t(0); - state request_counter:uint32_t(0); - state response_counter:uint32_t(0); - - timer t(0, 2 sec); - - reaction (startup) {= - fprintf (self->log_file, "(%lld, %u) physical_time:%lld " - "testing_echo_%d startup\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index - ); - =} - - reaction (t) -> req {= - int number = rand_r(&self->seed); - fprintf (self->log_file, "(%lld, %u) physical_time:%lld " - "testing_echo_%d sending echo request[%u] number:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, self->request_counter, number - ); - lf_set (req, number + self->bank_index); - ++self->request_counter; - =} - - reaction (resp) {= - int rsp = resp->value; - fprintf (self->log_file, "(%lld, %u) physical_time:%lld " - "testing_echo_%d echo response[%u] number:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, self->response_counter, rsp); - - ++self->response_counter; - if (self->response_counter == self->iterations) { - lf_request_stop(); - } - =} -} - -main reactor MultiBankToMultiPort_UnconnectedOutput_Test { - test = new [2] testing_echo(iterations = 20); - e = new echo (n_ports = 2); - - test.req -> e.request; - e.response -> test.resp; -} \ No newline at end of file diff --git a/test/C/src/MultiBank_UnconnectedOutput_Test.lf b/test/C/src/MultiBank_UnconnectedOutput_Test.lf index 6170299514..e10d4fe3c9 100644 --- a/test/C/src/MultiBank_UnconnectedOutput_Test.lf +++ b/test/C/src/MultiBank_UnconnectedOutput_Test.lf @@ -1,5 +1,4 @@ target C { - keepalive: true, workers: 1 }; @@ -30,51 +29,35 @@ reactor echo (bank_index:int(0)) { =} } -reactor testing_echo (bank_index:int(0), iterations:uint32_t(20)) { +reactor testing_echo (bank_index:int(0)) { output req:int; input resp:int; state seed:uint32_t(0); - state request_counter:uint32_t(0); - state response_counter:uint32_t(0); - timer t(0, 2 sec); - - reaction (startup) {= - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "testing_echo_%d startup\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index - ); - =} - - reaction (t) -> req {= + reaction (startup) -> req {= int number = rand_r(&self->seed); fprintf (stderr, "(%lld, %u) physical_time:%lld " - "testing_echo_%d sending echo request[%u] number:%d\n", + "testing_echo_%d sending echo request number:%d\n", lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, self->request_counter, number + self->bank_index, number ); lf_set (req, number + self->bank_index); - ++self->request_counter; =} reaction (resp) {= int rsp = resp->value; fprintf (stderr, "(%lld, %u) physical_time:%lld " - "testing_echo_%d echo response[%u] number:%d\n", + "testing_echo_%d echo response number:%d\n", lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, self->response_counter, rsp); + self->bank_index, rsp); - ++self->response_counter; - if (self->response_counter == self->iterations) { - lf_request_stop(); - } + lf_request_stop(); =} } main reactor MultiBank_UnconnectedOutput_Test { - test = new [2] testing_echo(iterations = 20); + test = new [2] testing_echo(); e = new [2] echo (); test.req -> e.request; diff --git a/test/C/src/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf b/test/C/src/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf index 74813d23af..8ebb1da8ce 100644 --- a/test/C/src/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf +++ b/test/C/src/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf @@ -1,23 +1,21 @@ target C { - keepalive: true, workers: 1 }; -reactor adder (bank_index:int(0), n_ports:int(1), log_file:FILE*({=stderr=})) { +reactor adder (bank_index:int(0), n_ports:int(1)) { input[n_ports] add_request:entry_T; output[n_ports] add_response:entry_T; - input[n_ports] result_request:entry_T; - output[n_ports] result_response:entry_T; + output[n_ports] unconnected:entry_T; state sum:entry_T(0); reaction (startup) {= - fprintf (self->log_file, "(%lld, %u) physical_time:%lld " - "adder_%d ports:%d " - "startup\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, self->n_ports + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "adder_%d ports:%d " + "startup\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, self->n_ports ); =} @@ -25,36 +23,20 @@ reactor adder (bank_index:int(0), n_ports:int(1), log_file:FILE*({=stde for (int i = 0; i < add_request_width; ++i) { if (add_request[i]->is_present) { int req = add_request[i]->value; - fprintf (self->log_file, "(%lld, %u) physical_time:%lld " - "adder_%d port:%d " - "received add request:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, i, req + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "adder_%d port:%d " + "received add request:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, i, req ); self->sum += req; lf_set (add_response[i], self->sum); } } =} - - reaction (result_request) -> result_response {= - for (int i = 0; i < result_request_width; ++i) { - if (result_request[i]->is_present) { - int req = result_request[i]->value; - fprintf (self->log_file, "(%lld, %u) physical_time:%lld " - "adder_%d port:%d " - "received query request:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, i, req - ); - self->sum += req; - lf_set (result_response[i], self->sum); - } - } - =} } -reactor testing_adder (bank_index:int(0), iterations:uint32_t(20), log_file:FILE*({=stderr=})) { +reactor testing_adder (bank_index:int(0)) { output add_req:int; output result_req:int; @@ -62,46 +44,30 @@ reactor testing_adder (bank_index:int(0), iterations:uint32_t(20), log_file:FILE input result_resp:int; state seed:uint32_t(0); - state request_counter:uint32_t(0); - state response_counter:uint32_t(0); - - timer t(0, 2 sec); - reaction (startup) {= - fprintf (self->log_file, "(%lld, %u) physical_time:%lld " - "testing_adder_%d startup\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index - ); - =} - - reaction (t) -> add_req {= + reaction (startup) -> add_req {= int number = rand_r(&self->seed) % 100; - fprintf (self->log_file, "(%lld, %u) physical_time:%lld " - "testing_adder_%d sending add request[%u] number:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, self->request_counter, number + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "testing_adder_%d sending add request number:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, number ); lf_set (add_req, number); - ++self->request_counter; =} reaction (add_resp) {= int rsp = add_resp->value; - fprintf (self->log_file, "(%lld, %u) physical_time:%lld " - "testing_adder_%d response[%u] sum:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, self->response_counter, rsp); + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "testing_adder_%d response sum:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, rsp); - ++self->response_counter; - if (self->response_counter == self->iterations) { - lf_request_stop(); - } + lf_request_stop(); =} } main reactor MultiPort_MultiBankTest_UnconnectedOutput_Test { - test = new [2] testing_adder(iterations = 20); + test = new [2] testing_adder(); a = new [2] adder (); test.add_req -> a.add_request; diff --git a/test/C/src/MultiPort_UnconnectedOutput_Test.lf b/test/C/src/MultiPort_UnconnectedOutput_Test.lf deleted file mode 100644 index a189ba29a8..0000000000 --- a/test/C/src/MultiPort_UnconnectedOutput_Test.lf +++ /dev/null @@ -1,91 +0,0 @@ -target C { - keepalive: true, - workers: 1 -}; - -reactor echo (n_ports:int(1)) { - input[n_ports] request:entry_T; - output[n_ports] response:entry_T; - - output[n_ports] unconnected:entry_T; - - reaction (startup) {= - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "echo ports:%d" - "startup\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->n_ports - ); - =} - - reaction (request) -> response {= - for (int i = 0; i < request_width; ++i) { - if (request[i]->is_present) { - int req = request[i]->value; - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "echo port:%d " - "received echo request:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - i, req - ); - lf_set (response[i], req + i); - } - } - =} -} - -reactor testing_echo (n_ports:int(1), iterations:uint32_t(20), log_file:FILE*({=stderr=})) { - output[n_ports] req:int; - input[n_ports] resp:int; - - state seed:uint32_t(0); - state request_counter:uint32_t(0); - state response_counter:uint32_t(0); - - timer t(0, 2 sec); - - reaction (startup) {= - fprintf (self->log_file, "(%lld, %u) physical_time:%lld " - "testing_echo ports:%d startup\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->n_ports - ); - =} - - reaction (t) -> req {= - int number = rand_r(&self->seed); - for (int i = 0; i < req_width; ++i) { - fprintf (self->log_file, "(%lld, %u) physical_time:%lld " - "testing_echo port:%d sending echo request[%u] number:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - i, self->request_counter, number + i - ); - lf_set (req[i], number + i); - } - ++self->request_counter; - if (self->request_counter == self->iterations) { - lf_request_stop(); - } - =} - - reaction (resp) {= - for (int i = 0; i < resp_width; ++i) { - if (resp[i]->is_present) { - int rsp = resp[i]->value; - fprintf (self->log_file, "(%lld, %u) physical_time:%lld " - "testing_echo port:%d echo response[%u] new_number:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - i, self->response_counter, rsp); - } - } - ++self->response_counter; - =} -} - -main reactor MultiPort_UnconnectedOutput_Test { - test = new testing_echo(n_ports = 2, iterations = 20); - e = new echo (n_ports = 2); - - test.req -> e.request; - e.response -> test.resp; -} \ No newline at end of file From c839b28829be3b025d16a6fc34433eb22648da80 Mon Sep 17 00:00:00 2001 From: Omer Majeed Date: Fri, 8 Sep 2023 15:17:47 +0500 Subject: [PATCH 470/516] merge master branch run time changes --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 809c454db0..09b75edfdb 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 809c454db07c1b5e03a789a4863137107b817da6 +Subproject commit 09b75edfdbfcd0206a89199fcb65b1678398a0c7 From 84db8f28ea627fb1fdf8b46170299dc4ec3d277e Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 8 Sep 2023 12:30:18 +0200 Subject: [PATCH 471/516] Removed util directory and dev scripts can be executed from anywhere --- bin/lfc-dev | 62 ++++++++++++++++++++++++++++++++- bin/lfc-dev.ps1 | 20 +++++++---- bin/lfd-dev | 62 ++++++++++++++++++++++++++++++++- bin/lfd-dev.ps1 | 20 +++++++---- bin/lff-dev | 62 ++++++++++++++++++++++++++++++++- bin/lff-dev.ps1 | 20 +++++++---- util/README.md | 4 --- util/scripts/launch.ps1 | 34 ------------------ util/scripts/launch.sh | 77 ----------------------------------------- 9 files changed, 222 insertions(+), 139 deletions(-) mode change 120000 => 100755 bin/lfc-dev mode change 120000 => 100755 bin/lfd-dev mode change 120000 => 100755 bin/lff-dev delete mode 100644 util/README.md delete mode 100644 util/scripts/launch.ps1 delete mode 100755 util/scripts/launch.sh diff --git a/bin/lfc-dev b/bin/lfc-dev deleted file mode 120000 index 1a3eb8bc0d..0000000000 --- a/bin/lfc-dev +++ /dev/null @@ -1 +0,0 @@ -../util/scripts/launch.sh \ No newline at end of file diff --git a/bin/lfc-dev b/bin/lfc-dev new file mode 100755 index 0000000000..76a9642549 --- /dev/null +++ b/bin/lfc-dev @@ -0,0 +1,61 @@ +#!/bin/bash + +#============================================================================ +# Description: Build and run the Lingua Franca compiler (lfc). +# Authors: Marten Lohstroh +# Christian Menard +# Usage: Usage: lfc-dev [options] files... +#============================================================================ + +#============================================================================ +# Preamble +#============================================================================ + +# 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. +set -euo pipefail + +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[31mfatal error: \e[0m$1" + exit 1 +} + +abs_path="$(find_dir "$0")" + +if [[ "${abs_path}" ]]; then + base=`dirname ${abs_path}` +else + fatal_error "Unable to determine absolute path to $0." +fi +#============================================================================ + +gradlew="${base}/gradlew" + +# Launch the tool. +"${gradlew}" --quiet -p "${base}" assemble ":cli:lfc:assemble" +"${base}/build/install/lf-cli/bin/lfc" "$@" diff --git a/bin/lfc-dev.ps1 b/bin/lfc-dev.ps1 index 90e6f9876a..56ecf3ac62 100644 --- a/bin/lfc-dev.ps1 +++ b/bin/lfc-dev.ps1 @@ -1,9 +1,15 @@ -#========================================================== -# Description: Run the lfc compiler. +#============================================================================ +# Description: Build and run the Lingua Franca compiler (lfc). # Authors: Ruomu Xu -# Usage: Usage: lfc [options] files... -#========================================================== +# Christian Menard +# Usage: Usage: lfc-dev [options] files... +#============================================================================ -$launchScript="$PSScriptRoot\..\util\scripts\launch.ps1" -# PS requires spattling: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Splatting?view=powershell-7.2 -. $launchScript @args + +# This script is in $base\bin +$base="$PSScriptRoot\..\" +$gradlew="${base}/gradlew.bat" + +# invoke script +& "${gradlew}" --quiet -p "${base}" assemble ":cli:lfc:assemble" +& "${base}/build/install/lf-cli/bin/lfc" @args diff --git a/bin/lfd-dev b/bin/lfd-dev deleted file mode 120000 index 1a3eb8bc0d..0000000000 --- a/bin/lfd-dev +++ /dev/null @@ -1 +0,0 @@ -../util/scripts/launch.sh \ No newline at end of file diff --git a/bin/lfd-dev b/bin/lfd-dev new file mode 100755 index 0000000000..c154379ac4 --- /dev/null +++ b/bin/lfd-dev @@ -0,0 +1,61 @@ +#!/bin/bash + +#============================================================================ +# Description: Build and run the Lingua Franca diagram generator (lfd). +# Authors: Marten Lohstroh +# Christian Menard +# Usage: Usage: lfd-dev [options] files... +#============================================================================ + +#============================================================================ +# Preamble +#============================================================================ + +# 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. +set -euo pipefail + +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[31mfatal error: \e[0m$1" + exit 1 +} + +abs_path="$(find_dir "$0")" + +if [[ "${abs_path}" ]]; then + base=`dirname ${abs_path}` +else + fatal_error "Unable to determine absolute path to $0." +fi +#============================================================================ + +gradlew="${base}/gradlew" + +# Launch the tool. +"${gradlew}" --quiet -p "${base}" assemble ":cli:lfd:assemble" +"${base}/build/install/lf-cli/bin/lfd" "$@" diff --git a/bin/lfd-dev.ps1 b/bin/lfd-dev.ps1 index 54ce9397f4..47d53f0731 100644 --- a/bin/lfd-dev.ps1 +++ b/bin/lfd-dev.ps1 @@ -1,9 +1,15 @@ -#========================================================== -# Description: Run the lff compiler. +#============================================================================ +# Description: Build and run the Lingua Franca diagram generator (lfd). # Authors: Ruomu Xu -# Usage: Usage: lff [options] files... -#========================================================== +# Christian Menard +# Usage: Usage: lfd-dev [options] files... +#============================================================================ -$launchScript="$PSScriptRoot\..\util\scripts\launch.ps1" -# PS requires spattling: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Splatting?view=powershell-7.2 -. $launchScript @args + +# This script is in $base\bin +$base="$PSScriptRoot\..\" +$gradlew="${base}/gradlew.bat" + +# invoke script +& "${gradlew}" --quiet -p "${base}" assemble ":cli:lfd:assemble" +& "${base}/build/install/lf-cli/bin/lfd" @args diff --git a/bin/lff-dev b/bin/lff-dev deleted file mode 120000 index 1a3eb8bc0d..0000000000 --- a/bin/lff-dev +++ /dev/null @@ -1 +0,0 @@ -../util/scripts/launch.sh \ No newline at end of file diff --git a/bin/lff-dev b/bin/lff-dev new file mode 100755 index 0000000000..3a656a8a96 --- /dev/null +++ b/bin/lff-dev @@ -0,0 +1,61 @@ +#!/bin/bash + +#============================================================================ +# Description: Build and run the Lingua Franca code formatter (lff). +# Authors: Marten Lohstroh +# Christian Menard +# Usage: Usage: lff-dev [options] files... +#============================================================================ + +#============================================================================ +# Preamble +#============================================================================ + +# 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. +set -euo pipefail + +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[31mfatal error: \e[0m$1" + exit 1 +} + +abs_path="$(find_dir "$0")" + +if [[ "${abs_path}" ]]; then + base=`dirname ${abs_path}` +else + fatal_error "Unable to determine absolute path to $0." +fi +#============================================================================ + +gradlew="${base}/gradlew" + +# Launch the tool. +"${gradlew}" --quiet -p "${base}" assemble ":cli:lff:assemble" +"${base}/build/install/lf-cli/bin/lff" "$@" diff --git a/bin/lff-dev.ps1 b/bin/lff-dev.ps1 index 54ce9397f4..32b7b2171f 100644 --- a/bin/lff-dev.ps1 +++ b/bin/lff-dev.ps1 @@ -1,9 +1,15 @@ -#========================================================== -# Description: Run the lff compiler. +#============================================================================ +# Description: Build and run the Lingua Franca code formatter (lff). # Authors: Ruomu Xu -# Usage: Usage: lff [options] files... -#========================================================== +# Christian Menard +# Usage: Usage: lff-dev [options] files... +#============================================================================ -$launchScript="$PSScriptRoot\..\util\scripts\launch.ps1" -# PS requires spattling: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Splatting?view=powershell-7.2 -. $launchScript @args + +# This script is in $base\bin +$base="$PSScriptRoot\..\" +$gradlew="${base}/gradlew.bat" + +# invoke script +& "${gradlew}" --quiet -p "${base}" assemble ":cli:lff:assemble" +& "${base}/build/install/lf-cli/bin/lff" @args diff --git a/util/README.md b/util/README.md deleted file mode 100644 index fd95c322f0..0000000000 --- a/util/README.md +++ /dev/null @@ -1,4 +0,0 @@ -## util - -This directory is intended for collecting utility programs associated with Lingua Franca -but that run and install independently of the main tool chain. diff --git a/util/scripts/launch.ps1 b/util/scripts/launch.ps1 deleted file mode 100644 index 9100c75434..0000000000 --- a/util/scripts/launch.ps1 +++ /dev/null @@ -1,34 +0,0 @@ -#========================================================== -# Description: Launch lfc or lff depending on the invoking script. -# Authors: Christian Menard, Peter Donovan, Ruomu Xu -# Usage: Usage: launch args... -# with invoker with name same as the programme to be invoked. -#========================================================== - -# If the invoker is Z:\nkamihara\lf\bin\lfc.ps1, $invokerName will strip out "lfc.ps1" and then get "lfc". -# See https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_automatic_variables?view=powershell-7.2#myinvocation -$invokerPath = $MyInvocation.PSCommandPath -$invokerName = [System.IO.Path]::GetFileNameWithoutExtension("$(Split-Path -Path $invokerPath -Leaf -Resolve)") - -$mainClassTable = @{"lfc" = "org.lflang.cli.Lfc"; "lff" = "org.lflang.cli.Lff"; "lfd" = "org.lflang.cli.Lfd"} -$tool = $null -foreach ($k in $mainClassTable.Keys) { - # This replacement is not ideal, but it is kinda analogous to its counterpart in launch.sh. - if ($invokerName.Contains(($k -replace "-dev", ""))) { - $tool = $k - break - } -} -if ($null -eq $tool) { - throw ("$invokerName is not a known lf command. Known commands are [$($mainClassTable.Keys)]. " + - "In case you use a symbolic or hard link to one of the Lingua Franca " + - "command line tools, make sure that the link's name ends with one of [$($mainClassTable.Keys)]") -} - -# This script is in $base\util\scripts -$base="$PSScriptRoot\..\..\" -$gradlew="${base}/gradlew.bat" - -# invoke script -& "${gradlew}" --quiet assemble ":cli:${tool}:assemble" -& "${base}/build/install/lf-cli/bin/${tool}" @args diff --git a/util/scripts/launch.sh b/util/scripts/launch.sh deleted file mode 100755 index 5e52051c60..0000000000 --- a/util/scripts/launch.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/bin/bash - -#============================================================================ -# Description: Run the Lingua Franca compiler. -# Authors: Marten Lohstroh -# Christian Menard -# Usage: Usage: lfc [options] files... -#============================================================================ - -#============================================================================ -# Preamble -#============================================================================ - -# 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. -set -euo pipefail - -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[1mlfc: \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 -#============================================================================ - -if [[ "${0%%-dev}" == *lfc ]]; then - tool="lfc" -elif [[ "${0%%-dev}" == *lff ]]; then - tool="lff" -elif [[ "${0%%-dev}" == *lfd ]]; then - tool="lfd" -else - known_commands="[lfc, lff, lfd]" - echo \ - "ERROR: $0 is not a known lf command! Known commands are ${known_commands}. - In case you use a symbolic or hard link to one of the Lingua Franca - command line tools, make sure that the link's name ends with one of - ${known_commands}." - exit 2 -fi - -gradlew="${base}/gradlew" - -# Launch the tool. -"${gradlew}" --quiet assemble ":cli:${tool}:assemble" -"${base}/build/install/lf-cli/bin/${tool}" "$@" From 1b3f505d480762b19d7a80c73d8eb5099630669f Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 8 Sep 2023 13:30:30 +0200 Subject: [PATCH 472/516] fix bug in readlink logic --- bin/lfc-dev | 16 +++++++--------- bin/lfd-dev | 16 +++++++--------- bin/lff-dev | 16 +++++++--------- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/bin/lfc-dev b/bin/lfc-dev index 76a9642549..2664248947 100755 --- a/bin/lfc-dev +++ b/bin/lfc-dev @@ -20,7 +20,7 @@ # is encountered, we return. set -euo pipefail -find_dir() ( +find_base_dir() ( start_dir=$PWD cd "$(dirname "$1")" link=$(readlink "$(basename "$1")") @@ -28,15 +28,15 @@ find_dir() ( while [ "${link}" ]; do if [[ "${count}" -lt 1000 ]]; then cd "$(dirname "${link}")" - link=$(readlink "$(basename "$1")") + link=$(readlink "$(basename "${link}")") ((count++)) else return fi done - real_path="$PWD/$(basename "$1")" + real_path="${PWD}" cd "${start_dir}" - echo `dirname "${real_path}"` + echo "$(dirname "${real_path}")" ) # Report fatal error and exit. @@ -45,12 +45,10 @@ function fatal_error() { exit 1 } -abs_path="$(find_dir "$0")" +base="$(find_base_dir "$0")" -if [[ "${abs_path}" ]]; then - base=`dirname ${abs_path}` -else - fatal_error "Unable to determine absolute path to $0." +if [[ -z "${base}" ]]; then + fatal_error "Unable to determine base path of $0." fi #============================================================================ diff --git a/bin/lfd-dev b/bin/lfd-dev index c154379ac4..ddb1e0e69c 100755 --- a/bin/lfd-dev +++ b/bin/lfd-dev @@ -20,7 +20,7 @@ # is encountered, we return. set -euo pipefail -find_dir() ( +find_base_dir() ( start_dir=$PWD cd "$(dirname "$1")" link=$(readlink "$(basename "$1")") @@ -28,15 +28,15 @@ find_dir() ( while [ "${link}" ]; do if [[ "${count}" -lt 1000 ]]; then cd "$(dirname "${link}")" - link=$(readlink "$(basename "$1")") + link=$(readlink "$(basename "${link}")") ((count++)) else return fi done - real_path="$PWD/$(basename "$1")" + real_path="${PWD}" cd "${start_dir}" - echo `dirname "${real_path}"` + echo "$(dirname "${real_path}")" ) # Report fatal error and exit. @@ -45,12 +45,10 @@ function fatal_error() { exit 1 } -abs_path="$(find_dir "$0")" +base="$(find_base_dir "$0")" -if [[ "${abs_path}" ]]; then - base=`dirname ${abs_path}` -else - fatal_error "Unable to determine absolute path to $0." +if [[ -z "${base}" ]]; then + fatal_error "Unable to determine base path of $0." fi #============================================================================ diff --git a/bin/lff-dev b/bin/lff-dev index 3a656a8a96..c0dc83c647 100755 --- a/bin/lff-dev +++ b/bin/lff-dev @@ -20,7 +20,7 @@ # is encountered, we return. set -euo pipefail -find_dir() ( +find_base_dir() ( start_dir=$PWD cd "$(dirname "$1")" link=$(readlink "$(basename "$1")") @@ -28,15 +28,15 @@ find_dir() ( while [ "${link}" ]; do if [[ "${count}" -lt 1000 ]]; then cd "$(dirname "${link}")" - link=$(readlink "$(basename "$1")") + link=$(readlink "$(basename "${link}")") ((count++)) else return fi done - real_path="$PWD/$(basename "$1")" + real_path="${PWD}" cd "${start_dir}" - echo `dirname "${real_path}"` + echo "$(dirname "${real_path}")" ) # Report fatal error and exit. @@ -45,12 +45,10 @@ function fatal_error() { exit 1 } -abs_path="$(find_dir "$0")" +base="$(find_base_dir "$0")" -if [[ "${abs_path}" ]]; then - base=`dirname ${abs_path}` -else - fatal_error "Unable to determine absolute path to $0." +if [[ -z "${base}" ]]; then + fatal_error "Unable to determine base path of $0." fi #============================================================================ From d234236fe238e4f5d5e81175f11d18fd08d214c3 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 8 Sep 2023 14:58:47 +0200 Subject: [PATCH 473/516] test invocation of lf*-dev scripts from another directory --- .github/scripts/test-lfc.sh | 5 +++++ .github/scripts/test-lfd.sh | 5 +++++ .github/scripts/test-lff.sh | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/.github/scripts/test-lfc.sh b/.github/scripts/test-lfc.sh index bc43342e44..27f4807da3 100755 --- a/.github/scripts/test-lfc.sh +++ b/.github/scripts/test-lfc.sh @@ -18,3 +18,8 @@ bin/lfc-dev test/C/src/Minimal.lf # Ensure that lfc can be invoked via symbolic links. test_with_links "lfc-dev" + +# Ensure that lfc can be invoked from outside the root directory. +cd bin +./lfc-dev --help +cd .. diff --git a/.github/scripts/test-lfd.sh b/.github/scripts/test-lfd.sh index 0f04fcc5b1..2ad7ce1108 100755 --- a/.github/scripts/test-lfd.sh +++ b/.github/scripts/test-lfd.sh @@ -18,3 +18,8 @@ bin/lfd-dev test/C/src/Minimal.lf # Ensure that lfd can be invoked via symbolic links. test_with_links "lfd-dev" + +# Ensure that lfd can be invoked from outside the root directory. +cd bin +./lfd-dev --help +cd .. diff --git a/.github/scripts/test-lff.sh b/.github/scripts/test-lff.sh index 273c26b429..11a3d36ceb 100755 --- a/.github/scripts/test-lff.sh +++ b/.github/scripts/test-lff.sh @@ -26,3 +26,8 @@ bin/lff-dev --dry-run test/Cpp/src/Minimal.lf # Ensure that lff can be invoked via symbolic links. test_with_links "lff-dev" + +# Ensure that lfc can be invoked from outside the root directory. +cd bin +./lff-dev --help +cd .. From 90405223e4c1d21cf693eb702dafaaf0ffc73325 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Sun, 10 Sep 2023 23:11:37 -0700 Subject: [PATCH 474/516] Apply formatter --- .../C/src/MultiBank_UnconnectedOutput_Test.lf | 100 +++++++-------- ...rt_MultiBankTest_UnconnectedOutput_Test.lf | 116 +++++++++--------- 2 files changed, 108 insertions(+), 108 deletions(-) diff --git a/test/C/src/MultiBank_UnconnectedOutput_Test.lf b/test/C/src/MultiBank_UnconnectedOutput_Test.lf index e10d4fe3c9..2aedcd2a3c 100644 --- a/test/C/src/MultiBank_UnconnectedOutput_Test.lf +++ b/test/C/src/MultiBank_UnconnectedOutput_Test.lf @@ -1,65 +1,65 @@ target C { - workers: 1 -}; + workers: 1 +} -reactor echo (bank_index:int(0)) { - input request:entry_T; - output response:entry_T; +reactor echo(bank_index: int = 0) { + input request: entry_T + output response: entry_T - output unconnected:entry_T; + output unconnected: entry_T - reaction (startup) {= - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "echo_%d " - "startup\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index - ); - =} + reaction(startup) {= + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "echo_%d " + "startup\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index + ); + =} - reaction (request) -> response {= - int req = request->value; - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "echo_%d " - "received echo request:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, req - ); - lf_set (response, req + self->bank_index); - =} + reaction(request) -> response {= + int req = request->value; + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "echo_%d " + "received echo request:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, req + ); + lf_set (response, req + self->bank_index); + =} } -reactor testing_echo (bank_index:int(0)) { - output req:int; - input resp:int; +reactor testing_echo(bank_index: int = 0) { + output req: int + input resp: int - state seed:uint32_t(0); + state seed: uint32_t = 0 - reaction (startup) -> req {= - int number = rand_r(&self->seed); - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "testing_echo_%d sending echo request number:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, number - ); - lf_set (req, number + self->bank_index); - =} + reaction(startup) -> req {= + int number = rand_r(&self->seed); + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "testing_echo_%d sending echo request number:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, number + ); + lf_set (req, number + self->bank_index); + =} - reaction (resp) {= - int rsp = resp->value; - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "testing_echo_%d echo response number:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, rsp); + reaction(resp) {= + int rsp = resp->value; + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "testing_echo_%d echo response number:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, rsp); - lf_request_stop(); - =} + lf_request_stop(); + =} } main reactor MultiBank_UnconnectedOutput_Test { - test = new [2] testing_echo(); - e = new [2] echo (); + test = new[2] testing_echo() + e = new[2] echo() - test.req -> e.request; - e.response -> test.resp; -} \ No newline at end of file + test.req -> e.request + e.response -> test.resp +} diff --git a/test/C/src/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf b/test/C/src/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf index 8ebb1da8ce..23936ec16e 100644 --- a/test/C/src/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf +++ b/test/C/src/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf @@ -1,75 +1,75 @@ target C { - workers: 1 -}; + workers: 1 +} -reactor adder (bank_index:int(0), n_ports:int(1)) { - input[n_ports] add_request:entry_T; - output[n_ports] add_response:entry_T; +reactor adder(bank_index: int = 0, n_ports: int = 1) { + input[n_ports] add_request: entry_T + output[n_ports] add_response: entry_T - output[n_ports] unconnected:entry_T; + output[n_ports] unconnected: entry_T - state sum:entry_T(0); + state sum: entry_T = 0 - reaction (startup) {= - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "adder_%d ports:%d " - "startup\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, self->n_ports - ); - =} + reaction(startup) {= + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "adder_%d ports:%d " + "startup\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, self->n_ports + ); + =} - reaction (add_request) -> add_response {= - for (int i = 0; i < add_request_width; ++i) { - if (add_request[i]->is_present) { - int req = add_request[i]->value; - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "adder_%d port:%d " - "received add request:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, i, req - ); - self->sum += req; - lf_set (add_response[i], self->sum); - } + reaction(add_request) -> add_response {= + for (int i = 0; i < add_request_width; ++i) { + if (add_request[i]->is_present) { + int req = add_request[i]->value; + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "adder_%d port:%d " + "received add request:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, i, req + ); + self->sum += req; + lf_set (add_response[i], self->sum); } - =} + } + =} } -reactor testing_adder (bank_index:int(0)) { - output add_req:int; - output result_req:int; - - input add_resp:int; - input result_resp:int; +reactor testing_adder(bank_index: int = 0) { + output add_req: int + output result_req: int + + input add_resp: int + input result_resp: int - state seed:uint32_t(0); + state seed: uint32_t = 0 - reaction (startup) -> add_req {= - int number = rand_r(&self->seed) % 100; - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "testing_adder_%d sending add request number:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, number - ); - lf_set (add_req, number); - =} + reaction(startup) -> add_req {= + int number = rand_r(&self->seed) % 100; + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "testing_adder_%d sending add request number:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, number + ); + lf_set (add_req, number); + =} - reaction (add_resp) {= - int rsp = add_resp->value; - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "testing_adder_%d response sum:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, rsp); + reaction(add_resp) {= + int rsp = add_resp->value; + fprintf (stderr, "(%lld, %u) physical_time:%lld " + "testing_adder_%d response sum:%d\n", + lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), + self->bank_index, rsp); - lf_request_stop(); - =} + lf_request_stop(); + =} } main reactor MultiPort_MultiBankTest_UnconnectedOutput_Test { - test = new [2] testing_adder(); - a = new [2] adder (); + test = new[2] testing_adder() + a = new[2] adder() - test.add_req -> a.add_request; - a.add_response -> test.add_resp; -} \ No newline at end of file + test.add_req -> a.add_request + a.add_response -> test.add_resp +} From 331a242537cdc4eb06aa2874fec6bbb25ee17426 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Mon, 11 Sep 2023 13:28:56 -0700 Subject: [PATCH 475/516] Simplify unconnected output test --- ...rt_MultiBankTest_UnconnectedOutput_Test.lf | 59 ++++++------------- 1 file changed, 18 insertions(+), 41 deletions(-) diff --git a/test/C/src/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf b/test/C/src/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf index 23936ec16e..ed1864b906 100644 --- a/test/C/src/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf +++ b/test/C/src/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf @@ -2,74 +2,51 @@ target C { workers: 1 } -reactor adder(bank_index: int = 0, n_ports: int = 1) { +reactor adder(n_ports: int = 1) { input[n_ports] add_request: entry_T - output[n_ports] add_response: entry_T + output add_response: entry_T output[n_ports] unconnected: entry_T state sum: entry_T = 0 - reaction(startup) {= - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "adder_%d ports:%d " - "startup\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, self->n_ports - ); - =} - reaction(add_request) -> add_response {= for (int i = 0; i < add_request_width; ++i) { if (add_request[i]->is_present) { - int req = add_request[i]->value; - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "adder_%d port:%d " - "received add request:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, i, req - ); - self->sum += req; - lf_set (add_response[i], self->sum); + self->sum += add_request[i]->value; } } + lf_set (add_response, self->sum); + self->sum = 0; =} } -reactor testing_adder(bank_index: int = 0) { +reactor testing_adder(bank_index: int = 0, bank_width: int = 1) { output add_req: int output result_req: int input add_resp: int - input result_resp: int - - state seed: uint32_t = 0 + input unconnected: int reaction(startup) -> add_req {= - int number = rand_r(&self->seed) % 100; - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "testing_adder_%d sending add request number:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, number - ); - lf_set (add_req, number); + lf_set (add_req, 42); =} reaction(add_resp) {= - int rsp = add_resp->value; - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "testing_adder_%d response sum:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, rsp); - - lf_request_stop(); + int sum = self->bank_width * 42; + int received = add_resp->value; + printf("Bank index: %d, received: %d\n", self->bank_index, received); + if (received != sum) { + printf("Wrong value. Should have been %d.\n", sum); + exit(1); + } =} } main reactor MultiPort_MultiBankTest_UnconnectedOutput_Test { - test = new[2] testing_adder() - a = new[2] adder() + test = new[2] testing_adder(bank_width=2) + a = new adder(n_ports=2) test.add_req -> a.add_request - a.add_response -> test.add_resp + (a.add_response)+ -> test.add_resp } From ee0be68e2a033fa236045ed651b5714db0555f26 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Mon, 11 Sep 2023 13:35:31 -0700 Subject: [PATCH 476/516] Move test into multiport directory --- .../MultiPort_MultiBankTest_UnconnectedOutput_Test.lf | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/C/src/{ => multiport}/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf (100%) diff --git a/test/C/src/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf b/test/C/src/multiport/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf similarity index 100% rename from test/C/src/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf rename to test/C/src/multiport/MultiPort_MultiBankTest_UnconnectedOutput_Test.lf From 0c9d040a1ade5cd6838cde8970f96cc3ddccac5c Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Mon, 11 Sep 2023 13:56:20 -0700 Subject: [PATCH 477/516] Simplify and move second example --- .../C/src/MultiBank_UnconnectedOutput_Test.lf | 65 ------------------- .../MultiBank_UnconnectedOutput_Test.lf | 44 +++++++++++++ 2 files changed, 44 insertions(+), 65 deletions(-) delete mode 100644 test/C/src/MultiBank_UnconnectedOutput_Test.lf create mode 100644 test/C/src/multiport/MultiBank_UnconnectedOutput_Test.lf diff --git a/test/C/src/MultiBank_UnconnectedOutput_Test.lf b/test/C/src/MultiBank_UnconnectedOutput_Test.lf deleted file mode 100644 index 2aedcd2a3c..0000000000 --- a/test/C/src/MultiBank_UnconnectedOutput_Test.lf +++ /dev/null @@ -1,65 +0,0 @@ -target C { - workers: 1 -} - -reactor echo(bank_index: int = 0) { - input request: entry_T - output response: entry_T - - output unconnected: entry_T - - reaction(startup) {= - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "echo_%d " - "startup\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index - ); - =} - - reaction(request) -> response {= - int req = request->value; - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "echo_%d " - "received echo request:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, req - ); - lf_set (response, req + self->bank_index); - =} -} - -reactor testing_echo(bank_index: int = 0) { - output req: int - input resp: int - - state seed: uint32_t = 0 - - reaction(startup) -> req {= - int number = rand_r(&self->seed); - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "testing_echo_%d sending echo request number:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, number - ); - lf_set (req, number + self->bank_index); - =} - - reaction(resp) {= - int rsp = resp->value; - fprintf (stderr, "(%lld, %u) physical_time:%lld " - "testing_echo_%d echo response number:%d\n", - lf_time_logical_elapsed(), lf_tag().microstep, lf_time_physical_elapsed(), - self->bank_index, rsp); - - lf_request_stop(); - =} -} - -main reactor MultiBank_UnconnectedOutput_Test { - test = new[2] testing_echo() - e = new[2] echo() - - test.req -> e.request - e.response -> test.resp -} diff --git a/test/C/src/multiport/MultiBank_UnconnectedOutput_Test.lf b/test/C/src/multiport/MultiBank_UnconnectedOutput_Test.lf new file mode 100644 index 0000000000..20e79de508 --- /dev/null +++ b/test/C/src/multiport/MultiBank_UnconnectedOutput_Test.lf @@ -0,0 +1,44 @@ +target C { + workers: 1 +} + +reactor echo(bank_index: int = 0) { + input request: entry_T + output response: entry_T + + output unconnected: entry_T + + reaction(request) -> response {= + int req = request->value; + lf_set (response, request->value); + =} +} + +reactor testing_echo(bank_index: int = 0) { + output req: int + input resp: int + + state seed: uint32_t = 0 + + reaction(startup) -> req {= + lf_set (req, 42 + self->bank_index); + =} + + reaction(resp) {= + int sum = self->bank_index + 42; + int received = resp->value; + printf("Bank index: %d, received: %d\n", self->bank_index, received); + if (received != sum) { + printf("Wrong value. Should have been %d.\n", sum); + exit(1); + } + =} +} + +main reactor MultiBank_UnconnectedOutput_Test { + test = new[2] testing_echo() + e = new[2] echo() + + test.req -> e.request + e.response -> test.resp +} From f6d845e1900cb6f957919370db018b194293185b Mon Sep 17 00:00:00 2001 From: "lingua-franca[bot]" <97201490+francabot@users.noreply.github.com> Date: Tue, 12 Sep 2023 21:35:44 -0700 Subject: [PATCH 478/516] Update CHANGELOG.md --- CHANGELOG.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dd1e426ca..9c14254321 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,47 @@ # Changelog + +## [v0.5.1](https://github.com/lf-lang/lingua-franca/tree/v0.5.1) (2023-09-12) + +**Highlights** + +This release addresses several issues in the C code generator and fixes Docker support for federations. + +**✨ Enhancements** + +- Avoid squeezing reaction, method, or preamble bodies onto a single line [\#1984](https://github.com/lf-lang/lingua-franca/pull/1984) (@petervdonovan) + +**🔧 Fixes** + +- Fix for setting federates' bank index [\#1989](https://github.com/lf-lang/lingua-franca/pull/1989) (@petervdonovan) +- Default hostname for RTI in dockerized federation changed from "localhost" to "rti" [\#1993](https://github.com/lf-lang/lingua-franca/pull/1993) (@byeong-gil) +- Fix for unconnected multiport and bank reactor bug [\#1953](https://github.com/lf-lang/lingua-franca/pull/1953) (@OmerMajNition) + +**🚧 Maintenance and Refactoring** + +- Gradlew not longer used to run dev version of lf cli tools [\#1988](https://github.com/lf-lang/lingua-franca/pull/1988) (@axmmisaka) +- More robust dev scripts and removed util directory [\#1995](https://github.com/lf-lang/lingua-franca/pull/1995) (@cmnrd) + +**🧪 Tests** + +- Tests for `lf_set_array` and persistent inputs [\#1987](https://github.com/lf-lang/lingua-franca/pull/1987) (@edwardalee) +- Minor fixes for C++ tests [\#1979](https://github.com/lf-lang/lingua-franca/pull/1979) (@revol-xut) + + +### Submodule [lf-lang/reactor-c](http://github.com/lf-lang/reactor-c) + +- No Changes + + +### Submodule [lf-lang/reactor-cpp](http://github.com/lf-lang/reactor-cpp) + +- No Changes + + +### Submodule [lf-lang/reactor-rs](http://github.com/lf-lang/reactor-rs) + +- No Changes + + ## [v0.5.0](https://github.com/lf-lang/lingua-franca/tree/v0.5.0) (2023-08-30) From 2f47d64b8ee29a0b0463b918bf31e9624435934f Mon Sep 17 00:00:00 2001 From: "lingua-franca[bot]" <97201490+francabot@users.noreply.github.com> Date: Tue, 12 Sep 2023 21:38:06 -0700 Subject: [PATCH 479/516] Bump version to 0.5.1 --- CHANGELOG.md | 2 +- core/src/main/resources/org/lflang/StringsBundle.properties | 2 +- gradle.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c14254321..2a0b9d9b58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # Changelog - + ## [v0.5.1](https://github.com/lf-lang/lingua-franca/tree/v0.5.1) (2023-09-12) **Highlights** diff --git a/core/src/main/resources/org/lflang/StringsBundle.properties b/core/src/main/resources/org/lflang/StringsBundle.properties index dc4f99684f..75eb042e79 100644 --- a/core/src/main/resources/org/lflang/StringsBundle.properties +++ b/core/src/main/resources/org/lflang/StringsBundle.properties @@ -1 +1 @@ -VERSION = 0.5.1-SNAPSHOT +VERSION = 0.5.1 diff --git a/gradle.properties b/gradle.properties index 245b5cb0ba..4b08da69cb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ [header] group=org.lflang -version=0.5.1-SNAPSHOT +version=0.5.1 [versions] antlrVersion=4.7.2 From d1d1e03cb8db5de7eb399ba6a3353dce0f62d4cb Mon Sep 17 00:00:00 2001 From: "lingua-franca[bot]" <97201490+francabot@users.noreply.github.com> Date: Tue, 12 Sep 2023 21:43:44 -0700 Subject: [PATCH 480/516] Bump version to 0.5.2-SNAPSHOT --- core/src/main/resources/org/lflang/StringsBundle.properties | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/resources/org/lflang/StringsBundle.properties b/core/src/main/resources/org/lflang/StringsBundle.properties index 75eb042e79..a84b5b44f1 100644 --- a/core/src/main/resources/org/lflang/StringsBundle.properties +++ b/core/src/main/resources/org/lflang/StringsBundle.properties @@ -1 +1 @@ -VERSION = 0.5.1 +VERSION = 0.5.2-SNAPSHOT diff --git a/gradle.properties b/gradle.properties index 4b08da69cb..4b062644e0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ [header] group=org.lflang -version=0.5.1 +version=0.5.2-SNAPSHOT [versions] antlrVersion=4.7.2 From ddf67f33ad09ae1813b108d4ce00003239e5ffd7 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Tue, 12 Sep 2023 23:14:52 -0700 Subject: [PATCH 481/516] Updated reactor-c submodule --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 09b75edfdb..9760f233d4 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 09b75edfdbfcd0206a89199fcb65b1678398a0c7 +Subproject commit 9760f233d4b1d2653e61c7feb7e3e1379d6e8344 From aac3c487e6bd335c5de97dd0fd51946bf4479871 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Wed, 13 Sep 2023 15:58:16 +0200 Subject: [PATCH 482/516] Enable the spotbugs plugin --- buildSrc/build.gradle | 2 ++ buildSrc/gradle.properties | 3 ++- .../groovy/org.lflang.java-conventions.gradle | 9 +++++++++ config/spotbugs/exclude.xml | 20 +++++++++++++++++++ gradle.properties | 1 + 5 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 config/spotbugs/exclude.xml diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index db34ac5c23..7c2d942ef8 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -13,6 +13,8 @@ dependencies { // https://mvnrepository.com/artifact/com.diffplug.spotless/spotless-lib-extra implementation group: 'com.diffplug.spotless', name: 'spotless-lib-extra', version: spotlessLibVersion + implementation group: 'com.github.spotbugs.snom', name: 'spotbugs-gradle-plugin', version: spotbugsPluginVersion + implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" implementation "com.diffplug.spotless:spotless-plugin-gradle:$spotlessVersion" diff --git a/buildSrc/gradle.properties b/buildSrc/gradle.properties index 217c42b57c..a8cd2581d4 100644 --- a/buildSrc/gradle.properties +++ b/buildSrc/gradle.properties @@ -2,4 +2,5 @@ spotlessVersion=6.11.0 spotlessLibVersion=2.30.0 kotlinVersion=1.6.21 -shadowJarVersion=7.1.2 \ No newline at end of file +shadowJarVersion=7.1.2 +spotbugsPluginVersion=5.1.3 \ No newline at end of file diff --git a/buildSrc/src/main/groovy/org.lflang.java-conventions.gradle b/buildSrc/src/main/groovy/org.lflang.java-conventions.gradle index 9fc4fa50f9..d5697d312c 100644 --- a/buildSrc/src/main/groovy/org.lflang.java-conventions.gradle +++ b/buildSrc/src/main/groovy/org.lflang.java-conventions.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'com.diffplug.spotless' id 'org.lflang.platform' + id 'com.github.spotbugs' } repositories { @@ -19,6 +20,14 @@ spotless { } } +spotbugs { + toolVersion = spotbugsToolVersion + excludeFilter.set( + rootProject.file('config/spotbugs/exclude.xml') + ) +} + + configurations.all { resolutionStrategy { dependencySubstitution { diff --git a/config/spotbugs/exclude.xml b/config/spotbugs/exclude.xml new file mode 100644 index 0000000000..fdd333f455 --- /dev/null +++ b/config/spotbugs/exclude.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/gradle.properties b/gradle.properties index 4b062644e0..636741363c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,6 +21,7 @@ xtextVersion=2.31.0 klighdVersion=2.3.0.v20230606 freehepVersion=2.4 swtVersion=3.124.0 +spotbugsToolVersion=4.7.3 [manifestPropertyNames] org.eclipse.xtext=xtextVersion From 9ea0a85beaf7446319133560fd02b088b1bc0d91 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Thu, 14 Sep 2023 14:56:32 +0200 Subject: [PATCH 483/516] treat spotbugs issues as warnings, not errors --- buildSrc/src/main/groovy/org.lflang.java-conventions.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/buildSrc/src/main/groovy/org.lflang.java-conventions.gradle b/buildSrc/src/main/groovy/org.lflang.java-conventions.gradle index d5697d312c..8730fcf7b7 100644 --- a/buildSrc/src/main/groovy/org.lflang.java-conventions.gradle +++ b/buildSrc/src/main/groovy/org.lflang.java-conventions.gradle @@ -25,6 +25,7 @@ spotbugs { excludeFilter.set( rootProject.file('config/spotbugs/exclude.xml') ) + ignoreFailures = true } From 1662f32c72a4c3869fda52493bf08003ca890ad4 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Thu, 14 Sep 2023 09:54:51 +0200 Subject: [PATCH 484/516] fix spotbugs warnings in integration tests --- .../org/lflang/tests/lsp/ErrorInserter.java | 23 ++++++++++++------- .../java/org/lflang/tests/lsp/LspTests.java | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/core/src/integrationTest/java/org/lflang/tests/lsp/ErrorInserter.java b/core/src/integrationTest/java/org/lflang/tests/lsp/ErrorInserter.java index a1948821eb..13b10156e2 100644 --- a/core/src/integrationTest/java/org/lflang/tests/lsp/ErrorInserter.java +++ b/core/src/integrationTest/java/org/lflang/tests/lsp/ErrorInserter.java @@ -4,14 +4,11 @@ import java.io.Closeable; import java.io.IOException; import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Random; +import java.nio.file.Paths; +import java.util.*; import java.util.function.BiFunction; import java.util.function.BiPredicate; import java.util.function.Function; @@ -125,7 +122,7 @@ public void write() throws IOException { if (!src.toFile().renameTo(swapFile(src).toFile())) { throw new IOException("Failed to create a swap file."); } - try (PrintWriter writer = new PrintWriter(src.toFile())) { + try (PrintWriter writer = new PrintWriter(src.toFile(), StandardCharsets.UTF_8)) { lines.forEach(writer::println); } } @@ -233,7 +230,13 @@ private void alter(BiFunction, String, Boolean> alterer) { /** Return the swap file associated with {@code f}. */ private static Path swapFile(Path p) { - return p.getParent().resolve("." + p.getFileName() + ".swp"); + final var parent = p.getParent(); + final var swpName = "." + p.getFileName() + ".swp"; + if (parent == null) { + return Paths.get(swpName); + } else { + return parent.resolve(swpName); + } } } @@ -265,6 +268,10 @@ public boolean hasNext() { @Override public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + T ret = current.item; current = current.previous; return ret; diff --git a/core/src/integrationTest/java/org/lflang/tests/lsp/LspTests.java b/core/src/integrationTest/java/org/lflang/tests/lsp/LspTests.java index 9270d9ea82..964963bd0e 100644 --- a/core/src/integrationTest/java/org/lflang/tests/lsp/LspTests.java +++ b/core/src/integrationTest/java/org/lflang/tests/lsp/LspTests.java @@ -91,7 +91,7 @@ void typescriptValidationTest() throws IOException { */ private void targetLanguageValidationTest(Target target, ErrorInserter.Builder builder) throws IOException { - long seed = new Random().nextLong(); + long seed = System.nanoTime(); System.out.printf( "Running validation tests for %s with random seed %d.%n", target.getDisplayName(), seed); Random random = new Random(seed); From 083f288fec12c08395439d5b8c3bab2fdce1e777 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Thu, 14 Sep 2023 10:45:06 +0200 Subject: [PATCH 485/516] make TestError immutable --- .../java/org/lflang/tests/LFTest.java | 4 ++-- .../java/org/lflang/tests/TestError.java | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/core/src/testFixtures/java/org/lflang/tests/LFTest.java b/core/src/testFixtures/java/org/lflang/tests/LFTest.java index ee67dcbb96..702f8cf4af 100644 --- a/core/src/testFixtures/java/org/lflang/tests/LFTest.java +++ b/core/src/testFixtures/java/org/lflang/tests/LFTest.java @@ -165,9 +165,9 @@ public void handleTestError(TestError e) { if (e.getMessage() != null) { issues.append(e.getMessage()); } - if (e.getException() != null) { + if (e.causeIsException()) { issues.append(System.lineSeparator()); - issues.append(TestBase.stackTraceToString(e.getException())); + issues.append(e.getOriginalStackTrace()); } } diff --git a/core/src/testFixtures/java/org/lflang/tests/TestError.java b/core/src/testFixtures/java/org/lflang/tests/TestError.java index efb7d50048..fa22da447e 100644 --- a/core/src/testFixtures/java/org/lflang/tests/TestError.java +++ b/core/src/testFixtures/java/org/lflang/tests/TestError.java @@ -5,12 +5,14 @@ /// Indicates an error during test execution public class TestError extends Exception { - private Throwable exception; - private Result result; + private final String stackTrace; + private final Result result; public TestError(String errorMessage, Result result, Throwable exception) { super(errorMessage); - this.exception = exception; + assert result != null; + + this.stackTrace = exception == null ? null : TestBase.stackTraceToString(exception); this.result = result; } @@ -26,7 +28,13 @@ public Result getResult() { return result; } - public Throwable getException() { - return exception; + /// Return true, if the TestError instance was created based on an exception. + public boolean causeIsException() { + return stackTrace != null; + } + + /// Retrieve the stack trace of the exception that caused the test error. + public String getOriginalStackTrace() { + return stackTrace; } } From 9ce13531e7bee266fa499e80f8cb8c0f6877c814 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Thu, 14 Sep 2023 10:55:13 +0200 Subject: [PATCH 486/516] fix spotbugs warnings in cli test fixtures --- .../java/org/lflang/cli/CliToolTestFixture.java | 15 ++++++++++----- .../java/org/lflang/cli/TestUtils.java | 5 ++++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/cli/base/src/testFixtures/java/org/lflang/cli/CliToolTestFixture.java b/cli/base/src/testFixtures/java/org/lflang/cli/CliToolTestFixture.java index eab8f1aa9c..0950ad0098 100644 --- a/cli/base/src/testFixtures/java/org/lflang/cli/CliToolTestFixture.java +++ b/cli/base/src/testFixtures/java/org/lflang/cli/CliToolTestFixture.java @@ -31,6 +31,7 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import org.hamcrest.Matcher; import org.opentest4j.AssertionFailedError; @@ -66,7 +67,11 @@ public ExecutionResult run(Path wd, String... args) { ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream err = new ByteArrayOutputStream(); - Io testIo = new Io(new PrintStream(err), new PrintStream(out), wd); + Io testIo = + new Io( + new PrintStream(err, false, StandardCharsets.UTF_8), + new PrintStream(out, false, StandardCharsets.UTF_8), + wd); int exitCode = testIo.fakeSystemExit(io -> runCliProgram(io, args)); return new ExecutionResult(out, err, exitCode); @@ -82,11 +87,11 @@ public ExecutionResult run(Path wd, String... args) { record ExecutionResult(ByteArrayOutputStream out, ByteArrayOutputStream err, int exitCode) { public String getOut() { - return out.toString(); + return out.toString(StandardCharsets.UTF_8); } public String getErr() { - return err.toString(); + return err.toString(StandardCharsets.UTF_8); } public void checkOk() { @@ -117,9 +122,9 @@ public void verify(ThrowingConsumer actions) { System.out.println("TEST FAILED"); System.out.println("> Return code: " + exitCode); System.out.println("> Standard output -------------------------"); - System.err.println(out.toString()); + System.err.println(out.toString(StandardCharsets.UTF_8)); System.out.println("> Standard error --------------------------"); - System.err.println(err.toString()); + System.err.println(err.toString(StandardCharsets.UTF_8)); System.out.println("> -----------------------------------------"); if (e instanceof Exception) { diff --git a/cli/base/src/testFixtures/java/org/lflang/cli/TestUtils.java b/cli/base/src/testFixtures/java/org/lflang/cli/TestUtils.java index 442742768f..aa471a9ca8 100644 --- a/cli/base/src/testFixtures/java/org/lflang/cli/TestUtils.java +++ b/cli/base/src/testFixtures/java/org/lflang/cli/TestUtils.java @@ -111,7 +111,10 @@ public TempDirBuilder file(String relativePath, String contents) throws IOExcept throw new IllegalArgumentException("Should be a relative path: " + relativePath); } Path filePath = curDir.resolve(relPath); - Files.createDirectories(filePath.getParent()); + final var parent = filePath.getParent(); + if (parent != null) { + Files.createDirectories(parent); + } Files.writeString(filePath, contents); return this; } From 468a4a5a1c17917661e45b5f4376ed0dfd0c11d0 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Thu, 14 Sep 2023 13:49:48 +0200 Subject: [PATCH 487/516] simplifications --- core/src/main/java/org/lflang/Target.java | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/lflang/Target.java b/core/src/main/java/org/lflang/Target.java index 096788484e..8ab6af2891 100644 --- a/core/src/main/java/org/lflang/Target.java +++ b/core/src/main/java/org/lflang/Target.java @@ -469,16 +469,10 @@ public boolean supportsInheritance() { /** Return true if the target supports multiports and banks of reactors. */ public boolean supportsMultiports() { - switch (this) { - case C: - case CCPP: - case CPP: - case Python: - case Rust: - case TS: - return true; - } - return false; + return switch (this) { + case C, CCPP, CPP, Python, Rust, TS -> true; + default -> false; + }; } /** @@ -494,14 +488,11 @@ public boolean supportsParameterizedWidths() { * this target. */ public boolean supportsReactionDeclarations() { - if (this.equals(Target.C) || this.equals(Target.CPP)) return true; - else return false; + return this.equals(Target.C) || this.equals(Target.CPP); } /** * Return true if this code for this target should be built using Docker if Docker is used. - * - * @return */ public boolean buildsUsingDocker() { return switch (this) { From 352925e2b479db7e83bf217eb26b37c4fbc6dd11 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Thu, 14 Sep 2023 14:23:57 +0200 Subject: [PATCH 488/516] clean up Target.java --- core/src/main/java/org/lflang/Target.java | 42 ++++------------------- 1 file changed, 6 insertions(+), 36 deletions(-) diff --git a/core/src/main/java/org/lflang/Target.java b/core/src/main/java/org/lflang/Target.java index 8ab6af2891..c7aadda74f 100644 --- a/core/src/main/java/org/lflang/Target.java +++ b/core/src/main/java/org/lflang/Target.java @@ -24,17 +24,15 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import javax.annotation.concurrent.Immutable; import org.lflang.lf.TargetDecl; /** - * Enumeration of targets and their associated properties. These classes are written in Java, not - * Xtend, because the enum implementation in Xtend more primitive. It is safer to use enums rather - * than string values since it allows faulty references to be caught at compile time. Switch - * statements that take as input an enum but do not have cases for all members of the enum are also - * reported by Xtend with a warning message. + * Enumeration of targets and their associated properties. * * @author Marten Lohstroh */ +@Immutable public enum Target { C( "C", @@ -90,8 +88,6 @@ public enum Target { CPP( "Cpp", true, - "cpp", - "Cpp", Arrays.asList( // List via: https://en.cppreference.com/w/cpp/keyword "alignas", // (since C++11) @@ -194,8 +190,6 @@ public enum Target { TS( "TypeScript", false, - "ts", - "TS", Arrays.asList( // List via: https://github.com/Microsoft/TypeScript/issues/2536 // Reserved words @@ -352,8 +346,6 @@ public enum Target { Rust( "Rust", true, - "rust", - "Rust", // In our Rust implementation, the only reserved keywords // are those that are a valid expression. Others may be escaped // with the syntax r#keyword. @@ -362,17 +354,11 @@ public enum Target { /** String representation of this target. */ private final String displayName; - /** Name of package containing Kotlin classes for the target language. */ - public final String packageName; - - /** Prefix of names of Kotlin classes for the target language. */ - public final String classNamePrefix; - /** Whether or not this target requires types. */ public final boolean requiresTypes; /** Reserved words in the target language. */ - public final Set keywords; + private final Set keywords; /** An unmodifiable list of all known targets. */ public static final List ALL = List.of(Target.values()); @@ -382,26 +368,12 @@ public enum Target { * * @param displayName String representation of this target. * @param requiresTypes Types Whether this target requires type annotations or not. - * @param packageName Name of package containing Kotlin classes for the target language. - * @param classNamePrefix Prefix of names of Kotlin classes for the target language. * @param keywords List of reserved strings in the target language. */ - Target( - String displayName, - boolean requiresTypes, - String packageName, - String classNamePrefix, - Collection keywords) { + Target(String displayName, boolean requiresTypes, Collection keywords) { this.displayName = displayName; this.requiresTypes = requiresTypes; this.keywords = Collections.unmodifiableSet(new LinkedHashSet<>(keywords)); - this.packageName = packageName; - this.classNamePrefix = classNamePrefix; - } - - /** Private constructor for targets without packageName and classNamePrefix. */ - Target(String displayName, boolean requiresTypes, Collection keywords) { - this(displayName, requiresTypes, "N/A", "N/A", keywords); // FIXME: prefix } /** @@ -491,9 +463,7 @@ public boolean supportsReactionDeclarations() { return this.equals(Target.C) || this.equals(Target.CPP); } - /** - * Return true if this code for this target should be built using Docker if Docker is used. - */ + /** Return true if this code for this target should be built using Docker if Docker is used. */ public boolean buildsUsingDocker() { return switch (this) { case TS -> false; From 2616fa05647d57f405dd4465b20df20cbdec11c8 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Thu, 14 Sep 2023 14:29:30 +0200 Subject: [PATCH 489/516] fix spotbugs warnings in TestBase --- .../java/org/lflang/tests/LFTest.java | 38 ++++++++++--------- .../java/org/lflang/tests/TestBase.java | 29 +------------- 2 files changed, 23 insertions(+), 44 deletions(-) diff --git a/core/src/testFixtures/java/org/lflang/tests/LFTest.java b/core/src/testFixtures/java/org/lflang/tests/LFTest.java index 702f8cf4af..fb2198d8be 100644 --- a/core/src/testFixtures/java/org/lflang/tests/LFTest.java +++ b/core/src/testFixtures/java/org/lflang/tests/LFTest.java @@ -1,11 +1,7 @@ package org.lflang.tests; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.Reader; +import java.io.*; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import org.eclipse.xtext.util.RuntimeIOException; @@ -67,11 +63,6 @@ public LFTest(LFTest test) { this(test.target, test.srcPath); } - /** Stream object for capturing standard and error output. */ - public OutputStream getOutputStream() { - return compilationLog; - } - public FileConfig getFileConfig() { return context.getFileConfig(); } @@ -84,6 +75,20 @@ public Path getSrcPath() { return srcPath; } + /** Redirect outputs for recording. */ + public void redirectOutputs() { + System.setOut(new PrintStream(compilationLog, false, StandardCharsets.UTF_8)); + System.setErr(new PrintStream(compilationLog, false, StandardCharsets.UTF_8)); + } + + /** End output redirection. */ + public void restoreOutputs() { + System.out.flush(); + System.err.flush(); + System.setOut(System.out); + System.setErr(System.err); + } + /** * Comparison implementation to allow for tests to be sorted (e.g., when added to a tree set) * based on their path (relative to the root of the test directory). @@ -148,12 +153,12 @@ public void reportErrors() { System.out.println( "Failed: " + this - + String.format(" in %.2f seconds\n", getExecutionTimeNanoseconds() / 1.0e9)); + + String.format(" in %.2f seconds%n", getExecutionTimeNanoseconds() / 1.0e9)); System.out.println( "-----------------------------------------------------------------------------"); System.out.println("Reason: " + this.result.message); printIfNotEmpty("Reported issues", this.issues.toString()); - printIfNotEmpty("Compilation output", this.compilationLog.toString()); + printIfNotEmpty("Compilation output", this.compilationLog.toString(StandardCharsets.UTF_8)); printIfNotEmpty("Execution output", this.execLog.toString()); System.out.println( "+---------------------------------------------------------------------------+"); @@ -258,14 +263,13 @@ public Thread recordStdErr(Process process) { private Thread recordStream(StringBuffer builder, InputStream inputStream) { return new Thread( () -> { - try (Reader reader = new InputStreamReader(inputStream)) { + try (Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { int len; char[] buf = new char[1024]; while ((len = reader.read(buf)) > 0) { if (Runtime.getRuntime().freeMemory() < Runtime.getRuntime().totalMemory() / 2) { - Runtime.getRuntime().gc(); - if (Runtime.getRuntime().freeMemory() < Runtime.getRuntime().totalMemory() / 2) - builder.delete(0, builder.length() / 2); + builder.delete(0, builder.length() / 2); + builder.insert(0, "[earlier messages were removed to free up memory]%n"); } builder.append(buf, 0, len); } diff --git a/core/src/testFixtures/java/org/lflang/tests/TestBase.java b/core/src/testFixtures/java/org/lflang/tests/TestBase.java index 9fb8b93dc0..0c78c8b005 100644 --- a/core/src/testFixtures/java/org/lflang/tests/TestBase.java +++ b/core/src/testFixtures/java/org/lflang/tests/TestBase.java @@ -9,7 +9,6 @@ import java.io.BufferedWriter; import java.io.File; import java.io.IOException; -import java.io.PrintStream; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Constructor; @@ -67,12 +66,6 @@ public abstract class TestBase extends LfInjectedTestBase { @Inject TestRegistry testRegistry; - /** Reference to System.out. */ - private static final PrintStream out = System.out; - - /** Reference to System.err. */ - private static final PrintStream err = System.err; - /** Execution timeout enforced for all tests. */ private static final long MAX_EXECUTION_TIME_SECONDS = 300; @@ -283,24 +276,6 @@ protected static boolean isLinux() { return OS.contains("linux"); } - /** End output redirection. */ - private static void restoreOutputs() { - System.out.flush(); - System.err.flush(); - System.setOut(out); - System.setErr(err); - } - - /** - * Redirect outputs to the given tests for recording. - * - * @param test The test to redirect outputs to. - */ - private static void redirectOutputs(LFTest test) { - System.setOut(new PrintStream(test.getOutputStream())); - System.setErr(new PrintStream(test.getOutputStream())); - } - /** * Run a test, print results on stderr. * @@ -694,7 +669,7 @@ private void validateAndRun(Set tests, Configurator configurator, TestLe for (var test : tests) { try { - redirectOutputs(test); + test.redirectOutputs(); configure(test, configurator, level); validate(test); if (level.compareTo(TestLevel.CODE_GEN) >= 0) { @@ -710,7 +685,7 @@ private void validateAndRun(Set tests, Configurator configurator, TestLe test.handleTestError( new TestError("Unknown exception during test execution", Result.TEST_EXCEPTION, e)); } finally { - restoreOutputs(); + test.restoreOutputs(); } done++; while (Math.floor(done * x) >= marks && marks < 78) { From f14d6c93f31f27e6e00228dab5ebda6f9804205e Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Thu, 14 Sep 2023 14:44:46 +0200 Subject: [PATCH 490/516] clean up LFTest --- .../java/org/lflang/tests/RunSingleTest.java | 2 +- .../java/org/lflang/tests/LFTest.java | 19 ++----------------- .../java/org/lflang/tests/TestRegistry.java | 5 ++--- 3 files changed, 5 insertions(+), 21 deletions(-) diff --git a/core/src/integrationTest/java/org/lflang/tests/RunSingleTest.java b/core/src/integrationTest/java/org/lflang/tests/RunSingleTest.java index 29e62e835b..fe813823bd 100644 --- a/core/src/integrationTest/java/org/lflang/tests/RunSingleTest.java +++ b/core/src/integrationTest/java/org/lflang/tests/RunSingleTest.java @@ -70,7 +70,7 @@ public void runSingleTest() throws FileNotFoundException { Class testClass = getTestInstance(target); - LFTest testCase = new LFTest(target, path.toAbsolutePath()); + LFTest testCase = new LFTest(path.toAbsolutePath()); TestBase.runSingleTestAndPrintResults(testCase, testClass, TestBase.pathToLevel(path)); } diff --git a/core/src/testFixtures/java/org/lflang/tests/LFTest.java b/core/src/testFixtures/java/org/lflang/tests/LFTest.java index fb2198d8be..e99fee4b54 100644 --- a/core/src/testFixtures/java/org/lflang/tests/LFTest.java +++ b/core/src/testFixtures/java/org/lflang/tests/LFTest.java @@ -6,7 +6,6 @@ import java.nio.file.Paths; import org.eclipse.xtext.util.RuntimeIOException; import org.lflang.FileConfig; -import org.lflang.Target; import org.lflang.generator.LFGeneratorContext; /** @@ -40,29 +39,19 @@ public class LFTest implements Comparable { /** String builder for collecting issues encountered during test execution. */ private final StringBuilder issues = new StringBuilder(); - /** The target of the test program. */ - private final Target target; - private long executionTimeNanoseconds; /** * Create a new test. * - * @param target The target of the test program. * @param srcFile The path to the file of the test program. */ - public LFTest(Target target, Path srcFile) { - this.target = target; + public LFTest(Path srcFile) { this.srcPath = srcFile; this.name = FileConfig.findPackageRoot(srcFile, s -> {}).relativize(srcFile).toString(); this.relativePath = Paths.get(name); } - /** Copy constructor */ - public LFTest(LFTest test) { - this(test.target, test.srcPath); - } - public FileConfig getFileConfig() { return context.getFileConfig(); } @@ -141,11 +130,7 @@ public boolean hasPassed() { return result == Result.TEST_PASS; } - /** - * Compile a string that contains all collected errors and return it. - * - * @return A string that contains all collected errors. - */ + /** Compile a string that contains all collected errors and return it. */ public void reportErrors() { if (this.hasFailed()) { System.out.println( diff --git a/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java b/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java index 17b9984652..e5bb3334cc 100644 --- a/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java +++ b/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java @@ -116,7 +116,7 @@ public Set getRegisteredTests(Target target, TestCategory category, bool if (copy) { Set copies = new TreeSet<>(); for (LFTest test : registered.getTests(target, category)) { - copies.add(new LFTest(test)); + copies.add(new LFTest(test.getSrcPath())); } return copies; } else { @@ -237,8 +237,7 @@ public FileVisitResult visitFile(Path path, BasicFileAttributes attr) { if (attr.isRegularFile() && path.toString().endsWith(".lf")) { // Try to parse the file. Resource r = rs.getResource(URI.createFileURI(path.toFile().getAbsolutePath()), true); - // FIXME: issue warning if target doesn't match! - LFTest test = new LFTest(target, path); + LFTest test = new LFTest(path); Iterator reactors = IteratorExtensions.filter(r.getAllContents(), Reactor.class); From b09b8b10269f35f5fa2ae8bad826758c453ae257 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Thu, 14 Sep 2023 17:22:38 +0200 Subject: [PATCH 491/516] fix warnings in TestRegistry --- .../java/org/lflang/tests/TestRegistry.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java b/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java index e5bb3334cc..80c20baafd 100644 --- a/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java +++ b/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java @@ -17,10 +17,12 @@ import java.util.EnumMap; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import java.util.TreeSet; + import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; @@ -42,9 +44,8 @@ public class TestRegistry { * 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" - }; + public static final List IGNORED_DIRECTORIES = + List.of("failing", "knownfailed", "failed", "fed-gen"); /** Path to the root of the repository. */ public static final Path LF_REPO_PATH = Paths.get("").toAbsolutePath(); @@ -204,7 +205,8 @@ public TestDirVisitor(ResourceSet rs, Target target, Path srcBasePath) { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { for (String ignored : IGNORED_DIRECTORIES) { - if (dir.getFileName().toString().equalsIgnoreCase(ignored)) { + final var name = dir.getFileName(); + if (name != null && name.toString().equalsIgnoreCase(ignored)) { return SKIP_SUBTREE; } } From 06ee3261e9bdaeeb4d5ea8939d6368cbe99a2d79 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Thu, 14 Sep 2023 17:50:54 +0200 Subject: [PATCH 492/516] fix spotbugs and idea warnings in TestBase --- .../java/org/lflang/tests/TestBase.java | 57 +++++++------------ 1 file changed, 19 insertions(+), 38 deletions(-) diff --git a/core/src/testFixtures/java/org/lflang/tests/TestBase.java b/core/src/testFixtures/java/org/lflang/tests/TestBase.java index 0c78c8b005..a589beb882 100644 --- a/core/src/testFixtures/java/org/lflang/tests/TestBase.java +++ b/core/src/testFixtures/java/org/lflang/tests/TestBase.java @@ -88,8 +88,6 @@ public abstract class TestBase extends LfInjectedTestBase { * @author Marten Lohstroh */ public enum TestLevel { - VALIDATION, - CODE_GEN, BUILD, EXECUTION } @@ -100,10 +98,11 @@ public enum TestLevel { * @author Anirudh Rengarajan */ public static TestLevel pathToLevel(Path path) { - while (path.getParent() != null) { - String name = path.getFileName().toString(); + path = path.getParent(); + while (path != null) { + final var name = path.getFileName(); for (var category : TestCategory.values()) { - if (category.name().equalsIgnoreCase(name)) { + if (name != null && name.toString().equalsIgnoreCase(category.name())) { return category.level; } } @@ -127,13 +126,11 @@ public static class Message { public static final String NO_ENCLAVE_SUPPORT = "Targeet does not support the enclave feature."; public static final String NO_DOCKER_SUPPORT = "Target does not support the 'docker' property."; public static final String NO_DOCKER_TEST_SUPPORT = "Docker tests are only supported on Linux."; - public static final String NO_GENERICS_SUPPORT = "Target does not support generic types."; /* Descriptions of collections of tests. */ public static final String DESC_SERIALIZATION = "Run serialization tests."; public static final String DESC_BASIC = "Run basic tests."; public static final String DESC_GENERICS = "Run generics tests."; - public static final String DESC_TYPE_PARMS = "Run tests for reactors with type parameters."; public static final String DESC_MULTIPORT = "Run multiport tests."; public static final String DESC_AS_FEDERATED = "Run non-federated tests in federated mode."; public static final String DESC_FEDERATED = "Run federated tests."; @@ -151,11 +148,6 @@ public static class Message { public static final String DESC_ROS2 = "Running tests using ROS2."; public static final String DESC_MODAL = "Run modal reactor tests."; public static final String DESC_VERIFIER = "Run verifier tests."; - - /* Missing dependency messages */ - public static final String MISSING_DOCKER = - "Executable 'docker' not found or 'docker' daemon thread not running"; - public static final String MISSING_ARDUINO_CLI = "Executable 'arduino-cli' not found"; } /** Constructor for test classes that test a single target. */ @@ -336,7 +328,7 @@ private static void checkAndReportFailures(Set tests) { var passed = tests.stream().filter(LFTest::hasPassed).toList(); var s = new StringBuffer(); s.append(THIN_LINE); - s.append("Passing: ").append(passed.size()).append("/").append(tests.size()).append("\n"); + s.append("Passing: ").append(passed.size()).append("/").append(tests.size()).append("%n"); s.append(THIN_LINE); passed.forEach( test -> @@ -344,7 +336,7 @@ private static void checkAndReportFailures(Set tests) { .append(test) .append( String.format( - " in %.2f seconds\n", test.getExecutionTimeNanoseconds() / 1.0e9))); + " in %.2f seconds%n", test.getExecutionTimeNanoseconds() / 1.0e9))); s.append(THIN_LINE); System.out.print(s); @@ -357,16 +349,14 @@ private static void checkAndReportFailures(Set tests) { } /** - * Configure a test by applying the given configurator and return a generator context. Also, if - * the given level is less than {@code TestLevel.BUILD}, add a {@code no-compile} flag to the - * generator context. If the configurator was not applied successfully, throw an AssertionError. + * Configure a test by applying the given configurator and return a generator context. + * If the configurator was not applied successfully, throw an AssertionError. * * @param test the test to configure. * @param configurator The configurator to apply to the test. - * @param level The level of testing in which the generator context will be used. */ - private void configure(LFTest test, Configurator configurator, TestLevel level) - throws IOException, TestError { + private void configure(LFTest test, Configurator configurator) + throws TestError { var props = new Properties(); props.setProperty("hierarchical-bin", "true"); @@ -412,11 +402,6 @@ private void configure(LFTest test, Configurator configurator, TestLevel level) test.configure(context); - // Set the no-compile flag the test is not supposed to reach the build stage. - if (level.compareTo(TestLevel.BUILD) < 0) { - context.getArgs().setProperty("no-compile", ""); - } - // Reload in case target properties have changed. context.loadTargetConfig(); // Update the test by applying the configuration. E.g., to carry out an AST transformation. @@ -462,9 +447,9 @@ protected void addExtraLfcArgs(Properties args, TargetConfig targetConfig) { * * @param test The test to generate code for. */ - private GeneratorResult generateCode(LFTest test) throws TestError { + private void generateCode(LFTest test) throws TestError { if (test.getFileConfig().resource == null) { - return GeneratorResult.NOTHING; + test.getContext().finish(GeneratorResult.NOTHING); } try { generator.doGenerate(test.getFileConfig().resource, fileAccess, test.getContext()); @@ -474,8 +459,6 @@ private GeneratorResult generateCode(LFTest test) throws TestError { if (generator.errorsOccurred()) { throw new TestError("Code generation unsuccessful.", Result.CODE_GEN_FAIL); } - - return test.getContext().getResult(); } /** @@ -507,13 +490,13 @@ private void execute(LFTest test) throws TestError { throw new TestError(Result.TEST_TIMEOUT); } else { if (stdoutException.get() != null || stderrException.get() != null) { - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); if (stdoutException.get() != null) { - sb.append("Error during stdout handling:" + System.lineSeparator()); + sb.append("Error during stdout handling:%n"); sb.append(stackTraceToString(stdoutException.get())); } if (stderrException.get() != null) { - sb.append("Error during stderr handling:" + System.lineSeparator()); + sb.append("Error during stderr handling:%n"); sb.append(stackTraceToString(stderrException.get())); } throw new TestError(sb.toString(), Result.TEST_EXCEPTION); @@ -551,7 +534,7 @@ public static String stackTraceToString(Throwable t) { } /** Bash script that is used to execute docker tests. */ - private static String DOCKER_RUN_SCRIPT = + private static final String DOCKER_RUN_SCRIPT = """ #!/bin/bash @@ -584,7 +567,7 @@ public static String stackTraceToString(Throwable t) { * *

If the script does not yet exist, it is created. */ - private Path getDockerRunScript() throws TestError { + private static synchronized Path getDockerRunScript() throws TestError { if (dockerRunScript != null) { return dockerRunScript; } @@ -670,11 +653,9 @@ private void validateAndRun(Set tests, Configurator configurator, TestLe for (var test : tests) { try { test.redirectOutputs(); - configure(test, configurator, level); + configure(test, configurator); validate(test); - if (level.compareTo(TestLevel.CODE_GEN) >= 0) { - generateCode(test); - } + generateCode(test); if (level == TestLevel.EXECUTION) { execute(test); } From 89c13d2ae8f28ca56254cd688f0cc0cb2c6f3d67 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Thu, 14 Sep 2023 18:00:42 +0200 Subject: [PATCH 493/516] fix spotbugs warnings in issue reporting --- .../src/test/kotlin/org/lflang/cli/LfcIssueReportingTest.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cli/lfc/src/test/kotlin/org/lflang/cli/LfcIssueReportingTest.kt b/cli/lfc/src/test/kotlin/org/lflang/cli/LfcIssueReportingTest.kt index c2839673be..21cdaf3043 100644 --- a/cli/lfc/src/test/kotlin/org/lflang/cli/LfcIssueReportingTest.kt +++ b/cli/lfc/src/test/kotlin/org/lflang/cli/LfcIssueReportingTest.kt @@ -38,7 +38,9 @@ import java.nio.file.Paths class SpyPrintStream { private val baout = ByteArrayOutputStream() - val ps = PrintStream(baout) + private val ps = PrintStream(baout, false, StandardCharsets.UTF_8) + + fun getSpiedErrIo(): Io = Io(err = ps) override fun toString(): String = baout.toString(StandardCharsets.UTF_8) } @@ -111,7 +113,7 @@ class LfcIssueReportingTest { val stderr = SpyPrintStream() - val io = Io(err = stderr.ps) + val io = stderr.getSpiedErrIo() val backend = ReportingBackend(io, AnsiColors(useColors).bold("lfc: "), AnsiColors(useColors), 2) val injector = LFStandaloneSetup(LFRuntimeModule(), LFStandaloneModule(backend, io)) .createInjectorAndDoEMFRegistration() From eddd59360dafe5a023a445b0c21e137dc07321fc Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Thu, 14 Sep 2023 18:10:05 +0200 Subject: [PATCH 494/516] formatting --- core/src/testFixtures/java/org/lflang/tests/TestBase.java | 7 +++---- .../testFixtures/java/org/lflang/tests/TestRegistry.java | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/core/src/testFixtures/java/org/lflang/tests/TestBase.java b/core/src/testFixtures/java/org/lflang/tests/TestBase.java index a589beb882..ee0d72ff02 100644 --- a/core/src/testFixtures/java/org/lflang/tests/TestBase.java +++ b/core/src/testFixtures/java/org/lflang/tests/TestBase.java @@ -349,14 +349,13 @@ private static void checkAndReportFailures(Set tests) { } /** - * Configure a test by applying the given configurator and return a generator context. - * If the configurator was not applied successfully, throw an AssertionError. + * Configure a test by applying the given configurator and return a generator context. If the + * configurator was not applied successfully, throw an AssertionError. * * @param test the test to configure. * @param configurator The configurator to apply to the test. */ - private void configure(LFTest test, Configurator configurator) - throws TestError { + private void configure(LFTest test, Configurator configurator) throws TestError { var props = new Properties(); props.setProperty("hierarchical-bin", "true"); diff --git a/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java b/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java index 80c20baafd..14b12647ad 100644 --- a/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java +++ b/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java @@ -22,7 +22,6 @@ import java.util.Set; import java.util.Stack; import java.util.TreeSet; - import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; From 5fb671c3f470da9ee667df9575814f7eb10ec55f Mon Sep 17 00:00:00 2001 From: Erling Rennemo Jellum Date: Thu, 14 Sep 2023 20:23:47 +0200 Subject: [PATCH 495/516] Update nrf52 handling of the zephyr counter drivers --- .../lib/platform/zephyr/boards/nrf52dk_nrf52832.overlay | 3 +++ .../lib/platform/zephyr/boards/nrf52dk_nrf52832_lf.conf | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/nrf52dk_nrf52832.overlay delete mode 100644 core/src/main/resources/lib/platform/zephyr/boards/nrf52dk_nrf52832_lf.conf diff --git a/core/src/main/resources/lib/platform/zephyr/boards/nrf52dk_nrf52832.overlay b/core/src/main/resources/lib/platform/zephyr/boards/nrf52dk_nrf52832.overlay new file mode 100644 index 0000000000..30472cd1fb --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/nrf52dk_nrf52832.overlay @@ -0,0 +1,3 @@ +&timer1 { + status = "okay"; +}; \ No newline at end of file diff --git a/core/src/main/resources/lib/platform/zephyr/boards/nrf52dk_nrf52832_lf.conf b/core/src/main/resources/lib/platform/zephyr/boards/nrf52dk_nrf52832_lf.conf deleted file mode 100644 index 8bc7cfdbc5..0000000000 --- a/core/src/main/resources/lib/platform/zephyr/boards/nrf52dk_nrf52832_lf.conf +++ /dev/null @@ -1 +0,0 @@ -CONFIG_COUNTER_TIMER1=y From c69a519847ae761a13f7d0e64922f88a8bd74fe2 Mon Sep 17 00:00:00 2001 From: Erling Rennemo Jellum Date: Fri, 15 Sep 2023 09:58:18 +0200 Subject: [PATCH 496/516] Add board-specific tests to the Zephyr regressions tests. We just make sure that HelloWorld programs compile for different boards. --- .gitmodules | 2 +- .../java/org/lflang/tests/runtime/CZephyrTest.java | 12 ++++++++++++ .../lib/platform/zephyr/boards/arduino_due.overlay | 4 ++++ .../platform/zephyr/boards/bl5340_dvk_cpuapp.conf | 5 +++++ .../zephyr/boards/bl5340_dvk_cpuapp.overlay | 12 ++++++++++++ .../platform/zephyr/boards/esp32c3_devkitm.overlay | 3 +++ .../platform/zephyr/boards/esp32s2_saola.overlay | 3 +++ .../platform/zephyr/boards/esp32s3_devkitm.overlay | 3 +++ .../platform/zephyr/boards/gd32e103v_eval.overlay | 10 ++++++++++ .../platform/zephyr/boards/gd32e507v_start.overlay | 10 ++++++++++ .../platform/zephyr/boards/gd32e507z_eval.overlay | 10 ++++++++++ .../platform/zephyr/boards/gd32f350r_eval.overlay | 10 ++++++++++ .../platform/zephyr/boards/gd32f403z_eval.overlay | 10 ++++++++++ .../platform/zephyr/boards/gd32f407v_start.overlay | 10 ++++++++++ .../platform/zephyr/boards/gd32f450i_eval.overlay | 10 ++++++++++ .../platform/zephyr/boards/gd32f450v_start.overlay | 10 ++++++++++ .../platform/zephyr/boards/gd32f450z_eval.overlay | 10 ++++++++++ .../platform/zephyr/boards/gd32f470i_eval.overlay | 10 ++++++++++ .../zephyr/boards/nrf51dk_nrf51422.overlay | 3 +++ .../zephyr/boards/nrf52840dk_nrf52840.overlay | 3 +++ .../zephyr/boards/nrf9160dk_nrf9160.overlay | 3 +++ .../zephyr/boards/s32z270dc2_rtu0_r52.overlay | 10 ++++++++++ .../zephyr/boards/s32z270dc2_rtu1_r52.overlay | 10 ++++++++++ .../lib/platform/zephyr/boards/sam4e_xpro.overlay | 4 ++++ .../platform/zephyr/boards/sam4s_xplained.overlay | 4 ++++ .../zephyr/boards/sam_e70_xplained.overlay | 4 ++++ .../zephyr/boards/sam_e70b_xplained.overlay | 4 ++++ .../platform/zephyr/boards/sam_v71_xult.overlay | 4 ++++ .../platform/zephyr/boards/sam_v71b_xult.overlay | 4 ++++ .../zephyr/boards/stm32h735g_disco.overlay | 6 ++++++ .../platform/zephyr/boards/stm32l562e_dk_ns.conf | 7 +++++++ .../java/org/lflang/tests/TestRegistry.java | 1 + test/C/src/zephyr/boards/ArduinoDue.lf | 14 ++++++++++++++ test/C/src/zephyr/boards/ESP32.lf | 14 ++++++++++++++ test/C/src/zephyr/boards/GD32.lf | 14 ++++++++++++++ test/C/src/zephyr/boards/NRF52.lf | 14 ++++++++++++++ test/C/src/zephyr/boards/NXPIMXRT1170.lf | 14 ++++++++++++++ test/C/src/zephyr/boards/NXPS32.lf | 14 ++++++++++++++ test/C/src/zephyr/boards/SAM.lf | 14 ++++++++++++++ test/C/src/zephyr/boards/STM32.lf | 12 ++++++++++++ 40 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/arduino_due.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/bl5340_dvk_cpuapp.conf create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/bl5340_dvk_cpuapp.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/esp32c3_devkitm.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/esp32s2_saola.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/esp32s3_devkitm.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/gd32e103v_eval.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/gd32e507v_start.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/gd32e507z_eval.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/gd32f350r_eval.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/gd32f403z_eval.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/gd32f407v_start.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/gd32f450i_eval.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/gd32f450v_start.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/gd32f450z_eval.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/gd32f470i_eval.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/nrf51dk_nrf51422.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/nrf52840dk_nrf52840.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/nrf9160dk_nrf9160.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/s32z270dc2_rtu0_r52.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/s32z270dc2_rtu1_r52.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/sam4e_xpro.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/sam4s_xplained.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/sam_e70_xplained.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/sam_e70b_xplained.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/sam_v71_xult.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/sam_v71b_xult.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/stm32h735g_disco.overlay create mode 100644 core/src/main/resources/lib/platform/zephyr/boards/stm32l562e_dk_ns.conf create mode 100644 test/C/src/zephyr/boards/ArduinoDue.lf create mode 100644 test/C/src/zephyr/boards/ESP32.lf create mode 100644 test/C/src/zephyr/boards/GD32.lf create mode 100644 test/C/src/zephyr/boards/NRF52.lf create mode 100644 test/C/src/zephyr/boards/NXPIMXRT1170.lf create mode 100644 test/C/src/zephyr/boards/NXPS32.lf create mode 100644 test/C/src/zephyr/boards/SAM.lf create mode 100644 test/C/src/zephyr/boards/STM32.lf diff --git a/.gitmodules b/.gitmodules index 9cd89ffe1a..1eaef285c7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "org.lflang/src/lib/c/reactor-c"] path = core/src/main/resources/lib/c/reactor-c - url = https://github.com/lf-lang/reactor-c.git + url = git@github.com:lf-lang/reactor-c.git [submodule "org.lflang/src/lib/cpp/reactor-cpp"] path = core/src/main/resources/lib/cpp/reactor-cpp url = https://github.com/lf-lang/reactor-cpp diff --git a/core/src/integrationTest/java/org/lflang/tests/runtime/CZephyrTest.java b/core/src/integrationTest/java/org/lflang/tests/runtime/CZephyrTest.java index 145f4b1d53..6ab2490abe 100644 --- a/core/src/integrationTest/java/org/lflang/tests/runtime/CZephyrTest.java +++ b/core/src/integrationTest/java/org/lflang/tests/runtime/CZephyrTest.java @@ -55,6 +55,18 @@ public void buildZephyrUnthreadedTests() { false); } + @Test + public void buildZephyrBoardTests() { + Assumptions.assumeTrue(isLinux(), "Zephyr tests only run on Linux"); + super.runTestsFor( + List.of(Target.C), + Message.DESC_ZEPHYR, + TestCategory.ZEPHYR_BOARDS::equals, + Configurators::noChanges, + TestLevel.BUILD, + false); + } + @Test public void buildZephyrThreadedTests() { Assumptions.assumeTrue(isLinux(), "Zephyr tests only run on Linux"); diff --git a/core/src/main/resources/lib/platform/zephyr/boards/arduino_due.overlay b/core/src/main/resources/lib/platform/zephyr/boards/arduino_due.overlay new file mode 100644 index 0000000000..12087adfaa --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/arduino_due.overlay @@ -0,0 +1,4 @@ +&tc0 { + clk = <4>; + status = "okay"; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/bl5340_dvk_cpuapp.conf b/core/src/main/resources/lib/platform/zephyr/boards/bl5340_dvk_cpuapp.conf new file mode 100644 index 0000000000..c9e4c07698 --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/bl5340_dvk_cpuapp.conf @@ -0,0 +1,5 @@ +# Enable RTC +CONFIG_I2C=y +CONFIG_COUNTER=y +CONFIG_COUNTER_MICROCHIP_MCP7940N=y +CONFIG_COUNTER_INIT_PRIORITY=65 diff --git a/core/src/main/resources/lib/platform/zephyr/boards/bl5340_dvk_cpuapp.overlay b/core/src/main/resources/lib/platform/zephyr/boards/bl5340_dvk_cpuapp.overlay new file mode 100644 index 0000000000..7869ff34ea --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/bl5340_dvk_cpuapp.overlay @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2021 Laird Connectivity + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&i2c1 { + /* Connect MCP7940N MFP pin TP9 to P0.04 */ + extrtc0: mcp7940n@6f { + int-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>; + }; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/esp32c3_devkitm.overlay b/core/src/main/resources/lib/platform/zephyr/boards/esp32c3_devkitm.overlay new file mode 100644 index 0000000000..241947b064 --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/esp32c3_devkitm.overlay @@ -0,0 +1,3 @@ +&timer0 { + status = "okay"; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/esp32s2_saola.overlay b/core/src/main/resources/lib/platform/zephyr/boards/esp32s2_saola.overlay new file mode 100644 index 0000000000..241947b064 --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/esp32s2_saola.overlay @@ -0,0 +1,3 @@ +&timer0 { + status = "okay"; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/esp32s3_devkitm.overlay b/core/src/main/resources/lib/platform/zephyr/boards/esp32s3_devkitm.overlay new file mode 100644 index 0000000000..241947b064 --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/esp32s3_devkitm.overlay @@ -0,0 +1,3 @@ +&timer0 { + status = "okay"; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/gd32e103v_eval.overlay b/core/src/main/resources/lib/platform/zephyr/boards/gd32e103v_eval.overlay new file mode 100644 index 0000000000..abebc86c8e --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/gd32e103v_eval.overlay @@ -0,0 +1,10 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2022 TOKITA Hiroshi + */ + +&timer0 { + status = "okay"; + prescaler = <29999>; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/gd32e507v_start.overlay b/core/src/main/resources/lib/platform/zephyr/boards/gd32e507v_start.overlay new file mode 100644 index 0000000000..9e20c4e607 --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/gd32e507v_start.overlay @@ -0,0 +1,10 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2022 TOKITA Hiroshi + */ + +&timer0 { + status = "okay"; + prescaler = <44999>; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/gd32e507z_eval.overlay b/core/src/main/resources/lib/platform/zephyr/boards/gd32e507z_eval.overlay new file mode 100644 index 0000000000..9e20c4e607 --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/gd32e507z_eval.overlay @@ -0,0 +1,10 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2022 TOKITA Hiroshi + */ + +&timer0 { + status = "okay"; + prescaler = <44999>; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/gd32f350r_eval.overlay b/core/src/main/resources/lib/platform/zephyr/boards/gd32f350r_eval.overlay new file mode 100644 index 0000000000..7b0464653b --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/gd32f350r_eval.overlay @@ -0,0 +1,10 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2022 TOKITA Hiroshi + */ + +&timer0 { + status = "okay"; + prescaler = <26999>; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/gd32f403z_eval.overlay b/core/src/main/resources/lib/platform/zephyr/boards/gd32f403z_eval.overlay new file mode 100644 index 0000000000..06918e09dd --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/gd32f403z_eval.overlay @@ -0,0 +1,10 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2022 TOKITA Hiroshi + */ + +&timer0 { + status = "okay"; + prescaler = <41999>; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/gd32f407v_start.overlay b/core/src/main/resources/lib/platform/zephyr/boards/gd32f407v_start.overlay new file mode 100644 index 0000000000..06918e09dd --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/gd32f407v_start.overlay @@ -0,0 +1,10 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2022 TOKITA Hiroshi + */ + +&timer0 { + status = "okay"; + prescaler = <41999>; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/gd32f450i_eval.overlay b/core/src/main/resources/lib/platform/zephyr/boards/gd32f450i_eval.overlay new file mode 100644 index 0000000000..ac50bfbe62 --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/gd32f450i_eval.overlay @@ -0,0 +1,10 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2022 TOKITA Hiroshi + */ + +&timer0 { + status = "okay"; + prescaler = <49999>; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/gd32f450v_start.overlay b/core/src/main/resources/lib/platform/zephyr/boards/gd32f450v_start.overlay new file mode 100644 index 0000000000..ac50bfbe62 --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/gd32f450v_start.overlay @@ -0,0 +1,10 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2022 TOKITA Hiroshi + */ + +&timer0 { + status = "okay"; + prescaler = <49999>; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/gd32f450z_eval.overlay b/core/src/main/resources/lib/platform/zephyr/boards/gd32f450z_eval.overlay new file mode 100644 index 0000000000..ac50bfbe62 --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/gd32f450z_eval.overlay @@ -0,0 +1,10 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2022 TOKITA Hiroshi + */ + +&timer0 { + status = "okay"; + prescaler = <49999>; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/gd32f470i_eval.overlay b/core/src/main/resources/lib/platform/zephyr/boards/gd32f470i_eval.overlay new file mode 100644 index 0000000000..9ed334331f --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/gd32f470i_eval.overlay @@ -0,0 +1,10 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2022 TOKITA Hiroshi + */ + +&timer0 { + status = "okay"; + prescaler = <59999>; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/nrf51dk_nrf51422.overlay b/core/src/main/resources/lib/platform/zephyr/boards/nrf51dk_nrf51422.overlay new file mode 100644 index 0000000000..0c88903dc5 --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/nrf51dk_nrf51422.overlay @@ -0,0 +1,3 @@ +&timer1 { + status = "okay"; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/nrf52840dk_nrf52840.overlay b/core/src/main/resources/lib/platform/zephyr/boards/nrf52840dk_nrf52840.overlay new file mode 100644 index 0000000000..5e37ad9fff --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/nrf52840dk_nrf52840.overlay @@ -0,0 +1,3 @@ +&rtc0 { + status = "okay"; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/nrf9160dk_nrf9160.overlay b/core/src/main/resources/lib/platform/zephyr/boards/nrf9160dk_nrf9160.overlay new file mode 100644 index 0000000000..5e37ad9fff --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/nrf9160dk_nrf9160.overlay @@ -0,0 +1,3 @@ +&rtc0 { + status = "okay"; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/s32z270dc2_rtu0_r52.overlay b/core/src/main/resources/lib/platform/zephyr/boards/s32z270dc2_rtu0_r52.overlay new file mode 100644 index 0000000000..e0f2025d37 --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/s32z270dc2_rtu0_r52.overlay @@ -0,0 +1,10 @@ +/* + * Copyright 2022 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&stm0 { + prescaler = <1>; + status = "okay"; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/s32z270dc2_rtu1_r52.overlay b/core/src/main/resources/lib/platform/zephyr/boards/s32z270dc2_rtu1_r52.overlay new file mode 100644 index 0000000000..e0f2025d37 --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/s32z270dc2_rtu1_r52.overlay @@ -0,0 +1,10 @@ +/* + * Copyright 2022 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&stm0 { + prescaler = <1>; + status = "okay"; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/sam4e_xpro.overlay b/core/src/main/resources/lib/platform/zephyr/boards/sam4e_xpro.overlay new file mode 100644 index 0000000000..12087adfaa --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/sam4e_xpro.overlay @@ -0,0 +1,4 @@ +&tc0 { + clk = <4>; + status = "okay"; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/sam4s_xplained.overlay b/core/src/main/resources/lib/platform/zephyr/boards/sam4s_xplained.overlay new file mode 100644 index 0000000000..12087adfaa --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/sam4s_xplained.overlay @@ -0,0 +1,4 @@ +&tc0 { + clk = <4>; + status = "okay"; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/sam_e70_xplained.overlay b/core/src/main/resources/lib/platform/zephyr/boards/sam_e70_xplained.overlay new file mode 100644 index 0000000000..12087adfaa --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/sam_e70_xplained.overlay @@ -0,0 +1,4 @@ +&tc0 { + clk = <4>; + status = "okay"; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/sam_e70b_xplained.overlay b/core/src/main/resources/lib/platform/zephyr/boards/sam_e70b_xplained.overlay new file mode 100644 index 0000000000..12087adfaa --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/sam_e70b_xplained.overlay @@ -0,0 +1,4 @@ +&tc0 { + clk = <4>; + status = "okay"; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/sam_v71_xult.overlay b/core/src/main/resources/lib/platform/zephyr/boards/sam_v71_xult.overlay new file mode 100644 index 0000000000..12087adfaa --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/sam_v71_xult.overlay @@ -0,0 +1,4 @@ +&tc0 { + clk = <4>; + status = "okay"; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/sam_v71b_xult.overlay b/core/src/main/resources/lib/platform/zephyr/boards/sam_v71b_xult.overlay new file mode 100644 index 0000000000..12087adfaa --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/sam_v71b_xult.overlay @@ -0,0 +1,4 @@ +&tc0 { + clk = <4>; + status = "okay"; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/stm32h735g_disco.overlay b/core/src/main/resources/lib/platform/zephyr/boards/stm32h735g_disco.overlay new file mode 100644 index 0000000000..00a10669ba --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/stm32h735g_disco.overlay @@ -0,0 +1,6 @@ +&timers2 { + st,prescaler = <83>; + counter { + status = "okay"; + }; +}; diff --git a/core/src/main/resources/lib/platform/zephyr/boards/stm32l562e_dk_ns.conf b/core/src/main/resources/lib/platform/zephyr/boards/stm32l562e_dk_ns.conf new file mode 100644 index 0000000000..52d11aac39 --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/boards/stm32l562e_dk_ns.conf @@ -0,0 +1,7 @@ +# +# Copyright (c) 2022 O.S.Systems +# +# SPDX-License-Identifier: Apache-2.0 +# +CONFIG_BUILD_WITH_TFM=y +CONFIG_TFM_PROFILE_TYPE_MEDIUM=y diff --git a/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java b/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java index 17b9984652..1ffa93905b 100644 --- a/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java +++ b/core/src/testFixtures/java/org/lflang/tests/TestRegistry.java @@ -334,6 +334,7 @@ public enum TestCategory { ARDUINO(false, "", TestLevel.BUILD), ZEPHYR_THREADED(false, "zephyr" + File.separator + "threaded", TestLevel.BUILD), ZEPHYR_UNTHREADED(false, "zephyr" + File.separator + "unthreaded", TestLevel.BUILD), + ZEPHYR_BOARDS(false, "zephyr" + File.separator + "boards", TestLevel.BUILD), VERIFIER(false, "verifier", TestLevel.EXECUTION), TARGET(false, "", TestLevel.EXECUTION); diff --git a/test/C/src/zephyr/boards/ArduinoDue.lf b/test/C/src/zephyr/boards/ArduinoDue.lf new file mode 100644 index 0000000000..8bc1358c10 --- /dev/null +++ b/test/C/src/zephyr/boards/ArduinoDue.lf @@ -0,0 +1,14 @@ +target C { + platform: { + name: Zephyr, + board: arduino_due + } +} + +main reactor { + timer t(0, 1 sec) + + reaction(t) {= + printf("Hello\n"); + =} +} diff --git a/test/C/src/zephyr/boards/ESP32.lf b/test/C/src/zephyr/boards/ESP32.lf new file mode 100644 index 0000000000..4e1a6936c3 --- /dev/null +++ b/test/C/src/zephyr/boards/ESP32.lf @@ -0,0 +1,14 @@ +target C { + platform: { + name: Zephyr, + board: esp32 + } +} + +main reactor { + timer t(0, 1 sec) + + reaction(t) {= + printf("Hello\n"); + =} +} diff --git a/test/C/src/zephyr/boards/GD32.lf b/test/C/src/zephyr/boards/GD32.lf new file mode 100644 index 0000000000..2cee0ef537 --- /dev/null +++ b/test/C/src/zephyr/boards/GD32.lf @@ -0,0 +1,14 @@ +target C { + platform: { + name: Zephyr, + board: gd32f403z_eval + } +} + +main reactor { + timer t(0, 1 sec) + + reaction(t) {= + printf("Hello\n"); + =} +} diff --git a/test/C/src/zephyr/boards/NRF52.lf b/test/C/src/zephyr/boards/NRF52.lf new file mode 100644 index 0000000000..4d2a4fa551 --- /dev/null +++ b/test/C/src/zephyr/boards/NRF52.lf @@ -0,0 +1,14 @@ +target C { + platform: { + name: Zephyr, + board: nrf52dk_nrf52832 + } +} + +main reactor { + timer t(0, 1 sec) + + reaction(t) {= + printf("Hello\n"); + =} +} diff --git a/test/C/src/zephyr/boards/NXPIMXRT1170.lf b/test/C/src/zephyr/boards/NXPIMXRT1170.lf new file mode 100644 index 0000000000..f3f36a22bb --- /dev/null +++ b/test/C/src/zephyr/boards/NXPIMXRT1170.lf @@ -0,0 +1,14 @@ +target C { + platform: { + name: Zephyr, + board: mimxrt1170_evk_cm7 + } +} + +main reactor { + timer t(0, 1 sec) + + reaction(t) {= + printf("Hello\n"); + =} +} diff --git a/test/C/src/zephyr/boards/NXPS32.lf b/test/C/src/zephyr/boards/NXPS32.lf new file mode 100644 index 0000000000..64bb94f46d --- /dev/null +++ b/test/C/src/zephyr/boards/NXPS32.lf @@ -0,0 +1,14 @@ +target C { + platform: { + name: Zephyr, + board: s32z270dc2_rtu0_r52 + } +} + +main reactor { + timer t(0, 1 sec) + + reaction(t) {= + printf("Hello\n"); + =} +} diff --git a/test/C/src/zephyr/boards/SAM.lf b/test/C/src/zephyr/boards/SAM.lf new file mode 100644 index 0000000000..32641481d0 --- /dev/null +++ b/test/C/src/zephyr/boards/SAM.lf @@ -0,0 +1,14 @@ +target C { + platform: { + name: Zephyr, + board: sam4s_xplained + } +} + +main reactor { + timer t(0, 1 sec) + + reaction(t) {= + printf("Hello\n"); + =} +} diff --git a/test/C/src/zephyr/boards/STM32.lf b/test/C/src/zephyr/boards/STM32.lf new file mode 100644 index 0000000000..c849ea0f30 --- /dev/null +++ b/test/C/src/zephyr/boards/STM32.lf @@ -0,0 +1,12 @@ +target C { + platform: "Zephyr", + board: stm32f4_disco +} + +main reactor { + timer t(0, 1 sec) + + reaction(t) {= + printf("Hello\n"); + =} +} From ff7f93f2dd09daa23779e3658116142f00c9e26d Mon Sep 17 00:00:00 2001 From: Erling Rennemo Jellum Date: Fri, 15 Sep 2023 09:58:38 +0200 Subject: [PATCH 497/516] Add a small README to explain Zephr board files in our source tree --- core/src/main/resources/lib/platform/zephyr/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 core/src/main/resources/lib/platform/zephyr/README.md diff --git a/core/src/main/resources/lib/platform/zephyr/README.md b/core/src/main/resources/lib/platform/zephyr/README.md new file mode 100644 index 0000000000..a0d92819ec --- /dev/null +++ b/core/src/main/resources/lib/platform/zephyr/README.md @@ -0,0 +1,3 @@ +# Zephyr platform files +These are files needed to compile LF programs for the Zephyr target. +All of the files in the `boards` directory are for enabling a Timer peripheral for different devices so that we can use a Counter device for time-keeping. These device tree config overlays are copied from the `alarm` example found in the zephyr source tree under `samples/drivers/counter/alarm`. \ No newline at end of file From e841d6453f295e919325df0dae4abc67fd9f2d28 Mon Sep 17 00:00:00 2001 From: Erling Rennemo Jellum Date: Fri, 15 Sep 2023 09:59:04 +0200 Subject: [PATCH 498/516] Do not generate CMake code for linking with Threads library when we are targeting Zephyr --- .../org/lflang/generator/c/CCmakeGenerator.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 0ee24fc39a..ea1db3713e 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -326,25 +326,23 @@ CodeBuilder generateCMakeCode( cMakeCode.newLine(); } - if (targetConfig.threading) { + if (targetConfig.threading && targetConfig.platformOptions.platform != Platform.ZEPHYR) { // If threaded computation is requested, add the threads option. cMakeCode.pr("# Find threads and link to it"); cMakeCode.pr("find_package(Threads REQUIRED)"); cMakeCode.pr("target_link_libraries(${LF_MAIN_TARGET} PRIVATE Threads::Threads)"); cMakeCode.newLine(); - // If the LF program itself is threaded, we need to define NUMBER_OF_WORKERS so that - // platform-specific C files will contain the appropriate functions + } + + // Add additional flags so runtime can distinguish between multi-threaded and single-threaded + // mode + if (targetConfig.threading) { cMakeCode.pr("# Set the number of workers to enable threading/tracing"); cMakeCode.pr( "target_compile_definitions(${LF_MAIN_TARGET} PUBLIC NUMBER_OF_WORKERS=" + targetConfig.workers + ")"); cMakeCode.newLine(); - } - - // Add additional flags so runtime can distinguish between multi-threaded and single-threaded - // mode - if (targetConfig.threading) { cMakeCode.pr("# Set flag to indicate a multi-threaded runtime"); cMakeCode.pr("target_compile_definitions( ${LF_MAIN_TARGET} PUBLIC LF_THREADED=1)"); } else { From 75646f90fa1082e2e719208ea6833fddfa096106 Mon Sep 17 00:00:00 2001 From: Erling Rennemo Jellum Date: Fri, 15 Sep 2023 10:04:31 +0200 Subject: [PATCH 499/516] Spotless --- core/src/main/resources/lib/platform/zephyr/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/resources/lib/platform/zephyr/README.md b/core/src/main/resources/lib/platform/zephyr/README.md index a0d92819ec..f74132f2a9 100644 --- a/core/src/main/resources/lib/platform/zephyr/README.md +++ b/core/src/main/resources/lib/platform/zephyr/README.md @@ -1,3 +1,3 @@ # Zephyr platform files -These are files needed to compile LF programs for the Zephyr target. -All of the files in the `boards` directory are for enabling a Timer peripheral for different devices so that we can use a Counter device for time-keeping. These device tree config overlays are copied from the `alarm` example found in the zephyr source tree under `samples/drivers/counter/alarm`. \ No newline at end of file +These are files needed to compile LF programs for the Zephyr target. +All of the files in the `boards` directory are for enabling a Timer peripheral for different devices so that we can use a Counter device for time-keeping. These device tree config overlays are copied from the `alarm` example found in the zephyr source tree under `samples/drivers/counter/alarm`. From b0adb56350526c6210906befcb47c55bb2396d23 Mon Sep 17 00:00:00 2001 From: Erling Rennemo Jellum Date: Fri, 15 Sep 2023 10:04:50 +0200 Subject: [PATCH 500/516] Bump reactor-c to include cmake fix --- core/src/main/resources/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 9760f233d4..7e031ec140 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 9760f233d4b1d2653e61c7feb7e3e1379d6e8344 +Subproject commit 7e031ec1400fbebc1f90a7ce41fa10ffa40549d2 From 7b2c92b7a8d7c5e013d2652592ed2f62ae894764 Mon Sep 17 00:00:00 2001 From: erling Date: Fri, 15 Sep 2023 10:09:56 +0200 Subject: [PATCH 501/516] Update .gitmodules --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 1eaef285c7..9cd89ffe1a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "org.lflang/src/lib/c/reactor-c"] path = core/src/main/resources/lib/c/reactor-c - url = git@github.com:lf-lang/reactor-c.git + url = https://github.com/lf-lang/reactor-c.git [submodule "org.lflang/src/lib/cpp/reactor-cpp"] path = core/src/main/resources/lib/cpp/reactor-cpp url = https://github.com/lf-lang/reactor-cpp From 600c44db5ba7e4718d77a1ddff2c8306564fcd87 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 15 Sep 2023 11:19:00 +0200 Subject: [PATCH 502/516] avoid using a dead dependency (jsr305) --- core/build.gradle | 4 ++++ core/src/main/java/org/lflang/Target.java | 2 +- gradle.properties | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/build.gradle b/core/build.gradle index 23b412b6a8..142c347e03 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -44,6 +44,10 @@ dependencies { testImplementation "org.opentest4j:opentest4j:$openTest4jVersion" testImplementation "org.eclipse.xtext:org.eclipse.xtext.testing:$xtextVersion" testImplementation "org.eclipse.xtext:org.eclipse.xtext.xbase.testing:$xtextVersion" + + // For spotbugs annotations + compileOnly "com.github.spotbugs:spotbugs-annotations:$spotbugsToolVersion" + compileOnly "net.jcip:jcip-annotations:$jcipVersion" } configurations { diff --git a/core/src/main/java/org/lflang/Target.java b/core/src/main/java/org/lflang/Target.java index c7aadda74f..5e9f72fea1 100644 --- a/core/src/main/java/org/lflang/Target.java +++ b/core/src/main/java/org/lflang/Target.java @@ -24,7 +24,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; -import javax.annotation.concurrent.Immutable; +import net.jcip.annotations.Immutable; import org.lflang.lf.TargetDecl; /** diff --git a/gradle.properties b/gradle.properties index 636741363c..edfd349707 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,6 @@ jacocoVersion=0.8.7 jsonVersion=20200518 jupiterVersion=5.8.2 jUnitPlatformVersion=1.8.2 -klighdVersion=2.3.0.v20230606 kotlinJvmTarget=17 lsp4jVersion=0.21.0 mwe2LaunchVersion=2.14.0 @@ -22,6 +21,7 @@ klighdVersion=2.3.0.v20230606 freehepVersion=2.4 swtVersion=3.124.0 spotbugsToolVersion=4.7.3 +jcipVersion=1.0 [manifestPropertyNames] org.eclipse.xtext=xtextVersion From ab439109823b91a55b92a4808c3b9648aad7be82 Mon Sep 17 00:00:00 2001 From: erlingrj Date: Sat, 16 Sep 2023 11:04:36 +0200 Subject: [PATCH 503/516] Exclude Zephyr board tests from the other C regression tests --- core/src/testFixtures/java/org/lflang/tests/Configurators.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/testFixtures/java/org/lflang/tests/Configurators.java b/core/src/testFixtures/java/org/lflang/tests/Configurators.java index 4191687278..5929f2a5af 100644 --- a/core/src/testFixtures/java/org/lflang/tests/Configurators.java +++ b/core/src/testFixtures/java/org/lflang/tests/Configurators.java @@ -115,6 +115,7 @@ public static boolean compatibleWithThreadingOff(TestCategory category) { || category == TestCategory.ARDUINO || category == TestCategory.VERIFIER || category == TestCategory.ZEPHYR_UNTHREADED + || category == TestCategory.ZEPHYR_BOARDS || category == TestCategory.ZEPHYR_THREADED; // SERIALIZATION and TARGET tests are excluded on Windows. From 9c18925bc3b83e27956ddd958f295ba9ff6fdfe4 Mon Sep 17 00:00:00 2001 From: erlingrj Date: Sat, 16 Sep 2023 11:12:35 +0200 Subject: [PATCH 504/516] Bump Zephyr to 3.4.0 --- .github/actions/setup-zephyr/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-zephyr/action.yml b/.github/actions/setup-zephyr/action.yml index 32cd307172..f414609105 100644 --- a/.github/actions/setup-zephyr/action.yml +++ b/.github/actions/setup-zephyr/action.yml @@ -6,7 +6,7 @@ runs: - name: Setup environment variables run: | echo "SDK_VERSION=0.16.1" >> $GITHUB_ENV - echo "ZEPHYR_VERSION=3.3.0" >> $GITHUB_ENV + echo "ZEPHYR_VERSION=3.4.0" >> $GITHUB_ENV shell: bash - name: Dependencies run: | From 56375242aa93b9ff78df1b265ec1ac04c65d39e5 Mon Sep 17 00:00:00 2001 From: erlingrj Date: Sat, 16 Sep 2023 12:30:25 +0200 Subject: [PATCH 505/516] Exclude Zephyr board tests from CCpp tests --- .../integrationTest/java/org/lflang/tests/runtime/CCppTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/integrationTest/java/org/lflang/tests/runtime/CCppTest.java b/core/src/integrationTest/java/org/lflang/tests/runtime/CCppTest.java index 6324098963..3bbbda7dad 100644 --- a/core/src/integrationTest/java/org/lflang/tests/runtime/CCppTest.java +++ b/core/src/integrationTest/java/org/lflang/tests/runtime/CCppTest.java @@ -44,6 +44,7 @@ private static boolean isExcludedFromCCpp(TestCategory category) { isMac() && (category == TestCategory.DOCKER_FEDERATED || category == TestCategory.DOCKER); excluded |= category == TestCategory.ZEPHYR_UNTHREADED; excluded |= category == TestCategory.ZEPHYR_THREADED; + excluded |= category == TestCategory.ZEPHYR_BOARDS; excluded |= category == TestCategory.ARDUINO; excluded |= category == TestCategory.NO_INLINING; excluded |= category == TestCategory.VERIFIER; From 626d6188abd59fd757b8371a72754f8e24cd0130 Mon Sep 17 00:00:00 2001 From: erlingrj Date: Sat, 16 Sep 2023 12:42:34 +0200 Subject: [PATCH 506/516] Update Zephyr CI --- .github/workflows/c-zephyr-tests.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/c-zephyr-tests.yml b/.github/workflows/c-zephyr-tests.yml index 49c073d03b..eb170bafcf 100644 --- a/.github/workflows/c-zephyr-tests.yml +++ b/.github/workflows/c-zephyr-tests.yml @@ -41,7 +41,9 @@ jobs: if: ${{ inputs.runtime-ref }} - name: Run Zephyr smoke tests run: | - ./gradlew core:integrationTest --tests org.lflang.tests.runtime.CZephyrTest.buildZephyr* core:integrationTestCodeCoverageReport + ./gradlew core:integrationTest \ + --tests org.lflang.tests.runtime.CZephyrTest.buildZephyrUnthreaded* \ + --tests org.lflang.tests.runtime.CZephyrTest.buildZephyrThreaded* core:integrationTestCodeCoverageReport ./.github/scripts/run-zephyr-tests.sh test/C/src-gen rm -r test/C/src-gen - name: Run basic tests @@ -54,6 +56,10 @@ jobs: ./gradlew core:integrationTest --tests org.lflang.tests.runtime.CZephyrTest.buildConcurrent* core:integrationTestCodeCoverageReport ./.github/scripts/run-zephyr-tests.sh test/C/src-gen rm -r test/C/src-gen + - name: Run Zephyr board tests + run: | + ./gradlew core:integrationTest --tests org.lflang.tests.runtime.CZephyrTest.buildZephyBoard core:integrationTestCodeCoverageReport + rm -r test/C/src-gen - name: Report to CodeCov uses: ./.github/actions/report-code-coverage with: From eac5f219313cd511eb27179ac3cadcecd8e02bd6 Mon Sep 17 00:00:00 2001 From: erlingrj Date: Sat, 16 Sep 2023 12:46:16 +0200 Subject: [PATCH 507/516] Zephyr v3.4.0 --- .../src/main/java/org/lflang/generator/c/CCmakeGenerator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index ea1db3713e..717f0714f5 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -144,8 +144,8 @@ CodeBuilder generateCMakeCode( cMakeCode.pr("# Selecting default board"); cMakeCode.pr("set(BOARD qemu_cortex_m3)"); } - cMakeCode.pr("# We recommend Zephyr v3.3.0 but we are compatible with older versions also"); - cMakeCode.pr("find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE} 3.3.0)"); + cMakeCode.pr("# We recommend Zephyr v3.4.0 but we are compatible with older versions also"); + cMakeCode.pr("find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE} 3.4.0)"); cMakeCode.newLine(); cMakeCode.pr("project(" + executableName + " LANGUAGES C)"); cMakeCode.newLine(); From c627127eb60df43e60531f922afbf02567650c6d Mon Sep 17 00:00:00 2001 From: erlingrj Date: Sat, 16 Sep 2023 12:49:33 +0200 Subject: [PATCH 508/516] Update STM32 test --- test/C/src/zephyr/boards/STM32.lf | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/C/src/zephyr/boards/STM32.lf b/test/C/src/zephyr/boards/STM32.lf index c849ea0f30..409f169b85 100644 --- a/test/C/src/zephyr/boards/STM32.lf +++ b/test/C/src/zephyr/boards/STM32.lf @@ -1,6 +1,8 @@ target C { - platform: "Zephyr", - board: stm32f4_disco + platform: { + name: Zephyr, + board: stm32f4_disco + } } main reactor { From 9b02b6cba6530115e3ad67eccd288625b77a8ba9 Mon Sep 17 00:00:00 2001 From: erling Date: Sat, 16 Sep 2023 12:50:14 +0200 Subject: [PATCH 509/516] Update core/src/main/resources/lib/platform/zephyr/boards/nrf52dk_nrf52832.overlay Co-authored-by: Marten Lohstroh --- .../lib/platform/zephyr/boards/nrf52dk_nrf52832.overlay | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/platform/zephyr/boards/nrf52dk_nrf52832.overlay b/core/src/main/resources/lib/platform/zephyr/boards/nrf52dk_nrf52832.overlay index 30472cd1fb..0c88903dc5 100644 --- a/core/src/main/resources/lib/platform/zephyr/boards/nrf52dk_nrf52832.overlay +++ b/core/src/main/resources/lib/platform/zephyr/boards/nrf52dk_nrf52832.overlay @@ -1,3 +1,3 @@ &timer1 { status = "okay"; -}; \ No newline at end of file +}; From 137a1499221439649875e17bad39d0a133b4f556 Mon Sep 17 00:00:00 2001 From: Erling Rennemo Jellum Date: Sat, 16 Sep 2023 14:36:54 +0200 Subject: [PATCH 510/516] Typo in CI --- .github/workflows/c-zephyr-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/c-zephyr-tests.yml b/.github/workflows/c-zephyr-tests.yml index eb170bafcf..200b35e30f 100644 --- a/.github/workflows/c-zephyr-tests.yml +++ b/.github/workflows/c-zephyr-tests.yml @@ -58,7 +58,7 @@ jobs: rm -r test/C/src-gen - name: Run Zephyr board tests run: | - ./gradlew core:integrationTest --tests org.lflang.tests.runtime.CZephyrTest.buildZephyBoard core:integrationTestCodeCoverageReport + ./gradlew core:integrationTest --tests org.lflang.tests.runtime.CZephyrTest.buildZephyBoards* core:integrationTestCodeCoverageReport rm -r test/C/src-gen - name: Report to CodeCov uses: ./.github/actions/report-code-coverage From 7d3905fe97c397991a70a98bb2ab5e77b388fe49 Mon Sep 17 00:00:00 2001 From: Erling Rennemo Jellum Date: Sat, 16 Sep 2023 15:31:09 +0200 Subject: [PATCH 511/516] Another typo... --- .github/workflows/c-zephyr-tests.yml | 2 +- .../java/org/lflang/tests/runtime/CZephyrTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/c-zephyr-tests.yml b/.github/workflows/c-zephyr-tests.yml index 200b35e30f..16001457fb 100644 --- a/.github/workflows/c-zephyr-tests.yml +++ b/.github/workflows/c-zephyr-tests.yml @@ -58,7 +58,7 @@ jobs: rm -r test/C/src-gen - name: Run Zephyr board tests run: | - ./gradlew core:integrationTest --tests org.lflang.tests.runtime.CZephyrTest.buildZephyBoards* core:integrationTestCodeCoverageReport + ./gradlew core:integrationTest --tests org.lflang.tests.runtime.CZephyrTest.buildZephyrBoards* core:integrationTestCodeCoverageReport rm -r test/C/src-gen - name: Report to CodeCov uses: ./.github/actions/report-code-coverage diff --git a/core/src/integrationTest/java/org/lflang/tests/runtime/CZephyrTest.java b/core/src/integrationTest/java/org/lflang/tests/runtime/CZephyrTest.java index 6ab2490abe..08b18b2a72 100644 --- a/core/src/integrationTest/java/org/lflang/tests/runtime/CZephyrTest.java +++ b/core/src/integrationTest/java/org/lflang/tests/runtime/CZephyrTest.java @@ -56,7 +56,7 @@ public void buildZephyrUnthreadedTests() { } @Test - public void buildZephyrBoardTests() { + public void buildZephyrBoardsTests() { Assumptions.assumeTrue(isLinux(), "Zephyr tests only run on Linux"); super.runTestsFor( List.of(Target.C), From 486377c395426dd9eac6496c227ed5335afe75dc Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 18 Sep 2023 14:41:32 +0200 Subject: [PATCH 512/516] Added test for unconnected outputs --- core/src/main/resources/lib/c/reactor-c | 2 +- test/C/src/UnconnectedOutput.lf | 34 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 test/C/src/UnconnectedOutput.lf diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 7e031ec140..f0bd1bc653 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 7e031ec1400fbebc1f90a7ce41fa10ffa40549d2 +Subproject commit f0bd1bc6533e4b47f2af9d4e9d8613fa5e670be0 diff --git a/test/C/src/UnconnectedOutput.lf b/test/C/src/UnconnectedOutput.lf new file mode 100644 index 0000000000..96a8fb3e7a --- /dev/null +++ b/test/C/src/UnconnectedOutput.lf @@ -0,0 +1,34 @@ +// Success here is not segfaulting. +target C { + timeout: 10 ms +} +reactor B(bank_index: int = 0) { + input in:int + output out_problem:int + reaction(in) -> out_problem {= + lf_set(out_problem, self->bank_index); + =} +} +reactor A { + input in:int + output out1:int + output out2:int + output out3:int + + b = new[3] B() + (in)+ -> b.in + b.out_problem -> out1, out2, out3 +} +main reactor { + m = new A() + timer t(0, 10 ms) + reaction(t) -> m.in {= + lf_set(m.in, 42); + =} + reaction(m.out3) {= + lf_print("out3 = %d", m.out3->value); + if (m.out3->value != 2) { + lf_print_error_and_exit("Expected 2."); + } + =} +} From f40157950be906b99771ca77251daaa727fe1e5c Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 18 Sep 2023 15:01:05 +0200 Subject: [PATCH 513/516] Formatted --- test/C/src/UnconnectedOutput.lf | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/test/C/src/UnconnectedOutput.lf b/test/C/src/UnconnectedOutput.lf index 96a8fb3e7a..571e6f1a04 100644 --- a/test/C/src/UnconnectedOutput.lf +++ b/test/C/src/UnconnectedOutput.lf @@ -1,30 +1,36 @@ // Success here is not segfaulting. target C { - timeout: 10 ms + timeout: 10 ms } + reactor B(bank_index: int = 0) { - input in:int - output out_problem:int + input in: int + output out_problem: int + reaction(in) -> out_problem {= lf_set(out_problem, self->bank_index); =} } + reactor A { - input in:int - output out1:int - output out2:int - output out3:int - + input in: int + output out1: int + output out2: int + output out3: int + b = new[3] B() (in)+ -> b.in b.out_problem -> out1, out2, out3 } + main reactor { m = new A() timer t(0, 10 ms) + reaction(t) -> m.in {= lf_set(m.in, 42); =} + reaction(m.out3) {= lf_print("out3 = %d", m.out3->value); if (m.out3->value != 2) { From 666a3ec347617bf5716651eceea7fce89a8e2fd4 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Mon, 18 Sep 2023 20:47:59 +0200 Subject: [PATCH 514/516] pull in https://github.com/lf-lang/reactor-cpp/pull/53 --- core/src/main/resources/lib/cpp/reactor-cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/cpp/reactor-cpp b/core/src/main/resources/lib/cpp/reactor-cpp index 521f1d852b..997421c0d3 160000 --- a/core/src/main/resources/lib/cpp/reactor-cpp +++ b/core/src/main/resources/lib/cpp/reactor-cpp @@ -1 +1 @@ -Subproject commit 521f1d852b6b3dc5850d3dcfc669ea812296db94 +Subproject commit 997421c0d35ef609b7ae202b4cbf2db38b884d12 From 57f92aa125140ebf5451499bc8fb7a35f002b10a Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Mon, 18 Sep 2023 20:48:22 +0200 Subject: [PATCH 515/516] improve shutdown tests --- test/Cpp/src/Starve.lf | 24 ++++++++++++++++++++++++ test/Cpp/src/TimeLimit.lf | 29 ++++++++++++++++++++++++++--- test/Cpp/src/Timeout_Test.lf | 16 ++++++++++++---- 3 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 test/Cpp/src/Starve.lf diff --git a/test/Cpp/src/Starve.lf b/test/Cpp/src/Starve.lf new file mode 100644 index 0000000000..ba9c45d998 --- /dev/null +++ b/test/Cpp/src/Starve.lf @@ -0,0 +1,24 @@ +target Cpp + +main reactor { + timer t(1 s) + logical action after_shutdown: void + + reaction(t) -> after_shutdown {= + reactor::log::Info() << "Timer triggered at " << get_tag(); + =} + + reaction(shutdown) -> after_shutdown {= + reactor::log::Info() << "Shutdown triggered at " << get_tag(); + if(get_elapsed_logical_time() != 1s || get_microstep() != 1) { + reactor::log::Error() << "Shutdown invoked at wrong tag"; + exit(2); + } + after_shutdown.schedule(); + =} + + reaction(after_shutdown) {= + reactor::log::Error() << "Executed a reaction after shutdown"; + exit(1); + =} +} diff --git a/test/Cpp/src/TimeLimit.lf b/test/Cpp/src/TimeLimit.lf index 1d11882740..28cb3894ef 100644 --- a/test/Cpp/src/TimeLimit.lf +++ b/test/Cpp/src/TimeLimit.lf @@ -19,23 +19,39 @@ reactor Clock(offset: time = 0, period: time = 1 sec) { reactor Destination { input x: int state s: int = 1 + state shutdown_invoked: bool = false + input check_shutdown: void reaction(x) {= //std::cout << "Received " << *x.get() << '\n'; if (*x.get() != s) { - std::cerr << "Error: Expected " << s << " and got " << *x.get() << '\n'; + reactor::log::Error() << "Expected " << s << " and got " << *x.get(); exit(1); } + if (*x.get() == 12 && !shutdown_invoked) { + reactor::log::Error() << "Shutdown wasn't invoked!"; + } s++; =} reaction(shutdown) {= std::cout << "**** shutdown reaction invoked.\n"; + shutdown_invoked = true; + if(get_microstep() != 1) { + reactor::log::Error() << "Expected microstep == 1, but got " << get_microstep(); + exit(2); + } if (s != 12) { - std::cerr << "ERROR: Expected 12 but got " << s << '\n'; + reactor::log::Error() << "Expected 12 but got " << s; exit(1); } =} + + reaction(check_shutdown) {= + if (!shutdown_invoked) { + reactor::log::Error() << "Shutdown wasn't invoked!"; + } + =} } main reactor TimeLimit(period: time = 1 sec) { @@ -44,7 +60,14 @@ main reactor TimeLimit(period: time = 1 sec) { d = new Destination() c.y -> d.x - reaction(stop) {= + logical action check: void + + reaction(stop) -> check {= environment()->sync_shutdown(); + check.schedule(); + =} + + reaction(check) -> d.check_shutdown {= + d.check_shutdown.set(); =} } diff --git a/test/Cpp/src/Timeout_Test.lf b/test/Cpp/src/Timeout_Test.lf index aac8a04934..6c0224941c 100644 --- a/test/Cpp/src/Timeout_Test.lf +++ b/test/Cpp/src/Timeout_Test.lf @@ -15,6 +15,8 @@ reactor Consumer { input in: int state success: bool = false + logical action after_shutdown: void + reaction(in) {= auto current{get_elapsed_logical_time()}; if(current > 11ms ){ @@ -25,15 +27,21 @@ reactor Consumer { } =} - reaction(shutdown) {= - reactor::log::Info() << "Shutdown invoked at " << get_elapsed_logical_time(); - if((get_elapsed_logical_time() == 11ms ) && (success == true)){ + reaction(shutdown) -> after_shutdown {= + reactor::log::Info() << "Shutdown invoked at tag " << get_tag(); + if((get_elapsed_logical_time() == 11ms ) && get_microstep() == 0 && (success == true)){ reactor::log::Info() << "SUCCESS: successfully enforced timeout."; + after_shutdown.schedule(); } else { - reactor::log::Error() << "Shutdown invoked at: " << get_elapsed_logical_time() << ". Failed to enforce timeout."; + reactor::log::Error() << "Failed to enforce timeout at the correct tag."; exit(1); } =} + + reaction(after_shutdown) {= + reactor::log::Error() << "Executed a reaction after timeout."; + exit(2); + =} } main reactor Timeout_Test { From f7386ed8148102d8b85a4ae5c2d5d73b96d00aa2 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Wed, 20 Sep 2023 16:41:54 +0200 Subject: [PATCH 516/516] update reactor-cpp, improve and add tests --- core/src/main/resources/lib/cpp/reactor-cpp | 2 +- test/Cpp/src/ShutdownAsync.lf | 28 +++++++++ test/Cpp/src/ShutdownSync.lf | 25 ++++++++ test/Cpp/src/Starve.lf | 2 +- test/Cpp/src/StarveZero.lf | 19 ++++++ test/Cpp/src/Timeout_Test.lf | 52 ---------------- test/Cpp/src/properties/Timeout.lf | 66 ++++++++++++++++----- test/Cpp/src/properties/TimeoutZero.lf | 28 +++------ 8 files changed, 132 insertions(+), 90 deletions(-) create mode 100644 test/Cpp/src/ShutdownAsync.lf create mode 100644 test/Cpp/src/ShutdownSync.lf create mode 100644 test/Cpp/src/StarveZero.lf delete mode 100644 test/Cpp/src/Timeout_Test.lf diff --git a/core/src/main/resources/lib/cpp/reactor-cpp b/core/src/main/resources/lib/cpp/reactor-cpp index 997421c0d3..fa685f1db9 160000 --- a/core/src/main/resources/lib/cpp/reactor-cpp +++ b/core/src/main/resources/lib/cpp/reactor-cpp @@ -1 +1 @@ -Subproject commit 997421c0d35ef609b7ae202b4cbf2db38b884d12 +Subproject commit fa685f1db99b1652e39a5cf3c6356a8db26b52bb diff --git a/test/Cpp/src/ShutdownAsync.lf b/test/Cpp/src/ShutdownAsync.lf new file mode 100644 index 0000000000..c1ec5ed8e5 --- /dev/null +++ b/test/Cpp/src/ShutdownAsync.lf @@ -0,0 +1,28 @@ +target Cpp + +main reactor { + logical action check_shutdown + timer request_shutdown(1 ms) + timer after_shutdown(2 ms) + + reaction(request_shutdown) -> check_shutdown {= + // async_shutdown can be called from external threads to shutdown + // at the next possible tag. If it is called from a reaction, the + // next possible tag should always be the next microstep. + environment()->async_shutdown(); + check_shutdown.schedule(); + =} + + reaction(shutdown, check_shutdown) {= + if (!(shutdown.is_present() && check_shutdown.is_present())) { + reactor::log::Error() << "shutdown was not triggered at the expcetd tag"; + exit(1); + } + reactor::log::Info() << "Success!"; + =} + + reaction(after_shutdown) {= + reactor::log::Error() << "triggered a reaction after shutdown"; + exit(2); + =} +} diff --git a/test/Cpp/src/ShutdownSync.lf b/test/Cpp/src/ShutdownSync.lf new file mode 100644 index 0000000000..a07e4f2a59 --- /dev/null +++ b/test/Cpp/src/ShutdownSync.lf @@ -0,0 +1,25 @@ +target Cpp + +main reactor { + logical action check_shutdown + timer request_shutdown(1 ms) + timer after_shutdown(2 ms) + + reaction(request_shutdown) -> check_shutdown {= + environment()->sync_shutdown(); + check_shutdown.schedule(); + =} + + reaction(shutdown, check_shutdown) {= + if (!(shutdown.is_present() && check_shutdown.is_present())) { + reactor::log::Error() << "shutdown was not triggered at the expcetd tag"; + exit(1); + } + reactor::log::Info() << "Success!"; + =} + + reaction(after_shutdown) {= + reactor::log::Error() << "triggered a reaction after shutdown"; + exit(2); + =} +} diff --git a/test/Cpp/src/Starve.lf b/test/Cpp/src/Starve.lf index ba9c45d998..73f4e4ecfc 100644 --- a/test/Cpp/src/Starve.lf +++ b/test/Cpp/src/Starve.lf @@ -19,6 +19,6 @@ main reactor { reaction(after_shutdown) {= reactor::log::Error() << "Executed a reaction after shutdown"; - exit(1); + exit(1); =} } diff --git a/test/Cpp/src/StarveZero.lf b/test/Cpp/src/StarveZero.lf new file mode 100644 index 0000000000..e7463f6424 --- /dev/null +++ b/test/Cpp/src/StarveZero.lf @@ -0,0 +1,19 @@ +target Cpp + +main reactor { + logical action after_shutdown: void + + reaction(shutdown) -> after_shutdown {= + reactor::log::Info() << "Shutdown triggered at " << get_tag(); + if(get_elapsed_logical_time() != 0s || get_microstep() != 1) { + reactor::log::Error() << "Shutdown invoked at wrong tag"; + exit(2); + } + after_shutdown.schedule(); + =} + + reaction(after_shutdown) {= + reactor::log::Error() << "Executed a reaction after shutdown"; + exit(1); + =} +} diff --git a/test/Cpp/src/Timeout_Test.lf b/test/Cpp/src/Timeout_Test.lf deleted file mode 100644 index 6c0224941c..0000000000 --- a/test/Cpp/src/Timeout_Test.lf +++ /dev/null @@ -1,52 +0,0 @@ -/** - * A test for the timeout functionality in Lingua Franca. - * - * @author Maiko Brants TU Dresden - * - * Modeled after the C version of this test. - */ -target Cpp { - timeout: 11 msec -} - -import Sender from "lib/LoopedActionSender.lf" - -reactor Consumer { - input in: int - state success: bool = false - - logical action after_shutdown: void - - reaction(in) {= - auto current{get_elapsed_logical_time()}; - if(current > 11ms ){ - reactor::log::Error() << "Received at: " << current.count() << ". Failed to enforce timeout."; - exit(1); - } else if(current == 11ms) { - success=true; - } - =} - - reaction(shutdown) -> after_shutdown {= - reactor::log::Info() << "Shutdown invoked at tag " << get_tag(); - if((get_elapsed_logical_time() == 11ms ) && get_microstep() == 0 && (success == true)){ - reactor::log::Info() << "SUCCESS: successfully enforced timeout."; - after_shutdown.schedule(); - } else { - reactor::log::Error() << "Failed to enforce timeout at the correct tag."; - exit(1); - } - =} - - reaction(after_shutdown) {= - reactor::log::Error() << "Executed a reaction after timeout."; - exit(2); - =} -} - -main reactor Timeout_Test { - consumer = new Consumer() - producer = new Sender(take_a_break_after=10, break_interval = 1 msec) - - producer.out -> consumer.in -} diff --git a/test/Cpp/src/properties/Timeout.lf b/test/Cpp/src/properties/Timeout.lf index a278d6d712..95d60963f5 100644 --- a/test/Cpp/src/properties/Timeout.lf +++ b/test/Cpp/src/properties/Timeout.lf @@ -1,31 +1,65 @@ +/** + * A test for the timeout functionality in Lingua Franca. + * + * @author Maiko Brants TU Dresden + * + * Modeled after the C version of this test. + */ target Cpp { - timeout: 1 sec + timeout: 11 msec } -main reactor { - timer t(1 sec, 1 sec) +import Sender from "../lib/LoopedActionSender.lf" + +reactor Consumer { + input in: int + state success: bool = false - state triggered: bool = false + logical action after_shutdown: void - reaction(t) {= - triggered = true; - if (get_elapsed_logical_time() != 1s) { - std::cout << "ERROR: triggered reaction at an unexpected tag"; + timer check_shutdown(11 ms) + + reaction(in) {= + auto current{get_elapsed_logical_time()}; + if(current > 11ms ){ + reactor::log::Error() << "Received at: " << current.count() << ". Failed to enforce timeout."; exit(1); + } else if(current == 11ms) { + success=true; } =} - reaction(shutdown) {= - if (get_elapsed_logical_time() != 1s || get_microstep() != 0) { - std::cout << "ERROR: shutdown invoked at an unexpected tag"; - exit(2); + reaction(shutdown) -> after_shutdown {= + reactor::log::Info() << "Shutdown invoked at tag " << get_tag(); + if((get_elapsed_logical_time() == 11ms ) && get_microstep() == 0 && (success == true)){ + reactor::log::Info() << "SUCCESS: successfully enforced timeout."; + after_shutdown.schedule(); + } else { + reactor::log::Error() << "Failed to enforce timeout at the correct tag."; + exit(1); } + =} - if (triggered) { - std::cout << "SUCCESS!\n"; - } else { - std::cout << "ERROR: reaction was not invoked!\n"; + reaction(check_shutdown, shutdown) {= + if (check_shutdown.is_present() && !shutdown.is_present()) { + reactor::log::Error() << "Shutdown was not triggered at the expected tag"; exit(2); } + if (!check_shutdown.is_present() && shutdown.is_present()) { + reactor::log::Error() << "Shutdown was triggered at an unxpected tag: " << get_tag(); + exit(2); + } + =} + + reaction(after_shutdown) {= + reactor::log::Error() << "Executed a reaction after timeout."; + exit(2); =} } + +main reactor { + consumer = new Consumer() + producer = new Sender(take_a_break_after=10, break_interval = 1 msec) + + producer.out -> consumer.in +} diff --git a/test/Cpp/src/properties/TimeoutZero.lf b/test/Cpp/src/properties/TimeoutZero.lf index 3aa45c7203..7f32565788 100644 --- a/test/Cpp/src/properties/TimeoutZero.lf +++ b/test/Cpp/src/properties/TimeoutZero.lf @@ -1,31 +1,19 @@ target Cpp { - timeout: 0 sec + timeout: 0 s } main reactor { - timer t(0, 1 sec) + timer should_never_trigger(1 s) - state triggered: bool = false - - reaction(t) {= - triggered = true; - if (get_elapsed_logical_time() != 0s) { - std::cout << "ERROR: triggered reaction at an unexpected tag"; + reaction(startup, shutdown) {= + if (!(startup.is_present() && shutdown.is_present())) { + reactor::log::Error() << "Shutdown was not triggered at startup"; exit(1); } =} - reaction(shutdown) {= - if (get_elapsed_logical_time() != 0s || get_microstep() != 0) { - std::cout << "ERROR: shutdown invoked at an unexpected tag"; - exit(2); - } - - if (triggered) { - std::cout << "SUCCESS!\n"; - } else { - std::cout << "ERROR: reaction was not invoked!\n"; - exit(2); - } + reaction(should_never_trigger) {= + reactor::log::Error() << "Executed a reaction after timeout."; + exit(2); =} }