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:

Formel für Addition als RegEx[1]
/^[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.

FormelRegEx
Figure 1. Syntaxdiagramm zur Formel für Addition

Dieser Syntaxgraph lässt sich durch einen deterministischen, endlichen Zustandsautomaten realisieren, welcher in folgendem Diagramm dargestellt wird.

StateChartAddition
Figure 2. StateChart zur Formel für Addition

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.

Table 1. Chomsky Hierarchie[2]
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.

AdditionFormulaBuilder.java
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;
    }

}
FormulaStates.java
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 {

    }
}
FormulaContext.java
public record FormulaContext(int summe, StringBuilder zahlBuilder) {
}

Im folgenden noch eine Testklasse um zu zeigen wie der Builder benutzt werden kann.

AdditionFormulaBuilderTest.java
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());
    }

}

1. Quelle regex101: https://regex101.com/r/9Bviwn/1
2. Quelle Wikipedia: https://de.wikipedia.org/wiki/Chomsky-Hierarchie#%C3%9Cbersicht