From 0741d9d62d5206fad250bf074b51002504db7b23 Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Thu, 6 May 2021 12:49:23 +0200 Subject: [PATCH] Added deserialization of FSMs --- .../andrewlalis/grammar_tool/GrammarTool.java | 12 +- .../grammar_tool/grammar/Symbol.java | 4 + .../machine/FiniteStateMachine.java | 140 +++++++++++++++++- .../grammar_tool/machine/State.java | 7 +- .../grammar_tool/machine/Transition.java | 15 +- 5 files changed, 169 insertions(+), 9 deletions(-) diff --git a/src/main/java/nl/andrewlalis/grammar_tool/GrammarTool.java b/src/main/java/nl/andrewlalis/grammar_tool/GrammarTool.java index d167c2d..2782e95 100644 --- a/src/main/java/nl/andrewlalis/grammar_tool/GrammarTool.java +++ b/src/main/java/nl/andrewlalis/grammar_tool/GrammarTool.java @@ -1,6 +1,7 @@ package nl.andrewlalis.grammar_tool; import nl.andrewlalis.grammar_tool.grammar.ContextFreeGrammar; +import nl.andrewlalis.grammar_tool.machine.FiniteStateMachine; public class GrammarTool { public static void main(String[] args) { @@ -14,7 +15,14 @@ public class GrammarTool { "C -> c,C | ε" ); System.out.println(g2); - ContextFreeGrammar productive = g2.toProductiveForm(); - System.out.println(productive); + + FiniteStateMachine f1 = FiniteStateMachine.fromString(""" + -> q0 : "" -> q1, "b" -> q2 + q1 : "c" -> q1, "a" -> q2, + q2 : "d" -> q2, "d" -> q3, + * q3 : "c" -> q1 + """); + System.out.println(f1); + System.out.println("Deterministic? " + f1.isDeterministic()); } } \ No newline at end of file diff --git a/src/main/java/nl/andrewlalis/grammar_tool/grammar/Symbol.java b/src/main/java/nl/andrewlalis/grammar_tool/grammar/Symbol.java index bcfda51..20566ee 100644 --- a/src/main/java/nl/andrewlalis/grammar_tool/grammar/Symbol.java +++ b/src/main/java/nl/andrewlalis/grammar_tool/grammar/Symbol.java @@ -37,6 +37,10 @@ public class Symbol implements Comparable { return this.identifier.compareTo(o.identifier); } + public boolean isEmpty() { + return this.identifier.isEmpty(); + } + public static Symbol of(String identifier) { return new Symbol(identifier); } 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 60a6f5d..36220d8 100644 --- a/src/main/java/nl/andrewlalis/grammar_tool/machine/FiniteStateMachine.java +++ b/src/main/java/nl/andrewlalis/grammar_tool/machine/FiniteStateMachine.java @@ -2,8 +2,13 @@ package nl.andrewlalis.grammar_tool.machine; import nl.andrewlalis.grammar_tool.grammar.Symbol; +import java.util.HashSet; +import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; public class FiniteStateMachine { private final Set alphabet; @@ -13,11 +18,11 @@ public class FiniteStateMachine { private final Set transitions; public FiniteStateMachine(Set alphabet, Set states, Set finalStates, State startState, Set transitions) { - this.alphabet = alphabet; - this.states = states; - this.finalStates = finalStates; - this.startState = startState; - this.transitions = transitions; + this.alphabet = Objects.requireNonNull(alphabet); + this.states = Objects.requireNonNull(states); + this.finalStates = Objects.requireNonNull(finalStates); + this.startState = Objects.requireNonNull(startState); + this.transitions = Objects.requireNonNull(transitions); this.ensureValidElements(); } @@ -60,4 +65,129 @@ public class FiniteStateMachine { public int hashCode() { return Objects.hash(alphabet, states, finalStates, startState, transitions); } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + List sortedStates = this.states.stream().sorted().collect(Collectors.toList()); + for (State state : sortedStates) { + if (this.startState.equals(state)) { + sb.append("-> "); + } else if (this.finalStates.contains(state)) { + sb.append(" * "); + } else { + sb.append(" "); + } + sb.append(state); + List sortedTransitions = this.getTransitionsStartingAt(state).stream().sorted().collect(Collectors.toList()); + if (!sortedTransitions.isEmpty()) { + sb.append(" : "); + for (int i = 0; i < sortedTransitions.size(); i++) { + Transition t = sortedTransitions.get(i); + sb.append('"').append(t.getAcceptingSymbol()).append('"').append(" -> ").append(t.getEndState()); + if (i < sortedTransitions.size() - 1) sb.append(", "); + } + } + sb.append("\n"); + } + return sb.toString(); + } + + public Set getTransitionsStartingAt(State state) { + Set stateTransitions = new HashSet<>(); + for (Transition t : this.transitions) { + if (t.getStartState().equals(state)) { + stateTransitions.add(t); + } + } + return stateTransitions; + } + + public Set getNextStates(State currentState, Symbol acceptingSymbol) { + Set nextStates = new HashSet<>(); + for (Transition t : this.transitions) { + if (t.getStartState().equals(currentState) && t.getAcceptingSymbol().equals(acceptingSymbol)) { + nextStates.add(t.getEndState()); + } + } + return nextStates; + } + + public boolean isDeterministic() { + for (State state : this.states) { + for (Symbol symbol : this.alphabet) { + if (this.getNextStates(state, symbol).size() > 1) { + return false; + } + } + } + return true; + } + + public static FiniteStateMachine fromTransitions(State startState, Set transitions, Set finalStates) { + Set alphabet = new HashSet<>(); + Set states = new HashSet<>(); + for (Transition t : transitions) { + states.add(t.getStartState()); + states.add(t.getEndState()); + alphabet.add(t.getAcceptingSymbol()); + } + return new FiniteStateMachine(alphabet, states,finalStates, startState, transitions); + } + + /** + * Constructs an FSM from a series of strings depicting states and their + * possible transitions. The following format is used: + *

+	 *     -> q0 : "a" -> q1, "" -> q2
+	 *        q1 : "b" -> q2, "c" -> q3
+	 *      * q2 : "c" -> q3
+	 *      * q3
+	 * 
+ * @param fsmString The string containing the FSM specification. + * @return The finite state machine that was created. + */ + public static FiniteStateMachine fromString(String fsmString) { + String[] stateStrings = fsmString.split("\\n+"); + State startState = null; + Set finalStates = new HashSet<>(); + Set transitions = new HashSet<>(); + for (String stateString : stateStrings) { + if (stateString.isBlank()) continue; + String[] parts = stateString.split("\\s*:\\s*"); + String stateDefinition = parts[0].trim(); + String[] definitionParts = stateDefinition.split("\\s+"); + String modifier = null; + String stateName; + if (definitionParts.length > 1) { + modifier = definitionParts[0].trim(); + stateName = definitionParts[1].trim(); + } else { + stateName = definitionParts[0].trim(); + } + State state = new State(stateName); + if (modifier != null) { + if (modifier.trim().equals("->")) { + startState = state; + } else if (modifier.trim().equals("*")) { + finalStates.add(state); + } else { + throw new IllegalArgumentException("Invalid state modifier: " + modifier); + } + } + if (parts.length == 1) continue; + String[] transitionDefinitions = parts[1].split("\\s*,\\s*"); + for (String transitionDefinition : transitionDefinitions) { + String[] transitionParts = transitionDefinition.split("\\s*->\\s*"); + if (transitionParts.length != 2) throw new IllegalArgumentException("Invalid transition format: " + transitionDefinition); + Pattern p = Pattern.compile("\"([^\"]*)\""); + Matcher m = p.matcher(transitionParts[0]); + if (!m.find()) throw new IllegalArgumentException("Invalid transition format: " + transitionDefinition); + Symbol acceptingSymbol = new Symbol(m.group(1)); + State endState = new State(transitionParts[1].trim()); + transitions.add(new Transition(state, acceptingSymbol, endState)); + } + } + return fromTransitions(startState, transitions, finalStates); + } } 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 eaf30bd..b8695af 100644 --- a/src/main/java/nl/andrewlalis/grammar_tool/machine/State.java +++ b/src/main/java/nl/andrewlalis/grammar_tool/machine/State.java @@ -2,7 +2,7 @@ package nl.andrewlalis.grammar_tool.machine; import java.util.Objects; -public class State { +public class State implements Comparable { private final String name; public State(String name) { @@ -26,4 +26,9 @@ public class State { public String toString() { return this.name; } + + @Override + public int compareTo(State o) { + return this.name.compareTo(o.name); + } } diff --git a/src/main/java/nl/andrewlalis/grammar_tool/machine/Transition.java b/src/main/java/nl/andrewlalis/grammar_tool/machine/Transition.java index 993d693..5480a65 100644 --- a/src/main/java/nl/andrewlalis/grammar_tool/machine/Transition.java +++ b/src/main/java/nl/andrewlalis/grammar_tool/machine/Transition.java @@ -6,7 +6,7 @@ import nl.andrewlalis.grammar_tool.grammar.Symbol; import java.util.Objects; @Getter -public class Transition { +public class Transition implements Comparable { private final State startState; private final Symbol acceptingSymbol; private final State endState; @@ -36,4 +36,17 @@ public class Transition { public String toString() { return String.format("%s (%s) -> %s", this.startState, this.acceptingSymbol, this.endState); } + + public boolean isEpsilon() { + return this.acceptingSymbol.isEmpty(); + } + + @Override + public int compareTo(Transition o) { + int result = this.startState.compareTo(o.getStartState()); + if (result != 0) return result; + int symbolResult = this.acceptingSymbol.compareTo(o.getAcceptingSymbol()); + if (symbolResult != 0) return symbolResult; + return this.endState.compareTo(o.getEndState()); + } }