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