pattern-matching 0.0.1 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dc55948d3772a5d4805826b923d12ae1f84f09cd
4
- data.tar.gz: 6cf392b7bd93afe0e752c0fca586c2454ca583c1
3
+ metadata.gz: 26d3f6768306dd2dba389a7dd481e135417164de
4
+ data.tar.gz: 5bdc90e25e848b7de5ce67d58f7cce70d1a7e434
5
5
  SHA512:
6
- metadata.gz: 630fb32894b0074e0e3f273f8d83c37e845c1becc3609e532d226e2e7308389bf744296d9ea411fa8e0b2dbb3c51f21c87e22db37db46726128d4be6dbbd0a15
7
- data.tar.gz: 9369b4042e2cd7760cd2ade36aa5c7e8cdc545f5071e20b67af0c0f98e6971ba17c193899ff6fdd8f6efd1608093e3b4530d129f3f8e16419a66af051224c254
6
+ metadata.gz: e6fd87d44de49a937de38e70e42c7ab470b0afce1f20fb5ef64ecb949234ce168b0ba75713394457c75c7de02565a86ee47eee26ea2f6b04b7a1e8da8b7368b2
7
+ data.tar.gz: fb843fe146735c3603eb641b0e2dc790da3be503889a13e8b737844598b2012fceb2f48974dda9aa955c0147348fa1dea784edfb4d2e647eb0b619d2636dd4a4
data/README.md CHANGED
@@ -4,6 +4,14 @@ A gem for adding Erlang-style pattern matching to Ruby classes.
4
4
 
5
5
  *NOTE: This is a work in progress. Expect changes.*
6
6
 
7
+ The project is hosted on the following sites:
8
+
9
+ * [RubyGems project page](https://rubygems.org/gems/pattern-matching)
10
+ * [Source code on GitHub](https://github.com/jdantonio/pattern_matching)
11
+ * [Continuous integration on Travis-CI](https://travis-ci.org/jdantonio/pattern_matching)
12
+ * [Dependency tracking on Gemnasium](https://gemnasium.com/jdantonio/pattern_matching)
13
+ * [Follow me on Twitter](https://twitter.com/jerrydantonio)
14
+
7
15
  ## Introduction
8
16
 
9
17
  [Ruby](http://www.ruby-lang.org/en/) is my favorite programming by far. As much as I love Ruby I've always been a little disappointed that Ruby doesn't support function overloading. Function overloading tends to reduce branching and keep functions signatures simpler. No sweat, I learned to do without. Then I started programming in [Erlang](http://www.erlang.org/)…
@@ -16,7 +24,8 @@ I've really started to enjoy working in Erlang. Erlang is good at all the things
16
24
  * Keep the semantics as idiomatic Ruby as possible
17
25
  * Support features that make sense in Ruby
18
26
  * Exclude features that only make sense in Erlang
19
- * Avoid using #method_missing
27
+ * Avoid using *method_missing*
28
+ * Keep it small (currently at 72 LOC)
20
29
 
21
30
  ### Features
22
31
 
@@ -33,26 +42,230 @@ I've really started to enjoy working in Erlang. Erlang is good at all the things
33
42
 
34
43
  ### To-do
35
44
 
36
- * Variable-length argument lists
37
45
  * Matching against array elements
38
46
  * Guard clauses
39
47
  * Support class methods
40
48
  * Support module instance methods
41
49
  * Support module methods
42
50
 
51
+ ## Supported Ruby versions
52
+
53
+ MRI 1.9.x and above. Anything else and your mileage may vary.
54
+
43
55
  ## Install
44
56
 
45
- gem install pattern-matching
57
+ ```shell
58
+ gem install pattern-matching
59
+ ```
46
60
 
47
61
  or add the following line to Gemfile:
48
62
 
49
- gem 'pattern-matching
63
+ ```ruby
64
+ gem 'pattern-matching'
65
+ ```
50
66
 
51
67
  and run `bundle install` from your shell.
52
68
 
53
- ## Supported Ruby versions
69
+ ## Usage
54
70
 
55
- MRI 1.9.x and above. Anything else and your mileage may vary.
71
+ First, familiarize yourself with Erlang [pattern matching](http://learnyousomeerlang.com/syntax-in-functions#pattern-matching).
72
+ This gem may not make much sense if you don't understand how Erlang dispatches
73
+ functions.
74
+
75
+ In the Ruby class file where you want to use pattern matching, require the
76
+ *pattern_matching* gem:
77
+
78
+ ```ruby
79
+ require 'pattern_matching'
80
+ ```
81
+
82
+ Then include `PatternMatching` in your class:
83
+
84
+ ```ruby
85
+ require 'pattern_matching'
86
+
87
+ class Foo
88
+ include PatternMatching
89
+
90
+ ...
91
+
92
+ end
93
+ ```
94
+
95
+ You can then define functions with `defn` instead of the normal *def* statement.
96
+ The syntax for `defn` is:
97
+
98
+ ```ruby
99
+ defn(:symbol_name_of_function, zero, or, more, parameters) { |block, arguments|
100
+ # code to execute
101
+ }
102
+ ```
103
+ You can then call your new function just like any other:
104
+
105
+ ```ruby
106
+ require 'pattern_matching'
107
+
108
+ class Foo
109
+ include PatternMatching
110
+
111
+ defn(:hello) {
112
+ puts "Hello, World!"
113
+ }
114
+ end
115
+
116
+ foo = Foo.new
117
+ foo.hello #=> "Hello, World!"
118
+ ```
119
+
120
+ Patterns to match against are included in the parameter list:
121
+
122
+ ```ruby
123
+ defn(:greet, :male) {
124
+ puts "Hello, sir!"
125
+ }
126
+
127
+ defn(:greet, :female) {
128
+ puts "Hello, ma'am!"
129
+ }
130
+
131
+ ...
132
+
133
+ foo.hello(:male) #=> "Hello, sir!"
134
+ foo.hello(:female) #=> "Hello, ma'am!"
135
+ ```
136
+
137
+ If a particular method call can not be matched a *NoMethodError* is thrown with
138
+ a reasonably helpful error message:
139
+
140
+ ```ruby
141
+ foo.greet(:unknown) #=> NoMethodError: no method `greet` matching [:unknown] found for class Foo
142
+ foo.greet #=> NoMethodError: no method `greet` matching [] found for class Foo
143
+ ```
144
+
145
+ Parameters that are expected to exist but that can take any value are considered
146
+ *unbound* parameters. Unbound parameters are specified by the `_` underscore
147
+ character or `UNBOUND`:
148
+
149
+ ```ruby
150
+ defn(:greet, _) do |name|
151
+ "Hello, #{name}!"
152
+ end
153
+
154
+ defn(:greet, UNBOUND, UNBOUND) do |first, last|
155
+ "Hello, #{first} #{last}!"
156
+ end
157
+
158
+ ...
159
+
160
+ foo.greet('Jerry') #=> "Hello, Jerry!"
161
+ ```
162
+
163
+ All unbound parameters will be passed to the block in the order they are specified in the definition:
164
+
165
+ ```ruby
166
+ defn(:greet, _, _) do |first, last|
167
+ "Hello, #{first} #{last}!"
168
+ end
169
+
170
+ ...
171
+
172
+ foo.greet('Jerry', "D'Antonio") #=> "Hello, Jerry D'Antonio!"
173
+ ```
174
+
175
+ If for some reason you don't care about one or more unbound parameters within
176
+ the block you can use the `_` underscore character in the block parameters list
177
+ as well:
178
+
179
+ ```ruby
180
+ defn(:greet, _, _, _) do |first, _, last|
181
+ "Hello, #{first} #{last}!"
182
+ end
183
+
184
+ ...
185
+
186
+ foo.greet('Jerry', "I'm not going to tell you my middle name!", "D'Antonio") #=> "Hello, Jerry D'Antonio!"
187
+ ```
188
+
189
+ Hash parameters can match against specific keys and either bound or unbound parameters. This allows for function dispatch by hash parameters without having to dig through the hash:
190
+
191
+ ```ruby
192
+ defn(:hashable, {foo: :bar}) { |opts|
193
+ :foo_bar
194
+ }
195
+ defn(:hashable, {foo: _}) { |f|
196
+ f
197
+ }
198
+
199
+ ...
200
+
201
+ foo.hashable({foo: :bar}) #=> :foo_bar
202
+ foo.hashable({foo: :baz}) #=> :baz
203
+ ```
204
+
205
+ The Ruby idiom of the final parameter being a hash is also supported:
206
+
207
+ ```ruby
208
+ defn(:options, _) { |opts|
209
+ opts
210
+ }
211
+
212
+ ...
213
+
214
+ foo.options(bar: :baz, one: 1, many: 2)
215
+ ```
216
+
217
+ As is the Ruby idiom of variable-length argument lists. The constant `ALL` as the last parameter
218
+ will match one or more arguments and pass them to the block as an array:
219
+
220
+ ```ruby
221
+ defn(:baz, Integer, ALL) { |int, args|
222
+ [int, args]
223
+ }
224
+ defn(:baz, ALL) { |args|
225
+ args
226
+ }
227
+ ```
228
+
229
+ Superclass polymorphism is supported as well. If an object cannot match a method
230
+ signature it will defer to the parent class:
231
+
232
+ ```ruby
233
+ class Bar
234
+ def greet
235
+ return 'Hello, World!'
236
+ end
237
+ end
238
+
239
+ class Foo < Bar
240
+ include PatternMatching
241
+
242
+ defn(:greet, _) do |name|
243
+ "Hello, #{name}!"
244
+ end
245
+ end
246
+
247
+ ...
248
+
249
+ foo.greet('Jerry') #=> "Hello, Jerry!"
250
+ foo.greet #=> "Hello, World!"
251
+ ```
252
+
253
+ ### Order Matters
254
+
255
+ As with Erlang, the order of pattern matches is significant. Patterns will be matched
256
+ *in the order declared* and the first match will be used. If a particular function call
257
+ can be matched by more than one pattern, the *first matched pattern* will be used. It
258
+ is the programmer's responsibility to ensure patterns are declared in the correct order.
259
+
260
+ ### Blocks and Procs and Lambdas, oh my!
261
+
262
+ When using this gem it is critical to remember that `defn` takes a block and
263
+ that blocks in Ruby have special rules. There are [plenty](https://www.google.com/search?q=ruby+block+proc+lambda)
264
+ of good tutorials on the web explaining [blocks](http://www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/)
265
+ and [Procs](https://coderwall.com/p/_-_mha) and [lambdas](http://railsguru.org/2010/03/learn-ruby-procs-blocks-lambda/)
266
+ in Ruby. Please read them. Please don't submit a bug report if you use a
267
+ `return` statement within your `defn` and your code blows up with a
268
+ [LocalJumpError](http://ruby-doc.org/core-2.0/LocalJumpError.html).
56
269
 
57
270
  ## Examples
58
271
 
@@ -64,34 +277,38 @@ This example is based on [Syntax in defnctions: Pattern Matching](http://learnyo
64
277
 
65
278
  Erlang:
66
279
 
67
- greet(male, Name) ->
68
- io:format("Hello, Mr. ~s!", [Name]);
69
- greet(female, Name) ->
70
- io:format("Hello, Mrs. ~s!", [Name]);
71
- greet(_, Name) ->
72
- io:format("Hello, ~s!", [Name]).
280
+ ```erlang
281
+ greet(male, Name) ->
282
+ io:format("Hello, Mr. ~s!", [Name]);
283
+ greet(female, Name) ->
284
+ io:format("Hello, Mrs. ~s!", [Name]);
285
+ greet(_, Name) ->
286
+ io:format("Hello, ~s!", [Name]).
287
+ ```
73
288
 
74
289
  Ruby:
75
290
 
76
- require 'pattern_matching'
291
+ ```ruby
292
+ require 'pattern_matching'
77
293
 
78
- class Foo
79
- include PatternMatching
80
-
81
- defn(:greet, _) do |name|
82
- "Hello, #{name}!"
83
- end
84
-
85
- defn(:greet, :male, _) { |name|
86
- "Hello, Mr. #{name}!"
87
- }
88
- defn(:greet, :female, _) { |name|
89
- "Hello, Ms. #{name}!"
90
- }
91
- defn(:greet, _, _) { |_, name|
92
- "Hello, #{name}!"
93
- }
94
- end
294
+ class Foo
295
+ include PatternMatching
296
+
297
+ defn(:greet, _) do |name|
298
+ "Hello, #{name}!"
299
+ end
300
+
301
+ defn(:greet, :male, _) { |name|
302
+ "Hello, Mr. #{name}!"
303
+ }
304
+ defn(:greet, :female, _) { |name|
305
+ "Hello, Ms. #{name}!"
306
+ }
307
+ defn(:greet, _, _) { |_, name|
308
+ "Hello, #{name}!"
309
+ }
310
+ end
311
+ ```
95
312
 
96
313
  ### Simple Functions with Overloading
97
314
 
@@ -99,83 +316,161 @@ This example is based on [Syntax in defnctions: Pattern Matching](http://learnyo
99
316
 
100
317
  Erlang:
101
318
 
102
- greet(Name) ->
103
- io:format("Hello, ~s!", [Name]).
319
+ ```erlang
320
+ greet(Name) ->
321
+ io:format("Hello, ~s!", [Name]).
104
322
 
105
- greet(male, Name) ->
106
- io:format("Hello, Mr. ~s!", [Name]);
107
- greet(female, Name) ->
108
- io:format("Hello, Mrs. ~s!", [Name]);
109
- greet(_, Name) ->
110
- io:format("Hello, ~s!", [Name]).
323
+ greet(male, Name) ->
324
+ io:format("Hello, Mr. ~s!", [Name]);
325
+ greet(female, Name) ->
326
+ io:format("Hello, Mrs. ~s!", [Name]);
327
+ greet(_, Name) ->
328
+ io:format("Hello, ~s!", [Name]).
329
+ ```
111
330
 
112
331
  Ruby:
113
332
 
114
- require 'pattern_matching'
115
-
116
- class Foo
117
- include PatternMatching
118
-
119
- defn(:greet, _) do |name|
120
- "Hello, #{name}!"
121
- end
122
-
123
- defn(:greet, :male, _) { |name|
124
- "Hello, Mr. #{name}!"
125
- }
126
- defn(:greet, :female, _) { |name|
127
- "Hello, Ms. #{name}!"
128
- }
129
- defn(:greet, nil, _) { |name|
130
- "Goodbye, #{name}!"
131
- }
132
- defn(:greet, _, _) { |_, name|
133
- "Hello, #{name}!"
134
- }
135
- end
333
+ ```ruby
334
+ require 'pattern_matching'
335
+
336
+ class Foo
337
+ include PatternMatching
338
+
339
+ defn(:greet, _) do |name|
340
+ "Hello, #{name}!"
341
+ end
342
+
343
+ defn(:greet, :male, _) { |name|
344
+ "Hello, Mr. #{name}!"
345
+ }
346
+ defn(:greet, :female, _) { |name|
347
+ "Hello, Ms. #{name}!"
348
+ }
349
+ defn(:greet, nil, _) { |name|
350
+ "Goodbye, #{name}!"
351
+ }
352
+ defn(:greet, _, _) { |_, name|
353
+ "Hello, #{name}!"
354
+ }
355
+ end
356
+ ```
136
357
 
137
358
  ### Matching by Class/Datatype
138
359
 
139
- Ruby:
140
-
141
- require 'pattern_matching'
142
-
143
- class Foo
144
- include PatternMatching
145
-
146
- defn(:concat, Integer, Integer) { |first, second|
147
- first + second
148
- }
149
- defn(:concat, Integer, String) { |first, second|
150
- "#{first} #{second}"
151
- }
152
- defn(:concat, String, String) { |first, second|
153
- first + second
154
- }
155
- defn(:concat, Integer, _) { |first, second|
156
- first + second.to_i
157
- }
158
- end
360
+ ```ruby
361
+ require 'pattern_matching'
362
+
363
+ class Foo
364
+ include PatternMatching
365
+
366
+ defn(:concat, Integer, Integer) { |first, second|
367
+ first + second
368
+ }
369
+ defn(:concat, Integer, String) { |first, second|
370
+ "#{first} #{second}"
371
+ }
372
+ defn(:concat, String, String) { |first, second|
373
+ first + second
374
+ }
375
+ defn(:concat, Integer, _) { |first, second|
376
+ first + second.to_i
377
+ }
378
+ end
379
+ ```
159
380
 
160
381
  ### Matching a Hash Parameter
161
382
 
162
- Ruby:
383
+ ```ruby
384
+ require 'pattern_matching'
163
385
 
164
- require 'pattern_matching'
165
-
166
- class Foo
167
- include PatternMatching
168
-
169
- defn(:hashable, {foo: :bar}) { |opts|
170
- # matches any hash with key :foo and value :bar
171
- :foo_bar
172
- }
173
- defn(:hashable, {foo: _}) { |opts|
174
- # matches any hash with :key foo regardless of value
175
- :foo_unbound
176
- }
177
- defn(:hashable, {}) { |opts|
178
- # matches any hash
179
- :unbound_unbound
180
- }
181
- end
386
+ class Foo
387
+ include PatternMatching
388
+
389
+ defn(:hashable, {foo: :bar}) { |opts|
390
+ # matches any hash with key :foo and value :bar
391
+ :foo_bar
392
+ }
393
+ defn(:hashable, {foo: _, bar: _}) { |f, b|
394
+ # matches any hash with keys :foo and :bar
395
+ # passes the values associated with those keys to the block
396
+ [f, b]
397
+ }
398
+ defn(:hashable, {foo: _}) { |f|
399
+ # matches any hash with key :foo
400
+ # passes the value associated with that key to the block
401
+ # must appear AFTER the prior match or it will override that one
402
+ f
403
+ }
404
+ defn(:hashable, {}) { ||
405
+ # matches an empty hash
406
+ :empty
407
+ }
408
+ defn(:hashable, _) { |opts|
409
+ # matches any hash (or any other value)
410
+ opts
411
+ }
412
+ end
413
+
414
+ ...
415
+
416
+ foo.hashable({foo: :bar}) #=> :foo_bar
417
+ foo.hashable({foo: :baz}) #=> :baz
418
+ foo.hashable({foo: 1, bar: 2}) #=> [1, 2]
419
+ foo.hashable({foo: 1, baz: 2}) #=> 1
420
+ foo.hashable({bar: :baz}) #=> {bar: :baz}
421
+ foo.hashable({}) #=> :empty
422
+ ```
423
+
424
+ ### Variable Length Argument Lists with ALL
425
+
426
+ ```ruby
427
+ defn(:all, :one, ALL) { |args|
428
+ args
429
+ }
430
+ defn(:all, :one, Integer, ALL) { |int, args|
431
+ [int, args]
432
+ }
433
+ defn(:all, 1, _, ALL) { |var, args|
434
+ [var, args]
435
+ }
436
+ defn(:all, ALL) { | args|
437
+ args
438
+ }
439
+
440
+ ...
441
+
442
+ foo.all(:one, 'a', 'bee', :see) #=> ['a', 'bee', :see]
443
+ foo.all(:one, 1, 'bee', :see) #=> [1, 'bee', :see]
444
+ foo.all(1, 'a', 'bee', :see) #=> ['a', ['bee', :see]]
445
+ foo.all('a', 'bee', :see) #=> ['a', 'bee', :see]
446
+ foo.all() #=> NoMethodError: no method `all` matching [] found for class Foo
447
+ ```
448
+
449
+ ## Copyright
450
+
451
+ *PatternMatching* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
452
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
453
+
454
+ ## License
455
+
456
+ Released under the MIT license.
457
+
458
+ http://www.opensource.org/licenses/mit-license.php
459
+
460
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
461
+ > of this software and associated documentation files (the "Software"), to deal
462
+ > in the Software without restriction, including without limitation the rights
463
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
464
+ > copies of the Software, and to permit persons to whom the Software is
465
+ > furnished to do so, subject to the following conditions:
466
+ >
467
+ > The above copyright notice and this permission notice shall be included in
468
+ > all copies or substantial portions of the Software.
469
+ >
470
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
471
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
472
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
473
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
474
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
475
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
476
+ > THE SOFTWARE.
@@ -1,20 +1,22 @@
1
1
  module PatternMatching
2
2
 
3
- VERSION = '0.0.1'
3
+ VERSION = '0.1.0'
4
4
 
5
5
  UNBOUND = Unbound = Class.new
6
+ ALL = All = Class.new
6
7
 
7
8
  def self.included(base)
8
9
 
9
10
  base.instance_variable_set(:@__function_pattern_matches__, Hash.new)
10
11
 
11
12
  def __match_pattern__(args, pattern) # :nodoc:
12
- return unless args.length == pattern.length
13
+ return unless (pattern.last == ALL && args.length >= pattern.length) \
14
+ || (args.length == pattern.length)
13
15
  pattern.each_with_index do |p, i|
16
+ break if p == ALL && i+1 == pattern.length
14
17
  arg = args[i]
15
18
  next if p.is_a?(Class) && arg.is_a?(p)
16
- if p.is_a?(Hash) && arg.is_a?(Hash)
17
- next if p.empty?
19
+ if p.is_a?(Hash) && arg.is_a?(Hash) && ! p.empty?
18
20
  p.each do |key, value|
19
21
  return false unless arg.has_key?(key)
20
22
  next if value == UNBOUND
@@ -29,26 +31,30 @@ module PatternMatching
29
31
 
30
32
  def __pattern_match__(func, *args, &block) # :nodoc:
31
33
  clazz = self.class
34
+ args = args.first
32
35
 
33
- matchers = clazz.instance_variable_get(:@__function_pattern_matches__)
34
- matchers = matchers[func]
36
+ # get the array of matchers for this function
37
+ matchers = clazz.instance_variable_get(:@__function_pattern_matches__)[func]
35
38
 
36
39
  # scan through all patterns for this function
37
- match = nil
38
- matchers.each do |matcher|
39
- if __match_pattern__(args.first, matcher.first)
40
- match = matcher
41
- break(matcher)
42
- end
43
- end
40
+ index = matchers.index{|matcher| __match_pattern__(args, matcher.first)}
44
41
 
45
- # if a match is found call the block
46
- if match.nil?
42
+ if index.nil?
47
43
  [:nomatch, nil]
48
44
  else
45
+ # if a match is found call the block
49
46
  argv = []
47
+ match = matchers[index]
50
48
  match.first.each_with_index do |p, i|
51
- argv << args.first[i] if p == UNBOUND || p.is_a?(Class) || p.is_a?(Hash)
49
+ if p == ALL && i == match.first.length-1
50
+ argv << args[(i..args.length)].reduce([]){|memo, arg| memo << arg }
51
+ elsif p.is_a?(Hash) && p.values.include?(UNBOUND)
52
+ p.each do |key, value|
53
+ argv << args[i][key] if value == UNBOUND
54
+ end
55
+ elsif p.is_a?(Hash) || p == UNBOUND || p.is_a?(Class)
56
+ argv << args[i]
57
+ end
52
58
  end
53
59
  return [:ok, self.instance_exec(*argv, &match.last)]
54
60
  end
@@ -56,6 +62,9 @@ module PatternMatching
56
62
 
57
63
  class << base
58
64
 
65
+ UNBOUND = PatternMatching::UNBOUND
66
+ ALL = PatternMatching::ALL
67
+
59
68
  def _() # :nodoc:
60
69
  return UNBOUND
61
70
  end
@@ -39,11 +39,21 @@ describe 'integration' do
39
39
  defn(:hashable, _, {foo: :bar}, _) { |_, opts, _|
40
40
  :foo_bar
41
41
  }
42
- defn(:hashable, _, {foo: _}, _) { |_, opts, _|
43
- :foo_unbound
42
+ defn(:hashable, _, {foo: _, bar: _}, _) { |_, f, b, _|
43
+ [f, b]
44
44
  }
45
- defn(:hashable, _, {}, _) { |_, opts, _|
46
- :unbound_unbound
45
+ defn(:hashable, _, {foo: _}, _) { |_, f, _|
46
+ f
47
+ }
48
+ defn(:hashable, _, {}, _) {
49
+ :empty
50
+ }
51
+ defn(:hashable, _, _, _) { |_, _, _|
52
+ :unbound
53
+ }
54
+
55
+ defn(:options, _) { |opts|
56
+ opts
47
57
  }
48
58
 
49
59
  defn(:recurse) {
@@ -71,9 +81,22 @@ describe 'integration' do
71
81
  defn(:concat, String, String) { |first, second|
72
82
  first + second
73
83
  }
74
- defn(:concat, Integer, _) { |first, second|
84
+ defn(:concat, Integer, UNBOUND) { |first, second|
75
85
  first + second.to_i
76
86
  }
87
+
88
+ defn(:all, :one, ALL) { |args|
89
+ args
90
+ }
91
+ defn(:all, :one, Integer, ALL) { |int, args|
92
+ [int, args]
93
+ }
94
+ defn(:all, 1, _, ALL) { |var, args|
95
+ [var, args]
96
+ }
97
+ defn(:all, ALL) { | args|
98
+ args
99
+ }
77
100
  end
78
101
 
79
102
  let(:name) { 'Pattern Matcher' }
@@ -88,10 +111,14 @@ describe 'integration' do
88
111
  specify { subject.greet(:unknown, 'Jerry').should eq 'Hello, Jerry!' }
89
112
  specify { subject.greet(nil, 'Jerry').should eq 'Goodbye, Jerry!' }
90
113
 
114
+ specify { subject.options(bar: :baz, one: 1, many: 2).should == {bar: :baz, one: 1, many: 2} }
115
+
91
116
  specify { subject.hashable(:male, {foo: :bar}, :female).should eq :foo_bar }
92
- specify { subject.hashable(:male, {foo: :baz}, :female).should eq :foo_unbound }
93
- specify { subject.hashable(:male, {foo: :bar, bar: :baz}, :female).should eq :foo_bar }
94
- specify { subject.hashable(:male, {bar: :baz}, :female).should eq :unbound_unbound }
117
+ specify { subject.hashable(:male, {foo: :baz}, :female).should eq :baz }
118
+ specify { subject.hashable(:male, {foo: 1, bar: 2}, :female).should eq [1, 2] }
119
+ specify { subject.hashable(:male, {foo: 1, baz: 2}, :female).should eq 1 }
120
+ specify { subject.hashable(:male, {bar: :baz}, :female).should eq :unbound }
121
+ specify { subject.hashable(:male, {}, :female).should eq :empty }
95
122
 
96
123
  specify { subject.recurse.should eq 'w00t!' }
97
124
  specify { subject.recurse(:match).should eq 'w00t!' }
@@ -104,4 +131,10 @@ describe 'integration' do
104
131
  specify { subject.concat('shoe', 'fly').should eq 'shoefly' }
105
132
  specify { subject.concat(1, 2.9).should eq 3 }
106
133
 
134
+ specify { subject.all(:one, 'a', 'bee', :see).should == ['a', 'bee', :see] }
135
+ specify { subject.all(:one, 1, 'bee', :see).should == [1, 'bee', :see] }
136
+ specify { subject.all(1, 'a', 'bee', :see).should == ['a', ['bee', :see]] }
137
+ specify { subject.all('a', 'bee', :see).should == ['a', 'bee', :see] }
138
+ specify { lambda { subject.all }.should raise_error(NoMethodError) }
139
+
107
140
  end
@@ -277,20 +277,24 @@ describe PatternMatching do
277
277
 
278
278
  context 'functions with hash arguments' do
279
279
 
280
- it 'matches when all hash keys and values match' do
280
+ it 'matches an empty argument hash with an empty parameter hash' do
281
281
 
282
- subject.defn(:foo, {bar: :baz}) { true }
283
- subject.new.foo(bar: :baz).should be_true
282
+ subject.defn(:foo, {}) { true }
283
+ subject.new.foo({}).should be_true
284
284
 
285
285
  lambda {
286
286
  subject.new.foo({one: :two})
287
287
  }.should raise_error(NoMethodError)
288
288
  end
289
289
 
290
- it 'matches when the pattern uses an empty hash' do
290
+ it 'matches when all hash keys and values match' do
291
291
 
292
- subject.defn(:foo, {}) { true }
292
+ subject.defn(:foo, {bar: :baz}) { true }
293
293
  subject.new.foo(bar: :baz).should be_true
294
+
295
+ lambda {
296
+ subject.new.foo({one: :two})
297
+ }.should raise_error(NoMethodError)
294
298
  end
295
299
 
296
300
  it 'matches when every pattern key/value are in the argument' do
@@ -305,9 +309,15 @@ describe PatternMatching do
305
309
  subject.new.foo(bar: :baz).should be_true
306
310
  end
307
311
 
312
+ it 'passes unbound values to the block' do
313
+
314
+ subject.defn(:foo, {bar: PatternMatching::UNBOUND}) {|arg| arg }
315
+ subject.new.foo(bar: :baz).should eq :baz
316
+ end
317
+
308
318
  it 'passes the matched hash to the block' do
309
319
 
310
- subject.defn(:foo, {bar: PatternMatching::UNBOUND}) { |args| args }
320
+ subject.defn(:foo, {bar: :baz}) { |opts| opts }
311
321
  subject.new.foo(bar: :baz).should == {bar: :baz}
312
322
  end
313
323
 
@@ -319,6 +329,23 @@ describe PatternMatching do
319
329
  subject.new.foo(:bar)
320
330
  }.should raise_error(NoMethodError)
321
331
  end
332
+
333
+ it 'supports idiomatic has-as-last-argument syntax' do
334
+
335
+ subject.defn(:foo, PatternMatching::UNBOUND) { |opts| opts }
336
+ subject.new.foo(bar: :baz, one: 1, many: 2).should == {bar: :baz, one: 1, many: 2}
337
+ end
338
+ end
339
+
340
+ context 'varaible-length argument lists' do
341
+
342
+ it 'supports ALL as the last parameter' do
343
+
344
+ subject.defn(:foo, 1, 2, PatternMatching::ALL) { |args| args }
345
+ subject.new.foo(1, 2, 3).should == [3]
346
+ subject.new.foo(1, 2, :foo, :bar).should == [:foo, :bar]
347
+ subject.new.foo(1, 2, :foo, :bar, one: 1, two: 2).should == [:foo, :bar, {one: 1, two: 2}]
348
+ end
322
349
  end
323
350
 
324
351
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pattern-matching
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jerry D'Antonio
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-05-05 00:00:00.000000000 Z
11
+ date: 2013-05-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler