pattern-match 0.5.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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