patm 0.0.1 → 0.1.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: 1adf094454c70d6f4147b4c44e134a0204274e6c
4
- data.tar.gz: 93e09c5195a23d9dfd2ac2e317841c4a6cb80afc
3
+ metadata.gz: a6f5f8bcb3f5f3031bac6f117418b1d3bc917e8a
4
+ data.tar.gz: 56f203d9076b6905894ffb752954c7caa41fcca6
5
5
  SHA512:
6
- metadata.gz: 80ada62d20ae8926cf97f27c120e70896e3f0d916e38058221afa60967b5a6701cb2ca8d1dd35c8807d104f616bddc62bd032f64e27857d67c2821e5a7aae2a9
7
- data.tar.gz: 824f33ef73d6561932e620ca7e657c74ad2767301b32ede0c47677fc4a50010277a603e419b17967273d86320288e9a08788c47595ed3179f332c6d9cddeb09c
6
+ metadata.gz: 7f01fa35d647fea7658029071682a77dea11e7da92968087c383028f13476ba7a7745a7f04093457c1a45beb92bc1328fab4c9feb464f55550ef3df08b38bb40
7
+ data.tar.gz: 9ac471509e902916d52693fc7fe0bac69fb84106b8a539bcbd1f6a6657c34150e6214e73d0555b28c32ac6038bbdfaa7f40073160327ac024a9be16c2d7d1bdd
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
1
  Gemfile.lock
2
+ *.gem
data/README.md CHANGED
@@ -79,6 +79,11 @@ end
79
79
 
80
80
  ## Changes
81
81
 
82
+ ### 0.1.0
83
+
84
+ - Faster matching with pattern compilation
85
+ - Fix StackOverflow bug for `[Patm.or()]`
86
+
82
87
  ### 0.0.1
83
88
 
84
89
  - Initial release
@@ -0,0 +1,99 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'patm.rb')
2
+ require 'benchmark'
3
+
4
+ P = Patm
5
+
6
+ def match_with_case(obj)
7
+ _1 = P._1
8
+ _2 = P._2
9
+ _3 = P._3
10
+ _4 = P._4
11
+ _xs = P::ARRAY_REST
12
+
13
+ case obj
14
+ when m = P.match([:assign, [:var_field, [:@ident, _1, [_2, _3]]], _4])
15
+ :as
16
+ when m = P.match([:massign, _1, _2])
17
+ :mas
18
+ when m = P.match([:var_ref, [:@ident, _1, [_2, _3]]])
19
+ :vr
20
+ when m = P.match([:params, _1, _2, _3, _xs])
21
+ :par
22
+ when m = P.match(P.or(nil, true, false, Numeric, String, Symbol, []))
23
+ :ignore
24
+ else
25
+ :else
26
+ end
27
+ end
28
+
29
+ @ruledef = lambda do|r|
30
+ _1 = P._1
31
+ _2 = P._2
32
+ _3 = P._3
33
+ _4 = P._4
34
+ _xs = P::ARRAY_REST
35
+
36
+ r.on [:assign, [:var_field, [:@ident, _1, [_2, _3]]], _4] do|m|
37
+ :as
38
+ end
39
+ r.on [:massign, _1, _2] do|m|
40
+ :mas
41
+ end
42
+ r.on [:var_ref, [:@ident, _1, [_2, _3]]] do|m|
43
+ :vr
44
+ end
45
+ r.on [:params, _1, _2, _3, _xs] do|m|
46
+ :par
47
+ end
48
+ r.on P.or(nil, true, false, Numeric, String, Symbol, []) do|m|
49
+ :ignore
50
+ end
51
+ r.else do|obj|
52
+ :else
53
+ end
54
+ end
55
+
56
+ @rule = P::Rule.new(&@ruledef)
57
+ @compiled_rule = P::Rule.new(true, &@ruledef)
58
+
59
+ def match_with_rule(obj)
60
+ @rule.apply(obj)
61
+ end
62
+
63
+ def match_with_compiled_rule(obj)
64
+ @compiled_rule.apply(obj)
65
+ end
66
+
67
+
68
+ VALUES = [
69
+ [ [:assign, [:var_field, [:@ident, 10, [20, 30]]], false], :as ],
70
+ [ [:massign, 1, 2], :mas],
71
+ [ [:var_ref, [:@ident, "x", [1, 2]]], :vr ],
72
+ [ [:params, 10, 20, 30, 40, 50, 60, 70, 80], :par ],
73
+ [ nil, :ignore ],
74
+ [ 100, :ignore ],
75
+ [ [1,2,3], :else],
76
+ ]
77
+
78
+
79
+ def bm(&b)
80
+ 10000.times do
81
+ VALUES.each do|obj, expected|
82
+ actual = b.call obj
83
+ raise "e:#{expected.inspect} a:#{actual.inspect}" unless actual == expected
84
+ end
85
+ end
86
+ end
87
+
88
+ Benchmark.bm(15) do|b|
89
+ b.report("case-when") { bm {|obj| match_with_case(obj) } }
90
+ b.report("rule") { bm {|obj| match_with_rule(obj) } }
91
+ b.report("compiled-rule") { bm {|obj| match_with_compiled_rule(obj) } }
92
+ end
93
+
94
+
95
+ # [:assign, [:var_field, [:@ident, _1, [_2, _3]]], _4] do|m|
96
+ # [:massign, _1, _2] do|m|
97
+ # [:var_ref, [:@ident, _1, [_2, _3]]] do|m|
98
+ # [:params, _1, _2, _3, _xs] do|m|
99
+ # p.or(nil, true, false, Numeric, String, Symbol, []) do|m|
data/lib/patm.rb CHANGED
@@ -11,20 +11,67 @@ module Patm
11
11
  when Pattern
12
12
  plain
13
13
  when Array
14
- if plain.last.is_a?(Pattern) && plain.last.rest?
15
- Arr.new(plain[0..-2].map{|p| build_from(p) }, plain.last)
16
- else
17
- Arr.new(plain.map{|p| build_from(p) })
18
- end
14
+ build_from_array(plain)
19
15
  else
20
16
  Obj.new(plain)
21
17
  end
22
18
  end
23
19
 
20
+ def self.build_from_array(array)
21
+ array = array.map{|a| build_from(a)}
22
+ rest_index = array.index(&:rest?)
23
+ if rest_index
24
+ head = array[0...rest_index]
25
+ rest = array[rest_index]
26
+ tail = array[(rest_index+1)..-1]
27
+ Arr.new(head, rest, tail)
28
+ else
29
+ Arr.new(array)
30
+ end
31
+ end
32
+
24
33
  def &(rhs)
25
34
  And.new([self, rhs])
26
35
  end
27
36
 
37
+ def compile
38
+ src, context, _ = self.compile_internal(0)
39
+
40
+ Compiled.new(self.inspect, src, context)
41
+ end
42
+
43
+ # free_index:Numeric -> [src, context, free_index]
44
+ # variables: _ctx, _match, _obj
45
+ def compile_internal(free_index, target_name = "_obj")
46
+ [
47
+ "_ctx[#{free_index}].execute(_match, #{target_name})",
48
+ [self],
49
+ free_index + 1
50
+ ]
51
+ end
52
+
53
+ class Compiled < self
54
+ def initialize(desc, src, context)
55
+ @desc = desc
56
+ @context = context
57
+ singleton_class = class <<self; self; end
58
+ @src = <<-RUBY
59
+ def execute(_match, _obj)
60
+ _ctx = @context
61
+ #{src}
62
+ end
63
+ RUBY
64
+ singleton_class.class_eval(@src)
65
+ end
66
+
67
+ attr_reader :src
68
+
69
+ def compile_internal(free_index, target_name = "_obj")
70
+ raise "Already compiled"
71
+ end
72
+ def inspect; "<compiled>#{@desc}"; end
73
+ end
74
+
28
75
  class Arr < self
29
76
  def initialize(head, rest = nil, tail = [])
30
77
  @head = head
@@ -45,7 +92,55 @@ module Patm
45
92
  (!@rest || @rest.execute(mmatch, obj[@head.size..-(@tail.size+1)]))
46
93
  end
47
94
 
48
- def inspect; (@head + [@rest] + @tail).inspect; end
95
+ def inspect
96
+ if @rest
97
+ (@head + [@rest] + @tail).inspect
98
+ else
99
+ (@head + @tail).inspect
100
+ end
101
+ end
102
+
103
+ def compile_internal(free_index, target_name = "_obj")
104
+ i = free_index
105
+ srcs = []
106
+ ctxs = []
107
+
108
+ srcs << "#{target_name}.is_a?(::Array)"
109
+
110
+ size_min = @head.size + @tail.size
111
+ if @rest
112
+ srcs << "#{target_name}.size >= #{size_min}"
113
+ else
114
+ srcs << "#{target_name}.size == #{size_min}"
115
+ end
116
+
117
+ elm_target_name = "#{target_name}_elm"
118
+ @head.each_with_index do|h, hi|
119
+ s, c, i = h.compile_internal(i, elm_target_name)
120
+ srcs << "(#{elm_target_name} = #{target_name}[#{hi}]; #{s})"
121
+ ctxs << c
122
+ end
123
+
124
+ srcs << "(#{target_name}_t = #{target_name}[(-#{@tail.size})..-1]; true)"
125
+ @tail.each_with_index do|t, ti|
126
+ s, c, i = t.compile_internal(i, elm_target_name)
127
+ srcs << "(#{elm_target_name} = #{target_name}_t[#{ti}]; #{s})"
128
+ ctxs << c
129
+ end
130
+
131
+ if @rest
132
+ tname = "#{target_name}_r"
133
+ s, c, i = @rest.compile_internal(i, tname)
134
+ srcs << "(#{tname} = #{target_name}[#{@head.size}..-(#{@tail.size+1})];#{s})"
135
+ ctxs << c
136
+ end
137
+
138
+ [
139
+ srcs.map{|s| "(#{s})"}.join(" &&\n"),
140
+ ctxs.flatten(1),
141
+ i
142
+ ]
143
+ end
49
144
  end
50
145
 
51
146
  class ArrRest < self
@@ -56,6 +151,13 @@ module Patm
56
151
  true
57
152
  end
58
153
  def inspect; "..."; end
154
+ def compile_internal(free_index, target_name = "_obj")
155
+ [
156
+ "true",
157
+ [],
158
+ free_index
159
+ ]
160
+ end
59
161
  end
60
162
 
61
163
  class Obj < self
@@ -70,11 +172,26 @@ module Patm
70
172
  def inspect
71
173
  "OBJ(#{@obj.inspect})"
72
174
  end
175
+
176
+ def compile_internal(free_index, target_name = "_obj")
177
+ [
178
+ "_ctx[#{free_index}] === #{target_name}",
179
+ [@obj],
180
+ free_index + 1,
181
+ ]
182
+ end
73
183
  end
74
184
 
75
185
  class Any < self
76
186
  def execute(match, obj); true; end
77
187
  def inspect; 'ANY'; end
188
+ def compile_internal(free_index, target_name = "_obj")
189
+ [
190
+ "true",
191
+ [],
192
+ free_index
193
+ ]
194
+ end
78
195
  end
79
196
 
80
197
  class Group < self
@@ -87,11 +204,41 @@ module Patm
87
204
  true
88
205
  end
89
206
  def inspect; "GROUP(#{@index})"; end
207
+ def compile_internal(free_index, target_name = "_obj")
208
+ [
209
+ "_match[#{@index}] = #{target_name}; true",
210
+ [],
211
+ free_index
212
+ ]
213
+ end
90
214
  end
91
215
 
92
- class Or < self
93
- def initialize(pats)
216
+ class LogicalOp < self
217
+ def initialize(pats, op_str)
94
218
  @pats = pats
219
+ @op_str = op_str
220
+ end
221
+ def compile_internal(free_index, target_name = "_obj")
222
+ srcs = []
223
+ i = free_index
224
+ ctxs = []
225
+ @pats.each do|pat|
226
+ s, c, i = pat.compile_internal(i, target_name)
227
+ srcs << s
228
+ ctxs << c
229
+ end
230
+
231
+ [
232
+ srcs.map{|s| "(#{s})" }.join(" #{@op_str}\n"),
233
+ ctxs.flatten(1),
234
+ i
235
+ ]
236
+ end
237
+ end
238
+
239
+ class Or < LogicalOp
240
+ def initialize(pats)
241
+ super(pats, '||')
95
242
  end
96
243
  def execute(mmatch, obj)
97
244
  @pats.any? do|pat|
@@ -99,16 +246,16 @@ module Patm
99
246
  end
100
247
  end
101
248
  def rest?
102
- @pats.any?(&rest?)
249
+ @pats.any?(&:rest?)
103
250
  end
104
251
  def inspect
105
252
  "OR(#{@pats.map(&:inspect).join(',')})"
106
253
  end
107
254
  end
108
255
 
109
- class And <self
256
+ class And < LogicalOp
110
257
  def initialize(pats)
111
- @pats = pats
258
+ super(pats, '&&')
112
259
  end
113
260
  def execute(mmatch, obj)
114
261
  @pats.all? do|pat|
@@ -129,18 +276,27 @@ module Patm
129
276
  ARRAY_REST = Pattern::ArrRest.new
130
277
 
131
278
  class Rule
132
- def initialize(&block)
279
+ def initialize(compile = false, &block)
280
+ @compile = compile
133
281
  # { Pattern => Proc }
134
282
  @rules = []
135
283
  block[self]
136
284
  end
137
285
 
138
286
  def on(pat, &block)
139
- @rules << [Pattern.build_from(pat), block]
287
+ if @compile
288
+ @rules << [Pattern.build_from(pat).compile, block]
289
+ else
290
+ @rules << [Pattern.build_from(pat), block]
291
+ end
140
292
  end
141
293
 
142
294
  def else(&block)
143
- @rules << [ANY, lambda {|m,o| block[o] }]
295
+ if @compile
296
+ @rules << [ANY.compile, lambda {|m,o| block[o] }]
297
+ else
298
+ @rules << [ANY, lambda {|m,o| block[o] }]
299
+ end
144
300
  end
145
301
 
146
302
  def apply(obj)
@@ -155,11 +311,12 @@ module Patm
155
311
  end
156
312
 
157
313
  class RuleCache
158
- def initialize
314
+ def initialize(compile = false)
315
+ @compile = compile
159
316
  @rules = {}
160
317
  end
161
318
  def match(rule_name, obj, &rule)
162
- (@rules[rule_name] ||= ::Patm::Rule.new(&rule)).apply(obj)
319
+ (@rules[rule_name] ||= ::Patm::Rule.new(@compile, &rule)).apply(obj)
163
320
  end
164
321
  end
165
322
 
@@ -206,6 +363,7 @@ module Patm
206
363
  end
207
364
 
208
365
  def self.match_array(head, rest_spat = nil, tail = [])
366
+ # TODO: deprecated
209
367
  Match.new(
210
368
  Pattern::Arr.new(
211
369
  head.map{|e| Pattern.build_from(e)},
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 = '0.0.1'
4
+ s.version = '0.1.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'
data/spec/patm_spec.rb CHANGED
@@ -22,16 +22,16 @@ module PatmHelper
22
22
 
23
23
  def match; @match; end
24
24
 
25
- def and_capture(g1, g2 = nil, g3 = nil)
25
+ def and_capture(g1, g2 = nil, g3 = nil, g4 = nil)
26
26
  these_matches(
27
- self, _capture(self, g1, g2, g3)
27
+ self, _capture(self, g1, g2, g3, g4)
28
28
  )
29
29
  end
30
30
  end
31
31
 
32
- matcher :_capture do|m, g1, g2, g3|
32
+ matcher :_capture do|m, g1, g2, g3, g4|
33
33
  match do|_|
34
- [m.match[1], m.match[2], m.match[3]] == [g1, g2, g3]
34
+ [m.match[1], m.match[2], m.match[3], m.match[4]] == [g1, g2, g3, g4]
35
35
  end
36
36
  end
37
37
 
@@ -60,6 +60,17 @@ describe "Usage:" do
60
60
  r.apply([1, 2, 3]).should == [2, 3]
61
61
  end
62
62
 
63
+ it 'with predefined Rule(compiled)' do
64
+ p = Patm
65
+ r = p::Rule.new(true) do|r|
66
+ r.on [1, p._1, p._2] do|m|
67
+ [m._1, m._2]
68
+ end
69
+ r.else {|obj| [] }
70
+ end
71
+ r.apply([1, 2, 3]).should == [2, 3]
72
+ end
73
+
63
74
  it 'with RuleCache' do
64
75
  p = Patm
65
76
  rs = p::RuleCache.new
@@ -74,6 +85,21 @@ describe "Usage:" do
74
85
 
75
86
  rs.match(:pattern_1, [1, 3, 5]) {|r| fail "should not reach here" }.should == [3, 5]
76
87
  end
88
+
89
+ it 'with RuleCache(compiled)' do
90
+ p = Patm
91
+ rs = p::RuleCache.new(true)
92
+
93
+ rs.match(:pattern_1, [1, 2, 3]) do|r|
94
+ r.on [1, p._1, p._2] do|m|
95
+ [m._1, m._2]
96
+ end
97
+ r.else {|obj| [] }
98
+ end
99
+ .should == [2, 3]
100
+
101
+ rs.match(:pattern_1, [1, 3, 5]) {|r| fail "should not reach here" }.should == [3, 5]
102
+ end
77
103
  end
78
104
 
79
105
  describe Patm::Pattern do
@@ -83,6 +109,10 @@ describe Patm::Pattern do
83
109
  subject { Patm::Pattern.build_from(plain) }
84
110
  instance_eval(&b)
85
111
  end
112
+ context "pattern '#{plain.inspect}'(Compiled)" do
113
+ subject { Patm::Pattern.build_from(plain).compile }
114
+ instance_eval(&b)
115
+ end
86
116
  end
87
117
 
88
118
  pattern 1 do
@@ -125,6 +155,10 @@ describe Patm::Pattern do
125
155
  it { should match_to('x').and_capture('x') }
126
156
  end
127
157
 
158
+ pattern Patm._1 & Patm._2 do
159
+ it { should match_to(1).and_capture(1, 1) }
160
+ end
161
+
128
162
  pattern [0, Patm._1, Patm._2] do
129
163
  it { should match_to([0, 1, 2]).and_capture(1, 2) }
130
164
  it { should_not match_to(['x', 1, 2]).and_capture(1, 2) }
@@ -140,4 +174,25 @@ describe Patm::Pattern do
140
174
  it { should match_to([0, 1]).and_capture([]) }
141
175
  it { should match_to([0, 1, 2, 3]).and_capture([2, 3]) }
142
176
  end
177
+
178
+ pattern [0, 1, Patm::ARRAY_REST, 2] do
179
+ it { should match_to([0,1,2]) }
180
+ it { should match_to([0,1,10,20,30,2]) }
181
+ it { should_not match_to([0,1]) }
182
+ end
183
+
184
+ pattern [0, [1, 2]] do
185
+ it { should match_to [0, [1, 2]] }
186
+ it { should_not match_to [0, [1, 3]] }
187
+ end
188
+
189
+ context 'regression' do
190
+ pattern [:assign, [:var_field, [:@ident, Patm._1, [Patm._2, Patm._3]]], Patm._4] do
191
+ it { should match_to([:assign, [:var_field, [:@ident, 10, [20, 30]]], false]).and_capture(10, 20, 30, false) }
192
+ end
193
+ pattern [Patm.or(1, 2)] do
194
+ it { should match_to [1] }
195
+ it { should match_to [2] }
196
+ end
197
+ end
143
198
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: patm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.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-04-24 00:00:00.000000000 Z
11
+ date: 2014-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -48,6 +48,7 @@ files:
48
48
  - Gemfile
49
49
  - LICENSE
50
50
  - README.md
51
+ - benchmark/benchmark.rb
51
52
  - lib/patm.rb
52
53
  - patm.gemspec
53
54
  - spec/patm_spec.rb
@@ -71,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
71
72
  version: '0'
72
73
  requirements: []
73
74
  rubyforge_project:
74
- rubygems_version: 2.2.2
75
+ rubygems_version: 2.0.14
75
76
  signing_key:
76
77
  specification_version: 4
77
78
  summary: PATtern Matching library