mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 15:37: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:
+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)
|
||||
}
|
||||
Reference in New Issue
Block a user