module RegularExpression::Compiler::Ruby

Public Class Methods

compile(cfg) click to toggle source

Generate Ruby code for a CFG. This looks just like the intepreter, but abstracted in time one level! rubocop:disable Layout/LineLength

# File lib/regular_expression/compiler/ruby.rb, line 21
def self.compile(cfg)
  ruby_src = []
  ruby_src.push "-> (string) {"
  ruby_src.push "  start_n = 0"
  ruby_src.push "  stack = []"
  ruby_src.push "  while start_n <= string.size"
  ruby_src.push "    string_n = start_n"
  ruby_src.push "    block = #{cfg.start.name.inspect}"
  ruby_src.push "    loop do"
  ruby_src.push "      case block"

  cfg.blocks.each do |block|
    ruby_src.push "      when #{block.name.inspect}"

    block.insns.each do |insn|
      case insn
      when Bytecode::Insns::PushIndex
        ruby_src.push "        stack << string_n"
      when Bytecode::Insns::PopIndex
        ruby_src.push "        string_n = stack.pop"
      when Bytecode::Insns::GuardBegin
        ruby_src.push "        return false if start_n != 0"
      when Bytecode::Insns::GuardEnd
        ruby_src.push "        if string_n == string.size"
        ruby_src.push "          block = #{cfg.exit_map[insn.guarded].name.inspect}"
        ruby_src.push "          next"
        ruby_src.push "        end"
      when Bytecode::Insns::JumpAny
        ruby_src.push "        if string_n < string.size"
        ruby_src.push "          string_n += 1"
        ruby_src.push "          block = #{cfg.exit_map[insn.target].name.inspect}"
        ruby_src.push "          next"
        ruby_src.push "        end"
      when Bytecode::Insns::JumpValue
        ruby_src.push "        if string_n < string.size && string[string_n] == #{insn.char.inspect}"
        ruby_src.push "          string_n += 1"
        ruby_src.push "          block = #{cfg.exit_map[insn.target].name.inspect}"
        ruby_src.push "          next"
        ruby_src.push "        end"
      when Bytecode::Insns::JumpValuesInvert
        ruby_src.push "        if string_n < string.size && !#{insn.chars.inspect}.include?(string[string_n])"
        ruby_src.push "          string_n += 1"
        ruby_src.push "          block = #{cfg.exit_map[insn.target].name.inspect}"
        ruby_src.push "          next"
        ruby_src.push "        end"
      when Bytecode::Insns::JumpRange
        ruby_src.push "        if string_n < string.size && string[string_n] >= #{insn.left.inspect} && string[string_n] <= #{insn.right.inspect}"
        ruby_src.push "          string_n += 1"
        ruby_src.push "          block = #{cfg.exit_map[insn.target].name.inspect}"
        ruby_src.push "          next"
        ruby_src.push "        end"
      when Bytecode::Insns::JumpRangeInvert
        ruby_src.push "        if string_n < string.size && (string[string_n] < #{insn.left.inspect} || string[string_n] > #{insn.right.inspect})"
        ruby_src.push "          string_n += 1"
        ruby_src.push "          block = #{cfg.exit_map[insn.target].name.inspect}"
        ruby_src.push "          next"
        ruby_src.push "        end"
      when Bytecode::Insns::Jump
        ruby_src.push "        block = #{cfg.exit_map[insn.target].name.inspect}"
        ruby_src.push "        next"
      when Bytecode::Insns::Match
        ruby_src.push "        return true"
      when Bytecode::Insns::Fail
        ruby_src.push "        start_n += 1"
        ruby_src.push "        break"
      else
        raise
      end
    end
  end

  ruby_src.push "      end"
  ruby_src.push "    end"
  ruby_src.push "  end"
  ruby_src.push "  false"
  ruby_src.push "}"
  ruby_src.push ""

  Compiled.new(ruby_src.join($/))
end