pattern_matching 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b06b9cf226bb98d933ddf95eb0b75788ae350bd2
4
+ data.tar.gz: 2f60e9f04cf9ffe1162c6f0689a8a510ef74d93c
5
+ SHA512:
6
+ metadata.gz: ce9d684d17669385f62e003c7a2961490d353bc42b069699ec2bdc9f7f865ef893f54c78cd4ac3cf48bff115976a5fb983c7679c89f9a47da8b77c3d44613e30
7
+ data.tar.gz: 76c6f315f0d7c474f8762739d6b6778d105f1492e84e3c1b0d5007b31f38f591e46a1632f961a882d1c29e135869e91dd20cc209f89c1eb80ed75256df91abf9
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in payment_adapters.gemspec
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,33 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ pattern_matching (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ansi (1.5.0)
10
+ builder (3.2.2)
11
+ minitest (5.9.1)
12
+ minitest-reporters (1.1.11)
13
+ ansi
14
+ builder
15
+ minitest (>= 5.0)
16
+ ruby-progressbar
17
+ rake (10.5.0)
18
+ ruby-progressbar (1.8.1)
19
+ turn-again-reporter (1.1.0)
20
+ minitest-reporters (~> 1.0, >= 1.0.8)
21
+
22
+ PLATFORMS
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ bundler (~> 1.7)
27
+ minitest-reporters (~> 1.1)
28
+ pattern_matching!
29
+ rake (~> 10.0)
30
+ turn-again-reporter (~> 1.1, >= 1.1.0)
31
+
32
+ BUNDLED WITH
33
+ 1.13.5
data/LICENSE.md ADDED
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2016, Paul Kwiatkowski
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ * Neither the name of adalog nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = Dir[File.join(File.dirname(__FILE__), 'test/**/*test.rb')]
7
+ end
@@ -0,0 +1,181 @@
1
+ # Allows for crude pattern-matching like behavior. Very crude.
2
+ # Currently just a Symbol as a status tag paired with a value.
3
+ # Provides capitalized methods (bad form, perhaps?) to make
4
+ # them stand out: `#Pattern`, `#Result` and `#Match`.
5
+ #
6
+ # Example usage:
7
+ #
8
+ # def some_computation
9
+ # Result(:ok, "awesome sauce")
10
+ # end
11
+ #
12
+ # case (result = some_computation)
13
+ # when Match(:ok)
14
+ # puts "Things went okay: #{result.value}"
15
+ # when Match(:error)
16
+ # puts "Something went wrong: #{result.value}"
17
+ # end
18
+ #
19
+ # # => "Things went okay: awesome sauce"
20
+ #
21
+ # Originally named `Result` but that would have a conflicting meaning
22
+ # with the typical use of the Result Monad in most languages/environments.
23
+ # So while this is not true pattern matching, it might one day grow into
24
+ # something of the sort, and so the name fits... in a limited way.
25
+ module PatternMatching
26
+
27
+ ##
28
+ # Configure behavior based on existing configuration.
29
+ # This is a lot of noodley-looking nested conditional logic but that's
30
+ # kind of the point with lots of boolean-based configuration of behavior!
31
+ def self.included(base)
32
+ if PatternMatching.config.use_binding_helper
33
+ base.const_set(PatternMatching.config.binding_helper, PatternMatching::BindingsSet.new)
34
+ if PatternMatching.config.default_binding_helper?
35
+ base.send(:include, PatternMatching::MethodsWithBindingHelper)
36
+ else
37
+ base.send(:include, PatternMatching.methods_with_custom_binding_helper)
38
+ end
39
+ else
40
+ base.send(:include, PatternMatching::Methods)
41
+ end
42
+
43
+ if PatternMatching.config.use_proc_helpers
44
+ if PatternMatching.config.default_proc_helpers?
45
+ base.send(:include, PatternMatching::ProcHelpers)
46
+ else
47
+ base.send(:include, PatternMatching.custom_proc_helpers)
48
+ end
49
+ end
50
+ end
51
+
52
+ ##
53
+ # Available configuration options are:
54
+ #
55
+ # - use_proc_helpers: controls whether or not helpers for sending
56
+ # messages and calling a method in the local context are included
57
+ # with this module.
58
+ #
59
+ # - use_binding_helper: controls whether or not bindings are enabled (and thus)
60
+ # whether or not helpers are included.
61
+ #
62
+ # - send_helper: the method name used as the proc helper for
63
+ # "sending a message" to the object when matching.
64
+ #
65
+ # - call_helper: the method name used as the proc helper for "calling a
66
+ # method in the current context" with the object as an argument when matching.
67
+ #
68
+ # - binding_helper: the method name used as the binding set for each match.
69
+ def self.configure(&block)
70
+ block.call(config)
71
+
72
+ unless config.default_proc_helpers?
73
+ build_custom_proc_helpers(config.send_helper, config.call_helper)
74
+ end
75
+
76
+ if config.use_binding_helper && !config.default_binding_helper?
77
+ build_custom_binding_helper(config.binding_helper)
78
+ puts "Using a custom binding helper are we?"
79
+ end
80
+ end
81
+
82
+ ##
83
+ # Simple class-instance variable to hold configuration
84
+ def self.config
85
+ @config ||= ::PatternMatching::Configuration.default
86
+ end
87
+
88
+ ##
89
+ # For of use in testing, because of how Ruby loading works when testing
90
+ # behavior that is thread-global (?) for the instance of this module.
91
+ def self.default_configuration!
92
+ @config = ::PatternMatching::Configuration.default
93
+ end
94
+
95
+ ##
96
+ # Will only be set with a proper module if a call to ::configure results in
97
+ # there being proc helpers with non-default names. Is not intelligently
98
+ # assigned, nor should it be used, when called in any manner before a call
99
+ # to ::configure that specifies an alternative value to config.call_helper
100
+ # and config.binding_helper.
101
+ def self.custom_proc_helpers
102
+ @custom_proc_helpers
103
+ end
104
+
105
+ ##
106
+ # Will only be set with a proper module if a call to ::configure results in
107
+ # a binding helper set to a name which is not the default. Is not intelligently
108
+ # assigned, nor should it be used, when called in any manner before a call
109
+ # to ::configure that specifies an alternative value to config.binding_helper
110
+ def self.methods_with_custom_binding_helper
111
+ @methods_with_custom_binding_helper
112
+ end
113
+
114
+ ##
115
+ # Implementations internal to the blocks of the define_method calls
116
+ # should remain identical to the implementations in the PatternMatching::ProcHelpers
117
+ # module, with the only change being that the produced module has different names for
118
+ # the methods behind the call and send helpers.
119
+ def self.build_custom_proc_helpers(send_helper, call_helper)
120
+ proc_helpers = ->() {
121
+ define_method(send_helper) do |symbol|
122
+ symbol.to_proc
123
+ end
124
+
125
+ define_method(call_helper) do |symbol|
126
+ Proc.new { |obj| self.send(symbol, obj) }
127
+ end
128
+ }
129
+
130
+ @custom_proc_helpers = Module.new
131
+ @custom_proc_helpers.class_exec(&proc_helpers)
132
+ end
133
+
134
+ ##
135
+ # The implementation of the METHODS heredoc should remain identical to the contents of
136
+ # the Match and Pattern methods of PatternMatching::MethodsWithBindingHelper except that
137
+ # here we are interpolating on our
138
+ def self.build_custom_binding_helper(binding_helper)
139
+ methods_with_binding = ->() {
140
+ eval(
141
+ <<-METHODS
142
+ def Match(*pattern)
143
+ result = ::PatternMatching::CaseEqualityReversal.new(*pattern)
144
+ (self.class)::#{binding_helper}._clear_bindings!(caller_locations(1,1)[0].label) unless result
145
+ result
146
+ end
147
+
148
+ def Pattern(*pattern)
149
+ (self.class)::#{binding_helper}._clear_bindings!(caller_locations(1,1)[0].label)
150
+ ::PatternMatching::PatternMatch.new(*pattern)
151
+ end
152
+ METHODS
153
+ )
154
+ }
155
+ @methods_with_custom_binding_helper = Module.new
156
+ @methods_with_custom_binding_helper.class_exec(&methods_with_binding)
157
+ end
158
+
159
+ ##
160
+ # Additional singleton constants and wildcards
161
+ Undefined = Object.new
162
+ Any = Object.new
163
+ Head = Object.new
164
+ Tail = Object.new
165
+ def Undefined.inspect ; "<Undefined>" ; end
166
+ def Undefined.to_s ; "<Undefined>" ; end
167
+ def Any.inspect ; "<Any>" ; end
168
+ def Any.to_s ; "<Any>" ; end
169
+ def Head.inspect ; "<Head>" ; end
170
+ def Head.to_s ; "<Head>" ; end
171
+ def Tail.inspect ; "<Tail>" ; end
172
+ def Tail.to_s ; "<Tail>" ; end
173
+
174
+ end
175
+
176
+
177
+ ##
178
+ # Include the rest of this library.
179
+ Dir[File.join(File.dirname(__FILE__), "pattern_matching", "*.rb")].each do |rb_file|
180
+ require rb_file
181
+ end
@@ -0,0 +1,59 @@
1
+ module PatternMatching
2
+ ##
3
+ # Taken nearly verbatim from the idea presented by Avdi Grimm in an episode of
4
+ # the fantastic Ruby Tapas series, including the use of the >> (right-shift) operator
5
+ # as a "guard" operator, as visually stands out compared to other define-able
6
+ # binary operators but it lacks idiomatic re-use the way that << (left-shift) does.
7
+ # It also feels a little bit like the \\ guards used in Elixir, which I would have
8
+ # used, if I could find a way to have Ruby treat either \\ or // as a binary operator.
9
+ class Bindings < BasicObject
10
+
11
+ def initialize
12
+ @bindings = ::Hash.new do |hash, key|
13
+ ::PatternMatching::Bindings::BoundValue.new(hash, key)
14
+ end
15
+ end
16
+
17
+ ##
18
+ # Hello darkness, my old friend.
19
+ def method_missing(msg, *)
20
+ @bindings[msg]
21
+ end
22
+
23
+
24
+ ##
25
+ # The interesting work of Bindings happens in this class.
26
+ #
27
+ # Allows setting up guards via the >> oprator.
28
+ #
29
+ # If guards pass, or no guards have been added to a BoundValue,
30
+ # then comparing via == or === with that BoundValue will always
31
+ # return true, and will save the compared-to value in the hash,
32
+ # presumably provided by an instance of the Bindings class.
33
+ class BoundValue
34
+ def initialize(bindings, name)
35
+ @bindings = bindings
36
+ @name = name
37
+ @guards = []
38
+ end
39
+
40
+ def ==(other)
41
+ return false unless @guards.all? { |g| g === other }
42
+ @bindings[@name] = other
43
+ true
44
+ end
45
+
46
+ def ===(other)
47
+ return false unless @guards.all? { |g| g === other }
48
+ @bindings[@name] = other
49
+ true
50
+ end
51
+
52
+ def >>(guard)
53
+ @guards << guard
54
+ self
55
+ end
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,84 @@
1
+ module PatternMatching
2
+ class BindingsSet
3
+
4
+ ##
5
+ # For reasons I have yet to determine, implementing BindingsSet as a subclass
6
+ # of BasicObject has caused stack overflow issues. So the current workaround
7
+ # is to just remove everything we possibly can from this class at load time
8
+ # via the very hamfisted Module#undef_method.
9
+ REMOVABLE_OBJECT_METHODS = [
10
+ :nil?,
11
+ :===,
12
+ :=~,
13
+ :!~,
14
+ :eql?,
15
+ :hash,
16
+ :<=>,
17
+ :class,
18
+ :singleton_class,
19
+ :clone,
20
+ :dup,
21
+ :taint,
22
+ :tainted?,
23
+ :untaint,
24
+ :untrust,
25
+ :untrusted?,
26
+ :trust,
27
+ :freeze,
28
+ :frozen?,
29
+ :to_s,
30
+ :inspect,
31
+ :methods,
32
+ :singleton_methods,
33
+ :protected_methods,
34
+ :private_methods,
35
+ :public_methods,
36
+ :instance_variables,
37
+ :instance_variable_get,
38
+ :instance_variable_set,
39
+ :instance_variable_defined?,
40
+ :remove_instance_variable,
41
+ :instance_of?,
42
+ :kind_of?,
43
+ :is_a?,
44
+ :tap,
45
+ :send,
46
+ :public_send,
47
+ :respond_to?,
48
+ :extend,
49
+ :display,
50
+ :method,
51
+ :public_method,
52
+ :singleton_method,
53
+ :define_singleton_method,
54
+ :to_enum,
55
+ :enum_for,
56
+ ]
57
+
58
+ REMOVABLE_OBJECT_METHODS.each do |msg|
59
+ undef_method(msg)
60
+ end
61
+
62
+
63
+ def initialize
64
+ @bindings = {}
65
+ end
66
+
67
+ ###
68
+ # Based on the caller's method name (obviously not an universally-optimal choice),
69
+ # cache a Bindings object and forward all messages there.
70
+ def method_missing(msg, *)
71
+ caller_label = caller_locations(1,1)[0].label
72
+ @bindings[caller_label] ||= PatternMatching::Bindings.new
73
+ @bindings[caller_label].send(msg)
74
+ end
75
+
76
+ ##
77
+ # Used internally as a hacky hook into auto-bindings with the B constant,
78
+ # when enabled. See documentation for automatic bindings.
79
+ def _clear_bindings!(caller_label)
80
+ @bindings.delete(caller_label)
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,15 @@
1
+ module PatternMatching
2
+ ##
3
+ # Used by #Match to invert the call to `===` by `when` clauses
4
+ class CaseEqualityReversal < BasicObject
5
+
6
+ def initialize(*pattern)
7
+ @pattern = pattern
8
+ end
9
+
10
+ def ===(other)
11
+ other.===(*@pattern)
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,50 @@
1
+ module PatternMatching
2
+ ##
3
+ # Available configuration options are:
4
+ #
5
+ # - use_proc_helpers: controls whether or not helpers for sending
6
+ # messages and calling a method in the local context are included
7
+ # with this module.
8
+ #
9
+ # - use_binding_helper: controls whether or not bindings are enabled (and thus)
10
+ # whether or not helpers are included.
11
+ #
12
+ # - send_helper: the method name used as the proc helper for
13
+ # "sending a message" to the object when matching.
14
+ #
15
+ # - call_helper: the method name used as the proc helper for "calling a
16
+ # method in the current context" with the object as an argument when matching.
17
+ #
18
+ # - binding_helper: the method name used as the binding set for each match.
19
+ class Configuration
20
+
21
+ def self.default
22
+ new(true, true, :S, :C, :B)
23
+ end
24
+
25
+ attr_accessor :use_proc_helpers,
26
+ :use_binding_helper,
27
+ :send_helper,
28
+ :call_helper,
29
+ :binding_helper
30
+
31
+ def initialize(use_proc_helpers, use_binding_helper, send_helper, call_helper, binding_helper)
32
+ @use_proc_helpers = use_proc_helpers
33
+ @use_binding_helper = use_binding_helper
34
+ @send_helper = send_helper
35
+ @call_helper = call_helper
36
+ @binding_helper = binding_helper
37
+ end
38
+
39
+
40
+ def default_proc_helpers?
41
+ :S == send_helper && :C == call_helper
42
+ end
43
+
44
+
45
+ def default_binding_helper?
46
+ :B == binding_helper
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,17 @@
1
+ module PatternMatching
2
+ module Methods
3
+
4
+ ##
5
+ # Wraps a matchable 'pattern' in an object that inverts `===` (case-equality method).
6
+ def Match(*pattern)
7
+ ::PatternMatching::CaseEqualityReversal.new(*pattern)
8
+ end
9
+
10
+ ##
11
+ # Wraps an argument list as a pattern for use in a call to #Match
12
+ def Pattern(*pattern)
13
+ ::PatternMatching::PatternMatch.new(*pattern)
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ module PatternMatching
2
+ module MethodsWithBindingHelper
3
+
4
+ ##
5
+ # Wraps a matchable 'pattern' in an object that inverts `===` (case-equality method).
6
+ def Match(*pattern)
7
+ result = ::PatternMatching::CaseEqualityReversal.new(*pattern)
8
+ (self.class)::B._clear_bindings!(caller_locations(1,1)[0].label) unless result
9
+ result
10
+ end
11
+
12
+ ##
13
+ # Wraps an argument list as a pattern for use in a call to #Match
14
+ def Pattern(*pattern)
15
+ (self.class)::B._clear_bindings!(caller_locations(1,1)[0].label)
16
+ ::PatternMatching::PatternMatch.new(*pattern)
17
+ end
18
+
19
+ end
20
+ end
21
+
@@ -0,0 +1,55 @@
1
+ module PatternMatching
2
+ ##
3
+ # Encapsulates pattern matching behaviors such as deep-matching of collection
4
+ # types as well as wildcard values such as `Any`.
5
+ class PatternMatch
6
+
7
+ attr_reader :pattern
8
+ alias_method :value, :pattern
9
+
10
+ def initialize(*pattern)
11
+ @pattern = pattern
12
+ end
13
+
14
+ ##
15
+ # As both self.pattern and other are slurped, they are guaranteed to begin as
16
+ # Arrays (i.e. they are enumerable). Thus all matches begin by checking a match
17
+ # as if the values are enumerable.
18
+ def ===(*other)
19
+ match_enumerable(pattern, other)
20
+ end
21
+
22
+
23
+ private ######################################################################
24
+
25
+ ##
26
+ # Enumerable arguments with different lengths are not equal, at least as long as
27
+ # wildcards such as Head and Tail remain un-implemented. This is used as a
28
+ # fast-reject clause. Otherwise, iteration is used to decide
29
+ #
30
+ # TODO: There might be a better way of determining which objects are legitimately
31
+ # useful as matachable collections than simply descending from Enumerable or
32
+ # responding to #length, #zip and #reduce, as is implicit here.
33
+ def match_enumerable(from_self, from_other)
34
+ return false if from_self.length < from_other.length
35
+ combined = from_self.zip(from_other)
36
+ combined.reduce(true) do |acc, (self_item, other_item)|
37
+ acc && match_item(self_item, other_item)
38
+ end
39
+ end
40
+
41
+ ##
42
+ # Handles matching for non-collection values, including the logic behind
43
+ # the wildcard Any. In the case of a collection, defers instead to #match_enumerable.
44
+ def match_item(from_self, from_other)
45
+ if Any == from_other
46
+ true
47
+ elsif Enumerable === from_other && Enumerable === from_self
48
+ match_enumerable(from_self, from_other)
49
+ else
50
+ from_other === from_self
51
+ end
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,19 @@
1
+ module PatternMatching
2
+ module ProcHelpers
3
+
4
+ ##
5
+ # S for 'send', as in "send message to object".
6
+ # Allows for prettier Proc pattern-matches than simply :sym.to_proc everywhere
7
+ def S(symbol)
8
+ symbol.to_proc
9
+ end
10
+
11
+ ##
12
+ # C for 'call', as in "call method in current context".
13
+ # Allows for prettier Method pattern-matches than method(:sym)
14
+ def C(symbol)
15
+ Proc.new { |obj| self.send(symbol, obj) }
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module PatternMatching
2
+ VERSION = '0.1.0'
3
+ end
File without changes
@@ -0,0 +1,21 @@
1
+ require 'test_helper'
2
+
3
+ PatternMatching.configure do |config|
4
+ config.use_proc_helpers = false
5
+ end
6
+
7
+ module ConfigurationTests
8
+ class NoProcHelpers < Minitest::Test
9
+ include PatternMatching
10
+
11
+ test "send helper is not defined" do
12
+ refute_respond_to(self, :S)
13
+ end
14
+
15
+ test "call helper is not defined" do
16
+ refute_respond_to(self, :C)
17
+ end
18
+
19
+ end
20
+ end
21
+
@@ -0,0 +1,52 @@
1
+ require 'test_helper'
2
+
3
+ PatternMatching.configure do |config|
4
+ config.use_proc_helpers = true
5
+ config.send_helper = :foo
6
+ config.call_helper = :bar
7
+ end
8
+
9
+ module ConfigurationTests
10
+ class RenamedProcHelpers < Minitest::Test
11
+ include PatternMatching
12
+
13
+ test "send helper is defined as foo" do
14
+ assert_respond_to(self, :foo)
15
+ end
16
+
17
+ test "call helper is defined as bar" do
18
+ assert_respond_to(self, :bar)
19
+ end
20
+
21
+
22
+ test "can use S as a shortcut for symbol-to-proc" do
23
+ result = Pattern(42, "", 555)
24
+ assert_match(result, foo(:even?), foo(:empty?), foo(:odd?))
25
+ refute_match(result, foo(:odd?), foo(:empty?), foo(:odd?))
26
+ end
27
+
28
+
29
+ def contains_e?(str)
30
+ str.kind_of?(String) && str.count('e') > 0
31
+ end
32
+
33
+
34
+ def forty_two?(val)
35
+ 42 == val
36
+ end
37
+
38
+
39
+ def triple_five?(val)
40
+ 555 == val
41
+ end
42
+
43
+
44
+ test "can use C as a shortcut for methods in current context" do
45
+ result = Pattern(42, "streetlight", 555)
46
+ assert_match(result, bar(:forty_two?), bar(:contains_e?), bar(:triple_five?))
47
+ refute_match(result, bar(:forty_two?), bar(:forty_two?), bar(:triple_five?))
48
+ end
49
+
50
+ end
51
+ end
52
+
@@ -0,0 +1,47 @@
1
+ require 'test_helper'
2
+
3
+ PatternMatching.default_configuration!
4
+
5
+ class ProcHelperTest < Minitest::Test
6
+ include PatternMatching
7
+
8
+
9
+ test "can use S as a shortcut for symbol-to-proc" do
10
+ result = Pattern(42, "", 555)
11
+
12
+ assert_match(result, S(:even?), S(:empty?), S(:odd?))
13
+
14
+ refute_match(result, S(:odd?), S(:empty?), S(:odd?))
15
+ refute_match(result, S(:even?), S(:empty?), S(:even?))
16
+ refute_match(result, S(:even?), S(:nil?), S(:odd?))
17
+ end
18
+
19
+
20
+ def contains_e?(str)
21
+ str.kind_of?(String) && str.count('e') > 0
22
+ end
23
+
24
+
25
+ def forty_two?(val)
26
+ 42 == val
27
+ end
28
+
29
+
30
+ def triple_five?(val)
31
+ 555 == val
32
+ end
33
+
34
+
35
+ test "can use C as a shortcut for methods in current context" do
36
+ result = Pattern(42, "streetlight", 555)
37
+
38
+ assert_match(result, C(:forty_two?), C(:contains_e?), C(:triple_five?))
39
+
40
+ refute_match(result, C(:forty_two?), C(:forty_two?), C(:triple_five?))
41
+ refute_match(result, C(:forty_two?), C(:contains_e?), C(:contains_e?))
42
+ refute_match(result, C(:forty_two?), C(:contains_e?), C(:forty_two?))
43
+ end
44
+
45
+
46
+ end
47
+
@@ -0,0 +1,134 @@
1
+ require 'test_helper'
2
+
3
+ class SimpleMatchingTest < Minitest::Test
4
+ include PatternMatching
5
+
6
+
7
+ test "matches status and exact value" do
8
+ result = Pattern( :ok, [1, 2, 3])
9
+ assert_match(result, :ok, [1, 2, 3])
10
+ refute_match(result, :ok, [1, 2])
11
+ refute_match(result, :ok, [3, 2, 1])
12
+ refute_match(result, :ok, [])
13
+ end
14
+
15
+
16
+ test "matches status without value" do
17
+ result = Pattern( :ok)
18
+ assert_match(result, :ok)
19
+ assert_match(result, Any)
20
+ refute_match(result, :ok, [1, 2, 3])
21
+ refute_match(result, :ok, Any)
22
+ refute_match(result, Any, Any)
23
+ end
24
+
25
+
26
+ test "matches class hierarchy" do
27
+ result = Pattern( :ok, [1, 2, 3])
28
+ assert_match(result, :ok, Array)
29
+ assert_match(result, Symbol, [1, 2, 3])
30
+ refute_match(result, :ok, [Array])
31
+ refute_match(result, :ok, [Fixnum])
32
+ refute_match(result, :ok, String)
33
+ refute_match(result, :ok, Fixnum)
34
+ refute_match(result, Symbol, [])
35
+ end
36
+
37
+
38
+ test "matches range inclusion" do
39
+ result = Pattern( 5)
40
+ assert_match(result, (1..10))
41
+ refute_match(result, (1...5))
42
+ end
43
+
44
+
45
+ test "matches by calling procs or lambdas" do
46
+ result = Pattern( 5)
47
+
48
+ assert_match(result, ->(val) { true })
49
+ assert_match(result, Proc.new { true })
50
+ assert_match(result, :odd?.to_proc)
51
+
52
+ refute_match(result, Proc.new { false })
53
+ refute_match(result, :even?.to_proc)
54
+ end
55
+
56
+
57
+ test "matches arrays recursively" do
58
+ result = Pattern( :ok, [1, 2, 3])
59
+
60
+ assert_match(result, :ok, [1, Any, 3])
61
+ assert_match(result, :ok, [Any, Any, Any])
62
+ refute_match(result, :ok, [1, Any])
63
+
64
+ assert_match(result, :ok, [1, :even?.to_proc, 3])
65
+ refute_match(result, :ok, [1, :odd?.to_proc, 3])
66
+
67
+ assert_match(result, Symbol, [1, Numeric, 3])
68
+ refute_match(result, Symbol, [1, Float, 3])
69
+
70
+ assert_match(result, Symbol, [1, (0..10), 3])
71
+ refute_match(result, Symbol, [1, (5..10), 3])
72
+ end
73
+
74
+
75
+ test "matches hash strucutre" do
76
+ result = Pattern( :ok, { a: 1, b: 2, c: 3 })
77
+ assert_match(result, :ok, { a: 1, b: 2, c: 3 })
78
+ assert_match(result, :ok, { a: Any, b: Any, c: Any })
79
+ assert_match(result, :ok, { a: Any, b: Any, c: Any })
80
+ refute_match(result, :ok, {})
81
+ refute_match(result, :ok, { d: Any})
82
+ refute_match(result, :ok, { a: 1, b: 2, c: 3, d: 4 })
83
+ refute_match(result, :ok, { a: 1, b: 2, c: 3, d: Any })
84
+ refute_match(result, :ok, { a: Any, b: Any, c: Any, d: Any })
85
+ refute_match(result, :ok, { a: 1 })
86
+ refute_match(result, :ok, { a: '1', b: '2', c: '3' })
87
+ refute_match(result, :ok, { a: Any, b: '2', c: Any })
88
+ end
89
+
90
+
91
+ test "matches Any deeply nested arrays" do
92
+ deeply_nested = [
93
+ 1,
94
+ "foo",
95
+ 2,
96
+ [ "bar",
97
+ [3, 4, :five],
98
+ "baz"
99
+ ],
100
+ ]
101
+ result = Pattern(deeply_nested)
102
+
103
+ assert_match(result, [
104
+ 1,
105
+ "foo",
106
+ 2,
107
+ [ "bar",
108
+ [Numeric, 4, :five],
109
+ "baz"
110
+ ],
111
+ ])
112
+
113
+ refute_match(result, [
114
+ 1,
115
+ "foo",
116
+ 2,
117
+ [ "bar",
118
+ [3, 4, 5],
119
+ "baz"
120
+ ],
121
+ ])
122
+
123
+ assert_match(result, [
124
+ Any,
125
+ Any,
126
+ Any,
127
+ [ Any,
128
+ [Any, Any, Any],
129
+ Any
130
+ ],
131
+ ])
132
+ end
133
+
134
+ end
@@ -0,0 +1,57 @@
1
+ require 'bundler/setup'
2
+ require 'minitest/autorun'
3
+ require 'minitest/reporters'
4
+ require 'minitest/reporters/turn_again_reporter'
5
+ Minitest::Reporters.use!(Minitest::Reporters::TurnAgainReporter.new)
6
+
7
+ # Require ourselves.
8
+ require 'pattern_matching'
9
+
10
+ ##
11
+ # Blatantly stolen from ActiveSupport::Testing::Declarative
12
+ # Allows for test files such as
13
+ # test "verify something" do
14
+ # ...
15
+ # end
16
+ # which become methods named test_verify_something, leaving a visual difference
17
+ # between tests themselves and any helper methods declared in the usual
18
+ # manner of `def some_helper_method`.
19
+ module DeclarativeTests
20
+ def test(name, &block)
21
+ test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
22
+ defined = instance_method(test_name) rescue false
23
+ raise "#{test_name} is already defined in #{self}" if defined
24
+ if block_given?
25
+ define_method(test_name, &block)
26
+ else
27
+ define_method(test_name) do
28
+ flunk "No implementation provided for #{name}"
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+
35
+ class Minitest::Test
36
+ extend DeclarativeTests
37
+
38
+ def assert_match(result, *pattern)
39
+ case result
40
+ when Match(*pattern)
41
+ assert(true)
42
+ else
43
+ assert(false, "Result of #{result.pattern}' should match pattern '#{pattern.join(', ')}'")
44
+ end
45
+ end
46
+
47
+
48
+ def refute_match(result, *pattern)
49
+ case result
50
+ when Match(*pattern)
51
+ assert(false, "Result of '#{result.pattern}' should not match pattern '#{pattern.join(', ')}'")
52
+ else
53
+ assert(true)
54
+ end
55
+ end
56
+
57
+ end
@@ -0,0 +1,79 @@
1
+ require 'test_helper'
2
+
3
+ PatternMatching.default_configuration!
4
+
5
+ class WildcardTest < Minitest::Test
6
+ include PatternMatching
7
+
8
+
9
+ test "matches with Any as items or enumerables" do
10
+ result = Pattern( :ok, [1, 2, 3])
11
+ assert_match(result, :ok, Any)
12
+ assert_match(result, Any, [1, 2, 3])
13
+ assert_match(result, Any, Any)
14
+ refute_match(result, Any, Any, Any)
15
+ end
16
+
17
+
18
+ test "matches Any internal to arrays" do
19
+ result = Pattern( [1, 2, 3])
20
+ assert_match(result, [1, Any, 3])
21
+ assert_match(result, [Any, Any, Any])
22
+ refute_match(result, [1, Any])
23
+ end
24
+
25
+
26
+ test "matches Any internal to hashes" do
27
+ result = Pattern( { a: 1, b: 2, c: 3 })
28
+
29
+ assert_match(result, Any)
30
+ assert_match(result, { a: 1, b: 2, Any => Any })
31
+ assert_match(result, { a: 1, b: 2, Any => 3 })
32
+ assert_match(result, { a: Any, b: Any, c: Any })
33
+ assert_match(result, { a: Any, b: Any, c: Any })
34
+
35
+ refute_match(result, {})
36
+ refute_match(result, { d: Any})
37
+ refute_match(result, { a: 1, b: 2, Any => 4 })
38
+ refute_match(result, { a: 1, b: 2, c: 3, d: Any })
39
+ refute_match(result, { a: Any, b: Any, c: Any, d: Any })
40
+ refute_match(result, { a: 1 })
41
+ refute_match(result, { a: '1', b: '2', c: '3' })
42
+ refute_match(result, { a: Any, b: '2', c: Any })
43
+ end
44
+
45
+
46
+ test "matches Any in deeply nested arrays" do
47
+ deeply_nested = [
48
+ 1,
49
+ "foo",
50
+ 2,
51
+ [ "bar",
52
+ [3, 4, :five],
53
+ "baz"
54
+ ],
55
+ ]
56
+ result = Pattern(deeply_nested)
57
+
58
+ assert_match(result, [
59
+ 1,
60
+ "foo",
61
+ 2,
62
+ [ "bar",
63
+ [Numeric, Any, :five],
64
+ "baz"
65
+ ],
66
+ ])
67
+
68
+ assert_match(result, [
69
+ Any,
70
+ Any,
71
+ Any,
72
+ [ Any,
73
+ [Any, Any, Any],
74
+ Any
75
+ ],
76
+ ])
77
+ end
78
+
79
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pattern_matching
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Paul Kwiatkowski
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-11-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest-reporters
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: turn-again-reporter
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 1.1.0
65
+ type: :development
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - "~>"
70
+ - !ruby/object:Gem::Version
71
+ version: '1.1'
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 1.1.0
75
+ description: Allows for pattern matching behavior, ala many functional languages,
76
+ using Ruby's case statements. Additionally, values can be bound at match time. Provides
77
+ a few helper modules to make this even more terse and feel more flexible.
78
+ email:
79
+ - paul@groupraise.com
80
+ executables: []
81
+ extensions: []
82
+ extra_rdoc_files: []
83
+ files:
84
+ - Gemfile
85
+ - Gemfile.lock
86
+ - LICENSE.md
87
+ - Rakefile
88
+ - lib/pattern_matching.rb
89
+ - lib/pattern_matching/bindings.rb
90
+ - lib/pattern_matching/bindings_set.rb
91
+ - lib/pattern_matching/case_equality_reversal.rb
92
+ - lib/pattern_matching/configuration.rb
93
+ - lib/pattern_matching/methods.rb
94
+ - lib/pattern_matching/methods_with_binding_helper.rb
95
+ - lib/pattern_matching/pattern_match.rb
96
+ - lib/pattern_matching/proc_helpers.rb
97
+ - lib/pattern_matching/version.rb
98
+ - test/configuration_test.rb
99
+ - test/configuration_tests/no_proc_helpers_test.rb
100
+ - test/configuration_tests/renamed_proc_helpers_test.rb
101
+ - test/proc_helper_test.rb
102
+ - test/simple_matching_test.rb
103
+ - test/test_helper.rb
104
+ - test/wildcard_test.rb
105
+ homepage: https://github.com/swifthand/pattern_matching
106
+ licenses:
107
+ - Revised BSD, see LICENSE.md
108
+ metadata: {}
109
+ post_install_message:
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 2.4.8
126
+ signing_key:
127
+ specification_version: 4
128
+ summary: Allows for pattern matching behavior with value bindings and wildcards.
129
+ test_files:
130
+ - test/configuration_test.rb
131
+ - test/configuration_tests/no_proc_helpers_test.rb
132
+ - test/configuration_tests/renamed_proc_helpers_test.rb
133
+ - test/proc_helper_test.rb
134
+ - test/simple_matching_test.rb
135
+ - test/test_helper.rb
136
+ - test/wildcard_test.rb