module Prism::Debug

This module is used for testing and debugging and is not meant to be used by consumers of this library.

Constants

AnonymousLocal

Used to hold the place of a local that will be in the local table but cannot be accessed directly from the source code. For example, the iteration variable in a for loop or the positional parameter on a method definition that is destructured.

Public Class Methods

Debug::cruby_locals(source) → Array click to toggle source

For the given source, compiles with CRuby and returns a list of all of the sets of local variables that were encountered.

# File prism/debug.rb, line 54
def self.cruby_locals(source)
  verbose, $VERBOSE = $VERBOSE, nil

  begin
    locals = [] #: Array[Array[Symbol | Integer]]
    stack = [ISeq.new(RubyVM::InstructionSequence.compile(source).to_a)]

    while (iseq = stack.pop)
      names = [*iseq.local_table]
      names.map!.with_index do |name, index|
        # When an anonymous local variable is present in the iseq's local
        # table, it is represented as the stack offset from the top.
        # However, when these are dumped to binary and read back in, they
        # are replaced with the symbol :#arg_rest. To consistently handle
        # this, we replace them here with their index.
        if name == :"#arg_rest"
          names.length - index + 1
        else
          name
        end
      end

      locals << names
      iseq.each_child { |child| stack << child }
    end

    locals
  ensure
    $VERBOSE = verbose
  end
end
Debug::newlines(source) → Array click to toggle source

For the given source string, return the byte offsets of every newline in the source.

# File prism/debug.rb, line 202
def self.newlines(source)
  Prism.parse(source).source.offsets
end
Debug::prism_locals(source) → Array click to toggle source

For the given source, parses with prism and returns a list of all of the sets of local variables that were encountered.

# File prism/debug.rb, line 98
def self.prism_locals(source)
  locals = [] #: Array[Array[Symbol | Integer]]
  stack = [Prism.parse(source).value] #: Array[Prism::node]

  while (node = stack.pop)
    case node
    when BlockNode, DefNode, LambdaNode
      names = node.locals
      params =
        if node.is_a?(DefNode)
          node.parameters
        elsif node.parameters.is_a?(NumberedParametersNode)
          nil
        else
          node.parameters&.parameters
        end

      # prism places parameters in the same order that they appear in the
      # source. CRuby places them in the order that they need to appear
      # according to their own internal calling convention. We mimic that
      # order here so that we can compare properly.
      if params
        sorted = [
          *params.requireds.map do |required|
            if required.is_a?(RequiredParameterNode)
              required.name
            else
              AnonymousLocal
            end
          end,
          *params.optionals.map(&:name),
          *((params.rest.name || :*) if params.rest && !params.rest.is_a?(ImplicitRestNode)),
          *params.posts.map do |post|
            if post.is_a?(RequiredParameterNode)
              post.name
            else
              AnonymousLocal
            end
          end,
          *params.keywords.grep(RequiredKeywordParameterNode).map(&:name),
          *params.keywords.grep(OptionalKeywordParameterNode).map(&:name),
        ]

        sorted << AnonymousLocal if params.keywords.any?

        if params.keyword_rest.is_a?(ForwardingParameterNode)
          sorted.push(:*, :**, :&, :"...")
        elsif params.keyword_rest.is_a?(KeywordRestParameterNode)
          sorted << (params.keyword_rest.name || :**)
        end

        # Recurse down the parameter tree to find any destructured
        # parameters and add them after the other parameters.
        param_stack = params.requireds.concat(params.posts).grep(MultiTargetNode).reverse
        while (param = param_stack.pop)
          case param
          when MultiTargetNode
            param_stack.concat(param.rights.reverse)
            param_stack << param.rest if param.rest&.expression && !sorted.include?(param.rest.expression.name)
            param_stack.concat(param.lefts.reverse)
          when RequiredParameterNode
            sorted << param.name
          when SplatNode
            sorted << param.expression.name
          end
        end

        if params.block
          sorted << (params.block.name || :&)
        end

        names = sorted.concat(names - sorted)
      end

      names.map!.with_index do |name, index|
        if name == AnonymousLocal
          names.length - index + 1
        else
          name
        end
      end

      locals << names
    when ClassNode, ModuleNode, ProgramNode, SingletonClassNode
      locals << node.locals
    when ForNode
      locals << [2]
    when PostExecutionNode
      locals.push([], [])
    when InterpolatedRegularExpressionNode
      locals << [] if node.once?
    end

    stack.concat(node.compact_child_nodes)
  end

  locals
end