pattern-matching 0.0.1 → 0.1.0

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