vadviktor
2/23/2014 - 4:11 PM

_test.rb

# A simplified version of Ruby's object system, implemented in Ruby. This only
# supports a subset of Ruby, but is intended to accurately model inheritance
# and method lookup in as little code as possible, for ease of understanding by
# Ruby programmers.
# 
# Classes implemented:
# 
#     * Object
#     * Module < Object
#     * Class < Module
#     * Kernel, a module included in all Classes
#     * UnboundMethod
#     * Method
# 
# This model implements the main features of Ruby's inheritance system:
# 
#     * Instance method inheritance with #include
#     * Instance method inheritance with #prepend
#     * Instance and singleton method inheritance through subclassing
#     * Metaclasses, singleton methods and #extend
# 
# It does diverge from Ruby on some edge cases. This has the effect of keeping
# the implementation simple while fixing what I consider to be bugs in the
# language ;)
# 
#     * The double-inclusion problem does not exist in this implementation
#     * Class does not violate Liskov -- classes can be passed to #include
#     * The superclass of a Class can be a Module
#     * Metaclasses can be instantiated and subclassed
# 
# Basically, a Class is just a Module that can be instantiated and that will
# inherit the singleton methods of its superclass.

module Rooby
  class Object
    RESERVED = [:instance_eval, :instance_exec, :==]
    (instance_methods - RESERVED).each { |m| undef_method m }
    
    attr_reader :class, :object_id
    
    def initialize(klass = nil)
      @class = klass
      @object_id = rand(2**32)
    end
    
    def metaclass
      @meta ||= Class.new(self, self.class)
    end
    
    def __send__(method_name, *args)
      if method = metaclass.instance_method(method_name)
        method.bind(self).call(*args)
      elsif method = metaclass.instance_method(:method_missing)
        method.bind(self).call(method_name, *args)
      else
        raise NoMethodError, "Undefined method `#{method_name}' for #{self}"
      end
    end
    alias :method_missing :__send__
  end
  
  class Module < Object
    def initialize(name = nil, &block)
      @name     = name
      @methods  = {}
      @includes = []
      @prepends = []
      instance_eval(&block) if block_given?
    end
    
    def ===(object)
      object.metaclass.ancestors.include?(self)
    end
    
    def ancestors(list = [])
      @includes.each { |m| m.ancestors(list) }
      list.unshift(self) unless list.include?(self)
      @prepends.each { |m| m.ancestors(list) }
      list
    end
    
    def define(method_name, &block)
      name = method_name.to_sym
      @methods[name] = UnboundMethod.new(self, name, block)
    end
    
    def include(mixin)
      @includes << mixin
    end
    
    def prepend(mixin)
      @prepends << mixin
    end
    
    def instance_method(method_name, include_ancestors = true)
      if include_ancestors
        ancestor = ancestors.find { |a| a.instance_methods(false).include?(method_name) }
        ancestor && ancestor.instance_method(method_name, false)
      else
        @methods[method_name]
      end
    end
    
    def instance_methods(include_ancestors = true, list = [])
      if include_ancestors
        @includes.each { |m| m.instance_methods(true, list) }
        @prepends.each { |m| m.instance_methods(true, list) }
      end
      @methods.each_key { |m| list << m unless list.include?(m) }
      list
    end
    
    def name
      case @name
      when String then @name
      else "<#{Class === self ? 'Class' : 'Module'}:#{@name}>"
      end
    end
    alias :inspect :name
  end
  
  class UnboundMethod < Object
    attr_reader :name
    
    def initialize(_module, name, block)
      @module = _module
      @name   = name
      @block  = block
    end
    
    def bind(receiver)
      Method.new(self, receiver, @block)
    end
    
    def inspect
      "<UnboundMethod: #{@module}##{name}>"
    end
  end
  
  class Method < Object
    def initialize(unbound, receiver, block)
      @unbound  = unbound
      @receiver = receiver
      @block    = block
    end
    
    def call(*args)
      @receiver.instance_exec(*args, &@block)
    end
    
    def inspect
      "<Method: #{@receiver}##{@unbound.name}>"
    end
  end
  
  Kernel = Module.new 'Kernel' do
    define :extend do |mixin|
      metaclass.include(mixin)
    end
    
    define :inspect do
      "<#{self.class.name}:#{object_id.to_s 16}>"
    end
    
    define :is_a? do |type|
      type === self
    end
    
    define :method do |method_name|
      metaclass.instance_method(method_name).bind(self)
    end
    
    define :methods do
      metaclass.instance_methods
    end
    
    define :respond_to? do |method_name|
      metaclass.instance_method(method_name) ? true : false
    end
    
    define :to_s do
      inspect
    end
  end
  
  class Class < Module
    attr_reader :superclass
    
    def initialize(name = nil, superclass = nil, &block)
      super(name, &block)
      @superclass = superclass
      if superclass
        @includes.unshift(superclass)
        metaclass.include(superclass.metaclass)
      end
      @includes.unshift(Kernel)
    end
    
    def new(*args)
      object = Object.new(self)
      object.__send__(:initialize, *args) if object.respond_to?(:initialize)
      object
    end
  end
end
$VERBOSE = nil
require File.expand_path('../rooby', __FILE__)


Person = Rooby::Class.new 'Person' do
  define :initialize do |name|
    @name = name
  end
  
  define :name do
    @name
  end
end

p Person.new('Alice').name      # "Alice"
p Person.new('Bob').name        # "Bob"


JSON = Rooby::Module.new 'JSON' do
  define :to_json do
    '{"hello": "world"}'
  end
end

Sum = Rooby::Module.new 'Sum' do
  define :wat do |a, b|
    a + b
  end
end

Product = Rooby::Module.new 'Product' do
  define :wat do |a, b|
    a * b
  end
end

Log = Rooby::Module.new 'Log' do
  define :wat do |a, b|
    Math.log(a) / Math.log(b)
  end
end

Foo = Rooby::Class.new 'Foo' do
  include JSON
  include Sum
  
  metaclass.define :a_singleton_method do
    [name, :this_is_cool]
  end
  
  define :hello do
    :hello_there
  end
  
  define :wat do |a, b|
    a ** b
  end
  
  define :method_missing do |sym, *args|
    [:missing, sym, args]
  end
end

Bar = Rooby::Class.new 'Bar', Foo


p Foo.ancestors                 # [Foo, Sum, JSON, Kernel]
p Foo.instance_methods.sort     # [:extend, :hello, :inspect, :is_a?, :method, :method_missing, :methods,
                                #     :respond_to?, :to_json, :to_s, :wat]

p Foo.a_singleton_method        # ["Foo", :this_is_cool]
p Bar.a_singleton_method        # ["Bar", :this_is_cool]

p Foo.new.is_a?(Foo)            # true
p Foo.new.is_a?(Bar)            # false
p Bar.new.is_a?(Foo)            # true
p Bar.new.is_a?(Bar)            # true
p Bar.new.is_a?(JSON)           # true

p Foo.new.method(:hello)        # <Method: <Foo:5af2d6dd>#hello>
p Foo.new.no_method(42)         # [:missing, :no_method, [42]]

p Foo.new.hello                 # :hello_there
p Foo.new.to_json               # "{\"hello\": \"world\"}"

p Foo.new.wat(3,4)              # 81
Foo.prepend(Product)
p Foo.ancestors                 # [Product, Foo, Sum, JSON, Kernel]
p Foo.new.wat(3,4)              # 12

o = Foo.new
p o.wat(3,4)                    # 12
o.extend(Log)
p o.wat(3,4)                    # 0.7924812503605781