diff --git a/org.lflang/src/org/lflang/CommonExtensions.kt b/org.lflang/src/org/lflang/CommonExtensions.kt index 64e08288d4..3e098d791c 100644 --- a/org.lflang/src/org/lflang/CommonExtensions.kt +++ b/org.lflang/src/org/lflang/CommonExtensions.kt @@ -158,6 +158,16 @@ fun Iterable.joinWithCommasLn( transform: (T) -> CharSequence = { it.toString() } ): String = joinWithCommas(prefix, postfix, skipLines = true, trailing, transform) +/** + * Join the elements of [this] sequence with newlines. The + * [prefix] and [postfix] are added even if this iterable is empty. + */ +fun Iterable.joinWithLn( + prefix: CharSequence = "", + postfix: CharSequence = "", + transform: (T) -> CharSequence +): String = joinToString(separator = "\n", prefix = prefix, postfix = postfix, transform = transform) + /** * Join this list with commas, surrounding it with angled brackets (`<...>`). * If this list is empty, returns an empty string. diff --git a/org.lflang/src/org/lflang/generator/ExpressionGenerator.java b/org.lflang/src/org/lflang/generator/ExpressionGenerator.java deleted file mode 100644 index df89b1b2cf..0000000000 --- a/org.lflang/src/org/lflang/generator/ExpressionGenerator.java +++ /dev/null @@ -1,164 +0,0 @@ -package org.lflang.generator; - -import java.util.List; -import java.util.ArrayList; -import java.util.stream.Collectors; - -import org.lflang.ASTUtils; -import org.lflang.TimeValue; -import org.lflang.lf.Assignment; -import org.lflang.lf.Expression; -import org.lflang.lf.Instantiation; -import org.lflang.lf.Parameter; -import org.lflang.lf.ParameterReference; -import org.lflang.lf.StateVar; -import org.lflang.lf.Time; -import org.lflang.TimeUnit; - -/** - * Encapsulates logic for representing {@code Value}s in a - * target language. - */ -public final class ExpressionGenerator { - - /** - * A {@code TimeInTargetLanguage} is a - * target-language-specific time representation - * strategy. - */ - public interface TimeInTargetLanguage { - String apply(TimeValue t); - } - - /** - * A {@code GetTargetReference} instance is a - * target-language-specific function. It provides the - * target language code that refers to the given - * parameter {@code param}. - */ - public interface GetTargetReference { - String apply(Parameter param); - } - - private final TimeInTargetLanguage timeInTargetLanguage; - private final GetTargetReference getTargetReference; - - /** - * Instantiates a target-language-specific - * ExpressionGenerator parameterized by {@code f}. - * @param f a time representation strategy - */ - public ExpressionGenerator(TimeInTargetLanguage f, GetTargetReference g) { - this.timeInTargetLanguage = f; - this.getTargetReference = g; - } - - /** - * Create a list of state initializers in target code. - * - * @param state The state variable to create initializers for - * @return A list of initializers in target code - */ - public List getInitializerList(StateVar state) { - List list = new ArrayList<>(); - // FIXME: Previously, we returned null if it was not initialized, which would have caused an - // NPE in TSStateGenerator. Is this the desired behavior? - if (!ASTUtils.isInitialized(state)) return list; - for (Expression expr : state.getInit()) { - if (expr instanceof ParameterReference) { - list.add(getTargetReference.apply(((ParameterReference)expr).getParameter())); - } else { - list.add(getTargetValue(expr, ASTUtils.isOfTimeType(state))); - } - } - return list; - } - - /** - * Create a list of default parameter initializers in target code. - * - * @param param The parameter to create initializers for - * @return A list of initializers in target code - */ - public List getInitializerList(Parameter param) { - List list = new ArrayList<>(); - if (param == null) return list; - for (Expression expr : param.getInit()) - list.add(getTargetValue(expr, ASTUtils.isOfTimeType(param))); - return list; - } - - /** - * Create a list of parameter initializers in target code in the context - * of an reactor instantiation. - * - * This respects the parameter assignments given in the reactor - * instantiation and falls back to the reactors default initializers - * if no value is assigned to it. - * - * @param param The parameter to create initializers for - * @return A list of initializers in target code - */ - public List getInitializerList(Parameter param, Instantiation i) { - List assignments = i.getParameters().stream() - .filter(it -> it.getLhs() == param) - .collect(Collectors.toList()); - if (assignments.isEmpty()) // Case 0: The parameter was not overwritten in the instantiation - return getInitializerList(param); - // Case 1: The parameter was overwritten in the instantiation - List list = new ArrayList<>(); - if (assignments.get(0) == null) return list; - for (Expression expr : assignments.get(0).getRhs()) - list.add(getTargetValue(expr, ASTUtils.isOfTimeType(param))); - return list; - } - - /** - * Return the time specified by {@code t}, expressed as - * code that is valid for some target languages. - */ - public String getTargetTime(TimeValue t) { - return timeInTargetLanguage.apply(t); - } - - /** - * Return the time specified by {@code t}, expressed as - * code that is valid for some target languages. - */ - public String getTargetTime(Time t) { - return timeInTargetLanguage.apply(new TimeValue(t.getInterval(), TimeUnit.fromName(t.getUnit()))); - } - - /** - * Return the time specified by {@code v}, expressed as - * code that is valid for some target languages. - */ - public String getTargetTime(Expression expr) { - return getTargetValue(expr, true); - } - - /** - * Get textual representation of an expression in the target language. - * - * If the value evaluates to 0, it is interpreted as a literal. - * - * @param expr A time AST node - * @return A time string in the target language - */ - public String getTargetValue(Expression expr) { - return ASTUtils.toText(expr); - } - - /** - * Get textual representation of an expression in the target language. - * - * @param expr A time AST node - * @param isTime Whether {@code v} is expected to be a time - * @return A time string in the target language - */ - public String getTargetValue(Expression expr, boolean isTime) { - if (expr instanceof Time) return getTargetTime((Time)expr); - if (isTime && ASTUtils.isZero(expr)) return timeInTargetLanguage.apply(TimeValue.ZERO); - return ASTUtils.toText(expr); - } -} diff --git a/org.lflang/src/org/lflang/generator/GeneratorUtils.kt b/org.lflang/src/org/lflang/generator/GeneratorUtils.kt index e124080f82..6d11501b80 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorUtils.kt +++ b/org.lflang/src/org/lflang/generator/GeneratorUtils.kt @@ -2,11 +2,16 @@ package org.lflang.generator import org.eclipse.emf.ecore.EObject import org.eclipse.xtext.nodemodel.util.NodeModelUtils -import org.lflang.ErrorReporter +import org.lflang.InferredType +import org.lflang.isInitWithBraces +import org.lflang.lf.Expression +import org.lflang.lf.Instantiation +import org.lflang.lf.LfFactory +import org.lflang.lf.Parameter +import org.lflang.lf.StateVar import org.lflang.toPath -import org.lflang.toUnixString import org.lflang.toTextTokenBased -import org.lflang.lf.Instantiation +import org.lflang.toUnixString /** A transparent type alias to document when a string contains target code. */ typealias TargetCode = String @@ -42,3 +47,48 @@ fun EObject.locationInfo(): LocationInfo { lfText = toTextTokenBased() ?: "" ) } + + +/** + * Returns the target code for the initial value of [sv]. + */ +fun TargetTypes.getTargetInitializer(sv: StateVar): TargetCode = + this.getTargetInitializer(sv.init, sv.type, sv.braces.isNotEmpty()) + +/** + * Returns the target code for the default value of the [param]. + */ +fun TargetTypes.getTargetInitializer(param: Parameter): TargetCode = + this.getTargetInitializer(param.init, param.type, param.isInitWithBraces) + +/** + * Returns the target code for the [getActualValue] of the + * param for this instantiation. + */ +fun TargetTypes.getTargetInitializer(param: Parameter, inst: Instantiation): TargetCode { + val init = inst.getActualValue(param) + return getTargetInitializer(init, param.type, param.isInitWithBraces) +} + +/** + * Return the actual value of a parameter for the given instantiation. + * The value is defaulted to the default value for the parameter if + * there is no explicit assignment. If there is no default value, the + * source code is invalid (param is required) + */ +fun Instantiation.getActualValue(param: Parameter): List = + parameters.firstOrNull { it.lhs == param }?.rhs + ?: param.init + ?: throw InvalidLfSourceException(this, "No value for parameter ${param.name}") + + +/** + * Return the target code for the given expression, given + * that it's a time expression. + */ +fun TargetTypes.getTargetTimeExpr(v: Expression): TargetCode = + this.getTargetExpr(v, InferredType.time()) + +/** If this is null, return the literal 0. */ +fun Expression?.orZero(): Expression = + this ?: LfFactory.eINSTANCE.createLiteral().apply { literal = "0" } diff --git a/org.lflang/src/org/lflang/generator/TargetTypes.java b/org.lflang/src/org/lflang/generator/TargetTypes.java index 8e72c2a3f0..38d918d72a 100644 --- a/org.lflang/src/org/lflang/generator/TargetTypes.java +++ b/org.lflang/src/org/lflang/generator/TargetTypes.java @@ -6,7 +6,6 @@ import org.lflang.ASTUtils; import org.lflang.InferredType; -import org.lflang.TimeUnit; import org.lflang.TimeValue; import org.lflang.lf.Action; import org.lflang.lf.Code; @@ -64,6 +63,10 @@ public interface TargetTypes { String getTargetVariableSizeListType(String baseType); + default String getTargetParamRef(ParameterReference expr, InferredType typeOrNull) { + return escapeIdentifier(expr.getParameter().getName()); + } + /** * Return an "undefined" type which is used as a default * when a type cannot be inferred. @@ -232,7 +235,7 @@ default String getTargetExpr(Expression expr, InferredType type) { if (ASTUtils.isZero(expr) && type != null && type.isTime) { return getTargetTimeExpr(TimeValue.ZERO); } else if (expr instanceof ParameterReference) { - return escapeIdentifier(((ParameterReference) expr).getParameter().getName()); + return getTargetParamRef((ParameterReference) expr, type); } else if (expr instanceof Time) { return getTargetTimeExpr((Time) expr); } else if (expr instanceof Literal) { @@ -249,6 +252,6 @@ default String getTargetExpr(Expression expr, InferredType type) { * target code. */ default String getTargetTimeExpr(Time t) { - return getTargetTimeExpr(new TimeValue(t.getInterval(), TimeUnit.fromName(t.getUnit()))); + return getTargetTimeExpr(ASTUtils.toTimeValue(t)); } } diff --git a/org.lflang/src/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt index 32ba3c27d5..4ef12ee201 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt @@ -28,6 +28,7 @@ import org.lflang.generator.PrependOperator import org.lflang.isBank import org.lflang.isMultiport import org.lflang.hasMultipleConnections +import org.lflang.joinWithLn import org.lflang.lf.* /** @@ -175,9 +176,9 @@ class CppAssembleMethodGenerator(private val reactor: Reactor) { """ |// connection $idx |std::vector<$portType> __lf_left_ports_$idx; - ${" |"..c.leftPorts.joinToString("\n") { addAllPortsToVector(it, "__lf_left_ports_$idx") }} + ${" |"..c.leftPorts.joinWithLn { addAllPortsToVector(it, "__lf_left_ports_$idx") }} |std::vector<$portType> __lf_right_ports_$idx; - ${" |"..c.rightPorts.joinToString("\n") { addAllPortsToVector(it, "__lf_right_ports_$idx") }} + ${" |"..c.rightPorts.joinWithLn { addAllPortsToVector(it, "__lf_right_ports_$idx") }} |lfutil::bind_multiple_ports(__lf_left_ports_$idx, __lf_right_ports_$idx, ${c.isIterated}); """.trimMargin() } diff --git a/org.lflang/src/org/lflang/generator/cpp/CppInstanceGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppInstanceGenerator.kt index a8968559ec..a7f6b6c920 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppInstanceGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppInstanceGenerator.kt @@ -132,7 +132,7 @@ class CppInstanceGenerator( } fun generateConstructorInitializers() = - reactor.instantiations.filter { it.isBank }.joinToString("\n") { generateConstructorInitializer(it) } + reactor.instantiations.filter { it.isBank }.joinWithLn { generateConstructorInitializer(it) } /** Generate constructor initializers for all reactor instantiations */ fun generateInitializers(): String = diff --git a/org.lflang/src/org/lflang/generator/cpp/CppPortGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppPortGenerator.kt index b2cb8aba3a..99f55655cd 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppPortGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppPortGenerator.kt @@ -26,6 +26,7 @@ package org.lflang.generator.cpp import org.lflang.inferredType import org.lflang.isMultiport +import org.lflang.joinWithLn import org.lflang.lf.Input import org.lflang.lf.Output import org.lflang.lf.Port @@ -89,8 +90,8 @@ class CppPortGenerator(private val reactor: Reactor) { } fun generateConstructorInitializers() = - reactor.inputs.filter { it.isMultiport }.joinToString("\n") { generateConstructorInitializer(it) } + - reactor.outputs.filter { it.isMultiport }.joinToString("\n") { generateConstructorInitializer(it) } + reactor.inputs.filter { it.isMultiport }.joinWithLn { generateConstructorInitializer(it) } + + reactor.outputs.filter { it.isMultiport }.joinWithLn { generateConstructorInitializer(it) } fun generateDeclarations() = reactor.inputs.joinToString("\n", "// input ports\n", postfix = "\n") { generateDeclaration(it) } + diff --git a/org.lflang/src/org/lflang/generator/cpp/CppReactionGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppReactionGenerator.kt index 8e2513dc71..ef58a5d2be 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppReactionGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppReactionGenerator.kt @@ -26,6 +26,7 @@ package org.lflang.generator.cpp import org.lflang.generator.PrependOperator import org.lflang.isBank +import org.lflang.joinWithLn import org.lflang.label import org.lflang.lf.* import org.lflang.priority @@ -193,15 +194,15 @@ class CppReactionGenerator( } private fun generateViews(r: Reaction) = - r.allReferencedContainers.joinToString("\n") { generateViewForContainer(r, it) } + r.allReferencedContainers.joinWithLn { generateViewForContainer(r, it) } private fun generateViewInitializers(r: Reaction) = r.allReferencedContainers.filterNot { it.isBank } - .joinToString("\n") { ", ${r.getViewInstanceName(it)}(${it.name}.get()) " } + .joinWithLn { ", ${r.getViewInstanceName(it)}(${it.name}.get()) " } private fun generateViewConstructorInitializers(r: Reaction) = r.allReferencedContainers.filter { it.isBank } - .joinToString("\n") { + .joinWithLn { val viewInstance = r.getViewInstanceName(it) """ $viewInstance.reserve(${it.name}.size()); diff --git a/org.lflang/src/org/lflang/generator/cpp/CppRos2PackageGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppRos2PackageGenerator.kt index ff5fa22982..f15adfcde2 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppRos2PackageGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppRos2PackageGenerator.kt @@ -1,6 +1,7 @@ package org.lflang.generator.cpp import org.lflang.generator.PrependOperator +import org.lflang.joinWithLn import org.lflang.toUnixString import java.nio.file.Path @@ -30,7 +31,7 @@ class CppRos2PackageGenerator(generator: CppGenerator, private val nodeName: Str | ament_cmake | ament_cmake_auto | - ${" |"..dependencies.joinToString("\n") { "$it" } } + ${" |"..dependencies.joinWithLn { "$it" } } | | ament_lint_auto | ament_lint_common @@ -71,7 +72,7 @@ class CppRos2PackageGenerator(generator: CppGenerator, private val nodeName: Str | |ament_auto_add_library($S{LF_MAIN_TARGET} SHARED | src/$nodeName.cc - ${" | "..sources.joinToString("\n") { "src/$it" }} + ${" | "..sources.joinWithLn { "src/$it" }} |) |ament_target_dependencies($S{LF_MAIN_TARGET} ${dependencies.joinToString(" ")}) |target_include_directories($S{LF_MAIN_TARGET} PUBLIC @@ -94,7 +95,7 @@ class CppRos2PackageGenerator(generator: CppGenerator, private val nodeName: Str | |ament_auto_package() | - ${" |"..(includeFiles?.joinToString("\n") { "include(\"$it\")" } ?: "")} + ${" |"..(includeFiles?.joinWithLn { "include(\"$it\")" } ?: "")} """.trimMargin() } } @@ -109,4 +110,4 @@ class CppRos2PackageGenerator(generator: CppGenerator, private val nodeName: Str |ros2 run ${fileConfig.name} ${fileConfig.name}_exe """.trimMargin() } -} \ No newline at end of file +} diff --git a/org.lflang/src/org/lflang/generator/cpp/CppStandaloneCmakeGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppStandaloneCmakeGenerator.kt index af8aa4cd03..cc5f2a661e 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppStandaloneCmakeGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppStandaloneCmakeGenerator.kt @@ -26,6 +26,7 @@ package org.lflang.generator.cpp import org.lflang.TargetConfig import org.lflang.generator.PrependOperator +import org.lflang.joinWithLn import org.lflang.toUnixString import java.nio.file.Path @@ -128,7 +129,7 @@ class CppStandaloneCmakeGenerator(private val targetConfig: TargetConfig, privat |set(LF_MAIN_TARGET ${fileConfig.name}) | |add_executable($S{LF_MAIN_TARGET} - ${" | "..sources.joinToString("\n") { it.toUnixString() }} + ${" | "..sources.joinWithLn { it.toUnixString() }} |) |target_include_directories($S{LF_MAIN_TARGET} PUBLIC | "$S{LF_SRC_PKG_PATH}/src" @@ -156,7 +157,7 @@ class CppStandaloneCmakeGenerator(private val targetConfig: TargetConfig, privat |set(${includesVarName(fileConfig.name)} $S{TARGET_INCLUDE_DIRECTORIES} CACHE STRING "Directories included in the main target." FORCE) |set($compilerIdName $S{CMAKE_CXX_COMPILER_ID} CACHE STRING "The name of the C++ compiler." FORCE) | - ${" |"..(includeFiles?.joinToString("\n") { "include(\"$it\")" } ?: "")} + ${" |"..(includeFiles?.joinWithLn { "include(\"$it\")" } ?: "")} """.trimMargin() } } diff --git a/org.lflang/src/org/lflang/generator/rust/RustCargoTomlEmitter.kt b/org.lflang/src/org/lflang/generator/rust/RustCargoTomlEmitter.kt index c4ef1b8a16..a4b9ba0548 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustCargoTomlEmitter.kt +++ b/org.lflang/src/org/lflang/generator/rust/RustCargoTomlEmitter.kt @@ -28,6 +28,7 @@ import org.lflang.TargetProperty.BuildType.* import org.lflang.escapeStringLiteral import org.lflang.generator.PrependOperator.rangeTo import org.lflang.joinWithCommas +import org.lflang.joinWithLn import org.lflang.withDQuotes import java.nio.file.Paths @@ -52,7 +53,7 @@ object RustCargoTomlEmitter : RustEmitterBase() { |env_logger = "0.9" |log = { version = "0.4", features = ["release_max_level_info"] } |clap = { version = "3.1.8", features = ["derive", "env"], optional = true } -${" |"..crate.dependencies.asIterable().joinToString("\n") { (name, spec) -> name + " = " + spec.toToml() }} +${" |"..crate.dependencies.asIterable().joinWithLn { (name, spec) -> name + " = " + spec.toToml() }} | |[[bin]] |name = "${gen.executableName}" diff --git a/org.lflang/src/org/lflang/generator/rust/RustEmitter.kt b/org.lflang/src/org/lflang/generator/rust/RustEmitter.kt index a801367116..7a36bce23e 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustEmitter.kt +++ b/org.lflang/src/org/lflang/generator/rust/RustEmitter.kt @@ -27,6 +27,7 @@ package org.lflang.generator.rust import org.lflang.generator.CodeMap import org.lflang.generator.PrependOperator import org.lflang.generator.rust.RustEmitter.generateRustProject +import org.lflang.joinWithLn import org.lflang.util.FileUtil import java.nio.file.Files import java.nio.file.Path @@ -95,7 +96,7 @@ object RustEmitter : RustEmitterBase() { """ |${generatedByComment("//")} | -${" |"..gen.reactors.joinToString("\n") { it.modDecl() }} +${" |"..gen.reactors.joinWithLn { it.modDecl() }} | """.trimMargin() } diff --git a/org.lflang/src/org/lflang/generator/rust/RustMainFileEmitter.kt b/org.lflang/src/org/lflang/generator/rust/RustMainFileEmitter.kt index 70d106f878..fb54556a2c 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustMainFileEmitter.kt +++ b/org.lflang/src/org/lflang/generator/rust/RustMainFileEmitter.kt @@ -30,6 +30,7 @@ import org.lflang.generator.PrependOperator import org.lflang.generator.PrependOperator.rangeTo import org.lflang.generator.UnsupportedGeneratorFeatureException import org.lflang.joinWithCommasLn +import org.lflang.joinWithLn import org.lflang.withoutQuotes @@ -54,10 +55,10 @@ object RustMainFileEmitter : RustEmitterBase() { |extern crate log; | |// user dependencies -${" |"..gen.crate.dependencies.keys.joinToString("\n") { "extern crate ${it.replace('-', '_')};" }} +${" |"..gen.crate.dependencies.keys.joinWithLn { "extern crate ${it.replace('-', '_')};" }} | |// user-defined modules -${" |"..gen.crate.modulesToIncludeInMain.joinToString("\n") { "mod ${it.fileName.toString().removeSuffix(".rs")};" }} +${" |"..gen.crate.modulesToIncludeInMain.joinWithLn { "mod ${it.fileName.toString().removeSuffix(".rs")};" }} | |use $rsRuntime::*; |use log::LevelFilter; diff --git a/org.lflang/src/org/lflang/generator/rust/RustReactorEmitter.kt b/org.lflang/src/org/lflang/generator/rust/RustReactorEmitter.kt index ab21972bd1..2fb80b341f 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustReactorEmitter.kt +++ b/org.lflang/src/org/lflang/generator/rust/RustReactorEmitter.kt @@ -104,7 +104,7 @@ ${" | "..otherComponents.joinWithCommasLn { it.toStructField() }} | | let __impl = { | // declare them all here so that they are visible to the initializers of state vars declared later -${" | "..reactor.stateVars.joinToString("\n") { "let ${it.lfName} = ${it.init};" }} +${" | "..reactor.stateVars.joinWithLn { "let ${it.lfName} = ${it.init};" }} | | $structName { | __phantom: std::marker::PhantomData, diff --git a/org.lflang/src/org/lflang/generator/ts/TSActionGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSActionGenerator.kt index 73fec6a7f9..e162c16c26 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSActionGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSActionGenerator.kt @@ -1,23 +1,18 @@ package org.lflang.generator.ts import org.lflang.federated.FederateInstance +import org.lflang.generator.getTargetTimeExpr import org.lflang.lf.Action -import org.lflang.lf.Expression import org.lflang.lf.ParameterReference -import org.lflang.lf.Type import java.util.* /** * Generator for actions in TypeScript target. */ -class TSActionGenerator ( - // TODO(hokeun): Remove dependency on TSGenerator. - private val tsGenerator: TSGenerator, +class TSActionGenerator( private val actions: List, private val federate: FederateInstance ) { - private fun Expression.getTargetValue(): String = tsGenerator.getTargetValueW(this) - private fun Type.getTargetType(): String = tsGenerator.getTargetTypeW(this) fun generateClassProperties(): String { val stateClassProperties = LinkedList() @@ -27,7 +22,7 @@ class TSActionGenerator ( // duplicate action if we included the one generated // by LF. if (action.name != "shutdown") { - stateClassProperties.add("${action.name}: __Action<${getActionType(action)}>;") + stateClassProperties.add("${action.name}: __Action<${action.tsActionType}>;") } } return stateClassProperties.joinToString("\n") @@ -45,21 +40,21 @@ class TSActionGenerator ( if (action.minDelay != null) { // Actions in the TypeScript target are constructed // with an optional minDelay argument which defaults to 0. - if (action.minDelay is ParameterReference) { - actionArgs+= ", " + (action.minDelay as ParameterReference).parameter.name + actionArgs += if (action.minDelay is ParameterReference) { + ", " + (action.minDelay as ParameterReference).parameter.name } else { - actionArgs+= ", " + action.minDelay.getTargetValue() + ", " + action.minDelay.toTsTime() } } if (action in networkMessageActions){ actionInstantiations.add( - "this.${action.name} = new __FederatePortAction<${getActionType(action)}>($actionArgs);") + "this.${action.name} = new __FederatePortAction<${action.tsActionType}>($actionArgs);") } else { actionInstantiations.add( - "this.${action.name} = new __Action<${getActionType(action)}>($actionArgs);") + "this.${action.name} = new __Action<${action.tsActionType}>($actionArgs);") } } } return actionInstantiations.joinToString("\n") } -} \ No newline at end of file +} diff --git a/org.lflang/src/org/lflang/generator/ts/TSConstructorGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSConstructorGenerator.kt index 070e6434d6..35ab8df514 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSConstructorGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSConstructorGenerator.kt @@ -4,6 +4,8 @@ import org.lflang.ErrorReporter import org.lflang.TargetConfig import org.lflang.federated.FederateInstance import org.lflang.generator.PrependOperator +import org.lflang.generator.getTargetInitializer +import org.lflang.joinWithLn import org.lflang.lf.Action import org.lflang.lf.Parameter import org.lflang.lf.Reactor @@ -24,26 +26,9 @@ class TSConstructorGenerator ( private val federate: FederateInstance, private val targetConfig: TargetConfig ) { - private fun getInitializerList(param: Parameter): List = - tsGenerator.getInitializerListW(param) - // Initializer functions - private fun getTargetInitializerHelper(param: Parameter, - list: List): String { - return if (list.size == 0) { - errorReporter.reportError(param, "Parameters must have a default value!") - } else if (list.size == 1) { - list[0] - } else { - list.joinToString(", ", "[", "]") - } - } - private fun getTargetInitializer(param: Parameter): String { - return getTargetInitializerHelper(param, getInitializerList(param)) - } - private fun initializeParameter(p: Parameter): String { - return """${p.name}: ${p.getTargetType()} = ${getTargetInitializer(p)}""" - } + private fun initializeParameter(p: Parameter): String = + "${p.name}: ${TSTypes.getTargetType(p)} = ${TSTypes.getTargetInitializer(p)}" private fun generateConstructorArguments(reactor: Reactor): String { val arguments = LinkedList() @@ -83,7 +68,7 @@ class TSConstructorGenerator ( port = 15045 } return """ - super(federationID, ${federate.id}, ${port}, + super(federationID, ${federate.id}, $port, "${federationRTIProperties()["host"]}", timeout, keepAlive, fast, success, fail); """ @@ -94,29 +79,17 @@ class TSConstructorGenerator ( // If the app is federated, register its // networkMessageActions with the RTIClient - private fun generateFederatePortActionRegistrations(networkMessageActions: List): String { - var fedPortID = 0; - val connectionInstantiations = LinkedList() - for (nAction in networkMessageActions) { - val registration = """ - this.registerFederatePortAction(${fedPortID}, this.${nAction.name}); - """ - connectionInstantiations.add(registration) - fedPortID++ + private fun generateFederatePortActionRegistrations(networkMessageActions: List): String = + networkMessageActions.withIndex().joinWithLn { (fedPortID, nAction) -> + "this.registerFederatePortAction($fedPortID, this.${nAction.name});" } - return connectionInstantiations.joinToString("\n") - } // Generate code for setting target configurations. - private fun generateTargetConfigurations(): String { - val targetConfigurations = LinkedList() - if ((reactor.isMain || reactor.isFederated) && - targetConfig.coordinationOptions.advance_message_interval != null) { - targetConfigurations.add( - "this.setAdvanceMessageInterval(${timeInTargetLanguage(targetConfig.coordinationOptions.advance_message_interval)})") - } - return targetConfigurations.joinToString("\n") - } + private fun generateTargetConfigurations(): String = + if ((reactor.isMain || reactor.isFederated) + && targetConfig.coordinationOptions.advance_message_interval != null + ) "this.setAdvanceMessageInterval(${targetConfig.coordinationOptions.advance_message_interval.toTsTime()})" + else "" // Generate code for registering Fed IDs that are connected to // this federate via ports in the TypeScript's FederatedApp. @@ -145,7 +118,7 @@ class TSConstructorGenerator ( ports: TSPortGenerator ): String { val connections = TSConnectionGenerator(reactor.connections, errorReporter) - val reactions = TSReactionGenerator(tsGenerator, errorReporter, reactor, federate) + val reactions = TSReactionGenerator(errorReporter, reactor, federate) return with(PrependOperator) { """ @@ -168,4 +141,4 @@ class TSConstructorGenerator ( """.trimMargin() } } -} \ No newline at end of file +} diff --git a/org.lflang/src/org/lflang/generator/ts/TSExtensions.kt b/org.lflang/src/org/lflang/generator/ts/TSExtensions.kt index 2e557db545..a01a01f4b3 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSExtensions.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSExtensions.kt @@ -1,12 +1,12 @@ package org.lflang.generator.ts import org.lflang.TimeValue +import org.lflang.generator.getTargetTimeExpr import org.lflang.isBank import org.lflang.isMultiport import org.lflang.lf.Action -import org.lflang.lf.Parameter +import org.lflang.lf.Expression import org.lflang.lf.Port -import org.lflang.lf.Type import org.lflang.lf.WidthSpec import org.lflang.toText @@ -32,45 +32,23 @@ fun WidthSpec.toTSCode(): String = terms.joinToString(" + ") { } } -private fun Type.getTargetType(): String = TSTypes.getTargetType(this) - /** * Return a TS type for the specified port. * If the type has not been specified, return * "Present" which is the base type for ports. - * @param port The port * @return The TS type. */ -fun getPortType(port: Port): String { - if (port.type != null) { - return port.type.getTargetType() - } else { - return "Present" - } -} - -fun Parameter.getTargetType(): String = TSTypes.getTargetType(this) +val Port.tsPortType: String + get() = type?.let { TSTypes.getTargetType(it) } ?: "Present" /** * Return a TS type for the specified action. * If the type has not been specified, return * "Present" which is the base type for Actions. - * @param action The action * @return The TS type. */ -fun getActionType(action: Action): String { - if (action.type != null) { - return action.type.getTargetType() - } else { - return "Present" - } -} +val Action.tsActionType: String + get() = type?.let { TSTypes.getTargetType(it) } ?: "Present" -fun timeInTargetLanguage(value: TimeValue): String { - return if (value.unit != null) { - "TimeValue.${value.unit.canonicalName}(${value.time})" - } else { - // The value must be zero. - "TimeValue.zero()" - } -} +fun Expression.toTsTime(): String = TSTypes.getTargetTimeExpr(this) +fun TimeValue.toTsTime(): String = TSTypes.getTargetTimeExpr(this) diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index 82cd08c616..04f00cc788 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -36,7 +36,6 @@ import org.lflang.federated.FederateInstance import org.lflang.federated.launcher.FedTSLauncher import org.lflang.federated.serialization.SupportedSerializers import org.lflang.generator.CodeMap -import org.lflang.generator.ExpressionGenerator import org.lflang.generator.GeneratorBase import org.lflang.generator.GeneratorResult import org.lflang.generator.GeneratorUtils @@ -47,13 +46,8 @@ import org.lflang.generator.PrependOperator import org.lflang.generator.ReactorInstance import org.lflang.generator.SubContext import org.lflang.generator.TargetTypes -import org.lflang.inferredType import org.lflang.lf.Action import org.lflang.lf.Expression -import org.lflang.lf.Instantiation -import org.lflang.lf.Parameter -import org.lflang.lf.StateVar -import org.lflang.lf.Type import org.lflang.lf.VarRef import org.lflang.scoping.LFGlobalScopeProvider import org.lflang.util.FileUtil @@ -91,9 +85,6 @@ class TSGenerator( */ val CONFIG_FILES = arrayOf("package.json", "tsconfig.json", "babel.config.js", ".eslintrc.json") - private val VG = - ExpressionGenerator(::timeInTargetLanguage) { param -> "this.${param.name}.get()" } - fun timeInTargetLanguage(value: TimeValue): String { return if (value.unit != null) { "TimeValue.${value.unit.canonicalName}(${value.magnitude})" @@ -120,16 +111,6 @@ class TSGenerator( // Wrappers to expose GeneratorBase methods. fun federationRTIPropertiesW() = federationRTIProperties - fun getTargetValueW(expr: Expression): String = VG.getTargetValue(expr, false) - fun getTargetTypeW(p: Parameter): String = TSTypes.getTargetType(p.inferredType) - fun getTargetTypeW(state: StateVar): String = TSTypes.getTargetType(state) - fun getTargetTypeW(t: Type): String = TSTypes.getTargetType(t) - - fun getInitializerListW(state: StateVar): List = VG.getInitializerList(state) - fun getInitializerListW(param: Parameter): List = VG.getInitializerList(param) - fun getInitializerListW(param: Parameter, i: Instantiation): List = - VG.getInitializerList(param, i) - /** Generate TypeScript code from the Lingua Franca model contained by the * specified resource. This is the main entry point for code * generation. @@ -312,7 +293,7 @@ class TSGenerator( targetConfig.protoFiles) tsCode.append(preambleGenerator.generatePreamble()) - val parameterGenerator = TSParameterPreambleGenerator(this, fileConfig, targetConfig, reactors) + val parameterGenerator = TSParameterPreambleGenerator(fileConfig, targetConfig, reactors) val (mainParameters, parameterCode) = parameterGenerator.generateParameters() tsCode.append(parameterCode) diff --git a/org.lflang/src/org/lflang/generator/ts/TSImportPreambleGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSImportPreambleGenerator.kt index e623d2e05d..3b34214a78 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSImportPreambleGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSImportPreambleGenerator.kt @@ -26,8 +26,8 @@ package org.lflang.generator.ts import org.lflang.generator.PrependOperator +import org.lflang.joinWithLn import java.nio.file.Path -import java.util.* /** * Preamble generator for imports in TypeScript target. @@ -48,54 +48,48 @@ class TSImportPreambleGenerator( * Default imports for importing all the core classes and helper classes * for CLI argument handling. */ - const val DEFAULT_IMPORTS = """ - |import commandLineArgs from 'command-line-args' - |import commandLineUsage from 'command-line-usage' - |import {Parameter as __Parameter, Timer as __Timer, Reactor as __Reactor, App as __App} from '@lf-lang/reactor-ts' - |import {Action as __Action, Startup as __Startup, FederatePortAction as __FederatePortAction} from '@lf-lang/reactor-ts' - |import {Bank as __Bank} from '@lf-lang/reactor-ts' - |import {FederatedApp as __FederatedApp} from '@lf-lang/reactor-ts' - |import {InPort as __InPort, OutPort as __OutPort, Port as __Port, WritablePort as __WritablePort, WritableMultiPort as __WritableMultiPort} from '@lf-lang/reactor-ts' - |import {InMultiPort as __InMultiPort, OutMultiPort as __OutMultiPort} from '@lf-lang/reactor-ts' - |import {Reaction as __Reaction} from '@lf-lang/reactor-ts' - |import {State as __State} from '@lf-lang/reactor-ts' - |import {TimeUnit, TimeValue, Tag as __Tag, Origin as __Origin} from '@lf-lang/reactor-ts' - |import {Args as __Args, Variable as __Variable, Triggers as __Triggers, Present, Read, Write, ReadWrite, MultiReadWrite, Sched} from '@lf-lang/reactor-ts' - |import {Log} from '@lf-lang/reactor-ts' - |import {ProcessedCommandLineArgs as __ProcessedCommandLineArgs, CommandLineOptionDefs as __CommandLineOptionDefs, CommandLineUsageDefs as __CommandLineUsageDefs, CommandLineOptionSpec as __CommandLineOptionSpec, unitBasedTimeValueCLAType as __unitBasedTimeValueCLAType, booleanCLAType as __booleanCLAType} from '@lf-lang/reactor-ts' - |""" + const val DEFAULT_IMPORTS = """ + import commandLineArgs from 'command-line-args' + import commandLineUsage from 'command-line-usage' + import {Parameter as __Parameter, Timer as __Timer, Reactor as __Reactor, App as __App} from '@lf-lang/reactor-ts' + import {Action as __Action, Startup as __Startup, FederatePortAction as __FederatePortAction} from '@lf-lang/reactor-ts' + import {Bank as __Bank} from '@lf-lang/reactor-ts' + import {FederatedApp as __FederatedApp} from '@lf-lang/reactor-ts' + import {InPort as __InPort, OutPort as __OutPort, Port as __Port, WritablePort as __WritablePort, WritableMultiPort as __WritableMultiPort} from '@lf-lang/reactor-ts' + import {InMultiPort as __InMultiPort, OutMultiPort as __OutMultiPort} from '@lf-lang/reactor-ts' + import {Reaction as __Reaction} from '@lf-lang/reactor-ts' + import {State as __State} from '@lf-lang/reactor-ts' + import {TimeUnit, TimeValue, Tag as __Tag, Origin as __Origin} from '@lf-lang/reactor-ts' + import {Args as __Args, Variable as __Variable, Triggers as __Triggers, Present, Read, Write, ReadWrite, MultiReadWrite, Sched} from '@lf-lang/reactor-ts' + import {Log} from '@lf-lang/reactor-ts' + import {ProcessedCommandLineArgs as __ProcessedCommandLineArgs, CommandLineOptionDefs as __CommandLineOptionDefs, CommandLineUsageDefs as __CommandLineUsageDefs, CommandLineOptionSpec as __CommandLineOptionSpec, unitBasedTimeValueCLAType as __unitBasedTimeValueCLAType, booleanCLAType as __booleanCLAType} from '@lf-lang/reactor-ts' + """ } private fun generateDefaultImports(): String { return with(PrependOperator) { """ - |// Code generated by the Lingua Franca compiler from: - |// file:/${filePath.toString()} - $DEFAULT_IMPORTS - | + |// Code generated by the Lingua Franca compiler from: + |// file:/$filePath +${" |"..DEFAULT_IMPORTS} + | """.trimMargin() } } private fun generateProtoPreamble(): String { - val protoFileImports = StringJoiner("\n") - for (file in protoFiles) { - var name = file + val protoFileImports = protoFiles.joinWithLn { file -> // Remove any extension the file name may have. - val dot = name.lastIndexOf('.') - if (dot > 0) { - name = name.substring(0, dot) - } - protoFileImports.add(""" - |import * as ${name} from "./${name}_pb" - """.trimMargin()) + val name = file.substringBeforeLast('.') + "import * as $name from \"./${name}_pb\"" } - return with(PrependOperator) {""" + return with(PrependOperator) { + """ |// Imports for protocol buffers - |${protoFileImports} +${" |"..protoFileImports} | | - """.trimMargin() + """.trimMargin() } } diff --git a/org.lflang/src/org/lflang/generator/ts/TSInstanceGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSInstanceGenerator.kt index 065b0dc320..2c349ebd19 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSInstanceGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSInstanceGenerator.kt @@ -2,7 +2,9 @@ package org.lflang.generator.ts import org.lflang.ErrorReporter import org.lflang.federated.FederateInstance +import org.lflang.generator.getTargetInitializer import org.lflang.isBank +import org.lflang.joinWithLn import org.lflang.lf.Instantiation import org.lflang.lf.Parameter import org.lflang.lf.Reactor @@ -15,11 +17,8 @@ import java.util.* /** * Generator for child reactor instantiations in TypeScript target. */ -class TSInstanceGenerator ( - // TODO(hokeun): Remove dependency on TSGenerator. - private val tsGenerator: TSGenerator, +class TSInstanceGenerator( private val errorReporter: ErrorReporter, - private val tsReactorGenerator: TSReactorGenerator, reactor: Reactor, federate: FederateInstance ) { @@ -37,36 +36,25 @@ class TSInstanceGenerator ( } } - private fun getInitializerList(param: Parameter, i: Instantiation): List = - tsGenerator.getInitializerListW(param, i) - - private fun getTargetInitializer(param: Parameter, i: Instantiation): String { - return tsReactorGenerator.getTargetInitializerHelper(param, getInitializerList(param, i)) - } - private fun getTypeParams(typeParms: List): String = - if (typeParms.isEmpty()) {""} else { - typeParms.joinToString(", ", "<", ">") { it.toText() }} + if (typeParms.isEmpty()) "" + else typeParms.joinToString(", ", "<", ">") { it.toText() } private fun getReactorParameterList(parameters: List): String = - if (parameters.isEmpty()) { "[__Reactor]" } else { - parameters.joinToString(", ", "[__Reactor, ", "]") { it.getTargetType() }} + parameters.joinToString(", ", "[__Reactor, ", "]") { TSTypes.getTargetType(it) } - fun generateClassProperties(): String { - val childReactorClassProperties = LinkedList() - for (childReactor in childReactors) { + fun generateClassProperties(): String = + childReactors.joinWithLn { childReactor -> if (childReactor.isBank) { - childReactorClassProperties.add("${childReactor.name}: " + + "${childReactor.name}: " + "__Bank<${childReactor.reactorClass.name}${getTypeParams(childReactor.typeParms)}, " + - "${getReactorParameterList(childReactor.reactor.parameters)}>") + "${getReactorParameterList(childReactor.reactor.parameters)}>" } else { - childReactorClassProperties.add("${childReactor.name}: " + - "${childReactor.reactorClass.name}${getTypeParams(childReactor.typeParms)}") + "${childReactor.name}: " + + "${childReactor.reactorClass.name}${getTypeParams(childReactor.typeParms)}" } } - return childReactorClassProperties.joinToString("\n") - } fun generateInstantiations(): String { val childReactorInstantiations = LinkedList() @@ -75,7 +63,7 @@ class TSInstanceGenerator ( childReactorArguments.add("this") for (parameter in childReactor.reactorClass.toDefinition().parameters) { - childReactorArguments.add(getTargetInitializer(parameter, childReactor)) + childReactorArguments.add(TSTypes.getTargetInitializer(parameter, childReactor)) } if (childReactor.isBank) { childReactorInstantiations.add( @@ -93,4 +81,4 @@ class TSInstanceGenerator ( } return childReactorInstantiations.joinToString("\n") } -} \ No newline at end of file +} diff --git a/org.lflang/src/org/lflang/generator/ts/TSParameterGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSParameterGenerator.kt index 1cd987b815..5ded5814b0 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSParameterGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSParameterGenerator.kt @@ -1,32 +1,22 @@ package org.lflang.generator.ts -import org.lflang.generator.PrependOperator +import org.lflang.joinWithLn import org.lflang.lf.Parameter -import java.util.* /** * Generate parameters for TypeScript target. */ -class TSParameterGenerator ( - // TODO(hokeun): Remove dependency on TSGenerator. - private val tsGenerator: TSGenerator, +class TSParameterGenerator( private val parameters: List ) { - private fun Parameter.getTargetType(): String = tsGenerator.getTargetTypeW(this) - fun generateClassProperties(): String { - val paramClassProperties = LinkedList() - for (param in parameters) { - paramClassProperties.add("${param.name}: __Parameter<${param.getTargetType()}>;") + fun generateClassProperties(): String = + parameters.joinWithLn { + "${it.name}: __Parameter<${TSTypes.getTargetType(it)}>;" } - return paramClassProperties.joinToString("\n") - } - fun generateInstantiations(): String { - val paramInstantiations = LinkedList() - for (param in parameters) { - paramInstantiations.add("this.${param.name} = new __Parameter(${param.name});") + fun generateInstantiations(): String = + parameters.joinWithLn { + "this.${it.name} = new __Parameter(${it.name});" } - return paramInstantiations.joinToString("\n") - } -} \ No newline at end of file +} diff --git a/org.lflang/src/org/lflang/generator/ts/TSParameterPreambleGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSParameterPreambleGenerator.kt index 02380fa9fd..7ef44319ef 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSParameterPreambleGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSParameterPreambleGenerator.kt @@ -27,8 +27,8 @@ package org.lflang.generator.ts import org.lflang.FileConfig import org.lflang.TargetConfig -import org.lflang.TimeValue -import org.lflang.generator.PrependOperator +import org.lflang.joinWithCommasLn +import org.lflang.joinWithLn import org.lflang.lf.Parameter import org.lflang.lf.Reactor import java.util.StringJoiner @@ -44,20 +44,13 @@ import java.util.StringJoiner */ class TSParameterPreambleGenerator( - private val tsGenerator: TSGenerator, private val fileConfig: FileConfig, private val targetConfig: TargetConfig, private val reactors: MutableList ) { - private fun getTargetType(p: Parameter): String = tsGenerator.getTargetTypeW(p) - private fun getTimeoutTimeValue(): String { - return if (targetConfig.timeout != null) { - timeInTargetLanguage(targetConfig.timeout) - } else { - "undefined" - } - } + private fun getTimeoutTimeValue(): String = + targetConfig.timeout?.toTsTime() ?: "undefined" private fun getParameters(): List { var mainReactor: Reactor? = null @@ -72,11 +65,11 @@ class TSParameterPreambleGenerator( /** * Assign results of parsing custom command line arguments */ - private fun assignCustomCLArgs(mainParameters: HashSet): String { - val code = StringJoiner("\n") - for (parameter in mainParameters) { - code.add(""" - |let __CL${parameter.name}: ${getTargetType(parameter)} | undefined = undefined; + private fun assignCustomCLArgs(mainParameters: HashSet): String = + mainParameters.joinWithLn { parameter -> + + """ + |let __CL${parameter.name}: ${TSTypes.getTargetType(parameter)} | undefined = undefined; |if (__processedCLArgs.${parameter.name} !== undefined) { | if (__processedCLArgs.${parameter.name} !== null) { | __CL${parameter.name} = __processedCLArgs.${parameter.name}; @@ -85,30 +78,25 @@ class TSParameterPreambleGenerator( | throw new Error("Custom '${parameter.name}' command line argument is malformed."); | } |} - """) + """ } - return code.toString() - } /** * Generate code for extracting custom command line arguments * from the object returned from commandLineArgs */ - private fun logCustomCLArgs(mainParameters: Set): String { - val code = StringJoiner("\n") - for (parameter in mainParameters) { + private fun logCustomCLArgs(mainParameters: Set): String = + mainParameters.joinWithLn { parameter -> // We can't allow the programmer's parameter names // to cause the generation of variables with a "__" prefix // because they could collide with other variables. // So prefix variables created here with __CL - code.add(""" + """ |if (__processedCLArgs.${parameter.name} !== undefined && __processedCLArgs.${parameter.name} !== null | && !__noStart) { | Log.global.info("'${parameter.name}' property overridden by command line argument."); - |}""") + |}""" } - return code.toString() - } fun generateParameters(): Pair, String> { /** @@ -126,7 +114,7 @@ class TSParameterPreambleGenerator( var customArgType: String? = null var customTypeLabel: String? = null - val paramType = getTargetType(parameter) + val paramType = TSTypes.getTargetType(parameter) if (paramType == "string") { mainParameters.add(parameter) customArgType = "String"; @@ -147,20 +135,24 @@ class TSParameterPreambleGenerator( if (customArgType != null) { clTypeExtension.add(parameter.name + ": " + paramType) if (customTypeLabel != null) { - customArgs.add(with(PrependOperator) {""" + customArgs.add( + """ |{ | name: '${parameter.name}', - | type: ${customArgType}, - | typeLabel: "{underline ${customTypeLabel}}", + | type: $customArgType, + | typeLabel: "{underline $customTypeLabel}", | description: 'Custom argument. Refer to ${fileConfig.srcFile} for documentation.' - |}""".trimMargin()}) + |}""".trimMargin() + ) } else { - customArgs.add(with(PrependOperator) {""" + customArgs.add( + """ |{ | name: '${parameter.name}', - | type: ${customArgType}, + | type: $customArgType, | description: 'Custom argument. Refer to ${fileConfig.srcFile} for documentation.' - |}""".trimMargin()}) + |}""".trimMargin() + ) } } } @@ -168,127 +160,126 @@ class TSParameterPreambleGenerator( val customArgsList = "[\n$customArgs]" val clTypeExtensionDef = "{$clTypeExtension}" - val codeText = with(PrependOperator) {""" - |// ************* App Parameters - |let __timeout: TimeValue | undefined = ${getTimeoutTimeValue()}; - |let __keepAlive: boolean = ${targetConfig.keepalive}; - |let __fast: boolean = ${targetConfig.fastMode}; - |let __federationID: string = 'Unidentified Federation' - | - |let __noStart = false; // If set to true, don't start the app. - | - |// ************* Custom Command Line Arguments - |let __additionalCommandLineArgs : __CommandLineOptionSpec = ${customArgsList}; - |let __customCommandLineArgs = __CommandLineOptionDefs.concat(__additionalCommandLineArgs); - |let __customCommandLineUsageDefs = __CommandLineUsageDefs; - |type __customCLTypeExtension = ${clTypeExtensionDef}; - |__customCommandLineUsageDefs[1].optionList = __customCommandLineArgs; - |const __clUsage = commandLineUsage(__customCommandLineUsageDefs); - | - |// Set App parameters using values from the constructor or command line args. - |// Command line args have precedence over values from the constructor - |let __processedCLArgs: __ProcessedCommandLineArgs & __customCLTypeExtension; - |try { - | __processedCLArgs = commandLineArgs(__customCommandLineArgs) as __ProcessedCommandLineArgs & __customCLTypeExtension; - |} catch (e){ - | Log.global.error(__clUsage); - | throw new Error("Command line argument parsing failed with: " + e); - |} - | - |// Fast Parameter - |if (__processedCLArgs.fast !== undefined) { - | if (__processedCLArgs.fast !== null) { - | __fast = __processedCLArgs.fast; - | } else { - | Log.global.error(__clUsage); - | throw new Error("'fast' command line argument is malformed."); - | } - |} - | - |// federationID Parameter - |if (__processedCLArgs.id !== undefined) { - | if (__processedCLArgs.id !== null) { - | __federationID = __processedCLArgs.id; - | } else { - | Log.global.error(__clUsage); - | throw new Error("'id (federationID)' command line argument is malformed."); - | } - |} - | - |// KeepAlive parameter - |if (__processedCLArgs.keepalive !== undefined) { - | if (__processedCLArgs.keepalive !== null) { - | __keepAlive = __processedCLArgs.keepalive; - | } else { - | Log.global.error(__clUsage); - | throw new Error("'keepalive' command line argument is malformed."); - | } - |} - | - |// Timeout parameter - |if (__processedCLArgs.timeout !== undefined) { - | if (__processedCLArgs.timeout !== null) { - | __timeout = __processedCLArgs.timeout; - | } else { - | Log.global.error(__clUsage); - | throw new Error("'timeout' command line argument is malformed."); - | } - |} - | - |// Logging parameter (not a constructor parameter, but a command line option) - |if (__processedCLArgs.logging !== undefined) { - | if (__processedCLArgs.logging !== null) { - | Log.global.level = __processedCLArgs.logging; - | } else { - | Log.global.error(__clUsage); - | throw new Error("'logging' command line argument is malformed."); - | } - |} else { - | Log.global.level = Log.levels.${targetConfig.logLevel.name}; // Default from target property. - |} - | - |// Help parameter (not a constructor parameter, but a command line option) - |// NOTE: this arg has to be checked after logging, because the help mode should - |// suppress debug statements from it changes logging - |if (__processedCLArgs.help === true) { - | Log.global.error(__clUsage); - | __noStart = true; - | // Don't execute the app if the help flag is given. - |} - | - |// Now the logging property has been set to its final value, - |// log information about how command line arguments were set, - |// but only if not in help mode. - | - |// Runtime command line arguments - |if (__processedCLArgs.fast !== undefined && __processedCLArgs.fast !== null - | && !__noStart) { - | Log.global.info("'fast' property overridden by command line argument."); - |} - |if (__processedCLArgs.id !== undefined && __processedCLArgs.id !== null - | && !__noStart) { - | Log.global.info("'id (federationID)' property overridden by command line argument."); - |} - |if (__processedCLArgs.keepalive !== undefined && __processedCLArgs.keepalive !== null - | && !__noStart) { - | Log.global.info("'keepalive' property overridden by command line argument."); - |} - |if (__processedCLArgs.timeout !== undefined && __processedCLArgs.timeout !== null - | && !__noStart) { - | Log.global.info("'timeout' property overridden by command line argument."); - |} - |if (__processedCLArgs.logging !== undefined && __processedCLArgs.logging !== null - | && !__noStart) { - | Log.global.info("'logging' property overridden by command line argument."); - |} - | - |// Custom command line arguments - |${logCustomCLArgs(mainParameters)} - |// Assign custom command line arguments - |${assignCustomCLArgs(mainParameters)} - | - """.trimMargin() - } + val codeText = """ + |// ************* App Parameters + |let __timeout: TimeValue | undefined = ${getTimeoutTimeValue()}; + |let __keepAlive: boolean = ${targetConfig.keepalive}; + |let __fast: boolean = ${targetConfig.fastMode}; + |let __federationID: string = 'Unidentified Federation' + | + |let __noStart = false; // If set to true, don't start the app. + | + |// ************* Custom Command Line Arguments + |let __additionalCommandLineArgs : __CommandLineOptionSpec = $customArgsList; + |let __customCommandLineArgs = __CommandLineOptionDefs.concat(__additionalCommandLineArgs); + |let __customCommandLineUsageDefs = __CommandLineUsageDefs; + |type __customCLTypeExtension = $clTypeExtensionDef; + |__customCommandLineUsageDefs[1].optionList = __customCommandLineArgs; + |const __clUsage = commandLineUsage(__customCommandLineUsageDefs); + | + |// Set App parameters using values from the constructor or command line args. + |// Command line args have precedence over values from the constructor + |let __processedCLArgs: __ProcessedCommandLineArgs & __customCLTypeExtension; + |try { + | __processedCLArgs = commandLineArgs(__customCommandLineArgs) as __ProcessedCommandLineArgs & __customCLTypeExtension; + |} catch (e){ + | Log.global.error(__clUsage); + | throw new Error("Command line argument parsing failed with: " + e); + |} + | + |// Fast Parameter + |if (__processedCLArgs.fast !== undefined) { + | if (__processedCLArgs.fast !== null) { + | __fast = __processedCLArgs.fast; + | } else { + | Log.global.error(__clUsage); + | throw new Error("'fast' command line argument is malformed."); + | } + |} + | + |// federationID Parameter + |if (__processedCLArgs.id !== undefined) { + | if (__processedCLArgs.id !== null) { + | __federationID = __processedCLArgs.id; + | } else { + | Log.global.error(__clUsage); + | throw new Error("'id (federationID)' command line argument is malformed."); + | } + |} + | + |// KeepAlive parameter + |if (__processedCLArgs.keepalive !== undefined) { + | if (__processedCLArgs.keepalive !== null) { + | __keepAlive = __processedCLArgs.keepalive; + | } else { + | Log.global.error(__clUsage); + | throw new Error("'keepalive' command line argument is malformed."); + | } + |} + | + |// Timeout parameter + |if (__processedCLArgs.timeout !== undefined) { + | if (__processedCLArgs.timeout !== null) { + | __timeout = __processedCLArgs.timeout; + | } else { + | Log.global.error(__clUsage); + | throw new Error("'timeout' command line argument is malformed."); + | } + |} + | + |// Logging parameter (not a constructor parameter, but a command line option) + |if (__processedCLArgs.logging !== undefined) { + | if (__processedCLArgs.logging !== null) { + | Log.global.level = __processedCLArgs.logging; + | } else { + | Log.global.error(__clUsage); + | throw new Error("'logging' command line argument is malformed."); + | } + |} else { + | Log.global.level = Log.levels.${targetConfig.logLevel.name}; // Default from target property. + |} + | + |// Help parameter (not a constructor parameter, but a command line option) + |// NOTE: this arg has to be checked after logging, because the help mode should + |// suppress debug statements from it changes logging + |if (__processedCLArgs.help === true) { + | Log.global.error(__clUsage); + | __noStart = true; + | // Don't execute the app if the help flag is given. + |} + | + |// Now the logging property has been set to its final value, + |// log information about how command line arguments were set, + |// but only if not in help mode. + | + |// Runtime command line arguments + |if (__processedCLArgs.fast !== undefined && __processedCLArgs.fast !== null + | && !__noStart) { + | Log.global.info("'fast' property overridden by command line argument."); + |} + |if (__processedCLArgs.id !== undefined && __processedCLArgs.id !== null + | && !__noStart) { + | Log.global.info("'id (federationID)' property overridden by command line argument."); + |} + |if (__processedCLArgs.keepalive !== undefined && __processedCLArgs.keepalive !== null + | && !__noStart) { + | Log.global.info("'keepalive' property overridden by command line argument."); + |} + |if (__processedCLArgs.timeout !== undefined && __processedCLArgs.timeout !== null + | && !__noStart) { + | Log.global.info("'timeout' property overridden by command line argument."); + |} + |if (__processedCLArgs.logging !== undefined && __processedCLArgs.logging !== null + | && !__noStart) { + | Log.global.info("'logging' property overridden by command line argument."); + |} + | + |// Custom command line arguments + |${logCustomCLArgs(mainParameters)} + |// Assign custom command line arguments + |${assignCustomCLArgs(mainParameters)} + | + """.trimMargin() return Pair(mainParameters, codeText) } diff --git a/org.lflang/src/org/lflang/generator/ts/TSPortGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSPortGenerator.kt index 10e2a4a644..338ef9600d 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSPortGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSPortGenerator.kt @@ -3,15 +3,12 @@ package org.lflang.generator.ts import org.lflang.isMultiport import org.lflang.lf.Input import org.lflang.lf.Output -import org.lflang.lf.Port -import org.lflang.lf.Type import java.util.* /** * Generate input and output ports for TypeScript target. */ class TSPortGenerator ( - // TODO(hokeun): Remove dependency on TSGenerator. private val inputs: List, private val outputs: List ) { @@ -20,16 +17,16 @@ class TSPortGenerator ( val portClassProperties = LinkedList() for (input in inputs) { if (input.isMultiport) { - portClassProperties.add("${input.name}: __InMultiPort<${getPortType(input)}>;") + portClassProperties.add("${input.name}: __InMultiPort<${input.tsPortType}>;") } else { - portClassProperties.add("${input.name}: __InPort<${getPortType(input)}>;") + portClassProperties.add("${input.name}: __InPort<${input.tsPortType}>;") } } for (output in outputs) { if (output.isMultiport) { - portClassProperties.add("${output.name}: __OutMultiPort<${getPortType(output)}>;") + portClassProperties.add("${output.name}: __OutMultiPort<${output.tsPortType}>;") } else { - portClassProperties.add("${output.name}: __OutPort<${getPortType(output)}>;") + portClassProperties.add("${output.name}: __OutPort<${output.tsPortType}>;") } } return portClassProperties.joinToString("\n") @@ -40,19 +37,19 @@ class TSPortGenerator ( for (input in inputs) { if (input.isMultiport) { porInstantiations.add( - "this.${input.name} = new __InMultiPort<${getPortType(input)}>(this, ${input.widthSpec.toTSCode()});") + "this.${input.name} = new __InMultiPort<${input.tsPortType}>(this, ${input.widthSpec.toTSCode()});") } else { - porInstantiations.add("this.${input.name} = new __InPort<${getPortType(input)}>(this);") + porInstantiations.add("this.${input.name} = new __InPort<${input.tsPortType}>(this);") } } for (output in outputs) { if (output.isMultiport) { porInstantiations.add( - "this.${output.name} = new __OutMultiPort<${getPortType(output)}>(this, ${output.widthSpec.toTSCode()});") + "this.${output.name} = new __OutMultiPort<${output.tsPortType}>(this, ${output.widthSpec.toTSCode()});") } else { - porInstantiations.add("this.${output.name} = new __OutPort<${getPortType(output)}>(this);") + porInstantiations.add("this.${output.name} = new __OutPort<${output.tsPortType}>(this);") } } return porInstantiations.joinToString("\n") } -} \ No newline at end of file +} diff --git a/org.lflang/src/org/lflang/generator/ts/TSReactionGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSReactionGenerator.kt index e18c1656a4..620861aee4 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSReactionGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSReactionGenerator.kt @@ -4,8 +4,10 @@ import org.lflang.ErrorReporter import org.lflang.ASTUtils import org.lflang.federated.FederateInstance import org.lflang.generator.PrependOperator +import org.lflang.generator.getTargetTimeExpr import org.lflang.isBank import org.lflang.isMultiport +import org.lflang.joinWithCommas import org.lflang.lf.* import org.lflang.lf.Timer import org.lflang.toText @@ -24,16 +26,10 @@ import java.util.LinkedList * @author {Hokeun Kim } */ class TSReactionGenerator( - // TODO(hokeun): Remove dependency on TSGenerator. - private val tsGenerator: TSGenerator, private val errorReporter: ErrorReporter, - private val reactor : Reactor, + private val reactor: Reactor, private val federate: FederateInstance ) { - private fun Expression.getTargetExpression(): String = tsGenerator.getTargetValueW(this) - private fun Parameter.getTargetType(): String = tsGenerator.getTargetTypeW(this) - private fun StateVar.getTargetType(): String = tsGenerator.getTargetTypeW(this) - private fun Type.getTargetType(): String = tsGenerator.getTargetTypeW(this) private fun VarRef.generateVarRef(): String { return if (this.container != null && this.container.isBank && this.variable is Port) { @@ -43,21 +39,6 @@ class TSReactionGenerator( } } - /** - * Return a TS type for the specified port. - * If the type has not been specified, return - * "Present" which is the base type for ports. - * @param port The port - * @return The TS type. - */ - private fun getPortType(port: Port): String { - if (port.type != null) { - return port.type.getTargetType() - } else { - return "Present" - } - } - private fun generateArg(v: VarRef): String { return if (v.container != null) { "__${v.container.name}_${v.variable.name}" @@ -74,10 +55,10 @@ class TSReactionGenerator( ): String { var deadlineArgs = "" val delay = reaction.deadline.delay - if (delay is ParameterReference) { - deadlineArgs += "this.${delay.parameter.name}.get()"; + deadlineArgs += if (delay is ParameterReference) { + "this.${delay.parameter.name}.get()" } else { - deadlineArgs += delay.getTargetExpression() + delay.toTsTime() } return with(PrependOperator) { @@ -118,7 +99,7 @@ class TSReactionGenerator( when (trigger.type) { BuiltinTrigger.STARTUP -> reactionTriggers.add("this.startup") BuiltinTrigger.SHUTDOWN -> reactionTriggers.add("this.shutdown") - else -> {} + else -> {} } } } @@ -126,7 +107,7 @@ class TSReactionGenerator( return with(PrependOperator) { """ | - |this.add${if (reaction.isMutation()) "Mutation" else "Reaction"}( + |this.add${if (reaction.isMutation) "Mutation" else "Reaction"}( | new __Triggers($reactionTriggers), | new __Args($reactFuncArgs), | function ($reactSignature) { @@ -140,27 +121,31 @@ class TSReactionGenerator( ${" | "..reactEpilogue} | // =============== END react epilogue | } - ${" | "..if (reaction.deadline != null) generateDeadlineHandler(reaction, reactPrologue, reactEpilogue, reactSignature) else "}"} + ${ + " | "..if (reaction.deadline != null) generateDeadlineHandler( + reaction, + reactPrologue, + reactEpilogue, + reactSignature + ) else "}" + } |); |""".trimMargin() - } + } } private fun generateReactionSignatureForTrigger(trigOrSource: VarRef): String { - var reactSignatureElementType = if (trigOrSource.variable is Timer) { - "__Tag" - } else if (trigOrSource.variable is Action) { - getActionType(trigOrSource.variable as Action) - } else if (trigOrSource.variable is Port) { - getPortType(trigOrSource.variable as Port) - } else { - errorReporter.reportError("Invalid trigger: ${trigOrSource.variable.name}") + val reactSignatureElementType = when (trigOrSource.variable) { + is Timer -> "__Tag" + is Action -> (trigOrSource.variable as Action).tsActionType + is Port -> (trigOrSource.variable as Port).tsPortType + else -> errorReporter.reportError("Invalid trigger: ${trigOrSource.variable.name}") } val portClassType = if (trigOrSource.variable.isMultiport) { - "__InMultiPort<${reactSignatureElementType}>" + "__InMultiPort<$reactSignatureElementType>" } else { - "Read<${reactSignatureElementType}>" + "Read<$reactSignatureElementType>" } return if (trigOrSource.container != null && trigOrSource.container.isBank) { "${generateArg(trigOrSource)}: Array<$portClassType>" @@ -172,13 +157,13 @@ class TSReactionGenerator( private fun generateReactionSignatureElementForPortEffect(effect: VarRef, isMutation: Boolean): String { val outputPort = effect.variable as Port val portClassType = if (outputPort.isMultiport) { - (if (isMutation) "__WritableMultiPort" else "MultiReadWrite") + "<${getPortType(effect.variable as Port)}>" + (if (isMutation) "__WritableMultiPort" else "MultiReadWrite") + "<${(effect.variable as Port).tsPortType}>" } else { - (if (isMutation) "__WritablePort" else "ReadWrite") + "<${getPortType(effect.variable as Port)}>" + (if (isMutation) "__WritablePort" else "ReadWrite") + "<${(effect.variable as Port).tsPortType}>" } return if (effect.container != null && effect.container.isBank) { - "Array<${portClassType}>" + "Array<$portClassType>" } else { portClassType } @@ -186,51 +171,53 @@ class TSReactionGenerator( private fun generateReactionEpilogueForPortEffect(effect: VarRef): String { val portEffect = effect.variable as Port - if (effect.container == null) { + val effectName = portEffect.name + return if (effect.container == null) { if (portEffect.isMultiport) { - return """ - |${portEffect.name}.forEach((__element, __index) => { - | if (__element !== undefined) { - | __${portEffect.name}.set(__index, __element); - | } - |});""".trimMargin() + """ + |$effectName.forEach((__element, __index) => { + | if (__element !== undefined) { + | __$effectName.set(__index, __element); + | } + |});""".trimMargin() } else { - return """ - |if (${portEffect.name} !== undefined) { - | __${portEffect.name}.set(${portEffect.name}); - |}""".trimMargin() + """ + |if ($effectName !== undefined) { + | __$effectName.set($effectName); + |}""".trimMargin() } } else { + val containerName = effect.container.name if (effect.container.isBank) { if (portEffect.isMultiport) { - return """ - |${effect.container.name}.forEach((__reactor, __reactorIndex) => { - | __reactor.${portEffect.name}.forEach((__element, __index) => { + """ + |$containerName.forEach((__reactor, __reactorIndex) => { + | __reactor.$effectName.forEach((__element, __index) => { | if (__element !== undefined) { - | __${effect.container.name}_${portEffect.name}[__reactorIndex].set(__index, __element) + | __${containerName}_$effectName[__reactorIndex].set(__index, __element) | } | }) |});""".trimMargin() } else { - return """ - |${effect.container.name}.forEach((__reactor, __reactorIndex) => { - | if (__reactor.${portEffect.name} !== undefined) { - | __${effect.container.name}_${portEffect.name}[__reactorIndex].set(__reactor.${portEffect.name}) + """ + |$containerName.forEach((__reactor, __reactorIndex) => { + | if (__reactor.$effectName !== undefined) { + | __${containerName}_$effectName[__reactorIndex].set(__reactor.$effectName) | } |});""".trimMargin() } } else { if (portEffect.isMultiport) { - return """ - |${effect.container.name}.${portEffect.name}.forEach((__element, __index) => { + """ + |$containerName.$effectName.forEach((__element, __index) => { | if (__element !== undefined) { - | __${effect.container.name}_${portEffect.name}.set(__index, __element) + | __${containerName}_$effectName.set(__index, __element) | } |});""".trimMargin() } else { - return """ - |if (${effect.container.name}.${portEffect.name} !== undefined) { - | __${effect.container.name}_${portEffect.name}.set(${effect.container.name}.${portEffect.name}) + """ + |if ($containerName.$effectName !== undefined) { + | __${containerName}_$effectName.set($containerName.$effectName) |}""".trimMargin() } } @@ -238,7 +225,7 @@ class TSReactionGenerator( } // TODO(hokeun): Decompose this function further. - private fun generateSingleReaction(reactor : Reactor, reaction: Reaction): String { + private fun generateSingleReaction(reactor: Reactor, reaction: Reaction): String { // Determine signature of the react function val reactSignature = StringJoiner(", ") reactSignature.add("this") @@ -254,14 +241,9 @@ class TSReactionGenerator( val reactFunctArgs = StringJoiner(", ") // Combine triggers and sources into a set // so we can iterate over their union - val triggersUnionSources = HashSet() - for (trigger in reaction.triggers) { - if (!(trigger is BuiltinTriggerRef)) { - triggersUnionSources.add(trigger as VarRef) - } - } - for (source in reaction.sources) { - triggersUnionSources.add(source) + val triggersUnionSources = mutableSetOf().also { + it.addAll(reaction.triggers.filterIsInstance()) + it.addAll(reaction.sources) } // Create a set of effect names so actions that appear @@ -272,112 +254,94 @@ class TSReactionGenerator( // unequal. // The key of the pair is the effect's container's name, // The effect of the pair is the effect's name - val effectSet = HashSet>() + val effectSet = reaction.effects.map { + val key = it.container?.name ?: "" // The container + val value = it.variable.name // The name of the effect + key to value + }.toMutableSet() - for (effect in reaction.effects) { - var key = ""; // The container, defaults to an empty string - val value = effect.variable.name; // The name of the effect - if (effect.container != null) { - key = effect.container.name - } - effectSet.add(Pair(key, value)) - } // The prologue to the react function writes state // and parameters to local variables of the same name - val reactPrologue = LinkedList() + val reactPrologue = mutableListOf() reactPrologue.add("const util = this.util;") // Add triggers and sources to the react function - val containerToArgs = HashMap>(); + val containerToArgs = mutableMapOf>() for (trigOrSource in triggersUnionSources) { // Actions that are both read and scheduled should only // appear once as a schedulable effect - var trigOrSourceKey = "" // The default for no container - val trigOrSourceValue = trigOrSource.variable.name - if (trigOrSource.container != null) { - trigOrSourceKey = trigOrSource.container.name - } - val trigOrSourcePair = Pair(trigOrSourceKey, trigOrSourceValue) + val trigOrSourceKey = trigOrSource.container?.name.orEmpty() + val triggerName = trigOrSource.variable.name + val trigOrSourcePair = trigOrSourceKey to triggerName - if (!effectSet.contains(trigOrSourcePair)) { + if (trigOrSourcePair !in effectSet) { reactSignature.add(generateReactionSignatureForTrigger(trigOrSource)) reactFunctArgs.add(trigOrSource.generateVarRef()) if (trigOrSource.container == null) { if (trigOrSource.variable.isMultiport) { - val inputPort = trigOrSource.variable as Port - reactPrologue.add( - "let ${inputPort.name} = ${generateArg(trigOrSource)}.values();") + reactPrologue.add("let $triggerName = ${generateArg(trigOrSource)}.values();") } else { - reactPrologue.add("let ${trigOrSource.variable.name} = ${generateArg(trigOrSource)}.get();") + reactPrologue.add("let $triggerName = ${generateArg(trigOrSource)}.get();") } } else { - var args = containerToArgs.get(trigOrSource.container) - if (args == null) { - // Create the HashSet for the container - // and handle it later. - args = HashSet() - containerToArgs.put(trigOrSource.container, args) - } + val args = containerToArgs.computeIfAbsent(trigOrSource.container) { mutableSetOf() } args.add(trigOrSource.variable) } } } - val schedActionSet = HashSet() + val schedActionSet = mutableSetOf() // The epilogue to the react function writes local // state variables back to the state - val reactEpilogue = LinkedList() + val reactEpilogue = mutableListOf() for (effect in reaction.effects) { - var reactSignatureElement = generateArg(effect) - if (effect.variable is Timer) { - errorReporter.reportError("A timer cannot be an effect of a reaction") - } else if (effect.variable is Action){ - reactSignatureElement += ": Sched<" + getActionType(effect.variable as Action) + ">" - schedActionSet.add(effect.variable as Action) - } else if (effect.variable is Port){ - reactSignatureElement += ": ${generateReactionSignatureElementForPortEffect(effect, reaction.isMutation())}" - reactEpilogue.add(generateReactionEpilogueForPortEffect(effect)) - } + val reactSignatureElement = generateArg(effect) + val functArg = effect.generateVarRef() + when (val effectVar = effect.variable) { + is Timer -> { + errorReporter.reportError("A timer cannot be an effect of a reaction") + } - reactSignature.add(reactSignatureElement) + is Action -> { + reactSignature.add("$reactSignatureElement: Sched<${effectVar.tsActionType}>") + schedActionSet.add(effectVar) + reactFunctArgs.add("this.schedulable($functArg)") + } - var functArg = effect.generateVarRef() - if (effect.variable is Action){ - reactFunctArgs.add("this.schedulable($functArg)") - } else if (effect.variable is Port) { - val port = effect.variable as Port - if (port.isMultiport) { - if (effect.container != null && effect.container.isBank) { - reactFunctArgs.add("this.${effect.container.name}.allWritable($functArg)") - } else { - reactFunctArgs.add("this.allWritable($functArg)") - } - } else { - if (effect.container != null && effect.container.isBank) { - reactFunctArgs.add("this.${effect.container.name}.writable($functArg)") + is Port -> { + val type = generateReactionSignatureElementForPortEffect(effect, reaction.isMutation) + reactSignature.add("$reactSignatureElement: $type") + reactEpilogue.add(generateReactionEpilogueForPortEffect(effect)) + val funcArgs = if (effectVar.isMultiport) { + if (effect.container?.isBank == true) { + "this.${effect.container.name}.allWritable($functArg)" + } else { + "this.allWritable($functArg)" + } } else { - reactFunctArgs.add("this.writable($functArg)") + if (effect.container?.isBank == true) { + "this.${effect.container.name}.writable($functArg)" + } else { + "this.writable($functArg)" + } } + reactFunctArgs.add(funcArgs) } } if (effect.container == null) { if (effect.variable.isMultiport) { val port = effect.variable as Port - reactPrologue.add("let ${port.name} = new Array<${getPortType(port)}>(__${port.name}.width());") + reactPrologue.add("let ${port.name} = new Array<${port.tsPortType}>(__${port.name}.width());") } else { reactPrologue.add("let ${effect.variable.name} = __${effect.variable.name}.get();") } } else { // Hierarchical references are handled later because there // could be references to other members of the same reactor. - var args = containerToArgs.get(effect.container) - if (args == null) { - args = HashSet(); - containerToArgs.put(effect.container, args) - } + val args = containerToArgs.computeIfAbsent(effect.container) { HashSet() } args.add(effect.variable) } } @@ -385,10 +349,8 @@ class TSReactionGenerator( // Iterate through the actions to handle the prologue's // "actions" object if (schedActionSet.size > 0) { - val prologueActionObjectBody = StringJoiner(", ") - for (act in schedActionSet) { - prologueActionObjectBody.add("${act.name}: __${act.name}") - } + val prologueActionObjectBody = + schedActionSet.joinWithCommas { "${it.name}: __${it.name}" } reactPrologue.add("let actions = {$prologueActionObjectBody};") } @@ -396,45 +358,47 @@ class TSReactionGenerator( for (param in reactor.parameters) { // Underscores are added to parameter names to prevent conflict with prologue - reactSignature.add("__${param.name}: __Parameter<${param.getTargetType()}>") - reactFunctArgs.add("this.${param.name}") - - reactPrologue.add("let ${param.name} = __${param.name}.get();") + val name = param.name + reactSignature.add("__$name: __Parameter<${TSTypes.getTargetType(param)}>") + reactFunctArgs.add("this.$name") + reactPrologue.add("let $name = __$name.get();") } // Add state to the react function for (state in reactor.stateVars) { // Underscores are added to state names to prevent conflict with prologue - reactSignature.add("__${state.name}: __State<${state.getTargetType()}>") - reactFunctArgs.add("this.${state.name}") - - reactPrologue.add("let ${state.name} = __${state.name}.get();") - reactEpilogue.add(with(PrependOperator) {""" - |if (${state.name} !== undefined) { - | __${state.name}.set(${state.name}); - |}""".trimMargin()}) + val name = state.name + reactSignature.add("__$name: __State<${TSTypes.getTargetType(state)}>") + reactFunctArgs.add("this.$name") + reactPrologue.add("let $name = __$name.get();") + reactEpilogue.add( + """ + |if ($name !== undefined) { + | __$name.set($name); + |}""".trimMargin() + ) } // Initialize objects to enable hierarchical references. - for (entry in containerToArgs.entries) { - val initializer = StringJoiner(", ") - for (variable in entry.value) { - initializer.add("${variable.name}: __${entry.key.name}_${variable.name}" + + for ((container, args) in containerToArgs.entries) { + val containerName = container.name + val initializer = args.joinWithCommas { variable -> + "${variable.name}: __${containerName}_${variable.name}" + // The parentheses are needed below to separate two if-else statements. - (if (entry.key.isBank) "[i]" else "") + - if (variable.isMultiport) ".values()" else ".get()") + (if (container.isBank) "[i]" else "") + + if (variable.isMultiport) ".values()" else ".get()" } - if (entry.key.isBank) { - reactPrologue.add( + val prologuePart = + if (container.isBank) { """ - |let ${entry.key.name} = [] - |for (let i = 0; i < ${entry.key.widthSpec.toTSCode()}; i++) { - | ${entry.key.name}.push({${initializer}}) - |}""".trimMargin() - ) - } else { - reactPrologue.add("let ${entry.key.name} = {${initializer}}") - } + |let $containerName = [] + |for (let i = 0; i < ${container.widthSpec.toTSCode()}; i++) { + | $containerName.push({$initializer}) + |}""".trimMargin() + } else { + "let $containerName = {$initializer}" + } + reactPrologue.add(prologuePart) } // Generate reaction as a formatted string. @@ -460,8 +424,9 @@ class TSReactionGenerator( // Do not add reactions created by generateNetworkOutputControlReactionBody // or generateNetworkInputControlReactionBody. if (reaction.code.toText().contains("generateNetworkOutputControlReactionBody") - || reaction.code.toText().contains("generateNetworkInputControlReactionBody")) { - continue; + || reaction.code.toText().contains("generateNetworkInputControlReactionBody") + ) { + continue } if (federate.contains(reaction)) { generatedReactions.add(reaction) @@ -479,4 +444,4 @@ class TSReactionGenerator( } return reactionCodes.joinToString("\n") } -} \ No newline at end of file +} diff --git a/org.lflang/src/org/lflang/generator/ts/TSReactorGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSReactorGenerator.kt index e599bd28c8..3574d76310 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSReactorGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSReactorGenerator.kt @@ -153,11 +153,11 @@ class TSReactorGenerator( "export class $reactorName extends __Reactor {" } - val instanceGenerator = TSInstanceGenerator(tsGenerator, errorReporter, this, reactor, federate) - val timerGenerator = TSTimerGenerator(tsGenerator, reactor.timers) - val parameterGenerator = TSParameterGenerator(tsGenerator, reactor.parameters) - val stateGenerator = TSStateGenerator(tsGenerator, reactor.stateVars) - val actionGenerator = TSActionGenerator(tsGenerator, reactor.actions, federate) + val instanceGenerator = TSInstanceGenerator(errorReporter, reactor, federate) + val timerGenerator = TSTimerGenerator(reactor.timers) + val parameterGenerator = TSParameterGenerator(reactor.parameters) + val stateGenerator = TSStateGenerator(reactor.stateVars) + val actionGenerator = TSActionGenerator(reactor.actions, federate) val portGenerator = TSPortGenerator(reactor.inputs, reactor.outputs) val constructorGenerator = TSConstructorGenerator(tsGenerator, errorReporter, reactor, federate, targetConfig) @@ -196,4 +196,4 @@ class TSReactorGenerator( """ }.trimMargin() } -} \ No newline at end of file +} diff --git a/org.lflang/src/org/lflang/generator/ts/TSStateGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSStateGenerator.kt index 58ed7d4eee..091da1d958 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSStateGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSStateGenerator.kt @@ -1,42 +1,35 @@ package org.lflang.generator.ts import org.lflang.ASTUtils +import org.lflang.generator.getTargetInitializer import org.lflang.lf.StateVar import java.util.* /** * Generator for state variables in TypeScript target. */ -class TSStateGenerator ( - private val tsGenerator: TSGenerator, +class TSStateGenerator( private val stateVars: List ) { - private fun StateVar.getTargetType(): String = tsGenerator.getTargetTypeW(this) fun generateClassProperties(): String { val stateClassProperties = LinkedList() for (stateVar in stateVars) { - stateClassProperties.add("${stateVar.name}: __State<${stateVar.getTargetType()}>;"); + stateClassProperties.add("${stateVar.name}: __State<${TSTypes.getTargetType(stateVar)}>;"); } return stateClassProperties.joinToString("\n") } - private fun getInitializerList(state: StateVar): List = - tsGenerator.getInitializerListW(state) - - private fun getTargetInitializer(state: StateVar): String { - return getInitializerList(state).joinToString(",") - } fun generateInstantiations(): String { val stateInstantiations = LinkedList() // Next handle states. for (stateVar in stateVars) { if (ASTUtils.isInitialized(stateVar)) { - stateInstantiations.add("this.${stateVar.name} = new __State(${getTargetInitializer(stateVar)});"); + stateInstantiations.add("this.${stateVar.name} = new __State(${TSTypes.getTargetInitializer(stateVar)});"); } else { stateInstantiations.add("this.${stateVar.name} = new __State(undefined);"); } } return stateInstantiations.joinToString("\n") } -} \ No newline at end of file +} diff --git a/org.lflang/src/org/lflang/generator/ts/TSTimerGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSTimerGenerator.kt index 0d47872142..2cb1f37894 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSTimerGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSTimerGenerator.kt @@ -1,5 +1,7 @@ package org.lflang.generator.ts +import org.lflang.generator.getTargetTimeExpr +import org.lflang.generator.orZero import org.lflang.lf.Expression import org.lflang.lf.Timer import java.util.* @@ -7,12 +9,9 @@ import java.util.* /** * Generator timers for TypeScript target. */ -class TSTimerGenerator ( - // TODO(hokeun): Remove dependency on TSGenerator. - private val tsGenerator: TSGenerator, +class TSTimerGenerator( private val timers: List ) { - private fun Expression.getTargetValue(): String = tsGenerator.getTargetValueW(this) fun generateClassProperties(): String { val timerClassProperties = LinkedList() @@ -25,8 +24,8 @@ class TSTimerGenerator ( fun generateInstantiations(): String { val timerInstantiations = LinkedList() for (timer in timers) { - val timerPeriod: String = timer.period?.getTargetValue() ?: "0" - val timerOffset: String = timer.offset?.getTargetValue() ?: "0" + val timerPeriod: String = timer.period.orZero().toTsTime() + val timerOffset: String = timer.offset.orZero().toTsTime() timerInstantiations.add("this.${timer.name} = new __Timer(this, $timerOffset, $timerPeriod);") } diff --git a/org.lflang/src/org/lflang/generator/ts/TSTypes.kt b/org.lflang/src/org/lflang/generator/ts/TSTypes.kt index d996f5638e..9b7b66f62f 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSTypes.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSTypes.kt @@ -1,7 +1,10 @@ package org.lflang.generator.ts import org.lflang.ASTUtils +import org.lflang.TimeValue import org.lflang.generator.TargetTypes +import org.lflang.generator.UnsupportedGeneratorFeatureException +import org.lflang.joinWithCommas import org.lflang.lf.StateVar object TSTypes : TargetTypes { @@ -31,11 +34,24 @@ object TSTypes : TargetTypes { return "Present" } - override fun getTargetFixedSizeListType(baseType: String, size: Int): String { - return "Array($size)<$baseType>" + override fun getTargetTimeExpr(value: TimeValue): String { + return if (value.unit != null) { + "TimeValue.${value.unit.canonicalName}(${value.time})" + } else { + // The value must be zero. + "TimeValue.zero()" + } + } + + override fun getTargetFixedSizeListType(baseType: String?, size: Int): String { + throw UnsupportedGeneratorFeatureException("TypeScript does not support fixed-size array types.") } override fun getTargetVariableSizeListType(baseType: String): String { - return "Array<$baseType>" + return "Array<$baseType>" // same as "$baseType[]" + } + + override fun getVariableSizeListInitExpression(contents: MutableList, withBraces: Boolean): String { + return contents.joinWithCommas("[", "]") } -} \ No newline at end of file +}