pattern-match 0.1.2 → 0.5.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 +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
|