module YARP::Debug
def self.yarp_locals(source)
For the given source, parses with YARP and returns a list of all of the
def self.yarp_locals(source) locals = [] stack = [YARP.parse(source).value] while (node = stack.pop) case node when BlockNode, DefNode, LambdaNode names = node.locals params = node.parameters params = params&.parameters unless node.is_a?(DefNode) # YARP 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.grep(RequiredParameterNode).map(&:constant_id), *params.optionals.map(&:constant_id), *((params.rest.name ? params.rest.name.to_sym : :*) if params.rest && params.rest.operator != ","), *params.posts.grep(RequiredParameterNode).map(&:constant_id), *params.keywords.reject(&:value).map { |param| param.name.chomp(":").to_sym }, *params.keywords.select(&:value).map { |param| param.name.chomp(":").to_sym } ] # TODO: When we get a ... parameter, we should be pushing * and & # onto the local list. We don't do that yet, so we need to add them # in here. if params.keyword_rest.is_a?(ForwardingParameterNode) sorted.push(:*, :&, :"...") 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(RequiredDestructuredParameterNode).reverse while (param = param_stack.pop) case param when RequiredDestructuredParameterNode param_stack.concat(param.parameters.reverse) when RequiredParameterNode sorted << param.constant_id when SplatNode sorted << param.expression.constant_id if param.expression end end names = sorted.concat(names - sorted) end locals << names when ClassNode, ModuleNode, ProgramNode, SingletonClassNode locals << node.locals when ForNode locals << [] when PostExecutionNode locals.push([], []) end stack.concat(node.child_nodes.compact) end locals end