Code generation and state machines

Hi folks,

I’m starting a project for fun in Ruby, partly to help learn the language.
One of the pieces it needs is a state machine.

I liked the code generation for tables and rows described in
http://www.codegeneration.net/tiki-read_article.php?articleId=9 so I thought
I’d take a stab at getting it to work on a simple level. (By the way, don’t
ever write an article where you show the syntax of a really neat trick but
leave the implementation out because it’s “too complicated”. That’s just
taunting!)

Here’s my attempt. It actually works, but I wanted to get the list’s
feedback.

Mostly I just want to know whether there are cleaner ways to do any parts of
it, or if there is a more Ruby way to do any of it. Any criticism is
welcome.

The thing I’m least happy with is that I needed to create accessors for the @@
class variables because I couldn’t figure out how to reference the child
class’s class variables from the run() method defined in the parent class.
Is there a neat way to do it?

Thanks,

Zellyn

···

Parent class of state machines. Defines the functions that allow

simple syntax in child classes

class StateMachine

Create a new state - simply add the given block to the states hash

under the state’s name

def StateMachine.state(name, &action)
module_eval <<-"end_eval"
puts “DEBUG: Defining state ‘#{name}’”
@@states || @@states = {}
@@states[name] = action
end_eval
end

Create a new transition. Each start state’s entry in the

transitions hash is an array of pairs. Each pair contains

and end state and a condition block

def StateMachine.transition (startState, endState, &condition)
puts "DEBUG: Defining transition from ‘#{startState}’ to ‘#{endState}’"
module_eval <<-"end_eval"
ary = @@transitions[startState] || []
ary.push([endState,condition])
@@transitions[startState] = ary
end_eval
end

set the start state

def StateMachine.startstate(name)
module_eval <<-“end_eval”
@@startState = name
end_eval
end

set the end state

def StateMachine.endstate(name)
module_eval <<-“end_eval”
@@endState = name
end_eval
end

Actually run the state machine.

- Start in the start state.

- Evaluate each state’s block on entering the state

- Try each transition for the start state. When a condition

evaluates to true, enter the corresponding target state.

- Quit when you reach the end state

def run
currentState = startState()

while (currentState != endState()) do
  states()[currentState].call()
  ary = transitions()[currentState]
  ary.each do |(target,condition)|
    if condition.call()
      currentState = target
      next
    end
  end
end

# and execute the final state's action
states()[currentState].call()

end

Trap child classes inheriting from this class, and add the

necessary class variables and their accessors.

def StateMachine.inherited(subclass)
subclass.module_eval <<-“end_eval”
@@states = {}
@@transitions = {}
@@startState = nil
@@endState = nil

  def states
    @@states
  end

  def transitions
    @@transitions
  end

  def startState
    @@startState
  end

  def endState
    @@endState
  end
end_eval

end
end

First simple state machine:

Start -> Second -> End

class SimpleState1 < StateMachine

state(“Start”) { puts “Start State (1)” }
state(“Second”) { puts “Second State (1)” }
state(“End”) { puts “End State (1)” }

startstate(“Start”)
transition(“Start”, “Second”) { 1 }
transition(“Second”,“End”) { 1 }
endstate(“End”)

end
require ‘StateMachine’

Second simple state machine

Start -> Second -> Third -> End

(with never-taken transition from Second -> End)

class SimpleState2 < StateMachine

state(“Start”) { puts “Start State (2)” }
state(“Second”) { puts “Second State (2)” }
state(“Third”) { puts “Third State (2)” }
state(“End”) { puts “End State (2)” }

startstate(“Start”)
transition(“Start”, “Second”) { 1 }
transition(“Second”,“End”) { 0 }
transition(“Second”,“Third”) { 1 }
transition(“Third”,“End”) { 1 }
endstate(“End”)

end

Test it all out. Make two state machines with similar state names so

that we’re sure we’re actually defining the states and transitions

in the right place - overlaps/clashes will show up clearly.

machine1 = SimpleState1.new()
machine2 = SimpleState2.new()

puts()
puts "Running State Machine 1:"
machine1.run

puts()
puts "Running State Machine 2:"
machine2.run

puts()
puts "Running State Machine 1 again:"
machine1.run

Output:

DEBUG: Defining state ‘Start’

DEBUG: Defining state ‘Second’

DEBUG: Defining state ‘End’

DEBUG: Defining transition from ‘Start’ to ‘Second’

DEBUG: Defining transition from ‘Second’ to ‘End’

DEBUG: Defining state ‘Start’

DEBUG: Defining state ‘Second’

DEBUG: Defining state ‘Third’

DEBUG: Defining state ‘End’

DEBUG: Defining transition from ‘Start’ to ‘Second’

DEBUG: Defining transition from ‘Second’ to ‘End’

DEBUG: Defining transition from ‘Second’ to ‘Third’

DEBUG: Defining transition from ‘Third’ to ‘End’

Running State Machine 1:

Start State (1)

Second State (1)

End State (1)

Running State Machine 2:

Start State (2)

Second State (2)

Third State (2)

End State (2)

Running State Machine 1 again:

Start State (1)

Second State (1)

End State (1)

I’d store the state info in an instance variable of the class instance and
not use a class variable, because the latter is inherited and thus visible
to sub classes.

I also wonder why your state machine runs automatically in method run.
Normally you trigger every transition externally with an event. That way
you could still do a looping with little additional effort but you had a
lot more flexibility. I’d possibly change it in a way that the current
event is passed to the condition block as an argument.

Also, is it possible that you use 0 to mean “false”? This is true for Perl
but not Ruby. In Ruby the only two values that are equivalent to false
are false itself and nil.

Regards

robert

I’d store the state info in an instance variable of the class instance and
not use a class variable, because the latter is inherited and thus visible
to sub classes.

Okay - I understand that. I take it class instance variables are not
inherited, then?

I also wonder why your state machine runs automatically in method run.

Yeah. The run method is as simple as possible to demonstrate the principles -
at the moment I’m worried about classes, superclasses, instances, and syntax
extension (the “state” and “transition” keyword-looking functions used to
easily define state machines). The run method will definitely change,
probably in the direction you’ve mentioned. I like the event idea - I’ll
have to think about it a little more.

Also, is it possible that you use 0 to mean “false”? This is true for Perl
but not Ruby. In Ruby the only two values that are equivalent to false
are false itself and nil.

Aah. Yes, oops! It’s going to be difficult getting used to that one!

Thanks for your help!

Zellyn

···

On Friday 20 June 2003 06:34, Robert Klemme wrote:

“Zellyn Hunter” ruby-list@zellyn.com schrieb im Newsbeitrag
news:200306200848.02798.ruby-list@zellyn.com

I’d store the state info in an instance variable of the class instance
and
not use a class variable, because the latter is inherited and thus
visible
to sub classes.

Okay - I understand that. I take it class instance variables are not
inherited, then?

Exactly.

I also wonder why your state machine runs automatically in method run.

Yeah. The run method is as simple as possible to demonstrate the
principles -
at the moment I’m worried about classes, superclasses, instances, and
syntax
extension (the “state” and “transition” keyword-looking functions used
to
easily define state machines).

Ah, I see.

The run method will definitely change,
probably in the direction you’ve mentioned. I like the event idea -
I’ll
have to think about it a little more.

It’s not my idea but it’s the typical way state machines are implemented.
I “guess” this has a lot to do with the tasks state machines are built
for, i.e., parsing etc. :slight_smile:

One more point: I’d make endState a collection (hash or array) because
typically there is more than one terminating state. You then could add a
method that checks for termination. I attach an example of a SM that
accepts the language a+b+.

Also, is it possible that you use 0 to mean “false”? This is true for
Perl
but not Ruby. In Ruby the only two values that are equivalent to
false
are false itself and nil.

Aah. Yes, oops! It’s going to be difficult getting used to that one!

Yeah. Took me a while in the beginning, too.

Thanks for your help!

You’re welcome!

Regards

robert

StateMachine.rb (1.35 KB)

StateTest.rb (640 Bytes)

···

On Friday 20 June 2003 06:34, Robert Klemme wrote: