rubybreaker 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+