rephrase 0.1 → 0.2
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/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
|
[](http://rubygems.org/gems/rephrase)
|
4
4
|
[](https://github.com/digital-fabric/rephrase/actions?query=workflow%3ATests)
|
5
5
|
[](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
|