diff --git a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/Action.kt b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/Action.kt index 14f7fb3a..3164e1a8 100644 --- a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/Action.kt +++ b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/Action.kt @@ -13,6 +13,7 @@ sealed interface Action { description.type, description.mode, buildVariables(description.input), + description.output, buildEvents(description.emits), ) @@ -67,12 +68,13 @@ internal constructor( val type: String, val mode: InvocationMode, val input: List, + val output: List, val emits: List, ) : EventRaisingAction { override fun raises(): List = emits override fun toString() = - "InvokeAction(type='$type', mode='$mode', input='$input', emits='$emits')" + "InvokeAction(type='$type', mode='$mode', input='$input', output='$output', emits='$emits')" } class MatchAction diff --git a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt index ceed4349..bd0d4ed8 100644 --- a/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt +++ b/src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt @@ -80,7 +80,10 @@ internal constructor( commandExecutionContext.coroutineScope.launch { runCatching { service.invoke(input) } - .onSuccess { emitEvents(it) } + .onSuccess { + assignServiceOutput(it, commandExecutionContext.scope.extent) + emitEvents(it) + } .onFailure { logger.error(it) { "service invocation failed" } } } @@ -108,6 +111,24 @@ internal constructor( handler(event) } } + + private fun assignServiceOutput(output: List, extent: Extent) { + invokeAction.output.forEach { variable -> + output + .firstOrNull { it.name == variable.reference } + ?.let { + runCatching { extent.set(variable.reference, it.value) } + .onFailure { e -> + logger.warn(e) { + "failed to assign service output to variable '${variable.reference}'" + } + } + } + ?: logger.warn { + "service output does not contain expected variable '${variable.reference}'" + } + } + } } class MatchActionCommand diff --git a/src/main/resources/pkl/csm/csml.pkl b/src/main/resources/pkl/csm/csml.pkl index 51f8e052..f494f516 100644 --- a/src/main/resources/pkl/csm/csml.pkl +++ b/src/main/resources/pkl/csm/csml.pkl @@ -96,6 +96,7 @@ class InvokeDescription extends ActionDescription { type: InvocationType mode: InvocationMode = "remote" input: Context + output: Listing emits: Listing } @@ -131,6 +132,10 @@ class LogDescription extends ActionDescription { message: Expression } +class ContextVariableReferenceDescription { + reference: String +} + typealias EventTopic = String(matches(Regex(#"^[a-zA-Z_]\w*$"#))) typealias EventChannel = "internal"|"external"|"peripheral" diff --git a/src/test/kotlin/at/ac/uibk/dps/cirrina/CompleteTest.kt b/src/test/kotlin/at/ac/uibk/dps/cirrina/CompleteTest.kt index 4f23416b..d75df9c7 100644 --- a/src/test/kotlin/at/ac/uibk/dps/cirrina/CompleteTest.kt +++ b/src/test/kotlin/at/ac/uibk/dps/cirrina/CompleteTest.kt @@ -27,7 +27,10 @@ class CompleteTest { val context = ContextInMemory() val server = mockHttpServer { input -> val v = input.firstOrNull { it.name == "v" } ?: error("variable 'v' not found") - listOf(ContextVariable("v", (v.value as Int) + 1)) + listOf( + ContextVariable("v", (v.value as Int) + 1), + ContextVariable("s", (v.value + 1) * 10), + ) } try { @@ -43,6 +46,7 @@ class CompleteTest { assertEquals(100, context.get("v")) assertEquals(true, context.get("b")) assertEquals(true, context.get("e")) + assertEquals(50500, context.get("f")) } finally { server.stop(1) } diff --git a/src/test/resources/pkl/complete/main.pkl b/src/test/resources/pkl/complete/main.pkl index 224af021..7032da1b 100644 --- a/src/test/resources/pkl/complete/main.pkl +++ b/src/test/resources/pkl/complete/main.pkl @@ -28,10 +28,14 @@ collaborativeStateMachine { type = "increment" mode = "local" input { ["v"] = "x" } + output { new ContextVariableReferenceDescription { reference = "s" } } emits { new Internal { topic = "e2" } } } } - exit { new Assign { expression = "e = true" } } + exit { + new Assign { expression = "e = true" } + new Assign { expression = "f = f + s" } + } on { ["e2"] = new Transition { to = "c" @@ -70,10 +74,13 @@ collaborativeStateMachine { } ["e"] = new Terminal { entry { new Assign { expression = "b = true" } } } } - transient { ["x"] = "0" } + transient { + ["x"] = "0" + ["s"] = "0" + } } } - persistent { ["b"] = "false"; ["v"] = "0"; ["e"] = "false" } + persistent { ["b"] = "false"; ["v"] = "0"; ["e"] = "false"; ["f"] = "0" } } instances { ["complete"] = new Instance { stateMachineName = "completeStateMachine" }