Class: VM

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(bc_io) ⇒ VM

Returns a new instance of VM.



109
110
111
112
113
114
115
116
117
118
# File 'lib/sdx/vm/vm.rb', line 109

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)
    @stack = []
    @byte_pos = 0
end

Instance Attribute Details

#bc_ioObject

Returns the value of attribute bc_io.



30
31
32
# File 'lib/sdx/vm/vm.rb', line 30

def bc_io
  @bc_io
end

Instance Method Details

#arity(val) ⇒ Object



90
91
92
93
94
95
96
97
98
# File 'lib/sdx/vm/vm.rb', line 90

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) ⇒ Object



125
126
127
# File 'lib/sdx/vm/vm.rb', line 125

def byte_pos=(n)
    @byte_pos = n
end

#call(val, *args) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/sdx/vm/vm.rb', line 44

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) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/sdx/vm/vm.rb', line 74

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

#clearObject



129
130
131
# File 'lib/sdx/vm/vm.rb', line 129

def clear
    @stack = []
end

#error(msg) ⇒ Object



225
226
227
228
229
# File 'lib/sdx/vm/vm.rb', line 225

def error(msg)
    puts "\x1b[0;31mError in VM: #{msg}\x1b[0;0m"
    @stack = []
    State::state = :error
end

#from_rb(val) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/sdx/vm/vm.rb', line 59

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_argsObject



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/sdx/vm/vm.rb', line 196

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_stringObject

when called, gets all bytes until STREND and returns them as string



188
189
190
191
192
193
194
# File 'lib/sdx/vm/vm.rb', line 188

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

#globalObject



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

def global
    @global
end

#interpret(do_end = true) ⇒ Object

builds stack from bytecode



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
# File 'lib/sdx/vm/vm.rb', line 231

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.to_f), :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) ⇒ Object

loads in next x bytes from file, returns in array



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/sdx/vm/vm.rb', line 133

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_stackObject



217
218
219
# File 'lib/sdx/vm/vm.rb', line 217

def pop_from_stack
    @stack.pop
end

#push_to_stack(to_push) ⇒ Object



213
214
215
# File 'lib/sdx/vm/vm.rb', line 213

def push_to_stack(to_push)
    @stack.push to_var to_push
end

#stackObject



221
222
223
# File 'lib/sdx/vm/vm.rb', line 221

def stack
    @stack
end

#to_var(val) ⇒ Object



100
101
102
103
104
105
106
107
# File 'lib/sdx/vm/vm.rb', line 100

def to_var(val)
    case val
    when Variable
        return val
    else
        return Variable.new val, (get_type val), @global
    end
end

#truthy(val) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
# File 'lib/sdx/vm/vm.rb', line 32

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