ruby-next 0.3.0 → 0.4.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 +12 -1
- data/README.md +11 -7
- metadata +3 -48
- data/bin/parse +0 -19
- data/bin/ruby-next +0 -16
- data/bin/transform +0 -21
- data/lib/ruby-next.rb +0 -37
- data/lib/ruby-next/cli.rb +0 -94
- data/lib/ruby-next/commands/base.rb +0 -42
- data/lib/ruby-next/commands/core_ext.rb +0 -166
- data/lib/ruby-next/commands/nextify.rb +0 -133
- data/lib/ruby-next/core.rb +0 -182
- data/lib/ruby-next/core/array/deconstruct.rb +0 -21
- data/lib/ruby-next/core/array/difference_union_intersection.rb +0 -25
- data/lib/ruby-next/core/constants/no_matching_pattern_error.rb +0 -17
- data/lib/ruby-next/core/enumerable/filter.rb +0 -25
- data/lib/ruby-next/core/enumerable/filter_map.rb +0 -38
- data/lib/ruby-next/core/enumerable/tally.rb +0 -14
- data/lib/ruby-next/core/enumerator/produce.rb +0 -20
- data/lib/ruby-next/core/hash/deconstruct_keys.rb +0 -21
- data/lib/ruby-next/core/hash/merge.rb +0 -14
- data/lib/ruby-next/core/kernel/then.rb +0 -10
- data/lib/ruby-next/core/proc/compose.rb +0 -19
- data/lib/ruby-next/core/runtime.rb +0 -10
- data/lib/ruby-next/core/string/split.rb +0 -11
- data/lib/ruby-next/core/struct/deconstruct.rb +0 -7
- data/lib/ruby-next/core/struct/deconstruct_keys.rb +0 -34
- data/lib/ruby-next/core/time/ceil.rb +0 -10
- data/lib/ruby-next/core/time/floor.rb +0 -9
- data/lib/ruby-next/core/unboundmethod/bind_call.rb +0 -9
- data/lib/ruby-next/core_ext.rb +0 -18
- data/lib/ruby-next/language.rb +0 -119
- data/lib/ruby-next/language/bootsnap.rb +0 -26
- data/lib/ruby-next/language/eval.rb +0 -64
- data/lib/ruby-next/language/parser.rb +0 -28
- data/lib/ruby-next/language/rewriters/args_forward.rb +0 -57
- data/lib/ruby-next/language/rewriters/base.rb +0 -105
- data/lib/ruby-next/language/rewriters/endless_range.rb +0 -60
- data/lib/ruby-next/language/rewriters/method_reference.rb +0 -33
- data/lib/ruby-next/language/rewriters/numbered_params.rb +0 -41
- data/lib/ruby-next/language/rewriters/pattern_matching.rb +0 -541
- data/lib/ruby-next/language/runtime.rb +0 -95
- data/lib/ruby-next/language/setup.rb +0 -43
- data/lib/ruby-next/language/unparser.rb +0 -8
- data/lib/ruby-next/utils.rb +0 -36
- data/lib/ruby-next/version.rb +0 -5
- data/lib/uby-next.rb +0 -68
@@ -1,133 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "fileutils"
|
4
|
-
require "pathname"
|
5
|
-
|
6
|
-
module RubyNext
|
7
|
-
module Commands
|
8
|
-
class Nextify < Base
|
9
|
-
using RubyNext
|
10
|
-
|
11
|
-
attr_reader :lib_path, :paths, :out_path, :min_version, :single_version
|
12
|
-
|
13
|
-
def run
|
14
|
-
log "RubyNext core strategy: #{RubyNext::Core.strategy}"
|
15
|
-
paths.each do |path|
|
16
|
-
contents = File.read(path)
|
17
|
-
transpile path, contents
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def parse!(args)
|
22
|
-
print_help = false
|
23
|
-
@min_version = MIN_SUPPORTED_VERSION
|
24
|
-
@single_version = false
|
25
|
-
|
26
|
-
optparser = base_parser do |opts|
|
27
|
-
opts.banner = "Usage: ruby-next nextify DIRECTORY_OR_FILE [options]"
|
28
|
-
|
29
|
-
opts.on("-o", "--output=OUTPUT", "Specify output directory or file or stdout") do |val|
|
30
|
-
@out_path = val
|
31
|
-
end
|
32
|
-
|
33
|
-
opts.on("--min-version=VERSION", "Specify the minimum Ruby version to support") do |val|
|
34
|
-
@min_version = Gem::Version.new(val)
|
35
|
-
end
|
36
|
-
|
37
|
-
opts.on("--single-version", "Only create one version of a file (for the earliest Ruby version)") do
|
38
|
-
@single_version = true
|
39
|
-
end
|
40
|
-
|
41
|
-
opts.on("--enable-method-reference", "Enable reverted method reference syntax (requires custom parser)") do
|
42
|
-
require "ruby-next/language/rewriters/method_reference"
|
43
|
-
Language.rewriters << Language::Rewriters::MethodReference
|
44
|
-
end
|
45
|
-
|
46
|
-
opts.on("--[no-]refine", "Do not inject `using RubyNext`") do |val|
|
47
|
-
Core.strategy = :core_ext unless val
|
48
|
-
end
|
49
|
-
|
50
|
-
opts.on("-h", "--help", "Print help") do
|
51
|
-
print_help = true
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
@lib_path = args[0]
|
56
|
-
|
57
|
-
if print_help
|
58
|
-
$stdout.puts optparser.help
|
59
|
-
exit 0
|
60
|
-
end
|
61
|
-
|
62
|
-
unless lib_path&.then(&File.method(:exist?))
|
63
|
-
$stdout.puts optparser.help
|
64
|
-
exit 2
|
65
|
-
end
|
66
|
-
|
67
|
-
optparser.parse!(args)
|
68
|
-
|
69
|
-
@paths =
|
70
|
-
if File.directory?(lib_path)
|
71
|
-
Dir[File.join(lib_path, "**/*.rb")]
|
72
|
-
elsif File.file?(lib_path)
|
73
|
-
[lib_path].tap do |_|
|
74
|
-
@lib_path = File.dirname(lib_path)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
private
|
80
|
-
|
81
|
-
def transpile(path, contents, version: min_version)
|
82
|
-
rewriters = Language.rewriters.select { |rw| rw.unsupported_version?(version) }
|
83
|
-
|
84
|
-
context = Language::TransformContext.new
|
85
|
-
new_contents = Language.transform contents, context: context, rewriters: rewriters
|
86
|
-
|
87
|
-
return unless context.dirty?
|
88
|
-
|
89
|
-
versions = context.sorted_versions
|
90
|
-
version = versions.shift
|
91
|
-
|
92
|
-
# First, store already transpiled contents in the minimum required version dir
|
93
|
-
save new_contents, path, version
|
94
|
-
|
95
|
-
return if versions.empty? || single_version?
|
96
|
-
|
97
|
-
# Then, generate the source code for the next version
|
98
|
-
transpile path, contents, version: version
|
99
|
-
end
|
100
|
-
|
101
|
-
def save(contents, path, version)
|
102
|
-
return $stdout.puts(contents) if out_path == "stdout"
|
103
|
-
|
104
|
-
paths = [Pathname.new(path).relative_path_from(Pathname.new(lib_path))]
|
105
|
-
|
106
|
-
paths.unshift(version.segments[0..1].join(".")) unless single_version?
|
107
|
-
|
108
|
-
next_path =
|
109
|
-
if out_path
|
110
|
-
if out_path.end_with?(".rb")
|
111
|
-
out_path
|
112
|
-
else
|
113
|
-
File.join(out_path, *paths)
|
114
|
-
end
|
115
|
-
else
|
116
|
-
File.join(
|
117
|
-
lib_path,
|
118
|
-
RUBY_NEXT_DIR,
|
119
|
-
*paths
|
120
|
-
)
|
121
|
-
end
|
122
|
-
|
123
|
-
FileUtils.mkdir_p File.dirname(next_path)
|
124
|
-
|
125
|
-
File.write(next_path, contents)
|
126
|
-
|
127
|
-
log "Generated: #{next_path}"
|
128
|
-
end
|
129
|
-
|
130
|
-
alias single_version? single_version
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
data/lib/ruby-next/core.rb
DELETED
@@ -1,182 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "set"
|
4
|
-
|
5
|
-
module RubyNext
|
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
|
-
|
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
|
-
|
119
|
-
# Inject `using RubyNext` at the top of the source code
|
120
|
-
def inject!(contents)
|
121
|
-
if contents.frozen?
|
122
|
-
contents = contents.sub(/^(\s*[^#\s].*)/, 'using RubyNext;\1')
|
123
|
-
else
|
124
|
-
contents.sub!(/^(\s*[^#\s].*)/, 'using RubyNext;\1')
|
125
|
-
end
|
126
|
-
contents
|
127
|
-
end
|
128
|
-
|
129
|
-
def patches
|
130
|
-
@patches ||= Patches.new
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
# Use refinements by default
|
135
|
-
self.strategy = :refine
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
require_relative "core/kernel/then"
|
140
|
-
|
141
|
-
require_relative "core/proc/compose"
|
142
|
-
|
143
|
-
require_relative "core/enumerable/tally"
|
144
|
-
require_relative "core/enumerable/filter"
|
145
|
-
require_relative "core/enumerable/filter_map"
|
146
|
-
|
147
|
-
require_relative "core/enumerator/produce"
|
148
|
-
|
149
|
-
require_relative "core/array/difference_union_intersection"
|
150
|
-
|
151
|
-
require_relative "core/hash/merge"
|
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
|
-
|
160
|
-
# Core extensions required for pattern matching
|
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
|
@@ -1,21 +0,0 @@
|
|
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,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
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 }
|
7
|
-
end
|
8
|
-
RUBY
|
9
|
-
end
|
10
|
-
|
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 }
|
15
|
-
end
|
16
|
-
RUBY
|
17
|
-
end
|
18
|
-
|
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 }
|
23
|
-
end
|
24
|
-
RUBY
|
25
|
-
end
|
@@ -1,17 +0,0 @@
|
|
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,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RubyNext::Core.patch Enumerable, method: :filter, version: "2.6" do
|
4
|
-
<<~RUBY
|
5
|
-
alias filter select
|
6
|
-
RUBY
|
7
|
-
end
|
8
|
-
|
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
|
19
|
-
|
20
|
-
RubyNext::Core.patch Hash, method: :filter!, version: "2.6" do
|
21
|
-
<<~RUBY
|
22
|
-
alias filter select
|
23
|
-
alias filter! select!
|
24
|
-
RUBY
|
25
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
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
|
21
|
-
end
|
22
|
-
result
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
RUBY
|
27
|
-
end
|
28
|
-
|
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
|
35
|
-
end
|
36
|
-
end
|
37
|
-
RUBY
|
38
|
-
end
|