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
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
+