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)` |
|
| `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` |
|
| `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
|
## The VuurRaaf language
|
||||||
|
|
||||||
@@ -382,6 +382,16 @@ fn main() {
|
|||||||
`try { } catch e { }` like explicit `throw`s
|
`try { } catch e { }` like explicit `throw`s
|
||||||
- for loops: `for x in arr { }` and ranges `for i in 0..10 { }` /
|
- 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
|
`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
|
- `break` / `continue` inside `while` and `for` loops (in `for` loops
|
||||||
`continue` advances the loop variable / iterator first)
|
`continue` advances the loop variable / iterator first)
|
||||||
- else-if chains: `if a { } else if b { } else { }`
|
- else-if chains: `if a { } else if b { } else { }`
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ pub enum StmtKind {
|
|||||||
assert_stmt
|
assert_stmt
|
||||||
try_stmt
|
try_stmt
|
||||||
throw_stmt
|
throw_stmt
|
||||||
|
defer_stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchArm is a single `value { body }` arm of a match statement.
|
// 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 {
|
.throw_stmt {
|
||||||
_ = c.check_expr(st.expr)!
|
_ = 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 }
|
TypeInfo{ kind: .bool_t }
|
||||||
}
|
}
|
||||||
.kw_and, .kw_or {
|
.kw_and, .kw_or, .kw_in {
|
||||||
TypeInfo{ kind: .bool_t }
|
TypeInfo{ kind: .bool_t }
|
||||||
}
|
}
|
||||||
.amp, .pipe, .caret, .lt_lt, .gt_gt {
|
.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_val' { TypeInfo{ kind: .string_t } }
|
||||||
'flag_has' { TypeInfo{ kind: .int_t } }
|
'flag_has' { TypeInfo{ kind: .int_t } }
|
||||||
'flag_positional' { TypeInfo{ kind: .array_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_is_dir' { TypeInfo{ kind: .int_t } }
|
||||||
// build-module builtins (.vrmm)
|
// build-module builtins (.vrmm)
|
||||||
'build_compile', 'build_assemble', 'build_link', 'build_exec', 'build_base',
|
'build_compile', 'build_assemble', 'build_link', 'build_exec', 'build_base',
|
||||||
|
|||||||
+110
-5
@@ -47,6 +47,12 @@ mut:
|
|||||||
modules map[string]bool // imported module names (bare `import os`)
|
modules map[string]bool // imported module names (bare `import os`)
|
||||||
fn_names map[string]bool // top-level function names usable as closure values
|
fn_names map[string]bool // top-level function names usable as closure values
|
||||||
captures []string // enclosing locals captured by the closure being compiled
|
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 {
|
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(g.argc))
|
||||||
g.code << obj.encode_i64(i64(vidx))
|
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 {
|
for st in fd.body {
|
||||||
g.gen_stmt(st)!
|
g.gen_stmt(st)!
|
||||||
}
|
}
|
||||||
g.code << op_ret // trailing return for fall-through
|
|
||||||
|
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
|
// reserve all local slots: the callee may be called with fewer arguments
|
||||||
// than declared (default parameters) or more (variadic), so the frame must
|
// than declared (default parameters) or more (variadic), so the frame must
|
||||||
// always cover slots 0..local_cnt-1
|
// always cover slots 0..local_cnt-1
|
||||||
@@ -245,6 +286,8 @@ fn (mut g Gen) gen_fn(fd FnDecl) ! {
|
|||||||
}
|
}
|
||||||
g.fixups.clear()
|
g.fixups.clear()
|
||||||
g.labels.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
|
// snapshot the live locals as debug info for the debugger: name -> slot
|
||||||
for name, slot in g.locals {
|
for name, slot in g.locals {
|
||||||
g.dbg_locals << obj.DbgLocal{ fn: sym, name: name, slot: slot }
|
g.dbg_locals << obj.DbgLocal{ fn: sym, name: name, slot: slot }
|
||||||
@@ -519,13 +562,34 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
|
|||||||
g.emit_label(end_l)
|
g.emit_label(end_l)
|
||||||
}
|
}
|
||||||
.ret_stmt {
|
.ret_stmt {
|
||||||
if st.has_val {
|
if g.has_defers {
|
||||||
g.gen_expr(st.expr)!
|
// save any return value in the hidden slot, jump into the
|
||||||
g.code << op_retv
|
// 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 {
|
} else {
|
||||||
g.code << op_ret
|
if st.has_val {
|
||||||
|
g.gen_expr(st.expr)!
|
||||||
|
g.code << op_retv
|
||||||
|
} else {
|
||||||
|
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 {
|
.assert_stmt {
|
||||||
g.gen_expr(st.expr)!
|
g.gen_expr(st.expr)!
|
||||||
g.code << op_assert
|
g.code << op_assert
|
||||||
@@ -1047,6 +1111,9 @@ fn builtin_spec(name string) (int, int) {
|
|||||||
'flag_val' { native_flag_val, 1 }
|
'flag_val' { native_flag_val, 1 }
|
||||||
'flag_has' { native_flag_has, 1 }
|
'flag_has' { native_flag_has, 1 }
|
||||||
'flag_positional' { native_flag_positional, 0 }
|
'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 }
|
'build_is_dir' { native_build_is_dir, 1 }
|
||||||
'cwd' { native_cwd, 0 }
|
'cwd' { native_cwd, 0 }
|
||||||
'json_pretty' { native_json_pretty, 1 }
|
'json_pretty' { native_json_pretty, 1 }
|
||||||
@@ -1257,6 +1324,7 @@ fn (mut g Gen) gen_binary(e Expr) ! {
|
|||||||
.le { op_le }
|
.le { op_le }
|
||||||
.gt { op_gt }
|
.gt { op_gt }
|
||||||
.ge { op_ge }
|
.ge { op_ge }
|
||||||
|
.kw_in { op_in }
|
||||||
.amp { op_and_b }
|
.amp { op_and_b }
|
||||||
.pipe { op_or_b }
|
.pipe { op_or_b }
|
||||||
.caret { op_xor }
|
.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) {
|
fn (mut g Gen) scan_stmt(st Stmt, mut bound map[string]bool, mut caps []string, mut seen map[string]bool) {
|
||||||
match st.kind {
|
match st.kind {
|
||||||
.expr_stmt {
|
.expr_stmt {
|
||||||
@@ -1623,6 +1723,11 @@ fn (mut g Gen) scan_stmt(st Stmt, mut bound map[string]bool, mut caps []string,
|
|||||||
.throw_stmt {
|
.throw_stmt {
|
||||||
g.scan_expr(st.expr, mut bound, mut caps, mut seen)
|
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 {}
|
.break_stmt, .continue_stmt {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -310,6 +310,7 @@ fn (mut l Lexer) lex_ident(line int, col int) Tok {
|
|||||||
'interface' { TokKind.kw_interface }
|
'interface' { TokKind.kw_interface }
|
||||||
'try' { TokKind.kw_try }
|
'try' { TokKind.kw_try }
|
||||||
'catch' { TokKind.kw_catch }
|
'catch' { TokKind.kw_catch }
|
||||||
|
'defer' { TokKind.kw_defer }
|
||||||
'throw' { TokKind.kw_throw }
|
'throw' { TokKind.kw_throw }
|
||||||
else { TokKind.ident }
|
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_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_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_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)
|
// native builtin ids (keep in sync with vm/opcodes.v)
|
||||||
const native_abs = 100
|
const native_abs = 100
|
||||||
@@ -191,3 +192,7 @@ const native_input = 196
|
|||||||
const native_flag_val = 197
|
const native_flag_val = 197
|
||||||
const native_flag_has = 198
|
const native_flag_has = 198
|
||||||
const native_flag_positional = 199
|
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()!
|
body := p.parse_block()!
|
||||||
return Stmt{ kind: .for_in_stmt, target: val_name, idx_target: idx_name, expr: first, body: body, line: t.line }
|
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 {
|
.kw_return {
|
||||||
p.advance()
|
p.advance()
|
||||||
mut e := Expr{}
|
mut e := Expr{}
|
||||||
@@ -664,7 +669,7 @@ fn (mut p Parser) parse_eq() !Expr {
|
|||||||
|
|
||||||
fn (mut p Parser) parse_rel() !Expr {
|
fn (mut p Parser) parse_rel() !Expr {
|
||||||
mut e := p.parse_shift()!
|
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()
|
op := p.advance()
|
||||||
rhs := p.parse_shift()!
|
rhs := p.parse_shift()!
|
||||||
e = bin_node(op.kind, e, rhs, op.line)
|
e = bin_node(op.kind, e, rhs, op.line)
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ pub enum TokKind {
|
|||||||
kw_interface
|
kw_interface
|
||||||
kw_try
|
kw_try
|
||||||
kw_catch
|
kw_catch
|
||||||
|
kw_defer
|
||||||
kw_throw
|
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
|
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() {
|
fn test_failing() {
|
||||||
// this one is meant to fail — shows up in `vr test` output
|
// this one is meant to fail — shows up in `vr test` output
|
||||||
assert 1 == 2
|
assert 1 == 2
|
||||||
|
|||||||
+83
@@ -118,6 +118,15 @@ fn (mut v Vm) native(id int, _argc int) ! {
|
|||||||
x := v.pop()!
|
x := v.pop()!
|
||||||
v.push(v.alloc_str(v.type_name(x)))!
|
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 {
|
native_split {
|
||||||
delim := v.pop_str()!
|
delim := v.pop_str()!
|
||||||
s := 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) {
|
if v.is_float(x) {
|
||||||
return 'float'
|
return 'float'
|
||||||
}
|
}
|
||||||
|
if v.is_none(x) {
|
||||||
|
return 'none'
|
||||||
|
}
|
||||||
|
if v.is_closure(x) {
|
||||||
|
return 'closure'
|
||||||
|
}
|
||||||
if v.is_arr(x) {
|
if v.is_arr(x) {
|
||||||
return 'array'
|
return 'array'
|
||||||
}
|
}
|
||||||
@@ -1248,6 +1263,74 @@ fn (mut v Vm) type_name(x i64) string {
|
|||||||
return 'int'
|
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
|
// 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
|
// 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.
|
// 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_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_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_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)
|
// native builtin ids (the operand to op_native)
|
||||||
const native_abs = 100
|
const native_abs = 100
|
||||||
@@ -190,3 +191,7 @@ const native_input = 196
|
|||||||
const native_flag_val = 197
|
const native_flag_val = 197
|
||||||
const native_flag_has = 198
|
const native_flag_has = 198
|
||||||
const native_flag_positional = 199
|
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_varargs { 'varargs' }
|
||||||
op_str_method { 'str_method' }
|
op_str_method { 'str_method' }
|
||||||
op_push_none { 'push_none' }
|
op_push_none { 'push_none' }
|
||||||
|
op_in { 'in' }
|
||||||
else { '??' }
|
else { '??' }
|
||||||
}
|
}
|
||||||
mut s := ''
|
mut s := ''
|
||||||
|
|||||||
@@ -632,6 +632,27 @@ fn (mut v Vm) exec() ! {
|
|||||||
op_slice {
|
op_slice {
|
||||||
v.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 {
|
op_native {
|
||||||
v.ip++
|
v.ip++
|
||||||
id := int(v.read_i64())
|
id := int(v.read_i64())
|
||||||
@@ -1005,6 +1026,25 @@ fn (mut v Vm) call(target int, argc int) {
|
|||||||
v.ip = target
|
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) ! {
|
fn (mut v Vm) ret(with_val bool) ! {
|
||||||
retval := if with_val { v.pop()! } else { v.enc_int(0) }
|
retval := if with_val { v.pop()! } else { v.enc_int(0) }
|
||||||
v.sp = v.bp - 1
|
v.sp = v.bp - 1
|
||||||
|
|||||||
Reference in New Issue
Block a user