ruby-next-core 0.8.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +47 -0
- data/README.md +85 -11
- data/bin/transform +9 -1
- data/lib/.rbnext/2.3/ruby-next/commands/core_ext.rb +167 -0
- data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +198 -0
- data/lib/.rbnext/2.3/ruby-next/language/eval.rb +66 -0
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +121 -0
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/endless_range.rb +63 -0
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/pattern_matching.rb +944 -0
- data/lib/.rbnext/2.3/ruby-next/utils.rb +65 -0
- data/lib/ruby-next.rb +8 -5
- data/lib/ruby-next/cli.rb +2 -2
- data/lib/ruby-next/commands/core_ext.rb +2 -2
- data/lib/ruby-next/commands/nextify.rb +64 -22
- data/lib/ruby-next/core.rb +39 -22
- data/lib/ruby-next/core/array/deconstruct.rb +9 -9
- data/lib/ruby-next/core/array/difference_union_intersection.rb +12 -12
- data/lib/ruby-next/core/constants/no_matching_pattern_error.rb +3 -3
- data/lib/ruby-next/core/enumerable/filter.rb +8 -8
- data/lib/ruby-next/core/enumerable/filter_map.rb +25 -25
- data/lib/ruby-next/core/enumerable/tally.rb +7 -7
- data/lib/ruby-next/core/enumerator/produce.rb +12 -12
- data/lib/ruby-next/core/hash/deconstruct_keys.rb +9 -9
- data/lib/ruby-next/core/hash/except.rb +11 -0
- data/lib/ruby-next/core/hash/merge.rb +8 -8
- data/lib/ruby-next/core/kernel/then.rb +2 -2
- data/lib/ruby-next/core/proc/compose.rb +11 -11
- data/lib/ruby-next/core/string/split.rb +6 -6
- data/lib/ruby-next/core/struct/deconstruct.rb +2 -2
- data/lib/ruby-next/core/struct/deconstruct_keys.rb +17 -17
- data/lib/ruby-next/core/symbol/end_with.rb +4 -4
- data/lib/ruby-next/core/symbol/start_with.rb +4 -4
- data/lib/ruby-next/core/time/ceil.rb +6 -5
- data/lib/ruby-next/core/time/floor.rb +4 -4
- data/lib/ruby-next/core/unboundmethod/bind_call.rb +4 -4
- data/lib/ruby-next/core_ext.rb +2 -2
- data/lib/ruby-next/language.rb +31 -5
- data/lib/ruby-next/language/eval.rb +10 -8
- data/lib/ruby-next/language/proposed.rb +3 -0
- data/lib/ruby-next/language/rewriters/args_forward.rb +24 -20
- data/lib/ruby-next/language/rewriters/base.rb +2 -2
- data/lib/ruby-next/language/rewriters/endless_method.rb +26 -3
- data/lib/ruby-next/language/rewriters/endless_range.rb +1 -0
- data/lib/ruby-next/language/rewriters/find_pattern.rb +44 -0
- data/lib/ruby-next/language/rewriters/method_reference.rb +2 -1
- data/lib/ruby-next/language/rewriters/numbered_params.rb +1 -0
- data/lib/ruby-next/language/rewriters/pattern_matching.rb +105 -13
- data/lib/ruby-next/language/rewriters/right_hand_assignment.rb +2 -1
- data/lib/ruby-next/language/rewriters/runtime.rb +6 -0
- data/lib/ruby-next/language/rewriters/runtime/dir.rb +32 -0
- data/lib/ruby-next/language/rewriters/safe_navigation.rb +87 -0
- data/lib/ruby-next/language/rewriters/shorthand_hash.rb +47 -0
- data/lib/ruby-next/language/rewriters/squiggly_heredoc.rb +36 -0
- data/lib/ruby-next/language/runtime.rb +3 -2
- data/lib/ruby-next/logging.rb +1 -1
- data/lib/ruby-next/rubocop.rb +15 -9
- data/lib/ruby-next/setup_self.rb +22 -0
- data/lib/ruby-next/utils.rb +30 -0
- data/lib/ruby-next/version.rb +1 -1
- data/lib/uby-next.rb +8 -4
- metadata +22 -7
@@ -3,14 +3,16 @@
|
|
3
3
|
module RubyNext
|
4
4
|
module Language
|
5
5
|
module KernelEval
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
if Utils.refine_modules?
|
7
|
+
refine Kernel do
|
8
|
+
def eval(source, bind = nil, *args)
|
9
|
+
new_source = ::RubyNext::Language::Runtime.transform(
|
10
|
+
source,
|
11
|
+
using: bind&.receiver == TOPLEVEL_BINDING.receiver || bind&.receiver&.is_a?(Module)
|
12
|
+
)
|
13
|
+
RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
|
14
|
+
super new_source, bind, *args
|
15
|
+
end
|
14
16
|
end
|
15
17
|
end
|
16
18
|
end
|
@@ -4,3 +4,6 @@
|
|
4
4
|
|
5
5
|
require "ruby-next/language/rewriters/method_reference"
|
6
6
|
RubyNext::Language.rewriters << RubyNext::Language::Rewriters::MethodReference
|
7
|
+
|
8
|
+
require "ruby-next/language/rewriters/shorthand_hash"
|
9
|
+
RubyNext::Language.rewriters << RubyNext::Language::Rewriters::ShorthandHash
|
@@ -4,16 +4,19 @@ module RubyNext
|
|
4
4
|
module Language
|
5
5
|
module Rewriters
|
6
6
|
class ArgsForward < Base
|
7
|
-
|
8
|
-
|
7
|
+
NAME = "args-forward"
|
8
|
+
SYNTAX_PROBE = "obj = Object.new; def obj.foo(...) super(1, ...); end"
|
9
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.2")
|
9
10
|
|
10
11
|
REST = :__rest__
|
11
12
|
BLOCK = :__block__
|
12
13
|
|
13
|
-
def
|
14
|
+
def on_forward_arg(node)
|
14
15
|
context.track! self
|
15
16
|
|
16
|
-
|
17
|
+
node = super(node)
|
18
|
+
|
19
|
+
replace(node.loc.expression, "*#{REST}, &#{BLOCK}")
|
17
20
|
|
18
21
|
node.updated(
|
19
22
|
:args,
|
@@ -25,34 +28,35 @@ module RubyNext
|
|
25
28
|
end
|
26
29
|
|
27
30
|
def on_send(node)
|
28
|
-
|
31
|
+
fargs = node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :forwarded_args }
|
32
|
+
return super(node) unless fargs
|
33
|
+
|
34
|
+
process_fargs(node, fargs)
|
35
|
+
end
|
36
|
+
|
37
|
+
def on_super(node)
|
38
|
+
fargs = node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :forwarded_args }
|
39
|
+
return super(node) unless fargs
|
40
|
+
|
41
|
+
process_fargs(node, fargs)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
29
45
|
|
30
|
-
|
46
|
+
def process_fargs(node, fargs)
|
47
|
+
replace(fargs.loc.expression, "*#{REST}, &#{BLOCK}")
|
31
48
|
|
32
49
|
process(
|
33
50
|
node.updated(
|
34
51
|
nil,
|
35
52
|
[
|
36
|
-
*node.children
|
53
|
+
*node.children.take(node.children.index(fargs)),
|
37
54
|
*forwarded_args
|
38
55
|
]
|
39
56
|
)
|
40
57
|
)
|
41
58
|
end
|
42
59
|
|
43
|
-
def on_super(node)
|
44
|
-
return super(node) unless node.children[0]&.type == :forwarded_args
|
45
|
-
|
46
|
-
replace(node.children[0].loc.expression, "*#{REST}, &#{BLOCK}")
|
47
|
-
|
48
|
-
node.updated(
|
49
|
-
nil,
|
50
|
-
forwarded_args
|
51
|
-
)
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
60
|
def forwarded_args
|
57
61
|
[
|
58
62
|
s(:splat, s(:lvar, REST)),
|
@@ -34,7 +34,7 @@ module RubyNext
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def key?(name)
|
37
|
-
!!fetch(name) { false }
|
37
|
+
!!fetch(name) { false } # rubocop:disable Style/RedundantFetchBlock
|
38
38
|
end
|
39
39
|
|
40
40
|
def fetch(name)
|
@@ -59,7 +59,7 @@ module RubyNext
|
|
59
59
|
def unsupported_syntax?
|
60
60
|
save_verbose, $VERBOSE = $VERBOSE, nil
|
61
61
|
eval_mid = Kernel.respond_to?(:eval_without_ruby_next) ? :eval_without_ruby_next : :eval
|
62
|
-
Kernel.send eval_mid, self::SYNTAX_PROBE
|
62
|
+
Kernel.send eval_mid, self::SYNTAX_PROBE, nil, __FILE__, __LINE__
|
63
63
|
false
|
64
64
|
rescue SyntaxError, NameError
|
65
65
|
true
|
@@ -4,8 +4,16 @@ module RubyNext
|
|
4
4
|
module Language
|
5
5
|
module Rewriters
|
6
6
|
class EndlessMethod < Base
|
7
|
+
NAME = "endless-method"
|
7
8
|
SYNTAX_PROBE = "obj = Object.new; def obj.foo() = 42"
|
8
|
-
MIN_SUPPORTED_VERSION = Gem::Version.new("
|
9
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
|
10
|
+
|
11
|
+
unless Parser::Meta::NODE_TYPES.include?(:def_e)
|
12
|
+
def on_def(node)
|
13
|
+
return on_def_e(node) if node.loc.end.nil?
|
14
|
+
super(node)
|
15
|
+
end
|
16
|
+
end
|
9
17
|
|
10
18
|
def on_def_e(node)
|
11
19
|
context.track! self
|
@@ -13,24 +21,39 @@ module RubyNext
|
|
13
21
|
replace(node.loc.assignment, "; ")
|
14
22
|
insert_after(node.loc.expression, "; end")
|
15
23
|
|
24
|
+
new_loc = node.loc.dup
|
25
|
+
new_loc.instance_variable_set(:@end, node.loc.expression)
|
26
|
+
|
16
27
|
process(
|
17
28
|
node.updated(
|
18
29
|
:def,
|
19
|
-
node.children
|
30
|
+
node.children,
|
31
|
+
location: new_loc
|
20
32
|
)
|
21
33
|
)
|
22
34
|
end
|
23
35
|
|
36
|
+
unless Parser::Meta::NODE_TYPES.include?(:def_e)
|
37
|
+
def on_defs(node)
|
38
|
+
return on_defs_e(node) if node.loc.end.nil?
|
39
|
+
super(node)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
24
43
|
def on_defs_e(node)
|
25
44
|
context.track! self
|
26
45
|
|
27
46
|
replace(node.loc.assignment, "; ")
|
28
47
|
insert_after(node.loc.expression, "; end")
|
29
48
|
|
49
|
+
new_loc = node.loc.dup
|
50
|
+
new_loc.instance_variable_set(:@end, node.loc.expression)
|
51
|
+
|
30
52
|
process(
|
31
53
|
node.updated(
|
32
54
|
:defs,
|
33
|
-
node.children
|
55
|
+
node.children,
|
56
|
+
location: new_loc
|
34
57
|
)
|
35
58
|
)
|
36
59
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ruby-next/language/rewriters/pattern_matching"
|
4
|
+
|
5
|
+
module RubyNext
|
6
|
+
module Language
|
7
|
+
module Rewriters
|
8
|
+
using RubyNext
|
9
|
+
|
10
|
+
# Separate pattern matching rewriter for Ruby 2.7 to
|
11
|
+
# transpile only case...in with a find pattern
|
12
|
+
class FindPattern < PatternMatching
|
13
|
+
NAME = "pattern-matching-find-pattern"
|
14
|
+
SYNTAX_PROBE = "case 0; in [*,0,*]; true; else; 1; end"
|
15
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
|
16
|
+
|
17
|
+
def on_case_match(node)
|
18
|
+
@has_find_pattern = false
|
19
|
+
process_regular_node(node).then do |new_node|
|
20
|
+
return new_node unless has_find_pattern
|
21
|
+
super(node)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_in_match(node)
|
26
|
+
@has_find_pattern = false
|
27
|
+
process_regular_node(node).then do |new_node|
|
28
|
+
return new_node unless has_find_pattern
|
29
|
+
super(node)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def on_find_pattern(node)
|
34
|
+
@has_find_pattern = true
|
35
|
+
super(node)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :has_find_pattern
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -4,8 +4,9 @@ module RubyNext
|
|
4
4
|
module Language
|
5
5
|
module Rewriters
|
6
6
|
class MethodReference < Base
|
7
|
+
NAME = "method-reference"
|
7
8
|
SYNTAX_PROBE = "Language.:transform"
|
8
|
-
MIN_SUPPORTED_VERSION = Gem::Version.new("
|
9
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("3.0.0")
|
9
10
|
|
10
11
|
def on_meth_ref(node)
|
11
12
|
context.track! self
|
@@ -10,6 +10,16 @@ module RubyNext
|
|
10
10
|
def to_ast_node
|
11
11
|
self
|
12
12
|
end
|
13
|
+
|
14
|
+
# Useful to generate simple operation nodes
|
15
|
+
# (e.g., 'a + b')
|
16
|
+
def -(val)
|
17
|
+
::Parser::AST::Node.new(:send, [self, :-, val.to_ast_node])
|
18
|
+
end
|
19
|
+
|
20
|
+
def +(val)
|
21
|
+
::Parser::AST::Node.new(:send, [self, :+, val.to_ast_node])
|
22
|
+
end
|
13
23
|
end
|
14
24
|
|
15
25
|
refine String do
|
@@ -216,6 +226,7 @@ module RubyNext
|
|
216
226
|
end
|
217
227
|
|
218
228
|
class PatternMatching < Base
|
229
|
+
NAME = "pattern-matching"
|
219
230
|
SYNTAX_PROBE = "case 0; in 0; true; else; 1; end"
|
220
231
|
MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
|
221
232
|
|
@@ -248,7 +259,7 @@ module RubyNext
|
|
248
259
|
rewrite_case_in! node, matchee_ast, case_clause
|
249
260
|
|
250
261
|
node.updated(
|
251
|
-
:
|
262
|
+
:kwbegin,
|
252
263
|
[
|
253
264
|
matchee_ast, case_clause
|
254
265
|
]
|
@@ -287,7 +298,7 @@ module RubyNext
|
|
287
298
|
pattern
|
288
299
|
]
|
289
300
|
).tap do |new_node|
|
290
|
-
replace(node.loc.expression, new_node)
|
301
|
+
replace(node.loc.expression, inline_blocks(unparse(new_node)))
|
291
302
|
end
|
292
303
|
end
|
293
304
|
|
@@ -306,14 +317,14 @@ module RubyNext
|
|
306
317
|
body_indent = " " * clause.children[2].loc.column
|
307
318
|
replace(
|
308
319
|
clause.loc.expression,
|
309
|
-
"when #{unparse(new_node.children[i].children[0])}" \
|
320
|
+
"when #{inline_blocks(unparse(new_node.children[i].children[0]))}" \
|
310
321
|
"#{padding}" \
|
311
322
|
"#{body_indent}#{clause.children[2].loc.expression.source}"
|
312
323
|
)
|
313
324
|
else
|
314
325
|
replace(
|
315
326
|
clause.loc.keyword.end.join(clause.children[0].loc.expression.end),
|
316
|
-
new_node.children[i].children[0]
|
327
|
+
inline_blocks(unparse(new_node.children[i].children[0]))
|
317
328
|
)
|
318
329
|
remove(clause.children[1].loc.expression) if clause.children[1]
|
319
330
|
replace(clause.loc.keyword, "when ")
|
@@ -429,6 +440,8 @@ module RubyNext
|
|
429
440
|
right =
|
430
441
|
if node.children.empty?
|
431
442
|
case_eq_clause(s(:array), s(:lvar, locals[:arr]))
|
443
|
+
elsif node.children.size > 1 && node.children.first.type == :match_rest && node.children.last.type == :match_rest
|
444
|
+
array_find(*node.children)
|
432
445
|
else
|
433
446
|
array_element(0, *node.children)
|
434
447
|
end
|
@@ -442,6 +455,7 @@ module RubyNext
|
|
442
455
|
end
|
443
456
|
|
444
457
|
alias array_pattern_with_tail_clause array_pattern_clause
|
458
|
+
alias find_pattern_clause array_pattern_clause
|
445
459
|
|
446
460
|
def deconstruct_node(matchee)
|
447
461
|
context.use_ruby_next!
|
@@ -477,9 +491,80 @@ module RubyNext
|
|
477
491
|
end
|
478
492
|
end
|
479
493
|
|
494
|
+
# [*a, 1, 2, *] -> arr.find.with_index { |_, i| (a = arr.take(i)) && arr[i] == 1 && arr[i + 1] == 2 }
|
495
|
+
def array_find(head, *nodes, tail)
|
496
|
+
index = s(:lvar, :__i__)
|
497
|
+
|
498
|
+
match_vars = []
|
499
|
+
|
500
|
+
head_match =
|
501
|
+
unless head.children.empty?
|
502
|
+
match_vars << s(:lvasgn, head.children[0].children[0])
|
503
|
+
|
504
|
+
arr_take = s(:send,
|
505
|
+
s(:lvar, locals[:arr]),
|
506
|
+
:take,
|
507
|
+
index)
|
508
|
+
|
509
|
+
match_var_clause(head.children[0], arr_take)
|
510
|
+
end
|
511
|
+
|
512
|
+
tail_match =
|
513
|
+
unless tail.children.empty?
|
514
|
+
match_vars << s(:lvasgn, tail.children[0].children[0])
|
515
|
+
|
516
|
+
match_var_clause(tail.children[0], arr_slice(index + nodes.size, -1))
|
517
|
+
end
|
518
|
+
|
519
|
+
nodes.each do |node|
|
520
|
+
if node.type == :match_var
|
521
|
+
match_vars << s(:lvasgn, node.children[0])
|
522
|
+
elsif node.type == :match_as
|
523
|
+
match_vars << s(:lvasgn, node.children[1].children[0])
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
pattern = array_rest_element(*nodes, index).then do |needle|
|
528
|
+
next needle unless head_match
|
529
|
+
s(:and,
|
530
|
+
needle,
|
531
|
+
head_match)
|
532
|
+
end.then do |headed_needle|
|
533
|
+
next headed_needle unless tail_match
|
534
|
+
|
535
|
+
s(:and,
|
536
|
+
headed_needle,
|
537
|
+
tail_match)
|
538
|
+
end
|
539
|
+
|
540
|
+
s(:block,
|
541
|
+
s(:send,
|
542
|
+
s(:send,
|
543
|
+
s(:lvar, locals[:arr]),
|
544
|
+
:find),
|
545
|
+
:with_index),
|
546
|
+
s(:args,
|
547
|
+
s(:arg, :_),
|
548
|
+
s(:arg, :__i__)),
|
549
|
+
pattern).then do |block|
|
550
|
+
next block if match_vars.empty?
|
551
|
+
|
552
|
+
# We need to declare match vars outside of `find` block
|
553
|
+
locals_declare = s(:masgn,
|
554
|
+
s(:mlhs, *match_vars),
|
555
|
+
s(:nil))
|
556
|
+
|
557
|
+
s(:or,
|
558
|
+
locals_declare,
|
559
|
+
block)
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
480
563
|
def array_match_rest(index, node, *tail)
|
564
|
+
size = tail.size + 1
|
481
565
|
child = node.children[0]
|
482
|
-
|
566
|
+
|
567
|
+
rest = arr_slice(index, -size).then do |r|
|
483
568
|
next r unless child
|
484
569
|
|
485
570
|
match_var_clause(
|
@@ -492,16 +577,16 @@ module RubyNext
|
|
492
577
|
|
493
578
|
s(:and,
|
494
579
|
rest,
|
495
|
-
array_rest_element(*tail))
|
580
|
+
array_rest_element(*tail, -(size - 1)))
|
496
581
|
end
|
497
582
|
|
498
|
-
def array_rest_element(head, *tail)
|
499
|
-
send("#{head.type}_array_element", head,
|
583
|
+
def array_rest_element(head, *tail, index)
|
584
|
+
send("#{head.type}_array_element", head, index).then do |node|
|
500
585
|
next node if tail.empty?
|
501
586
|
|
502
587
|
s(:and,
|
503
588
|
node,
|
504
|
-
array_rest_element(*tail))
|
589
|
+
array_rest_element(*tail, index + 1))
|
505
590
|
end
|
506
591
|
end
|
507
592
|
|
@@ -548,12 +633,12 @@ module RubyNext
|
|
548
633
|
s(:index, arr, index.to_ast_node)
|
549
634
|
end
|
550
635
|
|
551
|
-
def
|
636
|
+
def arr_slice(lindex, rindex, arr = s(:lvar, locals[:arr]))
|
552
637
|
s(:index,
|
553
638
|
arr,
|
554
639
|
s(:irange,
|
555
|
-
|
556
|
-
|
640
|
+
lindex.to_ast_node,
|
641
|
+
rindex.to_ast_node))
|
557
642
|
end
|
558
643
|
|
559
644
|
#=========== ARRAY PATTERN (END) ===============
|
@@ -816,11 +901,12 @@ module RubyNext
|
|
816
901
|
end
|
817
902
|
|
818
903
|
def respond_to_missing?(mid, *)
|
819
|
-
return true if mid.match?(/_(clause|array_element)/)
|
904
|
+
return true if mid.to_s.match?(/_(clause|array_element)/)
|
820
905
|
super
|
821
906
|
end
|
822
907
|
|
823
908
|
def method_missing(mid, *args, &block)
|
909
|
+
mid = mid.to_s
|
824
910
|
return case_eq_clause(*args) if mid.match?(/_clause$/)
|
825
911
|
return case_eq_array_element(*args) if mid.match?(/_array_element$/)
|
826
912
|
return case_eq_hash_element(*args) if mid.match?(/_hash_element$/)
|
@@ -846,6 +932,12 @@ module RubyNext
|
|
846
932
|
|
847
933
|
deconstructed_keys[key] = :"k#{deconstructed_keys.size}"
|
848
934
|
end
|
935
|
+
|
936
|
+
# Unparser generates `do .. end` blocks, we want to
|
937
|
+
# have single-line blocks with `{ ... }`.
|
938
|
+
def inline_blocks(source)
|
939
|
+
source.gsub(/do \|_, __i__\|\n\s*([^\n]+)\n\s*end/, '{ |_, __i__| \1 }')
|
940
|
+
end
|
849
941
|
end
|
850
942
|
end
|
851
943
|
end
|