mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 14:57:18 +00:00
More buildins and vscode exstention <CO-AUthored, ai>
This commit is contained in:
@@ -84,6 +84,13 @@ fn (mut v Vm) collect() {
|
||||
}
|
||||
}
|
||||
}
|
||||
for h in 0..v.closures.len {
|
||||
if closure_mark[h] {
|
||||
for j in 0..v.closures[h].captured.len {
|
||||
v.closures[h].captured[j] = v.remap(v.closures[h].captured[j], 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 {
|
||||
@@ -164,6 +171,9 @@ fn (mut v Vm) mark_value(x i64, mut str_mark []bool, mut arr_mark []bool, mut st
|
||||
h := v.hand(val)
|
||||
if h >= 0 && h < closure_mark.len && !closure_mark[h] {
|
||||
closure_mark[h] = true
|
||||
for cv in v.closures[h].captured {
|
||||
work << cv
|
||||
}
|
||||
}
|
||||
}
|
||||
else {}
|
||||
|
||||
@@ -113,7 +113,7 @@ fn (mut v Vm) json_parse_object(mut p JsonParser) !i64 {
|
||||
p.skip_ws()
|
||||
if p.peek() == `}` {
|
||||
p.pos++
|
||||
v.structs << StructVal{ fields: fields }
|
||||
v.structs << StructVal{ fields: fields, by_name: v.index_fields(fields) }
|
||||
return v.mkstruct_handle(v.structs.len - 1)
|
||||
}
|
||||
for {
|
||||
@@ -152,7 +152,7 @@ fn (mut v Vm) json_parse_object(mut p JsonParser) !i64 {
|
||||
}
|
||||
return p.error('expected "," or "}" in object')
|
||||
}
|
||||
v.structs << StructVal{ fields: fields }
|
||||
v.structs << StructVal{ fields: fields, by_name: v.index_fields(fields) }
|
||||
return v.mkstruct_handle(v.structs.len - 1)
|
||||
}
|
||||
|
||||
|
||||
+207
-6
@@ -16,6 +16,11 @@ import obj
|
||||
import compiler
|
||||
import assembler
|
||||
import linker
|
||||
import regex
|
||||
import encoding.base64
|
||||
import crypto.sha256
|
||||
import crypto.md5
|
||||
import encoding.csv
|
||||
|
||||
fn (mut v Vm) native(id int, _argc int) ! {
|
||||
match id {
|
||||
@@ -248,11 +253,11 @@ fn (mut v Vm) native(id int, _argc int) ! {
|
||||
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) {
|
||||
} else if v.is_struct(x) && v.valid_struct_handle(x) {
|
||||
s := v.structs[v.hand(x)]
|
||||
v.structs << StructVal{ fields: s.fields.clone(), by_name: s.by_name.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)))!
|
||||
@@ -679,12 +684,208 @@ fn (mut v Vm) native(id int, _argc int) ! {
|
||||
t := time.parse(s) or { return error('parse_time: ${err.msg()}') }
|
||||
v.push(v.enc_int(t.unix()))!
|
||||
}
|
||||
native_weekday {
|
||||
t := v.dec_int(v.pop()!)
|
||||
v.push(v.alloc_str(time.unix(t).weekday_str()))!
|
||||
}
|
||||
// -------------------------------------------------------------------
|
||||
// regex — RE2-style patterns via V's regex module. Every call compiles
|
||||
// the pattern fresh; the VuurRaaf-level module can cache if it needs to.
|
||||
native_regex_match {
|
||||
s := v.pop_str()!
|
||||
pat := v.pop_str()!
|
||||
mut re := regex.regex_opt(pat) or { return error('regex: ${err.msg()}') }
|
||||
v.push(v.enc_int(bool_i64(re.matches_string(s))))!
|
||||
}
|
||||
native_regex_find_all {
|
||||
s := v.pop_str()!
|
||||
pat := v.pop_str()!
|
||||
mut re := regex.regex_opt(pat) or { return error('regex: ${err.msg()}') }
|
||||
matches := re.find_all_str(s)
|
||||
mut arr := []i64{}
|
||||
for m in matches {
|
||||
v.strings << m
|
||||
arr << v.mkstr(v.strings.len - 1)
|
||||
}
|
||||
v.arrays << arr
|
||||
v.push(v.mkarr(v.arrays.len - 1))!
|
||||
}
|
||||
native_regex_replace {
|
||||
repl := v.pop_str()!
|
||||
s := v.pop_str()!
|
||||
pat := v.pop_str()!
|
||||
mut re := regex.regex_opt(pat) or { return error('regex: ${err.msg()}') }
|
||||
v.push(v.alloc_str(re.replace(s, repl)))!
|
||||
}
|
||||
native_regex_split {
|
||||
s := v.pop_str()!
|
||||
pat := v.pop_str()!
|
||||
mut re := regex.regex_opt(pat) or { return error('regex: ${err.msg()}') }
|
||||
parts := re.split(s)
|
||||
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))!
|
||||
}
|
||||
// -------------------------------------------------------------------
|
||||
// base64 + hashes
|
||||
native_base64_encode {
|
||||
s := v.pop_str()!
|
||||
v.push(v.alloc_str(base64.encode_str(s)))!
|
||||
}
|
||||
native_base64_decode {
|
||||
s := v.pop_str()!
|
||||
v.push(v.alloc_str(base64.decode_str(s)))!
|
||||
}
|
||||
native_sha256 {
|
||||
s := v.pop_str()!
|
||||
v.push(v.alloc_str(sha256.hexhash(s)))!
|
||||
}
|
||||
native_md5 {
|
||||
s := v.pop_str()!
|
||||
v.push(v.alloc_str(md5.hexhash(s)))!
|
||||
}
|
||||
native_csv_parse {
|
||||
s := v.pop_str()!
|
||||
mut r := csv.new_reader(s)
|
||||
// read() yields one row ([]string) at a time until EOF
|
||||
mut rows := [][]string{}
|
||||
for {
|
||||
row := r.read() or { break }
|
||||
if row.len == 0 {
|
||||
break
|
||||
}
|
||||
rows << row
|
||||
}
|
||||
mut outer := []i64{}
|
||||
for row in rows {
|
||||
mut inner := []i64{}
|
||||
for cell in row {
|
||||
v.strings << cell
|
||||
inner << v.mkstr(v.strings.len - 1)
|
||||
}
|
||||
v.arrays << inner
|
||||
outer << v.mkarr(v.arrays.len - 1)
|
||||
}
|
||||
v.arrays << outer
|
||||
v.push(v.mkarr(v.arrays.len - 1))!
|
||||
}
|
||||
// -------------------------------------------------------------------
|
||||
// extended HTTP: any method, custom headers (a {name: value} struct),
|
||||
// and a per-request timeout in milliseconds
|
||||
native_http_req {
|
||||
timeout_ms := int(v.dec_int(v.pop()!))
|
||||
headers_h := v.pop()!
|
||||
data := v.pop_str()!
|
||||
url := v.pop_str()!
|
||||
method := v.pop_str()!
|
||||
mut h := http.new_header()
|
||||
if v.is_struct(headers_h) && v.valid_struct_handle(headers_h) {
|
||||
for f in v.structs[v.hand(headers_h)].fields {
|
||||
if v.is_str(f.val) && v.valid_handle(f.val) {
|
||||
h.add_custom(f.name, v.strings[v.hand(f.val)]) or {}
|
||||
}
|
||||
}
|
||||
}
|
||||
m := match method.to_upper() {
|
||||
'GET' { http.Method.get }
|
||||
'POST' { http.Method.post }
|
||||
'PUT' { http.Method.put }
|
||||
'DELETE' { http.Method.delete }
|
||||
'PATCH' { http.Method.patch }
|
||||
'HEAD' { http.Method.head }
|
||||
else { return error('http_req: unsupported method "${method}"') }
|
||||
}
|
||||
resp := http.fetch(method: m, url: url, data: data, header: h,
|
||||
read_timeout: i64(timeout_ms) * time.millisecond) or {
|
||||
return error('http_req: ${err.msg()}')
|
||||
}
|
||||
v.push_http_response(resp)!
|
||||
}
|
||||
// -------------------------------------------------------------------
|
||||
// filesystem path helpers
|
||||
native_path_ext {
|
||||
p := v.pop_str()!
|
||||
v.push(v.alloc_str(os.file_ext(p)))!
|
||||
}
|
||||
native_path_abs {
|
||||
p := v.pop_str()!
|
||||
v.push(v.alloc_str(os.abs_path(p)))!
|
||||
}
|
||||
native_path_rel {
|
||||
base := v.pop_str()!
|
||||
p := v.pop_str()!
|
||||
v.push(v.alloc_str(v.rel_path(p, base)))!
|
||||
}
|
||||
// -------------------------------------------------------------------
|
||||
// process spawn with separate stdout/stderr pipes
|
||||
native_exec_full {
|
||||
cmd := v.pop_str()!
|
||||
mut code := 0
|
||||
mut out := ''
|
||||
mut errs := ''
|
||||
if os.user_os() == 'windows' {
|
||||
res := os.execute(cmd)
|
||||
code = res.exit_code
|
||||
out = res.output
|
||||
} else {
|
||||
mut p := os.new_process('sh')
|
||||
p.set_args(['-c', cmd])
|
||||
p.use_stdio_ctl = true
|
||||
p.run()
|
||||
p.wait()
|
||||
if p.err.len > 0 {
|
||||
return error('exec_full: ${p.err}')
|
||||
}
|
||||
code = p.code
|
||||
out = p.stdout_slurp()
|
||||
errs = p.stderr_slurp()
|
||||
}
|
||||
out_h := v.alloc_str(out)
|
||||
err_h := v.alloc_str(errs)
|
||||
mut fields := []Field{len: 3}
|
||||
fields[0] = Field{ name: 'code', val: v.enc_int(i64(code)) }
|
||||
fields[1] = Field{ name: 'stdout', val: out_h }
|
||||
fields[2] = Field{ name: 'stderr', val: err_h }
|
||||
v.structs << StructVal{ fields: fields, by_name: v.index_fields(fields) }
|
||||
v.push(v.mkstruct_handle(v.structs.len - 1))!
|
||||
}
|
||||
else {
|
||||
return error('unknown native builtin ${id}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rel_path computes a relative path from `base` to `p` (both absolutized),
|
||||
// e.g. rel_path("/a/b/c.txt", "/a") == "b/c.txt". Used by os.rel().
|
||||
fn (v Vm) rel_path(p string, base string) string {
|
||||
ap := os.abs_path(p).replace('\\', '/')
|
||||
ab := os.abs_path(base).replace('\\', '/')
|
||||
if ap == ab {
|
||||
return '.'
|
||||
}
|
||||
aparts := ap.split('/')
|
||||
bparts := ab.split('/')
|
||||
mut i := 0
|
||||
for i < aparts.len && i < bparts.len && aparts[i] == bparts[i] {
|
||||
i++
|
||||
}
|
||||
mut out := []string{}
|
||||
for _ in i..bparts.len {
|
||||
out << '..'
|
||||
}
|
||||
for j in i..aparts.len {
|
||||
out << aparts[j]
|
||||
}
|
||||
if out.len == 0 {
|
||||
return '.'
|
||||
}
|
||||
return out.join('/')
|
||||
}
|
||||
|
||||
// push_http_response wraps an HTTP response as a {status, body} struct value
|
||||
// so scripts can read res.status and res.body.
|
||||
fn (mut v Vm) push_http_response(resp http.Response) ! {
|
||||
@@ -692,7 +893,7 @@ fn (mut v Vm) push_http_response(resp http.Response) ! {
|
||||
mut fields := []Field{len: 2}
|
||||
fields[0] = Field{ name: 'status', val: v.enc_int(i64(resp.status_code)) }
|
||||
fields[1] = Field{ name: 'body', val: body_h }
|
||||
v.structs << StructVal{ fields: fields }
|
||||
v.structs << StructVal{ fields: fields, by_name: v.index_fields(fields) }
|
||||
v.push(v.mkstruct_handle(v.structs.len - 1))!
|
||||
}
|
||||
|
||||
|
||||
@@ -150,3 +150,24 @@ const native_now = 170
|
||||
const native_time_ms = 171
|
||||
const native_format_time = 172
|
||||
const native_parse_time = 173
|
||||
|
||||
// regex builtins
|
||||
const native_regex_match = 174
|
||||
const native_regex_find_all = 175
|
||||
const native_regex_replace = 176
|
||||
const native_regex_split = 177
|
||||
|
||||
// crypto/encoding builtins
|
||||
const native_base64_encode = 178
|
||||
const native_base64_decode = 179
|
||||
const native_sha256 = 180
|
||||
const native_md5 = 181
|
||||
const native_csv_parse = 182
|
||||
|
||||
// extended HTTP + filesystem/process builtins
|
||||
const native_http_req = 183
|
||||
const native_path_ext = 184
|
||||
const native_path_abs = 185
|
||||
const native_path_rel = 186
|
||||
const native_exec_full = 187
|
||||
const native_weekday = 188
|
||||
|
||||
+28
-21
@@ -12,14 +12,20 @@ mut:
|
||||
val i64
|
||||
}
|
||||
|
||||
// StructVal is a struct (or map) value. `fields` keeps insertion order for
|
||||
// rendering and JSON encoding; `by_name` is a hash index for O(1) field
|
||||
// lookup by name, so map-style access on large records stays fast.
|
||||
struct StructVal {
|
||||
mut:
|
||||
fields []Field
|
||||
fields []Field
|
||||
by_name map[string]int // field name -> index into fields
|
||||
}
|
||||
|
||||
// Handler records a try/catch handler pushed at runtime.
|
||||
struct Closure {
|
||||
entry int // code IP of the function body
|
||||
mut:
|
||||
captured []i64 // values of the enclosing locals this closure captures (by value)
|
||||
}
|
||||
|
||||
struct Handler {
|
||||
@@ -30,26 +36,27 @@ struct Handler {
|
||||
|
||||
struct Vm {
|
||||
mut:
|
||||
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)
|
||||
build_root string // directory of the .vrmm build module (build_root() builtin)
|
||||
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
|
||||
fns []obj.BinFn // function table (for stack traces)
|
||||
const_strs int // strings[0..const_strs] are bytecode constants, never collected
|
||||
last_heap int // heap size at the last GC check (allocation trigger)
|
||||
build_root string // directory of the .vrmm build module (build_root() builtin)
|
||||
}
|
||||
|
||||
fn bool_i64(b bool) i64 {
|
||||
|
||||
@@ -35,6 +35,7 @@ fn run_internal(bin obj.Bin, entry string, trace bool, args []string, root strin
|
||||
trace: trace
|
||||
prog_args: args
|
||||
lines: bin.lines
|
||||
fns: bin.fns
|
||||
const_strs: bin.strings.len
|
||||
build_root: root
|
||||
}
|
||||
@@ -61,7 +62,7 @@ fn run_internal(bin obj.Bin, entry string, trace bool, args []string, root strin
|
||||
v.bp = v.sp
|
||||
v.ip = entry_ip
|
||||
v.exec() or {
|
||||
return error('${err.msg()} at ${v.where()}')
|
||||
return error('${err.msg()} at ${v.where()}\n${v.stack_trace()}')
|
||||
}
|
||||
if v.did_exit {
|
||||
return v.exit_code
|
||||
@@ -75,14 +76,57 @@ fn run_internal(bin obj.Bin, entry string, trace bool, args []string, root strin
|
||||
// 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 {
|
||||
return 'line ${v.line_at(v.ip)} (ip ${v.ip})'
|
||||
}
|
||||
|
||||
// line_at maps a code offset to its source line via the line table.
|
||||
fn (v Vm) line_at(ip int) int {
|
||||
// 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
|
||||
// the most recent entry to find the last one at or before 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})'
|
||||
if ip >= int(v.lines[i].off) {
|
||||
return v.lines[i].line
|
||||
}
|
||||
}
|
||||
return '(ip ${v.ip})'
|
||||
return 0
|
||||
}
|
||||
|
||||
// func_at returns the name of the function whose body contains the given
|
||||
// code offset. Functions are laid out sequentially, so the enclosing
|
||||
// function is the one with the greatest entry point <= ip.
|
||||
fn (v Vm) func_at(ip int) string {
|
||||
mut name := '?'
|
||||
for f in v.fns {
|
||||
if f.entry <= ip {
|
||||
name = f.name
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// stack_trace renders the call chain at the moment an error is raised, from
|
||||
// the innermost frame out to main. Each frame's return address and saved bp
|
||||
// live in the frame header pushed by `call`: [retaddr, old_bp, argc] at
|
||||
// bp-3..bp-1. The synthetic entry frame has retaddr == -1 (the halt sentinel).
|
||||
fn (mut v Vm) stack_trace() string {
|
||||
mut out := []string{}
|
||||
mut bp := v.bp
|
||||
mut ip := v.ip
|
||||
mut guard := 0
|
||||
// note: sp may have dropped below bp (an error handler pops values), so
|
||||
// the frame chain is bounded by the guard and the old_bp < bp invariant
|
||||
for bp >= 3 && guard < 10000 {
|
||||
out << ' at ${v.func_at(ip)} (line ${v.line_at(ip)})'
|
||||
ret := v.dec_int(v.stack[bp - 3])
|
||||
old_bp := int(v.dec_int(v.stack[bp - 2]))
|
||||
if ret == -1 || old_bp < 0 || old_bp >= bp {
|
||||
break // reached the synthetic entry frame
|
||||
}
|
||||
ip = int(ret)
|
||||
bp = old_bp
|
||||
guard++
|
||||
}
|
||||
return out.join('\n')
|
||||
}
|
||||
|
||||
fn (mut v Vm) exec() ! {
|
||||
@@ -303,37 +347,67 @@ fn (mut v Vm) exec() ! {
|
||||
}
|
||||
op_aget {
|
||||
v.ip++
|
||||
idx := int(v.dec_int(v.pop()!))
|
||||
idxv := v.pop()!
|
||||
h := v.pop()!
|
||||
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})')
|
||||
// dynamic map read: m[key_expr] where the base is a struct and the
|
||||
// index evaluates to a string
|
||||
if v.is_struct(h) && v.valid_struct_handle(h) && v.is_str(idxv) && v.valid_handle(idxv) {
|
||||
fname := v.strings[v.hand(idxv)]
|
||||
s := v.structs[v.hand(h)]
|
||||
idx, ok := v.field_idx(s, fname)
|
||||
if ok {
|
||||
v.push(s.fields[idx].val)!
|
||||
} else {
|
||||
return error('no field "${fname}" on struct')
|
||||
}
|
||||
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')
|
||||
idx := int(v.dec_int(idxv))
|
||||
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')
|
||||
}
|
||||
}
|
||||
}
|
||||
op_aset {
|
||||
v.ip++
|
||||
val := v.pop()!
|
||||
idx := int(v.dec_int(v.pop()!))
|
||||
idxv := v.pop()!
|
||||
h := v.pop()!
|
||||
if !v.is_arr(h) || !v.valid_arr_handle(h) {
|
||||
return error('indexing a non-array value')
|
||||
// dynamic map write: m[key_expr] = v (string key on a struct)
|
||||
if v.is_struct(h) && v.valid_struct_handle(h) && v.is_str(idxv) && v.valid_handle(idxv) {
|
||||
fname := v.strings[v.hand(idxv)]
|
||||
mut s := v.structs[v.hand(h)]
|
||||
idx, ok := v.field_idx(s, fname)
|
||||
if ok {
|
||||
s.fields[idx].val = val
|
||||
} else {
|
||||
// setting a missing field adds it, so maps can grow
|
||||
s.fields << Field{ name: fname, val: val }
|
||||
s.by_name[fname] = s.fields.len - 1
|
||||
}
|
||||
v.structs[v.hand(h)] = s
|
||||
} else {
|
||||
idx := int(v.dec_int(idxv))
|
||||
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
|
||||
}
|
||||
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++
|
||||
@@ -371,7 +445,7 @@ fn (mut v Vm) exec() ! {
|
||||
}
|
||||
fields[i] = Field{ name: v.strings[v.hand(name)], val: val }
|
||||
}
|
||||
v.structs << StructVal{ fields: fields }
|
||||
v.structs << StructVal{ fields: fields, by_name: v.index_fields(fields) }
|
||||
v.push(v.mkstruct_handle(v.structs.len - 1))!
|
||||
}
|
||||
op_sget {
|
||||
@@ -385,15 +459,11 @@ fn (mut v Vm) exec() ! {
|
||||
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 {
|
||||
s := v.structs[v.hand(h)]
|
||||
idx, ok := v.field_idx(s, fname)
|
||||
if ok {
|
||||
v.push(s.fields[idx].val)!
|
||||
} else {
|
||||
return error('no field "${fname}" on struct')
|
||||
}
|
||||
}
|
||||
@@ -411,18 +481,14 @@ fn (mut v Vm) exec() ! {
|
||||
}
|
||||
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 {
|
||||
idx, ok := v.field_idx(s, fname)
|
||||
if ok {
|
||||
s.fields[idx].val = val
|
||||
} else {
|
||||
// setting a missing field adds it, so records can be built
|
||||
// incrementally from an empty `{}`
|
||||
s.fields << Field{ name: fname, val: val }
|
||||
s.by_name[fname] = s.fields.len - 1
|
||||
}
|
||||
v.structs[v.hand(h)] = s
|
||||
}
|
||||
@@ -526,7 +592,14 @@ fn (mut v Vm) exec() ! {
|
||||
op_closure {
|
||||
v.ip++
|
||||
entry := int(v.read_i64())
|
||||
v.closures << Closure{ entry: entry }
|
||||
n := int(v.read_i64())
|
||||
// the captured values were pushed by the compiler in capture
|
||||
// order; pop them back into the closure's own array
|
||||
mut captured := []i64{len: n}
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
captured[i] = v.pop()!
|
||||
}
|
||||
v.closures << Closure{ entry: entry, captured: captured }
|
||||
v.push(v.mkclosure(v.closures.len - 1))!
|
||||
}
|
||||
op_call_closure {
|
||||
@@ -537,17 +610,30 @@ fn (mut v Vm) exec() ! {
|
||||
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 (and drop the
|
||||
// duplicated tail), so the callee's retv lands exactly where the
|
||||
// call sequence began and no stale value is left below the
|
||||
// result. The caller's own local holding the closure sits below
|
||||
// the pushed sequence and is never touched.
|
||||
for i := 0; i < argc; i++ {
|
||||
v.stack[v.sp - argc - 1 + i] = v.stack[v.sp - argc + i]
|
||||
cl := v.closures[v.hand(h)]
|
||||
n := cl.captured.len
|
||||
c := v.sp - argc - 1 // closure slot
|
||||
// Rearrange the stack from [...closure, arg_0..arg_{argc-1}] to
|
||||
// [capture_0..capture_{n-1}, arg_0..arg_{argc-1}]: the captures
|
||||
// take over the closure slot, and the args shift by (1 - n) so
|
||||
// the callee sees captures as its leading locals followed by the
|
||||
// real arguments. v.call cleans the whole region up on ret.
|
||||
if n > 1 {
|
||||
// shifting right: copy backwards to avoid clobbering
|
||||
for i := argc - 1; i >= 0; i-- {
|
||||
v.stack[c + n + i] = v.stack[c + 1 + i]
|
||||
}
|
||||
} else {
|
||||
// shifting left (or no shift): copy forwards
|
||||
for i := 0; i < argc; i++ {
|
||||
v.stack[c + n + i] = v.stack[c + 1 + i]
|
||||
}
|
||||
}
|
||||
v.sp-- // drop the duplicated arg tail; closure slot was consumed
|
||||
v.call(entry, argc)
|
||||
for i in 0..n {
|
||||
v.stack[c + i] = cl.captured[i]
|
||||
}
|
||||
v.sp = c + n + argc
|
||||
v.call(cl.entry, argc + n)
|
||||
}
|
||||
op_argc {
|
||||
v.ip++
|
||||
@@ -595,6 +681,25 @@ fn (mut v Vm) exec() ! {
|
||||
}
|
||||
}
|
||||
|
||||
// field_idx returns the index of a named field via the hash index, or
|
||||
// (0, false) when the field does not exist.
|
||||
fn (v Vm) field_idx(s StructVal, fname string) (int, bool) {
|
||||
if fname in s.by_name {
|
||||
return s.by_name[fname], true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// index_fields builds the name -> index hash map for a freshly built field
|
||||
// list (used by mkstruct and other struct constructors).
|
||||
fn (v Vm) index_fields(fields []Field) map[string]int {
|
||||
mut m := map[string]int{}
|
||||
for i, f in fields {
|
||||
m[f.name] = i
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// 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() ! {
|
||||
@@ -608,13 +713,7 @@ fn (mut v Vm) op_shas() ! {
|
||||
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
|
||||
}
|
||||
}
|
||||
found := fname in v.structs[v.hand(h)].by_name
|
||||
v.push(v.enc_int(if found { 1 } else { 0 }))!
|
||||
}
|
||||
|
||||
@@ -629,19 +728,22 @@ fn (mut v Vm) op_sdel() ! {
|
||||
}
|
||||
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
|
||||
} fname := v.strings[v.hand(name)]
|
||||
mut s := v.structs[v.hand(h)]
|
||||
idx, ok := v.field_idx(s, fname)
|
||||
if ok {
|
||||
mut new_fields := []Field{}
|
||||
for i, f in s.fields {
|
||||
if i != idx {
|
||||
new_fields << f
|
||||
}
|
||||
}
|
||||
s.fields = new_fields
|
||||
s.by_name = v.index_fields(new_fields)
|
||||
v.structs[v.hand(h)] = s
|
||||
}
|
||||
v.push(h)!
|
||||
}
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user