patm 3.0.0 → 3.1.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 +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
|
[](https://travis-ci.org/todesking/patm)
|
4
|
+
[](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
|