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,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class AnonymousRestArgs < Base
7
+ NAME = "anonymous-rest-args"
8
+ SYNTAX_PROBE = "obj = Object.new; def obj.foo(*) bar(*); end"
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new("3.2.0")
10
+
11
+ REST = :__rest__
12
+ KWREST = :__kwrest__
13
+
14
+ def on_args(node)
15
+ rest = node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :restarg && child.children.first.nil? }
16
+ kwrest = node.children.find { |child| child.is_a?(::Parser::AST::Node) && child.type == :kwrestarg && child.children.first.nil? }
17
+
18
+ return super unless rest || kwrest
19
+
20
+ context.track! self
21
+
22
+ replace(rest.loc.expression, "*#{REST}") if rest
23
+ replace(kwrest.loc.expression, "**#{KWREST}") if kwrest
24
+
25
+ new_args = node.children.map do |child|
26
+ if child == rest
27
+ s(:restarg, REST)
28
+ elsif child == kwrest
29
+ s(:kwrestarg, KWREST)
30
+ else
31
+ child
32
+ end
33
+ end
34
+
35
+ node.updated(:args, new_args)
36
+ end
37
+
38
+ def on_send(node)
39
+ return super unless forwarded_args?(node)
40
+
41
+ process_send_args(node)
42
+ end
43
+
44
+ def on_super(node)
45
+ return super unless forwarded_args?(node)
46
+
47
+ process_send_args(node)
48
+ end
49
+
50
+ private
51
+
52
+ def forwarded_args?(node)
53
+ node.children.each do |child|
54
+ next unless child.is_a?(::Parser::AST::Node)
55
+
56
+ if child.type == :forwarded_restarg
57
+ return true
58
+ elsif child.type == :kwargs
59
+ child.children.each do |kwarg|
60
+ next unless kwarg.is_a?(::Parser::AST::Node)
61
+
62
+ return true if kwarg.type == :forwarded_kwrestarg
63
+ end
64
+ end
65
+ end
66
+
67
+ false
68
+ end
69
+
70
+ def process_send_args(node)
71
+ process(
72
+ node.updated(
73
+ nil,
74
+ node.children.map do |child|
75
+ next child unless child.is_a?(::Parser::AST::Node)
76
+
77
+ if child.type == :forwarded_restarg
78
+ replace(child.loc.expression, "*#{REST}")
79
+ s(:ksplat, s(:lvar, REST))
80
+ elsif child.type == :kwargs
81
+ child.updated(
82
+ nil,
83
+ child.children.map do |kwarg|
84
+ next kwarg unless kwarg.is_a?(::Parser::AST::Node)
85
+
86
+ if kwarg.type == :forwarded_kwrestarg
87
+ replace(kwarg.loc.expression, "**#{KWREST}")
88
+ s(:kwsplat, s(:lvar, KWREST))
89
+ else
90
+ kwarg
91
+ end
92
+ end
93
+ )
94
+ else
95
+ child
96
+ end
97
+ end
98
+ )
99
+ )
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class Abstract < ::Parser::TreeRewriter
7
+ NAME = "custom-rewriter"
8
+ SYNTAX_PROBE = "1 = [}"
9
+ MIN_SUPPORTED_VERSION = Gem::Version.new(RubyNext::NEXT_VERSION)
10
+
11
+ class << self
12
+ # Returns true if the syntax is not supported
13
+ # by the current Ruby (performs syntax check, not version check)
14
+ def unsupported_syntax?
15
+ save_verbose, $VERBOSE = $VERBOSE, nil
16
+ eval_mid = Kernel.respond_to?(:eval_without_ruby_next) ? :eval_without_ruby_next : :eval
17
+ Kernel.send eval_mid, self::SYNTAX_PROBE, nil, __FILE__, __LINE__
18
+ false
19
+ rescue SyntaxError, StandardError
20
+ true
21
+ ensure
22
+ $VERBOSE = save_verbose
23
+ end
24
+
25
+ # Returns true if the syntax is supported
26
+ # by the specified version
27
+ def unsupported_version?(version)
28
+ version < self::MIN_SUPPORTED_VERSION
29
+ end
30
+
31
+ def text?
32
+ false
33
+ end
34
+
35
+ def ast?
36
+ false
37
+ end
38
+
39
+ private
40
+
41
+ def transform(source)
42
+ Language.transform(source, rewriters: [self], using: false)
43
+ end
44
+ end
45
+
46
+ def initialize(context)
47
+ @context = context
48
+ super()
49
+ end
50
+
51
+ private
52
+
53
+ attr_reader :context
54
+ end
55
+ end
56
+ end
57
+ end
@@ -13,7 +13,7 @@ module RubyNext
13
13
 
14
14
  MSG
15
15
 
16
- class Base < ::Parser::TreeRewriter
16
+ class Base < Abstract
17
17
  class LocalsTracker
18
18
  using(Module.new do
19
19
  refine ::Parser::AST::Node do
@@ -65,39 +65,15 @@ module RubyNext
65
65
  end
66
66
  end
67
67
 
68
- class << self
69
- # Returns true if the syntax is not supported
70
- # by the current Ruby (performs syntax check, not version check)
71
- def unsupported_syntax?
72
- save_verbose, $VERBOSE = $VERBOSE, nil
73
- eval_mid = Kernel.respond_to?(:eval_without_ruby_next) ? :eval_without_ruby_next : :eval
74
- Kernel.send eval_mid, self::SYNTAX_PROBE, nil, __FILE__, __LINE__
75
- false
76
- rescue SyntaxError, StandardError
77
- true
78
- ensure
79
- $VERBOSE = save_verbose
80
- end
81
-
82
- # Returns true if the syntax is supported
83
- # by the specified version
84
- def unsupported_version?(version)
85
- self::MIN_SUPPORTED_VERSION > version
86
- end
87
-
88
- private
68
+ attr_reader :locals
89
69
 
90
- def transform(source)
91
- Language.transform(source, rewriters: [self], using: false)
92
- end
70
+ def self.ast?
71
+ true
93
72
  end
94
73
 
95
- attr_reader :locals
96
-
97
- def initialize(context)
98
- @context = context
74
+ def initialize(*args)
99
75
  @locals = LocalsTracker.new
100
- super()
76
+ super
101
77
  end
102
78
 
103
79
  def s(type, *children)
@@ -145,8 +121,6 @@ module RubyNext
145
121
 
146
122
  Unparser.unparse(ast).chomp
147
123
  end
148
-
149
- attr_reader :context
150
124
  end
151
125
  end
152
126
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyNext
4
+ module Language
5
+ module Rewriters
6
+ class ItParam < Base
7
+ using RubyNext
8
+
9
+ NAME = "it-param"
10
+ SYNTAX_PROBE = "proc { it.keys }.call({})"
11
+ MIN_SUPPORTED_VERSION = Gem::Version.new("3.4.0")
12
+
13
+ def on_block(node)
14
+ proc_or_lambda, args, body = *node.children
15
+
16
+ return super unless block_has_it?(body)
17
+
18
+ context.track! self
19
+
20
+ new_body = s(:begin,
21
+ s(:lvasgn, :it, s(:lvar, :_1)),
22
+ body)
23
+
24
+ insert_before(body.loc.expression, "it = _1;")
25
+
26
+ process(
27
+ node.updated(:numblock, [
28
+ proc_or_lambda,
29
+ args,
30
+ new_body
31
+ ])
32
+ )
33
+ end
34
+
35
+ private
36
+
37
+ # It's important to check if the current block refers to `it` variable somewhere
38
+ # (and not within a nested block), so we don't declare numbered params
39
+ def block_has_it?(node)
40
+ # traverse node children deeply
41
+ tree = [node]
42
+
43
+ while (child = tree.shift)
44
+ return true if it?(child)
45
+
46
+ if child.is_a?(Parser::AST::Node)
47
+ tree.unshift(*child.children.select { |c| c.is_a?(Parser::AST::Node) && c.type != :block && c.type != :numblock })
48
+ end
49
+ end
50
+ end
51
+
52
+ def it?(node)
53
+ node.is_a?(Parser::AST::Node) && node.type == :send && node.children[0].nil? && node.children[1] == :it && node.children[2].nil?
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -1,3 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Load edge Ruby features
4
+
5
+ require "ruby-next/language/rewriters/edge/it_param"
6
+
7
+ # We must add this rewriter before nubmered params rewriter to allow it to transform the source code further
8
+
9
+ number_params = RubyNext::Language.rewriters.index(RubyNext::Language::Rewriters::NumberedParams)
10
+
11
+ if number_params
12
+ RubyNext::Language.rewriters.insert(number_params, RubyNext::Language::Rewriters::ItParam)
13
+ else
14
+ RubyNext::Language.rewriters << RubyNext::Language::Rewriters::ItParam
15
+ end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "parser/rubynext"
4
+ RubyNext::Language.parser_class = ::Parser::RubyNext
5
+
3
6
  module RubyNext
4
7
  module Language
5
8
  module Rewriters
@@ -1,33 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "parser/rubynext"
4
+ RubyNext::Language.parser_class = ::Parser::RubyNext
5
+
3
6
  module RubyNext
4
7
  module Language
5
8
  module Rewriters
6
- class MethodReference < Base
9
+ class MethodReference < Text
7
10
  NAME = "method-reference"
8
11
  SYNTAX_PROBE = "Language.:transform"
9
12
  MIN_SUPPORTED_VERSION = Gem::Version.new(RubyNext::NEXT_VERSION)
10
13
 
11
- def on_meth_ref(node)
12
- context.track! self
13
-
14
- receiver, mid = *node.children
15
-
16
- replace(
17
- node.children.first.loc.expression.end.join(
18
- node.loc.expression.end
19
- ),
20
- ".method(:#{mid})"
21
- )
14
+ def safe_rewrite(source)
15
+ source.gsub(/\.:([\w_]+)/) do |match|
16
+ context.track! self
22
17
 
23
- node.updated(
24
- :send,
25
- [
26
- receiver,
27
- :method,
28
- s(:sym, mid)
29
- ]
30
- )
18
+ ".method(:#{$1})"
19
+ end
31
20
  end
32
21
  end
33
22
  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
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
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "pathname"
3
+ require "require-hooks/setup"
4
4
 
5
5
  require "ruby-next"
6
- require "ruby-next/utils"
7
6
  require "ruby-next/language"
8
7
  require "ruby-next/language/eval"
9
8
 
@@ -16,102 +15,26 @@ module RubyNext
16
15
  using RubyNext
17
16
 
18
17
  class << self
19
- include Utils
20
-
21
- def load(path, wrap: false)
22
- raise "RubyNext cannot handle `load(smth, wrap: true)`" if wrap
23
-
24
- contents = File.read(path)
18
+ def load(path, contents)
19
+ contents ||= File.read(path)
25
20
  new_contents = transform contents
26
21
 
27
22
  RubyNext.debug_source new_contents, path
28
23
 
29
- evaluate(new_contents, path)
30
- true
24
+ new_contents
31
25
  end
32
26
 
33
27
  def transform(contents, **options)
34
28
  Language.transform(contents, rewriters: Language.current_rewriters, **options)
35
29
  end
36
-
37
- def feature_path(path)
38
- path = resolve_feature_path(path)
39
- return if path.nil?
40
- return if File.extname(path) != ".rb"
41
- return unless Language.transformable?(path)
42
- path
43
- end
44
-
45
- if defined?(JRUBY_VERSION) || defined?(TruffleRuby)
46
- def evaluate(code, filepath)
47
- new_toplevel.eval(code, filepath)
48
- end
49
-
50
- def new_toplevel
51
- # Create new "toplevel" binding to avoid lexical scope re-use
52
- # (aka "leaking refinements")
53
- eval "proc{binding}.call", TOPLEVEL_BINDING, __FILE__, __LINE__
54
- end
55
- else
56
- def evaluate(code, filepath)
57
- # This is workaround to solve the "leaking refinements" problem in MRI
58
- RubyVM::InstructionSequence.compile(code, filepath).then do |iseq|
59
- iseq.eval
60
- end
61
- end
62
- end
63
30
  end
64
31
  end
65
32
  end
66
33
  end
67
34
 
68
- # Patch Kernel to hijack require/require_relative/load/eval
69
- module Kernel
70
- module_function
71
-
72
- alias_method :require_without_ruby_next, :require
73
- def require(path)
74
- realpath = RubyNext::Language::Runtime.feature_path(path)
75
- return require_without_ruby_next(path) unless realpath
76
-
77
- return false if $LOADED_FEATURES.include?(realpath)
78
-
79
- $LOADED_FEATURES << realpath
80
-
81
- RubyNext::Language::Runtime.load(realpath)
82
-
83
- true
84
- rescue => e
85
- $LOADED_FEATURES.delete realpath
86
- RubyNext.warn "RubyNext failed to require '#{path}': #{e.message}"
87
- require_without_ruby_next(path)
88
- end
89
-
90
- alias_method :require_relative_without_ruby_next, :require_relative
91
- def require_relative(path)
92
- loc = caller_locations(1..1).first
93
- from = loc.absolute_path || loc.path || File.join(Dir.pwd, "main")
94
- realpath = File.absolute_path(
95
- File.join(
96
- File.dirname(File.absolute_path(from)),
97
- path
98
- )
99
- )
100
- require(realpath)
101
- rescue => e
102
- RubyNext.warn "RubyNext failed to require relative '#{path}' from #{from}: #{e.message}"
103
- require_relative_without_ruby_next(path)
104
- end
105
-
106
- alias_method :load_without_ruby_next, :load
107
- def load(path, wrap = false)
108
- realpath = RubyNext::Language::Runtime.feature_path(path)
109
-
110
- return load_without_ruby_next(path, wrap) unless realpath
111
-
112
- RubyNext::Language::Runtime.load(realpath, wrap: wrap)
113
- rescue => e
114
- RubyNext.warn "RubyNext failed to load '#{path}': #{e.message}"
115
- load_without_ruby_next(path)
116
- end
35
+ RequireHooks.source_transform(
36
+ patterns: RubyNext::Language.include_patterns,
37
+ exclude_patterns: RubyNext::Language.exclude_patterns
38
+ ) do |path, contents|
39
+ RubyNext::Language::Runtime.load(path, contents)
117
40
  end
@@ -12,7 +12,10 @@ module RubyNext
12
12
  return if File.directory?(target_dir)
13
13
 
14
14
  Dir.chdir(root_dir) do
15
- unless system("bundle exec ruby-next nextify ./#{lib_dir} -o #{target_dir} --min-version=#{RubyNext.current_ruby_version} > /dev/null 2>&1")
15
+ command = "bundle exec ruby-next nextify " \
16
+ "./#{lib_dir} -o #{target_dir} --min-version=#{RubyNext.current_ruby_version}"
17
+
18
+ unless system("#{command} > /dev/null 2>&1")
16
19
  RubyNext.warn "Traspiled files are missing in: #{target_dir}. \n" \
17
20
  "Make sure you have gem 'ruby-next' in your Gemfile to auto-transpile the required files from source on load. " \
18
21
  "Otherwise the code from #{root_dir} may not work correctly."
@@ -43,7 +46,7 @@ module RubyNext
43
46
 
44
47
  dirname = File.realpath(dirname)
45
48
 
46
- return if Language.runtime? && Language.watch_dirs.include?(dirname)
49
+ return if Language.runtime? && Language.target_dir?(dirname)
47
50
 
48
51
  next_dirname = File.join(dirname, rbnext_dir)
49
52
 
@@ -5,6 +5,11 @@ save_verbose, $VERBOSE = $VERBOSE, nil
5
5
  require "parser/current"
6
6
  $VERBOSE = save_verbose
7
7
 
8
+ # For backward compatibility with older Unparser for EOL Rubies
9
+ if !Parser::Lexer.const_defined?(:ESCAPES)
10
+ Parser::Lexer::ESCAPES = Parser::LexerStrings::ESCAPES
11
+ end
12
+
8
13
  require "unparser"
9
14
 
10
15
  # For backward compatibility with older Unparser