pattern-matching 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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