-
Notifications
You must be signed in to change notification settings - Fork 0
/
amarshal.rb
369 lines (320 loc) · 8.65 KB
/
amarshal.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
=begin
= AMarshal
== Methods
--- AMarshal.dump(obj[, port])
--- AMarshal.load(port)
=end
module AMarshal
Next = :amarshal_try_next
def AMarshal.load(port)
port = port.read if port.kind_of? IO
eval port
end
def AMarshal.dump(obj, port='')
names = {}
def names.next_index
if defined? @next_index
@next_index += 1
else
@next_index = 1
end
@next_index - 1
end
name = dump_rec obj, port, names
port << "#{name}\n"
end
def AMarshal.dump_rec(obj, port, names)
id = obj.__id__
return names[id] if names.include? id
name = nil
init_proc = lambda {|init_method, *init_args|
dump_call(port, name, init_method,
init_args.map {|arg| dump_rec(arg, port, names)},
obj.private_methods(true).include?(init_method.to_s))
}
obj.am_nameinit(lambda {|name| names[id] = name}, init_proc) and
return name
next_index = names.next_index
port << "v = []\n" if next_index == 0
names[id] = name = "v[#{next_index}]"
obj.am_litinit(lambda {|lit| port << "#{name} = #{lit}\n"}, init_proc) and
return name
obj.am_allocinit(lambda {|alloc_receiver, alloc_method, *alloc_args|
receiver = dump_rec(alloc_receiver, port, names)
args = alloc_args.map {|arg| dump_rec(arg, port, names)}
port << "#{name} = "
dump_call(port, receiver, alloc_method, args,
alloc_receiver.private_methods(true).include?(alloc_method.to_s))
}, init_proc)
return name
end
def AMarshal.dump_call(port, receiver, method, args, private=false)
if private
port << "#{receiver}.__send__(:#{method}#{args.map {|arg| ", #{arg}"}})\n"
else
case method
when :[]=
port << "#{receiver}[#{args[0]}] = #{args[1]}\n"
when :<<
port << "#{receiver} << #{args[0]}\n"
else
if /\A([A-Za-z_][0-9A-Za-z_]*)=\z/ =~ method.to_s
port << "#{receiver}.#{$1} = #{args[0]}\n"
else
port << "#{receiver}.#{method}(#{args.map {|arg| arg.to_s}.join ","})\n"
end
end
end
end
end
[IO, Binding, Continuation, Data, Dir, File::Stat, MatchData, Method, Proc, Thread, ThreadGroup].each {|c|
c.class_eval {
def am_allocinit(alloc_proc, init_proc)
raise TypeError.new("can't dump #{self.class}")
end
}
}
class Object
def method_defined_at?(name, mod)
name = name.to_s
mod = mod.to_s
meth = method(name)
s = meth.inspect
raise "method inspection doesn't begin with \"#<\"" if /\A#</ !~ s
s = $'
raise "method inspection has unexpected method class name" if /\A#{Regexp.quote meth.class.to_s}/ !~ s
s = $'
raise "method inspection doesn't separated properly" if /\A: / !~ s
s = $'
raise "method inspection doesn't end with ">"" unless s.chomp! '>'
raise "method inspection has unexpected method name" unless /([.#])#{Regexp.quote name}\z/ =~ s
s = $`
mark = $1
if mark == '.'
s2 = self.inspect
raise "singleton method inspection has unexpected receiver inspection" unless /\A#{Regexp.quote s2}/ =~ s
unless $'.empty?
s = $'
raise "defining class of singleton method inspection doesn't end with \")\"" unless s.chomp! ')'
raise "defining class of singleton method inspection doesn't begin with \"(\"" unless /\A\(/ =~ s
s = $'
end
s == mod
else
raise "method inspection has unexpected receiver class" unless /\A#{Regexp.quote self.class.to_s}/ =~ s
unless $'.empty?
s = $'
raise "defining class of method inspection doesn't end with \")\"" unless s.chomp! ')'
raise "defining class of method inspection doesn't begin with \"(\"" unless /\A\(/ =~ s
s = $'
end
s == mod
end
end
def am_nameinit(name_proc, init_proc)
respond_to?(:am_name) and
catch(AMarshal::Next) {
name_proc.call(am_name)
#am_init_instance_variables init_proc
return true
}
return false
end
def am_litinit(lit_proc, init_proc)
respond_to?(:am_literal) and
self.class.instance_methods(false).include?("am_literal") and
catch(AMarshal::Next) {
lit_proc.call(am_literal)
am_init_instance_variables init_proc
am_init_extentions init_proc
return true
}
return false
end
def am_allocinit(alloc_proc, init_proc)
alloc_proc.call(self.class, :allocate) if alloc_proc
am_init_instance_variables init_proc
am_init_extentions init_proc
end
def am_init_instance_variables(init_proc)
self.instance_variables.each {|iv|
init_proc.call(:instance_variable_set, iv, eval(iv))
}
end
def instance_variable_set(var, val)
eval "#{var} = val"
end
def am_singleton?
return true unless self.singleton_methods(true).empty?
singleton_class = class << self
self
end
return true unless singleton_class.instance_variables.empty?
not (singleton_class.ancestors - self.class.ancestors).empty?
end
def am_init_extentions(init_proc)
unless self.singleton_methods(true).empty?
raise TypeError.new("singleton can't be dumped")
end
singleton_class = class << self
unless instance_variables.empty?
raise TypeError.new("singleton can't be dumped")
end
self
end
(singleton_class.ancestors - self.class.ancestors).reverse_each {|m|
init_proc.call(:extend, m)
}
end
end
class Array
def am_allocinit(alloc_proc, init_proc)
super
self.each_with_index {|v, i| init_proc.call(:<<, v)}
end
end
class Exception
def am_allocinit(alloc_proc, init_proc)
super
init_proc.call(:am_initialize, message)
init_proc.call(:set_backtrace, backtrace) if backtrace
end
alias am_initialize initialize
end
class FalseClass
alias am_name to_s
def am_init_extentions(init_proc)
end
end
class Hash
def am_allocinit(alloc_proc, init_proc)
raise TypeError.new("can't dump #{self.class} with default proc") if self.default_proc
super
self.each {|k, v| init_proc.call(:[]=, k, v)}
init_proc.call(:default=, self.default) if self.default != nil
end
end
class Module
def am_name
n = name
raise TypeError.new("can't dump anonymous module #{self.inspect}") if n.empty?
n
end
end
class Bignum
alias am_literal to_s
end
class Fixnum
alias am_name to_s
def am_init_extentions(init_proc)
end
end
class Float
# Float.am_nan, Float.am_pos_inf and Float.am_neg_inf are not a literal.
def am_literal
if self.nan?
"Float.am_nan"
elsif self.infinite?
if 0 < self
"Float.am_pos_inf"
else
"Float.am_neg_inf"
end
elsif self == 0.0
if 1.0 / self < 0
"-0.0"
else
"0.0"
end
else
str = '%.17g' % self
str << ".0" if /\A-?[0-9]+\z/ =~ str
str
end
end
def Float.am_nan() 0.0 / 0.0 end
def Float.am_pos_inf() 1.0 / 0.0 end
def Float.am_neg_inf() -1.0 / 0.0 end
end
class Range
def am_allocinit(alloc_proc, init_proc)
super
if self.method_defined_at?(:initialize, Range)
init = :initialize
else
init = :am_initialize
end
init_proc.call(init, first, last, exclude_end?)
end
alias am_initialize initialize
end
class Regexp
alias am_literal inspect
def am_allocinit(alloc_proc, init_proc)
super
if self.method_defined_at?(:initialize, Regexp)
init = :initialize
else
init = :am_initialize
end
init_proc.call(init, self.source, self.options)
end
alias am_initialize initialize
end
class String
alias am_literal dump
def am_allocinit(alloc_proc, init_proc)
super
if self.method_defined_at?(:initialize, String)
init = :initialize
else
init = :am_initialize
end
init_proc.call(init, String.new(self))
end
alias am_initialize initialize
end
class Struct
def am_allocinit(alloc_proc, init_proc)
super
self.each_pair {|m, v| init_proc.call(:[]=, m, v)}
end
end
class Symbol
def am_name
throw AMarshal::Next if %r{\A(?:[A-Za-z_][0-9A-Za-z_]*[?!=]?|\||\^|&|<=>|==|===|=~|>|>=|<|<=|<<|>>|\+|\-|\*|/|%|\*\*|~|\+@|\-@|\[\]|\[\]=|\`)\z} !~ (str = to_s)
":" + str
end
def am_allocinit(alloc_proc, init_proc)
alloc_proc.call(to_s, :intern)
super(nil, init_proc)
end
def am_init_extentions(init_proc)
end
end
class Time
def am_allocinit(alloc_proc, init_proc)
if self.class.method_defined_at?(:utc, Time)
utc_method = :utc
else
utc_method = :am_utc
end
t = self.dup.utc
alloc_proc.call(self.class, utc_method, t.year, t.mon, t.day, t.hour, t.min, t.sec, t.usec)
super(nil, init_proc)
init_proc.call(:localtime) unless utc?
end
class << Time
alias am_utc utc
end
end
class TrueClass
alias am_name to_s
def am_init_extentions(init_proc)
end
end
class NilClass
alias am_name inspect
def am_init_extentions(init_proc)
end
end