mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 14:17:18 +00:00
batch A
This commit is contained in:
@@ -230,7 +230,7 @@ Module functions are namespaced: `os.exists()`, `json.encode()`, ...
|
||||
| `crypto` | `sha256(s)`, `md5(s)`, `base64_encode(s)`, `base64_decode(s)` |
|
||||
| `cli` | getopt-style flag parsing over `args()`: `flag(name, default)`, `has(name)`, `positional()` — supports `--name value`, `--name=value`, boolean `--flag`, and short aliases `-n=value` |
|
||||
|
||||
Examples: `examples/imports.vr`, `examples/http.vr`, `examples/time.vr`, `examples/cli_tool.vr` (CLI flags + interactive input + typed errors), `examples/stdlib.vr` (tests for the regex/crypto/csv/os/time additions).
|
||||
Examples: `examples/imports.vr`, `examples/http.vr`, `examples/time.vr`, `examples/cli_tool.vr` (CLI flags + interactive input + typed errors), `examples/defer_reflection.vr` (`defer`, `in`, `type_info`, `range`), `examples/stdlib.vr` (tests for the regex/crypto/csv/os/time additions).
|
||||
|
||||
## The VuurRaaf language
|
||||
|
||||
@@ -382,6 +382,16 @@ fn main() {
|
||||
`try { } catch e { }` like explicit `throw`s
|
||||
- for loops: `for x in arr { }` and ranges `for i in 0..10 { }` /
|
||||
`for i in 0...10 { }`; loop variables are scoped to the loop body
|
||||
- `defer <stmt>` runs the statement when the function exits (LIFO, on every
|
||||
return path), so file/resource cleanup reads naturally: `defer remove(tmp)`
|
||||
- `x in col` membership operator works on arrays (`2 in [1,2,3]`), maps
|
||||
(`"key" in m`), and strings (`"sub" in s`), and with `not` (`not (x in a)`)
|
||||
- `type_info(v)` reflects any value into a `{kind, is_number, is_int, is_float,
|
||||
is_string, is_array, is_struct, is_none, is_closure, len, fields, contents}`
|
||||
struct for duck-typing and generic utilities
|
||||
- `range(a, b)` builds `[a, a+1, ..., b)` (descends when `a > b`) as an array
|
||||
- reflection: `type(x)` returns "int"/"float"/"string"/"array"/"struct"/
|
||||
"none"/"closure"; `keys(m)`, `has(m, k)`, `len(m)` inspect records
|
||||
- `break` / `continue` inside `while` and `for` loops (in `for` loops
|
||||
`continue` advances the loop variable / iterator first)
|
||||
- else-if chains: `if a { } else if b { } else { }`
|
||||
|
||||
@@ -68,6 +68,7 @@ pub enum StmtKind {
|
||||
assert_stmt
|
||||
try_stmt
|
||||
throw_stmt
|
||||
defer_stmt
|
||||
}
|
||||
|
||||
// MatchArm is a single `value { body }` arm of a match statement.
|
||||
|
||||
+8
-1
@@ -292,6 +292,11 @@ fn (mut c Checker) check_stmt(st Stmt) ! {
|
||||
.throw_stmt {
|
||||
_ = c.check_expr(st.expr)!
|
||||
}
|
||||
.defer_stmt {
|
||||
for s in st.body {
|
||||
c.check_stmt(s)!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,7 +496,7 @@ fn (mut c Checker) check_binary(e Expr) !TypeInfo {
|
||||
}
|
||||
TypeInfo{ kind: .bool_t }
|
||||
}
|
||||
.kw_and, .kw_or {
|
||||
.kw_and, .kw_or, .kw_in {
|
||||
TypeInfo{ kind: .bool_t }
|
||||
}
|
||||
.amp, .pipe, .caret, .lt_lt, .gt_gt {
|
||||
@@ -635,6 +640,8 @@ fn builtin_result_type(name string) TypeInfo {
|
||||
'flag_val' { TypeInfo{ kind: .string_t } }
|
||||
'flag_has' { TypeInfo{ kind: .int_t } }
|
||||
'flag_positional' { TypeInfo{ kind: .array_t } }
|
||||
'type_info' { TypeInfo{ kind: .struct_t } }
|
||||
'range' { TypeInfo{ kind: .array_t } }
|
||||
'build_is_dir' { TypeInfo{ kind: .int_t } }
|
||||
// build-module builtins (.vrmm)
|
||||
'build_compile', 'build_assemble', 'build_link', 'build_exec', 'build_base',
|
||||
|
||||
@@ -47,6 +47,12 @@ mut:
|
||||
modules map[string]bool // imported module names (bare `import os`)
|
||||
fn_names map[string]bool // top-level function names usable as closure values
|
||||
captures []string // enclosing locals captured by the closure being compiled
|
||||
defers []Stmt // deferred statements of the current function (in order)
|
||||
has_defers bool // current function registers deferred cleanup
|
||||
defer_ret_slot int // hidden local holding the return value while defers run
|
||||
defer_value_seen bool // a `return expr` (value) appeared, so restore+retv after defers
|
||||
cur_defer_label string // label of the current function's deferred-cleanup block
|
||||
namer_id int // unique-id source for generated label names
|
||||
}
|
||||
|
||||
fn gen(prog Program) !obj.Obj {
|
||||
@@ -226,10 +232,45 @@ fn (mut g Gen) gen_fn(fd FnDecl) ! {
|
||||
g.code << obj.encode_i64(i64(g.argc))
|
||||
g.code << obj.encode_i64(i64(vidx))
|
||||
}
|
||||
// one-pass defer handling: collect every `defer` statement in the body first
|
||||
// (so all returns can be redirected to the deferred-cleanup block), then
|
||||
// reserve a hidden slot to hold the return value across that block.
|
||||
g.defers = []Stmt{}
|
||||
g.has_defers = false
|
||||
g.defer_value_seen = false
|
||||
g.collect_defers(fd.body, mut g.defers)
|
||||
if g.defers.len > 0 {
|
||||
g.has_defers = true
|
||||
g.defer_ret_slot = g.local_cnt
|
||||
g.local_cnt++
|
||||
}
|
||||
g.cur_defer_label = g.new_label()
|
||||
for st in fd.body {
|
||||
g.gen_stmt(st)!
|
||||
}
|
||||
|
||||
if g.has_defers {
|
||||
g.code << op_jmp
|
||||
g.code << obj.encode_i64(0)
|
||||
g.fixups << Fixup{ name: g.cur_defer_label, off: u32(g.code.len) - 8 }
|
||||
} else {
|
||||
g.code << op_ret // trailing return for fall-through
|
||||
}
|
||||
// emit the deferred-cleanup block (LIFO) then the real return here, so a
|
||||
// `return` in the body jumps into this block and lands back on the return
|
||||
if g.has_defers {
|
||||
g.emit_label(g.cur_defer_label)
|
||||
for i := g.defers.len - 1; i >= 0; i-- {
|
||||
g.gen_stmt(g.defers[i])!
|
||||
}
|
||||
if g.defer_value_seen {
|
||||
// restore the saved return value and return it
|
||||
g.emit_load(g.defer_ret_slot)
|
||||
g.code << op_retv
|
||||
} else {
|
||||
g.code << op_ret
|
||||
}
|
||||
}
|
||||
// reserve all local slots: the callee may be called with fewer arguments
|
||||
// than declared (default parameters) or more (variadic), so the frame must
|
||||
// always cover slots 0..local_cnt-1
|
||||
@@ -245,6 +286,8 @@ fn (mut g Gen) gen_fn(fd FnDecl) ! {
|
||||
}
|
||||
g.fixups.clear()
|
||||
g.labels.clear()
|
||||
g.defers = []Stmt{} // reset for the next (possibly enclosing) function
|
||||
g.has_defers = false
|
||||
// snapshot the live locals as debug info for the debugger: name -> slot
|
||||
for name, slot in g.locals {
|
||||
g.dbg_locals << obj.DbgLocal{ fn: sym, name: name, slot: slot }
|
||||
@@ -519,6 +562,22 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
|
||||
g.emit_label(end_l)
|
||||
}
|
||||
.ret_stmt {
|
||||
if g.has_defers {
|
||||
// save any return value in the hidden slot, jump into the
|
||||
// deferred-cleanup block, which restores and returns it
|
||||
if st.has_val {
|
||||
g.defer_value_seen = true
|
||||
g.gen_expr(st.expr)!
|
||||
g.emit_store(g.defer_ret_slot)
|
||||
} else {
|
||||
g.code << op_push_i
|
||||
g.code << obj.encode_i64(0)
|
||||
g.emit_store(g.defer_ret_slot)
|
||||
}
|
||||
g.code << op_jmp
|
||||
g.code << obj.encode_i64(0)
|
||||
g.fixups << Fixup{ name: g.cur_defer_label, off: u32(g.code.len) - 8 }
|
||||
} else {
|
||||
if st.has_val {
|
||||
g.gen_expr(st.expr)!
|
||||
g.code << op_retv
|
||||
@@ -526,6 +585,11 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
|
||||
g.code << op_ret
|
||||
}
|
||||
}
|
||||
}
|
||||
.defer_stmt {
|
||||
// collected in gen_fn's pass and emitted in the cleanup block;
|
||||
// nothing to place at the declaration site
|
||||
}
|
||||
.assert_stmt {
|
||||
g.gen_expr(st.expr)!
|
||||
g.code << op_assert
|
||||
@@ -1047,6 +1111,9 @@ fn builtin_spec(name string) (int, int) {
|
||||
'flag_val' { native_flag_val, 1 }
|
||||
'flag_has' { native_flag_has, 1 }
|
||||
'flag_positional' { native_flag_positional, 0 }
|
||||
// structured reflection + sequence helper
|
||||
'type_info' { native_type_info, 1 }
|
||||
'range' { native_range, 2 }
|
||||
'build_is_dir' { native_build_is_dir, 1 }
|
||||
'cwd' { native_cwd, 0 }
|
||||
'json_pretty' { native_json_pretty, 1 }
|
||||
@@ -1257,6 +1324,7 @@ fn (mut g Gen) gen_binary(e Expr) ! {
|
||||
.le { op_le }
|
||||
.gt { op_gt }
|
||||
.ge { op_ge }
|
||||
.kw_in { op_in }
|
||||
.amp { op_and_b }
|
||||
.pipe { op_or_b }
|
||||
.caret { op_xor }
|
||||
@@ -1510,6 +1578,38 @@ fn (mut g Gen) maybe_capture(name string, bound map[string]bool, mut caps []stri
|
||||
}
|
||||
}
|
||||
|
||||
// collect_defers walks a statement list, gathering the inner statements of
|
||||
// every `defer` into `out` in source order. Called once per function before
|
||||
// code generation so all return sites can be redirected to the cleanup block.
|
||||
fn (mut g Gen) collect_defers(stmts []Stmt, mut out []Stmt) {
|
||||
for st in stmts {
|
||||
match st.kind {
|
||||
.defer_stmt {
|
||||
for d in st.body {
|
||||
out << d
|
||||
}
|
||||
}
|
||||
.if_stmt {
|
||||
g.collect_defers(st.body, mut out)
|
||||
g.collect_defers(st.els, mut out)
|
||||
}
|
||||
.match_stmt {
|
||||
for a in st.arms {
|
||||
g.collect_defers(a.body, mut out)
|
||||
}
|
||||
g.collect_defers(st.els_body, mut out)
|
||||
}
|
||||
.while_stmt, .for_range_stmt, .for_in_stmt, .try_stmt {
|
||||
g.collect_defers(st.body, mut out)
|
||||
if st.kind == .try_stmt {
|
||||
g.collect_defers(st.els, mut out)
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) scan_stmt(st Stmt, mut bound map[string]bool, mut caps []string, mut seen map[string]bool) {
|
||||
match st.kind {
|
||||
.expr_stmt {
|
||||
@@ -1623,6 +1723,11 @@ fn (mut g Gen) scan_stmt(st Stmt, mut bound map[string]bool, mut caps []string,
|
||||
.throw_stmt {
|
||||
g.scan_expr(st.expr, mut bound, mut caps, mut seen)
|
||||
}
|
||||
.defer_stmt {
|
||||
for s in st.body {
|
||||
g.scan_stmt(s, mut bound, mut caps, mut seen)
|
||||
}
|
||||
}
|
||||
.break_stmt, .continue_stmt {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,6 +310,7 @@ fn (mut l Lexer) lex_ident(line int, col int) Tok {
|
||||
'interface' { TokKind.kw_interface }
|
||||
'try' { TokKind.kw_try }
|
||||
'catch' { TokKind.kw_catch }
|
||||
'defer' { TokKind.kw_defer }
|
||||
'throw' { TokKind.kw_throw }
|
||||
else { TokKind.ident }
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ 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 an array at local dst
|
||||
const op_str_method = u8(61) // <name:str> <argc:i64> — call a string method (s.len(), s.contains(x), ...)
|
||||
const op_push_none = u8(62) // push the `none` sentinel
|
||||
const op_in = u8(63) // membership: x in col -> 0 or 1
|
||||
|
||||
// native builtin ids (keep in sync with vm/opcodes.v)
|
||||
const native_abs = 100
|
||||
@@ -191,3 +192,7 @@ const native_input = 196
|
||||
const native_flag_val = 197
|
||||
const native_flag_has = 198
|
||||
const native_flag_positional = 199
|
||||
|
||||
// structured reflection + sequence helper
|
||||
const native_type_info = 200
|
||||
const native_range = 201
|
||||
|
||||
+6
-1
@@ -403,6 +403,11 @@ fn (mut p Parser) parse_stmt() !Stmt {
|
||||
body := p.parse_block()!
|
||||
return Stmt{ kind: .for_in_stmt, target: val_name, idx_target: idx_name, expr: first, body: body, line: t.line }
|
||||
}
|
||||
.kw_defer {
|
||||
p.advance()
|
||||
inner := p.parse_stmt()!
|
||||
return Stmt{ kind: .defer_stmt, body: [inner], line: t.line }
|
||||
}
|
||||
.kw_return {
|
||||
p.advance()
|
||||
mut e := Expr{}
|
||||
@@ -664,7 +669,7 @@ fn (mut p Parser) parse_eq() !Expr {
|
||||
|
||||
fn (mut p Parser) parse_rel() !Expr {
|
||||
mut e := p.parse_shift()!
|
||||
for p.cur().kind == .lt || p.cur().kind == .le || p.cur().kind == .gt || p.cur().kind == .ge {
|
||||
for p.cur().kind == .lt || p.cur().kind == .le || p.cur().kind == .gt || p.cur().kind == .ge || p.cur().kind == .kw_in {
|
||||
op := p.advance()
|
||||
rhs := p.parse_shift()!
|
||||
e = bin_node(op.kind, e, rhs, op.line)
|
||||
|
||||
@@ -69,6 +69,7 @@ pub enum TokKind {
|
||||
kw_interface
|
||||
kw_try
|
||||
kw_catch
|
||||
kw_defer
|
||||
kw_throw
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// defer_reflection.vr — demonstrates the Batch-A features: `defer` cleanup,
|
||||
// the `in` membership operator, `type_info()` reflection, and `range()`.
|
||||
//
|
||||
// vr run examples/defer_reflection.vr
|
||||
|
||||
fn summarize(v) {
|
||||
// reflection: inspect any value and build a description
|
||||
let ti = type_info(v)
|
||||
let kind = ti.kind
|
||||
if ti.is_number {
|
||||
return "a number " + str(v)
|
||||
} else if ti.is_string {
|
||||
return "a string of length " + str(ti.len) + ": \"" + v + "\""
|
||||
} else if ti.is_array {
|
||||
return "an array with " + str(ti.len) + " elements"
|
||||
} else if ti.is_struct {
|
||||
return "a record with fields " + str(ti.fields)
|
||||
} else if ti.is_none {
|
||||
return "nothing"
|
||||
}
|
||||
return "a " + kind
|
||||
}
|
||||
|
||||
fn grep_lines(docs, term) {
|
||||
// demonstrate `in` on strings + arrays, with deferred cleanup
|
||||
let matches = []
|
||||
defer println(" -> searched " + str(len(docs)) + " docs for \"" + term + "\"")
|
||||
for d in docs {
|
||||
if term in d { // substring membership
|
||||
push(matches, d)
|
||||
}
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// range + in + type_info
|
||||
let nums = range(1, 6) // [1, 2, 3, 4, 5]
|
||||
println("nums = " + str(nums))
|
||||
println("3 present? " + str(3 in nums))
|
||||
println("7 present? " + str(7 in nums))
|
||||
|
||||
println(summarize(42))
|
||||
println(summarize("hello"))
|
||||
println(summarize([1, 2, 3]))
|
||||
println(summarize({x: 1, y: 2}))
|
||||
println(summarize(none))
|
||||
|
||||
println("---")
|
||||
let docs = ["Introducing VuurRaaf", "A tiny language", "VuurRaaf: daily driver"]
|
||||
let hits = grep_lines(docs, "VuurRaaf")
|
||||
println("hits: " + str(hits))
|
||||
}
|
||||
@@ -658,6 +658,83 @@ fn test_flag_parsing() {
|
||||
assert len(flag_positional()) == 0
|
||||
}
|
||||
|
||||
fn test_defer_cleanup() {
|
||||
// defer runs on every exit path; its effect is visible to the caller via
|
||||
// the shared mutable array (arrays are references).
|
||||
let log = []
|
||||
let a = defer_probe(log, 1)
|
||||
assert a == 0
|
||||
assert len(log) == 2
|
||||
assert log[0] == "open"
|
||||
assert log[1] == "close"
|
||||
}
|
||||
|
||||
fn defer_probe(log, mode) {
|
||||
push(log, "open")
|
||||
defer push(log, "close")
|
||||
if mode == 0 { // early return path also runs defer
|
||||
return 9
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
fn test_defer_lifo() {
|
||||
// defers run in reverse declaration order (LIFO), all before returning
|
||||
let logA = []
|
||||
let r = defer_lifo(logA)
|
||||
assert r == 0
|
||||
assert len(logA) == 2
|
||||
assert logA[0] == "2nd-registered"
|
||||
assert logA[1] == "1st-registered"
|
||||
}
|
||||
|
||||
fn defer_lifo(log) {
|
||||
defer push(log, "1st-registered")
|
||||
defer push(log, "2nd-registered")
|
||||
return 0
|
||||
}
|
||||
|
||||
fn test_in_operator() {
|
||||
let arr = [1, 2, 3]
|
||||
assert 2 in arr
|
||||
assert 9 in arr == false
|
||||
let m = {x: 1}
|
||||
assert "x" in m
|
||||
assert "zzz" in m == false
|
||||
let s = "hello world"
|
||||
assert "world" in s
|
||||
assert "nope" in s == false
|
||||
assert not (9 in arr)
|
||||
}
|
||||
|
||||
fn test_range_helper() {
|
||||
let up = range(0, 4)
|
||||
assert len(up) == 4
|
||||
assert up[0] == 0
|
||||
assert up[3] == 3
|
||||
let down = range(3, 0)
|
||||
assert down[0] == 3
|
||||
assert down[2] == 1
|
||||
assert len(down) == 3
|
||||
}
|
||||
|
||||
fn test_type_info() {
|
||||
let ti = type_info(42)
|
||||
assert ti.kind == "int"
|
||||
assert ti.is_int == 1
|
||||
assert ti.is_number == 1
|
||||
let ts = type_info("xyz")
|
||||
assert ts.is_string == 1
|
||||
assert ts.len == 3
|
||||
let tn = type_info(none)
|
||||
assert tn.kind == "none"
|
||||
assert tn.is_none == 1
|
||||
let to = type_info({a: 1})
|
||||
assert to.is_struct == 1
|
||||
assert len(to.fields) == 1
|
||||
assert to.fields[0] == "a"
|
||||
}
|
||||
|
||||
fn test_failing() {
|
||||
// this one is meant to fail — shows up in `vr test` output
|
||||
assert 1 == 2
|
||||
|
||||
+83
@@ -118,6 +118,15 @@ fn (mut v Vm) native(id int, _argc int) ! {
|
||||
x := v.pop()!
|
||||
v.push(v.alloc_str(v.type_name(x)))!
|
||||
}
|
||||
native_type_info {
|
||||
x := v.pop()!
|
||||
v.push(v.type_info_value(x))!
|
||||
}
|
||||
native_range {
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
v.push(v.range_value(a, b))!
|
||||
}
|
||||
native_split {
|
||||
delim := v.pop_str()!
|
||||
s := v.pop_str()!
|
||||
@@ -1239,6 +1248,12 @@ fn (mut v Vm) type_name(x i64) string {
|
||||
if v.is_float(x) {
|
||||
return 'float'
|
||||
}
|
||||
if v.is_none(x) {
|
||||
return 'none'
|
||||
}
|
||||
if v.is_closure(x) {
|
||||
return 'closure'
|
||||
}
|
||||
if v.is_arr(x) {
|
||||
return 'array'
|
||||
}
|
||||
@@ -1248,6 +1263,74 @@ fn (mut v Vm) type_name(x i64) string {
|
||||
return 'int'
|
||||
}
|
||||
|
||||
// type_info_value reflects a value into a struct for generic/duck-typed code:
|
||||
// { kind, is_number, is_int, is_float, is_string, is_array, is_struct,
|
||||
// is_none, is_closure, len, fields } where `fields` is the array of field
|
||||
// names (structs) and `len` is the element/character count for arrays/strings.
|
||||
fn (mut v Vm) type_info_value(x i64) i64 {
|
||||
kind := v.type_name(x)
|
||||
is_int := v.is_int(x)
|
||||
is_float := v.is_float(x)
|
||||
is_string := v.is_str(x) && v.valid_handle(x)
|
||||
is_array := v.is_arr(x) && v.valid_arr_handle(x)
|
||||
is_struct := v.is_struct(x) && v.valid_struct_handle(x)
|
||||
is_none := v.is_none(x)
|
||||
is_closure := v.is_closure(x) && v.valid_closure_handle(x)
|
||||
is_number := is_int || is_float
|
||||
// len: array -> element count; string -> character count; struct -> field count
|
||||
mut length := 0
|
||||
if is_array {
|
||||
length = v.arrays[v.hand(x)].len
|
||||
} else if is_string {
|
||||
length = v.strings[v.hand(x)].runes().len
|
||||
} else if is_struct {
|
||||
length = v.structs[v.hand(x)].fields.len
|
||||
}
|
||||
// fields: array of struct field names (empty otherwise)
|
||||
mut names := []i64{}
|
||||
if is_struct {
|
||||
for f in v.structs[v.hand(x)].fields {
|
||||
v.strings << f.name
|
||||
names << v.mkstr(v.strings.len - 1)
|
||||
}
|
||||
}
|
||||
v.arrays << names
|
||||
field_h := v.mkarr(v.arrays.len - 1)
|
||||
mut fields := []Field{}
|
||||
fields << Field{ name: 'kind', val: v.alloc_str(kind) }
|
||||
fields << Field{ name: 'is_number', val: v.enc_int(if is_number { 1 } else { 0 }) }
|
||||
fields << Field{ name: 'is_int', val: v.enc_int(if is_int { 1 } else { 0 }) }
|
||||
fields << Field{ name: 'is_float', val: v.enc_int(if is_float { 1 } else { 0 }) }
|
||||
fields << Field{ name: 'is_string', val: v.enc_int(if is_string { 1 } else { 0 }) }
|
||||
fields << Field{ name: 'is_array', val: v.enc_int(if is_array { 1 } else { 0 }) }
|
||||
fields << Field{ name: 'is_struct', val: v.enc_int(if is_struct { 1 } else { 0 }) }
|
||||
fields << Field{ name: 'is_none', val: v.enc_int(if is_none { 1 } else { 0 }) }
|
||||
fields << Field{ name: 'is_closure', val: v.enc_int(if is_closure { 1 } else { 0 }) }
|
||||
fields << Field{ name: 'len', val: v.enc_int(i64(length)) }
|
||||
fields << Field{ name: 'fields', val: field_h }
|
||||
fields << Field{ name: 'contents', val: x }
|
||||
v.structs << StructVal{ fields: fields, by_name: v.index_fields(fields) }
|
||||
return v.mkstruct_handle(v.structs.len - 1)
|
||||
}
|
||||
|
||||
// range_value builds the array [a, a+1, ..., b) as ints.
|
||||
fn (mut v Vm) range_value(a i64, b i64) i64 {
|
||||
start := v.dec_int(a)
|
||||
end := v.dec_int(b)
|
||||
mut arr := []i64{}
|
||||
if start <= end {
|
||||
for i := start; i < end; i++ {
|
||||
arr << v.enc_int(i)
|
||||
}
|
||||
} else {
|
||||
for i := start; i > end; i-- {
|
||||
arr << v.enc_int(i)
|
||||
}
|
||||
}
|
||||
v.arrays << arr
|
||||
return v.mkarr(v.arrays.len - 1)
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -66,6 +66,7 @@ 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), ...)
|
||||
const op_push_none = u8(62) // push the `none` sentinel
|
||||
const op_in = u8(63) // membership: x in col -> 0 or 1
|
||||
|
||||
// native builtin ids (the operand to op_native)
|
||||
const native_abs = 100
|
||||
@@ -190,3 +191,7 @@ const native_input = 196
|
||||
const native_flag_val = 197
|
||||
const native_flag_has = 198
|
||||
const native_flag_positional = 199
|
||||
|
||||
// structured reflection + sequence helper
|
||||
const native_type_info = 200
|
||||
const native_range = 201
|
||||
|
||||
@@ -121,6 +121,7 @@ fn (mut v Vm) trace_op(op u8) {
|
||||
op_varargs { 'varargs' }
|
||||
op_str_method { 'str_method' }
|
||||
op_push_none { 'push_none' }
|
||||
op_in { 'in' }
|
||||
else { '??' }
|
||||
}
|
||||
mut s := ''
|
||||
|
||||
@@ -632,6 +632,27 @@ fn (mut v Vm) exec() ! {
|
||||
op_slice {
|
||||
v.op_slice()!
|
||||
}
|
||||
op_in {
|
||||
v.ip++
|
||||
col := v.pop()!
|
||||
needle := v.pop()!
|
||||
// map/struct: string key membership
|
||||
if v.is_struct(col) && v.valid_struct_handle(col) {
|
||||
if v.is_str(needle) && v.valid_handle(needle) {
|
||||
key := v.strings[v.hand(needle)]
|
||||
v.push(v.enc_int(if key in v.structs[v.hand(col)].by_name { 1 } else { 0 }))!
|
||||
} else {
|
||||
v.push(v.enc_int(0))!
|
||||
}
|
||||
} else if v.is_arr(col) && v.valid_arr_handle(col) {
|
||||
found := v.arr_contains(v.arrays[v.hand(col)], needle)
|
||||
v.push(v.enc_int(if found { 1 } else { 0 }))!
|
||||
} else if v.is_str(col) && v.valid_handle(col) && v.is_str(needle) && v.valid_handle(needle) {
|
||||
v.push(v.enc_int(if v.strings[v.hand(col)].contains(v.strings[v.hand(needle)]) { 1 } else { 0 }))!
|
||||
} else {
|
||||
v.push(v.enc_int(0))!
|
||||
}
|
||||
}
|
||||
op_native {
|
||||
v.ip++
|
||||
id := int(v.read_i64())
|
||||
@@ -1005,6 +1026,25 @@ fn (mut v Vm) call(target int, argc int) {
|
||||
v.ip = target
|
||||
}
|
||||
|
||||
// arr_contains reports whether an array holds a value equal to needle
|
||||
// (comparing ints/floats numerically and strings by content).
|
||||
fn (mut v Vm) arr_contains(a []i64, needle i64) bool {
|
||||
for el in a {
|
||||
if v.vals_eq(el, needle) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// vals_eq compares two values for equality, treating int/float numerically.
|
||||
fn (mut v Vm) vals_eq(x i64, y i64) bool {
|
||||
if (v.is_float(x) || v.is_float(y)) && !v.is_closure(x) && !v.is_closure(y) && !v.is_str(x) && !v.is_str(y) && !v.is_arr(x) && !v.is_arr(y) && !v.is_struct(x) && !v.is_struct(y) {
|
||||
return v.to_f64(x) == v.to_f64(y)
|
||||
}
|
||||
return x == y
|
||||
}
|
||||
|
||||
fn (mut v Vm) ret(with_val bool) ! {
|
||||
retval := if with_val { v.pop()! } else { v.enc_int(0) }
|
||||
v.sp = v.bp - 1
|
||||
|
||||
Reference in New Issue
Block a user