diff --git a/pom.xml b/pom.xml index cbea213..5843cfa 100644 --- a/pom.xml +++ b/pom.xml @@ -20,5 +20,13 @@ 1.18.20 provided + + + + org.junit.jupiter + junit-jupiter-engine + 5.7.1 + test + \ No newline at end of file diff --git a/src/main/java/nl/andrewlalis/grammar_tool/machine/FiniteStateMachine.java b/src/main/java/nl/andrewlalis/grammar_tool/machine/FiniteStateMachine.java index 7950667..842c43b 100644 --- a/src/main/java/nl/andrewlalis/grammar_tool/machine/FiniteStateMachine.java +++ b/src/main/java/nl/andrewlalis/grammar_tool/machine/FiniteStateMachine.java @@ -46,6 +46,10 @@ public class FiniteStateMachine { } } + public int getStateCount() { + return this.states.size(); + } + public Set getTransitionsStartingAt(State state) { Set stateTransitions = new HashSet<>(); for (Transition t : this.transitions) { @@ -89,7 +93,7 @@ public class FiniteStateMachine { public boolean isDeterministic() { for (State state : this.states) { for (Symbol symbol : this.alphabet) { - if (this.getNextStates(state, symbol).size() > 1) { + if (this.getEpsilonClosure(this.getNextStates(state, symbol)).size() > 1) { return false; } } diff --git a/src/main/java/nl/andrewlalis/grammar_tool/machine/State.java b/src/main/java/nl/andrewlalis/grammar_tool/machine/State.java index ba90576..cf059c6 100644 --- a/src/main/java/nl/andrewlalis/grammar_tool/machine/State.java +++ b/src/main/java/nl/andrewlalis/grammar_tool/machine/State.java @@ -34,6 +34,10 @@ public class State implements Comparable { return this.name.compareTo(o.name); } + public static State of(String name) { + return new State(name); + } + public static State of(Set states) { if (states.isEmpty()) throw new IllegalArgumentException("Cannot construct state of empty states."); if (states.size() == 1) return states.stream().findAny().get(); diff --git a/src/test/java/nl/andrewlalis/grammar_tool/machine/FiniteStateMachineTest.java b/src/test/java/nl/andrewlalis/grammar_tool/machine/FiniteStateMachineTest.java new file mode 100644 index 0000000..5c53a73 --- /dev/null +++ b/src/test/java/nl/andrewlalis/grammar_tool/machine/FiniteStateMachineTest.java @@ -0,0 +1,98 @@ +package nl.andrewlalis.grammar_tool.machine; + +import nl.andrewlalis.grammar_tool.grammar.Symbol; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +public class FiniteStateMachineTest { + private static final String fsm1 = """ + -> q0 : "a" -> q1 + * q1 + """; + private static final String fsm2 = """ + -> q0 : "a" -> q1, "b" -> q2 + q1 : "a" -> q0, "" -> q2 + * q2 : "c" -> q2 + """; + private static final String fsm3 = """ + -> q0 : "a" -> q1, "b" -> q2 + q1 : "a" -> q0, "" -> q2, "c" -> q3 + * q2 : "c" -> q2 + q3 : "b" -> q2, "" -> q0 + """; + + @Test + public void testFromString() { + FiniteStateMachine f1 = FiniteStateMachine.fromString(fsm1); + FiniteStateMachine f1Eq = FiniteStateMachine.fromTransitions( + State.of("q0"), + Set.of(new Transition(State.of("q0"), Symbol.of("a"), State.of("q1"))), + Set.of(State.of("q1")) + ); + assertEquals(f1, f1Eq); + FiniteStateMachine f2 = FiniteStateMachine.fromString(fsm2); + FiniteStateMachine f2Eq = FiniteStateMachine.fromTransitions( + State.of("q0"), + Set.of( + new Transition(State.of("q0"), Symbol.of("a"), State.of("q1")), + new Transition(State.of("q0"), Symbol.of("b"), State.of("q2")), + new Transition(State.of("q1"), Symbol.of("a"), State.of("q0")), + new Transition(State.of("q1"), Symbol.EMPTY, State.of("q2")), + new Transition(State.of("q2"), Symbol.of("c"), State.of("q2")) + ), + Set.of(State.of("q2")) + ); + assertEquals(f2, f2Eq); + } + + @Test + public void testGetTransitionsStartingAt() { + FiniteStateMachine f1 = FiniteStateMachine.fromString(fsm1); + Set t0 = f1.getTransitionsStartingAt(State.of("q0")); + assertEquals(t0, Set.of(new Transition(State.of("q0"), Symbol.of("a"), State.of("q1")))); + Set t1 = f1.getTransitionsStartingAt(State.of("q1")); + assertEquals(t1, Set.of()); + FiniteStateMachine f2 = FiniteStateMachine.fromString(fsm2); + Set t2 = f2.getTransitionsStartingAt(State.of("q0")); + assertEquals(t2, Set.of( + new Transition(State.of("q0"), Symbol.of("a"), State.of("q1")), + new Transition(State.of("q0"), Symbol.of("b"), State.of("q2")) + )); + assertEquals(2, f2.getTransitionsStartingAt(State.of("q1")).size()); + } + + @Test + public void testIsDeterministic() { + assertTrue(FiniteStateMachine.fromString(fsm1).isDeterministic()); + assertFalse(FiniteStateMachine.fromString(fsm2).isDeterministic()); + assertFalse(FiniteStateMachine.fromString(fsm3).isDeterministic()); + assertTrue(FiniteStateMachine.fromString(fsm2).toDeterministic().isDeterministic()); + assertTrue(FiniteStateMachine.fromString(fsm3).toDeterministic().isDeterministic()); + } + + @Test + public void testToDeterministic() { + String nfsm1 = """ + -> q0 : "" -> q1, "b" -> q2 + q1 : "a" -> q2, "c" -> q1 + q2 : "d" -> q2, "d" -> q3 + * q3 : "c" -> q0 + """; + String dfsm1 = """ + -> q0 : "a" -> q2, "b" -> q2, "c" -> q1 + q1 : "a" -> q2, "c" -> q1 + q2 : "d" -> q3 + * q3 : "c" -> q0, "d" -> q3 + """; + FiniteStateMachine f1 = FiniteStateMachine.fromString(nfsm1); + assertFalse(f1.isDeterministic()); + FiniteStateMachine d1 = FiniteStateMachine.fromString(dfsm1); + assertTrue(d1.isDeterministic()); + FiniteStateMachine f1ToD1 = f1.toDeterministic(); + assertEquals(d1.getStateCount(), f1ToD1.getStateCount()); + assertTrue(f1ToD1.isDeterministic()); + } +}