patme 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +83 -36
- data/lib/patme.rb +1 -0
- data/lib/patme/arguments/optional.rb +1 -1
- data/lib/patme/implementation_builder.rb +30 -41
- data/lib/patme/version.rb +1 -1
- data/patme.gemspec +2 -0
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 34560aa2680a0d13d4447e3ce353dacfb6e69e8a
|
4
|
+
data.tar.gz: f2a5fd24660340288c657f2bd1905033944335cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5063c52ccbd5e41b766b8bcd648139db2fb2cd51faeef3078e28045a6d7e4b03fe1d366247938f23d1c4b901ac13b94cfc1a395435dfddf31841333bd30489bc
|
7
|
+
data.tar.gz: 6e3f90a826db1cd5676aaa8cb6c6cc441f8b1b1895ee2875c6f033016f4c6ae90fb0967882b954fe3125b20136be8c991fee4ae07e6688d26b3da28bd164cc2a
|
data/README.md
CHANGED
@@ -2,24 +2,18 @@
|
|
2
2
|
|
3
3
|
[](https://travis-ci.org/vizvamitra/patme)
|
4
4
|
|
5
|
-
This gem is my experiment on elixir-style pattern matching in ruby.
|
5
|
+
This gem is my experiment on elixir-style pattern matching in ruby. In it's current state it is no more than a proof of concept and I suggest not to use it in production.
|
6
6
|
|
7
|
-
The implementation is neither production-ready, nor complete. It is just a proof of concept.
|
8
7
|
|
9
8
|
## Info
|
10
9
|
|
11
|
-
Patme module stores your instance methods internally and removes them from your class. Then, when method is called, it tries to pattern-match given arguments with existing method implementations. If implementation was found, it executes it,
|
10
|
+
Patme module stores your instance methods internally and removes them from your class. Then, when method is called, it tries to pattern-match given arguments with existing method implementations. If implementation was found, it executes it, otherwise you'll get a NoMethodError.
|
12
11
|
|
13
12
|
Currently this gem supports 3 types of arguments: specific, arbitrary and optional. In method definition `def foo(agr1, arg2=1, _arg3=false)` arg1 is an arbitrary argument, arg2 is specific and arg3 is optional.
|
14
13
|
|
15
14
|
Patme supports class-based pattern matching. If method's specific argument is a `Class`, then it will be matched based on given argument's class using `is_a?`
|
16
15
|
|
17
16
|
|
18
|
-
## Limitations
|
19
|
-
|
20
|
-
Only pattern matching on instance methods is supported. Also you must use parentheses around method arguments.
|
21
|
-
|
22
|
-
|
23
17
|
## Usage
|
24
18
|
|
25
19
|
#### Recursion example
|
@@ -35,7 +29,7 @@ class Factorial
|
|
35
29
|
end
|
36
30
|
|
37
31
|
def calculate(n)
|
38
|
-
n *
|
32
|
+
n * calculate(n-1)
|
39
33
|
end
|
40
34
|
end
|
41
35
|
|
@@ -50,7 +44,7 @@ factorial_calculator.calculate(-1) # => endless recursion, don't do so ^_^
|
|
50
44
|
#### Class-based pattern matching
|
51
45
|
|
52
46
|
```ruby
|
53
|
-
require '
|
47
|
+
require 'patme'
|
54
48
|
|
55
49
|
SeriousError = Class.new(StandardError)
|
56
50
|
RegularError = Class.new(StandardError)
|
@@ -116,85 +110,138 @@ class MyClass
|
|
116
110
|
|
117
111
|
# This method will match only when arg1 ='test'
|
118
112
|
def foo(arg1='test')
|
119
|
-
"
|
113
|
+
"ran foo('test') with #{arg1}"
|
120
114
|
end
|
121
115
|
|
122
116
|
# Will match only when arg1 ='other'
|
123
117
|
def foo(arg1='other')
|
124
|
-
"
|
118
|
+
"ran foo('other') with #{arg1}"
|
125
119
|
end
|
126
120
|
|
127
121
|
# You can also use other basic types
|
128
122
|
def foo(arg1={a: 1, b: 2})
|
129
|
-
"
|
123
|
+
"ran foo({a: 1, b: 2}) with #{arg1}"
|
130
124
|
end
|
131
125
|
|
132
126
|
# Will match when arg2 = 'test' no matter what arg1 is
|
133
127
|
def foo(arg1, arg2='test')
|
134
|
-
"
|
128
|
+
"ran foo(any, 'test') with [#{arg1}, #{arg2}]"
|
135
129
|
end
|
136
130
|
|
137
131
|
# Will match with any arg1 and both with and without arg2
|
138
132
|
# if arg2 is not supplied, method will receive arg2 = 'default'
|
139
133
|
def foo(arg1, _arg2="default")
|
140
|
-
"
|
134
|
+
"ran foo(any, optional) with [#{arg1}, #{_arg2}]"
|
141
135
|
end
|
142
136
|
|
143
137
|
|
144
138
|
# Will match with any one argument.
|
145
139
|
def bar(arg1)
|
146
|
-
"
|
140
|
+
"ran bar(any) with #{arg1}"
|
147
141
|
end
|
148
142
|
|
149
143
|
# Won't ever match because previous definition of bar will be pattern-matched
|
150
144
|
# before this one
|
151
145
|
def bar(arg1='never')
|
152
|
-
"
|
146
|
+
"ran bar('never') with #{arg1}"
|
153
147
|
end
|
154
148
|
|
155
149
|
|
156
150
|
def baz(arg1='test')
|
157
|
-
"
|
151
|
+
"ran baz('test') with #{arg1}"
|
158
152
|
end
|
159
153
|
end
|
160
154
|
|
161
155
|
my_obj = MyClass.new
|
162
156
|
|
163
|
-
my_obj.foo('test') # => "
|
164
|
-
my_obj.foo('other') # => "
|
165
|
-
my_obj.foo({a: 1, b: 2}) # => "
|
166
|
-
my_obj.foo(1, 'test') # => "
|
167
|
-
my_obj.foo(1) # => "
|
168
|
-
my_obj.foo(1, 'some') # => "
|
157
|
+
my_obj.foo('test') # => "ran foo('test') with test"
|
158
|
+
my_obj.foo('other') # => "ran foo('other') with other"
|
159
|
+
my_obj.foo({a: 1, b: 2}) # => "ran foo({a: 1, b: 2}) with {:a => 1, :b => 2}"
|
160
|
+
my_obj.foo(1, 'test') # => "ran foo(any, 'test') with [1, test]"
|
161
|
+
my_obj.foo(1) # => "ran foo(any, optional) with [1, default]"
|
162
|
+
my_obj.foo(1, 'some') # => "ran foo(any, optional) with [1, some]"
|
169
163
|
|
170
|
-
my_obj.bar(1) # => "
|
171
|
-
my_obj.bar('never') # => "
|
164
|
+
my_obj.bar(1) # => "ran bar(any) with 1"
|
165
|
+
my_obj.bar('never') # => "ran bar(any) with never"
|
172
166
|
|
173
|
-
my_obj.baz('test') # => "
|
167
|
+
my_obj.baz('test') # => "ran baz('test') with test"
|
174
168
|
my_obj.baz(1) # => NoMethodError
|
175
169
|
```
|
176
170
|
|
177
171
|
|
172
|
+
## Limitations
|
173
|
+
|
174
|
+
Pattern-matching in it's current implementation is slow and the more implementations of a method you have the slower it would be:
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
require 'spec_helper'
|
178
|
+
require 'benchmark'
|
179
|
+
|
180
|
+
class BenchmarkDummy
|
181
|
+
def no_pm(arg1, arg2)
|
182
|
+
['no_pm', arg1, arg2]
|
183
|
+
end
|
184
|
+
|
185
|
+
include Patme::PatternMatching
|
186
|
+
|
187
|
+
def pm(arg1='test', arg2='lol')
|
188
|
+
['specific', arg1, arg2]
|
189
|
+
end
|
190
|
+
|
191
|
+
def pm(arg1, arg2)
|
192
|
+
['arbitrary', arg1, arg2]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
n = 1_000_000
|
197
|
+
foo = BenchmarkDummy.new
|
198
|
+
|
199
|
+
Benchmark.bm do |x|
|
200
|
+
x.report('no_pm '){ n.times{foo.no_pm('test', 'lol')} }
|
201
|
+
x.report('pm_specific '){ n.times{foo.pm('test', 'lol')} }
|
202
|
+
x.report('pm_arbitrary'){ n.times{foo.pm('some', 'arg')} }
|
203
|
+
end
|
204
|
+
|
205
|
+
# user system total real
|
206
|
+
# no_pm 0.200000 0.000000 0.200000 ( 0.205747)
|
207
|
+
# pm_specific 3.570000 0.000000 3.570000 ( 3.574598)
|
208
|
+
# pm_arbitrary 5.210000 0.000000 5.210000 ( 5.210410)
|
209
|
+
```
|
210
|
+
|
211
|
+
Only pattern matching on instance methods is supported.
|
212
|
+
|
213
|
+
Including Patme::PatternMatching into your class makes all of the methods below the `include` statement be pattern-matched (I think I'll fix this issue later).
|
214
|
+
|
215
|
+
|
178
216
|
## Todos
|
179
217
|
|
180
|
-
1. Add support for
|
181
|
-
2. Add
|
182
|
-
3. Add something to tell Parme not to pattern-match on given methods
|
218
|
+
1. Add support for class methods
|
219
|
+
2. Add something to tell Patme not to pattern-match on every method
|
183
220
|
|
184
221
|
```ruby
|
185
222
|
# Possible example
|
186
223
|
class Hello
|
187
|
-
include
|
224
|
+
include Patme::PatternMatching
|
188
225
|
|
189
|
-
# Comment after method header will tell
|
226
|
+
# Comment after method header will tell Patme not to touch this method
|
190
227
|
def tell(name) # ::pm_off
|
191
228
|
puts "Hello, #{name}"
|
192
229
|
end
|
230
|
+
|
231
|
+
# Or maybe it'll be better to use an annotation
|
232
|
+
pm_start
|
233
|
+
def yell(name="Boss", message)
|
234
|
+
raise CommonSenceError, "don't yell on your boss"
|
235
|
+
end
|
236
|
+
|
237
|
+
def yell(name, message)
|
238
|
+
puts message.upcase
|
239
|
+
end
|
240
|
+
pm_stop
|
193
241
|
end
|
194
242
|
```
|
195
243
|
|
196
|
-
|
197
|
-
5. Add watchers
|
244
|
+
3. Add guards
|
198
245
|
|
199
246
|
```ruby
|
200
247
|
# Possible example
|
@@ -215,7 +262,8 @@ my_obj.baz(1) # => NoMethodError
|
|
215
262
|
end
|
216
263
|
```
|
217
264
|
|
218
|
-
|
265
|
+
4. Add support for keyword arguments (`key:` - arbitrary, `key: value` - specific, `_key: value` - optional)
|
266
|
+
5. Your suggestions?
|
219
267
|
|
220
268
|
|
221
269
|
## Contributing
|
@@ -226,4 +274,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/vizvam
|
|
226
274
|
## License
|
227
275
|
|
228
276
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
229
|
-
|
data/lib/patme.rb
CHANGED
@@ -1,59 +1,48 @@
|
|
1
1
|
module Patme
|
2
2
|
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
3
|
+
# Parses method's arguments and creates method implementation.
|
4
|
+
#
|
5
|
+
# For example, if method looks like this:
|
6
|
+
#
|
7
|
+
# def foo(arg1, arg2='foo', _arg3='bar')
|
8
|
+
# # some code
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# then implementation will be created with the following arguments:
|
12
|
+
# arg1 - arbitrary argument
|
13
|
+
# arg2 - specific argument, 'foo'
|
14
|
+
# _arg3 - optional argument, defaults to 'bar'
|
8
15
|
class ImplementationBuilder
|
9
16
|
def initialize(method_obj)
|
10
17
|
@method_obj = method_obj
|
11
18
|
end
|
12
19
|
|
13
20
|
def build
|
14
|
-
|
15
|
-
args = build_args(method_params, values)
|
16
|
-
|
17
|
-
Patme::Implementation.new(@method_obj, args)
|
21
|
+
Patme::Implementation.new(@method_obj, arguments)
|
18
22
|
end
|
19
23
|
|
20
24
|
private
|
21
25
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
regex_str = params.map do |type, name|
|
26
|
-
case type
|
27
|
-
when :opt then "#{name}=(?<#{name}>.+)"
|
28
|
-
when :req
|
29
|
-
name =~ /^_/ ? "#{name}=(?<#{name}>.+)" : "#{name}"
|
30
|
-
end
|
31
|
-
end.join(', ') + '\s*\)\s*$'
|
32
|
-
|
33
|
-
match_data = header.match( Regexp.new(regex_str) )
|
34
|
-
match_data.names.map do |name|
|
35
|
-
[name.to_sym, eval(match_data[name.to_s])]
|
36
|
-
end.to_h
|
37
|
-
end
|
26
|
+
def arguments
|
27
|
+
method_ast = Parser::CurrentRuby.parse(@method_obj.source)
|
28
|
+
arguments_ast = method_ast.children[1].children
|
38
29
|
|
39
|
-
|
40
|
-
path, line = @method_obj.source_location
|
41
|
-
File.read(path).split("\n")[line - 1]
|
30
|
+
arguments_ast.map{|arg_ast| build_argument(arg_ast)}
|
42
31
|
end
|
43
32
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
33
|
+
def build_argument(ast_node)
|
34
|
+
name = ast_node.children[0]
|
35
|
+
type = ast_node.type
|
36
|
+
|
37
|
+
case type
|
38
|
+
when :optarg
|
39
|
+
default_value = eval ast_node.children[1].location.expression.source
|
40
|
+
arg_class = name[0] == '_' ? Arguments::Optional : Arguments::Specific
|
41
|
+
arg_class.new(default_value)
|
42
|
+
when :arg
|
43
|
+
Arguments::Arbitrary.new
|
44
|
+
else
|
45
|
+
raise "Argument #{name} has unsupported argument type: #{type}"
|
57
46
|
end
|
58
47
|
end
|
59
48
|
end
|
data/lib/patme/version.rb
CHANGED
data/patme.gemspec
CHANGED
@@ -21,6 +21,8 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
22
|
spec.require_paths = ["lib"]
|
23
23
|
|
24
|
+
spec.add_runtime_dependency 'parser', "~> 2.4"
|
25
|
+
|
24
26
|
spec.add_development_dependency "bundler", "~> 1.14"
|
25
27
|
spec.add_development_dependency "rake", "~> 10.0"
|
26
28
|
spec.add_development_dependency "rspec", "~> 3.0"
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: patme
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dmitrii Krasnov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-03-
|
11
|
+
date: 2017-03-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: parser
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.4'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.4'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|