pattern-match 0.5.0 → 0.5.1
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 +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +1 -0
- data/BSDL +1 -1
- data/COPYING +1 -1
- data/README.rdoc +13 -7
- data/lib/pattern-match.rb +3 -828
- data/lib/pattern-match/core.rb +710 -0
- data/lib/pattern-match/deconstructor.rb +60 -0
- data/lib/pattern-match/experimental.rb +46 -0
- data/lib/pattern-match/version.rb +1 -1
- data/pattern-match.gemspec +1 -0
- data/test/helper.rb +9 -0
- data/test/test_experimental.rb +92 -0
- data/test/{test_pattern-match.rb → test_standard.rb} +29 -81
- metadata +30 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 71fd4640c715750480dcad518ebc8fe68d468bf7
|
4
|
+
data.tar.gz: 84c2f108c050569e0dd6b9eb2f3ff79900ef65a3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba8572f327ebe6fef3109d1fe7d14ce22146a33e7c5602afc3457fa7a17110699ff1683af2d9f0cda81f98b124cc495a2a3923ef84ae5bca9df52f81ba82042f
|
7
|
+
data.tar.gz: af9f39647b62a984634f30afa28c5a7ad6d3d9b1fb39fdda5599aee5ffae9033de99aee5c6024178f23482800ee80478e1d4949857638ec0fb8233f21811b0b5
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/BSDL
CHANGED
data/COPYING
CHANGED
data/README.rdoc
CHANGED
@@ -42,7 +42,7 @@ You can specify pattern guard if you want.
|
|
42
42
|
|
43
43
|
== Patterns
|
44
44
|
=== Value
|
45
|
-
An object
|
45
|
+
An ordinary object is a value pattern.
|
46
46
|
|
47
47
|
The pattern matches an object such that <code>pattern === object</code>.
|
48
48
|
|
@@ -72,7 +72,7 @@ Consider the following example:
|
|
72
72
|
with(/(.)(.)/.('a', 'b')) { :match } #=> :match
|
73
73
|
end
|
74
74
|
|
75
|
-
Array, Regexp object(<code>/(.)(.)/</code>) are deconstructors.
|
75
|
+
Array class(<code>Array</code>), Regexp object(<code>/(.)(.)/</code>) are deconstructors.
|
76
76
|
You can use any object has the following features as deconstructor.
|
77
77
|
|
78
78
|
* PatternMatch::Deconstructable is included in a class of deconstructor
|
@@ -111,7 +111,7 @@ all objects bound to variable must be equal.
|
|
111
111
|
<code>And</code>, <code>Or</code>, <code>Not</code> return and/or/not pattern.
|
112
112
|
|
113
113
|
match([0, [1]]) do
|
114
|
-
with(a &
|
114
|
+
with(a & Fixnum, ! (_[2] | _[3])) { a } #=> 0
|
115
115
|
end
|
116
116
|
|
117
117
|
match(0) do
|
@@ -120,12 +120,12 @@ all objects bound to variable must be equal.
|
|
120
120
|
end
|
121
121
|
|
122
122
|
=== Quantifier
|
123
|
-
<code
|
124
|
-
<code>__n</code>(
|
123
|
+
<code>\___</code>, <code>\___?</code>,
|
124
|
+
<code>__n</code>(where n >= 0), <code>__n?</code> are quantifier patterns.
|
125
125
|
|
126
126
|
They are equivalent to <code>*</code>, <code>*?</code>,
|
127
127
|
<code>{n,}</code>, <code>{n,}?</code> in regular expression.
|
128
|
-
You can write as <code>*pattern</code> instead of <code>pattern, ___</code>.
|
128
|
+
You can write as <code>*pattern</code> instead of <code>pattern, \___</code>.
|
129
129
|
|
130
130
|
match([:a, 0, :b, :c]) do
|
131
131
|
with(_[a & Symbol, ___, b & Fixnum, c & Symbol, ___]) do
|
@@ -154,6 +154,7 @@ It is equivalent to <code>()</code> in regular expression.
|
|
154
154
|
* Hash.()
|
155
155
|
* AttributeMatcher
|
156
156
|
|
157
|
+
To use experimental features, you must also require 'pattern-match/experimental'.
|
157
158
|
See source code for more details.
|
158
159
|
|
159
160
|
== Pattern guard
|
@@ -220,8 +221,10 @@ Pattern guard can be specified as a second argument to <code>with</code>.
|
|
220
221
|
end #=> [1, 2, 4, 3, 4, 0]
|
221
222
|
|
222
223
|
# (D)
|
224
|
+
require 'pattern-match/experimental'
|
225
|
+
|
223
226
|
match({a: 0, b: 1}) do
|
224
|
-
with(Hash.(:a, b: Object.(
|
227
|
+
with(Hash.(:a, b: Object.(odd?: true))) do
|
225
228
|
a #=> 0
|
226
229
|
end
|
227
230
|
end
|
@@ -235,6 +238,9 @@ Pattern guard can be specified as a second argument to <code>with</code>.
|
|
235
238
|
end
|
236
239
|
end
|
237
240
|
|
241
|
+
* {RubyTextProcessing}[https://code.google.com/p/tokland/wiki/RubyTextProcessing]
|
242
|
+
* {yhara/tapl-ruby}[https://github.com/yhara/tapl-ruby]
|
243
|
+
|
238
244
|
== Reference
|
239
245
|
* {Pattern Matching in Ruby (at Sapporo RubyKaigi 2012) // Speaker Deck}[https://speakerdeck.com/k_tsj/patternmatchinginruby]
|
240
246
|
|
data/lib/pattern-match.rb
CHANGED
@@ -1,832 +1,7 @@
|
|
1
1
|
# pattern-match.rb
|
2
2
|
#
|
3
|
-
# Copyright (C) 2012-
|
3
|
+
# Copyright (C) 2012-2014 Kazuki Tsujimoto, All rights reserved.
|
4
4
|
|
5
5
|
require 'pattern-match/version'
|
6
|
-
|
7
|
-
|
8
|
-
module Deconstructable
|
9
|
-
def call(*subpatterns)
|
10
|
-
if Object == self
|
11
|
-
PatternKeywordArgStyleDeconstructor.new(Object, :respond_to?, :__send__, *subpatterns)
|
12
|
-
else
|
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
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
class Pattern
|
45
|
-
attr_accessor :parent, :next, :prev
|
46
|
-
|
47
|
-
def initialize(*subpatterns)
|
48
|
-
@parent = nil
|
49
|
-
@next = nil
|
50
|
-
@prev = nil
|
51
|
-
@subpatterns = subpatterns.map {|i| i.kind_of?(Pattern) ? i : PatternValue.new(i) }
|
52
|
-
set_subpatterns_relation
|
53
|
-
end
|
54
|
-
|
55
|
-
def vars
|
56
|
-
@subpatterns.map(&:vars).flatten
|
57
|
-
end
|
58
|
-
|
59
|
-
def ancestors
|
60
|
-
root? ? [self] : parent.ancestors.unshift(self)
|
61
|
-
end
|
62
|
-
|
63
|
-
def binding
|
64
|
-
vars.each_with_object({}) {|v, h| h[v.name] = v.val }
|
65
|
-
end
|
66
|
-
|
67
|
-
def &(pattern)
|
68
|
-
PatternAnd.new(self, pattern)
|
69
|
-
end
|
70
|
-
|
71
|
-
def |(pattern)
|
72
|
-
PatternOr.new(self, pattern)
|
73
|
-
end
|
74
|
-
|
75
|
-
def !@
|
76
|
-
PatternNot.new(self)
|
77
|
-
end
|
78
|
-
|
79
|
-
def to_a
|
80
|
-
[self, PatternQuantifier.new(0, true)]
|
81
|
-
end
|
82
|
-
|
83
|
-
def quantifier?
|
84
|
-
raise NotImplementedError
|
85
|
-
end
|
86
|
-
|
87
|
-
def quantified?
|
88
|
-
(@next and @next.quantifier?) || (root? ? false : @parent.quantified?)
|
89
|
-
end
|
90
|
-
|
91
|
-
def root
|
92
|
-
root? ? self : @parent.root
|
93
|
-
end
|
94
|
-
|
95
|
-
def root?
|
96
|
-
@parent == nil
|
97
|
-
end
|
98
|
-
|
99
|
-
def validate
|
100
|
-
@subpatterns.each(&:validate)
|
101
|
-
end
|
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
|
-
|
138
|
-
private
|
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
|
-
|
162
|
-
def set_subpatterns_relation
|
163
|
-
@subpatterns.each do |i|
|
164
|
-
i.parent = self
|
165
|
-
end
|
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
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
def quantifier?
|
196
|
-
true
|
197
|
-
end
|
198
|
-
|
199
|
-
def match(vals)
|
200
|
-
if @next
|
201
|
-
@next.match(vals)
|
202
|
-
else
|
203
|
-
vals.empty?
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
def longest?
|
208
|
-
@longest
|
209
|
-
end
|
210
|
-
|
211
|
-
def inspect
|
212
|
-
"#<#{self.class.name}: min_k=#{@min_k}, longest=#{@longest}>"
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
class PatternElement < Pattern
|
217
|
-
def quantifier?
|
218
|
-
false
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
class PatternDeconstructor < PatternElement
|
223
|
-
end
|
224
|
-
|
225
|
-
class PatternObjectDeconstructor < PatternDeconstructor
|
226
|
-
def initialize(deconstructor, *subpatterns)
|
227
|
-
super(*subpatterns)
|
228
|
-
@deconstructor = deconstructor
|
229
|
-
end
|
230
|
-
|
231
|
-
def match(vals)
|
232
|
-
super do |val|
|
233
|
-
deconstructed_vals = @deconstructor.deconstruct(val)
|
234
|
-
if @subpatterns.empty?
|
235
|
-
next deconstructed_vals.empty?
|
236
|
-
end
|
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
|
252
|
-
end
|
253
|
-
end
|
254
|
-
end
|
255
|
-
|
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
|
265
|
-
|
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
|
274
|
-
end
|
275
|
-
|
276
|
-
def inspect
|
277
|
-
"#<#{self.class.name}: klass=#{@klass.inspect}, spec=#{@spec.inspect}>"
|
278
|
-
end
|
279
|
-
|
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)] }]
|
295
|
-
end
|
296
|
-
end
|
297
|
-
|
298
|
-
class PatternVariable < PatternElement
|
299
|
-
attr_reader :name, :val
|
300
|
-
|
301
|
-
def initialize(name)
|
302
|
-
super()
|
303
|
-
@name = name
|
304
|
-
@val = nil
|
305
|
-
@bind_to = nil
|
306
|
-
end
|
307
|
-
|
308
|
-
def match(vals)
|
309
|
-
super do |val|
|
310
|
-
bind(val)
|
311
|
-
true
|
312
|
-
end
|
313
|
-
end
|
314
|
-
|
315
|
-
def vars
|
316
|
-
[self]
|
317
|
-
end
|
318
|
-
|
319
|
-
def set_bind_to(quantifier)
|
320
|
-
n = nest_level(quantifier)
|
321
|
-
if n == 0
|
322
|
-
@val = @bind_to = []
|
323
|
-
else
|
324
|
-
outer = @val
|
325
|
-
(n - 1).times do
|
326
|
-
outer = outer[-1]
|
327
|
-
end
|
328
|
-
@bind_to = []
|
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
|
338
|
-
else
|
339
|
-
outer = @val
|
340
|
-
(n - 1).times do
|
341
|
-
outer = outer[-1]
|
342
|
-
end
|
343
|
-
outer.pop
|
344
|
-
end
|
345
|
-
end
|
346
|
-
|
347
|
-
def inspect
|
348
|
-
"#<#{self.class.name}: name=#{name.inspect}, val=#{@val.inspect}>"
|
349
|
-
end
|
350
|
-
|
351
|
-
private
|
352
|
-
|
353
|
-
def bind(val)
|
354
|
-
if quantified?
|
355
|
-
@bind_to << val
|
356
|
-
else
|
357
|
-
@val = val
|
358
|
-
end
|
359
|
-
end
|
360
|
-
|
361
|
-
def nest_level(quantifier)
|
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
|
364
|
-
qs.index(quantifier) || (raise PatternMatchError)
|
365
|
-
end
|
366
|
-
end
|
367
|
-
|
368
|
-
class PatternValue < PatternElement
|
369
|
-
def initialize(val, compare_by = :===)
|
370
|
-
super()
|
371
|
-
@val = val
|
372
|
-
@compare_by = compare_by
|
373
|
-
end
|
374
|
-
|
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}>"
|
383
|
-
end
|
384
|
-
end
|
385
|
-
|
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
|
488
|
-
end
|
489
|
-
end
|
490
|
-
|
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|
|
535
|
-
begin
|
536
|
-
! @subpatterns[0].match([val])
|
537
|
-
rescue PatternNotMatch
|
538
|
-
true
|
539
|
-
end
|
540
|
-
end
|
541
|
-
end
|
542
|
-
|
543
|
-
def validate
|
544
|
-
super
|
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}>"
|
551
|
-
end
|
552
|
-
end
|
553
|
-
|
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
|
567
|
-
end
|
568
|
-
|
569
|
-
def validate
|
570
|
-
super
|
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}>"
|
576
|
-
end
|
577
|
-
end
|
578
|
-
|
579
|
-
class Env < BasicObject
|
580
|
-
def initialize(ctx, val)
|
581
|
-
@ctx = ctx
|
582
|
-
@val = val
|
583
|
-
end
|
584
|
-
|
585
|
-
private
|
586
|
-
|
587
|
-
def with(pat_or_val, guard_proc = nil, &block)
|
588
|
-
ctx = @ctx
|
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
|
607
|
-
pat.validate
|
608
|
-
if pat.match([@val])
|
609
|
-
ret = with_tmpbinding(ctx, pat.binding, &block)
|
610
|
-
::Kernel.throw(:exit_match, ret)
|
611
|
-
else
|
612
|
-
nil
|
613
|
-
end
|
614
|
-
rescue PatternNotMatch
|
615
|
-
end
|
616
|
-
|
617
|
-
def guard(&block)
|
618
|
-
block
|
619
|
-
end
|
620
|
-
|
621
|
-
def ___
|
622
|
-
PatternQuantifier.new(0, true)
|
623
|
-
end
|
624
|
-
|
625
|
-
def ___?
|
626
|
-
PatternQuantifier.new(0, false)
|
627
|
-
end
|
628
|
-
|
629
|
-
def method_missing(name, *)
|
630
|
-
case name.to_s
|
631
|
-
when /\A__(\d+)(\??)\z/
|
632
|
-
PatternQuantifier.new($1.to_i, ! $2.empty?)
|
633
|
-
else
|
634
|
-
PatternVariable.new(name)
|
635
|
-
end
|
636
|
-
end
|
637
|
-
|
638
|
-
def _(*vals)
|
639
|
-
case vals.length
|
640
|
-
when 0
|
641
|
-
uscore = PatternVariable.new(:_)
|
642
|
-
class << uscore
|
643
|
-
def [](*args)
|
644
|
-
Array.call(*args)
|
645
|
-
end
|
646
|
-
|
647
|
-
def vars
|
648
|
-
[]
|
649
|
-
end
|
650
|
-
|
651
|
-
private
|
652
|
-
|
653
|
-
def bind(val)
|
654
|
-
end
|
655
|
-
end
|
656
|
-
uscore
|
657
|
-
when 1
|
658
|
-
PatternValue.new(vals[0])
|
659
|
-
when 2
|
660
|
-
PatternValue.new(vals[0], vals[1])
|
661
|
-
else
|
662
|
-
raise MalformedPatternError
|
663
|
-
end
|
664
|
-
end
|
665
|
-
|
666
|
-
alias __ _
|
667
|
-
alias _l _
|
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
|
-
|
689
|
-
def with_tmpbinding(obj, binding, &block)
|
690
|
-
tmpbinding_module(obj).instance_eval do
|
691
|
-
begin
|
692
|
-
binding.each do |name, val|
|
693
|
-
stack = @stacks[name]
|
694
|
-
if stack.empty?
|
695
|
-
define_method(name) { stack[-1] }
|
696
|
-
private name
|
697
|
-
end
|
698
|
-
stack.push(val)
|
699
|
-
end
|
700
|
-
obj.instance_eval(&block)
|
701
|
-
ensure
|
702
|
-
binding.each do |name, _|
|
703
|
-
@stacks[name].pop
|
704
|
-
if @stacks[name].empty?
|
705
|
-
remove_method(name)
|
706
|
-
end
|
707
|
-
end
|
708
|
-
end
|
709
|
-
end
|
710
|
-
end
|
711
|
-
|
712
|
-
def tmpbinding_module(obj)
|
713
|
-
m = obj.singleton_class.ancestors.find {|i| i.kind_of?(TmpBindingModule) }
|
714
|
-
unless m
|
715
|
-
m = TmpBindingModule.new
|
716
|
-
m.instance_eval do
|
717
|
-
@stacks = ::Hash.new {|h, k| h[k] = [] }
|
718
|
-
end
|
719
|
-
obj.singleton_class.class_eval do
|
720
|
-
if respond_to?(:prepend, true)
|
721
|
-
prepend m
|
722
|
-
else
|
723
|
-
include m
|
724
|
-
end
|
725
|
-
end
|
726
|
-
end
|
727
|
-
m
|
728
|
-
end
|
729
|
-
end
|
730
|
-
|
731
|
-
class PatternNotMatch < Exception; end
|
732
|
-
class PatternMatchError < StandardError; end
|
733
|
-
class NoMatchingPatternError < PatternMatchError; end
|
734
|
-
class MalformedPatternError < PatternMatchError; end
|
735
|
-
|
736
|
-
# Make Pattern and its subclasses/Env private.
|
737
|
-
if respond_to?(:private_constant)
|
738
|
-
constants.each do |c|
|
739
|
-
klass = const_get(c)
|
740
|
-
next unless klass.kind_of?(Class)
|
741
|
-
if klass <= Pattern
|
742
|
-
private_constant c
|
743
|
-
end
|
744
|
-
end
|
745
|
-
private_constant :Env
|
746
|
-
end
|
747
|
-
end
|
748
|
-
|
749
|
-
module Kernel
|
750
|
-
private
|
751
|
-
|
752
|
-
def match(*vals, &block)
|
753
|
-
do_match = Proc.new do |val|
|
754
|
-
env = PatternMatch.const_get(:Env).new(self, val)
|
755
|
-
catch(:exit_match) do
|
756
|
-
env.instance_eval(&block)
|
757
|
-
raise ::PatternMatch::NoMatchingPatternError
|
758
|
-
end
|
759
|
-
end
|
760
|
-
case vals.length
|
761
|
-
when 0
|
762
|
-
do_match
|
763
|
-
when 1
|
764
|
-
do_match.(vals[0])
|
765
|
-
else
|
766
|
-
raise ArgumentError, "wrong number of arguments (#{vals.length} for 0..1)"
|
767
|
-
end
|
768
|
-
end
|
769
|
-
end
|
770
|
-
|
771
|
-
class Class
|
772
|
-
include PatternMatch::Deconstructable
|
773
|
-
|
774
|
-
def deconstruct(val)
|
775
|
-
raise NotImplementedError, "need to define `#{__method__}'"
|
776
|
-
end
|
777
|
-
|
778
|
-
private
|
779
|
-
|
780
|
-
def accept_self_instance_only(val)
|
781
|
-
raise PatternMatch::PatternNotMatch unless val.kind_of?(self)
|
782
|
-
end
|
783
|
-
end
|
784
|
-
|
785
|
-
class Hash
|
786
|
-
include PatternMatch::KeyMatcher
|
787
|
-
end
|
788
|
-
|
789
|
-
class << Array
|
790
|
-
def deconstruct(val)
|
791
|
-
accept_self_instance_only(val)
|
792
|
-
val
|
793
|
-
end
|
794
|
-
end
|
795
|
-
|
796
|
-
class << Struct
|
797
|
-
def deconstruct(val)
|
798
|
-
accept_self_instance_only(val)
|
799
|
-
val.values
|
800
|
-
end
|
801
|
-
end
|
802
|
-
|
803
|
-
class << Complex
|
804
|
-
def deconstruct(val)
|
805
|
-
accept_self_instance_only(val)
|
806
|
-
val.rect
|
807
|
-
end
|
808
|
-
end
|
809
|
-
|
810
|
-
class << Rational
|
811
|
-
def deconstruct(val)
|
812
|
-
accept_self_instance_only(val)
|
813
|
-
[val.numerator, val.denominator]
|
814
|
-
end
|
815
|
-
end
|
816
|
-
|
817
|
-
class << MatchData
|
818
|
-
def deconstruct(val)
|
819
|
-
accept_self_instance_only(val)
|
820
|
-
val.captures.empty? ? [val[0]] : val.captures
|
821
|
-
end
|
822
|
-
end
|
823
|
-
|
824
|
-
class Regexp
|
825
|
-
include PatternMatch::Deconstructable
|
826
|
-
|
827
|
-
def deconstruct(val)
|
828
|
-
m = Regexp.new("\\A#{source}\\z", options).match(val.to_s)
|
829
|
-
raise PatternMatch::PatternNotMatch unless m
|
830
|
-
m.captures.empty? ? [m[0]] : m.captures
|
831
|
-
end
|
832
|
-
end
|
6
|
+
require 'pattern-match/core'
|
7
|
+
require 'pattern-match/deconstructor'
|