tco_method 0.1.0 → 0.2.0
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.
- 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:
|