patm 3.0.0 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +19 -0
- data/lib/patm.rb +153 -60
- data/patm.gemspec +1 -1
- data/spec/patm_spec.rb +21 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fce015f4cd31a8ee0eb2227373c6e809302411ed
|
4
|
+
data.tar.gz: 7000cb5f66f204896bf207e8fee63a9f07387e60
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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).
|
data/lib/patm.rb
CHANGED
@@ -13,28 +13,36 @@ module Patm
|
|
13
13
|
when Pattern
|
14
14
|
plain
|
15
15
|
when ::Array
|
16
|
-
|
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
|
-
|
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
|
-
|
206
|
-
@head.
|
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
|
-
|
222
|
+
}
|
223
|
+
|
224
|
+
return false unless @tail.zip(obj[(-@tail.size)..-1]).all? {|pat, o|
|
210
225
|
pat.execute(mmatch, o)
|
211
|
-
}
|
212
|
-
|
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
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
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
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
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
|
-
|
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
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
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
|
-
|
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
|
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 = '3.
|
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'
|
data/spec/patm_spec.rb
CHANGED
@@ -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.
|
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-
|
11
|
+
date: 2014-05-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: simplecov
|