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