pattern-matching 0.1.0 → 0.2.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 +14 -6
- data/LICENSE +21 -21
- data/README.md +536 -476
- data/lib/pattern_matching.rb +133 -99
- data/spec/integration_spec.rb +170 -140
- data/spec/pattern_matching_spec.rb +393 -351
- data/spec/spec_helper.rb +17 -17
- metadata +12 -10
data/lib/pattern_matching.rb
CHANGED
@@ -1,99 +1,133 @@
|
|
1
|
-
module PatternMatching
|
2
|
-
|
3
|
-
VERSION = '0.
|
4
|
-
|
5
|
-
UNBOUND =
|
6
|
-
ALL =
|
7
|
-
|
8
|
-
def self.included(base)
|
9
|
-
|
10
|
-
base.instance_variable_set(:@__function_pattern_matches__, Hash.new)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
pattern.
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
return false unless arg
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
return
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
end
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
1
|
+
module PatternMatching
|
2
|
+
|
3
|
+
VERSION = '0.2.0'
|
4
|
+
|
5
|
+
UNBOUND = Class.new
|
6
|
+
ALL = Class.new
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
|
10
|
+
base.instance_variable_set(:@__function_pattern_matches__, Hash.new)
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def __match_pattern__(args, pattern) # :nodoc:
|
15
|
+
return unless (pattern.last == ALL && args.length >= pattern.length) \
|
16
|
+
|| (args.length == pattern.length)
|
17
|
+
pattern.each_with_index do |p, i|
|
18
|
+
break if p == ALL && i+1 == pattern.length
|
19
|
+
arg = args[i]
|
20
|
+
next if p.is_a?(Class) && arg.is_a?(p)
|
21
|
+
if p.is_a?(Hash) && arg.is_a?(Hash) && ! p.empty?
|
22
|
+
p.each do |key, value|
|
23
|
+
return false unless arg.has_key?(key)
|
24
|
+
next if value == UNBOUND
|
25
|
+
return false unless arg[key] == value
|
26
|
+
end
|
27
|
+
next
|
28
|
+
end
|
29
|
+
return false unless p == UNBOUND || p == arg
|
30
|
+
end
|
31
|
+
return true
|
32
|
+
end
|
33
|
+
|
34
|
+
def __unbound_args__(match, args) # :nodoc:
|
35
|
+
argv = []
|
36
|
+
match.first.each_with_index do |p, i|
|
37
|
+
if p == ALL && i == match.first.length-1
|
38
|
+
argv << args[(i..args.length)].reduce([]){|memo, arg| memo << arg }
|
39
|
+
elsif p.is_a?(Hash) && p.values.include?(UNBOUND)
|
40
|
+
p.each do |key, value|
|
41
|
+
argv << args[i][key] if value == UNBOUND
|
42
|
+
end
|
43
|
+
elsif p.is_a?(Hash) || p == UNBOUND || p.is_a?(Class)
|
44
|
+
argv << args[i]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
return argv
|
48
|
+
end
|
49
|
+
|
50
|
+
def __pattern_match__(func, *args, &block) # :nodoc:
|
51
|
+
clazz = self.class
|
52
|
+
args = args.first
|
53
|
+
|
54
|
+
# get the array of matchers for this function
|
55
|
+
matchers = clazz.instance_variable_get(:@__function_pattern_matches__)[func]
|
56
|
+
|
57
|
+
# scan through all patterns for this function
|
58
|
+
index = matchers.index do |matcher|
|
59
|
+
if __match_pattern__(args, matcher.first)
|
60
|
+
if matcher.last.nil?
|
61
|
+
true # no guard clause
|
62
|
+
else
|
63
|
+
self.instance_exec(*__unbound_args__(matcher, args), &matcher.last)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
if index.nil?
|
69
|
+
return [:nomatch, nil]
|
70
|
+
else
|
71
|
+
# if a match is found call the block
|
72
|
+
match = matchers[index]
|
73
|
+
argv = __unbound_args__(match, args)
|
74
|
+
return [:ok, self.instance_exec(*argv, &match[1])]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class << base
|
79
|
+
|
80
|
+
public
|
81
|
+
|
82
|
+
def _() # :nodoc:
|
83
|
+
return UNBOUND
|
84
|
+
end
|
85
|
+
|
86
|
+
def defn(func, *args, &block)
|
87
|
+
|
88
|
+
guard = Class.new do
|
89
|
+
def initialize(func, clazz, matcher)
|
90
|
+
@func = func
|
91
|
+
@clazz = clazz
|
92
|
+
@matcher = matcher
|
93
|
+
end
|
94
|
+
def when(&block)
|
95
|
+
unless block_given?
|
96
|
+
raise ArgumentError.new("block missing for `when` guard on function `#{@func}` of class #{@clazz}")
|
97
|
+
end
|
98
|
+
@matcher[@matcher.length-1] = block
|
99
|
+
return nil
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
block = Proc.new{} unless block_given?
|
104
|
+
pattern = __add_pattern_for__(func, *args, &block)
|
105
|
+
|
106
|
+
unless self.instance_methods(false).include?(func)
|
107
|
+
self.send(:define_method, func) do |*args, &block|
|
108
|
+
result, value = __pattern_match__(func, args, block)
|
109
|
+
return value if result == :ok
|
110
|
+
begin
|
111
|
+
super(*args, &block)
|
112
|
+
rescue NoMethodError
|
113
|
+
raise NoMethodError.new("no method `#{func}` matching #{args} found for class #{self.class}")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
return guard.new(func, self, pattern)
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def __add_pattern_for__(func, *args, &block) # :nodoc:
|
124
|
+
block = Proc.new{} unless block_given?
|
125
|
+
matchers = self.instance_variable_get(:@__function_pattern_matches__)
|
126
|
+
matchers[func] = [] unless matchers.has_key?(func)
|
127
|
+
matchers[func] << [args, block, nil]
|
128
|
+
return matchers[func].last
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/spec/integration_spec.rb
CHANGED
@@ -1,140 +1,170 @@
|
|
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: _, bar: _}, _) { |_, f, b, _|
|
43
|
-
[f, b]
|
44
|
-
}
|
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
|
57
|
-
}
|
58
|
-
|
59
|
-
defn(:recurse) {
|
60
|
-
'w00t!'
|
61
|
-
}
|
62
|
-
defn(:recurse, :match) {
|
63
|
-
recurse()
|
64
|
-
}
|
65
|
-
defn(:recurse, :super) {
|
66
|
-
greet()
|
67
|
-
}
|
68
|
-
defn(:recurse, :instance) {
|
69
|
-
@name
|
70
|
-
}
|
71
|
-
defn(:recurse, _) { |arg|
|
72
|
-
arg
|
73
|
-
}
|
74
|
-
|
75
|
-
defn(:concat, Integer, Integer) { |first, second|
|
76
|
-
first + second
|
77
|
-
}
|
78
|
-
defn(:concat, Integer, String) { |first, second|
|
79
|
-
"#{first} #{second}"
|
80
|
-
}
|
81
|
-
defn(:concat, String, String) { |first, second|
|
82
|
-
first + second
|
83
|
-
}
|
84
|
-
defn(:concat, Integer, UNBOUND) { |first, second|
|
85
|
-
first + second.to_i
|
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
|
-
}
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
specify { subject.
|
125
|
-
|
126
|
-
specify { subject.
|
127
|
-
|
128
|
-
|
129
|
-
specify { subject.
|
130
|
-
specify { subject.
|
131
|
-
specify { subject.
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
specify { subject.
|
136
|
-
specify { subject.
|
137
|
-
specify { subject.
|
138
|
-
specify {
|
139
|
-
|
140
|
-
|
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: _, bar: _}, _) { |_, f, b, _|
|
43
|
+
[f, b]
|
44
|
+
}
|
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
|
57
|
+
}
|
58
|
+
|
59
|
+
defn(:recurse) {
|
60
|
+
'w00t!'
|
61
|
+
}
|
62
|
+
defn(:recurse, :match) {
|
63
|
+
recurse()
|
64
|
+
}
|
65
|
+
defn(:recurse, :super) {
|
66
|
+
greet()
|
67
|
+
}
|
68
|
+
defn(:recurse, :instance) {
|
69
|
+
@name
|
70
|
+
}
|
71
|
+
defn(:recurse, _) { |arg|
|
72
|
+
arg
|
73
|
+
}
|
74
|
+
|
75
|
+
defn(:concat, Integer, Integer) { |first, second|
|
76
|
+
first + second
|
77
|
+
}
|
78
|
+
defn(:concat, Integer, String) { |first, second|
|
79
|
+
"#{first} #{second}"
|
80
|
+
}
|
81
|
+
defn(:concat, String, String) { |first, second|
|
82
|
+
first + second
|
83
|
+
}
|
84
|
+
defn(:concat, Integer, UNBOUND) { |first, second|
|
85
|
+
first + second.to_i
|
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
|
+
}
|
100
|
+
|
101
|
+
defn(:old_enough, _){ true }.when{|x| x >= 16 }
|
102
|
+
defn(:old_enough, _){ false }
|
103
|
+
|
104
|
+
defn(:right_age, _) {
|
105
|
+
true
|
106
|
+
}.when{|x| x >= 16 && x <= 104 }
|
107
|
+
|
108
|
+
defn(:right_age, _) {
|
109
|
+
false
|
110
|
+
}
|
111
|
+
|
112
|
+
defn(:wrong_age, _) {
|
113
|
+
true
|
114
|
+
}.when{|x| x < 16 || x > 104 }
|
115
|
+
|
116
|
+
defn(:wrong_age, _) {
|
117
|
+
false
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
let(:name) { 'Pattern Matcher' }
|
122
|
+
subject { Foo.new(name) }
|
123
|
+
|
124
|
+
specify { subject.greet.should eq 'Hello, World!' }
|
125
|
+
|
126
|
+
specify { subject.greet('Jerry').should eq 'Hello, Jerry!' }
|
127
|
+
|
128
|
+
specify { subject.greet(:male, 'Jerry').should eq 'Hello, Mr. Jerry!' }
|
129
|
+
specify { subject.greet(:female, 'Jeri').should eq 'Hello, Ms. Jeri!' }
|
130
|
+
specify { subject.greet(:unknown, 'Jerry').should eq 'Hello, Jerry!' }
|
131
|
+
specify { subject.greet(nil, 'Jerry').should eq 'Goodbye, Jerry!' }
|
132
|
+
|
133
|
+
specify { subject.options(bar: :baz, one: 1, many: 2).should == {bar: :baz, one: 1, many: 2} }
|
134
|
+
|
135
|
+
specify { subject.hashable(:male, {foo: :bar}, :female).should eq :foo_bar }
|
136
|
+
specify { subject.hashable(:male, {foo: :baz}, :female).should eq :baz }
|
137
|
+
specify { subject.hashable(:male, {foo: 1, bar: 2}, :female).should eq [1, 2] }
|
138
|
+
specify { subject.hashable(:male, {foo: 1, baz: 2}, :female).should eq 1 }
|
139
|
+
specify { subject.hashable(:male, {bar: :baz}, :female).should eq :unbound }
|
140
|
+
specify { subject.hashable(:male, {}, :female).should eq :empty }
|
141
|
+
|
142
|
+
specify { subject.recurse.should eq 'w00t!' }
|
143
|
+
specify { subject.recurse(:match).should eq 'w00t!' }
|
144
|
+
specify { subject.recurse(:super).should eq 'Hello, World!' }
|
145
|
+
specify { subject.recurse(:instance).should eq name }
|
146
|
+
specify { subject.recurse(:foo).should eq :foo }
|
147
|
+
|
148
|
+
specify { subject.concat(1, 1).should eq 2 }
|
149
|
+
specify { subject.concat(1, 'shoe').should eq '1 shoe' }
|
150
|
+
specify { subject.concat('shoe', 'fly').should eq 'shoefly' }
|
151
|
+
specify { subject.concat(1, 2.9).should eq 3 }
|
152
|
+
|
153
|
+
specify { subject.all(:one, 'a', 'bee', :see).should == ['a', 'bee', :see] }
|
154
|
+
specify { subject.all(:one, 1, 'bee', :see).should == [1, 'bee', :see] }
|
155
|
+
specify { subject.all(1, 'a', 'bee', :see).should == ['a', ['bee', :see]] }
|
156
|
+
specify { subject.all('a', 'bee', :see).should == ['a', 'bee', :see] }
|
157
|
+
specify { lambda { subject.all }.should raise_error(NoMethodError) }
|
158
|
+
|
159
|
+
specify { subject.old_enough(20).should be_true }
|
160
|
+
specify { subject.old_enough(10).should be_false }
|
161
|
+
|
162
|
+
specify { subject.right_age(20).should be_true }
|
163
|
+
specify { subject.right_age(10).should be_false }
|
164
|
+
specify { subject.right_age(110).should be_false }
|
165
|
+
|
166
|
+
specify { subject.wrong_age(20).should be_false }
|
167
|
+
specify { subject.wrong_age(10).should be_true }
|
168
|
+
specify { subject.wrong_age(110).should be_true }
|
169
|
+
|
170
|
+
end
|