module Prism::Debug

def self.prism_locals(source)

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

Debug::prism_locals(source) -> Array
:call-seq:
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),
        ]
        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