Skip to content

Commit 9894b21

Browse files
committed
Parse State Expressions using LiquidJava Parser
1 parent db538c8 commit 9894b21

2 files changed

Lines changed: 80 additions & 48 deletions

File tree

server/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@
173173
<dependency>
174174
<groupId>io.github.liquid-java</groupId>
175175
<artifactId>liquidjava-verifier</artifactId>
176-
<version>0.0.10</version>
176+
<version>0.0.11</version>
177177
</dependency>
178178
<dependency>
179179
<groupId>tools.aqua</groupId>

server/src/main/java/fsm/StateMachineParser.java

Lines changed: 79 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import java.util.ArrayList;
55
import java.util.List;
66

7+
import liquidjava.rj_language.ast.*;
8+
import liquidjava.rj_language.parsing.RefinementsParser;
79
import liquidjava.utils.Utils;
810
import spoon.Launcher;
911
import spoon.reflect.CtModel;
@@ -86,7 +88,7 @@ private static CtType<?> getType(CtModel model) {
8688
}
8789

8890
/**
89-
* Extracts the simple name from a class or interface
91+
* Gets the simple name from a class or interface
9092
* Uses name from ExternalRefinementsFor if present, otherwise uses class or interface name
9193
* @param ctType
9294
* @return class name
@@ -102,7 +104,7 @@ private static String getClassName(CtType<?> ctType) {
102104
}
103105

104106
/**
105-
* Extracts the possible states from a class or interface
107+
* Gets the possible states from a class or interface
106108
* @param ctType
107109
* @return list of states
108110
*/
@@ -117,7 +119,7 @@ private static List<String> getStates(CtType<?> ctType) {
117119
}
118120

119121
/**
120-
* Extracts the initial state from a class
122+
* Gets the initial state from a class
121123
* If not explicitely defined, uses the first state in the state set
122124
* @param ctClass
123125
* @return initial state
@@ -126,16 +128,19 @@ private static String getInitialStateFromClass(CtClass<?> ctClass, List<String>
126128
for (CtConstructor<?> constructor : ctClass.getConstructors()) {
127129
for (CtAnnotation<?> annotation : constructor.getAnnotations()) {
128130
if (annotation.getAnnotationType().getSimpleName().equals(STATE_REFINEMENT_ANNOTATION)) {
129-
Object to = annotation.getValueAsObject("to");
130-
return normalizeState(to);
131+
String to = annotation.getValueAsString("to");
132+
List<String> parsedStates = parseStateExpression(to, states);
133+
if (!parsedStates.isEmpty()) {
134+
return parsedStates.getFirst();
135+
}
131136
}
132137
}
133138
}
134-
return states.isEmpty() ? null : states.getFirst();
139+
return states.getFirst();
135140
}
136141

137142
/**
138-
* Extracts the initial state from an interface
143+
* Gets the initial state from an interface
139144
* If not explicitely defined, uses the first state in the state set
140145
* @param ctInterface
141146
* @param className
@@ -146,8 +151,11 @@ private static String getInitialStateFromInterface(CtInterface<?> ctInterface, S
146151
if (method.getSimpleName().equals(className)) {
147152
for (CtAnnotation<?> annotation : method.getAnnotations()) {
148153
if (annotation.getAnnotationType().getSimpleName().equals(STATE_REFINEMENT_ANNOTATION)) {
149-
Object to = annotation.getValueAsObject("to");
150-
return normalizeState(to);
154+
String to = annotation.getValueAsString("to");
155+
List<String> parsedStates = parseStateExpression(to, states);
156+
if (!parsedStates.isEmpty()) {
157+
return parsedStates.getFirst();
158+
}
151159
}
152160
}
153161
}
@@ -156,7 +164,7 @@ private static String getInitialStateFromInterface(CtInterface<?> ctInterface, S
156164
}
157165

158166
/**
159-
* Extracts transitions from a class
167+
* Gets transitions from a class
160168
* @param ctClass
161169
* @param states
162170
* @return list of StateMachineTransition
@@ -176,7 +184,7 @@ private static List<StateMachineTransition> getTransitionsFromClass(CtClass<?> c
176184
}
177185

178186
/**
179-
* Extracts transitions from an interface
187+
* Gets transitions from an interface
180188
* @param ctInterface
181189
* @param className
182190
* @param states
@@ -198,60 +206,84 @@ private static List<StateMachineTransition> getTransitionsFromInterface(CtInterf
198206
}
199207

200208
/**
201-
* Extracts transitions from the given annotation
202-
* @param annotation
203-
* @param methodName
209+
* Gets transitions from the given annotation
210+
* @param ann
211+
* @param method
204212
* @param states
205213
* @return list of StateMachineTransition
206214
*/
207-
private static List<StateMachineTransition> getTransitions(CtAnnotation<?> annotation, String methodName, List<String> states) {
215+
private static List<StateMachineTransition> getTransitions(CtAnnotation<?> ann, String method, List<String> states) {
208216
List<StateMachineTransition> transitions = new ArrayList<>();
209-
String from = normalizeState(annotation.getValueAsObject("from"));
210-
String to = normalizeState(annotation.getValueAsObject("to"));
217+
String from = ann.getValueAsString("from");
218+
String to = ann.getValueAsString("to");
211219

212220
// if has from but not to, to is the same as from (self-loop)
213221
if (from != null && to == null) {
214222
to = from;
215223
}
216224

217-
// if from is !state, from is all states except state (multiple transitions)
218-
if (from != null && from.startsWith("!")) {
219-
String excludedState = from.substring(1);
220-
if (states != null && to != null) {
221-
for (String state : states) {
222-
if (!state.equals(excludedState)) {
223-
transitions.add(new StateMachineTransition(state, to, methodName));
224-
}
225-
}
226-
}
227-
return transitions;
228-
}
225+
// parse from and to expressions
226+
List<String> fromStates = parseStateExpression(from, states);
227+
List<String> toStates = parseStateExpression(to, states);
229228

230-
// if has to but not from, from is all states (multiple transitions)
231-
if (to != null && from == null) {
232-
if (states != null) {
233-
for (String state : states) {
234-
transitions.add(new StateMachineTransition(state, to, methodName));
235-
}
236-
}
237-
return transitions;
229+
// if no from states, use all states
230+
if (fromStates.isEmpty() && to != null) {
231+
fromStates = new ArrayList<>(states);
238232
}
239233

240-
// normal transition
241-
if (from != null && to != null) {
242-
transitions.add(new StateMachineTransition(from, to, methodName));
234+
// create transitions for each combination of from and to states
235+
for (String fromState : fromStates) {
236+
for (String toState : toStates) {
237+
transitions.add(new StateMachineTransition(fromState, toState, method));
238+
}
243239
}
244240
return transitions;
245241
}
246242

247243
/**
248-
* Normalizes the state value by removing (this) and returning null if empty
249-
* @param value
250-
* @return normalized state
244+
* Parses a state expression and returns the list of states
245+
* @param expr
246+
* @param states
247+
* @return list of states
251248
*/
252-
private static String normalizeState(Object value) {
253-
if (value == null) return null;
254-
String normalized = value.toString().replace("(this)", "");
255-
return normalized.isEmpty() ? null : normalized;
249+
private static List<String> parseStateExpression(String expr, List<String> states) {
250+
if (expr == null || expr.isEmpty()) return new ArrayList<>();
251+
Expression ast = RefinementsParser.createAST(expr, "");
252+
return getStateExpressions(ast, states);
253+
}
254+
255+
/**
256+
* Gets state names from an expression AST recursively
257+
* @param expr
258+
* @param states
259+
* @return list of states
260+
*/
261+
private static List<String> getStateExpressions(Expression expr, List<String> states) {
262+
List<String> stateExpressions = new ArrayList<>();
263+
if (expr instanceof Var var) {
264+
stateExpressions.add(var.getName());
265+
} else if (expr instanceof FunctionInvocation func) {
266+
stateExpressions.add(func.getName());
267+
} else if (expr instanceof GroupExpression group) {
268+
stateExpressions.addAll(getStateExpressions(group.getExpression(), states));
269+
} else if (expr instanceof BinaryExpression bin) {
270+
String op = bin.getOperator();
271+
if (op.equals("||")) {
272+
// combine states from both operands
273+
stateExpressions.addAll(getStateExpressions(bin.getFirstOperand(), states));
274+
stateExpressions.addAll(getStateExpressions(bin.getSecondOperand(), states));
275+
}
276+
} else if (expr instanceof UnaryExpression unary) {
277+
if (unary.getOp().equals("!")) {
278+
// all except those in the expression
279+
List<String> negatedStates = getStateExpressions(unary.getExpression(), states);
280+
for (String state : states) {
281+
if (!negatedStates.contains(state)) {
282+
stateExpressions.add(state);
283+
}
284+
}
285+
}
286+
}
287+
return stateExpressions;
256288
}
257289
}

0 commit comments

Comments
 (0)