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,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
@@ -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