pattern-match 0.5.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 71fd4640c715750480dcad518ebc8fe68d468bf7
4
- data.tar.gz: 84c2f108c050569e0dd6b9eb2f3ff79900ef65a3
3
+ metadata.gz: ef7bf869eb224fa684f0d76e818765b7c29bac7a
4
+ data.tar.gz: 8f0250bc045bc40226c470eeecc0d4cb19c9948e
5
5
  SHA512:
6
- metadata.gz: ba8572f327ebe6fef3109d1fe7d14ce22146a33e7c5602afc3457fa7a17110699ff1683af2d9f0cda81f98b124cc495a2a3923ef84ae5bca9df52f81ba82042f
7
- data.tar.gz: af9f39647b62a984634f30afa28c5a7ad6d3d9b1fb39fdda5599aee5ffae9033de99aee5c6024178f23482800ee80478e1d4949857638ec0fb8233f21811b0b5
6
+ metadata.gz: da3c2aee6e261dc82bac97d578629b2d92a4449e6eb10ccd8f321fbd99330f2b505cde23df1e10080e7f9d2549ce40a9ba2c20c6e680d0fc6abe2c701cea3596
7
+ data.tar.gz: 5c7108710be125a049fe4fde57a1292e6e820a87cb414ef6fe7db18ad6d339da2549659381ea018b17de6469adcf51cb90240fa532c8ea92b44d027f3065f847
@@ -1,6 +1,30 @@
1
1
  rvm:
2
- - 1.9.2
3
2
  - 1.9.3
4
3
  - 2.0.0
5
- - 2.1.0
4
+ - 2.1
5
+ - 2.2
6
6
  - ruby-head
7
+ - jruby-19mode
8
+ env:
9
+ - TEST="test/test_*"
10
+ - TEST="test/test_standard.rb"
11
+ - DISABLE_REFINEMENTS=1 TEST="test/test_*"
12
+ - DISABLE_REFINEMENTS=1 TEST="test/test_standard.rb"
13
+ matrix:
14
+ exclude:
15
+ - rvm: 1.9.3
16
+ env: TEST="test/test_*"
17
+ - rvm: 1.9.3
18
+ env: TEST="test/test_standard.rb"
19
+ - rvm: 2.0.0
20
+ env: TEST="test/test_*"
21
+ - rvm: 2.0.0
22
+ env: TEST="test/test_standard.rb"
23
+ - rvm: jruby-19mode
24
+ env: TEST="test/test_*"
25
+ - rvm: jruby-19mode
26
+ env: TEST="test/test_standard.rb"
27
+ - rvm: jruby-19mode
28
+ env: DISABLE_REFINEMENTS=1 TEST="test/test_*"
29
+ allow_failures:
30
+ - rvm: ruby-head
data/BSDL CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (C) 2012-2014 Kazuki Tsujimoto, All rights reserved.
1
+ Copyright (C) 2012-2015 Kazuki Tsujimoto, All rights reserved.
2
2
 
3
3
  Redistribution and use in source and binary forms, with or without
4
4
  modification, are permitted provided that the following conditions
data/COPYING CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (C) 2012-2014 Kazuki Tsujimoto, All rights reserved.
1
+ Copyright (C) 2012-2015 Kazuki Tsujimoto, All rights reserved.
2
2
 
3
3
  You can redistribute it and/or modify it under either the terms of the
4
4
  2-clause BSDL (see the file BSDL), or the conditions below:
@@ -19,9 +19,10 @@ or
19
19
  $ bundle install --path vendor/bundle
20
20
 
21
21
  == Basic Usage
22
- pattern-match library provides Kernel#match.
22
+ pattern-match library provides <code>Object#match</code>.
23
23
 
24
24
  require 'pattern-match'
25
+ using PatternMatch
25
26
 
26
27
  match(object) do
27
28
  with(pattern[, guard]) do
@@ -111,7 +112,7 @@ all objects bound to variable must be equal.
111
112
  <code>And</code>, <code>Or</code>, <code>Not</code> return and/or/not pattern.
112
113
 
113
114
  match([0, [1]]) do
114
- with(a & Fixnum, ! (_[2] | _[3])) { a } #=> 0
115
+ with(_[a & Fixnum, ! (_[2] | _[3])]) { a } #=> 0
115
116
  end
116
117
 
117
118
  match(0) do
@@ -238,6 +239,18 @@ Pattern guard can be specified as a second argument to <code>with</code>.
238
239
  end
239
240
  end
240
241
 
242
+ match('0') do
243
+ with(/\d+/.(a << :to_i)) do
244
+ a #=> 0
245
+ end
246
+ end
247
+
248
+ match([Set[0, 1, 2], Set[3, 4]]) do
249
+ with(_[Set.(a, b), Set.(c)], guard { a + b * c == 2 } ) do
250
+ [a, b, c] #=> [2, 0, 3]
251
+ end
252
+ end
253
+
241
254
  * {RubyTextProcessing}[https://code.google.com/p/tokland/wiki/RubyTextProcessing]
242
255
  * {yhara/tapl-ruby}[https://github.com/yhara/tapl-ruby]
243
256
 
data/Rakefile CHANGED
@@ -6,5 +6,6 @@ require "rake/testtask"
6
6
  task :default => :test
7
7
  Rake::TestTask.new do |t|
8
8
  t.libs << "lib"
9
+ t.ruby_opts = ["-w"]
9
10
  t.test_files = FileList["test/test_*.rb"]
10
11
  end
@@ -1,7 +1,7 @@
1
1
  # pattern-match.rb
2
2
  #
3
- # Copyright (C) 2012-2014 Kazuki Tsujimoto, All rights reserved.
3
+ # Copyright (C) 2012-2015 Kazuki Tsujimoto, All rights reserved.
4
4
 
5
5
  require 'pattern-match/version'
6
- require 'pattern-match/core'
7
6
  require 'pattern-match/deconstructor'
7
+ require 'pattern-match/core'
@@ -1,24 +1,12 @@
1
- require 'pattern-match/version'
1
+ require 'pattern-match/deconstructor'
2
2
 
3
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
4
  module HasOrderedSubPatterns
17
5
  private
18
6
 
19
7
  def set_subpatterns_relation
20
8
  super
21
- @subpatterns.each_cons(2) do |a, b|
9
+ subpatterns.each_cons(2) do |a, b|
22
10
  a.next = b
23
11
  b.prev = a
24
12
  end
@@ -37,7 +25,7 @@ module PatternMatch
37
25
  end
38
26
 
39
27
  def vars
40
- @subpatterns.map(&:vars).flatten
28
+ subpatterns.map(&:vars).flatten
41
29
  end
42
30
 
43
31
  def ancestors
@@ -69,7 +57,11 @@ module PatternMatch
69
57
  end
70
58
 
71
59
  def quantified?
72
- (@next and @next.quantifier?) or (root? ? false : @parent.quantified?)
60
+ directly_quantified? or (root? ? false : @parent.quantified?)
61
+ end
62
+
63
+ def directly_quantified?
64
+ @next and @next.quantifier?
73
65
  end
74
66
 
75
67
  def root
@@ -81,13 +73,13 @@ module PatternMatch
81
73
  end
82
74
 
83
75
  def validate
84
- @subpatterns.each(&:validate)
76
+ subpatterns.each(&:validate)
85
77
  end
86
78
 
87
79
  def match(vals)
88
- if @next and @next.quantifier?
80
+ if directly_quantified?
89
81
  q = @next
90
- repeating_match(vals, q.longest?) do |vs, rest|
82
+ repeating_match(vals, q.greedy?) do |vs, rest|
91
83
  if vs.length < q.min_k
92
84
  next false
93
85
  end
@@ -106,7 +98,7 @@ module PatternMatch
106
98
  if @next
107
99
  @next.append(pattern)
108
100
  else
109
- if @subpatterns.empty?
101
+ if subpatterns.empty?
110
102
  if root?
111
103
  new_root = PatternAnd.new(self)
112
104
  self.parent = new_root
@@ -114,21 +106,23 @@ module PatternMatch
114
106
  pattern.parent = @parent
115
107
  @next = pattern
116
108
  else
117
- @subpatterns[-1].append(pattern)
109
+ subpatterns[-1].append(pattern)
118
110
  end
119
111
  end
120
112
  end
121
113
 
122
114
  def inspect
123
- "#<#{self.class.name}: subpatterns=#{@subpatterns.inspect}>"
115
+ "#<#{self.class.name}: subpatterns=#{subpatterns.inspect}>"
124
116
  end
125
117
 
126
118
  private
127
119
 
128
- def repeating_match(vals, longest)
120
+ attr_reader :subpatterns
121
+
122
+ def repeating_match(vals, is_greedy)
129
123
  quantifier = @next
130
- lp = longest_patterns(vals)
131
- (longest ? lp : lp.reverse).each do |(vs, rest)|
124
+ candidates = generate_candidates(vals)
125
+ (is_greedy ? candidates : candidates.reverse).each do |(vs, rest)|
132
126
  vars.each {|i| i.set_bind_to(quantifier) }
133
127
  begin
134
128
  if yield vs, rest
@@ -141,14 +135,14 @@ module PatternMatch
141
135
  false
142
136
  end
143
137
 
144
- def longest_patterns(vals)
138
+ def generate_candidates(vals)
145
139
  vals.length.downto(0).map do |n|
146
140
  [vals.take(n), vals.drop(n)]
147
141
  end
148
142
  end
149
143
 
150
144
  def set_subpatterns_relation
151
- @subpatterns.each do |i|
145
+ subpatterns.each do |i|
152
146
  i.parent = self
153
147
  end
154
148
  end
@@ -157,10 +151,10 @@ module PatternMatch
157
151
  class PatternQuantifier < Pattern
158
152
  attr_reader :min_k
159
153
 
160
- def initialize(min_k, longest)
154
+ def initialize(min_k, is_greedy)
161
155
  super()
162
156
  @min_k = min_k
163
- @longest = longest
157
+ @is_greedy = is_greedy
164
158
  end
165
159
 
166
160
  def validate
@@ -185,12 +179,12 @@ module PatternMatch
185
179
  end
186
180
  end
187
181
 
188
- def longest?
189
- @longest
182
+ def greedy?
183
+ @is_greedy
190
184
  end
191
185
 
192
186
  def inspect
193
- "#<#{self.class.name}: min_k=#{@min_k}, longest=#{@longest}>"
187
+ "#<#{self.class.name}: min_k=#{@min_k}, is_greedy=#{@is_greedy}>"
194
188
  end
195
189
  end
196
190
 
@@ -206,6 +200,8 @@ module PatternMatch
206
200
  class PatternObjectDeconstructor < PatternDeconstructor
207
201
  include HasOrderedSubPatterns
208
202
 
203
+ using PatternMatch if respond_to?(:using, true)
204
+
209
205
  def initialize(deconstructor, *subpatterns)
210
206
  super(*subpatterns)
211
207
  @deconstructor = deconstructor
@@ -214,57 +210,15 @@ module PatternMatch
214
210
  def match(vals)
215
211
  super do |val|
216
212
  deconstructed_vals = @deconstructor.deconstruct(val)
217
- if @subpatterns.empty?
213
+ if subpatterns.empty?
218
214
  next deconstructed_vals.empty?
219
215
  end
220
- @subpatterns[0].match(deconstructed_vals)
216
+ subpatterns[0].match(deconstructed_vals)
221
217
  end
222
218
  end
223
219
 
224
220
  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)] }]
221
+ "#<#{self.class.name}: deconstructor=#{@deconstructor.inspect}, subpatterns=#{subpatterns.inspect}>"
268
222
  end
269
223
  end
270
224
 
@@ -384,8 +338,8 @@ module PatternMatch
384
338
  end
385
339
 
386
340
  def match(vals)
387
- if @next and @next.quantifier?
388
- repeating_match(vals, @next.longest?) do |rewind|
341
+ if directly_quantified?
342
+ repeating_match(vals, @next.greedy?) do |rewind|
389
343
  if rewind.ntimes < @next.min_k
390
344
  next false
391
345
  end
@@ -400,20 +354,20 @@ module PatternMatch
400
354
 
401
355
  def validate
402
356
  super
403
- raise MalformedPatternError if @subpatterns.empty?
357
+ raise MalformedPatternError if subpatterns.empty?
404
358
  raise MalformedPatternError unless @parent.kind_of?(HasOrderedSubPatterns)
405
359
  end
406
360
 
407
361
  private
408
362
 
409
363
  def make_rewind(n)
410
- PatternRewind.new(n, @subpatterns[0], (@next and @next.quantifier?) ? @next.next : @next)
364
+ PatternRewind.new(n, subpatterns[0], directly_quantified? ? @next.next : @next)
411
365
  end
412
366
 
413
- def repeating_match(vals, longest)
367
+ def repeating_match(vals, is_greedy)
414
368
  quantifier = @next
415
- lp = longest_patterns(vals)
416
- (longest ? lp : lp.reverse).each do |rewind|
369
+ candidates = generate_candidates(vals)
370
+ (is_greedy ? candidates : candidates.reverse).each do |rewind|
417
371
  vars.each {|i| i.set_bind_to(quantifier) }
418
372
  begin
419
373
  with_rewind(rewind) do
@@ -428,37 +382,37 @@ module PatternMatch
428
382
  false
429
383
  end
430
384
 
431
- def longest_patterns(vals)
385
+ def generate_candidates(vals)
432
386
  vals.length.downto(0).map do |n|
433
387
  make_rewind(n)
434
388
  end
435
389
  end
436
390
 
437
391
  def with_rewind(rewind)
438
- @subpatterns[-1].next = rewind
392
+ subpatterns[-1].next = rewind
439
393
  yield rewind
440
394
  ensure
441
- @subpatterns[-1].next = nil
395
+ subpatterns[-1].next = nil
442
396
  end
443
397
  end
444
398
 
445
399
  class PatternAnd < PatternElement
446
400
  def match(vals)
447
401
  super do |val|
448
- @subpatterns.all? {|i| i.match([val]) }
402
+ subpatterns.all? {|i| i.match([val]) }
449
403
  end
450
404
  end
451
405
 
452
406
  def validate
453
407
  super
454
- raise MalformedPatternError if @subpatterns.empty?
408
+ raise MalformedPatternError if subpatterns.empty?
455
409
  end
456
410
  end
457
411
 
458
412
  class PatternOr < PatternElement
459
413
  def match(vals)
460
414
  super do |val|
461
- @subpatterns.find do |i|
415
+ subpatterns.find do |i|
462
416
  begin
463
417
  i.match([val])
464
418
  rescue PatternNotMatch
@@ -470,7 +424,7 @@ module PatternMatch
470
424
 
471
425
  def validate
472
426
  super
473
- raise MalformedPatternError if @subpatterns.empty?
427
+ raise MalformedPatternError if subpatterns.empty?
474
428
  raise MalformedPatternError unless vars.empty?
475
429
  end
476
430
  end
@@ -479,7 +433,7 @@ module PatternMatch
479
433
  def match(vals)
480
434
  super do |val|
481
435
  begin
482
- ! @subpatterns[0].match([val])
436
+ ! subpatterns[0].match([val])
483
437
  rescue PatternNotMatch
484
438
  true
485
439
  end
@@ -488,7 +442,7 @@ module PatternMatch
488
442
 
489
443
  def validate
490
444
  super
491
- raise MalformedPatternError unless @subpatterns.length == 1
445
+ raise MalformedPatternError unless subpatterns.length == 1
492
446
  raise MalformedPatternError unless vars.empty?
493
447
  end
494
448
  end
@@ -559,7 +513,7 @@ module PatternMatch
559
513
  ::Kernel.raise ::ArgumentError, "wrong number of arguments (#{args.length} for 0)" unless args.empty?
560
514
  case name.to_s
561
515
  when /\A__(\d+)(\??)\z/
562
- PatternQuantifier.new($1.to_i, ! $2.empty?)
516
+ PatternQuantifier.new($1.to_i, $2.empty?)
563
517
  else
564
518
  PatternVariable.new(name)
565
519
  end
@@ -570,8 +524,10 @@ module PatternMatch
570
524
  when 0
571
525
  uscore = PatternVariable.new(:_)
572
526
  class << uscore
527
+ using PatternMatch if respond_to?(:using, true)
528
+
573
529
  def [](*args)
574
- Array.call(*args)
530
+ Array.(*args)
575
531
  end
576
532
 
577
533
  def vars
@@ -685,26 +641,27 @@ module PatternMatch
685
641
  end
686
642
  private_constant :Env
687
643
  end
688
- end
689
644
 
690
- module Kernel
691
- private
645
+ refine Object do
692
646
 
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
647
+ private
648
+
649
+ def match(*vals, &block)
650
+ do_match = Proc.new do |val|
651
+ env = Env.new(self, val)
652
+ catch(:exit_match) do
653
+ env.instance_eval(&block)
654
+ raise NoMatchingPatternError
655
+ end
656
+ end
657
+ case vals.length
658
+ when 0
659
+ do_match
660
+ when 1
661
+ do_match.(vals[0])
662
+ else
663
+ raise ArgumentError, "wrong number of arguments (#{vals.length} for 0..1)"
699
664
  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
665
  end
709
666
  end
710
667
  end
@@ -1,60 +1,78 @@
1
- require 'pattern-match/core'
1
+ require 'pattern-match/version'
2
2
 
3
- class Class
4
- include PatternMatch::Deconstructable
3
+ module PatternMatch
4
+ refine Object do
5
+ private
5
6
 
6
- def deconstruct(val)
7
- raise NotImplementedError, "need to define `#{__method__}'"
7
+ def pattern_matcher(*subpatterns)
8
+ PatternObjectDeconstructor.new(self, *subpatterns)
9
+ end
8
10
  end
9
11
 
10
- private
12
+ module Deconstructable
13
+ using PatternMatch if respond_to?(:using, true)
11
14
 
12
- def accept_self_instance_only(val)
13
- raise PatternMatch::PatternNotMatch unless val.kind_of?(self)
15
+ def call(*subpatterns)
16
+ pattern_matcher(*subpatterns)
17
+ end
14
18
  end
15
- end
16
19
 
17
- class << Array
18
- def deconstruct(val)
19
- accept_self_instance_only(val)
20
- val
20
+ refine Class do
21
+ include PatternMatch::Deconstructable
22
+
23
+ def deconstruct(val)
24
+ raise NotImplementedError, "need to define `#{__method__}'"
25
+ end
26
+
27
+ private
28
+
29
+ def accept_self_instance_only(val)
30
+ raise PatternMatch::PatternNotMatch unless val.kind_of?(self)
31
+ end
21
32
  end
22
- end
23
33
 
24
- class << Struct
25
- def deconstruct(val)
26
- accept_self_instance_only(val)
27
- val.values
34
+ refine Array.singleton_class do
35
+ def deconstruct(val)
36
+ accept_self_instance_only(val)
37
+ val
38
+ end
28
39
  end
29
- end
30
40
 
31
- class << Complex
32
- def deconstruct(val)
33
- accept_self_instance_only(val)
34
- val.rect
41
+ refine Struct.singleton_class do
42
+ def deconstruct(val)
43
+ accept_self_instance_only(val)
44
+ val.values
45
+ end
35
46
  end
36
- end
37
47
 
38
- class << Rational
39
- def deconstruct(val)
40
- accept_self_instance_only(val)
41
- [val.numerator, val.denominator]
48
+ refine Complex.singleton_class do
49
+ def deconstruct(val)
50
+ accept_self_instance_only(val)
51
+ val.rect
52
+ end
42
53
  end
43
- end
44
54
 
45
- class << MatchData
46
- def deconstruct(val)
47
- accept_self_instance_only(val)
48
- val.captures.empty? ? [val[0]] : val.captures
55
+ refine Rational.singleton_class do
56
+ def deconstruct(val)
57
+ accept_self_instance_only(val)
58
+ [val.numerator, val.denominator]
59
+ end
60
+ end
61
+
62
+ refine MatchData.singleton_class do
63
+ def deconstruct(val)
64
+ accept_self_instance_only(val)
65
+ val.captures.empty? ? [val[0]] : val.captures
66
+ end
49
67
  end
50
- end
51
68
 
52
- class Regexp
53
- include PatternMatch::Deconstructable
69
+ refine Regexp do
70
+ include PatternMatch::Deconstructable
54
71
 
55
- def deconstruct(val)
56
- m = Regexp.new("\\A#{source}\\z", options).match(val.to_s)
57
- raise PatternMatch::PatternNotMatch unless m
58
- m.captures.empty? ? [m[0]] : m.captures
72
+ def deconstruct(val)
73
+ m = Regexp.new("\\A#{source}\\z", options).match(val.to_s)
74
+ raise PatternMatch::PatternNotMatch unless m
75
+ m.captures.empty? ? [m[0]] : m.captures
76
+ end
59
77
  end
60
78
  end
@@ -0,0 +1,25 @@
1
+ class Module
2
+ methods = Module.instance_methods(false) + Module.private_instance_methods(false)
3
+ if methods.include?(:refine)
4
+ if methods.include?(:__refine_orig)
5
+ raise LoadError, "can't re-define Module#refine"
6
+ end
7
+ alias_method :__refine_orig, :refine
8
+ remove_method :refine
9
+ end
10
+
11
+ def refine(klass, &blk)
12
+ klass.class_eval(&blk)
13
+ end
14
+
15
+ begin
16
+ require 'pattern-match'
17
+ ensure
18
+ remove_method :refine
19
+
20
+ if Kernel.respond_to?(:__refine_orig, true)
21
+ alias_method :refine, :__refine_orig
22
+ remove_method :__refine_orig
23
+ end
24
+ end
25
+ end
@@ -1,7 +1,57 @@
1
1
  require 'pattern-match/core'
2
+ require 'continuation'
3
+ require 'set'
4
+
5
+ raise LoadError, 'Module#prepend required' unless Module.respond_to?(:prepend, true)
2
6
 
3
7
  module PatternMatch
8
+ class Pattern
9
+ module Backtrackable
10
+ def match(vals)
11
+ matched = super
12
+ if root? and not matched and not choice_points.empty?
13
+ restore_choice_point
14
+ end
15
+ matched
16
+ end
17
+
18
+ def choice_points
19
+ if root?
20
+ @choice_points ||= []
21
+ else
22
+ @parent.choice_points
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def repeating_match(vals, is_greedy)
29
+ super do |vs, rest|
30
+ cont = nil
31
+ if callcc {|c| cont = c; yield vs, rest }
32
+ save_choice_point(cont)
33
+ true
34
+ else
35
+ false
36
+ end
37
+ end
38
+ end
39
+
40
+ def save_choice_point(choice_point)
41
+ choice_points.push(choice_point)
42
+ end
43
+
44
+ def restore_choice_point
45
+ choice_points.pop.call(false)
46
+ end
47
+ end
48
+
49
+ prepend Backtrackable
50
+ end
51
+
4
52
  module Deconstructable
53
+ using PatternMatch if respond_to?(:using, true)
54
+
5
55
  remove_method :call
6
56
  def call(*subpatterns)
7
57
  if Object == self
@@ -31,6 +81,98 @@ module PatternMatch
31
81
  end
32
82
  end
33
83
  end
84
+
85
+ class PatternKeywordArgStyleDeconstructor < PatternDeconstructor
86
+ def initialize(klass, checker, getter, *keyarg_subpatterns)
87
+ spec = normalize_keyword_arg(keyarg_subpatterns)
88
+ super(*spec.values)
89
+ @klass = klass
90
+ @checker = checker
91
+ @getter = getter
92
+ @spec = spec
93
+ end
94
+
95
+ def match(vals)
96
+ super do |val|
97
+ next false unless val.kind_of?(@klass)
98
+ next false unless @spec.keys.all? {|k| val.__send__(@checker, k) }
99
+ @spec.all? do |k, pat|
100
+ pat.match([val.__send__(@getter, k)]) rescue false
101
+ end
102
+ end
103
+ end
104
+
105
+ def inspect
106
+ "#<#{self.class.name}: klass=#{@klass.inspect}, spec=#{@spec.inspect}>"
107
+ end
108
+
109
+ private
110
+
111
+ def normalize_keyword_arg(subpatterns)
112
+ syms = subpatterns.take_while {|i| i.kind_of?(Symbol) }
113
+ rest = subpatterns.drop(syms.length)
114
+ hash = case rest.length
115
+ when 0
116
+ {}
117
+ when 1
118
+ rest[0]
119
+ else
120
+ raise MalformedPatternError
121
+ end
122
+ variables = Hash[syms.map {|i| [i, PatternVariable.new(i)] }]
123
+ Hash[variables.merge(hash).map {|k, v| [k, v.kind_of?(Pattern) ? v : PatternValue.new(v)] }]
124
+ end
125
+ end
126
+
127
+ class << Set
128
+ def pattern_matcher(*subpatterns)
129
+ PatternSetDeconstructor.new(self, *subpatterns)
130
+ end
131
+ end
132
+
133
+ class PatternSetDeconstructor < PatternDeconstructor
134
+ def initialize(klass, *subpatterns)
135
+ super(*subpatterns)
136
+ @klass = klass
137
+ end
138
+
139
+ def match(vals)
140
+ super do |val|
141
+ next false unless val.kind_of?(@klass)
142
+ members = val.to_a
143
+ next false unless subpatterns.length <= members.length
144
+ members.permutation(subpatterns.length).find do |perm|
145
+ cont = nil
146
+ if callcc {|c| cont = c; perm.zip(subpatterns).all? {|i, pat| pat.match([i]) } }
147
+ save_choice_point(cont)
148
+ true
149
+ else
150
+ false
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+
157
+ class PatternVariable
158
+ def <<(converter)
159
+ @converter = converter.respond_to?(:call) ? converter : converter.to_proc
160
+ self
161
+ end
162
+
163
+ prepend Module.new {
164
+ def initialize(name)
165
+ super
166
+ @converter = nil
167
+ end
168
+
169
+ private
170
+
171
+ def bind(val)
172
+ super(@converter ? @converter.call(val) : val)
173
+ end
174
+ }
175
+ end
34
176
  end
35
177
 
36
178
  class Hash
@@ -38,6 +180,8 @@ class Hash
38
180
  end
39
181
 
40
182
  class Object
183
+ using PatternMatch if respond_to?(:using, true)
184
+
41
185
  def assert_pattern(pattern)
42
186
  match(self) do
43
187
  Kernel.eval("with(#{pattern}) { self }", Kernel.binding)
@@ -1,3 +1,3 @@
1
1
  module PatternMatch
2
- VERSION = "0.5.1"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -16,8 +16,10 @@ Gem::Specification.new do |s|
16
16
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f) }
18
18
  s.require_paths = ['lib']
19
+ s.add_development_dependency 'test-unit'
19
20
  s.add_development_dependency 'rake'
20
21
  s.add_development_dependency 'simplecov'
21
22
  s.extra_rdoc_files = ['README.rdoc']
22
23
  s.rdoc_options = ['--main', 'README.rdoc']
24
+ s.licenses = ['2-clause BSDL', "Ruby's"]
23
25
  end
@@ -1,3 +1,11 @@
1
+ require 'test/unit/assertions'
2
+
3
+ module Test::Unit::Assertions
4
+ def pass
5
+ assert(true)
6
+ end
7
+ end
8
+
1
9
  begin
2
10
  if ENV['COVERAGE']
3
11
  require 'simplecov'
@@ -1,7 +1,16 @@
1
1
  require_relative 'helper'
2
- require 'test/unit'
3
- require_relative '../lib/pattern-match'
4
- require_relative '../lib/pattern-match/experimental'
2
+ require 'test-unit'
3
+ if ENV['DISABLE_REFINEMENTS']
4
+ require_relative '../lib/pattern-match/disable_refinements'
5
+ require_relative '../lib/pattern-match'
6
+ else
7
+ require_relative '../lib/pattern-match'
8
+ using PatternMatch
9
+ end
10
+ begin
11
+ require_relative '../lib/pattern-match/experimental'
12
+ rescue LoadError
13
+ end
5
14
 
6
15
  class TestExperimental < Test::Unit::TestCase
7
16
  def test_matcher_attribute_matcher
@@ -58,7 +67,7 @@ class TestExperimental < Test::Unit::TestCase
58
67
  match({a: 0, b: 1}) do
59
68
  with(Hash.(:a, :b, b: b2)) do
60
69
  assert_equal(0, a)
61
- assert_raise(NameError) { b }
70
+ assert_raises(NameError) { b }
62
71
  assert_equal(1, b2)
63
72
  end
64
73
  with(_) { flunk }
@@ -75,18 +84,65 @@ class TestExperimental < Test::Unit::TestCase
75
84
  with(_) { flunk }
76
85
  end
77
86
 
78
- assert_raise(PatternMatch::MalformedPatternError) do
87
+ assert_raises(PatternMatch::MalformedPatternError) do
79
88
  match(0) do
80
89
  with(Object.(a, b)) {}
81
90
  end
82
91
  end
83
92
  end
84
93
 
94
+ def test_matcher_class_set
95
+ match([Set[0, 1, 2], Set[3, 4]]) do
96
+ with(_[Set.(a, b), Set.(c)], guard { a + b * c == 2 } ) do
97
+ assert_equal(2, a)
98
+ assert_equal(0, b)
99
+ assert_equal(3, c)
100
+ end
101
+ end
102
+ end
103
+
85
104
  def test_object_assert_pattern
86
105
  assert_equal([0], [0].assert_pattern('_[Fixnum]'))
87
106
  assert_equal([0], [0].assert_pattern('_[a & Fixnum], guard { a.even? }'))
88
- assert_raise(PatternMatch::NoMatchingPatternError) do
107
+ assert_raises(PatternMatch::NoMatchingPatternError) do
89
108
  [0, 1].assert_pattern('_[Fixnum]')
90
109
  end
91
110
  end
92
- end
111
+
112
+ def test_pattern_variable_converter
113
+ match([0, 1]) do
114
+ with(_[a << :to_s, b << ->(i){ i.to_s }]) do
115
+ assert_equal('0', a)
116
+ assert_equal('1', b)
117
+ end
118
+ with(_) { flunk }
119
+ end
120
+ end
121
+
122
+ def test_quantifier_with_backtracking
123
+ match([[0, 0], [0, 1]]) do
124
+ with(_[_[*a, *_], _[*a, *_]]) do
125
+ assert_equal([0], a)
126
+ end
127
+ with(_) { flunk }
128
+ end
129
+
130
+ match([[0, 1], 2]) do
131
+ with(_[_[*a, *b], c], guard { a.empty? }) do
132
+ assert_equal([], a)
133
+ assert_equal([0, 1], b)
134
+ assert_equal(2, c)
135
+ end
136
+ with(_) { flunk }
137
+ end
138
+ end
139
+
140
+ def test_sequence_with_backtracking
141
+ match([[0, 0, 0], [0, 1, 2]]) do
142
+ with(_[_[Seq(a), ___, *_], _[Seq(a), ___, *_]]) do
143
+ assert_equal([0], a)
144
+ end
145
+ with(_) { flunk }
146
+ end
147
+ end
148
+ end if defined? PatternMatch::AttributeMatcher
@@ -1,6 +1,12 @@
1
1
  require_relative 'helper'
2
- require 'test/unit'
3
- require_relative '../lib/pattern-match'
2
+ require 'test-unit'
3
+ if ENV['DISABLE_REFINEMENTS']
4
+ require_relative '../lib/pattern-match/disable_refinements'
5
+ require_relative '../lib/pattern-match'
6
+ else
7
+ require_relative '../lib/pattern-match'
8
+ using PatternMatch
9
+ end
4
10
 
5
11
  class TestStandard < Test::Unit::TestCase
6
12
  def test_basic
@@ -18,10 +24,10 @@ class TestStandard < Test::Unit::TestCase
18
24
  with(_) { flunk }
19
25
  end
20
26
  assert_equal(4, ret)
21
- assert_raise(NameError) { a }
22
- assert_raise(NameError) { b }
27
+ assert_raises(NameError) { a }
28
+ assert_raises(NameError) { b }
23
29
 
24
- assert_raise(PatternMatch::NoMatchingPatternError) do
30
+ assert_raises(PatternMatch::NoMatchingPatternError) do
25
31
  match(0) do
26
32
  with(1) { flunk }
27
33
  with(2) { flunk }
@@ -39,7 +45,7 @@ class TestStandard < Test::Unit::TestCase
39
45
  with(_) { flunk }
40
46
  end
41
47
 
42
- assert_raise(ArgumentError) do
48
+ assert_raises(ArgumentError) do
43
49
  match(0) do
44
50
  p 1
45
51
  end
@@ -63,21 +69,21 @@ class TestStandard < Test::Unit::TestCase
63
69
  end
64
70
  assert_equal(1, a)
65
71
  assert_equal(2, b)
66
- assert_raise(NameError) { c }
72
+ assert_raises(NameError) { c }
67
73
  end
68
74
  end
69
75
  assert_equal(0, a)
70
- assert_raise(NameError) { b }
71
- assert_raise(NameError) { c }
76
+ assert_raises(NameError) { b }
77
+ assert_raises(NameError) { c }
72
78
  end
73
79
  end
74
- assert_raise(NameError) { a }
75
- assert_raise(NameError) { b }
76
- assert_raise(NameError) { c }
80
+ assert_raises(NameError) { a }
81
+ assert_raises(NameError) { b }
82
+ assert_raises(NameError) { c }
77
83
  end
78
84
 
79
85
  def test_lexical_scoping(rec_call = false, f = nil)
80
- skip 'not supported'
86
+ omit 'not supported'
81
87
  unless rec_call
82
88
  match(0) do
83
89
  with(a) do
@@ -90,7 +96,7 @@ class TestStandard < Test::Unit::TestCase
90
96
  end
91
97
  end
92
98
  else
93
- assert_raise(NameError) { a }
99
+ assert_raises(NameError) { a }
94
100
  assert_equal(0, f.())
95
101
  match(1) do
96
102
  with(a) do
@@ -105,7 +111,7 @@ class TestStandard < Test::Unit::TestCase
105
111
  end
106
112
 
107
113
  def test_override_singleton_method
108
- skip 'Module#prepend not supported' unless Module.respond_to?(:prepend, true)
114
+ omit 'Module#prepend is not defined' unless Module.respond_to?(:prepend, true)
109
115
  match(0) do
110
116
  with(_test_override_singleton_method) do
111
117
  def self._test_override_singleton_method
@@ -119,12 +125,12 @@ class TestStandard < Test::Unit::TestCase
119
125
  def test_uscore
120
126
  match([0, 1, Fixnum]) do
121
127
  with(_[_, ! _(Float), _(Fixnum, :==)]) do
122
- assert_raise(NameError) { _ }
128
+ assert_raises(NameError) { _ }
123
129
  end
124
130
  with(_) { flunk }
125
131
  end
126
132
 
127
- assert_raise(PatternMatch::MalformedPatternError) do
133
+ assert_raises(PatternMatch::MalformedPatternError) do
128
134
  match(0) do
129
135
  with(_(0, :==, nil)) {}
130
136
  end
@@ -228,25 +234,25 @@ class TestStandard < Test::Unit::TestCase
228
234
  with(_) { flunk }
229
235
  end
230
236
 
231
- assert_raise(PatternMatch::MalformedPatternError) do
237
+ assert_raises(PatternMatch::MalformedPatternError) do
232
238
  match(0) do
233
239
  with(___) {}
234
240
  end
235
241
  end
236
242
 
237
- assert_raise(PatternMatch::MalformedPatternError) do
243
+ assert_raises(PatternMatch::MalformedPatternError) do
238
244
  match(0) do
239
245
  with(_[___]) {}
240
246
  end
241
247
  end
242
248
 
243
- assert_raise(PatternMatch::MalformedPatternError) do
249
+ assert_raises(PatternMatch::MalformedPatternError) do
244
250
  match(0) do
245
251
  with(_[_[___]]) {}
246
252
  end
247
253
  end
248
254
 
249
- assert_raise(PatternMatch::MalformedPatternError) do
255
+ assert_raises(PatternMatch::MalformedPatternError) do
250
256
  match(0) do
251
257
  with(_[a, ___, ___]) {}
252
258
  end
@@ -304,6 +310,18 @@ class TestStandard < Test::Unit::TestCase
304
310
  end
305
311
  with(_) { flunk }
306
312
  end
313
+
314
+ match([0, 1]) do
315
+ with(_[a, __0, *_]) do
316
+ assert_equal([0, 1], a)
317
+ end
318
+ end
319
+
320
+ match([0, 1]) do
321
+ with(_[a, __0?, *_]) do
322
+ assert_equal([], a)
323
+ end
324
+ end
307
325
  end
308
326
 
309
327
  def test_sequence
@@ -382,25 +400,25 @@ class TestStandard < Test::Unit::TestCase
382
400
  with(_) { flunk }
383
401
  end
384
402
 
385
- assert_raise(PatternMatch::MalformedPatternError) do
403
+ assert_raises(PatternMatch::MalformedPatternError) do
386
404
  match(0) do
387
405
  with(Seq()) {}
388
406
  end
389
407
  end
390
408
 
391
- assert_raise(PatternMatch::MalformedPatternError) do
409
+ assert_raises(PatternMatch::MalformedPatternError) do
392
410
  match(0) do
393
411
  with(_[Seq()]) {}
394
412
  end
395
413
  end
396
414
 
397
- assert_raise(PatternMatch::MalformedPatternError) do
415
+ assert_raises(PatternMatch::MalformedPatternError) do
398
416
  match([0]) do
399
417
  with(_[a & Seq(0)]) {}
400
418
  end
401
419
  end
402
420
 
403
- assert_raise(NotImplementedError) do
421
+ assert_raises(NotImplementedError) do
404
422
  match([0]) do
405
423
  with(_[Seq(a & Fixnum, ___), ___]) {}
406
424
  end
@@ -448,7 +466,7 @@ class TestStandard < Test::Unit::TestCase
448
466
  with(_) { flunk }
449
467
  end
450
468
 
451
- assert_raise(PatternMatch::MalformedPatternError) do
469
+ assert_raises(PatternMatch::MalformedPatternError) do
452
470
  match(1) do
453
471
  with(a | b) {}
454
472
  end
@@ -459,19 +477,19 @@ class TestStandard < Test::Unit::TestCase
459
477
  with(_) { flunk }
460
478
  end
461
479
 
462
- assert_raise(PatternMatch::MalformedPatternError) do
480
+ assert_raises(PatternMatch::MalformedPatternError) do
463
481
  match(1) do
464
482
  with(! a) {}
465
483
  end
466
484
  end
467
485
 
468
- assert_raise(PatternMatch::MalformedPatternError) do
486
+ assert_raises(PatternMatch::MalformedPatternError) do
469
487
  match(1) do
470
488
  with(a | ___) {}
471
489
  end
472
490
  end
473
491
 
474
- assert_raise(PatternMatch::MalformedPatternError) do
492
+ assert_raises(PatternMatch::MalformedPatternError) do
475
493
  match(1) do
476
494
  with(a & ___) {}
477
495
  end
@@ -492,25 +510,25 @@ class TestStandard < Test::Unit::TestCase
492
510
  with(_) { flunk }
493
511
  end
494
512
 
495
- assert_raise(PatternMatch::MalformedPatternError) do
513
+ assert_raises(PatternMatch::MalformedPatternError) do
496
514
  match(1) do
497
515
  with(And()) {}
498
516
  end
499
517
  end
500
518
 
501
- assert_raise(PatternMatch::MalformedPatternError) do
519
+ assert_raises(PatternMatch::MalformedPatternError) do
502
520
  match(1) do
503
521
  with(Or()) {}
504
522
  end
505
523
  end
506
524
 
507
- assert_raise(PatternMatch::MalformedPatternError) do
525
+ assert_raises(PatternMatch::MalformedPatternError) do
508
526
  match(1) do
509
527
  with(Not()) {}
510
528
  end
511
529
  end
512
530
 
513
- assert_raise(PatternMatch::MalformedPatternError) do
531
+ assert_raises(PatternMatch::MalformedPatternError) do
514
532
  match(1) do
515
533
  with(Not(0, 1)) {}
516
534
  end
@@ -522,14 +540,14 @@ class TestStandard < Test::Unit::TestCase
522
540
  end
523
541
 
524
542
  def test_match_too_many_arguments
525
- assert_raise(ArgumentError) do
543
+ assert_raises(ArgumentError) do
526
544
  match(0, 1) do
527
545
  end
528
546
  end
529
547
  end
530
548
 
531
549
  def test_deconstructor_class
532
- assert_raise(NotImplementedError) do
550
+ assert_raises(NotImplementedError) do
533
551
  c = Class.new
534
552
  match(0) do
535
553
  with(c.(a)) do
@@ -610,4 +628,18 @@ class TestStandard < Test::Unit::TestCase
610
628
  with(_) { flunk }
611
629
  end
612
630
  end
631
+
632
+ def test_refinements
633
+ if ENV['DISABLE_REFINEMENTS']
634
+ assert_kind_of(PatternMatch.const_get(:Pattern), eval('Class.()', TOPLEVEL_BINDING))
635
+ assert_equal(0, eval('match(0) { with(_) { 0 } }', TOPLEVEL_BINDING))
636
+ else
637
+ assert_raises(NoMethodError) do
638
+ eval('Class.()', TOPLEVEL_BINDING)
639
+ end
640
+ assert_raises(NoMethodError) do
641
+ eval('match(0) { with(_) { 0 } }', TOPLEVEL_BINDING)
642
+ end
643
+ end
644
+ end
613
645
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pattern-match
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kazuki Tsujimoto
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-02 00:00:00.000000000 Z
11
+ date: 2015-02-07 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: test-unit
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rake
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -56,6 +70,7 @@ files:
56
70
  - lib/pattern-match.rb
57
71
  - lib/pattern-match/core.rb
58
72
  - lib/pattern-match/deconstructor.rb
73
+ - lib/pattern-match/disable_refinements.rb
59
74
  - lib/pattern-match/experimental.rb
60
75
  - lib/pattern-match/version.rb
61
76
  - pattern-match.gemspec
@@ -63,7 +78,9 @@ files:
63
78
  - test/test_experimental.rb
64
79
  - test/test_standard.rb
65
80
  homepage: https://github.com/k-tsj/pattern-match
66
- licenses: []
81
+ licenses:
82
+ - 2-clause BSDL
83
+ - Ruby's
67
84
  metadata: {}
68
85
  post_install_message:
69
86
  rdoc_options:
@@ -83,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
100
  version: '0'
84
101
  requirements: []
85
102
  rubyforge_project:
86
- rubygems_version: 2.2.0
103
+ rubygems_version: 2.4.5
87
104
  signing_key:
88
105
  specification_version: 4
89
106
  summary: A pattern matching library