Pushdown::

Automaton

module
Extended With
Loggability

A mixin that adds pushdown-automaton functionality to another module/class.

Public Class Methods

anchor
extended( object )

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
anchor
generate_event_method( name, object )

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|
                if ( 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 )
                else
                        return nil
                end
        end
end
anchor
generate_initial_state_method( name )

Generate the method that returns the initial state class for a pushdown state named name.

# File lib/pushdown/automaton.rb, line 177
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
anchor
generate_shadow_update_method( name, object )

Generate the timed update method for every pushdown state named name on the specified object.

# File lib/pushdown/automaton.rb, line 156
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|
                if ( stack = stack_method.bind(self).call )
                        stack.each do |state|
                                state.shadow_update( *args )
                        end
                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
anchor
generate_state_method( name, object )

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
anchor
generate_update_method( name, object )

Generate the timed update method for the active pushdown state named name on the specified object.

# File lib/pushdown/automaton.rb, line 137
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|
                if ( stack = stack_method.bind(self).call )
                        current_state = stack.last

                        result = current_state.update( *args )
                        return self.handle_pushdown_result( stack, result, name )
                else
                        return nil
                end
        end
end
anchor
install_state_methods( name, object )

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

anchor
pushdown_inferred_state_class( class_name )

Return the state class with the name inferred from the given class_name.

# File lib/pushdown/automaton.rb, line 216
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 )
rescue NameError => err
        raise Pushdown::DeclarationError,
                "failed to look up a state class for %p: %s" % [ class_name, err.message ]
end
anchor
pushdown_pluggable_state_class( state_base_class, class_name )

Derive a state class object named class_name via the (pluggable) state_base_class.

# File lib/pushdown/automaton.rb, line 231
def pushdown_pluggable_state_class( state_base_class, class_name )
        return state_base_class.get_subclass( class_name )
end
anchor
pushdown_state( name, initial_state: :initial, states: nil )

Declare a attribute name which is a pushdown state.

# File lib/pushdown/automaton.rb, line 188
def pushdown_state( name, initial_state: :initial, states: nil )
        @pushdown_states[ name ] = { initial_state: initial_state, states: states }

        Pushdown::Automaton.install_state_methods( name, self )
end
anchor
pushdown_state_class( state_name, class_name )

Return the Class object for the class named class_name of the pushdown state state_name.

# File lib/pushdown/automaton.rb, line 197
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