patm 3.0.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -0
  3. data/lib/patm.rb +153 -60
  4. data/patm.gemspec +1 -1
  5. data/spec/patm_spec.rb +21 -0
  6. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7dfd622177fbff6949aeba6e0ea9a37c503ca13e
4
- data.tar.gz: 49e7f41b2e40d4fa9724eb607340b6196a41ef0c
3
+ metadata.gz: fce015f4cd31a8ee0eb2227373c6e809302411ed
4
+ data.tar.gz: 7000cb5f66f204896bf207e8fee63a9f07387e60
5
5
  SHA512:
6
- metadata.gz: 7a0dd73809aadf15e43dab4c93cd857f38bf3bdb0666525f4791f117e415d0123df8dd90d0f66880362669efbaad2bfd3c0ecf82a17c2929c620711e6b773655
7
- data.tar.gz: 379c2300a12acd9662f5abffa284dc6ffc1854f027d5ca9a8e92e5bafb9a497581054a39998334c965bb21cd4e872035ff7e88471ee00983e4468f47421cc87b
6
+ metadata.gz: 778234a77d4bf3e4cbc1f3132e29f55ac1dad6f4fb5738f0d7da1145f3f8b4a1bcf9d3e36d7b9271dfd74cf047ed0880178ba6022f64479a91b4d338b8e7c589
7
+ data.tar.gz: cba96914ce05332a949d851a48fbfd3d3a840812b4fb402c9b6b6ec55b274bad532bdeea239c77bc9afb9f7a5adf8bd5e073188cb902222e8dbd322bc3bd505a
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # PATM: PATtern Matcher for Ruby
2
2
 
3
3
  [![Build Status](https://travis-ci.org/todesking/patm.svg?branch=master)](https://travis-ci.org/todesking/patm)
4
+ [![Code Climate](https://codeclimate.com/github/todesking/patm.png)](https://codeclimate.com/github/todesking/patm)
4
5
 
5
6
  PATM is extremely faster pattern match library.
6
7
 
@@ -117,6 +118,20 @@ matcher.match(1)
117
118
 
118
119
  Value patterns such as `1, :x, String, ...` matches if `pattern === target_value` is true.
119
120
 
121
+ ### Struct
122
+
123
+ ```ruby
124
+ A = Struct.new(:x, :y)
125
+
126
+ # ...
127
+ define_matcher :match_struct do|r|
128
+ # use Patm[struct_class].( ... ) for match struct
129
+ # argument is a Hash(member_name => pattern) or patterns that correspond struct members.
130
+ r.on Patm[A].(x: 1, y: Patm._1) {|m| m._1 }
131
+ r.on Patm[A].(2, Patm._1) {|m| m._1 }
132
+ end
133
+ ```
134
+
120
135
  ### Array
121
136
 
122
137
  `[1, 2, _any]` matches `[1, 2, 3]`, `[1, 2, :x]`, etc.
@@ -189,6 +204,10 @@ pattern_match 10.050000 0.070000 10.120000 ( 10.898683)
189
204
 
190
205
  ## Changes
191
206
 
207
+ ### 3.1.0
208
+
209
+ - Struct matcher
210
+
192
211
  ### 3.0.0
193
212
 
194
213
  - If given value can't match any pattern and no `else`, `Patm::NoMatchError` raised(Instead of return nil).
@@ -13,28 +13,36 @@ module Patm
13
13
  when Pattern
14
14
  plain
15
15
  when ::Array
16
- array = plain.map{|a| build_from(a)}
17
- rest_index = array.index(&:rest?)
18
- if rest_index
19
- head = array[0...rest_index]
20
- rest = array[rest_index]
21
- tail = array[(rest_index+1)..-1]
22
- Arr.new(head, rest, tail)
23
- else
24
- Arr.new(array)
25
- end
16
+ build_from_array(plain)
26
17
  when ::Hash
27
- Hash.new(
28
- plain.each_with_object({}) do|(k, v), h|
29
- h[k] = build_from(v) if k != Patm.exact
30
- end,
31
- plain[Patm.exact]
32
- )
18
+ build_from_hash(plain)
33
19
  else
34
20
  Obj.new(plain)
35
21
  end
36
22
  end
37
23
 
24
+ def self.build_from_array(plain)
25
+ array = plain.map{|a| build_from(a)}
26
+ rest_index = array.index(&:rest?)
27
+ if rest_index
28
+ head = array[0...rest_index]
29
+ rest = array[rest_index]
30
+ tail = array[(rest_index+1)..-1]
31
+ Arr.new(head, rest, tail)
32
+ else
33
+ Arr.new(array)
34
+ end
35
+ end
36
+
37
+ def self.build_from_hash(plain)
38
+ self::Hash.new(
39
+ plain.each_with_object({}) do|(k, v), h|
40
+ h[k] = build_from(v) if k != Patm.exact
41
+ end,
42
+ plain[Patm.exact]
43
+ )
44
+ end
45
+
38
46
  module Util
39
47
  def self.compile_value(value, free_index)
40
48
  if value.nil? || value.is_a?(Numeric) || value.is_a?(String) || value.is_a?(Symbol)
@@ -200,16 +208,24 @@ module Patm
200
208
  end
201
209
 
202
210
  def execute(mmatch, obj)
203
- size_min = @head.size + @tail.size
204
211
  return false unless obj.is_a?(Array)
205
- return false unless @rest ? (obj.size >= size_min) : (obj.size == size_min)
206
- @head.zip(obj[0..(@head.size - 1)]).all? {|pat, o|
212
+
213
+ size_min = @head.size + @tail.size
214
+ if @rest
215
+ return false if obj.size < size_min
216
+ else
217
+ return false if obj.size != size_min
218
+ end
219
+
220
+ return false unless @head.zip(obj[0..(@head.size - 1)]).all? {|pat, o|
207
221
  pat.execute(mmatch, o)
208
- } &&
209
- @tail.zip(obj[(-@tail.size)..-1]).all? {|pat, o|
222
+ }
223
+
224
+ return false unless @tail.zip(obj[(-@tail.size)..-1]).all? {|pat, o|
210
225
  pat.execute(mmatch, o)
211
- } &&
212
- (!@rest || @rest.execute(mmatch, obj[@head.size..-(@tail.size+1)]))
226
+ }
227
+
228
+ !@rest || @rest.execute(mmatch, obj[@head.size..-(@tail.size+1)])
213
229
  end
214
230
 
215
231
  def inspect
@@ -227,48 +243,59 @@ module Patm
227
243
 
228
244
  srcs << "#{target_name}.is_a?(::Array)"
229
245
 
230
- size_min = @head.size + @tail.size
231
- if @rest
232
- srcs << "#{target_name}.size >= #{size_min}"
233
- else
234
- srcs << "#{target_name}.size == #{size_min}"
246
+ i = compile_size_check(target_name, srcs, ctxs, i)
247
+
248
+ i = compile_part(target_name, @head, srcs, ctxs, i)
249
+
250
+ unless @tail.empty?
251
+ srcs << "#{target_name}_t = #{target_name}[(-#{@tail.size})..-1]; true"
252
+ i = compile_part("#{target_name}_t", @tail, srcs, ctxs, i)
235
253
  end
236
254
 
237
- elm_target_name = "#{target_name}_elm"
238
- @head.each_with_index do|h, hi|
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
255
+ i = compile_rest(target_name, srcs, ctxs, i)
256
+
257
+ [
258
+ srcs.compact.map{|s| "(#{s})"}.join(" &&\n"),
259
+ ctxs.flatten(1),
260
+ i
261
+ ]
262
+ end
263
+
264
+ private
265
+ def compile_size_check(target_name, srcs, ctxs, i)
266
+ size_min = @head.size + @tail.size
267
+ if @rest
268
+ srcs << "#{target_name}.size >= #{size_min}"
243
269
  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
270
+ srcs << "#{target_name}.size == #{size_min}"
247
271
  end
272
+ i
248
273
  end
249
274
 
250
- unless @tail.empty?
251
- srcs << "#{target_name}_t = #{target_name}[(-#{@tail.size})..-1]; true"
252
- @tail.each_with_index do|t, ti|
253
- s, c, i = t.compile_internal(i, elm_target_name)
254
- srcs << "#{elm_target_name} = #{target_name}_t[#{ti}]; #{s}" if s
255
- ctxs << c
275
+ def compile_part(target_name, part, srcs, ctxs, i)
276
+ part.each_with_index do|h, hi|
277
+ if h.is_a?(Obj)
278
+ s, c, i = h.compile_internal(i, "#{target_name}[#{hi}]")
279
+ srcs << "#{s}" if s
280
+ ctxs << c
281
+ else
282
+ elm_target_name = "#{target_name}_elm"
283
+ s, c, i = h.compile_internal(i, elm_target_name)
284
+ srcs << "#{elm_target_name} = #{target_name}[#{hi}]; #{s}" if s
285
+ ctxs << c
286
+ end
256
287
  end
288
+ i
257
289
  end
258
290
 
259
- if @rest
291
+ def compile_rest(target_name, srcs, ctxs, i)
292
+ return i unless @rest
260
293
  tname = "#{target_name}_r"
261
294
  s, c, i = @rest.compile_internal(i, tname)
262
295
  srcs << "#{tname} = #{target_name}[#{@head.size}..-(#{@tail.size+1})];#{s}" if s
263
296
  ctxs << c
264
- end
265
-
266
- [
267
- srcs.compact.map{|s| "(#{s})"}.join(" &&\n"),
268
- ctxs.flatten(1),
269
297
  i
270
- ]
271
- end
298
+ end
272
299
  end
273
300
 
274
301
  class ArrRest < self
@@ -323,6 +350,70 @@ module Patm
323
350
  end
324
351
  end
325
352
 
353
+ class Struct < self
354
+ def initialize(klass, pat)
355
+ @klass, @pat = klass, pat
356
+ end
357
+
358
+ def inspect
359
+ "STRUCT(#{@klass.name || "<unnamed>"}, #{@pat.inspect})"
360
+ end
361
+
362
+ def execute(match, obj)
363
+ obj.is_a?(@klass) && @pat.all?{|k, v| v.execute(match, obj[k]) }
364
+ end
365
+
366
+ def compile_internal(free_index, target_name = "_obj")
367
+ srcs = []
368
+ ctxs = []
369
+ i = free_index
370
+
371
+ if @klass.name
372
+ srcs << "#{target_name}.is_a?(::#{@klass.name})"
373
+ else
374
+ srcs << "#{target_name}.is_a?(_ctx[#{i}])"
375
+ ctxs << [@klass]
376
+ i += 1
377
+ end
378
+
379
+ @pat.each do|(member, v)|
380
+ s, c, i = v.compile_internal(i, "#{target_name}_elm")
381
+ srcs << "#{target_name}_elm = #{target_name}.#{member}; #{s}" if s
382
+ ctxs << c
383
+ end
384
+
385
+ [
386
+ srcs.map{|s| "(#{s})"}.join(" &&\n"),
387
+ ctxs.flatten(1),
388
+ i
389
+ ]
390
+ end
391
+
392
+ class Builder
393
+ def initialize(klass)
394
+ raise ArgumentError, "#{klass} is not Struct" unless klass.ancestors.include?(::Struct)
395
+ @klass = klass
396
+ end
397
+
398
+ # member_1_pat -> member_2_pat -> ... -> Pattern
399
+ # {member:Symbol => pattern} -> Pattern
400
+ def call(*args)
401
+ if args.size == 1 && args.first.is_a?(::Hash)
402
+ hash = args.first
403
+ hash.keys.each do|k|
404
+ raise ArgumentError, "#{k.inspect} is not member of #{@klass}" unless @klass.members.include?(k)
405
+ end
406
+ Struct.new(@klass, hash.each_with_object({}){|(k, plain), h|
407
+ h[k] = Pattern.build_from(plain)
408
+ })
409
+ else
410
+ raise ArgumentError, "Member size not match: expected #{@klass.members.size} but #{args.size}" unless args.size == @klass.members.size
411
+ Struct.new(@klass, ::Hash[*@klass.members.zip(args.map{|a| Pattern.build_from(a) }).flatten(1)])
412
+ end
413
+ end
414
+ end
415
+ end
416
+
326
417
  class Named < self
327
418
  def initialize(name)
328
419
  raise ::ArgumentError unless name.is_a?(Symbol) || name.is_a?(Numeric)
@@ -345,7 +436,8 @@ module Patm
345
436
  end
346
437
 
347
438
  class LogicalOp < self
348
- def initialize(pats, op_str)
439
+ def initialize(name, pats, op_str)
440
+ @name = name
349
441
  @pats = pats
350
442
  @op_str = op_str
351
443
  end
@@ -375,34 +467,31 @@ module Patm
375
467
  def opt?
376
468
  @pats.any?(&:opt?)
377
469
  end
470
+ def inspect
471
+ "#{@name}(#{@pats.map(&:inspect).join(',')})"
472
+ end
378
473
  end
379
474
 
380
475
  class Or < LogicalOp
381
476
  def initialize(pats)
382
- super(pats, '||')
477
+ super('OR', pats, '||')
383
478
  end
384
479
  def execute(mmatch, obj)
385
480
  @pats.any? do|pat|
386
481
  pat.execute(mmatch, obj)
387
482
  end
388
483
  end
389
- def inspect
390
- "OR(#{@pats.map(&:inspect).join(',')})"
391
- end
392
484
  end
393
485
 
394
486
  class And < LogicalOp
395
487
  def initialize(pats)
396
- super(pats, '&&')
488
+ super('AND', pats, '&&')
397
489
  end
398
490
  def execute(mmatch, obj)
399
491
  @pats.all? do|pat|
400
492
  pat.execute(mmatch, obj)
401
493
  end
402
494
  end
403
- def inspect
404
- "AND(#{@pats.map(&:inspect).join(',')})"
405
- end
406
495
  end
407
496
  end
408
497
 
@@ -434,6 +523,10 @@ module Patm
434
523
  EXACT
435
524
  end
436
525
 
526
+ def self.[](struct_klass)
527
+ Pattern::Struct::Builder.new(struct_klass)
528
+ end
529
+
437
530
  PREDEF_GROUP_SIZE = 100
438
531
 
439
532
  class <<self
@@ -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 = '3.0.0'
4
+ s.version = '3.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'
@@ -2,6 +2,7 @@ require 'simplecov'
2
2
  require 'simplecov-vim/formatter'
3
3
  SimpleCov.start do
4
4
  formatter SimpleCov::Formatter::VimFormatter
5
+ formatter SimpleCov::Formatter::HTMLFormatter
5
6
  end
6
7
 
7
8
  require File.join(File.dirname(__FILE__), '..', 'lib', 'patm.rb')
@@ -300,6 +301,26 @@ describe Patm::Pattern do
300
301
  it { should match_to({a: 1} => {b: 2}) }
301
302
  end
302
303
 
304
+ Struct1 = Struct.new(:a, :b)
305
+
306
+ pattern(Patm[Struct1].(1, Patm._1)) do
307
+ it { should_not match_to(nil) }
308
+ it { should_not match_to(Struct1.new(2, 2)) }
309
+ it { should match_to(Struct1.new(1, 2)).and_capture(2) }
310
+ end
311
+
312
+ pattern(Patm[Struct1].(a: 1, b: Patm._1)) do
313
+ it { should_not match_to(nil) }
314
+ it { should_not match_to(Struct1.new(2, 2)) }
315
+ it { should match_to(Struct1.new(1, 2)).and_capture(2) }
316
+ end
317
+
318
+ unnamed_struct = Struct.new(:x, :y)
319
+ pattern(Patm[unnamed_struct].(x: 1, y: Patm._1)) do
320
+ it { should_not match_to(nil) }
321
+ it { should match_to(unnamed_struct.new(1, 2)).and_capture(2) }
322
+ end
323
+
303
324
  context 'regression' do
304
325
  pattern [:assign, [:var_field, [:@ident, Patm._1, [Patm._2, Patm._3]]], Patm._4] do
305
326
  it { should match_to([:assign, [:var_field, [:@ident, 10, [20, 30]]], false]).and_capture(10, 20, 30, false) }
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: 3.0.0
4
+ version: 3.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-05-24 00:00:00.000000000 Z
11
+ date: 2014-05-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: simplecov