sirop 0.1 → 0.3
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 +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
|