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 +4 -4
- data/README.md +394 -99
- data/lib/pattern_matching.rb +25 -16
- data/spec/integration_spec.rb +41 -8
- data/spec/pattern_matching_spec.rb +33 -6
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 26d3f6768306dd2dba389a7dd481e135417164de
|
4
|
+
data.tar.gz: 5bdc90e25e848b7de5ce67d58f7cce70d1a7e434
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
|
57
|
+
```shell
|
58
|
+
gem install pattern-matching
|
59
|
+
```
|
46
60
|
|
47
61
|
or add the following line to Gemfile:
|
48
62
|
|
49
|
-
|
63
|
+
```ruby
|
64
|
+
gem 'pattern-matching'
|
65
|
+
```
|
50
66
|
|
51
67
|
and run `bundle install` from your shell.
|
52
68
|
|
53
|
-
##
|
69
|
+
## Usage
|
54
70
|
|
55
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
291
|
+
```ruby
|
292
|
+
require 'pattern_matching'
|
77
293
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
103
|
-
|
319
|
+
```erlang
|
320
|
+
greet(Name) ->
|
321
|
+
io:format("Hello, ~s!", [Name]).
|
104
322
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
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
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
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
|
-
|
383
|
+
```ruby
|
384
|
+
require 'pattern_matching'
|
163
385
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
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 © 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.
|
data/lib/pattern_matching.rb
CHANGED
@@ -1,20 +1,22 @@
|
|
1
1
|
module PatternMatching
|
2
2
|
|
3
|
-
VERSION = '0.0
|
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
|
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
|
34
|
-
matchers =
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/spec/integration_spec.rb
CHANGED
@@ -39,11 +39,21 @@ describe 'integration' do
|
|
39
39
|
defn(:hashable, _, {foo: :bar}, _) { |_, opts, _|
|
40
40
|
:foo_bar
|
41
41
|
}
|
42
|
-
defn(:hashable, _, {foo: _}, _) { |_,
|
43
|
-
|
42
|
+
defn(:hashable, _, {foo: _, bar: _}, _) { |_, f, b, _|
|
43
|
+
[f, b]
|
44
44
|
}
|
45
|
-
defn(:hashable, _, {}, _) { |_,
|
46
|
-
|
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,
|
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 :
|
93
|
-
specify { subject.hashable(:male, {foo:
|
94
|
-
specify { subject.hashable(:male, {
|
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
|
280
|
+
it 'matches an empty argument hash with an empty parameter hash' do
|
281
281
|
|
282
|
-
subject.defn(:foo, {
|
283
|
-
subject.new.foo(
|
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
|
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:
|
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
|
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-
|
11
|
+
date: 2013-05-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|