class VM

Attributes

bc_io[RW]

Public Class Methods

new(bc_io) click to toggle source
# File lib/sdx/vm/vm.rb, line 114
def initialize(bc_io) 
    @bc_io = bc_io 
    @global = GLOBAL_SCOPE.new
    @global.add_fn "__rb_call", (Variable.new (NativeFn.new 2, (Proc.new do |name, args|
        args = (codify args)[1..-2]
        from_rb eval "#{name.value.internal}(#{args})"
    end)), :fn, @global)
    @global.add_fn "__is_int", (Variable.new (NativeFn.new 1, (Proc.new do |n|
        case n.value
        when Int
            to_var (Bool.new true)
        else
            to_var (Bool.new false)
        end
    end)), :fn, @global)
    @global.add_fn "__is_num", (Variable.new (NativeFn.new 1, (Proc.new do |n|
        case n.value
        when Num
            to_var (Bool.new true)
        else
            to_var (Bool.new false)
        end
    end)), :fn, @global)
    @global.add_fn "__is_str", (Variable.new (NativeFn.new 1, (Proc.new do |n|
        case n.value
        when Str
            to_var (Bool.new true)
        else
            to_var (Bool.new false)
        end
    end)), :fn, @global)
    @global.add_fn "__is_list", (Variable.new (NativeFn.new 1, (Proc.new do |n|
        case n.value
        when List
            to_var (Bool.new true)
        else
            to_var (Bool.new false)
        end
    end)), :fn, @global)
    @global.add_fn "__is_bool", (Variable.new (NativeFn.new 1, (Proc.new do |n|
        case n.value
        when Bool
            to_var (Bool.new true)
        else
            to_var (Bool.new false)
        end
    end)), :fn, @global)
    @global.add_fn "__is_list", (Variable.new (NativeFn.new 1, (Proc.new do |n|
        case n.value
        when List
            to_var (Bool.new true)
        else
            to_var (Bool.new false)
        end
    end)), :fn, @global)
    @global.add_fn "__is_nil", (Variable.new (NativeFn.new 1, (Proc.new do |n|
        case n.value
        when Nil
            to_var (Bool.new true)
        else
            to_var (Bool.new false)
        end
    end)), :fn, @global)
    @stack = []
    @byte_pos = 0
end

Public Instance Methods

arity(val) click to toggle source
# File lib/sdx/vm/vm.rb, line 95
def arity(val)
    if val.respond_to? :value and val.value.respond_to? :fields
        if val.value.fields["__call"] and val.value.fields["__arity"]
            return val.value.fields["__arity"]
        end
    else
        return (to_var val).value.arity
    end
end
byte_pos=(n) click to toggle source
# File lib/sdx/vm/vm.rb, line 186
def byte_pos=(n)
    @byte_pos = n
end
call(val, *args) click to toggle source
# File lib/sdx/vm/vm.rb, line 49
def call(val, *args)
    if val.respond_to? :value and val.value.respond_to? :fields
        case val.value
        when Function
            return val.value.fields["__call"].call args.map { |x| to_var x }, val.scope
        else
            return val.value.fields["__call"].call args, val.scope
        end
    elsif val.respond_to? :fields
        return val.fields["__call"].call *args
    else
        return (to_var val).value.call args
    end
end
callable(val) click to toggle source
# File lib/sdx/vm/vm.rb, line 79
def callable(val)
    if val.respond_to? :value and val.value.respond_to? :fields
        if val.value.fields["__call"] and val.value.fields["__arity"]
            return true
        end
        return false
    elsif val.respond_to? :fields
        if val.fields["__call"] and val.fields["__arity"]
            return true
        end
        return false
    else
        return true
    end
end
clear() click to toggle source
# File lib/sdx/vm/vm.rb, line 190
def clear
    @stack = []
end
error(msg) click to toggle source
# File lib/sdx/vm/vm.rb, line 286
def error(msg)
    puts "\x1b[0;31mError in VM: #{msg}\x1b[0;0m"
    @stack = []
    State::state = :error
end
from_rb(val) click to toggle source
# File lib/sdx/vm/vm.rb, line 64
def from_rb(val)
    case val
    when Integer
        to_var (Int.new val)
    when String
        to_var (Str.new val)
    when Float
        to_var (Num.new val)
    when Array
        to_var (List.new val.map { |v| from_rb v })
    when Nil
        to_var (Nil_.new)
    end
end
get_args() click to toggle source
# File lib/sdx/vm/vm.rb, line 257
def get_args
    args = []
    this = ""
    while (byte = load_bytes(1, false)[0]) != 0x08
        if byte == 0x07
            args << this
            this = ""
        else
            this += byte.chr
        end
    end
    if this != ""
        args << this
    end
    args
end
get_string() click to toggle source
# File lib/sdx/vm/vm.rb, line 249
def get_string # when called, gets all bytes until STREND and returns them as string
    string = ""
    while (byte = load_bytes(1, false)[0]) != 24 # cant use STREND here, need byte as is
        string += byte.chr 
    end
    string
end
global() click to toggle source
# File lib/sdx/vm/vm.rb, line 182
def global
    @global
end
interpret(do_end=true) click to toggle source
# File lib/sdx/vm/vm.rb, line 292
def interpret(do_end=true) # builds stack from bytecode
    loop do  
        loaded_bytes = load_bytes(1) # loads in first byte for initial instruction
        break if loaded_bytes[0] == :end_prg # end of program reached
        break if State::state != :ok

        case loaded_bytes[0]
        when :make
            loaded_bytes.concat load_bytes(1) 
            case loaded_bytes[1]
            when :var
                var_name = get_string
                val = pop_from_stack
                @global.add_var var_name, val
                push_to_stack val # assignments evaluate to their value
            when :fn 
                # make fn <name>
                fn_name = get_string
                args = get_args
                size = get_string.to_i
                body = 
                    ((load_bytes size, false).map { |e| e.chr }).join ""
                fn = Function.new args, body
                @global.add_fn fn_name, (Variable.new fn, :fn, @global)
            when :object
                # make fn <name>
                obj_name = get_string
                args = get_args
                size = get_string.to_i
                body = 
                    ((load_bytes size, false).map { |e| e.chr }).join ""
                obj = Obj.new args, body
                @global.add_obj obj_name, (Variable.new obj, :obj, @global)
            end
        when :set
            var_name = get_string
            val = pop_from_stack
            @global.add_var var_name, val
            push_to_stack val # assignments evaluate to their value
        when :const
            loaded_bytes.concat load_bytes(1)
            case loaded_bytes[1]
            when :int
                val = get_string
                push_to_stack Variable.new (Int.new val.to_i), :int, @global
            when :num
                val = get_string
                push_to_stack Variable.new (Num.new val), :num, @global
            when :str
                val = get_string
                push_to_stack Variable.new (Str.new val), :str, @global
            when :list
                count = get_string.to_i
                vals = []
                count.times do
                    vals << pop_from_stack
                end
                vals.reverse!
                push_to_stack Variable.new (List.new vals, @global), :list, @global
            when :block
                size = get_string.to_i
                body = 
                    ((load_bytes size, false).map { |e| e.chr }).join ""
                push_to_stack Variable.new (Block.new body), :block, @global
            when :bool
                val = get_string
                t = {
                    "true" => true,
                    "false" => false,
                }
                push_to_stack Variable.new (Bool.new t[val]), :bool, @global
            when :nil
                push_to_stack Variable.new (Nil.new), :nil, @global
            end
        when :add
            b, a = pop_from_stack, pop_from_stack
            if a.value.fields["__add"]
                res = (call a.value.fields["__add"], b.value)
                push_to_stack (to_var res)
            else
                error "Cannot use + on #{codify a}"
            end
        when :sub
            b, a = pop_from_stack, pop_from_stack
            if a.value.fields["__sub"]
                res = (call a.value.fields["__sub"], b.value)
                push_to_stack (Variable.new res, (get_type res), @global) 
            else
                error "Cannot use - on #{codify a}"
            end
        when :mul
            b, a = pop_from_stack, pop_from_stack
            if a.value.fields["__mul"]
                res = (call a.value.fields["__mul"], b.value)
                push_to_stack (Variable.new res, (get_type res), @global) 
            else
                error "Cannot use * on #{codify a}"
            end
        when :div
            b, a = pop_from_stack, pop_from_stack
            if a.value.fields["__div"]
                res = (call a.value.fields["__div"], b.value)
                push_to_stack (Variable.new res, (get_type res), @global) 
            else
                error "Cannot use / on #{codify a}"
            end
        when :mod
            b, a = pop_from_stack, pop_from_stack
            if a.value.fields["__mod"]
                res = (call a.value.fields["__mod"], b.value)
                push_to_stack (Variable.new res, (get_type res), @global) 
            else
                error "Cannot use % on #{codify a}"
            end
        when :pow
            b, a = pop_from_stack, pop_from_stack
            if a.value.fields["__pow"]
                res = (call a.value.fields["__pow"], b.value)
                push_to_stack (Variable.new res, (get_type res), @global) 
            else
                error "Cannot use ^ on #{codify a}"
            end
        when :eq
            b, a = pop_from_stack, pop_from_stack
            if a.value.fields["__eq"]
                res = (call a.value.fields["__eq"], b.value)
                push_to_stack (Variable.new res, (get_type res), @global) 
            else
                error "Cannot use == on #{codify a}"
            end
        when :ne
            b, a = pop_from_stack, pop_from_stack
            if a.value.fields["__neq"]
                res = (call a.value.fields["__neq"], b.value)
                push_to_stack (Variable.new res, (get_type res), @global) 
            else
                error "Cannot use != on #{codify a}"
            end
        when :lt
            b, a = pop_from_stack, pop_from_stack
            if a.value.fields["__lt"]
                res = (call a.value.fields["__lt"], b.value)
                push_to_stack (Variable.new res, (get_type res), @global) 
            else
                error "Cannot use < on #{codify a}"
            end
        when :gt
            b, a = pop_from_stack, pop_from_stack
            if a.value.fields["__gt"]
                res = (call a.value.fields["__gt"], b.value)
                push_to_stack (Variable.new res, (get_type res), @global) 
            else
                error "Cannot use > on #{codify a}"
            end
        when :le
            b, a = pop_from_stack, pop_from_stack
            if a.value.fields["__le"]
                res = (call a.value.fields["__le"], b.value)
                push_to_stack (Variable.new res, (get_type res), @global) 
            else
                error "Cannot use <= on #{codify a}"
            end
        when :ge
            b, a = pop_from_stack, pop_from_stack
            if a.value.fields["__ge"]
                res = (call a.value.fields["__ge"], b.value)
                push_to_stack (Variable.new res, (get_type res), @global) 
            else
                error "Cannot use >= on #{codify a}"
            end
        when :jmpi
            val = pop_from_stack
            amt = get_string
            if truthy val
                @byte_pos += amt.to_i
            end
        when :jmpn
            val = pop_from_stack
            amt = get_string
            unless truthy val
                @byte_pos += amt.to_i
            end
        when :jmp
            amt = get_string
            @byte_pos += amt.to_i
        when :get
            name = get_string
            var = @global.get_var name
            if var
                push_to_stack var
            else
                error "No such variable #{name}"
            end
        when :reset
            val = pop_from_stack
            if val.value.fields["__reset"]
                call val.value.fields["__reset"]
                push_to_stack val
            else
                error "Cannot reset #{codify val}"
            end
        when :iter
            val = pop_from_stack
            if val.value.fields["__iter"]
                res = call val.value.fields["__iter"]
                push_to_stack res
            else
                error "Cannot iterate #{codify val}"
            end
        when :end
            if do_end
                @stack = []
            end
        when :call
            val = pop_from_stack
            if callable val
                args = []
                (arity val).internal.times do
                    break if State::state != :ok
                    this = pop_from_stack
                    if !this
                        error "Not enough arguments: expected #{val.value.fields["__arity"].internal}, got #{args.size}"
                    else
                        args << this
                    end
                end
                if State::state == :ok
                    scope = nil
                    begin
                        scope = val.scope
                    rescue
                        scope = @global
                    end
                    ret = call val, *args
                    if ret
                        push_to_stack ret
                    end
                end
            else
                error "Cannot call #{codify val}"
            end
        when :new
            val = pop_from_stack
            if val.value.fields["__new"] and val.value.fields["__arity"]
                args = []
                val.value.fields["__arity"].internal.times do
                    break if State::state != :ok
                    this = pop_from_stack
                    if !this
                        error "Not enough arguments: expected #{val.value.fields["__arity"].internal}, got #{args.size}"
                    else 
                        args << this
                    end
                end
                if State::state == :ok
                    f = Proc.new do |args, scope|
                        call val.value.fields["__new"], args, scope
                    end
                    ret = Variable.new (InstantiatedObj.new f.call args, @global), :instobj, @global
                    if ret
                        push_to_stack ret
                    end
                end
            else
                error "Cannot instantiate #{codify val}"
            end
        end
    end
end
load_bytes(x_bytes, to_symbol=true) click to toggle source
# File lib/sdx/vm/vm.rb, line 194
def load_bytes(x_bytes, to_symbol=true) # loads in next x bytes from file, returns in array
    insts = { # just a simple hash to make interpreter code more readable
        0x01 => :make,
        0x02 => :call,
        0x03 => :return,
        0x04 => :set,
        0x05 => :mut,
        0x06 => :exec,
        0x10 => :var,
        0x16 => :end_prg,
        0x17 => :fn,
        0x18 => :strend,
        0x21 => :const,
        0x23 => :add,
        0x24 => :sub,
        0x25 => :mul,
        0x26 => :div,
        0x27 => :mod,
        0x12 => :bool,
        0x13 => :int,
        0x14 => :str,
        0x15 => :num,
        0x29 => :jmpi,
        0x2a => :jmp,
        0x2b => :jmpn,
        0x20 => :get,
        0x2c => :list,
        0x2f => :nil,
        0x2d => :reset,
        0x2e => :iter,
        0x30 => :object,
        0x31 => :new,
        0x32 => :block,
        0x33 => :end,
        0x34 => :eq,
        0x35 => :ne,
        0x36 => :lt,
        0x37 => :gt,
        0x38 => :le,
        0x39 => :ge,
        0x28 => :pow,
    }
    bytes = []
    begin
        x_bytes.times do
            @bc_io.seek(@byte_pos)
            byte_integer = @bc_io.sysread(1).ord
            bytes.push(to_symbol ? insts[byte_integer] : byte_integer)
            @byte_pos += 1
        end
        # add rescue here eventually (mainly to handle end of file error)
    end
    bytes
end
pop_from_stack() click to toggle source
# File lib/sdx/vm/vm.rb, line 278
def pop_from_stack
    @stack.pop
end
push_to_stack(to_push) click to toggle source
# File lib/sdx/vm/vm.rb, line 274
def push_to_stack(to_push)
    @stack.push to_var to_push
end
stack() click to toggle source
# File lib/sdx/vm/vm.rb, line 282
def stack
    @stack
end
to_var(val) click to toggle source
# File lib/sdx/vm/vm.rb, line 105
def to_var(val)
    case val
    when Variable
        return val
    else
        return Variable.new val, (get_type val), @global
    end
end
truthy(val) click to toggle source
# File lib/sdx/vm/vm.rb, line 37
def truthy(val)
    case val.value
    when Bool
        return val.value.internal
    end
    if val.value.fields["__as_bool"]
        return (val.value.fields["__as_bool"].call).internal
    else
        return true
    end
end