rubybreaker 0.0.5 → 0.0.6
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/ABOUT.md +20 -0
- data/NEWS +5 -0
- data/README.md +16 -352
- data/Rakefile +30 -16
- data/TOPICS.md +55 -0
- data/TUTORIAL.md +291 -0
- data/VERSION +1 -1
- data/bin/rubybreaker +32 -14
- data/lib/rubybreaker/runtime/monitor.rb +1 -1
- data/lib/rubybreaker/runtime.rb +41 -21
- data/lib/rubybreaker/task.rb +15 -9
- data/lib/rubybreaker/test/rspec.rb +3 -3
- data/lib/rubybreaker/test/testcase.rb +3 -3
- data/lib/rubybreaker.rb +31 -16
- data/test/integrated/{tc_both_broken_breakable.rb → tc_both_documented_and_undocumented.rb} +3 -4
- data/test/integrated/tc_class_methods.rb +1 -1
- data/test/integrated/tc_inherit_broken.rb +1 -1
- data/test/integrated/tc_method_missing.rb +1 -1
- data/test/integrated/tc_namespace.rb +1 -1
- data/test/integrated/tc_simple1.rb +1 -1
- data/test/testtask/tc_testtask.rb +2 -2
- data/test/ts_integrated.rb +1 -1
- data/test/ts_rspec.rb +1 -1
- data/webpage/about.html +50 -0
- data/webpage/footer.html +6 -1
- data/webpage/header.html +9 -3
- data/webpage/images/logo.png +0 -0
- data/webpage/images/title.png +0 -0
- data/webpage/index.html +31 -367
- data/webpage/rdoc/Object.html +3 -103
- data/webpage/rdoc/Rake/RubyBreakerTestTask.html +80 -18
- data/webpage/rdoc/Rake.html +3 -7
- data/webpage/rdoc/RubyBreaker/Breakable.html +4 -8
- data/webpage/rdoc/RubyBreaker/Broken.html +4 -8
- data/webpage/rdoc/RubyBreaker/Context.html +3 -7
- data/webpage/rdoc/RubyBreaker/Errors/InternalError.html +3 -7
- data/webpage/rdoc/RubyBreaker/Errors/InvalidSubtypeCheck.html +3 -7
- data/webpage/rdoc/RubyBreaker/Errors/InvalidTypeConstruction.html +3 -7
- data/webpage/rdoc/RubyBreaker/Errors/SubtypeFailure.html +3 -7
- data/webpage/rdoc/RubyBreaker/Errors/TypeError.html +3 -7
- data/webpage/rdoc/RubyBreaker/Errors/UserError.html +3 -7
- data/webpage/rdoc/RubyBreaker/Errors.html +3 -7
- data/webpage/rdoc/RubyBreaker/ObjectPosition.html +3 -7
- data/webpage/rdoc/RubyBreaker/Position.html +3 -7
- data/webpage/rdoc/RubyBreaker/RDocSupport.html +3 -7
- data/webpage/rdoc/RubyBreaker/RubyTypeUtils.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/Inspector.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/MethodInfo.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/Monitor.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/MonitorInstaller.html +10 -14
- data/webpage/rdoc/RubyBreaker/Runtime/MonitorSwitch.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/MonitorUtils.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/ObjectWrapper.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/Pluggable.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/TypeSigParser.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/TypeSigUnparser.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/TypeSystem.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime.html +42 -39
- data/webpage/rdoc/RubyBreaker/TypeComparer.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/AnyType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/BlockType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/DuckType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/FusionType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/MethodListType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/MethodType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/NilType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/NominalType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/OptionalType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/OrType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/SelfType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/Type.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/VarLengthType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeUnparser.html +3 -7
- data/webpage/rdoc/RubyBreaker/Typing.html +3 -7
- data/webpage/rdoc/RubyBreaker/Util.html +3 -7
- data/webpage/rdoc/RubyBreaker.html +48 -15
- data/webpage/rdoc/Test/Unit.html +3 -7
- data/webpage/rdoc/Test.html +3 -7
- data/webpage/rdoc/created.rid +18 -17
- data/webpage/rdoc/index.html +3 -7
- data/webpage/rdoc/js/search_index.js +1 -1
- data/webpage/rdoc/table_of_contents.html +28 -36
- data/webpage/rubybreaker.css +8 -6
- data/webpage/topics.html +85 -0
- data/webpage/tutorial.html +331 -0
- metadata +14 -8
- data/lib/rubybreaker/doc.rb +0 -3
- data/webpage/rdoc/Kernel.html +0 -286
- data/webpage/rdoc/Test/Unit/TestCase.html +0 -309
data/ABOUT.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# About
|
|
2
|
+
|
|
3
|
+
RubyBreaker has its root in Rubydust (which stands for Ruby Dynamic
|
|
4
|
+
Unraveling of Static Types), an academic research project designed and
|
|
5
|
+
implemented at University of Maryland. However, unlike Rubydust,
|
|
6
|
+
RubyBreaker aims to be a practical documentation tool for Ruby rather than a
|
|
7
|
+
full-scale type inference tool. Although it is certainly possible that
|
|
8
|
+
RubyBreaker evolves into something more solid in its type system, the
|
|
9
|
+
primary goal of this project is to help Ruby programmers practically as much
|
|
10
|
+
as possible.
|
|
11
|
+
|
|
12
|
+
## Acknowledgment
|
|
13
|
+
|
|
14
|
+
The term, "Fusion Type," is first coined by Professor Michael W. Hicks at
|
|
15
|
+
University of Maryland and represents an object using a structural type with
|
|
16
|
+
respect to a nominal type.
|
|
17
|
+
|
|
18
|
+
## Copyright
|
|
19
|
+
Copyright (c) 2012 Jong-hoon (David) An. All Rights Reserved.
|
|
20
|
+
|
data/NEWS
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# VERSION 0.0.6
|
|
2
|
+
* Running RubyBreaker in shell mode does not require manual code change.
|
|
3
|
+
* Official RubyBreaker logo!
|
|
4
|
+
* Deprecating breakable(). Use break() instead.
|
|
5
|
+
|
|
1
6
|
# VERSION 0.0.5
|
|
2
7
|
* Rake::RubyBreakerTestTask is supported.
|
|
3
8
|
* Manual modification is no longer required if run as a Rake task.
|
data/README.md
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
* * *
|
|
2
|
-
|
|
3
1
|
# Introduction
|
|
4
2
|
|
|
5
3
|
RubyBreaker is a dynamic type documentation tool written in pure Ruby. It
|
|
6
4
|
provides the framework for dynamically instrumenting a Ruby program to
|
|
7
|
-
monitor objects during
|
|
5
|
+
monitor objects during the execution and document the observed type
|
|
8
6
|
information. In other words, RubyBreaker "breaks" Ruby code out of its
|
|
9
|
-
obscurity and wildness (as in "code breaking" or "horse
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
obscurity and wildness (as in "code breaking" or "horse breaking") by
|
|
8
|
+
auto-documenting type information. The type documentation generated by
|
|
9
|
+
RubyBreaker is also an executable Ruby code that can be used as an input to
|
|
10
|
+
subsequent analyses.
|
|
13
11
|
|
|
14
12
|
The primary goal of RubyBreaker is to assign a type signature to every
|
|
15
13
|
method in selected modules and classes. A type signature is written in the
|
|
16
14
|
RubyBreaker Type Annotation Language which resembles the documentation style
|
|
17
|
-
used in Ruby
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
used in Ruby Core Library Doc. No manual code change is required. Overall,
|
|
16
|
+
this tool should help Ruby programmers document their code more rigorously
|
|
17
|
+
and effectively.
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
Currently, RubyBreaker *cannot*
|
|
22
20
|
|
|
23
|
-
* Auto-
|
|
24
|
-
*
|
|
25
|
-
*
|
|
21
|
+
* Auto-document block arguments (inherent)
|
|
22
|
+
* Perform early dynamic type checks
|
|
23
|
+
* Support parametric polymorphic types
|
|
24
|
+
* Support RDoc or YARD output format
|
|
26
25
|
|
|
27
26
|
To contribute to the project, visit RubyBreaker's
|
|
28
27
|
[GitHub page](http://github.com/rockalizer/rubybreaker) and
|
|
29
|
-
[RubyGems page](http://rubygems.org/gems/rubybreaker).
|
|
30
|
-
be found
|
|
28
|
+
[RubyGems page](http://rubygems.org/gems/rubybreaker). The web version of
|
|
29
|
+
this document can be found
|
|
30
|
+
[here](http://rockalizer.webfactional.com/projects/rubybreaker).
|
|
31
31
|
|
|
32
32
|
## Requirements
|
|
33
33
|
|
|
@@ -43,339 +43,3 @@ It is as simple as running the following command:
|
|
|
43
43
|
|
|
44
44
|
$ gem install rubybreaker
|
|
45
45
|
|
|
46
|
-
* * *
|
|
47
|
-
|
|
48
|
-
# Tutorial
|
|
49
|
-
|
|
50
|
-
This tutorial will describe the basic usage of the tool, the RubyBreaker
|
|
51
|
-
Type Annotation Language, and the RubyBreaker Type System.
|
|
52
|
-
|
|
53
|
-
## Usage
|
|
54
|
-
|
|
55
|
-
RubyBreaker takes advantage of test cases that already come with the source
|
|
56
|
-
program. It is recommended that RubyBreaker is run as a Rake task, which
|
|
57
|
-
requires a minimum code change in the Rakefile and no code change in the
|
|
58
|
-
source program. If not used as a Rake task, it requires a minimum code
|
|
59
|
-
change in each test case or the source program but should not affect the
|
|
60
|
-
development process much. Let's briefly see how RubyBreaker can be run
|
|
61
|
-
directly as a command-line program to understand the general concept of the
|
|
62
|
-
tool. We will explain how to use RubyBreaker in a Rakefile later.
|
|
63
|
-
|
|
64
|
-
$ rubybreaker -v prog.rb
|
|
65
|
-
|
|
66
|
-
This runs RubyBreaker in verbose mode on `prog.rb`. Note that RubyBreaker
|
|
67
|
-
will actually run `prog.rb` (by simply `require`ing the program file).
|
|
68
|
-
Somewhere in the program, there has to be a _program entry point_ to
|
|
69
|
-
indicate where the _monitoring_ of objects starts. Let's assume `prog.rb`
|
|
70
|
-
as the following:
|
|
71
|
-
|
|
72
|
-
require "rubybreaker" # required if using "ruby" instead
|
|
73
|
-
class A
|
|
74
|
-
def foo(x)
|
|
75
|
-
x.to_s
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
class B
|
|
79
|
-
def bar(y,z)
|
|
80
|
-
y.foo(z)
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
RubyBreaker.run(A, B)
|
|
84
|
-
A.new.foo(1)
|
|
85
|
-
|
|
86
|
-
This example will show how `A#foo` method is given a type by RubyBreaker.
|
|
87
|
-
After running `rubybreaker -v prog.rb`, the following output will be
|
|
88
|
-
generated and saved into `prog.rubybreaker.rb`.
|
|
89
|
-
|
|
90
|
-
# This file is auto-generated by RubyBreaker
|
|
91
|
-
require "rubybreaker"
|
|
92
|
-
class A
|
|
93
|
-
typesig("foo(fixnum[to_s]) -> string")
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
Here, the `typesig` method call registers `foo` as a method type that takes
|
|
97
|
-
an object that has `Fixnum#to_s` method and returns a `String`. This
|
|
98
|
-
method is made available by importing `rubybreaker`. Now, assume that an
|
|
99
|
-
additional code, `B.new.bar(A.new,1)`, is added at the end of `prog.rb`. The
|
|
100
|
-
subsequent run will generate the following result:
|
|
101
|
-
|
|
102
|
-
# This file is auto-generated by RubyBreaker
|
|
103
|
-
require "rubybreaker"
|
|
104
|
-
class A
|
|
105
|
-
typesig("foo(fixnum[to_s]) -> string")
|
|
106
|
-
end
|
|
107
|
-
class B
|
|
108
|
-
typesig("bar(a[foo], fixnum[to_s]) -> string")
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
Keep in mind that RubyBreaker is designed to gather type information based
|
|
112
|
-
on the _actual_ execution of the source program. This means the program
|
|
113
|
-
should be equipped with test cases that have a reasonable program path
|
|
114
|
-
coverage. Additionally, RubyBreaker assumes that test runs are correct and
|
|
115
|
-
the program behaves correctly (for those test runs) as intended by the
|
|
116
|
-
programmer. This assumption is not a strong requirement, but is necessary to
|
|
117
|
-
obtain precise and accurate type information.
|
|
118
|
-
|
|
119
|
-
### Using Ruby Unit Testing Framework
|
|
120
|
-
|
|
121
|
-
Instead of manually inserting the entry point indicator into the program,
|
|
122
|
-
you can take advantage of Ruby's built-in testing framework. This is
|
|
123
|
-
preferred to modifying the source program directly, especially for the long
|
|
124
|
-
term program maintainability. But no worries! This method is as simple as
|
|
125
|
-
the previous one.
|
|
126
|
-
|
|
127
|
-
require "test/unit"
|
|
128
|
-
require "rubybreaker" # This should come after test/unit.
|
|
129
|
-
class TestClassA < Test::Unit::TestCase
|
|
130
|
-
def setup()
|
|
131
|
-
RubyBreaker.breakable(Class1, Class2, ...)
|
|
132
|
-
...
|
|
133
|
-
end
|
|
134
|
-
# ...tests!...
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
That's it! The only requirements are to indicate to RubyBreaker which modules
|
|
138
|
-
and classes to "break" and to place `require rubybreaker` _after_
|
|
139
|
-
`require test/unit`.
|
|
140
|
-
|
|
141
|
-
### Using RSpec
|
|
142
|
-
|
|
143
|
-
The requirement is same for RSpec but use `before` instead of `setup` to
|
|
144
|
-
specify which modules and classes to "break".
|
|
145
|
-
|
|
146
|
-
require "rspec"
|
|
147
|
-
require "rubybreaker"
|
|
148
|
-
|
|
149
|
-
describe "TestClassA Test"
|
|
150
|
-
before { RubyBreaker.breakable(Class1, Class2, ...) }
|
|
151
|
-
...
|
|
152
|
-
# ...tests!...
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
### Using Rakefile
|
|
156
|
-
|
|
157
|
-
By running RubyBreaker along with the Rakefile, you can avoid modifying the
|
|
158
|
-
source program at all. (You no longer need to import `rubybreaker` in the
|
|
159
|
-
test cases neither.) Therefore, this is the recommended way to use
|
|
160
|
-
RubyBreaker. The following code snippet describes how it can be done:
|
|
161
|
-
|
|
162
|
-
require "rubybreaker/task"
|
|
163
|
-
...
|
|
164
|
-
desc "Run RubyBreaker"
|
|
165
|
-
Rake::RubyBreakerTestTask.new(:"rubybreaker") do |t|
|
|
166
|
-
t.libs << "lib"
|
|
167
|
-
t.test_files = ["test/foo/tc_foo1.rb"]
|
|
168
|
-
# ...Other test task options..
|
|
169
|
-
t.rubybreaker_opts << "-v" # run in verbose mode
|
|
170
|
-
t.breakable = ["Class1", "Class2", ...] # specify what to monitor
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
Note that `RubyBrakerTestTask` can simply replace your `TestTask` block in
|
|
174
|
-
Rakefile. In fact, the former is a subclass of the latter and includes all
|
|
175
|
-
features supported by the latter. The only additional options are
|
|
176
|
-
`rubybreaker_opts` which is RubyBreaker's command-line options and
|
|
177
|
-
`breakable` which specifies which modules and classes to monitor. Since
|
|
178
|
-
`Class1` and `Class2` are not _recognized_ by this Rakefile, you must use
|
|
179
|
-
string literals to specify modules and classes (and with full namespace).
|
|
180
|
-
|
|
181
|
-
If this is the route you are taking, there needs no editing of the source
|
|
182
|
-
program whatsoever. This task will take care of instrumenting the specified
|
|
183
|
-
modules and classes at proper moments.
|
|
184
|
-
|
|
185
|
-
## Type Annotation
|
|
186
|
-
|
|
187
|
-
The annotation language used in RubyBreaker resembles the method
|
|
188
|
-
documentation used by Ruby Standard Library Doc. Each type signature
|
|
189
|
-
defines a method type using the name, argument types, block type, and return
|
|
190
|
-
type. But, let us consider a simple case where there is one argument type
|
|
191
|
-
and a return type.
|
|
192
|
-
|
|
193
|
-
class A
|
|
194
|
-
...
|
|
195
|
-
typesig("foo(fixnum) -> string")
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
In RubyBreaker, a type signature is recognized by the meta-class level
|
|
199
|
-
method `typesig` which takes a string as an argument. This string is the
|
|
200
|
-
actual type signature written in the Ruby Type Annotation Language. This
|
|
201
|
-
language is designed to reflect the common documentation practice used by
|
|
202
|
-
RubyDoc. It starts with the name of the method. In the above example, `foo`
|
|
203
|
-
is currently being given a type. The rest of the signature takes a typical
|
|
204
|
-
method type symbol, `(x) -> y` where `x` is the argument type and `y` is the
|
|
205
|
-
return type. In the example shown above, the method takes a `Fixnum` object
|
|
206
|
-
and returns a `String` object. Note that these types are in lowercase,
|
|
207
|
-
indicating they are objects and not modules or classes themselves.
|
|
208
|
-
|
|
209
|
-
There are several types that represent an object: nominal, duck, fusion,
|
|
210
|
-
nil, 'any', 'or', optional, variable-length, and block. Each type signature
|
|
211
|
-
itself represents a method type or a method list type (explained below).
|
|
212
|
-
|
|
213
|
-
### Nominal Type
|
|
214
|
-
|
|
215
|
-
This is the simplest and most intuitive way to represent an object. For
|
|
216
|
-
instance, `fixnum` is an object of type `Fixnum`. Use lower-case letters and
|
|
217
|
-
underscores instead of _camelized_ name. `MyClass`, for example would be
|
|
218
|
-
`my_class` in RubyBreaker type signatures. There is no particular
|
|
219
|
-
reason for this convention other than it is the common practice used in
|
|
220
|
-
RubyDoc. Use `/` to indicate the namespace delimiter `::`. For example,
|
|
221
|
-
`NamspaceA::ClassB` would be represented by `namespace_a/class_b` in
|
|
222
|
-
a RubyBreaker type signature.
|
|
223
|
-
|
|
224
|
-
### Self Type
|
|
225
|
-
|
|
226
|
-
This type is similar to the nominal type but is referring to the current
|
|
227
|
-
object--that is, the receiver of the method being typed. RubyBreaker will
|
|
228
|
-
auto-document the return type as a self type if the return value is the same
|
|
229
|
-
as the receiver of that call. It is also recommended to use this type over
|
|
230
|
-
a nominal type (if the return value is `self`) since it depicts more
|
|
231
|
-
precise return type.
|
|
232
|
-
|
|
233
|
-
### Duck Type
|
|
234
|
-
|
|
235
|
-
This type is inspired by the Ruby Language's duck typing, _"if it
|
|
236
|
-
walks like a duck and quacks like a duck, it must be a duck."_ Using this
|
|
237
|
-
type, an object can be represented simply by a list of method names. For
|
|
238
|
-
example `[walks, quacks]` is an object that has `walks` and `quacks`
|
|
239
|
-
methods. Note that these method names do *not* reveal any type
|
|
240
|
-
information for themselves.
|
|
241
|
-
|
|
242
|
-
### Fusion Type
|
|
243
|
-
|
|
244
|
-
Duck type is very flexible but can be too lenient when trying to restrict
|
|
245
|
-
the type of an object. RubyBreaker provides a type called *the fusion type*
|
|
246
|
-
which lists method names but with respect to a nominal type. For
|
|
247
|
-
example, `fixnum[to_f, to_s]` represents an object that has methods `to_f`
|
|
248
|
-
and `to_s` whose types are same as those of `Fixnum`. This is more
|
|
249
|
-
restrictive (precise) than `[to_f, to_s]` because the two methods must have
|
|
250
|
-
the same types as `to_f` and `to_s` methods, respectively, in `Fixnum`.
|
|
251
|
-
|
|
252
|
-
### Nil Type
|
|
253
|
-
|
|
254
|
-
A nil type represents a value of nil and is denoted by `nil`.
|
|
255
|
-
|
|
256
|
-
### Any Type
|
|
257
|
-
|
|
258
|
-
RubyBreaker also provides a way to represent an object that is compatible with
|
|
259
|
-
any type. This type is denoted by `?`. Use caution with this type because
|
|
260
|
-
it should be only used for an object that requires an arbitrary yet most
|
|
261
|
-
specific type--that is, `?` is a subtype of any other type, but any
|
|
262
|
-
other type is not a subtype of `?`. This becomes a bit complicated for
|
|
263
|
-
method or block argument types because of their contra-variance
|
|
264
|
-
characteristic. Please refer to the section *Subtyping*.
|
|
265
|
-
|
|
266
|
-
### Or Type
|
|
267
|
-
|
|
268
|
-
Any above types can be "or"ed together, using `||`, to represent an object
|
|
269
|
-
that can be either one or the other. It _does_ not represent an object that
|
|
270
|
-
has to be both (which is not supported by RubyBreaker).
|
|
271
|
-
|
|
272
|
-
### Optional Argument Type and Variable-Length Argument Type
|
|
273
|
-
|
|
274
|
-
Another useful features of Ruby are the optional argument type and the
|
|
275
|
-
variable-length argument type. The former represents an argument that has a
|
|
276
|
-
default value (and therefore does not have to be provided). The latter
|
|
277
|
-
represents zero or more arguments of the same type. These are denoted by
|
|
278
|
-
suffices, `?` and `*`, respectively.
|
|
279
|
-
|
|
280
|
-
### Block Type
|
|
281
|
-
|
|
282
|
-
One of the Ruby's prominent features is the block argument. It allows
|
|
283
|
-
the caller to pass in a piece of code to be executed inside the callee. This
|
|
284
|
-
code block can be executed by the Ruby construct, `yield`, or by directly
|
|
285
|
-
calling the `call` method of the block object. In RubyBreaker, this type can
|
|
286
|
-
be respresented by curly brackets. For instance, `{|fixnum,string| ->
|
|
287
|
-
string}` represents a block that takes two arguments--one `Fixnum` and one
|
|
288
|
-
`String`--and returns a `String`.
|
|
289
|
-
|
|
290
|
-
RubyBreaker does supports nested blocks as Ruby 1.9 finally allows them.
|
|
291
|
-
However, *keep in mind* that RubyBreaker *cannot* automatically document the
|
|
292
|
-
block types due to `yield` being a language construct rather than a method,
|
|
293
|
-
which means it cannot be captured by meta-programming!
|
|
294
|
-
|
|
295
|
-
### Method Type and Method List Types
|
|
296
|
-
|
|
297
|
-
Method type is similar to the block type, but it represents an actual method
|
|
298
|
-
and not a block object. It is the "root" type that the type annotation
|
|
299
|
-
language supports, along with method list types. Method _list_ type is a
|
|
300
|
-
collection of method types to represent more than one type information for
|
|
301
|
-
the given method. Why would this type be needed? Consider the following Ruby
|
|
302
|
-
code:
|
|
303
|
-
|
|
304
|
-
def foo(x)
|
|
305
|
-
case x
|
|
306
|
-
when Fixnum
|
|
307
|
-
1
|
|
308
|
-
when String
|
|
309
|
-
"1"
|
|
310
|
-
end
|
|
311
|
-
end
|
|
312
|
-
|
|
313
|
-
There is no way to document the type of `foo` without using a method list
|
|
314
|
-
type. Let's try to give a method type to `foo` without a method list. The
|
|
315
|
-
closest we can come up with would be `foo(fixnum or string) -> fixnum and
|
|
316
|
-
string`. But RubyBreaker does not have the "and" type in the type annotation
|
|
317
|
-
language because it gives me an headache! (By the way, it needs to be an
|
|
318
|
-
"and" type because the caller must handle both `Fixnum` and `String` return
|
|
319
|
-
values.)
|
|
320
|
-
|
|
321
|
-
It is a dilemma because Ruby programmers actually enjoy using this kind of
|
|
322
|
-
dynamic type checks in their code. To alleviate this headache, RubyBreaker
|
|
323
|
-
supports the method list type to represent different scenarios depending on
|
|
324
|
-
the argument types. Thus, the `foo` method shown above can be given the
|
|
325
|
-
following method list type:
|
|
326
|
-
|
|
327
|
-
typesig("foo(fixnum) -> fixnum")
|
|
328
|
-
typesig("foo(string) -> string")
|
|
329
|
-
|
|
330
|
-
These two type signatures simply tell RubyBreaker that `foo` has two method
|
|
331
|
-
types--one for a `Fixnum` argument and another for a `String` argument.
|
|
332
|
-
Depending on the argument type, the return type is determined. In this
|
|
333
|
-
example, a `Fixnum` is returned when the argument is also a `Fixnum` and a
|
|
334
|
-
`String` is returned when the argument is also a `String`. When
|
|
335
|
-
automatically documenting such a type, RubyBreaker looks for the (subtyping)
|
|
336
|
-
compatibility between the return types and "promote" the method type to a
|
|
337
|
-
method list type by spliting the type signature into two (or more in
|
|
338
|
-
subsequent "promotions").
|
|
339
|
-
|
|
340
|
-
## Type System
|
|
341
|
-
|
|
342
|
-
RubyBreaker comes with its own type system to auto-document the type
|
|
343
|
-
information. Each method in a "breakable" module is dynamically instrumented
|
|
344
|
-
to be monitored during runtime. This monitoring code observes the types of
|
|
345
|
-
the arguments, block, and return value of each method. Once this information
|
|
346
|
-
is gathered, RubyBreaker will compare it to the information gathered so far.
|
|
347
|
-
If these two types are "compatiable", RubyBreaker will choose more general
|
|
348
|
-
type of the two. Otherwise, RubyBreaker will use the method list type to
|
|
349
|
-
accommodate two "incompatible" types.
|
|
350
|
-
|
|
351
|
-
### Subtyping and Subclassing
|
|
352
|
-
|
|
353
|
-
RubyBreaker uses subtyping to choose one from the two "compatible" types.
|
|
354
|
-
Two types are "compatible" if one is subtype of another. This means that the
|
|
355
|
-
_subtype_ can be represented using the _supertype_ instead. This is why
|
|
356
|
-
RubyBrekaer chooses the latter to document both types. RubyBreaker relies on
|
|
357
|
-
subclassing of Ruby to determine a subtyping relationship between two types.
|
|
358
|
-
For example, `Fixnum` is considered to be subtype of `Numeric` since the
|
|
359
|
-
former is subclass of the latter. (Strictly speaking, `Fixnum` is not really
|
|
360
|
-
subtype of `Numeric` because some methods are overriden in `Fixnum` with
|
|
361
|
-
method types that are not subtype of the counterparts in `Numeric`. But,
|
|
362
|
-
RubyBreaker is lenient and considers them compatible--that is, `Numeric` can
|
|
363
|
-
represent any `Fixnum`.
|
|
364
|
-
|
|
365
|
-
### Pluggable Type System (Advanced)
|
|
366
|
-
|
|
367
|
-
Yes, RubyBreaker was designed with the replaceable type system in mind. In
|
|
368
|
-
other words, anyone can write his own type system and plug it into
|
|
369
|
-
RubyBreaker. *Technical documentation coming soon...*
|
|
370
|
-
|
|
371
|
-
* * *
|
|
372
|
-
|
|
373
|
-
# Acknowledgment
|
|
374
|
-
|
|
375
|
-
The term, "Fusion Type," is first coined by Professor Michael W. Hicks at
|
|
376
|
-
University of Maryland and represents an object using a structural type with
|
|
377
|
-
respect to a nominal type.
|
|
378
|
-
|
|
379
|
-
# Copyright
|
|
380
|
-
Copyright (c) 2012 Jong-hoon (David) An. All Rights Reserved.
|
|
381
|
-
|
data/Rakefile
CHANGED
|
@@ -19,6 +19,16 @@ rescue LoadError => e
|
|
|
19
19
|
puts "[WARNING] No rspec-core is installed on this computer."
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
# This method generates html pages with header and footer.
|
|
23
|
+
def gen_page(md_file, html_file)
|
|
24
|
+
dir = File.dirname(__FILE__)
|
|
25
|
+
header = File.read("#{dir}/webpage/header.html")
|
|
26
|
+
footer = File.read("#{dir}/webpage/footer.html")
|
|
27
|
+
body = RDiscount.new(File.read(md_file)).to_html
|
|
28
|
+
html = header + body + footer
|
|
29
|
+
File.open(html_file, "w") { |f| f.write(html) }
|
|
30
|
+
end
|
|
31
|
+
|
|
22
32
|
# Use rake/clean to remove generated files
|
|
23
33
|
CLEAN.concat(FileList["webpage/rdoc",
|
|
24
34
|
"rubybreaker-*.gem",
|
|
@@ -29,38 +39,42 @@ CLEAN.concat(FileList["webpage/rdoc",
|
|
|
29
39
|
# If no task specified, do test
|
|
30
40
|
task :default => [:test, :testtask_test]
|
|
31
41
|
|
|
42
|
+
# The complete list of tasks
|
|
43
|
+
all_tasks = [:parser,
|
|
44
|
+
:test,
|
|
45
|
+
:testtask_test,
|
|
46
|
+
:rspec,
|
|
47
|
+
:rdoc,
|
|
48
|
+
:webpage,
|
|
49
|
+
:gem]
|
|
50
|
+
|
|
51
|
+
# Remove rspec if RSpec is not defined (rspec is not installed)
|
|
52
|
+
all_tasks.delete(:rspec) if !defined?(RSpec)
|
|
53
|
+
|
|
32
54
|
desc "Do all"
|
|
33
|
-
task :all =>
|
|
34
|
-
:test,
|
|
35
|
-
:testtask_test,
|
|
36
|
-
:rspec,
|
|
37
|
-
:rdoc,
|
|
38
|
-
:webpage,
|
|
39
|
-
:gem] do |t|
|
|
40
|
-
end
|
|
55
|
+
task :all => all_tasks
|
|
41
56
|
|
|
42
57
|
desc "Generate gemspec"
|
|
43
58
|
task :gem do |t|
|
|
44
59
|
sh "gem build rubybreaker.gemspec"
|
|
45
60
|
end
|
|
46
61
|
|
|
62
|
+
desc "Generate RDoc"
|
|
47
63
|
Rake::RDocTask.new do |rd|
|
|
48
64
|
rd.rdoc_dir = "#{File.dirname(__FILE__)}/webpage/rdoc"
|
|
49
65
|
rd.rdoc_files.include("lib/**/*.rb")
|
|
50
66
|
rd.rdoc_files.exclude("lib/rubybreaker/type/type_grammar.rb")
|
|
67
|
+
rd.options << "README.md" << "TUTORIAL.md"
|
|
51
68
|
end
|
|
52
69
|
|
|
53
70
|
desc "Generate the webpage"
|
|
54
71
|
task :webpage do |t|
|
|
55
72
|
if defined?(RDiscount)
|
|
56
73
|
dir = File.dirname(__FILE__)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
footer = File.read("#{dir}/webpage/footer.html")
|
|
62
|
-
html = header + body + footer
|
|
63
|
-
File.open(output, "w") { |f| f.write(html) }
|
|
74
|
+
gen_page("#{dir}/README.md","#{dir}/webpage/index.html")
|
|
75
|
+
gen_page("#{dir}/TUTORIAL.md","#{dir}/webpage/tutorial.html")
|
|
76
|
+
gen_page("#{dir}/TOPICS.md","#{dir}/webpage/topics.html")
|
|
77
|
+
gen_page("#{dir}/ABOUT.md","#{dir}/webpage/about.html")
|
|
64
78
|
end
|
|
65
79
|
end
|
|
66
80
|
|
|
@@ -84,7 +98,7 @@ desc "Run rubybreaker testtask test"
|
|
|
84
98
|
Rake::RubyBreakerTestTask.new(:"testtask_test") do |t|
|
|
85
99
|
t.libs << "lib" << "test/tc_testtask/sample.rb"
|
|
86
100
|
t.test_files = ["test/testtask/tc_testtask.rb"]
|
|
87
|
-
t.
|
|
101
|
+
t.break = ["SampleClassA"]
|
|
88
102
|
end
|
|
89
103
|
|
|
90
104
|
if defined?(RSpec)
|
data/TOPICS.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Advanced Topics
|
|
2
|
+
|
|
3
|
+
## RubyBreaker Type System
|
|
4
|
+
|
|
5
|
+
RubyBreaker comes with its own type system to auto-document the type
|
|
6
|
+
information. Each method in a "breakable" module is dynamically instrumented
|
|
7
|
+
to be monitored during runtime. This monitoring code observes the types of
|
|
8
|
+
the arguments, block, and return value of each method. Once type information
|
|
9
|
+
for a method is gathered, RubyBreaker will compare it to the information
|
|
10
|
+
gathered so far for the method. If these two method types are
|
|
11
|
+
"compatiable", RubyBreaker will choose more general type from the two.
|
|
12
|
+
|
|
13
|
+
If two method types are not "compatible", RubyBreaker will "promote" the
|
|
14
|
+
method type to a method list type to accommodate more than one
|
|
15
|
+
"incompatible" types. Let's first understand what RubyBreaker considers two
|
|
16
|
+
types as "compatible".
|
|
17
|
+
|
|
18
|
+
### Subtyping and Subclassing
|
|
19
|
+
|
|
20
|
+
RubyBreaker uses _subtyping_ to determine two "compatible" method types,
|
|
21
|
+
which holds true if one is subtype or supertype of another. It chooses
|
|
22
|
+
the supertype of the two for the method because the objective is to find (1)
|
|
23
|
+
the most general type of the two and (2) the least general type that can
|
|
24
|
+
handle both. Note that, if the objective (2) is not required, we can
|
|
25
|
+
always use the most general method type:
|
|
26
|
+
|
|
27
|
+
(?*) -> basic_object
|
|
28
|
+
|
|
29
|
+
A method of this type takes any number of any objects and returns a
|
|
30
|
+
`BasicObject`. But this is _too_ general to use for type documetation.
|
|
31
|
+
Instead, we want to find the most specific general type possible (and
|
|
32
|
+
therefore, the least upper bound of the types observed for the method).
|
|
33
|
+
|
|
34
|
+
For simplicity (and practicality), RubyBreaker uses _subclassing_ to
|
|
35
|
+
determine subtyping for nominal types. For instance, `Fixnum` is considered
|
|
36
|
+
subtype of `Numeric` because the former is subclass of the latter. However,
|
|
37
|
+
keep in mind this is not necessarily true in the true subtyping theory
|
|
38
|
+
because some methods in the former override the counterparts in the latter,
|
|
39
|
+
resulting in different types that no longer hold the subtyping relationship.
|
|
40
|
+
|
|
41
|
+
If a method has some type information either from the manual documentation
|
|
42
|
+
or from the current documentation process, this information will be used
|
|
43
|
+
in addition to subclassing. For example, consider method types
|
|
44
|
+
`foo(class1[to_s]) -> string` and `foo(class2[to_s]) -> string`. Let's also
|
|
45
|
+
assume that classes `Class1` and `Class2` are being auto-documented. The
|
|
46
|
+
method `foo` will have the type `foo(class1[to_s])` if `Class1#to_s` is a
|
|
47
|
+
subtype of `Class2#to_s`. (The direction is not mistaken, it is due to the
|
|
48
|
+
contra-variant property of a method argument.) If these classes are neither
|
|
49
|
+
being auto-documented nor manaully documented, this holds only if `Class1`
|
|
50
|
+
is subclass of `Class2`.
|
|
51
|
+
|
|
52
|
+
## Writing a Custom Type System
|
|
53
|
+
|
|
54
|
+
_Coming soon_
|
|
55
|
+
|