pattern-match 0.1.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +1 -0
- data/README.rdoc +203 -37
- data/lib/pattern-match.rb +470 -119
- data/lib/pattern-match/version.rb +1 -1
- data/test/test_pattern-match.rb +435 -202
- metadata +9 -19
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a5dfb4c46f1d15a2d58257a25701821fb2346904
|
4
|
+
data.tar.gz: 65976df31a660ee698f2480a1532c4b51bb38f7c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fb509e20044ecfb152ae2211782d4c4fd0c3b39e31f9a9554b28f47c4e1fc9dbeb724f5cc17c3c60ac2c4c9e874648eebd2830db5d97761b8d1c96767ddaf879
|
7
|
+
data.tar.gz: 29110d2a035d427dcb48909f8bfd28353dd0b3dd6c0346438a5552761e2618bdf377dc8072ba4a578967a569d5481b64025bdac1780a6c73bedc9007faea7b85
|
data/.travis.yml
CHANGED
data/README.rdoc
CHANGED
@@ -3,6 +3,10 @@
|
|
3
3
|
A pattern matching library for Ruby.
|
4
4
|
|
5
5
|
== Installation
|
6
|
+
$ gem install pattern-match
|
7
|
+
|
8
|
+
or
|
9
|
+
|
6
10
|
$ git clone git://github.com/k-tsj/pattern-match.git
|
7
11
|
$ cd pattern-match
|
8
12
|
$ gem build pattern-match.gemspec
|
@@ -14,70 +18,232 @@ or
|
|
14
18
|
$ echo "gem 'pattern-match', :git => 'git://github.com/k-tsj/pattern-match.git'" > Gemfile
|
15
19
|
$ bundle install --path vendor/bundle
|
16
20
|
|
17
|
-
==
|
21
|
+
== Basic Usage
|
22
|
+
pattern-match library provides Kernel#match.
|
23
|
+
|
18
24
|
require 'pattern-match'
|
19
25
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
26
|
+
match(object) do
|
27
|
+
with(pattern[, guard]) do
|
28
|
+
...
|
29
|
+
end
|
30
|
+
with(pattern[, guard]) do
|
31
|
+
...
|
32
|
+
end
|
33
|
+
...
|
34
|
+
end
|
35
|
+
|
36
|
+
The patterns are run in sequence until the first one that matches.
|
37
|
+
|
38
|
+
If a pattern matches, a block passed to <code>with</code> is called and return its result.
|
39
|
+
If no pattern matches, a PatternMatch::NoMatchingPatternError exception is raised.
|
40
|
+
|
41
|
+
You can specify pattern guard if you want.
|
42
|
+
|
43
|
+
== Patterns
|
44
|
+
=== Value
|
45
|
+
An object (expect the instance of PatternMatch::Pattern) is a value pattern.
|
46
|
+
|
47
|
+
The pattern matches an object such that <code>pattern === object</code>.
|
48
|
+
|
49
|
+
match(0) do
|
50
|
+
with(Fixnum) { :match } #=> :match
|
51
|
+
end
|
52
|
+
|
53
|
+
If you want to use an another method of matching,
|
54
|
+
you have to use <code>_</code> as follows.
|
55
|
+
|
56
|
+
match(0) do
|
57
|
+
with(_(Fixnum, :==)) { :match }
|
58
|
+
end #=> NoMatchingPatternError
|
59
|
+
|
60
|
+
=== Deconstructor
|
61
|
+
A deconstructor pattern is (typically) of the form <code>deconstructor.([pattern, ...])</code>.
|
62
|
+
|
63
|
+
It is equivalent to Extractor in Scala.
|
64
|
+
|
65
|
+
Consider the following example:
|
66
|
+
|
67
|
+
match([0, 1]) do
|
68
|
+
with(Array.(0, 1)) { :match } #=> :match
|
69
|
+
end
|
70
|
+
|
71
|
+
match('ab') do
|
72
|
+
with(/(.)(.)/.('a', 'b')) { :match } #=> :match
|
73
|
+
end
|
74
|
+
|
75
|
+
Array, Regexp object(<code>/(.)(.)/</code>) are deconstructors.
|
76
|
+
You can use any object has the following features as deconstructor.
|
77
|
+
|
78
|
+
* PatternMatch::Deconstructable is included in a class of deconstructor
|
79
|
+
* Can be responded to <code>deconstruct</code> method
|
80
|
+
|
81
|
+
Note that <code>_[]</code> is provided as syntactic sugar for <code>Array.()</code>.
|
82
|
+
|
83
|
+
match([0, 1]) do
|
84
|
+
with(_[0, 1]) { :match } #=> :match
|
85
|
+
end
|
86
|
+
|
87
|
+
=== Variable
|
88
|
+
An identifier is a variable pattern.
|
89
|
+
|
90
|
+
It matches any value, and binds the variable name to that value.
|
91
|
+
A special case is the wild-card pattern <code>_</code> which matches any value,
|
92
|
+
and never binds.
|
93
|
+
|
94
|
+
match([0, 1]) do
|
95
|
+
with(_[a, b]) { [a, b] } #=> [0, 1]
|
96
|
+
end
|
97
|
+
|
98
|
+
match(0) do
|
99
|
+
with(_) { _ } #=> NameError
|
100
|
+
end
|
101
|
+
|
102
|
+
When several patterns with the same name occur in a single pattern,
|
103
|
+
all objects bound to variable must be equal.
|
104
|
+
|
105
|
+
match([0, 1]) do
|
106
|
+
with(_[a, a]) { a }
|
107
|
+
end #=> NoMatchingPatternError
|
108
|
+
|
109
|
+
=== And/Or/Not
|
110
|
+
<code>PatternMatch::Pattern#&</code>, <code>PatternMatch::Pattern#|</code>, <code>PatternMatch::Pattern#!@</code>,
|
111
|
+
<code>And</code>, <code>Or</code>, <code>Not</code> return and/or/not pattern.
|
112
|
+
|
113
|
+
match([0, [1]]) do
|
114
|
+
with(a & Finuxm, ! (_[2] | _[3])) { a } #=> 0
|
115
|
+
end
|
116
|
+
|
117
|
+
match(0) do
|
118
|
+
with(0 | 1 | 2) { } # (0 | 1 | 2) is evaluated to 3, so the pattern does not match.
|
119
|
+
with(Or(0, 1, 2)) { :match } #=> :match
|
120
|
+
end
|
121
|
+
|
122
|
+
=== Quantifier
|
123
|
+
<code>___</code>(triple underscore), <code>___?</code>,
|
124
|
+
<code>__n</code>(double underscore + n where n >= 0), <code>__n?</code> are quantifier patterns.
|
125
|
+
|
126
|
+
They are equivalent to <code>*</code>, <code>*?</code>,
|
127
|
+
<code>{n,}</code>, <code>{n,}?</code> in regular expression.
|
128
|
+
You can write as <code>*pattern</code> instead of <code>pattern, ___</code>.
|
129
|
+
|
130
|
+
match([:a, 0, :b, :c]) do
|
131
|
+
with(_[a & Symbol, ___, b & Fixnum, c & Symbol, ___]) do
|
132
|
+
a #=> [:a]
|
133
|
+
b #=> 0
|
134
|
+
c #=> [:b, :c]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
=== Sequence
|
139
|
+
<code>Seq</code> returns a sequence pattern.
|
140
|
+
|
141
|
+
It is equivalent to <code>()</code> in regular expression.
|
142
|
+
|
143
|
+
match([:a, 0, :b, 1]) do
|
144
|
+
with(_[Seq(a & Symbol, b & Fixnum), ___]) do
|
145
|
+
a #=> [:a, :b]
|
146
|
+
b #=> [0, 1]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
=== EXPERIMENTAL
|
151
|
+
* Object.()
|
152
|
+
* Matcher
|
153
|
+
* KeyMatcher
|
154
|
+
* Hash.()
|
155
|
+
* AttributeMatcher
|
156
|
+
|
157
|
+
See source code for more details.
|
158
|
+
|
159
|
+
== Pattern guard
|
160
|
+
Pattern guard can be specified as a second argument to <code>with</code>.
|
161
|
+
|
162
|
+
match([1, 2, 3, 4, 5]) do
|
163
|
+
with(_[*_, *a, *_], guard { a.inject(:*) == 12 }) do
|
164
|
+
a #=> [3, 4]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
== Examples
|
169
|
+
# (A)
|
37
170
|
Node = Struct.new(:left, :key, :right)
|
38
171
|
class R < Node; end
|
39
172
|
class B < Node; end
|
40
173
|
|
41
174
|
def balance(left, key, right)
|
42
|
-
match([left, key, right])
|
175
|
+
match([left, key, right]) do
|
43
176
|
with(_[R.(a, x, b), y, R.(c, z, d)]) { R[B[a, x, b], y, B[c, z, d]] }
|
44
177
|
with(_[R.(R.(a, x, b), y, c), z, d]) { R[B[a, x, b], y, B[c, z, d]] }
|
45
178
|
with(_[R.(a, x, R.(b, y, c)), z, d]) { R[B[a, x, b], y, B[c, z, d]] }
|
46
179
|
with(_[a, x, R.(b, y, R.(c, z, d))]) { R[B[a, x, b], y, B[c, z, d]] }
|
47
180
|
with(_[a, x, R.(R.(b, y, c), z, d)]) { R[B[a, x, b], y, B[c, z, d]] }
|
48
181
|
with(_) { B[left, key, right] }
|
49
|
-
|
182
|
+
end
|
50
183
|
end
|
51
184
|
|
52
|
-
|
185
|
+
# (B)
|
53
186
|
class EMail
|
54
187
|
def self.deconstruct(value)
|
55
|
-
value.to_s.split(/@/)
|
188
|
+
parts = value.to_s.split(/@/)
|
189
|
+
if parts.length == 2
|
190
|
+
parts
|
191
|
+
else
|
192
|
+
raise PatternMatch::PatternNotMatch
|
193
|
+
end
|
56
194
|
end
|
57
195
|
end
|
58
196
|
|
59
|
-
match(['foo-bar@example.com', 'baz-bar@example.com'])
|
60
|
-
with(_[mail & EMail.(name & /(\w+)-(\w+)/.(firstname, 'bar'), domain), ___])
|
61
|
-
|
62
|
-
|
63
|
-
|
197
|
+
match(['foo-bar@example.com', 'baz-bar@example.com']) do
|
198
|
+
with(_[mail & EMail.(name & /(\w+)-(\w+)/.(firstname, 'bar'), domain), ___]) do
|
199
|
+
mail #=> ["foo-bar@example.com", "baz-bar@example.com"]
|
200
|
+
name #=> ["foo-bar", "baz-bar"]
|
201
|
+
firstname #=> ["foo", "baz"]
|
202
|
+
domain #=> ["example.com", "example.com"]
|
203
|
+
end
|
204
|
+
end
|
64
205
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
206
|
+
# (C)
|
207
|
+
def replace_repeated(obj, &block)
|
208
|
+
ret = match(obj, &block)
|
209
|
+
if ret == obj
|
210
|
+
ret
|
211
|
+
else
|
212
|
+
replace_repeated(ret, &block)
|
213
|
+
end
|
214
|
+
rescue PatternMatch::NoMatchingPatternError
|
215
|
+
obj
|
216
|
+
end
|
217
|
+
|
218
|
+
replace_repeated([1, 2, 4, 4, 3, 3, 4, 0, 0]) do
|
219
|
+
with(_[*a, x, x, *b]) { [*a, x, *b] }
|
220
|
+
end #=> [1, 2, 4, 3, 4, 0]
|
221
|
+
|
222
|
+
# (D)
|
223
|
+
match({a: 0, b: 1}) do
|
224
|
+
with(Hash.(:a, b: Object.(:odd? => true))) do
|
225
|
+
a #=> 0
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
C = Struct.new(:a, :b) do
|
230
|
+
include PatternMatch::AttributeMatcher
|
231
|
+
end
|
232
|
+
match(C[0, 1]) do
|
233
|
+
with(C.(:b, a: 0)) do
|
234
|
+
b # => 1
|
235
|
+
end
|
236
|
+
end
|
72
237
|
|
73
|
-
|
238
|
+
== Reference
|
239
|
+
* {Pattern Matching in Ruby (at Sapporo RubyKaigi 2012) // Speaker Deck}[https://speakerdeck.com/k_tsj/patternmatchinginruby]
|
74
240
|
|
75
241
|
== Development
|
76
242
|
$ git clone git://github.com/k-tsj/pattern-match.git
|
77
243
|
$ cd pattern-match
|
78
244
|
$ gem install bundler (if you need)
|
79
245
|
$ bundle install --path vendor/bundle
|
80
|
-
$ rake test (or
|
81
|
-
$ rake build
|
246
|
+
$ bundle exec rake test (or "bundle exec rake")
|
247
|
+
$ bundle exec rake build
|
82
248
|
|
83
249
|
== Travis Build Status {<img src="https://secure.travis-ci.org/k-tsj/pattern-match.png"/>}[http://travis-ci.org/k-tsj/pattern-match]
|
data/lib/pattern-match.rb
CHANGED
@@ -8,13 +8,35 @@ module PatternMatch
|
|
8
8
|
module Deconstructable
|
9
9
|
def call(*subpatterns)
|
10
10
|
if Object == self
|
11
|
-
|
12
|
-
PatternObject.new(subpatterns[0])
|
13
|
-
elsif Hash == self
|
14
|
-
raise MalformedPatternError unless subpatterns.length == 1
|
15
|
-
PatternHash.new(subpatterns[0])
|
11
|
+
PatternKeywordArgStyleDeconstructor.new(Object, :respond_to?, :__send__, *subpatterns)
|
16
12
|
else
|
17
|
-
|
13
|
+
pattern_matcher(*subpatterns)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class ::Object
|
19
|
+
def pattern_matcher(*subpatterns)
|
20
|
+
PatternObjectDeconstructor.new(self, *subpatterns)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module AttributeMatcher
|
25
|
+
def self.included(klass)
|
26
|
+
class << klass
|
27
|
+
def pattern_matcher(*subpatterns)
|
28
|
+
PatternKeywordArgStyleDeconstructor.new(self, :respond_to?, :__send__, *subpatterns)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module KeyMatcher
|
35
|
+
def self.included(klass)
|
36
|
+
class << klass
|
37
|
+
def pattern_matcher(*subpatterns)
|
38
|
+
PatternKeywordArgStyleDeconstructor.new(self, :has_key?, :[], *subpatterns)
|
39
|
+
end
|
18
40
|
end
|
19
41
|
end
|
20
42
|
end
|
@@ -34,6 +56,10 @@ module PatternMatch
|
|
34
56
|
@subpatterns.map(&:vars).flatten
|
35
57
|
end
|
36
58
|
|
59
|
+
def ancestors
|
60
|
+
root? ? [self] : parent.ancestors.unshift(self)
|
61
|
+
end
|
62
|
+
|
37
63
|
def binding
|
38
64
|
vars.each_with_object({}) {|v, h| h[v.name] = v.val }
|
39
65
|
end
|
@@ -51,11 +77,19 @@ module PatternMatch
|
|
51
77
|
end
|
52
78
|
|
53
79
|
def to_a
|
54
|
-
[self, PatternQuantifier.new(0)]
|
80
|
+
[self, PatternQuantifier.new(0, true)]
|
81
|
+
end
|
82
|
+
|
83
|
+
def quantifier?
|
84
|
+
raise NotImplementedError
|
55
85
|
end
|
56
86
|
|
57
87
|
def quantified?
|
58
|
-
@next.
|
88
|
+
(@next and @next.quantifier?) || (root? ? false : @parent.quantified?)
|
89
|
+
end
|
90
|
+
|
91
|
+
def root
|
92
|
+
root? ? self : @parent.root
|
59
93
|
end
|
60
94
|
|
61
95
|
def root?
|
@@ -63,114 +97,205 @@ module PatternMatch
|
|
63
97
|
end
|
64
98
|
|
65
99
|
def validate
|
66
|
-
if root?
|
67
|
-
dup_vars = vars - vars.uniq {|i| i.name }
|
68
|
-
raise MalformedPatternError, "duplicate variables: #{dup_vars.map(&:name).join(', ')}" unless dup_vars.empty?
|
69
|
-
end
|
70
|
-
raise MalformedPatternError if @subpatterns.count {|i| i.kind_of?(PatternQuantifier) } > 1
|
71
100
|
@subpatterns.each(&:validate)
|
72
101
|
end
|
73
102
|
|
103
|
+
def match(vals)
|
104
|
+
if @next and @next.quantifier?
|
105
|
+
q = @next
|
106
|
+
repeating_match(vals, q.longest?) do |vs, rest|
|
107
|
+
if vs.length < q.min_k
|
108
|
+
next false
|
109
|
+
end
|
110
|
+
vs.all? {|v| yield(v) } and q.match(rest)
|
111
|
+
end
|
112
|
+
else
|
113
|
+
if vals.empty?
|
114
|
+
return false
|
115
|
+
end
|
116
|
+
val, *rest = vals
|
117
|
+
yield(val) and (@next ? @next.match(rest) : rest.empty?)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def append(pattern)
|
122
|
+
if @next
|
123
|
+
@next.append(pattern)
|
124
|
+
else
|
125
|
+
if @subpatterns.empty?
|
126
|
+
if root?
|
127
|
+
new_root = PatternAnd.new(self)
|
128
|
+
self.parent = new_root
|
129
|
+
end
|
130
|
+
pattern.parent = @parent
|
131
|
+
@next = pattern
|
132
|
+
else
|
133
|
+
@subpatterns[-1].append(pattern)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
74
138
|
private
|
75
139
|
|
140
|
+
def repeating_match(vals, longest)
|
141
|
+
quantifier = @next
|
142
|
+
lp = longest_patterns(vals)
|
143
|
+
(longest ? lp : lp.reverse).each do |(vs, rest)|
|
144
|
+
vars.each {|i| i.set_bind_to(quantifier) }
|
145
|
+
begin
|
146
|
+
if yield vs, rest
|
147
|
+
return true
|
148
|
+
end
|
149
|
+
rescue PatternNotMatch
|
150
|
+
end
|
151
|
+
vars.each {|i| i.unset_bind_to(quantifier) }
|
152
|
+
end
|
153
|
+
false
|
154
|
+
end
|
155
|
+
|
156
|
+
def longest_patterns(vals)
|
157
|
+
vals.length.downto(0).map do |n|
|
158
|
+
[vals.take(n), vals.drop(n)]
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
76
162
|
def set_subpatterns_relation
|
77
163
|
@subpatterns.each do |i|
|
78
164
|
i.parent = self
|
79
165
|
end
|
80
|
-
|
81
|
-
|
82
|
-
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
class PatternQuantifier < Pattern
|
170
|
+
attr_reader :min_k
|
171
|
+
|
172
|
+
def initialize(min_k, longest)
|
173
|
+
super()
|
174
|
+
@min_k = min_k
|
175
|
+
@longest = longest
|
176
|
+
end
|
177
|
+
|
178
|
+
def validate
|
179
|
+
super
|
180
|
+
raise MalformedPatternError unless @prev and ! @prev.quantifier?
|
181
|
+
seqs = ancestors.grep(PatternSequence).reverse
|
182
|
+
if seqs.any? {|i| i.next and i.next.quantifier? and not i.vars.empty? }
|
183
|
+
raise NotImplementedError
|
184
|
+
end
|
185
|
+
case @parent
|
186
|
+
when PatternObjectDeconstructor
|
187
|
+
# do nothing
|
188
|
+
when PatternSequence
|
189
|
+
# do nothing
|
190
|
+
else
|
191
|
+
raise MalformedPatternError
|
83
192
|
end
|
84
193
|
end
|
85
194
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
195
|
+
def quantifier?
|
196
|
+
true
|
197
|
+
end
|
198
|
+
|
199
|
+
def match(vals)
|
200
|
+
if @next
|
201
|
+
@next.match(vals)
|
202
|
+
else
|
203
|
+
vals.empty?
|
92
204
|
end
|
93
|
-
ary
|
94
205
|
end
|
95
|
-
end
|
96
206
|
|
97
|
-
|
98
|
-
|
99
|
-
super(*spec.values)
|
100
|
-
@spec = spec.map {|k, pat| [k.to_proc, pat] }
|
101
|
-
rescue
|
102
|
-
raise MalformedPatternError
|
207
|
+
def longest?
|
208
|
+
@longest
|
103
209
|
end
|
104
210
|
|
105
|
-
def
|
106
|
-
|
211
|
+
def inspect
|
212
|
+
"#<#{self.class.name}: min_k=#{@min_k}, longest=#{@longest}>"
|
107
213
|
end
|
108
214
|
end
|
109
215
|
|
110
|
-
class
|
111
|
-
def
|
112
|
-
|
113
|
-
@spec = spec
|
216
|
+
class PatternElement < Pattern
|
217
|
+
def quantifier?
|
218
|
+
false
|
114
219
|
end
|
220
|
+
end
|
115
221
|
|
116
|
-
|
117
|
-
raise PatternNotMatch unless val.kind_of?(Hash)
|
118
|
-
raise PatternNotMatch unless @spec.keys.all? {|k| val.has_key?(k) }
|
119
|
-
@spec.all? {|k, pat| pat.match(val[k]) rescue raise PatternNotMatch }
|
120
|
-
end
|
222
|
+
class PatternDeconstructor < PatternElement
|
121
223
|
end
|
122
224
|
|
123
|
-
class
|
225
|
+
class PatternObjectDeconstructor < PatternDeconstructor
|
124
226
|
def initialize(deconstructor, *subpatterns)
|
125
227
|
super(*subpatterns)
|
126
228
|
@deconstructor = deconstructor
|
127
229
|
end
|
128
230
|
|
129
|
-
def match(
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
return false unless quantifier.min_k <= k
|
135
|
-
else
|
136
|
-
return false unless @subpatterns.length == deconstructed_vals.length
|
137
|
-
end
|
138
|
-
@subpatterns.flat_map do |pat|
|
139
|
-
case
|
140
|
-
when pat.next.kind_of?(PatternQuantifier)
|
141
|
-
[]
|
142
|
-
when pat.kind_of?(PatternQuantifier)
|
143
|
-
pat.prev.vars.each {|v| v.set_bind_to(pat) }
|
144
|
-
Array.new(k, pat.prev)
|
145
|
-
else
|
146
|
-
[pat]
|
231
|
+
def match(vals)
|
232
|
+
super do |val|
|
233
|
+
deconstructed_vals = @deconstructor.deconstruct(val)
|
234
|
+
if @subpatterns.empty?
|
235
|
+
next deconstructed_vals.empty?
|
147
236
|
end
|
148
|
-
|
149
|
-
|
237
|
+
@subpatterns[0].match(deconstructed_vals)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def inspect
|
242
|
+
"#<#{self.class.name}: deconstructor=#{@deconstructor.inspect}, subpatterns=#{@subpatterns.inspect}>"
|
243
|
+
end
|
244
|
+
|
245
|
+
private
|
246
|
+
|
247
|
+
def set_subpatterns_relation
|
248
|
+
super
|
249
|
+
@subpatterns.each_cons(2) do |a, b|
|
250
|
+
a.next = b
|
251
|
+
b.prev = a
|
150
252
|
end
|
151
253
|
end
|
152
254
|
end
|
153
255
|
|
154
|
-
class
|
155
|
-
|
256
|
+
class PatternKeywordArgStyleDeconstructor < PatternDeconstructor
|
257
|
+
def initialize(klass, checker, getter, *keyarg_subpatterns)
|
258
|
+
spec = normalize_keyword_arg(keyarg_subpatterns)
|
259
|
+
super(*spec.values)
|
260
|
+
@klass = klass
|
261
|
+
@checker = checker
|
262
|
+
@getter = getter
|
263
|
+
@spec = spec
|
264
|
+
end
|
156
265
|
|
157
|
-
def
|
158
|
-
super
|
159
|
-
|
266
|
+
def match(vals)
|
267
|
+
super do |val|
|
268
|
+
next false unless val.kind_of?(@klass)
|
269
|
+
next false unless @spec.keys.all? {|k| val.__send__(@checker, k) }
|
270
|
+
@spec.all? do |k, pat|
|
271
|
+
pat.match([val.__send__(@getter, k)]) rescue false
|
272
|
+
end
|
273
|
+
end
|
160
274
|
end
|
161
275
|
|
162
|
-
def
|
163
|
-
|
276
|
+
def inspect
|
277
|
+
"#<#{self.class.name}: klass=#{@klass.inspect}, spec=#{@spec.inspect}>"
|
164
278
|
end
|
165
279
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
280
|
+
private
|
281
|
+
|
282
|
+
def normalize_keyword_arg(subpatterns)
|
283
|
+
syms = subpatterns.take_while {|i| i.kind_of?(Symbol) }
|
284
|
+
rest = subpatterns.drop(syms.length)
|
285
|
+
hash = case rest.length
|
286
|
+
when 0
|
287
|
+
{}
|
288
|
+
when 1
|
289
|
+
rest[0]
|
290
|
+
else
|
291
|
+
raise MalformedPatternError
|
292
|
+
end
|
293
|
+
variables = Hash[syms.map {|i, h| [i, PatternVariable.new(i)] }]
|
294
|
+
Hash[variables.merge(hash).map {|k, v| [k, v.kind_of?(Pattern) ? v : PatternValue.new(v)] }]
|
170
295
|
end
|
171
296
|
end
|
172
297
|
|
173
|
-
class PatternVariable <
|
298
|
+
class PatternVariable < PatternElement
|
174
299
|
attr_reader :name, :val
|
175
300
|
|
176
301
|
def initialize(name)
|
@@ -180,9 +305,11 @@ module PatternMatch
|
|
180
305
|
@bind_to = nil
|
181
306
|
end
|
182
307
|
|
183
|
-
def match(
|
184
|
-
|
185
|
-
|
308
|
+
def match(vals)
|
309
|
+
super do |val|
|
310
|
+
bind(val)
|
311
|
+
true
|
312
|
+
end
|
186
313
|
end
|
187
314
|
|
188
315
|
def vars
|
@@ -190,18 +317,37 @@ module PatternMatch
|
|
190
317
|
end
|
191
318
|
|
192
319
|
def set_bind_to(quantifier)
|
193
|
-
|
320
|
+
n = nest_level(quantifier)
|
321
|
+
if n == 0
|
322
|
+
@val = @bind_to = []
|
323
|
+
else
|
194
324
|
outer = @val
|
195
|
-
(
|
325
|
+
(n - 1).times do
|
196
326
|
outer = outer[-1]
|
197
327
|
end
|
198
328
|
@bind_to = []
|
199
329
|
outer << @bind_to
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
def unset_bind_to(quantifier)
|
334
|
+
n = nest_level(quantifier)
|
335
|
+
@bind_to = nil
|
336
|
+
if n == 0
|
337
|
+
# do nothing
|
200
338
|
else
|
201
|
-
|
339
|
+
outer = @val
|
340
|
+
(n - 1).times do
|
341
|
+
outer = outer[-1]
|
342
|
+
end
|
343
|
+
outer.pop
|
202
344
|
end
|
203
345
|
end
|
204
346
|
|
347
|
+
def inspect
|
348
|
+
"#<#{self.class.name}: name=#{name.inspect}, val=#{@val.inspect}>"
|
349
|
+
end
|
350
|
+
|
205
351
|
private
|
206
352
|
|
207
353
|
def bind(val)
|
@@ -213,56 +359,220 @@ module PatternMatch
|
|
213
359
|
end
|
214
360
|
|
215
361
|
def nest_level(quantifier)
|
216
|
-
|
362
|
+
raise PatternMatchError unless quantifier.kind_of?(PatternQuantifier)
|
363
|
+
qs = ancestors.map {|i| (i.next and i.next.quantifier?) ? i.next : nil }.find_all {|i| i }.reverse
|
217
364
|
qs.index(quantifier) || (raise PatternMatchError)
|
218
365
|
end
|
219
366
|
end
|
220
367
|
|
221
|
-
class PatternValue <
|
368
|
+
class PatternValue < PatternElement
|
222
369
|
def initialize(val, compare_by = :===)
|
223
370
|
super()
|
224
371
|
@val = val
|
225
372
|
@compare_by = compare_by
|
226
373
|
end
|
227
374
|
|
228
|
-
def match(
|
229
|
-
|
375
|
+
def match(vals)
|
376
|
+
super do |val|
|
377
|
+
@val.__send__(@compare_by, val)
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
def inspect
|
382
|
+
"#<#{self.class.name}: val=#{@val.inspect}>"
|
230
383
|
end
|
231
384
|
end
|
232
385
|
|
233
|
-
class
|
234
|
-
|
235
|
-
|
386
|
+
class PatternSequence < PatternElement
|
387
|
+
class PatternRewind < PatternElement
|
388
|
+
attr_reader :ntimes
|
389
|
+
|
390
|
+
def initialize(ntimes, head_pattern, next_pattern)
|
391
|
+
super()
|
392
|
+
@ntimes = ntimes
|
393
|
+
@head = head_pattern
|
394
|
+
@next = next_pattern
|
395
|
+
end
|
396
|
+
|
397
|
+
def match(vals)
|
398
|
+
if @ntimes > 0
|
399
|
+
@ntimes -= 1
|
400
|
+
@head.match(vals)
|
401
|
+
else
|
402
|
+
@next ? @next.match(vals) : vals.empty?
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
def inspect
|
407
|
+
"#<#{self.class.name}: ntimes=#{@ntimes} head=#{@head.inspect} next=#{@next.inspect}>"
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
def match(vals)
|
412
|
+
if @next and @next.quantifier?
|
413
|
+
repeating_match(vals, @next.longest?) do |rewind|
|
414
|
+
if rewind.ntimes < @next.min_k
|
415
|
+
next false
|
416
|
+
end
|
417
|
+
rewind.match(vals)
|
418
|
+
end
|
419
|
+
else
|
420
|
+
with_rewind(make_rewind(1)) do |rewind|
|
421
|
+
rewind.match(vals)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
def validate
|
427
|
+
super
|
428
|
+
if @subpatterns.empty?
|
429
|
+
raise MalformedPatternError
|
430
|
+
end
|
431
|
+
case @parent
|
432
|
+
when PatternObjectDeconstructor
|
433
|
+
# do nothing
|
434
|
+
when PatternSequence
|
435
|
+
# do nothing
|
436
|
+
else
|
437
|
+
raise MalformedPatternError
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
def inspect
|
442
|
+
"#<#{self.class.name}: subpatterns=#{@subpatterns.inspect}>"
|
443
|
+
end
|
444
|
+
|
445
|
+
private
|
446
|
+
|
447
|
+
def make_rewind(n)
|
448
|
+
PatternRewind.new(n, @subpatterns[0], (@next and @next.quantifier?) ? @next.next : @next)
|
449
|
+
end
|
450
|
+
|
451
|
+
def repeating_match(vals, longest)
|
452
|
+
quantifier = @next
|
453
|
+
lp = longest_patterns(vals)
|
454
|
+
(longest ? lp : lp.reverse).each do |rewind|
|
455
|
+
vars.each {|i| i.set_bind_to(quantifier) }
|
456
|
+
begin
|
457
|
+
with_rewind(rewind) do |rewind|
|
458
|
+
if yield rewind
|
459
|
+
return true
|
460
|
+
end
|
461
|
+
end
|
462
|
+
rescue PatternNotMatch
|
463
|
+
end
|
464
|
+
vars.each {|i| i.unset_bind_to(quantifier) }
|
465
|
+
end
|
466
|
+
false
|
467
|
+
end
|
468
|
+
|
469
|
+
def longest_patterns(vals)
|
470
|
+
vals.length.downto(0).map do |n|
|
471
|
+
make_rewind(n)
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
def with_rewind(rewind)
|
476
|
+
@subpatterns[-1].next = rewind
|
477
|
+
yield rewind
|
478
|
+
ensure
|
479
|
+
@subpatterns[-1].next = nil
|
480
|
+
end
|
481
|
+
|
482
|
+
def set_subpatterns_relation
|
483
|
+
super
|
484
|
+
@subpatterns.each_cons(2) do |a, b|
|
485
|
+
a.next = b
|
486
|
+
b.prev = a
|
487
|
+
end
|
236
488
|
end
|
237
489
|
end
|
238
490
|
|
239
|
-
class
|
240
|
-
def match(
|
241
|
-
|
491
|
+
class PatternAnd < PatternElement
|
492
|
+
def match(vals)
|
493
|
+
super do |val|
|
494
|
+
@subpatterns.all? {|i| i.match([val]) }
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
def validate
|
499
|
+
super
|
500
|
+
raise MalformedPatternError if @subpatterns.empty?
|
501
|
+
end
|
502
|
+
|
503
|
+
def inspect
|
504
|
+
"#<#{self.class.name}: subpatterns=#{@subpatterns.inspect}>"
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
class PatternOr < PatternElement
|
509
|
+
def match(vals)
|
510
|
+
super do |val|
|
511
|
+
@subpatterns.find do |i|
|
512
|
+
begin
|
513
|
+
i.match([val])
|
514
|
+
rescue PatternNotMatch
|
515
|
+
false
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
def validate
|
522
|
+
super
|
523
|
+
raise MalformedPatternError if @subpatterns.empty?
|
524
|
+
raise MalformedPatternError unless vars.empty?
|
525
|
+
end
|
526
|
+
|
527
|
+
def inspect
|
528
|
+
"#<#{self.class.name}: subpatterns=#{@subpatterns.inspect}>"
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
class PatternNot < PatternElement
|
533
|
+
def match(vals)
|
534
|
+
super do |val|
|
242
535
|
begin
|
243
|
-
|
536
|
+
! @subpatterns[0].match([val])
|
244
537
|
rescue PatternNotMatch
|
245
|
-
|
538
|
+
true
|
246
539
|
end
|
247
540
|
end
|
248
541
|
end
|
249
542
|
|
250
543
|
def validate
|
251
544
|
super
|
252
|
-
raise MalformedPatternError unless
|
545
|
+
raise MalformedPatternError unless @subpatterns.length == 1
|
546
|
+
raise MalformedPatternError unless vars.empty?
|
547
|
+
end
|
548
|
+
|
549
|
+
def inspect
|
550
|
+
"#<#{self.class.name}: subpatterns=#{@subpatterns.inspect}>"
|
253
551
|
end
|
254
552
|
end
|
255
553
|
|
256
|
-
class
|
257
|
-
def
|
258
|
-
|
259
|
-
|
260
|
-
|
554
|
+
class PatternCondition < PatternElement
|
555
|
+
def initialize(&condition)
|
556
|
+
super()
|
557
|
+
@condition = condition
|
558
|
+
end
|
559
|
+
|
560
|
+
def match(vals)
|
561
|
+
return false unless vals.empty?
|
562
|
+
if @condition.call
|
563
|
+
@next ? @next.match(vals) : true
|
564
|
+
else
|
565
|
+
false
|
566
|
+
end
|
261
567
|
end
|
262
568
|
|
263
569
|
def validate
|
264
570
|
super
|
265
|
-
raise MalformedPatternError
|
571
|
+
raise MalformedPatternError if ancestors.find {|i| i.next and ! i.next.kind_of?(PatternCondition) }
|
572
|
+
end
|
573
|
+
|
574
|
+
def inspect
|
575
|
+
"#<#{self.class.name}: condition=#{@condition.inspect}>"
|
266
576
|
end
|
267
577
|
end
|
268
578
|
|
@@ -275,10 +585,28 @@ module PatternMatch
|
|
275
585
|
private
|
276
586
|
|
277
587
|
def with(pat_or_val, guard_proc = nil, &block)
|
588
|
+
ctx = @ctx
|
278
589
|
pat = pat_or_val.kind_of?(Pattern) ? pat_or_val : PatternValue.new(pat_or_val)
|
590
|
+
pat.append(
|
591
|
+
PatternCondition.new do
|
592
|
+
pat.vars.each_with_object({}) do |v, h|
|
593
|
+
if h.has_key?(v.name)
|
594
|
+
unless h[v.name] == v.val
|
595
|
+
::Kernel.raise PatternNotMatch
|
596
|
+
end
|
597
|
+
else
|
598
|
+
h[v.name] = v.val
|
599
|
+
end
|
600
|
+
end
|
601
|
+
true
|
602
|
+
end
|
603
|
+
)
|
604
|
+
if guard_proc
|
605
|
+
pat.append(PatternCondition.new { with_tmpbinding(ctx, pat.binding, &guard_proc) })
|
606
|
+
end
|
279
607
|
pat.validate
|
280
|
-
if pat.match(@val)
|
281
|
-
ret = with_tmpbinding(
|
608
|
+
if pat.match([@val])
|
609
|
+
ret = with_tmpbinding(ctx, pat.binding, &block)
|
282
610
|
::Kernel.throw(:exit_match, ret)
|
283
611
|
else
|
284
612
|
nil
|
@@ -290,12 +618,18 @@ module PatternMatch
|
|
290
618
|
block
|
291
619
|
end
|
292
620
|
|
621
|
+
def ___
|
622
|
+
PatternQuantifier.new(0, true)
|
623
|
+
end
|
624
|
+
|
625
|
+
def ___?
|
626
|
+
PatternQuantifier.new(0, false)
|
627
|
+
end
|
628
|
+
|
293
629
|
def method_missing(name, *)
|
294
630
|
case name.to_s
|
295
|
-
when
|
296
|
-
PatternQuantifier.new(
|
297
|
-
when /\A__(\d+)\z/
|
298
|
-
PatternQuantifier.new($1.to_i)
|
631
|
+
when /\A__(\d+)(\??)\z/
|
632
|
+
PatternQuantifier.new($1.to_i, ! $2.empty?)
|
299
633
|
else
|
300
634
|
PatternVariable.new(name)
|
301
635
|
end
|
@@ -310,13 +644,14 @@ module PatternMatch
|
|
310
644
|
Array.call(*args)
|
311
645
|
end
|
312
646
|
|
313
|
-
def match(val)
|
314
|
-
true
|
315
|
-
end
|
316
|
-
|
317
647
|
def vars
|
318
648
|
[]
|
319
649
|
end
|
650
|
+
|
651
|
+
private
|
652
|
+
|
653
|
+
def bind(val)
|
654
|
+
end
|
320
655
|
end
|
321
656
|
uscore
|
322
657
|
when 1
|
@@ -331,6 +666,26 @@ module PatternMatch
|
|
331
666
|
alias __ _
|
332
667
|
alias _l _
|
333
668
|
|
669
|
+
def Seq(*subpatterns)
|
670
|
+
PatternSequence.new(*subpatterns)
|
671
|
+
end
|
672
|
+
|
673
|
+
def And(*subpatterns)
|
674
|
+
PatternAnd.new(*subpatterns)
|
675
|
+
end
|
676
|
+
|
677
|
+
def Or(*subpatterns)
|
678
|
+
PatternOr.new(*subpatterns)
|
679
|
+
end
|
680
|
+
|
681
|
+
def Not(*subpatterns)
|
682
|
+
PatternNot.new(*subpatterns)
|
683
|
+
end
|
684
|
+
|
685
|
+
|
686
|
+
class TmpBindingModule < ::Module
|
687
|
+
end
|
688
|
+
|
334
689
|
def with_tmpbinding(obj, binding, &block)
|
335
690
|
tmpbinding_module(obj).instance_eval do
|
336
691
|
begin
|
@@ -345,7 +700,8 @@ module PatternMatch
|
|
345
700
|
obj.instance_eval(&block)
|
346
701
|
ensure
|
347
702
|
binding.each do |name, _|
|
348
|
-
|
703
|
+
@stacks[name].pop
|
704
|
+
if @stacks[name].empty?
|
349
705
|
remove_method(name)
|
350
706
|
end
|
351
707
|
end
|
@@ -353,9 +709,6 @@ module PatternMatch
|
|
353
709
|
end
|
354
710
|
end
|
355
711
|
|
356
|
-
class TmpBindingModule < ::Module
|
357
|
-
end
|
358
|
-
|
359
712
|
def tmpbinding_module(obj)
|
360
713
|
m = obj.singleton_class.ancestors.find {|i| i.kind_of?(TmpBindingModule) }
|
361
714
|
unless m
|
@@ -429,6 +782,10 @@ class Class
|
|
429
782
|
end
|
430
783
|
end
|
431
784
|
|
785
|
+
class Hash
|
786
|
+
include PatternMatch::KeyMatcher
|
787
|
+
end
|
788
|
+
|
432
789
|
class << Array
|
433
790
|
def deconstruct(val)
|
434
791
|
accept_self_instance_only(val)
|
@@ -473,9 +830,3 @@ class Regexp
|
|
473
830
|
m.captures.empty? ? [m[0]] : m.captures
|
474
831
|
end
|
475
832
|
end
|
476
|
-
|
477
|
-
class Symbol
|
478
|
-
def call(*args)
|
479
|
-
Proc.new {|obj| obj.__send__(self, *args) }
|
480
|
-
end
|
481
|
-
end
|