ruby-next-core 0.15.3 → 1.0.1

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +127 -54
  4. data/bin/mspec +11 -0
  5. data/lib/.rbnext/2.1/ruby-next/commands/nextify.rb +295 -0
  6. data/lib/.rbnext/2.1/ruby-next/core.rb +10 -2
  7. data/lib/.rbnext/2.1/ruby-next/language.rb +59 -12
  8. data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +83 -3
  9. data/lib/.rbnext/2.3/ruby-next/config.rb +79 -0
  10. data/lib/.rbnext/2.3/ruby-next/core/data.rb +163 -0
  11. data/lib/.rbnext/2.3/ruby-next/language/eval.rb +4 -4
  12. data/lib/.rbnext/2.3/ruby-next/language/rewriters/2.7/pattern_matching.rb +2 -2
  13. data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +6 -32
  14. data/lib/.rbnext/2.3/ruby-next/utils.rb +3 -22
  15. data/lib/.rbnext/2.6/ruby-next/core/data.rb +163 -0
  16. data/lib/.rbnext/2.7/ruby-next/core/data.rb +163 -0
  17. data/lib/.rbnext/2.7/ruby-next/core.rb +10 -2
  18. data/lib/.rbnext/2.7/ruby-next/language/paco_parsers/string_literals.rb +109 -0
  19. data/lib/.rbnext/2.7/ruby-next/language/rewriters/2.7/pattern_matching.rb +2 -2
  20. data/lib/.rbnext/2.7/ruby-next/language/rewriters/text.rb +132 -0
  21. data/lib/.rbnext/3.2/ruby-next/commands/base.rb +55 -0
  22. data/lib/.rbnext/3.2/ruby-next/language/rewriters/2.7/pattern_matching.rb +1095 -0
  23. data/lib/.rbnext/3.2/ruby-next/rubocop.rb +210 -0
  24. data/lib/ruby-next/commands/nextify.rb +85 -3
  25. data/lib/ruby-next/config.rb +29 -2
  26. data/lib/ruby-next/core/data.rb +163 -0
  27. data/lib/ruby-next/core/matchdata/deconstruct.rb +9 -0
  28. data/lib/ruby-next/core/matchdata/deconstruct_keys.rb +20 -0
  29. data/lib/ruby-next/core/matchdata/named_captures.rb +11 -0
  30. data/lib/ruby-next/core/refinement/import.rb +44 -36
  31. data/lib/ruby-next/core/time/deconstruct_keys.rb +30 -0
  32. data/lib/ruby-next/core.rb +10 -2
  33. data/lib/ruby-next/irb.rb +2 -2
  34. data/lib/ruby-next/language/bootsnap.rb +2 -25
  35. data/lib/ruby-next/language/eval.rb +4 -4
  36. data/lib/ruby-next/language/paco_parser.rb +7 -0
  37. data/lib/ruby-next/language/paco_parsers/base.rb +47 -0
  38. data/lib/ruby-next/language/paco_parsers/comments.rb +26 -0
  39. data/lib/ruby-next/language/paco_parsers/string_literals.rb +109 -0
  40. data/lib/ruby-next/language/parser.rb +31 -6
  41. data/lib/ruby-next/language/rewriters/3.0/args_forward_leading.rb +2 -2
  42. data/lib/ruby-next/language/rewriters/3.1/oneline_pattern_parensless.rb +1 -1
  43. data/lib/ruby-next/language/rewriters/3.1/shorthand_hash.rb +2 -1
  44. data/lib/ruby-next/language/rewriters/3.2/anonymous_restargs.rb +104 -0
  45. data/lib/ruby-next/language/rewriters/abstract.rb +57 -0
  46. data/lib/ruby-next/language/rewriters/base.rb +6 -32
  47. data/lib/ruby-next/language/rewriters/edge/it_param.rb +58 -0
  48. data/lib/ruby-next/language/rewriters/edge.rb +12 -0
  49. data/lib/ruby-next/language/rewriters/proposed/bind_vars_pattern.rb +3 -0
  50. data/lib/ruby-next/language/rewriters/proposed/method_reference.rb +9 -20
  51. data/lib/ruby-next/language/rewriters/text.rb +132 -0
  52. data/lib/ruby-next/language/runtime.rb +9 -86
  53. data/lib/ruby-next/language/setup.rb +5 -2
  54. data/lib/ruby-next/language/unparser.rb +5 -0
  55. data/lib/ruby-next/language.rb +59 -12
  56. data/lib/ruby-next/pry.rb +1 -1
  57. data/lib/ruby-next/rubocop.rb +2 -0
  58. data/lib/ruby-next/utils.rb +3 -22
  59. data/lib/ruby-next/version.rb +1 -1
  60. data/lib/uby-next.rb +2 -2
  61. metadata +63 -10
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ # Mininum Ruby version supported by RubyNext
5
+ MIN_SUPPORTED_VERSION = Gem::Version.new("2.2.0")
6
+
7
+ # Where to store transpiled files (relative from the project LOAD_PATH, usually `lib/`)
8
+ RUBY_NEXT_DIR = ".rbnext"
9
+
10
+ # Defines last minor version for every major version
11
+ LAST_MINOR_VERSIONS = {
12
+ 2 => 8, # 2.8 is required for backward compatibility: some gems already uses it
13
+ 3 => 4
14
+ }.freeze
15
+
16
+ LATEST_VERSION = [3, 4].freeze
17
+
18
+ # A virtual version number used for proposed features
19
+ NEXT_VERSION = "1995.next.0"
20
+
21
+ class << self
22
+ # TruffleRuby claims its RUBY_VERSION to be X.Y while not supporting all the features
23
+ # Currently (23.0.1), it still doesn't support pattern matching, although claims to be "like 3.1".
24
+ # So, we fallback to 2.6.5 (since we cannot use 2.7).
25
+ if defined?(TruffleRuby)
26
+ def current_ruby_version
27
+ "2.6.5"
28
+ end
29
+ else
30
+ def current_ruby_version
31
+ ::RUBY_VERSION
32
+ end
33
+ end
34
+
35
+ # Returns true if we want to use edge syntax
36
+ def edge_syntax?
37
+ %w[y true 1].include?(ENV["RUBY_NEXT_EDGE"])
38
+ end
39
+
40
+ def proposed_syntax?
41
+ %w[y true 1].include?(ENV["RUBY_NEXT_PROPOSED"])
42
+ end
43
+
44
+ def next_ruby_version(version = current_ruby_version)
45
+ return if version == Gem::Version.new(NEXT_VERSION)
46
+
47
+ major, minor = Gem::Version.new(version).segments.map(&:to_i)
48
+
49
+ return Gem::Version.new(NEXT_VERSION) if major >= LATEST_VERSION.first && minor >= LATEST_VERSION.last
50
+
51
+ nxt =
52
+ if LAST_MINOR_VERSIONS[major] == minor
53
+ "#{major + 1}.0.0"
54
+ else
55
+ "#{major}.#{minor + 1}.0"
56
+ end
57
+
58
+ Gem::Version.new(nxt)
59
+ end
60
+
61
+ # Load transpile settings from the RC file (nextify command flags)
62
+ def load_from_rc(path = ".rbnextrc")
63
+ return unless File.exist?(path)
64
+
65
+ require "yaml"
66
+
67
+ args = ((((__safe_lvar__ = ((((__safe_lvar__ = ((((__safe_lvar__ = YAML.load_file(path)) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.fetch("nextify", ""))) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.lines)) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.flat_map { |line|
68
+ line.chomp.split(/\s+/)
69
+ })
70
+
71
+ ENV["RUBY_NEXT_EDGE"] ||= "true" if args.delete("--edge")
72
+ ENV["RUBY_NEXT_PROPOSED"] ||= "true" if args.delete("--proposed")
73
+ ENV["RUBY_NEXT_TRANSPILE_MODE"] ||= "rewrite" if args.delete("--transpile-mode=rewrite")
74
+ ENV["RUBY_NEXT_TRANSPILE_MODE"] ||= "ast" if args.delete("--transpile-mode=ast")
75
+ end
76
+ end
77
+
78
+ load_from_rc
79
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The code below originates from https://github.com/saturnflyer/polyfill-data
4
+
5
+ if !Object.const_defined?(:Data) || !Data.respond_to?(:define)
6
+
7
+ # Drop legacy Data class
8
+ begin
9
+ Object.send(:remove_const, :Data)
10
+ rescue
11
+ nil
12
+ end
13
+
14
+ class Data < Object
15
+ using RubyNext
16
+
17
+ class << self
18
+ undef_method :new
19
+ attr_reader :members
20
+ end
21
+
22
+ def self.define(*args, &block)
23
+ raise ArgumentError if args.any?(/=/)
24
+ if block
25
+ mod = Module.new
26
+ mod.define_singleton_method(:_) do |klass|
27
+ klass.class_eval(&block)
28
+ end
29
+ arity_converter = mod.method(:_)
30
+ end
31
+ klass = ::Class.new(self)
32
+
33
+ klass.instance_variable_set(:@members, args.map(&:to_sym).freeze)
34
+
35
+ klass.define_singleton_method(:new) do |*new_args, **new_kwargs, &block|
36
+ init_kwargs = if new_args.any?
37
+ raise ArgumentError, "unknown arguments #{new_args[members.size..-1].join(", ")}" if new_args.size > members.size
38
+ members.take(new_args.size).zip(new_args).to_h
39
+ else
40
+ new_kwargs
41
+ end
42
+
43
+ allocate.tap do |instance|
44
+ instance.send(:initialize, **init_kwargs, &block)
45
+ end.freeze
46
+ end
47
+
48
+ class << klass
49
+ alias_method :[], :new
50
+ undef_method :define
51
+ end
52
+
53
+ args.each do |arg|
54
+ if klass.method_defined?(arg)
55
+ raise ArgumentError, "duplicate member #{arg}"
56
+ end
57
+ klass.define_method(arg) do
58
+ @attributes[arg]
59
+ end
60
+ end
61
+
62
+ if arity_converter
63
+ klass.class_eval(&arity_converter)
64
+ end
65
+
66
+ klass
67
+ end
68
+
69
+ def self.inherited(subclass)
70
+ subclass.instance_variable_set(:@members, members)
71
+ end
72
+
73
+ def members
74
+ self.class.members
75
+ end
76
+
77
+ def initialize(**kwargs)
78
+ kwargs_size = kwargs.size
79
+ members_size = members.size
80
+
81
+ if kwargs_size > members_size
82
+ extras = kwargs.except(*members).keys
83
+
84
+ if extras.size > 1
85
+ raise ArgumentError, "unknown keywords: #{extras.map { |_1| ":#{_1}" }.join(", ")}"
86
+ else
87
+ raise ArgumentError, "unknown keyword: :#{extras.first}"
88
+ end
89
+ elsif kwargs_size < members_size
90
+ missing = members.select { |k| !kwargs.include?(k) }
91
+
92
+ if missing.size > 1
93
+ raise ArgumentError, "missing keywords: #{missing.map { |_1| ":#{_1}" }.join(", ")}"
94
+ else
95
+ raise ArgumentError, "missing keyword: :#{missing.first}"
96
+ end
97
+ end
98
+
99
+ @attributes = members.map { |m| [m, kwargs[m]] }.to_h
100
+ end
101
+
102
+ def deconstruct
103
+ @attributes.values
104
+ end
105
+
106
+ def deconstruct_keys(array)
107
+ raise TypeError unless array.is_a?(Array) || array.nil?
108
+ return @attributes if ((((__safe_lvar__ = array) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.first).nil?
109
+
110
+ @attributes.slice(*array)
111
+ end
112
+
113
+ def to_h(&block)
114
+ @attributes.to_h(&block)
115
+ end
116
+
117
+ def hash
118
+ to_h.hash
119
+ end
120
+
121
+ def eql?(other)
122
+ self.class == other.class && hash == other.hash
123
+ end
124
+
125
+ def ==(other)
126
+ self.class == other.class && to_h == other.to_h
127
+ end
128
+
129
+ def inspect
130
+ attribute_markers = @attributes.map do |key, value|
131
+ insect_key = key.to_s.start_with?("@") ? ":#{key}" : key
132
+ "#{insect_key}=#{value}"
133
+ end.join(", ")
134
+
135
+ display = ["data", self.class.name, attribute_markers].compact.join(" ")
136
+
137
+ "#<#{display}>"
138
+ end
139
+ alias_method :to_s, :inspect
140
+
141
+ def with(**kwargs)
142
+ return self if kwargs.empty?
143
+
144
+ self.class.new(**@attributes.merge(kwargs))
145
+ end
146
+
147
+ private
148
+
149
+ def marshal_dump
150
+ @attributes
151
+ end
152
+
153
+ def marshal_load(attributes)
154
+ @attributes = attributes
155
+ freeze
156
+ end
157
+
158
+ def initialize_copy(source)
159
+ super.freeze
160
+ end
161
+ end
162
+
163
+ end
@@ -11,7 +11,7 @@ module RubyNext
11
11
  using: ((((__safe_lvar__ = bind) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.receiver) == TOPLEVEL_BINDING.receiver || ((((__safe_lvar__ = ((((__safe_lvar__ = bind) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.receiver)) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.is_a?(Module))
12
12
  )
13
13
  RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
14
- super new_source, bind, *args
14
+ super(new_source, bind, *args)
15
15
  end
16
16
  end
17
17
  end
@@ -25,7 +25,7 @@ module RubyNext
25
25
  source = args.shift
26
26
  new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
27
27
  RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
28
- super new_source, *args
28
+ super(new_source, *args)
29
29
  end
30
30
  end
31
31
  end
@@ -39,7 +39,7 @@ module RubyNext
39
39
  new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
40
40
 
41
41
  RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
42
- super new_source, *args
42
+ super(new_source, *args)
43
43
  end
44
44
 
45
45
  def class_eval(*args, &block)
@@ -48,7 +48,7 @@ module RubyNext
48
48
  source = args.shift
49
49
  new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
50
50
  RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
51
- super new_source, *args
51
+ super(new_source, *args)
52
52
  end
53
53
  end
54
54
  end
@@ -185,7 +185,7 @@ module RubyNext
185
185
  # rubocop:disable Style/MissingRespondToMissing
186
186
  class Noop < Base
187
187
  # Return node itself, no memoization
188
- def method_missing(mid, node, *)
188
+ def method_missing(mid, node, *__rest__)
189
189
  node
190
190
  end
191
191
  end
@@ -1035,7 +1035,7 @@ module RubyNext
1035
1035
  s(:send, node, :respond_to?, mid.to_ast_node)
1036
1036
  end
1037
1037
 
1038
- def respond_to_missing?(mid, *)
1038
+ def respond_to_missing?(mid, *__rest__)
1039
1039
  return true if mid.to_s.match?(/_(clause|array_element)/)
1040
1040
  super
1041
1041
  end
@@ -12,7 +12,7 @@ module RubyNext
12
12
 
13
13
  MSG
14
14
 
15
- class Base < ::Parser::TreeRewriter
15
+ class Base < Abstract
16
16
  class LocalsTracker
17
17
  using(Module.new do
18
18
  refine ::Parser::AST::Node do
@@ -64,39 +64,15 @@ module RubyNext
64
64
  end
65
65
  end
66
66
 
67
- class << self
68
- # Returns true if the syntax is not supported
69
- # by the current Ruby (performs syntax check, not version check)
70
- def unsupported_syntax?
71
- save_verbose, $VERBOSE = $VERBOSE, nil
72
- eval_mid = Kernel.respond_to?(:eval_without_ruby_next) ? :eval_without_ruby_next : :eval
73
- Kernel.send eval_mid, self::SYNTAX_PROBE, nil, __FILE__, __LINE__
74
- false
75
- rescue SyntaxError, StandardError
76
- true
77
- ensure
78
- $VERBOSE = save_verbose
79
- end
80
-
81
- # Returns true if the syntax is supported
82
- # by the specified version
83
- def unsupported_version?(version)
84
- self::MIN_SUPPORTED_VERSION > version
85
- end
86
-
87
- private
67
+ attr_reader :locals
88
68
 
89
- def transform(source)
90
- Language.transform(source, rewriters: [self], using: false)
91
- end
69
+ def self.ast?
70
+ true
92
71
  end
93
72
 
94
- attr_reader :locals
95
-
96
- def initialize(context)
97
- @context = context
73
+ def initialize(*args)
98
74
  @locals = LocalsTracker.new
99
- super()
75
+ super
100
76
  end
101
77
 
102
78
  def s(type, *children)
@@ -144,8 +120,6 @@ module RubyNext
144
120
 
145
121
  Unparser.unparse(ast).chomp
146
122
  end
147
-
148
- attr_reader :context
149
123
  end
150
124
  end
151
125
  end
@@ -4,28 +4,6 @@ module RubyNext
4
4
  module Utils
5
5
  module_function
6
6
 
7
- if $LOAD_PATH.respond_to?(:resolve_feature_path)
8
- def resolve_feature_path(feature)
9
- ((((__safe_lvar__ = $LOAD_PATH.resolve_feature_path(feature)) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.last)
10
- rescue LoadError
11
- end
12
- else
13
- def resolve_feature_path(path)
14
- if File.file?(relative = File.expand_path(path))
15
- path = relative
16
- end
17
-
18
- path = "#{path}.rb" if File.extname(path).empty?
19
-
20
- return path if Pathname.new(path).absolute?
21
-
22
- $LOAD_PATH.find do |lp|
23
- lpath = File.join(lp, path)
24
- return File.realpath(lpath) if File.file?(lpath)
25
- end
26
- end
27
- end
28
-
29
7
  def source_with_lines(source, path)
30
8
  source.lines.map.with_index do |line, i|
31
9
  "#{(i + 1).to_s.rjust(4)}: #{line}"
@@ -36,6 +14,7 @@ module RubyNext
36
14
 
37
15
  # Returns true if modules refinement is supported in current version
38
16
  def refine_modules?
17
+ save_verbose, $VERBOSE = $VERBOSE, nil
39
18
  @refine_modules ||=
40
19
  begin
41
20
  # Make sure that including modules within refinements works
@@ -59,6 +38,8 @@ module RubyNext
59
38
  true
60
39
  rescue TypeError, NoMethodError
61
40
  false
41
+ ensure
42
+ $VERBOSE = save_verbose
62
43
  end
63
44
  end
64
45
  end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The code below originates from https://github.com/saturnflyer/polyfill-data
4
+
5
+ if !Object.const_defined?(:Data) || !Data.respond_to?(:define)
6
+
7
+ # Drop legacy Data class
8
+ begin
9
+ Object.send(:remove_const, :Data)
10
+ rescue
11
+ nil
12
+ end
13
+
14
+ class Data < Object
15
+ using RubyNext
16
+
17
+ class << self
18
+ undef_method :new
19
+ attr_reader :members
20
+ end
21
+
22
+ def self.define(*args, &block)
23
+ raise ArgumentError if args.any?(/=/)
24
+ if block
25
+ mod = Module.new
26
+ mod.define_singleton_method(:_) do |klass|
27
+ klass.class_eval(&block)
28
+ end
29
+ arity_converter = mod.method(:_)
30
+ end
31
+ klass = ::Class.new(self)
32
+
33
+ klass.instance_variable_set(:@members, args.map(&:to_sym).freeze)
34
+
35
+ klass.define_singleton_method(:new) do |*new_args, **new_kwargs, &block|
36
+ init_kwargs = if new_args.any?
37
+ raise ArgumentError, "unknown arguments #{new_args[members.size..-1].join(", ")}" if new_args.size > members.size
38
+ members.take(new_args.size).zip(new_args).to_h
39
+ else
40
+ new_kwargs
41
+ end
42
+
43
+ allocate.tap do |instance|
44
+ instance.send(:initialize, **init_kwargs, &block)
45
+ end.freeze
46
+ end
47
+
48
+ class << klass
49
+ alias_method :[], :new
50
+ undef_method :define
51
+ end
52
+
53
+ args.each do |arg|
54
+ if klass.method_defined?(arg)
55
+ raise ArgumentError, "duplicate member #{arg}"
56
+ end
57
+ klass.define_method(arg) do
58
+ @attributes[arg]
59
+ end
60
+ end
61
+
62
+ if arity_converter
63
+ klass.class_eval(&arity_converter)
64
+ end
65
+
66
+ klass
67
+ end
68
+
69
+ def self.inherited(subclass)
70
+ subclass.instance_variable_set(:@members, members)
71
+ end
72
+
73
+ def members
74
+ self.class.members
75
+ end
76
+
77
+ def initialize(**kwargs)
78
+ kwargs_size = kwargs.size
79
+ members_size = members.size
80
+
81
+ if kwargs_size > members_size
82
+ extras = kwargs.except(*members).keys
83
+
84
+ if extras.size > 1
85
+ raise ArgumentError, "unknown keywords: #{extras.map { |_1| ":#{_1}" }.join(", ")}"
86
+ else
87
+ raise ArgumentError, "unknown keyword: :#{extras.first}"
88
+ end
89
+ elsif kwargs_size < members_size
90
+ missing = members.select { |k| !kwargs.include?(k) }
91
+
92
+ if missing.size > 1
93
+ raise ArgumentError, "missing keywords: #{missing.map { |_1| ":#{_1}" }.join(", ")}"
94
+ else
95
+ raise ArgumentError, "missing keyword: :#{missing.first}"
96
+ end
97
+ end
98
+
99
+ @attributes = members.map { |m| [m, kwargs[m]] }.to_h
100
+ end
101
+
102
+ def deconstruct
103
+ @attributes.values
104
+ end
105
+
106
+ def deconstruct_keys(array)
107
+ raise TypeError unless array.is_a?(Array) || array.nil?
108
+ return @attributes if array&.first.nil?
109
+
110
+ @attributes.slice(*array)
111
+ end
112
+
113
+ def to_h(&block)
114
+ @attributes.to_h(&block)
115
+ end
116
+
117
+ def hash
118
+ to_h.hash
119
+ end
120
+
121
+ def eql?(other)
122
+ self.class == other.class && hash == other.hash
123
+ end
124
+
125
+ def ==(other)
126
+ self.class == other.class && to_h == other.to_h
127
+ end
128
+
129
+ def inspect
130
+ attribute_markers = @attributes.map do |key, value|
131
+ insect_key = key.to_s.start_with?("@") ? ":#{key}" : key
132
+ "#{insect_key}=#{value}"
133
+ end.join(", ")
134
+
135
+ display = ["data", self.class.name, attribute_markers].compact.join(" ")
136
+
137
+ "#<#{display}>"
138
+ end
139
+ alias_method :to_s, :inspect
140
+
141
+ def with(**kwargs)
142
+ return self if kwargs.empty?
143
+
144
+ self.class.new(**@attributes.merge(kwargs))
145
+ end
146
+
147
+ private
148
+
149
+ def marshal_dump
150
+ @attributes
151
+ end
152
+
153
+ def marshal_load(attributes)
154
+ @attributes = attributes
155
+ freeze
156
+ end
157
+
158
+ def initialize_copy(source)
159
+ super.freeze
160
+ end
161
+ end
162
+
163
+ end