Page RubyLanguage.


C pointers in Ruby

Ruby is fun. Here I implemented a C like "Pointer" object.

  • x = &y; <--> x = Pointer.new {:y}
  • v = *x; <--> v = x[]
  • *v = b; <--> v[] = x
  • ptr = &o.m <--> ptr = Pointer.new {"o.m"}
  • ptr = &tbl\[3\] <--> ptr = Pointer.new {"tbl\[3\]"}

Ruby promet le retour du "s'amuser en programmant". D'expérience je peux dire que ça marche. Evidément il faut qu'un jour passé vous vous soyez 'amusé à programmer', sinon il n'y a aucun espoir de 'retour', faut pas rêver.

Il y a quelques années j'avais, pour m'amuser donc, produit ma version du "pointeur" façon Ruby. Chacun se souvient de la définition de pointeur en C: "address of a left-expression", en gros.


Ruby code:

# A Pointer references a value or a lvalue.
# It has a content that can be changed.
# This is a very general purpose mechanism that add one level
# of indirection when accessing objects. Dereferencing must be
# done manually by the user. See also deref!() method that walks
# some objects to permanently dereference their pointers for you.
class Pointer

  # This is the block that gives the Ruby expression for the lvalue.
  # It is also the binding context where the lvalue lives.
  attr_reader :block
  
  # This is the lvalue expression. It is "self" for anonymous pointer.
  # The lvalue expression is computed once only, using the user provided
  # block (or a dummy block if the pointer is anonymous).
  attr_reader :lvalue
  
  # The Proc object that can get the value of the lvalue (Pointer's content)
  attr_reader :getter
  
  # The Proc object that can set the value of the lvalue (Pointer's content).
  attr_reader :setter

  # This is the value holder for anonymous pointers. It is useless when
  # the user provides a block at object creation time.
  attr :value
  
  # Creates a new Pointer. If provided, block parameter must
  # return a lvalue as a string, or anything that can to_s().
  # If no block then the created Pointer is _anonymous_.
  # If the init parameter is provided, instead of a block then
  # an anonymous Pointer is created with that initial value.
  def initialize( init = nil, &block )
    ref( init, &block)
  end
  
  # Reinitializes a Pointer.
  # Sets the lvalue we are referencing/pointing to.
  # If none, then this is an anonymous pointer that holds the value itself.
  # init parameter is the initial value of the anonymous pointer, unless it is
  # a Pointer. When init parameter is a Pointer, This Pointer is made a copy
  # of the init Pointer.
  # Returns self.
  def ref( init = nil, &lvalue_block )
    # Regular pointer, using a lvalue
    if lvalue_block then
      @block  = lvalue_block
      # Compute the lvalueExpr string/symbol, once only
      @lvalue = lvalue_block.call()
      # Compile getter & setter
      @getter = eval( "proc { #{@lvalue} }",         block)
      @setter = eval( "proc { |v| #{@lvalue} = v }", block)
      # ToDo: Avoid evil eval, but how ?
    # Copy constructor, called by initialize_copy()
    elsif init.kind_of? self.class() then
      @block  = init.binding()
      @lvalue = init.lvalue()
      @getter = init.getter()
      @setter = init.getter()
      @value  = init[] if init.anonymous?
    # Anonymous pointer
    else
      @lvalue = "self"
      @getter = proc { @value }
      @setter = proc { |v| @value = v }
      # For anonymous pointers, @ptrBlock == @ptrGetter, see anonymous?()
      @block  = @getter
      @value  = init
    end
    self
  end
  
  # Magical deref!(). Change This object into its content.
  # Whatever used to point on This object will now point
  # on (a clone of) the content object. Also, whatever used to
  # point on the content object, nows points on a clone of This Pointer.
  # ToDo: Test that using evil.rubyforge.net
  # Here I use swap(), which is not part of Ruby (yet). One
  # version is available at http://evil.rubyforge.net
  # Up
  def deref!
    # Deal with potential instance variables in subclasses
    super
    # Self becomes the original content, weird isn't it ?
    content = @getter.call()
    swap( content)
    # Cloned LogicPointer will point on This
    content[] = self
  rescue NoMethodError
    # Humm... if I can't swap()... then I don't
    super
  end
  
  # New 1.8 initializer called by dup() and clone()
  def initialize_copy proto
    ref proto
  end
  
  # Tells if This Pointer is anonymous. An anonymous pointer stores its
  # content. Non anomymous pointer's content is usually stored in some
  # lvalue.
  def anonymous?
    # See code in ref() where @ptrBlock is assigned
    @block == @getter
  end
  
  # Returns the content of the pointer.
  def []
    @getter.call()
  end
  
  # Assigns a new value to the content of This Pointer.
  def []= new_value
    @setter.call( new_value)
  end
  
  # Returns the Binding context where the lvalue lives.
  # For anonymous pointers, returns a Binding where "self" is the
  # anonymous pointer itself.
  def binding()
    eval( "binding()", @block)
  end
  
  # Reset the lvalue of This Pointer so that it now points
  # to something new. The block parameter should return the
  # lvalue as a string or as a symbol or anything that can to_s().
  # Returns self.
  # Note: eval( ptr.lvalue(), ptr.binding()) returns the content of ptr.
  # This is much less efficient then a plain ptr[] of course.
  def lvalue= &block
    ref( nil, &block)
  end
  
  # Returns the lvalue expression as a string together with the Pointer
  # content's inspect().
  def inspect()
    "<#{self.class()} #{lvalue()}>#{self[].inspect()}"
  end

end

PAGE PRECEDENTE

PAGE PRECEDENTE

PAGE PRECEDENTE

PAGE PRECEDENTE

PAGE PRECEDENTE

PAGE PRECEDENTE

Wiki : 2007-11-16 08h59 par JeanHuguesRobert | autres changements
visites. ©2006-2008 Virteal
Feedback Form