mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 14:57:18 +00:00
Production pass: floats, GC, type checking, and tooling
Adds floats, bitwise ops, UTF-8 strings with methods, try/catch, closures, generics validation, a compile-time type checker, a mark-and-sweep GC, source-level debug info, constant folding, and the repl/fmt/package-manager commands. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
// gc.v — mark-and-sweep garbage collector for the VuurRaaf VM.
|
||||
//
|
||||
// The collector runs between opcodes (never mid-instruction, so no live value
|
||||
// is ever hidden in a temporary). Roots are the value stack, which also holds
|
||||
// every frame's locals (they live at bp+idx). Arrays and structs are traced
|
||||
// transitively. The sweep compacts each pool and remaps surviving handles.
|
||||
//
|
||||
// String constants baked into the bytecode (op_push_s operands) live in
|
||||
// strings[0..const_strs] and are never collected; only runtime-allocated
|
||||
// strings participate in the cycle.
|
||||
module vm
|
||||
|
||||
const gc_alloc_trigger = 4096
|
||||
|
||||
// collect marks all heap values reachable from the stack, then sweeps and
|
||||
// compacts the pools, remapping handles on the stack and inside live
|
||||
// containers.
|
||||
fn (mut v Vm) collect() {
|
||||
// ---- mark ----
|
||||
mut str_mark := []bool{len: v.strings.len}
|
||||
mut arr_mark := []bool{len: v.arrays.len}
|
||||
mut struct_mark := []bool{len: v.structs.len}
|
||||
mut float_mark := []bool{len: v.floats.len}
|
||||
mut closure_mark := []bool{len: v.closures.len}
|
||||
for i in 0..v.sp {
|
||||
v.mark_value(v.stack[i], mut str_mark, mut arr_mark, mut struct_mark, mut float_mark, mut closure_mark)
|
||||
}
|
||||
// ---- remap tables: old index -> new index (-1 = collected) ----
|
||||
mut str_new := []int{len: v.strings.len, init: -1}
|
||||
mut arr_new := []int{len: v.arrays.len, init: -1}
|
||||
mut struct_new := []int{len: v.structs.len, init: -1}
|
||||
mut float_new := []int{len: v.floats.len, init: -1}
|
||||
mut closure_new := []int{len: v.closures.len, init: -1}
|
||||
mut nstr := v.const_strs
|
||||
for i in v.const_strs..v.strings.len {
|
||||
if str_mark[i] {
|
||||
str_new[i] = nstr
|
||||
nstr++
|
||||
}
|
||||
}
|
||||
mut narr := 0
|
||||
for i in 0..v.arrays.len {
|
||||
if arr_mark[i] {
|
||||
arr_new[i] = narr
|
||||
narr++
|
||||
}
|
||||
}
|
||||
mut nstruct := 0
|
||||
for i in 0..v.structs.len {
|
||||
if struct_mark[i] {
|
||||
struct_new[i] = nstruct
|
||||
nstruct++
|
||||
}
|
||||
}
|
||||
mut nfloat := 0
|
||||
for i in 0..v.floats.len {
|
||||
if float_mark[i] {
|
||||
float_new[i] = nfloat
|
||||
nfloat++
|
||||
}
|
||||
}
|
||||
mut nclosure := 0
|
||||
for i in 0..v.closures.len {
|
||||
if closure_mark[i] {
|
||||
closure_new[i] = nclosure
|
||||
nclosure++
|
||||
}
|
||||
}
|
||||
// ---- rewrite live references ----
|
||||
for i in 0..v.sp {
|
||||
v.stack[i] = v.remap(v.stack[i], str_new, arr_new, struct_new, float_new, closure_new)
|
||||
}
|
||||
for h in 0..v.arrays.len {
|
||||
if arr_mark[h] {
|
||||
for j in 0..v.arrays[h].len {
|
||||
v.arrays[h][j] = v.remap(v.arrays[h][j], str_new, arr_new, struct_new, float_new, closure_new)
|
||||
}
|
||||
}
|
||||
}
|
||||
for h in 0..v.structs.len {
|
||||
if struct_mark[h] {
|
||||
for j in 0..v.structs[h].fields.len {
|
||||
v.structs[h].fields[j].val = v.remap(v.structs[h].fields[j].val, str_new, arr_new, struct_new, float_new, closure_new)
|
||||
}
|
||||
}
|
||||
}
|
||||
// ---- compact pools ----
|
||||
mut strings := v.strings[..v.const_strs]
|
||||
for i in v.const_strs..v.strings.len {
|
||||
if str_mark[i] {
|
||||
strings << v.strings[i]
|
||||
}
|
||||
}
|
||||
v.strings = strings
|
||||
mut arrays := [][]i64{}
|
||||
for i in 0..v.arrays.len {
|
||||
if arr_mark[i] {
|
||||
arrays << v.arrays[i]
|
||||
}
|
||||
}
|
||||
v.arrays = arrays
|
||||
mut structs := []StructVal{}
|
||||
for i in 0..v.structs.len {
|
||||
if struct_mark[i] {
|
||||
structs << v.structs[i]
|
||||
}
|
||||
}
|
||||
v.structs = structs
|
||||
mut floats := []f64{}
|
||||
for i in 0..v.floats.len {
|
||||
if float_mark[i] {
|
||||
floats << v.floats[i]
|
||||
}
|
||||
}
|
||||
v.floats = floats
|
||||
mut closures := []Closure{}
|
||||
for i in 0..v.closures.len {
|
||||
if closure_mark[i] {
|
||||
closures << v.closures[i]
|
||||
}
|
||||
}
|
||||
v.closures = closures
|
||||
}
|
||||
|
||||
// mark_value traces a value and everything it references using an explicit
|
||||
// worklist (arrays of arrays can nest deeply; recursion could overflow).
|
||||
fn (mut v Vm) mark_value(x i64, mut str_mark []bool, mut arr_mark []bool, mut struct_mark []bool, mut float_mark []bool, mut closure_mark []bool) {
|
||||
mut work := []i64{}
|
||||
work << x
|
||||
for work.len > 0 {
|
||||
val := work.pop()
|
||||
match v.tag(val) {
|
||||
tag_str {
|
||||
h := v.hand(val)
|
||||
if h >= v.const_strs && h < str_mark.len && !str_mark[h] {
|
||||
str_mark[h] = true
|
||||
}
|
||||
}
|
||||
tag_arr {
|
||||
h := v.hand(val)
|
||||
if h >= 0 && h < arr_mark.len && !arr_mark[h] {
|
||||
arr_mark[h] = true
|
||||
for el in v.arrays[h] {
|
||||
work << el
|
||||
}
|
||||
}
|
||||
}
|
||||
tag_struct {
|
||||
h := v.hand(val)
|
||||
if h >= 0 && h < struct_mark.len && !struct_mark[h] {
|
||||
struct_mark[h] = true
|
||||
for f in v.structs[h].fields {
|
||||
work << f.val
|
||||
}
|
||||
}
|
||||
}
|
||||
tag_float {
|
||||
h := v.hand(val)
|
||||
if h >= 0 && h < float_mark.len && !float_mark[h] {
|
||||
float_mark[h] = true
|
||||
}
|
||||
}
|
||||
tag_closure {
|
||||
h := v.hand(val)
|
||||
if h >= 0 && h < closure_mark.len && !closure_mark[h] {
|
||||
closure_mark[h] = true
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remap translates a handle to its post-compaction index, leaving integers
|
||||
// and uncollected values untouched.
|
||||
fn (mut v Vm) remap(x i64, str_new []int, arr_new []int, struct_new []int, float_new []int, closure_new []int) i64 {
|
||||
match v.tag(x) {
|
||||
tag_str {
|
||||
h := v.hand(x)
|
||||
if h >= v.const_strs && h < str_new.len && str_new[h] >= 0 {
|
||||
return v.mkstr(str_new[h])
|
||||
}
|
||||
}
|
||||
tag_arr {
|
||||
h := v.hand(x)
|
||||
if h >= 0 && h < arr_new.len && arr_new[h] >= 0 {
|
||||
return v.mkarr(arr_new[h])
|
||||
}
|
||||
}
|
||||
tag_struct {
|
||||
h := v.hand(x)
|
||||
if h >= 0 && h < struct_new.len && struct_new[h] >= 0 {
|
||||
return v.mkstruct_handle(struct_new[h])
|
||||
}
|
||||
}
|
||||
tag_float {
|
||||
h := v.hand(x)
|
||||
if h >= 0 && h < float_new.len && float_new[h] >= 0 {
|
||||
return v.mkfloat(float_new[h])
|
||||
}
|
||||
}
|
||||
tag_closure {
|
||||
h := v.hand(x)
|
||||
if h >= 0 && h < closure_new.len && closure_new[h] >= 0 {
|
||||
return v.mkclosure(closure_new[h])
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
return x
|
||||
}
|
||||
+420
@@ -0,0 +1,420 @@
|
||||
// native.v — host builtins for the VuurRaaf VM.
|
||||
//
|
||||
// The op_native instruction carries a builtin id and an argument count. Each
|
||||
// builtin pops its arguments off the stack (left-to-right push order means the
|
||||
// last argument is on top) and pushes a single result (except exit(), which
|
||||
// halts the machine). This is where the language touches the host: file I/O,
|
||||
// environment, time, randomness, and the math/collection helpers.
|
||||
module vm
|
||||
|
||||
import os
|
||||
import math
|
||||
import rand
|
||||
import time
|
||||
|
||||
fn (mut v Vm) native(id int, _argc int) ! {
|
||||
match id {
|
||||
native_abs {
|
||||
x := v.pop()!
|
||||
if v.is_float(x) {
|
||||
v.push(v.push_float(math.abs(v.fval(x))))!
|
||||
} else {
|
||||
val := v.dec_int(x)
|
||||
v.push(v.enc_int(if val < 0 { -val } else { val }))!
|
||||
}
|
||||
}
|
||||
native_min {
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
if v.is_float(a) || v.is_float(b) {
|
||||
v.push(v.push_float(math.min(v.to_f64(a), v.to_f64(b))))!
|
||||
} else {
|
||||
x := v.dec_int(a)
|
||||
y := v.dec_int(b)
|
||||
v.push(v.enc_int(if x < y { x } else { y }))!
|
||||
}
|
||||
}
|
||||
native_max {
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
if v.is_float(a) || v.is_float(b) {
|
||||
v.push(v.push_float(math.max(v.to_f64(a), v.to_f64(b))))!
|
||||
} else {
|
||||
x := v.dec_int(a)
|
||||
y := v.dec_int(b)
|
||||
v.push(v.enc_int(if x > y { x } else { y }))!
|
||||
}
|
||||
}
|
||||
native_pow {
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
v.push(v.push_float(math.pow(v.to_f64(a), v.to_f64(b))))!
|
||||
}
|
||||
native_sqrt {
|
||||
x := v.pop()!
|
||||
v.push(v.push_float(math.sqrt(v.to_f64(x))))!
|
||||
}
|
||||
native_floor {
|
||||
x := v.pop()!
|
||||
v.push(v.enc_int(i64(math.floor(v.to_f64(x)))))!
|
||||
}
|
||||
native_ceil {
|
||||
x := v.pop()!
|
||||
v.push(v.enc_int(i64(math.ceil(v.to_f64(x)))))!
|
||||
}
|
||||
native_round {
|
||||
x := v.pop()!
|
||||
v.push(v.enc_int(i64(math.round(v.to_f64(x)))))!
|
||||
}
|
||||
native_rand {
|
||||
v.push(v.push_float(rand.f64()))!
|
||||
}
|
||||
native_rand_int {
|
||||
n := int(v.dec_int(v.pop()!))
|
||||
if n <= 0 {
|
||||
return error('rand_int() expects a positive bound')
|
||||
}
|
||||
v.push(v.enc_int(i64(rand.intn(n) or { return error('rand_int() failed') })))!
|
||||
}
|
||||
native_int {
|
||||
x := v.pop()!
|
||||
if v.is_str(x) && v.valid_handle(x) {
|
||||
v.push(v.enc_int(i64(v.strings[v.hand(x)].i64())))!
|
||||
} else if v.is_float(x) {
|
||||
v.push(v.enc_int(i64(v.fval(x))))!
|
||||
} else if v.is_arr(x) || v.is_struct(x) {
|
||||
return error('cannot convert a ${v.type_name(x)} to int')
|
||||
} else {
|
||||
v.push(x)!
|
||||
}
|
||||
}
|
||||
native_str {
|
||||
x := v.pop()!
|
||||
v.push(v.alloc_str(v.val_str(x, 0)))!
|
||||
}
|
||||
native_float {
|
||||
x := v.pop()!
|
||||
if v.is_str(x) && v.valid_handle(x) {
|
||||
v.push(v.push_float(v.strings[v.hand(x)].f64()))!
|
||||
} else if v.is_float(x) {
|
||||
v.push(x)!
|
||||
} else if v.is_arr(x) || v.is_struct(x) {
|
||||
return error('cannot convert a ${v.type_name(x)} to float')
|
||||
} else {
|
||||
v.push(v.push_float(f64(v.dec_int(x))))!
|
||||
}
|
||||
}
|
||||
native_type {
|
||||
x := v.pop()!
|
||||
v.push(v.alloc_str(v.type_name(x)))!
|
||||
}
|
||||
native_split {
|
||||
delim := v.pop_str()!
|
||||
s := v.pop_str()!
|
||||
parts := s.split(delim)
|
||||
mut arr := []i64{}
|
||||
for p in parts {
|
||||
v.strings << p
|
||||
arr << v.mkstr(v.strings.len - 1)
|
||||
}
|
||||
v.arrays << arr
|
||||
v.push(v.mkarr(v.arrays.len - 1))!
|
||||
}
|
||||
native_join {
|
||||
delim := v.pop_str()!
|
||||
h := v.pop()!
|
||||
if !v.is_arr(h) || !v.valid_arr_handle(h) {
|
||||
return error('join() expects an array as its first argument')
|
||||
}
|
||||
a := v.arrays[v.hand(h)]
|
||||
mut parts := []string{}
|
||||
for x in a {
|
||||
if v.is_str(x) && v.valid_handle(x) {
|
||||
parts << v.strings[v.hand(x)]
|
||||
} else {
|
||||
parts << v.val_str(x, 0)
|
||||
}
|
||||
}
|
||||
v.push(v.alloc_str(parts.join(delim)))!
|
||||
}
|
||||
native_contains {
|
||||
sub := v.pop_str()!
|
||||
s := v.pop_str()!
|
||||
v.push(v.enc_int(bool_i64(s.contains(sub))))!
|
||||
}
|
||||
native_starts_with {
|
||||
sub := v.pop_str()!
|
||||
s := v.pop_str()!
|
||||
v.push(v.enc_int(bool_i64(s.starts_with(sub))))!
|
||||
}
|
||||
native_ends_with {
|
||||
sub := v.pop_str()!
|
||||
s := v.pop_str()!
|
||||
v.push(v.enc_int(bool_i64(s.ends_with(sub))))!
|
||||
}
|
||||
native_trim {
|
||||
s := v.pop_str()!
|
||||
v.push(v.alloc_str(s.trim_space()))!
|
||||
}
|
||||
native_lower {
|
||||
s := v.pop_str()!
|
||||
v.push(v.alloc_str(s.to_lower()))!
|
||||
}
|
||||
native_upper {
|
||||
s := v.pop_str()!
|
||||
v.push(v.alloc_str(s.to_upper()))!
|
||||
}
|
||||
native_pop {
|
||||
h := v.pop()!
|
||||
if !v.is_arr(h) || !v.valid_arr_handle(h) {
|
||||
return error('pop() expects an array')
|
||||
}
|
||||
mut a := v.arrays[v.hand(h)]
|
||||
if a.len == 0 {
|
||||
return error('pop() on an empty array')
|
||||
}
|
||||
val := a[a.len - 1]
|
||||
v.arrays[v.hand(h)] = a[..a.len - 1]
|
||||
v.push(val)!
|
||||
}
|
||||
native_insert {
|
||||
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('insert() expects an array as its first argument')
|
||||
}
|
||||
a := v.arrays[v.hand(h)]
|
||||
if idx < 0 || idx > a.len {
|
||||
return error('insert index ${idx} out of bounds (len ${a.len})')
|
||||
}
|
||||
mut na := []i64{}
|
||||
for i, x in a {
|
||||
if i == idx {
|
||||
na << val
|
||||
}
|
||||
na << x
|
||||
}
|
||||
if idx == a.len {
|
||||
na << val
|
||||
}
|
||||
v.arrays[v.hand(h)] = na
|
||||
v.push(h)!
|
||||
}
|
||||
native_remove {
|
||||
idx := int(v.dec_int(v.pop()!))
|
||||
h := v.pop()!
|
||||
if !v.is_arr(h) || !v.valid_arr_handle(h) {
|
||||
return error('remove() expects an array as its first argument')
|
||||
}
|
||||
a := v.arrays[v.hand(h)]
|
||||
if idx < 0 || idx >= a.len {
|
||||
return error('remove index ${idx} out of bounds (len ${a.len})')
|
||||
}
|
||||
mut na := []i64{}
|
||||
for i, x in a {
|
||||
if i != idx {
|
||||
na << x
|
||||
}
|
||||
}
|
||||
v.arrays[v.hand(h)] = na
|
||||
v.push(h)!
|
||||
}
|
||||
native_sort {
|
||||
h := v.pop()!
|
||||
if !v.is_arr(h) || !v.valid_arr_handle(h) {
|
||||
return error('sort() expects an array')
|
||||
}
|
||||
mut a := v.arrays[v.hand(h)]
|
||||
// insertion sort by numeric value
|
||||
for i in 1..a.len {
|
||||
key := a[i]
|
||||
mut j := i - 1
|
||||
for j >= 0 && v.num_gt(a[j], key) {
|
||||
a[j + 1] = a[j]
|
||||
j--
|
||||
}
|
||||
a[j + 1] = key
|
||||
}
|
||||
v.push(h)!
|
||||
}
|
||||
native_clone {
|
||||
x := v.pop()!
|
||||
if v.is_arr(x) && v.valid_arr_handle(x) {
|
||||
v.arrays << v.arrays[v.hand(x)].clone()
|
||||
v.push(v.mkarr(v.arrays.len - 1))!
|
||||
} else if v.is_struct(x) && v.valid_struct_handle(x) {
|
||||
s := v.structs[v.hand(x)]
|
||||
v.structs << StructVal{ fields: s.fields.clone() }
|
||||
v.push(v.mkstruct_handle(v.structs.len - 1))!
|
||||
} else if v.is_str(x) && v.valid_handle(x) {
|
||||
v.push(v.alloc_str(v.strings[v.hand(x)]))!
|
||||
} else if v.is_float(x) {
|
||||
v.push(v.push_float(v.fval(x)))!
|
||||
} else {
|
||||
v.push(x)!
|
||||
}
|
||||
}
|
||||
native_reverse {
|
||||
h := v.pop()!
|
||||
if !v.is_arr(h) || !v.valid_arr_handle(h) {
|
||||
return error('reverse() expects an array')
|
||||
}
|
||||
mut a := v.arrays[v.hand(h)]
|
||||
for i in 0..a.len / 2 {
|
||||
a[i], a[a.len - 1 - i] = a[a.len - 1 - i], a[i]
|
||||
}
|
||||
v.push(h)!
|
||||
}
|
||||
native_index_of {
|
||||
val := v.pop()!
|
||||
h := v.pop()!
|
||||
if v.is_arr(h) && v.valid_arr_handle(h) {
|
||||
a := v.arrays[v.hand(h)]
|
||||
for i, x in a {
|
||||
if v.cmp(x, val, '==')! == 1 {
|
||||
v.push(v.enc_int(i64(i)))!
|
||||
return
|
||||
}
|
||||
}
|
||||
v.push(v.enc_int(-1))!
|
||||
return
|
||||
}
|
||||
if v.is_str(h) && v.valid_handle(h) {
|
||||
if !v.is_str(val) || !v.valid_handle(val) {
|
||||
return error('index_of() on a string expects a string needle')
|
||||
}
|
||||
s := v.strings[v.hand(h)]
|
||||
needle := v.strings[v.hand(val)]
|
||||
byte_idx := s.index(needle) or { -1 }
|
||||
if byte_idx < 0 {
|
||||
v.push(v.enc_int(-1))!
|
||||
return
|
||||
}
|
||||
// convert byte offset to a rune index so UTF-8 strings count characters
|
||||
rune_idx := s[..byte_idx].runes().len
|
||||
v.push(v.enc_int(i64(rune_idx)))!
|
||||
return
|
||||
}
|
||||
return error('index_of() expects an array or string as its first argument')
|
||||
}
|
||||
native_args {
|
||||
mut arr := []i64{}
|
||||
for s in v.prog_args {
|
||||
v.strings << s
|
||||
arr << v.mkstr(v.strings.len - 1)
|
||||
}
|
||||
v.arrays << arr
|
||||
v.push(v.mkarr(v.arrays.len - 1))!
|
||||
}
|
||||
native_getenv {
|
||||
name := v.pop_str()!
|
||||
v.push(v.alloc_str(os.getenv(name)))!
|
||||
}
|
||||
native_setenv {
|
||||
val := v.pop_str()!
|
||||
name := v.pop_str()!
|
||||
os.setenv(name, val, true)
|
||||
v.push(v.enc_int(0))!
|
||||
}
|
||||
native_exit {
|
||||
code := int(v.dec_int(v.pop()!))
|
||||
v.exit_code = i64(code)
|
||||
v.did_exit = true
|
||||
v.halted = true
|
||||
}
|
||||
native_time {
|
||||
v.push(v.push_float(f64(time.now().unix_milli()) / 1000.0))!
|
||||
}
|
||||
native_sleep {
|
||||
ms := int(v.dec_int(v.pop()!))
|
||||
time.sleep(time.Duration(ms) * time.millisecond)
|
||||
v.push(v.enc_int(0))!
|
||||
}
|
||||
native_read_file {
|
||||
path := v.pop_str()!
|
||||
content := os.read_file(path) or { return error('cannot read file "${path}": ${err}') }
|
||||
v.push(v.alloc_str(content))!
|
||||
}
|
||||
native_write_file {
|
||||
content := v.pop_str()!
|
||||
path := v.pop_str()!
|
||||
os.write_file(path, content) or { return error('cannot write file "${path}": ${err}') }
|
||||
v.push(v.enc_int(0))!
|
||||
}
|
||||
native_eprint {
|
||||
x := v.pop()!
|
||||
eprintln(v.val_str(x, 0))
|
||||
}
|
||||
else {
|
||||
return error('unknown native builtin ${id}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pop_str pops the top value and requires it to be a string.
|
||||
fn (mut v Vm) pop_str() !string {
|
||||
x := v.pop()!
|
||||
if !v.is_str(x) || !v.valid_handle(x) {
|
||||
return error('expected a string argument')
|
||||
}
|
||||
return v.strings[v.hand(x)]
|
||||
}
|
||||
|
||||
// type_name returns the type label of a tagged value.
|
||||
fn (mut v Vm) type_name(x i64) string {
|
||||
if v.is_str(x) {
|
||||
return 'string'
|
||||
}
|
||||
if v.is_float(x) {
|
||||
return 'float'
|
||||
}
|
||||
if v.is_arr(x) {
|
||||
return 'array'
|
||||
}
|
||||
if v.is_struct(x) {
|
||||
return 'struct'
|
||||
}
|
||||
return 'int'
|
||||
}
|
||||
|
||||
// str_method dispatches a string method call. The receiver was pushed before
|
||||
// the arguments, so it is the first argument from the native builtin's point
|
||||
// of view; delegating keeps the behavior identical to the free-function forms.
|
||||
fn (mut v Vm) str_method(name string, argc int) ! {
|
||||
bid := match name {
|
||||
'to_upper' { native_upper }
|
||||
'to_lower' { native_lower }
|
||||
'trim' { native_trim }
|
||||
'contains' { native_contains }
|
||||
'starts_with' { native_starts_with }
|
||||
'ends_with' { native_ends_with }
|
||||
'split' { native_split }
|
||||
'index_of' { native_index_of }
|
||||
'to_int' { native_int }
|
||||
'to_float' { native_float }
|
||||
else { -1 }
|
||||
}
|
||||
if bid >= 0 {
|
||||
// native builtins pop the first argument (the receiver) last
|
||||
v.native(bid, argc + 1)!
|
||||
return
|
||||
}
|
||||
if name == 'len' {
|
||||
h := v.pop()!
|
||||
if !v.is_str(h) || !v.valid_handle(h) {
|
||||
return error('len() on a non-string value')
|
||||
}
|
||||
v.push(v.enc_int(i64(v.strings[v.hand(h)].runes().len)))!
|
||||
return
|
||||
}
|
||||
return error('unknown string method "${name}"')
|
||||
}
|
||||
|
||||
// num_gt compares two values by their numeric value (int or float).
|
||||
fn (mut v Vm) num_gt(x i64, y i64) bool {
|
||||
if v.is_float(x) || v.is_float(y) {
|
||||
return v.to_f64(x) > v.to_f64(y)
|
||||
}
|
||||
return v.dec_int(x) > v.dec_int(y)
|
||||
}
|
||||
@@ -47,3 +47,61 @@ 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 op_slice = u8(44) // slice(arr/str, start, end) -> sliced arr/str
|
||||
const op_push_f = u8(45) // push a float literal (8-byte little-endian f64)
|
||||
const op_native = u8(46) // call a host builtin: <id:i64> <argc:i64>
|
||||
const op_and_b = u8(47) // bitwise AND
|
||||
const op_or_b = u8(48) // bitwise OR
|
||||
const op_xor = u8(49) // bitwise XOR
|
||||
const op_shl = u8(50) // shift left
|
||||
const op_shr = u8(51) // shift right
|
||||
const op_not_b = u8(52) // bitwise NOT
|
||||
const op_try = u8(53) // push handler: <catch_ip:i64>
|
||||
const op_throw = u8(54) // throw: pop error, unwind to nearest handler, push error
|
||||
const op_catch_done = u8(55) // pop handler (normal completion)
|
||||
const op_closure = u8(56) // push closure: <entry:i64>
|
||||
const op_call_closure = u8(57) // call closure: <argc:i64>
|
||||
const op_argc = u8(58) // push the current frame's arg count
|
||||
const op_load_dyn = u8(59) // pop idx, push stack[bp + idx]
|
||||
const op_varargs = u8(60) // <named:i64> <dst:i64> — collect args[named..argc-1] into array at local dst
|
||||
const op_str_method = u8(61) // <name:str> <argc:i64> — call a string method (s.len(), s.contains(x), ...)
|
||||
|
||||
// native builtin ids (the operand to op_native)
|
||||
const native_abs = 100
|
||||
const native_min = 101
|
||||
const native_max = 102
|
||||
const native_pow = 103
|
||||
const native_sqrt = 104
|
||||
const native_floor = 105
|
||||
const native_ceil = 106
|
||||
const native_round = 107
|
||||
const native_rand = 108
|
||||
const native_rand_int = 109
|
||||
const native_int = 110
|
||||
const native_str = 111
|
||||
const native_float = 112
|
||||
const native_type = 113
|
||||
const native_split = 114
|
||||
const native_join = 115
|
||||
const native_contains = 116
|
||||
const native_starts_with = 117
|
||||
const native_ends_with = 118
|
||||
const native_trim = 119
|
||||
const native_lower = 120
|
||||
const native_upper = 121
|
||||
const native_pop = 122
|
||||
const native_insert = 123
|
||||
const native_remove = 124
|
||||
const native_sort = 125
|
||||
const native_clone = 126
|
||||
const native_reverse = 127
|
||||
const native_index_of = 128
|
||||
const native_args = 129
|
||||
const native_getenv = 130
|
||||
const native_setenv = 131
|
||||
const native_exit = 132
|
||||
const native_time = 133
|
||||
const native_sleep = 134
|
||||
const native_read_file = 135
|
||||
const native_write_file = 136
|
||||
const native_eprint = 137
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// ops.v — arithmetic, comparison, and string operations for the VuurRaaf VM.
|
||||
module vm
|
||||
|
||||
import math
|
||||
|
||||
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 +')
|
||||
@@ -17,18 +19,61 @@ fn (mut v Vm) add(a i64, b i64) !i64 {
|
||||
if v.is_str(b) {
|
||||
return v.alloc_str(v.num_str(a) + v.strings[v.hand(b)])
|
||||
}
|
||||
if v.is_float(a) || v.is_float(b) {
|
||||
return v.push_float(v.to_f64(a) + v.to_f64(b))
|
||||
}
|
||||
return v.enc_int(v.dec_int(a) + v.dec_int(b))
|
||||
}
|
||||
|
||||
// to_f64 promotes an integer or float tagged value to f64.
|
||||
fn (mut v Vm) to_f64(x i64) f64 {
|
||||
if v.is_float(x) {
|
||||
return v.fval(x)
|
||||
}
|
||||
return f64(v.dec_int(x))
|
||||
}
|
||||
|
||||
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 {
|
||||
if v.is_float(x) {
|
||||
return fmt_float(v.fval(x))
|
||||
}
|
||||
return v.dec_int(x).str()
|
||||
}
|
||||
|
||||
// fmt_float renders an f64 nicely: integral values lose the trailing ".0",
|
||||
// -0 collapses to 0, and most floats avoid V's default scientific notation.
|
||||
fn fmt_float(f f64) string {
|
||||
if f == 0.0 {
|
||||
return '0'
|
||||
}
|
||||
if math.is_nan(f) {
|
||||
return 'NaN'
|
||||
}
|
||||
if math.is_inf(f, 1) {
|
||||
return 'Inf'
|
||||
}
|
||||
if math.is_inf(f, -1) {
|
||||
return '-Inf'
|
||||
}
|
||||
if f == math.floor(f) && math.abs(f) < 1e18 {
|
||||
return i64(f).str()
|
||||
}
|
||||
if math.abs(f) < 1e18 {
|
||||
mut s := '${f:.14f}'
|
||||
s = s.trim_right('0').trim_right('.')
|
||||
if s == '' {
|
||||
return '0'
|
||||
}
|
||||
return s
|
||||
}
|
||||
return f.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}"')
|
||||
@@ -39,6 +84,33 @@ fn (mut v Vm) arith(a i64, b i64, op string) !i64 {
|
||||
if v.is_struct(a) || v.is_struct(b) {
|
||||
return error('cannot use structs with "${op}"')
|
||||
}
|
||||
if v.is_float(a) || v.is_float(b) {
|
||||
x := v.to_f64(a)
|
||||
y := v.to_f64(b)
|
||||
match op {
|
||||
'-' {
|
||||
return v.push_float(x - y)
|
||||
}
|
||||
'*' {
|
||||
return v.push_float(x * y)
|
||||
}
|
||||
'/' {
|
||||
if y == 0.0 {
|
||||
return error('division by zero')
|
||||
}
|
||||
return v.push_float(x / y)
|
||||
}
|
||||
'%' {
|
||||
if y == 0.0 {
|
||||
return error('division by zero')
|
||||
}
|
||||
return v.push_float(math.fmod(x, y))
|
||||
}
|
||||
else {
|
||||
return error('internal: bad arith op "${op}"')
|
||||
}
|
||||
}
|
||||
}
|
||||
x := v.dec_int(a)
|
||||
y := v.dec_int(b)
|
||||
match op {
|
||||
@@ -97,6 +169,19 @@ fn (mut v Vm) cmp(a i64, b i64, op string) !i64 {
|
||||
if v.is_str(a) || v.is_str(b) {
|
||||
return error('cannot compare a string and a number')
|
||||
}
|
||||
if v.is_float(a) || v.is_float(b) {
|
||||
x := v.to_f64(a)
|
||||
y := v.to_f64(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}"') }
|
||||
})
|
||||
}
|
||||
x := v.dec_int(a)
|
||||
y := v.dec_int(b)
|
||||
return bool_i64(match op {
|
||||
|
||||
+30
-3
@@ -44,6 +44,12 @@ fn (mut v Vm) val_str(x i64, depth int) string {
|
||||
}
|
||||
return out + '}'
|
||||
}
|
||||
if v.is_float(x) && v.valid_float_handle(x) {
|
||||
return fmt_float(v.fval(x))
|
||||
}
|
||||
if v.is_closure(x) && v.valid_closure_handle(x) {
|
||||
return '<fn@${v.closures[v.hand(x)].entry}>'
|
||||
}
|
||||
return v.dec_int(x).str()
|
||||
}
|
||||
|
||||
@@ -52,6 +58,7 @@ fn (mut v Vm) trace_op(op u8) {
|
||||
op_halt { 'halt' }
|
||||
op_push_i { 'push_int' }
|
||||
op_push_s { 'push_str' }
|
||||
op_push_f { 'push_float' }
|
||||
op_load { 'load' }
|
||||
op_store { 'store' }
|
||||
op_pop { 'pop' }
|
||||
@@ -93,6 +100,23 @@ fn (mut v Vm) trace_op(op u8) {
|
||||
op_sdel { 'sdel' }
|
||||
op_slen { 'slen' }
|
||||
op_skeys { 'skeys' }
|
||||
op_slice { 'slice' }
|
||||
op_native { 'native' }
|
||||
op_and_b { 'and_b' }
|
||||
op_or_b { 'or_b' }
|
||||
op_xor { 'xor' }
|
||||
op_shl { 'shl' }
|
||||
op_shr { 'shr' }
|
||||
op_not_b { 'not_b' }
|
||||
op_try { 'try' }
|
||||
op_throw { 'throw' }
|
||||
op_catch_done { 'catch_done' }
|
||||
op_closure { 'closure' }
|
||||
op_call_closure { 'call_closure' }
|
||||
op_argc { 'argc' }
|
||||
op_load_dyn { 'load_dyn' }
|
||||
op_varargs { 'varargs' }
|
||||
op_str_method { 'str_method' }
|
||||
else { '??' }
|
||||
}
|
||||
mut s := ''
|
||||
@@ -100,10 +124,13 @@ fn (mut v Vm) trace_op(op u8) {
|
||||
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])]}"'
|
||||
x := v.stack[i]
|
||||
if v.is_str(x) && v.valid_handle(x) {
|
||||
s += '"${v.strings[v.hand(x)]}"'
|
||||
} else if v.is_closure(x) {
|
||||
s += '<fn>'
|
||||
} else {
|
||||
s += v.val_str(v.stack[i], 0)
|
||||
s += v.val_str(x, 0)
|
||||
}
|
||||
}
|
||||
println(' [ip=${v.ip:4}] ${name:-9} stack: [${s}]')
|
||||
|
||||
+32
-10
@@ -1,6 +1,8 @@
|
||||
// types.v — core types and constants for the VuurRaaf VM.
|
||||
module vm
|
||||
|
||||
import obj
|
||||
|
||||
const stack_cap = 65536
|
||||
|
||||
// Field is one `name: value` entry of a struct value.
|
||||
@@ -15,18 +17,38 @@ mut:
|
||||
fields []Field
|
||||
}
|
||||
|
||||
// Handler records a try/catch handler pushed at runtime.
|
||||
struct Closure {
|
||||
entry int // code IP of the function body
|
||||
}
|
||||
|
||||
struct Handler {
|
||||
ip int // catch_ip
|
||||
bp int // frame base at try point
|
||||
sp int // stack pointer right after the handler record
|
||||
}
|
||||
|
||||
struct Vm {
|
||||
mut:
|
||||
code []u8
|
||||
strings []string
|
||||
arrays [][]i64
|
||||
structs []StructVal
|
||||
stack []i64
|
||||
sp int
|
||||
bp int
|
||||
ip int
|
||||
trace bool
|
||||
halted bool
|
||||
code []u8
|
||||
strings []string
|
||||
arrays [][]i64
|
||||
structs []StructVal
|
||||
floats []f64
|
||||
closures []Closure
|
||||
stack []i64
|
||||
sp int
|
||||
bp int
|
||||
ip int
|
||||
trace bool
|
||||
halted bool
|
||||
prog_args []string
|
||||
exit_code i64
|
||||
did_exit bool
|
||||
handlers []Handler
|
||||
lines []obj.LineInfo // debug info: code offset -> source line
|
||||
const_strs int // strings[0..const_strs] are bytecode constants, never collected
|
||||
last_heap int // heap size at the last GC check (allocation trigger)
|
||||
}
|
||||
|
||||
fn bool_i64(b bool) i64 {
|
||||
|
||||
+90
-16
@@ -1,49 +1,101 @@
|
||||
// value.v — tagged value encoding and helpers for the VuurRaaf VM.
|
||||
//
|
||||
// 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)
|
||||
// Stack values are 64-bit tagged integers with three tag bits:
|
||||
// low bits 000 -> encoded integer (value = raw << 3)
|
||||
// low bits 001 -> string handle (handle = value >> 3, into v.strings)
|
||||
// low bits 010 -> struct handle (handle = value >> 3, into v.structs)
|
||||
// low bits 011 -> array handle (handle = value >> 3, into v.arrays)
|
||||
// low bits 100 -> float handle (handle = value >> 3, into v.floats)
|
||||
module vm
|
||||
|
||||
fn (mut v Vm) is_str(x i64) bool {
|
||||
return x & 3 == 1
|
||||
const tag_mask = u64(7)
|
||||
const tag_int = u64(0)
|
||||
const tag_str = u64(1)
|
||||
const tag_struct = u64(2)
|
||||
const tag_arr = u64(3)
|
||||
const tag_float = u64(4)
|
||||
const tag_closure = u64(5)
|
||||
|
||||
fn (mut v Vm) tag(x i64) u64 {
|
||||
return u64(x) & tag_mask
|
||||
}
|
||||
|
||||
fn (mut v Vm) is_arr(x i64) bool {
|
||||
return x & 3 == 3
|
||||
fn (mut v Vm) is_int(x i64) bool {
|
||||
return u64(x) & tag_mask == tag_int
|
||||
}
|
||||
|
||||
fn (mut v Vm) is_str(x i64) bool {
|
||||
return u64(x) & tag_mask == tag_str
|
||||
}
|
||||
|
||||
fn (mut v Vm) is_struct(x i64) bool {
|
||||
return x & 3 == 2
|
||||
return u64(x) & tag_mask == tag_struct
|
||||
}
|
||||
|
||||
fn (mut v Vm) is_arr(x i64) bool {
|
||||
return u64(x) & tag_mask == tag_arr
|
||||
}
|
||||
|
||||
fn (mut v Vm) is_float(x i64) bool {
|
||||
return u64(x) & tag_mask == tag_float
|
||||
}
|
||||
|
||||
fn (mut v Vm) is_closure(x i64) bool {
|
||||
return u64(x) & tag_mask == tag_closure
|
||||
}
|
||||
|
||||
// is_num reports whether x is an integer (tag 0). Floats are a distinct type.
|
||||
fn (mut v Vm) is_num(x i64) bool {
|
||||
return u64(x) & tag_mask == tag_int
|
||||
}
|
||||
|
||||
fn (mut v Vm) enc_int(x i64) i64 {
|
||||
return u64(x) << 2
|
||||
return i64(u64(x) << 3)
|
||||
}
|
||||
|
||||
fn (mut v Vm) dec_int(x i64) i64 {
|
||||
return x >> 2
|
||||
return x >> 3
|
||||
}
|
||||
|
||||
fn (mut v Vm) hand(x i64) int {
|
||||
return int(x >> 2)
|
||||
return int(x >> 3)
|
||||
}
|
||||
|
||||
fn (mut v Vm) mkstr(idx int) i64 {
|
||||
return (u64(idx) << 2) | 1
|
||||
return i64((u64(idx) << 3) | tag_str)
|
||||
}
|
||||
|
||||
fn (mut v Vm) mkarr(idx int) i64 {
|
||||
return (u64(idx) << 2) | 3
|
||||
return i64((u64(idx) << 3) | tag_arr)
|
||||
}
|
||||
|
||||
fn (mut v Vm) mkstruct_handle(idx int) i64 {
|
||||
return (u64(idx) << 2) | 2
|
||||
return i64((u64(idx) << 3) | tag_struct)
|
||||
}
|
||||
|
||||
fn (mut v Vm) mkfloat(idx int) i64 {
|
||||
return i64((u64(idx) << 3) | tag_float)
|
||||
}
|
||||
|
||||
fn (mut v Vm) mkclosure(idx int) i64 {
|
||||
return i64((u64(idx) << 3) | tag_closure)
|
||||
}
|
||||
|
||||
// push_float interns a float into the pool and returns its tagged handle.
|
||||
fn (mut v Vm) push_float(f f64) i64 {
|
||||
v.floats << f
|
||||
return v.mkfloat(v.floats.len - 1)
|
||||
}
|
||||
|
||||
// fval returns the f64 value of a float handle.
|
||||
fn (mut v Vm) fval(x i64) f64 {
|
||||
return v.floats[v.hand(x)]
|
||||
}
|
||||
|
||||
fn (mut v Vm) truthy(x i64) bool {
|
||||
if v.is_float(x) {
|
||||
return v.fval(x) != 0.0
|
||||
}
|
||||
return x != 0
|
||||
}
|
||||
|
||||
@@ -61,3 +113,25 @@ 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) valid_float_handle(x i64) bool {
|
||||
h := v.hand(x)
|
||||
return h >= 0 && h < v.floats.len
|
||||
}
|
||||
|
||||
fn (mut v Vm) valid_closure_handle(x i64) bool {
|
||||
h := v.hand(x)
|
||||
return h >= 0 && h < v.closures.len
|
||||
}
|
||||
|
||||
// valid_handle_for bounds-checks a handle against the pool matching its tag.
|
||||
fn (mut v Vm) valid_handle_for(x i64) bool {
|
||||
return match v.tag(x) {
|
||||
tag_str { v.valid_handle(x) }
|
||||
tag_struct { v.valid_struct_handle(x) }
|
||||
tag_arr { v.valid_arr_handle(x) }
|
||||
tag_float { v.valid_float_handle(x) }
|
||||
tag_closure { v.valid_closure_handle(x) }
|
||||
else { true }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,25 @@
|
||||
module vm
|
||||
|
||||
import obj
|
||||
import math
|
||||
|
||||
// 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 {
|
||||
return run_with_args(bin, entry, trace, []string{})
|
||||
}
|
||||
|
||||
// run_with_args is run() with command-line arguments exposed to the program
|
||||
// via the `args()` builtin.
|
||||
pub fn run_with_args(bin obj.Bin, entry string, trace bool, args []string) !i64 {
|
||||
mut v := Vm{
|
||||
code: bin.code
|
||||
strings: bin.strings.clone()
|
||||
stack: []i64{len: stack_cap}
|
||||
trace: trace
|
||||
code: bin.code
|
||||
strings: bin.strings.clone()
|
||||
stack: []i64{len: stack_cap}
|
||||
trace: trace
|
||||
prog_args: args
|
||||
lines: bin.lines
|
||||
const_strs: bin.strings.len
|
||||
}
|
||||
mut entry_ip := -1
|
||||
for f in bin.fns {
|
||||
@@ -38,15 +48,43 @@ pub fn run(bin obj.Bin, entry string, trace bool) !i64 {
|
||||
v.sp++
|
||||
v.bp = v.sp
|
||||
v.ip = entry_ip
|
||||
v.exec()!
|
||||
v.exec() or {
|
||||
return error('${err.msg()} at ${v.where()}')
|
||||
}
|
||||
if v.did_exit {
|
||||
return v.exit_code
|
||||
}
|
||||
if v.sp > 0 {
|
||||
return v.dec_int(v.stack[0])
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// where returns a source-level location for the current instruction pointer:
|
||||
// `line 12 (ip 345)` when debug info is available, otherwise just `(ip 345)`.
|
||||
fn (v Vm) where() string {
|
||||
// line table entries are recorded in code order, so walk backwards from
|
||||
// the most recent entry to find the last one at or before v.ip
|
||||
for i := v.lines.len - 1; i >= 0; i-- {
|
||||
if v.ip >= int(v.lines[i].off) {
|
||||
return 'line ${v.lines[i].line} (ip ${v.ip})'
|
||||
}
|
||||
}
|
||||
return '(ip ${v.ip})'
|
||||
}
|
||||
|
||||
fn (mut v Vm) exec() ! {
|
||||
for !v.halted {
|
||||
// garbage collection: runs between opcodes when the heap has grown by
|
||||
// gc_alloc_trigger entries since the last collection, so no live value
|
||||
// is ever mid-flight in an instruction handler
|
||||
heap := v.strings.len + v.arrays.len + v.structs.len + v.floats.len + v.closures.len
|
||||
if heap > v.last_heap + gc_alloc_trigger {
|
||||
v.collect()
|
||||
v.last_heap = v.strings.len + v.arrays.len + v.structs.len + v.floats.len + v.closures.len
|
||||
} else {
|
||||
v.last_heap = heap
|
||||
}
|
||||
op := v.code[v.ip]
|
||||
if v.trace {
|
||||
v.trace_op(op)
|
||||
@@ -64,6 +102,11 @@ fn (mut v Vm) exec() ! {
|
||||
idx := int(v.read_i64())
|
||||
v.push(v.mkstr(idx))!
|
||||
}
|
||||
op_push_f {
|
||||
v.ip++
|
||||
f := v.read_f64()
|
||||
v.push(v.push_float(f))!
|
||||
}
|
||||
op_load {
|
||||
v.ip++
|
||||
idx := int(v.read_i64())
|
||||
@@ -117,16 +160,17 @@ fn (mut v Vm) exec() ! {
|
||||
op_neg {
|
||||
v.ip++
|
||||
a := v.pop()!
|
||||
if v.is_str(a) {
|
||||
if v.is_float(a) {
|
||||
v.push(v.push_float(-v.fval(a)))!
|
||||
} else if v.is_str(a) {
|
||||
return error('cannot negate a string')
|
||||
}
|
||||
if v.is_arr(a) {
|
||||
} else if v.is_arr(a) {
|
||||
return error('cannot negate an array')
|
||||
}
|
||||
if v.is_struct(a) {
|
||||
} else if v.is_struct(a) {
|
||||
return error('cannot negate a struct')
|
||||
} else {
|
||||
v.push(v.enc_int(-v.dec_int(a)))!
|
||||
}
|
||||
v.push(v.enc_int(-v.dec_int(a)))!
|
||||
}
|
||||
op_eq {
|
||||
v.ip++
|
||||
@@ -247,14 +291,22 @@ fn (mut v Vm) exec() ! {
|
||||
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')
|
||||
if v.is_arr(h) && v.valid_arr_handle(h) {
|
||||
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])!
|
||||
} else if v.is_str(h) && v.valid_handle(h) {
|
||||
// rune-based string indexing: s[i] is the i-th character
|
||||
runes := v.strings[v.hand(h)].runes()
|
||||
if idx < 0 || idx >= runes.len {
|
||||
return error('string index ${idx} out of bounds (len ${runes.len})')
|
||||
}
|
||||
v.push(v.alloc_str(runes[idx].str()))!
|
||||
} else {
|
||||
return error('indexing a non-array, non-string 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++
|
||||
@@ -276,8 +328,10 @@ fn (mut v Vm) exec() ! {
|
||||
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 if v.is_str(h) && v.valid_handle(h) {
|
||||
v.push(v.enc_int(i64(v.strings[v.hand(h)].runes().len)))!
|
||||
} else {
|
||||
return error('len() on a non-array, non-struct value')
|
||||
return error('len() on a non-array, non-struct, non-string value')
|
||||
}
|
||||
}
|
||||
op_apush {
|
||||
@@ -370,6 +424,139 @@ fn (mut v Vm) exec() ! {
|
||||
op_skeys {
|
||||
v.op_skeys()!
|
||||
}
|
||||
op_slice {
|
||||
v.op_slice()!
|
||||
}
|
||||
op_native {
|
||||
v.ip++
|
||||
id := int(v.read_i64())
|
||||
argc := int(v.read_i64())
|
||||
v.native(id, argc)!
|
||||
}
|
||||
op_and_b {
|
||||
v.ip++
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
v.push(v.enc_int(v.dec_int(a) & v.dec_int(b)))!
|
||||
}
|
||||
op_or_b {
|
||||
v.ip++
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
v.push(v.enc_int(v.dec_int(a) | v.dec_int(b)))!
|
||||
}
|
||||
op_xor {
|
||||
v.ip++
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
v.push(v.enc_int(v.dec_int(a) ^ v.dec_int(b)))!
|
||||
}
|
||||
op_shl {
|
||||
v.ip++
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
x := v.dec_int(a)
|
||||
y := u32(v.dec_int(b))
|
||||
v.push(v.enc_int(x << y))!
|
||||
}
|
||||
op_shr {
|
||||
v.ip++
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
x := v.dec_int(a)
|
||||
y := u32(v.dec_int(b))
|
||||
v.push(v.enc_int(x >> y))!
|
||||
}
|
||||
op_not_b {
|
||||
v.ip++
|
||||
a := v.pop()!
|
||||
v.push(v.enc_int(~v.dec_int(a)))!
|
||||
}
|
||||
op_try {
|
||||
v.ip++
|
||||
catch_ip := int(v.read_i64())
|
||||
v.handlers << Handler{ ip: catch_ip, bp: v.bp, sp: v.sp }
|
||||
}
|
||||
op_throw {
|
||||
v.ip++
|
||||
err_val := v.pop()!
|
||||
if v.handlers.len == 0 {
|
||||
return error('unhandled throw: ${v.val_str(err_val, 0)}')
|
||||
}
|
||||
h := v.handlers[v.handlers.len - 1]
|
||||
v.handlers.delete_last()
|
||||
v.bp = h.bp
|
||||
v.sp = h.sp
|
||||
v.push(err_val)!
|
||||
v.ip = h.ip
|
||||
}
|
||||
op_catch_done {
|
||||
v.ip++
|
||||
if v.handlers.len > 0 {
|
||||
v.handlers.delete_last()
|
||||
}
|
||||
}
|
||||
op_closure {
|
||||
v.ip++
|
||||
entry := int(v.read_i64())
|
||||
v.closures << Closure{ entry: entry }
|
||||
v.push(v.mkclosure(v.closures.len - 1))!
|
||||
}
|
||||
op_call_closure {
|
||||
v.ip++
|
||||
argc := int(v.read_i64())
|
||||
// stack: [...closure, arg_0, ..., arg_{argc-1}]
|
||||
h := v.stack[v.sp - argc - 1]
|
||||
if !v.is_closure(h) || !v.valid_closure_handle(h) {
|
||||
return error('cannot call a non-function value')
|
||||
}
|
||||
entry := v.closures[v.hand(h)].entry
|
||||
// Shift args left to overwrite the closure slot, then
|
||||
// insert a return-value placeholder so the callee's
|
||||
// retv never overwrites the caller's local that held
|
||||
// the closure handle.
|
||||
for i := 0; i < argc; i++ {
|
||||
v.stack[v.sp - argc - 1 + i] = v.stack[v.sp - argc + i]
|
||||
}
|
||||
v.stack[v.sp - 1] = v.enc_int(0) // return-value placeholder
|
||||
v.sp-- // closure was removed; unwind one slot
|
||||
v.call(entry, argc)
|
||||
}
|
||||
op_argc {
|
||||
v.ip++
|
||||
argc := v.dec_int(v.stack[v.bp - 1])
|
||||
v.push(v.enc_int(argc))!
|
||||
}
|
||||
op_load_dyn {
|
||||
v.ip++
|
||||
idx := int(v.dec_int(v.pop()!))
|
||||
if v.bp + idx < 0 || v.bp + idx >= v.sp {
|
||||
return error('dynamic load index ${idx} out of range')
|
||||
}
|
||||
v.push(v.stack[v.bp + idx])!
|
||||
}
|
||||
op_varargs {
|
||||
v.ip++
|
||||
named := int(v.read_i64())
|
||||
dst := int(v.read_i64())
|
||||
argc := int(v.dec_int(v.stack[v.bp - 1]))
|
||||
mut n := argc - named
|
||||
if n < 0 {
|
||||
n = 0
|
||||
}
|
||||
mut arr := []i64{len: n}
|
||||
for i in 0..n {
|
||||
arr[i] = v.stack[v.bp + named + i]
|
||||
}
|
||||
v.arrays << arr
|
||||
v.stack[v.bp + dst] = v.mkarr(v.arrays.len - 1)
|
||||
}
|
||||
op_str_method {
|
||||
v.ip++
|
||||
sidx := int(v.read_i64())
|
||||
argc := int(v.read_i64())
|
||||
v.str_method(v.strings[sidx], argc)!
|
||||
}
|
||||
else {
|
||||
return error('unknown opcode ${op} at ip ${v.ip}')
|
||||
}
|
||||
@@ -453,6 +640,60 @@ fn (mut v Vm) op_skeys() ! {
|
||||
v.push(v.mkarr(v.arrays.len - 1))!
|
||||
}
|
||||
|
||||
// op_slice slices an array or string: stack = [value, start, end] → sliced value.
|
||||
// end == -1 means "open-ended" (slice to the end).
|
||||
fn (mut v Vm) op_slice() ! {
|
||||
v.ip++
|
||||
end_val := v.dec_int(v.pop()!)
|
||||
start_val := v.dec_int(v.pop()!)
|
||||
h := v.pop()!
|
||||
// --- array slicing ---
|
||||
if v.is_arr(h) && v.valid_arr_handle(h) {
|
||||
arr := v.arrays[v.hand(h)]
|
||||
mut s := if start_val < 0 { 0 } else { int(start_val) }
|
||||
mut e := if end_val < 0 { arr.len } else { int(end_val) }
|
||||
if s > arr.len {
|
||||
s = arr.len
|
||||
}
|
||||
if e > arr.len {
|
||||
e = arr.len
|
||||
}
|
||||
if s > e {
|
||||
e = s
|
||||
}
|
||||
mut sliced := []i64{}
|
||||
for i in s..e {
|
||||
sliced << arr[i]
|
||||
}
|
||||
v.arrays << sliced
|
||||
v.push(v.mkarr(v.arrays.len - 1))!
|
||||
return
|
||||
}
|
||||
// --- string slicing ---
|
||||
if v.is_str(h) && v.valid_handle(h) {
|
||||
src := v.strings[v.hand(h)]
|
||||
runes := src.runes()
|
||||
mut s := if start_val < 0 { 0 } else { int(start_val) }
|
||||
mut e := if end_val < 0 { runes.len } else { int(end_val) }
|
||||
if s > runes.len {
|
||||
s = runes.len
|
||||
}
|
||||
if e > runes.len {
|
||||
e = runes.len
|
||||
}
|
||||
if s > e {
|
||||
e = s
|
||||
}
|
||||
mut sliced := ''
|
||||
for i in s..e {
|
||||
sliced += runes[i].str()
|
||||
}
|
||||
v.push(v.alloc_str(sliced))!
|
||||
return
|
||||
}
|
||||
return error('slice() on a non-array, non-string value')
|
||||
}
|
||||
|
||||
fn (mut v Vm) read_i64() i64 {
|
||||
mut val := u64(0)
|
||||
for i in 0..8 {
|
||||
@@ -462,6 +703,15 @@ fn (mut v Vm) read_i64() i64 {
|
||||
return i64(val)
|
||||
}
|
||||
|
||||
fn (mut v Vm) read_f64() f64 {
|
||||
mut val := u64(0)
|
||||
for i in 0..8 {
|
||||
val |= u64(v.code[v.ip + i]) << u32(8 * i)
|
||||
}
|
||||
v.ip += 8
|
||||
return math.f64_from_bits(val)
|
||||
}
|
||||
|
||||
fn (mut v Vm) push(x i64) ! {
|
||||
if v.sp >= v.stack.len {
|
||||
return error('stack overflow')
|
||||
|
||||
Reference in New Issue
Block a user