# frozen_string_literal: truerequire"ripper"modulePrism# Note: This integration is not finished, and therefore still has many# inconsistencies with Ripper. If you'd like to help out, pull requests would# be greatly appreciated!## This class is meant to provide a compatibility layer between prism and# Ripper. It functions by parsing the entire tree first and then walking it# and executing each of the Ripper callbacks as it goes.## This class is going to necessarily be slower than the native Ripper API. It# is meant as a stopgap until developers migrate to using prism. It is also# meant as a test harness for the prism parser.## To use this class, you treat `Prism::RipperCompat` effectively as you would# treat the `Ripper` class.classRipperCompat<Visitor# This class mirrors the ::Ripper::SexpBuilder subclass of ::Ripper that# returns the arrays of [type, *children].classSexpBuilder<RipperCompatprivateRipper::PARSER_EVENTS.eachdo|event|define_method(:"on_#{event}")do|*args|[event,*args]endendRipper::SCANNER_EVENTS.eachdo|event|define_method(:"on_#{event}")do|value|[:"@#{event}",value,[lineno,column]]endendend# This class mirrors the ::Ripper::SexpBuilderPP subclass of ::Ripper that# returns the same values as ::Ripper::SexpBuilder except with a couple of# niceties that flatten linked lists into arrays.classSexpBuilderPP<SexpBuilderprivatedef_dispatch_event_new# :nodoc:[]enddef_dispatch_event_push(list,item)# :nodoc:list<<itemlistendRipper::PARSER_EVENT_TABLE.eachdo|event,arity|caseeventwhen/_new\z/alias_method:"on_#{event}",:_dispatch_event_newifarity==0when/_add\z/alias_method:"on_#{event}",:_dispatch_event_pushendendend# The source that is being parsed.attr_reader:source# The current line number of the parser.attr_reader:lineno# The current column number of the parser.attr_reader:column# Create a new RipperCompat object with the given source.definitialize(source)@source=source@result=nil@lineno=nil@column=nilend############################################################################# Public interface############################################################################# True if the parser encountered an error during parsing.deferror?result.failure?end# Parse the source and return the result.defparseresult.magic_comments.eachdo|magic_comment|on_magic_comment(magic_comment.key,magic_comment.value)endiferror?result.errors.eachdo|error|on_parse_error(error.message)endnilelseresult.value.accept(self)endend############################################################################# Visitor methods############################################################################# Visit an ArrayNode node.defvisit_array_node(node)elements=visit_elements(node.elements)unlessnode.elements.empty?bounds(node.location)on_array(elements)end# Visit a CallNode node.defvisit_call_node(node)ifnode.variable_call?ifnode.message.match?(/^[[:alpha:]_]/)bounds(node.message_loc)returnon_vcall(on_ident(node.message))endraiseNotImplementedError,"Non-alpha variable call"endifnode.opening_loc.nil?left=visit(node.receiver)ifnode.arguments&.arguments&.length==1right=visit(node.arguments.arguments.first)on_binary(left,node.name,right)elsif!node.arguments||node.arguments.empty?on_unary(node.name,left)elseraiseNotImplementedError,"More than two arguments for operator"endelseraiseNotImplementedError,"Non-nil opening_loc"endend# Visit a FloatNode node.defvisit_float_node(node)visit_number(node){|text|on_float(text)}end# Visit a ImaginaryNode node.defvisit_imaginary_node(node)visit_number(node){|text|on_imaginary(text)}end# Visit an IntegerNode node.defvisit_integer_node(node)visit_number(node){|text|on_int(text)}end# Visit a ParenthesesNode node.defvisit_parentheses_node(node)body=ifnode.body.nil?on_stmts_add(on_stmts_new,on_void_stmt)elsevisit(node.body)endbounds(node.location)on_paren(body)end# Visit a ProgramNode node.defvisit_program_node(node)statements=visit(node.statements)bounds(node.location)on_program(statements)end# Visit a RangeNode node.defvisit_range_node(node)left=visit(node.left)right=visit(node.right)bounds(node.location)ifnode.exclude_end?on_dot3(left,right)elseon_dot2(left,right)endend# Visit a RationalNode node.defvisit_rational_node(node)visit_number(node){|text|on_rational(text)}end# Visit a StatementsNode node.defvisit_statements_node(node)bounds(node.location)node.body.inject(on_stmts_new)do|stmts,stmt|on_stmts_add(stmts,visit(stmt))endend############################################################################# Entrypoints for subclasses############################################################################# This is a convenience method that runs the SexpBuilder subclass parser.defself.sexp_raw(source)SexpBuilder.new(source).parseend# This is a convenience method that runs the SexpBuilderPP subclass parser.defself.sexp(source)SexpBuilderPP.new(source).parseendprivate# Visit a list of elements, like the elements of an array or arguments.defvisit_elements(elements)bounds(elements.first.location)elements.inject(on_args_new)do|args,element|on_args_add(args,visit(element))endend# Visit a node that represents a number. We need to explicitly handle the# unary - operator.defvisit_number(node)slice=node.slicelocation=node.locationifslice[0]=="-"bounds_values(location.start_line,location.start_column+1)value=yieldslice[1..-1]bounds(node.location)on_unary(RUBY_ENGINE=="jruby"?:-::-@,value)elsebounds(location)yieldsliceendend# This method is responsible for updating lineno and column information# to reflect the current node.## This method could be drastically improved with some caching on the start# of every line, but for now it's good enough.defbounds(location)@lineno=location.start_line@column=location.start_columnend# If we need to do something unusual, we can directly update the line number# and column to reflect the current node.defbounds_values(lineno,column)@lineno=lineno@column=columnend# Lazily initialize the parse result.defresult@result||=Prism.parse(source)enddef_dispatch0;end# :nodoc:def_dispatch1(_);end# :nodoc:def_dispatch2(_,_);end# :nodoc:def_dispatch3(_,_,_);end# :nodoc:def_dispatch4(_,_,_,_);end# :nodoc:def_dispatch5(_,_,_,_,_);end# :nodoc:def_dispatch7(_,_,_,_,_,_,_);end# :nodoc:alias_method:on_parse_error,:_dispatch1alias_method:on_magic_comment,:_dispatch2(Ripper::SCANNER_EVENT_TABLE.merge(Ripper::PARSER_EVENT_TABLE)).eachdo|event,arity|alias_method:"on_#{event}",:"_dispatch#{arity}"endendend