-
Notifications
You must be signed in to change notification settings - Fork 34
Instantiating Automata using Fluent Interface Builders
Even though the main purpose of AutomataLib is to manipulate automata and graphs that result from user inputs or are otherwise dynamically loaded, it can often be helpful (e.g., for testing) to directly instantiate small automata directly from source code.
AutomataLib provides a very convenient way for doing so: using builders with fluent interfaces. Such a builder allows for writing your code as a chain of method invocations that form almost human-readable sentences. The fluent interface builders for AutomataLib were generated using Duzzt.
Suppose we want to create a DFA model for the automaton on the right, which accepts all binary numbers which are multiples of 3 (source). Before we start instantiating the automaton, we first set up the input alphabet: we want to use the numbers 0 and 1 only.
Alphabet<Integer> alphabet = Alphabets.integers(0, 1);
Note that the start and end values in the Alphabets.integers
method are inclusive (see also more information on instantiating alphabets). Now we can instantiate our DFA. For this example, we won't care about the state type; we hence use DFA<?,Integer>
. However, the returned automaton is actually a CompactDFA<Integer>
, a property we could also exploit.
To create an automaton as shown above, we write the following lines:
DFA<?,Integer> dfa = AutomatonBuilders.newDFA(alphabet)
.withInitial("S0")
.from("S0")
.on(0).loop()
.on(1).to("S1")
.from("S1")
.on(0).to("S2")
.on(1).to("S0")
.from("S2")
.on(0).to("S1")
.on(1).loop()
.withAccepting("S0")
.create();
Let's discuss this code line by line.
First, we state that we want to create a new DFA with our previously instantiated alphabet
. If we are unhappy with the default choice of CompactDFA
, and instead want to use our own automaton model, we could also use the AutomatonBuilders.forDFA
method and pass an instance of our MutableDFA
to this method (i.e., AutomatonBuilders.forDFA(new MyDFA<>(alphabet))...
).
In the next line, we state that the state denoted by "S0" will be our initial state. This name is for further reference such as in the from
or to
calls only; the resulting DFA will not reflect the chosen names in any way. Furthermore, we could use any Java object as state references; String
is merely a convenient choice where.
What follows is the actual definition of the transition function. With from
, we start a new "block" for the transitions originating from the given state, such as "S0". Each from
can be followed by any positive number of transitions. A transition is created by subsequently invoking on
and then to
. on
receives as argument the input symbol that triggers this transition, while to to
we pass the reference for the destination state. Alternatively, if the automaton loops in this state on the respective input, we can also invoke loop()
, which in our case would be equivalent to writing to("S0")
.
Note that we are creating a deterministic finite automaton, hence the to
method only takes exactly one argument. However, of the from
and on
methods there exist varargs versions, which allow for concisely specifying multiple transitions in one line. Note that if multiple state references are passed to from
, loop()
will for each of them create a loop instead of creating from each state a transition to each other state.
Finally, we state which of our states are accepting. In our example, this is just our initial state "S0". However, for the case of multiple accepting states, also for this method there exists a varargs overload which allows marking several states as accepting in one call. Of course, it is alternately possible to chain calls to withAccepting
.
The terminating create()
invocation terminates the embedded DSL fragment, and returns the automaton model according to our specification.