maccro 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +359 -0
- data/Rakefile +12 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/maccro.rb +147 -0
- data/lib/maccro/builtin.rb +105 -0
- data/lib/maccro/code_range.rb +63 -0
- data/lib/maccro/code_util.rb +106 -0
- data/lib/maccro/dsl.rb +182 -0
- data/lib/maccro/dsl/assign.rb +100 -0
- data/lib/maccro/dsl/expression.rb +166 -0
- data/lib/maccro/dsl/literal.rb +121 -0
- data/lib/maccro/dsl/node.rb +89 -0
- data/lib/maccro/dsl/value.rb +85 -0
- data/lib/maccro/kernel_ext.rb +15 -0
- data/lib/maccro/matched.rb +34 -0
- data/lib/maccro/rewrite_the_world.rb +4 -0
- data/lib/maccro/rule.rb +106 -0
- data/lib/maccro/version.rb +3 -0
- data/maccro.gemspec +27 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8015c1a7ab5cb06c29416d9801752700847984f780bed543684ad447adbc62a9
|
4
|
+
data.tar.gz: 35f530a6eb5ce1de62d16764088939eec432e409769a78e3a2ad79f9cdcb682a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 749783b767f55fe27e8097dd3954dc3a4bf20d430f7874e71fbc825fdbf77718592e374480e4dddb761f50718fe61370163623273a93829b58a61be5dd98f8d1
|
7
|
+
data.tar.gz: 6a8d9a2e6a7ae910127bc91cc5941afabe4faf4015056117683886eb9923cfdac8b3dccf12336f302bc8781294c8bfe8bd35c4562672f3b9fc59721777d601ea
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 TAGOMORI Satoshi
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,359 @@
|
|
1
|
+
# Maccro
|
2
|
+
|
3
|
+
Maccro is a library to introduce macro (dynamic code rewriting), written in Ruby 100%.
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
# name, before, after
|
7
|
+
Maccro.register(:double_less_than, 'e1 < e2 < e3', 'e1 < e2 && e2 < e3')
|
8
|
+
|
9
|
+
# This rewrites this code
|
10
|
+
class Foo
|
11
|
+
def foo(v)
|
12
|
+
if 1 < v < 2
|
13
|
+
"hit!"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Maccro.apply(Foo, Foo.instance_method(:foo))
|
19
|
+
|
20
|
+
# To this (valid Ruby) code dynamically
|
21
|
+
class Foo
|
22
|
+
def foo(v)
|
23
|
+
if 1 < v && v < 2
|
24
|
+
"hit!"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
Maccro comes from "Macro" and "Makkuro"(means "pure black" in Japanese).
|
31
|
+
|
32
|
+
### Why Maccro?
|
33
|
+
|
34
|
+
* New macro processor can depend on Ruby's new `RubyVM::AbstractSyntaxTree`
|
35
|
+
* Macro rules can be interoperable between Ruby versions (but only for Ruby 2.6 or later)
|
36
|
+
* Todo: Write other reasons
|
37
|
+
|
38
|
+
### LIMITATION
|
39
|
+
|
40
|
+
Maccro can:
|
41
|
+
|
42
|
+
* run with Ruby 2.6 or later
|
43
|
+
* rewrite code, only written in methods using `def` keyword, in `module` or `class`
|
44
|
+
* not rewrite singleton methods, which are used just after definition
|
45
|
+
* not rewrite methods from command line option (`-e`) or REPLs (irb/pry)
|
46
|
+
|
47
|
+
Maccro features below are not supported yet:
|
48
|
+
|
49
|
+
* Non-idempotent method calls
|
50
|
+
* Proc rewrite and local variable name matching (currntly, local variable name in before/after could be referred as VCALL)
|
51
|
+
* Specifying a type of literal by placeholders
|
52
|
+
* Handling method visibilities
|
53
|
+
* Rewriting singleton methods with non-self receiver
|
54
|
+
* Placeholder validation
|
55
|
+
* Multi time match placeholder
|
56
|
+
|
57
|
+
## Usage
|
58
|
+
|
59
|
+
Maccro users do:
|
60
|
+
* register rules how to rewrite methods, with code patterns
|
61
|
+
* or use built-in rules
|
62
|
+
* apply a set of registered rules to a method
|
63
|
+
* enable automatic applying to a module/class or to a file, or globally
|
64
|
+
|
65
|
+
### Terminology
|
66
|
+
|
67
|
+
* Rule
|
68
|
+
* a definition to rewrite codes, which has a name and two Ruby code snippets of Before and After
|
69
|
+
* Before
|
70
|
+
* a Ruby code snippet to match a pattern of code, which may contain a placeholder or placeholders to capture Ruby codes
|
71
|
+
* After
|
72
|
+
* a Ruby code snippet to replace matched code, which may contain a placeholder or placeholders to inject captured Ruby codes
|
73
|
+
* Placeholder
|
74
|
+
* a bare word in Before/After, to capture/replace Ruby code snippets
|
75
|
+
* its format is an alphabetical character and an integer number (>= 1) (e.g, `e1`, `e100`, `v1`)
|
76
|
+
* alphabetical characters represents the types of Ruby code (an expression, an value, etc)
|
77
|
+
|
78
|
+
### Writing Rules
|
79
|
+
|
80
|
+
When you write a new rule, it should have a symbol of unique name, and two Ruby code snippets as String, which represents the code pattern to be rewritten, and the code pattern how to rewrite it.
|
81
|
+
|
82
|
+
`Maccro.register(:name_of_this_rule, 'ruby_code_before', 'ruby-code-after')`
|
83
|
+
|
84
|
+
"Before" and "After" code snippets must be a valid Ruby code as themselves (that means it can be parsed without syntax error). We can check it using `ruby -cw -e 'code-snippet'`.
|
85
|
+
For example, the rule below will rewrite `Math.sin(@x)` to `my_own_sin(@x)`.
|
86
|
+
|
87
|
+
`Maccro.register(:rewrite_sin_to_mine, 'Math.sin(@x)', 'my_own_sin(@x)')`
|
88
|
+
|
89
|
+
This rule matches the code exactly equal to `Math.sin(x)`. The receiver must be the `Math` class, method must be the `Math.sin` and the argument must be the instance variable `@x`.
|
90
|
+
If you want to rewrite every `Math.sin` calls to `my_own_sin`, arguments should be a placeholder.
|
91
|
+
|
92
|
+
`Maccro.register(:rewrite_sin_to_mine, 'Math.sin(e1)', 'my_own_sin(e1)')`
|
93
|
+
|
94
|
+
The placeholder `e1` will match to an any expression (which returns a value / values, including literals, variables, function or method calls and if/unless). The `e1` in "Before" captures the actual expression used in the rewritten method definition, and the `e1` in "After" will be replaced with the captured code.
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
# Applying the rule: Maccro.register(:rewrite_sin_to_mine, 'Math.sin(e1)', 'my_own_sin(e1)')
|
98
|
+
|
99
|
+
# Before rewrite
|
100
|
+
def myfunc(x, y, z)
|
101
|
+
return [Math.sin(x), Math.sin(x + y), Math.sin(if x > y then z else 0 end)]
|
102
|
+
end
|
103
|
+
|
104
|
+
# After rewrite
|
105
|
+
def myfunc(x, y, z)
|
106
|
+
return [my_own_sin(x), my_own_sin(x + y), my_own_sin(if x > y then z else 0 end)]
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
A rule will match to codes of the method as much as possible, and will rewrite all matched pieces.
|
111
|
+
|
112
|
+
If the specified placeholder was `v1`, `vN` placeholders matches only with a value (a literal, a local variable, an instance variable, a global variable, etc), so the result will be:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
# Applying the rule: Maccro.register(:rewrite_sin_to_mine, 'Math.sin(v1)', 'my_own_sin(v1)')
|
116
|
+
|
117
|
+
# Before rewrite
|
118
|
+
def myfunc(x, y, z)
|
119
|
+
return [Math.sin(x), Math.sin(x + y), Math.sin(if x > y then z else 0 end)]
|
120
|
+
end
|
121
|
+
|
122
|
+
# After rewrite
|
123
|
+
def myfunc(x, y, z)
|
124
|
+
return [my_own_sin(x), Math.sin(x + y), Math.sin(if x > y then z else 0 end)]
|
125
|
+
# 2nd and 3rd expressions doesn't match to the rule
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
#### Placeholder details
|
130
|
+
|
131
|
+
Placeholders should be the combination of an alphabetic character and an integer. For example, `e1`, `v5`, `v100`, etc. Any integer numbers are available, and there are no need to be continuous numbers (you can use `e2` without `e1`).
|
132
|
+
|
133
|
+
Placeholders can be used in both of "Before" and "After" code snippets, and placeholders used in "After" must be in "Before" too to capture codes to be referred in "After" (otherwise, placeholders in "After" will be left as-is).
|
134
|
+
|
135
|
+
Types of placeholders are defined by alphabetic chars:
|
136
|
+
|
137
|
+
* `v`: values (local variable, instance variable, class variable and global variable, )
|
138
|
+
* local variables
|
139
|
+
* instance variables
|
140
|
+
* class variables
|
141
|
+
* global variables
|
142
|
+
* thread local variables (e.g., `$1` etc)
|
143
|
+
* constants
|
144
|
+
* strings
|
145
|
+
* regular expressions
|
146
|
+
* lambda, array, hash
|
147
|
+
* literals (integer, float, symbol, range, nil, true, false, etc)
|
148
|
+
* self
|
149
|
+
* `e`: expressions, any code which returns a value or values (
|
150
|
+
* all values (matches to `v`)
|
151
|
+
* if, unless, case
|
152
|
+
* and, or
|
153
|
+
* calls of functions, operators
|
154
|
+
* safe call operators (`&.`)
|
155
|
+
* super, yield
|
156
|
+
* match with regular expressions (`=~`)
|
157
|
+
* `defined?`
|
158
|
+
* defining methods and singleton methods
|
159
|
+
* double and trible colon `::` and `:::`
|
160
|
+
* dots and flip-flop
|
161
|
+
|
162
|
+
* TODO: implement placeholder for strings
|
163
|
+
* TODO: implement placeholder for symbols
|
164
|
+
* TODO: implement placeholder for numbers
|
165
|
+
|
166
|
+
#### Using a placeholder twice (or more)
|
167
|
+
|
168
|
+
TODO: using a placeholder twice in "Before" is not implemented now (it doesn't work correctly)
|
169
|
+
|
170
|
+
If "After" code contains a placeholder twice or more, these placeholders will be replaced with the same code snippet captured in "Before".
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
# Applying the rule: Maccro.register(:define_my_own_power, 'power(e1, 3)', 'e1 * e1 * e1')
|
174
|
+
|
175
|
+
# Before rewrite
|
176
|
+
def myfunc(x)
|
177
|
+
return power(x, 3)
|
178
|
+
end
|
179
|
+
|
180
|
+
# After rewrite
|
181
|
+
def myfunc(x)
|
182
|
+
return x * x * x
|
183
|
+
end
|
184
|
+
```
|
185
|
+
|
186
|
+
#### Rules for non-idempotent methods (methods which has side effects)
|
187
|
+
|
188
|
+
If the captured code has side effect and it'll be used more times than "Before", it'll be broken behavior.
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
# Applying the rule: Maccro.register(:define_longer_one, 'longer(e1, e2)', '(e1).length >= (e2).length ? e1 : e2')
|
192
|
+
|
193
|
+
# Before rewrite
|
194
|
+
def myfunc(str1, str2)
|
195
|
+
return longer(str1.succ!, str2.succ!)
|
196
|
+
end
|
197
|
+
|
198
|
+
# After rewrite
|
199
|
+
def myfunc(str1, str2)
|
200
|
+
return (str1.succ!).length >= (str2.succ!) ? str1.succ! : str2.succ!
|
201
|
+
end
|
202
|
+
```
|
203
|
+
|
204
|
+
That may cause unexpected results.
|
205
|
+
|
206
|
+
TODO: implement safe_reference option
|
207
|
+
|
208
|
+
#### Rules for limited source pattern
|
209
|
+
|
210
|
+
If the rule should rewrite the code which is surrounded a pattern of code, the `under` option will help the situation.
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
Macro.register(:rewrite_range_cover, '[e1, v1, e2]', '[(e1)...(e2)].cover?(v1)', under: 'my_dsl_function($TARGET)')'
|
214
|
+
```
|
215
|
+
|
216
|
+
This rule matches to the code in the code captured by `$TARGET`. The placehodler used in `under` option pattern is independent from the placeholders in "Before" and "After".
|
217
|
+
The example behavior is:
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
# Before rewrite
|
221
|
+
def myfunc(v)
|
222
|
+
if v > 1
|
223
|
+
my_dsl_function([1, v, 2] ? 1 : 2)
|
224
|
+
else
|
225
|
+
[1, v, 2] ? 1 : 2
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# After rewrite
|
230
|
+
def myfunc(v)
|
231
|
+
if v > 1
|
232
|
+
my_dsl_function([(1)...(2)].cover?(v) ? 1 : 2)
|
233
|
+
else
|
234
|
+
[1, v, 2] ? 1 : 2
|
235
|
+
end
|
236
|
+
end
|
237
|
+
```
|
238
|
+
|
239
|
+
In this example, the `[1, v, 2]` for the case of `v > 1` is afftected by the rule because it's in the code for the argument of `my_dsl_function`, but the other (for else) is not affected because there are no method call of `my_dsl_function`.
|
240
|
+
|
241
|
+
### Applying Rules
|
242
|
+
|
243
|
+
To apply registered rules on methods manually, call `Maccro.apply` with the module/class and its method. Maccro will try to match all rules to the mthod.
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
# register rules, then
|
247
|
+
Maccro.apply(MyClass, MyClass.instance_method(:foo))
|
248
|
+
```
|
249
|
+
|
250
|
+
When you want to try the selected rules, call `apply` method with `rules` keyword argument.
|
251
|
+
|
252
|
+
```ruby
|
253
|
+
Maccro.apply(MyClass, MyClass.instance_method(:foo), rules: [:rule1, :rule2, :rule3])
|
254
|
+
```
|
255
|
+
|
256
|
+
### Using Built-in Rules
|
257
|
+
|
258
|
+
Maccro has many built-in rules, for continuing less/greater-than or equal-to, for mathematical intervals and ActiveRecord utilities.
|
259
|
+
You can see the list of built-in rules here: [RULE](https://github.com/tagomoris/maccro/blob/master/lib/maccro/builtin.rb#L5).
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
require 'maccro/builtin'
|
263
|
+
|
264
|
+
Maccro::Builtin.register(:built_in_rule_name)
|
265
|
+
|
266
|
+
# or register all built-in rules
|
267
|
+
Maccro::Builtin.register_all
|
268
|
+
```
|
269
|
+
|
270
|
+
Built-in rules can be fetched via `Maccro::Builtin.rule(:name)` or `Maccro::Builtin.rules(:name1, :name2, :name3, ...)`. These rules can be used for `rules` of `Maccro.apply`.
|
271
|
+
|
272
|
+
### Enabling Automatic applying
|
273
|
+
|
274
|
+
Maccro has a feature to rewrite all defined methods using TracePoint. Users can enable Maccro only for a module/class or only for a path.
|
275
|
+
|
276
|
+
```ruby
|
277
|
+
require 'maccro'
|
278
|
+
Maccro.register(...)
|
279
|
+
|
280
|
+
# enable Maccro for a module (MyModule must be defined before)
|
281
|
+
Maccro.enable(target: MyModule, rules: [:name1, :name2, ...])
|
282
|
+
|
283
|
+
# or, enable Maccro for this file, with all rules registered
|
284
|
+
Maccro.enable(path: __FILE__)
|
285
|
+
|
286
|
+
module MyModule
|
287
|
+
# ...
|
288
|
+
end
|
289
|
+
```
|
290
|
+
|
291
|
+
Without any options, `Maccro.enable` enables all rules globally. That is strongly NOT recommended in libraries.
|
292
|
+
|
293
|
+
`Maccro.enable` rewrite all method definitions, defined AFTER `Maccro.enable()`. The methods defined before it will not be updated.
|
294
|
+
|
295
|
+
And `Maccro.enable` rewrites methods at the end of module/class definition. So you need to take care about singleton methods which are called in the class/module definition.
|
296
|
+
|
297
|
+
For example:
|
298
|
+
|
299
|
+
```ruby
|
300
|
+
Maccro.enable(path: __FILE__, rules: [:rewrite_foo_to_bar])
|
301
|
+
|
302
|
+
module MyModule
|
303
|
+
def self.foo
|
304
|
+
"foo" # this should be rewritten to "bar"
|
305
|
+
end
|
306
|
+
|
307
|
+
FOO = self.foo # this value is "foo" here
|
308
|
+
|
309
|
+
end # Maccro works here
|
310
|
+
|
311
|
+
foo = self.foo # this value is "bar" here
|
312
|
+
```
|
313
|
+
|
314
|
+
To enable Maccro globally to rewrite all defined methods by all registered rules, require the file for that. (That is strongly NOT recommended in libraries too!)
|
315
|
+
|
316
|
+
```ruby
|
317
|
+
require 'maccro/rewrite_the_world'
|
318
|
+
```
|
319
|
+
|
320
|
+
Or run ruby with this library.
|
321
|
+
|
322
|
+
```sh
|
323
|
+
$ ruby -rmaccro/rewrite_the_world file_to_run.rb
|
324
|
+
```
|
325
|
+
|
326
|
+
### API
|
327
|
+
|
328
|
+
#### `Maccro#register(name, before, after, **kwarg_options)`
|
329
|
+
|
330
|
+
* name: a symbol to represents the rule
|
331
|
+
* before: a string of Ruby code which matches to be rewritten
|
332
|
+
* after: a string of Ruby code which replaces the matched part
|
333
|
+
* kwarg_options:
|
334
|
+
* under: a string of Ruby code which matches to limit the affected area (must contain `$TARGET`)
|
335
|
+
* safe_reference: TODO: (NOT IMPLEMENTED NOW)
|
336
|
+
|
337
|
+
#### `Maccro#apply(module, method, **kwarg_options)`
|
338
|
+
|
339
|
+
* module: a module/class, the applied method is defined in
|
340
|
+
* method: a method object (an instance method or a singleton method)
|
341
|
+
* kwarg_options:
|
342
|
+
* rules: an array of symbols of rule names (default: all registered rules)
|
343
|
+
|
344
|
+
#### `Maccro#enable(**kwarg_options)`
|
345
|
+
|
346
|
+
* kwarg_options:
|
347
|
+
* target: a module/class to enable Maccro to rewrite all methods defined (exclusive with path)
|
348
|
+
* path: a file path to enable Maccro to rewrite all methods defined (exclusive with target)
|
349
|
+
* rules: an array of symbols of rule names (default: all registered rules)
|
350
|
+
|
351
|
+
`Maccro.enable` can be called for different targets or paths, but calling twice for the same target/path would make troubles.
|
352
|
+
|
353
|
+
## Contributing
|
354
|
+
|
355
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/tagomoris/maccro.
|
356
|
+
|
357
|
+
## License
|
358
|
+
|
359
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "maccro"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|