Tier Three: Added threads/jobs

This commit is contained in:
allexanderbergmns
2026-08-25 17:26:07 +02:00
parent d11f9c8d37
commit 415dec40a2
24 changed files with 495 additions and 47 deletions
+1
View File
@@ -80,6 +80,7 @@ pub mut:
pub struct Stmt {
pub mut:
kind StmtKind
mutable bool // let_stmt/destruct_stmt: bound with `mut` (reassignable)
target string
idx_target string // for_in_stmt: index variable name (empty when unused)
destruct_targets []string // destruct_stmt: names to bind
+37 -7
View File
@@ -40,14 +40,15 @@ struct FnSig {
struct Checker {
mut:
types map[string]TypeInfo // current scope: local name -> type
fns map[string]FnSig
structs map[string][]string
enums map[string][]string
consts map[string]TypeInfo
types map[string]TypeInfo // current scope: local name -> type
mutable map[string]bool // local names bound with `mut`, params, loop vars; only these may be reassigned
fns map[string]FnSig
structs map[string][]string
enums map[string][]string
consts map[string]TypeInfo
loop_depth int
checked map[string]bool // imported files already checked
modules map[string]bool // imported module names (bare `import os`)
checked map[string]bool // imported files already checked
modules map[string]bool // imported module names (bare `import os`)
}
// 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
if fd.recv_name.len > 0 {
c.types[fd.recv_name] = TypeInfo{ kind: .struct_t }
c.mutable[fd.recv_name] = true
}
for p in fd.params {
c.types[p] = TypeInfo{ kind: .unknown }
// parameters are reassignable: callers pass values, bodies may rewrite
c.mutable[p] = true
}
for st in fd.body {
c.check_stmt(st)!
@@ -165,11 +169,13 @@ fn (mut c Checker) check_stmt(st Stmt) ! {
.let_stmt {
t := c.check_expr(st.expr)!
c.types[st.target] = t
c.mutable[st.target] = st.mutable // false for `let`, true for `mut`
}
.destruct_stmt {
base := c.check_expr(st.expr)!
for name in st.destruct_targets {
c.types[name] = if st.destruct_field { TypeInfo{ kind: .unknown } } else { TypeInfo{ kind: .unknown } }
c.mutable[name] = false
}
_ = base
}
@@ -177,6 +183,9 @@ fn (mut c Checker) check_stmt(st Stmt) ! {
if st.target !in c.types {
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)!
}
.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(end_t, 'range end', st.line)!
c.types[st.target] = TypeInfo{ kind: .int_t }
c.mutable[st.target] = true // the loop advances it; bodies may too
c.loop_depth++
for s in st.body {
c.check_stmt(s)!
@@ -243,8 +253,10 @@ fn (mut c Checker) check_stmt(st Stmt) ! {
}
c.types[st.target] = TypeInfo{ kind: .unknown }
}
c.mutable[st.target] = true // loop vars are reassigned by iteration
if st.idx_target.len > 0 {
c.types[st.idx_target] = TypeInfo{ kind: .int_t }
c.mutable[st.idx_target] = true
}
c.loop_depth++
for s in st.body {
@@ -295,6 +307,10 @@ fn (mut c Checker) check_expr(e Expr) !TypeInfo {
c.types[e.name]
} else if e.name in c.consts {
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 {
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
bid, bargc := builtin_spec(e.name)
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 {
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
'json_encode', 'format', 'replace', 'pad', 'pad_left', 'repeat' { TypeInfo{ kind: .string_t } }
'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 } }
'build_is_dir' { TypeInfo{ kind: .int_t } }
// build-module builtins (.vrmm)
+44 -5
View File
@@ -45,6 +45,7 @@ mut:
enter_off u32
next_lbl int
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
}
@@ -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 }
}
// 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
for l in imported.lines {
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 }
}
}
for fd in prog.fns {
g.fn_names[fd.name] = true
}
for fd in prog.fns {
g.captures = []string{} // top-level functions capture nothing
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)
g.code << op_push_i
g.code << obj.encode_i64(i64(g.enum_vals[e.name]))
} else {
idx := g.locals[e.name] or {
return error('unknown variable "${e.name}" at line ${e.line}')
}
} else if e.name in g.locals {
idx := g.locals[e.name]
g.code << op_load
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 {
@@ -911,6 +927,20 @@ fn (mut g Gen) gen_call(e Expr) ! {
// host builtins (file I/O, OS, math, collections) go through op_native
bid, bargc := builtin_spec(e.name)
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 {
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_left' { native_pad_left, 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 }
'cwd' { native_cwd, 0 }
'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' }
'build_compile', 'build_assemble', 'build_link', 'build_exec', 'build_base',
'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' }
else { '' }
}
+1
View File
@@ -285,6 +285,7 @@ fn (mut l Lexer) lex_ident(line int, col int) Tok {
'fn' { TokKind.kw_fn }
'struct' { TokKind.kw_struct }
'let' { TokKind.kw_let }
'mut' { TokKind.kw_mut }
'if' { TokKind.kw_if }
'else' { TokKind.kw_else }
'while' { TokKind.kw_while }
+10
View File
@@ -172,3 +172,13 @@ const native_path_abs = 185
const native_path_rel = 186
const native_exec_full = 187
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
View File
@@ -315,9 +315,11 @@ fn (mut p Parser) parse_block() ![]Stmt {
fn (mut p Parser) parse_stmt() !Stmt {
t := p.cur()
match t.kind {
.kw_let {
.kw_let, .kw_mut {
mutable := t.kind == .kw_mut
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 {
is_field := p.cur().kind == .lbrace
p.advance()
@@ -344,7 +346,7 @@ fn (mut p Parser) parse_stmt() !Stmt {
name := p.expect(.ident, 'variable name')!
p.expect(.assign, "'='")!
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 {
return p.parse_if(t)!
}
+1
View File
@@ -44,6 +44,7 @@ pub enum TokKind {
kw_fn
kw_struct
kw_let
kw_mut
kw_if
kw_else
kw_while