filigree 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +5 -1
- data/lib/filigree/abstract_class.rb +4 -4
- data/lib/filigree/match.rb +140 -144
- data/lib/filigree/version.rb +5 -5
- data/lib/filigree/visitor.rb +20 -10
- data/test/tc_match.rb +6 -4
- data/test/tc_visitor.rb +11 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 178a6b1f4c1c7e4fa5572856e84f8e42e61ef423
|
4
|
+
data.tar.gz: 0b4d734851d85411da754286fbe7562dd894e8a7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 37ed52f1a1780895bfdb255625f9edd5a649c96595395851a1d99ae79ba695e7f45f4e5d443452342ce3aa77db665b24abf9a71c67579233bf806ce3177e41c0
|
7
|
+
data.tar.gz: 67ded0ee569137d4dd0b00d99e753af2db406beff41f8a6994b76bfff52521b4b7f08316c4d3ff3128f0180b7b1cea91aa2c1cbd1e7900278a2c46d5e7ae242b
|
data/README.md
CHANGED
@@ -164,7 +164,11 @@ Filigree's implementation of the visitor pattern is built on the pattern matchin
|
|
164
164
|
```Ruby
|
165
165
|
class Binary < Struct.new(:x, :y)
|
166
166
|
extend Filigree::Destructurable
|
167
|
-
include Filigree::
|
167
|
+
include Filigree::Visitable
|
168
|
+
|
169
|
+
def children
|
170
|
+
[x, y]
|
171
|
+
end
|
168
172
|
|
169
173
|
def destructure(_)
|
170
174
|
[x, y]
|
@@ -1,7 +1,7 @@
|
|
1
|
-
# Author:
|
2
|
-
# Project:
|
3
|
-
# Date:
|
4
|
-
# Description:
|
1
|
+
# Author: Chris Wailes <chris.wailes@gmail.com>
|
2
|
+
# Project: Filigree
|
3
|
+
# Date: 2013/4/19
|
4
|
+
# Description: An implementation of an AbstractClass module.
|
5
5
|
|
6
6
|
############
|
7
7
|
# Requires #
|
data/lib/filigree/match.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
# Author:
|
2
|
-
# Project:
|
3
|
-
# Date:
|
4
|
-
# Description:
|
1
|
+
# Author: Chris Wailes <chris.wailes@gmail.com>
|
2
|
+
# Project: Filigree
|
3
|
+
# Date: 2013/05/04
|
4
|
+
# Description: Pattern matching for Ruby.
|
5
5
|
|
6
6
|
############
|
7
7
|
# Requires #
|
@@ -22,146 +22,6 @@ require 'filigree/class'
|
|
22
22
|
# An error that indicates that no pattern matched a given object.
|
23
23
|
class MatchError < RuntimeError; end
|
24
24
|
|
25
|
-
###########
|
26
|
-
# Methods #
|
27
|
-
###########
|
28
|
-
|
29
|
-
# This is an implementation of pattern matching. The objects passed to match
|
30
|
-
# are tested against the patterns defined inside the match block. The return
|
31
|
-
# value of `match` will be the result of evaluating the block given to `with`.
|
32
|
-
|
33
|
-
# The most basic pattern is the literal. Here, the object or objects being
|
34
|
-
# matched will be tested for equality with value passed to `with`. In the
|
35
|
-
# example below, the call to `match` will return `:one`. Similar to the
|
36
|
-
# literal pattern is the wildcard pattern `_`. This will match any object.
|
37
|
-
|
38
|
-
# You may also match against variables. This can sometimes conflict with the
|
39
|
-
# next kind of pattern, which is a binding pattern. Here, the pattern will
|
40
|
-
# match any object, and then make the object it matched available to the with
|
41
|
-
# block via an attribute reader. This is accomplished using the method_missing
|
42
|
-
# callback, so if there is a variable or function with that name you might
|
43
|
-
# accidentally compare against a variable. To bind to a name that is already
|
44
|
-
# in scope you can use the {Filigree::MatchEnvironment#Bind} method. In
|
45
|
-
# addition, class and destructuring pattern results (see bellow) can be bound
|
46
|
-
# to a variable by using the {Filigree::BasicPattern#as} method.
|
47
|
-
|
48
|
-
# If you wish to match string patterns you may use regular expressions. Any
|
49
|
-
# object that isn't a string will fail to match against a regular expression.
|
50
|
-
# If the object being matched is a string then the regular expressions `match?`
|
51
|
-
# method is used. The result of the regular expression match is available
|
52
|
-
# inside the with block via the match_data accessor.
|
53
|
-
|
54
|
-
# When a class is used in a pattern it will match any object that is an
|
55
|
-
# instance of that class. If you wish to compare one regular expression to
|
56
|
-
# another, or one class to another, you can force the comparison using the
|
57
|
-
# {Filigree::MatchEnvironment#Literal} method.
|
58
|
-
#
|
59
|
-
# Destructuring patterns allow you to match against an instance of a class,
|
60
|
-
# while simultaneously binding values stored inside the object to variables
|
61
|
-
# in the context of the with block. A class that is destructurable must
|
62
|
-
# include the {Filigree::Destructurable} module. You can then destructure an
|
63
|
-
# object as shown bellow.
|
64
|
-
|
65
|
-
# Both `match` and `with` can take multiple arguments. When this happens, each
|
66
|
-
# object is paired up with the corresponding pattern. If they all match, then
|
67
|
-
# the `with` clause matches. In this way you can match against tuples.
|
68
|
-
|
69
|
-
# Any with clause can be given a guard clause by passing a lambda as the last
|
70
|
-
# argument to `with`. These are evaluated after the pattern is matched, and
|
71
|
-
# any bindings made in the pattern are available to the guard clause.
|
72
|
-
|
73
|
-
# If you wish to evaluate the same body on matching any of several patterns you
|
74
|
-
# may list them in order and then specify the body for the last pattern in the
|
75
|
-
# group.
|
76
|
-
|
77
|
-
# Patterns are evaluated in the order in which they are defined and the first
|
78
|
-
# pattern to match is the one chosen. You may define helper methods inside the
|
79
|
-
# match block. They will be re-defined every time the match statement is
|
80
|
-
# evaluated, so you should move any definitions outside any match calls that
|
81
|
-
# are being evaluated often.
|
82
|
-
|
83
|
-
# @example The literal pattern
|
84
|
-
# def foo(n)
|
85
|
-
# match 1 do
|
86
|
-
# with(1) { :one }
|
87
|
-
# with(2) { :two }
|
88
|
-
# with(_) { :other }
|
89
|
-
# end
|
90
|
-
# end
|
91
|
-
#
|
92
|
-
# foo(1)
|
93
|
-
|
94
|
-
# @example Matching against variables
|
95
|
-
# var = 42
|
96
|
-
# match 42 do
|
97
|
-
# with(var) { :hoopy }
|
98
|
-
# with(0) { :zero }
|
99
|
-
# end
|
100
|
-
|
101
|
-
# @example Binding patterns
|
102
|
-
# # Returns 42
|
103
|
-
# match 42 do
|
104
|
-
# with(x) { x }
|
105
|
-
# end
|
106
|
-
|
107
|
-
# x = 3
|
108
|
-
# # Returns 42
|
109
|
-
# match 42 do
|
110
|
-
# with(Bind(:x)) { x }
|
111
|
-
# with(42) { :hoopy }
|
112
|
-
# end
|
113
|
-
|
114
|
-
# @example Regular expression and class instance pattern
|
115
|
-
# def matcher(object)
|
116
|
-
# match object do
|
117
|
-
# with(/hoopy/) { 42 }
|
118
|
-
# with(Integer) { 'hoopy' }
|
119
|
-
# end
|
120
|
-
# end
|
121
|
-
|
122
|
-
# # Returns 42
|
123
|
-
# matcher('hoopy')
|
124
|
-
# # Returns 'hoopy'
|
125
|
-
# matcher(42)
|
126
|
-
|
127
|
-
# @example Destructuring an object
|
128
|
-
# class Foo
|
129
|
-
# include Filigree::Destructurable
|
130
|
-
# def initialize(a, b)
|
131
|
-
# @a = a
|
132
|
-
# @b = b
|
133
|
-
# end
|
134
|
-
#
|
135
|
-
# def destructure(_)
|
136
|
-
# [@a, @b]
|
137
|
-
# end
|
138
|
-
# end
|
139
|
-
|
140
|
-
# # Returns true
|
141
|
-
# match Foo.new(4, 2) do
|
142
|
-
# with(Foo.(4, 2)) { true }
|
143
|
-
# with(_) { false }
|
144
|
-
# end
|
145
|
-
|
146
|
-
# @example Using guard clauses
|
147
|
-
# match o do
|
148
|
-
# with(n, -> { n < 0 }) { :NEG }
|
149
|
-
# with(0) { :ZERO }
|
150
|
-
# with(n, -> { n > 0 }) { :POS }
|
151
|
-
# end
|
152
|
-
#
|
153
|
-
# @param [Object] objects Objects to be matched
|
154
|
-
# @param [Proc] block Block containing with clauses.
|
155
|
-
#
|
156
|
-
# @return [Object] Result of evaluating the matched pattern's block
|
157
|
-
def match(*objects, &block)
|
158
|
-
me = Filigree::MatchEnvironment.new
|
159
|
-
|
160
|
-
me.instance_exec &block
|
161
|
-
|
162
|
-
me.find_match(objects)
|
163
|
-
end
|
164
|
-
|
165
25
|
#######################
|
166
26
|
# Classes and Modules #
|
167
27
|
#######################
|
@@ -172,6 +32,142 @@ module Filigree
|
|
172
32
|
# Methods #
|
173
33
|
###########
|
174
34
|
|
35
|
+
# This is an implementation of pattern matching. The objects passed to match
|
36
|
+
# are tested against the patterns defined inside the match block. The return
|
37
|
+
# value of `match` will be the result of evaluating the block given to `with`.
|
38
|
+
|
39
|
+
# The most basic pattern is the literal. Here, the object or objects being
|
40
|
+
# matched will be tested for equality with value passed to `with`. In the
|
41
|
+
# example below, the call to `match` will return `:one`. Similar to the
|
42
|
+
# literal pattern is the wildcard pattern `_`. This will match any object.
|
43
|
+
|
44
|
+
# You may also match against variables. This can sometimes conflict with the
|
45
|
+
# next kind of pattern, which is a binding pattern. Here, the pattern will
|
46
|
+
# match any object, and then make the object it matched available to the with
|
47
|
+
# block via an attribute reader. This is accomplished using the method_missing
|
48
|
+
# callback, so if there is a variable or function with that name you might
|
49
|
+
# accidentally compare against a variable. To bind to a name that is already
|
50
|
+
# in scope you can use the {Filigree::MatchEnvironment#Bind} method. In
|
51
|
+
# addition, class and destructuring pattern results (see bellow) can be bound
|
52
|
+
# to a variable by using the {Filigree::BasicPattern#as} method.
|
53
|
+
|
54
|
+
# If you wish to match string patterns you may use regular expressions. Any
|
55
|
+
# object that isn't a string will fail to match against a regular expression.
|
56
|
+
# If the object being matched is a string then the regular expressions `match?`
|
57
|
+
# method is used. The result of the regular expression match is available
|
58
|
+
# inside the with block via the match_data accessor.
|
59
|
+
|
60
|
+
# When a class is used in a pattern it will match any object that is an
|
61
|
+
# instance of that class. If you wish to compare one regular expression to
|
62
|
+
# another, or one class to another, you can force the comparison using the
|
63
|
+
# {Filigree::MatchEnvironment#Literal} method.
|
64
|
+
#
|
65
|
+
# Destructuring patterns allow you to match against an instance of a class,
|
66
|
+
# while simultaneously binding values stored inside the object to variables
|
67
|
+
# in the context of the with block. A class that is destructurable must
|
68
|
+
# include the {Filigree::Destructurable} module. You can then destructure an
|
69
|
+
# object as shown bellow.
|
70
|
+
|
71
|
+
# Both `match` and `with` can take multiple arguments. When this happens, each
|
72
|
+
# object is paired up with the corresponding pattern. If they all match, then
|
73
|
+
# the `with` clause matches. In this way you can match against tuples.
|
74
|
+
|
75
|
+
# Any with clause can be given a guard clause by passing a lambda as the last
|
76
|
+
# argument to `with`. These are evaluated after the pattern is matched, and
|
77
|
+
# any bindings made in the pattern are available to the guard clause.
|
78
|
+
|
79
|
+
# If you wish to evaluate the same body on matching any of several patterns you
|
80
|
+
# may list them in order and then specify the body for the last pattern in the
|
81
|
+
# group.
|
82
|
+
|
83
|
+
# Patterns are evaluated in the order in which they are defined and the first
|
84
|
+
# pattern to match is the one chosen. You may define helper methods inside the
|
85
|
+
# match block. They will be re-defined every time the match statement is
|
86
|
+
# evaluated, so you should move any definitions outside any match calls that
|
87
|
+
# are being evaluated often.
|
88
|
+
|
89
|
+
# @example The literal pattern
|
90
|
+
# def foo(n)
|
91
|
+
# match 1 do
|
92
|
+
# with(1) { :one }
|
93
|
+
# with(2) { :two }
|
94
|
+
# with(_) { :other }
|
95
|
+
# end
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
# foo(1)
|
99
|
+
|
100
|
+
# @example Matching against variables
|
101
|
+
# var = 42
|
102
|
+
# match 42 do
|
103
|
+
# with(var) { :hoopy }
|
104
|
+
# with(0) { :zero }
|
105
|
+
# end
|
106
|
+
|
107
|
+
# @example Binding patterns
|
108
|
+
# # Returns 42
|
109
|
+
# match 42 do
|
110
|
+
# with(x) { x }
|
111
|
+
# end
|
112
|
+
|
113
|
+
# x = 3
|
114
|
+
# # Returns 42
|
115
|
+
# match 42 do
|
116
|
+
# with(Bind(:x)) { x }
|
117
|
+
# with(42) { :hoopy }
|
118
|
+
# end
|
119
|
+
|
120
|
+
# @example Regular expression and class instance pattern
|
121
|
+
# def matcher(object)
|
122
|
+
# match object do
|
123
|
+
# with(/hoopy/) { 42 }
|
124
|
+
# with(Integer) { 'hoopy' }
|
125
|
+
# end
|
126
|
+
# end
|
127
|
+
|
128
|
+
# # Returns 42
|
129
|
+
# matcher('hoopy')
|
130
|
+
# # Returns 'hoopy'
|
131
|
+
# matcher(42)
|
132
|
+
|
133
|
+
# @example Destructuring an object
|
134
|
+
# class Foo
|
135
|
+
# include Filigree::Destructurable
|
136
|
+
# def initialize(a, b)
|
137
|
+
# @a = a
|
138
|
+
# @b = b
|
139
|
+
# end
|
140
|
+
#
|
141
|
+
# def destructure(_)
|
142
|
+
# [@a, @b]
|
143
|
+
# end
|
144
|
+
# end
|
145
|
+
|
146
|
+
# # Returns true
|
147
|
+
# match Foo.new(4, 2) do
|
148
|
+
# with(Foo.(4, 2)) { true }
|
149
|
+
# with(_) { false }
|
150
|
+
# end
|
151
|
+
|
152
|
+
# @example Using guard clauses
|
153
|
+
# match o do
|
154
|
+
# with(n, -> { n < 0 }) { :NEG }
|
155
|
+
# with(0) { :ZERO }
|
156
|
+
# with(n, -> { n > 0 }) { :POS }
|
157
|
+
# end
|
158
|
+
#
|
159
|
+
# @param [Object] objects Objects to be matched
|
160
|
+
# @param [Proc] block Block containing with clauses.
|
161
|
+
#
|
162
|
+
# @return [Object] Result of evaluating the matched pattern's block
|
163
|
+
def match(*objects, &block)
|
164
|
+
me = Filigree::MatchEnvironment.new
|
165
|
+
|
166
|
+
me.instance_exec &block
|
167
|
+
|
168
|
+
me.find_match(objects)
|
169
|
+
end
|
170
|
+
|
175
171
|
# Wrap non-pattern objects in pattern objects so they can all be treated
|
176
172
|
# in the same way during pattern sorting and matching.
|
177
173
|
#
|
data/lib/filigree/version.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
# Author:
|
2
|
-
# Project:
|
3
|
-
# Date:
|
4
|
-
# Description:
|
1
|
+
# Author: Chris Wailes <chris.wailes@gmail.com>
|
2
|
+
# Project: Filigree
|
3
|
+
# Date: 2013/4/19
|
4
|
+
# Description: This file specifies the version number of Filigree.
|
5
5
|
|
6
6
|
module Filigree
|
7
|
-
VERSION = '0.3.
|
7
|
+
VERSION = '0.3.3'
|
8
8
|
end
|
data/lib/filigree/visitor.rb
CHANGED
@@ -32,7 +32,7 @@ module Filigree
|
|
32
32
|
#
|
33
33
|
# @param [Object] objects Objects to pattern match.
|
34
34
|
#
|
35
|
-
# @return [Object] Result of calling the matched pattern's block
|
35
|
+
# @return [Object, MatchError] Result of calling the matched pattern's block or MatchError if nothing was found
|
36
36
|
#
|
37
37
|
# @raise [MatchError] Raised when no matching pattern is found and
|
38
38
|
# strict matching is enabled.
|
@@ -58,7 +58,7 @@ module Filigree
|
|
58
58
|
# If we didn't find anything we raise a MatchError.
|
59
59
|
raise MatchError
|
60
60
|
else
|
61
|
-
|
61
|
+
MatchError
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
@@ -230,27 +230,37 @@ module Filigree
|
|
230
230
|
# Visit this object with the provided visitor in pre-, post-, or
|
231
231
|
# in-order traversal.
|
232
232
|
#
|
233
|
-
# @param [Visitor]
|
234
|
-
# @param [:preorder, :inorder, :postorder] method How to visit
|
233
|
+
# @param [Visitor] visitor Visitor to call
|
234
|
+
# @param [:preorder, :inorder, :postorder, :downup] method How to visit
|
235
235
|
#
|
236
|
-
# @return [
|
236
|
+
# @return [Boolean] If a pattern matched or not
|
237
237
|
def visit(visitor, method = :preorder)
|
238
238
|
case method
|
239
239
|
when :preorder
|
240
|
-
visitor.visit(self)
|
241
|
-
children.flatten.compact.
|
240
|
+
res = (visitor.visit(self) != MatchError)
|
241
|
+
children.flatten.compact.inject(false) { |mod, child| child.visit(visitor, :preorder) || mod } || res
|
242
242
|
|
243
243
|
when :inorder
|
244
|
+
mod = false
|
244
245
|
nodes = [self]
|
245
246
|
|
247
|
+
# Not all Ruby implementations support modifying arrays while
|
248
|
+
# you are iterating over them.
|
246
249
|
while node = nodes.shift
|
247
250
|
nodes += node.children.flatten.compact
|
248
|
-
visitor.visit(node)
|
251
|
+
mod = visitor.visit(node) || mod
|
249
252
|
end
|
250
253
|
|
254
|
+
mod
|
255
|
+
|
251
256
|
when :postorder
|
252
|
-
children.flatten.compact.
|
253
|
-
visitor.visit(self)
|
257
|
+
res = children.flatten.compact.inject(false) { |mod, child| child.visit(visitor, :postorder) || mod }
|
258
|
+
(visitor.visit(self) != MatchError) || res
|
259
|
+
|
260
|
+
when :downup
|
261
|
+
res = (visitor.visit(self) != MatchError)
|
262
|
+
res = children.flatten.compact.inject(false) { |mod, child| child.visit(visitor, :downup) || mod } || res
|
263
|
+
(visitor.visit(self) != MatchError) || res
|
254
264
|
end
|
255
265
|
end
|
256
266
|
end
|
data/test/tc_match.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
# Author:
|
2
|
-
# Project:
|
3
|
-
# Date:
|
4
|
-
# Description:
|
1
|
+
# Author: Chris Wailes <chris.wailes@gmail.com>
|
2
|
+
# Project: Filigree
|
3
|
+
# Date: 2013/05/04
|
4
|
+
# Description: Test cases for the match method.
|
5
5
|
|
6
6
|
############
|
7
7
|
# Requires #
|
@@ -19,6 +19,8 @@ require 'filigree/match'
|
|
19
19
|
|
20
20
|
class MatchTester < Minitest::Test
|
21
21
|
|
22
|
+
include Filigree
|
23
|
+
|
22
24
|
####################
|
23
25
|
# Internal Classes #
|
24
26
|
####################
|
data/test/tc_visitor.rb
CHANGED
@@ -230,7 +230,7 @@ class VisitorTester < Minitest::Test
|
|
230
230
|
def test_nonstrict_matching
|
231
231
|
nsmv = NonStrictMatchVisitor.new
|
232
232
|
|
233
|
-
|
233
|
+
assert_equal(MatchError, nsmv.visit(0))
|
234
234
|
end
|
235
235
|
|
236
236
|
def test_simple_visitor
|
@@ -305,5 +305,15 @@ class VisitorTester < Minitest::Test
|
|
305
305
|
tree.visit(nv, :postorder)
|
306
306
|
|
307
307
|
assert_equal expected, nv.vals
|
308
|
+
|
309
|
+
# Test down-up
|
310
|
+
nv = NodeVisitor.new
|
311
|
+
expected = ['F', 'B', 'A', 'A', 'D', 'C', 'C', 'E', 'E', 'D', 'B', 'G', 'I', 'H', 'H', 'I', 'G', 'F']
|
312
|
+
tree.visit(nv, :downup)
|
313
|
+
|
314
|
+
assert_equal expected, nv.vals
|
315
|
+
|
316
|
+
# Test behaviour whith no match.
|
317
|
+
assert(!tree.visit(HelperMethodVisitor.new), 'Visitable object falsely reported a matching.')
|
308
318
|
end
|
309
319
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: filigree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Wailes
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-05-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|