module Prism::Debug

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

Public Class Methods

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

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

  begin
    locals = []
    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

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

# File lib/prism/debug.rb, line 196
def self.newlines(source)
  Prism.parse(source).source.offsets
end

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

# File lib/prism/debug.rb, line 98
def self.prism_locals(source)
  locals = []
  stack = [Prism.parse(source).value]

  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),
        ]

        if params.keyword_rest.is_a?(ForwardingParameterNode)
          sorted.push(:*, :&, :"...")
        end

        sorted << AnonymousLocal if params.keywords.any?

        # 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
            param_stack.concat(param.lefts.reverse)
          when RequiredParameterNode
            sorted << param.name
          when SplatNode
            sorted << param.expression.name if param.expression
          end
        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