tco_method 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -0
- data/.travis.yml +0 -0
- data/Gemfile +0 -0
- data/Guardfile +0 -0
- data/LICENSE +0 -0
- data/README.md +102 -27
- data/Rakefile +0 -0
- data/lib/tco_method.rb +33 -43
- data/lib/tco_method/ambiguous_source_error.rb +42 -0
- data/lib/tco_method/block_extractor.rb +107 -0
- data/lib/tco_method/block_with_tco.rb +22 -0
- data/lib/tco_method/method_info.rb +0 -0
- data/lib/tco_method/method_reevaluator.rb +45 -0
- data/lib/tco_method/mixin.rb +20 -4
- data/lib/tco_method/version.rb +1 -1
- data/tco_method.gemspec +0 -0
- data/test/test_helper.rb +2 -2
- data/test/test_helpers/assertions.rb +0 -0
- data/test/test_helpers/fibbers.rb +37 -0
- data/test/unit/block_extractor_test.rb +150 -0
- data/test/unit/block_with_tco_test.rb +53 -0
- data/test/unit/method_info_test.rb +0 -0
- data/test/unit/method_reevaluator_test.rb +112 -0
- data/test/unit/mixin_test.rb +66 -46
- data/test/unit/tco_method_test.rb +42 -156
- metadata +15 -3
data/test/unit/mixin_test.rb
CHANGED
@@ -1,66 +1,86 @@
|
|
1
1
|
require "test_helper"
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
module TCOMethod
|
4
|
+
class MixinTest < TCOMethod::TestCase
|
5
|
+
TestClass = Class.new { extend TCOMethod::Mixin }
|
6
|
+
TestModule = Module.new { extend TCOMethod::Mixin }
|
6
7
|
|
7
8
|
|
8
|
-
|
9
|
-
|
9
|
+
context "Module extensions" do
|
10
|
+
subject { TestModule }
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
context "#tco_module_method" do
|
13
|
+
should "call MethodReevaluator#new with expected arguments" do
|
14
|
+
method_name = :some_method
|
15
|
+
args = [subject, method_name, :module]
|
16
|
+
MethodReevaluator.expects(:new).with(*args)
|
17
|
+
subject.tco_module_method(method_name)
|
18
|
+
end
|
17
19
|
end
|
18
|
-
end
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
context "#tco_method" do
|
22
|
+
should "call MethodReevaluator#new with expected arguments" do
|
23
|
+
method_name = :some_method
|
24
|
+
args = [subject, method_name, :instance]
|
25
|
+
MethodReevaluator.expects(:new).with(*args)
|
26
|
+
subject.tco_method(method_name)
|
27
|
+
end
|
25
28
|
end
|
26
|
-
end
|
27
29
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
context "#tco_eval" do
|
31
|
+
should "call TCOMethod.tco_eval with expected arguments" do
|
32
|
+
code = "some_code"
|
33
|
+
TCOMethod.expects(:tco_eval).with(code)
|
34
|
+
subject.tco_eval(code)
|
35
|
+
end
|
34
36
|
end
|
35
|
-
end
|
36
|
-
end
|
37
37
|
|
38
|
-
|
39
|
-
|
38
|
+
context "#with_tco" do
|
39
|
+
should "call TCOMethod.with_tco with the given block" do
|
40
|
+
# Mocha doesn't offer a good way for sensing passed blocks, so run
|
41
|
+
# through the process twice, once with a stub, once without.
|
42
|
+
|
43
|
+
# Stubbed
|
44
|
+
TCOMethod.expects(:with_tco).returns(true)
|
45
|
+
assert_equal true, subject.with_tco { }
|
40
46
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
+
# Now unstubbed to make sure the expected block is invoked.
|
48
|
+
TCOMethod.unstub(:with_tco)
|
49
|
+
|
50
|
+
# Must use some sort of global for sensing side effects because the
|
51
|
+
# block given to with_tco is called with a different binding than the
|
52
|
+
# one used here.
|
53
|
+
module ::WithTCOSensor
|
54
|
+
def self.call; @called = true; end
|
55
|
+
def self.called?; !!@called; end
|
56
|
+
end
|
57
|
+
|
58
|
+
result = subject.with_tco { ::WithTCOSensor.call; ::WithTCOSensor }
|
59
|
+
assert_equal true, ::WithTCOSensor.called?
|
60
|
+
assert_equal ::WithTCOSensor, result
|
61
|
+
end
|
47
62
|
end
|
48
63
|
end
|
49
64
|
|
50
|
-
context "
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
65
|
+
context "Class extensions" do
|
66
|
+
subject { TestClass }
|
67
|
+
|
68
|
+
context "#tco_class_method" do
|
69
|
+
should "call MethodReevaluator#new with expected arguments" do
|
70
|
+
method_name = :some_method
|
71
|
+
args = [subject, method_name, :module]
|
72
|
+
MethodReevaluator.expects(:new).with(*args)
|
73
|
+
subject.tco_class_method(method_name)
|
74
|
+
end
|
55
75
|
end
|
56
|
-
end
|
57
76
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
77
|
+
context "#tco_method" do
|
78
|
+
should "call MethodReevaluator#new with expected arguments" do
|
79
|
+
method_name = :some_method
|
80
|
+
args = [subject, method_name, :instance]
|
81
|
+
MethodReevaluator.expects(:new).with(*args)
|
82
|
+
subject.tco_method(method_name)
|
83
|
+
end
|
64
84
|
end
|
65
85
|
end
|
66
86
|
end
|
@@ -1,182 +1,68 @@
|
|
1
1
|
require "test_helper"
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
module TCOMethod
|
4
|
+
class TCOMethodTest < TCOMethod::TestCase
|
5
|
+
include TCOMethod::TestHelpers::Assertions
|
5
6
|
|
6
|
-
|
7
|
+
Subject = TCOMethod
|
7
8
|
|
8
|
-
|
9
|
-
|
9
|
+
# Grab source before it's recompiled for use later
|
10
|
+
InstanceFibYielderSource = TestClass.instance_method(:instance_fib_yielder).source
|
10
11
|
|
11
|
-
|
12
|
-
define_method(:singleton_block_method) { }
|
13
|
-
end
|
14
|
-
|
15
|
-
# Equivalent to the below, but provides a target for verifying that
|
16
|
-
# tco_module_method works on Classes and tco_class_method works on Modules.
|
17
|
-
def self.module_fib_yielder(index, back_one = 1, back_two = 0, &block)
|
18
|
-
yield back_two if index > 0
|
19
|
-
index < 1 ? back_two : module_fib_yielder(index - 1, back_one + back_two, back_one, &block)
|
20
|
-
end
|
21
|
-
|
22
|
-
# Equivalent to the above, but provides a target for verifying that
|
23
|
-
# tco_module_method works on Classes and tco_class_method works on Modules.
|
24
|
-
def self.class_fib_yielder(index, back_one = 1, back_two = 0, &block)
|
25
|
-
yield back_two if index > 0
|
26
|
-
index < 1 ? back_two : class_fib_yielder(index - 1, back_one + back_two, back_one, &block)
|
27
|
-
end
|
28
|
-
|
29
|
-
define_method(:instance_block_method) { }
|
30
|
-
|
31
|
-
# Equivalent to the above, but provides a target for verifying that
|
32
|
-
# instance methods work for both Classes and Modules
|
33
|
-
def instance_fib_yielder(index, back_one = 1, back_two = 0, &block)
|
34
|
-
yield back_two if index > 0
|
35
|
-
index < 1 ? back_two : instance_fib_yielder(index - 1, back_one + back_two, back_one, &block)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
TestModule = Module.new(&test_subject_builder)
|
40
|
-
TestClass = Class.new(&test_subject_builder)
|
41
|
-
|
42
|
-
# Grab source before it's recompiled for use later
|
43
|
-
InstanceFibYielderSource = TestClass.instance_method(:instance_fib_yielder).source
|
44
|
-
|
45
|
-
subject { Subject }
|
46
|
-
|
47
|
-
context Subject.name do
|
48
|
-
should "be defined" do
|
49
|
-
assert defined?(subject), "Expected #{subject.name} to be defined!"
|
50
|
-
end
|
51
|
-
end
|
12
|
+
subject { Subject }
|
52
13
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
:bad_code,
|
57
|
-
5,
|
58
|
-
proc { puts "hello" },
|
59
|
-
]
|
60
|
-
bad_code.each do |non_code|
|
61
|
-
assert_raises(ArgumentError) do
|
62
|
-
subject.tco_eval(non_code)
|
63
|
-
end
|
14
|
+
context Subject.name do
|
15
|
+
should "be defined" do
|
16
|
+
assert defined?(subject), "Expected #{subject.name} to be defined!"
|
64
17
|
end
|
65
18
|
end
|
66
19
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
fib_yielder = dummy_class.new.method(:instance_fib_yielder)
|
76
|
-
assert tail_call_optimized?(fib_yielder, 5)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
context "::reevaluate_method_with_tco" do
|
81
|
-
subject { Subject.method(:reevaluate_method_with_tco) }
|
82
|
-
|
83
|
-
[TestClass, TestModule].each do |method_owner|
|
84
|
-
method_owner_class = method_owner.class.name.downcase.to_sym
|
85
|
-
|
86
|
-
context "validation" do
|
87
|
-
should "raise ArgumentError unless receiver given" do
|
88
|
-
assert_raises(ArgumentError) do
|
89
|
-
subject.call(nil, :nil?, :instance)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
should "raise ArgumentError unless method name given" do
|
94
|
-
assert_raises(ArgumentError) do
|
95
|
-
subject.call(method_owner, nil, :instance)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
should "raise ArgumentError unless method owner given" do
|
20
|
+
context "::tco_eval" do
|
21
|
+
should "raise ArgumentError unless code is a String" do
|
22
|
+
bad_code = [
|
23
|
+
:bad_code,
|
24
|
+
5,
|
25
|
+
proc { puts "hello" },
|
26
|
+
]
|
27
|
+
bad_code.each do |non_code|
|
100
28
|
assert_raises(ArgumentError) do
|
101
|
-
subject.
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
should "raise TypeError for block methods" do
|
106
|
-
assert_raises(TypeError) do
|
107
|
-
subject.call(method_owner, :singleton_block_method, :class)
|
108
|
-
end
|
109
|
-
assert_raises(TypeError) do
|
110
|
-
subject.call(method_owner, :instance_block_method, :instance)
|
29
|
+
subject.tco_eval(non_code)
|
111
30
|
end
|
112
31
|
end
|
113
32
|
end
|
114
33
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
end
|
34
|
+
should "compile the given code with tail call optimization" do
|
35
|
+
EvalDummy = dummy_class = Class.new
|
36
|
+
subject.tco_eval(<<-CODE)
|
37
|
+
class #{dummy_class.name}
|
38
|
+
#{InstanceFibYielderSource}
|
121
39
|
end
|
40
|
+
CODE
|
122
41
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
subject.call(method_owner, :module_fib_yielder, :module)
|
128
|
-
tco_fib_yielder = method_owner.method(:module_fib_yielder)
|
129
|
-
assert tail_call_optimized?(tco_fib_yielder, 5)
|
42
|
+
fib_yielder = dummy_class.new.method(:instance_fib_yielder)
|
43
|
+
assert tail_call_optimized?(fib_yielder, 5)
|
44
|
+
end
|
45
|
+
end
|
130
46
|
|
131
|
-
|
132
|
-
|
133
|
-
|
47
|
+
context "::with_tco" do
|
48
|
+
should "raise ArgumentError if a block is not given" do
|
49
|
+
exception = assert_raises(ArgumentError) { subject.with_tco }
|
50
|
+
assert_match(/block required/i, exception.message)
|
51
|
+
end
|
134
52
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
53
|
+
should "compile the given code with tail call optimization" do
|
54
|
+
subject.with_tco do
|
55
|
+
class WithTCODummy
|
56
|
+
def countdown(count, &block)
|
57
|
+
yield count
|
58
|
+
count.zero? ? 0 : countdown(count - 1, &block)
|
139
59
|
end
|
140
60
|
end
|
141
|
-
|
142
|
-
should "re-compile the given method with tail call optimization" do
|
143
|
-
fib_yielder = method_owner.method(:class_fib_yielder)
|
144
|
-
refute tail_call_optimized?(fib_yielder, 5)
|
145
|
-
|
146
|
-
subject.call(method_owner, :class_fib_yielder, :module)
|
147
|
-
tco_fib_yielder = method_owner.method(:class_fib_yielder)
|
148
|
-
assert tail_call_optimized?(tco_fib_yielder, 5)
|
149
|
-
|
150
|
-
assert_equal fib_yielder.source_location, tco_fib_yielder.source_location
|
151
|
-
end
|
152
61
|
end
|
153
62
|
|
154
|
-
|
155
|
-
|
156
|
-
assert_raises(NameError) do
|
157
|
-
subject.call(method_owner, :marmalade, :instance)
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
should "re-compile the given method with tail call optimization" do
|
162
|
-
instance_class = instance_class_for_receiver(method_owner)
|
163
|
-
|
164
|
-
fib_yielder = instance_class.new.method(:instance_fib_yielder)
|
165
|
-
refute tail_call_optimized?(fib_yielder, 5)
|
166
|
-
|
167
|
-
subject.call(method_owner, :instance_fib_yielder, :instance)
|
168
|
-
tco_fib_yielder = instance_class.new.method(:instance_fib_yielder)
|
169
|
-
assert tail_call_optimized?(tco_fib_yielder, 5)
|
170
|
-
|
171
|
-
assert_equal fib_yielder.source_location, tco_fib_yielder.source_location
|
172
|
-
end
|
173
|
-
end
|
63
|
+
meth = WithTCODummy.new.method(:countdown)
|
64
|
+
assert tail_call_optimized?(meth, 5)
|
174
65
|
end
|
175
66
|
end
|
176
67
|
end
|
177
|
-
|
178
|
-
def instance_class_for_receiver(receiver)
|
179
|
-
return receiver if receiver.is_a?(Class)
|
180
|
-
Class.new { include receiver }
|
181
|
-
end
|
182
68
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tco_method
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Danny Guinther
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-11-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: method_source
|
@@ -67,13 +67,21 @@ files:
|
|
67
67
|
- README.md
|
68
68
|
- Rakefile
|
69
69
|
- lib/tco_method.rb
|
70
|
+
- lib/tco_method/ambiguous_source_error.rb
|
71
|
+
- lib/tco_method/block_extractor.rb
|
72
|
+
- lib/tco_method/block_with_tco.rb
|
70
73
|
- lib/tco_method/method_info.rb
|
74
|
+
- lib/tco_method/method_reevaluator.rb
|
71
75
|
- lib/tco_method/mixin.rb
|
72
76
|
- lib/tco_method/version.rb
|
73
77
|
- tco_method.gemspec
|
74
78
|
- test/test_helper.rb
|
75
79
|
- test/test_helpers/assertions.rb
|
80
|
+
- test/test_helpers/fibbers.rb
|
81
|
+
- test/unit/block_extractor_test.rb
|
82
|
+
- test/unit/block_with_tco_test.rb
|
76
83
|
- test/unit/method_info_test.rb
|
84
|
+
- test/unit/method_reevaluator_test.rb
|
77
85
|
- test/unit/mixin_test.rb
|
78
86
|
- test/unit/tco_method_test.rb
|
79
87
|
homepage: https://github.com/tdg5/tco_method
|
@@ -96,14 +104,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
104
|
version: '0'
|
97
105
|
requirements: []
|
98
106
|
rubyforge_project:
|
99
|
-
rubygems_version: 2.4.
|
107
|
+
rubygems_version: 2.4.8
|
100
108
|
signing_key:
|
101
109
|
specification_version: 4
|
102
110
|
summary: Simplifies compiling code with tail call optimization in MRI Ruby.
|
103
111
|
test_files:
|
104
112
|
- test/test_helper.rb
|
105
113
|
- test/test_helpers/assertions.rb
|
114
|
+
- test/test_helpers/fibbers.rb
|
115
|
+
- test/unit/block_extractor_test.rb
|
116
|
+
- test/unit/block_with_tco_test.rb
|
106
117
|
- test/unit/method_info_test.rb
|
118
|
+
- test/unit/method_reevaluator_test.rb
|
107
119
|
- test/unit/mixin_test.rb
|
108
120
|
- test/unit/tco_method_test.rb
|
109
121
|
has_rdoc:
|