pattern-matching 0.3.0 → 0.4.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.
@@ -1,205 +1,205 @@
1
- require 'spec_helper'
2
- require 'ostruct'
3
-
4
- class Bar
5
- def greet
6
- return 'Hello, World!'
7
- end
8
- end
9
-
10
- class Foo < Bar
11
- include PatternMatching
12
-
13
- attr_accessor :name
14
-
15
- defn(:initialize) { @name = 'baz' }
16
- defn(:initialize, _) {|name| @name = name.to_s }
17
-
18
- defn(:greet, _) do |name|
19
- "Hello, #{name}!"
20
- end
21
-
22
- defn(:greet, :male, _) { |name|
23
- "Hello, Mr. #{name}!"
24
- }
25
- defn(:greet, :female, _) { |name|
26
- "Hello, Ms. #{name}!"
27
- }
28
- defn(:greet, nil, _) { |name|
29
- "Goodbye, #{name}!"
30
- }
31
- defn(:greet, _, _) { |_, name|
32
- "Hello, #{name}!"
33
- }
34
-
35
- defn(:hashable, _, {foo: :bar}, _) { |_, opts, _|
36
- :foo_bar
37
- }
38
- defn(:hashable, _, {foo: _, bar: _}, _) { |_, f, b, _|
39
- [f, b]
40
- }
41
- defn(:hashable, _, {foo: _}, _) { |_, f, _|
42
- f
43
- }
44
- defn(:hashable, _, {}, _) {
45
- :empty
46
- }
47
- defn(:hashable, _, _, _) { |_, _, _|
48
- :unbound
49
- }
50
-
51
- defn(:options, _) { |opts|
52
- opts
53
- }
54
-
55
- defn(:recurse) {
56
- 'w00t!'
57
- }
58
- defn(:recurse, :match) {
59
- recurse()
60
- }
61
- defn(:recurse, :super) {
62
- greet()
63
- }
64
- defn(:recurse, :instance) {
65
- @name
66
- }
67
- defn(:recurse, _) { |arg|
68
- arg
69
- }
70
-
71
- defn(:concat, Integer, Integer) { |first, second|
72
- first + second
73
- }
74
- defn(:concat, Integer, String) { |first, second|
75
- "#{first} #{second}"
76
- }
77
- defn(:concat, String, String) { |first, second|
78
- first + second
79
- }
80
- defn(:concat, Integer, UNBOUND) { |first, second|
81
- first + second.to_i
82
- }
83
-
84
- defn(:all, :one, ALL) { |args|
85
- args
86
- }
87
- defn(:all, :one, Integer, ALL) { |int, args|
88
- [int, args]
89
- }
90
- defn(:all, 1, _, ALL) { |var, args|
91
- [var, args]
92
- }
93
- defn(:all, ALL) { | args|
94
- args
95
- }
96
-
97
- defn(:old_enough, _){ true }.when{|x| x >= 16 }
98
- defn(:old_enough, _){ false }
99
-
100
- defn(:right_age, _) {
101
- true
102
- }.when{|x| x >= 16 && x <= 104 }
103
-
104
- defn(:right_age, _) {
105
- false
106
- }
107
-
108
- defn(:wrong_age, _) {
109
- true
110
- }.when{|x| x < 16 || x > 104 }
111
-
112
- defn(:wrong_age, _) {
113
- false
114
- }
115
- end
116
-
117
- class Baz < Foo
118
- def boom_boom_room
119
- 'zoom zoom zoom'
120
- end
121
- def who(first, last)
122
- [first, last].join(' ')
123
- end
124
- end
125
-
126
- class Fizzbuzz < Baz
127
- include PatternMatching
128
- defn(:who, Integer) { |count|
129
- (1..count).each.reduce(:+)
130
- }
131
- defn(:who) { 0 }
132
- end
133
-
134
- describe 'integration' do
135
-
136
- let(:name) { 'Pattern Matcher' }
137
- subject { Foo.new(name) }
138
-
139
- specify { subject.greet.should eq 'Hello, World!' }
140
-
141
- specify { subject.greet('Jerry').should eq 'Hello, Jerry!' }
142
-
143
- specify { subject.greet(:male, 'Jerry').should eq 'Hello, Mr. Jerry!' }
144
- specify { subject.greet(:female, 'Jeri').should eq 'Hello, Ms. Jeri!' }
145
- specify { subject.greet(:unknown, 'Jerry').should eq 'Hello, Jerry!' }
146
- specify { subject.greet(nil, 'Jerry').should eq 'Goodbye, Jerry!' }
147
- specify {
148
- lambda { Foo.new.greet(1,2,3,4,5,6,7) }.should raise_error(NoMethodError)
149
- }
150
-
151
- specify { subject.options(bar: :baz, one: 1, many: 2).should == {bar: :baz, one: 1, many: 2} }
152
-
153
- specify { subject.hashable(:male, {foo: :bar}, :female).should eq :foo_bar }
154
- specify { subject.hashable(:male, {foo: :baz}, :female).should eq :baz }
155
- specify { subject.hashable(:male, {foo: 1, bar: 2}, :female).should eq [1, 2] }
156
- specify { subject.hashable(:male, {foo: 1, baz: 2}, :female).should eq 1 }
157
- specify { subject.hashable(:male, {bar: :baz}, :female).should eq :unbound }
158
- specify { subject.hashable(:male, {}, :female).should eq :empty }
159
-
160
- specify { subject.recurse.should eq 'w00t!' }
161
- specify { subject.recurse(:match).should eq 'w00t!' }
162
- specify { subject.recurse(:super).should eq 'Hello, World!' }
163
- specify { subject.recurse(:instance).should eq name }
164
- specify { subject.recurse(:foo).should eq :foo }
165
-
166
- specify { subject.concat(1, 1).should eq 2 }
167
- specify { subject.concat(1, 'shoe').should eq '1 shoe' }
168
- specify { subject.concat('shoe', 'fly').should eq 'shoefly' }
169
- specify { subject.concat(1, 2.9).should eq 3 }
170
-
171
- specify { subject.all(:one, 'a', 'bee', :see).should == ['a', 'bee', :see] }
172
- specify { subject.all(:one, 1, 'bee', :see).should == [1, 'bee', :see] }
173
- specify { subject.all(1, 'a', 'bee', :see).should == ['a', ['bee', :see]] }
174
- specify { subject.all('a', 'bee', :see).should == ['a', 'bee', :see] }
175
- specify { lambda { subject.all }.should raise_error(NoMethodError) }
176
-
177
- specify { subject.old_enough(20).should be_true }
178
- specify { subject.old_enough(10).should be_false }
179
-
180
- specify { subject.right_age(20).should be_true }
181
- specify { subject.right_age(10).should be_false }
182
- specify { subject.right_age(110).should be_false }
183
-
184
- specify { subject.wrong_age(20).should be_false }
185
- specify { subject.wrong_age(10).should be_true }
186
- specify { subject.wrong_age(110).should be_true }
187
-
188
- context 'inheritance' do
189
-
190
- specify { Fizzbuzz.new.greet(:male, 'Jerry').should eq 'Hello, Mr. Jerry!' }
191
- specify { Fizzbuzz.new.greet(:female, 'Jeri').should eq 'Hello, Ms. Jeri!' }
192
- specify { Fizzbuzz.new.greet(:unknown, 'Jerry').should eq 'Hello, Jerry!' }
193
- specify { Fizzbuzz.new.greet(nil, 'Jerry').should eq 'Goodbye, Jerry!' }
194
-
195
- specify { Fizzbuzz.new.who(5).should eq 15 }
196
- specify { Fizzbuzz.new.who().should eq 0 }
197
- specify {
198
- lambda {
199
- Fizzbuzz.new.who('Jerry', 'secret middle name', "D'Antonio")
200
- }.should raise_error(NoMethodError)
201
- }
202
-
203
- specify { Fizzbuzz.new.boom_boom_room.should eq 'zoom zoom zoom' }
204
- end
205
- end
1
+ require 'spec_helper'
2
+ require 'ostruct'
3
+
4
+ class Bar
5
+ def greet
6
+ return 'Hello, World!'
7
+ end
8
+ end
9
+
10
+ class Foo < Bar
11
+ include PatternMatching
12
+
13
+ attr_accessor :name
14
+
15
+ defn(:initialize) { @name = 'baz' }
16
+ defn(:initialize, _) {|name| @name = name.to_s }
17
+
18
+ defn(:greet, _) do |name|
19
+ "Hello, #{name}!"
20
+ end
21
+
22
+ defn(:greet, :male, _) { |name|
23
+ "Hello, Mr. #{name}!"
24
+ }
25
+ defn(:greet, :female, _) { |name|
26
+ "Hello, Ms. #{name}!"
27
+ }
28
+ defn(:greet, nil, _) { |name|
29
+ "Goodbye, #{name}!"
30
+ }
31
+ defn(:greet, _, _) { |_, name|
32
+ "Hello, #{name}!"
33
+ }
34
+
35
+ defn(:hashable, _, {foo: :bar}, _) { |_, opts, _|
36
+ :foo_bar
37
+ }
38
+ defn(:hashable, _, {foo: _, bar: _}, _) { |_, f, b, _|
39
+ [f, b]
40
+ }
41
+ defn(:hashable, _, {foo: _}, _) { |_, f, _|
42
+ f
43
+ }
44
+ defn(:hashable, _, {}, _) {
45
+ :empty
46
+ }
47
+ defn(:hashable, _, _, _) { |_, _, _|
48
+ :unbound
49
+ }
50
+
51
+ defn(:options, _) { |opts|
52
+ opts
53
+ }
54
+
55
+ defn(:recurse) {
56
+ 'w00t!'
57
+ }
58
+ defn(:recurse, :match) {
59
+ recurse()
60
+ }
61
+ defn(:recurse, :super) {
62
+ greet()
63
+ }
64
+ defn(:recurse, :instance) {
65
+ @name
66
+ }
67
+ defn(:recurse, _) { |arg|
68
+ arg
69
+ }
70
+
71
+ defn(:concat, Integer, Integer) { |first, second|
72
+ first + second
73
+ }
74
+ defn(:concat, Integer, String) { |first, second|
75
+ "#{first} #{second}"
76
+ }
77
+ defn(:concat, String, String) { |first, second|
78
+ first + second
79
+ }
80
+ defn(:concat, Integer, UNBOUND) { |first, second|
81
+ first + second.to_i
82
+ }
83
+
84
+ defn(:all, :one, ALL) { |args|
85
+ args
86
+ }
87
+ defn(:all, :one, Integer, ALL) { |int, args|
88
+ [int, args]
89
+ }
90
+ defn(:all, 1, _, ALL) { |var, args|
91
+ [var, args]
92
+ }
93
+ defn(:all, ALL) { | args|
94
+ args
95
+ }
96
+
97
+ defn(:old_enough, _){ true }.when{|x| x >= 16 }
98
+ defn(:old_enough, _){ false }
99
+
100
+ defn(:right_age, _) {
101
+ true
102
+ }.when{|x| x >= 16 && x <= 104 }
103
+
104
+ defn(:right_age, _) {
105
+ false
106
+ }
107
+
108
+ defn(:wrong_age, _) {
109
+ true
110
+ }.when{|x| x < 16 || x > 104 }
111
+
112
+ defn(:wrong_age, _) {
113
+ false
114
+ }
115
+ end
116
+
117
+ class Baz < Foo
118
+ def boom_boom_room
119
+ 'zoom zoom zoom'
120
+ end
121
+ def who(first, last)
122
+ [first, last].join(' ')
123
+ end
124
+ end
125
+
126
+ class Fizzbuzz < Baz
127
+ include PatternMatching
128
+ defn(:who, Integer) { |count|
129
+ (1..count).each.reduce(:+)
130
+ }
131
+ defn(:who) { 0 }
132
+ end
133
+
134
+ describe 'integration' do
135
+
136
+ let(:name) { 'Pattern Matcher' }
137
+ subject { Foo.new(name) }
138
+
139
+ specify { subject.greet.should eq 'Hello, World!' }
140
+
141
+ specify { subject.greet('Jerry').should eq 'Hello, Jerry!' }
142
+
143
+ specify { subject.greet(:male, 'Jerry').should eq 'Hello, Mr. Jerry!' }
144
+ specify { subject.greet(:female, 'Jeri').should eq 'Hello, Ms. Jeri!' }
145
+ specify { subject.greet(:unknown, 'Jerry').should eq 'Hello, Jerry!' }
146
+ specify { subject.greet(nil, 'Jerry').should eq 'Goodbye, Jerry!' }
147
+ specify {
148
+ lambda { Foo.new.greet(1,2,3,4,5,6,7) }.should raise_error(NoMethodError)
149
+ }
150
+
151
+ specify { subject.options(bar: :baz, one: 1, many: 2).should == {bar: :baz, one: 1, many: 2} }
152
+
153
+ specify { subject.hashable(:male, {foo: :bar}, :female).should eq :foo_bar }
154
+ specify { subject.hashable(:male, {foo: :baz}, :female).should eq :baz }
155
+ specify { subject.hashable(:male, {foo: 1, bar: 2}, :female).should eq [1, 2] }
156
+ specify { subject.hashable(:male, {foo: 1, baz: 2}, :female).should eq 1 }
157
+ specify { subject.hashable(:male, {bar: :baz}, :female).should eq :unbound }
158
+ specify { subject.hashable(:male, {}, :female).should eq :empty }
159
+
160
+ specify { subject.recurse.should eq 'w00t!' }
161
+ specify { subject.recurse(:match).should eq 'w00t!' }
162
+ specify { subject.recurse(:super).should eq 'Hello, World!' }
163
+ specify { subject.recurse(:instance).should eq name }
164
+ specify { subject.recurse(:foo).should eq :foo }
165
+
166
+ specify { subject.concat(1, 1).should eq 2 }
167
+ specify { subject.concat(1, 'shoe').should eq '1 shoe' }
168
+ specify { subject.concat('shoe', 'fly').should eq 'shoefly' }
169
+ specify { subject.concat(1, 2.9).should eq 3 }
170
+
171
+ specify { subject.all(:one, 'a', 'bee', :see).should == ['a', 'bee', :see] }
172
+ specify { subject.all(:one, 1, 'bee', :see).should == [1, 'bee', :see] }
173
+ specify { subject.all(1, 'a', 'bee', :see).should == ['a', ['bee', :see]] }
174
+ specify { subject.all('a', 'bee', :see).should == ['a', 'bee', :see] }
175
+ specify { lambda { subject.all }.should raise_error(NoMethodError) }
176
+
177
+ specify { subject.old_enough(20).should be_true }
178
+ specify { subject.old_enough(10).should be_false }
179
+
180
+ specify { subject.right_age(20).should be_true }
181
+ specify { subject.right_age(10).should be_false }
182
+ specify { subject.right_age(110).should be_false }
183
+
184
+ specify { subject.wrong_age(20).should be_false }
185
+ specify { subject.wrong_age(10).should be_true }
186
+ specify { subject.wrong_age(110).should be_true }
187
+
188
+ context 'inheritance' do
189
+
190
+ specify { Fizzbuzz.new.greet(:male, 'Jerry').should eq 'Hello, Mr. Jerry!' }
191
+ specify { Fizzbuzz.new.greet(:female, 'Jeri').should eq 'Hello, Ms. Jeri!' }
192
+ specify { Fizzbuzz.new.greet(:unknown, 'Jerry').should eq 'Hello, Jerry!' }
193
+ specify { Fizzbuzz.new.greet(nil, 'Jerry').should eq 'Goodbye, Jerry!' }
194
+
195
+ specify { Fizzbuzz.new.who(5).should eq 15 }
196
+ specify { Fizzbuzz.new.who().should eq 0 }
197
+ specify {
198
+ lambda {
199
+ Fizzbuzz.new.who('Jerry', 'secret middle name', "D'Antonio")
200
+ }.should raise_error(NoMethodError)
201
+ }
202
+
203
+ specify { Fizzbuzz.new.boom_boom_room.should eq 'zoom zoom zoom' }
204
+ end
205
+ end
@@ -1,408 +1,416 @@
1
- require 'spec_helper'
2
- require 'ostruct'
3
-
4
- describe PatternMatching do
5
-
6
- def new_clazz(&block)
7
- clazz = Class.new
8
- clazz.send(:include, PatternMatching)
9
- clazz.instance_eval(&block) if block_given?
10
- return clazz
11
- end
12
-
13
- subject { new_clazz }
14
-
15
- context '#defn declaration' do
16
-
17
- it 'can be used within a class declaration' do
18
- lambda {
19
- class Clazz
20
- include PatternMatching
21
- defn :foo
22
- end
23
- }.should_not raise_error
24
- end
25
-
26
- it 'can be used on a class object' do
27
- lambda {
28
- clazz = Class.new
29
- clazz.send(:include, PatternMatching)
30
- clazz.defn(:foo)
31
- }.should_not raise_error
32
- end
33
- end
34
-
35
- context 'constructor' do
36
-
37
- it 'can pattern match the constructor' do
38
-
39
- subject.defn(:initialize, PatternMatching::UNBOUND, PatternMatching::UNBOUND, PatternMatching::UNBOUND) { 'three args' }
40
- subject.defn(:initialize, PatternMatching::UNBOUND, PatternMatching::UNBOUND) { 'two args' }
41
- subject.defn(:initialize, PatternMatching::UNBOUND) { 'one arg' }
42
-
43
- lambda { subject.new(1) }.should_not raise_error
44
- lambda { subject.new(1, 2) }.should_not raise_error
45
- lambda { subject.new(1, 2, 3) }.should_not raise_error
46
- lambda { subject.new(1, 2, 3, 4) }.should raise_error
47
- end
48
- end
49
-
50
- context 'parameter count' do
51
-
52
- it 'does not match a call with not enough arguments' do
53
-
54
- subject.defn(:foo, true) { 'expected' }
55
-
56
- lambda {
57
- subject.new.foo()
58
- }.should raise_error(NoMethodError)
59
- end
60
-
61
- it 'does not match a call with too many arguments' do
62
-
63
- subject.defn(:foo, true) { 'expected' }
64
-
65
- lambda {
66
- subject.new.foo(true, false)
67
- }.should raise_error(NoMethodError)
68
- end
69
-
70
- end
71
-
72
- context 'recursion' do
73
-
74
- it 'defers unmatched calls to the superclass' do
75
-
76
- class UnmatchedCallTesterSuperclass
77
- def foo(bar)
78
- return bar
79
- end
80
- end
81
-
82
- class UnmatchedCallTesterSubclass < UnmatchedCallTesterSuperclass
83
- include PatternMatching
84
- defn(:foo) { 'baz' }
85
- end
86
-
87
- subject = UnmatchedCallTesterSubclass.new
88
- subject.foo(:bar).should eq :bar
89
- end
90
-
91
- it 'can call another match from within a match' do
92
-
93
- subject.defn(:foo, :bar) { |arg| foo(:baz) }
94
- subject.defn(:foo, :baz) { |arg| 'expected' }
95
-
96
- subject.new.foo(:bar).should eq 'expected'
97
- end
98
-
99
- it 'can call a superclass method from within a match' do
100
-
101
- class RecursiveCallTesterSuperclass
102
- def foo(bar)
103
- return bar
104
- end
105
- end
106
-
107
- class RecursiveCallTesterSubclass < RecursiveCallTesterSuperclass
108
- include PatternMatching
109
- defn(:foo, :bar) { foo(:baz) }
110
- end
111
-
112
- subject = RecursiveCallTesterSubclass.new
113
- subject.foo(:bar).should eq :baz
114
- end
115
- end
116
-
117
- context 'datatypes' do
118
-
119
- it 'matches an argument of the class given in the match parameter' do
120
-
121
- subject.defn(:foo, Integer) { 'expected' }
122
- subject.new.foo(100).should eq 'expected'
123
-
124
- lambda {
125
- subject.new.foo('hello')
126
- }.should raise_error(NoMethodError)
127
- end
128
-
129
- it 'passes the matched argument to the block' do
130
-
131
- subject.defn(:foo, Integer) { |arg| arg }
132
- subject.new.foo(100).should eq 100
133
- end
134
- end
135
-
136
- context 'function with no parameters' do
137
-
138
- it 'accepts no parameters' do
139
-
140
- subject.defn(:foo)
141
- obj = subject.new
142
-
143
- lambda {
144
- obj.foo
145
- }.should_not raise_error(NoMethodError)
146
- end
147
-
148
- it 'does not accept any parameters' do
149
-
150
- subject.defn(:foo)
151
- obj = subject.new
152
-
153
- lambda {
154
- obj.foo(1)
155
- }.should raise_error(NoMethodError)
156
- end
157
-
158
- it 'returns the correct value' do
159
- subject.defn(:foo){ true }
160
- subject.new.foo.should be_true
161
- end
162
- end
163
-
164
- context 'function with one parameter' do
165
-
166
- it 'matches a nil parameter' do
167
-
168
- subject.defn(:foo, nil) { 'expected' }
169
- subject.new.foo(nil).should eq 'expected'
170
-
171
- lambda {
172
- subject.new.foo('no match should be found')
173
- }.should raise_error(NoMethodError)
174
- end
175
-
176
- it 'matches a boolean parameter' do
177
-
178
- subject.defn(:foo, true) { 'expected' }
179
- subject.defn(:foo, false) { 'false case' }
180
-
181
- subject.new.foo(true).should eq 'expected'
182
- subject.new.foo(false).should eq 'false case'
183
-
184
- lambda {
185
- subject.new.foo('no match should be found')
186
- }.should raise_error(NoMethodError)
187
- end
188
-
189
- it 'matches a symbol parameter' do
190
-
191
- subject.defn(:foo, :bar) { 'expected' }
192
- subject.new.foo(:bar).should eq 'expected'
193
-
194
- lambda {
195
- subject.new.foo(:baz)
196
- }.should raise_error(NoMethodError)
197
- end
198
-
199
- it 'matches a number parameter' do
200
-
201
- subject.defn(:foo, 10) { 'expected' }
202
- subject.new.foo(10).should eq 'expected'
203
-
204
- lambda {
205
- subject.new.foo(11.0)
206
- }.should raise_error(NoMethodError)
207
- end
208
-
209
- it 'matches a string parameter' do
210
-
211
- subject.defn(:foo, 'bar') { 'expected' }
212
- subject.new.foo('bar').should eq 'expected'
213
-
214
- lambda {
215
- subject.new.foo('baz')
216
- }.should raise_error(NoMethodError)
217
- end
218
-
219
- it 'matches an array parameter' do
220
-
221
- subject.defn(:foo, [1, 2, 3]) { 'expected' }
222
- subject.new.foo([1, 2, 3]).should eq 'expected'
223
-
224
- lambda {
225
- subject.new.foo([3, 4, 5])
226
- }.should raise_error(NoMethodError)
227
- end
228
-
229
- it 'matches a hash parameter' do
230
-
231
- subject.defn(:foo, bar: 1, baz: 2) { 'expected' }
232
- subject.new.foo(bar: 1, baz: 2).should eq 'expected'
233
-
234
- lambda {
235
- subject.new.foo(foo: 0, bar: 1)
236
- }.should raise_error(NoMethodError)
237
- end
238
-
239
- it 'matches an object parameter' do
240
-
241
- subject.defn(:foo, OpenStruct.new(foo: :bar)) { 'expected' }
242
- subject.new.foo(OpenStruct.new(foo: :bar)).should eq 'expected'
243
-
244
- lambda {
245
- subject.new.foo(OpenStruct.new(bar: :baz))
246
- }.should raise_error(NoMethodError)
247
- end
248
-
249
- it 'matches an unbound parameter' do
250
-
251
- subject.defn(:foo, PatternMatching::UNBOUND) {|arg| arg }
252
- subject.new.foo(:foo).should eq :foo
253
- end
254
- end
255
-
256
- context 'function with two parameters' do
257
-
258
- it 'matches two bound arguments' do
259
-
260
- subject.defn(:foo, :male, :female){ 'expected' }
261
- subject.new.foo(:male, :female).should eq 'expected'
262
-
263
- lambda {
264
- subject.new.foo(1, 2)
265
- }.should raise_error(NoMethodError)
266
- end
267
-
268
- it 'matches two unbound arguments' do
269
-
270
- subject.defn(:foo, PatternMatching::UNBOUND, PatternMatching::UNBOUND) do |first, second|
271
- [first, second]
272
- end
273
- subject.new.foo(:male, :female).should eq [:male, :female]
274
- end
275
-
276
- it 'matches when the first argument is bound and the second is not' do
277
-
278
- subject.defn(:foo, :male, PatternMatching::UNBOUND) do |second|
279
- second
280
- end
281
- subject.new.foo(:male, :female).should eq :female
282
- end
283
-
284
- it 'matches when the second argument is bound and the first is not' do
285
-
286
- subject.defn(:foo, PatternMatching::UNBOUND, :female) do |first|
287
- first
288
- end
289
- subject.new.foo(:male, :female).should eq :male
290
- end
291
- end
292
-
293
- context 'functions with hash arguments' do
294
-
295
- it 'matches an empty argument hash with an empty parameter hash' do
296
-
297
- subject.defn(:foo, {}) { true }
298
- subject.new.foo({}).should be_true
299
-
300
- lambda {
301
- subject.new.foo({one: :two})
302
- }.should raise_error(NoMethodError)
303
- end
304
-
305
- it 'matches when all hash keys and values match' do
306
-
307
- subject.defn(:foo, {bar: :baz}) { true }
308
- subject.new.foo(bar: :baz).should be_true
309
-
310
- lambda {
311
- subject.new.foo({one: :two})
312
- }.should raise_error(NoMethodError)
313
- end
314
-
315
- it 'matches when every pattern key/value are in the argument' do
316
-
317
- subject.defn(:foo, {bar: :baz}) { true }
318
- subject.new.foo(foo: :bar, bar: :baz).should be_true
319
- end
320
-
321
- it 'matches when all keys with unbound values in the pattern have an argument' do
322
-
323
- subject.defn(:foo, {bar: PatternMatching::UNBOUND}) { true }
324
- subject.new.foo(bar: :baz).should be_true
325
- end
326
-
327
- it 'passes unbound values to the block' do
328
-
329
- subject.defn(:foo, {bar: PatternMatching::UNBOUND}) {|arg| arg }
330
- subject.new.foo(bar: :baz).should eq :baz
331
- end
332
-
333
- it 'passes the matched hash to the block' do
334
-
335
- subject.defn(:foo, {bar: :baz}) { |opts| opts }
336
- subject.new.foo(bar: :baz).should == {bar: :baz}
337
- end
338
-
339
- it 'does not match a non-hash argument' do
340
-
341
- subject.defn(:foo, {}) { true }
342
-
343
- lambda {
344
- subject.new.foo(:bar)
345
- }.should raise_error(NoMethodError)
346
- end
347
-
348
- it 'supports idiomatic has-as-last-argument syntax' do
349
-
350
- subject.defn(:foo, PatternMatching::UNBOUND) { |opts| opts }
351
- subject.new.foo(bar: :baz, one: 1, many: 2).should == {bar: :baz, one: 1, many: 2}
352
- end
353
- end
354
-
355
- context 'varaible-length argument lists' do
356
-
357
- it 'supports ALL as the last parameter' do
358
-
359
- subject.defn(:foo, 1, 2, PatternMatching::ALL) { |args| args }
360
- subject.new.foo(1, 2, 3).should == [3]
361
- subject.new.foo(1, 2, :foo, :bar).should == [:foo, :bar]
362
- subject.new.foo(1, 2, :foo, :bar, one: 1, two: 2).should == [:foo, :bar, {one: 1, two: 2}]
363
- end
364
- end
365
-
366
- context 'guard clauses' do
367
-
368
- it 'matches when the guard clause returns true' do
369
-
370
- subject.defn(:old_enough, PatternMatching::UNBOUND){
371
- true
372
- }.when{|x| x > 16 }
373
-
374
- subject.new.old_enough(20).should be_true
375
- end
376
-
377
- it 'does not match when the guard clause returns false' do
378
-
379
- subject.defn(:old_enough, PatternMatching::UNBOUND){
380
- true
381
- }.when{|x| x > 16 }
382
-
383
- lambda {
384
- subject.new.old_enough(10)
385
- }.should raise_error(NoMethodError)
386
- end
387
-
388
- it 'continues pattern matching when the guard clause returns false' do
389
-
390
- subject.defn(:old_enough, PatternMatching::UNBOUND){
391
- true
392
- }.when{|x| x > 16 }
393
-
394
- subject.defn(:old_enough, PatternMatching::UNBOUND) { false }
395
-
396
- subject.new.old_enough(10).should be_false
397
- end
398
-
399
- it 'raises an exception when the guard clause does not have a block' do
400
-
401
- lambda {
402
- subject.defn(:foo).when
403
- }.should raise_error(ArgumentError)
404
- end
405
-
406
- end
407
-
408
- end
1
+ require 'spec_helper'
2
+ require 'ostruct'
3
+
4
+ describe PatternMatching do
5
+
6
+ def new_clazz(&block)
7
+ clazz = Class.new
8
+ clazz.send(:include, PatternMatching)
9
+ clazz.instance_eval(&block) if block_given?
10
+ return clazz
11
+ end
12
+
13
+ subject { new_clazz }
14
+
15
+ context '#defn declaration' do
16
+
17
+ it 'can be used within a class declaration' do
18
+ lambda {
19
+ class Clazz
20
+ include PatternMatching
21
+ defn(:foo){}
22
+ end
23
+ }.should_not raise_error
24
+ end
25
+
26
+ it 'can be used on a class object' do
27
+ lambda {
28
+ clazz = Class.new
29
+ clazz.send(:include, PatternMatching)
30
+ clazz.defn(:foo){}
31
+ }.should_not raise_error
32
+ end
33
+
34
+ it 'requires a block' do
35
+ lambda {
36
+ clazz = Class.new
37
+ clazz.send(:include, PatternMatching)
38
+ clazz.defn(:foo)
39
+ }.should raise_error(ArgumentError)
40
+ end
41
+ end
42
+
43
+ context 'constructor' do
44
+
45
+ it 'can pattern match the constructor' do
46
+
47
+ subject.defn(:initialize, PatternMatching::UNBOUND, PatternMatching::UNBOUND, PatternMatching::UNBOUND) { 'three args' }
48
+ subject.defn(:initialize, PatternMatching::UNBOUND, PatternMatching::UNBOUND) { 'two args' }
49
+ subject.defn(:initialize, PatternMatching::UNBOUND) { 'one arg' }
50
+
51
+ lambda { subject.new(1) }.should_not raise_error
52
+ lambda { subject.new(1, 2) }.should_not raise_error
53
+ lambda { subject.new(1, 2, 3) }.should_not raise_error
54
+ lambda { subject.new(1, 2, 3, 4) }.should raise_error
55
+ end
56
+ end
57
+
58
+ context 'parameter count' do
59
+
60
+ it 'does not match a call with not enough arguments' do
61
+
62
+ subject.defn(:foo, true) { 'expected' }
63
+
64
+ lambda {
65
+ subject.new.foo()
66
+ }.should raise_error(NoMethodError)
67
+ end
68
+
69
+ it 'does not match a call with too many arguments' do
70
+
71
+ subject.defn(:foo, true) { 'expected' }
72
+
73
+ lambda {
74
+ subject.new.foo(true, false)
75
+ }.should raise_error(NoMethodError)
76
+ end
77
+
78
+ end
79
+
80
+ context 'recursion' do
81
+
82
+ it 'defers unmatched calls to the superclass' do
83
+
84
+ class UnmatchedCallTesterSuperclass
85
+ def foo(bar)
86
+ return bar
87
+ end
88
+ end
89
+
90
+ class UnmatchedCallTesterSubclass < UnmatchedCallTesterSuperclass
91
+ include PatternMatching
92
+ defn(:foo) { 'baz' }
93
+ end
94
+
95
+ subject = UnmatchedCallTesterSubclass.new
96
+ subject.foo(:bar).should eq :bar
97
+ end
98
+
99
+ it 'can call another match from within a match' do
100
+
101
+ subject.defn(:foo, :bar) { |arg| foo(:baz) }
102
+ subject.defn(:foo, :baz) { |arg| 'expected' }
103
+
104
+ subject.new.foo(:bar).should eq 'expected'
105
+ end
106
+
107
+ it 'can call a superclass method from within a match' do
108
+
109
+ class RecursiveCallTesterSuperclass
110
+ def foo(bar)
111
+ return bar
112
+ end
113
+ end
114
+
115
+ class RecursiveCallTesterSubclass < RecursiveCallTesterSuperclass
116
+ include PatternMatching
117
+ defn(:foo, :bar) { foo(:baz) }
118
+ end
119
+
120
+ subject = RecursiveCallTesterSubclass.new
121
+ subject.foo(:bar).should eq :baz
122
+ end
123
+ end
124
+
125
+ context 'datatypes' do
126
+
127
+ it 'matches an argument of the class given in the match parameter' do
128
+
129
+ subject.defn(:foo, Integer) { 'expected' }
130
+ subject.new.foo(100).should eq 'expected'
131
+
132
+ lambda {
133
+ subject.new.foo('hello')
134
+ }.should raise_error(NoMethodError)
135
+ end
136
+
137
+ it 'passes the matched argument to the block' do
138
+
139
+ subject.defn(:foo, Integer) { |arg| arg }
140
+ subject.new.foo(100).should eq 100
141
+ end
142
+ end
143
+
144
+ context 'function with no parameters' do
145
+
146
+ it 'accepts no parameters' do
147
+
148
+ subject.defn(:foo){}
149
+ obj = subject.new
150
+
151
+ lambda {
152
+ obj.foo
153
+ }.should_not raise_error(NoMethodError)
154
+ end
155
+
156
+ it 'does not accept any parameters' do
157
+
158
+ subject.defn(:foo){}
159
+ obj = subject.new
160
+
161
+ lambda {
162
+ obj.foo(1)
163
+ }.should raise_error(NoMethodError)
164
+ end
165
+
166
+ it 'returns the correct value' do
167
+ subject.defn(:foo){ true }
168
+ subject.new.foo.should be_true
169
+ end
170
+ end
171
+
172
+ context 'function with one parameter' do
173
+
174
+ it 'matches a nil parameter' do
175
+
176
+ subject.defn(:foo, nil) { 'expected' }
177
+ subject.new.foo(nil).should eq 'expected'
178
+
179
+ lambda {
180
+ subject.new.foo('no match should be found')
181
+ }.should raise_error(NoMethodError)
182
+ end
183
+
184
+ it 'matches a boolean parameter' do
185
+
186
+ subject.defn(:foo, true) { 'expected' }
187
+ subject.defn(:foo, false) { 'false case' }
188
+
189
+ subject.new.foo(true).should eq 'expected'
190
+ subject.new.foo(false).should eq 'false case'
191
+
192
+ lambda {
193
+ subject.new.foo('no match should be found')
194
+ }.should raise_error(NoMethodError)
195
+ end
196
+
197
+ it 'matches a symbol parameter' do
198
+
199
+ subject.defn(:foo, :bar) { 'expected' }
200
+ subject.new.foo(:bar).should eq 'expected'
201
+
202
+ lambda {
203
+ subject.new.foo(:baz)
204
+ }.should raise_error(NoMethodError)
205
+ end
206
+
207
+ it 'matches a number parameter' do
208
+
209
+ subject.defn(:foo, 10) { 'expected' }
210
+ subject.new.foo(10).should eq 'expected'
211
+
212
+ lambda {
213
+ subject.new.foo(11.0)
214
+ }.should raise_error(NoMethodError)
215
+ end
216
+
217
+ it 'matches a string parameter' do
218
+
219
+ subject.defn(:foo, 'bar') { 'expected' }
220
+ subject.new.foo('bar').should eq 'expected'
221
+
222
+ lambda {
223
+ subject.new.foo('baz')
224
+ }.should raise_error(NoMethodError)
225
+ end
226
+
227
+ it 'matches an array parameter' do
228
+
229
+ subject.defn(:foo, [1, 2, 3]) { 'expected' }
230
+ subject.new.foo([1, 2, 3]).should eq 'expected'
231
+
232
+ lambda {
233
+ subject.new.foo([3, 4, 5])
234
+ }.should raise_error(NoMethodError)
235
+ end
236
+
237
+ it 'matches a hash parameter' do
238
+
239
+ subject.defn(:foo, bar: 1, baz: 2) { 'expected' }
240
+ subject.new.foo(bar: 1, baz: 2).should eq 'expected'
241
+
242
+ lambda {
243
+ subject.new.foo(foo: 0, bar: 1)
244
+ }.should raise_error(NoMethodError)
245
+ end
246
+
247
+ it 'matches an object parameter' do
248
+
249
+ subject.defn(:foo, OpenStruct.new(foo: :bar)) { 'expected' }
250
+ subject.new.foo(OpenStruct.new(foo: :bar)).should eq 'expected'
251
+
252
+ lambda {
253
+ subject.new.foo(OpenStruct.new(bar: :baz))
254
+ }.should raise_error(NoMethodError)
255
+ end
256
+
257
+ it 'matches an unbound parameter' do
258
+
259
+ subject.defn(:foo, PatternMatching::UNBOUND) {|arg| arg }
260
+ subject.new.foo(:foo).should eq :foo
261
+ end
262
+ end
263
+
264
+ context 'function with two parameters' do
265
+
266
+ it 'matches two bound arguments' do
267
+
268
+ subject.defn(:foo, :male, :female){ 'expected' }
269
+ subject.new.foo(:male, :female).should eq 'expected'
270
+
271
+ lambda {
272
+ subject.new.foo(1, 2)
273
+ }.should raise_error(NoMethodError)
274
+ end
275
+
276
+ it 'matches two unbound arguments' do
277
+
278
+ subject.defn(:foo, PatternMatching::UNBOUND, PatternMatching::UNBOUND) do |first, second|
279
+ [first, second]
280
+ end
281
+ subject.new.foo(:male, :female).should eq [:male, :female]
282
+ end
283
+
284
+ it 'matches when the first argument is bound and the second is not' do
285
+
286
+ subject.defn(:foo, :male, PatternMatching::UNBOUND) do |second|
287
+ second
288
+ end
289
+ subject.new.foo(:male, :female).should eq :female
290
+ end
291
+
292
+ it 'matches when the second argument is bound and the first is not' do
293
+
294
+ subject.defn(:foo, PatternMatching::UNBOUND, :female) do |first|
295
+ first
296
+ end
297
+ subject.new.foo(:male, :female).should eq :male
298
+ end
299
+ end
300
+
301
+ context 'functions with hash arguments' do
302
+
303
+ it 'matches an empty argument hash with an empty parameter hash' do
304
+
305
+ subject.defn(:foo, {}) { true }
306
+ subject.new.foo({}).should be_true
307
+
308
+ lambda {
309
+ subject.new.foo({one: :two})
310
+ }.should raise_error(NoMethodError)
311
+ end
312
+
313
+ it 'matches when all hash keys and values match' do
314
+
315
+ subject.defn(:foo, {bar: :baz}) { true }
316
+ subject.new.foo(bar: :baz).should be_true
317
+
318
+ lambda {
319
+ subject.new.foo({one: :two})
320
+ }.should raise_error(NoMethodError)
321
+ end
322
+
323
+ it 'matches when every pattern key/value are in the argument' do
324
+
325
+ subject.defn(:foo, {bar: :baz}) { true }
326
+ subject.new.foo(foo: :bar, bar: :baz).should be_true
327
+ end
328
+
329
+ it 'matches when all keys with unbound values in the pattern have an argument' do
330
+
331
+ subject.defn(:foo, {bar: PatternMatching::UNBOUND}) { true }
332
+ subject.new.foo(bar: :baz).should be_true
333
+ end
334
+
335
+ it 'passes unbound values to the block' do
336
+
337
+ subject.defn(:foo, {bar: PatternMatching::UNBOUND}) {|arg| arg }
338
+ subject.new.foo(bar: :baz).should eq :baz
339
+ end
340
+
341
+ it 'passes the matched hash to the block' do
342
+
343
+ subject.defn(:foo, {bar: :baz}) { |opts| opts }
344
+ subject.new.foo(bar: :baz).should == {bar: :baz}
345
+ end
346
+
347
+ it 'does not match a non-hash argument' do
348
+
349
+ subject.defn(:foo, {}) { true }
350
+
351
+ lambda {
352
+ subject.new.foo(:bar)
353
+ }.should raise_error(NoMethodError)
354
+ end
355
+
356
+ it 'supports idiomatic has-as-last-argument syntax' do
357
+
358
+ subject.defn(:foo, PatternMatching::UNBOUND) { |opts| opts }
359
+ subject.new.foo(bar: :baz, one: 1, many: 2).should == {bar: :baz, one: 1, many: 2}
360
+ end
361
+ end
362
+
363
+ context 'varaible-length argument lists' do
364
+
365
+ it 'supports ALL as the last parameter' do
366
+
367
+ subject.defn(:foo, 1, 2, PatternMatching::ALL) { |args| args }
368
+ subject.new.foo(1, 2, 3).should == [3]
369
+ subject.new.foo(1, 2, :foo, :bar).should == [:foo, :bar]
370
+ subject.new.foo(1, 2, :foo, :bar, one: 1, two: 2).should == [:foo, :bar, {one: 1, two: 2}]
371
+ end
372
+ end
373
+
374
+ context 'guard clauses' do
375
+
376
+ it 'matches when the guard clause returns true' do
377
+
378
+ subject.defn(:old_enough, PatternMatching::UNBOUND){
379
+ true
380
+ }.when{|x| x > 16 }
381
+
382
+ subject.new.old_enough(20).should be_true
383
+ end
384
+
385
+ it 'does not match when the guard clause returns false' do
386
+
387
+ subject.defn(:old_enough, PatternMatching::UNBOUND){
388
+ true
389
+ }.when{|x| x > 16 }
390
+
391
+ lambda {
392
+ subject.new.old_enough(10)
393
+ }.should raise_error(NoMethodError)
394
+ end
395
+
396
+ it 'continues pattern matching when the guard clause returns false' do
397
+
398
+ subject.defn(:old_enough, PatternMatching::UNBOUND){
399
+ true
400
+ }.when{|x| x > 16 }
401
+
402
+ subject.defn(:old_enough, PatternMatching::UNBOUND) { false }
403
+
404
+ subject.new.old_enough(10).should be_false
405
+ end
406
+
407
+ it 'raises an exception when the guard clause does not have a block' do
408
+
409
+ lambda {
410
+ subject.defn(:foo).when
411
+ }.should raise_error(ArgumentError)
412
+ end
413
+
414
+ end
415
+
416
+ end