Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Python methods #1230

Merged
merged 7 commits into from
Jun 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions org.lflang/src/org/lflang/generator/c/CGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -1205,19 +1205,26 @@ private void generateReactorClass(ReactorDecl reactor) {
var constructorCode = new CodeBuilder();
generateAuxiliaryStructs(reactor);
generateSelfStruct(reactor, constructorCode);
CMethodGenerator.generateMethods(reactor, code, types);
generateMethods(reactor);
generateReactions(reactor, currentFederate);
generateConstructor(reactor, currentFederate, constructorCode);

code.pr("// =============== END reactor class " + reactor.getName());
code.pr("");
}

/**
* Generate methods for {@code reactor}.
*/
protected void generateMethods(ReactorDecl reactor) {
CMethodGenerator.generateMethods(reactor, code, types);
}

/**
* Generates preambles defined by user for a given reactor
* @param reactor The given reactor
*/
public void generateUserPreamblesForReactor(Reactor reactor) {
protected void generateUserPreamblesForReactor(Reactor reactor) {
for (Preamble p : convertToEmptyListIfNull(reactor.getPreambles())) {
code.pr("// *********** From the preamble, verbatim:");
code.prSourceLineNumber(p.getCode());
Expand Down Expand Up @@ -1506,7 +1513,7 @@ private void generateInteractingContainedReactors(
* @param decl The reactor declaration for the self struct
* @param constructorCode Code that is executed when the reactor is instantiated
*/
public void generateSelfStructExtension(
protected void generateSelfStructExtension(
CodeBuilder body,
ReactorDecl decl,
CodeBuilder constructorCode
Expand Down Expand Up @@ -1544,7 +1551,7 @@ public void generateReactions(ReactorDecl decl, FederateInstance federate) {
* @param decl The reactor.
* @param reactionIndex The position of the reaction within the reactor.
*/
public void generateReaction(Reaction reaction, ReactorDecl decl, int reactionIndex) {
protected void generateReaction(Reaction reaction, ReactorDecl decl, int reactionIndex) {
code.pr(CReactionGenerator.generateReaction(
reaction,
decl,
Expand Down Expand Up @@ -2031,7 +2038,7 @@ private void generateInitializeActionToken(ReactorInstance reactor) {
* is relevant to the federate.
* @param instance The reactor instance.
*/
public void generateReactorInstanceExtension(ReactorInstance instance) {
protected void generateReactorInstanceExtension(ReactorInstance instance) {
// Do nothing
}

Expand All @@ -2041,7 +2048,7 @@ public void generateReactorInstanceExtension(ReactorInstance instance) {
* of the same reactor.
* @param instance The reactor class instance
*/
public void generateStateVariableInitializations(ReactorInstance instance) {
protected void generateStateVariableInitializations(ReactorInstance instance) {
var reactorClass = instance.getDefinition().getReactorClass();
var selfRef = CUtil.reactorRef(instance);
for (StateVar stateVar : allStateVars(toDefinition(reactorClass))) {
Expand Down Expand Up @@ -2126,7 +2133,7 @@ private void generateModeStructure(ReactorInstance instance) {
* Generate runtime initialization code for parameters of a given reactor instance
* @param instance The reactor instance.
*/
public void generateParameterInitialization(ReactorInstance instance) {
protected void generateParameterInitialization(ReactorInstance instance) {
var selfRef = CUtil.reactorRef(instance);
// Declare a local bank_index variable so that initializers can use it.
initializeTriggerObjects.pr("int bank_index = "+CUtil.bankIndex(instance)+";");
Expand Down
25 changes: 17 additions & 8 deletions org.lflang/src/org/lflang/generator/python/PythonGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import org.eclipse.xtext.util.CancelIndicator;
import org.eclipse.xtext.xbase.lib.Exceptions;
import org.eclipse.xtext.xbase.lib.IterableExtensions;

import org.lflang.ASTUtils;
import org.lflang.ErrorReporter;
import org.lflang.FileConfig;
Expand All @@ -57,7 +58,6 @@
import org.lflang.federated.serialization.SupportedSerializers;
import org.lflang.generator.CodeBuilder;
import org.lflang.generator.CodeMap;
import org.lflang.generator.DockerGeneratorBase;
import org.lflang.generator.GeneratorResult;
import org.lflang.generator.GeneratorUtils;
import org.lflang.generator.IntegratedBuilder;
Expand All @@ -80,9 +80,10 @@
import org.lflang.lf.VarRef;
import org.lflang.util.FileUtil;
import org.lflang.util.LFCommand;
import com.google.common.base.Objects;
import org.lflang.util.StringUtil;

import com.google.common.base.Objects;


/**
* Generator for Python target. This class generates Python code defining each reactor
Expand Down Expand Up @@ -763,7 +764,7 @@ public String generateForwardBody(Action action, VarRef port) {
* @param reactionIndex The position of the reaction within the reactor.
*/
@Override
public void generateReaction(Reaction reaction, ReactorDecl decl, int reactionIndex) {
protected void generateReaction(Reaction reaction, ReactorDecl decl, int reactionIndex) {
Reactor reactor = ASTUtils.toDefinition(decl);

// Delay reactors and top-level reactions used in the top-level reactor(s) in federated execution are generated in C
Expand All @@ -784,7 +785,7 @@ public void generateReaction(Reaction reaction, ReactorDecl decl, int reactionIn
* @return Initialization code fore state variables of instance
*/
@Override
public void generateStateVariableInitializations(ReactorInstance instance) {
protected void generateStateVariableInitializations(ReactorInstance instance) {
// Do nothing
}

Expand All @@ -794,19 +795,27 @@ public void generateStateVariableInitializations(ReactorInstance instance) {
* @param instance The reactor instance.
*/
@Override
public void generateParameterInitialization(ReactorInstance instance) {
protected void generateParameterInitialization(ReactorInstance instance) {
// Do nothing
// Parameters are initialized in Python
}

/**
* Do nothing.
* Methods are generated in Python not C.
* @see PythonMethodGenerator
*/
@Override
protected void generateMethods(ReactorDecl reactor) { }

/**
* Generate C preambles defined by user for a given reactor
* Since the Python generator expects preambles written in C,
* this function is overridden and does nothing.
* @param reactor The given reactor
*/
@Override
public void generateUserPreamblesForReactor(Reactor reactor) {
protected void generateUserPreamblesForReactor(Reactor reactor) {
// Do nothing
}

Expand All @@ -817,7 +826,7 @@ public void generateUserPreamblesForReactor(Reactor reactor) {
* @param reactions The reactions of this instance.
*/
@Override
public void generateReactorInstanceExtension(
protected void generateReactorInstanceExtension(
ReactorInstance instance
) {
initializeTriggerObjects.pr(PythonReactionGenerator.generateCPythonReactionLinkers(instance, mainDef));
Expand All @@ -831,7 +840,7 @@ public void generateReactorInstanceExtension(
* @param constructorCode Code that is executed when the reactor is instantiated
*/
@Override
public void generateSelfStructExtension(
protected void generateSelfStructExtension(
CodeBuilder selfStructBody,
ReactorDecl decl,
CodeBuilder constructorCode
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.lflang.generator.python;

import java.util.stream.Collectors;

import org.lflang.ASTUtils;
import org.lflang.lf.Method;
import org.lflang.lf.MethodArgument;
import org.lflang.lf.Reactor;

/**
* Collection of functions to generate Python code to declare methods.
*
* @author {Soroush Bateni <[email protected]>}
*/
public class PythonMethodGenerator {

/**
* Generate a Python method definition for {@code method}.
*/
public static String generateMethod(Method method) {
return String.join("\n",
"# Implementation of method "+method.getName()+"().",
"def "+method.getName()+"(self, "+generateMethodArgumentList(method)+"):",
ASTUtils.toText(method.getCode()).indent(4)
);
}

/**
* Generate methods for a reactor class.
*
* @param reactor The reactor.
*/
public static String generateMethods(
Reactor reactor
) {
return ASTUtils.allMethods(reactor)
.stream()
.map(m -> generateMethod(m))
.collect(Collectors.joining());
}

/**
* Generate a list of arguments for {@code method} delimited with ', '.
*/
private static String generateMethodArgumentList(Method method) {
return String.join(", ",
method.getArguments()
.stream()
.map(MethodArgument::getName)
.collect(Collectors.toList()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public static String generatePythonGetters(ReactorDecl decl) {
for (Parameter param : getAllParameters(decl)) {
if (!param.getName().equals("bank_index")) {
lines.addAll(List.of(
"",
"@property",
"def "+param.getName()+"(self):",
" return self._"+param.getName()+" # pylint: disable=no-member",
Expand All @@ -67,11 +68,13 @@ public static String generatePythonGetters(ReactorDecl decl) {
}
// Create a special property for bank_index
lines.addAll(List.of(
"",
"@property",
"def bank_index(self):",
" return self._bank_index # pylint: disable=no-member",
""
));
lines.add("\n");
return String.join("\n", lines);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ public static String generateCPythonSTPCaller(ReactorDecl decl,
* @param reactorDeclName The name of the reactor for debugging purposes
* @param pythonFunctionName The name of the function in the .py file.
* @param cpythonFunctionName The name of the function in self struct of the .c file.
* @param pyObjectDescriptor CPython related descriptors for each object in "pyObjects".
* @param pyObjects CPython related objects
*/
private static String generateCPythonFunctionCaller(String reactorDeclName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,17 @@ public static String generatePythonClass(ReactorInstance instance, FederateInsta
// Handle runtime initializations
pythonClasses.pr(generatePythonConstructor(decl, types));
pythonClasses.pr(PythonParameterGenerator.generatePythonGetters(decl));
// Generate methods
pythonClasses.pr(PythonMethodGenerator.generateMethods(reactor));
// Generate reactions
List<Reaction> reactionToGenerate = ASTUtils.allReactions(reactor);
if (reactor.isFederated()) {
// Filter out reactions that are automatically generated in C in the top level federated reactor
reactionToGenerate.removeIf(it -> !federate.contains(it) || federate.networkReactions.contains(it));
}
pythonClasses.pr(PythonReactionGenerator.generatePythonReactions(reactor, reactionToGenerate));
pythonClasses.unindent();
pythonClasses.pr("\n");
instantiatedClasses.add(className);
}

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
29 changes: 29 additions & 0 deletions test/Python/src/Methods.lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Test LF methods.
target Python;

main reactor {

state foo(2)

method getFoo() {=
return self.foo
=}

method add(x) {=
self.foo += x
=}

reaction(startup){=
print(f"Foo is initialized to {self.getFoo()}")
if self.getFoo() != 2:
sys.stderr.write("Expected 2!");
exit(1)

self.add(40)
a = self.getFoo()
print(f"2 + 40 = {a}")
if a != 42:
sys.stderr.write("Expected 42!");
exit(1)
=}
}
23 changes: 23 additions & 0 deletions test/Python/src/MethodsRecursive.lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Test ability of methods to call each other and (recursively) themselves.
target Python;

main reactor {

state foo(2)

// Return the n-th Fibonacci number.
method fib(n) {=
if n <= 1:
return 1
return self.add(self.fib(n-1), self.fib(n-2))
=}

method add(x, y) {=
return x + y
=}

reaction(startup){=
for n in range(1, 10):
print(f"{n}-th Fibonacci number is {self.fib(n)}")
=}
}
38 changes: 38 additions & 0 deletions test/Python/src/MethodsSameName.lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# This tests that reactors can have methods with the same names.
target Python;

reactor Foo {

state foo(2)

method add(x) {=
self.foo += x
=}

reaction(startup){=
self.add(40)
print(f"Foo: 2 + 40 = {self.foo}")
if self.foo != 42:
sys.stderr.write("Expected 42!")
exit(1)
=}
}

main reactor {

state foo(2)

a = new Foo()

method add(x) {=
self.foo += x
=}

reaction(startup){=
self.add(40)
print(f"Main: 2 + 40 = {self.foo}")
if self.foo != 42:
sys.stderr.write("Expected 42!")
exit(1)
=}
}