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.
- data/AUTHORS +7 -0
- data/LICENSE +26 -0
- data/README.md +403 -0
- data/Rakefile +90 -0
- data/TODO +30 -0
- data/bin/gen_stub_rubylib +64 -0
- data/bin/rubybreaker +67 -0
- data/lib/rubybreaker/context.rb +122 -0
- data/lib/rubybreaker/debug.rb +48 -0
- data/lib/rubybreaker/error.rb +59 -0
- data/lib/rubybreaker/rubylib/core.rb +2316 -0
- data/lib/rubybreaker/rubylib.rb +3 -0
- data/lib/rubybreaker/runtime/inspector.rb +57 -0
- data/lib/rubybreaker/runtime/monitor.rb +235 -0
- data/lib/rubybreaker/runtime/object_wrapper.rb +77 -0
- data/lib/rubybreaker/runtime/overrides.rb +42 -0
- data/lib/rubybreaker/runtime/pluggable.rb +57 -0
- data/lib/rubybreaker/runtime/type_placeholder.rb +27 -0
- data/lib/rubybreaker/runtime/type_system.rb +228 -0
- data/lib/rubybreaker/runtime/typesig_parser.rb +45 -0
- data/lib/rubybreaker/runtime.rb +103 -0
- data/lib/rubybreaker/test/testcase.rb +39 -0
- data/lib/rubybreaker/test.rb +1 -0
- data/lib/rubybreaker/type/type.rb +241 -0
- data/lib/rubybreaker/type/type_comparer.rb +143 -0
- data/lib/rubybreaker/type/type_grammar.treetop +285 -0
- data/lib/rubybreaker/type/type_unparser.rb +142 -0
- data/lib/rubybreaker/type.rb +2 -0
- data/lib/rubybreaker/typing/rubytype.rb +47 -0
- data/lib/rubybreaker/typing/subtyping.rb +480 -0
- data/lib/rubybreaker/typing.rb +3 -0
- data/lib/rubybreaker/util.rb +31 -0
- data/lib/rubybreaker.rb +193 -0
- data/test/integrated/tc_method_missing.rb +30 -0
- data/test/integrated/tc_simple1.rb +77 -0
- data/test/runtime/tc_obj_wrapper.rb +73 -0
- data/test/runtime/tc_typesig_parser.rb +33 -0
- data/test/ts_integrated.rb +4 -0
- data/test/ts_runtime.rb +5 -0
- data/test/ts_type.rb +5 -0
- data/test/ts_typing.rb +4 -0
- data/test/type/tc_comparer.rb +211 -0
- data/test/type/tc_parser.rb +219 -0
- data/test/type/tc_unparser.rb +276 -0
- data/test/typing/tc_rubytype.rb +63 -0
- data/test/typing/tc_typing.rb +219 -0
- data/webpage/footer.html +5 -0
- data/webpage/generated_toc.js +319 -0
- data/webpage/header.html +14 -0
- data/webpage/images/logo.png +0 -0
- data/webpage/index.html +439 -0
- data/webpage/rubybreaker.css +53 -0
- metadata +119 -0
data/AUTHORS
ADDED
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
|
+
|