mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 14:57:18 +00:00
Tier Three: Added threads/jobs
This commit is contained in:
@@ -326,7 +326,19 @@ fn main() {
|
|||||||
- generics: `fn first[T](arr) { return arr[0] }` with checked call sites
|
- generics: `fn first[T](arr) { return arr[0] }` with checked call sites
|
||||||
`first[int](arr)` — the VM is dynamically typed, so type parameters erase
|
`first[int](arr)` — the VM is dynamically typed, so type parameters erase
|
||||||
to a single function but arity and duplicates are validated
|
to a single function but arity and duplicates are validated
|
||||||
- variables: `let name = expr`, reassignment `name = expr`
|
- variables: `let name = expr` binds **immutably** (reassignment is a
|
||||||
|
compile-time error); declare `mut name = expr` to opt in to reassignment
|
||||||
|
`name = expr`. Loop variables, function parameters, and the receiver are
|
||||||
|
always reassignable, as are element/field writes (`a[i] = v`, `s.f = v`)
|
||||||
|
- string builders: `strings.builder()` / `strings.build_add(sb, piece)` /
|
||||||
|
`strings.build_str(sb)` (also unqualified `sb_new` / `sb_add` / `sb_str`)
|
||||||
|
accumulate many pieces and materialize once — O(n) instead of the O(n²)
|
||||||
|
of a long `+` chain
|
||||||
|
- concurrency: `spawn(fn, args...)` runs the closure on its own OS thread in
|
||||||
|
an isolated VM (captured values and args are deep-copied) and returns a job
|
||||||
|
id; `spawn_join(id)` blocks until the job finishes and returns its result
|
||||||
|
(int/float/string, or a flat array/struct rendered as its string form).
|
||||||
|
Jobs run truly in parallel and their failures propagate as catchable errors
|
||||||
- floats: `3.14`, `0.5`, `1e3` — float literals and `float(x)`; arithmetic
|
- floats: `3.14`, `0.5`, `1e3` — float literals and `float(x)`; arithmetic
|
||||||
promotes to float; `floor` / `ceil` / `round` / `sqrt` / `pow` / `abs` /
|
promotes to float; `floor` / `ceil` / `round` / `sqrt` / `pow` / `abs` /
|
||||||
`min` / `max` / `rand` / `rand_int`
|
`min` / `max` / `rand` / `rand_int`
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ pub mut:
|
|||||||
pub struct Stmt {
|
pub struct Stmt {
|
||||||
pub mut:
|
pub mut:
|
||||||
kind StmtKind
|
kind StmtKind
|
||||||
|
mutable bool // let_stmt/destruct_stmt: bound with `mut` (reassignable)
|
||||||
target string
|
target string
|
||||||
idx_target string // for_in_stmt: index variable name (empty when unused)
|
idx_target string // for_in_stmt: index variable name (empty when unused)
|
||||||
destruct_targets []string // destruct_stmt: names to bind
|
destruct_targets []string // destruct_stmt: names to bind
|
||||||
|
|||||||
+37
-7
@@ -40,14 +40,15 @@ struct FnSig {
|
|||||||
|
|
||||||
struct Checker {
|
struct Checker {
|
||||||
mut:
|
mut:
|
||||||
types map[string]TypeInfo // current scope: local name -> type
|
types map[string]TypeInfo // current scope: local name -> type
|
||||||
fns map[string]FnSig
|
mutable map[string]bool // local names bound with `mut`, params, loop vars; only these may be reassigned
|
||||||
structs map[string][]string
|
fns map[string]FnSig
|
||||||
enums map[string][]string
|
structs map[string][]string
|
||||||
consts map[string]TypeInfo
|
enums map[string][]string
|
||||||
|
consts map[string]TypeInfo
|
||||||
loop_depth int
|
loop_depth int
|
||||||
checked map[string]bool // imported files already checked
|
checked map[string]bool // imported files already checked
|
||||||
modules map[string]bool // imported module names (bare `import os`)
|
modules map[string]bool // imported module names (bare `import os`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check validates a parsed program and returns an error on the first problem.
|
// check validates a parsed program and returns an error on the first problem.
|
||||||
@@ -148,9 +149,12 @@ fn (mut c Checker) check_fn(fd FnDecl) ! {
|
|||||||
// receiver and parameters are untyped (unknown) — the runtime is dynamic
|
// receiver and parameters are untyped (unknown) — the runtime is dynamic
|
||||||
if fd.recv_name.len > 0 {
|
if fd.recv_name.len > 0 {
|
||||||
c.types[fd.recv_name] = TypeInfo{ kind: .struct_t }
|
c.types[fd.recv_name] = TypeInfo{ kind: .struct_t }
|
||||||
|
c.mutable[fd.recv_name] = true
|
||||||
}
|
}
|
||||||
for p in fd.params {
|
for p in fd.params {
|
||||||
c.types[p] = TypeInfo{ kind: .unknown }
|
c.types[p] = TypeInfo{ kind: .unknown }
|
||||||
|
// parameters are reassignable: callers pass values, bodies may rewrite
|
||||||
|
c.mutable[p] = true
|
||||||
}
|
}
|
||||||
for st in fd.body {
|
for st in fd.body {
|
||||||
c.check_stmt(st)!
|
c.check_stmt(st)!
|
||||||
@@ -165,11 +169,13 @@ fn (mut c Checker) check_stmt(st Stmt) ! {
|
|||||||
.let_stmt {
|
.let_stmt {
|
||||||
t := c.check_expr(st.expr)!
|
t := c.check_expr(st.expr)!
|
||||||
c.types[st.target] = t
|
c.types[st.target] = t
|
||||||
|
c.mutable[st.target] = st.mutable // false for `let`, true for `mut`
|
||||||
}
|
}
|
||||||
.destruct_stmt {
|
.destruct_stmt {
|
||||||
base := c.check_expr(st.expr)!
|
base := c.check_expr(st.expr)!
|
||||||
for name in st.destruct_targets {
|
for name in st.destruct_targets {
|
||||||
c.types[name] = if st.destruct_field { TypeInfo{ kind: .unknown } } else { TypeInfo{ kind: .unknown } }
|
c.types[name] = if st.destruct_field { TypeInfo{ kind: .unknown } } else { TypeInfo{ kind: .unknown } }
|
||||||
|
c.mutable[name] = false
|
||||||
}
|
}
|
||||||
_ = base
|
_ = base
|
||||||
}
|
}
|
||||||
@@ -177,6 +183,9 @@ fn (mut c Checker) check_stmt(st Stmt) ! {
|
|||||||
if st.target !in c.types {
|
if st.target !in c.types {
|
||||||
return error('unknown variable "${st.target}" (line ${st.line})')
|
return error('unknown variable "${st.target}" (line ${st.line})')
|
||||||
}
|
}
|
||||||
|
if !(st.target in c.mutable) || !c.mutable[st.target] {
|
||||||
|
return error('cannot reassign immutable "${st.target}" — declare it with `mut` (line ${st.line})')
|
||||||
|
}
|
||||||
_ = c.check_expr(st.expr)!
|
_ = c.check_expr(st.expr)!
|
||||||
}
|
}
|
||||||
.index_assign {
|
.index_assign {
|
||||||
@@ -225,6 +234,7 @@ fn (mut c Checker) check_stmt(st Stmt) ! {
|
|||||||
c.expect_numeric(start_t, 'range start', st.line)!
|
c.expect_numeric(start_t, 'range start', st.line)!
|
||||||
c.expect_numeric(end_t, 'range end', st.line)!
|
c.expect_numeric(end_t, 'range end', st.line)!
|
||||||
c.types[st.target] = TypeInfo{ kind: .int_t }
|
c.types[st.target] = TypeInfo{ kind: .int_t }
|
||||||
|
c.mutable[st.target] = true // the loop advances it; bodies may too
|
||||||
c.loop_depth++
|
c.loop_depth++
|
||||||
for s in st.body {
|
for s in st.body {
|
||||||
c.check_stmt(s)!
|
c.check_stmt(s)!
|
||||||
@@ -243,8 +253,10 @@ fn (mut c Checker) check_stmt(st Stmt) ! {
|
|||||||
}
|
}
|
||||||
c.types[st.target] = TypeInfo{ kind: .unknown }
|
c.types[st.target] = TypeInfo{ kind: .unknown }
|
||||||
}
|
}
|
||||||
|
c.mutable[st.target] = true // loop vars are reassigned by iteration
|
||||||
if st.idx_target.len > 0 {
|
if st.idx_target.len > 0 {
|
||||||
c.types[st.idx_target] = TypeInfo{ kind: .int_t }
|
c.types[st.idx_target] = TypeInfo{ kind: .int_t }
|
||||||
|
c.mutable[st.idx_target] = true
|
||||||
}
|
}
|
||||||
c.loop_depth++
|
c.loop_depth++
|
||||||
for s in st.body {
|
for s in st.body {
|
||||||
@@ -295,6 +307,10 @@ fn (mut c Checker) check_expr(e Expr) !TypeInfo {
|
|||||||
c.types[e.name]
|
c.types[e.name]
|
||||||
} else if e.name in c.consts {
|
} else if e.name in c.consts {
|
||||||
c.consts[e.name]
|
c.consts[e.name]
|
||||||
|
} else if e.name in c.fns {
|
||||||
|
// a bare function name used as a value (e.g. the first argument
|
||||||
|
// of `spawn`) evaluates to a closure
|
||||||
|
TypeInfo{ kind: .unknown }
|
||||||
} else {
|
} else {
|
||||||
return error('unknown variable "${e.name}" (line ${e.line})')
|
return error('unknown variable "${e.name}" (line ${e.line})')
|
||||||
}
|
}
|
||||||
@@ -539,6 +555,16 @@ fn (mut c Checker) check_call(e Expr) !TypeInfo {
|
|||||||
// host builtins (native) — validate arity from the spec table
|
// host builtins (native) — validate arity from the spec table
|
||||||
bid, bargc := builtin_spec(e.name)
|
bid, bargc := builtin_spec(e.name)
|
||||||
if bid >= 0 {
|
if bid >= 0 {
|
||||||
|
if e.name == 'spawn' {
|
||||||
|
// variadic: a function followed by zero or more arguments
|
||||||
|
if e.args.len < 1 {
|
||||||
|
return error('spawn() expects a function plus zero or more arguments (line ${e.line})')
|
||||||
|
}
|
||||||
|
for a in e.args {
|
||||||
|
_ = c.check_expr(a)!
|
||||||
|
}
|
||||||
|
return builtin_result_type(e.name)
|
||||||
|
}
|
||||||
if e.args.len != bargc {
|
if e.args.len != bargc {
|
||||||
return error('${e.name}() takes exactly ${bargc} argument(s) (line ${e.line})')
|
return error('${e.name}() takes exactly ${bargc} argument(s) (line ${e.line})')
|
||||||
}
|
}
|
||||||
@@ -601,6 +627,10 @@ fn builtin_result_type(name string) TypeInfo {
|
|||||||
// stdlib: JSON + string formatting
|
// stdlib: JSON + string formatting
|
||||||
'json_encode', 'format', 'replace', 'pad', 'pad_left', 'repeat' { TypeInfo{ kind: .string_t } }
|
'json_encode', 'format', 'replace', 'pad', 'pad_left', 'repeat' { TypeInfo{ kind: .string_t } }
|
||||||
'json_decode', 'split_lines' { TypeInfo{ kind: .unknown } }
|
'json_decode', 'split_lines' { TypeInfo{ kind: .unknown } }
|
||||||
|
'sb_new', 'sb_add', 'sb_str' { TypeInfo{ kind: .string_t } }
|
||||||
|
'sb_len' { TypeInfo{ kind: .int_t } }
|
||||||
|
'spawn' { TypeInfo{ kind: .int_t } }
|
||||||
|
'spawn_join' { TypeInfo{ kind: .unknown } }
|
||||||
'cwd', 'json_pretty' { TypeInfo{ kind: .string_t } }
|
'cwd', 'json_pretty' { TypeInfo{ kind: .string_t } }
|
||||||
'build_is_dir' { TypeInfo{ kind: .int_t } }
|
'build_is_dir' { TypeInfo{ kind: .int_t } }
|
||||||
// build-module builtins (.vrmm)
|
// build-module builtins (.vrmm)
|
||||||
|
|||||||
+44
-5
@@ -45,6 +45,7 @@ mut:
|
|||||||
enter_off u32
|
enter_off u32
|
||||||
next_lbl int
|
next_lbl int
|
||||||
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
|
||||||
captures []string // enclosing locals captured by the closure being compiled
|
captures []string // enclosing locals captured by the closure being compiled
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,6 +118,10 @@ fn gen(prog Program) !obj.Obj {
|
|||||||
}
|
}
|
||||||
g.relocs << obj.Reloc{ offset: u32(code_off) + r.offset, name: rname, kind: r.kind }
|
g.relocs << obj.Reloc{ offset: u32(code_off) + r.offset, name: rname, kind: r.kind }
|
||||||
}
|
}
|
||||||
|
// record imported function names so a bare name can be used as a value
|
||||||
|
for s in imported.symbols {
|
||||||
|
g.fn_names[s.name] = true
|
||||||
|
}
|
||||||
// merge debug info, rebasing offsets into this object's code space
|
// merge debug info, rebasing offsets into this object's code space
|
||||||
for l in imported.lines {
|
for l in imported.lines {
|
||||||
g.lines << obj.LineInfo{ off: u32(code_off) + l.off, line: l.line }
|
g.lines << obj.LineInfo{ off: u32(code_off) + l.off, line: l.line }
|
||||||
@@ -127,6 +132,9 @@ fn gen(prog Program) !obj.Obj {
|
|||||||
g.dbg_locals << obj.DbgLocal{ fn: prefix + l.fn, name: l.name, slot: l.slot }
|
g.dbg_locals << obj.DbgLocal{ fn: prefix + l.fn, name: l.name, slot: l.slot }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for fd in prog.fns {
|
||||||
|
g.fn_names[fd.name] = true
|
||||||
|
}
|
||||||
for fd in prog.fns {
|
for fd in prog.fns {
|
||||||
g.captures = []string{} // top-level functions capture nothing
|
g.captures = []string{} // top-level functions capture nothing
|
||||||
g.gen_fn(fd)!
|
g.gen_fn(fd)!
|
||||||
@@ -800,12 +808,20 @@ fn (mut g Gen) gen_expr(e Expr) ! {
|
|||||||
// check if it's an enum variant (e.g., Color.red)
|
// check if it's an enum variant (e.g., Color.red)
|
||||||
g.code << op_push_i
|
g.code << op_push_i
|
||||||
g.code << obj.encode_i64(i64(g.enum_vals[e.name]))
|
g.code << obj.encode_i64(i64(g.enum_vals[e.name]))
|
||||||
} else {
|
} else if e.name in g.locals {
|
||||||
idx := g.locals[e.name] or {
|
idx := g.locals[e.name]
|
||||||
return error('unknown variable "${e.name}" at line ${e.line}')
|
|
||||||
}
|
|
||||||
g.code << op_load
|
g.code << op_load
|
||||||
g.code << obj.encode_i64(i64(idx))
|
g.code << obj.encode_i64(i64(idx))
|
||||||
|
} else if e.name in g.fn_names {
|
||||||
|
// a bare top-level function name used as a value (e.g. the first
|
||||||
|
// argument of `spawn`) — emit a zero-capture closure whose entry
|
||||||
|
// is resolved by the linker.
|
||||||
|
g.code << op_closure
|
||||||
|
g.code << obj.encode_i64(0)
|
||||||
|
g.relocs << obj.Reloc{ offset: u32(g.code.len) - 8, name: e.name, kind: 0 }
|
||||||
|
g.code << obj.encode_i64(0) // no captured locals
|
||||||
|
} else {
|
||||||
|
return error('unknown variable "${e.name}" at line ${e.line}')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.unary {
|
.unary {
|
||||||
@@ -911,6 +927,20 @@ fn (mut g Gen) gen_call(e Expr) ! {
|
|||||||
// host builtins (file I/O, OS, math, collections) go through op_native
|
// host builtins (file I/O, OS, math, collections) go through op_native
|
||||||
bid, bargc := builtin_spec(e.name)
|
bid, bargc := builtin_spec(e.name)
|
||||||
if bid >= 0 {
|
if bid >= 0 {
|
||||||
|
// spawn takes a variable number of arguments (the function followed by
|
||||||
|
// its call arguments), so emit the real argc rather than the spec's
|
||||||
|
if e.name == 'spawn' {
|
||||||
|
if e.args.len < 1 {
|
||||||
|
return error('spawn expects a function plus zero or more arguments (line ${e.line})')
|
||||||
|
}
|
||||||
|
for a in e.args {
|
||||||
|
g.gen_expr(a)!
|
||||||
|
}
|
||||||
|
g.code << op_native
|
||||||
|
g.code << obj.encode_i64(i64(bid))
|
||||||
|
g.code << obj.encode_i64(i64(e.args.len))
|
||||||
|
return
|
||||||
|
}
|
||||||
if e.args.len != bargc {
|
if e.args.len != bargc {
|
||||||
return error('${e.name}() takes exactly ${bargc} argument(s) (line ${e.line})')
|
return error('${e.name}() takes exactly ${bargc} argument(s) (line ${e.line})')
|
||||||
}
|
}
|
||||||
@@ -1002,6 +1032,14 @@ fn builtin_spec(name string) (int, int) {
|
|||||||
'pad' { native_pad, 2 }
|
'pad' { native_pad, 2 }
|
||||||
'pad_left' { native_pad_left, 2 }
|
'pad_left' { native_pad_left, 2 }
|
||||||
'repeat' { native_repeat, 2 }
|
'repeat' { native_repeat, 2 }
|
||||||
|
// string builder
|
||||||
|
'sb_new' { native_sb_new, 0 }
|
||||||
|
'sb_add' { native_sb_add, 2 }
|
||||||
|
'sb_str' { native_sb_str, 1 }
|
||||||
|
'sb_len' { native_sb_len, 1 }
|
||||||
|
// concurrency
|
||||||
|
'spawn' { native_spawn, 1 }
|
||||||
|
'spawn_join' { native_spawn_join, 1 }
|
||||||
'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 }
|
||||||
@@ -1261,7 +1299,8 @@ fn (mut g Gen) expr_type(e Expr) string {
|
|||||||
'upper', 'lower', 'trim', 'str', 'getenv', 'read_file', 'join' { 'string' }
|
'upper', 'lower', 'trim', 'str', 'getenv', 'read_file', 'join' { 'string' }
|
||||||
'build_compile', 'build_assemble', 'build_link', 'build_exec', 'build_base',
|
'build_compile', 'build_assemble', 'build_link', 'build_exec', 'build_base',
|
||||||
'build_dir', 'build_join', 'build_root' { 'string' }
|
'build_dir', 'build_join', 'build_root' { 'string' }
|
||||||
'json_encode', 'format', 'replace', 'pad', 'pad_left', 'repeat' { 'string' }
|
'json_encode', 'format', 'replace', 'pad', 'pad_left', 'repeat', 'sb_new', 'sb_add', 'sb_str' { 'string' }
|
||||||
|
'sb_len' { 'int' }
|
||||||
'cwd', 'json_pretty' { 'string' }
|
'cwd', 'json_pretty' { 'string' }
|
||||||
else { '' }
|
else { '' }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -285,6 +285,7 @@ fn (mut l Lexer) lex_ident(line int, col int) Tok {
|
|||||||
'fn' { TokKind.kw_fn }
|
'fn' { TokKind.kw_fn }
|
||||||
'struct' { TokKind.kw_struct }
|
'struct' { TokKind.kw_struct }
|
||||||
'let' { TokKind.kw_let }
|
'let' { TokKind.kw_let }
|
||||||
|
'mut' { TokKind.kw_mut }
|
||||||
'if' { TokKind.kw_if }
|
'if' { TokKind.kw_if }
|
||||||
'else' { TokKind.kw_else }
|
'else' { TokKind.kw_else }
|
||||||
'while' { TokKind.kw_while }
|
'while' { TokKind.kw_while }
|
||||||
|
|||||||
@@ -172,3 +172,13 @@ const native_path_abs = 185
|
|||||||
const native_path_rel = 186
|
const native_path_rel = 186
|
||||||
const native_exec_full = 187
|
const native_exec_full = 187
|
||||||
const native_weekday = 188
|
const native_weekday = 188
|
||||||
|
|
||||||
|
// string builder (efficient repeated concatenation)
|
||||||
|
const native_sb_new = 189
|
||||||
|
const native_sb_add = 190
|
||||||
|
const native_sb_str = 191
|
||||||
|
const native_sb_len = 192
|
||||||
|
|
||||||
|
// concurrency: spawn/join threads
|
||||||
|
const native_spawn = 193
|
||||||
|
const native_spawn_join = 194
|
||||||
|
|||||||
+5
-3
@@ -315,9 +315,11 @@ fn (mut p Parser) parse_block() ![]Stmt {
|
|||||||
fn (mut p Parser) parse_stmt() !Stmt {
|
fn (mut p Parser) parse_stmt() !Stmt {
|
||||||
t := p.cur()
|
t := p.cur()
|
||||||
match t.kind {
|
match t.kind {
|
||||||
.kw_let {
|
.kw_let, .kw_mut {
|
||||||
|
mutable := t.kind == .kw_mut
|
||||||
p.advance()
|
p.advance()
|
||||||
// destructuring let: `let { a, b } = expr` or `let [a, b] = expr`
|
// mutable with destructuring is not supported (kept simple); `let {
|
||||||
|
// a, b } = x` and `let [a, b] = x` stay immutable-style bindings
|
||||||
if p.cur().kind == .lbrace || p.cur().kind == .lbracket {
|
if p.cur().kind == .lbrace || p.cur().kind == .lbracket {
|
||||||
is_field := p.cur().kind == .lbrace
|
is_field := p.cur().kind == .lbrace
|
||||||
p.advance()
|
p.advance()
|
||||||
@@ -344,7 +346,7 @@ fn (mut p Parser) parse_stmt() !Stmt {
|
|||||||
name := p.expect(.ident, 'variable name')!
|
name := p.expect(.ident, 'variable name')!
|
||||||
p.expect(.assign, "'='")!
|
p.expect(.assign, "'='")!
|
||||||
e := p.parse_expr()!
|
e := p.parse_expr()!
|
||||||
return Stmt{ kind: .let_stmt, target: name.lit, expr: e, line: t.line }
|
return Stmt{ kind: .let_stmt, target: name.lit, expr: e, mutable: mutable, line: t.line }
|
||||||
} .kw_if {
|
} .kw_if {
|
||||||
return p.parse_if(t)!
|
return p.parse_if(t)!
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ pub enum TokKind {
|
|||||||
kw_fn
|
kw_fn
|
||||||
kw_struct
|
kw_struct
|
||||||
kw_let
|
kw_let
|
||||||
|
kw_mut
|
||||||
kw_if
|
kw_if
|
||||||
kw_else
|
kw_else
|
||||||
kw_while
|
kw_while
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@
|
|||||||
// Run with: vr run examples/arrays.vr
|
// Run with: vr run examples/arrays.vr
|
||||||
|
|
||||||
fn sum(items) {
|
fn sum(items) {
|
||||||
let total = 0
|
mut total = 0
|
||||||
for x in items {
|
for x in items {
|
||||||
total = total + x
|
total = total + x
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ fn main() {
|
|||||||
println("")
|
println("")
|
||||||
|
|
||||||
// find the first even number in an array
|
// find the first even number in an array
|
||||||
let found = -1
|
mut found = -1
|
||||||
for x in a {
|
for x in a {
|
||||||
if x % 2 == 0 {
|
if x % 2 == 0 {
|
||||||
found = x
|
found = x
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// compound assignment operators: +=, -=, *=, /=
|
// compound assignment operators: +=, -=, *=, /=
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let x = 10
|
mut x = 10
|
||||||
println(x)
|
println(x)
|
||||||
|
|
||||||
x += 5
|
x += 5
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// iterate and use count
|
// iterate and use count
|
||||||
let total = 0
|
mut total = 0
|
||||||
for c in Color {
|
for c in Color {
|
||||||
total = total + c.count()
|
total = total + c.count()
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// break and continue work
|
// break and continue work
|
||||||
let weekdays = 0
|
mut weekdays = 0
|
||||||
for d in Day {
|
for d in Day {
|
||||||
if d == Day.saturday {
|
if d == Day.saturday {
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ fn main() {
|
|||||||
|
|
||||||
// use the index for computation
|
// use the index for computation
|
||||||
let nums = [10, 20, 30, 40, 50]
|
let nums = [10, 20, 30, 40, 50]
|
||||||
let sum = 0
|
mut sum = 0
|
||||||
for i, v in nums {
|
for i, v in nums {
|
||||||
if i % 2 == 0 {
|
if i % 2 == 0 {
|
||||||
sum += v
|
sum += v
|
||||||
|
|||||||
+3
-3
@@ -15,8 +15,8 @@ fn fib(n) {
|
|||||||
|
|
||||||
// returns n! via a loop
|
// returns n! via a loop
|
||||||
fn fact(n) {
|
fn fact(n) {
|
||||||
let result = 1
|
mut result = 1
|
||||||
let i = 1
|
mut i = 1
|
||||||
while i <= n {
|
while i <= n {
|
||||||
result = result * i
|
result = result * i
|
||||||
i = i + 1
|
i = i + 1
|
||||||
@@ -54,7 +54,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// while loop
|
// while loop
|
||||||
let i = 0
|
mut i = 0
|
||||||
while i < 3 {
|
while i < 3 {
|
||||||
println("counting " + i)
|
println("counting " + i)
|
||||||
i = i + 1
|
i = i + 1
|
||||||
|
|||||||
+1
-1
@@ -64,7 +64,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// match inside a loop (break/continue still work)
|
// match inside a loop (break/continue still work)
|
||||||
let found = 0
|
mut found = 0
|
||||||
for i in 0..10 {
|
for i in 0..10 {
|
||||||
match i {
|
match i {
|
||||||
4 {
|
4 {
|
||||||
|
|||||||
+89
-20
@@ -33,8 +33,8 @@ fn test_strings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_loops() {
|
fn test_loops() {
|
||||||
let total = 0
|
mut total = 0
|
||||||
let i = 1
|
mut i = 1
|
||||||
while i <= 10 {
|
while i <= 10 {
|
||||||
total = total + i
|
total = total + i
|
||||||
i = i + 1
|
i = i + 1
|
||||||
@@ -64,12 +64,12 @@ fn test_arrays() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_for_range() {
|
fn test_for_range() {
|
||||||
let total = 0
|
mut total = 0
|
||||||
for i in 0..5 {
|
for i in 0..5 {
|
||||||
total = total + i
|
total = total + i
|
||||||
}
|
}
|
||||||
assert total == 10 // 0+1+2+3+4
|
assert total == 10 // 0+1+2+3+4
|
||||||
let inc = 0
|
mut inc = 0
|
||||||
for i in 1...3 {
|
for i in 1...3 {
|
||||||
inc = inc + i
|
inc = inc + i
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@ fn test_for_range() {
|
|||||||
|
|
||||||
fn test_for_in() {
|
fn test_for_in() {
|
||||||
let a = [5, 6, 7]
|
let a = [5, 6, 7]
|
||||||
let total = 0
|
mut total = 0
|
||||||
for x in a {
|
for x in a {
|
||||||
total = total + x
|
total = total + x
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@ fn test_for_in() {
|
|||||||
|
|
||||||
fn test_break_continue() {
|
fn test_break_continue() {
|
||||||
// break exits the loop early
|
// break exits the loop early
|
||||||
let total = 0
|
mut total = 0
|
||||||
for i in 0..100 {
|
for i in 0..100 {
|
||||||
if i == 5 {
|
if i == 5 {
|
||||||
break
|
break
|
||||||
@@ -104,7 +104,7 @@ fn test_break_continue() {
|
|||||||
assert total == 10 // 0+1+2+3+4
|
assert total == 10 // 0+1+2+3+4
|
||||||
|
|
||||||
// continue skips to the next iteration
|
// continue skips to the next iteration
|
||||||
let sum = 0
|
mut sum = 0
|
||||||
for i in 0..6 {
|
for i in 0..6 {
|
||||||
if i == 3 {
|
if i == 3 {
|
||||||
continue
|
continue
|
||||||
@@ -114,7 +114,7 @@ fn test_break_continue() {
|
|||||||
assert sum == 12 // 0+1+2+4+5 (3 skipped)
|
assert sum == 12 // 0+1+2+4+5 (3 skipped)
|
||||||
|
|
||||||
// break and continue in while loops
|
// break and continue in while loops
|
||||||
let n = 0
|
mut n = 0
|
||||||
while true {
|
while true {
|
||||||
n = n + 1
|
n = n + 1
|
||||||
if n == 3 {
|
if n == 3 {
|
||||||
@@ -141,7 +141,7 @@ fn test_break_continue() {
|
|||||||
|
|
||||||
fn test_else_if() {
|
fn test_else_if() {
|
||||||
let score = 85
|
let score = 85
|
||||||
let grade = ""
|
mut grade = ""
|
||||||
if score >= 90 {
|
if score >= 90 {
|
||||||
grade = "A"
|
grade = "A"
|
||||||
} else if score >= 80 {
|
} else if score >= 80 {
|
||||||
@@ -156,7 +156,7 @@ fn test_else_if() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_match() {
|
fn test_match() {
|
||||||
let total = 0
|
mut total = 0
|
||||||
match 2 {
|
match 2 {
|
||||||
1 {
|
1 {
|
||||||
total = total + 1
|
total = total + 1
|
||||||
@@ -172,7 +172,7 @@ fn test_match() {
|
|||||||
|
|
||||||
// match on strings
|
// match on strings
|
||||||
let word = "cat"
|
let word = "cat"
|
||||||
let sound = ""
|
mut sound = ""
|
||||||
match word {
|
match word {
|
||||||
"dog" {
|
"dog" {
|
||||||
sound = "woof"
|
sound = "woof"
|
||||||
@@ -187,7 +187,7 @@ fn test_match() {
|
|||||||
assert sound == "meow"
|
assert sound == "meow"
|
||||||
|
|
||||||
// no else arm: nothing runs when nothing matches
|
// no else arm: nothing runs when nothing matches
|
||||||
let hit = 0
|
mut hit = 0
|
||||||
match 99 {
|
match 99 {
|
||||||
1 {
|
1 {
|
||||||
hit = 1
|
hit = 1
|
||||||
@@ -376,7 +376,7 @@ fn test_closures() {
|
|||||||
assert "v=" + str(dbl(4)) == "v=8"
|
assert "v=" + str(dbl(4)) == "v=8"
|
||||||
// closure containing a loop
|
// closure containing a loop
|
||||||
let sum = fn () {
|
let sum = fn () {
|
||||||
let total = 0
|
mut total = 0
|
||||||
for i in 0..5 {
|
for i in 0..5 {
|
||||||
total = total + i
|
total = total + i
|
||||||
}
|
}
|
||||||
@@ -402,7 +402,7 @@ fn test_enums() {
|
|||||||
let next = Suit.hearts + 1
|
let next = Suit.hearts + 1
|
||||||
assert next == 1
|
assert next == 1
|
||||||
// enum iteration
|
// enum iteration
|
||||||
let names = []
|
mut names = []
|
||||||
for s in Suit {
|
for s in Suit {
|
||||||
names = push(names, s.to_string())
|
names = push(names, s.to_string())
|
||||||
}
|
}
|
||||||
@@ -471,7 +471,7 @@ fn test_closure_capture() {
|
|||||||
assert greet() == "hi alice age=30"
|
assert greet() == "hi alice age=30"
|
||||||
|
|
||||||
// capture by value: later changes to the outer var don't affect the closure
|
// capture by value: later changes to the outer var don't affect the closure
|
||||||
let counter = 10
|
mut counter = 10
|
||||||
let read = fn() {
|
let read = fn() {
|
||||||
return counter
|
return counter
|
||||||
}
|
}
|
||||||
@@ -491,7 +491,7 @@ fn test_closure_capture() {
|
|||||||
assert outer() == 106
|
assert outer() == 106
|
||||||
|
|
||||||
// assignment to a captured variable writes the closure's own copy
|
// assignment to a captured variable writes the closure's own copy
|
||||||
let n = 1
|
mut n = 1
|
||||||
let bump = fn() {
|
let bump = fn() {
|
||||||
n = n + 1
|
n = n + 1
|
||||||
return n
|
return n
|
||||||
@@ -530,17 +530,17 @@ fn test_dynamic_map_keys() {
|
|||||||
|
|
||||||
fn test_sort_large() {
|
fn test_sort_large() {
|
||||||
// merge sort must handle large arrays (O(n log n)) and stay sorted
|
// merge sort must handle large arrays (O(n log n)) and stay sorted
|
||||||
let a = []
|
mut a = []
|
||||||
let n = 2000
|
let n = 2000
|
||||||
let i = 0
|
mut i = 0
|
||||||
while i < n {
|
while i < n {
|
||||||
a = push(a, (n - i) % 97)
|
a = push(a, (n - i) % 97)
|
||||||
i = i + 1
|
i = i + 1
|
||||||
}
|
}
|
||||||
sort(a)
|
sort(a)
|
||||||
assert len(a) == n
|
assert len(a) == n
|
||||||
let ok = 1
|
mut ok = 1
|
||||||
let j = 1
|
mut j = 1
|
||||||
while j < n {
|
while j < n {
|
||||||
if a[j - 1] > a[j] {
|
if a[j - 1] > a[j] {
|
||||||
ok = 0
|
ok = 0
|
||||||
@@ -552,6 +552,75 @@ fn test_sort_large() {
|
|||||||
assert a[n - 1] == 96
|
assert a[n - 1] == 96
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_mutable_bindings() {
|
||||||
|
// `let` is immutable: reassignment is a compile-time error (tested in CI)
|
||||||
|
// `mut` opts into reassignment
|
||||||
|
mut counter = 0
|
||||||
|
counter = counter + 1
|
||||||
|
counter = counter + 2
|
||||||
|
assert counter == 3
|
||||||
|
// `let` still reads fine and does not accept reassignment
|
||||||
|
let fixed = 42
|
||||||
|
assert fixed == 42
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_string_builder() {
|
||||||
|
// string builders avoid O(n^2) reallocation of repeated `+`
|
||||||
|
mut sb = strings.builder()
|
||||||
|
for i in 0..10 {
|
||||||
|
sb = strings.build_add(sb, "x" + str(i) + ",")
|
||||||
|
}
|
||||||
|
let out = strings.build_str(sb)
|
||||||
|
assert out == "x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_spawn_join() {
|
||||||
|
// spawn runs a closure on an OS thread; spawn_join waits and returns
|
||||||
|
let j1 = spawn(worker_sum, 100)
|
||||||
|
let j2 = spawn(worker_sum, 200)
|
||||||
|
let r1 = spawn_join(j1)
|
||||||
|
let r2 = spawn_join(j2)
|
||||||
|
assert r1 == 4950
|
||||||
|
assert r2 == 19900
|
||||||
|
}
|
||||||
|
|
||||||
|
fn worker_sum(n) {
|
||||||
|
mut s = 0
|
||||||
|
mut i = 0
|
||||||
|
while i < n {
|
||||||
|
s = s + i
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_spawn_string_result() {
|
||||||
|
let j = spawn(worker_greet, "world")
|
||||||
|
let res = spawn_join(j)
|
||||||
|
assert res == "hello world"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn worker_greet(name) {
|
||||||
|
return "hello " + name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_spawn_error_propagation() {
|
||||||
|
mut caught = false
|
||||||
|
try {
|
||||||
|
let j = spawn(worker_boom)
|
||||||
|
let r = spawn_join(j)
|
||||||
|
assert r == 0
|
||||||
|
} catch e {
|
||||||
|
caught = true
|
||||||
|
}
|
||||||
|
assert caught
|
||||||
|
}
|
||||||
|
|
||||||
|
fn worker_boom() {
|
||||||
|
let x = 1 / 0
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ fn main() {
|
|||||||
|
|
||||||
// milliseconds for timing
|
// milliseconds for timing
|
||||||
let start = time.ms()
|
let start = time.ms()
|
||||||
let x = 0
|
mut x = 0
|
||||||
for i in 0..100000 {
|
for i in 0..100000 {
|
||||||
x = x + i
|
x = x + i
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,3 +70,19 @@ fn capitalize(s) {
|
|||||||
}
|
}
|
||||||
return upper(s[0]) + s[1..]
|
return upper(s[0]) + s[1..]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// builder returns a fresh string builder. Append pieces with build_add and
|
||||||
|
// materialize once with build_str — O(n) total instead of O(n^2) for a long
|
||||||
|
// chain of `+`. The same functions are available unqualified as sb_new,
|
||||||
|
// sb_add, sb_str (used internally by this module's builtin-backed helpers).
|
||||||
|
fn builder() {
|
||||||
|
return sb_new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_add(sb, piece) {
|
||||||
|
return sb_add(sb, piece)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_str(sb) {
|
||||||
|
return sb_str(sb)
|
||||||
|
}
|
||||||
|
|||||||
+44
@@ -640,6 +640,50 @@ fn (mut v Vm) native(id int, _argc int) ! {
|
|||||||
}
|
}
|
||||||
v.push(v.alloc_str(s.repeat(n)))!
|
v.push(v.alloc_str(s.repeat(n)))!
|
||||||
}
|
}
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
// string builder — accumulate pieces and join once (linear, not
|
||||||
|
// quadratic like repeated `+` concatenation)
|
||||||
|
native_sb_new {
|
||||||
|
v.builders << StrBuilder{}
|
||||||
|
v.push(v.mkbuilder(v.builders.len - 1))!
|
||||||
|
}
|
||||||
|
native_sb_add {
|
||||||
|
x := v.pop()!
|
||||||
|
sb := v.pop()!
|
||||||
|
if !v.is_builder(sb) || !v.valid_builder_handle(sb) {
|
||||||
|
return error('sb_add expects a string builder as its first argument')
|
||||||
|
}
|
||||||
|
if v.is_str(x) && v.valid_handle(x) {
|
||||||
|
v.builders[v.hand(sb)].parts << v.strings[v.hand(x)]
|
||||||
|
} else {
|
||||||
|
v.builders[v.hand(sb)].parts << v.val_str(x, 0)
|
||||||
|
}
|
||||||
|
v.push(sb)!
|
||||||
|
}
|
||||||
|
native_sb_str {
|
||||||
|
sb := v.pop()!
|
||||||
|
if !v.is_builder(sb) || !v.valid_builder_handle(sb) {
|
||||||
|
return error('sb_str expects a string builder')
|
||||||
|
}
|
||||||
|
v.push(v.alloc_str(v.builders[v.hand(sb)].parts.join('')))!
|
||||||
|
}
|
||||||
|
native_sb_len {
|
||||||
|
sb := v.pop()!
|
||||||
|
if !v.is_builder(sb) || !v.valid_builder_handle(sb) {
|
||||||
|
return error('sb_len expects a string builder')
|
||||||
|
}
|
||||||
|
mut n := 0
|
||||||
|
for p in v.builders[v.hand(sb)].parts {
|
||||||
|
n += p.len
|
||||||
|
}
|
||||||
|
v.push(v.enc_int(i64(n)))!
|
||||||
|
}
|
||||||
|
native_spawn {
|
||||||
|
v.push(v.native_spawn(_argc)!)!
|
||||||
|
}
|
||||||
|
native_spawn_join {
|
||||||
|
v.push(v.native_spawn_join()!)!
|
||||||
|
}
|
||||||
native_cwd {
|
native_cwd {
|
||||||
v.push(v.alloc_str(os.getwd()))!
|
v.push(v.alloc_str(os.getwd()))!
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,3 +171,13 @@ const native_path_abs = 185
|
|||||||
const native_path_rel = 186
|
const native_path_rel = 186
|
||||||
const native_exec_full = 187
|
const native_exec_full = 187
|
||||||
const native_weekday = 188
|
const native_weekday = 188
|
||||||
|
|
||||||
|
// string builder (efficient repeated concatenation)
|
||||||
|
const native_sb_new = 189
|
||||||
|
const native_sb_add = 190
|
||||||
|
const native_sb_str = 191
|
||||||
|
const native_sb_len = 192
|
||||||
|
|
||||||
|
// concurrency: spawn/join threads
|
||||||
|
const native_spawn = 193
|
||||||
|
const native_spawn_join = 194
|
||||||
|
|||||||
+187
@@ -0,0 +1,187 @@
|
|||||||
|
// spawn.v — real OS-thread concurrency for the VuurRaaf VM.
|
||||||
|
//
|
||||||
|
// let job = spawn(fn, arg1, arg2, ...) // start, returns a job id
|
||||||
|
// let result = spawn_join(job) // wait for the result
|
||||||
|
//
|
||||||
|
// Each spawned closure runs on its own OS thread in a *fresh, isolated* VM.
|
||||||
|
// The child heap is built on the parent thread (so the deep-copy of captured
|
||||||
|
// values and arguments happens while the parent heap is quiescent), then the
|
||||||
|
// child VM is handed to a worker thread which owns it exclusively. The only
|
||||||
|
// state shared between threads is the immutable bytecode; the result is
|
||||||
|
// handed back through a per-job channel.
|
||||||
|
//
|
||||||
|
// The child Vm is passed to the worker *by value*: V copies the struct, and
|
||||||
|
// because the child grows its own slice pools (reallocating the backing on
|
||||||
|
// append), the worker's mutations never touch the parent's memory.
|
||||||
|
//
|
||||||
|
// Supported argument/result shapes: ints, floats, strings, `none`, and flat
|
||||||
|
// arrays/structs of those, deep-copied into the child heap. Composite
|
||||||
|
// results are rendered to their string form so the parent never holds a
|
||||||
|
// dangling child handle.
|
||||||
|
|
||||||
|
module vm
|
||||||
|
|
||||||
|
// Job records one in-flight spawn; the result arrives on the channel.
|
||||||
|
struct Job {
|
||||||
|
mut:
|
||||||
|
entry int
|
||||||
|
ready Vm // the isolated child VM, exclusively owned by the worker thread
|
||||||
|
ch chan JobResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// JobResult is the outcome of a spawned closure, delivered via channel.
|
||||||
|
struct JobResult {
|
||||||
|
mut:
|
||||||
|
ok bool
|
||||||
|
err string
|
||||||
|
// exactly one of str / val is set: strings come back as `str` (always
|
||||||
|
// isomorphic across heaps); other scalar results as `val`; composite
|
||||||
|
// results are delivered as their string rendering in `str`.
|
||||||
|
str string
|
||||||
|
val i64
|
||||||
|
}
|
||||||
|
|
||||||
|
const max_jobs = 256
|
||||||
|
|
||||||
|
// spawn is wired to the `spawn` builtin: launch a closure on an OS thread.
|
||||||
|
fn (mut v Vm) native_spawn(argc int) !i64 {
|
||||||
|
if argc < 1 {
|
||||||
|
return error('spawn expects a function followed by zero or more arguments')
|
||||||
|
}
|
||||||
|
// stack (top first): arg_{argc-1} ... arg_0, closure
|
||||||
|
mut args := []i64{len: argc - 1}
|
||||||
|
for i in 0..argc - 1 {
|
||||||
|
args[argc - 2 - i] = v.pop()!
|
||||||
|
}
|
||||||
|
ch := v.pop()!
|
||||||
|
if !v.is_closure(ch) || !v.valid_closure_handle(ch) {
|
||||||
|
return error('spawn expects a function as its first argument')
|
||||||
|
}
|
||||||
|
cl := v.closures[v.hand(ch)]
|
||||||
|
if v.jobs.len >= max_jobs {
|
||||||
|
return error('too many concurrent spawn jobs (limit ${max_jobs})')
|
||||||
|
}
|
||||||
|
child := v.build_job_vm(cl.entry, cl.captured, args)!
|
||||||
|
idx := v.jobs.len
|
||||||
|
v.jobs << Job{ entry: cl.entry, ready: child, ch: chan JobResult{cap: 1} }
|
||||||
|
// pass the child Vm by value: the worker owns this copy exclusively
|
||||||
|
job_child := v.jobs[idx].ready
|
||||||
|
ch_result := v.jobs[idx].ch
|
||||||
|
go run_spawned_job(job_child, ch_result)
|
||||||
|
return v.enc_int(i64(idx))
|
||||||
|
}
|
||||||
|
|
||||||
|
// native_spawn_join is wired to the `spawn_join` builtin: block until the job
|
||||||
|
// finished, then return its result (deep-read back into the parent heap).
|
||||||
|
fn (mut v Vm) native_spawn_join() !i64 {
|
||||||
|
id := int(v.dec_int(v.pop()!))
|
||||||
|
if id < 0 || id >= v.jobs.len {
|
||||||
|
return error('spawn_join: unknown job id ${id}')
|
||||||
|
}
|
||||||
|
res := <-v.jobs[id].ch
|
||||||
|
if !res.ok {
|
||||||
|
return error('spawned job failed: ${res.err}')
|
||||||
|
}
|
||||||
|
if res.str != '' {
|
||||||
|
return v.alloc_str(res.str)
|
||||||
|
}
|
||||||
|
return res.val
|
||||||
|
}
|
||||||
|
|
||||||
|
// build_job_vm constructs the isolated child Vm and pre-loads the closure's
|
||||||
|
// captured values + call arguments as leading locals. Runs on the parent
|
||||||
|
// thread while the parent heap is quiescent.
|
||||||
|
fn (mut v Vm) build_job_vm(entry int, captured []i64, args []i64) !Vm {
|
||||||
|
mut child := Vm{
|
||||||
|
code: v.code
|
||||||
|
strings: v.strings.clone()
|
||||||
|
stack: []i64{len: stack_cap}
|
||||||
|
lines: v.lines
|
||||||
|
fns: v.fns
|
||||||
|
const_strs: v.const_strs
|
||||||
|
max_ops: v.max_ops
|
||||||
|
}
|
||||||
|
// Synthetic entry frame laid out exactly like Vm.call: [ip, old_bp, argc,
|
||||||
|
// local_0..local_{argc-1}] with bp at local_0 (so stack[bp-3]=ip,
|
||||||
|
// stack[bp-2]=old_bp, stack[bp-1]=argc). The synthetic caller is placed at
|
||||||
|
// bp = 3 + n so that the closure's final `ret` — which reads ip(bp-3),
|
||||||
|
// old_bp(bp-2), argc(bp-1), unwinds `sp -= argc`, then halts (ip == -1) and
|
||||||
|
// pushes the return value — lands back at slot (3+n)-3-n = 0, mirroring
|
||||||
|
// how a top-level `main` returns.
|
||||||
|
n := captured.len + args.len
|
||||||
|
child.bp = 3 + n
|
||||||
|
child.sp = 3 + n
|
||||||
|
base := child.bp - 3 // ip slot
|
||||||
|
child.stack[base] = child.enc_int(-1) // return ip: signals the synthetic frame
|
||||||
|
child.stack[base + 1] = child.enc_int(0) // old_bp
|
||||||
|
child.stack[base + 2] = child.enc_int(i64(n)) // argc (captured + args)
|
||||||
|
for i, c in captured {
|
||||||
|
child.stack[child.bp + i] = v.copy_into(mut child, c)!
|
||||||
|
}
|
||||||
|
for i, a in args {
|
||||||
|
child.stack[child.bp + captured.len + i] = v.copy_into(mut child, a)!
|
||||||
|
}
|
||||||
|
child.sp = child.bp + n
|
||||||
|
child.ip = entry
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
|
||||||
|
// run_spawned_job is the worker thread entry: it drives the exclusively-owned
|
||||||
|
// child VM to completion and sends the result back. The child is passed by
|
||||||
|
// value — V copies the struct (including the freshly-allocated slice backings
|
||||||
|
// built on the parent thread), so this worker's mutations never alias the
|
||||||
|
// parent's memory.
|
||||||
|
fn run_spawned_job(child Vm, ch chan JobResult) {
|
||||||
|
mut c := child
|
||||||
|
c.exec() or {
|
||||||
|
ch <- JobResult{ ok: false, err: err.msg() }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res := c.stack[0] // left by ret from the synthetic entry frame
|
||||||
|
if c.is_str(res) && c.valid_handle(res) {
|
||||||
|
ch <- JobResult{ ok: true, str: c.strings[c.hand(res)] }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch <- JobResult{ ok: true, val: res }
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy_into deep-copies a parent value into the child heap, re-interned so
|
||||||
|
// the child's handles are valid in its own pools. Runs on the parent thread.
|
||||||
|
fn (mut v Vm) copy_into(mut child Vm, x i64) !i64 {
|
||||||
|
if v.is_int(x) {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
if v.is_none(x) {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
if v.is_float(x) && v.valid_float_handle(x) {
|
||||||
|
child.floats << v.fval(x)
|
||||||
|
return child.mkfloat(child.floats.len - 1)
|
||||||
|
}
|
||||||
|
if v.is_str(x) && v.valid_handle(x) {
|
||||||
|
child.strings << v.strings[v.hand(x)]
|
||||||
|
return child.mkstr(child.strings.len - 1)
|
||||||
|
}
|
||||||
|
if v.is_arr(x) && v.valid_arr_handle(x) {
|
||||||
|
src := v.arrays[v.hand(x)]
|
||||||
|
mut na := []i64{len: src.len}
|
||||||
|
for i, el in src {
|
||||||
|
na[i] = v.copy_into(mut child, el)!
|
||||||
|
}
|
||||||
|
child.arrays << na
|
||||||
|
return child.mkarr(child.arrays.len - 1)
|
||||||
|
}
|
||||||
|
if v.is_struct(x) && v.valid_struct_handle(x) {
|
||||||
|
s := v.structs[v.hand(x)]
|
||||||
|
mut nf := []Field{len: s.fields.len}
|
||||||
|
for i, fld in s.fields {
|
||||||
|
nf[i] = Field{ name: fld.name, val: v.copy_into(mut child, fld.val)! }
|
||||||
|
}
|
||||||
|
child.structs << StructVal{ fields: nf, by_name: child.index_fields(nf) }
|
||||||
|
return child.mkstruct_handle(child.structs.len - 1)
|
||||||
|
}
|
||||||
|
if v.is_closure(x) {
|
||||||
|
return error('spawn: closures capturing composite/closure values are not supported')
|
||||||
|
}
|
||||||
|
return error('spawn: unsupported value type for cross-thread copy')
|
||||||
|
}
|
||||||
+10
@@ -34,6 +34,13 @@ struct Handler {
|
|||||||
sp int // stack pointer right after the handler record
|
sp int // stack pointer right after the handler record
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StrBuilder accumulates string parts so repeated concatenation stays linear
|
||||||
|
// (a `+ cat a + b + c` chain reallocates on every step; a builder joins once).
|
||||||
|
struct StrBuilder {
|
||||||
|
mut:
|
||||||
|
parts []string
|
||||||
|
}
|
||||||
|
|
||||||
// DbgMode says what the debugger should do after an interactive session ends.
|
// DbgMode says what the debugger should do after an interactive session ends.
|
||||||
enum DbgMode {
|
enum DbgMode {
|
||||||
run // keep going until the next breakpoint (or the end)
|
run // keep going until the next breakpoint (or the end)
|
||||||
@@ -66,6 +73,8 @@ mut:
|
|||||||
structs []StructVal
|
structs []StructVal
|
||||||
floats []f64
|
floats []f64
|
||||||
closures []Closure
|
closures []Closure
|
||||||
|
builders []StrBuilder
|
||||||
|
jobs []Job
|
||||||
stack []i64
|
stack []i64
|
||||||
sp int
|
sp int
|
||||||
bp int
|
bp int
|
||||||
@@ -79,6 +88,7 @@ mut:
|
|||||||
lines []obj.LineInfo // debug info: code offset -> source line
|
lines []obj.LineInfo // debug info: code offset -> source line
|
||||||
fns []obj.BinFn // function table (for stack traces)
|
fns []obj.BinFn // function table (for stack traces)
|
||||||
const_strs int // strings[0..const_strs] are bytecode constants, never collected
|
const_strs int // strings[0..const_strs] are bytecode constants, never collected
|
||||||
|
bin obj.Bin // the linked program (kept so spawn() can build isolated child VMs)
|
||||||
last_heap int // heap size at the last GC check (allocation trigger)
|
last_heap int // heap size at the last GC check (allocation trigger)
|
||||||
build_root string // directory of the .vrmm build module (build_root() builtin)
|
build_root string // directory of the .vrmm build module (build_root() builtin)
|
||||||
dbg DbgState // interactive debugger state (vr debug)
|
dbg DbgState // interactive debugger state (vr debug)
|
||||||
|
|||||||
+15
@@ -15,6 +15,7 @@ const tag_struct = u64(2)
|
|||||||
const tag_arr = u64(3)
|
const tag_arr = u64(3)
|
||||||
const tag_float = u64(4)
|
const tag_float = u64(4)
|
||||||
const tag_closure = u64(5)
|
const tag_closure = u64(5)
|
||||||
|
const tag_builder = u64(7)
|
||||||
|
|
||||||
// none_val is the sentinel for the `none` literal (and JSON null). Tag 110
|
// none_val is the sentinel for the `none` literal (and JSON null). Tag 110
|
||||||
// is not a valid encoded integer (those are multiples of 8) nor any handle,
|
// is not a valid encoded integer (those are multiples of 8) nor any handle,
|
||||||
@@ -50,6 +51,10 @@ fn (mut v Vm) is_closure(x i64) bool {
|
|||||||
return u64(x) & tag_mask == tag_closure
|
return u64(x) & tag_mask == tag_closure
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) is_builder(x i64) bool {
|
||||||
|
return u64(x) & tag_mask == tag_builder
|
||||||
|
}
|
||||||
|
|
||||||
fn (mut v Vm) is_none(x i64) bool {
|
fn (mut v Vm) is_none(x i64) bool {
|
||||||
return x == none_val
|
return x == none_val
|
||||||
}
|
}
|
||||||
@@ -91,6 +96,10 @@ fn (mut v Vm) mkclosure(idx int) i64 {
|
|||||||
return i64((u64(idx) << 3) | tag_closure)
|
return i64((u64(idx) << 3) | tag_closure)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) mkbuilder(idx int) i64 {
|
||||||
|
return i64((u64(idx) << 3) | tag_builder)
|
||||||
|
}
|
||||||
|
|
||||||
// push_float interns a float into the pool and returns its tagged handle.
|
// push_float interns a float into the pool and returns its tagged handle.
|
||||||
fn (mut v Vm) push_float(f f64) i64 {
|
fn (mut v Vm) push_float(f f64) i64 {
|
||||||
v.floats << f
|
v.floats << f
|
||||||
@@ -137,6 +146,11 @@ fn (mut v Vm) valid_closure_handle(x i64) bool {
|
|||||||
return h >= 0 && h < v.closures.len
|
return h >= 0 && h < v.closures.len
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) valid_builder_handle(x i64) bool {
|
||||||
|
h := v.hand(x)
|
||||||
|
return h >= 0 && h < v.builders.len
|
||||||
|
}
|
||||||
|
|
||||||
// valid_handle_for bounds-checks a handle against the pool matching its tag.
|
// valid_handle_for bounds-checks a handle against the pool matching its tag.
|
||||||
fn (mut v Vm) valid_handle_for(x i64) bool {
|
fn (mut v Vm) valid_handle_for(x i64) bool {
|
||||||
return match v.tag(x) {
|
return match v.tag(x) {
|
||||||
@@ -145,6 +159,7 @@ fn (mut v Vm) valid_handle_for(x i64) bool {
|
|||||||
tag_arr { v.valid_arr_handle(x) }
|
tag_arr { v.valid_arr_handle(x) }
|
||||||
tag_float { v.valid_float_handle(x) }
|
tag_float { v.valid_float_handle(x) }
|
||||||
tag_closure { v.valid_closure_handle(x) }
|
tag_closure { v.valid_closure_handle(x) }
|
||||||
|
tag_builder { v.valid_builder_handle(x) }
|
||||||
else { true }
|
else { true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ fn new_vm(bin obj.Bin, entry string, opts RunOpts) !Vm {
|
|||||||
lines: bin.lines
|
lines: bin.lines
|
||||||
fns: bin.fns
|
fns: bin.fns
|
||||||
const_strs: bin.strings.len
|
const_strs: bin.strings.len
|
||||||
|
bin: bin
|
||||||
build_root: opts.root
|
build_root: opts.root
|
||||||
dbg_locals: bin.locals
|
dbg_locals: bin.locals
|
||||||
max_ops: opts.max_ops
|
max_ops: opts.max_ops
|
||||||
|
|||||||
Reference in New Issue
Block a user