rubybreaker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/AUTHORS +7 -0
  2. data/LICENSE +26 -0
  3. data/README.md +403 -0
  4. data/Rakefile +90 -0
  5. data/TODO +30 -0
  6. data/bin/gen_stub_rubylib +64 -0
  7. data/bin/rubybreaker +67 -0
  8. data/lib/rubybreaker/context.rb +122 -0
  9. data/lib/rubybreaker/debug.rb +48 -0
  10. data/lib/rubybreaker/error.rb +59 -0
  11. data/lib/rubybreaker/rubylib/core.rb +2316 -0
  12. data/lib/rubybreaker/rubylib.rb +3 -0
  13. data/lib/rubybreaker/runtime/inspector.rb +57 -0
  14. data/lib/rubybreaker/runtime/monitor.rb +235 -0
  15. data/lib/rubybreaker/runtime/object_wrapper.rb +77 -0
  16. data/lib/rubybreaker/runtime/overrides.rb +42 -0
  17. data/lib/rubybreaker/runtime/pluggable.rb +57 -0
  18. data/lib/rubybreaker/runtime/type_placeholder.rb +27 -0
  19. data/lib/rubybreaker/runtime/type_system.rb +228 -0
  20. data/lib/rubybreaker/runtime/typesig_parser.rb +45 -0
  21. data/lib/rubybreaker/runtime.rb +103 -0
  22. data/lib/rubybreaker/test/testcase.rb +39 -0
  23. data/lib/rubybreaker/test.rb +1 -0
  24. data/lib/rubybreaker/type/type.rb +241 -0
  25. data/lib/rubybreaker/type/type_comparer.rb +143 -0
  26. data/lib/rubybreaker/type/type_grammar.treetop +285 -0
  27. data/lib/rubybreaker/type/type_unparser.rb +142 -0
  28. data/lib/rubybreaker/type.rb +2 -0
  29. data/lib/rubybreaker/typing/rubytype.rb +47 -0
  30. data/lib/rubybreaker/typing/subtyping.rb +480 -0
  31. data/lib/rubybreaker/typing.rb +3 -0
  32. data/lib/rubybreaker/util.rb +31 -0
  33. data/lib/rubybreaker.rb +193 -0
  34. data/test/integrated/tc_method_missing.rb +30 -0
  35. data/test/integrated/tc_simple1.rb +77 -0
  36. data/test/runtime/tc_obj_wrapper.rb +73 -0
  37. data/test/runtime/tc_typesig_parser.rb +33 -0
  38. data/test/ts_integrated.rb +4 -0
  39. data/test/ts_runtime.rb +5 -0
  40. data/test/ts_type.rb +5 -0
  41. data/test/ts_typing.rb +4 -0
  42. data/test/type/tc_comparer.rb +211 -0
  43. data/test/type/tc_parser.rb +219 -0
  44. data/test/type/tc_unparser.rb +276 -0
  45. data/test/typing/tc_rubytype.rb +63 -0
  46. data/test/typing/tc_typing.rb +219 -0
  47. data/webpage/footer.html +5 -0
  48. data/webpage/generated_toc.js +319 -0
  49. data/webpage/header.html +14 -0
  50. data/webpage/images/logo.png +0 -0
  51. data/webpage/index.html +439 -0
  52. data/webpage/rubybreaker.css +53 -0
  53. metadata +119 -0
@@ -0,0 +1,480 @@
1
+ #--
2
+ # This file is the mechanism behind the default type system of RubyBreaker.
3
+ # Typing works as follows:
4
+ #
5
+ # In RubyBreaker, there are two *kinds* of modules (classes)--Broken and
6
+ # non-Broken. The former refers to modules that already have type
7
+ # information, and the latter refers to ones without prior type information.
8
+ # Although it might be optional as an add-on, RubyBreaker does not generate
9
+ # any constraint graph to resolve subtype relations. This is the main
10
+ # difference between Rubydust and RubyBreaker.
11
+ #
12
+ # If a module is not Broken but is Breakable (i.e., monitored), then it is
13
+ # treated as non-Broken. At the end of the execution, the module will be
14
+ # Broken--i.e., its type information is now revealed. If the user wishes to
15
+ # use the result of the analysis, this type information will be documented
16
+ # and the module itself can be used as Broken in future execution.
17
+ #
18
+ # Consider the following examples:
19
+ #
20
+ # class Numeric
21
+ # typesig("+(numeric) -> numeric")
22
+ # ...
23
+ # end
24
+ #
25
+ # class A
26
+ # include RubyBreaker::Breakable
27
+ # def m(x,y)
28
+ # return x+y
29
+ # end
30
+ # end
31
+ #
32
+ # a = A.new
33
+ # assert_equal(3,a.m(1,2))
34
+ #
35
+ # Type of A#m will be "m(fixnum[+],numeric) -> numeric". Note that the type
36
+ # signature for Numeric#+ is given by RubyBreaker in base.rb. The first
37
+ # argument of A#m gets fixnum[+] since x is a Numeric and was invoked +
38
+ # method. The second argument simply gets Numeric since no method was
39
+ # invoked on the object but + method takes a Numeric.
40
+ #
41
+ # The basis of RubyBreaker typing is object subtyping. It is object typing
42
+ # because RubyBreaker supports duck types and fusion types where individual
43
+ # methods can be specified in the type.
44
+ #
45
+
46
+ require_relative "rubytype"
47
+ require_relative "../type"
48
+
49
+ module RubyBreaker
50
+
51
+ # This module contains subtyping logic used in RubyBreaker. See
52
+ # _rubytype.rb_ for logic related to subclassing which is directly related
53
+ # to pure Ruby types.
54
+ module Typing
55
+
56
+ include TypeDefs
57
+
58
+ private
59
+
60
+ # Thie method checks if the module has all the methods specified in
61
+ # meths array
62
+ def self.module_has_methods?(mod, meths)
63
+ has_all = true
64
+ mod_meths = mod.instance_methods
65
+ meths.each do |m|
66
+ if !mod_meths.include?(m)
67
+ has_all = false
68
+ break
69
+ end
70
+ end
71
+ return has_all
72
+ end
73
+
74
+ # This method checks if the duck type has all the methods specified in
75
+ # meths array
76
+ def self.duck_has_methods?(duck, meths)
77
+ has_all = true
78
+ meths.each do |m|
79
+ if !duck.meth_names.include?(m)
80
+ has_all = false
81
+ break
82
+ end
83
+ end
84
+ return has_all
85
+ end
86
+
87
+ # This method checks if a duck type (LHS) is a subtype of the other
88
+ # type. There are a few cases to consider:
89
+ #
90
+ # If RHS is a nominal type, every method in RHS must exist in LHS.
91
+ # This is because the subtype has the same number of or more methods
92
+ # than supertype.
93
+ #
94
+ # If RHS is a duck type OR a fusion type, every method in RHS must
95
+ # exist in LHS.
96
+ #
97
+ # If RHS is an "or" type, LHS must be a subtype of one of RHS'es inner
98
+ # types.
99
+ #
100
+ # All other cases would not satisfy this subtyping relationship.
101
+ #
102
+ def self.duck_subtype_rel?(lhs,rhs)
103
+ return false unless lhs.kind_of?(DuckType)
104
+ if rhs.instance_of?(NominalType) # Don't include self type
105
+ # Ok, this is unlikely but we still do a check
106
+ is_subtype = self.duck_has_methods?(lhs, rhs.mod.instance_methods)
107
+ elsif rhs.kind_of?(DuckType) # duck type and fusion type
108
+ is_subtype = self.duck_has_methods?(lhs, rhs.meth_names)
109
+ elsif rhs.instance_of?(OrType)
110
+ is_subtype = false
111
+ rhs.types.each {|rhs_inner_t|
112
+ # Only one has to be in subtype relation
113
+ if self.duck_subtype_rel?(lhs,rhs_inner_t)
114
+ is_subtype = true
115
+ break
116
+ end
117
+ }
118
+ else
119
+ is_subtype = false
120
+ end
121
+ return is_subtype
122
+ end
123
+
124
+ # Procedure subtype relation check is for both methods and blocks. A
125
+ # typical "method" subtype is satified if
126
+ #
127
+ # - Each argument in RHS is a subtype of its counterpart in LHS. This
128
+ # includes the block argument. It's called contra-variance.
129
+ # - The return in LHS is a subtype of its counterpart in RHS. It's
130
+ # called co-variance.
131
+ #
132
+ # Here is the explanation to why we do this:
133
+ #
134
+ # Assume m1 <: m2 (i.e, m1 is a subtype of m2)
135
+ #
136
+ # Then, any place where m2 is used, m1 should be able to replace m2.
137
+ # In other words, m1's arguments should accept more types than m2's
138
+ # arguments. On the other hand, m1's return should be more restrictive
139
+ # than m2's return to make sure it would work after the call. There it
140
+ # is, you just received a mini type systems class without paying
141
+ # thousands of dollars.
142
+ #
143
+ # This method also takes care of optional argument and variable length
144
+ # argument. This method is the ONLY one that should handle those types.
145
+ #
146
+ def self.proc_subtype_rel?(lhs, rhs)
147
+
148
+ # use kind_of? because method_subtype_rel? calls this method
149
+ return false unless lhs.kind_of?(BlockType) & rhs.kind_of?(BlockType)
150
+
151
+ is_subtype = true
152
+
153
+ if lhs.arg_types.length != rhs.arg_types.length
154
+ is_subtype = false
155
+ # elsif !self.subtype_rel?(rhs.blk_type, lhs.blk_type)
156
+ # is_subtype = false
157
+ elsif !self.subtype_rel?(lhs.ret_type, rhs.ret_type)
158
+ is_subtype = false
159
+ else
160
+
161
+ # Subtyping with optional type and variable length type works as
162
+ # follows:
163
+ #
164
+ # (x?) <: (x) since optional argument can replace original
165
+ # argument
166
+ #
167
+ # (x*) <: (x) for the same reason
168
+ #
169
+ # (x*) <: (x?) since LHS can have more arguments
170
+ #
171
+
172
+ is_subtype = true
173
+ # check arguments
174
+ rhs.arg_types.each_with_index {|rhs_arg,i|
175
+ lhs_arg = lhs.arg_types[i]
176
+ if lhs_arg.kind_of?(OptionalType)
177
+ lhs_arg = lhs_arg.type
178
+ if rhs_arg.kind_of?(OptionalType)
179
+ rhs_arg = rhs_arg.type
180
+ elsif rhs_arg.kind_of?(VarLengthType)
181
+ is_subtype = false
182
+ break
183
+ end
184
+ elsif lhs_arg.kind_of?(VarLengthType)
185
+ lhs_arg = lhs_arg.type
186
+ if rhs_arg.kind_of?(OptionalType) ||
187
+ rhs_arg.kind_of?(VarLengthType)
188
+ rhs_arg = rhs_arg.type
189
+ end
190
+ end
191
+ if !self.subtype_rel?(rhs_arg, lhs_arg)
192
+ is_subtype = false
193
+ break
194
+ end
195
+ }
196
+ end
197
+ return is_subtype
198
+ end
199
+
200
+ # This method checks the subtype relation between two method types.
201
+ # There are several cases to consider:
202
+ #
203
+ # Case 1: If RHS is a MethodListType, we give up. It is actually
204
+ # difficult to figure out the subtype relation in this case.
205
+ # Case 2: If LHS is a MethodListType, only one of them has to be in
206
+ # subtype relation to RHS.
207
+ # Case 3: If both are Methods, then do a typical method subtype
208
+ # comparison.
209
+ #
210
+ # Note: See methodtypelist_subtype_rel? if LHS is a MethodListType.
211
+ #
212
+ def self.method_subtype_rel?(lhs,rhs)
213
+ is_subtype = true
214
+ # First check if LHS and RHS are either a method type or a method list
215
+ # type. Otherwise, return false.
216
+ if (!lhs.instance_of?(MethodType) && !lhs.instance_of?(MethodListType)) ||
217
+ (!rhs.instance_of?(MethodType) && !rhs.instance_of?(MethodListType))
218
+ is_subtype = false
219
+ elsif lhs.instance_of?(MethodListType)
220
+ # Every method type in LHS must be a subtype of RHS
221
+ is_subtype = true
222
+ lhs.types.each {|type|
223
+ if !self.method_subtype_rel?(type,rhs)
224
+ is_subtype = false
225
+ break
226
+ end
227
+ }
228
+ elsif rhs.instance_of?(MethodListType)
229
+ # One of the RHS has to be a supertype
230
+ is_subtype = false
231
+ rhs.types.each {|type|
232
+ if self.method_subtype_rel?(lhs,type)
233
+ is_subtype = true
234
+ break
235
+ end
236
+ }
237
+ else # ok, both are MethodType
238
+ # Remember the contra-variance in arguments, co-variance in return
239
+ if lhs.meth_name != rhs.meth_name
240
+ is_subtype = false
241
+ else
242
+ # re-use block subtype relation check
243
+ is_subtype = self.proc_subtype_rel?(lhs, rhs)
244
+ end
245
+ end
246
+ return is_subtype
247
+ end
248
+
249
+ # This method checks if each method in the subtype is indeed a subtype
250
+ # of the counterpart in the supertype.
251
+ def self.methods_subtype_rel?(sub_meth_types, super_meth_types)
252
+ # wider means, a fewer number of methods
253
+ return false unless sub_meth_types.size <= super_meth_types
254
+ is_subtype = true
255
+ sub_meth_types.each_pair { |meth_name, sub_meth_type|
256
+ super_meth_type = super_meth_types[meth_name]
257
+ if super_meth_type == nil ||
258
+ !method_subtype_rel?(sub_meth_type, super_meth_type)
259
+ is_subtype = false
260
+ break
261
+ end
262
+ }
263
+ return is_subtype
264
+ end
265
+
266
+ # This method determines if the fusion type (LHS) is a subtype of the
267
+ # other type (RHS). There are many cases to consider:
268
+ #
269
+ # If LHS is Broken, there are several cases to consider:
270
+ #
271
+ # If RHS is a nominal type, there are a few cases to consider:
272
+ #
273
+ # If LHS is a subclass of RHS, then there is a subtype relation
274
+ #
275
+ # If RHS is Broken, each method in RHS must be a supertype of the
276
+ # counterpart in LHS.
277
+ #
278
+ # Otherwise, each in LHS must exist in RHS.
279
+ #
280
+ # If RHS is a fusion type, ther eare a few cases to consider:
281
+ #
282
+ # If LHS is a subclass of RHS, then LHS is a subtype
283
+ #
284
+ # If RHS is Broken, each method in RHS must be a supertype of the
285
+ # counterpart in LHS.
286
+ #
287
+ # Otherwise, every method in RHS must exist in LHS.
288
+ #
289
+ # If RHS is a duck type, then every method in RHS must exist in LHS.
290
+ #
291
+ # Otherwise, there is no subtype relation.
292
+ #
293
+ # If LHS is not Broken, subtyping satifies only when LHS has all
294
+ # methods in RHS
295
+ #
296
+ def self.fusion_subtype_rel?(lhs,rhs)
297
+ return false unless lhs.kind_of?(FusionType)
298
+ if lhs.mod.kind_of?(Broken)
299
+ if rhs.instance_of?(NominalType) # don't include self type
300
+ if RubyTypeUtils.subclass_rel?(lhs.mod, rhs.mod)
301
+ is_subtype = true
302
+ elsif rhs.mod.kind_of?(Broken)
303
+ # then do a type check for each method
304
+ lhs_meths = Inspect.inspect_all(lhs.mod)
305
+ rhs_meths = Inspect.inspect_all(rhs.mod)
306
+ is_subtype = self.methods_subtype_rel?(lhs_meths,rhs_meths)
307
+ else
308
+ # if not, the only possible way is if lhs has all the rhs' methods
309
+ is_subtype = self.duck_has_methods?(lhs, rhs.mod.instance_methods)
310
+ end
311
+ elsif rhs.instance_of?(FusionType)
312
+ if RubyTypeUtils.subclass_rel?(lhs.mod, rhs.mod)
313
+ is_subtype = true
314
+ elsif rhs.mod.kind_of?(Broken)
315
+ # then do a type check for each method
316
+ lhs_meths = Inspect.inspect_all(lhs.mod)
317
+ rhs_meths = Inspect.inspect_meths(rhs.mod, lhs.meths.keys)
318
+ is_subtype = self.methods_subtype_rel?(lhs_meths,rhs_meths)
319
+ else
320
+ is_subtype = self.duck_has_methods?(lhs, rhs.meth_names)
321
+ end
322
+ elsif rhs.instance_of?(DuckType)
323
+ is_subtype = self.duck_has_methods?(lhs, rhs.meth_names)
324
+ else
325
+ is_subtype = false
326
+ end
327
+ else
328
+ # lhs is not broken, so only thing we can do is method check
329
+ is_subtype = self.duck_subtype_rel?(lhs, rhs)
330
+ end
331
+ return is_subtype
332
+ end
333
+
334
+ # Self type works exactly like nominal type except that, if RHS is a
335
+ # self type, then only self type is allowed in LHS.
336
+ #
337
+ # For example, consider you are inside Fixnum
338
+ #
339
+ # self <: Fixnum
340
+ # self <: Numeric
341
+ # self <: Object
342
+ # self <: self
343
+ #
344
+ # but,
345
+ #
346
+ # Fixnum !<: self
347
+ #
348
+ def self.self_subtype_rel?(lhs,rhs)
349
+ self.nominal_subtype_rel?(lhs,rhs)
350
+ end
351
+
352
+ # This method checks the subtype relation when LHS is a nominal type.
353
+ # There are several cases to consider:
354
+ #
355
+ # my_class <: your_class is true only if MyClass and YourClass have
356
+ # (Ruby) subclass relationship.
357
+ #
358
+ # my_class <: a[foo] is true if
359
+ #
360
+ # (1) MyClass is a subtype of A or
361
+ # (2) Both MyClass and A are Broken and every method in MyClass is a
362
+ # subtype of the counterpart method in A. (This should be rare).
363
+ # Or,
364
+ # (3) A are not Broken and MyClass has all methods of A.
365
+ #
366
+ #
367
+ def self.nominal_subtype_rel?(lhs,rhs)
368
+ return false unless lhs.kind_of?(NominalType) # Self type is a nominal type
369
+ if rhs.instance_of?(SelfType)
370
+ is_subtype = lhs.instance_of?(SelfType)
371
+ elsif rhs.instance_of?(NominalType) # don't include self type
372
+ is_subtype = RubyTypeUtils.subclass_rel?(lhs.mod, rhs.mod)
373
+ elsif rhs.instance_of?(FusionType)
374
+ # If RHS is a superclass or included module then true
375
+ # If both LHS and RHS are Broken, do a subtype check on each method
376
+ # If only RHS is broken, look at each method's type
377
+ # If RHS is not broken, sorry no subtype relationship
378
+ if RubyTypeUtils.subclass_rel?(lhs.mod, rhs.mod)
379
+ is_subtype = true
380
+ elsif lhs.kind_of?(Broken) && rhs.kind_of?(Broken)
381
+ is_subtype = true
382
+ lhs_methods = lhs.mod.instance_methods
383
+ rhs.meth_names.each {|m|
384
+ lhs_m = Inspector.inspect_meth(lhs, m)
385
+ rhs_m = Inspector.inspect_meth(rhs, m)
386
+ if !meth_subtype_rel?(lhs_m, rhs_m)
387
+ is_subtype = false
388
+ break
389
+ end
390
+ }
391
+ else
392
+ is_subtype = self.module_has_methods?(lhs.mod, rhs.meth_names)
393
+ end
394
+ elsif rhs.instance_of?(DuckType)
395
+ # Do simple method existence check since there is no nominal type to
396
+ # inspect.
397
+ is_subtype = self.module_has_methods?(lhs.mod, rhs.meth_names)
398
+ else
399
+ # No other possibility of subtype relation
400
+ is_subtype = false
401
+ end
402
+ return is_subtype
403
+ end
404
+
405
+ # If OrType is on the LHS, the only possible subtype relation is if each
406
+ # child type in OrType is a subtype of RHS. For example,
407
+ #
408
+ # numeric or string <: object
409
+ #
410
+ # is true only if numeric is subype of object and string is subtype of
411
+ # object.
412
+ #
413
+ def self.or_subtype_rel?(lhs,rhs)
414
+ return false if !lhs.kind_of?(OrType)
415
+ is_subtype = true
416
+ # Each type in LHS has to be a subtype of RHS
417
+ lhs.types.each do |t|
418
+ if !self.subtype_rel?(t,rhs)
419
+ is_subtype = false
420
+ break
421
+ end
422
+ end
423
+ return is_subtype
424
+ end
425
+
426
+ public
427
+
428
+ # This method determines if one type is a subtype of another. This check
429
+ # is for RubyBreaker defined types. See _TypeDefs_ module for more
430
+ # detail.
431
+ #
432
+ # lhs:: The allegedly "subtype"
433
+ # rhs:: The allegedly "supertype"
434
+ #
435
+ def self.subtype_rel?(lhs, rhs)
436
+
437
+ # Don't even bother if they are same object or syntactically
438
+ # equivalent. NOTE: would this really help improve performance???
439
+ return true if (lhs.equal?(rhs) || lhs.eql?(rhs))
440
+
441
+ # Break down the cases by what LHS is.
442
+ is_subtype = false
443
+ if lhs.instance_of?(NilType)
444
+ is_subtype = rhs.instance_of?(NilType)
445
+ elsif lhs.instance_of?(AnyType)
446
+ is_subtype = true
447
+ elsif lhs.instance_of?(SelfType)
448
+ is_subtype = self.self_subtype_rel?(lhs,rhs)
449
+ elsif lhs.instance_of?(NominalType)
450
+ is_subtype = self.nominal_subtype_rel?(lhs,rhs)
451
+ elsif lhs.instance_of?(FusionType)
452
+ is_subtype = self.fusion_subtype_rel?(lhs,rhs)
453
+ elsif lhs.instance_of?(DuckType)
454
+ is_subtype = self.duck_subtype_rel?(lhs,rhs)
455
+ elsif lhs.instance_of?(MethodType)
456
+ is_subtype = self.method_subtype_rel?(lhs,rhs)
457
+ elsif lhs.instance_of?(OrType)
458
+ is_subtype = self.or_subtype_rel?(lhs,rhs)
459
+ elsif lhs.instance_of?(BlockType)
460
+ is_subtype = self.proc_subtype_rel?(lhs,rhs)
461
+ elsif lhs.instance_of?(MethodListType)
462
+ is_subtype = self.method_subtype_rel?(lhs,rhs)
463
+ end
464
+ return is_subtype
465
+ end
466
+
467
+ end
468
+
469
+ #--
470
+ # Reopening the class to allow subtyping more accessible.
471
+ class TypeDefs::Type
472
+
473
+ # This is a shorthand for calling Typing.subtype_rel?
474
+ def subtype_of?(rhs)
475
+ return Typing.subtype_rel?(self,rhs)
476
+ end
477
+ end
478
+
479
+ end
480
+
@@ -0,0 +1,3 @@
1
+ require_relative "type/type_comparer"
2
+ require_relative "typing/subtyping"
3
+
@@ -0,0 +1,31 @@
1
+ #--
2
+ # This file contains utility functions that are useful for RubyBreaker. They
3
+ # may have been taken from other sources.
4
+
5
+ module RubyBreaker
6
+
7
+ module Utilities
8
+
9
+ # File activesupport/lib/active_support/inflector/methods.rb, line 48
10
+ def self.underscore(camel_cased_word)
11
+ word = camel_cased_word.to_s.dup
12
+ word.gsub!(/::/, '/')
13
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
14
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
15
+ word.tr!("-", "_")
16
+ word.downcase!
17
+ word
18
+ end
19
+
20
+ # File activesupport/lib/active_support/inflector/methods.rb
21
+ def self.camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
22
+ if first_letter_in_uppercase
23
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
24
+ else
25
+ lower_case_and_underscored_word.to_s[0].chr.downcase + camelize(lower_case_and_underscored_word)[1..-1]
26
+ end
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,193 @@
1
+ #--
2
+ # This library dynamically profiles and resolves the type information
3
+ # observed at runtime and generates type annotation at the end. It can be
4
+ # run as either a stand-alone script or as a Ruby library.
5
+
6
+ require_relative "rubybreaker/runtime"
7
+ require_relative "rubybreaker/test"
8
+
9
+ # RubyBreaker is a dynamic instrumentation and monitoring tool that
10
+ # documents type information automatically.
11
+ module RubyBreaker
12
+
13
+ # This constant contains the copyright information.
14
+ COPYRIGHT = "Copyright (c) 2012 Jong-hoon (David) An. All Rights Reserved."
15
+
16
+ # Options for RubyBreaker
17
+ OPTIONS = {
18
+ :debug => false, # in debug mode?
19
+ :verbose => true, # in verbose mode?
20
+ :mode => :lib, # bin or lib?
21
+ :io_file => nil, # generate input/output other than default?
22
+ :append => true, # append to the input file (if there is)?
23
+ :stdout => true, # also display on the screen?
24
+ :rubylib => true, # include core ruby library documentation?
25
+ }
26
+
27
+ # Broken takes higher precedence than Breakable. Once a module is
28
+ # "declared" to be Broken, it cannot be Breakable.
29
+ #
30
+ # TODO: In future, there will be a module to support both states.
31
+
32
+ # This array lists modules/classes that will be monitored.
33
+ BREAKABLE = []
34
+
35
+ # This array lists modules/classes that are actually instrumented with a
36
+ # monitor.
37
+ INSTALLED = []
38
+
39
+ # This array lists "broken" classes--i.e., with type signatures
40
+ BROKEN = []
41
+
42
+ # This array lists monitored modules/classes that are outputed.
43
+ DOCUMENTED = []
44
+
45
+ # Extension used by RubyBreaker for output/input
46
+ EXTENSION = "rubybreaker"
47
+
48
+ # This module has a set of entry points to the program and misc. methods
49
+ # for running RubyBreaker.
50
+ module Main
51
+
52
+ include TypeDefs
53
+ include Runtime
54
+
55
+ public
56
+
57
+ # This method is the trigger point to install a monitor in each
58
+ # module/class.
59
+ def self.setup()
60
+
61
+ BREAKABLE.each do |m|
62
+ # Avoid already installed module or now Broken module. Remember,
63
+ # once a module is a declared to be Broken, it wins. Broken modules
64
+ # cannot be Breakable!
65
+ unless INSTALLED.include?(m) || BROKEN.include?(m)
66
+ MonitorInstaller.install_module_monitor(m,true)
67
+ INSTALLED << m
68
+ end
69
+ end
70
+
71
+ # At the end, we generate an output of the type information.
72
+ at_exit do
73
+ self.output
74
+ end
75
+
76
+ end
77
+
78
+ # Reads the input file if specified or exists
79
+ def self.input()
80
+ return unless OPTIONS[:io_file] && File.exist?(OPTIONS[:io_file])
81
+ eval "load \"#{OPTIONS[:io_file]}\"", TOPLEVEL_BINDING
82
+ end
83
+
84
+ # This method will generate the output
85
+ def self.output()
86
+
87
+ io_exist = OPTIONS[:io_file] && File.exist?(OPTIONS[:io_file])
88
+
89
+ str = ""
90
+ pp = PrettyPrint.new(str)
91
+
92
+ # Document each module that was monitored
93
+ INSTALLED.each do |mod|
94
+
95
+ # Skip it if we already have seen it
96
+ next if DOCUMENTED.include?(mod)
97
+ DOCUMENTED << mod
98
+
99
+ pp.text("class #{mod.to_s}")
100
+ pp.nest(2) do
101
+ pp.breakable("")
102
+ pp.text("include RubyBreaker::Broken")
103
+ meth_type_map = Inspector.inspect_all(mod)
104
+ meth_type_map.each { |meth_name, meth_type|
105
+ case meth_type
106
+ when MethodType
107
+ pp.breakable()
108
+ pp.text("typesig(\"")
109
+ TypeUnparser.unparse_pp(pp,meth_type)
110
+ pp.text("\")")
111
+ when MethodListType
112
+ meth_type.types.each { |real_meth_type|
113
+ pp.breakable()
114
+ pp.text("typesig(\"")
115
+ TypeUnparser.unparse_pp(pp,real_meth_type)
116
+ pp.text("\")")
117
+ }
118
+ else
119
+ # Can't happen
120
+ end
121
+ }
122
+ end
123
+ pp.breakable()
124
+ pp.text("end")
125
+ pp.breakable()
126
+ end
127
+ pp.flush
128
+
129
+ # First, display the result on the stdout if set
130
+ puts str if OPTIONS[:stdout]
131
+
132
+ # If this was a library mode run, exit now.
133
+ return if OPTIONS[:mode] == :lib
134
+
135
+ # Append the result to the input file (or create a new file)
136
+ open(OPTIONS[:io_file],"a") do |f|
137
+ unless io_exist
138
+ f.puts "# This file is auto-generated by RubyBreaker"
139
+ # f.puts "require \"rubybreaker\""
140
+ end
141
+ f.puts str
142
+ end
143
+
144
+ end
145
+
146
+ # This method will run the input file
147
+ def self.run()
148
+
149
+ # First, take care of the program file.
150
+
151
+ argv0 = ARGV[0]
152
+ prog_file = argv0
153
+ prog_file = File.expand_path(prog_file)
154
+
155
+ # It is ok to omit .rb extension. So try to see if prog_file.rb exists
156
+ if !File.exist?(prog_file) && !File.extname(prog_file) == ".rb"
157
+ prog_file = "#{prog_file}.rb"
158
+ end
159
+
160
+ if !File.exist?(prog_file)
161
+ puts "ERROR: '#{argv0}' is an invalid file."
162
+ exit(1)
163
+ end
164
+
165
+ # Then, input/output file if specified
166
+ if !OPTIONS[:io_file] || OPTIONS[:io_file].empty?
167
+ OPTIONS[:io_file] = "#{File.basename(argv0, ".rb")}.#{EXTENSION}"
168
+ end
169
+ OPTIONS[:io_file] = File.absolute_path(OPTIONS[:io_file])
170
+
171
+ if OPTIONS[:rubylib]
172
+ # Load the core library type documentation
173
+ eval("require \"#{File.dirname(__FILE__)}/rubybreaker/rubylib\"", TOPLEVEL_BINDING)
174
+ end
175
+
176
+ # Read the input file first (as it might contain type documentation
177
+ # already)
178
+ Main.input()
179
+
180
+ # Finally, require the program file! Let it run! Wheeee!
181
+ eval "require '#{prog_file}'", TOPLEVEL_BINDING
182
+
183
+ end
184
+
185
+ end
186
+
187
+ # Just redirecting
188
+ def self.monitor()
189
+ Main.setup()
190
+ end
191
+
192
+ end
193
+