patm 1.0.0 → 2.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.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -2
  3. data/lib/patm.rb +178 -43
  4. data/patm.gemspec +1 -1
  5. data/spec/patm_spec.rb +55 -6
  6. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0e616e4136344078fc78da517f68682dd2ace261
4
- data.tar.gz: 759ff1f8066ad8b0f9ea3e1cd28573458b62fc5d
3
+ metadata.gz: d2a4036ae8e3a9b302bd189406ebb284f3efcc54
4
+ data.tar.gz: 10b6f6831b67755280f6e6ba5faf145a3ddf8910
5
5
  SHA512:
6
- metadata.gz: 3471bde54cc18238144344e507e66a40cf116bfea3aebb3c4b6c85c18e27edb7931f0db1474f684a5dc6c1044a9a9e89c4a46f8b222e9056ed562d877658abfd
7
- data.tar.gz: cb68d3f85838c99f3f39089f78f53e64f5ec8f21734c111563c18e773a17ad29388e563952185aa636b2d0fbef838ed4a4491ec42fae7c3491c2392a6fa6aa24
6
+ metadata.gz: 50f3198622f079bc716eb064a4e8cfbdad3f63bfd6f7c9f76c750fefd35e86301513c79542b777c5c4d7594165632acdcbb00e213661a544cc8e61fbf439ef72
7
+ data.tar.gz: 093665d2fcb727165d6fda3cd9c214805be766f70b15cf565ec7670aa585a21c280514687298dd2aa64558248633fbee7f815f836a9e6207c00ced7ece6e2b7e
data/README.md CHANGED
@@ -102,21 +102,36 @@ end
102
102
 
103
103
  ## Patterns
104
104
 
105
- ### `1`, `:x`, String, ...
105
+ ### Value
106
106
 
107
- Normal pattern matches if `pattern === value` is true.
107
+ Value patterns such as `1, :x, String, ...` matches if `pattern === target_value` is true.
108
108
 
109
109
  ### Array
110
110
 
111
+ `[1, 2, _any]` matches `[1, 2, 3]`, `[1, 2, :x]`, etc.
112
+
111
113
  `[1, 2, _xs]` matches `[1, 2]`, `[1, 2, 3]`, `[1, 2, 3, 4]`, etc.
114
+
112
115
  `[1, _xs, 2]` matches `[1, 2]`, `[1, 10, 2]`, etc.
113
116
 
114
117
  Note: More than one `_xs` in same array is invalid.
115
118
 
119
+ ### Hash
120
+
121
+ `{a: 1}` matches `{a: 1}`, `{a: 1, b: 2}`, etc.
122
+
123
+ `{a: 1, Patm.exact => true}` matches only `{a: 1}`.
124
+
125
+ `{a: 1, b: Patm.opt(2)}` matches `{a: 1}`, `{a: 1, b: 2}`.
126
+
116
127
  ### Capture
117
128
 
118
129
  `_1`, `_2`, etc matches any value, and capture the value as correspond match group.
119
130
 
131
+ `Pattern#[capture_name]` also used for capture.`Patm._any[:foo]` capture any value as `foo`.
132
+
133
+ Captured values are accessible through `Match#_1, _2, ...` and `Match#[capture_name]`
134
+
120
135
  ### Compose
121
136
 
122
137
  `_1&[_any, _any]` matches any two element array, and capture the array as _1.
@@ -125,6 +140,13 @@ Note: More than one `_xs` in same array is invalid.
125
140
 
126
141
  ## Changes
127
142
 
143
+ ### 2.0.0(Unreleased)
144
+
145
+ - Named capture
146
+ - Patm::GROUPS is obsolete. Use `pattern[number_or_name]` or `Patm._1, _2, ...` instead.
147
+ - More optimize for compiled pattern.
148
+ - Hash patterns
149
+
128
150
  ### 1.0.0
129
151
 
130
152
  - DSL
@@ -5,7 +5,7 @@ module Patm
5
5
  case plain
6
6
  when Pattern
7
7
  plain
8
- when Array
8
+ when ::Array
9
9
  array = plain.map{|a| build_from(a)}
10
10
  rest_index = array.index(&:rest?)
11
11
  if rest_index
@@ -16,13 +16,47 @@ module Patm
16
16
  else
17
17
  Arr.new(array)
18
18
  end
19
+ when ::Hash
20
+ Hash.new(
21
+ plain.each_with_object({}) do|(k, v), h|
22
+ h[k] = build_from(v) if k != Patm.exact
23
+ end,
24
+ plain[Patm.exact]
25
+ )
19
26
  else
20
27
  Obj.new(plain)
21
28
  end
22
29
  end
23
30
 
31
+ module Util
32
+ def self.compile_value(value, free_index)
33
+ if value.is_a?(Numeric) || value.is_a?(String) || value.is_a?(Symbol)
34
+ [
35
+ value.inspect,
36
+ [],
37
+ free_index,
38
+ ]
39
+ else
40
+ [
41
+ "_ctx[#{free_index}]",
42
+ [value],
43
+ free_index + 1,
44
+ ]
45
+ end
46
+ end
47
+ end
48
+
49
+ # Use in Hash pattern.
50
+ def opt
51
+ Opt.new(self)
52
+ end
53
+
24
54
  def execute(match, obj); true; end
25
55
 
56
+ def opt?
57
+ false
58
+ end
59
+
26
60
  def rest?
27
61
  false
28
62
  end
@@ -31,14 +65,18 @@ module Patm
31
65
  And.new([self, rhs])
32
66
  end
33
67
 
68
+ def [](name)
69
+ self & Named.new(name)
70
+ end
71
+
34
72
  def compile
35
73
  src, context, _ = self.compile_internal(0)
36
74
 
37
- Compiled.new(self.inspect, src, context)
75
+ Compiled.new(self.inspect, src || "true", context)
38
76
  end
39
77
 
40
- # free_index:Numeric -> [src, context, free_index]
41
- # variables: _ctx, _match, _obj
78
+ # free_index:Numeric -> target_name:String -> [src:String|nil, context:Array, free_index:Numeric]
79
+ # variables: _ctx, _match, (target_name)
42
80
  def compile_internal(free_index, target_name = "_obj")
43
81
  [
44
82
  "_ctx[#{free_index}].execute(_match, #{target_name})",
@@ -69,6 +107,82 @@ module Patm
69
107
  def inspect; "<compiled>#{@desc}"; end
70
108
  end
71
109
 
110
+ class Hash < self
111
+ def initialize(hash, exact)
112
+ @pat = hash
113
+ @exact = exact
114
+ @non_opt_count = @pat.values.count{|v| !v.opt? }
115
+ end
116
+ def execute(match, obj)
117
+ return false unless obj.is_a?(::Hash)
118
+ obj.size >= @non_opt_count &&
119
+ (!@exact || obj.keys.all?{|k| @pat.has_key?(k) }) &&
120
+ @pat.all? {|k, pat|
121
+ if obj.has_key?(k)
122
+ pat.execute(match, obj[k])
123
+ else
124
+ pat.opt?
125
+ end
126
+ }
127
+ end
128
+ def inspect; @pat.inspect; end
129
+ def compile_internal(free_index, target_name = "_obj")
130
+ i = free_index
131
+ ctxs = []
132
+ src = []
133
+
134
+ ctxs << [@pat]
135
+ i += 1
136
+
137
+ pat = "_ctx[#{free_index}]"
138
+ src << "#{target_name}.is_a?(::Hash)"
139
+ src << "#{target_name}.size >= #{@non_opt_count}"
140
+ if @exact
141
+ src << "#{target_name}.keys.all?{|k| #{pat}.has_key?(k) }"
142
+ end
143
+ tname = "#{target_name}_elm"
144
+ @pat.each do|k, v|
145
+ key_src, c, i = Util.compile_value(k, i)
146
+ ctxs << c
147
+ s, c, i = v.compile_internal(i, tname)
148
+ body =
149
+ if s
150
+ "(#{tname} = #{target_name}[#{key_src}]; #{s})"
151
+ else
152
+ "true"
153
+ end
154
+ src <<
155
+ if v.opt?
156
+ "(!#{target_name}.has_key?(#{key_src}) || #{body})"
157
+ else
158
+ "(#{target_name}.has_key?(#{key_src}) && #{body})"
159
+ end
160
+ ctxs << c
161
+ end
162
+ [
163
+ src.join(" &&\n"),
164
+ ctxs.flatten(1),
165
+ i,
166
+ ]
167
+ end
168
+ end
169
+
170
+ class Opt < self
171
+ def initialize(pat)
172
+ @pat = pat
173
+ end
174
+ def opt?
175
+ true
176
+ end
177
+ def execute(match, obj)
178
+ @pat.execute(match, obj)
179
+ end
180
+ def inspect; "?#{@pat.inspect}"; end
181
+ def compile_internal(free_index, target_name = "_obj")
182
+ @pat.compile_internal(free_index, target_name)
183
+ end
184
+ end
185
+
72
186
  class Arr < self
73
187
  def initialize(head, rest = nil, tail = [])
74
188
  @head = head
@@ -114,26 +228,28 @@ module Patm
114
228
  elm_target_name = "#{target_name}_elm"
115
229
  @head.each_with_index do|h, hi|
116
230
  s, c, i = h.compile_internal(i, elm_target_name)
117
- srcs << "(#{elm_target_name} = #{target_name}[#{hi}]; #{s})"
231
+ srcs << "(#{elm_target_name} = #{target_name}[#{hi}]; #{s})" if s
118
232
  ctxs << c
119
233
  end
120
234
 
121
- srcs << "(#{target_name}_t = #{target_name}[(-#{@tail.size})..-1]; true)"
122
- @tail.each_with_index do|t, ti|
123
- s, c, i = t.compile_internal(i, elm_target_name)
124
- srcs << "(#{elm_target_name} = #{target_name}_t[#{ti}]; #{s})"
125
- ctxs << c
235
+ unless @tail.empty?
236
+ srcs << "(#{target_name}_t = #{target_name}[(-#{@tail.size})..-1]; true)"
237
+ @tail.each_with_index do|t, ti|
238
+ s, c, i = t.compile_internal(i, elm_target_name)
239
+ srcs << "(#{elm_target_name} = #{target_name}_t[#{ti}]; #{s})" if s
240
+ ctxs << c
241
+ end
126
242
  end
127
243
 
128
244
  if @rest
129
245
  tname = "#{target_name}_r"
130
246
  s, c, i = @rest.compile_internal(i, tname)
131
- srcs << "(#{tname} = #{target_name}[#{@head.size}..-(#{@tail.size+1})];#{s})"
247
+ srcs << "(#{tname} = #{target_name}[#{@head.size}..-(#{@tail.size+1})];#{s})" if s
132
248
  ctxs << c
133
249
  end
134
250
 
135
251
  [
136
- srcs.map{|s| "(#{s})"}.join(" &&\n"),
252
+ srcs.compact.map{|s| "(#{s})"}.join(" &&\n"),
137
253
  ctxs.flatten(1),
138
254
  i
139
255
  ]
@@ -150,7 +266,7 @@ module Patm
150
266
  def inspect; "..."; end
151
267
  def compile_internal(free_index, target_name = "_obj")
152
268
  [
153
- "true",
269
+ nil,
154
270
  [],
155
271
  free_index
156
272
  ]
@@ -171,10 +287,11 @@ module Patm
171
287
  end
172
288
 
173
289
  def compile_internal(free_index, target_name = "_obj")
290
+ val_src, c, i = Util.compile_value(@obj, free_index)
174
291
  [
175
- "_ctx[#{free_index}] === #{target_name}",
176
- [@obj],
177
- free_index + 1,
292
+ "#{val_src} === #{target_name}",
293
+ c,
294
+ i
178
295
  ]
179
296
  end
180
297
  end
@@ -184,26 +301,28 @@ module Patm
184
301
  def inspect; 'ANY'; end
185
302
  def compile_internal(free_index, target_name = "_obj")
186
303
  [
187
- "true",
304
+ nil,
188
305
  [],
189
306
  free_index
190
307
  ]
191
308
  end
192
309
  end
193
310
 
194
- class Group < self
195
- def initialize(index)
196
- @index = index
311
+ class Named < self
312
+ def initialize(name)
313
+ raise ::ArgumentError unless name.is_a?(Symbol) || name.is_a?(Numeric)
314
+ @name = name
197
315
  end
198
- attr_reader :index
199
- def execute(mmatch, obj)
200
- mmatch[@index] = obj
316
+ attr_reader :name
317
+ alias index name # compatibility
318
+ def execute(match, obj)
319
+ match[@name] = obj
201
320
  true
202
321
  end
203
- def inspect; "GROUP(#{@index})"; end
322
+ def inspect; "NAMED(#{@name})"; end
204
323
  def compile_internal(free_index, target_name = "_obj")
205
324
  [
206
- "_match[#{@index}] = #{target_name}; true",
325
+ "_match[#{@name.inspect}] = #{target_name}; true",
207
326
  [],
208
327
  free_index
209
328
  ]
@@ -226,11 +345,17 @@ module Patm
226
345
  end
227
346
 
228
347
  [
229
- srcs.map{|s| "(#{s})" }.join(" #{@op_str}\n"),
348
+ srcs.compact.map{|s| "(#{s})" }.join(" #{@op_str}\n"),
230
349
  ctxs.flatten(1),
231
350
  i
232
351
  ]
233
352
  end
353
+ def rest?
354
+ @pats.any?(&:rest?)
355
+ end
356
+ def opt?
357
+ @pats.any?(&:opt?)
358
+ end
234
359
  end
235
360
 
236
361
  class Or < LogicalOp
@@ -242,9 +367,6 @@ module Patm
242
367
  pat.execute(mmatch, obj)
243
368
  end
244
369
  end
245
- def rest?
246
- @pats.any?(&:rest?)
247
- end
248
370
  def inspect
249
371
  "OR(#{@pats.map(&:inspect).join(',')})"
250
372
  end
@@ -259,17 +381,12 @@ module Patm
259
381
  pat.execute(mmatch, obj)
260
382
  end
261
383
  end
262
- def rest?
263
- @pats.any?(&:rest?)
264
- end
265
384
  def inspect
266
385
  "AND(#{@pats.map(&:inspect).join(',')})"
267
386
  end
268
387
  end
269
388
  end
270
389
 
271
- GROUP = 100.times.map{|i| Pattern::Group.new(i) }
272
-
273
390
  def self.or(*pats)
274
391
  Pattern::Or.new(pats.map{|p| Pattern.build_from(p) })
275
392
  end
@@ -282,10 +399,28 @@ module Patm
282
399
  @xs = Pattern::ArrRest.new
283
400
  end
284
401
 
402
+ # Use in hash value.
403
+ # Mark this pattern is optional.
404
+ def self.opt(pat = _any)
405
+ Pattern::Opt.new(Pattern.build_from(pat))
406
+ end
407
+
408
+ EXACT = Object.new
409
+ def EXACT.inspect
410
+ "EXACT"
411
+ end
412
+ # Use in Hash key.
413
+ # Specify exact match or not.
414
+ def self.exact
415
+ EXACT
416
+ end
417
+
418
+ PREDEF_GROUP_SIZE = 100
419
+
285
420
  class <<self
286
- GROUP.each do|g|
287
- define_method "_#{g.index}" do
288
- g
421
+ PREDEF_GROUP_SIZE.times do|i|
422
+ define_method "_#{i}" do
423
+ Pattern::Named.new(i)
289
424
  end
290
425
  end
291
426
  end
@@ -349,9 +484,9 @@ module Patm
349
484
  @group[i] = val
350
485
  end
351
486
 
352
- Patm::GROUP.each do|g|
353
- define_method "_#{g.index}" do
354
- self[g.index]
487
+ PREDEF_GROUP_SIZE.times.each do|i|
488
+ define_method "_#{i}" do
489
+ self[i]
355
490
  end
356
491
  end
357
492
  end
@@ -367,9 +502,9 @@ module Patm
367
502
  end
368
503
 
369
504
  def [](i); @match[i]; end
370
- Patm::GROUP.each do|g|
371
- define_method "_#{g.index}" do
372
- @match[g.index]
505
+ PREDEF_GROUP_SIZE.times do|i|
506
+ define_method "_#{i}" do
507
+ @match[i]
373
508
  end
374
509
  end
375
510
  end
@@ -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 = '1.0.0'
4
+ s.version = '2.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'
@@ -24,14 +24,20 @@ module PatmHelper
24
24
 
25
25
  def and_capture(g1, g2 = nil, g3 = nil, g4 = nil)
26
26
  these_matches(
27
- self, _capture(self, g1, g2, g3, g4)
27
+ self, _capture(self, {1 => g1, 2 => g2, 3 => g3, 4 => g4})
28
+ )
29
+ end
30
+
31
+ def and_named_capture(capture)
32
+ these_matches(
33
+ self, _capture(self, capture)
28
34
  )
29
35
  end
30
36
  end
31
37
 
32
- matcher :_capture do|m, g1, g2, g3, g4|
38
+ matcher :_capture do|m, capture|
33
39
  match do|_|
34
- [m.match[1], m.match[2], m.match[3], m.match[4]] == [g1, g2, g3, g4]
40
+ [m.match[1], m.match[2], m.match[3], m.match[4]] == capture.values_at(1,2,3,4)
35
41
  end
36
42
  end
37
43
 
@@ -206,9 +212,9 @@ describe Patm::Pattern do
206
212
  it { should match_to([0, 1, 2, 3]).and_capture([2, 3]) }
207
213
  end
208
214
 
209
- pattern [0, 1, Patm._xs, 2] do
210
- it { should match_to([0,1,2]) }
211
- it { should match_to([0,1,10,20,30,2]) }
215
+ pattern [0, 1, Patm._xs[1], 2] do
216
+ it { should match_to([0,1,2]).and_capture([]) }
217
+ it { should match_to([0,1,10,20,30,2]).and_capture([10,20,30]) }
212
218
  it { should_not match_to([0,1]) }
213
219
  end
214
220
 
@@ -217,6 +223,45 @@ describe Patm::Pattern do
217
223
  it { should_not match_to [0, [1, 3]] }
218
224
  end
219
225
 
226
+ pattern Patm._any[:x] do
227
+ it { should match_to("aaa").and_named_capture(:x => "aaa") }
228
+ end
229
+
230
+ pattern(a: Patm._any[1]) do
231
+ it { should_not match_to {} }
232
+ it { should match_to(a: 1).and_capture(1) }
233
+ it { should match_to(a: 1, b: 2).and_capture(1) }
234
+ end
235
+
236
+ pattern(a: Patm._any, Patm.exact => false) do
237
+ it { should_not match_to(b: 1) }
238
+ it { should match_to(a: 1) }
239
+ it { should match_to(a: 1, b: 2) }
240
+ end
241
+
242
+ pattern(a: Patm._any, Patm.exact => true) do
243
+ it { should_not match_to(b: 1) }
244
+ it { should match_to(a: 1) }
245
+ it { should_not match_to(a: 1, b: 2) }
246
+ end
247
+
248
+ pattern(a: Patm._any[1].opt) do
249
+ it { should match_to({}).and_capture(nil) }
250
+ it { should match_to({a: 1}).and_capture(1) }
251
+ end
252
+
253
+ pattern(a: Patm._any[1], b: Patm._any[2].opt) do
254
+ it { should_not match_to({}) }
255
+ it { should match_to({a: 1}).and_capture(1, nil) }
256
+ it { should match_to({a: 1, b: 2}).and_capture(1, 2) }
257
+ end
258
+
259
+ pattern({a: 1} => {b: 2}) do
260
+ it { should_not match_to({a: 1} => {b: 0}) }
261
+ it { should_not match_to({a: 0} => {b: 2}) }
262
+ it { should match_to({a: 1} => {b: 2}) }
263
+ end
264
+
220
265
  context 'regression' do
221
266
  pattern [:assign, [:var_field, [:@ident, Patm._1, [Patm._2, Patm._3]]], Patm._4] do
222
267
  it { should match_to([:assign, [:var_field, [:@ident, 10, [20, 30]]], false]).and_capture(10, 20, 30, false) }
@@ -225,5 +270,9 @@ describe Patm::Pattern do
225
270
  it { should match_to [1] }
226
271
  it { should match_to [2] }
227
272
  end
273
+ pattern [Patm._1, 2, [Patm._any, 3], Patm._xs[1], 4] do
274
+ it { should match_to([1, 2, [10, 3], 4]).and_capture([]) }
275
+ it { should match_to([1, 2, [10, 3], 20, 4]).and_capture([20]) }
276
+ end
228
277
  end
229
278
  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: 1.0.0
4
+ version: 2.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-04-26 00:00:00.000000000 Z
11
+ date: 2014-05-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -72,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
72
72
  version: '0'
73
73
  requirements: []
74
74
  rubyforge_project:
75
- rubygems_version: 2.0.14
75
+ rubygems_version: 2.2.2
76
76
  signing_key:
77
77
  specification_version: 4
78
78
  summary: PATtern Matching library