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.
@@ -1,99 +1,133 @@
1
- module PatternMatching
2
-
3
- VERSION = '0.1.0'
4
-
5
- UNBOUND = Unbound = Class.new
6
- ALL = All = Class.new
7
-
8
- def self.included(base)
9
-
10
- base.instance_variable_set(:@__function_pattern_matches__, Hash.new)
11
-
12
- def __match_pattern__(args, pattern) # :nodoc:
13
- return unless (pattern.last == ALL && args.length >= pattern.length) \
14
- || (args.length == pattern.length)
15
- pattern.each_with_index do |p, i|
16
- break if p == ALL && i+1 == pattern.length
17
- arg = args[i]
18
- next if p.is_a?(Class) && arg.is_a?(p)
19
- if p.is_a?(Hash) && arg.is_a?(Hash) && ! p.empty?
20
- p.each do |key, value|
21
- return false unless arg.has_key?(key)
22
- next if value == UNBOUND
23
- return false unless arg[key] == value
24
- end
25
- next
26
- end
27
- return false unless p == UNBOUND || p == arg
28
- end
29
- return true
30
- end
31
-
32
- def __pattern_match__(func, *args, &block) # :nodoc:
33
- clazz = self.class
34
- args = args.first
35
-
36
- # get the array of matchers for this function
37
- matchers = clazz.instance_variable_get(:@__function_pattern_matches__)[func]
38
-
39
- # scan through all patterns for this function
40
- index = matchers.index{|matcher| __match_pattern__(args, matcher.first)}
41
-
42
- if index.nil?
43
- [:nomatch, nil]
44
- else
45
- # if a match is found call the block
46
- argv = []
47
- match = matchers[index]
48
- match.first.each_with_index do |p, i|
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
58
- end
59
- return [:ok, self.instance_exec(*argv, &match.last)]
60
- end
61
- end
62
-
63
- class << base
64
-
65
- UNBOUND = PatternMatching::UNBOUND
66
- ALL = PatternMatching::ALL
67
-
68
- def _() # :nodoc:
69
- return UNBOUND
70
- end
71
-
72
- def __add_pattern_for__(func, *args, &block) # :nodoc:
73
- block = Proc.new{} unless block_given?
74
- matchers = self.instance_variable_get(:@__function_pattern_matches__)
75
- matchers[func] = [] unless matchers.has_key?(func)
76
- matchers[func] << [args, block]
77
- end
78
-
79
- def defn(func, *args, &block)
80
-
81
- block = Proc.new{} unless block_given?
82
- __add_pattern_for__(func, *args, &block)
83
-
84
- unless self.instance_methods(false).include?(func)
85
- self.send(:define_method, func) do |*args, &block|
86
- result, value = __pattern_match__(func, args, block)
87
- return value if result == :ok
88
- begin
89
- super(*args, &block)
90
- rescue NoMethodError
91
- raise NoMethodError.new("no method `#{func}` matching #{args} found for class #{self.class}")
92
- end
93
- end
94
- end
95
- end
96
-
97
- end
98
- end
99
- end
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
@@ -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
- end
101
-
102
- let(:name) { 'Pattern Matcher' }
103
- subject { Foo.new(name) }
104
-
105
- specify { subject.greet.should eq 'Hello, World!' }
106
-
107
- specify { subject.greet('Jerry').should eq 'Hello, Jerry!' }
108
-
109
- specify { subject.greet(:male, 'Jerry').should eq 'Hello, Mr. Jerry!' }
110
- specify { subject.greet(:female, 'Jeri').should eq 'Hello, Ms. Jeri!' }
111
- specify { subject.greet(:unknown, 'Jerry').should eq 'Hello, Jerry!' }
112
- specify { subject.greet(nil, 'Jerry').should eq 'Goodbye, Jerry!' }
113
-
114
- specify { subject.options(bar: :baz, one: 1, many: 2).should == {bar: :baz, one: 1, many: 2} }
115
-
116
- specify { subject.hashable(:male, {foo: :bar}, :female).should eq :foo_bar }
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 }
122
-
123
- specify { subject.recurse.should eq 'w00t!' }
124
- specify { subject.recurse(:match).should eq 'w00t!' }
125
- specify { subject.recurse(:super).should eq 'Hello, World!' }
126
- specify { subject.recurse(:instance).should eq name }
127
- specify { subject.recurse(:foo).should eq :foo }
128
-
129
- specify { subject.concat(1, 1).should eq 2 }
130
- specify { subject.concat(1, 'shoe').should eq '1 shoe' }
131
- specify { subject.concat('shoe', 'fly').should eq 'shoefly' }
132
- specify { subject.concat(1, 2.9).should eq 3 }
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
-
140
- end
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