patme 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/vizvamitra/patme.svg?branch=master)](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
|