patm 2.0.1 → 3.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: 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