class SyntaxTree::YARV::VM

Constants

DLEXT

Methods for overriding runtime behavior

FROZEN_CORE
SOEXT

Attributes

events[R]
frame[R]
stack[R]

Public Class Methods

new(events = NullEvents.new) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 216
def initialize(events = NullEvents.new)
  @events = events
  @stack = Stack.new(events)
  @frame = nil
end
run(iseq) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 222
def self.run(iseq)
  new.run_top_frame(iseq)
end

Public Instance Methods

catch(tag, &block) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 623
def catch(tag, &block)
  Kernel.catch(tag, &block)
end
const_base() click to toggle source

Helper methods for instructions

# File lib/syntax_tree/yarv/vm.rb, line 494
def const_base
  frame.nesting.last
end
eval( source, binding = TOPLEVEL_BINDING, filename = "(eval)", lineno = 1 ) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 610
def eval(
  source,
  binding = TOPLEVEL_BINDING,
  filename = "(eval)",
  lineno = 1
)
  Kernel.yarv_eval(source, binding, filename, lineno)
end
find_catch_entry(frame, type) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 344
def find_catch_entry(frame, type)
  iseq = frame.iseq
  iseq.catch_table.find do |catch_entry|
    next unless catch_entry.is_a?(type)

    begin_pc = iseq.insns.index(catch_entry.begin_label)
    end_pc = iseq.insns.index(catch_entry.end_label)

    (begin_pc...end_pc).cover?(frame.pc)
  end
end
frame_at(level) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 498
def frame_at(level)
  current = frame
  level.times { current = current.parent }
  current
end
frame_svar() click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 504
def frame_svar
  current = frame
  current = current.parent while current.is_a?(BlockFrame)
  current
end
frame_yield() click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 510
def frame_yield
  current = frame
  current = current.parent until current.is_a?(MethodFrame)
  current
end
frozen_core() click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 516
def frozen_core
  FROZEN_CORE
end
jump(label) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 520
def jump(label)
  Jump.new(label)
end
leave() click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 524
def leave
  Leave.new(pop)
end
load(filepath) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 606
def load(filepath)
  require_internal(filepath, loading: true)
end
local_get(index, level) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 528
def local_get(index, level)
  stack[frame_at(level).stack_index + index]
end
local_set(index, level, value) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 532
def local_set(index, level, value)
  stack[frame_at(level).stack_index + index] = value
end
require(filepath) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 598
def require(filepath)
  require_internal(filepath, loading: false)
end
require_internal(filepath, loading: false) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 549
def require_internal(filepath, loading: false)
  case (extname = File.extname(filepath))
  when ""
    # search for all the extensions
    searching = filepath
    extensions = ["", ".rb", DLEXT, SOEXT]
  when ".rb", DLEXT, SOEXT
    # search only for the given extension name
    searching = File.basename(filepath, extname)
    extensions = [extname]
  else
    # we don't handle these extensions, raise a load error
    raise LoadError, "cannot load such file -- #{filepath}"
  end

  if filepath.start_with?("/")
    # absolute path, search only in the given directory
    directories = [File.dirname(searching)]
    searching = File.basename(searching)
  else
    # relative path, search in the load path
    directories = $LOAD_PATH
  end

  directories.each do |directory|
    extensions.each do |extension|
      absolute_path = File.join(directory, "#{searching}#{extension}")
      next unless File.exist?(absolute_path)

      if !loading && $LOADED_FEATURES.include?(absolute_path)
        return false
      elsif extension == ".rb"
        require_resolved(absolute_path)
        return true
      elsif loading
        return Kernel.send(:yarv_load, filepath)
      else
        return Kernel.send(:yarv_require, filepath)
      end
    end
  end

  if loading
    Kernel.send(:yarv_load, filepath)
  else
    Kernel.send(:yarv_require, filepath)
  end
end
require_relative(filepath) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 602
def require_relative(filepath)
  Kernel.yarv_require_relative(filepath)
end
require_resolved(filepath) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 543
def require_resolved(filepath)
  $LOADED_FEATURES << filepath
  iseq = RubyVM::InstructionSequence.compile_file(filepath)
  run_top_frame(InstructionSequence.from(iseq.to_a))
end
run_block_frame(iseq, frame, *args, **kwargs, &block) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 360
def run_block_frame(iseq, frame, *args, **kwargs, &block)
  run_frame(BlockFrame.new(iseq, frame, stack.length)) do
    setup_arguments(iseq, args, kwargs, block)
  end
end
run_class_frame(iseq, clazz) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 366
def run_class_frame(iseq, clazz)
  run_frame(ClassFrame.new(iseq, frame, stack.length, clazz))
end
run_frame(frame) { || ... } click to toggle source

Helper methods for frames

# File lib/syntax_tree/yarv/vm.rb, line 230
def run_frame(frame)
  # First, set the current frame to the given value.
  previous = @frame
  @frame = frame
  events.publish_frame_change(@frame)

  # Next, set up the local table for the frame. This is actually incorrect
  # as it could use the values already on the stack, but for now we're
  # just doing this for simplicity.
  stack.concat(Array.new(frame.iseq.local_table.size))

  # Yield so that some frame-specific setup can be done.
  start_label = yield if block_given?
  frame.pc = frame.iseq.insns.index(start_label) if start_label

  # Finally we can execute the instructions one at a time. If they return
  # jumps or leaves we will handle those appropriately.
  loop do
    case (insn = frame.iseq.insns[frame.pc])
    when Integer
      frame.line = insn
      frame.pc += 1
    when Symbol
      events.publish_tracepoint(insn)
      frame.pc += 1
    when InstructionSequence::Label
      # skip labels
      frame.pc += 1
    else
      begin
        events.publish_instruction(frame.iseq, insn)
        result = insn.call(self)
      rescue ReturnError => error
        raise if frame.iseq.type != :method

        stack.slice!(frame.stack_index..)
        @frame = frame.parent
        events.publish_frame_change(@frame)

        return error.value
      rescue BreakError => error
        raise if frame.iseq.type != :block

        catch_entry =
          find_catch_entry(frame, InstructionSequence::CatchBreak)
        raise unless catch_entry

        stack.slice!(
          (
            frame.stack_index + frame.iseq.local_table.size +
              catch_entry.restore_sp
          )..
        )
        @frame = frame
        events.publish_frame_change(@frame)

        frame.pc = frame.iseq.insns.index(catch_entry.exit_label)
        push(result = error.value)
      rescue NextError => error
        raise if frame.iseq.type != :block

        catch_entry =
          find_catch_entry(frame, InstructionSequence::CatchNext)
        raise unless catch_entry

        stack.slice!(
          (
            frame.stack_index + frame.iseq.local_table.size +
              catch_entry.restore_sp
          )..
        )
        @frame = frame
        events.publish_frame_change(@frame)

        frame.pc = frame.iseq.insns.index(catch_entry.exit_label)
        push(result = error.value)
      rescue Exception => error
        catch_entry =
          find_catch_entry(frame, InstructionSequence::CatchRescue)
        raise unless catch_entry

        stack.slice!(
          (
            frame.stack_index + frame.iseq.local_table.size +
              catch_entry.restore_sp
          )..
        )
        @frame = frame
        events.publish_frame_change(@frame)

        frame.pc = frame.iseq.insns.index(catch_entry.exit_label)
        push(result = run_rescue_frame(catch_entry.iseq, frame, error))
      end

      case result
      when Jump
        frame.pc = frame.iseq.insns.index(result.label) + 1
      when Leave
        # this shouldn't be necessary, but is because we're not handling
        # the stack correctly at the moment
        stack.slice!(frame.stack_index..)

        # restore the previous frame
        @frame = previous || frame.parent
        events.publish_frame_change(@frame) if @frame

        return result.value
      else
        frame.pc += 1
      end
    end
  end
end
run_method_frame(name, nesting, iseq, _self, *args, **kwargs, &block) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 370
def run_method_frame(name, nesting, iseq, _self, *args, **kwargs, &block)
  run_frame(
    MethodFrame.new(
      iseq,
      nesting,
      frame,
      stack.length,
      _self,
      name,
      block
    )
  ) { setup_arguments(iseq, args, kwargs, block) }
end
run_rescue_frame(iseq, frame, error) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 384
def run_rescue_frame(iseq, frame, error)
  run_frame(RescueFrame.new(iseq, frame, stack.length)) do
    local_set(0, 0, error)
    nil
  end
end
run_top_frame(iseq) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 356
def run_top_frame(iseq)
  run_frame(TopFrame.new(iseq))
end
setup_arguments(iseq, args, kwargs, block) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 391
def setup_arguments(iseq, args, kwargs, block)
  locals = [*args]
  local_index = 0
  start_label = nil

  # First, set up all of the leading arguments. These are positional and
  # required arguments at the start of the argument list.
  if (lead_num = iseq.argument_options[:lead_num])
    lead_num.times do
      local_set(local_index, 0, locals.shift)
      local_index += 1
    end
  end

  # Next, set up all of the optional arguments. The opt array contains
  # the labels that the frame should start at if the optional is
  # present. The last element of the array is the label that the frame
  # should start at if all of the optional arguments are present.
  if (opt = iseq.argument_options[:opt])
    opt[0...-1].each do |label|
      if locals.empty?
        start_label = label
        break
      else
        local_set(local_index, 0, locals.shift)
        local_index += 1
      end

      start_label = opt.last if start_label.nil?
    end
  end

  # If there is a splat argument, then we'll set that up here. It will
  # grab up all of the remaining positional arguments.
  if (rest_start = iseq.argument_options[:rest_start])
    if (post_start = iseq.argument_options[:post_start])
      length = post_start - rest_start
      local_set(local_index, 0, locals[0...length])
      locals = locals[length..]
    else
      local_set(local_index, 0, locals.dup)
      locals.clear
    end
    local_index += 1
  end

  # Next, set up any post arguments. These are positional arguments that
  # come after the splat argument.
  if (post_num = iseq.argument_options[:post_num])
    post_num.times do
      local_set(local_index, 0, locals.shift)
      local_index += 1
    end
  end

  if (keyword_option = iseq.argument_options[:keyword])
    # First, set up the keyword bits array.
    keyword_bits =
      keyword_option.map do |config|
        kwargs.key?(config.is_a?(Array) ? config[0] : config)
      end

    iseq.local_table.locals.each_with_index do |local, index|
      # If this is the keyword bits local, then set it appropriately.
      if local.name.is_a?(Integer)
        local_set(index, 0, keyword_bits)
        next
      end

      # First, find the configuration for this local in the keywords
      # list if it exists.
      name = local.name
      config =
        keyword_option.find do |keyword|
          keyword.is_a?(Array) ? keyword[0] == name : keyword == name
        end

      # If the configuration doesn't exist, then the local is not a
      # keyword local.
      next unless config

      if !config.is_a?(Array)
        # required keyword
        local_set(index, 0, kwargs.fetch(name))
      elsif !config[1].nil?
        # optional keyword with embedded default value
        local_set(index, 0, kwargs.fetch(name, config[1]))
      else
        # optional keyword with expression default value
        local_set(index, 0, kwargs[name])
      end
    end
  end

  local_set(local_index, 0, block) if iseq.argument_options[:block_start]

  start_label
end
throw(tag, value = nil) click to toggle source
# File lib/syntax_tree/yarv/vm.rb, line 619
def throw(tag, value = nil)
  Kernel.throw(tag, value)
end