Skip to content

Commit d8e2a9e

Browse files
committed
dsl-kotlin: ZFL → Semantic → FlowIR → Layout → FlowViewModel (v1 draft)
1 parent 783221e commit d8e2a9e

33 files changed

+2086
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ target/
2828
.settings
2929
.springBeans
3030
.sts4-cache
31+
bin/
3132

3233
### NetBeans ###
3334
/nbproject/private/

build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
plugins {
22
kotlin("multiplatform") version "2.0.21"
3+
kotlin("plugin.serialization") version "2.0.21"
34
id("com.strumenta.antlr-kotlin") version "1.0.9"
45
id("com.vanniktech.maven.publish") version "0.30.0"
56
id("org.jetbrains.kotlinx.kover") version "0.9.4"
@@ -137,6 +138,7 @@ kotlin {
137138
sourceSets {
138139
val commonMain by getting {
139140
dependencies {
141+
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
140142
implementation("com.strumenta:antlr-kotlin-runtime:1.0.3")
141143
}
142144
kotlin.srcDir(generateKotlinGrammarSource)
@@ -171,6 +173,10 @@ java {
171173
}
172174
}
173175

176+
tasks.clean {
177+
delete("bin")
178+
}
179+
174180
// generateJavaGrammarSource must run before jvmProcessResources
175181
tasks.named("jvmProcessResources") { dependsOn(generateJavaGrammarSource) }
176182
tasks.named("compileKotlinJvm") { dependsOn(generateJavaGrammarSource) }
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.zenwave360.language.eventflow.ir
2+
3+
import io.zenwave360.language.source.SourceRef
4+
import kotlinx.serialization.Serializable
5+
6+
/**
7+
* Directed relationship between two flow nodes.
8+
*/
9+
@Serializable
10+
data class FlowEdge(
11+
val id: String,
12+
val source: String,
13+
val target: String,
14+
val type: FlowEdgeType,
15+
val label: String? = null,
16+
val sourceRef: SourceRef? = null
17+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.zenwave360.language.eventflow.ir
2+
3+
import kotlinx.serialization.Serializable
4+
5+
/**
6+
* Semantic meaning of a relationship between nodes.
7+
*/
8+
@Serializable
9+
enum class FlowEdgeType {
10+
CAUSATION,
11+
TRIGGER,
12+
CONDITIONAL
13+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.zenwave360.language.eventflow.ir
2+
3+
import kotlinx.serialization.Serializable
4+
import kotlinx.serialization.encodeToString
5+
import kotlinx.serialization.json.Json
6+
7+
/**
8+
* Canonical, language-agnostic representation of an event-driven flow.
9+
*
10+
* This model is semantic, deterministic, and layout-agnostic.
11+
*/
12+
@Serializable
13+
data class FlowIR(
14+
val nodes: List<FlowNode>,
15+
val edges: List<FlowEdge>
16+
) {
17+
/**
18+
* Converts this FlowIR to JSON string.
19+
*
20+
* @param pretty If true, formats the JSON with indentation for readability. Default is false.
21+
* @return JSON string representation of this FlowIR
22+
*/
23+
fun toJson(pretty: Boolean = false): String {
24+
val json = if (pretty) {
25+
Json {
26+
prettyPrint = true
27+
encodeDefaults = true
28+
}
29+
} else {
30+
Json {
31+
encodeDefaults = true
32+
}
33+
}
34+
return json.encodeToString(this)
35+
}
36+
37+
/**
38+
* Alias for toJson(pretty = true) for backward compatibility.
39+
*/
40+
fun toJsonString(): String = toJson(pretty = true)
41+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.zenwave360.language.eventflow.ir
2+
3+
import io.zenwave360.language.source.SourceRef
4+
import kotlinx.serialization.Serializable
5+
6+
/**
7+
* A node in an event-driven flow.
8+
*/
9+
@Serializable
10+
data class FlowNode(
11+
val id: String,
12+
val type: FlowNodeType,
13+
val label: String,
14+
val system: String?,
15+
val sourceRef: SourceRef
16+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.zenwave360.language.eventflow.ir
2+
3+
import kotlinx.serialization.Serializable
4+
5+
/**
6+
* Semantic types of nodes in an event flow.
7+
*/
8+
@Serializable
9+
enum class FlowNodeType {
10+
COMMAND,
11+
EVENT,
12+
POLICY
13+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package io.zenwave360.language.eventflow.ir
2+
3+
import io.zenwave360.language.zfl.semantic.*
4+
5+
/**
6+
* Transforms a ZFL semantic model into the canonical EventFlow IR.
7+
*
8+
* Mental model:
9+
* Each when in ZFL expresses:
10+
* (trigger event[s]) → [optional policy] → command → emitted event[s]
11+
*
12+
* The transformer's job is to make this chain explicit as nodes and edges.
13+
*/
14+
class ZflToFlowIrTransformer {
15+
16+
fun transform(semanticModel: ZflSemanticModel): FlowIR {
17+
val nodeMap = mutableMapOf<String, FlowNode>()
18+
val edgeList = mutableListOf<FlowEdge>()
19+
20+
semanticModel.flows.forEach { flow ->
21+
// 1. Register all commands as nodes
22+
flow.commands.forEach { command ->
23+
nodeMap[command.name] = FlowNode(
24+
id = command.name,
25+
type = FlowNodeType.COMMAND,
26+
label = command.name,
27+
system = command.system,
28+
sourceRef = command.sourceRef
29+
)
30+
}
31+
32+
// 2. Register all events as nodes
33+
flow.events.forEach { event ->
34+
nodeMap[event.name] = FlowNode(
35+
id = event.name,
36+
type = FlowNodeType.EVENT,
37+
label = event.name,
38+
system = event.system,
39+
sourceRef = event.sourceRef
40+
)
41+
}
42+
43+
// 3. Register all policies as nodes
44+
flow.policies.forEach { policy ->
45+
val policyId = policyNodeId(policy)
46+
nodeMap[policyId] = FlowNode(
47+
id = policyId,
48+
type = FlowNodeType.POLICY,
49+
label = policy.name,
50+
system = policy.system,
51+
sourceRef = policy.sourceRef
52+
)
53+
}
54+
55+
// 4. Build edges from when-clauses
56+
buildEdgesFromWhens(flow, edgeList)
57+
}
58+
59+
return FlowIR(
60+
nodes = nodeMap.values.toList(),
61+
edges = edgeList
62+
)
63+
}
64+
65+
/**
66+
* Builds edges from when-clauses.
67+
*
68+
* Each when expresses: triggers → [policy?] → command → events
69+
*/
70+
private fun buildEdgesFromWhens(flow: ZflFlow, edgeList: MutableList<FlowEdge>) {
71+
flow.whens.forEach { when_ ->
72+
val commandName = when_.command
73+
74+
// Find if this when has a conditional policy
75+
val policy = flow.policies.find { it.toCommand == commandName }
76+
77+
// Create edges from triggers to command (possibly through policy)
78+
when_.triggers.forEach { triggerName ->
79+
if (policy != null) {
80+
// Trigger → Policy → Command
81+
val policyId = policyNodeId(policy)
82+
83+
edgeList.add(FlowEdge(
84+
id = "$triggerName$policyId",
85+
source = triggerName,
86+
target = policyId,
87+
type = FlowEdgeType.TRIGGER,
88+
sourceRef = when_.sourceRef
89+
))
90+
91+
edgeList.add(FlowEdge(
92+
id = "$policyId$commandName",
93+
source = policyId,
94+
target = commandName,
95+
type = FlowEdgeType.CONDITIONAL,
96+
label = policy.name,
97+
sourceRef = policy.sourceRef
98+
))
99+
} else {
100+
// Direct: Trigger → Command
101+
edgeList.add(FlowEdge(
102+
id = "$triggerName$commandName",
103+
source = triggerName,
104+
target = commandName,
105+
type = FlowEdgeType.TRIGGER,
106+
sourceRef = when_.sourceRef
107+
))
108+
}
109+
}
110+
111+
// Create edges from command to emitted events
112+
when_.events.forEach { eventName ->
113+
edgeList.add(FlowEdge(
114+
id = "$commandName$eventName",
115+
source = commandName,
116+
target = eventName,
117+
type = FlowEdgeType.CAUSATION,
118+
sourceRef = when_.sourceRef
119+
))
120+
}
121+
}
122+
}
123+
124+
/**
125+
* Generates a unique ID for a policy node.
126+
*/
127+
private fun policyNodeId(policy: ZflPolicy): String =
128+
"policy:${policy.name}:${policy.toCommand}"
129+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.zenwave360.language.eventflow.view
2+
3+
import kotlinx.serialization.Serializable
4+
5+
@Serializable
6+
data class FlowBounds(
7+
val x: Double,
8+
val y: Double,
9+
val width: Double,
10+
val height: Double
11+
)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.zenwave360.language.eventflow.view
2+
3+
import io.zenwave360.language.eventflow.ir.FlowEdgeType
4+
import io.zenwave360.language.source.SourceRef
5+
import kotlinx.serialization.Serializable
6+
7+
@Serializable
8+
data class FlowEdgeView(
9+
val id: String,
10+
val source: String,
11+
val target: String,
12+
val type: FlowEdgeType,
13+
val label: String?,
14+
val sourceRef: SourceRef?
15+
)

0 commit comments

Comments
 (0)