From 09e0af945f601f87cf55f339302b4a86bb3a625b Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 29 Jan 2026 21:41:50 +0000 Subject: [PATCH 1/4] Add Conditional Transitions --- server/pom.xml | 14 +++--- .../src/main/java/fsm/StateMachineParser.java | 45 +++++++++++-------- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/server/pom.xml b/server/pom.xml index d94dd9f..dc7d640 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -21,9 +21,9 @@ - 20 - 20 - 20 + 21 + 21 + 21 @@ -31,8 +31,8 @@ maven-compiler-plugin ${pVersion.compiler} - 20 - 20 + 21 + 21 @@ -96,8 +96,8 @@ 0.24.0 UTF-8 - 20 - 20 + 21 + 21 1.3.0 10.4.2 diff --git a/server/src/main/java/fsm/StateMachineParser.java b/server/src/main/java/fsm/StateMachineParser.java index e33b842..5fdb5e7 100644 --- a/server/src/main/java/fsm/StateMachineParser.java +++ b/server/src/main/java/fsm/StateMachineParser.java @@ -261,29 +261,36 @@ private static List parseStateExpression(String expr, List state */ private static List getStateExpressions(Expression expr, List states) { List stateExpressions = new ArrayList<>(); - if (expr instanceof Var var) { - stateExpressions.add(var.getName()); - } else if (expr instanceof FunctionInvocation func) { - stateExpressions.add(func.getName()); - } else if (expr instanceof GroupExpression group) { - stateExpressions.addAll(getStateExpressions(group.getExpression(), states)); - } else if (expr instanceof BinaryExpression bin) { - String op = bin.getOperator(); - if (op.equals("||")) { - // combine states from both operands - stateExpressions.addAll(getStateExpressions(bin.getFirstOperand(), states)); - stateExpressions.addAll(getStateExpressions(bin.getSecondOperand(), states)); + switch (expr) { + case Var var -> stateExpressions.add(var.getName()); + case FunctionInvocation func -> stateExpressions.add(func.getName()); + case GroupExpression group -> stateExpressions.addAll(getStateExpressions(group.getExpression(), states)); + case BinaryExpression bin -> { + String op = bin.getOperator(); + if (op.equals("||")) { + // combine states from both operands + stateExpressions.addAll(getStateExpressions(bin.getFirstOperand(), states)); + stateExpressions.addAll(getStateExpressions(bin.getSecondOperand(), states)); + } } - } else if (expr instanceof UnaryExpression unary) { - if (unary.getOp().equals("!")) { - // all except those in the expression - List negatedStates = getStateExpressions(unary.getExpression(), states); - for (String state : states) { - if (!negatedStates.contains(state)) { - stateExpressions.add(state); + case UnaryExpression unary -> { + if (unary.getOp().equals("!")) { + // all except those in the expression + List negatedStates = getStateExpressions(unary.getExpression(), states); + for (String state : states) { + if (!negatedStates.contains(state)) { + stateExpressions.add(state); + } } } } + case Ite ite -> { + // combine states from then and else branches + // TODO: handle conditional transitions + stateExpressions.addAll(getStateExpressions(ite.getThen(), states)); + stateExpressions.addAll(getStateExpressions(ite.getElse(), states)); + } + default -> {} } return stateExpressions; } From a294c49988c634764e6898a75fb139cafc5b77e5 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 29 Jan 2026 21:52:34 +0000 Subject: [PATCH 2/4] Multiple Initial States --- client/src/types/fsm.ts | 2 +- client/src/webview/mermaid.ts | 6 ++-- client/src/webview/views/diagram.ts | 2 +- server/src/main/java/fsm/StateMachine.java | 2 +- .../src/main/java/fsm/StateMachineParser.java | 34 +++++++++---------- 5 files changed, 24 insertions(+), 22 deletions(-) diff --git a/client/src/types/fsm.ts b/client/src/types/fsm.ts index 943938b..f7c726c 100644 --- a/client/src/types/fsm.ts +++ b/client/src/types/fsm.ts @@ -2,7 +2,7 @@ export type StateMachine = { className: string; - initial: string; + initialStates: string[]; states: string[]; transitions: { from: string; to: string; label: string }[]; }; diff --git a/client/src/webview/mermaid.ts b/client/src/webview/mermaid.ts index 63114f0..58fb992 100644 --- a/client/src/webview/mermaid.ts +++ b/client/src/webview/mermaid.ts @@ -16,8 +16,10 @@ export function createMermaidDiagram(sm: StateMachine): string { lines.push('---'); lines.push('stateDiagram-v2'); - // initial state - lines.push(` [*] --> ${sm.initial}`); + // initial states + sm.initialStates.forEach(state => { + lines.push(` [*] --> ${state}`); + }); // transitions sm.transitions.forEach(transition => { diff --git a/client/src/webview/views/diagram.ts b/client/src/webview/views/diagram.ts index 190034e..b8d3fa2 100644 --- a/client/src/webview/views/diagram.ts +++ b/client/src/webview/views/diagram.ts @@ -12,7 +12,7 @@ export function renderStateMachineView(sm: StateMachine, diagram: string, select

States: ${sm.states.join(', ')}

-

Initial state: ${sm.initial}

+

Initial state${sm.initialStates.length > 1 ? 's' : ''}: ${sm.initialStates.join(', ')}

Number of states: ${sm.states.length}

Number of transitions: ${sm.transitions.length + 1}

diff --git a/server/src/main/java/fsm/StateMachine.java b/server/src/main/java/fsm/StateMachine.java index 8e988e8..c26d07c 100644 --- a/server/src/main/java/fsm/StateMachine.java +++ b/server/src/main/java/fsm/StateMachine.java @@ -7,7 +7,7 @@ */ public record StateMachine( String className, - String initial, + List initialStates, List states, List transitions ) { } diff --git a/server/src/main/java/fsm/StateMachineParser.java b/server/src/main/java/fsm/StateMachineParser.java index 5fdb5e7..adb78f0 100644 --- a/server/src/main/java/fsm/StateMachineParser.java +++ b/server/src/main/java/fsm/StateMachineParser.java @@ -2,7 +2,9 @@ import java.net.URI; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import liquidjava.rj_language.ast.*; import liquidjava.rj_language.parsing.RefinementsParser; @@ -52,20 +54,20 @@ public static StateMachine parse(String uri) { String className = getClassName(ctType); // extract initial state and transitions - String initial; + List initialStates; List transitions; if (ctType instanceof CtClass ctClass) { - initial = getInitialStateFromClass(ctClass, states); + initialStates = getInitialStatesFromClass(ctClass, states); transitions = getTransitionsFromClass(ctClass, states); } else if (ctType instanceof CtInterface ctInterface) { - initial = getInitialStateFromInterface(ctInterface, className, states); + initialStates = getInitialStatesFromInterface(ctInterface, className, states); transitions = getTransitionsFromInterface(ctInterface, className, states); } else { return null; } if (transitions.isEmpty()) return null; // no transitions found - return new StateMachine(className, initial, states, transitions); + return new StateMachine(className, initialStates, states, transitions); } catch (Exception e) { e.printStackTrace(); @@ -119,25 +121,24 @@ private static List getStates(CtType ctType) { } /** - * Gets the initial state from a class + * Gets the initial states from a class * If not explicitely defined, uses the first state in the state set * @param ctClass the CtClass * @param states the list of states - * @return initial state + * @return initial states */ - private static String getInitialStateFromClass(CtClass ctClass, List states) { + private static List getInitialStatesFromClass(CtClass ctClass, List states) { + Set initialStates = new HashSet<>(); for (CtConstructor constructor : ctClass.getConstructors()) { for (CtAnnotation annotation : constructor.getAnnotations()) { if (annotation.getAnnotationType().getSimpleName().equals(STATE_REFINEMENT_ANNOTATION)) { String to = annotation.getValueAsString("to"); List parsedStates = parseStateExpression(to, states); - if (!parsedStates.isEmpty()) { - return parsedStates.getFirst(); - } + initialStates.addAll(parsedStates); } } } - return states.getFirst(); + return initialStates.isEmpty() ? List.of(states.get(0)) : initialStates.stream().toList(); } /** @@ -145,23 +146,22 @@ private static String getInitialStateFromClass(CtClass ctClass, List * If not explicitely defined, uses the first state in the state set * @param ctInterface the CtInterface * @param className the class name - * @return initial state + * @return initial states */ - private static String getInitialStateFromInterface(CtInterface ctInterface, String className, List states) { + private static List getInitialStatesFromInterface(CtInterface ctInterface, String className, List states) { + Set initialStates = new HashSet<>(); for (CtMethod method : ctInterface.getMethods()) { if (method.getSimpleName().equals(className)) { for (CtAnnotation annotation : method.getAnnotations()) { if (annotation.getAnnotationType().getSimpleName().equals(STATE_REFINEMENT_ANNOTATION)) { String to = annotation.getValueAsString("to"); List parsedStates = parseStateExpression(to, states); - if (!parsedStates.isEmpty()) { - return parsedStates.getFirst(); - } + initialStates.addAll(parsedStates); } } } } - return states.isEmpty() ? null : states.getFirst(); + return initialStates.isEmpty() ? List.of(states.get(0)) : initialStates.stream().toList(); } /** From 67e095c5533d97ab18b96bd43215ea605d1ceb05 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 29 Jan 2026 22:03:00 +0000 Subject: [PATCH 3/4] Merge Loop Transitions --- client/src/webview/mermaid.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/client/src/webview/mermaid.ts b/client/src/webview/mermaid.ts index 58fb992..d0dfd5e 100644 --- a/client/src/webview/mermaid.ts +++ b/client/src/webview/mermaid.ts @@ -21,9 +21,19 @@ export function createMermaidDiagram(sm: StateMachine): string { lines.push(` [*] --> ${state}`); }); - // transitions + // group transitions by from/to states and merge labels + const transitionMap = new Map(); sm.transitions.forEach(transition => { - lines.push(` ${transition.from} --> ${transition.to} : ${transition.label}`); + const key = `${transition.from}|${transition.to}`; + if (!transitionMap.has(key)) transitionMap.set(key, []); + transitionMap.get(key).push(transition.label); + }); + + // add transitions + transitionMap.forEach((labels, key) => { + const [from, to] = key.split('|'); + const mergedLabel = labels.join(', '); + lines.push(` ${from} --> ${to} : ${mergedLabel}`); }); return lines.join('\n'); From a15894c970f8e1e11f2cfa6a94c850efa8cf05ba Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 29 Jan 2026 22:29:50 +0000 Subject: [PATCH 4/4] Minor Changes --- server/pom.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/server/pom.xml b/server/pom.xml index dc7d640..d94dd9f 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -21,9 +21,9 @@ - 21 - 21 - 21 + 20 + 20 + 20
@@ -31,8 +31,8 @@ maven-compiler-plugin ${pVersion.compiler} - 21 - 21 + 20 + 20 @@ -96,8 +96,8 @@ 0.24.0 UTF-8 - 21 - 21 + 20 + 20 1.3.0 10.4.2