lib/rufo/parser.rb



# frozen_string_literal: true

require "ripper"

class Rufo::Parser < Ripper
  def compile_error(msg)
    raise ::Rufo::SyntaxError.new(msg, lineno)
  end

  def on_parse_error(msg)
    raise ::Rufo::SyntaxError.new(msg, lineno)
  end

  def self.sexp_unparsable_code(code)
    code_type = detect_unparsable_code_type(code)

    case code_type
    when :yield
      extract_original_code_sexp(
        "def __rufo_dummy; #{code}; end",
        ->(exp) { exp => [:def, *, [:bodystmt, exps, *]]; exps }
      )
    when :next, :break, :redo
      extract_original_code_sexp(
        "loop do; #{code}; end",
        ->(exp) { exp => [:method_add_block, *, [:do_block, nil, [:bodystmt, [[:void_stmt], *exps], *]]]; exps }
      )
    when :retry
      extract_original_code_sexp(
        "begin; rescue; #{code}; end",
        ->(exp) { exp => [:begin, [:bodystmt, Array, [:rescue, nil, nil, exps, *], *]]; exps }
      )
    end
  end

  def self.detect_unparsable_code_type(code)
    tokens = self.lex(code)
    token = tokens.find { |_, kind| kind != :on_sp && kind != :on_ignored_nl }

    case token
    in [_, :on_kw, "yield" | "next" | "break" | "retry" | "redo" => kw, _]
      kw.to_sym
    else
      nil
    end
  end

  def self.extract_original_code_sexp(decorated_code, extractor)
    sexp = self.sexp(decorated_code)
    return nil unless sexp

    # [:program, [exp]]
    exp = sexp[1][0]
    code_exps = extractor.call(exp)
    [:program, code_exps]
  end
end