Module: Pushdown::Automaton

Extended by:
Loggability
Defined in:
lib/pushdown/automaton.rb

Overview

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

Defined Under Namespace

Modules: InstanceMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(object) ⇒ Object

Extension callback – add some stuff to extending objects.



18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/pushdown/automaton.rb', line 18

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_event_method(name, object) ⇒ Object

Generate the external event handler method for the pushdown state named name on the specified object.



119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/pushdown/automaton.rb', line 119

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_initial_state_method(name) ⇒ Object

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



171
172
173
174
175
176
177
178
# File 'lib/pushdown/automaton.rb', line 171

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_shadow_update_method(name, object) ⇒ Object

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



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/pushdown/automaton.rb', line 151

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_state_method(name, object) ⇒ Object

Generate the method used to access the current state object.



108
109
110
111
112
113
114
# File 'lib/pushdown/automaton.rb', line 108

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_update_method(name, object) ⇒ Object

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



135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/pushdown/automaton.rb', line 135

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

.install_state_methods(name, object) ⇒ Object

Generate the pushdown API methods for the pushdown automaton with the given name and install them in the extending object.



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/pushdown/automaton.rb', line 85

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

Instance Method Details

#pushdown_inferred_state_class(class_name) ⇒ Object

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



210
211
212
213
214
215
216
217
# File 'lib/pushdown/automaton.rb', line 210

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

#pushdown_pluggable_state_class(state_base_class, class_name) ⇒ Object

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



222
223
224
# File 'lib/pushdown/automaton.rb', line 222

def pushdown_pluggable_state_class( state_base_class, class_name )
  return state_base_class.get_subclass( class_name )
end

#pushdown_state(name, initial_state:, states: nil) ⇒ Object

Declare a attribute name which is a pushdown state.



182
183
184
185
186
# File 'lib/pushdown/automaton.rb', line 182

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

#pushdown_state_class(state_name, class_name) ⇒ Object

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



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/pushdown/automaton.rb', line 191

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