pattern_matching 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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