ruby-next 0.0.1.26 → 0.1.0
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 +38 -0
- data/LICENSE.txt +21 -0
- data/README.md +201 -11
- data/bin/parse +19 -0
- data/bin/ruby-next +16 -0
- data/bin/transform +18 -0
- data/lib/ruby-next/cli.rb +55 -0
- data/lib/ruby-next/commands/base.rb +42 -0
- data/lib/ruby-next/commands/nextify.rb +113 -0
- data/lib/ruby-next/core/array/difference_union_intersection.rb +31 -0
- data/lib/ruby-next/core/enumerable/filter.rb +23 -0
- data/lib/ruby-next/core/enumerable/filter_map.rb +50 -0
- data/lib/ruby-next/core/enumerable/tally.rb +28 -0
- data/lib/ruby-next/core/hash/merge.rb +16 -0
- data/lib/ruby-next/core/kernel/then.rb +12 -0
- data/lib/ruby-next/core/pattern_matching.rb +37 -0
- data/lib/ruby-next/core/proc/compose.rb +21 -0
- data/lib/ruby-next/core/runtime.rb +10 -0
- data/lib/ruby-next/core.rb +28 -0
- data/lib/ruby-next/language/parser.rb +23 -0
- data/lib/ruby-next/language/rewriters/args_forward.rb +57 -0
- data/lib/ruby-next/language/rewriters/base.rb +87 -0
- data/lib/ruby-next/language/rewriters/endless_range.rb +60 -0
- data/lib/ruby-next/language/rewriters/method_reference.rb +27 -0
- data/lib/ruby-next/language/rewriters/numbered_params.rb +41 -0
- data/lib/ruby-next/language/rewriters/pattern_matching.rb +464 -0
- data/lib/ruby-next/language/runtime.rb +149 -0
- data/lib/ruby-next/language/setup.rb +43 -0
- data/lib/ruby-next/language/unparser.rb +23 -0
- data/lib/ruby-next/language.rb +100 -0
- data/lib/ruby-next/utils.rb +39 -0
- data/lib/ruby-next/version.rb +5 -0
- data/lib/ruby-next.rb +37 -0
- data/lib/uby-next.rb +66 -0
- metadata +69 -7
@@ -0,0 +1,464 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
using RubyNext
|
4
|
+
|
5
|
+
module RubyNext
|
6
|
+
module Language
|
7
|
+
module Rewriters
|
8
|
+
using(Module.new do
|
9
|
+
refine ::Parser::AST::Node do
|
10
|
+
def to_ast_node
|
11
|
+
self
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
refine String do
|
16
|
+
def to_ast_node
|
17
|
+
::Parser::AST::Node.new(:str, [self])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
refine Symbol do
|
22
|
+
def to_ast_node
|
23
|
+
::Parser::AST::Node.new(:sym, [self])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
refine Integer do
|
28
|
+
def to_ast_node
|
29
|
+
::Parser::AST::Node.new(:int, [self])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end)
|
33
|
+
|
34
|
+
class PatternMatching < Base
|
35
|
+
SYNTAX_PROBE = "case 0; in 0; true; else; 1; end"
|
36
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
|
37
|
+
|
38
|
+
MATCHEE = :__matchee__
|
39
|
+
MATCHEE_ARR = :__matchee_arr__
|
40
|
+
MATCHEE_HASH = :__matchee_hash__
|
41
|
+
|
42
|
+
def on_case_match(node)
|
43
|
+
context.track! self
|
44
|
+
context.use_ruby_next!
|
45
|
+
|
46
|
+
@deconstructed = []
|
47
|
+
|
48
|
+
matchee_ast =
|
49
|
+
s(:lvasgn, MATCHEE, node.children[0])
|
50
|
+
|
51
|
+
ifs_ast = locals.with(
|
52
|
+
matchee: MATCHEE,
|
53
|
+
arr: MATCHEE_ARR,
|
54
|
+
hash: MATCHEE_HASH
|
55
|
+
) do
|
56
|
+
build_if_clause(node.children[1], node.children[2..-1])
|
57
|
+
end
|
58
|
+
|
59
|
+
node.updated(
|
60
|
+
:begin,
|
61
|
+
[
|
62
|
+
matchee_ast, ifs_ast
|
63
|
+
]
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def build_if_clause(node, rest)
|
70
|
+
if node&.type == :in_pattern
|
71
|
+
build_in_pattern(node, rest)
|
72
|
+
else
|
73
|
+
raise "Unexpected else in the middle of case ... in" if rest && rest.size > 0
|
74
|
+
# else clause must be present
|
75
|
+
node || no_matching_pattern
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def build_in_pattern(clause, rest)
|
80
|
+
[
|
81
|
+
with_guard(
|
82
|
+
send(
|
83
|
+
:"#{clause.children[0].type}_clause",
|
84
|
+
clause.children[0]
|
85
|
+
),
|
86
|
+
clause.children[1] # guard
|
87
|
+
),
|
88
|
+
clause.children[2] || s(:nil) # expression
|
89
|
+
].then do |children|
|
90
|
+
if rest && rest.size > 0
|
91
|
+
children << build_if_clause(rest.first, rest[1..-1])
|
92
|
+
end
|
93
|
+
|
94
|
+
s(:if, *children)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def const_pattern_clause(node)
|
99
|
+
const, pattern = *node.children
|
100
|
+
|
101
|
+
case_eq_clause(const).then do |node|
|
102
|
+
next node if pattern.nil?
|
103
|
+
|
104
|
+
s(:and,
|
105
|
+
node,
|
106
|
+
send(:"#{pattern.type}_clause", pattern))
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def match_alt_clause(node)
|
111
|
+
children = node.children.map do |child|
|
112
|
+
send :"#{child.type}_clause", child
|
113
|
+
end
|
114
|
+
s(:or, *children)
|
115
|
+
end
|
116
|
+
|
117
|
+
def match_as_clause(node)
|
118
|
+
s(:and,
|
119
|
+
case_eq_clause(node.children[0]),
|
120
|
+
match_var_clause(node.children[1], s(:lvar, locals[:matchee])))
|
121
|
+
end
|
122
|
+
|
123
|
+
def match_var_clause(node, left = s(:lvar, locals[:matchee]))
|
124
|
+
s(:or,
|
125
|
+
s(:lvasgn, node.children[0], left),
|
126
|
+
s(:true)) # rubocop:disable Lint/BooleanSymbol
|
127
|
+
end
|
128
|
+
|
129
|
+
def pin_clause(node)
|
130
|
+
case_eq_clause node.children[0]
|
131
|
+
end
|
132
|
+
|
133
|
+
def case_eq_clause(node, right = s(:lvar, locals[:matchee]))
|
134
|
+
s(:send,
|
135
|
+
node, :===, right)
|
136
|
+
end
|
137
|
+
|
138
|
+
#=========== ARRAY PATTERN (START) ===============
|
139
|
+
|
140
|
+
def array_pattern_clause(node, matchee = s(:lvar, locals[:matchee]))
|
141
|
+
deconstruct_node(matchee).then do |dnode|
|
142
|
+
right =
|
143
|
+
if node.children.empty?
|
144
|
+
case_eq_clause(s(:array), s(:lvar, locals[:arr]))
|
145
|
+
else
|
146
|
+
array_element(0, *node.children)
|
147
|
+
end
|
148
|
+
|
149
|
+
# already deconsrtructed
|
150
|
+
next right if dnode.nil?
|
151
|
+
|
152
|
+
# if there is no rest or tail, match the size first
|
153
|
+
unless node.type == :array_pattern_with_tail || node.children.any? { |n| n.type == :match_rest }
|
154
|
+
right =
|
155
|
+
s(:and,
|
156
|
+
s(:send,
|
157
|
+
node.children.size.to_ast_node,
|
158
|
+
:==,
|
159
|
+
s(:send, s(:lvar, locals[:arr]), :size)),
|
160
|
+
right)
|
161
|
+
end
|
162
|
+
|
163
|
+
s(:and,
|
164
|
+
dnode,
|
165
|
+
right)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
alias array_pattern_with_tail_clause array_pattern_clause
|
170
|
+
|
171
|
+
def deconstruct_node(matchee)
|
172
|
+
# only deconstruct once per case
|
173
|
+
return if deconstructed.include?(locals[:arr])
|
174
|
+
|
175
|
+
right = s(:send, matchee, :deconstruct)
|
176
|
+
|
177
|
+
deconstructed << locals[:arr]
|
178
|
+
s(:and,
|
179
|
+
s(:or,
|
180
|
+
s(:lvasgn, locals[:arr], right),
|
181
|
+
s(:true)), # rubocop:disable Lint/BooleanSymbol
|
182
|
+
s(:or,
|
183
|
+
case_eq_clause(s(:const, nil, :Array), s(:lvar, locals[:arr])),
|
184
|
+
raise_error(:TypeError)))
|
185
|
+
end
|
186
|
+
|
187
|
+
def array_element(index, head, *tail)
|
188
|
+
return array_match_rest(index, head, *tail) if head.type == :match_rest
|
189
|
+
|
190
|
+
send("#{head.type}_array_element", head, index).then do |node|
|
191
|
+
next node if tail.empty?
|
192
|
+
|
193
|
+
s(:and,
|
194
|
+
node,
|
195
|
+
array_element(index + 1, *tail))
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def array_match_rest(index, node, *tail)
|
200
|
+
child = node.children[0]
|
201
|
+
rest = arr_rest_items(index, tail.size).then do |r|
|
202
|
+
next r unless child
|
203
|
+
match_var_clause(
|
204
|
+
child,
|
205
|
+
r
|
206
|
+
)
|
207
|
+
end
|
208
|
+
|
209
|
+
return rest if tail.empty?
|
210
|
+
|
211
|
+
s(:and,
|
212
|
+
rest,
|
213
|
+
array_rest_element(*tail))
|
214
|
+
end
|
215
|
+
|
216
|
+
def array_rest_element(head, *tail)
|
217
|
+
send("#{head.type}_array_element", head, -(tail.size + 1)).then do |node|
|
218
|
+
next node if tail.empty?
|
219
|
+
|
220
|
+
s(:and,
|
221
|
+
node,
|
222
|
+
array_rest_element(*tail))
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def array_pattern_array_element(node, index)
|
227
|
+
element = arr_item_at(index)
|
228
|
+
locals.with(arr: locals[:arr, index]) do
|
229
|
+
array_pattern_clause(node, element)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def match_alt_array_element(node, index)
|
234
|
+
children = node.children.map do |child, i|
|
235
|
+
send :"#{child.type}_array_element", child, index
|
236
|
+
end
|
237
|
+
s(:or, *children)
|
238
|
+
end
|
239
|
+
|
240
|
+
def match_var_array_element(node, index)
|
241
|
+
match_var_clause(node, arr_item_at(index))
|
242
|
+
end
|
243
|
+
|
244
|
+
def pin_array_element(node, index)
|
245
|
+
case_eq_array_element node.children[0], index
|
246
|
+
end
|
247
|
+
|
248
|
+
def case_eq_array_element(node, index)
|
249
|
+
case_eq_clause(node, arr_item_at(index))
|
250
|
+
end
|
251
|
+
|
252
|
+
def arr_item_at(index, arr = s(:lvar, locals[:arr]))
|
253
|
+
s(:index, arr, index.to_ast_node)
|
254
|
+
end
|
255
|
+
|
256
|
+
def arr_rest_items(index, size, arr = s(:lvar, locals[:arr]))
|
257
|
+
s(:index,
|
258
|
+
arr,
|
259
|
+
s(:irange,
|
260
|
+
s(:int, index),
|
261
|
+
s(:int, -(size + 1))))
|
262
|
+
end
|
263
|
+
|
264
|
+
#=========== ARRAY PATTERN (END) ===============
|
265
|
+
|
266
|
+
#=========== HASH PATTERN (START) ===============
|
267
|
+
|
268
|
+
def hash_pattern_clause(node, matchee = s(:lvar, locals[:matchee]))
|
269
|
+
# Optimization: avoid hash modifications when not needed
|
270
|
+
# (we use #dup and #delete when "reading" values when **rest is present
|
271
|
+
# to assign the rest of the hash copy to it)
|
272
|
+
@hash_match_rest = node.children.any? { |child| child.type == :match_rest }
|
273
|
+
keys = hash_pattern_keys(node.children)
|
274
|
+
|
275
|
+
deconstruct_keys_node(keys, matchee).then do |dnode|
|
276
|
+
right =
|
277
|
+
if node.children.empty?
|
278
|
+
case_eq_clause(s(:hash), s(:lvar, locals[:hash]))
|
279
|
+
else
|
280
|
+
hash_element(*node.children)
|
281
|
+
end
|
282
|
+
|
283
|
+
return dnode if right.nil?
|
284
|
+
|
285
|
+
s(:and,
|
286
|
+
dnode,
|
287
|
+
right)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def hash_pattern_keys(children)
|
292
|
+
return s(:nil) if children.empty?
|
293
|
+
|
294
|
+
children.filter_map do |child|
|
295
|
+
return s(:nil) if child.type == :match_rest
|
296
|
+
|
297
|
+
send("#{child.type}_hash_key", child)
|
298
|
+
end.then { |keys| s(:array, *keys) }
|
299
|
+
end
|
300
|
+
|
301
|
+
def pair_hash_key(node)
|
302
|
+
node.children[0]
|
303
|
+
end
|
304
|
+
|
305
|
+
def match_var_hash_key(node)
|
306
|
+
s(:sym, node.children[0])
|
307
|
+
end
|
308
|
+
|
309
|
+
def deconstruct_keys_node(keys, matchee = s(:lvar, locals[:matchee]))
|
310
|
+
# Deconstruct once and use a copy of the hash for each pattern if we need **rest.
|
311
|
+
hash_dup =
|
312
|
+
if @hash_match_rest
|
313
|
+
s(:lvasgn, locals[:hash], s(:send, s(:lvar, locals[:hash, :src]), :dup))
|
314
|
+
else
|
315
|
+
s(:lvasgn, locals[:hash], s(:lvar, locals[:hash, :src]))
|
316
|
+
end
|
317
|
+
|
318
|
+
# Create a copy of the original hash if already deconstructed
|
319
|
+
return hash_dup if deconstructed.include?(locals[:hash])
|
320
|
+
|
321
|
+
deconstructed << locals[:hash]
|
322
|
+
|
323
|
+
right = s(:send,
|
324
|
+
matchee, :deconstruct_keys, keys)
|
325
|
+
|
326
|
+
s(:and,
|
327
|
+
s(:or,
|
328
|
+
s(:lvasgn, locals[:hash, :src], right),
|
329
|
+
s(:true)), # rubocop:disable Lint/BooleanSymbol
|
330
|
+
s(:and,
|
331
|
+
s(:or,
|
332
|
+
case_eq_clause(s(:const, nil, :Hash), s(:lvar, locals[:hash, :src])),
|
333
|
+
raise_error(:TypeError)),
|
334
|
+
hash_dup))
|
335
|
+
end
|
336
|
+
|
337
|
+
def hash_pattern_hash_element(node, key)
|
338
|
+
element = hash_value_at(key)
|
339
|
+
locals.with(hash: locals[:hash, deconstructed.size]) do
|
340
|
+
hash_pattern_clause(node, element)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def hash_element(head, *tail)
|
345
|
+
send("#{head.type}_hash_element", head).then do |node|
|
346
|
+
next node if tail.empty?
|
347
|
+
|
348
|
+
right = hash_element(*tail)
|
349
|
+
|
350
|
+
next node if right.nil?
|
351
|
+
|
352
|
+
s(:and,
|
353
|
+
node,
|
354
|
+
right)
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def pair_hash_element(node, _key = nil)
|
359
|
+
key, val = *node.children
|
360
|
+
send("#{val.type}_hash_element", val, key)
|
361
|
+
end
|
362
|
+
|
363
|
+
def match_alt_hash_element(node, key)
|
364
|
+
element_node = s(:lvasgn, locals[:hash, :el], hash_value_at(key))
|
365
|
+
|
366
|
+
children = locals.with(hash_element: locals[:hash, :el]) do
|
367
|
+
node.children.map do |child, i|
|
368
|
+
send :"#{child.type}_hash_element", child, key
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
s(:and,
|
373
|
+
s(:or,
|
374
|
+
element_node,
|
375
|
+
s(:true)), # rubocop:disable Lint/BooleanSymbol
|
376
|
+
s(:or, *children))
|
377
|
+
end
|
378
|
+
|
379
|
+
def match_var_hash_element(node, key = nil)
|
380
|
+
key ||= node.children[0]
|
381
|
+
# We need to check whether key is present first
|
382
|
+
s(:and,
|
383
|
+
hash_has_key(key),
|
384
|
+
match_var_clause(node, hash_value_at(key)))
|
385
|
+
end
|
386
|
+
|
387
|
+
def match_rest_hash_element(node, _key = nil)
|
388
|
+
# case {}; in **; end
|
389
|
+
return if node.children.empty?
|
390
|
+
|
391
|
+
child = node.children[0]
|
392
|
+
|
393
|
+
raise ArgumentError, "Unknown hash match_rest child: #{child.type}" unless child.type == :match_var
|
394
|
+
|
395
|
+
match_var_clause(child, s(:lvar, locals[:hash]))
|
396
|
+
end
|
397
|
+
|
398
|
+
def case_eq_hash_element(node, key)
|
399
|
+
case_eq_clause node, hash_value_at(key)
|
400
|
+
end
|
401
|
+
|
402
|
+
def hash_value_at(key, hash = s(:lvar, locals[:hash]))
|
403
|
+
return s(:lvar, locals.fetch(:hash_element)) if locals.key?(:hash_element)
|
404
|
+
|
405
|
+
if @hash_match_rest
|
406
|
+
s(:send,
|
407
|
+
hash, :delete,
|
408
|
+
key.to_ast_node)
|
409
|
+
else
|
410
|
+
s(:index,
|
411
|
+
hash,
|
412
|
+
key.to_ast_node)
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
def hash_has_key(key, hash = s(:lvar, locals[:hash]))
|
417
|
+
s(:send,
|
418
|
+
hash, :key?,
|
419
|
+
key.to_ast_node)
|
420
|
+
end
|
421
|
+
|
422
|
+
#=========== HASH PATTERN (END) ===============
|
423
|
+
|
424
|
+
def with_guard(node, guard)
|
425
|
+
return node unless guard
|
426
|
+
|
427
|
+
s(:and,
|
428
|
+
node,
|
429
|
+
guard.children[0]).then do |expr|
|
430
|
+
next expr unless guard.type == :unless_guard
|
431
|
+
s(:send, expr, :!)
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
def no_matching_pattern
|
436
|
+
raise_error :NoMatchingPatternError
|
437
|
+
end
|
438
|
+
|
439
|
+
def raise_error(type)
|
440
|
+
s(:send, s(:const, nil, :Kernel), :raise,
|
441
|
+
s(:const, nil, type),
|
442
|
+
s(:send,
|
443
|
+
s(:lvar, locals[:matchee]), :inspect))
|
444
|
+
end
|
445
|
+
|
446
|
+
def respond_to_missing?(mid, *)
|
447
|
+
return true if mid.match?(/_(clause|array_element)/)
|
448
|
+
super
|
449
|
+
end
|
450
|
+
|
451
|
+
def method_missing(mid, *args, &block)
|
452
|
+
return case_eq_clause(args.first) if mid.match?(/_clause$/)
|
453
|
+
return case_eq_array_element(*args) if mid.match?(/_array_element$/)
|
454
|
+
return case_eq_hash_element(*args) if mid.match?(/_hash_element$/)
|
455
|
+
super
|
456
|
+
end
|
457
|
+
|
458
|
+
private
|
459
|
+
|
460
|
+
attr_reader :deconstructed
|
461
|
+
end
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
|
5
|
+
require "ruby-next"
|
6
|
+
require "ruby-next/utils"
|
7
|
+
require "ruby-next/language"
|
8
|
+
|
9
|
+
using RubyNext
|
10
|
+
|
11
|
+
module RubyNext
|
12
|
+
module Language
|
13
|
+
# Module responsible for runtime transformations
|
14
|
+
module Runtime
|
15
|
+
# Apply only rewriters required for the current version
|
16
|
+
REWRITERS = RubyNext::Language.rewriters.select(&:unsupported_syntax?)
|
17
|
+
|
18
|
+
class << self
|
19
|
+
include Utils
|
20
|
+
|
21
|
+
attr_reader :watch_dirs
|
22
|
+
|
23
|
+
def load(path, wrap: false)
|
24
|
+
raise "RubyNext cannot handle `load(smth, wrap: true)`" if wrap
|
25
|
+
|
26
|
+
contents = File.read(path)
|
27
|
+
new_contents = transform contents
|
28
|
+
|
29
|
+
puts source_with_lines(new_contents) if ENV["RUBY_NEXT_DEBUG"] == "1"
|
30
|
+
|
31
|
+
TOPLEVEL_BINDING.eval(new_contents, path)
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
def transform(contents, **options)
|
36
|
+
Language.transform(contents, rewriters: REWRITERS, **options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def transformable?(path)
|
40
|
+
watch_dirs.any? { |dir| path.start_with?(dir) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def feature_path(path)
|
44
|
+
path = resolve_feature_path(path)
|
45
|
+
return if path.nil?
|
46
|
+
return if File.extname(path) != ".rb"
|
47
|
+
return unless transformable?(path)
|
48
|
+
path
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
attr_writer :watch_dirs
|
54
|
+
end
|
55
|
+
|
56
|
+
self.watch_dirs = %w[app lib spec test].map { |path| File.join(Dir.pwd, path) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Patch Kernel to hijack require/require_relative/load/eval
|
62
|
+
module Kernel
|
63
|
+
module_function # rubocop:disable Style/ModuleFunction
|
64
|
+
|
65
|
+
alias_method :require_without_ruby_next, :require
|
66
|
+
def require(path)
|
67
|
+
realpath = RubyNext::Language::Runtime.feature_path(path)
|
68
|
+
return require_without_ruby_next(path) unless realpath
|
69
|
+
|
70
|
+
return false if $LOADED_FEATURES.include?(realpath)
|
71
|
+
|
72
|
+
RubyNext::Language::Runtime.load(realpath)
|
73
|
+
|
74
|
+
$LOADED_FEATURES << realpath
|
75
|
+
true
|
76
|
+
rescue => e
|
77
|
+
warn "RubyNext failed to require '#{path}': #{e.message}"
|
78
|
+
require_without_ruby_next(path)
|
79
|
+
end
|
80
|
+
|
81
|
+
alias_method :require_relative_without_ruby_next, :require_relative
|
82
|
+
def require_relative(path)
|
83
|
+
from = caller_locations(1..1).first.absolute_path || File.join(Dir.pwd, "main")
|
84
|
+
realpath = File.absolute_path(
|
85
|
+
File.join(
|
86
|
+
File.dirname(File.absolute_path(from)),
|
87
|
+
path
|
88
|
+
)
|
89
|
+
)
|
90
|
+
require(realpath)
|
91
|
+
rescue => e
|
92
|
+
warn "RubyNext failed to require relative '#{path}' from #{from}: #{e.message}"
|
93
|
+
require_relative_without_ruby_next(path)
|
94
|
+
end
|
95
|
+
|
96
|
+
alias_method :load_without_ruby_next, :load
|
97
|
+
def load(path, wrap = false)
|
98
|
+
realpath = RubyNext::Language::Runtime.feature_path(path)
|
99
|
+
|
100
|
+
return load_without_ruby_next(path, wrap) unless realpath
|
101
|
+
|
102
|
+
RubyNext::Language::Runtime.load(realpath, wrap: wrap)
|
103
|
+
rescue => e
|
104
|
+
warn "RubyNext failed to load '#{path}': #{e.message}"
|
105
|
+
load_without_ruby_next(path)
|
106
|
+
end
|
107
|
+
|
108
|
+
alias_method :eval_without_ruby_next, :eval
|
109
|
+
def eval(source, *args)
|
110
|
+
new_source = RubyNext::Language::Runtime.transform(source, eval: true)
|
111
|
+
eval_without_ruby_next new_source, *args
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Patch BasicObject to hijack instance_eval
|
116
|
+
class BasicObject
|
117
|
+
alias_method :instance_eval_without_ruby_next, :instance_eval
|
118
|
+
|
119
|
+
def instance_eval(*args, &block)
|
120
|
+
return instance_eval_without_ruby_next(*args, &block) if block_given?
|
121
|
+
|
122
|
+
source = args.shift
|
123
|
+
new_source = ::RubyNext::Language::Runtime.transform(source, eval: true)
|
124
|
+
instance_eval_without_ruby_next new_source, *args
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Patch Module to hijack class_eval/module_eval
|
129
|
+
class Module
|
130
|
+
alias_method :module_eval_without_ruby_next, :module_eval
|
131
|
+
|
132
|
+
def module_eval(*args, &block)
|
133
|
+
return module_eval_without_ruby_next(*args, &block) if block_given?
|
134
|
+
|
135
|
+
source = args.shift
|
136
|
+
new_source = ::RubyNext::Language::Runtime.transform(source, eval: true)
|
137
|
+
module_eval_without_ruby_next new_source, *args
|
138
|
+
end
|
139
|
+
|
140
|
+
alias_method :class_eval_without_ruby_next, :class_eval
|
141
|
+
|
142
|
+
def class_eval(*args, &block)
|
143
|
+
return class_eval_without_ruby_next(*args, &block) if block_given?
|
144
|
+
|
145
|
+
source = args.shift
|
146
|
+
new_source = ::RubyNext::Language::Runtime.transform(source, eval: true)
|
147
|
+
class_eval_without_ruby_next new_source, *args
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Make sure Core is loaded
|
4
|
+
require "ruby-next"
|
5
|
+
|
6
|
+
module RubyNext
|
7
|
+
module Language
|
8
|
+
class << self
|
9
|
+
def setup_gem_load_path(lib_dir = "lib", rbnext_dir: RUBY_NEXT_DIR)
|
10
|
+
called_from = caller_locations(1, 1).first.path
|
11
|
+
dirname = File.dirname(called_from)
|
12
|
+
|
13
|
+
loop do
|
14
|
+
basename = File.basename(dirname)
|
15
|
+
raise "Couldn't find gem's load dir: #{lib_dir}" if basename == dirname
|
16
|
+
|
17
|
+
break if basename == lib_dir
|
18
|
+
|
19
|
+
dirname = File.dirname(basename)
|
20
|
+
end
|
21
|
+
|
22
|
+
current_index = $LOAD_PATH.index(dirname)
|
23
|
+
|
24
|
+
raise "Gem's lib is not in the $LOAD_PATH: #{dirname}" if current_index.nil?
|
25
|
+
|
26
|
+
version = RubyNext.next_version
|
27
|
+
|
28
|
+
loop do
|
29
|
+
break unless version
|
30
|
+
|
31
|
+
version_dir = File.join(dirname, rbnext_dir, version.segments[0..1].join("."))
|
32
|
+
|
33
|
+
if File.exist?(version_dir)
|
34
|
+
$LOAD_PATH.insert current_index, version_dir
|
35
|
+
current_index += 1
|
36
|
+
end
|
37
|
+
|
38
|
+
version = RubyNext.next_version(version)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Require current parser without warnings
|
4
|
+
save_verbose, $VERBOSE = $VERBOSE, nil
|
5
|
+
require "parser/current"
|
6
|
+
$VERBOSE = save_verbose
|
7
|
+
|
8
|
+
require "unparser"
|
9
|
+
|
10
|
+
# Unparser patches
|
11
|
+
|
12
|
+
# Unparser doesn't support endless ranges
|
13
|
+
# Source: https://github.com/mbj/unparser/blob/a4f959d58b660ef0630659efa5882fc20936eb18/lib/unparser/emitter/literal/range.rb
|
14
|
+
# TODO: propose a PR
|
15
|
+
class Unparser::Emitter::Literal::Range
|
16
|
+
private
|
17
|
+
|
18
|
+
def dispatch
|
19
|
+
visit(begin_node)
|
20
|
+
write(TOKENS.fetch(node.type))
|
21
|
+
visit(end_node) unless end_node.nil?
|
22
|
+
end
|
23
|
+
end
|