ruby-next 0.0.1.26 → 0.1.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 +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,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
unless [].respond_to?(:tally)
|
4
|
+
module RubyNext
|
5
|
+
module Core
|
6
|
+
module EnumerableTally
|
7
|
+
def tally
|
8
|
+
each_with_object({}) do |v, acc|
|
9
|
+
acc[v] ||= 0
|
10
|
+
acc[v] += 1
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
RubyNext.module_eval do
|
18
|
+
refine Enumerable do
|
19
|
+
include RubyNext::Core::EnumerableTally
|
20
|
+
end
|
21
|
+
|
22
|
+
# Refine Array seprately, 'cause refining modules is vulnerable to prepend:
|
23
|
+
# - https://bugs.ruby-lang.org/issues/13446
|
24
|
+
refine Array do
|
25
|
+
include RubyNext::Core::EnumerableTally
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
unless {}.method(:merge).arity == -1
|
4
|
+
RubyNext.module_eval do
|
5
|
+
refine Hash do
|
6
|
+
def merge(*others)
|
7
|
+
return super if others.size == 1
|
8
|
+
return dup if others.size == 0
|
9
|
+
|
10
|
+
merge(others.shift).tap do |new_h|
|
11
|
+
others.each { |h| new_h.merge!(h) }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
unless Object.new.respond_to?(:then)
|
4
|
+
RubyNext.module_eval do
|
5
|
+
# Refine object, 'cause refining modules (Kernel) is vulnerable to prepend:
|
6
|
+
# - https://bugs.ruby-lang.org/issues/13446
|
7
|
+
# - Rails added `Kernel.prepend` in 6.1: https://github.com/rails/rails/commit/3124007bd674dcdc9c3b5c6b2964dfb7a1a0733c
|
8
|
+
refine Object do
|
9
|
+
alias then yield_self
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
unless defined?(NoMatchingPatternError)
|
4
|
+
class NoMatchingPatternError < RuntimeError
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
# Add `#deconstruct` and `#deconstruct_keys` to core classes
|
9
|
+
unless [].respond_to?(:deconstruct)
|
10
|
+
RubyNext.module_eval do
|
11
|
+
refine Array do
|
12
|
+
def deconstruct
|
13
|
+
self
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
refine Struct do
|
18
|
+
alias deconstruct to_a
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
unless {}.respond_to?(:deconstruct_keys)
|
24
|
+
RubyNext.module_eval do
|
25
|
+
refine Hash do
|
26
|
+
def deconstruct_keys(_)
|
27
|
+
self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
refine Struct do
|
32
|
+
def deconstruct_keys(_)
|
33
|
+
to_h
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Style/LambdaCall
|
4
|
+
unless proc {}.respond_to?(:<<)
|
5
|
+
RubyNext.module_eval do
|
6
|
+
refine Proc do
|
7
|
+
def <<(other)
|
8
|
+
raise TypeError, "callable object is expected" unless other.respond_to?(:call)
|
9
|
+
this = self
|
10
|
+
proc { |*args, &block| this.(other.(*args, &block)) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def >>(other)
|
14
|
+
raise TypeError, "callable object is expected" unless other.respond_to?(:call)
|
15
|
+
this = self
|
16
|
+
proc { |*args, &block| other.(this.(*args, &block)) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
# rubocop:enable Style/LambdaCall
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Extend `Language.transform` to inject `using RubyNext` to every file
|
4
|
+
RubyNext::Language.singleton_class.prepend(Module.new do
|
5
|
+
def transform(contents, **hargs)
|
6
|
+
# We cannot activate refinements in eval
|
7
|
+
RubyNext::Core.inject!(contents) unless hargs[:eval]
|
8
|
+
super(contents, **hargs)
|
9
|
+
end
|
10
|
+
end)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyNext
|
4
|
+
module Core
|
5
|
+
class << self
|
6
|
+
# Inject `using RubyNext` at the top of the source code
|
7
|
+
def inject!(contents)
|
8
|
+
contents.sub!(/^(\s*[^#\s].*)/, 'using RubyNext;\1')
|
9
|
+
contents
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
require_relative "core/kernel/then"
|
16
|
+
|
17
|
+
require_relative "core/proc/compose"
|
18
|
+
|
19
|
+
require_relative "core/enumerable/tally"
|
20
|
+
require_relative "core/enumerable/filter"
|
21
|
+
require_relative "core/enumerable/filter_map"
|
22
|
+
|
23
|
+
require_relative "core/array/difference_union_intersection"
|
24
|
+
|
25
|
+
require_relative "core/hash/merge"
|
26
|
+
|
27
|
+
# Core extensions required for pattern matching
|
28
|
+
require_relative "core/pattern_matching"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ruby-next/utils"
|
4
|
+
|
5
|
+
require "parser/ruby27"
|
6
|
+
RubyNext::Language::Parser = Parser::Ruby27
|
7
|
+
|
8
|
+
# See https://github.com/whitequark/parser/#usage
|
9
|
+
Parser::Builders::Default.emit_lambda = true
|
10
|
+
Parser::Builders::Default.emit_procarg0 = true
|
11
|
+
Parser::Builders::Default.emit_encoding = true
|
12
|
+
Parser::Builders::Default.emit_index = true
|
13
|
+
|
14
|
+
Parser::Builders::Default.prepend(Module.new do
|
15
|
+
def match_hash_var_from_str(begin_t, strings, end_t)
|
16
|
+
super.tap do
|
17
|
+
string = strings[0]
|
18
|
+
next unless string.type == :str
|
19
|
+
name, = *string
|
20
|
+
@parser.static_env.declare(name)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyNext
|
4
|
+
module Language
|
5
|
+
module Rewriters
|
6
|
+
class ArgsForward < Base
|
7
|
+
SYNTAX_PROBE = "obj = Object.new; def obj.foo(...) super(...); end"
|
8
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
|
9
|
+
|
10
|
+
REST = :__rest__
|
11
|
+
BLOCK = :__block__
|
12
|
+
|
13
|
+
def on_forward_args(node)
|
14
|
+
context.track! self
|
15
|
+
|
16
|
+
node.updated(
|
17
|
+
:args,
|
18
|
+
[
|
19
|
+
s(:restarg, REST),
|
20
|
+
s(:blockarg, BLOCK)
|
21
|
+
]
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_send(node)
|
26
|
+
return unless node.children[2]&.type == :forwarded_args
|
27
|
+
|
28
|
+
node.updated(
|
29
|
+
nil,
|
30
|
+
[
|
31
|
+
*node.children[0..1],
|
32
|
+
*forwarded_args
|
33
|
+
]
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def on_super(node)
|
38
|
+
return unless node.children[0]&.type == :forwarded_args
|
39
|
+
|
40
|
+
node.updated(
|
41
|
+
nil,
|
42
|
+
forwarded_args
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def forwarded_args
|
49
|
+
[
|
50
|
+
s(:splat, s(:lvar, REST)),
|
51
|
+
s(:block_pass, s(:lvar, BLOCK))
|
52
|
+
]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
using RubyNext
|
4
|
+
|
5
|
+
module RubyNext
|
6
|
+
module Language
|
7
|
+
module Rewriters
|
8
|
+
class Base < ::Parser::TreeRewriter
|
9
|
+
class LocalsTracker
|
10
|
+
attr_reader :stacks
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@stacks = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def with(**locals)
|
17
|
+
stacks << locals
|
18
|
+
yield.tap { stacks.pop }
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](name, suffix = nil)
|
22
|
+
fetch(name).then do |name|
|
23
|
+
next name unless suffix
|
24
|
+
:"#{name}#{suffix}__"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def key?(name)
|
29
|
+
!!fetch(name) { false }
|
30
|
+
end
|
31
|
+
|
32
|
+
def fetch(name)
|
33
|
+
ind = -1
|
34
|
+
|
35
|
+
loop do
|
36
|
+
break stacks[ind][name] if stacks[ind].key?(name)
|
37
|
+
ind -= 1
|
38
|
+
break if stacks[ind].nil?
|
39
|
+
end.then do |name|
|
40
|
+
next name unless name.nil?
|
41
|
+
|
42
|
+
return yield if block_given?
|
43
|
+
raise ArgumentError, "Local var not found in scope: #{name}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class << self
|
49
|
+
# Returns true if the syntax is supported
|
50
|
+
# by the current Ruby (performs syntax check, not version check)
|
51
|
+
def unsupported_syntax?
|
52
|
+
save_verbose, $VERBOSE = $VERBOSE, nil
|
53
|
+
eval_mid = Kernel.respond_to?(:eval_without_ruby_next) ? :eval_without_ruby_next : :eval
|
54
|
+
Kernel.send eval_mid, self::SYNTAX_PROBE
|
55
|
+
false
|
56
|
+
rescue SyntaxError, NameError
|
57
|
+
true
|
58
|
+
ensure
|
59
|
+
$VERBOSE = save_verbose
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns true if the syntax is supported
|
63
|
+
# by the specified version
|
64
|
+
def unsupported_version?(version)
|
65
|
+
self::MIN_SUPPORTED_VERSION > version
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
attr_reader :locals
|
70
|
+
|
71
|
+
def initialize(context)
|
72
|
+
@context = context
|
73
|
+
@locals = LocalsTracker.new
|
74
|
+
super()
|
75
|
+
end
|
76
|
+
|
77
|
+
def s(type, *children)
|
78
|
+
::Parser::AST::Node.new(type, children)
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
attr_reader :context
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyNext
|
4
|
+
module Language
|
5
|
+
module Rewriters
|
6
|
+
class EndlessRange < Base
|
7
|
+
SYNTAX_PROBE = "[0, 1][1..]"
|
8
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("2.6.0")
|
9
|
+
|
10
|
+
def on_index(node)
|
11
|
+
@current_index = node
|
12
|
+
new_index = process(node.children.last)
|
13
|
+
return unless new_index != node.children.last
|
14
|
+
|
15
|
+
node.updated(
|
16
|
+
nil,
|
17
|
+
[
|
18
|
+
node.children.first,
|
19
|
+
new_index
|
20
|
+
]
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_erange(node)
|
25
|
+
return unless node.children.last.nil?
|
26
|
+
|
27
|
+
context.track! self
|
28
|
+
|
29
|
+
new_end =
|
30
|
+
if index_arg?(node)
|
31
|
+
s(:int, -1)
|
32
|
+
else
|
33
|
+
s(:const,
|
34
|
+
s(:const,
|
35
|
+
s(:cbase), :Float),
|
36
|
+
:INFINITY)
|
37
|
+
end
|
38
|
+
|
39
|
+
node.updated(
|
40
|
+
:irange,
|
41
|
+
[
|
42
|
+
node.children.first,
|
43
|
+
new_end
|
44
|
+
]
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
alias_method :on_irange, :on_erange
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
attr_reader :current_index
|
53
|
+
|
54
|
+
def index_arg?(node)
|
55
|
+
current_index&.children&.include?(node)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyNext
|
4
|
+
module Language
|
5
|
+
module Rewriters
|
6
|
+
class MethodReference < Base
|
7
|
+
SYNTAX_PROBE = "Language.:transform"
|
8
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
|
9
|
+
|
10
|
+
def on_meth_ref(node)
|
11
|
+
context.track! self
|
12
|
+
|
13
|
+
receiver, mid = *node.children
|
14
|
+
|
15
|
+
node.updated(
|
16
|
+
:send,
|
17
|
+
[
|
18
|
+
receiver,
|
19
|
+
:method,
|
20
|
+
s(:sym, mid)
|
21
|
+
]
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
using RubyNext
|
4
|
+
|
5
|
+
module RubyNext
|
6
|
+
module Language
|
7
|
+
module Rewriters
|
8
|
+
class NumberedParams < Base
|
9
|
+
SYNTAX_PROBE = "proc { _1 }.call(1)"
|
10
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
|
11
|
+
|
12
|
+
def on_numblock(node)
|
13
|
+
context.track! self
|
14
|
+
|
15
|
+
proc_or_lambda, num, *rest = *node.children
|
16
|
+
|
17
|
+
node.updated(
|
18
|
+
:block,
|
19
|
+
[
|
20
|
+
proc_or_lambda,
|
21
|
+
proc_args(num),
|
22
|
+
*rest
|
23
|
+
]
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def proc_args(n)
|
30
|
+
return s(:args, s(:procarg0, :_1)) if n == 1
|
31
|
+
|
32
|
+
(1..n).map do |numero|
|
33
|
+
s(:arg, :"_#{numero}")
|
34
|
+
end.then do |args|
|
35
|
+
s(:args, *args)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|