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 +7 -0
- data/.ruby-version +1 -0
- data/README.md +11 -0
- data/Rakefile +8 -0
- data/lib/match.rb +49 -0
- data/lib/rubo_claus/version.rb +3 -0
- data/lib/rubo_claus.rb +41 -0
- data/rubo_claus.gemspec +19 -0
- data/test/test_rubo_claus.rb +204 -0
- metadata +69 -0
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
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
|
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
|
data/rubo_claus.gemspec
ADDED
@@ -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
|