pattern-match 0.1.2 → 0.5.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a5dfb4c46f1d15a2d58257a25701821fb2346904
4
+ data.tar.gz: 65976df31a660ee698f2480a1532c4b51bb38f7c
5
+ SHA512:
6
+ metadata.gz: fb509e20044ecfb152ae2211782d4c4fd0c3b39e31f9a9554b28f47c4e1fc9dbeb724f5cc17c3c60ac2c4c9e874648eebd2830db5d97761b8d1c96767ddaf879
7
+ data.tar.gz: 29110d2a035d427dcb48909f8bfd28353dd0b3dd6c0346438a5552761e2618bdf377dc8072ba4a578967a569d5481b64025bdac1780a6c73bedc9007faea7b85
data/.travis.yml CHANGED
@@ -1,4 +1,5 @@
1
1
  rvm:
2
2
  - 1.9.2
3
3
  - 1.9.3
4
+ - 2.0.0
4
5
  - ruby-head
data/README.rdoc CHANGED
@@ -3,6 +3,10 @@
3
3
  A pattern matching library for Ruby.
4
4
 
5
5
  == Installation
6
+ $ gem install pattern-match
7
+
8
+ or
9
+
6
10
  $ git clone git://github.com/k-tsj/pattern-match.git
7
11
  $ cd pattern-match
8
12
  $ gem build pattern-match.gemspec
@@ -14,70 +18,232 @@ or
14
18
  $ echo "gem 'pattern-match', :git => 'git://github.com/k-tsj/pattern-match.git'" > Gemfile
15
19
  $ bundle install --path vendor/bundle
16
20
 
17
- == Example
21
+ == Basic Usage
22
+ pattern-match library provides Kernel#match.
23
+
18
24
  require 'pattern-match'
19
25
 
20
- ## (A)
21
- match([0, [1, 2, 3, 4]]) {
22
- with(_[a, _[b, *c, d]]) { # Same as `Array.(a, Array.(b, *c, d))'
23
- p [a, b, c, d] #=> [0, 1, [2, 3], 4]
24
- }
25
- }
26
-
27
- ## (B)
28
- # From util.match in Gauche: http://practical-scheme.net/gauche/man/?l=en&p=util.match
29
- match([[0, 1], [2, 3]]) {
30
- with(_[_[a, b], ___]) {
31
- p [a, b] #=> [[0, 2], [1, 3]]
32
- }
33
- }
34
-
35
- ## (C)
36
- # balance in a red-black tree
26
+ match(object) do
27
+ with(pattern[, guard]) do
28
+ ...
29
+ end
30
+ with(pattern[, guard]) do
31
+ ...
32
+ end
33
+ ...
34
+ end
35
+
36
+ The patterns are run in sequence until the first one that matches.
37
+
38
+ If a pattern matches, a block passed to <code>with</code> is called and return its result.
39
+ If no pattern matches, a PatternMatch::NoMatchingPatternError exception is raised.
40
+
41
+ You can specify pattern guard if you want.
42
+
43
+ == Patterns
44
+ === Value
45
+ An object (expect the instance of PatternMatch::Pattern) is a value pattern.
46
+
47
+ The pattern matches an object such that <code>pattern === object</code>.
48
+
49
+ match(0) do
50
+ with(Fixnum) { :match } #=> :match
51
+ end
52
+
53
+ If you want to use an another method of matching,
54
+ you have to use <code>_</code> as follows.
55
+
56
+ match(0) do
57
+ with(_(Fixnum, :==)) { :match }
58
+ end #=> NoMatchingPatternError
59
+
60
+ === Deconstructor
61
+ A deconstructor pattern is (typically) of the form <code>deconstructor.([pattern, ...])</code>.
62
+
63
+ It is equivalent to Extractor in Scala.
64
+
65
+ Consider the following example:
66
+
67
+ match([0, 1]) do
68
+ with(Array.(0, 1)) { :match } #=> :match
69
+ end
70
+
71
+ match('ab') do
72
+ with(/(.)(.)/.('a', 'b')) { :match } #=> :match
73
+ end
74
+
75
+ Array, Regexp object(<code>/(.)(.)/</code>) are deconstructors.
76
+ You can use any object has the following features as deconstructor.
77
+
78
+ * PatternMatch::Deconstructable is included in a class of deconstructor
79
+ * Can be responded to <code>deconstruct</code> method
80
+
81
+ Note that <code>_[]</code> is provided as syntactic sugar for <code>Array.()</code>.
82
+
83
+ match([0, 1]) do
84
+ with(_[0, 1]) { :match } #=> :match
85
+ end
86
+
87
+ === Variable
88
+ An identifier is a variable pattern.
89
+
90
+ It matches any value, and binds the variable name to that value.
91
+ A special case is the wild-card pattern <code>_</code> which matches any value,
92
+ and never binds.
93
+
94
+ match([0, 1]) do
95
+ with(_[a, b]) { [a, b] } #=> [0, 1]
96
+ end
97
+
98
+ match(0) do
99
+ with(_) { _ } #=> NameError
100
+ end
101
+
102
+ When several patterns with the same name occur in a single pattern,
103
+ all objects bound to variable must be equal.
104
+
105
+ match([0, 1]) do
106
+ with(_[a, a]) { a }
107
+ end #=> NoMatchingPatternError
108
+
109
+ === And/Or/Not
110
+ <code>PatternMatch::Pattern#&</code>, <code>PatternMatch::Pattern#|</code>, <code>PatternMatch::Pattern#!@</code>,
111
+ <code>And</code>, <code>Or</code>, <code>Not</code> return and/or/not pattern.
112
+
113
+ match([0, [1]]) do
114
+ with(a & Finuxm, ! (_[2] | _[3])) { a } #=> 0
115
+ end
116
+
117
+ match(0) do
118
+ with(0 | 1 | 2) { } # (0 | 1 | 2) is evaluated to 3, so the pattern does not match.
119
+ with(Or(0, 1, 2)) { :match } #=> :match
120
+ end
121
+
122
+ === Quantifier
123
+ <code>___</code>(triple underscore), <code>___?</code>,
124
+ <code>__n</code>(double underscore + n where n >= 0), <code>__n?</code> are quantifier patterns.
125
+
126
+ They are equivalent to <code>*</code>, <code>*?</code>,
127
+ <code>{n,}</code>, <code>{n,}?</code> in regular expression.
128
+ You can write as <code>*pattern</code> instead of <code>pattern, ___</code>.
129
+
130
+ match([:a, 0, :b, :c]) do
131
+ with(_[a & Symbol, ___, b & Fixnum, c & Symbol, ___]) do
132
+ a #=> [:a]
133
+ b #=> 0
134
+ c #=> [:b, :c]
135
+ end
136
+ end
137
+
138
+ === Sequence
139
+ <code>Seq</code> returns a sequence pattern.
140
+
141
+ It is equivalent to <code>()</code> in regular expression.
142
+
143
+ match([:a, 0, :b, 1]) do
144
+ with(_[Seq(a & Symbol, b & Fixnum), ___]) do
145
+ a #=> [:a, :b]
146
+ b #=> [0, 1]
147
+ end
148
+ end
149
+
150
+ === EXPERIMENTAL
151
+ * Object.()
152
+ * Matcher
153
+ * KeyMatcher
154
+ * Hash.()
155
+ * AttributeMatcher
156
+
157
+ See source code for more details.
158
+
159
+ == Pattern guard
160
+ Pattern guard can be specified as a second argument to <code>with</code>.
161
+
162
+ match([1, 2, 3, 4, 5]) do
163
+ with(_[*_, *a, *_], guard { a.inject(:*) == 12 }) do
164
+ a #=> [3, 4]
165
+ end
166
+ end
167
+
168
+ == Examples
169
+ # (A)
37
170
  Node = Struct.new(:left, :key, :right)
38
171
  class R < Node; end
39
172
  class B < Node; end
40
173
 
41
174
  def balance(left, key, right)
42
- match([left, key, right]) {
175
+ match([left, key, right]) do
43
176
  with(_[R.(a, x, b), y, R.(c, z, d)]) { R[B[a, x, b], y, B[c, z, d]] }
44
177
  with(_[R.(R.(a, x, b), y, c), z, d]) { R[B[a, x, b], y, B[c, z, d]] }
45
178
  with(_[R.(a, x, R.(b, y, c)), z, d]) { R[B[a, x, b], y, B[c, z, d]] }
46
179
  with(_[a, x, R.(b, y, R.(c, z, d))]) { R[B[a, x, b], y, B[c, z, d]] }
47
180
  with(_[a, x, R.(R.(b, y, c), z, d)]) { R[B[a, x, b], y, B[c, z, d]] }
48
181
  with(_) { B[left, key, right] }
49
- }
182
+ end
50
183
  end
51
184
 
52
- ## (D)
185
+ # (B)
53
186
  class EMail
54
187
  def self.deconstruct(value)
55
- value.to_s.split(/@/).tap {|parts| raise PatternNotMatch unless parts.length == 2 }
188
+ parts = value.to_s.split(/@/)
189
+ if parts.length == 2
190
+ parts
191
+ else
192
+ raise PatternMatch::PatternNotMatch
193
+ end
56
194
  end
57
195
  end
58
196
 
59
- match(['foo-bar@example.com', 'baz-bar@example.com']) {
60
- with(_[mail & EMail.(name & /(\w+)-(\w+)/.(firstname, 'bar'), domain), ___]) {
61
- p [firstname, name, domain, mail] # => [["foo", "baz"], ["foo-bar", "baz-bar"], ["example.com", "example.com"], ["foo-bar@example.com", "baz-bar@example.com"]]
62
- }
63
- }
197
+ match(['foo-bar@example.com', 'baz-bar@example.com']) do
198
+ with(_[mail & EMail.(name & /(\w+)-(\w+)/.(firstname, 'bar'), domain), ___]) do
199
+ mail #=> ["foo-bar@example.com", "baz-bar@example.com"]
200
+ name #=> ["foo-bar", "baz-bar"]
201
+ firstname #=> ["foo", "baz"]
202
+ domain #=> ["example.com", "example.com"]
203
+ end
204
+ end
64
205
 
65
- ## (E)
66
- match(10) {
67
- with(Object.(:to_i => a, :foobar => b)) { :not_match }
68
- with(Object.(:to_i => a, :to_s.(16) => b)) {
69
- p [a, b] #=> [10, "a"]
70
- }
71
- }
206
+ # (C)
207
+ def replace_repeated(obj, &block)
208
+ ret = match(obj, &block)
209
+ if ret == obj
210
+ ret
211
+ else
212
+ replace_repeated(ret, &block)
213
+ end
214
+ rescue PatternMatch::NoMatchingPatternError
215
+ obj
216
+ end
217
+
218
+ replace_repeated([1, 2, 4, 4, 3, 3, 4, 0, 0]) do
219
+ with(_[*a, x, x, *b]) { [*a, x, *b] }
220
+ end #=> [1, 2, 4, 3, 4, 0]
221
+
222
+ # (D)
223
+ match({a: 0, b: 1}) do
224
+ with(Hash.(:a, b: Object.(:odd? => true))) do
225
+ a #=> 0
226
+ end
227
+ end
228
+
229
+ C = Struct.new(:a, :b) do
230
+ include PatternMatch::AttributeMatcher
231
+ end
232
+ match(C[0, 1]) do
233
+ with(C.(:b, a: 0)) do
234
+ b # => 1
235
+ end
236
+ end
72
237
 
73
- You can see another example in test/test_pattern-match.rb.
238
+ == Reference
239
+ * {Pattern Matching in Ruby (at Sapporo RubyKaigi 2012) // Speaker Deck}[https://speakerdeck.com/k_tsj/patternmatchinginruby]
74
240
 
75
241
  == Development
76
242
  $ git clone git://github.com/k-tsj/pattern-match.git
77
243
  $ cd pattern-match
78
244
  $ gem install bundler (if you need)
79
245
  $ bundle install --path vendor/bundle
80
- $ rake test (or simply type "rake")
81
- $ rake build
246
+ $ bundle exec rake test (or "bundle exec rake")
247
+ $ bundle exec rake build
82
248
 
83
249
  == Travis Build Status {<img src="https://secure.travis-ci.org/k-tsj/pattern-match.png"/>}[http://travis-ci.org/k-tsj/pattern-match]
data/lib/pattern-match.rb CHANGED
@@ -8,13 +8,35 @@ module PatternMatch
8
8
  module Deconstructable
9
9
  def call(*subpatterns)
10
10
  if Object == self
11
- raise MalformedPatternError unless subpatterns.length == 1
12
- PatternObject.new(subpatterns[0])
13
- elsif Hash == self
14
- raise MalformedPatternError unless subpatterns.length == 1
15
- PatternHash.new(subpatterns[0])
11
+ PatternKeywordArgStyleDeconstructor.new(Object, :respond_to?, :__send__, *subpatterns)
16
12
  else
17
- PatternDeconstructor.new(self, *subpatterns)
13
+ pattern_matcher(*subpatterns)
14
+ end
15
+ end
16
+ end
17
+
18
+ class ::Object
19
+ def pattern_matcher(*subpatterns)
20
+ PatternObjectDeconstructor.new(self, *subpatterns)
21
+ end
22
+ end
23
+
24
+ module AttributeMatcher
25
+ def self.included(klass)
26
+ class << klass
27
+ def pattern_matcher(*subpatterns)
28
+ PatternKeywordArgStyleDeconstructor.new(self, :respond_to?, :__send__, *subpatterns)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ module KeyMatcher
35
+ def self.included(klass)
36
+ class << klass
37
+ def pattern_matcher(*subpatterns)
38
+ PatternKeywordArgStyleDeconstructor.new(self, :has_key?, :[], *subpatterns)
39
+ end
18
40
  end
19
41
  end
20
42
  end
@@ -34,6 +56,10 @@ module PatternMatch
34
56
  @subpatterns.map(&:vars).flatten
35
57
  end
36
58
 
59
+ def ancestors
60
+ root? ? [self] : parent.ancestors.unshift(self)
61
+ end
62
+
37
63
  def binding
38
64
  vars.each_with_object({}) {|v, h| h[v.name] = v.val }
39
65
  end
@@ -51,11 +77,19 @@ module PatternMatch
51
77
  end
52
78
 
53
79
  def to_a
54
- [self, PatternQuantifier.new(0)]
80
+ [self, PatternQuantifier.new(0, true)]
81
+ end
82
+
83
+ def quantifier?
84
+ raise NotImplementedError
55
85
  end
56
86
 
57
87
  def quantified?
58
- @next.kind_of?(PatternQuantifier) || (root? ? false : @parent.quantified?)
88
+ (@next and @next.quantifier?) || (root? ? false : @parent.quantified?)
89
+ end
90
+
91
+ def root
92
+ root? ? self : @parent.root
59
93
  end
60
94
 
61
95
  def root?
@@ -63,114 +97,205 @@ module PatternMatch
63
97
  end
64
98
 
65
99
  def validate
66
- if root?
67
- dup_vars = vars - vars.uniq {|i| i.name }
68
- raise MalformedPatternError, "duplicate variables: #{dup_vars.map(&:name).join(', ')}" unless dup_vars.empty?
69
- end
70
- raise MalformedPatternError if @subpatterns.count {|i| i.kind_of?(PatternQuantifier) } > 1
71
100
  @subpatterns.each(&:validate)
72
101
  end
73
102
 
103
+ def match(vals)
104
+ if @next and @next.quantifier?
105
+ q = @next
106
+ repeating_match(vals, q.longest?) do |vs, rest|
107
+ if vs.length < q.min_k
108
+ next false
109
+ end
110
+ vs.all? {|v| yield(v) } and q.match(rest)
111
+ end
112
+ else
113
+ if vals.empty?
114
+ return false
115
+ end
116
+ val, *rest = vals
117
+ yield(val) and (@next ? @next.match(rest) : rest.empty?)
118
+ end
119
+ end
120
+
121
+ def append(pattern)
122
+ if @next
123
+ @next.append(pattern)
124
+ else
125
+ if @subpatterns.empty?
126
+ if root?
127
+ new_root = PatternAnd.new(self)
128
+ self.parent = new_root
129
+ end
130
+ pattern.parent = @parent
131
+ @next = pattern
132
+ else
133
+ @subpatterns[-1].append(pattern)
134
+ end
135
+ end
136
+ end
137
+
74
138
  private
75
139
 
140
+ def repeating_match(vals, longest)
141
+ quantifier = @next
142
+ lp = longest_patterns(vals)
143
+ (longest ? lp : lp.reverse).each do |(vs, rest)|
144
+ vars.each {|i| i.set_bind_to(quantifier) }
145
+ begin
146
+ if yield vs, rest
147
+ return true
148
+ end
149
+ rescue PatternNotMatch
150
+ end
151
+ vars.each {|i| i.unset_bind_to(quantifier) }
152
+ end
153
+ false
154
+ end
155
+
156
+ def longest_patterns(vals)
157
+ vals.length.downto(0).map do |n|
158
+ [vals.take(n), vals.drop(n)]
159
+ end
160
+ end
161
+
76
162
  def set_subpatterns_relation
77
163
  @subpatterns.each do |i|
78
164
  i.parent = self
79
165
  end
80
- @subpatterns.each_cons(2) do |a, b|
81
- a.next = b
82
- b.prev = a
166
+ end
167
+ end
168
+
169
+ class PatternQuantifier < Pattern
170
+ attr_reader :min_k
171
+
172
+ def initialize(min_k, longest)
173
+ super()
174
+ @min_k = min_k
175
+ @longest = longest
176
+ end
177
+
178
+ def validate
179
+ super
180
+ raise MalformedPatternError unless @prev and ! @prev.quantifier?
181
+ seqs = ancestors.grep(PatternSequence).reverse
182
+ if seqs.any? {|i| i.next and i.next.quantifier? and not i.vars.empty? }
183
+ raise NotImplementedError
184
+ end
185
+ case @parent
186
+ when PatternObjectDeconstructor
187
+ # do nothing
188
+ when PatternSequence
189
+ # do nothing
190
+ else
191
+ raise MalformedPatternError
83
192
  end
84
193
  end
85
194
 
86
- def ancestors
87
- ary = []
88
- pat = self
89
- until pat == nil
90
- ary << pat
91
- pat = pat.parent
195
+ def quantifier?
196
+ true
197
+ end
198
+
199
+ def match(vals)
200
+ if @next
201
+ @next.match(vals)
202
+ else
203
+ vals.empty?
92
204
  end
93
- ary
94
205
  end
95
- end
96
206
 
97
- class PatternObject < Pattern
98
- def initialize(spec)
99
- super(*spec.values)
100
- @spec = spec.map {|k, pat| [k.to_proc, pat] }
101
- rescue
102
- raise MalformedPatternError
207
+ def longest?
208
+ @longest
103
209
  end
104
210
 
105
- def match(val)
106
- @spec.all? {|k, pat| pat.match(k.(val)) rescue raise PatternNotMatch }
211
+ def inspect
212
+ "#<#{self.class.name}: min_k=#{@min_k}, longest=#{@longest}>"
107
213
  end
108
214
  end
109
215
 
110
- class PatternHash < Pattern
111
- def initialize(spec)
112
- super(*spec.values)
113
- @spec = spec
216
+ class PatternElement < Pattern
217
+ def quantifier?
218
+ false
114
219
  end
220
+ end
115
221
 
116
- def match(val)
117
- raise PatternNotMatch unless val.kind_of?(Hash)
118
- raise PatternNotMatch unless @spec.keys.all? {|k| val.has_key?(k) }
119
- @spec.all? {|k, pat| pat.match(val[k]) rescue raise PatternNotMatch }
120
- end
222
+ class PatternDeconstructor < PatternElement
121
223
  end
122
224
 
123
- class PatternDeconstructor < Pattern
225
+ class PatternObjectDeconstructor < PatternDeconstructor
124
226
  def initialize(deconstructor, *subpatterns)
125
227
  super(*subpatterns)
126
228
  @deconstructor = deconstructor
127
229
  end
128
230
 
129
- def match(val)
130
- deconstructed_vals = @deconstructor.deconstruct(val)
131
- k = deconstructed_vals.length - (@subpatterns.length - 2)
132
- quantifier = @subpatterns.find {|i| i.kind_of?(PatternQuantifier) }
133
- if quantifier
134
- return false unless quantifier.min_k <= k
135
- else
136
- return false unless @subpatterns.length == deconstructed_vals.length
137
- end
138
- @subpatterns.flat_map do |pat|
139
- case
140
- when pat.next.kind_of?(PatternQuantifier)
141
- []
142
- when pat.kind_of?(PatternQuantifier)
143
- pat.prev.vars.each {|v| v.set_bind_to(pat) }
144
- Array.new(k, pat.prev)
145
- else
146
- [pat]
231
+ def match(vals)
232
+ super do |val|
233
+ deconstructed_vals = @deconstructor.deconstruct(val)
234
+ if @subpatterns.empty?
235
+ next deconstructed_vals.empty?
147
236
  end
148
- end.zip(deconstructed_vals).all? do |pat, v|
149
- pat.match(v)
237
+ @subpatterns[0].match(deconstructed_vals)
238
+ end
239
+ end
240
+
241
+ def inspect
242
+ "#<#{self.class.name}: deconstructor=#{@deconstructor.inspect}, subpatterns=#{@subpatterns.inspect}>"
243
+ end
244
+
245
+ private
246
+
247
+ def set_subpatterns_relation
248
+ super
249
+ @subpatterns.each_cons(2) do |a, b|
250
+ a.next = b
251
+ b.prev = a
150
252
  end
151
253
  end
152
254
  end
153
255
 
154
- class PatternQuantifier < Pattern
155
- attr_reader :min_k
256
+ class PatternKeywordArgStyleDeconstructor < PatternDeconstructor
257
+ def initialize(klass, checker, getter, *keyarg_subpatterns)
258
+ spec = normalize_keyword_arg(keyarg_subpatterns)
259
+ super(*spec.values)
260
+ @klass = klass
261
+ @checker = checker
262
+ @getter = getter
263
+ @spec = spec
264
+ end
156
265
 
157
- def initialize(min_k)
158
- super()
159
- @min_k = min_k
266
+ def match(vals)
267
+ super do |val|
268
+ next false unless val.kind_of?(@klass)
269
+ next false unless @spec.keys.all? {|k| val.__send__(@checker, k) }
270
+ @spec.all? do |k, pat|
271
+ pat.match([val.__send__(@getter, k)]) rescue false
272
+ end
273
+ end
160
274
  end
161
275
 
162
- def match(val)
163
- raise PatternMatchError, 'must not happen'
276
+ def inspect
277
+ "#<#{self.class.name}: klass=#{@klass.inspect}, spec=#{@spec.inspect}>"
164
278
  end
165
279
 
166
- def validate
167
- super
168
- raise MalformedPatternError unless @prev
169
- raise MalformedPatternError unless @parent.kind_of?(PatternDeconstructor)
280
+ private
281
+
282
+ def normalize_keyword_arg(subpatterns)
283
+ syms = subpatterns.take_while {|i| i.kind_of?(Symbol) }
284
+ rest = subpatterns.drop(syms.length)
285
+ hash = case rest.length
286
+ when 0
287
+ {}
288
+ when 1
289
+ rest[0]
290
+ else
291
+ raise MalformedPatternError
292
+ end
293
+ variables = Hash[syms.map {|i, h| [i, PatternVariable.new(i)] }]
294
+ Hash[variables.merge(hash).map {|k, v| [k, v.kind_of?(Pattern) ? v : PatternValue.new(v)] }]
170
295
  end
171
296
  end
172
297
 
173
- class PatternVariable < Pattern
298
+ class PatternVariable < PatternElement
174
299
  attr_reader :name, :val
175
300
 
176
301
  def initialize(name)
@@ -180,9 +305,11 @@ module PatternMatch
180
305
  @bind_to = nil
181
306
  end
182
307
 
183
- def match(val)
184
- bind(val)
185
- true
308
+ def match(vals)
309
+ super do |val|
310
+ bind(val)
311
+ true
312
+ end
186
313
  end
187
314
 
188
315
  def vars
@@ -190,18 +317,37 @@ module PatternMatch
190
317
  end
191
318
 
192
319
  def set_bind_to(quantifier)
193
- if @val
320
+ n = nest_level(quantifier)
321
+ if n == 0
322
+ @val = @bind_to = []
323
+ else
194
324
  outer = @val
195
- (nest_level(quantifier) - 1).times do
325
+ (n - 1).times do
196
326
  outer = outer[-1]
197
327
  end
198
328
  @bind_to = []
199
329
  outer << @bind_to
330
+ end
331
+ end
332
+
333
+ def unset_bind_to(quantifier)
334
+ n = nest_level(quantifier)
335
+ @bind_to = nil
336
+ if n == 0
337
+ # do nothing
200
338
  else
201
- @val = @bind_to = []
339
+ outer = @val
340
+ (n - 1).times do
341
+ outer = outer[-1]
342
+ end
343
+ outer.pop
202
344
  end
203
345
  end
204
346
 
347
+ def inspect
348
+ "#<#{self.class.name}: name=#{name.inspect}, val=#{@val.inspect}>"
349
+ end
350
+
205
351
  private
206
352
 
207
353
  def bind(val)
@@ -213,56 +359,220 @@ module PatternMatch
213
359
  end
214
360
 
215
361
  def nest_level(quantifier)
216
- qs = ancestors.map {|i| i.next.kind_of?(PatternQuantifier) ? i.next : nil }.find_all {|i| i }.reverse
362
+ raise PatternMatchError unless quantifier.kind_of?(PatternQuantifier)
363
+ qs = ancestors.map {|i| (i.next and i.next.quantifier?) ? i.next : nil }.find_all {|i| i }.reverse
217
364
  qs.index(quantifier) || (raise PatternMatchError)
218
365
  end
219
366
  end
220
367
 
221
- class PatternValue < Pattern
368
+ class PatternValue < PatternElement
222
369
  def initialize(val, compare_by = :===)
223
370
  super()
224
371
  @val = val
225
372
  @compare_by = compare_by
226
373
  end
227
374
 
228
- def match(val)
229
- @val.__send__(@compare_by, val)
375
+ def match(vals)
376
+ super do |val|
377
+ @val.__send__(@compare_by, val)
378
+ end
379
+ end
380
+
381
+ def inspect
382
+ "#<#{self.class.name}: val=#{@val.inspect}>"
230
383
  end
231
384
  end
232
385
 
233
- class PatternAnd < Pattern
234
- def match(val)
235
- @subpatterns.all? {|i| i.match(val) }
386
+ class PatternSequence < PatternElement
387
+ class PatternRewind < PatternElement
388
+ attr_reader :ntimes
389
+
390
+ def initialize(ntimes, head_pattern, next_pattern)
391
+ super()
392
+ @ntimes = ntimes
393
+ @head = head_pattern
394
+ @next = next_pattern
395
+ end
396
+
397
+ def match(vals)
398
+ if @ntimes > 0
399
+ @ntimes -= 1
400
+ @head.match(vals)
401
+ else
402
+ @next ? @next.match(vals) : vals.empty?
403
+ end
404
+ end
405
+
406
+ def inspect
407
+ "#<#{self.class.name}: ntimes=#{@ntimes} head=#{@head.inspect} next=#{@next.inspect}>"
408
+ end
409
+ end
410
+
411
+ def match(vals)
412
+ if @next and @next.quantifier?
413
+ repeating_match(vals, @next.longest?) do |rewind|
414
+ if rewind.ntimes < @next.min_k
415
+ next false
416
+ end
417
+ rewind.match(vals)
418
+ end
419
+ else
420
+ with_rewind(make_rewind(1)) do |rewind|
421
+ rewind.match(vals)
422
+ end
423
+ end
424
+ end
425
+
426
+ def validate
427
+ super
428
+ if @subpatterns.empty?
429
+ raise MalformedPatternError
430
+ end
431
+ case @parent
432
+ when PatternObjectDeconstructor
433
+ # do nothing
434
+ when PatternSequence
435
+ # do nothing
436
+ else
437
+ raise MalformedPatternError
438
+ end
439
+ end
440
+
441
+ def inspect
442
+ "#<#{self.class.name}: subpatterns=#{@subpatterns.inspect}>"
443
+ end
444
+
445
+ private
446
+
447
+ def make_rewind(n)
448
+ PatternRewind.new(n, @subpatterns[0], (@next and @next.quantifier?) ? @next.next : @next)
449
+ end
450
+
451
+ def repeating_match(vals, longest)
452
+ quantifier = @next
453
+ lp = longest_patterns(vals)
454
+ (longest ? lp : lp.reverse).each do |rewind|
455
+ vars.each {|i| i.set_bind_to(quantifier) }
456
+ begin
457
+ with_rewind(rewind) do |rewind|
458
+ if yield rewind
459
+ return true
460
+ end
461
+ end
462
+ rescue PatternNotMatch
463
+ end
464
+ vars.each {|i| i.unset_bind_to(quantifier) }
465
+ end
466
+ false
467
+ end
468
+
469
+ def longest_patterns(vals)
470
+ vals.length.downto(0).map do |n|
471
+ make_rewind(n)
472
+ end
473
+ end
474
+
475
+ def with_rewind(rewind)
476
+ @subpatterns[-1].next = rewind
477
+ yield rewind
478
+ ensure
479
+ @subpatterns[-1].next = nil
480
+ end
481
+
482
+ def set_subpatterns_relation
483
+ super
484
+ @subpatterns.each_cons(2) do |a, b|
485
+ a.next = b
486
+ b.prev = a
487
+ end
236
488
  end
237
489
  end
238
490
 
239
- class PatternOr < Pattern
240
- def match(val)
241
- @subpatterns.find do |i|
491
+ class PatternAnd < PatternElement
492
+ def match(vals)
493
+ super do |val|
494
+ @subpatterns.all? {|i| i.match([val]) }
495
+ end
496
+ end
497
+
498
+ def validate
499
+ super
500
+ raise MalformedPatternError if @subpatterns.empty?
501
+ end
502
+
503
+ def inspect
504
+ "#<#{self.class.name}: subpatterns=#{@subpatterns.inspect}>"
505
+ end
506
+ end
507
+
508
+ class PatternOr < PatternElement
509
+ def match(vals)
510
+ super do |val|
511
+ @subpatterns.find do |i|
512
+ begin
513
+ i.match([val])
514
+ rescue PatternNotMatch
515
+ false
516
+ end
517
+ end
518
+ end
519
+ end
520
+
521
+ def validate
522
+ super
523
+ raise MalformedPatternError if @subpatterns.empty?
524
+ raise MalformedPatternError unless vars.empty?
525
+ end
526
+
527
+ def inspect
528
+ "#<#{self.class.name}: subpatterns=#{@subpatterns.inspect}>"
529
+ end
530
+ end
531
+
532
+ class PatternNot < PatternElement
533
+ def match(vals)
534
+ super do |val|
242
535
  begin
243
- i.match(val)
536
+ ! @subpatterns[0].match([val])
244
537
  rescue PatternNotMatch
245
- false
538
+ true
246
539
  end
247
540
  end
248
541
  end
249
542
 
250
543
  def validate
251
544
  super
252
- raise MalformedPatternError unless vars.length == 0
545
+ raise MalformedPatternError unless @subpatterns.length == 1
546
+ raise MalformedPatternError unless vars.empty?
547
+ end
548
+
549
+ def inspect
550
+ "#<#{self.class.name}: subpatterns=#{@subpatterns.inspect}>"
253
551
  end
254
552
  end
255
553
 
256
- class PatternNot < Pattern
257
- def match(val)
258
- ! @subpatterns[0].match(val)
259
- rescue PatternNotMatch
260
- true
554
+ class PatternCondition < PatternElement
555
+ def initialize(&condition)
556
+ super()
557
+ @condition = condition
558
+ end
559
+
560
+ def match(vals)
561
+ return false unless vals.empty?
562
+ if @condition.call
563
+ @next ? @next.match(vals) : true
564
+ else
565
+ false
566
+ end
261
567
  end
262
568
 
263
569
  def validate
264
570
  super
265
- raise MalformedPatternError unless vars.length == 0
571
+ raise MalformedPatternError if ancestors.find {|i| i.next and ! i.next.kind_of?(PatternCondition) }
572
+ end
573
+
574
+ def inspect
575
+ "#<#{self.class.name}: condition=#{@condition.inspect}>"
266
576
  end
267
577
  end
268
578
 
@@ -275,10 +585,28 @@ module PatternMatch
275
585
  private
276
586
 
277
587
  def with(pat_or_val, guard_proc = nil, &block)
588
+ ctx = @ctx
278
589
  pat = pat_or_val.kind_of?(Pattern) ? pat_or_val : PatternValue.new(pat_or_val)
590
+ pat.append(
591
+ PatternCondition.new do
592
+ pat.vars.each_with_object({}) do |v, h|
593
+ if h.has_key?(v.name)
594
+ unless h[v.name] == v.val
595
+ ::Kernel.raise PatternNotMatch
596
+ end
597
+ else
598
+ h[v.name] = v.val
599
+ end
600
+ end
601
+ true
602
+ end
603
+ )
604
+ if guard_proc
605
+ pat.append(PatternCondition.new { with_tmpbinding(ctx, pat.binding, &guard_proc) })
606
+ end
279
607
  pat.validate
280
- if pat.match(@val) and (guard_proc ? with_tmpbinding(@ctx, pat.binding, &guard_proc) : true)
281
- ret = with_tmpbinding(@ctx, pat.binding, &block)
608
+ if pat.match([@val])
609
+ ret = with_tmpbinding(ctx, pat.binding, &block)
282
610
  ::Kernel.throw(:exit_match, ret)
283
611
  else
284
612
  nil
@@ -290,12 +618,18 @@ module PatternMatch
290
618
  block
291
619
  end
292
620
 
621
+ def ___
622
+ PatternQuantifier.new(0, true)
623
+ end
624
+
625
+ def ___?
626
+ PatternQuantifier.new(0, false)
627
+ end
628
+
293
629
  def method_missing(name, *)
294
630
  case name.to_s
295
- when '___'
296
- PatternQuantifier.new(0)
297
- when /\A__(\d+)\z/
298
- PatternQuantifier.new($1.to_i)
631
+ when /\A__(\d+)(\??)\z/
632
+ PatternQuantifier.new($1.to_i, ! $2.empty?)
299
633
  else
300
634
  PatternVariable.new(name)
301
635
  end
@@ -310,13 +644,14 @@ module PatternMatch
310
644
  Array.call(*args)
311
645
  end
312
646
 
313
- def match(val)
314
- true
315
- end
316
-
317
647
  def vars
318
648
  []
319
649
  end
650
+
651
+ private
652
+
653
+ def bind(val)
654
+ end
320
655
  end
321
656
  uscore
322
657
  when 1
@@ -331,6 +666,26 @@ module PatternMatch
331
666
  alias __ _
332
667
  alias _l _
333
668
 
669
+ def Seq(*subpatterns)
670
+ PatternSequence.new(*subpatterns)
671
+ end
672
+
673
+ def And(*subpatterns)
674
+ PatternAnd.new(*subpatterns)
675
+ end
676
+
677
+ def Or(*subpatterns)
678
+ PatternOr.new(*subpatterns)
679
+ end
680
+
681
+ def Not(*subpatterns)
682
+ PatternNot.new(*subpatterns)
683
+ end
684
+
685
+
686
+ class TmpBindingModule < ::Module
687
+ end
688
+
334
689
  def with_tmpbinding(obj, binding, &block)
335
690
  tmpbinding_module(obj).instance_eval do
336
691
  begin
@@ -345,7 +700,8 @@ module PatternMatch
345
700
  obj.instance_eval(&block)
346
701
  ensure
347
702
  binding.each do |name, _|
348
- if @stacks[name].tap(&:pop).empty?
703
+ @stacks[name].pop
704
+ if @stacks[name].empty?
349
705
  remove_method(name)
350
706
  end
351
707
  end
@@ -353,9 +709,6 @@ module PatternMatch
353
709
  end
354
710
  end
355
711
 
356
- class TmpBindingModule < ::Module
357
- end
358
-
359
712
  def tmpbinding_module(obj)
360
713
  m = obj.singleton_class.ancestors.find {|i| i.kind_of?(TmpBindingModule) }
361
714
  unless m
@@ -429,6 +782,10 @@ class Class
429
782
  end
430
783
  end
431
784
 
785
+ class Hash
786
+ include PatternMatch::KeyMatcher
787
+ end
788
+
432
789
  class << Array
433
790
  def deconstruct(val)
434
791
  accept_self_instance_only(val)
@@ -473,9 +830,3 @@ class Regexp
473
830
  m.captures.empty? ? [m[0]] : m.captures
474
831
  end
475
832
  end
476
-
477
- class Symbol
478
- def call(*args)
479
- Proc.new {|obj| obj.__send__(self, *args) }
480
- end
481
- end