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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -1
  3. data/README.md +11 -7
  4. metadata +3 -48
  5. data/bin/parse +0 -19
  6. data/bin/ruby-next +0 -16
  7. data/bin/transform +0 -21
  8. data/lib/ruby-next.rb +0 -37
  9. data/lib/ruby-next/cli.rb +0 -94
  10. data/lib/ruby-next/commands/base.rb +0 -42
  11. data/lib/ruby-next/commands/core_ext.rb +0 -166
  12. data/lib/ruby-next/commands/nextify.rb +0 -133
  13. data/lib/ruby-next/core.rb +0 -182
  14. data/lib/ruby-next/core/array/deconstruct.rb +0 -21
  15. data/lib/ruby-next/core/array/difference_union_intersection.rb +0 -25
  16. data/lib/ruby-next/core/constants/no_matching_pattern_error.rb +0 -17
  17. data/lib/ruby-next/core/enumerable/filter.rb +0 -25
  18. data/lib/ruby-next/core/enumerable/filter_map.rb +0 -38
  19. data/lib/ruby-next/core/enumerable/tally.rb +0 -14
  20. data/lib/ruby-next/core/enumerator/produce.rb +0 -20
  21. data/lib/ruby-next/core/hash/deconstruct_keys.rb +0 -21
  22. data/lib/ruby-next/core/hash/merge.rb +0 -14
  23. data/lib/ruby-next/core/kernel/then.rb +0 -10
  24. data/lib/ruby-next/core/proc/compose.rb +0 -19
  25. data/lib/ruby-next/core/runtime.rb +0 -10
  26. data/lib/ruby-next/core/string/split.rb +0 -11
  27. data/lib/ruby-next/core/struct/deconstruct.rb +0 -7
  28. data/lib/ruby-next/core/struct/deconstruct_keys.rb +0 -34
  29. data/lib/ruby-next/core/time/ceil.rb +0 -10
  30. data/lib/ruby-next/core/time/floor.rb +0 -9
  31. data/lib/ruby-next/core/unboundmethod/bind_call.rb +0 -9
  32. data/lib/ruby-next/core_ext.rb +0 -18
  33. data/lib/ruby-next/language.rb +0 -119
  34. data/lib/ruby-next/language/bootsnap.rb +0 -26
  35. data/lib/ruby-next/language/eval.rb +0 -64
  36. data/lib/ruby-next/language/parser.rb +0 -28
  37. data/lib/ruby-next/language/rewriters/args_forward.rb +0 -57
  38. data/lib/ruby-next/language/rewriters/base.rb +0 -105
  39. data/lib/ruby-next/language/rewriters/endless_range.rb +0 -60
  40. data/lib/ruby-next/language/rewriters/method_reference.rb +0 -33
  41. data/lib/ruby-next/language/rewriters/numbered_params.rb +0 -41
  42. data/lib/ruby-next/language/rewriters/pattern_matching.rb +0 -541
  43. data/lib/ruby-next/language/runtime.rb +0 -95
  44. data/lib/ruby-next/language/setup.rb +0 -43
  45. data/lib/ruby-next/language/unparser.rb +0 -8
  46. data/lib/ruby-next/utils.rb +0 -36
  47. data/lib/ruby-next/version.rb +0 -5
  48. 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,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RubyNext::Core.patch Struct, method: :deconstruct, version: "2.7" do
4
- <<~'RUBY'
5
- alias deconstruct to_a
6
- RUBY
7
- 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
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RubyNext::Core.patch Time, method: :ceil, version: "2.7" do
4
- <<~'RUBY'
5
- def ceil(den = 0)
6
- change = subsec.ceil(den) - subsec
7
- self + change
8
- end
9
- RUBY
10
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RubyNext::Core.patch Time, method: :floor, version: "2.7" do
4
- <<~'RUBY'
5
- def floor(den = 0)
6
- self - (subsec % (10**-den))
7
- end
8
- RUBY
9
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RubyNext::Core.patch UnboundMethod, method: :bind_call, version: "2.7" do
4
- <<~'RUBY'
5
- def bind_call(receiver, *args, &block)
6
- bind(receiver).call(*args, &block)
7
- end
8
- RUBY
9
- end
@@ -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
@@ -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