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
data/AUTHORS ADDED
@@ -0,0 +1,7 @@
1
+ # AUTHOR
2
+
3
+ * Jong-hoon (David) An <rockalizer@gmail.com>
4
+
5
+ # CONTRIBUTORS
6
+
7
+
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright 2012 Jong-hoon (David) An. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions are met:
5
+
6
+ 1. Redistributions of source code must retain the above copyright notice,
7
+ this list of conditions and the following disclaimer.
8
+
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY JONG-HOON (DAVID) AN ''AS IS'' AND ANY EXPRESS
14
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
16
+ NO EVENT SHALL JONG-HOON (DAVID) AN OR CONTRIBUTORS BE LIABLE FOR ANY
17
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ The views and conclusions contained in the software and documentation are those
25
+ of the authors and should not be interpreted as representing official policies,
26
+ either expressed or implied, of Jong-hoon (David) An.
data/README.md ADDED
@@ -0,0 +1,403 @@
1
+ * * *
2
+
3
+ # Introduction
4
+
5
+ RubyBreaker is a dynamic type documentation tool written purely in Ruby. It
6
+ provides the framework for dynamically instrumenting a Ruby program to
7
+ monitor objects during executions and document the observed type
8
+ information. The type documentation generated by RubyBreaker is also an
9
+ executable Ruby code. It contains type signatures that can be interpreted by
10
+ RubyBreaker Runtime Library and can be used in future documentation of the
11
+ program.
12
+
13
+ The primary goal of RubyBreaker is to assign a type signature to every
14
+ method in designated modules and classes. A type signature is written in
15
+ the RubyBreaker Type Annotation Language which resembles the documentation
16
+ style used in RubyDoc. Overall, this tool should help Ruby programmers
17
+ document their code more rigorously and effectively. Currently, manual
18
+ modification of the user program is required to run RubyBreaker, but this is
19
+ kept minimal.
20
+
21
+ ## Limitations
22
+
23
+ * It only works on toy Ruby programs so far :)
24
+ * Block argument cannot be auto-documented. (Inherent)
25
+ * Manual modification (minimal) of code is required.
26
+ * No parametric polymorphic types are supported.
27
+
28
+ ## Requirements
29
+
30
+ Ruby 1.9.x and TreeTop 1.x
31
+
32
+ If the most recent Ruby 1.9 is installed on the computer, it will probably
33
+ work. If TreeTop is not installed, use RubyGems or download from the
34
+ following URL: [TreeTop](http://treetop.rubyforge.org/)
35
+
36
+ ## Installation
37
+
38
+ It is as simple as running the following:
39
+
40
+ $ gem install rubybreaker
41
+
42
+ You probably want to test out your installation by running
43
+ `rake test` in your RubyBreaker directory:
44
+
45
+ $ rake test
46
+
47
+ * * *
48
+
49
+ # Tutorial
50
+
51
+ This tutorial will describe the basic usage of the tool, the RubyBreaker
52
+ Type Annotation Language, and the RubyBreaker Type System.
53
+
54
+ ## Usage
55
+
56
+ There are two ways to use RubyBreaker:
57
+
58
+ $ rubybreaker prog.rb
59
+
60
+ Or, use it as a Ruby library and just run the program on Ruby.
61
+
62
+ $ ruby prog.rb
63
+
64
+ Both methods require manual modification of the code, but the former will
65
+ generate the output into a separate `.rubybreaker` file whereas the latter
66
+ will display the output on the screen. The former will also automatically
67
+ import the `.rubybreaker` file for the user program of which the output is
68
+ appended at the end. Consequently, this output/input file will grow as more
69
+ analysis is done on the program.
70
+
71
+ For example, let us assume `prog.rb` as the following:
72
+
73
+ require "rubybreaker"
74
+ class A
75
+ include RubyBreaker::Breakable
76
+ def foo(x)
77
+ x.to_s
78
+ end
79
+ end
80
+ class B
81
+ # include RubyBreaker::Breakable
82
+ def bar(y,z)
83
+ y.foo(z)
84
+ end
85
+ end
86
+ RubyBreaker.monitor()
87
+ A.new.foo(1)
88
+
89
+ Do not worry about other parts of the code for now. This example is to show
90
+ how `foo` method is *typed* by RubyBreaker. After running `rubybreaker
91
+ prog.rb`, the following output will be generated and saved into
92
+ `prog.rubybreaker`.
93
+
94
+ require "rubybreaker"
95
+ class A
96
+ include RubyBreaker::Broken
97
+ typesig("foo(fixnum[to_s]) -> string")
98
+ end
99
+
100
+ Now, assume that the last line of `prog.rb` is changed to
101
+ `B.new.bar(A.new,1)` and the `include` command in class `B` is uncommented.
102
+ The subsequent analysis will generate the following result:
103
+
104
+ # This is auto-generated by RubyBreaker
105
+ require "rubybreaker"
106
+ class A
107
+ include RubyBreaker::Broken
108
+ typesig("foo(fixnum[to_s]) -> string")
109
+ end
110
+ class B
111
+ include RubyBreaker::Broken
112
+ typesig("bar(a[foo], fixnum[to_s]) -> string")
113
+ end
114
+
115
+ RubyBreaker is designed to gather type information based on the actual
116
+ execution of a program. This means the program should be equipped with
117
+ test suites that cover a reasonable number of program paths for accurate
118
+ results. Additionally, RubyBreaker assumes that test runs are correct
119
+ and the program behaves correctly (for the test runs) as intended by
120
+ the programmer. This assumption is not a strong requirement, but is
121
+ necessary to obtain precise type information.
122
+
123
+ In order to use RubyBreaker, there needs two kinds of manual code changes.
124
+ First, the user must indicate which modules are subject to analysis and
125
+ which modules can be used for the analysis. Next, the user has to indicate
126
+ where the entry point of the program is. Alternatively, he has to make a
127
+ small change to the test cases to use RubyBreaker's testing framework.
128
+
129
+ ### Breakable and Broken
130
+
131
+ In order to indicate modules and classes that already have type information
132
+ or to designate those that need to be auto-documented, the user must be
133
+ familiar with the two most important modules of RubyBreaker--`Breakable` and
134
+ `Broken`. The former refers to a module (or a class) that needs dynamic
135
+ instrumentation and monitoring for getting type information. The latter
136
+ refers to a module that have type information already documented in type
137
+ signature form.
138
+
139
+ For example, consider the following Ruby code:
140
+
141
+ require "rubybreaker"
142
+ class A
143
+ include RubyBreaker::Breakable
144
+ def foo(x)
145
+ x.to_s
146
+ end
147
+ end
148
+
149
+ By including `Breakable`, class `A` is subject to dynamic instrumentation
150
+ and monitoring. On the other hand, the following class is a `Broken` module.
151
+ (Yes, like a crazy wild horse that has been _broken_!)
152
+
153
+ require "rubybreaker"
154
+ class B
155
+ include RubyBreaker::Broken
156
+ typesig("bar(fixnum[to_s]) -> string")
157
+ def foo(x)
158
+ x.to_s
159
+ end
160
+ end
161
+
162
+ This tells RubyBreaker that class `B` has type information in place, and
163
+ therefore, it will use the information for analyzing `Breakable` modules
164
+ elsewhere (if applicable). In this example, a method `foo` has a type
165
+ signature `bar(fixnum[to_s]) -> string`, which means it takes an object that
166
+ has `Fixnum`'s `to_s` method and returns a string. More detail on the type
167
+ annotation language will be explained in later section.
168
+
169
+ Currently, both `Breakable` and `Broken` only support instance methods.
170
+ Furthermore, class and module methods can neither be monitored nor used for
171
+ analysis. It is important to keep in mind that `Broken` module always wins.
172
+ In other words, if a module is declared as both `Broken` and `Breakable`, it
173
+ is treated as `Broken`.
174
+
175
+ ### Program Entry Point
176
+
177
+ In Ruby, as soon as a file is `require`d, the execution of that file begins.
178
+ For RubyBreaker, however, it is not trivial to find the actual starting
179
+ point of the program because there *has* to be a clear point in time at
180
+ which monitoring of `Breakable` modules begins. *This is necessary as
181
+ attempting to instrument and monitor at the same time will cause an infinite
182
+ loop!*
183
+
184
+ Indicating the program entry point is simply done by inserting the following
185
+ line at the code (assuming "`require 'rubybreaker'`" is already placed at
186
+ the top of the file):
187
+
188
+ RubyBreaker.monitor()
189
+
190
+ It basically tells RubyBreaker to start monitoring. What really happens at
191
+ this point is that all `Breakable` modules are dynamically instrumented so
192
+ that they are ready to be monitored. Any execution after this point will
193
+ run the instrumented code (for `Breakable` modules) which will gather type
194
+ information for methods.
195
+
196
+ Although this seems simple and easy, this is not the recommended way for
197
+ analyzing a program. Why? Because RubyBreaker has a built-in testing
198
+ framework that (supposedly :)) works seemlessly with the existing tests of
199
+ the program.
200
+
201
+ ### Using RubyBreaker Testing Framework
202
+
203
+ Instead of manually inserting the entry point indicator into the program,
204
+ the user can take advantage of the Ruby Unit Test framework. This is the
205
+ recommended way of using RubyBreaker, especially for a long term program
206
+ maintainability. But no worries! This method is as simple as the previous
207
+ one.
208
+
209
+ require "rubybreaker"
210
+ require "test/unit"
211
+ class TestClassA < Test::Unit::TestCase
212
+ include RubyBreaker::TestCase
213
+ # ...tests!...
214
+ end
215
+
216
+ That's it!
217
+
218
+ Currently, RubyBreaker only supports the standard unit test framework.
219
+ Other testing frameworks such as RSpec and Cucumber are not supported at the
220
+ moment (but will be in future/hopefully).
221
+
222
+ ## Type Annotation
223
+
224
+ The annotation language used in RubyBreaker resembles the method
225
+ documentation used by Ruby Standard Library Doc. Each type signature
226
+ defines a method type using the name, argument types, block type, and return
227
+ type. But, let us consider a simple case where there is one argument type
228
+ and a return type.
229
+
230
+ class A
231
+ ...
232
+ typesig("foo(fixnum) -> string")
233
+ end
234
+
235
+ In RubyBreaker, a type signature is recognized by the meta-class level
236
+ method `typesig` which takes a string as an argument. This string is the
237
+ actual type signature written in the Ruby Type Annotation Language. This
238
+ language is designed to reflect the common documentation practice used by
239
+ RubyDoc. It starts with the name of the method. In the above example, `foo`
240
+ is currently being given a type. The rest of the signature takes a typical
241
+ method type symbol, `(x) -> y` where `x` is the argument type and `y` is the
242
+ return type. In the example shown above, the method takes a `Fixnum` object
243
+ and returns a `String` object. Note that these types are in lowercase,
244
+ indicating they are objects and not modules or classes themselves.
245
+
246
+ There are several types that represent an object: nominal, duck, fusion,
247
+ nil, 'any', and block. Each type signature itself represents a method type
248
+ or a method list type (explained below).
249
+
250
+ ### Nominal Type
251
+
252
+ This is the simplest and most intuitive way to represent an object. For
253
+ instance, `fixnum` is an object of type `Fixnum`. Use lower-case letters and
254
+ underscores instead of _camelized_ name. `MyClass`, for example would be
255
+ `my_class` in RubyBreaker type signatures. There is no particular
256
+ reason for this convention other than it is the common practice used in
257
+ RubyDoc.
258
+
259
+ ### Self Type
260
+
261
+ This type is similar to the nominal type but is referring to the current
262
+ object--that is, the receiver of the method being typed. RubyBreaker will
263
+ auto-document the return type as a self type if the return value is the same
264
+ as the receiver of that call. It is also recommended to use this type over
265
+ a nominal type (if the return value is `self`) since it depicts more
266
+ precise return type.
267
+
268
+ ### Duck Type
269
+
270
+ This type is inspired by the Ruby Language's duck typing, _"if it
271
+ walks like a duck and quacks like a duck, it must be a duck."_ Using this
272
+ type, an object can be represented simply by a list of method names. For
273
+ example `[walks, quacks]` is an object that has `walks` and `quacks`
274
+ methods. Note that these method names do *not* reveal any type
275
+ information for themselves.
276
+
277
+ ### Fusion Type
278
+
279
+ Duck type is very flexible but can be too lenient when trying to restrict
280
+ the type of an object. RubyBreaker provides a type called *the fusion type*
281
+ which lists method names but with respect to a nominal type. For
282
+ example, `fixnum[to_f, to_s]` represents an object that has methods `to_f`
283
+ and `to_s` whose types are same as those of `Fixnum`. This is more
284
+ restrictive (precise) than `[to_f, to_s]` because the two methods must have
285
+ the same types as `to_f` and `to_s` methods, respectively, in `Fixnum`.
286
+
287
+ ### Nil Type
288
+
289
+ A nil type represents a value of nil and is denoted by `nil`.
290
+
291
+ ### Any Type
292
+
293
+ RubyBreaker also provides a way to represent an object that is compatible with
294
+ any type. This type is denoted by `?`. Use caution with this type because
295
+ it should be only used for an object that requires an arbitrary yet most
296
+ specific type--that is, `?` is a subtype of any other type, but any
297
+ other type is not a subtype of `?`. This becomes a bit complicated for
298
+ method or block argument types because of their contra-variance
299
+ characteristic. Please kefer to the section *Subtyping*.
300
+
301
+ ### Block Type
302
+
303
+ One of the Ruby's prominent features is the block argument. It allows
304
+ the caller to pass in a piece of code to be executed inside the callee. This
305
+ code block can be executed by the Ruby construct, `yield`, or by directly
306
+ calling the `call` method of the block object. In RubyBreaker, this type can
307
+ be respresented by curly brackets. For instance, `{|fixnum,string| ->
308
+ string}` represents a block that takes two arguments--one `Fixnum` and one
309
+ `String`--and returns a `String`.
310
+
311
+ RubyBreaker does supports nested blocks as Ruby 1.9 finally allows them.
312
+ However, *keep in mind* that RubyBreaker *cannot* automatically document the
313
+ block types due to `yield` being a language construct rather than a method,
314
+ which means it cannot be captured by meta-programming!
315
+
316
+ ### Optional Argument Type and Variable-Length Argument Type
317
+
318
+ Another useful features of Ruby are the optional argument type and the
319
+ variable-length argument type. The former represents an argument that has a
320
+ default value (and therefore does not have to be provided). The latter
321
+ represents zero or more arguments of the same type. These are denoted by
322
+ suffices, `?` and `*`, respectively.
323
+
324
+ ### Method Type and Method List Types
325
+
326
+ Method type is similar to the block type, but it represents an actual method
327
+ and not a block object. It is the "root" type that the type annotation
328
+ language supports, along with method list types. Method _list_ type is a
329
+ collection of method types to represent more than one type information for
330
+ the given method. Why would this type be needed? Consider the following Ruby
331
+ code:
332
+
333
+ def foo(x)
334
+ case x
335
+ when Fixnum
336
+ 1
337
+ when String
338
+ "1"
339
+ end
340
+ end
341
+
342
+ There is no way to document the type of `foo` without using a method list
343
+ type. Let's try to give a method type to `foo` without a method list. The
344
+ closest we can come up with would be `foo(fixnum or string) -> fixnum and
345
+ string`. But RubyBreaker does not have the "and" type in the type annotation
346
+ language because it gives me an headache! (By the way, it needs to be an
347
+ "and" type because the caller must handle both `Fixnum` and `String` return
348
+ values.)
349
+
350
+ It is a dilemma because Ruby programmers actually enjoy using this kind of
351
+ dynamic type checks in their code. To alleviate this headache, RubyBreaker
352
+ supports the method list type to represent different scenarios depending on
353
+ the argument types. Thus, the `foo` method shown above can be given the
354
+ following method list type:
355
+
356
+ typesig("foo(fixnum) -> fixnum")
357
+ typesig("foo(string) -> string")
358
+
359
+ These two type signatures simply tell RubyBreaker that `foo` has two method
360
+ types--one for a `Fixnum` argument and another for a `String` argument.
361
+ Depending on the argument type, the return type is determined. In this
362
+ example, a `Fixnum` is returned when the argument is also a `Fixnum` and a
363
+ `String` is returned when the argument is also a `String`. When
364
+ automatically documenting such a type, RubyBreaker looks for the (subtyping)
365
+ compatibility between the return types and "promote" the method type to a
366
+ method list type by spliting the type signature into two (or more in
367
+ subsequent "promotions").
368
+
369
+ ## Type System
370
+
371
+ RubyBreaker comes with its own type system that is used to document type
372
+ information. This section describes how RubyBreaker determines which type(s)
373
+ to document. *More documentation coming soon...*
374
+
375
+ ### Subtyping
376
+
377
+ *Documentation coming soon...*
378
+
379
+ ### Subtyping vs. Subclassing
380
+
381
+ *Documentation coming soon...*
382
+
383
+ ### Pluggable Type System (Advanced)
384
+
385
+ Yes, RubyBreaker was designed with the replaceable type system in mind. In
386
+ other words, anyone can write his own type system and plug it into
387
+ RubyBreaker.
388
+
389
+ *Documentation coming soon...*
390
+
391
+ * * *
392
+
393
+ # Acknowledgment
394
+
395
+ The term, "Fusion Type," is first coined by Professor Michael W. Hicks at
396
+ University of Maryland and represents an object using a structural type with
397
+ respect to a nominal type.
398
+
399
+ * * *
400
+
401
+ # Copyright
402
+ Copyright (c) 2012 Jong-hoon (David) An. All Rights Reserved.
403
+
data/Rakefile ADDED
@@ -0,0 +1,90 @@
1
+ # This program runs tasks to generate RubyBreaker type parser and performs
2
+ # unit tests on individual modules in RubyBreaker.
3
+
4
+ require "rake"
5
+ require "rake/testtask"
6
+ require "rdoc/task"
7
+
8
+ begin
9
+ require "rdiscount" # used to generate the doc html page
10
+ rescue LoadError => e
11
+ puts "[WARNING] No rdiscount is installed."
12
+ end
13
+
14
+ # If no task specified, do test
15
+ task :default => [:test]
16
+
17
+ desc "Do all"
18
+ task :all => [:parser, :test, :rdoc, :webpage, :gem] do |t|
19
+ end
20
+
21
+ desc "Clean"
22
+ task :clean do |t|
23
+ sh 'rm -r -f html'
24
+ sh 'rm webpage/index.html'
25
+ sh 'rm lib/rubybreaker/type/type_grammar.rb'
26
+ end
27
+
28
+ desc "Generate gemspec"
29
+ task :gem do |t|
30
+ sh "gem build rubybreaker.gemspec"
31
+ end
32
+
33
+ Rake::RDocTask.new do |rd|
34
+ rd.rdoc_files.include("lib/**/*.rb")
35
+ rd.rdoc_files.exclude("lib/rubybreaker/rubylib/*.rb", "lib/rubybreaker/type/type_grammar.rb")
36
+ end
37
+
38
+ # desc "Generate RDoc"
39
+ # task :doc do |t|
40
+ # sh "rdoc -x lib/rubybreaker/rubylib -x lib/rubybreaker/type/type_grammar lib"
41
+ # end
42
+
43
+ desc "Generate the webpage"
44
+ task :webpage do |t|
45
+ break unless defined?(RDiscount)
46
+ dir = File.dirname(__FILE__)
47
+ readme_md = "#{dir}/README.md"
48
+ output = "#{dir}/webpage/index.html"
49
+ body = RDiscount.new(File.read(readme_md)).to_html
50
+ # header = <<-EOS
51
+ # <html>
52
+ # <head>
53
+ # <title>RubyBreaker</title>
54
+ # <LINK REL=StyleSheet HREF="rubybreaker.css" TYPE="text/css">
55
+ # <script type="text/javascript" src="generated_toc.js"> </script>
56
+ # </head>
57
+ # <body onLoad="createTOC()">
58
+ # <center>
59
+ # <div id="content">
60
+ # <div id="logo">
61
+ # <img src="images/logo.png" border="0">
62
+ # </div>
63
+ # <hr />
64
+ # <div id="generated-toc"></div>
65
+ # EOS
66
+ # footer = <<-EOS
67
+ # </div>
68
+ # </center>
69
+ # </body>
70
+ # </html>
71
+ # EOS
72
+ header = File.read("#{dir}/webpage/header.html")
73
+ footer = File.read("#{dir}/webpage/footer.html")
74
+ html = header + body + footer
75
+ File.open(output, "w") { |f| f.write(html) }
76
+ end
77
+
78
+ desc "Generate parser"
79
+ task :parser do |t|
80
+ # XXX: This is not really needed, would it perform better?
81
+ # sh "tt -f #{File.dirname(__FILE__)}/lib/rubybreaker/type/type_grammar.treetop"
82
+ end
83
+
84
+ desc "Run basic tests"
85
+ Rake::TestTask.new("test") do |t|
86
+ dir = File.dirname(__FILE__)
87
+ t.libs << "lib"
88
+ t.test_files = FileList["test/*.rb"]
89
+ end
90
+
data/TODO ADDED
@@ -0,0 +1,30 @@
1
+ # Variable-length Argument Type
2
+
3
+ Currently, the variable-length argument type cannot be auto-documented.
4
+ This can be done by looking at the method arity and initializes the method
5
+ type to have a variable-length argument type for the last argument.
6
+
7
+ # Module Methods and Class Methods
8
+
9
+ RubyBreaker only monitors instance methods at the moment.
10
+
11
+ # Dynamic Type Check
12
+
13
+ Although type documentation is the main purpose of RubyBreaker, this
14
+ documentation can be used to enforce correct types at runtime. It is often
15
+ helpful to find type errors earlier on during execution to narrow down the
16
+ root cause of the problem.
17
+
18
+ # Hybrid of Breakable and Broken
19
+
20
+ A module or class cannot be both Breakable and Broken. Once it is declared
21
+ to be Broken, it is Broken no matter what. In theory, it is possible to
22
+ selectively auto-document methods that are not yet documented.
23
+
24
+ # PERMANENT LIMITATIONS
25
+
26
+ * Block argument cannot be auto-documented.
27
+ * Manual modification (minimal) of code is required.
28
+ * No parametric polymorphic types are supported. The lack of this type is
29
+ not a bug but a feature! Polymorphic types are just headaches!
30
+
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env ruby
2
+ # This program creates a stub documentation for Ruby Core Library.
3
+
4
+ require "prettyprint"
5
+
6
+ BLACKLIST = [:Config, :Class, :Module]
7
+
8
+ def get_const(const_name, binding, visited)
9
+ return false if BLACKLIST.include?(:"#{const_name}")
10
+ validated = true
11
+ begin
12
+ const = eval(const_name.to_s, binding)
13
+ rescue
14
+ validated = false
15
+ end
16
+ return nil unless const.kind_of?(Module) && !visited.include?(const)
17
+ return const
18
+ end
19
+
20
+ def visit(pp, mod, binding, visited=[])
21
+ visited << mod
22
+ keyword = mod.kind_of?(Class) ? "class" : "module"
23
+ new_binding = eval("#{keyword} #{mod.name}; binding() end", binding)
24
+ pp.breakable()
25
+ pp.text("#{keyword} #{mod.name}")
26
+ pp.group(2) do
27
+ pp.breakable(";")
28
+ pp.text("include RubyBreaker::Broken")
29
+ inst_meths = mod.instance_methods(false).sort
30
+ inst_meths.each {|meth|
31
+ pp.breakable(";")
32
+ pp.text("typesig(\"#{meth}(?*) -> basic_object\")")
33
+ }
34
+ consts = mod.constants.sort
35
+ consts.each do |const_name|
36
+ const = get_const(const_name, new_binding, visited)
37
+ next unless const
38
+ pp.breakable()
39
+ visit(pp, const, new_binding, visited)
40
+ end
41
+ end
42
+ pp.breakable()
43
+ pp.text("end # of #{mod.name}")
44
+ pp.breakable(";")
45
+ end
46
+
47
+ def visit_toplevel(pp,visited=[])
48
+ visited << Object
49
+ Object.constants.sort.each do |const_name|
50
+ const = get_const(const_name, TOPLEVEL_BINDING, visited)
51
+ next unless const
52
+ pp.breakable()
53
+ visit(pp, const, TOPLEVEL_BINDING, visited)
54
+ end
55
+ end
56
+
57
+ str = ""
58
+ pp = PrettyPrint.new(str)
59
+ pp.text("# This file is auto-generated.")
60
+ pp.breakable()
61
+ visit_toplevel(pp)
62
+ pp.flush()
63
+ puts str.strip
64
+
data/bin/rubybreaker ADDED
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+ require "optparse"
3
+
4
+ dir = File.dirname(__FILE__)
5
+ require "#{dir}/../lib/rubybreaker"
6
+
7
+ module RubyBreaker
8
+
9
+ # This method is the main method for running RubyBreaker as a shell
10
+ # program.
11
+ def self.main()
12
+
13
+ option_parser = OptionParser.new do |opts|
14
+
15
+ opts.banner = "Usage: #{File.basename(__FILE__)} [options] in_file[.rb]"
16
+
17
+ opts.on("-f","--io-file FILE","Specify an input/output file") do |f|
18
+ OPTIONS[:output] = f
19
+ end
20
+
21
+ opts.on("-s","--[no-]stdout","Show output on the screen") do |b|
22
+ OPTIONS[:stdout] = b
23
+ end
24
+
25
+ opts.on("-a", "--[no-]append", "Append output to the input file") do |b|
26
+ OPTIONS[:append] = b
27
+ end
28
+
29
+ opts.on("-v","--verbose","Show messages in detail") do
30
+ OPTIONS[:verbose] = true
31
+ end
32
+
33
+ opts.on("--[no-]rubylib", "Use Core Ruby Library documentation") do |b|
34
+ OPTIONS[:rubylib] = b
35
+ end
36
+
37
+ opts.on("-h","--help","Show this help text") do
38
+ puts opts
39
+ exit
40
+ end
41
+
42
+ end
43
+
44
+ option_parser.parse!
45
+
46
+ OPTIONS[:mode] = :bin # indicate that RubyBreaker is being run as a
47
+ # binary (program).
48
+
49
+ puts COPYRIGHT
50
+ puts
51
+
52
+ # There has to be an input file
53
+ if ARGV.length < 1 then
54
+ puts option_parser.banner
55
+ exit(1)
56
+ end
57
+
58
+ Main.run()
59
+
60
+ end
61
+
62
+ end
63
+
64
+ RubyBreaker.main()
65
+
66
+
67
+