patm 2.0.1 → 3.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: 64949366066d3d6c55747fc4de1163fc77624491
4
- data.tar.gz: 84b8d7f389aa5be66d911c42cb8c9d25041657b8
3
+ metadata.gz: 7dfd622177fbff6949aeba6e0ea9a37c503ca13e
4
+ data.tar.gz: 49e7f41b2e40d4fa9724eb607340b6196a41ef0c
5
5
  SHA512:
6
- metadata.gz: 9f4e569ff98ea968a05c736076b01eb8e8d33a2f10a40879e4ec02a5db58c16389dee0c694ca4d2c1dc979de4a46ace0f72d9eba82c9e044012ba12655da5360
7
- data.tar.gz: 098c8031f081a57b2958cde2f9643e7a053d1c9c4871ed449690895e95594a91e0a48a0ff8338c5cb967e63315d80a40cc231ceb4b8c662a1bcd6ec9efc0dfb1
6
+ metadata.gz: 7a0dd73809aadf15e43dab4c93cd857f38bf3bdb0666525f4791f117e415d0123df8dd90d0f66880362669efbaad2bfd3c0ecf82a17c2929c620711e6b773655
7
+ data.tar.gz: 379c2300a12acd9662f5abffa284dc6ffc1854f027d5ca9a8e92e5bafb9a497581054a39998334c965bb21cd4e872035ff7e88471ee00983e4468f47421cc87b
data/.gitignore CHANGED
@@ -1,2 +1,4 @@
1
1
  Gemfile.lock
2
2
  *.gem
3
+ coverage
4
+ coverage.vim
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.2
4
+ - 2.0.0
5
+ - 1.9.3
6
+ - 1.9.2
7
+ script: rspec
data/README.md CHANGED
@@ -1,32 +1,15 @@
1
1
  # PATM: PATtern Matcher for Ruby
2
2
 
3
- ## Usage
4
-
5
- ```ruby
6
- require 'patm'
7
- ```
3
+ [![Build Status](https://travis-ci.org/todesking/patm.svg?branch=master)](https://travis-ci.org/todesking/patm)
8
4
 
9
- ```ruby
10
- # With case(simple but slow)
11
- def match(obj)
12
- p = Patm
13
- _xs = Patm._xs
14
- case obj
15
- when m = Patm.match([:x, p._1, p._2])
16
- [m._2, m._1]
17
- when m = Patm.match([1, _xs&p._1])
18
- m._1
19
- end
20
- end
5
+ PATM is extremely faster pattern match library.
21
6
 
22
- match([1, 2, 3])
23
- # => [2, 3]
7
+ Features: Match value/classes, Capture, Array/Hash decomposition.
24
8
 
25
- match([:x, :y, :z])
26
- # => [:z, :y]
9
+ ## Usage
27
10
 
28
- match([])
29
- # => nil
11
+ ```ruby
12
+ require 'patm'
30
13
  ```
31
14
 
32
15
  ```ruby
@@ -46,6 +29,9 @@ class A
46
29
  _self.match1(m._1)
47
30
  end
48
31
  # ...
32
+ r.else do
33
+ nil
34
+ end
49
35
  end
50
36
  end
51
37
 
@@ -53,6 +39,29 @@ A.new.match1([:x, 1, 2])
53
39
  # => [1, 2]
54
40
  ```
55
41
 
42
+ ```ruby
43
+ # With case(simple but slow)
44
+ def match(obj)
45
+ p = Patm
46
+ _xs = Patm._xs
47
+ case obj
48
+ when m = Patm.match([:x, p._1, p._2])
49
+ [m._2, m._1]
50
+ when m = Patm.match([1, _xs&p._1])
51
+ m._1
52
+ end
53
+ end
54
+
55
+ match([1, 2, 3])
56
+ # => [2, 3]
57
+
58
+ match([:x, :y, :z])
59
+ # => [:z, :y]
60
+
61
+ match([])
62
+ # => nil
63
+ ```
64
+
56
65
  ```ruby
57
66
  # With pre-built Rule
58
67
  rule = Patm::Rule.new do|r|
@@ -76,29 +85,31 @@ rule.apply([])
76
85
  # => nil
77
86
  ```
78
87
 
88
+ ## DSL
89
+
79
90
  ```ruby
80
- # With cached rules
81
- class A
82
- def initialize
83
- @rules = Patm::RuleCache.new
91
+ class PatternMatcher
92
+ extend Patm::DSL
93
+
94
+ define_matcher(:match) do|r| # r is instance of Patm::Rule
95
+ # r.on( PATTERN ) {|match, _self|
96
+ # First argument is instance of Patm::Match. Use it to access captured value.
97
+ # ex. m._1, m._2, ..., m[capture_name]
98
+ #
99
+ # Second argument is instance of the class. Use it to access other methods.
100
+ # ex. _self.other_method
101
+ # }
102
+ #
103
+ # r.else {|value, _self|
104
+ # First argument is the value. Second is instance of the class.
105
+ # }
84
106
  end
107
+ end
85
108
 
86
- def match1(obj)
87
- @rules.match(:match1, obj) do|r|
88
- p = Patm
89
- r.on [:x, p._1, p._2] do|m|
90
- [m._1, m._2]
91
- end
92
- end
93
- end
109
+ matcher = PatternMatcher.new
94
110
 
95
- def match2(obj)
96
- @rules.match(:match2, obj) do|r|
97
- # ...
98
- end
99
- end
100
- end
101
- ```
111
+ matcher.match(1)
112
+ ```
102
113
 
103
114
  ## Patterns
104
115
 
@@ -138,8 +149,52 @@ Captured values are accessible through `Match#_1, _2, ...` and `Match#[capture_n
138
149
  `Patm.or(1, 2)` matches 1 or 2.
139
150
 
140
151
 
152
+ ## Performance
153
+
154
+ see [benchmark code](./benchmark/comparison.rb) for details
155
+
156
+ Machine: MacBook Air(Late 2010) C2D 1.8GHz, OS X 10.9.2
157
+
158
+ ```
159
+ RUBY_VERSION: 2.1.2 p95
160
+
161
+ Benchmark: Empty(x10000)
162
+ user system total real
163
+ manual 0.010000 0.000000 0.010000 ( 0.012252)
164
+ patm 0.060000 0.000000 0.060000 ( 0.057050)
165
+ pattern_match 1.710000 0.010000 1.720000 ( 1.765749)
166
+
167
+ Benchmark: SimpleConst(x10000)
168
+ user system total real
169
+ manual 0.020000 0.000000 0.020000 ( 0.018274)
170
+ patm 0.060000 0.000000 0.060000 ( 0.075068)
171
+ patm_case 0.160000 0.000000 0.160000 ( 0.161002)
172
+ pattern_match 1.960000 0.020000 1.980000 ( 2.007936)
173
+
174
+ Benchmark: ArrayDecomposition(x10000)
175
+ user system total real
176
+ manual 0.050000 0.000000 0.050000 ( 0.047948)
177
+ patm 0.250000 0.000000 0.250000 ( 0.254039)
178
+ patm_case 1.710000 0.000000 1.710000 ( 1.765656)
179
+ pattern_match 12.890000 0.060000 12.950000 ( 13.343334)
180
+
181
+ Benchmark: VarArray(x10000)
182
+ user system total real
183
+ manual 0.050000 0.000000 0.050000 ( 0.052425)
184
+ patm 0.210000 0.000000 0.210000 ( 0.223190)
185
+ patm_case 1.440000 0.000000 1.440000 ( 1.587535)
186
+ pattern_match 10.050000 0.070000 10.120000 ( 10.898683)
187
+ ```
188
+
189
+
141
190
  ## Changes
142
191
 
192
+ ### 3.0.0
193
+
194
+ - If given value can't match any pattern and no `else`, `Patm::NoMatchError` raised(Instead of return nil).
195
+ - RuleCache is now obsoleted. Use DSL.
196
+ - More optimized compiler
197
+
143
198
  ### 2.0.1
144
199
 
145
200
  - Bugfix: About pattern `Patm._1 & Array`.
@@ -53,8 +53,8 @@ end
53
53
  end
54
54
  end
55
55
 
56
- @rule = P::Rule.new(false, &@ruledef)
57
- @compiled_rule = P::Rule.new(true, &@ruledef)
56
+ @rule = P::Rule.new(&@ruledef)
57
+ @compiled_rule = P::Rule.new(&@ruledef).compile
58
58
 
59
59
  def match_with_rule(obj)
60
60
  @rule.apply(obj)
@@ -0,0 +1,209 @@
1
+ require 'benchmark'
2
+
3
+ def benchmark(klass, n)
4
+ puts "Benchmark: #{klass}(x#{n})"
5
+
6
+ target_methods = klass.instance_methods - Object.instance_methods - [:test_values]
7
+ validate(klass, target_methods)
8
+
9
+ Benchmark.bm('pattern-match'.size) do|b|
10
+ obj = klass.new
11
+ test_values = obj.test_values
12
+ target_methods.each do|method_name|
13
+ b.report(method_name) do
14
+ m = obj.method(method_name)
15
+ n.times { test_values.each {|val| m.call(val) } }
16
+ end
17
+ end
18
+ end
19
+ puts
20
+ end
21
+
22
+ def validate(klass, target_methods)
23
+ obj = klass.new
24
+ obj.test_values.each do|val|
25
+ results = target_methods.map {|name| [name, obj.public_send(name, val)] }
26
+ unless results.each_cons(2).all?{|(_, a), (_, b)| a == b }
27
+ raise "ERROR: Result not match. val=#{val.inspect}, #{results.map{|n,r| "#{n}=#{r.inspect}"}.join(', ')}"
28
+ end
29
+ end
30
+ end
31
+
32
+ load File.join(File.dirname(__FILE__), '../lib/patm.rb')
33
+ require 'pattern-match'
34
+
35
+ class Empty
36
+ extend Patm::DSL
37
+
38
+ def manual(obj)
39
+ nil
40
+ end
41
+
42
+ define_matcher :patm do|r|
43
+ r.else { nil }
44
+ end
45
+
46
+ def pattern_match(obj)
47
+ match(obj) do
48
+ with(_) { nil }
49
+ end
50
+ end
51
+
52
+ def test_values
53
+ [1, 2, 3]
54
+ end
55
+ end
56
+
57
+ class SimpleConst
58
+ extend Patm::DSL
59
+
60
+ def manual(obj)
61
+ if obj == 1
62
+ 100
63
+ elsif obj == 2
64
+ 200
65
+ else
66
+ 300
67
+ end
68
+ end
69
+
70
+ define_matcher :patm do|r|
71
+ r.on(1) { 100 }
72
+ r.on(2) { 200 }
73
+ r.else { 300 }
74
+ end
75
+
76
+ def patm_case(obj)
77
+ case obj
78
+ when m = Patm.match(1)
79
+ 100
80
+ when m = Patm.match(2)
81
+ 200
82
+ else
83
+ 300
84
+ end
85
+ end
86
+
87
+ def pattern_match(obj)
88
+ match(obj) do
89
+ with(1) { 100 }
90
+ with(2) { 200 }
91
+ with(_) { 300 }
92
+ end
93
+ end
94
+
95
+ def test_values
96
+ [1, 2, 3]
97
+ end
98
+ end
99
+
100
+ class ArrayDecomposition
101
+ extend Patm::DSL
102
+
103
+ def manual(obj)
104
+ return 100 unless obj
105
+ return nil unless obj.is_a?(Array)
106
+ return nil if obj.size != 3
107
+ return nil unless obj[0] == 1
108
+
109
+ if obj[2] == 2
110
+ obj[1]
111
+ else
112
+ [obj[1], obj[2]]
113
+ end
114
+ end
115
+
116
+ define_matcher :patm do|r|
117
+ _1, _2 = Patm._1, Patm._2
118
+ r.on([1, _1, 2]) {|m| m._1 }
119
+ r.on([1, _1, _2]) {|m| [m._1, m._2] }
120
+ r.on(nil) { 100 }
121
+ r.else { nil }
122
+ end
123
+
124
+ def patm_case(obj)
125
+ _1, _2 = Patm._1, Patm._2
126
+ case obj
127
+ when m = Patm.match([1, _1, 2])
128
+ m._1
129
+ when m = Patm.match([1, _1, _2])
130
+ [m._1, m._2]
131
+ when m = Patm.match(nil)
132
+ 100
133
+ else
134
+ nil
135
+ end
136
+ end
137
+
138
+ def pattern_match(obj)
139
+ match(obj) do
140
+ with(_[1, _1, 2]) { _1 }
141
+ with(_[1, _1, _2]) { [_1, _2] }
142
+ with(nil) { 100 }
143
+ with(_) { nil }
144
+ end
145
+ end
146
+
147
+ def test_values
148
+ [
149
+ [],
150
+ [1, 9, 2],
151
+ [1, 9, 3],
152
+ [1, 9, 1],
153
+ [1],
154
+ "foo",
155
+ nil
156
+ ]
157
+ end
158
+ end
159
+
160
+ class VarArray
161
+ extend Patm::DSL
162
+
163
+ def manual(obj)
164
+ return nil unless obj.is_a?(Array) && obj.size >= 2 && obj[0] == 1 && obj[1] == 2
165
+ return obj[2..-1]
166
+ end
167
+
168
+ define_matcher :patm do|r|
169
+ r.on([1, 2, Patm._xs[1]]) {|m| m[1] }
170
+ r.else { nil }
171
+ end
172
+
173
+ def patm_case(obj)
174
+ case obj
175
+ when m = Patm.match([1, 2, Patm._xs[1]])
176
+ m._1
177
+ else
178
+ nil
179
+ end
180
+ end
181
+
182
+ def pattern_match(obj)
183
+ match(obj) do
184
+ with(_[1, 2, *_1]) { _1 }
185
+ with(_) { nil }
186
+ end
187
+ end
188
+
189
+ def test_values
190
+ [
191
+ nil,
192
+ 100,
193
+ [],
194
+ [1, 2],
195
+ [1, 2, 3],
196
+ [1, 2, 3, 4],
197
+ [1, 10, 100],
198
+ ]
199
+ end
200
+ end
201
+
202
+
203
+ puts "RUBY_VERSION: #{RUBY_VERSION} p#{RUBY_PATCHLEVEL}"
204
+ puts
205
+
206
+ benchmark Empty, 10000
207
+ benchmark SimpleConst, 10000
208
+ benchmark ArrayDecomposition, 10000
209
+ benchmark VarArray, 10000
data/lib/patm.rb CHANGED
@@ -1,4 +1,11 @@
1
1
  module Patm
2
+ class NoMatchError < StandardError
3
+ def initialize(value)
4
+ super("Pattern not match: value=#{value.inspect}")
5
+ @value = value
6
+ end
7
+ attr_reader :value
8
+ end
2
9
  class Pattern
3
10
 
4
11
  def self.build_from(plain)
@@ -30,7 +37,7 @@ module Patm
30
37
 
31
38
  module Util
32
39
  def self.compile_value(value, free_index)
33
- if value.is_a?(Numeric) || value.is_a?(String) || value.is_a?(Symbol)
40
+ if value.nil? || value.is_a?(Numeric) || value.is_a?(String) || value.is_a?(Symbol)
34
41
  [
35
42
  value.inspect,
36
43
  [],
@@ -90,6 +97,7 @@ module Patm
90
97
  @desc = desc
91
98
  @context = context
92
99
  singleton_class = class <<self; self; end
100
+ @src_body = src
93
101
  @src = <<-RUBY
94
102
  def execute(_match, _obj)
95
103
  _ctx = @context
@@ -99,10 +107,11 @@ module Patm
99
107
  singleton_class.class_eval(@src)
100
108
  end
101
109
 
110
+ attr_reader :src_body
102
111
  attr_reader :src
103
112
 
104
113
  def compile_internal(free_index, target_name = "_obj")
105
- raise "Already compiled"
114
+ raise "already compiled"
106
115
  end
107
116
  def inspect; "<compiled>#{@desc}"; end
108
117
  end
@@ -227,16 +236,22 @@ module Patm
227
236
 
228
237
  elm_target_name = "#{target_name}_elm"
229
238
  @head.each_with_index do|h, hi|
230
- s, c, i = h.compile_internal(i, elm_target_name)
231
- srcs << "(#{elm_target_name} = #{target_name}[#{hi}]; #{s})" if s
232
- ctxs << c
239
+ if h.is_a?(Obj)
240
+ s, c, i = h.compile_internal(i, "#{target_name}[#{hi}]")
241
+ srcs << "#{s}" if s
242
+ ctxs << c
243
+ else
244
+ s, c, i = h.compile_internal(i, elm_target_name)
245
+ srcs << "#{elm_target_name} = #{target_name}[#{hi}]; #{s}" if s
246
+ ctxs << c
247
+ end
233
248
  end
234
249
 
235
250
  unless @tail.empty?
236
- srcs << "(#{target_name}_t = #{target_name}[(-#{@tail.size})..-1]; true)"
251
+ srcs << "#{target_name}_t = #{target_name}[(-#{@tail.size})..-1]; true"
237
252
  @tail.each_with_index do|t, ti|
238
253
  s, c, i = t.compile_internal(i, elm_target_name)
239
- srcs << "(#{elm_target_name} = #{target_name}_t[#{ti}]; #{s})" if s
254
+ srcs << "#{elm_target_name} = #{target_name}_t[#{ti}]; #{s}" if s
240
255
  ctxs << c
241
256
  end
242
257
  end
@@ -244,7 +259,7 @@ module Patm
244
259
  if @rest
245
260
  tname = "#{target_name}_r"
246
261
  s, c, i = @rest.compile_internal(i, tname)
247
- srcs << "(#{tname} = #{target_name}[#{@head.size}..-(#{@tail.size+1})];#{s})" if s
262
+ srcs << "#{tname} = #{target_name}[#{@head.size}..-(#{@tail.size+1})];#{s}" if s
248
263
  ctxs << c
249
264
  end
250
265
 
@@ -434,20 +449,15 @@ module Patm
434
449
  end
435
450
 
436
451
  class Rule
437
- def initialize(compile = true, &block)
438
- @compile = compile
439
- # { Pattern => Proc }
452
+ def initialize(&block)
453
+ # [[Pattern, Proc]...]
440
454
  @rules = []
441
- @else = ->(obj){nil}
455
+ @else = nil
442
456
  block[self]
443
457
  end
444
458
 
445
459
  def on(pat, &block)
446
- if @compile
447
- @rules << [Pattern.build_from(pat).compile, block]
448
- else
449
- @rules << [Pattern.build_from(pat), block]
450
- end
460
+ @rules << [Pattern.build_from(pat), block]
451
461
  end
452
462
 
453
463
  def else(&block)
@@ -461,36 +471,82 @@ module Patm
461
471
  return block.call(match, _self)
462
472
  end
463
473
  end
464
- @else[obj, _self]
474
+ @else ? @else[obj, _self] : (raise NoMatchError.new(obj))
475
+ end
476
+
477
+ def inspect
478
+ "Rule{#{@rules.map(&:first).map(&:inspect).join(', ')}#{@else ? ', _' : ''}}"
465
479
  end
466
- end
467
480
 
468
- class RuleCache
469
- def initialize(compile = true)
470
- @compile = compile
471
- @rules = {}
481
+ def compile_call(block, *args)
482
+ "call(#{args[0...block.arity].join(', ')})"
483
+ end
484
+
485
+ def compile
486
+ i = 0
487
+ ctxs = []
488
+ srcs = []
489
+ @rules.each do|pat, block|
490
+ s, c, i = pat.compile_internal(i, '_obj')
491
+ ctxs << c
492
+ ctxs << [block]
493
+ srcs << "if (#{s || 'true'})\n_ctx[#{i}].#{compile_call(block, "::Patm::Match.new(_match)"," _self")}"
494
+ i += 1
495
+ end
496
+ src = srcs.join("\nels")
497
+ if @else
498
+ src << "\nelse\n" unless srcs.empty?
499
+ src << "_ctx[#{i}].#{compile_call(@else, "_obj"," _self")}"
500
+ ctxs << [@else]
501
+ i += 1
502
+ else
503
+ src << "\nelse\n" unless srcs.empty?
504
+ src << "raise ::Patm::NoMatchError.new(_obj)"
505
+ end
506
+ src << "\nend" unless srcs.empty?
507
+ Compiled.new(
508
+ src,
509
+ ctxs.flatten(1)
510
+ )
472
511
  end
473
- def match(rule_name, obj, _self = nil, &rule)
474
- (@rules[rule_name] ||= ::Patm::Rule.new(@compile, &rule)).apply(obj, _self)
512
+
513
+ class Compiled
514
+ def initialize(src_body, context)
515
+ @src_body = src_body
516
+ @context = context
517
+ @src = <<-RUBY
518
+ def apply(_obj, _self = nil)
519
+ _ctx = @context
520
+ _match = {}
521
+ #{@src_body}
522
+ end
523
+ RUBY
524
+
525
+ singleton_class = class <<self; self; end
526
+ singleton_class.class_eval(@src)
527
+ end
528
+
529
+ attr_reader :src_body
530
+ attr_reader :context
475
531
  end
476
532
  end
477
533
 
478
534
  class Match
479
- def initialize
480
- @group = {}
535
+ def initialize(data = {})
536
+ @data = data
481
537
  end
482
538
 
483
539
  def [](i)
484
- @group[i]
540
+ @data[i]
485
541
  end
486
542
 
487
543
  def []=(i, val)
488
- @group[i] = val
544
+ @data[i] = val
489
545
  end
490
546
 
491
547
  PREDEF_GROUP_SIZE.times.each do|i|
492
548
  define_method "_#{i}" do
493
- self[i]
549
+ @data[i]
494
550
  end
495
551
  end
496
552
  end
@@ -515,11 +571,18 @@ module Patm
515
571
 
516
572
  module DSL
517
573
  def define_matcher(name, &rule)
518
- @patm_rules ||= RuleCache.new
519
- rules = @patm_rules
520
- define_method name do|obj|
521
- rules.match(name, obj, self, &rule)
522
- end
574
+ rule = Rule.new(&rule).compile
575
+ ctx = rule.context
576
+ self.class_variable_set("@@_patm_ctx_#{name}", ctx)
577
+ src = <<-RUBY
578
+ def #{name}(_obj)
579
+ _self = self
580
+ _ctx = self.#{self.name ? 'class' : 'singleton_class'}.class_variable_get(:@@_patm_ctx_#{name})
581
+ _match = {}
582
+ #{rule.src_body}
583
+ end
584
+ RUBY
585
+ class_eval(src)
523
586
  end
524
587
  end
525
588
  end
data/patm.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.platform = Gem::Platform::RUBY
3
3
  s.name = 'patm'
4
- s.version = '2.0.1'
4
+ s.version = '3.0.0'
5
5
  s.summary = 'PATtern Matching library'
6
6
  s.description = 'Pattern matching library for plain data structure'
7
7
  s.required_ruby_version = '>= 1.9.0'
@@ -16,6 +16,9 @@ Gem::Specification.new do |s|
16
16
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
17
17
  s.require_paths = ["lib"]
18
18
 
19
+ s.add_development_dependency('simplecov', '~> 0.7.1')
20
+ s.add_development_dependency('simplecov-vim')
19
21
  s.add_development_dependency('rspec', '~>2.14')
20
22
  s.add_development_dependency('pry', '~>0.9')
23
+ s.add_development_dependency('pattern-match', '=0.5.1') # for benchmarking
21
24
  end
data/spec/patm_spec.rb CHANGED
@@ -1,43 +1,64 @@
1
+ require 'simplecov'
2
+ require 'simplecov-vim/formatter'
3
+ SimpleCov.start do
4
+ formatter SimpleCov::Formatter::VimFormatter
5
+ end
6
+
1
7
  require File.join(File.dirname(__FILE__), '..', 'lib', 'patm.rb')
2
8
  require 'pry'
3
9
 
4
10
  module PatmHelper
5
- extend RSpec::Matchers::DSL
11
+ module Pattern
12
+ extend RSpec::Matchers::DSL
6
13
 
7
- matcher :these_matches do|*matches|
8
- match do|actual|
9
- matches.all?{|m| m.matches?(actual) }
14
+ matcher :these_matches do|*matches|
15
+ match do|actual|
16
+ matches.all?{|m| m.matches?(actual) }
17
+ end
10
18
  end
11
- end
12
19
 
13
- matcher :match_to do|expected|
14
- match do|actual|
15
- exec(actual, expected)
16
- end
20
+ matcher :match_to do|expected|
21
+ match do|actual|
22
+ exec(actual, expected)
23
+ end
17
24
 
18
- def exec(actual, expected)
19
- @match = Patm::Match.new
20
- actual.execute(@match, expected)
21
- end
25
+ def exec(actual, expected)
26
+ @match = Patm::Match.new
27
+ actual.execute(@match, expected)
28
+ end
29
+
30
+ def match; @match; end
22
31
 
23
- def match; @match; end
32
+ def and_capture(g1, g2 = nil, g3 = nil, g4 = nil)
33
+ these_matches(
34
+ self, _capture(self, {1 => g1, 2 => g2, 3 => g3, 4 => g4})
35
+ )
36
+ end
24
37
 
25
- def and_capture(g1, g2 = nil, g3 = nil, g4 = nil)
26
- these_matches(
27
- self, _capture(self, {1 => g1, 2 => g2, 3 => g3, 4 => g4})
28
- )
38
+ def and_named_capture(capture)
39
+ these_matches(
40
+ self, _capture(self, capture)
41
+ )
42
+ end
29
43
  end
30
44
 
31
- def and_named_capture(capture)
32
- these_matches(
33
- self, _capture(self, capture)
34
- )
45
+ matcher :_capture do|m, capture|
46
+ match do|_|
47
+ [m.match[1], m.match[2], m.match[3], m.match[4]] == capture.values_at(1,2,3,4)
48
+ end
35
49
  end
36
50
  end
37
51
 
38
- matcher :_capture do|m, capture|
39
- match do|_|
40
- [m.match[1], m.match[2], m.match[3], m.match[4]] == capture.values_at(1,2,3,4)
52
+ module Rule
53
+ extend RSpec::Matchers::DSL
54
+ matcher :converts do|value, result|
55
+ match do|rule|
56
+ @matched = rule.apply(value, nil)
57
+ @matched == result
58
+ end
59
+ failure_message_for_should do|rule|
60
+ "match #{value.inspect} to #{rule.inspect}: expected #{result} but #{@matched.inspect}"
61
+ end
41
62
  end
42
63
  end
43
64
 
@@ -57,7 +78,7 @@ describe "Usage:" do
57
78
 
58
79
  it 'with predefined Rule' do
59
80
  p = Patm
60
- r = p::Rule.new(false) do|r|
81
+ r = p::Rule.new do|r|
61
82
  r.on [1, p._1, p._2] do|m|
62
83
  [m._1, m._2]
63
84
  end
@@ -68,43 +89,13 @@ describe "Usage:" do
68
89
 
69
90
  it 'with predefined Rule(compiled)' do
70
91
  p = Patm
71
- r = p::Rule.new(true) do|r|
72
- r.on [1, p._1, p._2] do|m|
73
- [m._1, m._2]
74
- end
75
- r.else {|obj| [] }
76
- end
77
- r.apply([1, 2, 3]).should == [2, 3]
78
- end
79
-
80
- it 'with RuleCache' do
81
- p = Patm
82
- rs = p::RuleCache.new
83
-
84
- rs.match(:pattern_1, [1, 2, 3]) do|r|
85
- r.on [1, p._1, p._2] do|m|
86
- [m._1, m._2]
87
- end
88
- r.else {|obj| [] }
89
- end
90
- .should == [2, 3]
91
-
92
- rs.match(:pattern_1, [1, 3, 5]) {|r| fail "should not reach here" }.should == [3, 5]
93
- end
94
-
95
- it 'with RuleCache(compiled)' do
96
- p = Patm
97
- rs = p::RuleCache.new(true)
98
-
99
- rs.match(:pattern_1, [1, 2, 3]) do|r|
92
+ r = p::Rule.new do|r|
100
93
  r.on [1, p._1, p._2] do|m|
101
94
  [m._1, m._2]
102
95
  end
103
96
  r.else {|obj| [] }
104
97
  end
105
- .should == [2, 3]
106
-
107
- rs.match(:pattern_1, [1, 3, 5]) {|r| fail "should not reach here" }.should == [3, 5]
98
+ r.compile.apply([1, 2, 3]).should == [2, 3]
108
99
  end
109
100
 
110
101
  it 'with DSL' do
@@ -139,8 +130,55 @@ describe "Usage:" do
139
130
  end
140
131
  end
141
132
 
133
+ describe Patm::Rule do
134
+ include PatmHelper::Rule
135
+ def self.rule(name, definition, &block)
136
+ [[false, "#{name}"], [true, "#{name}(compiled)"]].each do|compile, name|
137
+ describe name do
138
+ subject { Patm::Rule.new(&definition).tap{|r| break r.compile if compile } }
139
+ self.instance_eval(&block)
140
+ end
141
+ end
142
+ end
143
+
144
+ rule(:rule1, ->(r){
145
+ r.on([1, Patm._1, Patm._2]) {|m| [m._1, m._2] }
146
+ r.else { [] }
147
+ }) do
148
+ it { should converts([1, 2, 3], [2, 3]) }
149
+ it { should converts([1], []) }
150
+ end
151
+
152
+ rule(:rule2, ->(r) {
153
+ r.on(1) { 100 }
154
+ }) do
155
+ it { should converts(1, 100) }
156
+ it { expect { subject.apply(nil) }.to raise_error(Patm::NoMatchError) }
157
+ end
158
+
159
+ context 'regression' do
160
+ rule(:reg1, ->(r){
161
+ _1, _2 = Patm._1, Patm._2
162
+ r.on([1, _1, 2]) {|m| m._1 }
163
+ r.on([1, _1, _2]) {|m| [m._1, m._2] }
164
+ r.on(nil) { 100 }
165
+ r.else { nil }
166
+ }) do
167
+ it { should converts([1, 2, 2], 2) }
168
+ it { should converts([1, 2, 3], [2, 3]) }
169
+ it { should converts(nil, 100) }
170
+ it { should converts([], nil) }
171
+ end
172
+
173
+ rule(:reg2, ->(r) { r.else { nil }}) do
174
+ it { should converts(1, nil) }
175
+ it { should converts("hoge", nil) }
176
+ end
177
+ end
178
+ end
179
+
142
180
  describe Patm::Pattern do
143
- include PatmHelper
181
+ include PatmHelper::Pattern
144
182
  def self.pattern(plain, &b)
145
183
  context "pattern '#{plain.inspect}'" do
146
184
  subject { Patm::Pattern.build_from(plain) }
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: patm
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - todesking
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-12 00:00:00.000000000 Z
11
+ date: 2014-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: simplecov
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 0.7.1
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 0.7.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: simplecov-vim
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: rspec
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +66,20 @@ dependencies:
38
66
  - - ~>
39
67
  - !ruby/object:Gem::Version
40
68
  version: '0.9'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pattern-match
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '='
74
+ - !ruby/object:Gem::Version
75
+ version: 0.5.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '='
81
+ - !ruby/object:Gem::Version
82
+ version: 0.5.1
41
83
  description: Pattern matching library for plain data structure
42
84
  email: discommucative@gmail.com
43
85
  executables: []
@@ -45,10 +87,12 @@ extensions: []
45
87
  extra_rdoc_files: []
46
88
  files:
47
89
  - .gitignore
90
+ - .travis.yml
48
91
  - Gemfile
49
92
  - LICENSE
50
93
  - README.md
51
94
  - benchmark/benchmark.rb
95
+ - benchmark/comparison.rb
52
96
  - lib/patm.rb
53
97
  - patm.gemspec
54
98
  - spec/patm_spec.rb