ruby-next 0.3.0 → 0.4.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 +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,14 +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: :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
|
11
|
-
end
|
12
|
-
end
|
13
|
-
RUBY
|
14
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
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
|
9
|
-
|
10
|
-
Enumerator.new(Float::INFINITY) do |y|
|
11
|
-
val = rest.empty? ? yield() : rest.pop
|
12
|
-
|
13
|
-
loop do
|
14
|
-
y << val
|
15
|
-
val = yield(val)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
RUBY
|
20
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RubyNext::Core.patch Hash, method: :deconstruct_keys, version: "2.7" do
|
4
|
-
<<~RUBY
|
5
|
-
def deconstruct_keys(_)
|
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: Hash, name: "HashRespondToDeconstructKeys", method: :deconstruct_keys, version: "2.7" do
|
14
|
-
<<~RUBY
|
15
|
-
def respond_to?(mid, *)
|
16
|
-
return true if mid == :deconstruct_keys
|
17
|
-
super
|
18
|
-
end
|
19
|
-
RUBY
|
20
|
-
end
|
21
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RubyNext::Core.patch Hash, method: :merge, version: "2.6", supported: {}.method(:merge).arity < 0, core_ext: :prepend do
|
4
|
-
<<~RUBY
|
5
|
-
def merge(*others)
|
6
|
-
return super if others.size == 1
|
7
|
-
return dup if others.size == 0
|
8
|
-
|
9
|
-
merge(others.shift).tap do |new_h|
|
10
|
-
others.each { |h| new_h.merge!(h) }
|
11
|
-
end
|
12
|
-
end
|
13
|
-
RUBY
|
14
|
-
end
|
@@ -1,10 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Refine object, 'cause refining modules (Kernel) is vulnerable to prepend:
|
4
|
-
# - https://bugs.ruby-lang.org/issues/13446
|
5
|
-
# - Rails added `Kernel.prepend` in 6.1: https://github.com/rails/rails/commit/3124007bd674dcdc9c3b5c6b2964dfb7a1a0733c
|
6
|
-
RubyNext::Core.patch Kernel, method: :then, version: "2.6", refineable: Object do
|
7
|
-
<<~RUBY
|
8
|
-
alias then yield_self
|
9
|
-
RUBY
|
10
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# rubocop:disable Style/LambdaCall
|
4
|
-
RubyNext::Core.patch Proc, name: "ProcCompose", method: :<<, version: "2.6" do
|
5
|
-
<<~RUBY
|
6
|
-
def <<(other)
|
7
|
-
raise TypeError, "callable object is expected" unless other.respond_to?(:call)
|
8
|
-
this = self
|
9
|
-
proc { |*args, &block| this.(other.(*args, &block)) }
|
10
|
-
end
|
11
|
-
|
12
|
-
def >>(other)
|
13
|
-
raise TypeError, "callable object is expected" unless other.respond_to?(:call)
|
14
|
-
this = self
|
15
|
-
proc { |*args, &block| other.(this.(*args, &block)) }
|
16
|
-
end
|
17
|
-
RUBY
|
18
|
-
end
|
19
|
-
# rubocop:enable Style/LambdaCall
|
@@ -1,10 +0,0 @@
|
|
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, using: true, **hargs)
|
6
|
-
# We cannot activate refinements in eval
|
7
|
-
new_contents = RubyNext::Core.inject!(contents) if using
|
8
|
-
super(new_contents || contents, using: using, **hargs)
|
9
|
-
end
|
10
|
-
end)
|
@@ -1,11 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RubyNext::Core.patch String, method: :split, version: "2.6", supported: ("a b".split(" ", &proc {}) == "a b"), core_ext: :prepend do
|
4
|
-
<<~RUBY
|
5
|
-
def split(*args, &block)
|
6
|
-
return super unless block_given?
|
7
|
-
super.each { |el| yield el }
|
8
|
-
self
|
9
|
-
end
|
10
|
-
RUBY
|
11
|
-
end
|
@@ -1,34 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Source: https://github.com/ruby/ruby/blob/b76a21aa45fff75909a66f8b20fc5856705f7862/struct.c#L953-L980
|
4
|
-
RubyNext::Core.patch Struct, method: :deconstruct_keys, version: "2.7" do
|
5
|
-
<<~'RUBY'
|
6
|
-
def deconstruct_keys(keys)
|
7
|
-
raise TypeError, "wrong argument type #{keys.class} (expected Array or nil)" if keys && !keys.is_a?(Array)
|
8
|
-
|
9
|
-
return to_h unless keys
|
10
|
-
|
11
|
-
return {} if size < keys.size
|
12
|
-
|
13
|
-
keys.each_with_object({}) do |k, acc|
|
14
|
-
# if k is Symbol and not a member of a Struct return {}
|
15
|
-
next if (Symbol === k || String === k) && !members.include?(k.to_sym)
|
16
|
-
# if k is Integer check that index is not ouf of bounds
|
17
|
-
next if Integer === k && k > size - 1
|
18
|
-
acc[k] = self[k]
|
19
|
-
end
|
20
|
-
end
|
21
|
-
RUBY
|
22
|
-
end
|
23
|
-
|
24
|
-
# We need to hack `respond_to?` in Ruby 2.5, since it's not working with refinements
|
25
|
-
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.6")
|
26
|
-
RubyNext::Core.patch refineable: Struct, name: "StructRespondToDeconstruct", method: :deconstruct_keys, version: "2.7" do
|
27
|
-
<<~RUBY
|
28
|
-
def respond_to?(mid, *)
|
29
|
-
return true if mid == :deconstruct_keys || mid == :deconstruct
|
30
|
-
super
|
31
|
-
end
|
32
|
-
RUBY
|
33
|
-
end
|
34
|
-
end
|
data/lib/ruby-next/core_ext.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "core"
|
4
|
-
|
5
|
-
# Monkey-patch core classes using the same patches as for refinements
|
6
|
-
RubyNext::Core.patches.extensions.each do |mod, patches|
|
7
|
-
patches.each do |patch|
|
8
|
-
next if patch.supported?
|
9
|
-
|
10
|
-
if patch.prepend?
|
11
|
-
mod.prepend(patch.to_module)
|
12
|
-
else
|
13
|
-
mod.module_eval(patch.body, *patch.location)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
RubyNext::Core.strategy = :core_ext
|
data/lib/ruby-next/language.rb
DELETED
@@ -1,119 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
gem "parser", ">= 2.7.0.0"
|
4
|
-
gem "unparser", ">= 0.4.7"
|
5
|
-
|
6
|
-
require "set"
|
7
|
-
|
8
|
-
require "ruby-next"
|
9
|
-
|
10
|
-
module RubyNext
|
11
|
-
# Language module contains tools to transpile newer Ruby syntax
|
12
|
-
# into an older one.
|
13
|
-
#
|
14
|
-
# It works the following way:
|
15
|
-
# - Takes a Ruby source code as input
|
16
|
-
# - Generates the AST using the edge parser (via the `parser` gem)
|
17
|
-
# - Pass this AST through the list of processors (one feature = one processor)
|
18
|
-
# - Each processor may modify the AST
|
19
|
-
# - Generates a transpiled source code from the transformed AST (via the `unparser` gem)
|
20
|
-
module Language
|
21
|
-
using RubyNext
|
22
|
-
|
23
|
-
require "ruby-next/language/parser"
|
24
|
-
require "ruby-next/language/unparser"
|
25
|
-
|
26
|
-
class TransformContext
|
27
|
-
attr_reader :versions, :use_ruby_next
|
28
|
-
|
29
|
-
def initialize
|
30
|
-
# Minimum supported RubyNext version
|
31
|
-
@min_version = MIN_SUPPORTED_VERSION
|
32
|
-
@dirty = false
|
33
|
-
@versions = Set.new
|
34
|
-
@use_ruby_next = false
|
35
|
-
end
|
36
|
-
|
37
|
-
# Called by rewriter when it performs transfomrations
|
38
|
-
def track!(rewriter)
|
39
|
-
@dirty = true
|
40
|
-
versions << rewriter.class::MIN_SUPPORTED_VERSION
|
41
|
-
end
|
42
|
-
|
43
|
-
def use_ruby_next!
|
44
|
-
@use_ruby_next = true
|
45
|
-
end
|
46
|
-
|
47
|
-
alias use_ruby_next? use_ruby_next
|
48
|
-
|
49
|
-
def dirty?
|
50
|
-
@dirty == true
|
51
|
-
end
|
52
|
-
|
53
|
-
def min_version
|
54
|
-
versions.min
|
55
|
-
end
|
56
|
-
|
57
|
-
def sorted_versions
|
58
|
-
versions.to_a.sort
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
class << self
|
63
|
-
attr_accessor :rewriters
|
64
|
-
attr_reader :watch_dirs
|
65
|
-
|
66
|
-
def transform(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new)
|
67
|
-
parse(source).then do |ast|
|
68
|
-
rewriters.inject(ast) do |tree, rewriter|
|
69
|
-
rewriter.new(context).process(tree)
|
70
|
-
end.then do |new_ast|
|
71
|
-
next source unless context.dirty?
|
72
|
-
|
73
|
-
Unparser.unparse(new_ast)
|
74
|
-
end.then do |source|
|
75
|
-
next source unless RubyNext::Core.refine?
|
76
|
-
next source unless using && context.use_ruby_next?
|
77
|
-
|
78
|
-
Core.inject! source.dup
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
def transformable?(path)
|
84
|
-
watch_dirs.any? { |dir| path.start_with?(dir) }
|
85
|
-
end
|
86
|
-
|
87
|
-
# Rewriters required for the current version
|
88
|
-
def current_rewriters
|
89
|
-
@current_rewriters ||= rewriters.select(&:unsupported_syntax?)
|
90
|
-
end
|
91
|
-
|
92
|
-
private
|
93
|
-
|
94
|
-
attr_writer :watch_dirs
|
95
|
-
end
|
96
|
-
|
97
|
-
self.rewriters = []
|
98
|
-
self.watch_dirs = %w[app lib spec test].map { |path| File.join(Dir.pwd, path) }
|
99
|
-
|
100
|
-
require "ruby-next/language/rewriters/base"
|
101
|
-
|
102
|
-
require "ruby-next/language/rewriters/endless_range"
|
103
|
-
rewriters << Rewriters::EndlessRange
|
104
|
-
|
105
|
-
require "ruby-next/language/rewriters/pattern_matching"
|
106
|
-
rewriters << Rewriters::PatternMatching
|
107
|
-
|
108
|
-
require "ruby-next/language/rewriters/args_forward"
|
109
|
-
rewriters << Rewriters::ArgsForward
|
110
|
-
|
111
|
-
require "ruby-next/language/rewriters/numbered_params"
|
112
|
-
rewriters << Rewriters::NumberedParams
|
113
|
-
|
114
|
-
if ENV["RUBY_NEXT_ENABLE_METHOD_REFERENCE"] == "1"
|
115
|
-
require "ruby-next/language/rewriters/method_reference"
|
116
|
-
RubyNext::Language.rewriters << RubyNext::Language::Rewriters::MethodReference
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "ruby-next"
|
4
|
-
require "ruby-next/utils"
|
5
|
-
require "ruby-next/language"
|
6
|
-
|
7
|
-
# Patch bootsnap to transform source code.
|
8
|
-
# Based on https://github.com/kddeisz/preval/blob/master/lib/preval.rb
|
9
|
-
load_iseq = RubyVM::InstructionSequence.method(:load_iseq)
|
10
|
-
|
11
|
-
if load_iseq.source_location[0].include?("/bootsnap/")
|
12
|
-
Bootsnap::CompileCache::ISeq.singleton_class.prepend(
|
13
|
-
Module.new do
|
14
|
-
def input_to_storage(source, path)
|
15
|
-
return super unless RubyNext::Language.transformable?(path)
|
16
|
-
source = RubyNext::Language.transform(source, rewriters: RubyNext::Language.current_rewriters)
|
17
|
-
|
18
|
-
$stdout.puts ::RubyNext::Utils.source_with_lines(source, path) if ENV["RUBY_NEXT_DEBUG"] == "1"
|
19
|
-
|
20
|
-
RubyVM::InstructionSequence.compile(source, path, path).to_binary
|
21
|
-
rescue SyntaxError
|
22
|
-
raise Bootsnap::CompileCache::Uncompilable, "syntax error"
|
23
|
-
end
|
24
|
-
end
|
25
|
-
)
|
26
|
-
end
|
@@ -1,64 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RubyNext
|
4
|
-
module Language
|
5
|
-
module KernelEval
|
6
|
-
refine Kernel do
|
7
|
-
def eval(source, bind = nil, *args)
|
8
|
-
new_source = ::RubyNext::Language::Runtime.transform(
|
9
|
-
source,
|
10
|
-
using: bind&.receiver == TOPLEVEL_BINDING.receiver || bind&.receiver&.is_a?(Module)
|
11
|
-
)
|
12
|
-
$stdout.puts ::RubyNext::Utils.source_with_lines(new_source, "(#{caller_locations(1, 1).first})") if ENV["RUBY_NEXT_DEBUG"] == "1"
|
13
|
-
super new_source, bind, *args
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
module InstanceEval # :nodoc:
|
19
|
-
refine Object do
|
20
|
-
def instance_eval(*args, &block)
|
21
|
-
return super(*args, &block) if block_given?
|
22
|
-
|
23
|
-
source = args.shift
|
24
|
-
new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
|
25
|
-
$stdout.puts ::RubyNext::Utils.source_with_lines(new_source, "(#{caller_locations(1, 1).first})") if ENV["RUBY_NEXT_DEBUG"] == "1"
|
26
|
-
super new_source, *args
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
module ClassEval
|
32
|
-
refine Module do
|
33
|
-
def module_eval(*args, &block)
|
34
|
-
return super(*args, &block) if block_given?
|
35
|
-
|
36
|
-
source = args.shift
|
37
|
-
new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
|
38
|
-
$stdout.puts ::RubyNext::Utils.source_with_lines(new_source, "(#{caller_locations(1, 1).first})") if ENV["RUBY_NEXT_DEBUG"] == "1"
|
39
|
-
super new_source, *args
|
40
|
-
end
|
41
|
-
|
42
|
-
def class_eval(*args, &block)
|
43
|
-
return super(*args, &block) if block_given?
|
44
|
-
|
45
|
-
source = args.shift
|
46
|
-
new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
|
47
|
-
$stdout.puts ::RubyNext::Utils.source_with_lines(new_source, "(#{caller_locations(1, 1).first})") if ENV["RUBY_NEXT_DEBUG"] == "1"
|
48
|
-
super new_source, *args
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# Refinements for `eval`-like methods.
|
54
|
-
# Transpiling eval is only possible if we do not use local from the binding,
|
55
|
-
# because we cannot access the binding of caller (without non-production ready hacks).
|
56
|
-
#
|
57
|
-
# This module is meant mainly for testing purposes.
|
58
|
-
module Eval
|
59
|
-
include InstanceEval
|
60
|
-
include ClassEval
|
61
|
-
include KernelEval
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|