Class: Trith::Machine

Inherits:
Object
  • Object
show all
Defined in:
lib/trith/machine.rb

Overview

The Trith virtual machine.

Defined Under Namespace

Classes: InvalidOperandError, InvalidOperatorError, StackUnderflowError

Instance Attribute Summary (collapse)

Class Method Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (Machine) initialize(stack = [], queue = [], env = {}) {|machine| ... }

Initializes a new virtual machine with the given initial stack, queue and environment.

Parameters:

  • (Array) stack (defaults to: [])
  • (Array) queue (defaults to: [])
  • (Hash) env (defaults to: {})

Yields:

  • (machine)

Yield Parameters:



44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/trith/machine.rb', line 44

def initialize(stack = [], queue = [], env = {}, &block)
  @stack, @queue, @env = stack, queue, {}
  import!(Trith::Core)
  env.each do |name, operator|
    if operator.is_a?(Proc)
      define!(name, &operator)
    else
      define!(name, operator)
    end
  end
  execute(&block) if block_given?
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

- (Object) method_missing(operator, *operands, &block) (protected)



278
279
280
281
282
283
284
# File 'lib/trith/machine.rb', line 278

def method_missing(operator, *operands, &block)
  begin
    super
  rescue NoMethodError => e
    raise InvalidOperatorError.new(operator)
  end
end

Instance Attribute Details

- (Proc) execute_hook

Returns:

  • (Proc)


12
13
14
# File 'lib/trith/machine.rb', line 12

def execute_hook
  @execute_hook
end

- (Array) queue (readonly)

Returns:

  • (Array)


9
10
11
# File 'lib/trith/machine.rb', line 9

def queue
  @queue
end

- (Array) stack (readonly)

Returns:

  • (Array)


6
7
8
# File 'lib/trith/machine.rb', line 6

def stack
  @stack
end

Class Method Details

+ (Object) self.run(code) + (Object) self.run(code) {|machine| ... }

Executes code in a new virtual machine instance.

The machine will halt when the queue is exhausted, when encountering a halt operator, or when encountering an error condition.

Overloads:

  • + (Object) self.run(code)

    Parameters:

    • (Array) code

    Returns:

    • (Object)
  • + (Object) self.run(code) {|machine| ... }

    Parameters:

    • (Array) code

    Yields:

    • (machine)

    Yield Parameters:

    Returns:

    • (Object)

Returns:

  • (Object)


31
32
33
# File 'lib/trith/machine.rb', line 31

def self.run(code = [], &block)
  self.new([], code).execute(&block).peek
end

Instance Method Details

- (Array) capture_continuation(full = false) (protected)

Captures the current contents of the code queue, returning them to the caller after first clearing the queue.

Returns:

  • (Array)


347
348
349
350
351
# File 'lib/trith/machine.rb', line 347

def capture_continuation(full = false)
  continuation = @queue.dup
  @queue.clear
  continuation
end

- (Machine) define!(name, code = [], options = {}, &block)

Defines a new operator name that will execute the given code.

Parameters:

  • (Symbol, #to_sym) name
  • (Array) code (defaults to: [])
  • (Hash{Symbol => Object}) options (defaults to: {})

Returns:



87
88
89
90
91
92
93
94
# File 'lib/trith/machine.rb', line 87

def define!(name, code = [], options = {}, &block)
  @env[name = name.to_sym] = lambda { execute(code, &block) }
  unless options[:method] == false
    this = class << self; self; end
    this.send(:define_method, name, &@env[name])
  end
  self
end

- (Machine) execute(code) - (Machine) execute(code) {|machine| ... }

Executes the virtual machine until it halts.

The machine will halt when the queue is exhausted, when encountering a halt operator, or when encountering an error condition.

Overloads:

  • - (Machine) execute(code)

    Parameters:

    • (Array) code

    Returns:

  • - (Machine) execute(code) {|machine| ... }

    Parameters:

    • (Array) code

    Yields:

    • (machine)

    Yield Parameters:

    Returns:

Returns:



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/trith/machine.rb', line 213

def execute(code = [], &block)
  @queue.unshift(*code) unless code.empty?

  catch(:halt) do # thrown in `#shift`
    Kernel.loop do
      execute_hook.call if execute_hook && !@queue.empty?

      op = shift
      case
        when operator?(op)
          if fn = @env[op]
            fn.call
          else
            unshift(op)
            raise InvalidOperatorError.new(op)
          end
        when operand?(op)
          push(op)
        else
          raise InvalidOperandError.new(op)
      end
    end
  end

  if block_given?
    case block.arity
      when 1 then block.call(self)
      else instance_eval(&block)
    end
  end

  return self
end

- (Machine) import!(mod)

Imports operators from the given module into the virtual machine's current environment.

Parameters:

  • (Module) module

Returns:



71
72
73
74
75
76
77
78
# File 'lib/trith/machine.rb', line 71

def import!(mod)
  case mod
    when Module   then import_module!(mod)
    when Function then # TODO
    else # TODO: error
  end
  self
end

- import_module!(mod) (protected)

This method returns an undefined value.

Imports operators from the given module into the virtual machine's current environment.

Parameters:

  • (Module) module


292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/trith/machine.rb', line 292

def import_module!(mod)
  # Include all instance methods from the module into `self`:
  this = class << self; self; end
  this.send(:include, mod)

  # Create wrapper methods to support operators that need their
  # arguments and result marshalled from/to the virtual machine stack:
  mod.public_instance_methods(true).map(&:to_sym).each do |method|
    op = mod.instance_method(method).bind(self)
    case method
      when :stack_ # FIXME
        @env[:stack] = op
      else
        @env[method] = this.send(:define_method, method) do |*args|
          result = op.call(*(!args.empty? ? args : (op.arity > 0 ? pop(op.arity) : [])))
          push(result) unless result.equal?(self)
          return self
        end
    end
  end
end

- (String) inspect

Returns a developer-friendly representation of this machine.

Returns:

  • (String)


61
62
63
# File 'lib/trith/machine.rb', line 61

def inspect
  sprintf("#<%s:%#0x(%s : %s)>", self.class.name, __id__, @stack.inspect, @queue.inspect)
end

- (Boolean) operand?(op) (protected)

Returns true if op is an operand.

Parameters:

  • (Object) op

Returns:

  • (Boolean)


263
264
265
# File 'lib/trith/machine.rb', line 263

def operand?(op)
  !operator?(op)
end

- (Boolean) operator?(op) (protected)

Returns true if op is an operator.

Parameters:

  • (Object) op

Returns:

  • (Boolean)


254
255
256
# File 'lib/trith/machine.rb', line 254

def operator?(op)
  op.is_a?(Symbol)
end

- (Object) peek

Returns the operand at the top of the stack.

If the stack is empty, returns nil.

Returns:

  • (Object)


110
111
112
# File 'lib/trith/machine.rb', line 110

def peek
  @stack.last
end

- (Object) pop - (Array) pop(n)

Pops and returns one or more operands off the top of the stack.

If the stack doesn't have sufficient operands, raises a stack underflow error.

Overloads:

  • - (Object) pop

    Returns:

    • (Object)
  • - (Array) pop(n)

    Parameters:

    • (Integer) n

    Returns:

    • (Array)

Returns:

  • (Object)

Raises:



129
130
131
132
133
134
135
# File 'lib/trith/machine.rb', line 129

def pop(n = nil)
  if @stack.size < (n || 1)
    raise StackUnderflowError.new
  else
    n ? @stack.pop(n) : @stack.pop
  end
end

- (Machine) push - (Machine) push(*ops)

Pushes the given operands onto the top of the stack.

If no operands were given, shifts an operand off the front of the queue.

Overloads:

Returns:



151
152
153
154
# File 'lib/trith/machine.rb', line 151

def push(*ops)
  @stack.push(*(ops.empty? ? [shift] : ops))
  self
end

- (Boolean) quotation?(op) (protected)

Returns true if op is a quotation.

Parameters:

  • (Object) op

Returns:

  • (Boolean)


272
273
274
# File 'lib/trith/machine.rb', line 272

def quotation?(op)
  op.is_a?(Array)
end

- (Object) shift - (Array) shift(n)

Shifts and returns one or more operands off the front of the queue.

If the queue doesn't have sufficient operands, throws a :halt symbol.

Overloads:

  • - (Object) shift

    Returns:

    • (Object)
  • - (Array) shift(n)

    Parameters:

    • (Integer) n

    Returns:

    • (Array)

Returns:

  • (Object)


170
171
172
173
174
175
176
# File 'lib/trith/machine.rb', line 170

def shift(n = nil)
  if @queue.size < (n || 1)
    throw(:halt) # caught in `#execute`
  else
    n ? @queue.shift(n) : @queue.shift
  end
end

- (Array) to_a

Returns the concatenation of the stack and queue.

Returns:

  • (Array)


100
101
102
# File 'lib/trith/machine.rb', line 100

def to_a
  (@stack + @queue).to_a
end

- (Machine) unshift - (Machine) unshift(*ops)

Prepends the given operands onto the front of the queue.

If no operands were given, pops an operand off the top of the stack.

Overloads:

Returns:



191
192
193
194
# File 'lib/trith/machine.rb', line 191

def unshift(*ops)
  @queue.unshift(*(ops.empty? ? [pop] : ops))
  self
end

- with_current_continuation { ... } (protected)

This method returns an undefined value.

Invokes the given block with an empty code queue, passing the previous queue contents as an argument.

Yields:



337
338
339
340
# File 'lib/trith/machine.rb', line 337

def with_current_continuation(&block)
  block.call(capture_continuation)
  self
end

- with_saved_continuation(name = nil) { ... } (protected)

This method returns an undefined value.

Invokes the given block with an empty code queue, restoring the queue contents after the block returns.

Parameters:

  • (Symbol) name (defaults to: nil)

Yields:



321
322
323
324
325
326
327
328
329
# File 'lib/trith/machine.rb', line 321

def with_saved_continuation(name = nil, &block)
  cc = capture_continuation
  case block.arity
    when 1 then block.call(cc)
    else block.call
  end
  @queue.push(*cc) unless cc.empty?
  self
end