patm 1.0.0 → 2.0.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 +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