pattern-match 0.1.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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