diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt index 6081a13e..02fc6a9e 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt @@ -208,4 +208,9 @@ fun DataNode.filter(predicate: (Name, Data) -> Boolean): DataNod fun DataNode.first(): Data = data().first().second +/** + * Check that node is compatible with given type meaning that each element could be cast to the type + */ +expect fun DataNode<*>.checkType(type: KClass<*>) + //fun DataNode.filterIsInstance(type: KClass): DataNode = filter{_,data -> type.} \ No newline at end of file diff --git a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/GroupBuilder.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/GroupBuilder.kt similarity index 91% rename from dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/GroupBuilder.kt rename to dataforge-data/src/commonMain/kotlin/hep/dataforge/data/GroupBuilder.kt index 68e9e092..0820c162 100644 --- a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/GroupBuilder.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/GroupBuilder.kt @@ -18,7 +18,6 @@ package hep.dataforge.data import hep.dataforge.meta.Meta import hep.dataforge.meta.get import hep.dataforge.meta.string -import java.util.* interface GroupRule { operator fun invoke(node: DataNode): Map> @@ -40,7 +39,8 @@ object GroupBuilder { * @param defaultTagValue * @return */ - fun byValue(key: String, defaultTagValue: String): GroupRule = object : GroupRule { + fun byValue(key: String, defaultTagValue: String): GroupRule = object : + GroupRule { override fun invoke(node: DataNode): Map> { val map = HashMap>() @@ -62,7 +62,12 @@ object GroupBuilder { // ) fun byMeta(config: Meta): GroupRule { //TODO expand grouping options - return config["byValue"]?.string?.let { byValue(it, config["defaultValue"]?.string ?: "default") } + return config["byValue"]?.string?.let { + byValue( + it, + config["defaultValue"]?.string ?: "default" + ) + } ?: object : GroupRule { override fun invoke(node: DataNode): Map> = mapOf("" to node) } diff --git a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/JoinAction.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/JoinAction.kt similarity index 94% rename from dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/JoinAction.kt rename to dataforge-data/src/commonMain/kotlin/hep/dataforge/data/JoinAction.kt index 6f2cf3f9..228b6c81 100644 --- a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/JoinAction.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/JoinAction.kt @@ -9,7 +9,6 @@ import hep.dataforge.names.toName import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.reflect.KClass -import kotlin.reflect.full.isSuperclassOf class JoinGroup(var name: String, internal val node: DataNode) { @@ -76,9 +75,7 @@ class JoinAction( ) : Action { override fun invoke(node: DataNode, meta: Meta): DataNode { - if (!this.inputType.isSuperclassOf(node.type)) { - error("$inputType expected, but ${node.type} received") - } + node.checkType(inputType) return DataNode.build(outputType) { JoinGroupBuilder(meta).apply(action).buildGroups(node).forEach { group -> diff --git a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/PipeAction.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/PipeAction.kt similarity index 92% rename from dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/PipeAction.kt rename to dataforge-data/src/commonMain/kotlin/hep/dataforge/data/PipeAction.kt index 268fafec..f106df40 100644 --- a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/PipeAction.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/PipeAction.kt @@ -5,7 +5,6 @@ import hep.dataforge.names.Name import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.reflect.KClass -import kotlin.reflect.full.isSuperclassOf class ActionEnv(val name: Name, val meta: Meta) @@ -33,9 +32,8 @@ class PipeAction( ) : Action { override fun invoke(node: DataNode, meta: Meta): DataNode { - if (!this.inputType.isSuperclassOf(node.type)) { - error("$inputType expected, but ${node.type} received") - } + node.checkType(inputType) + return DataNode.build(outputType) { node.data().forEach { (name, data) -> //merging data meta with action meta (data meta is primary) diff --git a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/SplitAction.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/SplitAction.kt similarity index 87% rename from dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/SplitAction.kt rename to dataforge-data/src/commonMain/kotlin/hep/dataforge/data/SplitAction.kt index 38740124..3aa08990 100644 --- a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/SplitAction.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/SplitAction.kt @@ -10,11 +10,10 @@ import kotlin.collections.set import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.reflect.KClass -import kotlin.reflect.full.isSuperclassOf class FragmentRule(val name: Name, var meta: MetaBuilder) { - lateinit var result: suspend (T) -> R + lateinit var result: suspend (T) -> R fun result(f: suspend (T) -> R) { result = f; @@ -43,9 +42,7 @@ class SplitAction( ) : Action { override fun invoke(node: DataNode, meta: Meta): DataNode { - if (!this.inputType.isSuperclassOf(node.type)) { - error("$inputType expected, but ${node.type} received") - } + node.checkType(inputType) return DataNode.build(outputType) { node.data().forEach { (name, data) -> @@ -56,7 +53,7 @@ class SplitAction( // apply individual fragment rules to result - split.fragments.forEach { fragmentName, rule -> + split.fragments.forEach { (fragmentName, rule) -> val env = FragmentRule(fragmentName, laminate.builder()) rule(env) diff --git a/dataforge-data/src/jsMain/kotlin/hep/dataforge/data/checkType.kt b/dataforge-data/src/jsMain/kotlin/hep/dataforge/data/checkType.kt new file mode 100644 index 00000000..56d1d0c9 --- /dev/null +++ b/dataforge-data/src/jsMain/kotlin/hep/dataforge/data/checkType.kt @@ -0,0 +1,10 @@ +package hep.dataforge.data + +import kotlin.reflect.KClass + +/** + * Check that node is compatible with given type meaning that each element could be cast to the type + */ +actual fun DataNode<*>.checkType(type: KClass<*>) { + //Not supported in js yet +} \ No newline at end of file diff --git a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/checkType.kt b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/checkType.kt new file mode 100644 index 00000000..0b4c602f --- /dev/null +++ b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/checkType.kt @@ -0,0 +1,13 @@ +package hep.dataforge.data + +import kotlin.reflect.KClass +import kotlin.reflect.full.isSuperclassOf + +/** + * Check that node is compatible with given type meaning that each element could be cast to the type + */ +actual fun DataNode<*>.checkType(type: KClass<*>) { + if (!type.isSuperclassOf(type)) { + error("$type expected, but $type received") + } +} \ No newline at end of file diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt index 0e07147c..eaaac235 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt @@ -58,9 +58,13 @@ fun TaskModel.buildInput(workspace: Workspace): DataTree { }.build() } +@DslMarker +annotation class TaskBuildScope + /** * A builder for [TaskModel] */ +@TaskBuildScope class TaskModelBuilder(val name: String, meta: Meta = EmptyMeta) { /** * Meta for current task. By default uses the whole input meta diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt index 99348682..e6284b47 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt @@ -11,6 +11,7 @@ import hep.dataforge.names.toName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.GlobalScope +@TaskBuildScope interface WorkspaceBuilder { val parentContext: Context var context: Context diff --git a/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/GenericTask.kt b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/GenericTask.kt index e2236c6a..ddb5e94d 100644 --- a/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/GenericTask.kt +++ b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/GenericTask.kt @@ -1,5 +1,6 @@ package hep.dataforge.workspace +import hep.dataforge.context.Context import hep.dataforge.data.* import hep.dataforge.descriptors.NodeDescriptor import hep.dataforge.meta.Meta @@ -63,6 +64,7 @@ class GenericTask( //TODO add validation } +@TaskBuildScope class KTaskBuilder(val name: String) { private var modelTransform: TaskModelBuilder.(Meta) -> Unit = { data("*") } var descriptor: NodeDescriptor? = null @@ -73,7 +75,7 @@ class KTaskBuilder(val name: String) { private class DataTransformation( val from: String = "", val to: String = "", - val transform: Workspace.() -> TaskModel.(DataNode) -> DataNode + val transform: (Context, TaskModel, DataNode) -> DataNode ) { operator fun invoke(workspace: Workspace, model: TaskModel, node: DataNode): DataNode? { val localData = if (from.isEmpty()) { @@ -81,7 +83,7 @@ class KTaskBuilder(val name: String) { } else { node.getNode(from.toName()) ?: return null } - return transform(workspace).invoke(model, localData) + return transform(workspace.context, model, localData) } } @@ -91,27 +93,23 @@ class KTaskBuilder(val name: String) { this.modelTransform = modelTransform } - //class TaskEnv(val workspace: Workspace, val model: TaskModel) - fun transform( inputType: KClass, from: String = "", to: String = "", - transform: Workspace.() -> TaskModel.(DataNode) -> DataNode + block: TaskModel.(Context, DataNode) -> DataNode ) { - dataTransforms += DataTransformation(from, to) { - { data: DataNode -> - transform().invoke(this, data.cast(inputType)) - } + dataTransforms += DataTransformation(from, to) { context, model, data -> + block(model, context, data.cast(inputType)) } } inline fun transform( from: String = "", to: String = "", - noinline transform: Workspace.() -> TaskModel.(DataNode) -> DataNode + noinline block: TaskModel.(Context, DataNode) -> DataNode ) { - transform(T::class, from, to, transform) + transform(T::class, from, to, block) } /** @@ -120,69 +118,74 @@ class KTaskBuilder(val name: String) { inline fun action( from: String = "", to: String = "", - crossinline actionBuilder: Workspace.() -> Action + crossinline block: Context.() -> Action ) { - transform(from, to) { - val res: TaskModel.(DataNode) -> DataNode = { data: DataNode -> - actionBuilder().invoke(data, meta) - } - res + transform(from, to) { context, data: DataNode -> + block(context).invoke(data, meta) } } + class TaskEnv(val name: Name, val meta: Meta, val context: Context) + inline fun pipeAction( from: String = "", to: String = "", - crossinline block: Workspace.() -> PipeBuilder.() -> Unit + crossinline block: PipeBuilder.(Context) -> Unit ) { action(from, to) { + val context = this PipeAction( inputType = T::class, - outputType = R::class, - block = block() - ) + outputType = R::class + ) { block(context) } } } inline fun pipe( from: String = "", to: String = "", - crossinline block: Workspace.() -> suspend ActionEnv.(T) -> R + crossinline block: suspend TaskEnv.(T) -> R ) { action(from, to) { + val context = this PipeAction( inputType = T::class, outputType = R::class - ) { result(block()) } + ) { + result = { data -> + TaskEnv(name, meta, context).block(data) + } + } } } - inline fun joinAction( from: String = "", to: String = "", - crossinline block: Workspace.() -> JoinGroupBuilder.() -> Unit + crossinline block: JoinGroupBuilder.(Context) -> Unit ) { action(from, to) { JoinAction( inputType = T::class, - outputType = R::class, - action = block() - ) + outputType = R::class + ) { block(this@action) } } } inline fun join( from: String = "", to: String = "", - crossinline block: Workspace.() -> suspend ActionEnv.(Map) -> R + crossinline block: suspend TaskEnv.(Map) -> R ) { action(from, to) { + val context = this JoinAction( inputType = T::class, outputType = R::class, action = { - result(actionMeta[TaskModel.MODEL_TARGET_KEY]?.string ?: "@anonimous", block()) + result(actionMeta[TaskModel.MODEL_TARGET_KEY]?.string ?: "@anonimous") { data -> + TaskEnv(name, meta, context).block(data) + } } ) } @@ -191,21 +194,20 @@ class KTaskBuilder(val name: String) { inline fun splitAction( from: String = "", to: String = "", - crossinline block: Workspace.() -> SplitBuilder.() -> Unit + crossinline block: SplitBuilder.(Context) -> Unit ) { action(from, to) { SplitAction( inputType = T::class, - outputType = R::class, - action = block() - ) + outputType = R::class + ) { block(this@action) } } } /** * Use DSL to create a descriptor for this task */ - fun descriptor(transform: NodeDescriptor.() -> Unit) { + fun description(transform: NodeDescriptor.() -> Unit) { this.descriptor = NodeDescriptor.build(transform) } diff --git a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt index 62b72505..92e3aa29 100644 --- a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt +++ b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt @@ -18,29 +18,25 @@ class SimpleWorkspaceTest { model { allData() } - pipe { - { data -> - context.logger.info { "Starting square on $data" } - data * data - } + pipe { data -> + context.logger.info { "Starting square on $data" } + data * data } } - task("sum"){ + task("sum") { model { dependsOn("square") } - join { - { data -> - context.logger.info { "Starting sum" } - data.values.sum() - } + join { data -> + context.logger.info { "Starting sum" } + data.values.sum() } } } @Test - fun testWorkspace(){ + fun testWorkspace() { val node = workspace.run("sum") val res = node.first() assertEquals(328350, res.get())