sirop 0.1 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +110 -1
- data/lib/sirop/finder.rb +29 -0
- data/lib/sirop/injection.rb +11 -0
- data/lib/sirop/prism_ext.rb +34 -0
- data/lib/sirop/sourcifier.rb +308 -0
- data/lib/sirop/version.rb +1 -1
- data/lib/sirop.rb +48 -8
- metadata +8 -6
- data/lib/sirop/block_finder.rb +0 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a44761c6907c2bd7358e0b7d6c4079b8582949121ff32161b508a0950d0032c
|
4
|
+
data.tar.gz: 99cf621e80d420ec1a4245e8fd9efdbb06633614982d804d45f545f0a1966a0b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de992959d865f0ace64d1be7b9c0149a10d193f54e2bfa0e9b20dcd2fe70f7bf4fe3ec5984c29a75cf89bc70dc385d29747ba372174bfe4286654b3543e0f946
|
7
|
+
data.tar.gz: '00393520ab9df84615c35a104411d4aad118c07ac4d4212debbf21df536f90883dd924e31e4d9064d4ce7344fa425716c292d4f9fbe3e1fb3b675ccea054fe95'
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
# 2024-04-19 0.3
|
2
|
+
|
3
|
+
- Add support for injecting (prefixed) parameters to a lambda node
|
4
|
+
- Fix whitespace adjustment in DSL compiler
|
5
|
+
- Improve DSL compiler to support embedded string expressions
|
6
|
+
- Correctly handle eval'd proc
|
7
|
+
|
8
|
+
# 2024-02-27 0.2
|
9
|
+
|
10
|
+
- Update README
|
11
|
+
- Remove support for Ruby < 3.2
|
12
|
+
- Implement general purpose Finder for finding nodes
|
13
|
+
- Implement DSL compiler (for tests)
|
14
|
+
- Implement Sirop.to_source
|
15
|
+
|
1
16
|
# 2024-02-20 0.1
|
2
17
|
|
3
18
|
- Find node for a given `Proc` object
|
data/README.md
CHANGED
@@ -1,4 +1,113 @@
|
|
1
1
|
# Sirop
|
2
2
|
|
3
|
-
Sirop is a Ruby code
|
3
|
+
Sirop is a Ruby gem for manipulating Ruby source code. Sirop is very young, so
|
4
|
+
the following information might be incomplete, out of date, or simply wrong!
|
4
5
|
|
6
|
+
## Use Cases
|
7
|
+
|
8
|
+
Some of the use cases addressed by Sirop are:
|
9
|
+
|
10
|
+
- Compile DSLs into optimized Ruby code. This is especially interesting for HTML
|
11
|
+
templating DSLs in libraries like Phlex, Papercraft etc.
|
12
|
+
[Example](https://github.com/digital-fabric/sirop/blob/main/test/dsl_compiler.rb)
|
13
|
+
- Get the source of a given block or method.
|
14
|
+
- Rewrite parts of Ruby code, for implementing Ruby macros (and why not?).
|
15
|
+
|
16
|
+
## Limitations
|
17
|
+
|
18
|
+
- Sirop supports Ruby 3.2 or newer.
|
19
|
+
- Sirop can be used only on blocks and methods defined in a file, so cannot
|
20
|
+
really be used on dynamically `eval`'d Ruby code, or in an IRB/Pry session.
|
21
|
+
|
22
|
+
## Getting the AST/source of a Ruby proc or method
|
23
|
+
|
24
|
+
To get the AST of a proc or a method, use `Sirop.to_ast`:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
# for a proc
|
28
|
+
mul = ->(x, y) { x * y }
|
29
|
+
Sirop.to_ast(mul) #=> ...
|
30
|
+
|
31
|
+
# for a method
|
32
|
+
def foo; :bar; end
|
33
|
+
Sirop.to_ast(method(:foo)) #=> ...
|
34
|
+
```
|
35
|
+
|
36
|
+
To get the source of a proc or a method, use `Sirop.to_source`:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
mul = ->(x, y) { x * y }
|
40
|
+
Sirop.to_source(mul) #=> "->(x, y) { x * y }"
|
41
|
+
|
42
|
+
def foo; :bar; end
|
43
|
+
Sirop.to_source(method(:foo)) #=> "def foo; :bar; end"
|
44
|
+
```
|
45
|
+
|
46
|
+
## Rewriting Ruby code
|
47
|
+
|
48
|
+
You can consult the [DSL compiler
|
49
|
+
example](https://github.com/digital-fabric/sirop/blob/main/test/dsl_compiler.rb). This example intercepts method calls by defining a `visit_call_node` method:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
# Annotated with some explanations
|
53
|
+
def visit_call_node(node)
|
54
|
+
# don't rewrite if the call has a receiver
|
55
|
+
return super if node.receiver
|
56
|
+
|
57
|
+
# set HTML location start
|
58
|
+
@html_location_start ||= node.location
|
59
|
+
# get method arguments...
|
60
|
+
inner_text, attrs = tag_args(node)
|
61
|
+
# and block
|
62
|
+
block = node.block
|
63
|
+
|
64
|
+
# emit HTML tag according to given arguments
|
65
|
+
if inner_text
|
66
|
+
emit_tag_open(node, attrs)
|
67
|
+
emit_tag_inner_text(inner_text)
|
68
|
+
emit_tag_close(node)
|
69
|
+
elsif block
|
70
|
+
emit_tag_open(node, attrs)
|
71
|
+
visit(block.body)
|
72
|
+
emit_tag_close(node)
|
73
|
+
else
|
74
|
+
emit_tag_open_close(node, attrs)
|
75
|
+
end
|
76
|
+
# set HTML location end
|
77
|
+
@html_location_end = node.location
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
## Future directions
|
82
|
+
|
83
|
+
- Implement a macro expander with support for `quote`/`unquote`:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
trace_macro = Sirop.macro do |ast|
|
87
|
+
source = Sirop.to_source(ast)
|
88
|
+
quote do
|
89
|
+
result = unquote(ast)
|
90
|
+
puts "The result of #{source} is: #{result}"
|
91
|
+
result
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def add(x, y)
|
96
|
+
trace(x + y)
|
97
|
+
end
|
98
|
+
|
99
|
+
Sirop.expand_macros(method(:add), trace: trace_macro)
|
100
|
+
```
|
101
|
+
|
102
|
+
- Implement a DSL compiler with hooks for easier usage in DSL libraries.
|
103
|
+
|
104
|
+
## Contributing
|
105
|
+
|
106
|
+
We gladly welcome contributions from anyone! Some areas that need work currently
|
107
|
+
are:
|
108
|
+
|
109
|
+
- Documentation
|
110
|
+
- More test cases for Ruby syntax in the Sirop tests. Look here:
|
111
|
+
https://github.com/digital-fabric/sirop/tree/main/test/fixtures
|
112
|
+
|
113
|
+
Please feel free to contribute PR's and issues
|
data/lib/sirop/finder.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'prism'
|
4
|
+
|
5
|
+
module Sirop
|
6
|
+
class Finder < Prism::BasicVisitor
|
7
|
+
def self.find(*, &)
|
8
|
+
finder = self.new
|
9
|
+
finder.find(*, &)
|
10
|
+
end
|
11
|
+
|
12
|
+
def find(root, key, &)
|
13
|
+
instance_exec(&)
|
14
|
+
@key = key
|
15
|
+
catch(key) do
|
16
|
+
visit(root)
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def found!(node)
|
22
|
+
throw(@key, node)
|
23
|
+
end
|
24
|
+
|
25
|
+
def method_missing(sym, node, *args)
|
26
|
+
visit_child_nodes(node)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'prism'
|
4
|
+
|
5
|
+
class Prism::BasicVisitor
|
6
|
+
def on(type, &)
|
7
|
+
singleton_class.define_method(:"visit_#{type}_node", &)
|
8
|
+
self
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Prism::ParametersNode
|
13
|
+
attr_accessor :injected_prefix
|
14
|
+
end
|
15
|
+
|
16
|
+
class Prism::BlockParametersNode
|
17
|
+
attr_accessor :injected_parameters
|
18
|
+
end
|
19
|
+
|
20
|
+
class Prism::LambdaNode
|
21
|
+
# @param params [String] injected parameters
|
22
|
+
# @return [void]
|
23
|
+
def inject_parameters(params)
|
24
|
+
if parameters
|
25
|
+
if parameters.parameters
|
26
|
+
parameters.parameters.injected_prefix = Sirop::Injection.new(params)
|
27
|
+
else
|
28
|
+
parameters.injected_parameters = Sirop::Injection.new(params)
|
29
|
+
end
|
30
|
+
else
|
31
|
+
instance_variable_set(:@parameters, Sirop::Injection.new("(#{params})"))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,308 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'prism'
|
4
|
+
|
5
|
+
module Sirop
|
6
|
+
#
|
7
|
+
class Sourcifier < Prism::BasicVisitor
|
8
|
+
attr_reader :buffer
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@buffer = +''
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_source(node)
|
15
|
+
@buffer.clear
|
16
|
+
visit(node)
|
17
|
+
@buffer
|
18
|
+
end
|
19
|
+
|
20
|
+
def loc_start(loc)
|
21
|
+
[loc.start_line, loc.start_column]
|
22
|
+
end
|
23
|
+
|
24
|
+
def loc_end(loc)
|
25
|
+
[loc.end_line, loc.end_column]
|
26
|
+
end
|
27
|
+
|
28
|
+
def emit(str)
|
29
|
+
@buffer << str
|
30
|
+
end
|
31
|
+
|
32
|
+
def adjust_whitespace(loc)
|
33
|
+
return if loc.is_a?(Sirop::Injection)
|
34
|
+
|
35
|
+
if @last_loc_start
|
36
|
+
line_diff = loc.start_line - @last_loc_end.first
|
37
|
+
if line_diff > 0
|
38
|
+
@buffer << "\n" * line_diff
|
39
|
+
@buffer << ' ' * loc.start_column
|
40
|
+
elsif line_diff == 0
|
41
|
+
ofs = loc.start_column - @last_loc_end.last
|
42
|
+
if ofs > 0
|
43
|
+
@buffer << ' ' * ofs
|
44
|
+
end
|
45
|
+
end
|
46
|
+
else
|
47
|
+
# empty buffer
|
48
|
+
@buffer << ' ' * loc.start_column
|
49
|
+
end
|
50
|
+
@last_loc = loc
|
51
|
+
@last_loc_start = loc_start(loc)
|
52
|
+
@last_loc_end = loc_end(loc)
|
53
|
+
end
|
54
|
+
|
55
|
+
def emit_code(loc, semicolon: false)
|
56
|
+
return if !loc
|
57
|
+
|
58
|
+
emit_semicolon(loc) if semicolon
|
59
|
+
return visit(loc) if loc.is_a?(Prism::Node)
|
60
|
+
|
61
|
+
adjust_whitespace(loc)
|
62
|
+
emit(loc.slice)
|
63
|
+
end
|
64
|
+
|
65
|
+
def emit_verbatim(node)
|
66
|
+
emit_code(node.location)
|
67
|
+
end
|
68
|
+
|
69
|
+
def emit_str(str)
|
70
|
+
emit(str)
|
71
|
+
@last_loc_end[1] += str.size
|
72
|
+
end
|
73
|
+
|
74
|
+
def emit_comma
|
75
|
+
emit_str(',')
|
76
|
+
end
|
77
|
+
|
78
|
+
def emit_semicolon(loc)
|
79
|
+
loc = loc.location if loc.is_a?(Prism::Node)
|
80
|
+
emit_str(';') if loc.start_line == @last_loc.end_line
|
81
|
+
end
|
82
|
+
|
83
|
+
def method_missing(sym, node, *args)
|
84
|
+
puts '!' * 40
|
85
|
+
p node
|
86
|
+
raise NotImplementedError, "Don't know how to handle #{sym}"
|
87
|
+
visit_child_nodes(node)
|
88
|
+
end
|
89
|
+
|
90
|
+
VISIT_PLANS = {
|
91
|
+
and: [:left, :operator_loc, :right],
|
92
|
+
assoc: :visit_child_nodes,
|
93
|
+
assoc_splat: [:operator_loc, :value],
|
94
|
+
block: [:opening_loc, :parameters, :body, :closing_loc],
|
95
|
+
block_argument: [:operator_loc, :expression],
|
96
|
+
block_parameter: [:operator_loc, :name_loc],
|
97
|
+
block_parameters: [:opening_loc, :injected_parameters, :parameters, :closing_loc],
|
98
|
+
break: [:keyword_loc, :arguments],
|
99
|
+
constant_path: [:parent, :delimiter_loc, :child],
|
100
|
+
constant_read: :emit_verbatim,
|
101
|
+
else: [:else_keyword_loc, :statements],
|
102
|
+
embedded_statements: [:opening_loc, :statements, :closing_loc],
|
103
|
+
false: :emit_verbatim,
|
104
|
+
integer: :emit_verbatim,
|
105
|
+
keyword_rest_parameter: [:operator_loc, :name_loc],
|
106
|
+
keyword_parameter: :emit_verbatim,
|
107
|
+
lambda: [:operator_loc, :parameters, :opening_loc, :body,
|
108
|
+
:closing_loc],
|
109
|
+
local_variable_read: :emit_verbatim,
|
110
|
+
local_variable_write: [:name_loc, :operator_loc, :value],
|
111
|
+
next: [:keyword_loc, :arguments],
|
112
|
+
nil: :emit_verbatim,
|
113
|
+
optional_parameter: [:name_loc, :operator_loc, :value],
|
114
|
+
optional_keyword_parameter: [:name_loc, :value],
|
115
|
+
or: [:left, :operator_loc, :right],
|
116
|
+
parentheses: [:opening_loc, :body, :closing_loc],
|
117
|
+
required_parameter: :emit_verbatim,
|
118
|
+
required_keyword_parameter: :emit_verbatim,
|
119
|
+
rest_parameter: [:operator_loc, :name_loc],
|
120
|
+
splat: [:operator_loc, :expression],
|
121
|
+
statements: :visit_child_nodes,
|
122
|
+
string: :emit_verbatim,
|
123
|
+
symbol: :emit_verbatim,
|
124
|
+
true: :emit_verbatim,
|
125
|
+
yield: [:keyword_loc, :lparen_loc, :arguments, :rparen_loc],
|
126
|
+
}
|
127
|
+
|
128
|
+
VISIT_PLANS.each do |key, plan|
|
129
|
+
sym = :"visit_#{key}_node"
|
130
|
+
define_method(sym) { |n| visit_plan(plan, n) }
|
131
|
+
end
|
132
|
+
|
133
|
+
def visit_plan(plan, node)
|
134
|
+
return send(plan, node) if plan.is_a?(Symbol)
|
135
|
+
|
136
|
+
insert_semicolon = false
|
137
|
+
plan.each_with_index do |sym, idx|
|
138
|
+
if sym == :semicolon
|
139
|
+
insert_semicolon = true
|
140
|
+
next
|
141
|
+
end
|
142
|
+
|
143
|
+
obj = node.send(sym)
|
144
|
+
emit_code(obj, semicolon: insert_semicolon)
|
145
|
+
insert_semicolon = false
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def visit_comma_separated_nodes(list, comma = false)
|
150
|
+
if list
|
151
|
+
list.each_with_index do |child, idx|
|
152
|
+
emit_comma if comma
|
153
|
+
emit_code(child)
|
154
|
+
comma = true
|
155
|
+
end
|
156
|
+
end
|
157
|
+
comma
|
158
|
+
end
|
159
|
+
|
160
|
+
def visit_parameters_node(node)
|
161
|
+
comma = false
|
162
|
+
# injected_prefix is a custom attribute added by Sirop to the
|
163
|
+
# ParametersNode class (in lib/sirop/prism_ext.rb). It is used
|
164
|
+
# as a way to add a first parameter to a block or method.
|
165
|
+
if node.injected_prefix
|
166
|
+
emit_code(node.injected_prefix)
|
167
|
+
# adjust last_loc_end for proper whitespace after comma
|
168
|
+
@last_loc_end[1] -= 2 if @last_loc_end
|
169
|
+
# binding.irb
|
170
|
+
comma = true
|
171
|
+
end
|
172
|
+
comma = visit_comma_separated_nodes(node.requireds, comma)
|
173
|
+
comma = visit_comma_separated_nodes(node.optionals, comma)
|
174
|
+
comma = visit_comma_separated_nodes(node.posts, comma)
|
175
|
+
comma = visit_comma_separated_nodes(node.keywords, comma)
|
176
|
+
if node.rest
|
177
|
+
emit_comma if comma
|
178
|
+
comma = true
|
179
|
+
emit_code(node.rest)
|
180
|
+
end
|
181
|
+
if node.keyword_rest
|
182
|
+
emit_comma if comma
|
183
|
+
comma = true
|
184
|
+
emit_code(node.keyword_rest)
|
185
|
+
end
|
186
|
+
if node.block
|
187
|
+
emit_comma if comma
|
188
|
+
comma = true
|
189
|
+
emit_code(node.block)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def visit_arguments_node(node)
|
194
|
+
visit_comma_separated_nodes(node.arguments)
|
195
|
+
end
|
196
|
+
|
197
|
+
def visit_keyword_hash_node(node)
|
198
|
+
visit_comma_separated_nodes(node.elements)
|
199
|
+
end
|
200
|
+
|
201
|
+
def visit_if_node(node)
|
202
|
+
if !node.if_keyword_loc
|
203
|
+
return visit_if_node_ternary(node)
|
204
|
+
elsif !node.end_keyword_loc
|
205
|
+
return visit_if_node_guard(node)
|
206
|
+
end
|
207
|
+
|
208
|
+
emit_code(node.if_keyword_loc)
|
209
|
+
emit_code(node.predicate)
|
210
|
+
emit_code(node.then_keyword_loc)
|
211
|
+
emit_code(node.statements)
|
212
|
+
emit_code(node.consequent) if node.consequent
|
213
|
+
emit_code(node.end_keyword_loc) if node.if_keyword_loc.slice == 'if'
|
214
|
+
end
|
215
|
+
|
216
|
+
def visit_if_node_ternary(node)
|
217
|
+
emit_code(node.predicate)
|
218
|
+
emit_code(node.then_keyword_loc)
|
219
|
+
emit_code(node.statements)
|
220
|
+
emit_code(node.consequent)
|
221
|
+
end
|
222
|
+
|
223
|
+
def visit_if_node_guard(node)
|
224
|
+
emit_code(node.statements)
|
225
|
+
emit_code(node.if_keyword_loc)
|
226
|
+
emit_code(node.predicate)
|
227
|
+
end
|
228
|
+
|
229
|
+
def visit_case_node(node)
|
230
|
+
emit_code(node.case_keyword_loc)
|
231
|
+
emit_code(node.predicate)
|
232
|
+
node.conditions.each { |c| emit_code(c) }
|
233
|
+
emit_code(node.consequent)
|
234
|
+
emit_code(node.end_keyword_loc)
|
235
|
+
end
|
236
|
+
|
237
|
+
def visit_when_node(node)
|
238
|
+
emit_code(node.keyword_loc)
|
239
|
+
visit_comma_separated_nodes(node.conditions)
|
240
|
+
emit_code(node.statements)
|
241
|
+
end
|
242
|
+
|
243
|
+
def visit_interpolated_symbol_node(node)
|
244
|
+
emit_code(node.opening_loc)
|
245
|
+
node.parts.each { |p| emit_code(p) }
|
246
|
+
emit_code(node.closing_loc)
|
247
|
+
end
|
248
|
+
alias_method :visit_interpolated_string_node, :visit_interpolated_symbol_node
|
249
|
+
|
250
|
+
def visit_def_node(node)
|
251
|
+
emit_code(node.def_keyword_loc)
|
252
|
+
emit_code(node.name_loc)
|
253
|
+
last_loc = node.name_loc
|
254
|
+
|
255
|
+
if node.parameters
|
256
|
+
emit_str('(')
|
257
|
+
emit_code(node.parameters)
|
258
|
+
emit_str(')')
|
259
|
+
last_loc = node.parameters.location
|
260
|
+
end
|
261
|
+
|
262
|
+
emit_code(node.body, semicolon: true)
|
263
|
+
emit_code(node.end_keyword_loc, semicolon: true)
|
264
|
+
end
|
265
|
+
|
266
|
+
def visit_call_node(node)
|
267
|
+
if node.receiver && !node.call_operator_loc && !node.arguments
|
268
|
+
return visit_call_node_unary_op(node)
|
269
|
+
end
|
270
|
+
|
271
|
+
block = node.block
|
272
|
+
|
273
|
+
emit_code(node.receiver)
|
274
|
+
emit_code(node.call_operator_loc)
|
275
|
+
emit_code(node.message_loc)
|
276
|
+
emit_code(node.opening_loc)
|
277
|
+
emit_code(node.arguments)
|
278
|
+
|
279
|
+
if block.is_a?(Prism::BlockArgumentNode)
|
280
|
+
emit_comma if node.arguments&.arguments.size > 0
|
281
|
+
emit_code(block)
|
282
|
+
block = nil
|
283
|
+
end
|
284
|
+
emit_code(node.closing_loc)
|
285
|
+
emit_code(block)
|
286
|
+
end
|
287
|
+
|
288
|
+
def visit_call_node_unary_op(node)
|
289
|
+
emit_code(node.message_loc)
|
290
|
+
emit_code(node.receiver)
|
291
|
+
end
|
292
|
+
|
293
|
+
def visit_while_node(node)
|
294
|
+
return visit_while_node_guard(node) if !node.closing_loc
|
295
|
+
|
296
|
+
emit_code(node.keyword_loc)
|
297
|
+
emit_code(node.predicate)
|
298
|
+
emit_code(node.statements, semicolon: true)
|
299
|
+
emit_code(node.closing_loc, semicolon: true)
|
300
|
+
end
|
301
|
+
|
302
|
+
def visit_while_node_guard(node)
|
303
|
+
emit_code(node.statements)
|
304
|
+
emit_code(node.keyword_loc)
|
305
|
+
emit_code(node.predicate)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
data/lib/sirop/version.rb
CHANGED
data/lib/sirop.rb
CHANGED
@@ -1,27 +1,67 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'prism'
|
4
|
-
require 'sirop/
|
4
|
+
require 'sirop/injection'
|
5
|
+
require 'sirop/prism_ext'
|
6
|
+
require 'sirop/finder'
|
7
|
+
require 'sirop/sourcifier'
|
5
8
|
|
6
9
|
module Sirop
|
10
|
+
class Error < StandardError
|
11
|
+
end
|
12
|
+
|
7
13
|
class << self
|
8
|
-
def
|
14
|
+
def to_ast(obj)
|
9
15
|
case obj
|
10
16
|
when Proc
|
11
|
-
|
17
|
+
proc_ast(obj)
|
18
|
+
when UnboundMethod, Method
|
19
|
+
method_ast(obj)
|
12
20
|
else
|
13
21
|
raise ArgumentError, "Invalid object type"
|
14
22
|
end
|
15
23
|
end
|
16
24
|
|
17
|
-
def
|
25
|
+
def to_source(obj)
|
26
|
+
obj = to_ast(obj) if !obj.is_a?(Prism::Node)
|
27
|
+
Sourcifier.new.to_source(obj)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def proc_ast(proc)
|
18
33
|
fn, lineno = proc.source_location
|
19
34
|
pr = Prism.parse(IO.read(fn), filepath: fn)
|
20
35
|
program = pr.value
|
21
|
-
|
22
|
-
|
23
|
-
|
36
|
+
|
37
|
+
Finder.find(program, proc) do
|
38
|
+
on(:lambda) do |node|
|
39
|
+
found!(node) if node.location.start_line == lineno
|
40
|
+
super(node)
|
41
|
+
end
|
42
|
+
on(:call) do |node|
|
43
|
+
case node.name
|
44
|
+
when :proc, :lambda
|
45
|
+
found!(node) if node.block && node.block.location.start_line == lineno
|
46
|
+
end
|
47
|
+
super(node)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
rescue Errno::ENOENT
|
51
|
+
raise Sirop::Error, "Could not get source for proc"
|
52
|
+
end
|
53
|
+
|
54
|
+
def method_ast(method)
|
55
|
+
fn, lineno = method.source_location
|
56
|
+
pr = Prism.parse(IO.read(fn), filepath: fn)
|
57
|
+
program = pr.value
|
58
|
+
|
59
|
+
Finder.find(program, method) do
|
60
|
+
on(:def) do |node|
|
61
|
+
found!(node) if node.name == method.name && node.location.start_line == lineno
|
62
|
+
super(node)
|
63
|
+
end
|
64
|
+
end
|
24
65
|
end
|
25
|
-
|
26
66
|
end
|
27
67
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sirop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.3'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: prism
|
@@ -48,15 +48,17 @@ files:
|
|
48
48
|
- CHANGELOG.md
|
49
49
|
- README.md
|
50
50
|
- lib/sirop.rb
|
51
|
-
- lib/sirop/
|
51
|
+
- lib/sirop/finder.rb
|
52
|
+
- lib/sirop/injection.rb
|
53
|
+
- lib/sirop/prism_ext.rb
|
54
|
+
- lib/sirop/sourcifier.rb
|
52
55
|
- lib/sirop/version.rb
|
53
56
|
homepage: http://github.com/digital-fabric/sirop
|
54
57
|
licenses:
|
55
58
|
- MIT
|
56
59
|
metadata:
|
57
|
-
source_code_uri: https://github.com/digital-fabric/sirop
|
58
|
-
documentation_uri: https://www.rubydoc.info/gems/sirop
|
59
60
|
homepage_uri: https://github.com/digital-fabric/sirop
|
61
|
+
documentation_uri: https://www.rubydoc.info/gems/sirop
|
60
62
|
changelog_uri: https://github.com/digital-fabric/sirop/blob/main/CHANGELOG.md
|
61
63
|
post_install_message:
|
62
64
|
rdoc_options:
|
@@ -70,7 +72,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
70
72
|
requirements:
|
71
73
|
- - ">="
|
72
74
|
- !ruby/object:Gem::Version
|
73
|
-
version: '3.
|
75
|
+
version: '3.2'
|
74
76
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
77
|
requirements:
|
76
78
|
- - ">="
|
data/lib/sirop/block_finder.rb
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Sirop
|
4
|
-
class BlockFinder < Prism::BasicVisitor
|
5
|
-
attr_accessor :block_node
|
6
|
-
|
7
|
-
def initialize(proc, lineno)
|
8
|
-
@proc = proc
|
9
|
-
@lineno = lineno
|
10
|
-
end
|
11
|
-
|
12
|
-
def find(program)
|
13
|
-
# p program
|
14
|
-
# puts
|
15
|
-
catch(@proc) {
|
16
|
-
visit(program)
|
17
|
-
nil
|
18
|
-
}
|
19
|
-
end
|
20
|
-
|
21
|
-
def visit_lambda_node(node)
|
22
|
-
if node.location.start_line == @lineno
|
23
|
-
throw @proc, node
|
24
|
-
else
|
25
|
-
visit_child_nodes(node)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def visit_call_node(node)
|
30
|
-
case node.name
|
31
|
-
when :proc, :lambda
|
32
|
-
if node.block && node.block.location.start_line == @lineno
|
33
|
-
throw @proc, node
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def method_missing(sym, node, *args)
|
39
|
-
visit_child_nodes(node)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|