pattern-matching 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +181 -0
- data/lib/pattern_matching.rb +90 -0
- data/spec/integration_spec.rb +107 -0
- data/spec/pattern_matching_spec.rb +324 -0
- data/spec/spec_helper.rb +17 -0
- metadata +70 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dc55948d3772a5d4805826b923d12ae1f84f09cd
|
4
|
+
data.tar.gz: 6cf392b7bd93afe0e752c0fca586c2454ca583c1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 630fb32894b0074e0e3f273f8d83c37e845c1becc3609e532d226e2e7308389bf744296d9ea411fa8e0b2dbb3c51f21c87e22db37db46726128d4be6dbbd0a15
|
7
|
+
data.tar.gz: 9369b4042e2cd7760cd2ade36aa5c7e8cdc545f5071e20b67af0c0f98e6971ba17c193899ff6fdd8f6efd1608093e3b4530d129f3f8e16419a66af051224c254
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) Jerry D'Antonio -- released under the MIT license.
|
2
|
+
|
3
|
+
http://www.opensource.org/licenses/mit-license.php
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
# PatternMatching [![Build Status](https://secure.travis-ci.org/jdantonio/pattern_matching.png)](http://travis-ci.org/jdantonio/pattern_matching?branch=master) [![Dependency Status](https://gemnasium.com/jdantonio/pattern_matching.png)](https://gemnasium.com/jdantonio/pattern_matching)
|
2
|
+
|
3
|
+
A gem for adding Erlang-style pattern matching to Ruby classes.
|
4
|
+
|
5
|
+
*NOTE: This is a work in progress. Expect changes.*
|
6
|
+
|
7
|
+
## Introduction
|
8
|
+
|
9
|
+
[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/)…
|
10
|
+
|
11
|
+
I've really started to enjoy working in Erlang. Erlang is good at all the things Ruby is bad at and vice versa. Together, Ruby and Erlang make me happy. My favotite Erlang feature is, without question, [pattern matching](http://learnyousomeerlang.com/syntax-in-functions#pattern-matching). Pattern matching is like function overloading cranked to 11. So one day I was musing on Twitter and one of my friends responded with "Build it!" So I did. And here it is.
|
12
|
+
|
13
|
+
### Goals
|
14
|
+
|
15
|
+
* Stay true to the spirit of Erlang pattern matching, if not the semantics
|
16
|
+
* Keep the semantics as idiomatic Ruby as possible
|
17
|
+
* Support features that make sense in Ruby
|
18
|
+
* Exclude features that only make sense in Erlang
|
19
|
+
* Avoid using #method_missing
|
20
|
+
|
21
|
+
### Features
|
22
|
+
|
23
|
+
* Basic pattern matching for instance methods.
|
24
|
+
* Parameter count matching
|
25
|
+
* Mathing against primitive values
|
26
|
+
* Matching by class/datatype
|
27
|
+
* Matching against specific key/vaue pairs in hashes
|
28
|
+
* Matching against the presence of keys within hashes
|
29
|
+
* Reasonable error messages when no match is found
|
30
|
+
* Dispatching to superclass methods when no match is found
|
31
|
+
* Recursive calls to other pattern matches
|
32
|
+
* Recursive calls to superclass methods
|
33
|
+
|
34
|
+
### To-do
|
35
|
+
|
36
|
+
* Variable-length argument lists
|
37
|
+
* Matching against array elements
|
38
|
+
* Guard clauses
|
39
|
+
* Support class methods
|
40
|
+
* Support module instance methods
|
41
|
+
* Support module methods
|
42
|
+
|
43
|
+
## Install
|
44
|
+
|
45
|
+
gem install pattern-matching
|
46
|
+
|
47
|
+
or add the following line to Gemfile:
|
48
|
+
|
49
|
+
gem 'pattern-matching
|
50
|
+
|
51
|
+
and run `bundle install` from your shell.
|
52
|
+
|
53
|
+
## Supported Ruby versions
|
54
|
+
|
55
|
+
MRI 1.9.x and above. Anything else and your mileage may vary.
|
56
|
+
|
57
|
+
## Examples
|
58
|
+
|
59
|
+
For more examples see the integration tests in *spec/integration_spec.rb*.
|
60
|
+
|
61
|
+
### Simple Functions
|
62
|
+
|
63
|
+
This example is based on [Syntax in defnctions: Pattern Matching](http://learnyousomeerlang.com/syntax-in-defnctions) in [Learn You Some Erlang for Great Good!](http://learnyousomeerlang.com/).
|
64
|
+
|
65
|
+
Erlang:
|
66
|
+
|
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]).
|
73
|
+
|
74
|
+
Ruby:
|
75
|
+
|
76
|
+
require 'pattern_matching'
|
77
|
+
|
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
|
95
|
+
|
96
|
+
### Simple Functions with Overloading
|
97
|
+
|
98
|
+
This example is based on [Syntax in defnctions: Pattern Matching](http://learnyousomeerlang.com/syntax-in-defnctions) in [Learn You Some Erlang for Great Good!](http://learnyousomeerlang.com/).
|
99
|
+
|
100
|
+
Erlang:
|
101
|
+
|
102
|
+
greet(Name) ->
|
103
|
+
io:format("Hello, ~s!", [Name]).
|
104
|
+
|
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]).
|
111
|
+
|
112
|
+
Ruby:
|
113
|
+
|
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
|
136
|
+
|
137
|
+
### Matching by Class/Datatype
|
138
|
+
|
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
|
159
|
+
|
160
|
+
### Matching a Hash Parameter
|
161
|
+
|
162
|
+
Ruby:
|
163
|
+
|
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
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module PatternMatching
|
2
|
+
|
3
|
+
VERSION = '0.0.1'
|
4
|
+
|
5
|
+
UNBOUND = Unbound = Class.new
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
|
9
|
+
base.instance_variable_set(:@__function_pattern_matches__, Hash.new)
|
10
|
+
|
11
|
+
def __match_pattern__(args, pattern) # :nodoc:
|
12
|
+
return unless args.length == pattern.length
|
13
|
+
pattern.each_with_index do |p, i|
|
14
|
+
arg = args[i]
|
15
|
+
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?
|
18
|
+
p.each do |key, value|
|
19
|
+
return false unless arg.has_key?(key)
|
20
|
+
next if value == UNBOUND
|
21
|
+
return false unless arg[key] == value
|
22
|
+
end
|
23
|
+
next
|
24
|
+
end
|
25
|
+
return false unless p == UNBOUND || p == arg
|
26
|
+
end
|
27
|
+
return true
|
28
|
+
end
|
29
|
+
|
30
|
+
def __pattern_match__(func, *args, &block) # :nodoc:
|
31
|
+
clazz = self.class
|
32
|
+
|
33
|
+
matchers = clazz.instance_variable_get(:@__function_pattern_matches__)
|
34
|
+
matchers = matchers[func]
|
35
|
+
|
36
|
+
# 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
|
44
|
+
|
45
|
+
# if a match is found call the block
|
46
|
+
if match.nil?
|
47
|
+
[:nomatch, nil]
|
48
|
+
else
|
49
|
+
argv = []
|
50
|
+
match.first.each_with_index do |p, i|
|
51
|
+
argv << args.first[i] if p == UNBOUND || p.is_a?(Class) || p.is_a?(Hash)
|
52
|
+
end
|
53
|
+
return [:ok, self.instance_exec(*argv, &match.last)]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class << base
|
58
|
+
|
59
|
+
def _() # :nodoc:
|
60
|
+
return UNBOUND
|
61
|
+
end
|
62
|
+
|
63
|
+
def __add_pattern_for__(func, *args, &block) # :nodoc:
|
64
|
+
block = Proc.new{} unless block_given?
|
65
|
+
matchers = self.instance_variable_get(:@__function_pattern_matches__)
|
66
|
+
matchers[func] = [] unless matchers.has_key?(func)
|
67
|
+
matchers[func] << [args, block]
|
68
|
+
end
|
69
|
+
|
70
|
+
def defn(func, *args, &block)
|
71
|
+
|
72
|
+
block = Proc.new{} unless block_given?
|
73
|
+
__add_pattern_for__(func, *args, &block)
|
74
|
+
|
75
|
+
unless self.instance_methods(false).include?(func)
|
76
|
+
self.send(:define_method, func) do |*args, &block|
|
77
|
+
result, value = __pattern_match__(func, args, block)
|
78
|
+
return value if result == :ok
|
79
|
+
begin
|
80
|
+
super(*args, &block)
|
81
|
+
rescue NoMethodError
|
82
|
+
raise NoMethodError.new("no method `#{func}` matching #{args} found for class #{self.class}")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
describe 'integration' do
|
5
|
+
|
6
|
+
class Bar
|
7
|
+
|
8
|
+
def greet
|
9
|
+
return 'Hello, World!'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Foo < Bar
|
14
|
+
include PatternMatching
|
15
|
+
|
16
|
+
attr_accessor :name
|
17
|
+
|
18
|
+
def initialize(name = 'baz')
|
19
|
+
@name = name
|
20
|
+
end
|
21
|
+
|
22
|
+
defn(:greet, _) do |name|
|
23
|
+
"Hello, #{name}!"
|
24
|
+
end
|
25
|
+
|
26
|
+
defn(:greet, :male, _) { |name|
|
27
|
+
"Hello, Mr. #{name}!"
|
28
|
+
}
|
29
|
+
defn(:greet, :female, _) { |name|
|
30
|
+
"Hello, Ms. #{name}!"
|
31
|
+
}
|
32
|
+
defn(:greet, nil, _) { |name|
|
33
|
+
"Goodbye, #{name}!"
|
34
|
+
}
|
35
|
+
defn(:greet, _, _) { |_, name|
|
36
|
+
"Hello, #{name}!"
|
37
|
+
}
|
38
|
+
|
39
|
+
defn(:hashable, _, {foo: :bar}, _) { |_, opts, _|
|
40
|
+
:foo_bar
|
41
|
+
}
|
42
|
+
defn(:hashable, _, {foo: _}, _) { |_, opts, _|
|
43
|
+
:foo_unbound
|
44
|
+
}
|
45
|
+
defn(:hashable, _, {}, _) { |_, opts, _|
|
46
|
+
:unbound_unbound
|
47
|
+
}
|
48
|
+
|
49
|
+
defn(:recurse) {
|
50
|
+
'w00t!'
|
51
|
+
}
|
52
|
+
defn(:recurse, :match) {
|
53
|
+
recurse()
|
54
|
+
}
|
55
|
+
defn(:recurse, :super) {
|
56
|
+
greet()
|
57
|
+
}
|
58
|
+
defn(:recurse, :instance) {
|
59
|
+
@name
|
60
|
+
}
|
61
|
+
defn(:recurse, _) { |arg|
|
62
|
+
arg
|
63
|
+
}
|
64
|
+
|
65
|
+
defn(:concat, Integer, Integer) { |first, second|
|
66
|
+
first + second
|
67
|
+
}
|
68
|
+
defn(:concat, Integer, String) { |first, second|
|
69
|
+
"#{first} #{second}"
|
70
|
+
}
|
71
|
+
defn(:concat, String, String) { |first, second|
|
72
|
+
first + second
|
73
|
+
}
|
74
|
+
defn(:concat, Integer, _) { |first, second|
|
75
|
+
first + second.to_i
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
let(:name) { 'Pattern Matcher' }
|
80
|
+
subject { Foo.new(name) }
|
81
|
+
|
82
|
+
specify { subject.greet.should eq 'Hello, World!' }
|
83
|
+
|
84
|
+
specify { subject.greet('Jerry').should eq 'Hello, Jerry!' }
|
85
|
+
|
86
|
+
specify { subject.greet(:male, 'Jerry').should eq 'Hello, Mr. Jerry!' }
|
87
|
+
specify { subject.greet(:female, 'Jeri').should eq 'Hello, Ms. Jeri!' }
|
88
|
+
specify { subject.greet(:unknown, 'Jerry').should eq 'Hello, Jerry!' }
|
89
|
+
specify { subject.greet(nil, 'Jerry').should eq 'Goodbye, Jerry!' }
|
90
|
+
|
91
|
+
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 }
|
95
|
+
|
96
|
+
specify { subject.recurse.should eq 'w00t!' }
|
97
|
+
specify { subject.recurse(:match).should eq 'w00t!' }
|
98
|
+
specify { subject.recurse(:super).should eq 'Hello, World!' }
|
99
|
+
specify { subject.recurse(:instance).should eq name }
|
100
|
+
specify { subject.recurse(:foo).should eq :foo }
|
101
|
+
|
102
|
+
specify { subject.concat(1, 1).should eq 2 }
|
103
|
+
specify { subject.concat(1, 'shoe').should eq '1 shoe' }
|
104
|
+
specify { subject.concat('shoe', 'fly').should eq 'shoefly' }
|
105
|
+
specify { subject.concat(1, 2.9).should eq 3 }
|
106
|
+
|
107
|
+
end
|
@@ -0,0 +1,324 @@
|
|
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 'parameter count' do
|
36
|
+
|
37
|
+
it 'does not match a call with not enough arguments' do
|
38
|
+
|
39
|
+
subject.defn(:foo, true) { 'expected' }
|
40
|
+
|
41
|
+
lambda {
|
42
|
+
subject.new.foo()
|
43
|
+
}.should raise_error(NoMethodError)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'does not match a call with too many arguments' do
|
47
|
+
|
48
|
+
subject.defn(:foo, true) { 'expected' }
|
49
|
+
|
50
|
+
lambda {
|
51
|
+
subject.new.foo(true, false)
|
52
|
+
}.should raise_error(NoMethodError)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'recursion' do
|
58
|
+
|
59
|
+
it 'defers unmatched calls to the superclass' do
|
60
|
+
|
61
|
+
class UnmatchedCallTesterSuperclass
|
62
|
+
def foo(bar)
|
63
|
+
return bar
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class UnmatchedCallTesterSubclass < UnmatchedCallTesterSuperclass
|
68
|
+
include PatternMatching
|
69
|
+
defn(:foo) { 'baz' }
|
70
|
+
end
|
71
|
+
|
72
|
+
subject = UnmatchedCallTesterSubclass.new
|
73
|
+
subject.foo(:bar).should eq :bar
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'can call another match from within a match' do
|
77
|
+
|
78
|
+
subject.defn(:foo, :bar) { |arg| foo(:baz) }
|
79
|
+
subject.defn(:foo, :baz) { |arg| 'expected' }
|
80
|
+
|
81
|
+
subject.new.foo(:bar).should eq 'expected'
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'can call a superclass method from within a match' do
|
85
|
+
|
86
|
+
class RecursiveCallTesterSuperclass
|
87
|
+
def foo(bar)
|
88
|
+
return bar
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class RecursiveCallTesterSubclass < RecursiveCallTesterSuperclass
|
93
|
+
include PatternMatching
|
94
|
+
defn(:foo, :bar) { foo(:baz) }
|
95
|
+
end
|
96
|
+
|
97
|
+
subject = RecursiveCallTesterSubclass.new
|
98
|
+
subject.foo(:bar).should eq :baz
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'datatypes' do
|
103
|
+
|
104
|
+
it 'matches an argument of the class given in the match parameter' do
|
105
|
+
|
106
|
+
subject.defn(:foo, Integer) { 'expected' }
|
107
|
+
subject.new.foo(100).should eq 'expected'
|
108
|
+
|
109
|
+
lambda {
|
110
|
+
subject.new.foo('hello')
|
111
|
+
}.should raise_error(NoMethodError)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'passes the matched argument to the block' do
|
115
|
+
|
116
|
+
subject.defn(:foo, Integer) { |arg| arg }
|
117
|
+
subject.new.foo(100).should eq 100
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'function with no parameters' do
|
122
|
+
|
123
|
+
it 'accepts no parameters' do
|
124
|
+
|
125
|
+
subject.defn(:foo)
|
126
|
+
obj = subject.new
|
127
|
+
|
128
|
+
lambda {
|
129
|
+
obj.foo
|
130
|
+
}.should_not raise_error(NoMethodError)
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'does not accept any parameters' do
|
134
|
+
|
135
|
+
subject.defn(:foo)
|
136
|
+
obj = subject.new
|
137
|
+
|
138
|
+
lambda {
|
139
|
+
obj.foo(1)
|
140
|
+
}.should raise_error(NoMethodError)
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'returns the correct value' do
|
144
|
+
subject.defn(:foo){ true }
|
145
|
+
subject.new.foo.should be_true
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context 'function with one parameter' do
|
150
|
+
|
151
|
+
it 'matches a nil parameter' do
|
152
|
+
|
153
|
+
subject.defn(:foo, nil) { 'expected' }
|
154
|
+
subject.new.foo(nil).should eq 'expected'
|
155
|
+
|
156
|
+
lambda {
|
157
|
+
subject.new.foo('no match should be found')
|
158
|
+
}.should raise_error(NoMethodError)
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'matches a boolean parameter' do
|
162
|
+
|
163
|
+
subject.defn(:foo, true) { 'expected' }
|
164
|
+
subject.defn(:foo, false) { 'false case' }
|
165
|
+
|
166
|
+
subject.new.foo(true).should eq 'expected'
|
167
|
+
subject.new.foo(false).should eq 'false case'
|
168
|
+
|
169
|
+
lambda {
|
170
|
+
subject.new.foo('no match should be found')
|
171
|
+
}.should raise_error(NoMethodError)
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'matches a symbol parameter' do
|
175
|
+
|
176
|
+
subject.defn(:foo, :bar) { 'expected' }
|
177
|
+
subject.new.foo(:bar).should eq 'expected'
|
178
|
+
|
179
|
+
lambda {
|
180
|
+
subject.new.foo(:baz)
|
181
|
+
}.should raise_error(NoMethodError)
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'matches a number parameter' do
|
185
|
+
|
186
|
+
subject.defn(:foo, 10) { 'expected' }
|
187
|
+
subject.new.foo(10).should eq 'expected'
|
188
|
+
|
189
|
+
lambda {
|
190
|
+
subject.new.foo(11.0)
|
191
|
+
}.should raise_error(NoMethodError)
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'matches a string parameter' do
|
195
|
+
|
196
|
+
subject.defn(:foo, 'bar') { 'expected' }
|
197
|
+
subject.new.foo('bar').should eq 'expected'
|
198
|
+
|
199
|
+
lambda {
|
200
|
+
subject.new.foo('baz')
|
201
|
+
}.should raise_error(NoMethodError)
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'matches an array parameter' do
|
205
|
+
|
206
|
+
subject.defn(:foo, [1, 2, 3]) { 'expected' }
|
207
|
+
subject.new.foo([1, 2, 3]).should eq 'expected'
|
208
|
+
|
209
|
+
lambda {
|
210
|
+
subject.new.foo([3, 4, 5])
|
211
|
+
}.should raise_error(NoMethodError)
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'matches a hash parameter' do
|
215
|
+
|
216
|
+
subject.defn(:foo, bar: 1, baz: 2) { 'expected' }
|
217
|
+
subject.new.foo(bar: 1, baz: 2).should eq 'expected'
|
218
|
+
|
219
|
+
lambda {
|
220
|
+
subject.new.foo(foo: 0, bar: 1)
|
221
|
+
}.should raise_error(NoMethodError)
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'matches an object parameter' do
|
225
|
+
|
226
|
+
subject.defn(:foo, OpenStruct.new(foo: :bar)) { 'expected' }
|
227
|
+
subject.new.foo(OpenStruct.new(foo: :bar)).should eq 'expected'
|
228
|
+
|
229
|
+
lambda {
|
230
|
+
subject.new.foo(OpenStruct.new(bar: :baz))
|
231
|
+
}.should raise_error(NoMethodError)
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'matches an unbound parameter' do
|
235
|
+
|
236
|
+
subject.defn(:foo, PatternMatching::UNBOUND) {|arg| arg }
|
237
|
+
subject.new.foo(:foo).should eq :foo
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
context 'function with two parameters' do
|
242
|
+
|
243
|
+
it 'matches two bound arguments' do
|
244
|
+
|
245
|
+
subject.defn(:foo, :male, :female){ 'expected' }
|
246
|
+
subject.new.foo(:male, :female).should eq 'expected'
|
247
|
+
|
248
|
+
lambda {
|
249
|
+
subject.new.foo(1, 2)
|
250
|
+
}.should raise_error(NoMethodError)
|
251
|
+
end
|
252
|
+
|
253
|
+
it 'matches two unbound arguments' do
|
254
|
+
|
255
|
+
subject.defn(:foo, PatternMatching::UNBOUND, PatternMatching::UNBOUND) do |first, second|
|
256
|
+
[first, second]
|
257
|
+
end
|
258
|
+
subject.new.foo(:male, :female).should eq [:male, :female]
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'matches when the first argument is bound and the second is not' do
|
262
|
+
|
263
|
+
subject.defn(:foo, :male, PatternMatching::UNBOUND) do |second|
|
264
|
+
second
|
265
|
+
end
|
266
|
+
subject.new.foo(:male, :female).should eq :female
|
267
|
+
end
|
268
|
+
|
269
|
+
it 'matches when the second argument is bound and the first is not' do
|
270
|
+
|
271
|
+
subject.defn(:foo, PatternMatching::UNBOUND, :female) do |first|
|
272
|
+
first
|
273
|
+
end
|
274
|
+
subject.new.foo(:male, :female).should eq :male
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
context 'functions with hash arguments' do
|
279
|
+
|
280
|
+
it 'matches when all hash keys and values match' do
|
281
|
+
|
282
|
+
subject.defn(:foo, {bar: :baz}) { true }
|
283
|
+
subject.new.foo(bar: :baz).should be_true
|
284
|
+
|
285
|
+
lambda {
|
286
|
+
subject.new.foo({one: :two})
|
287
|
+
}.should raise_error(NoMethodError)
|
288
|
+
end
|
289
|
+
|
290
|
+
it 'matches when the pattern uses an empty hash' do
|
291
|
+
|
292
|
+
subject.defn(:foo, {}) { true }
|
293
|
+
subject.new.foo(bar: :baz).should be_true
|
294
|
+
end
|
295
|
+
|
296
|
+
it 'matches when every pattern key/value are in the argument' do
|
297
|
+
|
298
|
+
subject.defn(:foo, {bar: :baz}) { true }
|
299
|
+
subject.new.foo(foo: :bar, bar: :baz).should be_true
|
300
|
+
end
|
301
|
+
|
302
|
+
it 'matches when all keys with unbound values in the pattern have an argument' do
|
303
|
+
|
304
|
+
subject.defn(:foo, {bar: PatternMatching::UNBOUND}) { true }
|
305
|
+
subject.new.foo(bar: :baz).should be_true
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'passes the matched hash to the block' do
|
309
|
+
|
310
|
+
subject.defn(:foo, {bar: PatternMatching::UNBOUND}) { |args| args }
|
311
|
+
subject.new.foo(bar: :baz).should == {bar: :baz}
|
312
|
+
end
|
313
|
+
|
314
|
+
it 'does not match a non-hash argument' do
|
315
|
+
|
316
|
+
subject.defn(:foo, {}) { true }
|
317
|
+
|
318
|
+
lambda {
|
319
|
+
subject.new.foo(:bar)
|
320
|
+
}.should raise_error(NoMethodError)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'pattern_matching'
|
2
|
+
|
3
|
+
# import all the support files
|
4
|
+
Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].each { |f| require File.expand_path(f) }
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
|
8
|
+
config.before(:suite) do
|
9
|
+
end
|
10
|
+
|
11
|
+
config.before(:each) do
|
12
|
+
end
|
13
|
+
|
14
|
+
config.after(:each) do
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pattern-matching
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jerry D'Antonio
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-05-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: |2
|
28
|
+
A gem for adding Erlang-style pattern matching to Ruby classes.
|
29
|
+
email: jerry.dantonio@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files:
|
33
|
+
- README.md
|
34
|
+
- LICENSE
|
35
|
+
files:
|
36
|
+
- README.md
|
37
|
+
- LICENSE
|
38
|
+
- lib/pattern_matching.rb
|
39
|
+
- spec/integration_spec.rb
|
40
|
+
- spec/pattern_matching_spec.rb
|
41
|
+
- spec/spec_helper.rb
|
42
|
+
homepage: https://github.com/jdantonio/pattern_matching/
|
43
|
+
licenses:
|
44
|
+
- MIT
|
45
|
+
metadata: {}
|
46
|
+
post_install_message: Happy matching!
|
47
|
+
rdoc_options: []
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.9.2
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
requirements: []
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 2.0.3
|
63
|
+
signing_key:
|
64
|
+
specification_version: 4
|
65
|
+
summary: Erlang-style pattern matching to Ruby classes.
|
66
|
+
test_files:
|
67
|
+
- spec/integration_spec.rb
|
68
|
+
- spec/pattern_matching_spec.rb
|
69
|
+
- spec/spec_helper.rb
|
70
|
+
has_rdoc:
|