5

I'm looking at ways to implement an FSM, which led to my first encounter with coroutines.

I saw several examples (here, here and here) which hint that a state machine can be implemented by a single coroutine. However, what I noticed all these machines have in common is that, excluding cycles, they are trees - that is, there is a single path (excluding cycles) from the start node to every other node - and that maps nicely to the hierarchical control flow provided by nested ifs. The state machine I'm trying to model has at least one state with more than one path from the start node to it (if cycles are eliminated, it's a directed acyclic graph). and I can't imagine what kind of control flow (apart from gotos) can achieve that, or if it's possible at all.

Alternatively, I may use a separate coroutine to handle each state and yield to some sort of dispatcher coroutine. However, I don't see any particular benefit of using coroutines over regular functions in this setup.

Here's a simple state machine that I have trouble modelling:

A --'a'--> B
A --'b'--> C
B --'a'--> C
B --'b'--> A
C --'a'--> A
C --'b'--> B

And here is what I have so far. The final implementation will be in C++ using Boost, but I'm using Python for prototyping.

#!/usr/bin/python3

def StateMachine():
    while True:
        print("  Entered state A")
        input = (yield)
        if input == "a":
            print("  Entered state B")
            input = (yield)
            if input == "a":
                # How to enter state C from here?
                pass
            elif input == "b":
                continue
        elif input == "b":
            print("  Entered state C")
            input = (yield)
            if input == "b":
                continue
            elif input == "a":
                # How to enter state B from here?
                pass

if __name__ == "__main__":
    sm = StateMachine()
    sm.__next__()
    while True:
        for line in input():
        sm.send(line)

Can this coroutine be fixed to correctly model the state machine? Or do I have to go some other way about it?

mcmlxxxvi
  • 1,358
  • 1
  • 11
  • 21

2 Answers2

2

I would model the state machine explicitly:

def StateMachine():
    state = 'A'
    while True:
        print(state)
        input = (yield)
        if state == 'A':
            if input == 'a':
                state = 'B'
            elif input == 'b':
                state = 'C'
            else:
                break
        elif state == 'B':
            if input == 'a':
                state = 'C'
            elif input == 'b':
                state = 'A'
            else:
                break
        elif state == 'C':
            if input == 'a':
                state = 'A'
            elif input == 'b':
                state = 'B'
            else:
                break
        else:
            break

This should translate very neatly to C++ using nested switch statements or a state transition table.

If you prefer an implicit model, you need a way to handle the cycles in your state machine. In C or C++, this would probably end up being goto. I wouldn't recommend this approach, but if you're more comfortable with it than explicit state, here's what it might look like:

#include <stdio.h>

#define start(state) switch(state) { case 0:;
#define finish default:;}
#define yield(state, value) do { state = __LINE__; return (value); case __LINE__:; } while(0)

struct coroutine {
    int state;
};

int
run(struct coroutine *c, char input)
{
    start(c->state)
    {
A:
        printf("Entered state A\n");
        yield(c->state, 1);
        if(input == 'a') goto B;
        if(input == 'b') goto C;
B:
        printf("Entered state B\n");
        yield(c->state, 1);
        if(input == 'a') goto C;
        if(input == 'b') goto A;
C:
        printf("Entered state C\n");
        yield(c->state, 1);
        if(input == 'a') goto A;
        if(input == 'b') goto B;
    } finish;

    return 0;
}

int
main(void)
{
    int a;
    struct coroutine c = {0};

    while((a = getchar()) != EOF && run(&c, a));

    return 0;
}
laindir
  • 1,650
  • 1
  • 14
  • 19
  • Having explicit states is always an option, but is something I'm trying to avoid. – mcmlxxxvi Dec 21 '13 at 02:23
  • I think the problem you're going to have is that your trying to represent a cyclic graph as a tree. That's only going to be possible if you allow a way to re-enter the tree; that's going to take either something like a `goto` into the middle of the tree or a variable to save the state so that the tree can be re-entered at the top. – laindir Dec 31 '13 at 15:38
  • I believe so myself, but I can't find a reference to "coroutines alone can't be used to implement a cyclic finite state machine". – mcmlxxxvi Jan 01 '14 at 15:41
  • It's not really specific to coroutines. You want to implement each state transition as its own if branch, but you need to jump across branches of the tree. Python doesn't have `goto`, but C and C++ do, I'll edit my answer to include an example in C with `goto`s. I don't think it's the best approach, but it does demonstrate what's possible. – laindir Jan 02 '14 at 18:59
  • After coding it, it does look cleaner than I expected (I started with a hybrid of if branch logic, like you had in Python, but switched to just `goto`, which cleaned things up substantially). It should probably handle inputs other than just 'a' and 'b' (maybe by just returning 0). The `#define`s at the top are a little icky, but allow you to implement generators in standard C, with a little syntactic sugar. Python is basically doing the same thing behind the scenes with its `yield` keyword. – laindir Jan 02 '14 at 19:17
  • 2
    An additional side note that I wanted to avoid mentioning for fear of muddying the waters is this: coroutines or generators themselves are often implemented behind the scenes as state machines (where the state is the line or instruction to execute on the next resume). I feel I have to at least point this out, since there really isn't any "behind the scenes" in C. That's what those `#define` macros are really all about. – laindir Jan 02 '14 at 19:30
  • Thank you for your time. We ended up using Boost meta-state machine since we had old code that could be modified, but I got my answer: coroutines as control flow alone, in general, are **not** sufficient to implement a finite state machine. – mcmlxxxvi Jan 14 '14 at 06:50
0

Theoretically, every finite state machine can be implemented with a single coroutine with just one of the following structures:

  • tests on auxiliary variables
  • goto statements
  • multi-level exits from loops

Of course, if-then-else and while structures are considered available. On the other hand, such an implementation is not always feasible with only single-level exits.

Thus, the specific example is written, without state variables and goto' s, as follows:

#!/usr/bin/python3 
# Python doesn't support multilevel exits, so try/except can be used instead.
# However, for this FSM, single-level exits are sufficient.  
def StateMachine():
  while True:
    while True:
      print("  Entered state A")
      input = (yield)
      if input == "a":
          print("  Entered state B")
          input = (yield)
          if input == "a": 
            break
          elif input == "b": 
            pass
      elif input == "b": 
            break    
    while True:
      print("  Entered state C")
      input = (yield)
      if input == "a": 
          break
      elif input == "b": 
          print("  Entered state B")
          input = (yield)
          if input == "a": 
            pass
          elif input == "b": 
            break
if __name__=="__main__":
     sm = StateMachine()
     sm.__next__()
     while True:
       for line in input():
        sm.send(line)
Abc
  • 1