class DFA
attr_accessor :states, :currentstate, :match
def initialize(states=[])
@states = states
@currentstate = states[0]
@match = []
end
def give input
# Can't match if state is nil
if @currentstate.nil? then puts "** [#{input}] Current state is nil"
else
# Call the specified entry action
@currentstate.onexit.call input
# Find the next state (match returns state name, so we use find)
@currentstate = @states.find { |s| s.name == @currentstate.match(input) }
# Call the specified exit action, as long as the state isn't nil
@currentstate.onentry.call input unless @currentstate.nil?
end
# If the input matched, record it
@match.push input unless @currentstate.nil?
@currentstate
end
# Call give on each member of a string
# See givea return values
def gives input
givea input.split ''
end
# Call give on each member of an array
# Returns false if input doesn't match
# Otherwise returns list of states the machine went through
def givea input
input.collect do |item|
st = give item
return false if st.nil?
st
end
end
def inspect
return "<DFA: [#{@states.collect { |s| s.name }.join ', '}]>"
end
# Will output Ruby to match this DFA
def render
puts "def match_dfa(input)"
end
end
class State
attr_accessor :name, :transitions, :onexit, :onentry, :final
def initialize(name, transitions={})
@name = name
@transitions = transitions
@onentry = proc {}
@onexit = proc {}
@final = false
end
def match input
ret = @transitions[input]
ret = @transitions[:dot] if ret.nil? and @transitions.keys.include? :dot
raise ":error state reached with '#{input}'" if ret == :error
return ret
end
def inspect
return "<State: #{@name} :: #{@transitions.inspect}>"
end
def transition(params)
@transitions.update params
end
def on_exit(&block)
@onexit = block
end
def on_enter(&block)
@onentry = block
end
alias :old_eq :==
def ==(other)
if other.class == String
@name == other
else
old_eq(other)
end
end
end
####
# Utility function to define States/Machines
####
def state(name, *args, &block)
s = State.new(name)
s.final = true if args.include? "final" or args.include? :final
s.instance_eval &block if block_given?
s
end
# Adds status messages as entry and exit actions
def verbose_state(name, transitions)
s = State.new(name, transitions)
s.on_exit { |c| puts "+ input[#{c}] >> enter state[#{@name}]" }
s.on_enter { |c| puts "- exit state[#{@name}] >> input[#{c}]" }
end
def automaton *args
states = []
until args.empty?
a, b = args.take 2
states.push State.new(a, b)
args = args.drop 2
end
return DFA.new(states)
end