State Machine Syntax in Rogue

State Machine Syntax in Rogue

I've added state machine convenience syntax to Rogue for the upcoming v1.11 release. Here's a preview!

Stoplight State Machine

Consider this toy Stoplight state machine.

There are three states, RED, GREEN, and YELLOW. The only input is an ADVANCE signal. Certain messages are displayed on ADVANCE and/or when we ENTER or LEAVE a state - we'll broadly (and informally) classify ENTER and LEAVE as signals as well.

Here's how I'd like to use my Stoplight state machine in Rogue.

local stoplight = Stoplight()
println stoplight  # RED

stoplight.ADVANCE  # [Green light go]

stoplight.ADVANCE  # [Yellow light go very fast]

stoplight.ADVANCE  # *Stoplight Camera*
                   # [Red light stop]
                   # "Don't get a ticket!"

It's not difficult to implement this in Rogue's previously existing "oomperative" syntax, but there is a lot of boilerplate and it is a bit tedious. Here's the "old way".

class Stoplight
  DEFINITIONS
    RED    = 0
    GREEN  = 1
    YELLOW = 2
    
  PROPERTIES
    state = -1 # signals entering state 0 without LEAVE'ing the old

  METHODS
    method ADVANCE
      which (state)
        case RED:    state = GREEN
        case GREEN:  state = YELLOW
        case YELLOW
          state = RED
          println @|"Don't get a ticket!"
      endWhich

    method description->String
      return state->String

    method ENTER
      which (state)
        case RED:    println "[Red light stop]"
        case GREEN:  println "[Green light go]"
        case YELLOW: println "[Yellow light go very fast]"
      endWhich

    method LEAVE
      which (state)
        case YELLOW: println "*Stoplight Camera*"
      endWhich

    method set_state( new_state:Int32 )
      if (new_state == -1) new_state = 0
      else                 LEAVE
      @state = new_state
      ENTER
endClass

Here's the above example re-written in Rogue 1.11.

class Stoplight
  METHODS
    method description->String
      return state->String

  STATES
    > RED
      method ENTER   println "[Red light stop]"
      method ADVANCE [> GREEN]

    > GREEN
      method ENTER   println "[Green light go]"
      method ADVANCE [> YELLOW]

    > YELLOW
      method ENTER   println "[Yellow light go very fast]"
      method ADVANCE [> RED]
        println @|"Don't get a ticket!"
      method LEAVE   println "*Stoplight Camera*"
endClass

Notes

  1. Internally this compiles into approximately the same code as before.
  2. The state machine syntax allows us to define the state actions "inside out". Each state's signal responses are defined in a cluster instead of having to spread them across multiple method definitions.
  3. > STATE_NAME defines a new state which can contain any number of methods.
  4. The methods are just regular methods that can accept parameters and return values, except that each state's version of the method is considered a "fragment" of the whole method. Each fragment of same-signature methods is combined into a single method with an auto-generated which-case to check the state.
  5. A class with STATES internally generates DEFINITIONS for all states.
  6. [> STATE_NAME] is new syntax. It is a change state command. Writing [> RED] is equivalent to writing state = RED. While a change state is often written on the end of the first line of a signal handler method, the command can be written other places in the method as well.
  7. A property state= : Int32 is automatically added and a set_state(new_state:Int32) setter method is automatically generated. It signals LEAVE with state still set to the old value, sets the new state, and then calls ENTER.
  8. The first > STATE defined is automatically the start state.

Detect 1011

Here is one more example. Let's implement the following state machine diagram (taken from here) in Rogue's new state machine syntax.

Here is a minimal version - you would create an object, send input signals ZERO and ONE to it, and retrieve count at the end.

class Detect1011
  PROPERTIES
    count = 0

  STATES
    > S0
      method ONE  [> S1]
    > S1
      method ZERO [> S2]
    > S2
      method ZERO [> S0]
      method ONE  [> S3]
    > S3
      method ZERO [> S2]
      method ONE  [> S1]
        ++count
endClass

Here's a longer alternative that includes test code and more verbose output.

Detect1011( "Rogue" )

class Detect1011
  PROPERTIES
    found = false

  METHODS
    method init( text:String )
      local utf8 = text.to_utf8

      # Print character symbols
      print   Character(forEach in utf8) + "       "
      println

      # Print character binary
      print( (forEach in utf8)->String(&binary) )
      println

      # Print marker at end of each 1011
      local reader = BitReader(text.to_utf8)
      while (reader.has_another)
        local bit = reader.read(1)
        if (bit) ONE
        else     ZERO

        if (found)
          print '^'
          found = false
        else
          print ' '
        endIf
      endWhile
      println

  STATES
    > S0
      method ONE  [> S1]
    > S1
      method ZERO [> S2]
    > S2
      method ZERO [> S0]
      method ONE  [> S3]
    > S3
      method ZERO [> S2]
      method ONE  [> S1]
        found = true
endClass

Here's the output, with additional spaces to make it easier to read.

R        o        g        u        e       
01010010 01101111 01100111 01110101 01100101
              ^     ^        ^        ^     

Version History

  • 2021-12-14 - Original
  • 2022-05-13 - Updated to reflect latest syntax (v1.13/v2.0)