From 239e87533c9a9c33988aeab7d480b34a58495d00 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Tue, 4 Jan 2022 18:15:46 -0800 Subject: [PATCH 01/64] add federated send class test --- .../src/federated/DistributedSendClass.lf | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 test/Python/src/federated/DistributedSendClass.lf diff --git a/test/Python/src/federated/DistributedSendClass.lf b/test/Python/src/federated/DistributedSendClass.lf new file mode 100644 index 0000000000..05e6bbfed3 --- /dev/null +++ b/test/Python/src/federated/DistributedSendClass.lf @@ -0,0 +1,27 @@ +target Python; + +preamble {= + class C: + def __init__(self): + pass +=} + +reactor A { + input o + reaction(o) {= + request_stop() + =} +} + +reactor B { + output o + reaction(startup) -> o {= + o.set(C()) + =} +} + +federated reactor { + a = new A(); + b = new B(); + b.o -> a.o; +} \ No newline at end of file From a29401675bbdaa9b3334591c0232da35d3e9f8eb Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Tue, 4 Jan 2022 18:31:34 -0800 Subject: [PATCH 02/64] add more send class tests --- .../src/federated/DistributedStructAsType.lf | 13 ++++++++++++ .../DistributedStructAsTypeDirect.lf | 13 ++++++++++++ .../federated/DistributedStructParallel.lf | 21 +++++++++++++++++++ .../src/federated/DistributedStructPrint.lf | 14 +++++++++++++ .../src/federated/DistributedStructScale.lf | 16 ++++++++++++++ 5 files changed, 77 insertions(+) create mode 100644 test/Python/src/federated/DistributedStructAsType.lf create mode 100644 test/Python/src/federated/DistributedStructAsTypeDirect.lf create mode 100644 test/Python/src/federated/DistributedStructParallel.lf create mode 100644 test/Python/src/federated/DistributedStructPrint.lf create mode 100644 test/Python/src/federated/DistributedStructScale.lf diff --git a/test/Python/src/federated/DistributedStructAsType.lf b/test/Python/src/federated/DistributedStructAsType.lf new file mode 100644 index 0000000000..6ae5aa2a8c --- /dev/null +++ b/test/Python/src/federated/DistributedStructAsType.lf @@ -0,0 +1,13 @@ +target Python {files: ../include/hello.py}; + +import Source, Print from "../StructAsType.lf" + +preamble {= + import hello +=} + +federated reactor { + s = new Source(); + p = new Print(); + s.out -> p._in; +} \ No newline at end of file diff --git a/test/Python/src/federated/DistributedStructAsTypeDirect.lf b/test/Python/src/federated/DistributedStructAsTypeDirect.lf new file mode 100644 index 0000000000..4f113427c5 --- /dev/null +++ b/test/Python/src/federated/DistributedStructAsTypeDirect.lf @@ -0,0 +1,13 @@ +target Python {files: ../include/hello.py}; + +import Source, Print from "../StructAsTypeDirect.lf" + +preamble {= + import hello +=} + +federated reactor { + s = new Source(); + p = new Print(); + s.out -> p._in; +} \ No newline at end of file diff --git a/test/Python/src/federated/DistributedStructParallel.lf b/test/Python/src/federated/DistributedStructParallel.lf new file mode 100644 index 0000000000..36de3d0867 --- /dev/null +++ b/test/Python/src/federated/DistributedStructParallel.lf @@ -0,0 +1,21 @@ +// Source allocates a class object and then sends it to two reactors, +// each of which want to modify it. +target Python {files: ["../include/hello.py"]}; +import Source from "../StructScale.lf"; +import Check, Print from "../StructParallel.lf" + +preamble {= +import hello +=} + +federated reactor { + s = new Source(); + c1 = new Print(); + c2 = new Print(scale = 3); + p1 = new Check(expected = 84); + p2 = new Check(expected = 126); + s.out -> c1._in; + s.out -> c2._in; + c1.out -> p1._in; + c2.out -> p2._in; +} \ No newline at end of file diff --git a/test/Python/src/federated/DistributedStructPrint.lf b/test/Python/src/federated/DistributedStructPrint.lf new file mode 100644 index 0000000000..5dc010e65c --- /dev/null +++ b/test/Python/src/federated/DistributedStructPrint.lf @@ -0,0 +1,14 @@ +// Source produces a dynamically allocated class object, which it passes +// to Print. Reference counting ensures that the struct is freed. +target Python {files: ["../include/hello.py"]}; +import Print, Check from "../StructPrint.lf" + +preamble {= +import hello +=} + +federated reactor { + s = new Print(); + p = new Check(); + s.out -> p._in; +} \ No newline at end of file diff --git a/test/Python/src/federated/DistributedStructScale.lf b/test/Python/src/federated/DistributedStructScale.lf new file mode 100644 index 0000000000..c2685ab003 --- /dev/null +++ b/test/Python/src/federated/DistributedStructScale.lf @@ -0,0 +1,16 @@ +// Source produces a dynamically allocated class object, which it passes +// to Scale. Scale modifies it and passes it to Print. +target Python {files: ["../include/hello.py"]}; +import Source, TestInput, Print from "../StructScale.lf" + +preamble {= +import hello +=} + +federated reactor { + s = new Source(); + c = new Print(); + p = new TestInput(expected=84); + s.out -> c._in; + c.out -> p._in; +} \ No newline at end of file From 24e8a2b991d6e8a7e3f68cf78a564136f2e7e62d Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Fri, 21 Jan 2022 16:32:08 -0800 Subject: [PATCH 03/64] add no react test --- .../src/federated/DistributedNoReact.lf | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 test/Python/src/federated/DistributedNoReact.lf diff --git a/test/Python/src/federated/DistributedNoReact.lf b/test/Python/src/federated/DistributedNoReact.lf new file mode 100644 index 0000000000..100cb8d016 --- /dev/null +++ b/test/Python/src/federated/DistributedNoReact.lf @@ -0,0 +1,24 @@ +target Python; + +preamble {= + class C: + def __init__(self): + pass +=} + +reactor A { + input o +} + +reactor B { + output o + reaction(startup) -> o {= + o.set(C()) + =} +} + +federated reactor { + a = new A(); + b = new B(); + b.o -> a.o; +} \ No newline at end of file From c63aceb24cb3db70d3ef100388c97eaf202d0852 Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Fri, 21 Jan 2022 19:03:34 -0600 Subject: [PATCH 04/64] Updated the Python generator to use the new bank index scheme --- .../generator/python/PythonGenerator.xtend | 199 +++++++++++------- 1 file changed, 125 insertions(+), 74 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 81229c1c42..3ae93c10e8 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -79,6 +79,8 @@ import org.lflang.lf.VarRef import static extension org.lflang.ASTUtils.* import static extension org.lflang.JavaAstUtils.* +import org.lflang.lf.Assignment +import java.util.LinkedList /** * Generator for Python target. This class generates Python code defining each reactor @@ -309,11 +311,40 @@ class PythonGenerator extends CGenerator { * @return Initialization code */ protected def String getPythonInitializer(ParameterInstance p) { - if (p.getInitialValue.size > 1) { - // parameters are initialized as immutable tuples - return p.getInitialValue.join('(', ', ', ')', [it.pythonTargetValue]) + // Handle overrides in the intantiation. + // In case there is more than one assignment to this parameter, we need to + // find the last one. + var lastAssignment = null as Assignment; + for (assignment: p.parent.definition.parameters) { + if (assignment.lhs == p.definition) { + lastAssignment = assignment; + } + } + + var list = new LinkedList(); + if (lastAssignment !== null) { + // The parameter has an assignment. + // Right hand side can be a list. Collect the entries. + for (value: lastAssignment.rhs) { + if (value.parameter !== null) { + // The parameter is being assigned a parameter value. + // Assume that parameter belongs to the parent's parent. + // This should have been checked by the validator. + list.add(PyUtil.reactorRef(p.parent.parent) + "." + value.parameter.name); + } else { + list.add(value.targetTime) + } + } } else { - return p.getInitialValue.get(0).getPythonTargetValue + for (i : p.parent.initialParameterValue(p.definition)) { + list.add(i.getPythonTargetValue) + } + } + + if (list.size == 1) { + return list.get(0) + } else { + return list.join('(', ', ', ')', [it]) } } @@ -502,60 +533,56 @@ class PythonGenerator extends CGenerator { return } - // Do not generate classes that don't have any reactions - // Do not generate the main federated class, which is always implemented in C - if (!instance.definition.reactorClass.toDefinition.allReactions.isEmpty && !decl.toDefinition.isFederated) { - if (federate.contains(instance) && !instantiatedClasses.contains(className)) { + if (federate.contains(instance) && !instantiatedClasses.contains(className)) { - pythonClasses.append(''' - - # Python class for reactor «className» - class _«className»: - '''); + pythonClasses.append(''' + + # Python class for reactor «className» + class _«className»: + '''); - // Generate preamble code - pythonClasses.append(''' - - «generatePythonPreamblesForReactor(decl.toDefinition)» - ''') + // Generate preamble code + pythonClasses.append(''' + + «generatePythonPreamblesForReactor(decl.toDefinition)» + ''') - val reactor = decl.toDefinition + val reactor = decl.toDefinition - // Handle runtime initializations - pythonClasses.append(''' - «' '»def __init__(self, **kwargs): + // Handle runtime initializations + pythonClasses.append(''' + «' '»def __init__(self, **kwargs): + ''') + + + pythonClasses.append(generateParametersAndStateVariables(decl)) + + + var reactionIndex = 0 + for (reaction : reactor.allReactions) { + val reactionParameters = new StringBuilder() // Will contain parameters for the function (e.g., Foo(x,y,z,...) + val inits = new StringBuilder() // Will contain initialization code for some parameters + generatePythonReactionParametersAndInitializations(reactionParameters, inits, reactor, reaction) + pythonClasses.append(''' def «pythonReactionFunctionName(reactionIndex)»(self «reactionParameters»): ''') + pythonClasses.append(''' «inits» + ''') + pythonClasses.append(''' «reaction.code.toText» + ''') + pythonClasses.append(''' return 0 - - pythonClasses.append(generateParametersAndStateVariables(decl)) - - - var reactionIndex = 0 - for (reaction : reactor.allReactions) { - val reactionParameters = new StringBuilder() // Will contain parameters for the function (e.g., Foo(x,y,z,...) - val inits = new StringBuilder() // Will contain initialization code for some parameters - generatePythonReactionParametersAndInitializations(reactionParameters, inits, reactor, reaction) - pythonClasses.append(''' def «pythonReactionFunctionName(reactionIndex)»(self «reactionParameters»): - ''') - pythonClasses.append(''' «inits» - ''') - pythonClasses.append(''' «reaction.code.toText» - ''') - pythonClasses.append(''' return 0 - - ''') - - // Now generate code for the deadline violation function, if there is one. - if (reaction.deadline !== null) { - pythonClasses. - append(''' «generateDeadlineFunctionForReaction(reaction, reactionIndex, reactionParameters.toString)» - ''') - } + ''') - reactionIndex = reactionIndex + 1; + // Now generate code for the deadline violation function, if there is one. + if (reaction.deadline !== null) { + pythonClasses. + append(''' «generateDeadlineFunctionForReaction(reaction, reactionIndex, reactionParameters.toString)» + ''') } - instantiatedClasses.add(className) + + reactionIndex = reactionIndex + 1; } + instantiatedClasses.add(className) } for (child : instance.children) { @@ -700,36 +727,57 @@ class PythonGenerator extends CGenerator { return } - // Do not instantiate reactor classes that don't have a reaction in Python - // Do not instantiate the federated main reactor since it is generated in C - if (!instance.definition.reactorClass.toDefinition.allReactions.isEmpty && !instance.definition.reactorClass.toDefinition.isFederated) { - if (federate.contains(instance) && instance.width > 0 && !instance.definition.reactorClass.toDefinition.allReactions.isEmpty) { - // 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. - pythonClassesInstantiation. - append(''' - «instance.uniqueID»_lf = [ - ''') - for (var i = 0; i < instance.totalWidth; i++) { - pythonClassesInstantiation. - append(''' - _«className»( - _bank_index = «i%instance.width», - «FOR param : instance.parameters» - «IF !param.name.equals("bank_index")» - _«param.name»=«param.pythonInitializer»,«ENDIF»«ENDFOR»), - ''') - } - pythonClassesInstantiation. - append(''' - ] - ''') - } + if (federate.contains(instance) && instance.width > 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. var reactorClass = instance.definition.reactorClass + var fullName = instance.fullName + pr(pythonClassesInstantiation, ''' + + # Start initializing «fullName» of class «className» + for «PyUtil.bankIndexName(instance)» in range(«instance.width»): + ''') + indent(pythonClassesInstantiation); + pr(pythonClassesInstantiation, ''' + «PyUtil.reactorRef(instance)» = \ + _«className»( + _bank_index = «PyUtil.bankIndex(instance)», + «FOR param : instance.parameters» + «IF !param.name.equals("bank_index")» + _«param.name»=«param.pythonInitializer», + «ENDIF»«ENDFOR» + ) + ''') } for (child : instance.children) { generatePythonClassInstantiation(child, pythonClassesInstantiation, federate) } + unindent(pythonClassesInstantiation); + } + + + /** + * 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. + * @param pythonClassesInstantiation StringBuilder to hold the generated code. + * @param federate Will check if instance (or any of its children) belong to + * federate before generating code for them. + */ + def void generateListsToHoldClassInstances( + ReactorInstance instance, + StringBuilder pythonClassesInstantiation, + FederateInstance federate + ) { + if(federate !== null && !federate.contains(instance)) return; + pr(pythonClassesInstantiation, ''' + «PyUtil.reactorRefName(instance)» = [None] * «instance.totalWidth» + ''') + for (child : instance.children) { + generateListsToHoldClassInstances(child, pythonClassesInstantiation, federate); + } } /** @@ -744,6 +792,9 @@ class PythonGenerator extends CGenerator { // Generate reactor classes in Python this.main.generatePythonReactorClass(pythonClasses, federate) + // Create empty lists to hold reactor instances + this.main.generateListsToHoldClassInstances(pythonClassesInstantiation, federate) + // Instantiate generated classes this.main.generatePythonClassInstantiation(pythonClassesInstantiation, federate) From 90ca83dee68215a8e4ab5ad49b13d369c0e2353c Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Fri, 21 Jan 2022 19:04:01 -0600 Subject: [PATCH 05/64] Added the nested banks test --- test/Python/src/multiport/NestedBanks.lf | 67 ++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 test/Python/src/multiport/NestedBanks.lf diff --git a/test/Python/src/multiport/NestedBanks.lf b/test/Python/src/multiport/NestedBanks.lf new file mode 100644 index 0000000000..8d5f118ccb --- /dev/null +++ b/test/Python/src/multiport/NestedBanks.lf @@ -0,0 +1,67 @@ +/** + * Test of nested banks with multiports. + * @author Edward A. Lee + */ + target Python; + main reactor { + a = new[2] A(); + c = new[3] C(); + d = new D(); + e = new E(); + + (a.x)+ -> c.z, d.u, e.t; + } + reactor A(bank_index(0)) { + output[4] x; + b = new[2] B(a_bank_index = bank_index); + b.y -> x; + } + reactor B(a_bank_index(0), bank_index(0)) { + output[2] y; + reaction(startup) -> y {= + base = self.a_bank_index * 4 + self.bank_index * 2 + y[0].set(base) + y[1].set(base + 1) + =} + } + reactor C(bank_index(0)) { + input[2] z; + f = new F(c_bank_index = bank_index); + g = new G(c_bank_index = bank_index); + z -> f.w, g.s; + } + reactor D { + input[2] u; + reaction(u) {= + for (i, p) in enumerate(u): + print(f"d.u[{i}] received {p.value}.") + if p.value != (6 + i): + sys.stderr.write(f"ERROR: Expected {6 + i} but received {p.value}.\n") + exit(1) + =} + } + reactor E { + input[8] t; + reaction(t) {= + for (i, p) in enumerate(t): + print(f"e.t[{i}] received {p.value}.") + =} + } + reactor F(c_bank_index(0)) { + input w; + reaction(w) {= + print(f"c[{self.c_bank_index}].f.w received {w.value}.") + if w.value != self.c_bank_index * 2: + sys.stderr.write(f"ERROR: Expected {self.c_bank_index * 2} but received {w.value}.\n") + exit(1) + =} + } + reactor G(c_bank_index(0)) { + input s; + reaction(s) {= + print(f"c[{self.c_bank_index}].g.s received {s.value}.") + if s.value != (self.c_bank_index * 2 + 1): + sys.stderr.write(f"ERROR: Expected {self.c_bank_index * 2 + 1} but received {s.value}.\n") + exit(1) + =} + } \ No newline at end of file From 85ad7ae84b03ec3023fb09e46daae16c03b83b7f Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Fri, 21 Jan 2022 19:29:28 -0600 Subject: [PATCH 06/64] Comments only --- .../src/org/lflang/generator/python/PythonGenerator.xtend | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 3ae93c10e8..f6c12af23c 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -305,9 +305,13 @@ class PythonGenerator extends CGenerator { } /** - * Create a Python list for parameter initialization in target code. + * 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 initializers for + * @param p The parameter instance to create initializer for * @return Initialization code */ protected def String getPythonInitializer(ParameterInstance p) { From 12b9192e1138ace877d7f6e55bc9641b3079bd2a Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Fri, 21 Jan 2022 19:34:26 -0600 Subject: [PATCH 07/64] Added PyUtil --- .../org/lflang/generator/python/PyUtil.java | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 org.lflang/src/org/lflang/generator/python/PyUtil.java diff --git a/org.lflang/src/org/lflang/generator/python/PyUtil.java b/org.lflang/src/org/lflang/generator/python/PyUtil.java new file mode 100644 index 0000000000..e0677b631c --- /dev/null +++ b/org.lflang/src/org/lflang/generator/python/PyUtil.java @@ -0,0 +1,81 @@ +/* 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. +***************/ + +package org.lflang.generator.python; + +import org.lflang.generator.ReactorInstance; +import org.lflang.generator.c.CUtil; + + +/** + * 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 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. + */ + static public String reactorRefName(ReactorInstance instance) { + return instance.uniqueID() + "_lf"; + } + + /** + * 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)}. + */ + static public String reactorRef(ReactorInstance instance, String runtimeIndex) { + if (runtimeIndex == null) runtimeIndex = runtimeIndex(instance); + return PyUtil.reactorRefName(instance) + "[" + runtimeIndex + "]"; + } + + /** + * 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. + */ + static public String reactorRef(ReactorInstance instance) { + return PyUtil.reactorRef(instance, null); + } + +} From ca476259ae79e7f99c31ab65e68b99e0013bfb50 Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Fri, 21 Jan 2022 19:37:14 -0600 Subject: [PATCH 08/64] Updated source code format of the PythonGenerator --- .../generator/python/PythonGenerator.xtend | 765 +++++++++--------- 1 file changed, 374 insertions(+), 391 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index f6c12af23c..1cca141173 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -1,29 +1,28 @@ /* Generator for the Python target. */ /************* -Copyright (c) 2019, The University of California at Berkeley. + * 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: + * 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. - -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. -***************/ + * 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 import java.io.File @@ -91,12 +90,12 @@ import java.util.LinkedList * * 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 } */ class PythonGenerator extends CGenerator { - - // Used to add statements that come before reactor classes and user code + + // Used to add statements that come before reactor classes and user code var pythonPreamble = new StringBuilder() // Used to add module requirements to setup.py (delimited with ,) @@ -118,38 +117,38 @@ class PythonGenerator extends CGenerator { } /** - * Generic struct for ports with primitive types and - * statically allocated arrays in Lingua Franca. - * This template is defined as - * typedef struct { - * PyObject* value; - * bool is_present; - * int num_destinations; - * FEDERATED_CAPSULE_EXTENSION - * } generic_port_instance_struct; - * - * @see reactor-c-py/lib/pythontarget.h - */ - val generic_port_type = "generic_port_instance_struct" + * Generic struct for ports with primitive types and + * statically allocated arrays in Lingua Franca. + * This template is defined as + * typedef struct { + * PyObject* value; + * bool is_present; + * int num_destinations; + * FEDERATED_CAPSULE_EXTENSION + * } generic_port_instance_struct; + * + * @see reactor-c-py/lib/pythontarget.h + */ + val generic_port_type = "generic_port_instance_struct" /** - * Generic struct for ports with dynamically allocated - * array types (a.k.a. token types) in Lingua Franca. - * This template is defined as - * typedef struct { - * PyObject_HEAD - * PyObject* value; - * bool is_present; - * int num_destinations; - * lf_token_t* token; - * int length; - * FEDERATED_CAPSULE_EXTENSION - * } generic_port_instance_with_token_struct; - * - * @see reactor-c-py/lib/pythontarget.h - */ - val generic_port_type_with_token = "generic_port_instance_with_token_struct" - + * Generic struct for ports with dynamically allocated + * array types (a.k.a. token types) in Lingua Franca. + * This template is defined as + * typedef struct { + * PyObject_HEAD + * PyObject* value; + * bool is_present; + * int num_destinations; + * lf_token_t* token; + * int length; + * FEDERATED_CAPSULE_EXTENSION + * } generic_port_instance_with_token_struct; + * + * @see reactor-c-py/lib/pythontarget.h + */ + val generic_port_type_with_token = "generic_port_instance_with_token_struct" + /** * Generic struct for actions. * This template is defined as @@ -165,22 +164,22 @@ class PythonGenerator extends CGenerator { * @see reactor-c-py/lib/pythontarget.h */ val generic_action_type = "generic_action_instance_struct" - - /** Returns the Target enum for this generator */ + + /** Returns the Target enum for this generator */ override getTarget() { return Target.Python } val protoNames = new HashSet() - - //////////////////////////////////////////// - //// Public methods + + // ////////////////////////////////////////// + // // Public methods override printInfo() { println("Generating code for: " + fileConfig.resource.getURI.toString) println('******** Mode: ' + fileConfig.context.mode) println('******** Generated sources: ' + fileConfig.getSrcGenPath) } - + /** * Print information about necessary steps to install the supporting * Python C extension for the generated program. @@ -189,53 +188,52 @@ class PythonGenerator extends CGenerator { */ def printSetupInfo() { println(''' - - ##################################### - To compile and install the generated code, do: - cd «fileConfig.srcGenPath»«File.separator» - python3 -m pip install --force-reinstall . + ##################################### + To compile and install the generated code, do: + + cd «fileConfig.srcGenPath»«File.separator» + python3 -m pip install --force-reinstall . '''); } - + /** * Print information on how to execute the generated program. */ def printRunInfo() { println(''' - - ##################################### - To run the generated program, use: - python3 «fileConfig.srcGenPath»«File.separator»«topLevelName».py - - ##################################### + ##################################### + To run the generated program, use: + + python3 «fileConfig.srcGenPath»«File.separator»«topLevelName».py + + ##################################### '''); } - + /** * Print information on how to execute the generated federation. */ def printFedRunInfo() { println(''' - - ##################################### - To run the generated program, run: - bash «fileConfig.binPath»/«fileConfig.name» - - ##################################### + ##################################### + To run the generated program, run: + + bash «fileConfig.binPath»/«fileConfig.name» + + ##################################### '''); } override getTargetTypes() { return types; } - - //////////////////////////////////////////// - //// Protected methods - - /** + + // ////////////////////////////////////////// + // // Protected methods + /** * Override to convert some C types to their * Python equivalent. * Examples: @@ -245,22 +243,22 @@ class PythonGenerator extends CGenerator { */ private def getPythonTargetValue(Value v) { var String returnValue = ""; - switch(v.toText) { + switch (v.toText) { case "false": returnValue = "False" case "true": returnValue = "True" default: returnValue = v.targetValue } - + // Parameters in Python are always prepended with a 'self.' // predicate. Therefore, we need to append the returned value // if it is a parameter. if (v.parameter !== null) { returnValue = "self." + returnValue; } - + return returnValue; } - + /** * Create a list of state initializers in target code. * @@ -285,26 +283,26 @@ class PythonGenerator extends CGenerator { } return list } - - /** + + /** * Create a Python tuple for parameter initialization in target code. * * @param p The parameter instance to create initializers for * @return Initialization code */ - protected def String getPythonInitializer(StateVar state) throws Exception { - if (state.init.size > 1) { - // state variables are initialized as mutable lists - return state.init.join('[', ', ', ']', [it.pythonTargetValue]) - } else if (state.isInitialized) { - return state.init.get(0).getPythonTargetValue - } else { - return "None" - } - + protected def String getPythonInitializer(StateVar state) throws Exception { + if (state.init.size > 1) { + // state variables are initialized as mutable lists + return state.init.join('[', ', ', ']', [it.pythonTargetValue]) + } else if (state.isInitialized) { + return state.init.get(0).getPythonTargetValue + } else { + return "None" + } + } - - /** + + /** * 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 @@ -314,22 +312,22 @@ class PythonGenerator extends CGenerator { * @param p The parameter instance to create initializer for * @return Initialization code */ - protected def String getPythonInitializer(ParameterInstance p) { + protected def String getPythonInitializer(ParameterInstance p) { // Handle overrides in the intantiation. // In case there is more than one assignment to this parameter, we need to // find the last one. var lastAssignment = null as Assignment; - for (assignment: p.parent.definition.parameters) { + for (assignment : p.parent.definition.parameters) { if (assignment.lhs == p.definition) { lastAssignment = assignment; } } - + var list = new LinkedList(); if (lastAssignment !== null) { // The parameter has an assignment. // Right hand side can be a list. Collect the entries. - for (value: lastAssignment.rhs) { + for (value : lastAssignment.rhs) { if (value.parameter !== null) { // The parameter is being assigned a parameter value. // Assume that parameter belongs to the parent's parent. @@ -344,30 +342,30 @@ class PythonGenerator extends CGenerator { list.add(i.getPythonTargetValue) } } - + if (list.size == 1) { return list.get(0) } else { return list.join('(', ', ', ')', [it]) } - + } - + /** * Create a Python list for parameter initialization in target code. * * @param p The parameter to create initializers for * @return Initialization code */ - protected def String getPythonInitializer(Parameter p) { + protected def String getPythonInitializer(Parameter p) { if (p.init.size > 1) { // parameters are initialized as immutable tuples return p.init.join('(', ', ', ')', [it.pythonTargetValue]) } else { return p.init.get(0).pythonTargetValue } - } - + } + /** * 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 @@ -376,8 +374,8 @@ class PythonGenerator extends CGenerator { * @param decl Reactor declaration * @param reaction The reaction to be used to generate parameters for */ - def generatePythonReactionParametersAndInitializations(StringBuilder parameters, StringBuilder inits, ReactorDecl decl, - Reaction reaction) { + def generatePythonReactionParametersAndInitializations(StringBuilder parameters, StringBuilder inits, + ReactorDecl decl, Reaction reaction) { val reactor = decl.toDefinition var generatedParams = new LinkedHashSet() @@ -481,21 +479,19 @@ class PythonGenerator extends CGenerator { } } - + /** * Handle initialization for state variable * @param state a state variable */ def String getTargetInitializer(StateVar state) { - if(!state.isInitialized) - { + if (!state.isInitialized) { return '''None''' } - + '''«FOR init : state.pythonInitializerList SEPARATOR ", "»«init»«ENDFOR»''' } - - + /** * Wrapper function for the more elaborate generatePythonReactorClass that keeps track * of visited reactors to avoid duplicate generation @@ -504,13 +500,11 @@ class PythonGenerator extends CGenerator { * @param federate The federate instance for the reactor instance * @param instantiatedClasses A list of visited instances to avoid generating duplicates */ - def generatePythonReactorClass(ReactorInstance instance, StringBuilder pythonClasses, FederateInstance federate) - { + def generatePythonReactorClass(ReactorInstance instance, StringBuilder pythonClasses, FederateInstance federate) { var instantiatedClasses = new ArrayList() generatePythonReactorClass(instance, pythonClasses, federate, instantiatedClasses) } - - + /** * Generate a Python class corresponding to decl * @param instance The reactor instance to be generated @@ -557,10 +551,8 @@ class PythonGenerator extends CGenerator { pythonClasses.append(''' «' '»def __init__(self, **kwargs): ''') - - + pythonClasses.append(generateParametersAndStateVariables(decl)) - var reactionIndex = 0 for (reaction : reactor.allReactions) { @@ -593,7 +585,7 @@ class PythonGenerator extends CGenerator { generatePythonReactorClass(child, pythonClasses, federate, instantiatedClasses) } } - + /** * Generate code that instantiates and initializes parameters and state variables for a reactor 'decl'. * @@ -603,10 +595,10 @@ class PythonGenerator extends CGenerator { protected def StringBuilder generateParametersAndStateVariables(ReactorDecl decl) { val reactor = decl.toDefinition var StringBuilder temporary_code = new StringBuilder() - + temporary_code.append(''' #Define parameters and their default values ''') - + for (param : decl.toDefinition.allParameters) { if (!types.getTargetType(param).equals("PyObject*")) { // If type is given, use it @@ -617,18 +609,17 @@ class PythonGenerator extends CGenerator { // If type is not given, just pass along the initialization temporary_code.append(''' self._«param.name» = «param.pythonInitializer» ''') - + } } - + // Handle parameters that are set in instantiation temporary_code.append(''' # Handle parameters that are set in instantiation ''') temporary_code.append(''' self.__dict__.update(kwargs) ''') - - + temporary_code.append(''' # Define state variables ''') // Next, handle state variables @@ -645,21 +636,21 @@ class PythonGenerator extends CGenerator { } else { // If neither the type nor the initialization is given, use None temporary_code.append(''' self.«stateVar.name» = None - ''') + ''') } - } - + } + temporary_code.append(''' - ''') - + ''') + // Next, create getters for parameters for (param : decl.toDefinition.allParameters) { - if (param.name.equals("bank_index")){ + if (param.name.equals("bank_index")) { // Do nothing } else { temporary_code.append(''' @property - ''') + ''') temporary_code.append(''' def «param.name»(self): ''') temporary_code.append(''' return self._«param.name» @@ -667,19 +658,19 @@ class PythonGenerator extends CGenerator { ''') } } - + // Create a special property for bank_index temporary_code.append(''' @property - ''') + ''') temporary_code.append(''' def bank_index(self): ''') temporary_code.append(''' return self._bank_index ''') - + return temporary_code; } - + /** * Generate the function that is executed whenever the deadline of the reaction * with the given reaction index is missed @@ -687,15 +678,15 @@ class PythonGenerator extends CGenerator { * @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 */ - def generateDeadlineFunctionForReaction(Reaction reaction, int reactionIndex, String reactionParameters)''' + def generateDeadlineFunctionForReaction(Reaction reaction, int reactionIndex, String reactionParameters) ''' «val deadlineFunctionName = 'deadline_function_' + reactionIndex» - + def «deadlineFunctionName»(self «reactionParameters»): «reaction.deadline.code.toText» return 0 ''' - + /** * Generates preambles defined by user for a given reactor. * The preamble code is put inside the reactor class. @@ -707,7 +698,7 @@ class PythonGenerator extends CGenerator { # End of preamble. «ENDFOR» ''' - + /** * Instantiate classes in Python. * Instances are always instantiated as a list of className = [_className, _className, ...] depending on the size of the bank. @@ -722,19 +713,18 @@ class PythonGenerator extends CGenerator { if (instance !== this.main && !federate.contains(instance)) { return } - + val className = instance.definition.reactorClass.name - - + // Do not instantiate delay reactors in Python - if(className.contains(GEN_DELAY_CLASS_NAME)) { + if (className.contains(GEN_DELAY_CLASS_NAME)) { return } if (federate.contains(instance) && instance.width > 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. var reactorClass = instance.definition.reactorClass - var fullName = instance.fullName + var fullName = instance.fullName pr(pythonClassesInstantiation, ''' # Start initializing «fullName» of class «className» @@ -742,13 +732,13 @@ class PythonGenerator extends CGenerator { ''') indent(pythonClassesInstantiation); pr(pythonClassesInstantiation, ''' - «PyUtil.reactorRef(instance)» = \ - _«className»( - _bank_index = «PyUtil.bankIndex(instance)», - «FOR param : instance.parameters» - «IF !param.name.equals("bank_index")» - _«param.name»=«param.pythonInitializer», - «ENDIF»«ENDFOR» + «PyUtil.reactorRef(instance)» = \ + _«className»( + _bank_index = «PyUtil.bankIndex(instance)», + «FOR param : instance.parameters» + «IF !param.name.equals("bank_index")» + _«param.name»=«param.pythonInitializer», + «ENDIF»«ENDFOR» ) ''') } @@ -758,8 +748,7 @@ class PythonGenerator extends CGenerator { } unindent(pythonClassesInstantiation); } - - + /** * Generate code to instantiate a Python list that will hold the Python * class instance of reactor instance. Will recursively do @@ -771,7 +760,7 @@ class PythonGenerator extends CGenerator { * federate before generating code for them. */ def void generateListsToHoldClassInstances( - ReactorInstance instance, + ReactorInstance instance, StringBuilder pythonClassesInstantiation, FederateInstance federate ) { @@ -783,63 +772,61 @@ class PythonGenerator extends CGenerator { generateListsToHoldClassInstances(child, pythonClassesInstantiation, federate); } } - + /** * Generate all Python classes if they have a reaction * @param federate The federate instance used to generate classes */ def generatePythonReactorClasses(FederateInstance federate) { - + var StringBuilder pythonClasses = new StringBuilder() var StringBuilder pythonClassesInstantiation = new StringBuilder() - + // Generate reactor classes in Python this.main.generatePythonReactorClass(pythonClasses, federate) - + // Create empty lists to hold reactor instances this.main.generateListsToHoldClassInstances(pythonClassesInstantiation, federate) - + // Instantiate generated classes this.main.generatePythonClassInstantiation(pythonClassesInstantiation, federate) '''«pythonClasses» - ''' + - '''# Instantiate classes - ''' + - '''«pythonClassesInstantiation» + ''' + '''# Instantiate classes + ''' + '''«pythonClassesInstantiation» ''' } - + /** * Generate the Python code constructed from reactor classes and user-written classes. * @return the code body */ def generatePythonCode(FederateInstance federate) ''' - # 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. - from LinguaFranca«topLevelName» import ( # pylint: disable=no-name-in-module - Tag, action_capsule_t, compare_tags, get_current_tag, get_elapsed_logical_time, - get_elapsed_physical_time, get_logical_time, get_microstep, get_physical_time, - get_start_time, port_capsule, port_instance_token, request_stop, schedule_copy, - start - ) - 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 - import sys - import copy - - «pythonPreamble.toString» - - «generatePythonReactorClasses(federate)» - - «PythonMainGenerator.generateCode()» - ''' - + # 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. + from LinguaFranca«topLevelName» import ( # pylint: disable=no-name-in-module + Tag, action_capsule_t, compare_tags, get_current_tag, get_elapsed_logical_time, + get_elapsed_physical_time, get_logical_time, get_microstep, get_physical_time, + get_start_time, port_capsule, port_instance_token, request_stop, schedule_copy, + start + ) + 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 + import sys + import copy + + «pythonPreamble.toString» + + «generatePythonReactorClasses(federate)» + + «PythonMainGenerator.generateCode()» + ''' + /** * Generate the setup.py required to compile and install the module. * Currently, the package name is based on filename which does not support sharing the setup.py for multiple .lf files. @@ -849,26 +836,25 @@ class PythonGenerator extends CGenerator { * so that platform-specific C files will contain the appropriate functions. */ def generatePythonSetupFile() ''' - from setuptools import setup, Extension - - linguafranca«topLevelName»module = Extension("LinguaFranca«topLevelName»", - sources = ["«topLevelName».c", «FOR src : targetConfig.compileAdditionalSources SEPARATOR ", "» "«src»"«ENDFOR»], - define_macros=[('MODULE_NAME', 'LinguaFranca«topLevelName»')«IF (targetConfig.threads !== 0 || (targetConfig.tracing !== null))», - ('NUMBER_OF_WORKERS', '«targetConfig.threads»')«ENDIF»]) - - setup(name="LinguaFranca«topLevelName»", version="1.0", - ext_modules = [linguafranca«topLevelName»module], - install_requires=['LinguaFrancaBase' «pythonRequiredModules»],) - ''' - + from setuptools import setup, Extension + + linguafranca«topLevelName»module = Extension("LinguaFranca«topLevelName»", + sources = ["«topLevelName».c", «FOR src : targetConfig.compileAdditionalSources SEPARATOR ", "» "«src»"«ENDFOR»], + define_macros=[('MODULE_NAME', 'LinguaFranca«topLevelName»')«IF (targetConfig.threads !== 0 || (targetConfig.tracing !== null))», + ('NUMBER_OF_WORKERS', '«targetConfig.threads»')«ENDIF»]) + + setup(name="LinguaFranca«topLevelName»", version="1.0", + ext_modules = [linguafranca«topLevelName»module], + install_requires=['LinguaFrancaBase' «pythonRequiredModules»],) + ''' + /** * Generate the necessary Python files * @param fsa The file system access (used to write the result). * @param federate The federate instance */ - def generatePythonFiles(IFileSystemAccess2 fsa, FederateInstance federate) - { - var file = new File(fileConfig.getSrcGenPath.toFile, topLevelName + ".py") + def generatePythonFiles(IFileSystemAccess2 fsa, FederateInstance federate) { + var file = new File(fileConfig.getSrcGenPath.toFile, topLevelName + ".py") if (file.exists) { file.delete } @@ -878,7 +864,7 @@ class PythonGenerator extends CGenerator { } val codeMaps = #{file.toPath -> CodeMap.fromGeneratedCode(generatePythonCode(federate).toString)} JavaGeneratorUtils.writeSourceCodeToFile(codeMaps.get(file.toPath).generatedCode, file.absolutePath) - + val setupPath = fileConfig.getSrcGenPath.resolve("setup.py") // Handle Python setup System.out.println("Generating setup file to " + setupPath) @@ -887,27 +873,25 @@ class PythonGenerator extends CGenerator { // Append file.delete } - + // Create the setup file JavaGeneratorUtils.writeSourceCodeToFile(generatePythonSetupFile, setupPath.toString) - + return codeMaps } - + /** * Execute the command that compiles and installs the current Python module */ def pythonCompileCode(LFGeneratorContext context) { // if we found the compile command, we will also find the install command val installCmd = commandFactory.createCommand( - '''python3''', - #["-m", "pip", "install", "--force-reinstall", "."], - fileConfig.srcGenPath) + '''python3''', #["-m", "pip", "install", "--force-reinstall", "."], fileConfig.srcGenPath) if (installCmd === null) { errorReporter.reportError( "The Python target requires Python >= 3.6, pip >= 20.0.2, and setuptools >= 45.2.0-1 to compile the generated code. " + - "Auto-compiling can be disabled using the \"no-compile: true\" target property.") + "Auto-compiling can be disabled using the \"no-compile: true\" target property.") return } @@ -919,10 +903,11 @@ class PythonGenerator extends CGenerator { if (installCmd.run(context.cancelIndicator) == 0) { println("Successfully installed python extension.") } else { - errorReporter.reportError("Failed to install python extension due to the following errors:\n" + installCmd.getErrors()) + errorReporter.reportError("Failed to install python extension due to the following errors:\n" + + installCmd.getErrors()) } } - + /** * Generate top-level preambles and #include of pqueue.c and either reactor.c or reactor_threaded.c * depending on whether threads are specified in target directive. @@ -930,9 +915,9 @@ class PythonGenerator extends CGenerator { * private variables if such commands are specified in the target directive. */ override generatePreamble() { - + val models = new LinkedHashSet - + for (r : this.reactors ?: emptyList) { // The following assumes all reactors have a container. // This means that generated reactors **have** to be @@ -951,7 +936,7 @@ class PythonGenerator extends CGenerator { } pr(CGenerator.defineLogLevel(this)) - + if (isFederated) { // FIXME: Instead of checking // #ifdef FEDERATED, we could @@ -964,7 +949,7 @@ class PythonGenerator extends CGenerator { // The coordination is centralized. pr(''' #define FEDERATED_CENTRALIZED - ''') + ''') } else if (targetConfig.coordination === CoordinationType.DECENTRALIZED) { // The coordination is decentralized pr(''' @@ -972,7 +957,7 @@ class PythonGenerator extends CGenerator { ''') } } - + // Handle target parameters. // First, if there are federates, then ensure that threading is enabled. if (isFederated) { @@ -982,7 +967,7 @@ class PythonGenerator extends CGenerator { // worker thread to process incoming messages. if (targetConfig.threads < federate.networkMessageActions.size + 1) { targetConfig.threads = federate.networkMessageActions.size + 1; - } + } } } @@ -1002,11 +987,11 @@ class PythonGenerator extends CGenerator { super.parseTargetParameters() } - + /** * Add necessary code to the source and necessary build supports to * enable the requested serializations in 'enabledSerializations' - */ + */ override enableSupportForSerializationIfApplicable(CancelIndicator cancelIndicator) { if (!targetConfig.protoFiles.isNullOrEmpty) { // Enable support for proto serialization @@ -1036,11 +1021,10 @@ class PythonGenerator extends CGenerator { case ROS2: { // FIXME: Not supported yet } - } } } - + /** * Process a given .proto file. * @@ -1049,11 +1033,9 @@ class PythonGenerator extends CGenerator { * @param filename Name of the file to process. */ override processProtoFile(String filename, CancelIndicator cancelIndicator) { - val protoc = commandFactory.createCommand( - "protoc", - #['''--python_out=«this.fileConfig.getSrcGenPath»''', filename], - fileConfig.srcPath) - //val protoc = createCommand("protoc", #['''--python_out=src-gen/«topLevelName»''', topLevelName], codeGenConfig.outPath) + val protoc = commandFactory.createCommand("protoc", + #['''--python_out=«this.fileConfig.getSrcGenPath»''', filename], fileConfig.srcPath) + // val protoc = createCommand("protoc", #['''--python_out=src-gen/«topLevelName»''', topLevelName], codeGenConfig.outPath) if (protoc === null) { errorReporter.reportError("Processing .proto files requires libprotoc >= 3.6.1") return @@ -1065,8 +1047,8 @@ class PythonGenerator extends CGenerator { errorReporter.reportError("protoc returns error code " + returnCode) } } - - /** + + /** * Generate code for the body of a reaction that handles the * action that is triggered by receiving a message from a remote * federate. @@ -1086,7 +1068,7 @@ class PythonGenerator extends CGenerator { Action action, VarRef sendingPort, VarRef receivingPort, - int receivingPortID, + int receivingPortID, FederateInstance sendingFed, FederateInstance receivingFed, int receivingBankIndex, @@ -1102,7 +1084,9 @@ class PythonGenerator extends CGenerator { gstate = PyGILState_Ensure(); ''') result.append(PythonGeneratorExtension.generateNetworkReceiverBody( - action, sendingPort, receivingPort, + action, + sendingPort, + receivingPort, receivingPortID, sendingFed, receivingFed, @@ -1119,8 +1103,8 @@ class PythonGenerator extends CGenerator { '''); return result.toString(); } - - /** + + /** * 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. @@ -1138,7 +1122,7 @@ class PythonGenerator extends CGenerator { override generateNetworkSenderBody( VarRef sendingPort, VarRef receivingPort, - int receivingPortID, + int receivingPortID, FederateInstance sendingFed, int sendingBankIndex, int sendingChannelIndex, @@ -1147,7 +1131,7 @@ class PythonGenerator extends CGenerator { boolean isPhysical, Delay delay, SupportedSerializers serializer - ) { + ) { var result = new StringBuilder(); result.append(''' // Acquire the GIL (Global Interpreter Lock) to be able to call Python APIs. @@ -1174,7 +1158,7 @@ class PythonGenerator extends CGenerator { '''); return result.toString(); } - + /** * Create a launcher script that executes all the federates and the RTI. * @@ -1193,7 +1177,7 @@ class PythonGenerator extends CGenerator { federationRTIProperties ); } - + /** * Generate the aliases for inputs, outputs, and struct type definitions for * actions of the specified reactor in the specified federate. @@ -1246,8 +1230,8 @@ class PythonGenerator extends CGenerator { } } - - /** + + /** * For the specified action, return a declaration for action struct to * contain the value of the action. * This will return an empty string for an action with no type. @@ -1257,13 +1241,13 @@ class PythonGenerator extends CGenerator { override valueDeclaration(Action action) { return "PyObject* value;" } - + /** Add necessary include files specific to the target language. * Note. The core files always need to be (and will be) copied * uniformly across all target languages. */ override includeTargetLanguageHeaders() { - pr('''#define _LF_GARBAGE_COLLECTED''') + pr('''#define _LF_GARBAGE_COLLECTED''') if (targetConfig.tracing !== null) { var filename = ""; if (targetConfig.tracing.traceFileName !== null) { @@ -1271,20 +1255,20 @@ class PythonGenerator extends CGenerator { } pr('#define LINGUA_FRANCA_TRACE ' + filename) } - + pr('#include "pythontarget.c"') if (targetConfig.tracing !== null) { - pr('#include "core/trace.c"') + pr('#include "core/trace.c"') } } - + /** * Return true if the host operating system is compatible and * otherwise report an error and return false. */ override isOSCompatible() { if (JavaGeneratorUtils.isHostWindows) { - if (isFederated) { + if (isFederated) { errorReporter.reportError( "Federated LF programs with a Python target are currently not supported on Windows. Exiting code generation." ) @@ -1303,31 +1287,33 @@ class PythonGenerator extends CGenerator { * @param context FIXME: Undocumented argument. No idea what this is. */ override void doGenerate(Resource resource, IFileSystemAccess2 fsa, LFGeneratorContext context) { - + // If there are federates, assign the number of threads in the CGenerator to 1 if (isFederated) { targetConfig.threads = 1; } - + // Prevent the CGenerator from compiling the C code. // The PythonGenerator will compiler it. val compileStatus = targetConfig.noCompile; targetConfig.noCompile = true; targetConfig.useCmake = false; // Force disable the CMake because - // it interferes with the Python target functionality + // it interferes with the Python target functionality val cGeneratedPercentProgress = (IntegratedBuilder.VALIDATED_PERCENT_PROGRESS + 100) / 2 super.doGenerate(resource, fsa, new SubContext( - context, IntegratedBuilder.VALIDATED_PERCENT_PROGRESS, cGeneratedPercentProgress + context, + IntegratedBuilder.VALIDATED_PERCENT_PROGRESS, + cGeneratedPercentProgress )) val compilingFederatesContext = new SubContext(context, cGeneratedPercentProgress, 100) - + targetConfig.noCompile = compileStatus if (errorsOccurred) { context.unsuccessfulFinish() return; } - + var baseFileName = topLevelName // Keep a separate file config for each federate val oldFileConfig = fileConfig; @@ -1349,18 +1335,20 @@ class PythonGenerator extends CGenerator { 100 * federateCount / federates.size() ) // If there are no federates, compile and install the generated code - new PythonValidator(fileConfig, errorReporter, codeMaps, protoNames).doValidate(context.cancelIndicator) + new PythonValidator(fileConfig, errorReporter, codeMaps, protoNames).doValidate( + context.cancelIndicator) if (!errorsOccurred() && context.mode != Mode.LSP_MEDIUM) { compilingFederatesContext.reportProgress( - String.format("Validation complete. Compiling and installing %d/%d Python modules...", federateCount, federates.size()), + String.format("Validation complete. Compiling and installing %d/%d Python modules...", + federateCount, federates.size()), 100 * federateCount / federates.size() ) - pythonCompileCode(context) // Why is this invoked here if the current federate is not a parameter? + pythonCompileCode(context) // Why is this invoked here if the current federate is not a parameter? } } else { printSetupInfo(); } - + if (!isFederated) { printRunInfo(); } @@ -1375,12 +1363,14 @@ class PythonGenerator extends CGenerator { if (errorReporter.getErrorsOccurred()) { context.unsuccessfulFinish() } else if (!isFederated) { - context.finish(GeneratorResult.Status.COMPILED, '''«topLevelName».py''', fileConfig.srcGenPath, fileConfig, codeMaps, "python3") + context.finish(GeneratorResult.Status.COMPILED, '''«topLevelName».py''', fileConfig.srcGenPath, fileConfig, + codeMaps, "python3") } else { - context.finish(GeneratorResult.Status.COMPILED, fileConfig.name, fileConfig.binPath, fileConfig, codeMaps, "bash") + context.finish(GeneratorResult.Status.COMPILED, fileConfig.name, fileConfig.binPath, fileConfig, codeMaps, + "bash") } } - + /** * Copy Python specific target code to the src-gen directory * Also, copy all files listed in the target property `files` into the @@ -1404,57 +1394,56 @@ class PythonGenerator extends CGenerator { fileConfig.copyFileFromClassPath( "/lib/c/reactor-c/include/ctarget.h", fileConfig.getSrcGenPath.resolve("ctarget.h").toString - ) + ) } - - - /** Return the function name in Python + + /** Return the function name in Python * @param reactor The reactor * @param reactionIndex The reaction index. * @return The function name for the reaction. */ def pythonReactionFunctionName(int reactionIndex) { - "reaction_function_" + reactionIndex + "reaction_function_" + reactionIndex } - + /** * 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 generateDelayBody(Action action, VarRef port) { + override generateDelayBody(Action action, VarRef port) { val ref = JavaAstUtils.generateVarRef(port); // Note that the action.type set by the base class is actually // the port type. if (action.inferredType.isTokenType) { ''' - 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. - schedule_token(«action.name», 0, «ref»->token); - } + 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. + schedule_token(«action.name», 0, «ref»->token); + } ''' } else { ''' - // Create a token. - #if NUMBER_OF_WORKERS > 0 - // Need to lock the mutex first. - lf_mutex_lock(&mutex); - #endif - lf_token_t* t = create_token(sizeof(PyObject*)); - #if NUMBER_OF_WORKERS > 0 - lf_mutex_unlock(&mutex); - #endif - t->value = self->_lf_«ref»->value; - t->length = 1; // Length is 1 - - // Pass the token along - schedule_token(«action.name», 0, t); + // Create a token. + #if NUMBER_OF_WORKERS > 0 + // Need to lock the mutex first. + lf_mutex_lock(&mutex); + #endif + lf_token_t* t = create_token(sizeof(PyObject*)); + #if NUMBER_OF_WORKERS > 0 + lf_mutex_unlock(&mutex); + #endif + t->value = self->_lf_«ref»->value; + t->length = 1; // Length is 1 + + // Pass the token along + schedule_token(«action.name», 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 @@ -1469,11 +1458,11 @@ class PythonGenerator extends CGenerator { super.generateForwardBody(action, port) } else { ''' - SET(«outputName», «action.name»->token->value); + SET(«outputName», «action.name»->token->value); ''' } } - + /** 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), @@ -1483,35 +1472,36 @@ class PythonGenerator extends CGenerator { * @param reactionIndex The position of the reaction within the reactor. */ override generateReaction(Reaction reaction, ReactorDecl decl, int reactionIndex) { - + val reactor = decl.toDefinition - + // Delay reactors and top-level reactions used in the top-level reactor(s) in federated execution are generated in C - if(reactor.name.contains(GEN_DELAY_CLASS_NAME) || ((decl === this.mainDef?.reactorClass) && reactor.isFederated)) { + if (reactor.name.contains(GEN_DELAY_CLASS_NAME) || + ((decl === this.mainDef?.reactorClass) && reactor.isFederated)) { super.generateReaction(reaction, decl, reactionIndex) return } - + // Contains "O" characters. The number of these characters depend on the number of inputs to the reaction val StringBuilder pyObjectDescriptor = new StringBuilder() - + // Contains the actual comma separated list of inputs to the reaction of type generic_port_instance_struct or generic_port_instance_with_token_struct. // Each input must be cast to (PyObject *) val StringBuilder pyObjects = new StringBuilder() - + // Create a unique function name for each reaction. val functionName = reactionFunctionName(decl, reactionIndex) - + // Generate the function name in Python val pythonFunctionName = pythonReactionFunctionName(reactionIndex); - + // 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. var actionsAsTriggers = new LinkedHashSet(); - + // Next, add the triggers (input and actions; timers are not needed). // TODO: handle triggers for (TriggerRef trigger : reaction.triggers ?: emptyList) { @@ -1520,7 +1510,8 @@ class PythonGenerator extends CGenerator { generatePortVariablesToSendToPythonReaction(pyObjectDescriptor, pyObjects, trigger, decl) } else if (trigger.variable instanceof Action) { actionsAsTriggers.add(trigger.variable as Action) - generateActionVariableToSendToPythonReaction(pyObjectDescriptor, pyObjects, trigger.variable as Action, decl) + generateActionVariableToSendToPythonReaction(pyObjectDescriptor, pyObjects, + trigger.variable as Action, decl) } } } @@ -1529,59 +1520,59 @@ class PythonGenerator extends CGenerator { // Declare an argument for every input. // NOTE: this does not include contained outputs. for (input : reactor.inputs) { - generateInputVariablesToSendToPythonReaction(pyObjectDescriptor, pyObjects, input, decl) + generateInputVariablesToSendToPythonReaction(pyObjectDescriptor, pyObjects, input, decl) } } - + // Next add non-triggering inputs. for (VarRef src : reaction.sources ?: emptyList) { - if(src.variable instanceof Port) - { + if (src.variable instanceof Port) { generatePortVariablesToSendToPythonReaction(pyObjectDescriptor, pyObjects, src, decl) } else if (src.variable instanceof Action) { - //TODO: handle actions + // TODO: handle actions actionsAsTriggers.add(src.variable as Action) - generateActionVariableToSendToPythonReaction(pyObjectDescriptor, pyObjects, src.variable as Action, decl) + generateActionVariableToSendToPythonReaction(pyObjectDescriptor, pyObjects, src.variable as Action, + decl) } } - + // Next, handle effects if (reaction.effects !== null) { for (effect : reaction.effects) { - if(effect.variable instanceof Action) - { + if (effect.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.variable)) { - generateActionVariableToSendToPythonReaction(pyObjectDescriptor, pyObjects, effect.variable as Action, decl) + generateActionVariableToSendToPythonReaction(pyObjectDescriptor, pyObjects, + effect.variable as Action, decl) } } else { if (effect.variable instanceof Output) { - generateOutputVariablesToSendToPythonReaction(pyObjectDescriptor, pyObjects, effect.variable as Output, decl) - } else if (effect.variable instanceof Input ) { + generateOutputVariablesToSendToPythonReaction(pyObjectDescriptor, pyObjects, + effect.variable as Output, decl) + } else if (effect.variable instanceof Input) { // It is the input of a contained reactor. - generateVariablesForSendingToContainedReactors(pyObjectDescriptor, pyObjects, effect.container, effect.variable as Input, decl) + generateVariablesForSendingToContainedReactors(pyObjectDescriptor, pyObjects, effect.container, + effect.variable as Input, decl) } else { errorReporter.reportError( reaction, "In generateReaction(): " + effect.variable.name + " is neither an input nor an output." ) } - + } } } - - + pr('void ' + functionName + '(void* instance_args) {') indent() - + // First, generate C initializations super.generateInitializationForReaction("", reaction, decl, reactionIndex) - - + prSourceLineNumber(reaction.code) - + pr(''' // Acquire the GIL (Global Interpreter Lock) to be able to call Python APIs. PyGILState_STATE gstate; @@ -1607,10 +1598,10 @@ class PythonGenerator extends CGenerator { /* Release the thread. No Python API allowed beyond this point. */ PyGILState_Release(gstate); ''') - + unindent() pr("}") - + // Now generate code for the deadline violation function, if there is one. if (reaction.deadline !== null) { // The following name has to match the choice in generateReactionInstances @@ -1618,9 +1609,9 @@ class PythonGenerator extends CGenerator { pr('void ' + deadlineFunctionName + '(void* instance_args) {') indent(); - + super.generateInitializationForReaction("", reaction, decl, reactionIndex) - + pr(''' // Acquire the GIL (Global Interpreter Lock) to be able to call Python APIs. PyGILState_STATE gstate; @@ -1648,13 +1639,12 @@ class PythonGenerator extends CGenerator { /* Release the thread. No Python API allowed beyond this point. */ PyGILState_Release(gstate); ''') - + unindent() pr("}") } } - - + /** * Generate code for parameter variables of a reactor in the form "parameter.type parameter.name;" * @@ -1669,10 +1659,10 @@ class PythonGenerator extends CGenerator { for (parameter : reactor.allParameters) { prSourceLineNumber(builder, parameter) // Assume all parameters are integers - pr(builder,'''int «parameter.name» ;'''); + pr(builder, '''int «parameter.name» ;'''); } } - + /** * Generate code that initializes the state variables for a given instance. * Unlike parameters, state variables are uniformly initialized for all instances @@ -1684,7 +1674,7 @@ class PythonGenerator extends CGenerator { override generateStateVariableInitializations(ReactorInstance instance) { // Do nothing } - + /** * Generate runtime initialization code in C for parameters of a given reactor instance. * All parameters are also initialized in Python code, but those parameters that are @@ -1706,19 +1696,19 @@ class PythonGenerator extends CGenerator { // If it fails, we defer the initialization to Python. var nameOfSelfStruct = CUtil.reactorRef(instance) for (parameter : instance.parameters) { - val initializer = parameter.getInitializer + val initializer = parameter.getInitializer try { // Attempt to convert it to integer val number = Integer.parseInt(initializer); pr(initializeTriggerObjects, ''' «nameOfSelfStruct»->«parameter.name» = «number»; ''') - } catch (NumberFormatException ex){ + } catch (NumberFormatException ex) { // Ignore initialization in C for this parameter } } } - + /** * This function is overridden in the Python generator to do nothing. * The state variables are initialized in Python code directly. @@ -1726,10 +1716,10 @@ class PythonGenerator extends CGenerator { * @param builder The StringBuilder that the generated code is appended to * @return */ - override generateStateVariablesForReactor(StringBuilder builder, Reactor reactor) { + override generateStateVariablesForReactor(StringBuilder builder, Reactor reactor) { // Do nothing } - + /** * Generates C preambles defined by user for a given reactor * Since the Python generator expects preambles written in C, @@ -1739,8 +1729,7 @@ class PythonGenerator extends CGenerator { override generateUserPreamblesForReactor(Reactor reactor) { // Do nothing } - - + /** * Generate code that is executed while the reactor instance is being initialized. * This wraps the reaction functions in a Python function. @@ -1748,24 +1737,23 @@ class PythonGenerator extends CGenerator { * @param reactions The reactions of this instance. */ override void generateReactorInstanceExtension( - ReactorInstance instance, Iterable reactions + ReactorInstance instance, + Iterable reactions ) { var nameOfSelfStruct = CUtil.reactorRef(instance) var reactor = instance.definition.reactorClass.toDefinition - - // Delay reactors and top-level reactions used in the top-level reactor(s) in federated execution are generated in C - if (reactor.name.contains(GEN_DELAY_CLASS_NAME) || - ((instance.definition.reactorClass === this.mainDef?.reactorClass) - && reactor.isFederated) - ) { + + // Delay reactors and top-level reactions used in the top-level reactor(s) in federated execution are generated in C + if (reactor.name.contains(GEN_DELAY_CLASS_NAME) || + ((instance.definition.reactorClass === this.mainDef?.reactorClass) && reactor.isFederated)) { return } - + // Initialize the name field to the unique name of the instance pr(initializeTriggerObjects, ''' «nameOfSelfStruct»->_lf_name = "«instance.uniqueID»_lf"; '''); - + for (reaction : reactions) { val pythonFunctionName = pythonReactionFunctionName(reaction.index) // Create a PyObject for each reaction @@ -1775,21 +1763,20 @@ class PythonGenerator extends CGenerator { «nameOfSelfStruct»->_lf_name, «CUtil.runtimeIndex(instance)», "«pythonFunctionName»"); - ''') - + ''') + if (reaction.definition.deadline !== null) { pr(initializeTriggerObjects, ''' - «nameOfSelfStruct»->_lf_py_deadline_function_«reaction.index» = - get_python_function("«topLevelName»", - «nameOfSelfStruct»->_lf_name, - «CUtil.runtimeIndex(instance)», - "deadline_function_«reaction.index»"); + «nameOfSelfStruct»->_lf_py_deadline_function_«reaction.index» = + get_python_function("«topLevelName»", + «nameOfSelfStruct»->_lf_name, + «CUtil.runtimeIndex(instance)», + "deadline_function_«reaction.index»"); ''') } } } - - + /** * 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 @@ -1798,26 +1785,26 @@ class PythonGenerator extends CGenerator { * @param constructorCode Code that is executed when the reactor is instantiated * @param destructorCode Code that is executed when the reactor instance is freed */ - override generateSelfStructExtension(StringBuilder selfStructBody, ReactorDecl decl, FederateInstance instance, StringBuilder constructorCode, StringBuilder destructorCode) { + override generateSelfStructExtension(StringBuilder selfStructBody, ReactorDecl decl, FederateInstance instance, + StringBuilder constructorCode, StringBuilder destructorCode) { val reactor = decl.toDefinition // Add the name field pr(selfStructBody, '''char *_lf_name; '''); - + var reactionIndex = 0 - for (reaction : reactor.allReactions) - { + for (reaction : reactor.allReactions) { // Create a PyObject for each reaction pr(selfStructBody, '''PyObject* _lf_py_reaction_function_«reactionIndex»;''') - - if (reaction.deadline !== null) { + + if (reaction.deadline !== null) { pr(selfStructBody, '''PyObject* _lf_py_deadline_function_«reactionIndex»;''') } - + reactionIndex++ } } - + /** * Generate code to convert C actions to Python action capsules * @see pythontarget.h @@ -1827,7 +1814,8 @@ class PythonGenerator extends CGenerator { * @param port The port. * @param reactor The reactor. */ - def generateActionVariableToSendToPythonReaction(StringBuilder pyObjectDescriptor, StringBuilder pyObjects, Action action, ReactorDecl decl) { + def generateActionVariableToSendToPythonReaction(StringBuilder pyObjectDescriptor, StringBuilder pyObjects, + Action action, ReactorDecl decl) { pyObjectDescriptor.append("O") // 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. @@ -1866,7 +1854,7 @@ class PythonGenerator extends CGenerator { } } } - + /** Generate into the specified string builder the code to * send local variables for output ports to a Python reaction function * from the "self" struct. @@ -1899,7 +1887,7 @@ class PythonGenerator extends CGenerator { pyObjects.append(''', convert_C_port_to_py(«output.name»,«output.name»_width) ''') } } - + /** 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). @@ -1912,21 +1900,18 @@ class PythonGenerator extends CGenerator { StringBuilder pyObjects, Instantiation definition, Input input, - ReactorDecl decl - ) - { - if(JavaAstUtils.isMultiport(input)) - { + ReactorDecl decl + ) { + if (JavaAstUtils.isMultiport(input)) { pyObjectDescriptor.append("O") - pyObjects.append(''', convert_C_port_to_py(«definition.name».«input.name», «definition.name».«input.name»_width)''') - } - else - { + pyObjects. + append(''', convert_C_port_to_py(«definition.name».«input.name», «definition.name».«input.name»_width)''') + } else { pyObjectDescriptor.append("O") - pyObjects.append(''', convert_C_port_to_py(«definition.name».«input.name», -2)''') + pyObjects.append(''', convert_C_port_to_py(«definition.name».«input.name», -2)''') } } - + /** Generate into the specified string builder the code to * send local variables for input ports to a Python reaction function * from the "self" struct. @@ -1940,9 +1925,8 @@ class PythonGenerator extends CGenerator { StringBuilder pyObjectDescriptor, StringBuilder pyObjects, Input input, - ReactorDecl decl - ) - { + ReactorDecl decl + ) { // 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, @@ -1963,17 +1947,16 @@ class PythonGenerator extends CGenerator { } else if (!input.isMutable && JavaAstUtils.isMultiport(input)) { // Non-mutable, multiport, primitive. // TODO: support multiports - pyObjectDescriptor.append("O") + pyObjectDescriptor.append("O") pyObjects.append(''', convert_C_port_to_py(«input.name»,«input.name»_width) ''') } else { // Mutable, multiport, primitive type // TODO: support mutable multiports - - pyObjectDescriptor.append("O") + pyObjectDescriptor.append("O") pyObjects.append(''', convert_C_port_to_py(«input.name»,«input.name»_width) ''') } } - + /** * Write a Dockerfile for the current federate as given by filename. * The file will go into src-gen/filename.Dockerfile. @@ -2021,7 +2004,7 @@ class PythonGenerator extends CGenerator { ##################################### ''') } - + /** * Convert C types to formats used in Py_BuildValue and PyArg_PurseTuple. * This is unused but will be useful to enable inter-compatibility between From 8943031ade7eb85f7607e193cad833a4f024529d Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Fri, 21 Jan 2022 23:56:19 -0600 Subject: [PATCH 09/64] Fixed syntax error --- .../src/org/lflang/generator/python/PythonGenerator.xtend | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 1cca141173..c3876575a0 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -843,9 +843,9 @@ class PythonGenerator extends CGenerator { define_macros=[('MODULE_NAME', 'LinguaFranca«topLevelName»')«IF (targetConfig.threads !== 0 || (targetConfig.tracing !== null))», ('NUMBER_OF_WORKERS', '«targetConfig.threads»')«ENDIF»]) - setup(name="LinguaFranca«topLevelName»", version="1.0", - ext_modules = [linguafranca«topLevelName»module], - install_requires=['LinguaFrancaBase' «pythonRequiredModules»],) + setup(name="LinguaFranca«topLevelName»", version="1.0", + ext_modules = [linguafranca«topLevelName»module], + install_requires=['LinguaFrancaBase' «pythonRequiredModules»],) ''' /** From 9c8b937791b71fa64931118bf28f2af5f96d047c Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Sat, 22 Jan 2022 01:07:44 -0600 Subject: [PATCH 10/64] First step toward supporting sending/receiving to contained banks (C side) --- .../generator/python/PythonGenerator.xtend | 221 +++++++++++++----- 1 file changed, 169 insertions(+), 52 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index c3876575a0..b964233ae4 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -31,6 +31,7 @@ import java.util.ArrayList import java.util.HashMap import java.util.HashSet import java.util.LinkedHashSet +import java.util.LinkedList import java.util.List import org.eclipse.emf.ecore.resource.Resource import org.eclipse.xtext.generator.IFileSystemAccess2 @@ -61,6 +62,7 @@ import org.lflang.generator.SubContext import org.lflang.generator.c.CGenerator import org.lflang.generator.c.CUtil import org.lflang.lf.Action +import org.lflang.lf.Assignment import org.lflang.lf.Delay import org.lflang.lf.Input import org.lflang.lf.Instantiation @@ -78,8 +80,6 @@ import org.lflang.lf.VarRef import static extension org.lflang.ASTUtils.* import static extension org.lflang.JavaAstUtils.* -import org.lflang.lf.Assignment -import java.util.LinkedList /** * Generator for Python target. This class generates Python code defining each reactor @@ -1462,45 +1462,27 @@ class PythonGenerator extends CGenerator { ''' } } - - /** 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 reactor The reactor. - * @param reactionIndex The position of the reaction within the reactor. + + + /** + * 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 pyObjectDescriptor For each port object created, a Python-specific descriptor will be added to this that + * then can be used as an argument to Py_BuildValue + * (@see docs.python.org/3/c-api). + * @param pyObjects A "," delimited list of expressions that would be (or result in a creation of) a PyObject. */ - override generateReaction(Reaction reaction, ReactorDecl decl, int reactionIndex) { - - val reactor = decl.toDefinition - - // Delay reactors and top-level reactions used in the top-level reactor(s) in federated execution are generated in C - if (reactor.name.contains(GEN_DELAY_CLASS_NAME) || - ((decl === this.mainDef?.reactorClass) && reactor.isFederated)) { - super.generateReaction(reaction, decl, reactionIndex) - return - } - - // Contains "O" characters. The number of these characters depend on the number of inputs to the reaction - val StringBuilder pyObjectDescriptor = new StringBuilder() - - // Contains the actual comma separated list of inputs to the reaction of type generic_port_instance_struct or generic_port_instance_with_token_struct. - // Each input must be cast to (PyObject *) - val StringBuilder pyObjects = new StringBuilder() - - // Create a unique function name for each reaction. - val functionName = reactionFunctionName(decl, reactionIndex) - - // Generate the function name in Python - val pythonFunctionName = pythonReactionFunctionName(reactionIndex); - - // 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. + protected def void generatePythonInitializationForReaction( + Reaction reaction, + ReactorDecl decl, + StringBuilder pyObjectDescriptor, + StringBuilder pyObjects + ) { var actionsAsTriggers = new LinkedHashSet(); + val Reactor reactor = decl.toDefinition; // Next, add the triggers (input and actions; timers are not needed). // TODO: handle triggers @@ -1564,19 +1546,60 @@ class PythonGenerator extends CGenerator { } } } + } + + /** 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 reactor The reactor. + * @param reactionIndex The position of the reaction within the reactor. + */ + override generateReaction(Reaction reaction, ReactorDecl decl, int reactionIndex) { + + val reactor = decl.toDefinition + + // Delay reactors and top-level reactions used in the top-level reactor(s) in federated execution are generated in C + if (reactor.name.contains(GEN_DELAY_CLASS_NAME) || + ((decl === this.mainDef?.reactorClass) && reactor.isFederated)) { + super.generateReaction(reaction, decl, reactionIndex) + return + } + + // Contains "O" characters. The number of these characters depend on the number of inputs to the reaction + val StringBuilder pyObjectDescriptor = new StringBuilder() + + // Contains the actual comma separated list of inputs to the reaction of type generic_port_instance_struct or generic_port_instance_with_token_struct. + // Each input must be cast to (PyObject *) + val StringBuilder pyObjects = new StringBuilder() + + // Create a unique function name for each reaction. + val functionName = reactionFunctionName(decl, reactionIndex) + + // Generate the function name in Python + val pythonFunctionName = pythonReactionFunctionName(reactionIndex); pr('void ' + functionName + '(void* instance_args) {') indent() // First, generate C initializations super.generateInitializationForReaction("", reaction, decl, reactionIndex) - + prSourceLineNumber(reaction.code) + // Ensure that GIL is locked pr(''' // Acquire the GIL (Global Interpreter Lock) to be able to call Python APIs. PyGILState_STATE gstate; gstate = PyGILState_Ensure(); + ''') + + // Generate Python-related initializations + generatePythonInitializationForReaction(reaction, decl, pyObjectDescriptor, pyObjects) + + // Call the Python reaction + pr(''' DEBUG_PRINT("Calling reaction function «decl.name».«pythonFunctionName»"); PyObject *rValue = PyObject_CallObject( @@ -1844,13 +1867,60 @@ class PythonGenerator extends CGenerator { if (port.variable instanceof Input) { generateInputVariablesToSendToPythonReaction(pyObjectDescriptor, pyObjects, port.variable as Input, decl) } else { - if (!JavaAstUtils.isMultiport(port.variable as Port)) { - pyObjectDescriptor.append("O") - pyObjects.append(''', convert_C_port_to_py(«port.container.name».«port.variable.name», -2)''') + pyObjectDescriptor.append("O") + val output = port.variable as Output + val reactorName = port.container.name + // port is an output of a contained reactor. + if (port.container.widthSpec !== null) { + var String widthSpec = "-2" + if (JavaAstUtils.isMultiport(port.variable as Port)) { + widthSpec = '''self->_lf_«reactorName»[i].«output.name»_width''' + } + // Output is in a bank. + // Create a Python list + pr(''' + PyObject* «reactorName»_py_list = PyList_New(«reactorName»_width); + + if(«reactorName»_py_list == NULL) { + error_print("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 < «reactorName»_width; i++) { + if (PyList_Append( + «reactorName»_py_list, + convert_C_port_to_py( + self->_lf_«reactorName»[i].«output.name», + «widthSpec» + ) + ) != 0) { + error_print("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); + } + } + + ''') + pyObjects.append(''', «reactorName»_py_list''') } else { - pyObjectDescriptor.append("O") - pyObjects. - append(''', convert_C_port_to_py(«port.container.name».«port.variable.name», «port.container.name».«port.variable.name»_width) ''') + var String widthSpec = "-2" + if (JavaAstUtils.isMultiport(port.variable as Port)) { + widthSpec = '''«port.container.name».«port.variable.name»_width''' + } + pyObjects.append(''', convert_C_port_to_py(«reactorName».«port.variable.name», «widthSpec»)''') } } } @@ -1902,13 +1972,60 @@ class PythonGenerator extends CGenerator { Input input, ReactorDecl decl ) { - if (JavaAstUtils.isMultiport(input)) { - pyObjectDescriptor.append("O") + pyObjectDescriptor.append("O") + + if (definition.widthSpec !== null) { + var String widthSpec = "-2" + if (JavaAstUtils.isMultiport(input)) { + widthSpec = '''self->_lf_«definition.name»[i].«input.name»_width''' + } + // Contained reactor is a bank. + // Create a Python list + pr(''' + PyObject* «definition.name»_py_list = PyList_New(«definition.name»_width); + + if(«definition.name»_py_list == NULL) { + error_print("Could not create the list needed for «definition.name»."); + 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 < «definition.name»_width; i++) { + if (PyList_Append( + «definition.name»_py_list, + convert_C_port_to_py( + self->_lf_«definition.name»[i].«input.name», + «widthSpec» + ) + ) != 0) { + error_print("Could not add elements to the list for «definition.name»."); + 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); + } + } + + ''') + pyObjects.append(''', «definition.name»_py_list''') + } + else { + var String widthSpec = "-2" + if (JavaAstUtils.isMultiport(input)) { + widthSpec = '''«definition.name».«input.name»_width''' + } pyObjects. - append(''', convert_C_port_to_py(«definition.name».«input.name», «definition.name».«input.name»_width)''') - } else { - pyObjectDescriptor.append("O") - pyObjects.append(''', convert_C_port_to_py(«definition.name».«input.name», -2)''') + append(''', convert_C_port_to_py(«definition.name».«input.name», «widthSpec»)''') } } From 22cb830d7114587015889961e608e0c6759d1eac Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Sat, 22 Jan 2022 01:33:48 -0600 Subject: [PATCH 11/64] Interim commit to fix the Python side --- .../generator/python/PythonGenerator.xtend | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index b964233ae4..24f2034a6f 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -410,11 +410,7 @@ class PythonGenerator extends CGenerator { } else { // Handle contained reactors' ports generatedParams.add('''«trigger.container.name»_«trigger.variable.name»''') - inits.append('''«trigger.container.name» = Make - ''') - inits. - append('''«trigger.container.name».«trigger.variable.name» = «trigger.container.name»_«trigger.variable.name» - ''') + generatePythonPortVariableInReaction(trigger, inits) } } else if (trigger.variable instanceof Action) { @@ -438,10 +434,7 @@ class PythonGenerator extends CGenerator { if (src.variable instanceof Output) { // Output of a contained reactor generatedParams.add('''«src.container.name»_«src.variable.name»''') - inits.append('''«src.container.name» = Make - ''') - inits.append('''«src.container.name».«src.variable.name» = «src.container.name»_«src.variable.name» - ''') + generatePythonPortVariableInReaction(src, inits) } else { generatedParams.add(src.variable.name) if (src.variable instanceof Input) { @@ -458,11 +451,7 @@ class PythonGenerator extends CGenerator { for (effect : reaction.effects ?: emptyList) { if (effect.variable instanceof Input) { generatedParams.add('''«effect.container.name»_«effect.variable.name»''') - inits.append('''«effect.container.name» = Make - ''') - inits. - append('''«effect.container.name».«effect.variable.name» = «effect.container.name»_«effect.variable.name» - ''') + generatePythonPortVariableInReaction(effect, inits) } else { generatedParams.add(effect.variable.name) if (effect.variable instanceof Port) { @@ -479,6 +468,28 @@ class PythonGenerator extends CGenerator { } } + + /** + * Generate into the specified string builder (inits) the code to + * initialize local variable for port. + * @param + */ + protected def StringBuilder generatePythonPortVariableInReaction(VarRef port, StringBuilder inits) { + if (port.container.widthSpec !== null) { + // It's a bank + inits.append(''' + «port.container.name» = [Make] * len(«port.container.name»_«port.variable.name») + for i in range(len(«port.container.name»_«port.variable.name»)): + «port.container.name»[i].«port.variable.name» = «port.container.name»_«port.variable.name»[i] + ''') + + } else { + inits.append('''«port.container.name» = Make + ''') + inits.append('''«port.container.name».«port.variable.name» = «port.container.name»_«port.variable.name» + ''') + } + } /** * Handle initialization for state variable From 9b2c4a6e6efdfbec46fbc301b9aa153250140daf Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Mon, 24 Jan 2022 14:11:39 -0600 Subject: [PATCH 12/64] Slightly adjusted example --- .../src/Intersection/Carla/CarlaIntersection.lf | 11 ++++++----- .../org/lflang/generator/python/PythonGenerator.xtend | 2 -- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/experimental/Python/src/Intersection/Carla/CarlaIntersection.lf b/experimental/Python/src/Intersection/Carla/CarlaIntersection.lf index 85798eab24..1da7f3ee4a 100644 --- a/experimental/Python/src/Intersection/Carla/CarlaIntersection.lf +++ b/experimental/Python/src/Intersection/Carla/CarlaIntersection.lf @@ -33,14 +33,15 @@ try: sys.version_info.major, sys.version_info.minor, "win-amd64" if os.name == "nt" else "linux-x86_64"))[0]) -except IndexError: - pass +except IndexError as e: + print(e) + sys.stderr.write("ERROR: Could not find the Carla .egg file.\nPlease make sure that" + " CARLA_INSTALL_DIR in CarlaIntersection.lf points to the correct location.\n") try: import carla -except ImportError: - sys.stderr.write("ERROR: Could not find the Carla .egg file.\nPlease make sure that" - " CARLA_INSTALL_DIR in CarlaIntersection.lf points to the correct location.\n") +except ImportError as e: + sys.stderr.write(e) try: import queue diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 24f2034a6f..c88a90f606 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -1923,7 +1923,6 @@ class PythonGenerator extends CGenerator { exit(1); } } - ''') pyObjects.append(''', «reactorName»_py_list''') } else { @@ -2026,7 +2025,6 @@ class PythonGenerator extends CGenerator { exit(1); } } - ''') pyObjects.append(''', «definition.name»_py_list''') } From 94142e13efd44aef8d7bdff95e87cb2694d7616c Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Mon, 24 Jan 2022 14:16:10 -0600 Subject: [PATCH 13/64] Skeleton of the intersection demo in ROS --- .../carla_intersection/__init__.py | 0 .../carla_intersection/rsu.py | 125 ++++++++++++++++++ .../carla_intersection/vehicle.py | 35 +++++ .../launch/intersection_demo.launch.py | 14 ++ .../Carla/ROS/carla_intersection/package.xml | 22 +++ .../resource/carla_intersection | 0 .../Carla/ROS/carla_intersection/setup.cfg | 4 + .../Carla/ROS/carla_intersection/setup.py | 31 +++++ .../carla_intersection/test/test_copyright.py | 23 ++++ .../carla_intersection/test/test_flake8.py | 25 ++++ .../carla_intersection/test/test_pep257.py | 23 ++++ .../carla_intersection_msgs/CMakeLists.txt | 32 +++++ .../ROS/carla_intersection_msgs/msg/Grant.msg | 4 + .../carla_intersection_msgs/msg/Request.msg | 3 + .../ROS/carla_intersection_msgs/package.xml | 24 ++++ 15 files changed, 365 insertions(+) create mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/__init__.py create mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/rsu.py create mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/vehicle.py create mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/launch/intersection_demo.launch.py create mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/package.xml create mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/resource/carla_intersection create mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/setup.cfg create mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/setup.py create mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_copyright.py create mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_flake8.py create mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_pep257.py create mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/CMakeLists.txt create mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/msg/Grant.msg create mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/msg/Request.msg create mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/package.xml diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/__init__.py b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/rsu.py b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/rsu.py new file mode 100644 index 0000000000..1ca3cca00b --- /dev/null +++ b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/rsu.py @@ -0,0 +1,125 @@ +import rclpy +from rclpy.node import Node + +from math import sin, cos, sqrt, atan2, radians, pi + +try: + from math import isclose +except ImportError: + def isclose(a, b, rel_tol=1e-09, abs_tol=0.0): + return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) + +from carla_intersection_msgs import Request, Grant +from geometry_msgs import Vector3 + +BILLION = 1000000000 + +class coordinate: + """ + Represent a GPS coordinate in the form of x (lat), + y (lon), and z (alt). + """ + + def __init__(self, x = 0.0, y = 0.0, z = 0.0): + self.x = x + self.y = y + self.z = z + def __init__(self, vector:Vector3): + self.x = vector.x + self.y = vector.y + self.z = vector.z + def distance(self, coordinate2): + """ + Calculate the great circle distance between two points + on the earth (specified in decimal degrees) + Taken from: https://stackoverflow.com/a/15737218/783868 + """ + # Currently ignores altitude + # Convert decimal degrees to radians + lat1 = radians(self.x) + lon1 = radians(self.y) + lat2 = radians(coordinate2.x) + lon2 = radians(coordinate2.y) + + # Haversine formula + dlon = lon2 - lon1 + dlat = lat2 - lat1 + a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2 + c = 2 * atan2(sqrt(a), sqrt(1 - a)) + # Radius of earth in kilometers is 6371 + km = 6371.0 * c + m = km * 1000.0 + return m + +class RSU(Node): + def __init__(self): + super().__init__("rsu") + + self.declare_parameter('num_entries', 4) + self.declare_parameter('intersection_width', 42) + self.declare_parameter('nominal_speed_in_intersection', 14) + self.declare_parameter('intersection_pos', [-0.000007632,-0.001124366,2.792485]) + + self.grant_ = self.create_publisher(Grant, "rsu_grant", 10) + self.request_ = self.create_subscription(Request, "rsu_request", self.request_callback, 10) + + def request_callback(self, request): + self.active_participants[request.requestor_id] = 1 + if request.speed == 0: + # Avoid division by zero + request.speed = 0.001 + # Calculate the time it will take the approaching vehicle to + # arrive at its current speed. Note that this is + # time from the time the vehicle sends the message + # according to the arriving vehicle's clock. + speed_in_m_per_sec = request.speed + dr = self.intersection_pos.distance(request.position) + print("*** RSU: Vehicle {}'s distance to intersection is {}.".format(request.requestor_id+1, dr)) + arrival_in = dr / speed_in_m_per_sec + + time_message_sent = self.get_clock().now().to_msg() + + # Convert the time interval to nsec (it is in seconds). + arrival_time_ns = time_message_sent.sec * 1000000000 + time_message_sent.nanosec + (arrival_in * BILLION) + + response = Grant() + if arrival_time_ns >= self.earliest_free: + # Vehicle can maintain speed. + response.target_speed = request.speed + response.arrival_time = arrival_time_ns + else: + # Could be smarter than this, but just send the nominal speed in intersection. + response.target_speed = self.nominal_speed_in_intersection + # Vehicle has to slow down and maybe stop. + response.arrival_time = self.earliest_free + + response.intersection_pos = self.intersection_pos + response.requestor_id = request.requestor_id + self.grant_.publish(response) + # Update earliest free on the assumption that the vehicle + # maintains its target speed (on average) within the intersection. + time_in_intersection = (BILLION * self.intersection_width) / (response.target_speed) + self.earliest_free = response.arrival_time + time_in_intersection + + print("*** RSU: Granted access to vehicle {} to enter at " + "time {} with average target velocity {} m/s. Next available time is {}".format( + response.requestor_id + 1, + response.arrival_time, + response.target_speed, + self.earliest_free) + ) + +def main(args=None): + rclpy.init(args=args) + + rsu = RSU() + + rclpy.spin(rsu) + + # Destroy the node explicitly + rsu.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/vehicle.py b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/vehicle.py new file mode 100644 index 0000000000..088d1b2fac --- /dev/null +++ b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/vehicle.py @@ -0,0 +1,35 @@ +import rclpy +from rclpy.node import Node + +import random + +from std_msgs.msg import String + +from carla_intersection_msgs import Request, Grant +from geometry_msgs import Vector3 + +class Vehicle(Node): + def __init__(self): + super().__init__(f"vehicle_{random.randint(0,1000)}") + + self.declare_parameter('vehicle_id', 0) + + self.vehicle_stat_ = self.create_publisher(Grant, "vehicle_grant", 10) + self.request_ = self.create_subscription(Request, "rsu_request", self.request_callback, 10) + + + +def main(args=None): + rclpy.init(args=args) + + ego_vehicle = Vehicle() + + rclpy.spin(ego_vehicle) + + # Destroy the node explicitly + ego_vehicle.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/launch/intersection_demo.launch.py b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/launch/intersection_demo.launch.py new file mode 100644 index 0000000000..86fa5f8052 --- /dev/null +++ b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/launch/intersection_demo.launch.py @@ -0,0 +1,14 @@ +import launch +import launch.actions +import launch.substitutions +import launch_ros.actions + + +def generate_launch_description(): + return launch.LaunchDescription([ + launch_ros.actions.Node( + package='example1', + executable='node1', + output='screen' + ), + ]) diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/package.xml b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/package.xml new file mode 100644 index 0000000000..9c60db9d15 --- /dev/null +++ b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/package.xml @@ -0,0 +1,22 @@ + + + + carla_intersection + 0.0.0 + TODO: Package description + soroush + TODO: License declaration + + rclpy + carla_intersection_msgs + geometry_msgs + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/resource/carla_intersection b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/resource/carla_intersection new file mode 100644 index 0000000000..e69de29bb2 diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/setup.cfg b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/setup.cfg new file mode 100644 index 0000000000..daf4c1293d --- /dev/null +++ b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/carla_intersection +[install] +install_scripts=$base/lib/carla_intersection diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/setup.py b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/setup.py new file mode 100644 index 0000000000..8bfa97772f --- /dev/null +++ b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/setup.py @@ -0,0 +1,31 @@ +from setuptools import setup + +package_name = 'carla_intersection' + +setup( + name=package_name, + version='0.0.0', + packages=[package_name], + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='soroush', + maintainer_email='soroosh129@gmail.com', + description='TODO: Package description', + license='TODO: License declaration', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + ], + }, +) + +entry_points={ + 'console_scripts': [ + 'rsu = carla_intersection.rsu:main', + ], +}, diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_copyright.py b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_copyright.py new file mode 100644 index 0000000000..cc8ff03f79 --- /dev/null +++ b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_copyright.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_flake8.py b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_flake8.py new file mode 100644 index 0000000000..27ee1078ff --- /dev/null +++ b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_pep257.py b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_pep257.py new file mode 100644 index 0000000000..b234a3840f --- /dev/null +++ b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/CMakeLists.txt b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/CMakeLists.txt new file mode 100644 index 0000000000..2bfa33b90f --- /dev/null +++ b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.8) +project(carla_intersection_msgs) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +# uncomment the following section in order to fill in +# further dependencies manually. +# find_package( REQUIRED) + +find_package(rosidl_default_generators REQUIRED) + +rosidl_generate_interfaces(${PROJECT_NAME} + "msg/Request.msg" + "msg/Grant.msg" +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # uncomment the line when a copyright and license is not present in all source files + #set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # uncomment the line when this package is not in a git repo + #set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/msg/Grant.msg b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/msg/Grant.msg new file mode 100644 index 0000000000..edd5a6773a --- /dev/null +++ b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/msg/Grant.msg @@ -0,0 +1,4 @@ +int64 requestor_id +float64 target_speed +builtin_interfaces/Time arrival_time +geometry_msgs/Vector3 intersection_position \ No newline at end of file diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/msg/Request.msg b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/msg/Request.msg new file mode 100644 index 0000000000..e87f9d4e41 --- /dev/null +++ b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/msg/Request.msg @@ -0,0 +1,3 @@ +int64 requestor_id +float64 speed +geometry_msgs/Vector3 position \ No newline at end of file diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/package.xml b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/package.xml new file mode 100644 index 0000000000..57c857aa36 --- /dev/null +++ b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/package.xml @@ -0,0 +1,24 @@ + + + + carla_intersection_msgs + 0.0.0 + TODO: Package description + soroush + TODO: License declaration + + ament_cmake + + rosidl_default_generators + + rosidl_default_runtime + + rosidl_interface_packages + + ament_lint_auto + ament_lint_common + + + ament_cmake + + From 2495ab5a1e988f05bf3ea472abed4418da2ec6bc Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Mon, 24 Jan 2022 14:22:02 -0600 Subject: [PATCH 14/64] Revert "Skeleton of the intersection demo in ROS" This reverts commit 94142e13efd44aef8d7bdff95e87cb2694d7616c. --- .../carla_intersection/__init__.py | 0 .../carla_intersection/rsu.py | 125 ------------------ .../carla_intersection/vehicle.py | 35 ----- .../launch/intersection_demo.launch.py | 14 -- .../Carla/ROS/carla_intersection/package.xml | 22 --- .../resource/carla_intersection | 0 .../Carla/ROS/carla_intersection/setup.cfg | 4 - .../Carla/ROS/carla_intersection/setup.py | 31 ----- .../carla_intersection/test/test_copyright.py | 23 ---- .../carla_intersection/test/test_flake8.py | 25 ---- .../carla_intersection/test/test_pep257.py | 23 ---- .../carla_intersection_msgs/CMakeLists.txt | 32 ----- .../ROS/carla_intersection_msgs/msg/Grant.msg | 4 - .../carla_intersection_msgs/msg/Request.msg | 3 - .../ROS/carla_intersection_msgs/package.xml | 24 ---- 15 files changed, 365 deletions(-) delete mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/__init__.py delete mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/rsu.py delete mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/vehicle.py delete mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/launch/intersection_demo.launch.py delete mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/package.xml delete mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/resource/carla_intersection delete mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/setup.cfg delete mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/setup.py delete mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_copyright.py delete mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_flake8.py delete mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_pep257.py delete mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/CMakeLists.txt delete mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/msg/Grant.msg delete mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/msg/Request.msg delete mode 100644 experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/package.xml diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/__init__.py b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/rsu.py b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/rsu.py deleted file mode 100644 index 1ca3cca00b..0000000000 --- a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/rsu.py +++ /dev/null @@ -1,125 +0,0 @@ -import rclpy -from rclpy.node import Node - -from math import sin, cos, sqrt, atan2, radians, pi - -try: - from math import isclose -except ImportError: - def isclose(a, b, rel_tol=1e-09, abs_tol=0.0): - return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) - -from carla_intersection_msgs import Request, Grant -from geometry_msgs import Vector3 - -BILLION = 1000000000 - -class coordinate: - """ - Represent a GPS coordinate in the form of x (lat), - y (lon), and z (alt). - """ - - def __init__(self, x = 0.0, y = 0.0, z = 0.0): - self.x = x - self.y = y - self.z = z - def __init__(self, vector:Vector3): - self.x = vector.x - self.y = vector.y - self.z = vector.z - def distance(self, coordinate2): - """ - Calculate the great circle distance between two points - on the earth (specified in decimal degrees) - Taken from: https://stackoverflow.com/a/15737218/783868 - """ - # Currently ignores altitude - # Convert decimal degrees to radians - lat1 = radians(self.x) - lon1 = radians(self.y) - lat2 = radians(coordinate2.x) - lon2 = radians(coordinate2.y) - - # Haversine formula - dlon = lon2 - lon1 - dlat = lat2 - lat1 - a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2 - c = 2 * atan2(sqrt(a), sqrt(1 - a)) - # Radius of earth in kilometers is 6371 - km = 6371.0 * c - m = km * 1000.0 - return m - -class RSU(Node): - def __init__(self): - super().__init__("rsu") - - self.declare_parameter('num_entries', 4) - self.declare_parameter('intersection_width', 42) - self.declare_parameter('nominal_speed_in_intersection', 14) - self.declare_parameter('intersection_pos', [-0.000007632,-0.001124366,2.792485]) - - self.grant_ = self.create_publisher(Grant, "rsu_grant", 10) - self.request_ = self.create_subscription(Request, "rsu_request", self.request_callback, 10) - - def request_callback(self, request): - self.active_participants[request.requestor_id] = 1 - if request.speed == 0: - # Avoid division by zero - request.speed = 0.001 - # Calculate the time it will take the approaching vehicle to - # arrive at its current speed. Note that this is - # time from the time the vehicle sends the message - # according to the arriving vehicle's clock. - speed_in_m_per_sec = request.speed - dr = self.intersection_pos.distance(request.position) - print("*** RSU: Vehicle {}'s distance to intersection is {}.".format(request.requestor_id+1, dr)) - arrival_in = dr / speed_in_m_per_sec - - time_message_sent = self.get_clock().now().to_msg() - - # Convert the time interval to nsec (it is in seconds). - arrival_time_ns = time_message_sent.sec * 1000000000 + time_message_sent.nanosec + (arrival_in * BILLION) - - response = Grant() - if arrival_time_ns >= self.earliest_free: - # Vehicle can maintain speed. - response.target_speed = request.speed - response.arrival_time = arrival_time_ns - else: - # Could be smarter than this, but just send the nominal speed in intersection. - response.target_speed = self.nominal_speed_in_intersection - # Vehicle has to slow down and maybe stop. - response.arrival_time = self.earliest_free - - response.intersection_pos = self.intersection_pos - response.requestor_id = request.requestor_id - self.grant_.publish(response) - # Update earliest free on the assumption that the vehicle - # maintains its target speed (on average) within the intersection. - time_in_intersection = (BILLION * self.intersection_width) / (response.target_speed) - self.earliest_free = response.arrival_time + time_in_intersection - - print("*** RSU: Granted access to vehicle {} to enter at " - "time {} with average target velocity {} m/s. Next available time is {}".format( - response.requestor_id + 1, - response.arrival_time, - response.target_speed, - self.earliest_free) - ) - -def main(args=None): - rclpy.init(args=args) - - rsu = RSU() - - rclpy.spin(rsu) - - # Destroy the node explicitly - rsu.destroy_node() - rclpy.shutdown() - - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/vehicle.py b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/vehicle.py deleted file mode 100644 index 088d1b2fac..0000000000 --- a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/carla_intersection/vehicle.py +++ /dev/null @@ -1,35 +0,0 @@ -import rclpy -from rclpy.node import Node - -import random - -from std_msgs.msg import String - -from carla_intersection_msgs import Request, Grant -from geometry_msgs import Vector3 - -class Vehicle(Node): - def __init__(self): - super().__init__(f"vehicle_{random.randint(0,1000)}") - - self.declare_parameter('vehicle_id', 0) - - self.vehicle_stat_ = self.create_publisher(Grant, "vehicle_grant", 10) - self.request_ = self.create_subscription(Request, "rsu_request", self.request_callback, 10) - - - -def main(args=None): - rclpy.init(args=args) - - ego_vehicle = Vehicle() - - rclpy.spin(ego_vehicle) - - # Destroy the node explicitly - ego_vehicle.destroy_node() - rclpy.shutdown() - - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/launch/intersection_demo.launch.py b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/launch/intersection_demo.launch.py deleted file mode 100644 index 86fa5f8052..0000000000 --- a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/launch/intersection_demo.launch.py +++ /dev/null @@ -1,14 +0,0 @@ -import launch -import launch.actions -import launch.substitutions -import launch_ros.actions - - -def generate_launch_description(): - return launch.LaunchDescription([ - launch_ros.actions.Node( - package='example1', - executable='node1', - output='screen' - ), - ]) diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/package.xml b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/package.xml deleted file mode 100644 index 9c60db9d15..0000000000 --- a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/package.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - carla_intersection - 0.0.0 - TODO: Package description - soroush - TODO: License declaration - - rclpy - carla_intersection_msgs - geometry_msgs - - ament_copyright - ament_flake8 - ament_pep257 - python3-pytest - - - ament_python - - diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/resource/carla_intersection b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/resource/carla_intersection deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/setup.cfg b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/setup.cfg deleted file mode 100644 index daf4c1293d..0000000000 --- a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/setup.cfg +++ /dev/null @@ -1,4 +0,0 @@ -[develop] -script_dir=$base/lib/carla_intersection -[install] -install_scripts=$base/lib/carla_intersection diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/setup.py b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/setup.py deleted file mode 100644 index 8bfa97772f..0000000000 --- a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/setup.py +++ /dev/null @@ -1,31 +0,0 @@ -from setuptools import setup - -package_name = 'carla_intersection' - -setup( - name=package_name, - version='0.0.0', - packages=[package_name], - data_files=[ - ('share/ament_index/resource_index/packages', - ['resource/' + package_name]), - ('share/' + package_name, ['package.xml']), - ], - install_requires=['setuptools'], - zip_safe=True, - maintainer='soroush', - maintainer_email='soroosh129@gmail.com', - description='TODO: Package description', - license='TODO: License declaration', - tests_require=['pytest'], - entry_points={ - 'console_scripts': [ - ], - }, -) - -entry_points={ - 'console_scripts': [ - 'rsu = carla_intersection.rsu:main', - ], -}, diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_copyright.py b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_copyright.py deleted file mode 100644 index cc8ff03f79..0000000000 --- a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_copyright.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2015 Open Source Robotics Foundation, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ament_copyright.main import main -import pytest - - -@pytest.mark.copyright -@pytest.mark.linter -def test_copyright(): - rc = main(argv=['.', 'test']) - assert rc == 0, 'Found errors' diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_flake8.py b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_flake8.py deleted file mode 100644 index 27ee1078ff..0000000000 --- a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_flake8.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2017 Open Source Robotics Foundation, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ament_flake8.main import main_with_errors -import pytest - - -@pytest.mark.flake8 -@pytest.mark.linter -def test_flake8(): - rc, errors = main_with_errors(argv=[]) - assert rc == 0, \ - 'Found %d code style errors / warnings:\n' % len(errors) + \ - '\n'.join(errors) diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_pep257.py b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_pep257.py deleted file mode 100644 index b234a3840f..0000000000 --- a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection/test/test_pep257.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2015 Open Source Robotics Foundation, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ament_pep257.main import main -import pytest - - -@pytest.mark.linter -@pytest.mark.pep257 -def test_pep257(): - rc = main(argv=['.', 'test']) - assert rc == 0, 'Found code style errors / warnings' diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/CMakeLists.txt b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/CMakeLists.txt deleted file mode 100644 index 2bfa33b90f..0000000000 --- a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -cmake_minimum_required(VERSION 3.8) -project(carla_intersection_msgs) - -if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - add_compile_options(-Wall -Wextra -Wpedantic) -endif() - -# find dependencies -find_package(ament_cmake REQUIRED) -# uncomment the following section in order to fill in -# further dependencies manually. -# find_package( REQUIRED) - -find_package(rosidl_default_generators REQUIRED) - -rosidl_generate_interfaces(${PROJECT_NAME} - "msg/Request.msg" - "msg/Grant.msg" -) - -if(BUILD_TESTING) - find_package(ament_lint_auto REQUIRED) - # the following line skips the linter which checks for copyrights - # uncomment the line when a copyright and license is not present in all source files - #set(ament_cmake_copyright_FOUND TRUE) - # the following line skips cpplint (only works in a git repo) - # uncomment the line when this package is not in a git repo - #set(ament_cmake_cpplint_FOUND TRUE) - ament_lint_auto_find_test_dependencies() -endif() - -ament_package() diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/msg/Grant.msg b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/msg/Grant.msg deleted file mode 100644 index edd5a6773a..0000000000 --- a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/msg/Grant.msg +++ /dev/null @@ -1,4 +0,0 @@ -int64 requestor_id -float64 target_speed -builtin_interfaces/Time arrival_time -geometry_msgs/Vector3 intersection_position \ No newline at end of file diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/msg/Request.msg b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/msg/Request.msg deleted file mode 100644 index e87f9d4e41..0000000000 --- a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/msg/Request.msg +++ /dev/null @@ -1,3 +0,0 @@ -int64 requestor_id -float64 speed -geometry_msgs/Vector3 position \ No newline at end of file diff --git a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/package.xml b/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/package.xml deleted file mode 100644 index 57c857aa36..0000000000 --- a/experimental/Python/src/Intersection/Carla/ROS/carla_intersection_msgs/package.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - carla_intersection_msgs - 0.0.0 - TODO: Package description - soroush - TODO: License declaration - - ament_cmake - - rosidl_default_generators - - rosidl_default_runtime - - rosidl_interface_packages - - ament_lint_auto - ament_lint_common - - - ament_cmake - - From 0cdba6d43801e3bd79027f608c055cea20857e10 Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Mon, 24 Jan 2022 14:22:18 -0600 Subject: [PATCH 15/64] Revert "Slightly adjusted example" This reverts commit 9b2c4a6e6efdfbec46fbc301b9aa153250140daf. --- .../src/Intersection/Carla/CarlaIntersection.lf | 11 +++++------ .../org/lflang/generator/python/PythonGenerator.xtend | 2 ++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/experimental/Python/src/Intersection/Carla/CarlaIntersection.lf b/experimental/Python/src/Intersection/Carla/CarlaIntersection.lf index 1da7f3ee4a..85798eab24 100644 --- a/experimental/Python/src/Intersection/Carla/CarlaIntersection.lf +++ b/experimental/Python/src/Intersection/Carla/CarlaIntersection.lf @@ -33,15 +33,14 @@ try: sys.version_info.major, sys.version_info.minor, "win-amd64" if os.name == "nt" else "linux-x86_64"))[0]) -except IndexError as e: - print(e) - sys.stderr.write("ERROR: Could not find the Carla .egg file.\nPlease make sure that" - " CARLA_INSTALL_DIR in CarlaIntersection.lf points to the correct location.\n") +except IndexError: + pass try: import carla -except ImportError as e: - sys.stderr.write(e) +except ImportError: + sys.stderr.write("ERROR: Could not find the Carla .egg file.\nPlease make sure that" + " CARLA_INSTALL_DIR in CarlaIntersection.lf points to the correct location.\n") try: import queue diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index c88a90f606..24f2034a6f 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -1923,6 +1923,7 @@ class PythonGenerator extends CGenerator { exit(1); } } + ''') pyObjects.append(''', «reactorName»_py_list''') } else { @@ -2025,6 +2026,7 @@ class PythonGenerator extends CGenerator { exit(1); } } + ''') pyObjects.append(''', «definition.name»_py_list''') } From 67bb9a57f03407cff13e83e9cb7da952726220f8 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Thu, 13 Jan 2022 17:51:16 -0800 Subject: [PATCH 16/64] Ported LinguaFrancaScopingTest to Java --- .../compiler/LinguaFrancaScopingTest.java | 244 ++++++++++++++++++ .../compiler/LinguaFrancaScopingTest.xtend | 155 ----------- 2 files changed, 244 insertions(+), 155 deletions(-) create mode 100644 org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.java delete mode 100644 org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.xtend diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.java new file mode 100644 index 0000000000..0fbff89850 --- /dev/null +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.java @@ -0,0 +1,244 @@ +/* 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. +***************/ +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.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.extension.ExtendWith; +import org.lflang.generator.LFGenerator; +import com.google.inject.Provider; + +@ExtendWith(InjectionExtension.class) +@InjectWith(LFInjectorProvider.class) + +/** + * 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 { +// Java 17: +// Model model = """ +// target C; +// reactor From { +// output y:int; +// } +// reactor To { +// input x:int; +// } +// +// main reactor { +// a = new From(); +// d = new To(); +// a.x -> d.y; +// } +// """; +// Java 11: + + Model model = parser.parse(String.join( + System.getProperty("line.separator"), + "target C;", + "reactor From {", + " output y:int;", + "}", + "reactor To {", + " input x:int;", + "}\n", + "main reactor {", + " a = new From();", + " d = new To();", + " 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 { +// Java 17: +// Model model = """ +// target C; +// main reactor { +// reaction(unknown) {==} +// } +// """ +// Java 11: + Model model = parser.parse(String.join( + System.getProperty("line.separator"), + "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 { +// Java 17: +// Model model = """ +// target C; +// main reactor { +// reaction() unknown {==} +// } +// """ +// Java 11: + Model model = parser.parse(String.join( + System.getProperty("line.separator"), + "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 { +// Java 17: +// Model model = """ +// target C; +// main reactor { +// reaction() -> unknown {==} +// } +// """ +// Java 11: + + Model model = parser.parse(String.join( + System.getProperty("line.separator"), + "target C;", + "main reactor {", + " reaction() -> 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/LinguaFrancaScopingTest.xtend b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.xtend deleted file mode 100644 index ea41293187..0000000000 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.xtend +++ /dev/null @@ -1,155 +0,0 @@ -/* 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. -***************/ -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.lflang.lf.Model -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.^extension.ExtendWith -import org.eclipse.xtext.testing.validation.ValidationTestHelper -import org.lflang.lf.LfPackage -import org.eclipse.xtext.linking.impl.XtextLinkingDiagnostic -import org.lflang.tests.LFInjectorProvider - -@ExtendWith(InjectionExtension) -@InjectWith(LFInjectorProvider) - -/** - * Test harness for ensuring that cross-references are - * established correctly and reported when faulty. - */ -class LinguaFrancaScopingTest { - @Inject extension ParseHelper - @Inject extension ValidationTestHelper - - /** - * Ensure that invalid references to contained reactors are reported. - */ - @Test - def void unresolvedReactorReference() { - val model = ''' - target C; - reactor From { - output y:int; - } - reactor To { - input x:int; - } - - main reactor WrongConnect { - a = new From(); - d = new To(); - s.y -> d.x; - } - '''.parse - - Assertions.assertNotNull(model) - Assertions.assertTrue(model.eResource.errors.isEmpty, - "Encountered unexpected error while parsing.") - model.assertError(LfPackage::eINSTANCE.varRef, - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Instantiation 's'") - model.assertError(LfPackage::eINSTANCE.varRef, - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Variable 'y'") - } - - - /** - * Ensure that invalid references to ports - * of contained reactors are reported. - */ - @Test - def void unresolvedHierarchicalPortReference() { - val model = ''' - target C; - reactor From { - output y:int; - } - reactor To { - input x:int; - } - - main reactor WrongConnect { - a = new From(); - d = new To(); - a.x -> d.y; - } - '''.parse - - Assertions.assertNotNull(model) - Assertions.assertTrue(model.eResource.errors.isEmpty, - "Encountered unexpected error while parsing.") - model.assertError(LfPackage::eINSTANCE.varRef, - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Variable 'x'") - model.assertError(LfPackage::eINSTANCE.varRef, - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Variable 'y'") - } - - @Test - def void unresolvedReferenceInTriggerClause() { - ''' - target C; - main reactor { - reaction(unknown) {==} - } - '''.parse.assertError(LfPackage::eINSTANCE.varRef, - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Variable 'unknown'.") - } - - @Test - def void unresolvedReferenceInUseClause() { - ''' - target C; - main reactor { - reaction() unknown {==} - } - '''.parse.assertError(LfPackage::eINSTANCE.varRef, - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Variable 'unknown'.") - } - - @Test - def void unresolvedReferenceInEffectsClause() { - ''' - target C; - main reactor { - reaction() -> unknown {==} - } - '''.parse.assertError(LfPackage::eINSTANCE.varRef, - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Variable 'unknown'.") - } - -} From 413e8e3dcf71ae695dc955a068a09a2a4c9bb5d9 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Thu, 13 Jan 2022 23:27:38 -0800 Subject: [PATCH 17/64] Ported one more class and added a utility. --- .../compiler/LinguaFrancaASTUtilsTest.java | 299 ++++++++++++++++++ .../compiler/LinguaFrancaASTUtilsTest.xtend | 223 ------------- .../compiler/LinguaFrancaScopingTest.java | 2 +- org.lflang/src/org/lflang/util/XtendUtil.java | 29 +- 4 files changed, 328 insertions(+), 225 deletions(-) create mode 100644 org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java delete mode 100644 org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.xtend diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java new file mode 100644 index 0000000000..def20a0098 --- /dev/null +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java @@ -0,0 +1,299 @@ +/* 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. +***************/ + +package org.lflang.tests.compiler; + +import java.util.LinkedList; +import java.util.List; +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.eclipse.emf.ecore.EObject; +import org.lflang.ASTUtils; +import org.lflang.lf.Instantiation; +import org.lflang.lf.Model; +import org.lflang.lf.Parameter; +import org.lflang.lf.StateVar; +import java.util.stream.Collectors; +import org.lflang.tests.LFInjectorProvider; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.lflang.ASTUtils.*; +import static org.lflang.util.XtendUtil.*; + +/** + * Collection of unit tests on the ASTutils. + * + * @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 { +// Java 17: +// Model model = parser.parse(""" +// target Cpp; +// main reactor Foo { +// state a(); +// state b:int(3); +// state c:int[](1,2,3); +// state d(1 sec); +// state e(1 sec, 2 sec, 3 sec); +// } +// """); +// Java 11: + Model model = parser.parse(String.join( + System.getProperty("line.separator"), + "target Cpp;", + "main reactor {", + " state a();", + " state b:int(3);", + " state c:int[](1,2,3);", + " state d(1 sec);", + " state e(1 sec, 2 sec, 3 sec);", + "}" + )); + + + + 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 { +// Java 17: +// Model model = parser.parse(""" +// target Cpp; +// main reactor Foo { +// state a; +// state b:int; +// state c:int[]; +// state d:time; +// state e:time[]; +// } +// ''' +// Java 11: + Model model = parser.parse(String.join( + System.getProperty("line.separator"), + "target Cpp;", + "main reactor {", + " state a;", + " state b:int;", + " state c:int[];", + " state d:time;", + " state e:time;", + "}" + )); + + 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)); + } + }); + + } + + /** + * 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 { + +// Java 17: +// Model model = parser.parse(""" +// target C; +// 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); +// } +// """ +// Java 11: + + Model model = parser.parse(String.join( + System.getProperty("line.separator"), + "target C;", + "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);", + "}" + )); + + Assertions.assertNotNull(model); + Assertions.assertTrue(model.eResource().getErrors().isEmpty(), + "Encountered unexpected error while parsing: " + + model.eResource().getErrors()); + + // Find all the Instantiations. + Instantiation a1 = null; + Instantiation a2 = null; + Instantiation b1 = null; + Instantiation b2 = null; + + + List objs = asStream(model.eAllContents()).filter(obj -> + (obj instanceof Instantiation) + ).collect(Collectors.toList()); + for (EObject obj : objs) { + if (obj instanceof Instantiation) { + Instantiation inst = (Instantiation)obj; + if (inst.getName() == "a1") { + a1 = inst; + } + if (inst.getName() == "a2") { + a2 = inst; + } + if (inst.getName() == "b1") { + b1 = inst; + } + if (inst.getName() == "b1") { + b2 = inst; + } + } + } + + // Construct all relevant instantiation lists. + var list_a1 = new LinkedList(); + list_a1.add(a1); + var list_a2 = new LinkedList(); + list_a2.add(a2); + var list_a1b1 = new LinkedList(); + list_a1b1.add(a1); + list_a1b1.add(b1); + var list_a2b1 = new LinkedList(); + list_a2b1.add(a2); + list_a2b1.add(b1); + var list_a1b2 = new LinkedList(); + list_a1b2.add(a1); + list_a1b2.add(b2); + var list_a2b2 = new LinkedList(); + list_a2b2.add(a2); + list_a2b2.add(b2); + var list_b1 = new LinkedList(); + list_b1.add(b1); + var list_b2 = new LinkedList(); + list_b2.add(b2); + + /* 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.assertEquals(values.get(0).getLiteral(), "1"); + + values = ASTUtils.initialValue(parameter, list_a1); + Assertions.assertEquals(values.get(0).getLiteral(), "2"); + + values = ASTUtils.initialValue(parameter, list_a2); + Assertions.assertEquals(values.get(0).getLiteral(), "-1"); + + values = ASTUtils.initialValue(parameter, list_a1b1); + Assertions.assertEquals(values.get(0).getLiteral(), "3"); + + values = ASTUtils.initialValue(parameter, list_a2b1); + Assertions.assertEquals(values.get(0).getLiteral(), "-1"); + + values = ASTUtils.initialValue(parameter, list_a1b2); + Assertions.assertEquals(values.get(0).getLiteral(), "-2"); + + values = ASTUtils.initialValue(parameter, list_a2b2); + Assertions.assertEquals(values.get(0).getLiteral(), "-1"); + } else if (parameter.getName() == "y") { + var values = ASTUtils.initialValue(parameter, null); + Assertions.assertEquals(values.get(0).getLiteral(), "2"); + + try { + values = ASTUtils.initialValue(parameter, list_a1); + } catch (IllegalArgumentException ex) { + Assertions.assertTrue(ex.getMessage().startsWith("Parameter y is not")); + } + + values = ASTUtils.initialValue(parameter, list_b1); + Assertions.assertEquals(values.get(0).getLiteral(), "3"); + + values = ASTUtils.initialValue(parameter, list_b2); + Assertions.assertEquals(values.get(0).getLiteral(), "-2"); + } + } + }); + } +} diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.xtend b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.xtend deleted file mode 100644 index 48b88a71d3..0000000000 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.xtend +++ /dev/null @@ -1,223 +0,0 @@ -/* 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. -***************/ - -package org.lflang.tests.compiler - -import java.util.LinkedList -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.lflang.ASTUtils -import org.lflang.lf.Instantiation -import org.lflang.lf.Model -import org.lflang.lf.Parameter -import org.lflang.lf.StateVar -import org.lflang.tests.LFInjectorProvider -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.^extension.ExtendWith - -import static extension org.lflang.ASTUtils.* - -/** - * Collection of unit tests on the ASTutils. - * - * @author{Christian Menard } - */ -@ExtendWith(InjectionExtension) -@InjectWith(LFInjectorProvider) -class LinguaFrancaASTUtilsTest { - @Inject extension ParseHelper - - /** - * Test that isInititialized returns true for inititialized state variables - */ - @Test - def void initializedState() { - val model = ''' - target Cpp; - main reactor Foo { - state a(); - state b:int(3); - state c:int[](1,2,3); - state d(1 sec); - state e(1 sec, 2 sec, 3 sec); - } - '''.parse - - Assertions.assertNotNull(model) - Assertions.assertTrue(model.eResource.errors.isEmpty, - "Encountered unexpected error while parsing: " + - model.eResource.errors) - - for (state : model.eAllContents.filter(StateVar).toList) { - Assertions.assertTrue(state.isInitialized) - } - } - - /** - * Test that isInititialized returns false for uninititialized state variables - */ - @Test - def void uninitializedState() { - val model = ''' - target Cpp; - main reactor Foo { - state a; - state b:int; - state c:int[]; - state d:time; - state e:time[]; - } - '''.parse - - Assertions.assertNotNull(model) - Assertions.assertTrue(model.eResource.errors.isEmpty, - "Encountered unexpected error while parsing: " + - model.eResource.errors) - - for (state : model.eAllContents.filter(StateVar).toList) { - Assertions.assertFalse(state.isInitialized) - } - } - - /** - * 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 - def void initialValue() { - val model = ''' - target C; - 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); - } - '''.parse - Assertions.assertNotNull(model) - Assertions.assertTrue(model.eResource.errors.isEmpty, - "Encountered unexpected error while parsing: " + - model.eResource.errors) - - // Find all the Instantiations. - var a1 = null as Instantiation; - var a2 = null as Instantiation; - var b1 = null as Instantiation; - var b2 = null as Instantiation; - for (instantiation: model.eAllContents.filter(Instantiation).toList) { - switch (instantiation.name) { - case "a1": a1 = instantiation - case "a2": a2 = instantiation - case "b1": b1 = instantiation - case "b2": b2 = instantiation - } - } - - // Construct all relevant instantiation lists. - val list_a1 = new LinkedList(); - list_a1.add(a1); - val list_a2 = new LinkedList(); - list_a2.add(a2); - val list_a1b1 = new LinkedList(); - list_a1b1.add(a1); - list_a1b1.add(b1); - val list_a2b1 = new LinkedList(); - list_a2b1.add(a2); - list_a2b1.add(b1); - val list_a1b2 = new LinkedList(); - list_a1b2.add(a1); - list_a1b2.add(b2); - val list_a2b2 = new LinkedList(); - list_a2b2.add(a2); - list_a2b2.add(b2); - val list_b1 = new LinkedList(); - list_b1.add(b1); - val list_b2 = new LinkedList(); - list_b2.add(b2); - - /* 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 - */ - for (parameter : model.eAllContents.filter(Parameter).toList) { - if (parameter.name == 'x') { - var values = ASTUtils.initialValue(parameter, null); - Assertions.assertEquals(values.get(0).literal, "1"); - - values = ASTUtils.initialValue(parameter, list_a1); - Assertions.assertEquals(values.get(0).literal, "2"); - - values = ASTUtils.initialValue(parameter, list_a2); - Assertions.assertEquals(values.get(0).literal, "-1"); - - values = ASTUtils.initialValue(parameter, list_a1b1); - Assertions.assertEquals(values.get(0).literal, "3"); - - values = ASTUtils.initialValue(parameter, list_a2b1); - Assertions.assertEquals(values.get(0).literal, "-1"); - - values = ASTUtils.initialValue(parameter, list_a1b2); - Assertions.assertEquals(values.get(0).literal, "-2"); - - values = ASTUtils.initialValue(parameter, list_a2b2); - Assertions.assertEquals(values.get(0).literal, "-1"); - } else if (parameter.name == 'y') { - var values = ASTUtils.initialValue(parameter, null); - Assertions.assertEquals(values.get(0).literal, "2"); - - try { - values = ASTUtils.initialValue(parameter, list_a1); - } catch (IllegalArgumentException ex) { - Assertions.assertTrue(ex.message.startsWith("Parameter y is not")); - } - - values = ASTUtils.initialValue(parameter, list_b1); - Assertions.assertEquals(values.get(0).literal, "3"); - - values = ASTUtils.initialValue(parameter, list_b2); - Assertions.assertEquals(values.get(0).literal, "-2"); - } - } - } -} 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 0fbff89850..70c3880a49 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.java @@ -149,7 +149,7 @@ public void unresolvedHierarchicalPortReference() throws Exception { "}", "reactor To {", " input x:int;", - "}\n", + "}", "main reactor {", " a = new From();", " d = new To();", diff --git a/org.lflang/src/org/lflang/util/XtendUtil.java b/org.lflang/src/org/lflang/util/XtendUtil.java index 0cf944cee4..4a89307b4e 100644 --- a/org.lflang/src/org/lflang/util/XtendUtil.java +++ b/org.lflang/src/org/lflang/util/XtendUtil.java @@ -24,10 +24,15 @@ package org.lflang.util; +import java.util.Iterator; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + /** * A utility class for things missing from Xtend. * * @author Clément Fournier + * @author Marten Lohstroh */ public final class XtendUtil { @@ -36,10 +41,32 @@ private XtendUtil() { } /** - * Returns the bitwise OR of the two given long integers. + * Return the bitwise OR of the two given long integers. * Xtend doesn't support bitwise operators. */ public static long longOr(long a, long b) { return a | b; } + + /** + * 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 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); + } } From 1ef5a513f5f75df8bb8c4683a3ea4591bdb1b278 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Wed, 19 Jan 2022 21:06:06 -0800 Subject: [PATCH 18/64] Respond to comments from @oowekyala --- .../compiler/LinguaFrancaASTUtilsTest.java | 150 +++++++++--------- 1 file changed, 73 insertions(+), 77 deletions(-) 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 def20a0098..d0cbf1674a 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java @@ -27,26 +27,28 @@ package org.lflang.tests.compiler; -import java.util.LinkedList; +import static org.lflang.ASTUtils.isInitialized; +import static org.lflang.util.XtendUtil.asStream; + import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + import javax.inject.Inject; + +import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.testing.InjectWith; import org.eclipse.xtext.testing.extensions.InjectionExtension; import org.eclipse.xtext.testing.util.ParseHelper; -import org.eclipse.emf.ecore.EObject; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.lflang.ASTUtils; import org.lflang.lf.Instantiation; import org.lflang.lf.Model; import org.lflang.lf.Parameter; import org.lflang.lf.StateVar; -import java.util.stream.Collectors; import org.lflang.tests.LFInjectorProvider; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import static org.lflang.ASTUtils.*; -import static org.lflang.util.XtendUtil.*; /** * Collection of unit tests on the ASTutils. @@ -145,6 +147,38 @@ public void uninitializedState() throws Exception { }); } + /** + * Return a map from strings to instantiations given a model. + * + * @param model The model to discover instantiations in. + */ + private Map getInsts(Model model) { + Instantiation a1 = null; + Instantiation a2 = null; + Instantiation b1 = null; + Instantiation b2 = null; + + List objs = asStream(model.eAllContents()) + .filter(obj -> (obj instanceof Instantiation)) + .collect(Collectors.toList()); + + for (EObject obj : objs) { + if (obj instanceof Instantiation) { + Instantiation inst = (Instantiation) obj; + if (inst.getName().equals("a1")) { + a1 = inst; + } else if (inst.getName().equals("a2")) { + a2 = inst; + } else if (inst.getName().equals("b1")) { + b1 = inst; + } else if (inst.getName().equals("b2")) { + b2 = inst; + } + } + } + + return Map.of("a1", a1, "a2", a2, "b1", b1, "b2", b2); + } /** * Test reading initial values of parameters. @@ -188,55 +222,7 @@ public void initialValue() throws Exception { "Encountered unexpected error while parsing: " + model.eResource().getErrors()); - // Find all the Instantiations. - Instantiation a1 = null; - Instantiation a2 = null; - Instantiation b1 = null; - Instantiation b2 = null; - - - List objs = asStream(model.eAllContents()).filter(obj -> - (obj instanceof Instantiation) - ).collect(Collectors.toList()); - for (EObject obj : objs) { - if (obj instanceof Instantiation) { - Instantiation inst = (Instantiation)obj; - if (inst.getName() == "a1") { - a1 = inst; - } - if (inst.getName() == "a2") { - a2 = inst; - } - if (inst.getName() == "b1") { - b1 = inst; - } - if (inst.getName() == "b1") { - b2 = inst; - } - } - } - - // Construct all relevant instantiation lists. - var list_a1 = new LinkedList(); - list_a1.add(a1); - var list_a2 = new LinkedList(); - list_a2.add(a2); - var list_a1b1 = new LinkedList(); - list_a1b1.add(a1); - list_a1b1.add(b1); - var list_a2b1 = new LinkedList(); - list_a2b1.add(a2); - list_a2b1.add(b1); - var list_a1b2 = new LinkedList(); - list_a1b2.add(a1); - list_a1b2.add(b2); - var list_a2b2 = new LinkedList(); - list_a2b2.add(a2); - list_a2b2.add(b2); - var list_b1 = new LinkedList(); - list_b1.add(b1); - var list_b2 = new LinkedList(); - list_b2.add(b2); + var map = getInsts(model); /* Check for this: * initialValue(x, null) returns 1 @@ -259,38 +245,48 @@ public void initialValue() throws Exception { if (parameter.getName() == "x") { var values = ASTUtils.initialValue(parameter, null); Assertions.assertEquals(values.get(0).getLiteral(), "1"); - - values = ASTUtils.initialValue(parameter, list_a1); + + values = ASTUtils.initialValue(parameter, + List.of(map.get("a1"))); Assertions.assertEquals(values.get(0).getLiteral(), "2"); - - values = ASTUtils.initialValue(parameter, list_a2); + + values = ASTUtils.initialValue(parameter, + List.of(map.get("a2"))); Assertions.assertEquals(values.get(0).getLiteral(), "-1"); - - values = ASTUtils.initialValue(parameter, list_a1b1); + + values = ASTUtils.initialValue(parameter, + List.of(map.get("a1"), map.get("b1"))); Assertions.assertEquals(values.get(0).getLiteral(), "3"); - - values = ASTUtils.initialValue(parameter, list_a2b1); + + values = ASTUtils.initialValue(parameter, + List.of(map.get("a2"), map.get("b1"))); Assertions.assertEquals(values.get(0).getLiteral(), "-1"); - - values = ASTUtils.initialValue(parameter, list_a1b2); + + values = ASTUtils.initialValue(parameter, + List.of(map.get("a1"), map.get("b2"))); Assertions.assertEquals(values.get(0).getLiteral(), "-2"); - - values = ASTUtils.initialValue(parameter, list_a2b2); + + values = ASTUtils.initialValue(parameter, + List.of(map.get("a2"), map.get("b2"))); Assertions.assertEquals(values.get(0).getLiteral(), "-1"); } else if (parameter.getName() == "y") { var values = ASTUtils.initialValue(parameter, null); Assertions.assertEquals(values.get(0).getLiteral(), "2"); - + try { - values = ASTUtils.initialValue(parameter, list_a1); + values = ASTUtils.initialValue(parameter, + List.of(map.get("a1"))); } catch (IllegalArgumentException ex) { - Assertions.assertTrue(ex.getMessage().startsWith("Parameter y is not")); + Assertions.assertTrue(ex.getMessage() + .startsWith("Parameter y is not")); } - - values = ASTUtils.initialValue(parameter, list_b1); + + values = ASTUtils.initialValue(parameter, + List.of(map.get("b1"))); Assertions.assertEquals(values.get(0).getLiteral(), "3"); - - values = ASTUtils.initialValue(parameter, list_b2); + + values = ASTUtils.initialValue(parameter, + List.of(map.get("b2"))); Assertions.assertEquals(values.get(0).getLiteral(), "-2"); } } From 943cae08f5d1c51234244c126d29e119a27b5050 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Sat, 22 Jan 2022 22:08:18 -0800 Subject: [PATCH 19/64] Update org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Clément Fournier --- .../compiler/LinguaFrancaASTUtilsTest.java | 29 +++---------------- 1 file changed, 4 insertions(+), 25 deletions(-) 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 d0cbf1674a..7b738bf043 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java @@ -153,31 +153,10 @@ public void uninitializedState() throws Exception { * @param model The model to discover instantiations in. */ private Map getInsts(Model model) { - Instantiation a1 = null; - Instantiation a2 = null; - Instantiation b1 = null; - Instantiation b2 = null; - - List objs = asStream(model.eAllContents()) - .filter(obj -> (obj instanceof Instantiation)) - .collect(Collectors.toList()); - - for (EObject obj : objs) { - if (obj instanceof Instantiation) { - Instantiation inst = (Instantiation) obj; - if (inst.getName().equals("a1")) { - a1 = inst; - } else if (inst.getName().equals("a2")) { - a2 = inst; - } else if (inst.getName().equals("b1")) { - b1 = inst; - } else if (inst.getName().equals("b2")) { - b2 = inst; - } - } - } - - return Map.of("a1", a1, "a2", a2, "b1", b1, "b2", b2); + return asStream(model.eAllContents()) + .filter(obj -> obj instanceof Instantiation) + .map(obj -> (Instantiation) obj) + .collect(Collectors.toMap(Instantiation::getName, it -> it)); } /** From 0d7363ab8e788c9f951c0b309b4e6ac0a574a174 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Thu, 20 Jan 2022 15:03:50 -0800 Subject: [PATCH 20/64] cleanup scripts --- .../src/DigitalTwin/KeyFob/{ => scripts/cloud}/cleanup.sh | 0 .../DigitalTwin/KeyFob/{ => scripts/cloud}/run_local_copy.sh | 0 .../Python/src/DigitalTwin/KeyFob/{ => scripts/cloud}/setup.sh | 0 example/Python/src/DigitalTwin/KeyFob/scripts/local/run_fob.sh | 2 ++ example/Python/src/DigitalTwin/KeyFob/scripts/local/run_rti.sh | 1 + example/Python/src/DigitalTwin/KeyFob/scripts/local/run_twin.sh | 2 ++ 6 files changed, 5 insertions(+) rename example/Python/src/DigitalTwin/KeyFob/{ => scripts/cloud}/cleanup.sh (100%) rename example/Python/src/DigitalTwin/KeyFob/{ => scripts/cloud}/run_local_copy.sh (100%) rename example/Python/src/DigitalTwin/KeyFob/{ => scripts/cloud}/setup.sh (100%) create mode 100755 example/Python/src/DigitalTwin/KeyFob/scripts/local/run_fob.sh create mode 100755 example/Python/src/DigitalTwin/KeyFob/scripts/local/run_rti.sh create mode 100755 example/Python/src/DigitalTwin/KeyFob/scripts/local/run_twin.sh diff --git a/example/Python/src/DigitalTwin/KeyFob/cleanup.sh b/example/Python/src/DigitalTwin/KeyFob/scripts/cloud/cleanup.sh similarity index 100% rename from example/Python/src/DigitalTwin/KeyFob/cleanup.sh rename to example/Python/src/DigitalTwin/KeyFob/scripts/cloud/cleanup.sh diff --git a/example/Python/src/DigitalTwin/KeyFob/run_local_copy.sh b/example/Python/src/DigitalTwin/KeyFob/scripts/cloud/run_local_copy.sh similarity index 100% rename from example/Python/src/DigitalTwin/KeyFob/run_local_copy.sh rename to example/Python/src/DigitalTwin/KeyFob/scripts/cloud/run_local_copy.sh diff --git a/example/Python/src/DigitalTwin/KeyFob/setup.sh b/example/Python/src/DigitalTwin/KeyFob/scripts/cloud/setup.sh similarity index 100% rename from example/Python/src/DigitalTwin/KeyFob/setup.sh rename to example/Python/src/DigitalTwin/KeyFob/scripts/cloud/setup.sh diff --git a/example/Python/src/DigitalTwin/KeyFob/scripts/local/run_fob.sh b/example/Python/src/DigitalTwin/KeyFob/scripts/local/run_fob.sh new file mode 100755 index 0000000000..cd41af8e74 --- /dev/null +++ b/example/Python/src/DigitalTwin/KeyFob/scripts/local/run_fob.sh @@ -0,0 +1,2 @@ +cd ../../../src-gen/DigitalTwin/KeyFob/KeyFobDemo +python3 fob/KeyFobDemo_fob.py -i 1 \ No newline at end of file diff --git a/example/Python/src/DigitalTwin/KeyFob/scripts/local/run_rti.sh b/example/Python/src/DigitalTwin/KeyFob/scripts/local/run_rti.sh new file mode 100755 index 0000000000..7321705ec1 --- /dev/null +++ b/example/Python/src/DigitalTwin/KeyFob/scripts/local/run_rti.sh @@ -0,0 +1 @@ +RTI -i 1 -n 2 \ No newline at end of file diff --git a/example/Python/src/DigitalTwin/KeyFob/scripts/local/run_twin.sh b/example/Python/src/DigitalTwin/KeyFob/scripts/local/run_twin.sh new file mode 100755 index 0000000000..99ae7bb6c5 --- /dev/null +++ b/example/Python/src/DigitalTwin/KeyFob/scripts/local/run_twin.sh @@ -0,0 +1,2 @@ +cd ../../../src-gen/DigitalTwin/KeyFob/KeyFobDemo +python3 twin/KeyFobDemo_twin.py -i 1 \ No newline at end of file From 06610a9575eb185b737c0613817c30338867b6de Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Thu, 20 Jan 2022 15:08:00 -0800 Subject: [PATCH 21/64] improve log readability --- example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf | 2 +- example/Python/src/DigitalTwin/KeyFob/README.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf index 4ca8b4e937..dfc4f90126 100644 --- a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf +++ b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf @@ -55,7 +55,7 @@ reactor KeyFob { elapsed_ptime, tag, remote, locked = line return (f"At (tag: ({'{:,}'.format(tag.time)} ns, {tag.microstep}), " f"lag: {'{:,}'.format(elapsed_ptime - tag.time)} ns), " - f"{'Received' if remote else 'Set'} lock state as: {'Locked' if locked else 'Unlocked'}") + f"{'[Remote]' if remote else '[Local]'} Updated lock state to: {'Locked' if locked else 'Unlocked'}") # log structure: (elapsed_physical_time:int, tag:int, remote:bool, locked:bool) def append_log(self, remote, locked): diff --git a/example/Python/src/DigitalTwin/KeyFob/README.md b/example/Python/src/DigitalTwin/KeyFob/README.md index 797978755b..043606b558 100644 --- a/example/Python/src/DigitalTwin/KeyFob/README.md +++ b/example/Python/src/DigitalTwin/KeyFob/README.md @@ -50,7 +50,7 @@ For clarity purposes, I will use `user$ ` to denote a local terminal, `user@rti- Run the `setup.sh` script to set up the RTI and the digital twin on the cloud: ```bash -user$ ./setup.sh +user$ ./scripts/cloud/setup.sh ``` When the script finishes, ssh into the digital twin: @@ -72,7 +72,7 @@ user@twin-vm ~ $ docker container attach CONTAINER_ID Open another terminal in the directory where the `docker-compose.yml` is located. Run `run_local_copy.sh` to run the local key fob: ```bash -user$ ./run_local_copy.sh +user$ ./scripts/cloud/run_local_copy.sh ``` Now you should see the key fobs in each terminal syncing with each other through the RTI on the cloud. @@ -81,7 +81,7 @@ Now you should see the key fobs in each terminal syncing with each other through Run the clean up script: ```bash -user$ ./cleanup.sh +user$ ./scripts/cloud/cleanup.sh ``` ### Conclusion From 0e8f6b5aa23953fc5cb09b0dd5195620248dc8f2 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Thu, 20 Jan 2022 15:15:35 -0800 Subject: [PATCH 22/64] update dev docs --- .../src/DigitalTwin/KeyFob/dev/README.md | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/example/Python/src/DigitalTwin/KeyFob/dev/README.md b/example/Python/src/DigitalTwin/KeyFob/dev/README.md index 9d48fb4022..8eaf1e095e 100644 --- a/example/Python/src/DigitalTwin/KeyFob/dev/README.md +++ b/example/Python/src/DigitalTwin/KeyFob/dev/README.md @@ -28,30 +28,37 @@ NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP rti-vm us-central1-a n1-standard-1 10.128.0.7 34.133.143.163 RUNNING ``` +Export the `EXTERNAL_IP` of the RTI: +```bash +RTI_IP=`gcloud compute instances list | grep 'rti-vm' | awk '{print $5}'` +``` + ### Running the digital twin -Build Digital Twin example locally, after changing the “at” line of the federated reactor: +Build `KeyFobDemo.lf` locally: ```bash -user$ lfc DigitalTwin.lf +user$ lfc KeyFobDemo.lf ``` -Go to the directory where the generated code is located, which is usually located at `src-gen/DigitalTwin/DigitalTwin`. There should be a docker-compose.yml in that directory. Build the docker images of the two key fobs: +Go to the directory where the generated code is located, which is usually located at `src-gen/DigitalTwin/KeyFob/KeyFobDemo`. There should be a docker-compose.yml in that directory. Build the docker images of the two key fobs: ```bash -user$ docker compose build fob twin -—no-cache +user$ docker compose build fob twin --no-cache ``` Tag and push the digital twin's docker image to the cloud: ```bash -user$ docker tag digitaltwin_twin gcr.io/$PROJECT_ID/twin +user$ docker tag keyfobdemo_twin gcr.io/$PROJECT_ID/twin user$ docker push gcr.io/$PROJECT_ID/twin ``` -Create a VM for the digital twin: +Create a VM for the digital twin, passing the address of the RTI: ```bash -user$ gcloud compute instances create-with-container twin-vm \ +gcloud compute instances create-with-container twin-vm \ --container-image=gcr.io/$PROJECT_ID/twin \ --container-arg="-i" \ --container-arg=1 \ + --container-arg="--rti" \ + --container-arg=$RTI_IP \ --container-stdin \ --container-tty ``` @@ -73,9 +80,9 @@ user@twin-vm ~ $ docker container attach CONTAINER_ID ### Running the local key fob -Open another terminal in the directory where the `docker-compose.yml` is located. Run: +Open another terminal in the directory where the `docker-compose.yml` is located. Pass in the address of the RTI. Run: ```bash -user$ docker compose run --rm fob +user$ docker compose run --rm fob -i 1 --rti $RTI_IP ``` Now you should see the key fobs in each terminal syncing with each other through the RTI on the cloud. From 99c939be66e1fe9665ba0da451432ca1fa95211c Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Fri, 14 Jan 2022 18:32:12 -0800 Subject: [PATCH 23/64] nonfunctional double unlock demo --- .../DoubleUnlock/DoubleUnlockDemo.lf | 149 ++++++++++++++++++ .../src/DigitalTwin/DoubleUnlock/README.md | 0 2 files changed, 149 insertions(+) create mode 100644 example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf create mode 100644 example/Python/src/DigitalTwin/DoubleUnlock/README.md diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf new file mode 100644 index 0000000000..ac9b706434 --- /dev/null +++ b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf @@ -0,0 +1,149 @@ +/** + * Example of a basic digital twin setup, with two federates mutating and + * maintaining a "lock status" state. + * + * For run instructions, see README.md in the same directory. + * + * @author Hou Seng Wong (housengw@berkeley.edu) + */ + + target Python { + docker: true +}; + +preamble {= + import curses + import threading + + # lock states + LOCKED = "locked" + DRIVER_DOOR_UNLOCKED = "d_unlocked" + ALL_DOORS_UNLOCKED = "a_unlocked" + + # whether the lock action is remote or not. + REMOTE = True + LOCAL = False +=} + +/** + * A key fob that detects "lock" and "unlock" key presses, + * and sends and receives lock status to and from other key fobs. + */ +reactor KeyFobDoubleUnlock { + /* logger / window related state variables */ + state window({=None=}); + state listener({=None=}); + state log({=list()=}); + state max_log_len(3); + + /* KeyFob related state variables */ + state lock_status({=LOCKED=}); + input get_lock_action; + output send_lock_action; + logical action lock; + logical action unlock; + physical action press_lock; + physical action press_unlock; + + preamble {= + def print_intro_message(self): + self.window.addstr(0, 0, "Press 'l' to lock, 'u' to unlock, 'q' to quit") + self.window.refresh() + + def print_lock_status(self): + self.window.addstr(1, 0, f"Lock Status: {self.lock_status}") + self.window.clrtoeol() + self.window.refresh() + + def print_log(self): + text = "Log is empty" + if len(self.log) > 0: + for i, line in enumerate(self.log): + log_message = self.format_log_message(line) + self.window.addstr(2 + i, 0, log_message) + self.window.clrtoeol() + self.window.refresh() + + def format_log_message(self, line): + elapsed_ptime, tag, remote, new_lock_status = line + return (f"At (tag: ({'{:,}'.format(tag.time)} ns, {tag.microstep}), " + f"lag: {'{:,}'.format(elapsed_ptime - tag.time)} ns), " + f"{'Received' if remote else 'Set'} lock state as: {new_lock_status}") + + # log structure: (elapsed_physical_time:int, tag:int, remote:bool, new_lock_status:str) + def append_log(self, remote, new_lock_status): + elapsed_tag = Tag(get_elapsed_logical_time(), get_microstep()) + log_entry = (get_elapsed_physical_time(), elapsed_tag, remote, new_lock_status) + self.log.append(log_entry) + if len(self.log) > self.max_log_len: + self.log.pop(0) + + def update_lock_status(self, remote, new_lock_status): + self.append_log(remote=True, lock_status=new_lock_status) + self.lock_status = new_lock_status + self.print_lock_status() + self.print_log() + + def listen_for_keypress(self, press_lock, press_unlock): + key = "" + while key != ord("q"): + key = self.window.getch() + if key == ord("l"): + press_lock.schedule(0) + elif key == ord("u"): + press_unlock.schedule(0) + request_stop() + =} + + reaction(startup) -> press_lock, press_unlock {= + self.window = curses.initscr() + curses.cbreak() + curses.noecho() + self.window.keypad(True) + self.print_intro_message() + self.print_lock_status() + self.print_log() + t = threading.Thread(target=self.listen_for_keypress, args=(press_lock, press_unlock)) + self.listener = t + t.start() + =} + + reaction(press_lock) -> lock, send_lock_action {= + lock.schedule(0, LOCAL) + send_lock_action.set(True) + =} + + reaction(press_unlock) -> unlock, send_lock_action {= + unlock.schedule(0, LOCAL) + send_lock_action.set(False) + =} + + reaction(lock) {= + new_lock_status = self.derive_lock_status(self.lock_status, lock=True) + self.update_lock_status(remote=lock.value, new_lock_status=new_lock_status) + =} + + reaction(unlock) {= + new_lock_status = self.derive_lock_status(self.lock_status, lock=False) + self.update_lock_status(remote=unlock.value, new_lock_status=new_lock_status) + =} + + reaction(get_lock_action) -> lock, unlock {= + if get_lock_action.value: + lock.schedule(0, REMOTE) + else: + unlock.schedule(0, REMOTE) + =} + + reaction(shutdown) {= + self.listener.join() + curses.endwin() + =} +} + +federated reactor { + fob = new KeyFobDoubleUnlock(); + twin = new KeyFobDoubleUnlock(); + fob.send_lock_action -> twin.get_lock_action; + twin.send_lock_action -> fob.get_lock_action; +} \ No newline at end of file diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/README.md b/example/Python/src/DigitalTwin/DoubleUnlock/README.md new file mode 100644 index 0000000000..e69de29bb2 From 943326165e5b24a5d82160086bd50c5672d10bd1 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Sun, 16 Jan 2022 16:47:04 -0800 Subject: [PATCH 24/64] refactor Key fob demo --- .../src/DigitalTwin/KeyFob/KeyFobDemo.lf | 55 ++++++++++--------- example/Python/src/DigitalTwin/utils.py | 36 ++++++++++++ 2 files changed, 64 insertions(+), 27 deletions(-) create mode 100644 example/Python/src/DigitalTwin/utils.py diff --git a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf index dfc4f90126..55e3bf3751 100644 --- a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf +++ b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf @@ -8,12 +8,14 @@ */ target Python { - docker: true + docker: true, + files: ["../utils.py"] }; preamble {= import curses import threading + from utils import Logger, Window =} /** @@ -21,35 +23,31 @@ preamble {= * and sends and receives lock status to and from other key fobs. */ reactor KeyFob { - state locked({=False=}); + /* logger / window related state variables */ + state logger({=None=}); state window({=None=}); + + /* KeyFob related state variables */ + state locked({=False=}); state listener({=None=}); - state log({=list()=}); - state max_log_len(3); + + /* I/O ports and actions */ input get_lock_status; output send_lock_status; physical action press_lock; physical action press_unlock; preamble {= - def print_intro_message(self): - self.window.addstr(0, 0, "Press 'l' to lock, 'u' to unlock, 'q' to quit") - self.window.refresh() - + def lock_status_str(self, locked): + return "Locked" if locked else "Unlocked" + def print_lock_status(self): - status = "Locked" if self.locked else "Unlocked" - self.window.addstr(1, 0, f"Lock Status: {status}") - self.window.clrtoeol() - self.window.refresh() + self.window.change_line(1, f"Lock Status: {self.lock_status_str(self.locked)}") def print_log(self): - text = "Log is empty" - if len(self.log) > 0: - for i, line in enumerate(self.log): - log_message = self.format_log_message(line) - self.window.addstr(2 + i, 0, log_message) - self.window.clrtoeol() - self.window.refresh() + if self.logger.log_size() > 0: + for i, line in enumerate(self.logger.get_log()): + self.window.change_line(2 + i, line) def format_log_message(self, line): elapsed_ptime, tag, remote, locked = line @@ -61,9 +59,7 @@ reactor KeyFob { def append_log(self, remote, locked): elapsed_tag = Tag(get_elapsed_logical_time(), get_microstep()) log_entry = (get_elapsed_physical_time(), elapsed_tag, remote, locked) - self.log.append(log_entry) - if len(self.log) > self.max_log_len: - self.log.pop(0) + self.logger.append_log(self.format_log_message(log_entry)) def listen_for_keypress(self, press_lock, press_unlock): key = "" @@ -77,13 +73,15 @@ reactor KeyFob { =} reaction(startup) -> press_lock, press_unlock {= - self.window = curses.initscr() - curses.cbreak() - curses.noecho() - self.window.keypad(True) - self.print_intro_message() + # Set up the logger and the curses window + self.window = Window() + self.logger = Logger() + self.window.change_line(0, "Press 'l' to lock, 'u' to unlock, 'q' to quit") self.print_lock_status() self.print_log() + self.window.refresh() + + # Spawn thread to listen for key presses t = threading.Thread(target=self.listen_for_keypress, args=(press_lock, press_unlock)) self.listener = t t.start() @@ -94,6 +92,7 @@ reactor KeyFob { self.locked = True self.print_lock_status() self.print_log() + self.window.refresh() send_lock_status.set(True) =} @@ -102,6 +101,7 @@ reactor KeyFob { self.locked = False self.print_lock_status() self.print_log() + self.window.refresh() send_lock_status.set(False) =} @@ -110,6 +110,7 @@ reactor KeyFob { self.locked = get_lock_status.value self.print_lock_status() self.print_log() + self.window.refresh() =} reaction(shutdown) {= diff --git a/example/Python/src/DigitalTwin/utils.py b/example/Python/src/DigitalTwin/utils.py new file mode 100644 index 0000000000..769bd4d9b3 --- /dev/null +++ b/example/Python/src/DigitalTwin/utils.py @@ -0,0 +1,36 @@ +import curses + +class Logger: + def __init__(self, max_lines=3): + self.max_lines = max_lines + self.log = [] + + def append_log(self, msg): + self.log.append(msg) + if len(self.log) > self.max_lines: + self.log.pop(0) + + def get_log(self): + return self.log + + def log_size(self): + return len(self.log) + + +class Window: + def __init__(self): + self.window = curses.initscr() + curses.cbreak() + curses.noecho() + self.window.keypad(True) + + def change_line(self, y, new_msg): + self.window.addstr(y, 0, new_msg) + self.window.clrtoeol() + + def refresh(self): + self.window.refresh() + + def getch(self): + return self.window.getch() + From b73da8c4e0edaf130beb9d60b82d1ecb1d53104d Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Sun, 16 Jan 2022 16:51:37 -0800 Subject: [PATCH 25/64] rename lock_status to lock_state --- .../src/DigitalTwin/KeyFob/KeyFobDemo.lf | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf index 55e3bf3751..41823f1618 100644 --- a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf +++ b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf @@ -32,17 +32,17 @@ reactor KeyFob { state listener({=None=}); /* I/O ports and actions */ - input get_lock_status; - output send_lock_status; + input get_lock_state; + output send_lock_state; physical action press_lock; physical action press_unlock; preamble {= - def lock_status_str(self, locked): + def lock_state_str(self, locked): return "Locked" if locked else "Unlocked" - def print_lock_status(self): - self.window.change_line(1, f"Lock Status: {self.lock_status_str(self.locked)}") + def print_lock_state(self): + self.window.change_line(1, f"Lock Status: {self.lock_state_str(self.locked)}") def print_log(self): if self.logger.log_size() > 0: @@ -53,7 +53,11 @@ reactor KeyFob { elapsed_ptime, tag, remote, locked = line return (f"At (tag: ({'{:,}'.format(tag.time)} ns, {tag.microstep}), " f"lag: {'{:,}'.format(elapsed_ptime - tag.time)} ns), " +<<<<<<< HEAD f"{'[Remote]' if remote else '[Local]'} Updated lock state to: {'Locked' if locked else 'Unlocked'}") +======= + f"{'Received' if remote else 'Set'} lock state as: {self.lock_state_str(locked)}") +>>>>>>> rename lock_status to lock_state # log structure: (elapsed_physical_time:int, tag:int, remote:bool, locked:bool) def append_log(self, remote, locked): @@ -77,7 +81,7 @@ reactor KeyFob { self.window = Window() self.logger = Logger() self.window.change_line(0, "Press 'l' to lock, 'u' to unlock, 'q' to quit") - self.print_lock_status() + self.print_lock_state() self.print_log() self.window.refresh() @@ -87,28 +91,28 @@ reactor KeyFob { t.start() =} - reaction(press_lock) -> send_lock_status {= + reaction(press_lock) -> send_lock_state {= self.append_log(remote=False, locked=True) self.locked = True - self.print_lock_status() + self.print_lock_state() self.print_log() self.window.refresh() - send_lock_status.set(True) + send_lock_state.set(True) =} - reaction(press_unlock) -> send_lock_status {= + reaction(press_unlock) -> send_lock_state {= self.append_log(remote=False, locked=False) self.locked = False - self.print_lock_status() + self.print_lock_state() self.print_log() self.window.refresh() - send_lock_status.set(False) + send_lock_state.set(False) =} - reaction(get_lock_status) {= - self.append_log(remote=True, locked=get_lock_status.value) - self.locked = get_lock_status.value - self.print_lock_status() + reaction(get_lock_state) {= + self.append_log(remote=True, locked=get_lock_state.value) + self.locked = get_lock_state.value + self.print_lock_state() self.print_log() self.window.refresh() =} @@ -122,6 +126,6 @@ reactor KeyFob { federated reactor { fob = new KeyFob(); twin = new KeyFob(); - fob.send_lock_status -> twin.get_lock_status; - twin.send_lock_status -> fob.get_lock_status; + fob.send_lock_state -> twin.get_lock_state; + twin.send_lock_state -> fob.get_lock_state; } \ No newline at end of file From f3124e20bafabfa6cbf53f6633ae3ce46aa41c15 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Sun, 16 Jan 2022 17:31:42 -0800 Subject: [PATCH 26/64] functional double unlock demo without autolock --- .../DoubleUnlock/DoubleUnlockDemo.lf | 155 ++++++++++-------- 1 file changed, 84 insertions(+), 71 deletions(-) diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf index ac9b706434..372ab14ada 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf +++ b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf @@ -8,81 +8,79 @@ */ target Python { - docker: true + docker: true, + files: ["../utils.py"] }; preamble {= import curses import threading - - # lock states - LOCKED = "locked" - DRIVER_DOOR_UNLOCKED = "d_unlocked" - ALL_DOORS_UNLOCKED = "a_unlocked" - - # whether the lock action is remote or not. - REMOTE = True - LOCAL = False + from utils import Logger, Window + import enum + + class LockStates(enum.Enum): + Locked = 0 + DriverUnlocked = 1 + AllUnlocked = 2 + + class LockStateNames(enum.Enum): + Locked = "Locked" + DriverUnlocked = "Driver's door unlocked" + AllUnlocked = "All doors unlocked" =} /** * A key fob that detects "lock" and "unlock" key presses, * and sends and receives lock status to and from other key fobs. */ -reactor KeyFobDoubleUnlock { +reactor DoubleUnlockKeyFob { /* logger / window related state variables */ + state logger({=None=}); state window({=None=}); - state listener({=None=}); - state log({=list()=}); - state max_log_len(3); + state main_message_begins(0); /* KeyFob related state variables */ - state lock_status({=LOCKED=}); + state lock_state(0); + state listener({=None=}); + state auto_lock_duration(5) # seconds + + /* I/O ports and actions */ input get_lock_action; output send_lock_action; - logical action lock; - logical action unlock; physical action press_lock; physical action press_unlock; + logical action do_lock; preamble {= - def print_intro_message(self): - self.window.addstr(0, 0, "Press 'l' to lock, 'u' to unlock, 'q' to quit") - self.window.refresh() - - def print_lock_status(self): - self.window.addstr(1, 0, f"Lock Status: {self.lock_status}") - self.window.clrtoeol() - self.window.refresh() + def lock_state_str(self, lock_state): + if lock_state == LockStates.Locked: + return LockStateNames.Locked.value + elif lock_state == LockStates.DriverUnlocked: + return LockStateNames.DriverUnlocked.value + elif lock_state == LockStates.AllUnlocked: + return LockStateNames.AllUnlocked.value + else: + return "ERROR: Lock state is invalid" + + def print_lock_state(self): + self.window.change_line(self.main_message_begins, f"Lock State: {self.lock_state_str(self.lock_state)}") def print_log(self): - text = "Log is empty" - if len(self.log) > 0: - for i, line in enumerate(self.log): - log_message = self.format_log_message(line) - self.window.addstr(2 + i, 0, log_message) - self.window.clrtoeol() - self.window.refresh() + if self.logger.log_size() > 0: + for i, line in enumerate(self.logger.get_log()): + self.window.change_line(self.main_message_begins + 1 + i, line) def format_log_message(self, line): - elapsed_ptime, tag, remote, new_lock_status = line + elapsed_ptime, tag, remote, do_lock = line return (f"At (tag: ({'{:,}'.format(tag.time)} ns, {tag.microstep}), " f"lag: {'{:,}'.format(elapsed_ptime - tag.time)} ns), " - f"{'Received' if remote else 'Set'} lock state as: {new_lock_status}") + f"{'Received' if remote else 'Sent'} lock action: {'Lock' if do_lock else 'Unlock'}") - # log structure: (elapsed_physical_time:int, tag:int, remote:bool, new_lock_status:str) - def append_log(self, remote, new_lock_status): + # log structure: (elapsed_physical_time:int, tag:int, remote:bool, do_lock:bool) + def append_log(self, remote, do_lock): elapsed_tag = Tag(get_elapsed_logical_time(), get_microstep()) - log_entry = (get_elapsed_physical_time(), elapsed_tag, remote, new_lock_status) - self.log.append(log_entry) - if len(self.log) > self.max_log_len: - self.log.pop(0) - - def update_lock_status(self, remote, new_lock_status): - self.append_log(remote=True, lock_status=new_lock_status) - self.lock_status = new_lock_status - self.print_lock_status() - self.print_log() + log_entry = (get_elapsed_physical_time(), elapsed_tag, remote, do_lock) + self.logger.append_log(self.format_log_message(log_entry)) def listen_for_keypress(self, press_lock, press_unlock): key = "" @@ -96,43 +94,58 @@ reactor KeyFobDoubleUnlock { =} reaction(startup) -> press_lock, press_unlock {= - self.window = curses.initscr() - curses.cbreak() - curses.noecho() - self.window.keypad(True) - self.print_intro_message() - self.print_lock_status() + # Set up the logger and the curses window + self.window = Window() + self.logger = Logger() + self.window.change_line(0, "Press 'l' to lock, 'u' to unlock, 'q' to quit") + self.window.change_line(1, "") + self.window.change_line(2, "Rules:") + self.window.change_line(3, "1. Pressing 'l' locks all doors.") + self.window.change_line(4, "2. Pressing 'u' unlocks driver's door if driver's door was locked.") + self.window.change_line(5, "3. Pressing 'u' unlocks all door if driver's door was NOT locked.") + self.window.change_line(6, "") + self.window.change_line(7, "All doors automatically lock after 5 seconds if no unlock signal is received. (not yet implemented)") + self.window.change_line(8, "") + self.main_message_begins = 9 + self.auto_lock_duration = 5 + self.lock_state = LockStates.Locked + self.print_lock_state() self.print_log() + self.window.refresh() + + # Spawn thread to listen for key presses t = threading.Thread(target=self.listen_for_keypress, args=(press_lock, press_unlock)) self.listener = t t.start() =} - reaction(press_lock) -> lock, send_lock_action {= - lock.schedule(0, LOCAL) + reaction(press_lock) -> send_lock_action, do_lock {= + self.append_log(remote=False, do_lock=True) + do_lock.schedule(0, True) send_lock_action.set(True) =} - reaction(press_unlock) -> unlock, send_lock_action {= - unlock.schedule(0, LOCAL) + reaction(press_unlock) -> send_lock_action, do_lock {= + self.append_log(remote=False, do_lock=False) + do_lock.schedule(0, False) send_lock_action.set(False) =} - reaction(lock) {= - new_lock_status = self.derive_lock_status(self.lock_status, lock=True) - self.update_lock_status(remote=lock.value, new_lock_status=new_lock_status) + reaction(get_lock_action) -> do_lock {= + self.append_log(remote=True, do_lock=get_lock_action.value) + do_lock.schedule(0, get_lock_action.value) =} - reaction(unlock) {= - new_lock_status = self.derive_lock_status(self.lock_status, lock=False) - self.update_lock_status(remote=unlock.value, new_lock_status=new_lock_status) - =} - - reaction(get_lock_action) -> lock, unlock {= - if get_lock_action.value: - lock.schedule(0, REMOTE) - else: - unlock.schedule(0, REMOTE) + reaction(do_lock) {= + if do_lock.value: + self.lock_state = LockStates.Locked + elif self.lock_state == LockStates.Locked: + self.lock_state = LockStates.DriverUnlocked + elif self.lock_state == LockStates.DriverUnlocked: + self.lock_state = LockStates.AllUnlocked + self.print_lock_state() + self.print_log() + self.window.refresh() =} reaction(shutdown) {= @@ -142,8 +155,8 @@ reactor KeyFobDoubleUnlock { } federated reactor { - fob = new KeyFobDoubleUnlock(); - twin = new KeyFobDoubleUnlock(); + fob = new DoubleUnlockKeyFob(); + twin = new DoubleUnlockKeyFob(); fob.send_lock_action -> twin.get_lock_action; twin.send_lock_action -> fob.get_lock_action; } \ No newline at end of file From 8852721f1c051c28ad1799f6fe5636c1e6071941 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Mon, 17 Jan 2022 18:06:04 -0800 Subject: [PATCH 27/64] refactor messages --- .../DoubleUnlock/DoubleUnlockDemo.lf | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf index 372ab14ada..542b235b99 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf +++ b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf @@ -97,16 +97,20 @@ reactor DoubleUnlockKeyFob { # Set up the logger and the curses window self.window = Window() self.logger = Logger() - self.window.change_line(0, "Press 'l' to lock, 'u' to unlock, 'q' to quit") - self.window.change_line(1, "") - self.window.change_line(2, "Rules:") - self.window.change_line(3, "1. Pressing 'l' locks all doors.") - self.window.change_line(4, "2. Pressing 'u' unlocks driver's door if driver's door was locked.") - self.window.change_line(5, "3. Pressing 'u' unlocks all door if driver's door was NOT locked.") - self.window.change_line(6, "") - self.window.change_line(7, "All doors automatically lock after 5 seconds if no unlock signal is received. (not yet implemented)") - self.window.change_line(8, "") - self.main_message_begins = 9 + messages = [ + "Press 'l' to send a lock signal, 'u' to send an unlock signal, 'q' to quit", + "", + "Rules:", + "1. A lock signal locks all doors.", + "2. An unlock signal unlocks driver's door if driver's door was locked.", + "3. An unlock signal unlocks all door if driver's door was NOT locked.", + "", + "All doors automatically lock after 5 seconds if no unlock signal is received. (not yet implemented)", + "" + ] + for i, msg in enumerate(messages): + self.window.change_line(i, msg) + self.main_message_begins = len(messages) self.auto_lock_duration = 5 self.lock_state = LockStates.Locked self.print_lock_state() From 446dcc0436c037da37d7a9a9e639bc3dabdc0a46 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Fri, 21 Jan 2022 14:49:01 -0800 Subject: [PATCH 28/64] resolve conflicts --- example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf index 41823f1618..5f3d4c2d20 100644 --- a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf +++ b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf @@ -53,11 +53,7 @@ reactor KeyFob { elapsed_ptime, tag, remote, locked = line return (f"At (tag: ({'{:,}'.format(tag.time)} ns, {tag.microstep}), " f"lag: {'{:,}'.format(elapsed_ptime - tag.time)} ns), " -<<<<<<< HEAD - f"{'[Remote]' if remote else '[Local]'} Updated lock state to: {'Locked' if locked else 'Unlocked'}") -======= - f"{'Received' if remote else 'Set'} lock state as: {self.lock_state_str(locked)}") ->>>>>>> rename lock_status to lock_state + f"{'[Remote]' if remote else '[Local]'} Updated lock state to: {self.lock_state_str(locked)}") # log structure: (elapsed_physical_time:int, tag:int, remote:bool, locked:bool) def append_log(self, remote, locked): From 9744d3d69bb1a65ba6de169b71e249f5d93aaffc Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Mon, 17 Jan 2022 19:17:41 -0800 Subject: [PATCH 29/64] add autolock feature --- .../DoubleUnlock/DoubleUnlockDemo.lf | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf index 542b235b99..e66c544b75 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf +++ b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf @@ -33,7 +33,7 @@ preamble {= * A key fob that detects "lock" and "unlock" key presses, * and sends and receives lock status to and from other key fobs. */ -reactor DoubleUnlockKeyFob { +reactor DoubleUnlockKeyFob(auto_lock_duration(5)) { /* logger / window related state variables */ state logger({=None=}); state window({=None=}); @@ -42,7 +42,7 @@ reactor DoubleUnlockKeyFob { /* KeyFob related state variables */ state lock_state(0); state listener({=None=}); - state auto_lock_duration(5) # seconds + state auto_lock_counter; /* I/O ports and actions */ input get_lock_action; @@ -51,6 +51,9 @@ reactor DoubleUnlockKeyFob { physical action press_unlock; logical action do_lock; + /* Autolock timer */ + timer autolock_timer(0, 100 msec); + preamble {= def lock_state_str(self, lock_state): if lock_state == LockStates.Locked: @@ -71,15 +74,15 @@ reactor DoubleUnlockKeyFob { self.window.change_line(self.main_message_begins + 1 + i, line) def format_log_message(self, line): - elapsed_ptime, tag, remote, do_lock = line + elapsed_ptime, tag, remote, do_lock, auto = line return (f"At (tag: ({'{:,}'.format(tag.time)} ns, {tag.microstep}), " f"lag: {'{:,}'.format(elapsed_ptime - tag.time)} ns), " - f"{'Received' if remote else 'Sent'} lock action: {'Lock' if do_lock else 'Unlock'}") + f"{'[Auto] ' if auto else ''}{'[Remote]' if remote else '[Local]'} lock action: {'Lock' if do_lock else 'Unlock'}") - # log structure: (elapsed_physical_time:int, tag:int, remote:bool, do_lock:bool) - def append_log(self, remote, do_lock): + # log structure: (elapsed_physical_time:int, tag:int, remote:bool, do_lock:bool, auto:bool) + def append_log(self, auto, remote, do_lock): elapsed_tag = Tag(get_elapsed_logical_time(), get_microstep()) - log_entry = (get_elapsed_physical_time(), elapsed_tag, remote, do_lock) + log_entry = (get_elapsed_physical_time(), elapsed_tag, remote, do_lock, auto) self.logger.append_log(self.format_log_message(log_entry)) def listen_for_keypress(self, press_lock, press_unlock): @@ -91,6 +94,9 @@ reactor DoubleUnlockKeyFob { elif key == ord("u"): press_unlock.schedule(0) request_stop() + + def reset_autolock_counter(self): + self.auto_lock_counter = self.auto_lock_duration =} reaction(startup) -> press_lock, press_unlock {= @@ -111,8 +117,8 @@ reactor DoubleUnlockKeyFob { for i, msg in enumerate(messages): self.window.change_line(i, msg) self.main_message_begins = len(messages) - self.auto_lock_duration = 5 self.lock_state = LockStates.Locked + self.reset_autolock_counter() self.print_lock_state() self.print_log() self.window.refresh() @@ -124,19 +130,19 @@ reactor DoubleUnlockKeyFob { =} reaction(press_lock) -> send_lock_action, do_lock {= - self.append_log(remote=False, do_lock=True) + self.append_log(auto=False, remote=False, do_lock=True) do_lock.schedule(0, True) send_lock_action.set(True) =} reaction(press_unlock) -> send_lock_action, do_lock {= - self.append_log(remote=False, do_lock=False) + self.append_log(auto=False, remote=False, do_lock=False) do_lock.schedule(0, False) send_lock_action.set(False) =} reaction(get_lock_action) -> do_lock {= - self.append_log(remote=True, do_lock=get_lock_action.value) + self.append_log(auto=False, remote=True, do_lock=get_lock_action.value) do_lock.schedule(0, get_lock_action.value) =} @@ -145,6 +151,7 @@ reactor DoubleUnlockKeyFob { self.lock_state = LockStates.Locked elif self.lock_state == LockStates.Locked: self.lock_state = LockStates.DriverUnlocked + self.reset_autolock_counter() elif self.lock_state == LockStates.DriverUnlocked: self.lock_state = LockStates.AllUnlocked self.print_lock_state() @@ -152,6 +159,14 @@ reactor DoubleUnlockKeyFob { self.window.refresh() =} + reaction(autolock_timer) -> do_lock {= + if self.auto_lock_counter > 0: + self.auto_lock_counter -= 0.1 + elif self.lock_state != LockStates.Locked: + self.append_log(auto=True, remote=False, do_lock=True) + do_lock.schedule(0, True) + =} + reaction(shutdown) {= self.listener.join() curses.endwin() From 3716c5c23e5960e1e31d5ad1e14b7028ae858cb7 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Mon, 17 Jan 2022 20:12:03 -0800 Subject: [PATCH 30/64] added intermediate logical action --- .../DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf index e66c544b75..e608c76ad6 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf +++ b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf @@ -49,6 +49,8 @@ reactor DoubleUnlockKeyFob(auto_lock_duration(5)) { output send_lock_action; physical action press_lock; physical action press_unlock; + logical action handle_press_lock; + logical action handle_press_unlock; logical action do_lock; /* Autolock timer */ @@ -129,13 +131,21 @@ reactor DoubleUnlockKeyFob(auto_lock_duration(5)) { t.start() =} - reaction(press_lock) -> send_lock_action, do_lock {= - self.append_log(auto=False, remote=False, do_lock=True) + reaction(press_lock) -> handle_press_lock {= + handle_press_lock.schedule(0) + =} + + reaction(press_unlock) -> handle_press_unlock {= + handle_press_unlock.schedule(0) + =} + + reaction(handle_press_lock) -> do_lock, send_lock_action {= + self.append_log(auto=False, remote=False, do_lock=True) do_lock.schedule(0, True) send_lock_action.set(True) =} - reaction(press_unlock) -> send_lock_action, do_lock {= + reaction(handle_press_unlock) -> do_lock, send_lock_action {= self.append_log(auto=False, remote=False, do_lock=False) do_lock.schedule(0, False) send_lock_action.set(False) From e24d54d21963cb49887d719b66121e2ed4baa300 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Mon, 17 Jan 2022 21:00:05 -0800 Subject: [PATCH 31/64] add skeleton of Tester --- .../DoubleUnlock/DoubleUnlockDemo.lf | 9 ++ .../src/DigitalTwin/DoubleUnlock/Tester.lf | 86 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf index e608c76ad6..02e636fe41 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf +++ b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf @@ -46,6 +46,7 @@ reactor DoubleUnlockKeyFob(auto_lock_duration(5)) { /* I/O ports and actions */ input get_lock_action; + input get_lock_press_from_tester; output send_lock_action; physical action press_lock; physical action press_unlock; @@ -151,6 +152,14 @@ reactor DoubleUnlockKeyFob(auto_lock_duration(5)) { send_lock_action.set(False) =} + reaction(get_lock_press_from_tester) -> handle_press_lock, handle_press_unlock {= + press_lock_val = get_lock_press_from_tester.value + if press_lock_val: + handle_press_lock.schedule(0) + else: + handle_press_unlock.schedule(0) + =} + reaction(get_lock_action) -> do_lock {= self.append_log(auto=False, remote=True, do_lock=get_lock_action.value) do_lock.schedule(0, get_lock_action.value) diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf b/example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf new file mode 100644 index 0000000000..2db851da3d --- /dev/null +++ b/example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf @@ -0,0 +1,86 @@ +target Python { + docker: true, + files: ["../utils.py"] +}; + +import DoubleUnlockKeyFob from "DoubleUnlockDemo.lf"; + +preamble {= + import curses + import threading + from utils import Logger, Window +=} + +# TODO: Why does messages from tester to key fobs not get processed on the event QUEUE??? +reactor DoubleUnlockKeyFobTester { + state logger({=None=}); + state window({=None=}); + + physical action key_press; + logical action simulate_press_fob; + logical action simulate_press_twin; + output send_lock_press_to_fob; + output send_lock_press_to_twin; + + + preamble {= + def listen_for_keypress(self, key_press): + key = "" + while key != ord("q"): + key = self.window.getch() + if key != ord("q"): + key_press.schedule(0, key) + request_stop() + =} + + reaction(startup) -> key_press {= + self.window = Window() + self.logger = Logger() + messages = [ + "Press 'l'. ';' to send a lock signal, 'u', 'i' to send an unlock signal, 'q' to quit", + ] + for i, msg in enumerate(messages): + self.window.change_line(i, msg) + self.main_message_begins = len(messages) + + # Spawn thread to listen for key presses + t = threading.Thread(target=self.listen_for_keypress, args=(key_press, )) + self.listener = t + t.start() + =} + + reaction(key_press) -> simulate_press_fob, simulate_press_twin {= + key = key_press.value + if key == ord("l"): + simulate_press_fob.schedule(0, True) + elif key == ord(";"): + simulate_press_twin.schedule(0, True) + elif key == ord("u"): + simulate_press_fob.schedule(0, False) + elif key == ord("i"): + simulate_press_twin.schedule(0, False) + =} + + reaction(simulate_press_fob) -> send_lock_press_to_fob {= + send_lock_press_to_fob.set(simulate_press_fob.value) + =} + + reaction(simulate_press_twin) -> send_lock_press_to_twin {= + send_lock_press_to_twin.set(simulate_press_twin.value) + =} + + reaction(shutdown) {= + self.listener.join() + curses.endwin() + =} +} + +federated reactor { + fob = new DoubleUnlockKeyFob(); + twin = new DoubleUnlockKeyFob(); + tester = new DoubleUnlockKeyFobTester(); + tester.send_lock_press_to_fob -> fob.get_lock_press_from_tester; + tester.send_lock_press_to_twin -> twin.get_lock_press_from_tester; + fob.send_lock_action -> twin.get_lock_action; + twin.send_lock_action -> fob.get_lock_action; +} \ No newline at end of file From 58709a5307468e96335a024e09dad3e2eac907d8 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Wed, 19 Jan 2022 12:13:19 -0800 Subject: [PATCH 32/64] add run scripts and fix tester --- .../src/DigitalTwin/DoubleUnlock/Tester.lf | 63 +++++-------------- .../src/DigitalTwin/DoubleUnlock/run_fob.sh | 3 + .../src/DigitalTwin/DoubleUnlock/run_rti.sh | 1 + .../DigitalTwin/DoubleUnlock/run_tester.sh | 3 + .../src/DigitalTwin/DoubleUnlock/run_twin.sh | 3 + 5 files changed, 25 insertions(+), 48 deletions(-) create mode 100755 example/Python/src/DigitalTwin/DoubleUnlock/run_fob.sh create mode 100755 example/Python/src/DigitalTwin/DoubleUnlock/run_rti.sh create mode 100755 example/Python/src/DigitalTwin/DoubleUnlock/run_tester.sh create mode 100755 example/Python/src/DigitalTwin/DoubleUnlock/run_twin.sh diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf b/example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf index 2db851da3d..535bdfb953 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf +++ b/example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf @@ -5,74 +5,41 @@ target Python { import DoubleUnlockKeyFob from "DoubleUnlockDemo.lf"; -preamble {= - import curses - import threading - from utils import Logger, Window -=} # TODO: Why does messages from tester to key fobs not get processed on the event QUEUE??? -reactor DoubleUnlockKeyFobTester { +reactor DoubleUnlockKeyFobTester(initial_delay(5)) { state logger({=None=}); state window({=None=}); - physical action key_press; + logical action do_test; logical action simulate_press_fob; logical action simulate_press_twin; output send_lock_press_to_fob; output send_lock_press_to_twin; - - - preamble {= - def listen_for_keypress(self, key_press): - key = "" - while key != ord("q"): - key = self.window.getch() - if key != ord("q"): - key_press.schedule(0, key) - request_stop() + + reaction(startup) -> do_test {= + print(f"Test starts in {self.initial_delay} seconds...") + do_test.schedule(SEC(self.initial_delay)) =} - reaction(startup) -> key_press {= - self.window = Window() - self.logger = Logger() - messages = [ - "Press 'l'. ';' to send a lock signal, 'u', 'i' to send an unlock signal, 'q' to quit", - ] - for i, msg in enumerate(messages): - self.window.change_line(i, msg) - self.main_message_begins = len(messages) - - # Spawn thread to listen for key presses - t = threading.Thread(target=self.listen_for_keypress, args=(key_press, )) - self.listener = t - t.start() - =} - - reaction(key_press) -> simulate_press_fob, simulate_press_twin {= - key = key_press.value - if key == ord("l"): - simulate_press_fob.schedule(0, True) - elif key == ord(";"): - simulate_press_twin.schedule(0, True) - elif key == ord("u"): - simulate_press_fob.schedule(0, False) - elif key == ord("i"): - simulate_press_twin.schedule(0, False) + reaction(do_test) -> simulate_press_fob, simulate_press_twin {= + # simulate_press_fob.schedule(0, True) + # simulate_press_twin.schedule(0, True) + simulate_press_fob.schedule(0, False) + simulate_press_twin.schedule(0, False) =} reaction(simulate_press_fob) -> send_lock_press_to_fob {= + tag = get_current_tag() + print(f"Sent lock press {simulate_press_fob.value} to fob at ({tag.time}, {tag.microstep})") send_lock_press_to_fob.set(simulate_press_fob.value) =} reaction(simulate_press_twin) -> send_lock_press_to_twin {= + tag = get_current_tag() + print(f"Sent lock press {simulate_press_twin.value} to twin at ({tag.time}, {tag.microstep})") send_lock_press_to_twin.set(simulate_press_twin.value) =} - - reaction(shutdown) {= - self.listener.join() - curses.endwin() - =} } federated reactor { diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/run_fob.sh b/example/Python/src/DigitalTwin/DoubleUnlock/run_fob.sh new file mode 100755 index 0000000000..a4c165aa72 --- /dev/null +++ b/example/Python/src/DigitalTwin/DoubleUnlock/run_fob.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd ../../../src-gen/DigitalTwin/DoubleUnlock/Tester/fob +python3 Tester_fob.py -i 1 diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/run_rti.sh b/example/Python/src/DigitalTwin/DoubleUnlock/run_rti.sh new file mode 100755 index 0000000000..b865088205 --- /dev/null +++ b/example/Python/src/DigitalTwin/DoubleUnlock/run_rti.sh @@ -0,0 +1 @@ +RTI -i 1 -n 3 \ No newline at end of file diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/run_tester.sh b/example/Python/src/DigitalTwin/DoubleUnlock/run_tester.sh new file mode 100755 index 0000000000..62c539e10b --- /dev/null +++ b/example/Python/src/DigitalTwin/DoubleUnlock/run_tester.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd ../../../src-gen/DigitalTwin/DoubleUnlock/Tester/tester +python3 Tester_tester.py -i 1 diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/run_twin.sh b/example/Python/src/DigitalTwin/DoubleUnlock/run_twin.sh new file mode 100755 index 0000000000..38720f8dcf --- /dev/null +++ b/example/Python/src/DigitalTwin/DoubleUnlock/run_twin.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd ../../../src-gen/DigitalTwin/DoubleUnlock/Tester/twin +python3 Tester_twin.py -i 1 From 12b9e2adc01c6d8ba20a3da5f0fcf2cd16323dfc Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Fri, 21 Jan 2022 15:01:19 -0800 Subject: [PATCH 33/64] add readme for double unlock demo --- .../Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf | 6 +++--- example/Python/src/DigitalTwin/DoubleUnlock/README.md | 6 ++++++ example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf | 6 +++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf index 02e636fe41..c416c291ab 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf +++ b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf @@ -1,6 +1,6 @@ /** - * Example of a basic digital twin setup, with two federates mutating and - * maintaining a "lock status" state. + * Example of a basic digital twin setup, with two federates + * maintaining a shared state "lock state". * * For run instructions, see README.md in the same directory. * @@ -31,7 +31,7 @@ preamble {= /** * A key fob that detects "lock" and "unlock" key presses, - * and sends and receives lock status to and from other key fobs. + * and sends and receives lock state to and from other key fobs. */ reactor DoubleUnlockKeyFob(auto_lock_duration(5)) { /* logger / window related state variables */ diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/README.md b/example/Python/src/DigitalTwin/DoubleUnlock/README.md index e69de29bb2..1b20e4c402 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/README.md +++ b/example/Python/src/DigitalTwin/DoubleUnlock/README.md @@ -0,0 +1,6 @@ +# Double Unlock Key Fob (Digital Twin) Example + +This example is similar to the Key Fob Demo, but with 3 states: Locked, Driver's Door Unlocked, and All Doors Unlocked. +A tester program `Tester.lf` is also included to demonstrate generating simulated signals at arbitrary logical time. + +To run the program, do `lfc Tester.lf` and use the bash scripts to launch each federate in a separate terminal window. diff --git a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf index 5f3d4c2d20..6272976afe 100644 --- a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf +++ b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf @@ -1,6 +1,6 @@ /** - * Example of a basic digital twin setup, with two federates mutating and - * maintaining a "lock status" state. + * Example of a basic digital twin setup, with two federates + * maintaining a shared state "locked". * * For run instructions, see README.md in the same directory. * @@ -20,7 +20,7 @@ preamble {= /** * A key fob that detects "lock" and "unlock" key presses, - * and sends and receives lock status to and from other key fobs. + * and sends and receives lock state to and from other key fobs. */ reactor KeyFob { /* logger / window related state variables */ From 603fde3587625265c110e2d467fcd1d33f9f2efa Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Fri, 21 Jan 2022 15:06:09 -0800 Subject: [PATCH 34/64] add link to demo --- example/Python/src/DigitalTwin/KeyFob/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/example/Python/src/DigitalTwin/KeyFob/README.md b/example/Python/src/DigitalTwin/KeyFob/README.md index 043606b558..e2d8a180a4 100644 --- a/example/Python/src/DigitalTwin/KeyFob/README.md +++ b/example/Python/src/DigitalTwin/KeyFob/README.md @@ -2,6 +2,8 @@ This example shows two federates, one hosted locally and the other hosted on Google Cloud, interacting via an RTI that is also hosted on Google Cloud. +Check out this (video)[https://www.youtube.com/watch?v=s7dYKLoHXVE] for a recorded demo. + ## Before we start Make sure you have a Google Cloud Platform (GCP) account and a project set up. From e438f0b19ed004e2183cfe4fd85722181a1d52ad Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Fri, 21 Jan 2022 16:34:33 -0800 Subject: [PATCH 35/64] rename tester to simulator --- example/Python/src/DigitalTwin/DoubleUnlock/README.md | 4 ++-- .../src/DigitalTwin/DoubleUnlock/{Tester.lf => Simulator.lf} | 0 example/Python/src/DigitalTwin/DoubleUnlock/run_fob.sh | 4 ++-- example/Python/src/DigitalTwin/DoubleUnlock/run_simulator.sh | 3 +++ example/Python/src/DigitalTwin/DoubleUnlock/run_tester.sh | 3 --- example/Python/src/DigitalTwin/DoubleUnlock/run_twin.sh | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) rename example/Python/src/DigitalTwin/DoubleUnlock/{Tester.lf => Simulator.lf} (100%) create mode 100755 example/Python/src/DigitalTwin/DoubleUnlock/run_simulator.sh delete mode 100755 example/Python/src/DigitalTwin/DoubleUnlock/run_tester.sh diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/README.md b/example/Python/src/DigitalTwin/DoubleUnlock/README.md index 1b20e4c402..8cfde843b0 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/README.md +++ b/example/Python/src/DigitalTwin/DoubleUnlock/README.md @@ -1,6 +1,6 @@ # Double Unlock Key Fob (Digital Twin) Example This example is similar to the Key Fob Demo, but with 3 states: Locked, Driver's Door Unlocked, and All Doors Unlocked. -A tester program `Tester.lf` is also included to demonstrate generating simulated signals at arbitrary logical time. +A simulator program `Simulator.lf` is also included to demonstrate generating simulated signals at arbitrary logical time. -To run the program, do `lfc Tester.lf` and use the bash scripts to launch each federate in a separate terminal window. +To run the program, do `lfc Simulator.lf` and use the bash scripts to launch each federate in a separate terminal window. diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf b/example/Python/src/DigitalTwin/DoubleUnlock/Simulator.lf similarity index 100% rename from example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf rename to example/Python/src/DigitalTwin/DoubleUnlock/Simulator.lf diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/run_fob.sh b/example/Python/src/DigitalTwin/DoubleUnlock/run_fob.sh index a4c165aa72..3c9dd7d2da 100755 --- a/example/Python/src/DigitalTwin/DoubleUnlock/run_fob.sh +++ b/example/Python/src/DigitalTwin/DoubleUnlock/run_fob.sh @@ -1,3 +1,3 @@ #!/bin/bash -cd ../../../src-gen/DigitalTwin/DoubleUnlock/Tester/fob -python3 Tester_fob.py -i 1 +cd ../../../src-gen/DigitalTwin/DoubleUnlock/Simulator/fob +python3 Simulator_fob.py -i 1 diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/run_simulator.sh b/example/Python/src/DigitalTwin/DoubleUnlock/run_simulator.sh new file mode 100755 index 0000000000..666d22241a --- /dev/null +++ b/example/Python/src/DigitalTwin/DoubleUnlock/run_simulator.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd ../../../src-gen/DigitalTwin/DoubleUnlock/Simulator/simulator +python3 Simulator_tester.py -i 1 diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/run_tester.sh b/example/Python/src/DigitalTwin/DoubleUnlock/run_tester.sh deleted file mode 100755 index 62c539e10b..0000000000 --- a/example/Python/src/DigitalTwin/DoubleUnlock/run_tester.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -cd ../../../src-gen/DigitalTwin/DoubleUnlock/Tester/tester -python3 Tester_tester.py -i 1 diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/run_twin.sh b/example/Python/src/DigitalTwin/DoubleUnlock/run_twin.sh index 38720f8dcf..b37cc57d20 100755 --- a/example/Python/src/DigitalTwin/DoubleUnlock/run_twin.sh +++ b/example/Python/src/DigitalTwin/DoubleUnlock/run_twin.sh @@ -1,3 +1,3 @@ #!/bin/bash -cd ../../../src-gen/DigitalTwin/DoubleUnlock/Tester/twin -python3 Tester_twin.py -i 1 +cd ../../../src-gen/DigitalTwin/DoubleUnlock/Simulator/twin +python3 Simulator_twin.py -i 1 From 17f9c232191a8a0ba079bdb6d4e44fdec0bcc654 Mon Sep 17 00:00:00 2001 From: housengw <46389172+housengw@users.noreply.github.com> Date: Fri, 21 Jan 2022 15:35:06 -0800 Subject: [PATCH 36/64] Update DoubleUnlockDemo.lf --- .../Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf index c416c291ab..28467318c0 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf +++ b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf @@ -114,7 +114,7 @@ reactor DoubleUnlockKeyFob(auto_lock_duration(5)) { "2. An unlock signal unlocks driver's door if driver's door was locked.", "3. An unlock signal unlocks all door if driver's door was NOT locked.", "", - "All doors automatically lock after 5 seconds if no unlock signal is received. (not yet implemented)", + "All doors automatically lock after 5 seconds if no unlock signal is received. ", "" ] for i, msg in enumerate(messages): @@ -197,4 +197,4 @@ federated reactor { twin = new DoubleUnlockKeyFob(); fob.send_lock_action -> twin.get_lock_action; twin.send_lock_action -> fob.get_lock_action; -} \ No newline at end of file +} From 8ca7994f7a0c0e2b8608223aaabb8cbe0e63d7a6 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Sat, 22 Jan 2022 10:15:07 -0800 Subject: [PATCH 37/64] add comment and clean up --- .../src/DigitalTwin/DoubleUnlock/Simulator.lf | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/Simulator.lf b/example/Python/src/DigitalTwin/DoubleUnlock/Simulator.lf index 535bdfb953..38749705c8 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/Simulator.lf +++ b/example/Python/src/DigitalTwin/DoubleUnlock/Simulator.lf @@ -1,3 +1,12 @@ +/** + * Example of a reactor that sends simulated key presses at arbitrary + * logical time to the key fobs. + * + * For run instructions, see README.md in the same directory. + * + * @author Hou Seng Wong (housengw@berkeley.edu) + */ + target Python { docker: true, files: ["../utils.py"] @@ -6,7 +15,6 @@ target Python { import DoubleUnlockKeyFob from "DoubleUnlockDemo.lf"; -# TODO: Why does messages from tester to key fobs not get processed on the event QUEUE??? reactor DoubleUnlockKeyFobTester(initial_delay(5)) { state logger({=None=}); state window({=None=}); @@ -23,10 +31,12 @@ reactor DoubleUnlockKeyFobTester(initial_delay(5)) { =} reaction(do_test) -> simulate_press_fob, simulate_press_twin {= - # simulate_press_fob.schedule(0, True) - # simulate_press_twin.schedule(0, True) simulate_press_fob.schedule(0, False) simulate_press_twin.schedule(0, False) + + # Feel free to add other simulated key presses... + # simulate_press_fob.schedule(0, True) + # simulate_press_twin.schedule(0, True) =} reaction(simulate_press_fob) -> send_lock_press_to_fob {= From 22e2299c8d7cc57e2f4d202dfedb8d98701e71a2 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 18 Jan 2022 21:15:52 -0800 Subject: [PATCH 38/64] Update RELEASES.md. --- RELEASES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index 8f5b35594b..25b2b536f9 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,4 +1,6 @@ # Version 0.1.0-beta-SNAPSHOT +- LF programs with the TypeScript target can now be compiled on Windows (#850). +- Generated code is validated on save for all targets except C (#828) ## Language From f1ef334a95ec7ef0a5b26ea64c8cf3329a8f2834 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 18 Jan 2022 22:33:33 -0800 Subject: [PATCH 39/64] Update RELEASES.md. --- RELEASES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 25b2b536f9..bde551fabd 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,6 +1,6 @@ # Version 0.1.0-beta-SNAPSHOT - LF programs with the TypeScript target can now be compiled on Windows (#850). -- Generated code is validated on save for all targets except C (#828) +- In the VS Code extension, generated code is validated when an LF file is saved for all targets except C (#828). Generated C code is only validated when it is fully compiled. ## Language From b3f902a5628c040e5f0d0be41a1a390bcdbb9403 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 18 Jan 2022 22:41:50 -0800 Subject: [PATCH 40/64] CI: Re-order steps. This responds to the following error: "pathspec 'D:\a\lingua-franca\lingua-franca\vcpkg' did not match any file(s) known to git". --- .github/workflows/ci.yml | 2 +- .github/workflows/lsp-tests.yml | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45517c0ffc..c1278e6d55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: # Run language server tests. lsp-tests: - uses: lf-lang/lingua-franca/.github/workflows/lsp-tests.yml@master + uses: lf-lang/lingua-franca/.github/workflows/lsp-tests.yml@vscode-validate-generated-code-cleanups # Run the C integration tests. c-tests: diff --git a/.github/workflows/lsp-tests.yml b/.github/workflows/lsp-tests.yml index a0b5e5ea7b..4e354bd2e5 100644 --- a/.github/workflows/lsp-tests.yml +++ b/.github/workflows/lsp-tests.yml @@ -29,12 +29,6 @@ jobs: uses: actions/setup-java@v1.4.3 with: java-version: 11 - - name: Check out lingua-franca repository - uses: actions/checkout@v2 - with: - repository: lf-lang/lingua-franca - submodules: true - ref: ${{ inputs.compiler-ref }} - name: Setup Node.js environment uses: actions/setup-node@v2.1.2 - name: Install pnpm @@ -61,6 +55,12 @@ jobs: vcpkgDirectory: ${{ github.workspace }}/vcpkg/ vcpkgTriplet: x64-windows-static if: ${{ runner.os == 'Windows' }} + - name: Check out lingua-franca repository + uses: actions/checkout@v2 + with: + repository: lf-lang/lingua-franca + submodules: true + ref: ${{ inputs.compiler-ref }} - name: Run language server Python tests without PyLint run: ./gradlew test --tests org.lflang.tests.lsp.LspTests.pythonSyntaxOnlyValidationTest - name: Report to CodeCov From 49c2f910c0fb7d07ac61e5a81554ccf3c5b20188 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 18 Jan 2022 23:20:58 -0800 Subject: [PATCH 41/64] Python: Handle empty output. This problem does not manifest on my machine, so I can only hope that this solves it. --- .../src/org/lflang/generator/python/PythonValidator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonValidator.java b/org.lflang/src/org/lflang/generator/python/PythonValidator.java index dbbd60fa89..8ac5aa33f6 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonValidator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonValidator.java @@ -246,7 +246,8 @@ public Strategy getErrorReportingStrategy() { public Strategy getOutputReportingStrategy() { return (validationOutput, errorReporter, codeMaps) -> { try { - for (PylintMessage message : mapper.readValue(validationOutput, PylintMessage[].class)) { + if (validationOutput.isBlank()) return; + for (PylintMessage message : mapper.readValue(validationOutput.strip(), PylintMessage[].class)) { if (shouldIgnore(message)) continue; CodeMap map = codeMaps.get(message.getPath(fileConfig.getSrcGenPath())); if (map != null) { From 683dd6635f204e4a4bb9446e1b1569e41b3ab454 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 19 Jan 2022 15:31:21 -0800 Subject: [PATCH 42/64] Revert "Python: Handle empty output." This reverts commit f5aae38216298da7e0070cb2c9505a7b43f4f138. --- .../src/org/lflang/generator/python/PythonValidator.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonValidator.java b/org.lflang/src/org/lflang/generator/python/PythonValidator.java index 8ac5aa33f6..dbbd60fa89 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonValidator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonValidator.java @@ -246,8 +246,7 @@ public Strategy getErrorReportingStrategy() { public Strategy getOutputReportingStrategy() { return (validationOutput, errorReporter, codeMaps) -> { try { - if (validationOutput.isBlank()) return; - for (PylintMessage message : mapper.readValue(validationOutput.strip(), PylintMessage[].class)) { + for (PylintMessage message : mapper.readValue(validationOutput, PylintMessage[].class)) { if (shouldIgnore(message)) continue; CodeMap map = codeMaps.get(message.getPath(fileConfig.getSrcGenPath())); if (map != null) { From c6f1f4c13a7c81387ff53da8e1f5dc0d538bc264 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 19 Jan 2022 15:33:17 -0800 Subject: [PATCH 43/64] LFCommand: Do not forget to shut down thread pool. This seems to have prevented normal program termination (it hangs when done). --- org.lflang/src/org/lflang/generator/Validator.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/Validator.java b/org.lflang/src/org/lflang/generator/Validator.java index 718ee2a5d6..5951172825 100644 --- a/org.lflang/src/org/lflang/generator/Validator.java +++ b/org.lflang/src/org/lflang/generator/Validator.java @@ -13,6 +13,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.stream.Collectors; @@ -85,9 +86,11 @@ private static List> getFutures(List> tasks) throws In } break; default: - futures = Executors.newFixedThreadPool( + ExecutorService service = Executors.newFixedThreadPool( Math.min(Runtime.getRuntime().availableProcessors(), tasks.size()) - ).invokeAll(tasks); + ); + futures = service.invokeAll(tasks); + service.shutdown(); } return futures; } From 505cda4c29c8a0063aee32909a2f6ad4b5c0725d Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 19 Jan 2022 16:27:29 -0800 Subject: [PATCH 44/64] Python: Tolerate output from an older Pylint version. --- .../generator/python/PythonValidator.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonValidator.java b/org.lflang/src/org/lflang/generator/python/PythonValidator.java index dbbd60fa89..f68a6520ef 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonValidator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonValidator.java @@ -21,6 +21,8 @@ import org.lflang.generator.Validator; import org.lflang.util.LFCommand; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; @@ -62,8 +64,8 @@ private static final class PylintMessage { private String obj; private int line; private int column; - private int endLine; - private int endColumn; + private Integer endLine; + private Integer endColumn; private Path path; private String symbol; private String message; @@ -81,7 +83,10 @@ private static final class PylintMessage { @JsonProperty("message-id") public void setMessageId(String messageId) { this.messageId = messageId; } public Position getStart() { return Position.fromZeroBased(line - 1, column); } - public Position getEnd() { return Position.fromZeroBased(endLine - 1, endColumn); } + 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: @@ -272,8 +277,10 @@ public Strategy getOutputReportingStrategy() { } } } catch (JsonProcessingException e) { - // This should be impossible unless Pylint's API changes. Maybe it's fine to fail quietly - // like this in case that happens -- this will go to stderr, so you can see it if you look. + 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." + ); e.printStackTrace(); } }; From 6c5b62b08ddf52bd851318b2691b76b31b8d835f Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 19 Jan 2022 16:55:15 -0800 Subject: [PATCH 45/64] Error reporting: One more special case for code generators. --- org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt | 3 ++- org.lflang/src/org/lflang/generator/rust/RustGenerator.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt index 725a41b614..f7aa7ebe05 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt @@ -204,7 +204,8 @@ class CppGenerator( if (cmakeReturnCode == 0) { // If cmake succeeded, run make val makeCommand = createMakeCommand(cppFileConfig.buildPath, version) - val makeReturnCode = CppValidator(cppFileConfig, errorReporter, codeMaps).run(makeCommand, context.cancelIndicator) + val makeReturnCode = if (context.mode == Mode.STANDALONE) makeCommand.run() else + CppValidator(cppFileConfig, errorReporter, codeMaps).run(makeCommand, context.cancelIndicator) if (makeReturnCode == 0) { println("SUCCESS (compiling generated C++ code)") diff --git a/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt b/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt index 5365c15638..431bb7dc13 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt +++ b/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt @@ -127,7 +127,8 @@ class RustGenerator( fileConfig.srcGenPath.toAbsolutePath() ) ?: return - val cargoReturnCode = RustValidator(fileConfig, errorReporter, codeMaps).run(cargoCommand, context.cancelIndicator) + val cargoReturnCode = if (context.mode == TargetConfig.Mode.STANDALONE) cargoCommand.run() else + RustValidator(fileConfig, errorReporter, codeMaps).run(cargoCommand, context.cancelIndicator) if (cargoReturnCode == 0) { println("SUCCESS (compiling generated Rust code)") From 80e70a79768b47350d615e7b7f91fbc4c824bc24 Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Mon, 24 Jan 2022 17:35:17 -0600 Subject: [PATCH 46/64] Fixed compile errors --- .../src/org/lflang/federated/FedASTUtils.java | 10 ++-- .../org/lflang/generator/python/PyUtil.java | 49 +++++++++++-------- .../generator/python/PythonGenerator.xtend | 46 ++++++++++++----- .../org/lflang/validation/LFValidator.xtend | 7 +-- .../Python/src/federated/BroadcastFeedback.lf | 5 +- 5 files changed, 73 insertions(+), 44 deletions(-) diff --git a/org.lflang/src/org/lflang/federated/FedASTUtils.java b/org.lflang/src/org/lflang/federated/FedASTUtils.java index 6a12e777d8..81c7e5d5e0 100644 --- a/org.lflang/src/org/lflang/federated/FedASTUtils.java +++ b/org.lflang/src/org/lflang/federated/FedASTUtils.java @@ -619,10 +619,12 @@ private static void addNetworkOutputControlReaction( Input newTriggerForControlReactionVariable = factory.createInput(); newTriggerForControlReactionVariable.setName(triggerName); - // The input needs a type. All targets have a Time type, so we use that. - Type portType = factory.createType(); - portType.setId(generator.getTargetTypes().getTargetTimeType()); - newTriggerForControlReactionVariable.setType(portType); + if (generator.getTarget().requiresTypes) { + // The input needs a type. All targets have a Time type, so we use that. + Type portType = factory.createType(); + portType.setId(generator.getTargetTypes().getTargetTimeType()); + newTriggerForControlReactionVariable.setType(portType); + } top.getInputs().add(newTriggerForControlReactionVariable); diff --git a/org.lflang/src/org/lflang/generator/python/PyUtil.java b/org.lflang/src/org/lflang/generator/python/PyUtil.java index e0677b631c..8dd38ec174 100644 --- a/org.lflang/src/org/lflang/generator/python/PyUtil.java +++ b/org.lflang/src/org/lflang/generator/python/PyUtil.java @@ -39,39 +39,46 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * @author{Soroush Bateni } */ public class PyUtil extends CUtil { - /** - * 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. + + /** + * 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. */ static public String reactorRefName(ReactorInstance instance) { return instance.uniqueID() + "_lf"; } - /** - * 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)}. + /** + * 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)}. */ static public String reactorRef(ReactorInstance instance, String runtimeIndex) { if (runtimeIndex == null) runtimeIndex = runtimeIndex(instance); return PyUtil.reactorRefName(instance) + "[" + runtimeIndex + "]"; } - /** - * 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. + /** + * 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. */ static public String reactorRef(ReactorInstance instance) { diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 24f2034a6f..03b9bc69b4 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -564,9 +564,21 @@ class PythonGenerator extends CGenerator { ''') pythonClasses.append(generateParametersAndStateVariables(decl)) - + + var reactionToGenerate = reactor.allReactions + + if (reactor.isFederated) { + // Filter out reactions that are automatically generated in C in the top level federated reactor + reactionToGenerate.removeIf([ + if (!federate.contains(it)) return true; + if (federate.networkReactions.contains(it)) return true + return false + + ]) + } + var reactionIndex = 0 - for (reaction : reactor.allReactions) { + for (reaction : reactionToGenerate) { val reactionParameters = new StringBuilder() // Will contain parameters for the function (e.g., Foo(x,y,z,...) val inits = new StringBuilder() // Will contain initialization code for some parameters generatePythonReactionParametersAndInitializations(reactionParameters, inits, reactor, reaction) @@ -635,13 +647,8 @@ class PythonGenerator extends CGenerator { ''') // Next, handle state variables for (stateVar : reactor.allStateVars) { - if (!types.getTargetType(stateVar).equals("PyObject*")) { - // If type is given, use it - temporary_code. - append(''' self.«stateVar.name»:«types.getPythonType(stateVar.inferredType)» = «stateVar.pythonInitializer» - ''') - } else if (stateVar.isInitialized) { - // If type is not given, pass along the initialization directly if it is present + if (stateVar.isInitialized) { + // If initialized, pass along the initialization directly if it is present temporary_code.append(''' self.«stateVar.name» = «stateVar.pythonInitializer» ''') } else { @@ -650,16 +657,31 @@ class PythonGenerator extends CGenerator { ''') } } + + temporary_code.append(''' + + ''') + temporary_code.append(''' # Define parameters + self._bank_index = 0 + + ''') + + for (param : decl.toDefinition.allParameters) { + if (!param.name.equals("bank_index")) { + temporary_code.append(''' self._«param.name» = «param.pythonInitializer» + ''') + } + } + + temporary_code.append(''' ''') // Next, create getters for parameters for (param : decl.toDefinition.allParameters) { - if (param.name.equals("bank_index")) { - // Do nothing - } else { + if (!param.name.equals("bank_index")) { temporary_code.append(''' @property ''') temporary_code.append(''' def «param.name»(self): diff --git a/org.lflang/src/org/lflang/validation/LFValidator.xtend b/org.lflang/src/org/lflang/validation/LFValidator.xtend index 741401dca5..c639991447 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.xtend +++ b/org.lflang/src/org/lflang/validation/LFValidator.xtend @@ -83,6 +83,7 @@ import org.lflang.lf.WidthSpec import static extension org.lflang.ASTUtils.* import static extension org.lflang.JavaAstUtils.* import org.lflang.federated.serialization.SupportedSerializers +import org.lflang.lf.ReactorDecl /** * Custom validation checks for Lingua Franca programs. @@ -1282,11 +1283,11 @@ class LFValidator extends BaseLFValidator { Literals.TYPE__STARS ) } - } - else if (this.target == Target.Python) { + } else if (this.target == Target.Python) { if (type !== null) { error( - "Types are not allowed in the Python target", + "Types are not allowed in the Python target (found type " + type.id + + " in " + (type.eContainer as Variable).name +").", Literals.TYPE__ID ) } diff --git a/test/Python/src/federated/BroadcastFeedback.lf b/test/Python/src/federated/BroadcastFeedback.lf index 224d71eb74..b5fe4d230d 100644 --- a/test/Python/src/federated/BroadcastFeedback.lf +++ b/test/Python/src/federated/BroadcastFeedback.lf @@ -5,9 +5,6 @@ target Python { timeout: 1 sec }; reactor SenderAndReceiver { - preamble {= - import sys - =} output out; input[2] inp; state received(False); @@ -23,7 +20,7 @@ reactor SenderAndReceiver { reaction(shutdown) {= if not self.received: print("Failed to receive broadcast") - self.sys.exit(1) + sys.exit(1) =} } federated reactor { From c98f551c6686ee020fa5d927d24eb25490b7a7a8 Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Mon, 24 Jan 2022 17:48:47 -0600 Subject: [PATCH 47/64] Fixed merge artifacts --- .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 c1278e6d55..45517c0ffc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: # Run language server tests. lsp-tests: - uses: lf-lang/lingua-franca/.github/workflows/lsp-tests.yml@vscode-validate-generated-code-cleanups + uses: lf-lang/lingua-franca/.github/workflows/lsp-tests.yml@master # Run the C integration tests. c-tests: From 84d8f27bf580884368e22bfef8afd2efd7dde45b Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 24 Jan 2022 16:54:10 -0800 Subject: [PATCH 48/64] Tests: Code block start/end may be commented out. The problem was that there was a commented-out line that included a "{=". The error inserter didn't detect that it was commented, so it inserted the syntax error in an inappropriate place. --- .../src/org/lflang/tests/lsp/ErrorInserter.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 fcc33720a3..0ddb149ccb 100644 --- a/org.lflang.tests/src/org/lflang/tests/lsp/ErrorInserter.java +++ b/org.lflang.tests/src/org/lflang/tests/lsp/ErrorInserter.java @@ -177,11 +177,13 @@ private void alter(BiFunction, String, Boolean> alterer) { int lineNumber = 0; while (it.hasNext()) { String current = it.next(); - if (current.contains("=}")) inCodeBlock = false; + String uncommented = current.contains("//") ? + current.substring(0, current.indexOf("//")) : current; + if (uncommented.contains("=}")) inCodeBlock = false; if (inCodeBlock && alterer.apply(it, current)) badLines.add(lineNumber); - if (current.contains("{=")) inCodeBlock = true; - if (current.contains("{=") && current.contains("=}")) { - inCodeBlock = current.lastIndexOf("{=") > current.lastIndexOf("=}"); + if (uncommented.contains("{=")) inCodeBlock = true; + if (uncommented.contains("{=") && uncommented.contains("=}")) { + inCodeBlock = uncommented.lastIndexOf("{=") > uncommented.lastIndexOf("=}"); } lineNumber++; } From 5013c1446315bf557a3daef3a8f58e703f0d6404 Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Mon, 24 Jan 2022 21:21:47 -0600 Subject: [PATCH 49/64] Addressed issue in the workaround --- .../generator/python/PythonGenerator.xtend | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 03b9bc69b4..1dc58ef794 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -582,7 +582,7 @@ class PythonGenerator extends CGenerator { val reactionParameters = new StringBuilder() // Will contain parameters for the function (e.g., Foo(x,y,z,...) val inits = new StringBuilder() // Will contain initialization code for some parameters generatePythonReactionParametersAndInitializations(reactionParameters, inits, reactor, reaction) - pythonClasses.append(''' def «pythonReactionFunctionName(reactionIndex)»(self «reactionParameters»): + pythonClasses.append(''' def «pythonReactionFunctionName(reactionIndex)»(self«reactionParameters»): ''') pythonClasses.append(''' «inits» ''') @@ -635,6 +635,19 @@ class PythonGenerator extends CGenerator { } } + + // Instantiate parameters so that the linter doesn't complain about them not existing + temporary_code.append(''' # Define parameters + self._bank_index = 0 + + ''') + + for (param : decl.toDefinition.allParameters) { + if (!param.name.equals("bank_index")) { + temporary_code.append(''' self._«param.name» = «param.pythonInitializer» + ''') + } + } // Handle parameters that are set in instantiation temporary_code.append(''' # Handle parameters that are set in instantiation @@ -658,22 +671,6 @@ class PythonGenerator extends CGenerator { } } - temporary_code.append(''' - - ''') - - temporary_code.append(''' # Define parameters - self._bank_index = 0 - - ''') - - for (param : decl.toDefinition.allParameters) { - if (!param.name.equals("bank_index")) { - temporary_code.append(''' self._«param.name» = «param.pythonInitializer» - ''') - } - } - temporary_code.append(''' From 4e6a93c681a5ccc849d0d2179a019c252d838ebf Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Mon, 24 Jan 2022 21:44:21 -0600 Subject: [PATCH 50/64] Applied suggestion from @petervdonovan --- .../generator/python/PythonGenerator.xtend | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 1dc58ef794..214955c358 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -635,19 +635,6 @@ class PythonGenerator extends CGenerator { } } - - // Instantiate parameters so that the linter doesn't complain about them not existing - temporary_code.append(''' # Define parameters - self._bank_index = 0 - - ''') - - for (param : decl.toDefinition.allParameters) { - if (!param.name.equals("bank_index")) { - temporary_code.append(''' self._«param.name» = «param.pythonInitializer» - ''') - } - } // Handle parameters that are set in instantiation temporary_code.append(''' # Handle parameters that are set in instantiation @@ -683,7 +670,7 @@ class PythonGenerator extends CGenerator { ''') temporary_code.append(''' def «param.name»(self): ''') - temporary_code.append(''' return self._«param.name» + temporary_code.append(''' return self._«param.name» # pylint: disable=no-member ''') } @@ -694,7 +681,7 @@ class PythonGenerator extends CGenerator { ''') temporary_code.append(''' def bank_index(self): ''') - temporary_code.append(''' return self._bank_index + temporary_code.append(''' return self._bank_index # pylint: disable=no-member ''') From 32f2804eba58c3ac8b994e2c1a4d4280917855dc Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Tue, 25 Jan 2022 17:27:40 -0600 Subject: [PATCH 51/64] Use the __main__ module to load functions --- .../src/org/lflang/generator/python/PythonGenerator.xtend | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 9163c5af82..3639ce51d6 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -1798,7 +1798,7 @@ class PythonGenerator extends CGenerator { // Create a PyObject for each reaction pr(initializeTriggerObjects, ''' «nameOfSelfStruct»->_lf_py_reaction_function_«reaction.index» = - get_python_function("«topLevelName»", + get_python_function("__main__", «nameOfSelfStruct»->_lf_name, «CUtil.runtimeIndex(instance)», "«pythonFunctionName»"); @@ -1807,7 +1807,7 @@ class PythonGenerator extends CGenerator { if (reaction.definition.deadline !== null) { pr(initializeTriggerObjects, ''' «nameOfSelfStruct»->_lf_py_deadline_function_«reaction.index» = - get_python_function("«topLevelName»", + get_python_function("__main__", «nameOfSelfStruct»->_lf_name, «CUtil.runtimeIndex(instance)», "deadline_function_«reaction.index»"); From 845b9b3df19e49c5097ad96f3243b4a565793cb9 Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Tue, 25 Jan 2022 17:27:49 -0600 Subject: [PATCH 52/64] Added timeouts to tests --- org.lflang/src/lib/py/reactor-c-py | 2 +- test/Python/src/federated/DistributedNoReact.lf | 2 +- test/Python/src/federated/DistributedStructAsType.lf | 2 +- test/Python/src/federated/DistributedStructAsTypeDirect.lf | 2 +- test/Python/src/federated/DistributedStructParallel.lf | 2 +- test/Python/src/federated/DistributedStructPrint.lf | 2 +- test/Python/src/federated/DistributedStructScale.lf | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/org.lflang/src/lib/py/reactor-c-py b/org.lflang/src/lib/py/reactor-c-py index ca321197e7..4e19241ef2 160000 --- a/org.lflang/src/lib/py/reactor-c-py +++ b/org.lflang/src/lib/py/reactor-c-py @@ -1 +1 @@ -Subproject commit ca321197e7b25dbee2d4baccd4bb1e6df23bfc1c +Subproject commit 4e19241ef26662a006c72e4dcc7543c0d3840179 diff --git a/test/Python/src/federated/DistributedNoReact.lf b/test/Python/src/federated/DistributedNoReact.lf index 100cb8d016..7599b591fa 100644 --- a/test/Python/src/federated/DistributedNoReact.lf +++ b/test/Python/src/federated/DistributedNoReact.lf @@ -1,4 +1,4 @@ -target Python; +target Python { timeout: 2 sec }; preamble {= class C: diff --git a/test/Python/src/federated/DistributedStructAsType.lf b/test/Python/src/federated/DistributedStructAsType.lf index 6ae5aa2a8c..351ceeb660 100644 --- a/test/Python/src/federated/DistributedStructAsType.lf +++ b/test/Python/src/federated/DistributedStructAsType.lf @@ -1,4 +1,4 @@ -target Python {files: ../include/hello.py}; +target Python {files: ../include/hello.py, timeout: 2 secs}; import Source, Print from "../StructAsType.lf" diff --git a/test/Python/src/federated/DistributedStructAsTypeDirect.lf b/test/Python/src/federated/DistributedStructAsTypeDirect.lf index 4f113427c5..10dd11386b 100644 --- a/test/Python/src/federated/DistributedStructAsTypeDirect.lf +++ b/test/Python/src/federated/DistributedStructAsTypeDirect.lf @@ -1,4 +1,4 @@ -target Python {files: ../include/hello.py}; +target Python {files: ../include/hello.py, timeout: 2 secs}; import Source, Print from "../StructAsTypeDirect.lf" diff --git a/test/Python/src/federated/DistributedStructParallel.lf b/test/Python/src/federated/DistributedStructParallel.lf index 36de3d0867..2fcc10838b 100644 --- a/test/Python/src/federated/DistributedStructParallel.lf +++ b/test/Python/src/federated/DistributedStructParallel.lf @@ -1,6 +1,6 @@ // Source allocates a class object and then sends it to two reactors, // each of which want to modify it. -target Python {files: ["../include/hello.py"]}; +target Python {files: ["../include/hello.py"], timeout: 2 secs}; import Source from "../StructScale.lf"; import Check, Print from "../StructParallel.lf" diff --git a/test/Python/src/federated/DistributedStructPrint.lf b/test/Python/src/federated/DistributedStructPrint.lf index 5dc010e65c..2a48d8c5f1 100644 --- a/test/Python/src/federated/DistributedStructPrint.lf +++ b/test/Python/src/federated/DistributedStructPrint.lf @@ -1,6 +1,6 @@ // Source produces a dynamically allocated class object, which it passes // to Print. Reference counting ensures that the struct is freed. -target Python {files: ["../include/hello.py"]}; +target Python {files: ["../include/hello.py"], timeout: 2 sec}; import Print, Check from "../StructPrint.lf" preamble {= diff --git a/test/Python/src/federated/DistributedStructScale.lf b/test/Python/src/federated/DistributedStructScale.lf index c2685ab003..21e8418c29 100644 --- a/test/Python/src/federated/DistributedStructScale.lf +++ b/test/Python/src/federated/DistributedStructScale.lf @@ -1,6 +1,6 @@ // Source produces a dynamically allocated class object, which it passes // to Scale. Scale modifies it and passes it to Print. -target Python {files: ["../include/hello.py"]}; +target Python {files: ["../include/hello.py"], timeout: 2 sec}; import Source, TestInput, Print from "../StructScale.lf" preamble {= From 1b399eb685b2e960ed7addd3df4c0611189e219d Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Tue, 25 Jan 2022 17:43:05 -0600 Subject: [PATCH 53/64] Use the docker hub image for RTI in the compose file --- org.lflang/src/org/lflang/generator/c/CGenerator.xtend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 2b17a30c48..d48a897981 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -936,7 +936,7 @@ class CGenerator extends GeneratorBase { if (targetConfig.dockerOptions !== null) { if (isFederated) { - appendRtiToDockerComposeServices(dockerComposeServices, rtiName, "rti:rti", federates.size); + appendRtiToDockerComposeServices(dockerComposeServices, rtiName, "lflang/rti:rti", federates.size); } writeFederatesDockerComposeFile(dockerComposeDir, dockerComposeServices, dockerComposeNetworkName); } From 4e639775297918a1289c2c7bdca3b365d85aeea7 Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Sun, 14 Nov 2021 10:42:23 -0600 Subject: [PATCH 54/64] Updated test since port values are read-only --- test/Python/src/StructAsTypeDirect.lf | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/Python/src/StructAsTypeDirect.lf b/test/Python/src/StructAsTypeDirect.lf index 4620decb6d..62d8dcc33a 100644 --- a/test/Python/src/StructAsTypeDirect.lf +++ b/test/Python/src/StructAsTypeDirect.lf @@ -7,17 +7,18 @@ import hello reactor Source { output out; reaction(startup) -> out {= - out.value = hello.hello() - out.value.name = "Earth" - out.value.value = 42 - out.set(out.value) + out_value = out.value + out_value = hello.hello() + out_value.name = "Earth" + out_value.value = 42 + out.set(out_value) =} } // expected parameter is for testing. reactor Print(expected(42)) { input _in; reaction(_in) {= - print("Received: name = {:s}, value = {:d}\n".format(_in.value.name, _in.value.value)) + print("Received: name = {:s}, value = {:d}".format(_in.value.name, _in.value.value)) if _in.value.value != self.expected: sys.stderr.write("ERROR: Expected value to be {:d}.\n".format(self.expected)) exit(1) From 19ccbf6e525a0c81ce30abd6d1c1e9b149c2ca79 Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Tue, 25 Jan 2022 20:30:34 -0600 Subject: [PATCH 55/64] Added containarized variant of send class --- .../federated/DistributedSendClassContainerized.lf | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 test/Python/src/docker/federated/DistributedSendClassContainerized.lf diff --git a/test/Python/src/docker/federated/DistributedSendClassContainerized.lf b/test/Python/src/docker/federated/DistributedSendClassContainerized.lf new file mode 100644 index 0000000000..5ca3e0f8d9 --- /dev/null +++ b/test/Python/src/docker/federated/DistributedSendClassContainerized.lf @@ -0,0 +1,12 @@ +target Python { + coordination: centralized, + docker: true +}; + +import A, B from "../../federated/DistributedSendClass.lf"; + +federated reactor at rti { + a = new A(); + b = new B(); + b.o -> a.o; +} \ No newline at end of file From 634ed8e221456a388f8636038530cce5ab1e7930 Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Tue, 25 Jan 2022 23:01:40 -0600 Subject: [PATCH 56/64] New lines --- .../src/docker/federated/DistributedSendClassContainerized.lf | 2 +- test/Python/src/federated/DistributedNoReact.lf | 2 +- test/Python/src/federated/DistributedSendClass.lf | 2 +- test/Python/src/federated/DistributedStructAsType.lf | 2 +- test/Python/src/federated/DistributedStructAsTypeDirect.lf | 2 +- test/Python/src/federated/DistributedStructParallel.lf | 2 +- test/Python/src/federated/DistributedStructPrint.lf | 2 +- test/Python/src/federated/DistributedStructScale.lf | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/Python/src/docker/federated/DistributedSendClassContainerized.lf b/test/Python/src/docker/federated/DistributedSendClassContainerized.lf index 5ca3e0f8d9..0d5101784a 100644 --- a/test/Python/src/docker/federated/DistributedSendClassContainerized.lf +++ b/test/Python/src/docker/federated/DistributedSendClassContainerized.lf @@ -9,4 +9,4 @@ federated reactor at rti { a = new A(); b = new B(); b.o -> a.o; -} \ No newline at end of file +} diff --git a/test/Python/src/federated/DistributedNoReact.lf b/test/Python/src/federated/DistributedNoReact.lf index 7599b591fa..a3dec19ce6 100644 --- a/test/Python/src/federated/DistributedNoReact.lf +++ b/test/Python/src/federated/DistributedNoReact.lf @@ -21,4 +21,4 @@ federated reactor { a = new A(); b = new B(); b.o -> a.o; -} \ No newline at end of file +} diff --git a/test/Python/src/federated/DistributedSendClass.lf b/test/Python/src/federated/DistributedSendClass.lf index 05e6bbfed3..d8b7f7ae82 100644 --- a/test/Python/src/federated/DistributedSendClass.lf +++ b/test/Python/src/federated/DistributedSendClass.lf @@ -24,4 +24,4 @@ federated reactor { a = new A(); b = new B(); b.o -> a.o; -} \ No newline at end of file +} diff --git a/test/Python/src/federated/DistributedStructAsType.lf b/test/Python/src/federated/DistributedStructAsType.lf index 351ceeb660..5477dd9642 100644 --- a/test/Python/src/federated/DistributedStructAsType.lf +++ b/test/Python/src/federated/DistributedStructAsType.lf @@ -10,4 +10,4 @@ federated reactor { s = new Source(); p = new Print(); s.out -> p._in; -} \ No newline at end of file +} diff --git a/test/Python/src/federated/DistributedStructAsTypeDirect.lf b/test/Python/src/federated/DistributedStructAsTypeDirect.lf index 10dd11386b..33b338b836 100644 --- a/test/Python/src/federated/DistributedStructAsTypeDirect.lf +++ b/test/Python/src/federated/DistributedStructAsTypeDirect.lf @@ -10,4 +10,4 @@ federated reactor { s = new Source(); p = new Print(); s.out -> p._in; -} \ No newline at end of file +} diff --git a/test/Python/src/federated/DistributedStructParallel.lf b/test/Python/src/federated/DistributedStructParallel.lf index 2fcc10838b..e5d7e0ff49 100644 --- a/test/Python/src/federated/DistributedStructParallel.lf +++ b/test/Python/src/federated/DistributedStructParallel.lf @@ -18,4 +18,4 @@ federated reactor { s.out -> c2._in; c1.out -> p1._in; c2.out -> p2._in; -} \ No newline at end of file +} diff --git a/test/Python/src/federated/DistributedStructPrint.lf b/test/Python/src/federated/DistributedStructPrint.lf index 2a48d8c5f1..7d6129302a 100644 --- a/test/Python/src/federated/DistributedStructPrint.lf +++ b/test/Python/src/federated/DistributedStructPrint.lf @@ -11,4 +11,4 @@ federated reactor { s = new Print(); p = new Check(); s.out -> p._in; -} \ No newline at end of file +} diff --git a/test/Python/src/federated/DistributedStructScale.lf b/test/Python/src/federated/DistributedStructScale.lf index 21e8418c29..749fda06c0 100644 --- a/test/Python/src/federated/DistributedStructScale.lf +++ b/test/Python/src/federated/DistributedStructScale.lf @@ -13,4 +13,4 @@ federated reactor { p = new TestInput(expected=84); s.out -> c._in; c.out -> p._in; -} \ No newline at end of file +} From 3c08c561ca57603e5c13b7173d4b0b66898a6e76 Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Tue, 25 Jan 2022 23:05:59 -0600 Subject: [PATCH 57/64] Added to RELEASE.md --- RELEASES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index bde551fabd..78a3b9833f 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -47,3 +47,6 @@ The `lfc` command line application is suitable for: - [C++](https://github.com/icyphy/lingua-franca/wiki/Writing-Reactors-in-Cpp) - [Python](https://github.com/icyphy/lingua-franca/wiki/Writing-Reactors-in-Python) - [TypeScript](https://github.com/icyphy/lingua-franca/wiki/Writing-Reactors-in-TypeScript) + +### Bug Fixes +- fixed an issue where top-level custom Python classes were being serialized incorrectly From 6d6d65394bd27a4b2891a38d5fc5b1d804526e5e Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Wed, 26 Jan 2022 16:29:02 -0600 Subject: [PATCH 58/64] Fixed a bug in the Python code --- .../generator/python/PythonGenerator.xtend | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 3639ce51d6..c96b986162 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -398,7 +398,7 @@ class PythonGenerator extends CGenerator { append(''' «trigger.variable.name»[i].value = copy.deepcopy(mutable_«trigger.variable.name»[i].value) ''') } else { - inits.append('''«trigger.variable.name» = Make + inits.append('''«trigger.variable.name» = Make() ''') inits. append('''«trigger.variable.name».value = copy.deepcopy(mutable_«trigger.variable.name».value) @@ -478,13 +478,14 @@ class PythonGenerator extends CGenerator { if (port.container.widthSpec !== null) { // It's a bank inits.append(''' - «port.container.name» = [Make] * len(«port.container.name»_«port.variable.name») + «port.container.name» = [None] * len(«port.container.name»_«port.variable.name») for i in range(len(«port.container.name»_«port.variable.name»)): + «port.container.name»[i] = Make() «port.container.name»[i].«port.variable.name» = «port.container.name»_«port.variable.name»[i] ''') } else { - inits.append('''«port.container.name» = Make + inits.append('''«port.container.name» = Make() ''') inits.append('''«port.container.name».«port.variable.name» = «port.container.name»_«port.variable.name» ''') @@ -1910,8 +1911,9 @@ class PythonGenerator extends CGenerator { } for (int i = 0; i < «reactorName»_width; i++) { - if (PyList_Append( - «reactorName»_py_list, + if (PyList_SetItem( + «reactorName»_py_list, + i, convert_C_port_to_py( self->_lf_«reactorName»[i].«output.name», «widthSpec» @@ -2013,8 +2015,9 @@ class PythonGenerator extends CGenerator { } for (int i = 0; i < «definition.name»_width; i++) { - if (PyList_Append( - «definition.name»_py_list, + if (PyList_SetItem( + «definition.name»_py_list, + i, convert_C_port_to_py( self->_lf_«definition.name»[i].«input.name», «widthSpec» From 9d539148be7201ae418aec35c145f63f83fccf64 Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Wed, 26 Jan 2022 16:39:47 -0600 Subject: [PATCH 59/64] Added tests --- .../src/multiport/BankReactionsInContainer.lf | 63 +++++++++++++++++++ .../src/multiport/NestedInterleavedBanks.lf | 34 ++++++++++ .../Python/src/multiport/ReactionsToNested.lf | 38 +++++++++++ 3 files changed, 135 insertions(+) create mode 100644 test/Python/src/multiport/BankReactionsInContainer.lf create mode 100644 test/Python/src/multiport/NestedInterleavedBanks.lf create mode 100644 test/Python/src/multiport/ReactionsToNested.lf diff --git a/test/Python/src/multiport/BankReactionsInContainer.lf b/test/Python/src/multiport/BankReactionsInContainer.lf new file mode 100644 index 0000000000..2ac425094d --- /dev/null +++ b/test/Python/src/multiport/BankReactionsInContainer.lf @@ -0,0 +1,63 @@ +/** + * This tests an output that is broadcast back to a multiport input of a bank. + */ +target Python { + timeout: 1 sec, +}; +reactor R (bank_index(0)) { + output[2] out; + input[2] inp; + state received(false); + + reaction(startup) -> out {= + for (i, p) in enumerate(out): + value = self.bank_index * 2 + i + p.set(value) + print(f"Inner sending {value} to bank {self.bank_index} channel {i}.") + =} + + reaction(inp) {= + for (i, p) in enumerate(inp): + if p.is_present: + print(f"Inner received {p.value} in bank {self.bank_index}, channel {i}") + self.received = True + if p.value != (self.bank_index * 2 + i): + sys.stderr.write(f"ERROR: Expected {self.bank_index * 2 + i}.\n") + exit(1) + =} + reaction(shutdown) {= + print("Inner shutdown invoked.") + if self.received is not True: + sys.stderr.write(f"ERROR: Received no input.") + exit(1) + =} +} +main reactor { + s = new[2] R(); + state received(false); + + reaction(startup) -> s.inp {= + count = 0 + for i in range(len(s)): + for (j, p) in enumerate(s[i].inp): + print(f"Sending {count} to bank {i} channel {j}.") + p.set(count) + count+=1 + =} + reaction(s.out) {= + for i in range(len(s)): + for (j, p) in enumerate(s[i].out): + if p.is_present: + print(f"Outer received {p.value} on bank {i} channel {j}.") + self.received = True + if p.value != i * 2 + j: + sys.stderr.write(f"ERROR: Expected {i*2+j}.\n") + exit(1) + =} + reaction(shutdown) {= + print("Outer shutdown invoked.") + if self.received is not True: + sys.stderr.write(f"ERROR: Received no input.\n") + exit(1) + =} +} diff --git a/test/Python/src/multiport/NestedInterleavedBanks.lf b/test/Python/src/multiport/NestedInterleavedBanks.lf new file mode 100644 index 0000000000..e0be0d5595 --- /dev/null +++ b/test/Python/src/multiport/NestedInterleavedBanks.lf @@ -0,0 +1,34 @@ +/** + * Test nested banks with interleaving. + * @author Edward A. Lee + */ +target Python; +reactor A(bank_index(0), outer_bank_index(0)) { + output[2] p; + reaction(startup) -> p {= + for i, port in enumerate(p): + port.set(self.outer_bank_index * 4 + self.bank_index * 2 + i + 1) + print(f"A sending {port.value}.") + =} +} +reactor B(bank_index(0)) { + output[4] q; + a = new[2] A(outer_bank_index = bank_index); + interleaved(a.p) -> q; +} +reactor C { + input[8] i; + reaction(i) {= + expected = [1, 3, 2, 4, 5, 7, 6, 8] + for j, port in enumerate(i): + print(f"C received {port.value}.") + if port.value != expected[j]: + sys.stderr.write(f"ERROR: Expected {expected[j]}.\n") + exit(1) + =} +} +main reactor { + b = new[2] B(); + c = new C(); + b.q -> c.i; +} \ No newline at end of file diff --git a/test/Python/src/multiport/ReactionsToNested.lf b/test/Python/src/multiport/ReactionsToNested.lf new file mode 100644 index 0000000000..f2ee46b355 --- /dev/null +++ b/test/Python/src/multiport/ReactionsToNested.lf @@ -0,0 +1,38 @@ +// This test connects a simple counting source to tester +// that checks against its own count. +target Python { + timeout: 1 sec +}; +reactor T(expected(0)) { + input z; + state received(false); + reaction(z) {= + print(f"T received {z.value}.") + self.received = True + if z.value != self.expected: + sys.stderr.write(f"ERROR: Expected {self.response}") + exit(1) + =} + reaction(shutdown) {= + if self.received is not True: + sys.stderr.write(f"ERROR: No input received.") + exit(1) + =} +} + +reactor D { + input[2] y; + t1 = new T(expected = 42); + t2 = new T(expected = 43); + y -> t1.z, t2.z; +} + +main reactor { + d = new D(); + reaction(startup) -> d.y {= + d.y[0].set(42) + =} + reaction(startup) -> d.y {= + d.y[1].set(43) + =} +} \ No newline at end of file From 584a21ee59fb7d46bc796355d90fc3e33ef9939d Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Wed, 26 Jan 2022 16:56:22 -0600 Subject: [PATCH 60/64] Factored out redundant code --- .../generator/python/PythonGenerator.xtend | 137 ++++++++---------- 1 file changed, 61 insertions(+), 76 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index c96b986162..589ffb33a2 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -471,8 +471,10 @@ class PythonGenerator extends CGenerator { /** * Generate into the specified string builder (inits) the code to - * initialize local variable for port. - * @param + * initialize local variable for port so that it can be used in the body of + * the Python function. + * @param port The port to generate code for. + * @param inits The generated code will be put in inits. */ protected def StringBuilder generatePythonPortVariableInReaction(VarRef port, StringBuilder inits) { if (port.container.widthSpec !== null) { @@ -1895,43 +1897,7 @@ class PythonGenerator extends CGenerator { } // Output is in a bank. // Create a Python list - pr(''' - PyObject* «reactorName»_py_list = PyList_New(«reactorName»_width); - - if(«reactorName»_py_list == NULL) { - error_print("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 < «reactorName»_width; i++) { - if (PyList_SetItem( - «reactorName»_py_list, - i, - convert_C_port_to_py( - self->_lf_«reactorName»[i].«output.name», - «widthSpec» - ) - ) != 0) { - error_print("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); - } - } - - ''') + generatePythonListForContainedBank(reactorName, output, widthSpec) pyObjects.append(''', «reactorName»_py_list''') } else { var String widthSpec = "-2" @@ -1942,6 +1908,61 @@ class PythonGenerator extends CGenerator { } } } + + /** + * 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 generate 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. + */ + protected def void generatePythonListForContainedBank(String reactorName, Port port, String widthSpec) { + pr(''' + PyObject* «reactorName»_py_list = PyList_New(«reactorName»_width); + + if(«reactorName»_py_list == NULL) { + error_print("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 < «reactorName»_width; i++) { + if (PyList_SetItem( + «reactorName»_py_list, + i, + convert_C_port_to_py( + self->_lf_«reactorName»[i].«port.name», + «widthSpec» + ) + ) != 0) { + error_print("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 into the specified string builder the code to * send local variables for output ports to a Python reaction function @@ -1999,43 +2020,7 @@ class PythonGenerator extends CGenerator { } // Contained reactor is a bank. // Create a Python list - pr(''' - PyObject* «definition.name»_py_list = PyList_New(«definition.name»_width); - - if(«definition.name»_py_list == NULL) { - error_print("Could not create the list needed for «definition.name»."); - 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 < «definition.name»_width; i++) { - if (PyList_SetItem( - «definition.name»_py_list, - i, - convert_C_port_to_py( - self->_lf_«definition.name»[i].«input.name», - «widthSpec» - ) - ) != 0) { - error_print("Could not add elements to the list for «definition.name»."); - 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); - } - } - - ''') + generatePythonListForContainedBank(definition.name, input, widthSpec); pyObjects.append(''', «definition.name»_py_list''') } else { From d1375be492dc19e9ada7c30502275d17f6cd3f1a Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Wed, 26 Jan 2022 21:56:15 -0600 Subject: [PATCH 61/64] Updated RELEASES.md --- RELEASES.md | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 78a3b9833f..e6c96c9810 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -49,4 +49,32 @@ The `lfc` command line application is suitable for: - [TypeScript](https://github.com/icyphy/lingua-franca/wiki/Writing-Reactors-in-TypeScript) ### Bug Fixes -- fixed an issue where top-level custom Python classes were being serialized incorrectly +- fixed an issue where top-level custom Python classes were being serialized + incorrectly + +### New Features +- [Python] `bank_index` (useful for banks of reactors) is now a proper parameter + that can be passed down the reactor hierarchy via parameter assignment. For + example, the following code snippet now works as expected: + ```Python + target Python; + reactor Bar (bank_index(0), parent_bank_index(0)) { + reaction(startup) {= + print(f"My parent bank index is {self.parent_bank_index}.") + =} + } + reactor Foo (bank_index(0)) { + bar = new[2] Bar(parent_bank_index = bank_index) + } + main reactor { + f = new[2] Foo() + } + ``` + The output will be: + + ```bash + My parent bank index is 0. + My parent bank index is 1. + My parent bank index is 1. + My parent bank index is 0. + ``` From d93492d48e7a3cc9ef888781559bd3d71cc8ae02 Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Wed, 26 Jan 2022 22:10:29 -0600 Subject: [PATCH 62/64] Documentation only --- .../generator/python/PythonGenerator.xtend | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index aa0559f1bf..b0299a5c32 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -722,7 +722,7 @@ class PythonGenerator extends CGenerator { * 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 pythonClassesInstantiation The class instantiations are appended to this string builder + * @param pythonClassesInstantiation The class instantiations are appended to this code builder * @param federate The federate instance for the reactor instance */ def void generatePythonClassInstantiation(ReactorInstance instance, CodeBuilder pythonClassesInstantiation, @@ -1852,12 +1852,14 @@ class PythonGenerator extends CGenerator { /** * Generate code to convert C actions to Python action capsules - * @see pythontarget.h - * @param builder The string builder into which to write the code. - * @param structs A map from reactor instantiations to a place to write - * struct fields. - * @param port The port. - * @param reactor The reactor. + * @see pythontarget.h. + * @param pyObjectDescriptor A string representing a list of Python format types (e.g., "O") that + * can be passed to Py_BuildValue. The object type for the converted action will + * be appended to this string (e.g., "OO"). + * @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. */ def generateActionVariableToSendToPythonReaction(StringBuilder pyObjectDescriptor, StringBuilder pyObjects, Action action, ReactorDecl decl) { @@ -1867,18 +1869,18 @@ class PythonGenerator extends CGenerator { pyObjects.append(''', convert_C_action_to_py(«action.name»)''') } - /** Generate into the specified string builder the code to - * send local variables for ports to a Python 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 string builder into which to write the code. - * @param structs A map from reactor instantiations to a place to write - * struct fields. - * @param port The port. - * @param reactor The reactor. + /** + * 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 pyObjectDescriptor A string representing a list of Python format types (e.g., "O") that + * can be passed to Py_BuildValue. The object type for the converted port will + * be appended to this string (e.g., "OO"). + * @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. */ private def generatePortVariablesToSendToPythonReaction( StringBuilder pyObjectDescriptor, From 5841696d466e0f087557a500020d1d74126fb510 Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Thu, 27 Jan 2022 00:46:37 -0600 Subject: [PATCH 63/64] Fixed the usage of the Make class --- .../src/org/lflang/generator/python/PythonGenerator.xtend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index b0299a5c32..823bfebef2 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -481,7 +481,7 @@ class PythonGenerator extends CGenerator { ''') } else { - inits.pr('''«port.container.name» = Make()''') + inits.pr('''«port.container.name» = Make''') inits.pr('''«port.container.name».«port.variable.name» = «port.container.name»_«port.variable.name»''') } From a776a1030c96a84c3f7db67ebdd5fb4d51a14ced Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Fri, 28 Jan 2022 13:44:28 -0600 Subject: [PATCH 64/64] Comments only --- .../src/org/lflang/generator/python/PythonGenerator.xtend | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 823bfebef2..e63e46d484 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -1,7 +1,8 @@ /* Generator for the Python target. */ /************* - * Copyright (c) 2019, The University of California at Berkeley. + * Copyright (c) 2022, The University of California at Berkeley. + * Copyright (c) 2022, 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: @@ -466,7 +467,7 @@ class PythonGenerator extends CGenerator { /** * 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 function. + * the Python reaction. * @param port The port to generate code for. * @param inits The generated code will be put in inits. */ @@ -1921,7 +1922,7 @@ class PythonGenerator extends CGenerator { * (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 generate Python function will have the signature reaction_function_0(self, s_out), where + * 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}. *