maccro 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ *~
10
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in maccro.gemspec
4
+ gemspec
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
@@ -0,0 +1,12 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib' << 'test'
7
+ test.test_files = FileList['test/**/test_*.rb']
8
+ test.warning = true
9
+ test.verbose = true
10
+ end
11
+
12
+ task :default => :test
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__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here