State Machine Syntax in Rogue
![State Machine Syntax in Rogue](/content/images/size/w2000/2021/12/Cover.jpeg)
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.
![](https://programbling.com/content/images/2021/12/Stoplight-State-Machine.png)
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.
![](https://programbling.com/content/images/2021/12/SM2.jpeg)
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)