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,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
|