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
- 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_NAMEdefines 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
DEFINITIONSfor all states.
[> 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.
- A property
state= : Int32is automatically added and a
set_state(new_state:Int32)setter method is automatically generated. It signals
statestill set to the old value, sets the new
state, and then calls
- The first
> STATEdefined is automatically the start state.
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
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 ^ ^ ^ ^
- 2021-12-14 - Original
- 2022-05-13 - Updated to reflect latest syntax (v1.13/v2.0)