ruby-next-core 0.2.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +68 -0
- data/LICENSE.txt +21 -0
- data/README.md +279 -0
- data/bin/parse +19 -0
- data/bin/ruby-next +16 -0
- data/bin/transform +21 -0
- data/lib/ruby-next.rb +37 -0
- data/lib/ruby-next/cli.rb +55 -0
- data/lib/ruby-next/commands/base.rb +42 -0
- data/lib/ruby-next/commands/nextify.rb +118 -0
- data/lib/ruby-next/core.rb +34 -0
- data/lib/ruby-next/core/array/difference_union_intersection.rb +31 -0
- data/lib/ruby-next/core/enumerable/filter.rb +23 -0
- data/lib/ruby-next/core/enumerable/filter_map.rb +50 -0
- data/lib/ruby-next/core/enumerable/tally.rb +28 -0
- data/lib/ruby-next/core/enumerator/produce.rb +22 -0
- data/lib/ruby-next/core/hash/merge.rb +16 -0
- data/lib/ruby-next/core/kernel/then.rb +12 -0
- data/lib/ruby-next/core/pattern_matching.rb +37 -0
- data/lib/ruby-next/core/proc/compose.rb +21 -0
- data/lib/ruby-next/core/runtime.rb +10 -0
- data/lib/ruby-next/language.rb +117 -0
- data/lib/ruby-next/language/bootsnap.rb +26 -0
- data/lib/ruby-next/language/eval.rb +64 -0
- data/lib/ruby-next/language/parser.rb +24 -0
- data/lib/ruby-next/language/rewriters/args_forward.rb +57 -0
- data/lib/ruby-next/language/rewriters/base.rb +105 -0
- data/lib/ruby-next/language/rewriters/endless_range.rb +60 -0
- data/lib/ruby-next/language/rewriters/method_reference.rb +31 -0
- data/lib/ruby-next/language/rewriters/numbered_params.rb +41 -0
- data/lib/ruby-next/language/rewriters/pattern_matching.rb +522 -0
- data/lib/ruby-next/language/runtime.rb +96 -0
- data/lib/ruby-next/language/setup.rb +43 -0
- data/lib/ruby-next/language/unparser.rb +8 -0
- data/lib/ruby-next/utils.rb +36 -0
- data/lib/ruby-next/version.rb +5 -0
- data/lib/uby-next.rb +68 -0
- metadata +117 -0
| @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            unless defined?(NoMatchingPatternError)
         | 
| 4 | 
            +
              class NoMatchingPatternError < RuntimeError
         | 
| 5 | 
            +
              end
         | 
| 6 | 
            +
            end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            # Add `#deconstruct` and `#deconstruct_keys` to core classes
         | 
| 9 | 
            +
            unless [].respond_to?(:deconstruct)
         | 
| 10 | 
            +
              RubyNext.module_eval do
         | 
| 11 | 
            +
                refine Array do
         | 
| 12 | 
            +
                  def deconstruct
         | 
| 13 | 
            +
                    self
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                refine Struct do
         | 
| 18 | 
            +
                  alias deconstruct to_a
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            unless {}.respond_to?(:deconstruct_keys)
         | 
| 24 | 
            +
              RubyNext.module_eval do
         | 
| 25 | 
            +
                refine Hash do
         | 
| 26 | 
            +
                  def deconstruct_keys(_)
         | 
| 27 | 
            +
                    self
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                refine Struct do
         | 
| 32 | 
            +
                  def deconstruct_keys(_)
         | 
| 33 | 
            +
                    to_h
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
            end
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # rubocop:disable Style/LambdaCall
         | 
| 4 | 
            +
            unless proc {}.respond_to?(:<<)
         | 
| 5 | 
            +
              RubyNext.module_eval do
         | 
| 6 | 
            +
                refine Proc do
         | 
| 7 | 
            +
                  def <<(other)
         | 
| 8 | 
            +
                    raise TypeError, "callable object is expected" unless other.respond_to?(:call)
         | 
| 9 | 
            +
                    this = self
         | 
| 10 | 
            +
                    proc { |*args, &block| this.(other.(*args, &block)) }
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def >>(other)
         | 
| 14 | 
            +
                    raise TypeError, "callable object is expected" unless other.respond_to?(:call)
         | 
| 15 | 
            +
                    this = self
         | 
| 16 | 
            +
                    proc { |*args, &block| other.(this.(*args, &block)) }
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
| 21 | 
            +
            # rubocop:enable Style/LambdaCall
         | 
| @@ -0,0 +1,10 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # Extend `Language.transform` to inject `using RubyNext` to every file
         | 
| 4 | 
            +
            RubyNext::Language.singleton_class.prepend(Module.new do
         | 
| 5 | 
            +
              def transform(contents, using: true, **hargs)
         | 
| 6 | 
            +
                # We cannot activate refinements in eval
         | 
| 7 | 
            +
                new_contents = RubyNext::Core.inject!(contents) if using
         | 
| 8 | 
            +
                super(new_contents || contents, using: using, **hargs)
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
            end)
         | 
| @@ -0,0 +1,117 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            gem "parser", ">= 2.7.0.0"
         | 
| 4 | 
            +
            gem "unparser", ">= 0.4.7"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            require "set"
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            require "ruby-next"
         | 
| 9 | 
            +
            using RubyNext
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            module RubyNext
         | 
| 12 | 
            +
              # Language module contains tools to transpile newer Ruby syntax
         | 
| 13 | 
            +
              # into an older one.
         | 
| 14 | 
            +
              #
         | 
| 15 | 
            +
              # It works the following way:
         | 
| 16 | 
            +
              #   - Takes a Ruby source code as input
         | 
| 17 | 
            +
              #   - Generates the AST using the edge parser (via the `parser` gem)
         | 
| 18 | 
            +
              #   - Pass this AST through the list of processors (one feature = one processor)
         | 
| 19 | 
            +
              #   - Each processor may modify the AST
         | 
| 20 | 
            +
              #   - Generates a transpiled source code from the transformed AST (via the `unparser` gem)
         | 
| 21 | 
            +
              module Language
         | 
| 22 | 
            +
                require "ruby-next/language/parser"
         | 
| 23 | 
            +
                require "ruby-next/language/unparser"
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                class TransformContext
         | 
| 26 | 
            +
                  attr_reader :versions, :use_ruby_next
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def initialize
         | 
| 29 | 
            +
                    # Minimum supported RubyNext version
         | 
| 30 | 
            +
                    @min_version = MIN_SUPPORTED_VERSION
         | 
| 31 | 
            +
                    @dirty = false
         | 
| 32 | 
            +
                    @versions = Set.new
         | 
| 33 | 
            +
                    @use_ruby_next = false
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  # Called by rewriter when it performs transfomrations
         | 
| 37 | 
            +
                  def track!(rewriter)
         | 
| 38 | 
            +
                    @dirty = true
         | 
| 39 | 
            +
                    versions << rewriter.class::MIN_SUPPORTED_VERSION
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  def use_ruby_next!
         | 
| 43 | 
            +
                    @use_ruby_next = true
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  alias use_ruby_next? use_ruby_next
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  def dirty?
         | 
| 49 | 
            +
                    @dirty == true
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  def min_version
         | 
| 53 | 
            +
                    versions.min
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  def sorted_versions
         | 
| 57 | 
            +
                    versions.to_a.sort
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                class << self
         | 
| 62 | 
            +
                  attr_accessor :rewriters
         | 
| 63 | 
            +
                  attr_reader :watch_dirs
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  def transform(source, rewriters: self.rewriters, using: true, context: TransformContext.new)
         | 
| 66 | 
            +
                    parse(source).then do |ast|
         | 
| 67 | 
            +
                      rewriters.inject(ast) do |tree, rewriter|
         | 
| 68 | 
            +
                        rewriter.new(context).process(tree)
         | 
| 69 | 
            +
                      end.then do |new_ast|
         | 
| 70 | 
            +
                        next source unless context.dirty?
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                        Unparser.unparse(new_ast)
         | 
| 73 | 
            +
                      end.then do |source|
         | 
| 74 | 
            +
                        next source unless using && context.use_ruby_next?
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                        Core.inject! source.dup
         | 
| 77 | 
            +
                      end
         | 
| 78 | 
            +
                    end
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  def transformable?(path)
         | 
| 82 | 
            +
                    watch_dirs.any? { |dir| path.start_with?(dir) }
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  # Rewriters required for the current version
         | 
| 86 | 
            +
                  def current_rewriters
         | 
| 87 | 
            +
                    @current_rewriters ||= rewriters.select(&:unsupported_syntax?)
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  private
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                  attr_writer :watch_dirs
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                self.rewriters = []
         | 
| 96 | 
            +
                self.watch_dirs = %w[app lib spec test].map { |path| File.join(Dir.pwd, path) }
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                require "ruby-next/language/rewriters/base"
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                require "ruby-next/language/rewriters/endless_range"
         | 
| 101 | 
            +
                rewriters << Rewriters::EndlessRange
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                require "ruby-next/language/rewriters/pattern_matching"
         | 
| 104 | 
            +
                rewriters << Rewriters::PatternMatching
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                require "ruby-next/language/rewriters/args_forward"
         | 
| 107 | 
            +
                rewriters << Rewriters::ArgsForward
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                require "ruby-next/language/rewriters/numbered_params"
         | 
| 110 | 
            +
                rewriters << Rewriters::NumberedParams
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                if ENV["RUBY_NEXT_ENABLE_METHOD_REFERENCE"] == "1"
         | 
| 113 | 
            +
                  require "ruby-next/language/rewriters/method_reference"
         | 
| 114 | 
            +
                  RubyNext::Language.rewriters << RubyNext::Language::Rewriters::MethodReference
         | 
| 115 | 
            +
                end
         | 
| 116 | 
            +
              end
         | 
| 117 | 
            +
            end
         | 
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "ruby-next"
         | 
| 4 | 
            +
            require "ruby-next/utils"
         | 
| 5 | 
            +
            require "ruby-next/language"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            # Patch bootsnap to transform source code.
         | 
| 8 | 
            +
            # Based on https://github.com/kddeisz/preval/blob/master/lib/preval.rb
         | 
| 9 | 
            +
            load_iseq = RubyVM::InstructionSequence.method(:load_iseq)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            if load_iseq.source_location[0].include?("/bootsnap/")
         | 
| 12 | 
            +
              Bootsnap::CompileCache::ISeq.singleton_class.prepend(
         | 
| 13 | 
            +
                Module.new do
         | 
| 14 | 
            +
                  def input_to_storage(source, path)
         | 
| 15 | 
            +
                    return super unless RubyNext::Language.transformable?(path)
         | 
| 16 | 
            +
                    source = RubyNext::Language.transform(source, rewriters: RubyNext::Language.current_rewriters)
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    $stdout.puts ::RubyNext::Utils.source_with_lines(source, path) if ENV["RUBY_NEXT_DEBUG"] == "1"
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    RubyVM::InstructionSequence.compile(source, path, path).to_binary
         | 
| 21 | 
            +
                  rescue SyntaxError
         | 
| 22 | 
            +
                    raise Bootsnap::CompileCache::Uncompilable, "syntax error"
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              )
         | 
| 26 | 
            +
            end
         | 
| @@ -0,0 +1,64 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RubyNext
         | 
| 4 | 
            +
              module Language
         | 
| 5 | 
            +
                module KernelEval
         | 
| 6 | 
            +
                  refine Kernel do
         | 
| 7 | 
            +
                    def eval(source, bind = nil, *args)
         | 
| 8 | 
            +
                      new_source = ::RubyNext::Language::Runtime.transform(
         | 
| 9 | 
            +
                        source,
         | 
| 10 | 
            +
                        using: bind&.receiver == TOPLEVEL_BINDING.receiver || bind&.receiver&.is_a?(Module)
         | 
| 11 | 
            +
                      )
         | 
| 12 | 
            +
                      $stdout.puts ::RubyNext::Utils.source_with_lines(new_source, "(#{caller_locations(1, 1).first})") if ENV["RUBY_NEXT_DEBUG"] == "1"
         | 
| 13 | 
            +
                      super new_source, bind, *args
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                module InstanceEval # :nodoc:
         | 
| 19 | 
            +
                  refine Object do
         | 
| 20 | 
            +
                    def instance_eval(*args, &block)
         | 
| 21 | 
            +
                      return super(*args, &block) if block_given?
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                      source = args.shift
         | 
| 24 | 
            +
                      new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
         | 
| 25 | 
            +
                      $stdout.puts ::RubyNext::Utils.source_with_lines(new_source, "(#{caller_locations(1, 1).first})") if ENV["RUBY_NEXT_DEBUG"] == "1"
         | 
| 26 | 
            +
                      super new_source, *args
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                module ClassEval
         | 
| 32 | 
            +
                  refine Module do
         | 
| 33 | 
            +
                    def module_eval(*args, &block)
         | 
| 34 | 
            +
                      return super(*args, &block) if block_given?
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                      source = args.shift
         | 
| 37 | 
            +
                      new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
         | 
| 38 | 
            +
                      $stdout.puts ::RubyNext::Utils.source_with_lines(new_source, "(#{caller_locations(1, 1).first})") if ENV["RUBY_NEXT_DEBUG"] == "1"
         | 
| 39 | 
            +
                      super new_source, *args
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    def class_eval(*args, &block)
         | 
| 43 | 
            +
                      return super(*args, &block) if block_given?
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                      source = args.shift
         | 
| 46 | 
            +
                      new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
         | 
| 47 | 
            +
                      $stdout.puts ::RubyNext::Utils.source_with_lines(new_source, "(#{caller_locations(1, 1).first})") if ENV["RUBY_NEXT_DEBUG"] == "1"
         | 
| 48 | 
            +
                      super new_source, *args
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                # Refinements for `eval`-like methods.
         | 
| 54 | 
            +
                # Transpiling eval is only possible if we do not use local from the binding,
         | 
| 55 | 
            +
                # because we cannot access the binding of caller (without non-production ready hacks).
         | 
| 56 | 
            +
                #
         | 
| 57 | 
            +
                # This module is meant mainly for testing purposes.
         | 
| 58 | 
            +
                module Eval
         | 
| 59 | 
            +
                  include InstanceEval
         | 
| 60 | 
            +
                  include ClassEval
         | 
| 61 | 
            +
                  include KernelEval
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
            end
         | 
| @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "parser/ruby27"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module RubyNext
         | 
| 6 | 
            +
              module Language
         | 
| 7 | 
            +
                class Builder < ::Parser::Builders::Default
         | 
| 8 | 
            +
                  modernize
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                class << self
         | 
| 12 | 
            +
                  def parser
         | 
| 13 | 
            +
                    ::Parser::Ruby27.new(Builder.new)
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def parse(source, file = "(string)")
         | 
| 17 | 
            +
                    buffer = ::Parser::Source::Buffer.new(file).tap do |buffer|
         | 
| 18 | 
            +
                      buffer.source = source
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
                    parser.parse(buffer)
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
            end
         | 
| @@ -0,0 +1,57 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RubyNext
         | 
| 4 | 
            +
              module Language
         | 
| 5 | 
            +
                module Rewriters
         | 
| 6 | 
            +
                  class ArgsForward < Base
         | 
| 7 | 
            +
                    SYNTAX_PROBE = "obj = Object.new; def obj.foo(...) super(...); end"
         | 
| 8 | 
            +
                    MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                    REST = :__rest__
         | 
| 11 | 
            +
                    BLOCK = :__block__
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    def on_forward_args(node)
         | 
| 14 | 
            +
                      context.track! self
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                      node.updated(
         | 
| 17 | 
            +
                        :args,
         | 
| 18 | 
            +
                        [
         | 
| 19 | 
            +
                          s(:restarg, REST),
         | 
| 20 | 
            +
                          s(:blockarg, BLOCK)
         | 
| 21 | 
            +
                        ]
         | 
| 22 | 
            +
                      )
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    def on_send(node)
         | 
| 26 | 
            +
                      return unless node.children[2]&.type == :forwarded_args
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                      node.updated(
         | 
| 29 | 
            +
                        nil,
         | 
| 30 | 
            +
                        [
         | 
| 31 | 
            +
                          *node.children[0..1],
         | 
| 32 | 
            +
                          *forwarded_args
         | 
| 33 | 
            +
                        ]
         | 
| 34 | 
            +
                      )
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    def on_super(node)
         | 
| 38 | 
            +
                      return unless node.children[0]&.type == :forwarded_args
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                      node.updated(
         | 
| 41 | 
            +
                        nil,
         | 
| 42 | 
            +
                        forwarded_args
         | 
| 43 | 
            +
                      )
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    private
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    def forwarded_args
         | 
| 49 | 
            +
                      [
         | 
| 50 | 
            +
                        s(:splat, s(:lvar, REST)),
         | 
| 51 | 
            +
                        s(:block_pass, s(:lvar, BLOCK))
         | 
| 52 | 
            +
                      ]
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
            end
         | 
| @@ -0,0 +1,105 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            using RubyNext
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module RubyNext
         | 
| 6 | 
            +
              module Language
         | 
| 7 | 
            +
                module Rewriters
         | 
| 8 | 
            +
                  CUSTOM_PARSER_REQUIRED = <<~MSG
         | 
| 9 | 
            +
                    The %s feature is not a part of the latest stable Ruby release
         | 
| 10 | 
            +
                    and is not supported by your Parser gem version.
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                    Use RubyNext's parser to use it: https://github.com/ruby-next/parser
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  MSG
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  class Base < ::Parser::TreeRewriter
         | 
| 17 | 
            +
                    class LocalsTracker
         | 
| 18 | 
            +
                      attr_reader :stacks
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                      def initialize
         | 
| 21 | 
            +
                        @stacks = []
         | 
| 22 | 
            +
                      end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                      def with(**locals)
         | 
| 25 | 
            +
                        stacks << locals
         | 
| 26 | 
            +
                        yield.tap { stacks.pop }
         | 
| 27 | 
            +
                      end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                      def [](name, suffix = nil)
         | 
| 30 | 
            +
                        fetch(name).then do |name|
         | 
| 31 | 
            +
                          next name unless suffix
         | 
| 32 | 
            +
                          :"#{name}#{suffix}__"
         | 
| 33 | 
            +
                        end
         | 
| 34 | 
            +
                      end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                      def key?(name)
         | 
| 37 | 
            +
                        !!fetch(name) { false }
         | 
| 38 | 
            +
                      end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                      def fetch(name)
         | 
| 41 | 
            +
                        ind = -1
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                        loop do
         | 
| 44 | 
            +
                          break stacks[ind][name] if stacks[ind].key?(name)
         | 
| 45 | 
            +
                          ind -= 1
         | 
| 46 | 
            +
                          break if stacks[ind].nil?
         | 
| 47 | 
            +
                        end.then do |name|
         | 
| 48 | 
            +
                          next name unless name.nil?
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                          return yield if block_given?
         | 
| 51 | 
            +
                          raise ArgumentError, "Local var not found in scope: #{name}"
         | 
| 52 | 
            +
                        end
         | 
| 53 | 
            +
                      end
         | 
| 54 | 
            +
                    end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                    class << self
         | 
| 57 | 
            +
                      # Returns true if the syntax is supported
         | 
| 58 | 
            +
                      # by the current Ruby (performs syntax check, not version check)
         | 
| 59 | 
            +
                      def unsupported_syntax?
         | 
| 60 | 
            +
                        save_verbose, $VERBOSE = $VERBOSE, nil
         | 
| 61 | 
            +
                        eval_mid = Kernel.respond_to?(:eval_without_ruby_next) ? :eval_without_ruby_next : :eval
         | 
| 62 | 
            +
                        Kernel.send eval_mid, self::SYNTAX_PROBE
         | 
| 63 | 
            +
                        false
         | 
| 64 | 
            +
                      rescue SyntaxError, NameError
         | 
| 65 | 
            +
                        true
         | 
| 66 | 
            +
                      ensure
         | 
| 67 | 
            +
                        $VERBOSE = save_verbose
         | 
| 68 | 
            +
                      end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                      # Returns true if the syntax is supported
         | 
| 71 | 
            +
                      # by the specified version
         | 
| 72 | 
            +
                      def unsupported_version?(version)
         | 
| 73 | 
            +
                        self::MIN_SUPPORTED_VERSION > version
         | 
| 74 | 
            +
                      end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                      private
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                      def transform(source)
         | 
| 79 | 
            +
                        Language.transform(source, rewriters: [self], using: false)
         | 
| 80 | 
            +
                      end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                      def warn_custom_parser_required_for(feature)
         | 
| 83 | 
            +
                        warn(CUSTOM_PARSER_REQUIRED % feature)
         | 
| 84 | 
            +
                      end
         | 
| 85 | 
            +
                    end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                    attr_reader :locals
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                    def initialize(context)
         | 
| 90 | 
            +
                      @context = context
         | 
| 91 | 
            +
                      @locals = LocalsTracker.new
         | 
| 92 | 
            +
                      super()
         | 
| 93 | 
            +
                    end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                    def s(type, *children)
         | 
| 96 | 
            +
                      ::Parser::AST::Node.new(type, children)
         | 
| 97 | 
            +
                    end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                    private
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                    attr_reader :context
         | 
| 102 | 
            +
                  end
         | 
| 103 | 
            +
                end
         | 
| 104 | 
            +
              end
         | 
| 105 | 
            +
            end
         |