色々行き詰まっているので、誰得な新機能を入れました。Rubyのしくみ読者用。
例えば、嘘偽りの無い ancestors が欲しい時はこうします。
require 'objspace'
def ObjectSpace.internal_ancestors_of klass
ancestors = []
while klass
ancestors << klass
klass = ObjectSpace.internal_super_of(klass)
end
ancestors
end
require 'pp'
pp String.ancestors
pp ObjectSpace.internal_ancestors_of(String)
結果。
[String, Comparable, Object, PP::ObjectMixin, Kernel, BasicObject] [String, #<InternalObject:0x7c671c T_ICLASS>, Object, #<InternalObject:0x282eb58 T_ICLASS>, #<InternalObject:0x7c7568 T_ICLASS>, BasicObject]
ほうら、わかりやすい。
クラス / モジュールの参照図を書いてみるのも分かりやすい。
require 'objspace'
require 'pp'
def ObjectSpace.object_id_of obj
if obj.kind_of?(ObjectSpace::InternalObjectWrapper)
obj.internal_object_id
else
obj.object_id
end
end
class Module
def references
h = {} # object_id -> [klass, class_of, super]
stack = [self]
while klass = stack.pop
obj_id = ObjectSpace.object_id_of(klass)
next if h.has_key?(obj_id)
cls = ObjectSpace.internal_class_of(klass)
sup = ObjectSpace.internal_super_of(klass)
stack << cls if cls
stack << sup if sup
h[obj_id] = [klass, cls, sup].map{|e| (defined?(e.name) && e.name) ? e.name : e.inspect}
end
h.values
end
end
pp String.references
[["String", "#<Class:String>", "#<InternalObject:0x806718 T_ICLASS>"], ["#<InternalObject:0x806718 T_ICLASS>", "Comparable", "Object"], ["Object", "#<Class:Object>", "#<InternalObject:0x271dbc4 T_ICLASS>"], ["#<InternalObject:0x271dbc4 T_ICLASS>", "PP::ObjectMixin", "#<InternalObject:0x807564 T_ICLASS>"], ["#<InternalObject:0x807564 T_ICLASS>", "Kernel", "BasicObject"], ["BasicObject", "#<Class:BasicObject>", "nil"], ["#<Class:BasicObject>", "#<Class:#<Class:BasicObject>>", "Class"], ["Class", "#<Class:Class>", "Module"], ["Module", "#<Class:Module>", "Object"], ["#<Class:Module>", "#<Class:#<Class:Module>>", "#<Class:Object>"], ["#<Class:Object>", "#<Class:#<Class:Object>>", "#<Class:BasicObject>"], ["#<Class:#<Class:Object>>", "#<Class:#<Class:Class>>", "#<Class:#<Class:BasicObject>>"], ["#<Class:#<Class:BasicObject>>", "#<Class:#<Class:Class>>", "#<Class:Class>"], ["#<Class:Class>", "#<Class:#<Class:Class>>", "#<Class:Module>"], ["#<Class:#<Class:Class>>", "#<Class:#<Class:Class>>", "#<Class:#<Class:Module>>"], ["#<Class:#<Class:Module>>", "#<Class:#<Class:Class>>", "#<Class:#<Class:Object>>"], ["Kernel", "#<Class:Kernel>", "nil"], ["#<Class:Kernel>", "Class", "Module"], ["PP::ObjectMixin", "Module", "nil"], ["Comparable", "Module", "nil"], ["#<Class:String>", "#<Class:Class>", "#<Class:Object>"]]
この配列の各要素は m, k, s の配列になっており、m のクラスは k、m の super は s という関係になっています。
で、これはグラフなので、graphviz に食わせるようにしてみると、
require 'objspace'
require 'pp'
def ObjectSpace.object_id_of obj
if obj.kind_of?(ObjectSpace::InternalObjectWrapper)
obj.internal_object_id
else
obj.object_id
end
end
class Module
def references
h = {} # object_id -> [klass, class_of, super]
stack = [self]
while klass = stack.pop
obj_id = ObjectSpace.object_id_of(klass)
next if h.has_key?(obj_id)
cls = ObjectSpace.internal_class_of(klass)
sup = ObjectSpace.internal_super_of(klass)
stack << cls if cls
stack << sup if sup
h[obj_id] = [klass, cls, sup].map{|e| (defined?(e.name) && e.name) ? e.name : e.inspect}
end
h.values
end
end
class C
end
rank_set = {}
puts "digraph mod_h {"
C.references.each{|(m, s, k)|
# next if /singleton/ =~ m
puts "#{m.dump} -> #{s.dump} [label=\"super\"];"
puts "#{m.dump} -> #{k.dump} [label=\"klass\"];"
unless rank = rank_set[m]
rank = rank_set[m] = 0
end
unless rank_set[s]
rank_set[s] = rank + 1
end
unless rank_set[k]
rank_set[k] = rank
end
}
rs = [] # [[mods...], ...]
rank_set.each{|m, r|
rs[r] = [] unless rs[r]
rs[r] << m
}
rs.each{|ms|
puts "{rank = same; #{ms.map{|m| m.dump}.join(", ")}};"
}
puts "}"
こんな絵が出ます。
http://www.atdot.net/fp_store/f.z1u2pn/file.g.png
ちょっと複雑なものを食わせてみると、
module M0; end module M1; end class C0; end class C1 < C0 include M0 prepend M1 end
http://www.atdot.net/fp_store/f.wfu2pn/file.g.png
凄いコトに。
方向を弄ってみたら、
http://www.atdot.net/fp_store/f.9nu2pn/file.g.png
ちょっと見やすくなったかな。
k と s の順番間違えてた。