[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Invoking a Class block on an Instance

Gavin Kistner

5/26/2005 6:36:00 AM

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 )
'<root> <foo /> </root>'.each_byte{ |c|
break unless parser.transition( c.chr )
}






--
(-, /\ \/ / /\/

2 Answers

Pit Capitain

5/26/2005 8:58:00 AM

0

Gavin Kistner schrieb:
> ...
> 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).

Hi Gavin,

executing a code block in the context of an object is normally done via
instance_eval, but I, too, couldn't find a way to pass parameters to the
block.

One workaround is to create methods for the states. If you change the
following methods of FiniteStateMachine, it should work:

class FiniteStateMachine

...

def self.add_state( start_state, &block )
name = "__state_#{start_state}"
states[ start_state ] = name
define_method( name, &block )
end

...

def transition( trigger )
@state = send( self.class.states[ @state ], trigger )
end

end

Regards,
Pit


Gavin Kistner

5/26/2005 2:01:00 PM

0

On May 26, 2005, at 2:57 AM, Pit Capitain wrote:
> Gavin Kistner schrieb:
>> 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).
>
> executing a code block in the context of an object is normally done
> via instance_eval, but I, too, couldn't find a way to pass
> parameters to the block.
>
> One workaround is to create methods for the states. If you change
> the following methods of FiniteStateMachine, it should work:

Thanks for this; it solved my current problem.

I would still like to know how (if it's possible) I can do the Ruby
equivalent of this JS code:

var addSome = function( addend ){ return this.val + addend }

var obj1 = new Object;
obj1.val = 10;

var obj2 = new Object;
obj2.val = 100;

var sum1 = addSome.call( obj1, 10 ); //20
var sum2 = addSome.call( obj2, 10 ); //110

Is this the problem people are talking about when they say that Ruby
doesn't have "first class" functions/methods?

--
(-, /\ \/ / /\/