rephrase 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +40 -0
- data/lib/rephrase/version.rb +1 -1
- data/lib/rephrase.rb +138 -4
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 50dd6a940c19895850f10694544b163b9362ab853242972760a885f555750a54
|
4
|
+
data.tar.gz: 0d41895ceb222d573384ae073bc183d0eaf5ee3d3429acc5f59f894af4af628f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz: '
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0513931ee88c085b5026f71e9e5f427664a745ba93fa94e0d20422d7b690dec9bce8142fc8129ebd59379c0f3ee7f425d898a671011762c2c373c8266ea47a83'
|
7
|
+
data.tar.gz: 42757fb4d2ca4cad5feff6b19a42350662875864e8c262bced0dd6dcb73c1ed12d20630884ee09e9351a3411ca438360b76909e2a927467561c25e911415f50d
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -3,3 +3,43 @@
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/rephrase.svg)](http://rubygems.org/gems/rephrase)
|
4
4
|
[![Modulation Test](https://github.com/digital-fabric/rephrase/workflows/Tests/badge.svg)](https://github.com/digital-fabric/rephrase/actions?query=workflow%3ATests)
|
5
5
|
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/digital-fabric/rephrase/blob/master/LICENSE)
|
6
|
+
|
7
|
+
## Summary
|
8
|
+
|
9
|
+
Rephrase converts Ruby procs or methods to source code, allowing you to
|
10
|
+
reformat, reinterpret or otherwise manipulate the generated source code.
|
11
|
+
Possible uses include:
|
12
|
+
|
13
|
+
- Generating code from DSL blocks.
|
14
|
+
- Inlining loops.
|
15
|
+
- Macro exansion.
|
16
|
+
- World domination (???)
|
17
|
+
|
18
|
+
## How does it do it?
|
19
|
+
|
20
|
+
Rephrase uses the `RubyVM::AbstractSyntaxTree` API to get the AST of a proc or
|
21
|
+
method. This allows you to manipulate code at runtime, and to be able to access
|
22
|
+
its binding.
|
23
|
+
|
24
|
+
## How to use it
|
25
|
+
|
26
|
+
Use `Rephrase.to_source` to get the unmodified source code of a proc or method,
|
27
|
+
e.g.:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
require 'rephrase'
|
31
|
+
|
32
|
+
example = proc { 2 + 2 }
|
33
|
+
Rephrase.to_source(example) #=> "proc do\n2 + 2\nend"
|
34
|
+
```
|
35
|
+
|
36
|
+
## How to rephrase code
|
37
|
+
|
38
|
+
Further documentation is forthcoming...
|
39
|
+
|
40
|
+
## Limitations
|
41
|
+
|
42
|
+
- Works only on MRI.
|
43
|
+
- Ruby 2.7 or newer.
|
44
|
+
- Generated source code will not be formatted identically to the source
|
45
|
+
(indentation, line breaks etc.)
|
data/lib/rephrase/version.rb
CHANGED
data/lib/rephrase.rb
CHANGED
@@ -4,35 +4,76 @@
|
|
4
4
|
# for use in Pry, see https://dev.to/okinawarb/using-rubyvm-abstractsyntaxtree-of-in-pry-4cm3
|
5
5
|
|
6
6
|
class Rephrase
|
7
|
+
# Class for faking a node
|
7
8
|
class FakeNode
|
9
|
+
# Constructs a FakeNode with type `:LIST_EMBEDDED`
|
10
|
+
# @param children [Array] child nodes
|
8
11
|
def self.list(children)
|
9
12
|
new(:LIST_EMBEDDED, children)
|
10
13
|
end
|
11
14
|
|
15
|
+
# Constructs a FakeNode with type `:ITER_SCOPE`
|
16
|
+
# @param children [Array] child nodes
|
12
17
|
def self.iter_scope(children)
|
13
18
|
new(:ITER_SCOPE, children)
|
14
19
|
end
|
15
20
|
|
16
21
|
attr_reader :type, :children
|
17
22
|
|
23
|
+
# Initializes a FakeNode
|
24
|
+
# @param type [Symbol] node type
|
25
|
+
# @param children [Array] child nodes
|
18
26
|
def initialize(type, children)
|
19
27
|
@type = type
|
20
28
|
@children = children
|
21
29
|
end
|
22
30
|
end
|
31
|
+
|
32
|
+
# Converts a :block or method to its source code
|
33
|
+
# @param code [Proc, BoundMethod, UnboundMethod] proc or method
|
34
|
+
# @return [String] converted source code
|
35
|
+
def self.to_source(code)
|
36
|
+
ast = RubyVM::AbstractSyntaxTree.of(code)
|
37
|
+
new.convert({}, ast)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Pretty-prints an AST
|
41
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
42
|
+
def self.pp_ast(node, level = 0)
|
43
|
+
case node
|
44
|
+
when RubyVM::AbstractSyntaxTree::Node
|
45
|
+
puts "#{' ' * level}#{node.type.inspect} (#{node.first_lineno})"
|
46
|
+
node.children.each { |c| pp_ast(c, level + 1) }
|
47
|
+
when Array
|
48
|
+
puts "#{' ' * level}["
|
49
|
+
node.each { |c| pp_ast(c, level + 1) }
|
50
|
+
puts "#{' ' * level}]"
|
51
|
+
else
|
52
|
+
puts "#{' ' * level}#{node.inspect}"
|
53
|
+
return
|
54
|
+
end
|
55
|
+
end
|
23
56
|
|
57
|
+
# Converts an AST node to a string containing its source code
|
58
|
+
# @param ctx [Hash] context object
|
59
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
60
|
+
# @return [String] source code
|
24
61
|
def convert(ctx, node)
|
25
62
|
ctx[:buffer] ||= +''
|
26
63
|
ctx[:indent] ||= 0
|
27
64
|
method_name = :"on_#{node.type.downcase}"
|
28
|
-
|
29
|
-
|
30
|
-
ctx[:buffer]
|
31
|
-
else
|
65
|
+
|
66
|
+
if !respond_to?(method_name)
|
32
67
|
raise "Could not convert #{node.type} node to ruby"
|
33
68
|
end
|
69
|
+
|
70
|
+
send(method_name, ctx, node)
|
71
|
+
ctx[:buffer]
|
34
72
|
end
|
35
73
|
|
74
|
+
# Converts a :SCOPE AST node
|
75
|
+
# @param ctx [Hash] context object
|
76
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
36
77
|
def on_scope(ctx, node)
|
37
78
|
body = node.children.last
|
38
79
|
# if body.type != :BLOCK
|
@@ -41,6 +82,9 @@ class Rephrase
|
|
41
82
|
emit(ctx, "proc do", [body], "end")
|
42
83
|
end
|
43
84
|
|
85
|
+
# Converts a :BLOCK AST node
|
86
|
+
# @param ctx [Hash] context object
|
87
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
44
88
|
def on_block(ctx, node)
|
45
89
|
last_idx = node.children.size - 1
|
46
90
|
node.children.each_with_index do |c, idx|
|
@@ -49,12 +93,18 @@ class Rephrase
|
|
49
93
|
end
|
50
94
|
end
|
51
95
|
|
96
|
+
# Converts a :ITER AST node
|
97
|
+
# @param ctx [Hash] context object
|
98
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
52
99
|
def on_iter(ctx, node)
|
53
100
|
call, scope = node.children
|
54
101
|
emit(ctx, call)
|
55
102
|
emit(ctx, FakeNode.iter_scope(scope.children))
|
56
103
|
end
|
57
104
|
|
105
|
+
# Converts a :ITER_SCOPE AST (fake) node
|
106
|
+
# @param ctx [Hash] context object
|
107
|
+
# @param node [Rephrase::FakeNode] AST node
|
58
108
|
def on_iter_scope(ctx, node)
|
59
109
|
args, arg_spec, body = node.children
|
60
110
|
emit(ctx, " do")
|
@@ -64,15 +114,24 @@ class Rephrase
|
|
64
114
|
emit(ctx, "\nend")
|
65
115
|
end
|
66
116
|
|
117
|
+
# Converts a :CONST AST node
|
118
|
+
# @param ctx [Hash] context object
|
119
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
67
120
|
def on_const(ctx, node)
|
68
121
|
emit(ctx, node.children.first.to_s)
|
69
122
|
end
|
70
123
|
|
124
|
+
# Converts a :COLON2 AST node
|
125
|
+
# @param ctx [Hash] context object
|
126
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
71
127
|
def on_colon2(ctx, node)
|
72
128
|
left, right = node.children
|
73
129
|
emit(ctx, left, "::", right.to_s)
|
74
130
|
end
|
75
131
|
|
132
|
+
# Converts a :CALL AST node
|
133
|
+
# @param ctx [Hash] context object
|
134
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
76
135
|
def on_call(ctx, node)
|
77
136
|
receiver, method, args = node.children
|
78
137
|
args = args && FakeNode.list(args.children)
|
@@ -88,16 +147,25 @@ class Rephrase
|
|
88
147
|
end
|
89
148
|
end
|
90
149
|
|
150
|
+
# Converts a :FCALL AST node
|
151
|
+
# @param ctx [Hash] context object
|
152
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
91
153
|
def on_fcall(ctx, node)
|
92
154
|
method, args = node.children
|
93
155
|
args = args && FakeNode.list(args.children)
|
94
156
|
emit(ctx, "#{method}(", args, ")")
|
95
157
|
end
|
96
158
|
|
159
|
+
# Converts a :VCALL AST node
|
160
|
+
# @param ctx [Hash] context object
|
161
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
97
162
|
def on_vcall(ctx, node)
|
98
163
|
emit(ctx, node.children.first.to_s, "()")
|
99
164
|
end
|
100
165
|
|
166
|
+
# Converts a :OPCALL AST node
|
167
|
+
# @param ctx [Hash] context object
|
168
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
101
169
|
def on_opcall(ctx, node)
|
102
170
|
left, op, right = node.children
|
103
171
|
if op == :!
|
@@ -107,16 +175,25 @@ class Rephrase
|
|
107
175
|
end
|
108
176
|
end
|
109
177
|
|
178
|
+
# Converts a :DASGN_CURR AST node
|
179
|
+
# @param ctx [Hash] context object
|
180
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
110
181
|
def on_dasgn_curr(ctx, node)
|
111
182
|
left, right = node.children
|
112
183
|
emit(ctx, left.to_s, " = ", right)
|
113
184
|
end
|
114
185
|
|
186
|
+
# Converts a :IASGN AST node
|
187
|
+
# @param ctx [Hash] context object
|
188
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
115
189
|
def on_iasgn(ctx, node)
|
116
190
|
left, right = node.children
|
117
191
|
emit(ctx, left.to_s, " = ", right)
|
118
192
|
end
|
119
193
|
|
194
|
+
# Converts a :IF AST node
|
195
|
+
# @param ctx [Hash] context object
|
196
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
120
197
|
def on_if(ctx, node)
|
121
198
|
cond, branch1, branch2 = node.children
|
122
199
|
if branch2
|
@@ -126,6 +203,9 @@ class Rephrase
|
|
126
203
|
end
|
127
204
|
end
|
128
205
|
|
206
|
+
# Converts a :UNLESS AST node
|
207
|
+
# @param ctx [Hash] context object
|
208
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
129
209
|
def on_unless(ctx, node)
|
130
210
|
cond, branch1, branch2 = node.children
|
131
211
|
if branch2
|
@@ -135,39 +215,66 @@ class Rephrase
|
|
135
215
|
end
|
136
216
|
end
|
137
217
|
|
218
|
+
# Converts a :WHILE AST node
|
219
|
+
# @param ctx [Hash] context object
|
220
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
138
221
|
def on_while(ctx, node)
|
139
222
|
cond, body = node.children
|
140
223
|
emit(ctx, "while ", cond, "\n", body, "\nend")
|
141
224
|
end
|
142
225
|
|
226
|
+
# Converts a :LIT AST node
|
227
|
+
# @param ctx [Hash] context object
|
228
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
143
229
|
def on_lit(ctx, node)
|
144
230
|
emit(ctx, node.children.first.inspect)
|
145
231
|
end
|
146
232
|
|
233
|
+
# Converts a :NIL AST node
|
234
|
+
# @param ctx [Hash] context object
|
235
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
147
236
|
def on_nil(ctx, node)
|
148
237
|
emit(ctx, "nil")
|
149
238
|
end
|
150
239
|
|
240
|
+
# Converts a :TRUE AST node
|
241
|
+
# @param ctx [Hash] context object
|
242
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
151
243
|
def on_true(ctx, node)
|
152
244
|
emit(ctx, "true")
|
153
245
|
end
|
154
246
|
|
247
|
+
# Converts a :FALSE AST node
|
248
|
+
# @param ctx [Hash] context object
|
249
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
155
250
|
def on_false(ctx, node)
|
156
251
|
emit(ctx, "false")
|
157
252
|
end
|
158
253
|
|
254
|
+
# Converts a :DVAR AST node
|
255
|
+
# @param ctx [Hash] context object
|
256
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
159
257
|
def on_dvar(ctx, node)
|
160
258
|
emit(ctx, node.children.first.to_s)
|
161
259
|
end
|
162
260
|
|
261
|
+
# Converts a :IVAR AST node
|
262
|
+
# @param ctx [Hash] context object
|
263
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
163
264
|
def on_ivar(ctx, node)
|
164
265
|
emit(ctx, node.children.first.to_s)
|
165
266
|
end
|
166
267
|
|
268
|
+
# Converts a :STR AST node
|
269
|
+
# @param ctx [Hash] context object
|
270
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
167
271
|
def on_str(ctx, node)
|
168
272
|
emit(ctx, node.children.first.inspect)
|
169
273
|
end
|
170
274
|
|
275
|
+
# Converts a :DSTR AST node
|
276
|
+
# @param ctx [Hash] context object
|
277
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
171
278
|
def on_dstr(ctx, node)
|
172
279
|
prefix, evstr1, rest = node.children
|
173
280
|
emit(ctx, "\"", prefix.inspect[1..-2], evstr1)
|
@@ -186,20 +293,32 @@ class Rephrase
|
|
186
293
|
emit(ctx, "\"")
|
187
294
|
end
|
188
295
|
|
296
|
+
# Converts a :EVSTR AST node
|
297
|
+
# @param ctx [Hash] context object
|
298
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
189
299
|
def on_evstr(ctx, node)
|
190
300
|
emit(ctx, "\#{", node.children.first, "}")
|
191
301
|
end
|
192
302
|
|
303
|
+
# Converts a :AND AST node
|
304
|
+
# @param ctx [Hash] context object
|
305
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
193
306
|
def on_and(ctx, node)
|
194
307
|
left, right = node.children
|
195
308
|
emit(ctx, left, " && ", right)
|
196
309
|
end
|
197
310
|
|
311
|
+
# Converts a :OR AST node
|
312
|
+
# @param ctx [Hash] context object
|
313
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
198
314
|
def on_or(ctx, node)
|
199
315
|
left, right = node.children
|
200
316
|
emit(ctx, left, " || ", right)
|
201
317
|
end
|
202
318
|
|
319
|
+
# Converts a :LIST AST node
|
320
|
+
# @param ctx [Hash] context object
|
321
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
203
322
|
def on_list(ctx, node)
|
204
323
|
items = node.children[0..-2]
|
205
324
|
last_idx = items.size - 1
|
@@ -211,6 +330,9 @@ class Rephrase
|
|
211
330
|
emit(ctx, "]")
|
212
331
|
end
|
213
332
|
|
333
|
+
# Converts a :LIST_EMBEDDED AST (fake) node
|
334
|
+
# @param ctx [Hash] context object
|
335
|
+
# @param node [Rephrase::FakeNode] AST node
|
214
336
|
def on_list_embedded(ctx, node)
|
215
337
|
items = node.children.compact
|
216
338
|
last_idx = items.size - 1
|
@@ -220,6 +342,9 @@ class Rephrase
|
|
220
342
|
end
|
221
343
|
end
|
222
344
|
|
345
|
+
# Converts a :HASH AST node
|
346
|
+
# @param ctx [Hash] context object
|
347
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
223
348
|
def on_hash(ctx, node)
|
224
349
|
list = node.children.first
|
225
350
|
idx = 0
|
@@ -235,6 +360,9 @@ class Rephrase
|
|
235
360
|
emit(ctx, "}")
|
236
361
|
end
|
237
362
|
|
363
|
+
# Converts a :ATTRASGN AST node
|
364
|
+
# @param ctx [Hash] context object
|
365
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
238
366
|
def on_attrasgn(ctx, node)
|
239
367
|
left, op, args = node.children
|
240
368
|
emit(ctx, left)
|
@@ -249,12 +377,18 @@ class Rephrase
|
|
249
377
|
end
|
250
378
|
end
|
251
379
|
|
380
|
+
# Converts a :RESCUE AST node
|
381
|
+
# @param ctx [Hash] context object
|
382
|
+
# @param node [RubyVM::AbstractSyntaxTree::Node] AST node
|
252
383
|
def on_rescue(ctx, node)
|
253
384
|
code, rescue_body = node.children
|
254
385
|
emit(ctx, code)
|
255
386
|
emit(ctx, " rescue ", rescue_body.children[1])
|
256
387
|
end
|
257
388
|
|
389
|
+
# Emits code into the context buffer
|
390
|
+
# @param ctx [Hash] context object
|
391
|
+
# @param entries [Array<String, Symbol>] code entries
|
258
392
|
def emit(ctx, *entries)
|
259
393
|
entries.each do |e|
|
260
394
|
case e
|