ruby-next-core 0.15.2 → 1.0.0.rc.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -0
  3. data/README.md +118 -48
  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 +54 -10
  8. data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +82 -2
  9. data/lib/.rbnext/2.3/ruby-next/config.rb +79 -0
  10. data/lib/.rbnext/2.3/ruby-next/core/data.rb +159 -0
  11. data/lib/.rbnext/2.3/ruby-next/language/rewriters/2.7/pattern_matching.rb +44 -9
  12. data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +6 -32
  13. data/lib/.rbnext/2.3/ruby-next/utils.rb +3 -22
  14. data/lib/.rbnext/2.6/ruby-next/core/data.rb +159 -0
  15. data/lib/.rbnext/2.7/ruby-next/core/data.rb +159 -0
  16. data/lib/.rbnext/2.7/ruby-next/core.rb +10 -2
  17. data/lib/.rbnext/2.7/ruby-next/language/paco_parsers/string_literals.rb +109 -0
  18. data/lib/.rbnext/2.7/ruby-next/language/rewriters/2.7/pattern_matching.rb +44 -9
  19. data/lib/.rbnext/2.7/ruby-next/language/rewriters/text.rb +132 -0
  20. data/lib/.rbnext/3.2/ruby-next/commands/base.rb +55 -0
  21. data/lib/.rbnext/3.2/ruby-next/language/rewriters/2.7/pattern_matching.rb +1095 -0
  22. data/lib/.rbnext/3.2/ruby-next/rubocop.rb +210 -0
  23. data/lib/ruby-next/commands/nextify.rb +84 -2
  24. data/lib/ruby-next/config.rb +27 -0
  25. data/lib/ruby-next/core/data.rb +159 -0
  26. data/lib/ruby-next/core/matchdata/deconstruct.rb +9 -0
  27. data/lib/ruby-next/core/matchdata/deconstruct_keys.rb +20 -0
  28. data/lib/ruby-next/core/matchdata/named_captures.rb +11 -0
  29. data/lib/ruby-next/core/refinement/import.rb +44 -36
  30. data/lib/ruby-next/core/time/deconstruct_keys.rb +30 -0
  31. data/lib/ruby-next/core.rb +10 -2
  32. data/lib/ruby-next/irb.rb +2 -2
  33. data/lib/ruby-next/language/bootsnap.rb +2 -25
  34. data/lib/ruby-next/language/paco_parser.rb +7 -0
  35. data/lib/ruby-next/language/paco_parsers/base.rb +47 -0
  36. data/lib/ruby-next/language/paco_parsers/comments.rb +26 -0
  37. data/lib/ruby-next/language/paco_parsers/string_literals.rb +109 -0
  38. data/lib/ruby-next/language/parser.rb +24 -2
  39. data/lib/ruby-next/language/rewriters/2.7/pattern_matching.rb +42 -7
  40. data/lib/ruby-next/language/rewriters/3.0/args_forward_leading.rb +2 -2
  41. data/lib/ruby-next/language/rewriters/3.1/oneline_pattern_parensless.rb +1 -1
  42. data/lib/ruby-next/language/rewriters/3.2/anonymous_restargs.rb +104 -0
  43. data/lib/ruby-next/language/rewriters/abstract.rb +57 -0
  44. data/lib/ruby-next/language/rewriters/base.rb +6 -32
  45. data/lib/ruby-next/language/rewriters/edge/it_param.rb +58 -0
  46. data/lib/ruby-next/language/rewriters/edge.rb +12 -0
  47. data/lib/ruby-next/language/rewriters/proposed/bind_vars_pattern.rb +3 -0
  48. data/lib/ruby-next/language/rewriters/proposed/method_reference.rb +9 -20
  49. data/lib/ruby-next/language/rewriters/text.rb +132 -0
  50. data/lib/ruby-next/language/runtime.rb +9 -86
  51. data/lib/ruby-next/language/setup.rb +5 -2
  52. data/lib/ruby-next/language/unparser.rb +5 -0
  53. data/lib/ruby-next/language.rb +54 -10
  54. data/lib/ruby-next/pry.rb +1 -1
  55. data/lib/ruby-next/rubocop.rb +2 -0
  56. data/lib/ruby-next/utils.rb +3 -22
  57. data/lib/ruby-next/version.rb +1 -1
  58. data/lib/uby-next.rb +2 -2
  59. metadata +65 -12
@@ -0,0 +1,159 @@
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..].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 members
70
+ self.class.members
71
+ end
72
+
73
+ def initialize(**kwargs)
74
+ kwargs_size = kwargs.size
75
+ members_size = members.size
76
+
77
+ if kwargs_size > members_size
78
+ extras = kwargs.except(*members).keys
79
+
80
+ if extras.size > 1
81
+ raise ArgumentError, "unknown keywords: #{extras.map { |_1| ":#{_1}" }.join(", ")}"
82
+ else
83
+ raise ArgumentError, "unknown keyword: :#{extras.first}"
84
+ end
85
+ elsif kwargs_size < members_size
86
+ missing = members.select { |k| !kwargs.include?(k) }
87
+
88
+ if missing.size > 1
89
+ raise ArgumentError, "missing keywords: #{missing.map { |_1| ":#{_1}" }.join(", ")}"
90
+ else
91
+ raise ArgumentError, "missing keyword: :#{missing.first}"
92
+ end
93
+ end
94
+
95
+ @attributes = members.map { |m| [m, kwargs[m]] }.to_h
96
+ end
97
+
98
+ def deconstruct
99
+ @attributes.values
100
+ end
101
+
102
+ def deconstruct_keys(array)
103
+ raise TypeError unless array.is_a?(Array) || array.nil?
104
+ return @attributes if array&.first.nil?
105
+
106
+ @attributes.slice(*array)
107
+ end
108
+
109
+ def to_h(&block)
110
+ @attributes.to_h(&block)
111
+ end
112
+
113
+ def hash
114
+ to_h.hash
115
+ end
116
+
117
+ def eql?(other)
118
+ self.class == other.class && hash == other.hash
119
+ end
120
+
121
+ def ==(other)
122
+ self.class == other.class && to_h == other.to_h
123
+ end
124
+
125
+ def inspect
126
+ attribute_markers = @attributes.map do |key, value|
127
+ insect_key = key.to_s.start_with?("@") ? ":#{key}" : key
128
+ "#{insect_key}=#{value}"
129
+ end.join(", ")
130
+
131
+ display = ["data", self.class.name, attribute_markers].compact.join(" ")
132
+
133
+ "#<#{display}>"
134
+ end
135
+ alias_method :to_s, :inspect
136
+
137
+ def with(**kwargs)
138
+ return self if kwargs.empty?
139
+
140
+ self.class.new(**@attributes.merge(kwargs))
141
+ end
142
+
143
+ private
144
+
145
+ def marshal_dump
146
+ @attributes
147
+ end
148
+
149
+ def marshal_load(attributes)
150
+ @attributes = attributes
151
+ freeze
152
+ end
153
+
154
+ def initialize_copy(source)
155
+ super.freeze
156
+ end
157
+ end
158
+
159
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
3
+ require "set" # rubocop:disable Lint/RedundantRequireStatement
4
4
 
5
5
  require "ruby-next/config"
6
6
  require "ruby-next/utils"
@@ -78,7 +78,7 @@ module RubyNext
78
78
  end
79
79
 
80
80
  def native_location?(location)
81
- location.nil? || location.first.match?(/(<internal:|resource:\/truffleruby\/core)/)
81
+ location.nil? || location.first.match?(/(<internal:|resource:\/truffleruby\/core|uri:classloader:\/jruby)/)
82
82
  end
83
83
  end
84
84
 
@@ -197,6 +197,11 @@ require "ruby-next/core/matchdata/match"
197
197
  require "ruby-next/core/enumerable/compact"
198
198
  require "ruby-next/core/integer/try_convert"
199
199
 
200
+ require "ruby-next/core/matchdata/deconstruct"
201
+ require "ruby-next/core/matchdata/deconstruct_keys"
202
+ require "ruby-next/core/matchdata/named_captures"
203
+ require "ruby-next/core/time/deconstruct_keys"
204
+
200
205
  # Generate refinements
201
206
  RubyNext.module_eval do
202
207
  RubyNext::Core.patches.refined.each do |mod, patches|
@@ -210,3 +215,6 @@ RubyNext.module_eval do
210
215
  end
211
216
  end
212
217
  end
218
+
219
+ # Load backports
220
+ require "ruby-next/core/data" unless ENV["RUBY_NEXT_DISABLE_DATA"] == "true"
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module PacoParsers
6
+ class StringLiterals < Base
7
+ PAIRS = {"[" => "]", "{" => "}", "<" => ">"}.freeze
8
+
9
+ def default
10
+ all_strings.fmap do |result|
11
+ reduce_tokens(result.flatten)
12
+ end
13
+ end
14
+
15
+ def all_strings
16
+ alt(
17
+ single_quoted,
18
+ double_quoted,
19
+ external_cmd_exec,
20
+ quoted,
21
+ quoted_expanded
22
+ )
23
+ # heredoc,
24
+ # heredoc_expanded
25
+ end
26
+
27
+ def quoted
28
+ seq(
29
+ string("%q"),
30
+ any_char.bind do |char|
31
+ end_symbol = string(PAIRS[char] || char)
32
+ escapable_string(succeed(char), end_symbol)
33
+ end
34
+ )
35
+ end
36
+
37
+ def single_quoted
38
+ escapable_string(string("'"))
39
+ end
40
+
41
+ def quoted_expanded
42
+ seq(
43
+ alt(string("%Q"), string("%")),
44
+ any_char.bind do |char|
45
+ end_symbol = string(PAIRS[char] || char)
46
+ escapable_string(succeed(char), end_symbol, interpolate: true)
47
+ end
48
+ )
49
+ end
50
+
51
+ def external_cmd_exec
52
+ escapable_string(string("`"), interpolate: true)
53
+ end
54
+
55
+ def double_quoted
56
+ escapable_string(string('"'), interpolate: true)
57
+ end
58
+
59
+ def escapable_string(left, right = nil, interpolate: false)
60
+ right ||= left
61
+ seq(
62
+ left,
63
+ many(
64
+ alt(
65
+ *[
66
+ seq(string("\\"), right).fmap { |_1| [:literal, _1] },
67
+ interpolate ? seq(
68
+ string('#{'),
69
+ lazy { alt(balanced("{", "}", alt(all_strings, any_char)), many(none_of("}"))) },
70
+ string("}")
71
+ ) : nil,
72
+ not_followed_by(right).bind { any_char }.fmap { |_1| [:literal, _1] }
73
+ ].compact
74
+ )
75
+ ),
76
+ right
77
+ )
78
+ end
79
+
80
+ private
81
+
82
+ def reduce_tokens(tokens)
83
+ state = :literal
84
+
85
+ tokens.each_with_object([]) do |v, acc|
86
+ if v == :literal
87
+ acc << [:literal, +""] unless state == :literal
88
+ state = :next_literal
89
+ next acc
90
+ end
91
+
92
+ if state == :next_literal
93
+ state = :literal
94
+ acc.last[1] << v
95
+ next acc
96
+ end
97
+
98
+ if state == :literal
99
+ acc << [:code, +""]
100
+ end
101
+
102
+ state = :code
103
+ acc.last[1] << v
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ 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
@@ -425,15 +425,15 @@ module RubyNext
425
425
  s(:begin,
426
426
  s(:and,
427
427
  node,
428
- send(:"#{pattern.type}_clause", pattern)))
428
+ send(:"#{pattern.type}_clause", pattern, right)))
429
429
  end
430
430
  end
431
431
 
432
- def match_alt_clause(node)
432
+ def match_alt_clause(node, matchee = s(:lvar, locals[:matchee]))
433
433
  children = locals.with(ALTERNATION_MARKER => true) do
434
434
  node.children.map.with_index do |child, i|
435
435
  predicates.terminate! if i == 1
436
- send :"#{child.type}_clause", child
436
+ send :"#{child.type}_clause", child, matchee
437
437
  end
438
438
  end
439
439
  s(:begin, s(:or, *children))
@@ -663,6 +663,14 @@ module RubyNext
663
663
  end
664
664
  end
665
665
 
666
+ def const_pattern_array_element(node, index)
667
+ element = arr_item_at(index)
668
+ locals.with(arr: locals[:arr, index]) do
669
+ predicates.push :"i#{index}"
670
+ const_pattern_clause(node, element).tap { predicates.pop }
671
+ end
672
+ end
673
+
666
674
  def match_alt_array_element(node, index)
667
675
  children = node.children.map do |child, i|
668
676
  send :"#{child.type}_array_element", child, index
@@ -671,11 +679,19 @@ module RubyNext
671
679
  end
672
680
 
673
681
  def match_var_array_element(node, index)
674
- match_var_clause(node, arr_item_at(index))
682
+ element = arr_item_at(index)
683
+ locals.with(arr: locals[:arr, index]) do
684
+ predicates.push :"i#{index}"
685
+ match_var_clause(node, element).tap { predicates.pop }
686
+ end
675
687
  end
676
688
 
677
689
  def match_as_array_element(node, index)
678
- match_as_clause(node, arr_item_at(index))
690
+ element = arr_item_at(index)
691
+ locals.with(arr: locals[:arr, index]) do
692
+ predicates.push :"i#{index}"
693
+ match_as_clause(node, element).tap { predicates.pop }
694
+ end
679
695
  end
680
696
 
681
697
  def pin_array_element(node, index)
@@ -844,6 +860,15 @@ module RubyNext
844
860
  end
845
861
  end
846
862
 
863
+ def const_pattern_hash_element(node, key)
864
+ element = hash_value_at(key)
865
+ key_index = deconstructed_key(key)
866
+ locals.with(hash: locals[:hash, key_index]) do
867
+ predicates.push :"k#{key_index}"
868
+ const_pattern_clause(node, element).tap { predicates.pop }
869
+ end
870
+ end
871
+
847
872
  def hash_element(head, *tail)
848
873
  send("#{head.type}_hash_element", head).then do |node|
849
874
  next node if tail.empty?
@@ -884,12 +909,22 @@ module RubyNext
884
909
  end
885
910
 
886
911
  def match_as_hash_element(node, key)
887
- match_as_clause(node, hash_value_at(key))
912
+ element = hash_value_at(key)
913
+ key_index = deconstructed_key(key)
914
+ locals.with(hash: locals[:hash, key_index]) do
915
+ predicates.push :"k#{key_index}"
916
+ match_as_clause(node, element).tap { predicates.pop }
917
+ end
888
918
  end
889
919
 
890
920
  def match_var_hash_element(node, key = nil)
891
921
  key ||= node.children[0]
892
- match_var_clause(node, hash_value_at(key))
922
+ element = hash_value_at(key)
923
+ key_index = deconstructed_key(key)
924
+ locals.with(hash: locals[:hash, key_index]) do
925
+ predicates.push :"k#{key_index}"
926
+ match_var_clause(node, element).tap { predicates.pop }
927
+ end
893
928
  end
894
929
 
895
930
  def match_nil_pattern_hash_element(node, _key = nil)
@@ -1000,7 +1035,7 @@ module RubyNext
1000
1035
  s(:send, node, :respond_to?, mid.to_ast_node)
1001
1036
  end
1002
1037
 
1003
- def respond_to_missing?(mid, *)
1038
+ def respond_to_missing?(mid, *__rest__)
1004
1039
  return true if mid.to_s.match?(/_(clause|array_element)/)
1005
1040
  super
1006
1041
  end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby-next/language/paco_parser"
4
+
5
+ module RubyNext
6
+ module Language
7
+ module Rewriters
8
+ class Text < Abstract
9
+ using RubyNext
10
+
11
+ class Normalizer < PacoParsers::Base
12
+ attr_reader :store
13
+
14
+ def initialize
15
+ @store = []
16
+ end
17
+
18
+ def normalizing(source)
19
+ many(
20
+ alt(
21
+ ruby_comment,
22
+ ruby_string,
23
+ ruby_code
24
+ )
25
+ ).parse(source, with_callstack: true)
26
+ .then(&:join)
27
+ .then do |_1|
28
+ if block_given?
29
+ yield _1
30
+ else
31
+ _1
32
+ end
33
+ end
34
+ .then do |new_source|
35
+ restore(new_source)
36
+ end
37
+ end
38
+
39
+ def ruby_comment
40
+ parse_comments.fmap do |result|
41
+ store << result
42
+ "# A#{store.size}Я\n"
43
+ end
44
+ end
45
+
46
+ def ruby_string
47
+ parse_strings.fmap do |result|
48
+ result.each_with_object([]) do |(type, str), acc|
49
+ if type == :literal
50
+ store << str
51
+ acc << "_A#{store.size}Я_"
52
+ else
53
+ acc << str
54
+ end
55
+ acc
56
+ end.join
57
+ end
58
+ end
59
+
60
+ def ruby_code
61
+ any_char
62
+ end
63
+
64
+ def restore(source)
65
+ source.gsub(/(?:\# |_)A(\d+)Я(?:_|\n)/m) do |*args|
66
+ store[$1.to_i - 1]
67
+ end
68
+ end
69
+
70
+ def parse_comments
71
+ memoize { PacoParsers::Comments.new.default }
72
+ end
73
+
74
+ def parse_strings
75
+ memoize { PacoParsers::StringLiterals.new.default }
76
+ end
77
+ end
78
+
79
+ # Base class for rewriting parsers which adds the #track! method
80
+ class PacoParser < PacoParsers::Base
81
+ attr_reader :rewriter, :context
82
+
83
+ def initialize(rewriter, context)
84
+ @rewriter = rewriter
85
+ @context = context
86
+ end
87
+
88
+ def track!
89
+ context.track!(rewriter)
90
+ end
91
+ end
92
+
93
+ class << self
94
+ def parser(&block)
95
+ @paco_parser = Class.new(PacoParser, &block)
96
+ end
97
+
98
+ def paco_parser
99
+ return @paco_parser if @paco_parser
100
+
101
+ superclass.paco_parser if superclass.respond_to?(:paco_parser)
102
+ end
103
+
104
+ def text?
105
+ true
106
+ end
107
+ end
108
+
109
+ # Rewrite source code by ignoring string literals and comments
110
+ def rewrite(source)
111
+ Normalizer.new.normalizing(source) do |normalized|
112
+ safe_rewrite(normalized)
113
+ end
114
+ end
115
+
116
+ def safe_rewrite(source)
117
+ source
118
+ end
119
+
120
+ private
121
+
122
+ def parse(source)
123
+ parser_class = self.class.paco_parser
124
+ raise "No parser defined for #{self.class}" unless parser_class
125
+
126
+ paco_parser = self.class.paco_parser.new(self, context)
127
+ paco_parser.parse(source)
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+
5
+ module RubyNext
6
+ module Commands
7
+ class Base
8
+ class << self
9
+ def run(args)
10
+ new(args).run
11
+ end
12
+ end
13
+
14
+ attr_reader :dry_run
15
+ alias dry_run? dry_run
16
+
17
+ def initialize(args)
18
+ parse! args
19
+ end
20
+
21
+ def parse!(*__rest__)
22
+ raise NotImplementedError
23
+ end
24
+
25
+ def run
26
+ raise NotImplementedError
27
+ end
28
+
29
+ def log(msg)
30
+ return unless CLI.verbose?
31
+
32
+ if CLI.dry_run?
33
+ $stdout.puts "[DRY RUN] #{msg}"
34
+ else
35
+ $stdout.puts msg
36
+ end
37
+ end
38
+
39
+ def base_parser
40
+ OptionParser.new do |opts|
41
+ yield opts
42
+
43
+ opts.on("-V", "Turn on verbose mode") do
44
+ CLI.verbose = true
45
+ end
46
+
47
+ opts.on("--dry-run", "Print verbose output without generating files") do
48
+ CLI.dry_run = true
49
+ CLI.verbose = true
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end