Beispiel RegEx Statechart Builder
In den folgenden Abschnitten möchte ich nun das Konzept der FluentBuilder auf Statecharts anwenden. Zunächst möchte ich einen sehr einfachen Zustandsautomaten zur Addition verwenden. Dieser Zustandsautomat lässt sich über folgenden regulären Ausdruck beschreiben:
/^[0-9]+(\+[0-9])+=/gm
Der Ausdruck soll wie folgt interpretiert werden: Es kann eine Zahl eingegeben werden und zu dieser Zahl können beliebige Zahlen addiert werden. Erst wenn ein Gleichheitszeichen eingegeben wird, soll die Formel enden und das Ergebnis der Addition vom Interpreter bereitgestellt werden.
Dieser Syntaxgraph lässt sich durch einen deterministischen, endlichen Zustandsautomaten realisieren, welcher in folgendem Diagramm dargestellt wird.
Da es sich bei dem Automaten um die Implementierung eines regulären Ausdruckes handelt, befinden wir uns in der Chomsky-Hierarchie auf der Ebene regulärer Sprachen, welche dem Chomsky Typ-3 entsprechen.
Zur Vertiefung des Themas lesen Sie gern auch meinen Exkurs zum Matching regulärer Ausdrücke.
Grammatik | Sprachen | Automaten |
---|---|---|
Typ-0 Beliebige formale Grammatik |
rekursiv aufzählbar (nicht „nur“ rekursiv, die wären entscheidbar!) |
Turingmaschine (egal ob deterministisch oder nicht-deterministisch) |
Typ-1 Kontextsensitive Grammatik |
kontextsensitiv |
linear platzbeschränkte nichtdeterministische Turingmaschine |
Typ-2 Kontextfreie Grammatik |
kontextfrei |
nichtdeterministischer Kellerautomat |
Typ-3 Reguläre Grammatik |
regulär |
Endlicher Automat (egal ob deterministisch oder nicht-deterministisch) |
Eine mögliche Implementierung des Builders wird im folgenden dargestellt.
public abstract class AdditionFormulaBuilder implements FormulaStates.Zahl1{
FormulaContext formulaContext;
private AdditionFormulaBuilder(){
this.formulaContext =new FormulaContext(0, new StringBuilder());
}
public static FormulaStates.Zahl1 builder() {
final AdditionFormulaBuilder builder = new AdditionFormulaBuilder() {
// will be never called
// because is internal overridden by lambda in return of builder() method
public FormulaContext build() {
throw new UnsupportedOperationException("Call of method forbidden");
}
};
// create new interface instance
return () -> builder.formulaContext;
}
}
public interface FormulaStates {
FormulaContext build();
public interface Zahl1 extends FormulaStates {
default FormulaStates.Zahl1 digit(int digit) {
if (digit < 0 || digit > 9) {
throw new IllegalArgumentException("wrong digit:" + digit);
}
final FormulaContext context = build();
// append digit to current number
context.zahlBuilder().append(digit);
return this;
}
default FormulaStates.Plus plus() {
final FormulaContext context = build();
final int zahl = Integer.parseInt(context.zahlBuilder().toString());
FormulaContext contextNeu = new FormulaContext(zahl, new StringBuilder());
return () -> contextNeu;
}
}
public interface Plus extends FormulaStates {
default FormulaStates.Zahl digit(int digit) {
if (digit < 0 || digit > 9) {
throw new IllegalArgumentException("wrong digit:" + digit);
}
final FormulaContext context = build();
// append digit to current number
context.zahlBuilder().append(digit);
return this::build;
}
}
public interface Zahl extends FormulaStates {
default FormulaStates.Zahl digit(int digit) {
if (digit < 0 || digit > 9) {
throw new IllegalArgumentException("wrong digit:" + digit);
}
final FormulaContext context = build();
// append digit to current number
context.zahlBuilder().append(digit);
return this;
}
default FormulaStates.Plus plus() {
final FormulaContext context = build();
final int zahl = Integer.parseInt(context.zahlBuilder().toString());
FormulaContext contextNeu = new FormulaContext(context.summe() + zahl, new StringBuilder());
return () -> contextNeu;
}
default FormulaStates.Final equals() {
final FormulaContext context = build();
final int zahl = Integer.parseInt(context.zahlBuilder().toString());
FormulaContext contextNeu = new FormulaContext(context.summe() + zahl, new StringBuilder());
return () -> contextNeu;
}
}
interface Final extends FormulaStates {
}
}
public record FormulaContext(int summe, StringBuilder zahlBuilder) {
}
Im folgenden noch eine Testklasse um zu zeigen wie der Builder benutzt werden kann.
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class AdditionFormulaBuilderTest {
@Test
@DisplayName("11+7=")
void add11und7(){
final FormulaContext context = AdditionFormulaBuilder.builder()
.digit(1)
.digit(1)
.plus()
.digit(7)
.equals()
.build();
assertEquals(18,context.summe());
assertEquals(0,context.zahlBuilder().length());
}
}
Wieder zur Übersicht oder zurück zum Beispiel der Plantuml Diagrammerstellung.