Class: Rjq::VM

Inherits:
Object
  • Object
show all
Defined in:
lib/rjq/vm.rb

Defined Under Namespace

Classes: InstructionBudget, RecordingInputQueue

Instance Method Summary collapse

Constructor Details

#initialize(program, opts = {}, instruction_budget: nil) ⇒ VM

Returns a new instance of VM.



54
55
56
57
58
# File 'lib/rjq/vm.rb', line 54

def initialize(program, opts = {}, instruction_budget: nil)
  @program = program
  @opts = opts
  @instruction_budget = instruction_budget || InstructionBudget.new(opts[:max_instructions])
end

Instance Method Details

#execute_block(instructions, input, context) ⇒ Object



84
85
86
87
88
89
90
91
92
# File 'lib/rjq/vm.rb', line 84

def execute_block(instructions, input, context)
  output = []
  each_block(instructions, input, context) { |value| output << value }
  output
rescue Rjq::RuntimeError => e
  raise e.prepend_outputs(output)
rescue BreakSignal => e
  raise BreakSignal.new(e.label, e.value, outputs: output + Array(e.outputs))
end

#paths_for_block(instructions, input, context) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/rjq/vm.rb', line 100

def paths_for_block(instructions, input, context)
  stack = []
  instructions.each_with_index do |instruction, index|
    execute_path_instruction(instruction, stack, input, context)
  rescue Rjq::RuntimeError => e
    remaining = instructions[(index + 1)..].to_a
    raise if remaining.empty?

    if e.is_a?(InvalidPathError)
      raise InvalidPathError.new(invalid_path_message(remaining, e.result, input, context), e.result,
                                 outputs: e.outputs)
    end
    raise
  end
  stack.pop || []
end

#replacement_filters(block, captured_context) ⇒ Object



125
126
127
128
129
130
131
132
133
# File 'lib/rjq/vm.rb', line 125

def replacement_filters(block, captured_context)
  if block.instructions.last&.op == :append
    left = BytecodeBlock.new(instructions: block.instructions[0...-1])
    right = block.instructions.last.arg1
    return replacement_filters(left, captured_context) + replacement_filters(right, captured_context)
  end

  [BytecodeFilter.new(self, block, captured_context: captured_context)]
end

#run(input_value) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/rjq/vm.rb', line 60

def run(input_value)
  Enumerator.new do |yielder|
    @call_depth = 0
    Value.validate!(input_value)
    @opts.fetch(:variables, {}).each_value { |value| Value.validate!(value) }
    context = context_with_definitions(base_context)
    output_count = 0
    begin
      each_block(@program.program.instructions, input_value, context).each do |value|
        output_count += 1
        max_outputs = @opts[:max_outputs]
        raise RuntimeError, "output limit exceeded (#{max_outputs})" if max_outputs && output_count > max_outputs

        yielder << value
      end
    rescue ErrorValue => e
      Array(e.outputs).each { |value| yielder << value }
      raise
    rescue SystemStackError
      raise ResourceLimitError, 'execution recursion exceeded the host stack limit'
    end
  end
end

#source_all?(instructions, input, context) ⇒ Boolean

Returns:

  • (Boolean)


121
122
123
# File 'lib/rjq/vm.rb', line 121

def source_all?(instructions, input, context, &)
  each_block(instructions, input, context).all?(&)
end

#source_any?(instructions, input, context) ⇒ Boolean

Returns:

  • (Boolean)


117
118
119
# File 'lib/rjq/vm.rb', line 117

def source_any?(instructions, input, context, &)
  each_block(instructions, input, context).any?(&)
end

#stream_block(block, input, context) ⇒ Object



135
136
137
# File 'lib/rjq/vm.rb', line 135

def stream_block(block, input, context)
  each_block(block.instructions, input, context)
end

#take_block(instructions, input, context, count) ⇒ Object



94
95
96
97
98
# File 'lib/rjq/vm.rb', line 94

def take_block(instructions, input, context, count)
  return [] if count <= 0

  each_block(instructions, input, context).take(count)
end