From: Florian Gross Date: 2004-12-03T04:02:43+09:00 Subject: Re: any way to manipulate variables and bindings? --------------060202070907020201090607 Content-Type: text/plain; charset=us-ascii; format=flowed Content-Transfer-Encoding: 7bit itsme213 wrote: > def foo > bar [:a, :b, :c] > a > b > c > end > > I would like a, b, and c to be local variables with values 1, 2, 3 > (positions of :a, :b, :c). Is there some implementation of > def bar (symbol_list) > symbol_list.each_with_index { |s, i| > ??? > } > end > that will do this? You need the binding of the caller. Then you can just do eval("#{symbol} = value", context) where context is the binding. I have attached binding_of_caller.rb -- it provides Binding.of_caller which yields the binding of the caller. See the documentation, it has to be used in a relatively odd way: def inc(name) Binding.of_caller do |context| eval("#{name} += 1") end end x = 0; inc(:x); x # => 1 I have also attached variable.rb -- this provides variable boxing via Variable[:foo] which creates an object for that variable. You can do var.value = 5 and so on. Note that this does not yet work with instance and class variables. It can be used like this: def inc(variable) variable.value += 1 end x = 0; inc(Variable[:x]); x # => 1 It would also be interesting to know what you are using this for. --------------060202070907020201090607 Content-Type: text/plain; name="binding_of_caller.rb" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="binding_of_caller.rb" begin require 'simplecc' rescue LoadError def Continuation.create(*args, &block) cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?} result ||= args return *[cc, *result] end end # This method returns the binding of the method that called your # method. It will raise an Exception when you're not inside a method. # # It's used like this: # def inc_counter(amount = 1) # Binding.of_caller do |binding| # # Create a lambda that will increase the variable 'counter' # # in the caller of this method when called. # inc = eval("lambda { |arg| counter += arg }", binding) # # We can refer to amount from inside this block safely. # inc.call(amount) # end # # No other statements can go here. Put them inside the block. # end # counter = 0 # 2.times { inc_counter } # counter # => 2 # # Binding.of_caller must be the last statement in the method. # This means that you will have to put everything you want to # do after the call to Binding.of_caller into the block of it. # This should be no problem however, because Ruby has closures. # If you don't do this an Exception will be raised. Because of # the way that Binding.of_caller is implemented it has to be # done this way. def Binding.of_caller(&block) old_critical = Thread.critical Thread.critical = true count = 0 cc, result, error, extra_data = Continuation.create(nil, nil) error.call if error tracer = lambda do |*args| type, filename, context, extra_data = args[0], args[1], args[4], args if type == "return" count += 1 # First this method and then calling one will return -- # the trace event of the second event gets the context # of the method which called the method that called this # method. if count == 2 # It would be nice if we could restore the trace_func # that was set before we swapped in our own one, but # this is impossible without overloading set_trace_func # in current Ruby. set_trace_func(nil) cc.call(eval("binding", context), nil, extra_data) end elsif type != "line" set_trace_func(nil) error_msg = "Binding.of_caller used in non-method context or " + "trailing statements of method using it aren't in the block." cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil) end end unless result set_trace_func(tracer) return nil else Thread.critical = old_critical case block.arity when 1 then yield(result) else yield(result, extra_data) end end end --------------060202070907020201090607 Content-Type: text/plain; name="variable.rb" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="variable.rb" require 'binding_of_caller' class Variable def self.new(name, context = nil) return super(name, context) unless context.nil? Binding.of_caller do |context| super(name, context) end end class << self; alias :[] :new; end attr_reader :context, :name def initialize(name, context) unless /^[a-z_][a-zA-Z0-9_]*$/.match(name.to_s) raise(NameError, "Illegal variable name: #{name.inspect}") end @name, @context = name, context @setter = lambda do |value| eval "#{@name} = ObjectSpace._id2ref(#{value.id})", context end exists = lambda do eval "local_variables.include?(#{@name.to_s.inspect})", context end @getter = lambda do eval("#{@name}", context) if exists.call end end def []=(value); @setter.call(value); end def []; @getter.call; end alias :value= :[]= alias :value :[] def inspect "Variable[#{@name.inspect}, #{@context.inspect}]" end end --------------060202070907020201090607--