#! /usr/bin/env ruby # MarshalDumper written by Shin'ya Adzumi # version: 0.01 if VERSION < "1.6.2" then # Marshal Version < "4.5" STDERR.puts "warning: #{__FILE__} is supported ruby version 1.6.2 or later" end module MarshalDumper module_function def dump(obj,*arg) if arg[0].kind_of? IO then output = arg.shift end arg.each do |a| unless a.kind_of? Integer then raise TypeError,"instance of IO or Integer needed" end end @hash = Hash::new unless defined? @hash id = Thread::current.id @hash[id] = [[],[]] str = MarshalDumpControl::dump(obj,*arg) @hash.delete id if output then output.print str end str end def index(obj) id = Thread::current.id if defined? @hash and @hash.key? id then if obj.kind_of? Symbol @hash[id][0].index(obj) else @hash[id][1].index(obj) end else raise RuntimeError, "cannot call this method directly." end end def set(type) id = Thread::current.id if defined? @hash and @hash.key? id then if type.kind_of? Symbol @hash[id][0] << type else @hash[id][1] << type end else raise RuntimeError, "cannot call this method directly." end end class MarshalDumpControl if VERSION < "1.7" then MARSHAL_MAJOR = Marshal::dump(nil)[0] MARSHAL_MINOR = Marshal::dump(nil)[1] else MARSHAL_MAJOR = Marshal::MAJOR_VERSION MARSHAL_MINOR = Marshal::MINOR_VERSION end TYPE_NIL = ?0 TYPE_TRUE = ?T TYPE_FALSE = ?F TYPE_FIXNUM = ?i TYPE_UCLASS = ?C TYPE_OBJECT = ?o TYPE_USERDEF = ?u TYPE_FLOAT = ?f TYPE_BIGNUM = ?l TYPE_STRING = ?" TYPE_REGEXP = ?/ TYPE_ARRAY = ?[ TYPE_HASH = ?{ TYPE_HASH_DEF = ?} TYPE_STRUCT = ?S TYPE_MODULE_OLD = ?M TYPE_CLASS = ?c TYPE_MODULE = ?m TYPE_SYMBOL = ?: TYPE_SYMLINK = ?; TYPE_IVAR = ?I TYPE_LINK = ?@ T_OBJECT = 0x02 T_CLASS = 0x03 T_MODULE = 0x05 T_FLOAT = 0x06 T_STRING = 0x07 T_REGEXP = 0x08 T_ARRAY = 0x09 T_FIXNUM = 0x0a T_HASH = 0x0b T_STRUCT = 0x0c T_BIGNUM = 0x0d T_FILE = 0x0e T_DATA = 0x22 T_MATCH = 0x23 T_SYMBOL = 0x24 BUILTIN_CLASS_TABLE = { Class.to_s => T_CLASS, Module.to_s => T_MODULE, Fixnum.to_s => T_FIXNUM, Bignum.to_s => T_BIGNUM, Float.to_s => T_FLOAT, Array.to_s => T_ARRAY, Hash.to_s => T_HASH, String.to_s => T_STRING, Symbol.to_s => T_SYMBOL, Regexp.to_s => T_REGEXP, MatchData.to_s => T_MATCH, IO.to_s => T_FILE, Struct.to_s => T_STRUCT, Continuation.to_s => T_DATA, Dir.to_s => T_DATA, Thread.to_s => T_DATA, Object.to_s => T_OBJECT } def initialize(major=MARSHAL_MAJOR,minor=MARSHAL_MINOR) @major = major @minor = minor @str = "" end def self.dump(obj,*arg) ver = [] if arg.size > 3 then raise ArgumentError,"wrong # of arguments (#{arg.size} for 3)" end if arg.size > 1 then ver = arg.slice!(-2,2) end dumper = self::new(*ver) dumper.dump(obj,*arg) end def dump(obj,limit=100) rb_w_byte(@major) rb_w_byte(@minor) rb_w_object(obj,limit) @str end private def rb_w_byte(num) @str += format('%c',num) end def rb_w_bytes(str) rb_w_long(str.length) @str += str end def rb_w_long(num) case num when 0 rb_w_byte(0) when 1..122 rb_w_byte(num+5) when -123..-1 rb_w_byte(num-5) else ary = [0] for i in 1..4 ary[i] = num & 0xff num = num >> 8 if num == 0 then ary[0] = i break elsif num == -1 then ary[0] = -i break end end ary.each do |a| rb_w_byte(a) end end end def rb_w_float(obj) rb_w_bytes(fomat("%.16g",obj)) end def rb_w_unique(obj) rb_w_symbol(obj.intern) end def rb_w_fixnum(obj) rb_w_byte(TYPE_FIXNUM) rb_w_long(obj) end def rb_w_symbol(obj) idx = MarshalDumper::index(obj) if idx.nil? then rb_w_byte(TYPE_SYMBOL) rb_w_bytes(obj.to_s) MarshalDumper::set(obj) else rb_w_byte(TYPE_SYMLINK) rb_w_bytes(idx) end end def rb_w_uclass(obj,klass) if obj.class != klass then rb_w_byte(TYPE_UCLASS) rb_w_unique(obj.class.name) end end def rb_w_object(obj,limit) unless limit > 0 then raise ArgumentError,"exceed depth limit" end case obj when nil,true,false rb_w_byte(eval("TYPE_#{obj.to_s.upcase}")) when Fixnum num = obj >> 31 if num == 0 or num == -1 then rb_w_fixnum(obj) else rb_w_object2(obj,limit) end when Symbol rb_w_symbol(obj) return true else rb_w_object2(obj,limit) end end def rb_w_object2(obj,limit) limit -= 1 idx = MarshalDumper::index(obj) unless idx.nil? then rb_w_byte(TYPE_LINK) rb_w_long(idx) return true end MarshalDumper::set(obj) if obj.respond_to? :_dump then rb_w_byte(TYPE_USERDEF) rb_w_unique(obj.class.to_s) str = obj._dump if str.type != String then raise TypeErrir,"_dump() must return String" end rb_w_bytes(str) return true end #=begin if obj.respond_to? :_pre_format then obj = obj._pre_format end #=end # TYPE_IVAR? case builtin(obj) when T_CLASS if obj.to_s[0] == ?\# then raise ArgumentError,"can't dump anonymous class #{obj.to_s}" end rb_w_byte(TYPE_CLASS) rb_w_bytes(obj.to_s) when T_MODULE if obj.to_s[0] == ?\# then raise ArgumentError,"can't dump anonymous module #{obj.to_s}" end rb_w_byte(TYPE_CLASS) rb_w_bytes(obj.to_s) when T_FLOAT rb_w_byte(TYPE_FLOAT) rb_w_float(obj) when T_BIGNUM,T_FIXNUM rb_w_byte(TYPE_BIGNUM) rb_w_byte((obj<0)??-:?+) num = obj.abs rb_w_long((format("%x",num).length+3)/4) while num>0 rb_w_byte(num&0xff) num = num >> 8 rb_w_byte(num&0xff) num = num >> 8 end when T_STRING rb_w_uclass(obj,String) rb_w_byte(TYPE_STRING) when T_REGEXP rb_w_uclass(obj,Regexp) rb_w_byte(TYPE_REGEXP) rb_w_bytes(obj.source) rb_w_byte(rb_reqexp_option(obj)) when T_ARRAY rb_w_uclass(obj,Array) rb_w_byte(TYPE_ARRAY) rb_w_long(obj.size) obj.each do |o| rb_w_object(o,limit) end when T_HASH rb_w_uclass(obj,Array) unless obj.default.nil? then rb_w_byte(TYPE_HASH_DEF) else rb_w_byte(TYPE_HASH) end rb_w_long(obj.size) obj.each do |k,v| rb_w_object(k,limit) rb_w_object(v,limit) end unless obj.default.nil? then rb_w_object(obj.default,limit) end when T_STRUCT rb_w_byte(TYPE_STRUCT) rb_w_unique(obj.class.to_s) rb_w_long(obj.size) members = obj.members values = obj.values while !members.empty? rb_w_symbol(members.shift) rb_w_object(values.shift,limit) end when T_OBJECT rb_w_byte(TYPE_OBJECT) # FL_SINGLETON? rb_w_unique(obj.class.to_s) ivars = obj.instance_variables if obj.kind_of? Range then rb_w_long(ivars.size+3) [[:excl,:exclude_end?],[:end,:end],[:begin,:begin]].each do |i| rb_w_symble(i[0]) rb_w_object(obj.__send__(i[1])) end else rb_w_long(ivars.size) end ivars.each do |i| rb_w_symbol(i) rb_w_object(obj.instance_eval{eval(i)},limit) end else raise TypeError,"can't dump #{obj.class.to_s}" end end if VERSION < "1.7" then def rb_regexp_option(obj) optdig = 0 if obj.inspect =~ /\/([mixpnesu]*)$/ then opt = $1 opt.each_byte do |b| case b when 'm' optdig |= Regexp::MULTILINE when 'i' optdig |= Regexp::IGNORECASE when 'x' optdig |= Regexp::EXTENDED when 'p' optdig |= 0x08 when 'n' optdig |= 0x10 when 'e' optdig |= 0x20 when 's' optdig |= 0x30 when 'u' optdig |= 0x40 end end end optdig end else def rb_regexp_option(obj) obj.option end end def builtin(obj) obj.class.ancestors.each do |c| return BUILTIN_CLASS_TABLE[c.to_s] if BUILTIN_CLASS_TABLE.key? c.to_s end return T_OBJECT end end end if __FILE__ == $0 obj = 0x1234567890 p MarshalDumper::dump(obj) p Marshal::dump(obj) end