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
- Internally this compiles into approximately the same code as before.
- 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.
> STATE_NAME
defines a new state which can contain any number of methods.- 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.
- A class with
STATES
internally generatesDEFINITIONS
for all states. [> STATE_NAME]
is new syntax. It is a change state command. Writing[> RED]
is equivalent to writingstate = RED
. While a change state is often written on the end of the first line of a signal handlermethod
, the command can be written other places in the method as well.- A property
state= : Int32
is automatically added and aset_state(new_state:Int32)
setter method is automatically generated. It signalsLEAVE
withstate
still set to the old value, sets the newstate
, and then callsENTER
. - 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)