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