From: Gavin Kistner Date: 2005-05-26T15:36:20+09:00 Subject: Invoking a Class block on an Instance --Apple-Mail-1--804272010 Content-Transfer-Encoding: 7bit Content-Type: text/plain; charset=US-ASCII; delsp=yes; format=flowed For "fun" I started writing my own finite state machine system tonight. (Or rather, what my idea of one is.) I wanted to be able to subclass the FSM with specific classes that define the states; each state is a combination of a unique object, and a block that handles that state. Each time the state machine is fed a new piece of input, it yields that information to the block for the current state, and uses the return value as the next state. I need a way to aggregate data as I crawl through the states, so I thought I'd use instance variables and call some convenience instance methods on my FSM subclass. Unfortunately, I can't figure out how to get an instance of a class to invoke a block stored in its parent class, using the scope of the instance for the block. In JS this would be easy, but I don't yet grok how to have Ruby run a set of code on any arbitrary scope (while passing parameters to that code). Code follows. I've pared down some of the (limited) bounds checks for cleanliness, and removed most of the (undone) states. The problem occurs when the :tag_start state tries to call the (instance) "start_element" method, and complains that no such method exists on the XMLParser class. /Users/gavinkistner/Desktop/tmp.rb:61: undefined method `start_element' for XMLParser:Class (NoMethodError) class FiniteStateMachine def self.states @states ||= {} end def self.add_state( start_state, &block ) states[ start_state ] = block end def initialize( current_state=nil ) @state = current_state end def transition( trigger ) @state = self.class.states[ @state ].call( trigger ) end end class XMLParser < FiniteStateMachine WHITESPACE = /\s/ STARTCHAR = /[a-z_]/i NAMECHAR = /[a-z0-9_.-]/i def start_element( tag_name ) if @doc new_element = @doc.create_element( tag_name ) @current_parent.append_child( new_element ) @current_parent = new_element else @current_parent = MyXML::Document.new( tag_name ) end end def close_element @current_parent = @current_parent.parent_node end add_state( :pre ){ |char| @str = '' case char when WHITESPACE then :pre when '<' then :open end } add_state( :open ){ |char| case char when STARTCHAR @str << char :tag_start end } add_state( :tag_start ){ |char| case char when NAMECHAR @str << char :tag_start when WHITESPACE start_element( @str ) :attrs when '>' start_element( @str ) :chilluns when '/' start_element( @str ) :empty_tag end } end parser = XMLParser.new( :pre ) ' '.each_byte{ |c| break unless parser.transition( c.chr ) } -- (-, /\ \/ / /\/ --Apple-Mail-1--804272010--