module Pushdown::Automaton
A mixin that adds pushdown-automaton functionality to another module/class.
Public Class Methods
Extension callback – add some stuff to extending objects.
# File lib/pushdown/automaton.rb, line 17 def self::extended( object ) super unless object.respond_to?( :log ) object.extend( Loggability ) object.log_to( :pushdown ) end object.instance_variable_set( :@pushdown_states, {} ) object.singleton_class.attr_reader( :pushdown_states ) object.include( Pushdown::Automaton::InstanceMethods ) end
Generate the external event handler method for the pushdown state named name
on the specified object
.
# File lib/pushdown/automaton.rb, line 118 def self::generate_event_method( name, object ) self.log.debug "Generating event method for %p: handle_%s_event" % [ object, name ] stack_method = object.instance_method( "#{name}_stack" ) meth = lambda do |event, *args| stack = stack_method.bind( self ).call current_state = stack.last result = current_state.on_event( event, *args ) return self.handle_pushdown_result( stack, result, name ) end end
Generate the method that returns the initial state class for a pushdown state named name
.
# File lib/pushdown/automaton.rb, line 170 def self::generate_initial_state_method( name ) self.log.debug "Generating initial state method for %p" % [ name ] return lambda do config = self.pushdown_states[ name ] class_name = config[ :initial_state ] return self.pushdown_state_class( name, class_name ) end end
Generate the timed update method for every pushdown state named name
on the specified object
.
# File lib/pushdown/automaton.rb, line 150 def self::generate_shadow_update_method( name, object ) self.log.debug "Generating shadow update method for %p: shadow_update_%s" % [ object, name ] stack_method = object.instance_method( "#{name}_stack" ) meth = lambda do |*args| stack = stack_method.bind( self ).call stack.each do |state| state.shadow_update( *args ) end # :TODO: Calling/return convention? Could do something like #flat_map the # results? Or map to a hash keyed by state object? Is it useful enough to justify # the object churn of a method that might potentionally be in a hot loop? return nil end end
Generate the method used to access the current state object.
# File lib/pushdown/automaton.rb, line 107 def self::generate_state_method( name, object ) self.log.debug "Generating current state method for %p: %p" % [ object, name ] stack_method = object.instance_method( "#{name}_stack" ) meth = lambda { stack_method.bind(self).call&.last } return meth end
Generate the timed update method for the active pushdown state named name
on the specified object
.
# File lib/pushdown/automaton.rb, line 134 def self::generate_update_method( name, object ) self.log.debug "Generating update method for %p: update_%s" % [ object, name ] stack_method = object.instance_method( "#{name}_stack" ) meth = lambda do |*args| stack = stack_method.bind( self ).call current_state = stack.last result = current_state.update( *args ) return self.handle_pushdown_result( stack, result, name ) end end
Generate the pushdown API methods for the pushdown automaton with the given name
and install them in the extending object
.
# File lib/pushdown/automaton.rb, line 84 def self::install_state_methods( name, object ) self.log.debug "Installing pushdown methods for %p in %p" % [ name, object ] object.attr_reader( "#{name}_stack" ) # Relies on the above method having already been declared state_method = self.generate_state_method( name, object ) object.define_method( name, &state_method ) event_method = self.generate_event_method( name, object ) object.define_method( "handle_#{name}_event", &event_method ) update_method = self.generate_update_method( name, object ) object.define_method( "update_#{name}", &update_method ) update_method = self.generate_shadow_update_method( name, object ) object.define_method( "shadow_update_#{name}", &update_method ) initial_state_method = self.generate_initial_state_method( name ) object.define_singleton_method( "initial_#{name}", &initial_state_method ) end
Public Instance Methods
Return the state class with the name inferred from the given class_name
.
# File lib/pushdown/automaton.rb, line 209 def pushdown_inferred_state_class( class_name ) constant_name = class_name.to_s.capitalize.gsub( /_(\p{Alnum})/ ) do |match| match[ 1 ].capitalize end self.log.debug "Inferred state class for %p is: %s" % [ class_name, constant_name ] return self.const_get( constant_name ) end
Derive a state class object named class_name
via the (pluggable) state_base_class
.
# File lib/pushdown/automaton.rb, line 221 def pushdown_pluggable_state_class( state_base_class, class_name ) return state_base_class.get_subclass( class_name ) end
Declare a attribute name
which is a pushdown state.
# File lib/pushdown/automaton.rb, line 181 def pushdown_state( name, initial_state:, states: nil ) @pushdown_states[ name ] = { initial_state: initial_state, states: states } Pushdown::Automaton.install_state_methods( name, self ) end
Return the Class object for the class named class_name
of the pushdown state state_name
.
# File lib/pushdown/automaton.rb, line 190 def pushdown_state_class( state_name, class_name ) config = self.pushdown_states[ state_name ] or raise "No pushdown state named %p" % [ state_name ] states = config[ :states ] case states when NilClass return self.pushdown_inferred_state_class( class_name ) when Class return self.pushdown_pluggable_state_class( states, class_name ) when Hash return states[ class_name ] else raise "don't know how to derive a state class from %p (%p)" % [ states, states.class ] end end