ruby-next-core 0.15.2 → 1.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
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