ruby-next-core 0.2.0 → 0.3.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 +26 -0
- data/README.md +69 -7
- data/bin/ruby-next +1 -1
- data/lib/ruby-next/cli.rb +45 -6
- data/lib/ruby-next/commands/core_ext.rb +166 -0
- data/lib/ruby-next/commands/nextify.rb +18 -3
- data/lib/ruby-next/core.rb +149 -1
- data/lib/ruby-next/core/array/deconstruct.rb +21 -0
- data/lib/ruby-next/core/array/difference_union_intersection.rb +15 -21
- data/lib/ruby-next/core/constants/no_matching_pattern_error.rb +17 -0
- data/lib/ruby-next/core/enumerable/filter.rb +20 -18
- data/lib/ruby-next/core/enumerable/filter_map.rb +28 -40
- data/lib/ruby-next/core/enumerable/tally.rb +9 -23
- data/lib/ruby-next/core/enumerator/produce.rb +12 -14
- data/lib/ruby-next/core/hash/deconstruct_keys.rb +21 -0
- data/lib/ruby-next/core/hash/merge.rb +8 -10
- data/lib/ruby-next/core/kernel/then.rb +7 -9
- data/lib/ruby-next/core/proc/compose.rb +12 -14
- data/lib/ruby-next/core/string/split.rb +11 -0
- data/lib/ruby-next/core/struct/deconstruct.rb +7 -0
- data/lib/ruby-next/core/struct/deconstruct_keys.rb +34 -0
- data/lib/ruby-next/core/time/ceil.rb +10 -0
- data/lib/ruby-next/core/time/floor.rb +9 -0
- data/lib/ruby-next/core/unboundmethod/bind_call.rb +9 -0
- data/lib/ruby-next/core_ext.rb +18 -0
- data/lib/ruby-next/language.rb +4 -2
- data/lib/ruby-next/language/parser.rb +5 -1
- data/lib/ruby-next/language/rewriters/base.rb +2 -2
- data/lib/ruby-next/language/rewriters/method_reference.rb +3 -1
- data/lib/ruby-next/language/rewriters/numbered_params.rb +2 -2
- data/lib/ruby-next/language/rewriters/pattern_matching.rb +36 -17
- data/lib/ruby-next/language/runtime.rb +2 -3
- data/lib/ruby-next/version.rb +1 -1
- metadata +13 -3
- data/lib/ruby-next/core/pattern_matching.rb +0 -37
data/lib/ruby-next/core.rb
CHANGED
@@ -1,8 +1,121 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "set"
|
4
|
+
|
3
5
|
module RubyNext
|
4
6
|
module Core
|
7
|
+
# Patch contains the extension implementation
|
8
|
+
# and meta information (e.g., Ruby version).
|
9
|
+
class Patch
|
10
|
+
attr_reader :refineables, :name, :mod, :method_name, :version, :body, :singleton, :core_ext, :supported, :native, :location
|
11
|
+
|
12
|
+
# Create a new patch for module/class (mod)
|
13
|
+
# with the specified uniq name
|
14
|
+
#
|
15
|
+
# `core_ext` defines the strategy for core extensions:
|
16
|
+
# - :patch — extend class directly
|
17
|
+
# - :prepend — extend class by prepending a module (e.g., when needs `super`)
|
18
|
+
def initialize(mod = nil, method:, name: nil, version:, supported: nil, native: nil, location: nil, refineable: mod, core_ext: :patch, singleton: nil)
|
19
|
+
@mod = mod
|
20
|
+
@method_name = method
|
21
|
+
@version = version
|
22
|
+
if method_name && mod
|
23
|
+
@supported = supported.nil? ? mod.method_defined?(method_name) : supported
|
24
|
+
# define whether running Ruby has a native implementation for this method
|
25
|
+
# for that, we check the source_location (which is nil for C defined methods)
|
26
|
+
@native = native.nil? ? (supported? && mod.instance_method(method_name).source_location.nil?) : native
|
27
|
+
end
|
28
|
+
@singleton = singleton
|
29
|
+
@refineables = Array(refineable)
|
30
|
+
@body = yield
|
31
|
+
@core_ext = core_ext
|
32
|
+
@location = location || build_location(caller_locations(1, 5))
|
33
|
+
@name = name || build_module_name
|
34
|
+
end
|
35
|
+
|
36
|
+
def prepend?
|
37
|
+
core_ext == :prepend
|
38
|
+
end
|
39
|
+
|
40
|
+
def core_ext?
|
41
|
+
!mod.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
alias supported? supported
|
45
|
+
alias native? native
|
46
|
+
alias singleton? singleton
|
47
|
+
|
48
|
+
def to_module
|
49
|
+
Module.new.tap do |ext|
|
50
|
+
ext.module_eval(body, *location)
|
51
|
+
|
52
|
+
RubyNext::Core.const_set(name, ext)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def build_module_name
|
59
|
+
mod_name = singleton? ? singleton.name : mod.name
|
60
|
+
camelized_method_name = method_name.to_s.split("_").map(&:capitalize).join
|
61
|
+
|
62
|
+
"#{mod_name}#{camelized_method_name}".gsub(/\W/, "")
|
63
|
+
end
|
64
|
+
|
65
|
+
def build_location(trace_locations)
|
66
|
+
# The caller_locations behaviour depends on implementaion,
|
67
|
+
# e.g. in JRuby https://github.com/jruby/jruby/issues/6055
|
68
|
+
while trace_locations.first.label != "patch"
|
69
|
+
trace_locations.shift
|
70
|
+
end
|
71
|
+
|
72
|
+
trace_location = trace_locations[1]
|
73
|
+
|
74
|
+
[trace_location.absolute_path, trace_location.lineno + 2]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Registry for patches
|
79
|
+
class Patches
|
80
|
+
attr_reader :extensions, :refined
|
81
|
+
|
82
|
+
def initialize
|
83
|
+
@names = Set.new
|
84
|
+
@extensions = Hash.new { |h, k| h[k] = [] }
|
85
|
+
@refined = Hash.new { |h, k| h[k] = [] }
|
86
|
+
end
|
87
|
+
|
88
|
+
# Register new patch
|
89
|
+
def <<(patch)
|
90
|
+
raise ArgumentError, "Patch already registered: #{patch.name}" if @names.include?(patch.name)
|
91
|
+
@names << patch.name
|
92
|
+
@extensions[patch.mod] << patch if patch.core_ext?
|
93
|
+
patch.refineables.each { |r| @refined[r] << patch } unless patch.native?
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
5
97
|
class << self
|
98
|
+
STRATEGIES = %i[refine core_ext].freeze
|
99
|
+
|
100
|
+
attr_reader :strategy
|
101
|
+
|
102
|
+
def strategy=(val)
|
103
|
+
raise ArgumentError, "Unknown strategy: #{val}. Available: #{STRATEGIES.join(",")}" unless STRATEGIES.include?(val)
|
104
|
+
@strategy = val
|
105
|
+
end
|
106
|
+
|
107
|
+
def refine?
|
108
|
+
strategy == :refine
|
109
|
+
end
|
110
|
+
|
111
|
+
def core_ext?
|
112
|
+
strategy == :core_ext
|
113
|
+
end
|
114
|
+
|
115
|
+
def patch(*args, **kwargs, &block)
|
116
|
+
patches << Patch.new(*args, **kwargs, &block)
|
117
|
+
end
|
118
|
+
|
6
119
|
# Inject `using RubyNext` at the top of the source code
|
7
120
|
def inject!(contents)
|
8
121
|
if contents.frozen?
|
@@ -12,7 +125,14 @@ module RubyNext
|
|
12
125
|
end
|
13
126
|
contents
|
14
127
|
end
|
128
|
+
|
129
|
+
def patches
|
130
|
+
@patches ||= Patches.new
|
131
|
+
end
|
15
132
|
end
|
133
|
+
|
134
|
+
# Use refinements by default
|
135
|
+
self.strategy = :refine
|
16
136
|
end
|
17
137
|
end
|
18
138
|
|
@@ -30,5 +150,33 @@ require_relative "core/array/difference_union_intersection"
|
|
30
150
|
|
31
151
|
require_relative "core/hash/merge"
|
32
152
|
|
153
|
+
require_relative "core/string/split"
|
154
|
+
|
155
|
+
require_relative "core/unboundmethod/bind_call"
|
156
|
+
|
157
|
+
require_relative "core/time/floor"
|
158
|
+
require_relative "core/time/ceil"
|
159
|
+
|
33
160
|
# Core extensions required for pattern matching
|
34
|
-
|
161
|
+
# Required for pattern matching with refinements
|
162
|
+
unless defined?(NoMatchingPatternError)
|
163
|
+
class NoMatchingPatternError < RuntimeError
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
require_relative "core/constants/no_matching_pattern_error"
|
168
|
+
require_relative "core/array/deconstruct"
|
169
|
+
require_relative "core/hash/deconstruct_keys"
|
170
|
+
require_relative "core/struct/deconstruct"
|
171
|
+
require_relative "core/struct/deconstruct_keys"
|
172
|
+
|
173
|
+
# Generate refinements
|
174
|
+
RubyNext.module_eval do
|
175
|
+
RubyNext::Core.patches.refined.each do |mod, patches|
|
176
|
+
refine mod do
|
177
|
+
patches.each do |patch|
|
178
|
+
module_eval(patch.body, *patch.location)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RubyNext::Core.patch Array, method: :deconstruct, version: "2.7" do
|
4
|
+
<<~RUBY
|
5
|
+
def deconstruct
|
6
|
+
self
|
7
|
+
end
|
8
|
+
RUBY
|
9
|
+
end
|
10
|
+
|
11
|
+
# We need to hack `respond_to?` in Ruby 2.5, since it's not working with refinements
|
12
|
+
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.6")
|
13
|
+
RubyNext::Core.patch refineable: Array, name: "ArrayRespondToDeconstruct", method: :deconstruct, version: "2.7" do
|
14
|
+
<<~RUBY
|
15
|
+
def respond_to?(mid, *)
|
16
|
+
return true if mid == :deconstruct
|
17
|
+
super
|
18
|
+
end
|
19
|
+
RUBY
|
20
|
+
end
|
21
|
+
end
|
@@ -1,31 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
others.reduce(Array.new(self).uniq) { |acc, arr| acc | arr }
|
8
|
-
end
|
3
|
+
RubyNext::Core.patch Array, method: :union, version: "2.6" do
|
4
|
+
<<~RUBY
|
5
|
+
def union(*others)
|
6
|
+
others.reduce(Array.new(self).uniq) { |acc, arr| acc | arr }
|
9
7
|
end
|
10
|
-
|
8
|
+
RUBY
|
11
9
|
end
|
12
10
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
others.reduce(Array.new(self)) { |acc, arr| acc - arr }
|
18
|
-
end
|
11
|
+
RubyNext::Core.patch Array, method: :difference, version: "2.6" do
|
12
|
+
<<~RUBY
|
13
|
+
def difference(*others)
|
14
|
+
others.reduce(Array.new(self)) { |acc, arr| acc - arr }
|
19
15
|
end
|
20
|
-
|
16
|
+
RUBY
|
21
17
|
end
|
22
18
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
others.reduce(Array.new(self)) { |acc, arr| acc & arr }
|
28
|
-
end
|
19
|
+
RubyNext::Core.patch Array, method: :intersection, version: "2.7" do
|
20
|
+
<<~RUBY
|
21
|
+
def intersection(*others)
|
22
|
+
others.reduce(Array.new(self)) { |acc, arr| acc & arr }
|
29
23
|
end
|
30
|
-
|
24
|
+
RUBY
|
31
25
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Special patch to define the error constant in generated files
|
4
|
+
RubyNext::Core.patch Object,
|
5
|
+
name: "NoMatchingPatternError",
|
6
|
+
method: nil,
|
7
|
+
refineable: [],
|
8
|
+
version: "2.7",
|
9
|
+
# avoid defining the constant twice, 'causae it's already included in core
|
10
|
+
# we only use the contents in `ruby-next core_ext`.
|
11
|
+
supported: true,
|
12
|
+
location: [__FILE__, __LINE__ + 2] do
|
13
|
+
<<~RUBY
|
14
|
+
class NoMatchingPatternError < RuntimeError
|
15
|
+
end
|
16
|
+
RUBY
|
17
|
+
end
|
@@ -1,23 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
RubyNext::Core.patch Enumerable, method: :filter, version: "2.6" do
|
4
|
+
<<~RUBY
|
5
|
+
alias filter select
|
6
|
+
RUBY
|
7
|
+
end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
9
|
+
# Refine Array seprately, 'cause refining modules is vulnerable to prepend:
|
10
|
+
# - https://bugs.ruby-lang.org/issues/13446
|
11
|
+
#
|
12
|
+
# Also, Array also have `filter!`
|
13
|
+
RubyNext::Core.patch Array, method: :filter!, version: "2.6" do
|
14
|
+
<<~RUBY
|
15
|
+
alias filter select
|
16
|
+
alias filter! select!
|
17
|
+
RUBY
|
18
|
+
end
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
RubyNext::Core.patch Hash, method: :filter!, version: "2.6" do
|
21
|
+
<<~RUBY
|
22
|
+
alias filter select
|
23
|
+
alias filter! select!
|
24
|
+
RUBY
|
23
25
|
end
|
@@ -1,50 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
22
|
-
result
|
23
|
-
end
|
3
|
+
# Refine Array seprately, 'cause refining modules is vulnerable to prepend:
|
4
|
+
# - https://bugs.ruby-lang.org/issues/13446
|
5
|
+
RubyNext::Core.patch Enumerable, method: :filter_map, version: "2.7", refineable: [Enumerable, Array] do
|
6
|
+
<<~RUBY
|
7
|
+
def filter_map
|
8
|
+
if block_given?
|
9
|
+
result = []
|
10
|
+
each do |element|
|
11
|
+
res = yield element
|
12
|
+
result << res if res
|
13
|
+
end
|
14
|
+
result
|
15
|
+
else
|
16
|
+
Enumerator.new do |yielder|
|
17
|
+
result = []
|
18
|
+
each do |element|
|
19
|
+
res = yielder.yield element
|
20
|
+
result << res if res
|
24
21
|
end
|
22
|
+
result
|
25
23
|
end
|
26
24
|
end
|
27
25
|
end
|
28
|
-
|
29
|
-
|
30
|
-
RubyNext.module_eval do
|
31
|
-
refine Enumerable do
|
32
|
-
include RubyNext::Core::EnumerableFilterMap
|
33
|
-
end
|
26
|
+
RUBY
|
27
|
+
end
|
34
28
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
29
|
+
RubyNext::Core.patch Enumerator::Lazy, method: :filter_map, version: "2.7" do
|
30
|
+
<<~RUBY
|
31
|
+
def filter_map
|
32
|
+
Enumerator::Lazy.new(self) do |yielder, *values|
|
33
|
+
result = yield(*values)
|
34
|
+
yielder << result if result
|
41
35
|
end
|
42
36
|
end
|
43
|
-
|
44
|
-
# Refine Array seprately, 'cause refining modules is vulnerable to prepend:
|
45
|
-
# - https://bugs.ruby-lang.org/issues/13446
|
46
|
-
refine Array do
|
47
|
-
include RubyNext::Core::EnumerableFilterMap
|
48
|
-
end
|
49
|
-
end
|
37
|
+
RUBY
|
50
38
|
end
|
@@ -1,28 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
12
|
-
end
|
3
|
+
# Refine Array seprately, 'cause refining modules is vulnerable to prepend:
|
4
|
+
# - https://bugs.ruby-lang.org/issues/13446
|
5
|
+
RubyNext::Core.patch Enumerable, method: :tally, version: "2.7", refineable: [Enumerable, Array] do
|
6
|
+
<<~RUBY
|
7
|
+
def tally
|
8
|
+
each_with_object({}) do |v, acc|
|
9
|
+
acc[v] ||= 0
|
10
|
+
acc[v] += 1
|
13
11
|
end
|
14
12
|
end
|
15
|
-
|
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
|
13
|
+
RUBY
|
28
14
|
end
|
@@ -1,22 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
raise ArgumentError, "No block given" unless block
|
3
|
+
RubyNext::Core.patch Enumerator.singleton_class, method: :produce, singleton: Enumerator, version: "2.7" do
|
4
|
+
<<~'RUBY'
|
5
|
+
# Based on https://github.com/zverok/enumerator_generate
|
6
|
+
def produce(*rest, &block)
|
7
|
+
raise ArgumentError, "wrong number of arguments (given #{rest.size}, expected 0..1)" if rest.size > 1
|
8
|
+
raise ArgumentError, "No block given" unless block
|
10
9
|
|
11
|
-
|
12
|
-
|
10
|
+
Enumerator.new(Float::INFINITY) do |y|
|
11
|
+
val = rest.empty? ? yield() : rest.pop
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
end
|
13
|
+
loop do
|
14
|
+
y << val
|
15
|
+
val = yield(val)
|
18
16
|
end
|
19
17
|
end
|
20
18
|
end
|
21
|
-
|
19
|
+
RUBY
|
22
20
|
end
|