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
@@ -0,0 +1,710 @@
|
|
1
|
+
require 'pattern-match/version'
|
2
|
+
|
3
|
+
module PatternMatch
|
4
|
+
module Deconstructable
|
5
|
+
def call(*subpatterns)
|
6
|
+
pattern_matcher(*subpatterns)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class ::Object
|
11
|
+
def pattern_matcher(*subpatterns)
|
12
|
+
PatternObjectDeconstructor.new(self, *subpatterns)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module HasOrderedSubPatterns
|
17
|
+
private
|
18
|
+
|
19
|
+
def set_subpatterns_relation
|
20
|
+
super
|
21
|
+
@subpatterns.each_cons(2) do |a, b|
|
22
|
+
a.next = b
|
23
|
+
b.prev = a
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Pattern
|
29
|
+
attr_accessor :parent, :next, :prev
|
30
|
+
|
31
|
+
def initialize(*subpatterns)
|
32
|
+
@parent = nil
|
33
|
+
@next = nil
|
34
|
+
@prev = nil
|
35
|
+
@subpatterns = subpatterns.map {|i| i.kind_of?(Pattern) ? i : PatternValue.new(i) }
|
36
|
+
set_subpatterns_relation
|
37
|
+
end
|
38
|
+
|
39
|
+
def vars
|
40
|
+
@subpatterns.map(&:vars).flatten
|
41
|
+
end
|
42
|
+
|
43
|
+
def ancestors
|
44
|
+
root? ? [self] : parent.ancestors.unshift(self)
|
45
|
+
end
|
46
|
+
|
47
|
+
def quasibinding
|
48
|
+
vars.each_with_object({}) {|v, h| h[v.name] = v.val }
|
49
|
+
end
|
50
|
+
|
51
|
+
def &(pattern)
|
52
|
+
PatternAnd.new(self, pattern)
|
53
|
+
end
|
54
|
+
|
55
|
+
def |(pattern)
|
56
|
+
PatternOr.new(self, pattern)
|
57
|
+
end
|
58
|
+
|
59
|
+
def !@
|
60
|
+
PatternNot.new(self)
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_a
|
64
|
+
[self, PatternQuantifier.new(0, true)]
|
65
|
+
end
|
66
|
+
|
67
|
+
def quantifier?
|
68
|
+
raise NotImplementedError
|
69
|
+
end
|
70
|
+
|
71
|
+
def quantified?
|
72
|
+
(@next and @next.quantifier?) or (root? ? false : @parent.quantified?)
|
73
|
+
end
|
74
|
+
|
75
|
+
def root
|
76
|
+
root? ? self : @parent.root
|
77
|
+
end
|
78
|
+
|
79
|
+
def root?
|
80
|
+
@parent == nil
|
81
|
+
end
|
82
|
+
|
83
|
+
def validate
|
84
|
+
@subpatterns.each(&:validate)
|
85
|
+
end
|
86
|
+
|
87
|
+
def match(vals)
|
88
|
+
if @next and @next.quantifier?
|
89
|
+
q = @next
|
90
|
+
repeating_match(vals, q.longest?) do |vs, rest|
|
91
|
+
if vs.length < q.min_k
|
92
|
+
next false
|
93
|
+
end
|
94
|
+
vs.all? {|v| yield(v) } and q.match(rest)
|
95
|
+
end
|
96
|
+
else
|
97
|
+
if vals.empty?
|
98
|
+
return false
|
99
|
+
end
|
100
|
+
val, *rest = vals
|
101
|
+
yield(val) and (@next ? @next.match(rest) : rest.empty?)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def append(pattern)
|
106
|
+
if @next
|
107
|
+
@next.append(pattern)
|
108
|
+
else
|
109
|
+
if @subpatterns.empty?
|
110
|
+
if root?
|
111
|
+
new_root = PatternAnd.new(self)
|
112
|
+
self.parent = new_root
|
113
|
+
end
|
114
|
+
pattern.parent = @parent
|
115
|
+
@next = pattern
|
116
|
+
else
|
117
|
+
@subpatterns[-1].append(pattern)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def inspect
|
123
|
+
"#<#{self.class.name}: subpatterns=#{@subpatterns.inspect}>"
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def repeating_match(vals, longest)
|
129
|
+
quantifier = @next
|
130
|
+
lp = longest_patterns(vals)
|
131
|
+
(longest ? lp : lp.reverse).each do |(vs, rest)|
|
132
|
+
vars.each {|i| i.set_bind_to(quantifier) }
|
133
|
+
begin
|
134
|
+
if yield vs, rest
|
135
|
+
return true
|
136
|
+
end
|
137
|
+
rescue PatternNotMatch
|
138
|
+
end
|
139
|
+
vars.each {|i| i.unset_bind_to(quantifier) }
|
140
|
+
end
|
141
|
+
false
|
142
|
+
end
|
143
|
+
|
144
|
+
def longest_patterns(vals)
|
145
|
+
vals.length.downto(0).map do |n|
|
146
|
+
[vals.take(n), vals.drop(n)]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def set_subpatterns_relation
|
151
|
+
@subpatterns.each do |i|
|
152
|
+
i.parent = self
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class PatternQuantifier < Pattern
|
158
|
+
attr_reader :min_k
|
159
|
+
|
160
|
+
def initialize(min_k, longest)
|
161
|
+
super()
|
162
|
+
@min_k = min_k
|
163
|
+
@longest = longest
|
164
|
+
end
|
165
|
+
|
166
|
+
def validate
|
167
|
+
super
|
168
|
+
raise MalformedPatternError unless @prev and ! @prev.quantifier?
|
169
|
+
raise MalformedPatternError unless @parent.kind_of?(HasOrderedSubPatterns)
|
170
|
+
seqs = ancestors.grep(PatternSequence).reverse
|
171
|
+
if seqs.any? {|i| i.next and i.next.quantifier? and not i.vars.empty? }
|
172
|
+
raise NotImplementedError
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def quantifier?
|
177
|
+
true
|
178
|
+
end
|
179
|
+
|
180
|
+
def match(vals)
|
181
|
+
if @next
|
182
|
+
@next.match(vals)
|
183
|
+
else
|
184
|
+
vals.empty?
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def longest?
|
189
|
+
@longest
|
190
|
+
end
|
191
|
+
|
192
|
+
def inspect
|
193
|
+
"#<#{self.class.name}: min_k=#{@min_k}, longest=#{@longest}>"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
class PatternElement < Pattern
|
198
|
+
def quantifier?
|
199
|
+
false
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class PatternDeconstructor < PatternElement
|
204
|
+
end
|
205
|
+
|
206
|
+
class PatternObjectDeconstructor < PatternDeconstructor
|
207
|
+
include HasOrderedSubPatterns
|
208
|
+
|
209
|
+
def initialize(deconstructor, *subpatterns)
|
210
|
+
super(*subpatterns)
|
211
|
+
@deconstructor = deconstructor
|
212
|
+
end
|
213
|
+
|
214
|
+
def match(vals)
|
215
|
+
super do |val|
|
216
|
+
deconstructed_vals = @deconstructor.deconstruct(val)
|
217
|
+
if @subpatterns.empty?
|
218
|
+
next deconstructed_vals.empty?
|
219
|
+
end
|
220
|
+
@subpatterns[0].match(deconstructed_vals)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def inspect
|
225
|
+
"#<#{self.class.name}: deconstructor=#{@deconstructor.inspect}, subpatterns=#{@subpatterns.inspect}>"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
class PatternKeywordArgStyleDeconstructor < PatternDeconstructor
|
230
|
+
def initialize(klass, checker, getter, *keyarg_subpatterns)
|
231
|
+
spec = normalize_keyword_arg(keyarg_subpatterns)
|
232
|
+
super(*spec.values)
|
233
|
+
@klass = klass
|
234
|
+
@checker = checker
|
235
|
+
@getter = getter
|
236
|
+
@spec = spec
|
237
|
+
end
|
238
|
+
|
239
|
+
def match(vals)
|
240
|
+
super do |val|
|
241
|
+
next false unless val.kind_of?(@klass)
|
242
|
+
next false unless @spec.keys.all? {|k| val.__send__(@checker, k) }
|
243
|
+
@spec.all? do |k, pat|
|
244
|
+
pat.match([val.__send__(@getter, k)]) rescue false
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def inspect
|
250
|
+
"#<#{self.class.name}: klass=#{@klass.inspect}, spec=#{@spec.inspect}>"
|
251
|
+
end
|
252
|
+
|
253
|
+
private
|
254
|
+
|
255
|
+
def normalize_keyword_arg(subpatterns)
|
256
|
+
syms = subpatterns.take_while {|i| i.kind_of?(Symbol) }
|
257
|
+
rest = subpatterns.drop(syms.length)
|
258
|
+
hash = case rest.length
|
259
|
+
when 0
|
260
|
+
{}
|
261
|
+
when 1
|
262
|
+
rest[0]
|
263
|
+
else
|
264
|
+
raise MalformedPatternError
|
265
|
+
end
|
266
|
+
variables = Hash[syms.map {|i| [i, PatternVariable.new(i)] }]
|
267
|
+
Hash[variables.merge(hash).map {|k, v| [k, v.kind_of?(Pattern) ? v : PatternValue.new(v)] }]
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
class PatternVariable < PatternElement
|
272
|
+
attr_reader :name, :val
|
273
|
+
|
274
|
+
def initialize(name)
|
275
|
+
super()
|
276
|
+
@name = name
|
277
|
+
@val = nil
|
278
|
+
@bind_to = nil
|
279
|
+
end
|
280
|
+
|
281
|
+
def match(vals)
|
282
|
+
super do |val|
|
283
|
+
bind(val)
|
284
|
+
true
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def vars
|
289
|
+
[self]
|
290
|
+
end
|
291
|
+
|
292
|
+
def set_bind_to(quantifier)
|
293
|
+
n = nest_level(quantifier)
|
294
|
+
if n == 0
|
295
|
+
@val = @bind_to = []
|
296
|
+
else
|
297
|
+
outer = @val
|
298
|
+
(n - 1).times do
|
299
|
+
outer = outer[-1]
|
300
|
+
end
|
301
|
+
@bind_to = []
|
302
|
+
outer << @bind_to
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def unset_bind_to(quantifier)
|
307
|
+
n = nest_level(quantifier)
|
308
|
+
@bind_to = nil
|
309
|
+
if n == 0
|
310
|
+
# do nothing
|
311
|
+
else
|
312
|
+
outer = @val
|
313
|
+
(n - 1).times do
|
314
|
+
outer = outer[-1]
|
315
|
+
end
|
316
|
+
outer.pop
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def inspect
|
321
|
+
"#<#{self.class.name}: name=#{name.inspect}, val=#{@val.inspect}>"
|
322
|
+
end
|
323
|
+
|
324
|
+
private
|
325
|
+
|
326
|
+
def bind(val)
|
327
|
+
if quantified?
|
328
|
+
@bind_to << val
|
329
|
+
else
|
330
|
+
@val = val
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
def nest_level(quantifier)
|
335
|
+
raise PatternMatchError unless quantifier.kind_of?(PatternQuantifier)
|
336
|
+
qs = ancestors.map {|i| (i.next and i.next.quantifier?) ? i.next : nil }.compact.reverse
|
337
|
+
qs.index(quantifier) || (raise PatternMatchError)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
class PatternValue < PatternElement
|
342
|
+
def initialize(val, compare_by = :===)
|
343
|
+
super()
|
344
|
+
@val = val
|
345
|
+
@compare_by = compare_by
|
346
|
+
end
|
347
|
+
|
348
|
+
def match(vals)
|
349
|
+
super do |val|
|
350
|
+
@val.__send__(@compare_by, val)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def inspect
|
355
|
+
"#<#{self.class.name}: val=#{@val.inspect}>"
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
class PatternSequence < PatternElement
|
360
|
+
include HasOrderedSubPatterns
|
361
|
+
|
362
|
+
class PatternRewind < PatternElement
|
363
|
+
attr_reader :ntimes
|
364
|
+
|
365
|
+
def initialize(ntimes, head_pattern, next_pattern)
|
366
|
+
super()
|
367
|
+
@ntimes = ntimes
|
368
|
+
@head = head_pattern
|
369
|
+
@next = next_pattern
|
370
|
+
end
|
371
|
+
|
372
|
+
def match(vals)
|
373
|
+
if @ntimes > 0
|
374
|
+
@ntimes -= 1
|
375
|
+
@head.match(vals)
|
376
|
+
else
|
377
|
+
@next ? @next.match(vals) : vals.empty?
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
def inspect
|
382
|
+
"#<#{self.class.name}: ntimes=#{@ntimes} head=#{@head.inspect} next=#{@next.inspect}>"
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
def match(vals)
|
387
|
+
if @next and @next.quantifier?
|
388
|
+
repeating_match(vals, @next.longest?) do |rewind|
|
389
|
+
if rewind.ntimes < @next.min_k
|
390
|
+
next false
|
391
|
+
end
|
392
|
+
rewind.match(vals)
|
393
|
+
end
|
394
|
+
else
|
395
|
+
with_rewind(make_rewind(1)) do |rewind|
|
396
|
+
rewind.match(vals)
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
def validate
|
402
|
+
super
|
403
|
+
raise MalformedPatternError if @subpatterns.empty?
|
404
|
+
raise MalformedPatternError unless @parent.kind_of?(HasOrderedSubPatterns)
|
405
|
+
end
|
406
|
+
|
407
|
+
private
|
408
|
+
|
409
|
+
def make_rewind(n)
|
410
|
+
PatternRewind.new(n, @subpatterns[0], (@next and @next.quantifier?) ? @next.next : @next)
|
411
|
+
end
|
412
|
+
|
413
|
+
def repeating_match(vals, longest)
|
414
|
+
quantifier = @next
|
415
|
+
lp = longest_patterns(vals)
|
416
|
+
(longest ? lp : lp.reverse).each do |rewind|
|
417
|
+
vars.each {|i| i.set_bind_to(quantifier) }
|
418
|
+
begin
|
419
|
+
with_rewind(rewind) do
|
420
|
+
if yield rewind
|
421
|
+
return true
|
422
|
+
end
|
423
|
+
end
|
424
|
+
rescue PatternNotMatch
|
425
|
+
end
|
426
|
+
vars.each {|i| i.unset_bind_to(quantifier) }
|
427
|
+
end
|
428
|
+
false
|
429
|
+
end
|
430
|
+
|
431
|
+
def longest_patterns(vals)
|
432
|
+
vals.length.downto(0).map do |n|
|
433
|
+
make_rewind(n)
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
def with_rewind(rewind)
|
438
|
+
@subpatterns[-1].next = rewind
|
439
|
+
yield rewind
|
440
|
+
ensure
|
441
|
+
@subpatterns[-1].next = nil
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
class PatternAnd < PatternElement
|
446
|
+
def match(vals)
|
447
|
+
super do |val|
|
448
|
+
@subpatterns.all? {|i| i.match([val]) }
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
def validate
|
453
|
+
super
|
454
|
+
raise MalformedPatternError if @subpatterns.empty?
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
class PatternOr < PatternElement
|
459
|
+
def match(vals)
|
460
|
+
super do |val|
|
461
|
+
@subpatterns.find do |i|
|
462
|
+
begin
|
463
|
+
i.match([val])
|
464
|
+
rescue PatternNotMatch
|
465
|
+
false
|
466
|
+
end
|
467
|
+
end
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
def validate
|
472
|
+
super
|
473
|
+
raise MalformedPatternError if @subpatterns.empty?
|
474
|
+
raise MalformedPatternError unless vars.empty?
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
class PatternNot < PatternElement
|
479
|
+
def match(vals)
|
480
|
+
super do |val|
|
481
|
+
begin
|
482
|
+
! @subpatterns[0].match([val])
|
483
|
+
rescue PatternNotMatch
|
484
|
+
true
|
485
|
+
end
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
def validate
|
490
|
+
super
|
491
|
+
raise MalformedPatternError unless @subpatterns.length == 1
|
492
|
+
raise MalformedPatternError unless vars.empty?
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
class PatternCondition < PatternElement
|
497
|
+
def initialize(&condition)
|
498
|
+
super()
|
499
|
+
@condition = condition
|
500
|
+
end
|
501
|
+
|
502
|
+
def match(vals)
|
503
|
+
return false unless vals.empty?
|
504
|
+
if @condition.call
|
505
|
+
@next ? @next.match(vals) : true
|
506
|
+
else
|
507
|
+
false
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
def validate
|
512
|
+
super
|
513
|
+
raise MalformedPatternError if ancestors.find {|i| i.next and ! i.next.kind_of?(PatternCondition) }
|
514
|
+
end
|
515
|
+
|
516
|
+
def inspect
|
517
|
+
"#<#{self.class.name}: condition=#{@condition.inspect}>"
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
class Env < BasicObject
|
522
|
+
def initialize(ctx, val)
|
523
|
+
@ctx = ctx
|
524
|
+
@val = val
|
525
|
+
end
|
526
|
+
|
527
|
+
private
|
528
|
+
|
529
|
+
def with(pat_or_val, guard_proc = nil, &block)
|
530
|
+
ctx = @ctx
|
531
|
+
pat = pat_or_val.kind_of?(Pattern) ? pat_or_val : PatternValue.new(pat_or_val)
|
532
|
+
pat.append(PatternCondition.new { check_for_duplicate_vars(pat.vars) })
|
533
|
+
if guard_proc
|
534
|
+
pat.append(PatternCondition.new { with_quasibinding(ctx, pat.quasibinding, &guard_proc) })
|
535
|
+
end
|
536
|
+
pat.validate
|
537
|
+
if pat.match([@val])
|
538
|
+
ret = with_quasibinding(ctx, pat.quasibinding, &block)
|
539
|
+
::Kernel.throw(:exit_match, ret)
|
540
|
+
else
|
541
|
+
nil
|
542
|
+
end
|
543
|
+
rescue PatternNotMatch
|
544
|
+
end
|
545
|
+
|
546
|
+
def guard(&block)
|
547
|
+
block
|
548
|
+
end
|
549
|
+
|
550
|
+
def ___
|
551
|
+
PatternQuantifier.new(0, true)
|
552
|
+
end
|
553
|
+
|
554
|
+
def ___?
|
555
|
+
PatternQuantifier.new(0, false)
|
556
|
+
end
|
557
|
+
|
558
|
+
def method_missing(name, *args)
|
559
|
+
::Kernel.raise ::ArgumentError, "wrong number of arguments (#{args.length} for 0)" unless args.empty?
|
560
|
+
case name.to_s
|
561
|
+
when /\A__(\d+)(\??)\z/
|
562
|
+
PatternQuantifier.new($1.to_i, ! $2.empty?)
|
563
|
+
else
|
564
|
+
PatternVariable.new(name)
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
def _(*vals)
|
569
|
+
case vals.length
|
570
|
+
when 0
|
571
|
+
uscore = PatternVariable.new(:_)
|
572
|
+
class << uscore
|
573
|
+
def [](*args)
|
574
|
+
Array.call(*args)
|
575
|
+
end
|
576
|
+
|
577
|
+
def vars
|
578
|
+
[]
|
579
|
+
end
|
580
|
+
|
581
|
+
private
|
582
|
+
|
583
|
+
def bind(val)
|
584
|
+
end
|
585
|
+
end
|
586
|
+
uscore
|
587
|
+
when 1
|
588
|
+
PatternValue.new(vals[0])
|
589
|
+
when 2
|
590
|
+
PatternValue.new(vals[0], vals[1])
|
591
|
+
else
|
592
|
+
::Kernel.raise MalformedPatternError
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
alias __ _
|
597
|
+
alias _l _
|
598
|
+
|
599
|
+
def Seq(*subpatterns)
|
600
|
+
PatternSequence.new(*subpatterns)
|
601
|
+
end
|
602
|
+
|
603
|
+
def And(*subpatterns)
|
604
|
+
PatternAnd.new(*subpatterns)
|
605
|
+
end
|
606
|
+
|
607
|
+
def Or(*subpatterns)
|
608
|
+
PatternOr.new(*subpatterns)
|
609
|
+
end
|
610
|
+
|
611
|
+
def Not(*subpatterns)
|
612
|
+
PatternNot.new(*subpatterns)
|
613
|
+
end
|
614
|
+
|
615
|
+
def check_for_duplicate_vars(vars)
|
616
|
+
vars.each_with_object({}) do |v, h|
|
617
|
+
if h.has_key?(v.name)
|
618
|
+
unless h[v.name] == v.val
|
619
|
+
return false
|
620
|
+
end
|
621
|
+
else
|
622
|
+
h[v.name] = v.val
|
623
|
+
end
|
624
|
+
end
|
625
|
+
true
|
626
|
+
end
|
627
|
+
|
628
|
+
class QuasiBindingModule < ::Module
|
629
|
+
end
|
630
|
+
|
631
|
+
def with_quasibinding(obj, quasibinding, &block)
|
632
|
+
quasibinding_module(obj).module_eval do
|
633
|
+
begin
|
634
|
+
quasibinding.each do |name, val|
|
635
|
+
stack = @stacks[name]
|
636
|
+
if stack.empty?
|
637
|
+
define_method(name) { stack[-1] }
|
638
|
+
private name
|
639
|
+
end
|
640
|
+
stack.push(val)
|
641
|
+
end
|
642
|
+
obj.instance_eval(&block)
|
643
|
+
ensure
|
644
|
+
quasibinding.each do |name, _|
|
645
|
+
@stacks[name].pop
|
646
|
+
if @stacks[name].empty?
|
647
|
+
remove_method(name)
|
648
|
+
end
|
649
|
+
end
|
650
|
+
end
|
651
|
+
end
|
652
|
+
end
|
653
|
+
|
654
|
+
def quasibinding_module(obj)
|
655
|
+
m = obj.singleton_class.ancestors.find {|i| i.kind_of?(QuasiBindingModule) }
|
656
|
+
unless m
|
657
|
+
m = QuasiBindingModule.new do
|
658
|
+
@stacks = ::Hash.new {|h, k| h[k] = [] }
|
659
|
+
end
|
660
|
+
obj.singleton_class.class_eval do
|
661
|
+
if respond_to?(:prepend, true)
|
662
|
+
prepend m
|
663
|
+
else
|
664
|
+
include m
|
665
|
+
end
|
666
|
+
end
|
667
|
+
end
|
668
|
+
m
|
669
|
+
end
|
670
|
+
end
|
671
|
+
|
672
|
+
class PatternNotMatch < Exception; end
|
673
|
+
class PatternMatchError < StandardError; end
|
674
|
+
class NoMatchingPatternError < PatternMatchError; end
|
675
|
+
class MalformedPatternError < PatternMatchError; end
|
676
|
+
|
677
|
+
# Make Pattern and its subclasses/Env private.
|
678
|
+
if respond_to?(:private_constant)
|
679
|
+
constants.each do |c|
|
680
|
+
klass = const_get(c)
|
681
|
+
next unless klass.kind_of?(Class)
|
682
|
+
if klass <= Pattern
|
683
|
+
private_constant c
|
684
|
+
end
|
685
|
+
end
|
686
|
+
private_constant :Env
|
687
|
+
end
|
688
|
+
end
|
689
|
+
|
690
|
+
module Kernel
|
691
|
+
private
|
692
|
+
|
693
|
+
def match(*vals, &block)
|
694
|
+
do_match = Proc.new do |val|
|
695
|
+
env = PatternMatch.const_get(:Env).new(self, val)
|
696
|
+
catch(:exit_match) do
|
697
|
+
env.instance_eval(&block)
|
698
|
+
raise PatternMatch::NoMatchingPatternError
|
699
|
+
end
|
700
|
+
end
|
701
|
+
case vals.length
|
702
|
+
when 0
|
703
|
+
do_match
|
704
|
+
when 1
|
705
|
+
do_match.(vals[0])
|
706
|
+
else
|
707
|
+
raise ArgumentError, "wrong number of arguments (#{vals.length} for 0..1)"
|
708
|
+
end
|
709
|
+
end
|
710
|
+
end
|