rubo_claus 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: e4ddf83f73f07a31517416ef5bea818d7d574561
4
+ data.tar.gz: 9940e9766b9af282e20d30f3d5f98102b2501a48
5
+ SHA512:
6
+ metadata.gz: c7f8f1c381054d7de8a4bc3dcd212daba86dd996e658315f20bd599ff741df5aad5530fead18846f58b40fda772bd82e6f2971206512ec22173ac789623d972a
7
+ data.tar.gz: 84bc8b619df55125bca97b0843d2e0bdb54ffe9dad803cce6c01fce7df958012047d5db61d58f4951c1437fe84c864940085304f35530a74973b689a8260bbb2
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.3.1
data/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # RuboClaus
2
+
3
+ ## Ruby method pattern matching ala Erlang/Elixir
4
+
5
+ __NOTE__
6
+
7
+ This is purely a thought experiment at this point. Don't freak out,
8
+ no one even said this was a good idea or will be usable yet. This is
9
+ purely exploratory at this point.
10
+
11
+ _You have been warned_
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << 'test'
5
+ end
6
+
7
+ desc "Run tests"
8
+ task :default => :test
data/lib/match.rb ADDED
@@ -0,0 +1,49 @@
1
+ module Match
2
+ def deep_match?(lhs, rhs)
3
+ return match_array(lhs, rhs) if [lhs, rhs].all? { |side| side.is_a? Array }
4
+ return match_hash(lhs, rhs) if [lhs, rhs].all? { |side| side.is_a? Hash }
5
+ false
6
+ end
7
+
8
+ def single_match?(lhs, rhs)
9
+ return true if lhs == :any
10
+ return true if lhs == rhs
11
+ return true if lhs == rhs.class
12
+ false
13
+ end
14
+
15
+ def match?(lhs, rhs)
16
+ return true if lhs.is_a?(RuboClaus::CatchAll)
17
+ return false if lhs.args.length != rhs.length
18
+ any_match?(lhs.args, rhs)
19
+ end
20
+
21
+ def any_match?(lhs, rhs)
22
+ return true if single_match?(lhs, rhs)
23
+ deep_match?(lhs, rhs)
24
+ end
25
+
26
+ private def match_array(lhs, rhs)
27
+ return false if lhs.length != rhs.length
28
+ lhs.zip(rhs) { |array| return false unless any_match?(*array) } || true
29
+ end
30
+
31
+ private def match_hash(lhs, rhs)
32
+ lhs, rhs = handle_destructuring(lhs, rhs) if lhs.keys.length < rhs.keys.length
33
+ return false unless keys_match?(lhs, rhs)
34
+ return false unless values_match?(lhs, rhs)
35
+ true
36
+ end
37
+
38
+ private def handle_destructuring(lhs, rhs)
39
+ [lhs, rhs.select {|k, v| lhs.keys.include? k }]
40
+ end
41
+
42
+ private def keys_match?(lhs, rhs)
43
+ lhs.keys.sort == rhs.keys.sort
44
+ end
45
+
46
+ private def values_match?(lhs, rhs)
47
+ any_match?(lhs.values, rhs.values)
48
+ end
49
+ end
@@ -0,0 +1,3 @@
1
+ module RuboClaus
2
+ VERSION = '0.1.0'
3
+ end
data/lib/rubo_claus.rb ADDED
@@ -0,0 +1,41 @@
1
+ require 'match'
2
+
3
+ module RuboClaus
4
+ include Match
5
+ Clause = Struct.new(:args, :function)
6
+ CatchAll = Struct.new(:proc)
7
+
8
+ class NoPatternMatchError < NoMethodError; end
9
+
10
+ module ClassMethods
11
+ def define_function(symbol, &block)
12
+ @function_name = symbol
13
+ block.call
14
+ end
15
+
16
+ def clauses(*klauses)
17
+ define_method(@function_name) do |*runtime_args|
18
+ matching_function = find_matching_function(klauses, runtime_args)
19
+ matching_function ? matching_function.call(*runtime_args) : raise(NoPatternMatchError)
20
+ end
21
+ end
22
+
23
+ def clause(args, function)
24
+ Clause.new(args, function)
25
+ end
26
+
27
+ def catch_all(_proc)
28
+ CatchAll.new(_proc)
29
+ end
30
+ end
31
+
32
+ private def find_matching_function(klauses, runtime_args)
33
+ clause = klauses.find { |pattern| match?(pattern, runtime_args) }
34
+ return clause.function if clause.is_a?(Clause)
35
+ return clause.proc if clause.is_a?(CatchAll)
36
+ end
37
+
38
+ def self.included(klass)
39
+ klass.extend ClassMethods
40
+ end
41
+ end
@@ -0,0 +1,19 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'rubo_claus/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'rubo_claus'
7
+ s.version = RuboClaus::VERSION
8
+ s.date = '2016-08-09'
9
+ s.summary = 'Ruby Method Pattern Matcher'
10
+ s.description = 'Define ruby methods with pattern matching much like Erlang/Elixir'
11
+ s.authors = ['Omid Bachari', 'Craig P Jolicoeur']
12
+ s.email = ['omid@mojotech.com', 'craig@mojotech.com']
13
+ s.files = `git ls-files`.split($/)
14
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
15
+ s.require_paths = ["lib"]
16
+ s.license = 'MIT'
17
+
18
+ s.add_development_dependency 'rake', '~> 0'
19
+ end
@@ -0,0 +1,204 @@
1
+ require 'minitest/autorun'
2
+ require 'rubo_claus'
3
+ require 'rubo_claus/version'
4
+
5
+ class RuboClausTest < Minitest::Test
6
+ def test_has_version_number
7
+ refute_nil RuboClaus::VERSION
8
+ end
9
+ end
10
+
11
+ class MyClass
12
+ include RuboClaus
13
+
14
+ define_function :add_one do
15
+ clauses(
16
+ clause([0], proc { |n| "Dont add one to zero please!" }),
17
+ clause([NilClass], proc { |n| 42 }),
18
+ clause([:any], proc { |n| n + 1 }),
19
+ catch_all(proc { raise NoMethodError, "no clause defined} "})
20
+ )
21
+ end
22
+
23
+ define_function :greeting do
24
+ clauses(
25
+ clause([String], proc { |str| "Hello, #{str}!" }),
26
+ clause([String, String], proc { |greet, str| "#{greet}, #{str}!"})
27
+ )
28
+ end
29
+
30
+ define_function :any_test do
31
+ clauses(
32
+ clause([1, :any, 3], proc { |n1, any, n3| "#{n1} ANY 2" }),
33
+ clause([:any, 2, 3], proc { |any, n2, n3| "ANY 2 3" }),
34
+ clause([:any, 2, :any], proc { |ne1, n2, ne2| "ANY 2 ANY" })
35
+ )
36
+ end
37
+
38
+ define_function :print_string_in_array do
39
+ clauses(
40
+ clause([1, [:any, [String]], Fixnum], proc { |n, n1, n2| "#{n1[1][0]}" })
41
+ )
42
+ end
43
+
44
+ define_function :integer_in_nested_array do
45
+ clauses(
46
+ clause([Fixnum, 2, [[[[[Fixnum]]]]]], proc { |n, n1, n2| n2[0][0][0][0][0] })
47
+ )
48
+ end
49
+
50
+ define_function :hash_keys do
51
+ clauses(
52
+ clause([Hash], proc { |hash| hash.keys })
53
+ )
54
+ end
55
+
56
+ define_function :friend_hash do
57
+ clauses(
58
+ clause([{friend: String, foe: :any}], proc { |n| "I know #{n[:friend]}" })
59
+ )
60
+ end
61
+
62
+ define_function :friend_hash_des do
63
+ clauses(
64
+ clause([{friend: String}], proc { |n| "I know everything about #{n[:friend]}" })
65
+ )
66
+ end
67
+
68
+ define_function :get_people do
69
+ clauses(
70
+ clause([:any, [1, 2, String, String], {people: String}], proc { |n, n1, n2| n2[:people] })
71
+ )
72
+ end
73
+
74
+ define_function :night_emergency_phone do
75
+ param_shape = {username: String, friends: Array, phone: {fax: :any, mobile: String, emergency: {day: String, night: String}}}
76
+
77
+ clauses(
78
+ clause([param_shape], proc do |data|
79
+ data[:phone][:emergency][:night]
80
+ end)
81
+ )
82
+ end
83
+ end
84
+
85
+ class MyClassTest < Minitest::Test
86
+ def test_simple_clauses
87
+ k = MyClass.new
88
+ assert_equal "Dont add one to zero please!", k.add_one(0)
89
+ assert_equal 3, k.add_one(2)
90
+ assert_equal 42, k.add_one(nil)
91
+ assert_raises NoMethodError do
92
+ k.add_one(1,2,3, [:b])
93
+ end
94
+ end
95
+
96
+ def test_variadic_arity
97
+ k = MyClass.new
98
+ assert_equal "Hello, Newman!", k.greeting("Newman")
99
+ assert_equal "Bye Bye, Dolly!", k.greeting("Bye Bye", "Dolly")
100
+ end
101
+
102
+ def test_mixed_location_any_terms
103
+ k = MyClass.new
104
+ assert_equal "1 ANY 2", k.any_test(1,999,3)
105
+ assert_equal "ANY 2 3", k.any_test(999,2,3)
106
+ assert_equal "ANY 2 ANY", k.any_test(999,2,987)
107
+ end
108
+
109
+ def test_no_catch_all_clause_defined
110
+ k = MyClass.new
111
+
112
+ assert_raises RuboClaus::NoPatternMatchError do
113
+ k.greeting(1,2,3,)
114
+ end
115
+
116
+ assert_raises RuboClaus::NoPatternMatchError do
117
+ k.greeting(["Goat"])
118
+ end
119
+ end
120
+
121
+ def test_nested_array_with_shape
122
+ k = MyClass.new
123
+
124
+ assert_equal "Inner string", k.print_string_in_array(1, [1, ["Inner string"]], 3)
125
+
126
+ assert_raises RuboClaus::NoPatternMatchError do
127
+ k.print_string_in_array(3, [1, "Inner string"], 3)
128
+ end
129
+
130
+ assert_raises RuboClaus::NoPatternMatchError do
131
+ k.print_string_in_array(3, [1, "Inner string"], "S")
132
+ end
133
+
134
+ assert_raises RuboClaus::NoPatternMatchError do
135
+ k.print_string_in_array(3, [1, []], "S")
136
+ end
137
+
138
+ assert_raises RuboClaus::NoPatternMatchError do
139
+ k.print_string_in_array(3, [[]], "S")
140
+ end
141
+ end
142
+
143
+ def test_nested_array
144
+ k = MyClass.new
145
+
146
+ assert_equal 3, k.integer_in_nested_array(1, 2, [[[[[3]]]]])
147
+
148
+ assert_raises RuboClaus::NoPatternMatchError do
149
+ k.integer_in_nested_array(1, 2, [[[[["Dog"]]]]])
150
+ end
151
+ end
152
+
153
+ def test_hash
154
+ k = MyClass.new
155
+
156
+ assert_equal [:dog], k.hash_keys({dog: :bone})
157
+
158
+ assert_raises RuboClaus::NoPatternMatchError do
159
+ k.hash_keys([{}])
160
+ end
161
+ end
162
+
163
+ def test_hash_with_keys
164
+ k = MyClass.new
165
+
166
+ assert_equal "I know John", k.friend_hash({friend: "John", foe: 3})
167
+
168
+ assert_raises RuboClaus::NoPatternMatchError do
169
+ k.friend_hash({})
170
+ end
171
+ end
172
+
173
+ def test_hash_with_keys_destructured
174
+ k = MyClass.new
175
+
176
+ assert_equal "I know everything about John", k.friend_hash_des({friend: "John", foe: 3})
177
+
178
+ assert_raises RuboClaus::NoPatternMatchError do
179
+ k.friend_hash_des({})
180
+ end
181
+ end
182
+
183
+ def test_compound_hash_array
184
+ k = MyClass.new
185
+
186
+ assert_equal "John", k.get_people("Douglas", [1, 2, "Anything", "Another any string"], {people: "John"})
187
+
188
+ assert_raises RuboClaus::NoPatternMatchError do
189
+ k.get_people("Douglas", [], {people: "John"})
190
+ end
191
+ end
192
+
193
+ def test_three_dimensional_compound_data_structure
194
+ k = MyClass.new
195
+
196
+ param = {username: "Sally Moe", friends: [], phone: {fax: "NA", mobile: "123-345-1232", emergency: {day: "123-123-1234", night: "999-999-9999"}}}
197
+ assert_equal "999-999-9999", k.night_emergency_phone(param)
198
+
199
+ assert_raises RuboClaus::NoPatternMatchError do
200
+ param = {username: "Sally Moe", friends: "", phone: {fax: "NA", mobile: "123-345-1232", emergency: [{day: "123-123-1234", night: "999-999-9999"}]}}
201
+ k.night_emergency_phone(param)
202
+ end
203
+ end
204
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubo_claus
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Omid Bachari
8
+ - Craig P Jolicoeur
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-08-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ description: Define ruby methods with pattern matching much like Erlang/Elixir
29
+ email:
30
+ - omid@mojotech.com
31
+ - craig@mojotech.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - ".ruby-version"
37
+ - README.md
38
+ - Rakefile
39
+ - lib/match.rb
40
+ - lib/rubo_claus.rb
41
+ - lib/rubo_claus/version.rb
42
+ - rubo_claus.gemspec
43
+ - test/test_rubo_claus.rb
44
+ homepage:
45
+ licenses:
46
+ - MIT
47
+ metadata: {}
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubyforge_project:
64
+ rubygems_version: 2.5.1
65
+ signing_key:
66
+ specification_version: 4
67
+ summary: Ruby Method Pattern Matcher
68
+ test_files:
69
+ - test/test_rubo_claus.rb