mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 16:47:01 +00:00
874 lines
19 KiB
V
874 lines
19 KiB
V
// vm.v — the VuurRaaf runtime: a small stack-based virtual machine.
|
|
//
|
|
// Stack values are 64-bit tagged integers with two tag bits:
|
|
// low bits 00 -> encoded number (value = raw << 2)
|
|
// low bits 01 -> string handle (handle = value >> 2, into v.strings)
|
|
// low bits 10 -> struct handle (handle = value >> 2, into v.structs)
|
|
// low bits 11 -> array handle (handle = value >> 2, into v.arrays)
|
|
// Encoding numbers with a constant shift means no integer ever collides with
|
|
// a string, struct, or array handle.
|
|
//
|
|
// Call convention: CALL pushes a frame (retaddr, old bp, argc) and copies the
|
|
// arguments into the callee's local slots; the callee reserves extra locals
|
|
// with `enter n` and cleans up with `ret`/`retv`.
|
|
module vm
|
|
|
|
import obj
|
|
|
|
// opcodes — keep in sync with the compiler, assembler, and this interpreter
|
|
const op_halt = u8(0)
|
|
const op_push_i = u8(1)
|
|
const op_push_s = u8(2)
|
|
const op_load = u8(3)
|
|
const op_store = u8(4)
|
|
const op_pop = u8(5)
|
|
const op_dup = u8(6)
|
|
const op_add = u8(7)
|
|
const op_sub = u8(8)
|
|
const op_mul = u8(9)
|
|
const op_div = u8(10)
|
|
const op_mod = u8(11)
|
|
const op_neg = u8(12)
|
|
const op_eq = u8(13)
|
|
const op_ne = u8(14)
|
|
const op_lt = u8(15)
|
|
const op_le = u8(16)
|
|
const op_gt = u8(17)
|
|
const op_ge = u8(18)
|
|
const op_and = u8(19)
|
|
const op_or = u8(20)
|
|
const op_not = u8(21)
|
|
const op_jmp = u8(22)
|
|
const op_jz = u8(23)
|
|
const op_jnz = u8(24)
|
|
const op_call = u8(25)
|
|
const op_ret = u8(26)
|
|
const op_retv = u8(27)
|
|
const op_print = u8(28)
|
|
const op_println = u8(29)
|
|
const op_assert = u8(30)
|
|
const op_enter = u8(31)
|
|
const op_mkarray = u8(32)
|
|
const op_aget = u8(33)
|
|
const op_aset = u8(34)
|
|
const op_alen = u8(35)
|
|
const op_apush = u8(36)
|
|
const op_mkstruct = u8(37)
|
|
const op_sget = u8(38)
|
|
const op_sset = u8(39)
|
|
const op_shas = u8(40) // has(map, "key") -> 1 if key exists, 0 otherwise
|
|
const op_sdel = u8(41) // delete(map, "key") -> removes the key
|
|
const op_slen = u8(42) // slen(struct) -> number of fields
|
|
const op_skeys = u8(43) // skeys(struct) -> array of field name strings
|
|
|
|
const stack_cap = 65536
|
|
|
|
// Field is one `name: value` entry of a struct value.
|
|
struct Field {
|
|
mut:
|
|
name string
|
|
val i64
|
|
}
|
|
|
|
struct StructVal {
|
|
mut:
|
|
fields []Field
|
|
}
|
|
|
|
struct Vm {
|
|
mut:
|
|
code []u8
|
|
strings []string
|
|
arrays [][]i64
|
|
structs []StructVal
|
|
stack []i64
|
|
sp int
|
|
bp int
|
|
ip int
|
|
trace bool
|
|
halted bool
|
|
}
|
|
|
|
// run executes the function named `entry` from the executable `bin` and
|
|
// returns its return value (0 if it never returns one).
|
|
pub fn run(bin obj.Bin, entry string, trace bool) !i64 {
|
|
mut v := Vm{
|
|
code: bin.code
|
|
strings: bin.strings.clone()
|
|
stack: []i64{len: stack_cap}
|
|
trace: trace
|
|
}
|
|
mut entry_ip := -1
|
|
for f in bin.fns {
|
|
if f.name == entry {
|
|
entry_ip = f.entry
|
|
break
|
|
}
|
|
}
|
|
if entry_ip < 0 {
|
|
names := bin.fns.map(fn (f obj.BinFn) string {
|
|
return f.name
|
|
})
|
|
return error('no function "${entry}" in program (available: ${names.join(', ')})')
|
|
}
|
|
// synthetic frame: retaddr = -1 (halt sentinel), old bp = 0, argc = 0
|
|
v.stack[v.sp] = v.enc_int(-1)
|
|
v.sp++
|
|
v.stack[v.sp] = v.enc_int(0)
|
|
v.sp++
|
|
v.stack[v.sp] = v.enc_int(0)
|
|
v.sp++
|
|
v.bp = v.sp
|
|
v.ip = entry_ip
|
|
v.exec()!
|
|
if v.sp > 0 {
|
|
return v.dec_int(v.stack[0])
|
|
}
|
|
return 0
|
|
}
|
|
|
|
fn (mut v Vm) exec() ! {
|
|
for !v.halted {
|
|
op := v.code[v.ip]
|
|
if v.trace {
|
|
v.trace_op(op)
|
|
}
|
|
match op {
|
|
op_halt {
|
|
v.halted = true
|
|
}
|
|
op_push_i {
|
|
v.ip++
|
|
v.push(v.enc_int(v.read_i64()))!
|
|
}
|
|
op_push_s {
|
|
v.ip++
|
|
idx := int(v.read_i64())
|
|
v.push(v.mkstr(idx))!
|
|
}
|
|
op_load {
|
|
v.ip++
|
|
idx := int(v.read_i64())
|
|
v.push(v.stack[v.bp + idx])!
|
|
}
|
|
op_store {
|
|
v.ip++
|
|
idx := int(v.read_i64())
|
|
v.stack[v.bp + idx] = v.pop()!
|
|
}
|
|
op_pop {
|
|
v.ip++
|
|
v.pop()!
|
|
}
|
|
op_dup {
|
|
v.ip++
|
|
a := v.pop()!
|
|
v.push(a)!
|
|
v.push(a)!
|
|
}
|
|
op_add {
|
|
v.ip++
|
|
b := v.pop()!
|
|
a := v.pop()!
|
|
v.push(v.add(a, b)!)!
|
|
}
|
|
op_sub {
|
|
v.ip++
|
|
b := v.pop()!
|
|
a := v.pop()!
|
|
v.push(v.arith(a, b, '-')!)!
|
|
}
|
|
op_mul {
|
|
v.ip++
|
|
b := v.pop()!
|
|
a := v.pop()!
|
|
v.push(v.arith(a, b, '*')!)!
|
|
}
|
|
op_div {
|
|
v.ip++
|
|
b := v.pop()!
|
|
a := v.pop()!
|
|
v.push(v.arith(a, b, '/')!)!
|
|
}
|
|
op_mod {
|
|
v.ip++
|
|
b := v.pop()!
|
|
a := v.pop()!
|
|
v.push(v.arith(a, b, '%')!)!
|
|
}
|
|
op_neg {
|
|
v.ip++
|
|
a := v.pop()!
|
|
if v.is_str(a) {
|
|
return error('cannot negate a string')
|
|
}
|
|
if v.is_arr(a) {
|
|
return error('cannot negate an array')
|
|
}
|
|
if v.is_struct(a) {
|
|
return error('cannot negate a struct')
|
|
}
|
|
v.push(v.enc_int(-v.dec_int(a)))!
|
|
}
|
|
op_eq {
|
|
v.ip++
|
|
b := v.pop()!
|
|
a := v.pop()!
|
|
v.push(v.enc_int(v.cmp(a, b, '==')!))!
|
|
}
|
|
op_ne {
|
|
v.ip++
|
|
b := v.pop()!
|
|
a := v.pop()!
|
|
v.push(v.enc_int(v.cmp(a, b, '!=')!))!
|
|
}
|
|
op_lt {
|
|
v.ip++
|
|
b := v.pop()!
|
|
a := v.pop()!
|
|
v.push(v.enc_int(v.cmp(a, b, '<')!))!
|
|
}
|
|
op_le {
|
|
v.ip++
|
|
b := v.pop()!
|
|
a := v.pop()!
|
|
v.push(v.enc_int(v.cmp(a, b, '<=')!))!
|
|
}
|
|
op_gt {
|
|
v.ip++
|
|
b := v.pop()!
|
|
a := v.pop()!
|
|
v.push(v.enc_int(v.cmp(a, b, '>')!))!
|
|
}
|
|
op_ge {
|
|
v.ip++
|
|
b := v.pop()!
|
|
a := v.pop()!
|
|
v.push(v.enc_int(v.cmp(a, b, '>=')!))!
|
|
}
|
|
op_and {
|
|
v.ip++
|
|
b := v.pop()!
|
|
a := v.pop()!
|
|
v.push(v.enc_int(bool_i64(v.truthy(a) && v.truthy(b))))!
|
|
}
|
|
op_or {
|
|
v.ip++
|
|
b := v.pop()!
|
|
a := v.pop()!
|
|
v.push(v.enc_int(bool_i64(v.truthy(a) || v.truthy(b))))!
|
|
}
|
|
op_not {
|
|
v.ip++
|
|
a := v.pop()!
|
|
v.push(v.enc_int(bool_i64(!v.truthy(a))))!
|
|
}
|
|
op_jmp {
|
|
v.ip++
|
|
v.ip = int(v.read_i64())
|
|
}
|
|
op_jz {
|
|
v.ip++
|
|
target := int(v.read_i64())
|
|
if !v.truthy(v.pop()!) {
|
|
v.ip = target
|
|
}
|
|
}
|
|
op_jnz {
|
|
v.ip++
|
|
target := int(v.read_i64())
|
|
if v.truthy(v.pop()!) {
|
|
v.ip = target
|
|
}
|
|
}
|
|
op_call {
|
|
v.ip++
|
|
target := int(v.read_i64())
|
|
argc := int(v.read_i64())
|
|
v.call(target, argc)
|
|
}
|
|
op_ret {
|
|
v.ret(false)!
|
|
}
|
|
op_retv {
|
|
v.ret(true)!
|
|
}
|
|
op_print {
|
|
v.ip++
|
|
v.print_val(v.pop()!)
|
|
}
|
|
op_println {
|
|
v.ip++
|
|
v.print_val(v.pop()!)
|
|
println('')
|
|
}
|
|
op_assert {
|
|
v.ip++
|
|
if !v.truthy(v.pop()!) {
|
|
return error('assertion failed (ip ${v.ip})')
|
|
}
|
|
}
|
|
op_enter {
|
|
v.ip++
|
|
n := int(v.read_i64())
|
|
for _ in 0..n {
|
|
v.push(0)!
|
|
}
|
|
}
|
|
op_mkarray {
|
|
v.ip++
|
|
n := int(v.read_i64())
|
|
mut arr := []i64{len: n}
|
|
for i := n - 1; i >= 0; i-- {
|
|
arr[i] = v.pop()!
|
|
}
|
|
v.arrays << arr
|
|
v.push(v.mkarr(v.arrays.len - 1))!
|
|
}
|
|
op_aget {
|
|
v.ip++
|
|
idx := int(v.dec_int(v.pop()!))
|
|
h := v.pop()!
|
|
if !v.is_arr(h) || !v.valid_arr_handle(h) {
|
|
return error('indexing a non-array value')
|
|
}
|
|
a := v.arrays[v.hand(h)]
|
|
if idx < 0 || idx >= a.len {
|
|
return error('array index ${idx} out of bounds (len ${a.len})')
|
|
}
|
|
v.push(a[idx])!
|
|
}
|
|
op_aset {
|
|
v.ip++
|
|
val := v.pop()!
|
|
idx := int(v.dec_int(v.pop()!))
|
|
h := v.pop()!
|
|
if !v.is_arr(h) || !v.valid_arr_handle(h) {
|
|
return error('indexing a non-array value')
|
|
}
|
|
if idx < 0 || idx >= v.arrays[v.hand(h)].len {
|
|
return error('array index ${idx} out of bounds (len ${v.arrays[v.hand(h)].len})')
|
|
}
|
|
v.arrays[v.hand(h)][idx] = val
|
|
}
|
|
op_alen {
|
|
v.ip++
|
|
h := v.pop()!
|
|
if v.is_arr(h) && v.valid_arr_handle(h) {
|
|
v.push(v.enc_int(i64(v.arrays[v.hand(h)].len)))!
|
|
} else if v.is_struct(h) && v.valid_struct_handle(h) {
|
|
v.push(v.enc_int(i64(v.structs[v.hand(h)].fields.len)))!
|
|
} else {
|
|
return error('len() on a non-array, non-struct value')
|
|
}
|
|
}
|
|
op_apush {
|
|
v.ip++
|
|
val := v.pop()!
|
|
h := v.pop()!
|
|
if !v.is_arr(h) || !v.valid_arr_handle(h) {
|
|
return error('push() on a non-array value')
|
|
}
|
|
v.arrays[v.hand(h)] << val
|
|
v.push(h)!
|
|
}
|
|
op_mkstruct {
|
|
v.ip++
|
|
n := int(v.read_i64())
|
|
mut fields := []Field{len: n}
|
|
// stack holds (name, value) pairs; pop from the last field back
|
|
for i := n - 1; i >= 0; i-- {
|
|
val := v.pop()!
|
|
name := v.pop()!
|
|
if !v.is_str(name) || !v.valid_handle(name) {
|
|
return error('internal: struct field name is not a string')
|
|
}
|
|
fields[i] = Field{ name: v.strings[v.hand(name)], val: val }
|
|
}
|
|
v.structs << StructVal{ fields: fields }
|
|
v.push(v.mkstruct_handle(v.structs.len - 1))!
|
|
}
|
|
op_sget {
|
|
v.ip++
|
|
name := v.pop()!
|
|
h := v.pop()!
|
|
if !v.is_struct(h) || !v.valid_struct_handle(h) {
|
|
return error('field access on a non-struct value')
|
|
}
|
|
if !v.is_str(name) || !v.valid_handle(name) {
|
|
return error('internal: field name is not a string')
|
|
}
|
|
fname := v.strings[v.hand(name)]
|
|
mut found := false
|
|
for f in v.structs[v.hand(h)].fields {
|
|
if f.name == fname {
|
|
v.push(f.val)!
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return error('no field "${fname}" on struct')
|
|
}
|
|
}
|
|
op_sset {
|
|
v.ip++
|
|
// stack: [struct, value, "name"] — the name is on top
|
|
name := v.pop()!
|
|
val := v.pop()!
|
|
h := v.pop()!
|
|
if !v.is_struct(h) || !v.valid_struct_handle(h) {
|
|
return error('field assignment on a non-struct value')
|
|
}
|
|
if !v.is_str(name) || !v.valid_handle(name) {
|
|
return error('internal: field name is not a string')
|
|
}
|
|
fname := v.strings[v.hand(name)]
|
|
mut s := v.structs[v.hand(h)]
|
|
mut found := false
|
|
for i, f in s.fields {
|
|
if f.name == fname {
|
|
s.fields[i].val = val
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
// setting a missing field adds it, so records can be built
|
|
// incrementally from an empty `{}`
|
|
s.fields << Field{ name: fname, val: val }
|
|
}
|
|
v.structs[v.hand(h)] = s
|
|
}
|
|
op_shas {
|
|
v.op_shas()!
|
|
}
|
|
op_sdel {
|
|
v.op_sdel()!
|
|
}
|
|
op_slen {
|
|
v.op_slen()!
|
|
}
|
|
op_skeys {
|
|
v.op_skeys()!
|
|
}
|
|
else {
|
|
return error('unknown opcode ${op} at ip ${v.ip}')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// op_shas checks if a struct has a field with the given name.
|
|
// stack: struct, "key" → pushes 1 if found, 0 if not.
|
|
fn (mut v Vm) op_shas() ! {
|
|
v.ip++
|
|
name := v.pop()!
|
|
h := v.pop()!
|
|
if !v.is_struct(h) || !v.valid_struct_handle(h) {
|
|
return error('has() on a non-struct value')
|
|
}
|
|
if !v.is_str(name) || !v.valid_handle(name) {
|
|
return error('internal: field name is not a string')
|
|
}
|
|
fname := v.strings[v.hand(name)]
|
|
mut found := false
|
|
for f in v.structs[v.hand(h)].fields {
|
|
if f.name == fname {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
v.push(v.enc_int(if found { 1 } else { 0 }))!
|
|
}
|
|
|
|
// op_sdel removes a field from a struct.
|
|
// stack: struct, "key" → pushes the struct handle back.
|
|
fn (mut v Vm) op_sdel() ! {
|
|
v.ip++
|
|
name := v.pop()!
|
|
h := v.pop()!
|
|
if !v.is_struct(h) || !v.valid_struct_handle(h) {
|
|
return error('delete() on a non-struct value')
|
|
}
|
|
if !v.is_str(name) || !v.valid_handle(name) {
|
|
return error('internal: field name is not a string')
|
|
}
|
|
fname := v.strings[v.hand(name)]
|
|
mut s := v.structs[v.hand(h)]
|
|
mut new_fields := []Field{}
|
|
for f in s.fields {
|
|
if f.name != fname {
|
|
new_fields << f
|
|
}
|
|
}
|
|
s.fields = new_fields
|
|
v.structs[v.hand(h)] = s
|
|
v.push(h)!
|
|
}
|
|
|
|
// op_slen returns the number of fields in a struct.
|
|
// stack: struct → pushes field count.
|
|
fn (mut v Vm) op_slen() ! {
|
|
v.ip++
|
|
h := v.pop()!
|
|
if !v.is_struct(h) || !v.valid_struct_handle(h) {
|
|
return error('len() on a non-struct value')
|
|
}
|
|
v.push(v.enc_int(i64(v.structs[v.hand(h)].fields.len)))!
|
|
}
|
|
|
|
// op_skeys returns an array of field name strings.
|
|
// stack: struct → pushes array handle.
|
|
fn (mut v Vm) op_skeys() ! {
|
|
v.ip++
|
|
h := v.pop()!
|
|
if !v.is_struct(h) || !v.valid_struct_handle(h) {
|
|
return error('keys() on a non-struct value')
|
|
}
|
|
mut arr := []i64{}
|
|
for f in v.structs[v.hand(h)].fields {
|
|
v.strings << f.name
|
|
arr << v.mkstr(v.strings.len - 1)
|
|
}
|
|
v.arrays << arr
|
|
v.push(v.mkarr(v.arrays.len - 1))!
|
|
}
|
|
|
|
fn (mut v Vm) read_i64() i64 {
|
|
mut val := u64(0)
|
|
for i in 0..8 {
|
|
val |= u64(v.code[v.ip + i]) << u32(8 * i)
|
|
}
|
|
v.ip += 8
|
|
return i64(val)
|
|
}
|
|
|
|
fn (mut v Vm) push(x i64) ! {
|
|
if v.sp >= v.stack.len {
|
|
return error('stack overflow')
|
|
}
|
|
v.stack[v.sp] = x
|
|
v.sp++
|
|
}
|
|
|
|
fn (mut v Vm) pop() !i64 {
|
|
if v.sp <= 0 {
|
|
return error('stack underflow')
|
|
}
|
|
v.sp--
|
|
return v.stack[v.sp]
|
|
}
|
|
|
|
fn (mut v Vm) call(target int, argc int) {
|
|
v.stack[v.sp] = v.enc_int(i64(v.ip)) // return address (ip already past both operands)
|
|
v.sp++
|
|
v.stack[v.sp] = v.enc_int(i64(v.bp))
|
|
v.sp++
|
|
v.stack[v.sp] = v.enc_int(i64(argc))
|
|
v.sp++
|
|
v.bp = v.sp
|
|
// copy the arguments below the frame into local slots 0..argc-1
|
|
for i in 0..argc {
|
|
v.stack[v.bp + i] = v.stack[v.bp - 3 - argc + i]
|
|
}
|
|
v.sp = v.bp + argc
|
|
v.ip = target
|
|
}
|
|
|
|
fn (mut v Vm) ret(with_val bool) ! {
|
|
retval := if with_val { v.pop()! } else { v.enc_int(0) }
|
|
v.sp = v.bp - 1
|
|
argc := int(v.dec_int(v.stack[v.sp]))
|
|
v.sp = v.bp - 2
|
|
old_bp := int(v.dec_int(v.stack[v.sp]))
|
|
v.sp = v.bp - 3
|
|
ip := int(v.dec_int(v.stack[v.sp]))
|
|
v.sp -= argc
|
|
v.bp = old_bp
|
|
if ip == -1 {
|
|
// returned to the synthetic frame: we are done
|
|
v.halted = true
|
|
v.push(retval)!
|
|
return
|
|
}
|
|
v.ip = ip
|
|
v.push(retval)!
|
|
}
|
|
|
|
fn (mut v Vm) is_str(x i64) bool {
|
|
return x & 3 == 1
|
|
}
|
|
|
|
fn (mut v Vm) is_arr(x i64) bool {
|
|
return x & 3 == 3
|
|
}
|
|
|
|
fn (mut v Vm) is_struct(x i64) bool {
|
|
return x & 3 == 2
|
|
}
|
|
|
|
fn (mut v Vm) enc_int(x i64) i64 {
|
|
return u64(x) << 2
|
|
}
|
|
|
|
fn (mut v Vm) dec_int(x i64) i64 {
|
|
return x >> 2
|
|
}
|
|
|
|
fn (mut v Vm) hand(x i64) int {
|
|
return int(x >> 2)
|
|
}
|
|
|
|
fn (mut v Vm) mkstr(idx int) i64 {
|
|
return (u64(idx) << 2) | 1
|
|
}
|
|
|
|
fn (mut v Vm) mkarr(idx int) i64 {
|
|
return (u64(idx) << 2) | 3
|
|
}
|
|
|
|
fn (mut v Vm) mkstruct_handle(idx int) i64 {
|
|
return (u64(idx) << 2) | 2
|
|
}
|
|
|
|
fn (mut v Vm) truthy(x i64) bool {
|
|
return x != 0
|
|
}
|
|
|
|
fn bool_i64(b bool) i64 {
|
|
return if b { i64(1) } else { i64(0) }
|
|
}
|
|
|
|
fn (mut v Vm) add(a i64, b i64) !i64 {
|
|
if v.is_arr(a) || v.is_arr(b) {
|
|
return error('cannot add arrays with +')
|
|
}
|
|
if v.is_struct(a) || v.is_struct(b) {
|
|
return error('cannot add structs with +')
|
|
}
|
|
if v.is_str(a) && v.is_str(b) {
|
|
return v.alloc_str(v.strings[v.hand(a)] + v.strings[v.hand(b)])
|
|
}
|
|
if v.is_str(a) {
|
|
return v.alloc_str(v.strings[v.hand(a)] + v.num_str(b))
|
|
}
|
|
if v.is_str(b) {
|
|
return v.alloc_str(v.num_str(a) + v.strings[v.hand(b)])
|
|
}
|
|
return v.enc_int(v.dec_int(a) + v.dec_int(b))
|
|
}
|
|
|
|
fn (mut v Vm) alloc_str(s string) i64 {
|
|
v.strings << s
|
|
return v.mkstr(v.strings.len - 1)
|
|
}
|
|
|
|
fn (mut v Vm) num_str(x i64) string {
|
|
return v.dec_int(x).str()
|
|
}
|
|
|
|
fn (mut v Vm) arith(a i64, b i64, op string) !i64 {
|
|
if v.is_str(a) || v.is_str(b) {
|
|
return error('cannot use strings with "${op}"')
|
|
}
|
|
if v.is_arr(a) || v.is_arr(b) {
|
|
return error('cannot use arrays with "${op}"')
|
|
}
|
|
if v.is_struct(a) || v.is_struct(b) {
|
|
return error('cannot use structs with "${op}"')
|
|
}
|
|
x := v.dec_int(a)
|
|
y := v.dec_int(b)
|
|
match op {
|
|
'-' {
|
|
return v.enc_int(x - y)
|
|
}
|
|
'*' {
|
|
return v.enc_int(x * y)
|
|
}
|
|
'/' {
|
|
if y == 0 {
|
|
return error('division by zero')
|
|
}
|
|
return v.enc_int(x / y)
|
|
}
|
|
'%' {
|
|
if y == 0 {
|
|
return error('division by zero')
|
|
}
|
|
return v.enc_int(x % y)
|
|
}
|
|
else {
|
|
return error('internal: bad arith op "${op}"')
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (mut v Vm) cmp(a i64, b i64, op string) !i64 {
|
|
if v.is_arr(a) || v.is_arr(b) {
|
|
// arrays compare by identity (handle equality) with ==/!=
|
|
if op == '==' || op == '!=' {
|
|
return bool_i64(if op == '==' { a == b } else { a != b })
|
|
}
|
|
return error('cannot order arrays')
|
|
}
|
|
if v.is_struct(a) || v.is_struct(b) {
|
|
// structs compare by identity (handle equality) with ==/!=
|
|
if op == '==' || op == '!=' {
|
|
return bool_i64(if op == '==' { a == b } else { a != b })
|
|
}
|
|
return error('cannot order structs')
|
|
}
|
|
if v.is_str(a) && v.is_str(b) {
|
|
sa := v.strings[v.hand(a)]
|
|
sb := v.strings[v.hand(b)]
|
|
return bool_i64(match op {
|
|
'==' { sa == sb }
|
|
'!=' { sa != sb }
|
|
'<' { sa < sb }
|
|
'<=' { sa <= sb }
|
|
'>' { sa > sb }
|
|
'>=' { sa >= sb }
|
|
else { return error('internal: bad cmp op "${op}"') }
|
|
})
|
|
}
|
|
if v.is_str(a) || v.is_str(b) {
|
|
return error('cannot compare a string and a number')
|
|
}
|
|
x := v.dec_int(a)
|
|
y := v.dec_int(b)
|
|
return bool_i64(match op {
|
|
'==' { x == y }
|
|
'!=' { x != y }
|
|
'<' { x < y }
|
|
'<=' { x <= y }
|
|
'>' { x > y }
|
|
'>=' { x >= y }
|
|
else { return error('internal: bad cmp op "${op}"') }
|
|
})
|
|
}
|
|
|
|
fn (mut v Vm) print_val(x i64) {
|
|
print(v.val_str(x, 0))
|
|
}
|
|
|
|
// val_str renders a value: strings as-is, arrays as [a, b, ...] (with a depth
|
|
// guard so self-referential arrays cannot hang the printer), numbers as ints.
|
|
fn (mut v Vm) val_str(x i64, depth int) string {
|
|
if depth > 16 {
|
|
return '...'
|
|
}
|
|
if v.is_str(x) && v.valid_handle(x) {
|
|
return v.strings[v.hand(x)]
|
|
}
|
|
if v.is_arr(x) && v.valid_arr_handle(x) {
|
|
a := v.arrays[v.hand(x)]
|
|
mut s := '['
|
|
limit := if a.len > 20 { 20 } else { a.len }
|
|
for i in 0..limit {
|
|
if i > 0 {
|
|
s += ', '
|
|
}
|
|
s += v.val_str(a[i], depth + 1)
|
|
}
|
|
if a.len > limit {
|
|
s += ', ...'
|
|
}
|
|
return s + ']'
|
|
}
|
|
if v.is_struct(x) && v.valid_struct_handle(x) {
|
|
s := v.structs[v.hand(x)]
|
|
mut out := '{'
|
|
limit := if s.fields.len > 20 { 20 } else { s.fields.len }
|
|
for i in 0..limit {
|
|
if i > 0 {
|
|
out += ', '
|
|
}
|
|
out += s.fields[i].name + ': ' + v.val_str(s.fields[i].val, depth + 1)
|
|
}
|
|
if s.fields.len > limit {
|
|
out += ', ...'
|
|
}
|
|
return out + '}'
|
|
}
|
|
return v.dec_int(x).str()
|
|
}
|
|
|
|
fn (mut v Vm) valid_handle(x i64) bool {
|
|
h := v.hand(x)
|
|
return h >= 0 && h < v.strings.len
|
|
}
|
|
|
|
fn (mut v Vm) valid_arr_handle(x i64) bool {
|
|
h := v.hand(x)
|
|
return h >= 0 && h < v.arrays.len
|
|
}
|
|
|
|
fn (mut v Vm) valid_struct_handle(x i64) bool {
|
|
h := v.hand(x)
|
|
return h >= 0 && h < v.structs.len
|
|
}
|
|
|
|
fn (mut v Vm) trace_op(op u8) {
|
|
name := match op {
|
|
op_halt { 'halt' }
|
|
op_push_i { 'push_int' }
|
|
op_push_s { 'push_str' }
|
|
op_load { 'load' }
|
|
op_store { 'store' }
|
|
op_pop { 'pop' }
|
|
op_dup { 'dup' }
|
|
op_add { 'add' }
|
|
op_sub { 'sub' }
|
|
op_mul { 'mul' }
|
|
op_div { 'div' }
|
|
op_mod { 'mod' }
|
|
op_neg { 'neg' }
|
|
op_eq { 'eq' }
|
|
op_ne { 'ne' }
|
|
op_lt { 'lt' }
|
|
op_le { 'le' }
|
|
op_gt { 'gt' }
|
|
op_ge { 'ge' }
|
|
op_and { 'and' }
|
|
op_or { 'or' }
|
|
op_not { 'not' }
|
|
op_jmp { 'jmp' }
|
|
op_jz { 'jz' }
|
|
op_jnz { 'jnz' }
|
|
op_call { 'call' }
|
|
op_ret { 'ret' }
|
|
op_retv { 'retv' }
|
|
op_print { 'print' }
|
|
op_println { 'println' }
|
|
op_assert { 'assert' }
|
|
op_enter { 'enter' }
|
|
op_mkarray { 'mkarray' }
|
|
op_aget { 'aget' }
|
|
op_aset { 'aset' }
|
|
op_alen { 'alen' }
|
|
op_apush { 'apush' }
|
|
op_mkstruct { 'mkstruct' }
|
|
op_sget { 'sget' }
|
|
op_sset { 'sset' }
|
|
op_shas { 'shas' }
|
|
op_sdel { 'sdel' }
|
|
op_slen { 'slen' }
|
|
op_skeys { 'skeys' }
|
|
else { '??' }
|
|
}
|
|
mut s := ''
|
|
for i in 0..v.sp {
|
|
if i > 0 {
|
|
s += ' '
|
|
}
|
|
if v.is_str(v.stack[i]) && v.valid_handle(v.stack[i]) {
|
|
s += '"${v.strings[v.hand(v.stack[i])]}"'
|
|
} else {
|
|
s += v.val_str(v.stack[i], 0)
|
|
}
|
|
}
|
|
println(' [ip=${v.ip:4}] ${name:-9} stack: [${s}]')
|
|
}
|