Bunch of updates

This commit is contained in:
allexanderbergmns
2026-08-24 18:24:58 +02:00
parent 50712c5f17
commit 4b7a2f4c67
10 changed files with 626 additions and 22 deletions
+195 -10
View File
@@ -49,6 +49,10 @@ const op_apush = u8(36)
const op_mkstruct = u8(37) const op_mkstruct = u8(37)
const op_sget = u8(38) const op_sget = u8(38)
const op_sset = u8(39) const op_sset = u8(39)
const op_shas = u8(40)
const op_sdel = u8(41)
const op_slen = u8(42)
const op_skeys = u8(43)
// compile parses and compiles VuurRaaf source into an object file. // compile parses and compiles VuurRaaf source into an object file.
pub fn compile(src string) !obj.Obj { pub fn compile(src string) !obj.Obj {
@@ -89,6 +93,7 @@ mut:
structs map[string][]string // declared struct name -> field list structs map[string][]string // declared struct name -> field list
enums map[string][]string // enum name -> variant list enums map[string][]string // enum name -> variant list
enum_vals map[string]int // 'Enum.variant' -> integer value enum_vals map[string]int // 'Enum.variant' -> integer value
consts map[string]i64 // constant name -> integer value
local_cnt int local_cnt int
argc int argc int
cur_fn string cur_fn string
@@ -111,6 +116,20 @@ fn gen(prog Program) !obj.Obj {
g.enum_vals['${ed.name}.${v}'] = i g.enum_vals['${ed.name}.${v}'] = i
} }
} }
// register constants
for cd in prog.consts {
if cd.name in g.consts {
return error('duplicate constant declaration "${cd.name}"')
}
// constants must be compile-time integer expressions
if cd.value.kind == .int_lit {
g.consts[cd.name] = cd.value.int_v
} else if cd.value.kind == .bool_lit {
g.consts[cd.name] = cd.value.int_v
} else {
return error('constant "${cd.name}" must be an integer or boolean literal (line ${cd.line})')
}
}
// register struct declarations // register struct declarations
for sd in prog.structs { for sd in prog.structs {
if sd.name in g.structs { if sd.name in g.structs {
@@ -220,10 +239,18 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
g.code << obj.encode_i64(i64(idx)) g.code << obj.encode_i64(i64(idx))
} }
.index_assign { .index_assign {
g.gen_expr(st.base)! // if the index is a string literal, use struct field set (map style)
g.gen_expr(st.idx)! if st.idx.kind == .str_lit {
g.gen_expr(st.expr)! g.gen_expr(st.base)!
g.code << op_aset g.gen_expr(st.expr)!
g.emit_field_name(st.idx.str_v)
g.code << op_sset
} else {
g.gen_expr(st.base)!
g.gen_expr(st.idx)!
g.gen_expr(st.expr)!
g.code << op_aset
}
} }
.field_assign { .field_assign {
// a.b = v → a, v, "b" sset (field name on top of the stack) // a.b = v → a, v, "b" sset (field name on top of the stack)
@@ -350,6 +377,11 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
g.emit_label(end_l) g.emit_label(end_l)
} }
.for_in_stmt { .for_in_stmt {
// for x in EnumType { ... } → iterate over enum variants as integers
if st.expr.kind == .ident && st.expr.name in g.enums {
g.gen_for_enum(st.target, st.expr.name, st.body, st.line)!
return
}
// for x in arr → idx := 0; while idx < len(arr) { x := arr[idx]; body; idx++ } // for x in arr → idx := 0; while idx < len(arr) { x := arr[idx]; body; idx++ }
arr_idx := g.new_local() arr_idx := g.new_local()
idx_idx := g.new_local() idx_idx := g.new_local()
@@ -498,6 +530,20 @@ fn (mut g Gen) gen_expr(e Expr) ! {
.method_call { .method_call {
// p.dist(x) → call <Type>.dist p, x // p.dist(x) → call <Type>.dist p, x
recv_t := g.method_receiver_type(e)! recv_t := g.method_receiver_type(e)!
// built-in: enum.to_string() generates a match on the integer value
if e.name == 'to_string' && recv_t in g.enums && e.args.len == 0 {
g.gen_enum_to_string(recv_t, *e.left, e.line)!
return
}
// built-in: enum.count() returns the number of variants
if e.name == 'count' && recv_t in g.enums && e.args.len == 0 {
g.gen_expr(*e.left)!
g.code << op_pop
variants := g.enums[recv_t]
g.code << op_push_i
g.code << obj.encode_i64(i64(variants.len))
return
}
g.gen_expr(*e.left)! g.gen_expr(*e.left)!
for a in e.args { for a in e.args {
g.gen_expr(a)! g.gen_expr(a)!
@@ -508,17 +554,28 @@ fn (mut g Gen) gen_expr(e Expr) ! {
g.code << obj.encode_i64(i64(e.args.len + 1)) // receiver + args g.code << obj.encode_i64(i64(e.args.len + 1)) // receiver + args
} }
.index { .index {
g.gen_expr(*e.left)! // if the index is a string literal, use struct field access (map style)
g.gen_expr(*e.right)! if e.right.kind == .str_lit {
g.code << op_aget g.gen_expr(*e.left)!
g.emit_field_name(e.right.str_v)
g.code << op_sget
} else {
g.gen_expr(*e.left)!
g.gen_expr(*e.right)!
g.code << op_aget
}
} }
.bool_lit { .bool_lit {
g.code << op_push_i g.code << op_push_i
g.code << obj.encode_i64(e.int_v) g.code << obj.encode_i64(e.int_v)
} }
.ident { .ident {
// check if it's an enum variant (e.g., Color.red) // check if it's a constant
if e.name in g.enum_vals { if e.name in g.consts {
g.code << op_push_i
g.code << obj.encode_i64(g.consts[e.name])
} else if e.name in g.enum_vals {
// 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 {
@@ -572,6 +629,32 @@ fn (mut g Gen) gen_call(e Expr) ! {
g.code << op_apush g.code << op_apush
return return
} }
if e.name == 'has' {
if e.args.len != 2 {
return error('has() takes exactly two arguments (line ${e.line})')
}
g.gen_expr(e.args[0])!
g.gen_expr(e.args[1])!
g.code << op_shas
return
}
if e.name == 'delete' {
if e.args.len != 2 {
return error('delete() takes exactly two arguments (line ${e.line})')
}
g.gen_expr(e.args[0])!
g.gen_expr(e.args[1])!
g.code << op_sdel
return
}
if e.name == 'keys' {
if e.args.len != 1 {
return error('keys() takes exactly one argument (line ${e.line})')
}
g.gen_expr(e.args[0])!
g.code << op_skeys
return
}
for a in e.args { for a in e.args {
g.gen_expr(a)! g.gen_expr(a)!
} }
@@ -674,7 +757,8 @@ fn (mut g Gen) expr_type(e Expr) string {
// method_receiver_type resolves the struct type a method call is made on. // method_receiver_type resolves the struct type a method call is made on.
// The receiver must be a plain variable whose type the compiler knows // The receiver must be a plain variable whose type the compiler knows
// (from a typed literal, an assignment, or a method receiver binding). // (from a typed literal, an assignment, or a method receiver binding)
// or an enum variant expression (e.g. Color.red).
fn (mut g Gen) method_receiver_type(e Expr) !string { fn (mut g Gen) method_receiver_type(e Expr) !string {
recv := e.left recv := e.left
if recv.kind == .ident { if recv.kind == .ident {
@@ -683,9 +767,110 @@ fn (mut g Gen) method_receiver_type(e Expr) !string {
return t return t
} }
} }
// enum variant: Color.red → type is "Color"
if recv.kind == .field && recv.left.kind == .ident {
key := '${recv.left.name}.${recv.name}'
if key in g.enum_vals {
return recv.left.name
}
}
return error('cannot resolve method "${e.name}": receiver type unknown (line ${e.line})') return error('cannot resolve method "${e.name}": receiver type unknown (line ${e.line})')
} }
// gen_enum_to_string generates bytecode for `e.to_string()` on an enum value.
// It emits a match statement that maps each integer variant to its string name.
fn (mut g Gen) gen_enum_to_string(enum_name string, recv Expr, line int) ! {
variants := g.enums[enum_name] or {
return error('unknown enum "${enum_name}" at line ${line}')
}
// store the receiver in a temp local
subj_idx := g.new_local()
g.gen_expr(recv)!
g.emit_store(subj_idx)
// end label for the match
end_l := g.new_label()
for i, v in variants {
next_l := g.new_label()
// load subject, push variant integer, compare
g.emit_load(subj_idx)
g.code << op_push_i
g.code << obj.encode_i64(i64(i))
g.code << op_eq
g.code << op_jz
g.code << obj.encode_i64(0)
g.fixups << Fixup{ name: next_l, off: u32(g.code.len) - 8 }
// push the variant name as a string
g.code << op_push_s
g.code << obj.encode_i64(0)
g.relocs << obj.Reloc{ offset: u32(g.code.len) - 8, name: v, kind: 1 }
// jump to end
g.code << op_jmp
g.code << obj.encode_i64(0)
g.fixups << Fixup{ name: end_l, off: u32(g.code.len) - 8 }
g.emit_label(next_l)
}
// else: push "unknown"
g.code << op_push_s
g.code << obj.encode_i64(0)
g.relocs << obj.Reloc{ offset: u32(g.code.len) - 8, name: 'unknown', kind: 1 }
g.emit_label(end_l)
}
// gen_for_enum generates a for loop that iterates over all variants of an enum.
// for x in Color { ... } → for i in 0..count { x = i; ... } (x typed as Color)
fn (mut g Gen) gen_for_enum(var_name string, enum_name string, body []Stmt, line int) ! {
variants := g.enums[enum_name] or {
return error('unknown enum "${enum_name}" at line ${line}')
}
count := variants.len
// i := 0
var_idx := g.new_local()
bound_idx := g.new_local()
g.code << op_push_i
g.code << obj.encode_i64(0)
g.emit_store(var_idx)
g.code << op_push_i
g.code << obj.encode_i64(i64(count))
g.emit_store(bound_idx)
loop_l := g.new_label()
inc_l := g.new_label()
end_l := g.new_label()
g.emit_label(loop_l)
g.emit_load(var_idx)
g.emit_load(bound_idx)
g.code << op_lt
g.code << op_jz
g.code << obj.encode_i64(0)
g.fixups << Fixup{ name: end_l, off: u32(g.code.len) - 8 }
g.loops << LoopCtx{ break_l: end_l, continue_l: inc_l }
prev := g.locals[var_name] or { -1 }
prev_t := g.types[var_name] or { '' }
g.locals[var_name] = var_idx
g.types[var_name] = enum_name // type the loop variable as the enum
for s in body {
g.gen_stmt(s)!
}
if prev >= 0 {
g.locals[var_name] = prev
} else {
g.locals.delete(var_name)
}
if prev_t.len > 0 {
g.types[var_name] = prev_t
}
g.loops.delete_last()
g.emit_label(inc_l)
g.emit_load(var_idx)
g.code << op_push_i
g.code << obj.encode_i64(1)
g.code << op_add
g.emit_store(var_idx)
g.code << op_jmp
g.code << obj.encode_i64(0)
g.fixups << Fixup{ name: loop_l, off: u32(g.code.len) - 8 }
g.emit_label(end_l)
}
// emit_field_name pushes a field name as a string constant. Like string // emit_field_name pushes a field name as a string constant. Like string
// literals it goes through a kind-1 relocation so multi-file links rebase it. // literals it goes through a kind-1 relocation so multi-file links rebase it.
fn (mut g Gen) emit_field_name(name string) { fn (mut g Gen) emit_field_name(name string) {
+2
View File
@@ -51,6 +51,7 @@ pub enum TokKind {
kw_assert kw_assert
kw_import kw_import
kw_enum kw_enum
kw_const
} }
pub struct Tok { pub struct Tok {
@@ -278,6 +279,7 @@ fn (mut l Lexer) lex_ident(line int) Tok {
'assert' { TokKind.kw_assert } 'assert' { TokKind.kw_assert }
'import' { TokKind.kw_import } 'import' { TokKind.kw_import }
'enum' { TokKind.kw_enum } 'enum' { TokKind.kw_enum }
'const' { TokKind.kw_const }
else { TokKind.ident } else { TokKind.ident }
} }
return Tok{ kind: kind, lit: lit, line: line } return Tok{ kind: kind, lit: lit, line: line }
+36 -9
View File
@@ -1,10 +1,11 @@
// parser.v — recursive-descent parser for the VuurRaaf language. // parser.v — recursive-descent parser for the VuurRaaf language.
// //
// Grammar (informal): // Grammar (informal):
// program := import* (struct | enum | fn)* // program := import* (struct | enum | const | fn)*
// import := 'import' STRING // import := 'import' STRING
// struct := 'struct' IDENT '{' [IDENT (',' IDENT)*] '}' // struct := 'struct' IDENT '{' [IDENT (',' IDENT)*] '}'
// enum := 'enum' IDENT '{' [IDENT (',' IDENT)*] '}' // enum := 'enum' IDENT '{' [IDENT (',' IDENT)*] '}'
// const := 'const' IDENT '=' expr
// fn := 'fn' [ '(' IDENT IDENT ')' ] IDENT '(' [IDENT (',' IDENT)*] ')' block // fn := 'fn' [ '(' IDENT IDENT ')' ] IDENT '(' [IDENT (',' IDENT)*] ')' block
// block := '{' stmt* '}' // block := '{' stmt* '}'
// stmt := 'let' IDENT '=' expr // stmt := 'let' IDENT '=' expr
@@ -145,12 +146,21 @@ pub mut:
line int line int
} }
// ConstDecl is a `const NAME = value` declaration.
pub struct ConstDecl {
pub mut:
name string
value Expr
line int
}
pub struct Program { pub struct Program {
pub mut: pub mut:
fns []FnDecl fns []FnDecl
structs []StructDecl structs []StructDecl
enums []EnumDecl enums []EnumDecl
imports []ImportDecl imports []ImportDecl
consts []ConstDecl
} }
pub fn parse(toks []Tok) !Program { pub fn parse(toks []Tok) !Program {
@@ -209,6 +219,7 @@ fn (mut p Parser) parse_program() !Program {
match p.cur().kind { match p.cur().kind {
.kw_struct { prog.structs << p.parse_struct_decl()! } .kw_struct { prog.structs << p.parse_struct_decl()! }
.kw_enum { prog.enums << p.parse_enum_decl()! } .kw_enum { prog.enums << p.parse_enum_decl()! }
.kw_const { prog.consts << p.parse_const_decl()! }
.kw_fn { prog.fns << p.parse_fn()! } .kw_fn { prog.fns << p.parse_fn()! }
else { return error('unexpected token "${p.cur().lit}" at line ${p.cur().line}') } else { return error('unexpected token "${p.cur().lit}" at line ${p.cur().line}') }
} }
@@ -271,6 +282,15 @@ fn (mut p Parser) parse_enum_decl() !EnumDecl {
return EnumDecl{ name: name.lit, variants: variants, line: t.line } return EnumDecl{ name: name.lit, variants: variants, line: t.line }
} }
// parse_const_decl parses `const NAME = expr`.
fn (mut p Parser) parse_const_decl() !ConstDecl {
t := p.expect(.kw_const, "'const'")!
name := p.expect(.ident, 'constant name')!
p.expect(.assign, "'='")!
value := p.parse_expr()!
return ConstDecl{ name: name.lit, value: value, line: t.line }
}
// parse_fn parses `fn name(params) { }` or a method `fn (p Type) name(params) { }`. // parse_fn parses `fn name(params) { }` or a method `fn (p Type) name(params) { }`.
fn (mut p Parser) parse_fn() !FnDecl { fn (mut p Parser) parse_fn() !FnDecl {
fn_tok := p.expect(.kw_fn, "'fn'")! fn_tok := p.expect(.kw_fn, "'fn'")!
@@ -710,16 +730,22 @@ fn (mut p Parser) parse_args() ![]Expr {
return args return args
} }
// parse_struct_fields parses `{ name: expr, ... }` and returns the fields. // parse_struct_fields parses `{ name: expr, ... }` or `{ "key": expr, ... }` and returns the fields.
fn (mut p Parser) parse_struct_fields() ![]StructField { fn (mut p Parser) parse_struct_fields() ![]StructField {
p.expect(.lbrace, "'{'")! p.expect(.lbrace, "'{'")!
mut fields := []StructField{} mut fields := []StructField{}
if p.cur().kind != .rbrace { if p.cur().kind != .rbrace {
for { for {
name := p.expect(.ident, 'field name')! // field name can be an identifier or a string literal (for maps)
mut fname := ''
if p.cur().kind == .str_lit {
fname = p.advance().lit
} else {
fname = p.expect(.ident, 'field name')!.lit
}
p.expect(.colon, "':'")! p.expect(.colon, "':'")!
val := p.parse_expr()! val := p.parse_expr()!
fields << StructField{ name: name.lit, val: val } fields << StructField{ name: fname, val: val }
if p.cur().kind == .comma { if p.cur().kind == .comma {
p.advance() p.advance()
continue continue
@@ -737,5 +763,6 @@ fn (mut p Parser) looks_like_struct_lit() bool {
if p.pos + 2 >= p.toks.len { if p.pos + 2 >= p.toks.len {
return false return false
} }
return p.toks[p.pos + 1].kind == .ident && p.toks[p.pos + 2].kind == .colon // typed struct literal: `{ ident :` or map literal: `{ "key" :`
return (p.toks[p.pos + 1].kind == .ident || p.toks[p.pos + 1].kind == .str_lit) && p.toks[p.pos + 2].kind == .colon
} }
+50
View File
@@ -0,0 +1,50 @@
// constants.vr — demonstrates const declarations
const MAX_SIZE = 100
const MIN_VALUE = 0
const IS_DEBUG = 1
const VERSION = 42
fn calculate(x) {
// constants work inside functions
if x > MAX_SIZE {
return MAX_SIZE
}
if x < MIN_VALUE {
return MIN_VALUE
}
return x
}
fn main() {
// constants at top level
assert MAX_SIZE == 100
assert MIN_VALUE == 0
assert IS_DEBUG == 1
assert VERSION == 42
println("Constants: 100, 0, 1, 42")
// constants in expressions
let limit = MAX_SIZE * 2
assert limit == 200
println("MAX_SIZE * 2 = 200")
// constants in conditions
if IS_DEBUG == 1 {
println("Debug mode is ON")
}
// constants in functions
let val = calculate(150)
assert val == 100
println("calculate(150) = 100")
let val2 = calculate(-5)
assert val2 == 0
println("calculate(-5) = 0")
// constants with boolean operations
assert MAX_SIZE > MIN_VALUE
assert VERSION == 42
println("All constant assertions passed!")
}
+46
View File
@@ -0,0 +1,46 @@
// enum_count.vr — demonstrates built-in enum.count()
enum Color {
red
green
blue
}
enum Day {
monday
tuesday
wednesday
thursday
friday
saturday
sunday
}
enum Coin {
penny
nickel
dime
quarter
}
fn main() {
// Color.count() = 3
let c = Color.red
assert c.count() == 3
println("Color has 3 variants")
// Day.count() = 7
assert Day.monday.count() == 7
println("Day has 7 variants")
// Coin.count() = 4
let coin = Coin.dime
assert coin.count() == 4
println("Coin has 4 variants")
// on a variant literal directly
assert Color.blue.count() == 3
println("Color.blue.count() = 3")
println("All count assertions passed!")
}
+64
View File
@@ -0,0 +1,64 @@
// enum_iter.vr — demonstrates for-in over enum types
enum Color {
red
green
blue
}
enum Day {
monday
tuesday
wednesday
thursday
friday
saturday
sunday
}
fn main() {
// iterate over Color variants
println("Colors:")
for c in Color {
print(" ")
println(c.to_string())
}
// iterate and use count
let total = 0
for c in Color {
total = total + c.count()
}
assert total == 9 // 3 + 3 + 3
println("Sum of counts: 9")
// iterate and use to_string in a match
for d in Day {
if d == Day.saturday or d == Day.sunday {
println(d.to_string() + " is weekend")
}
}
// break and continue work
let weekdays = 0
for d in Day {
if d == Day.saturday {
break
}
weekdays = weekdays + 1
}
assert weekdays == 5
println("Weekday count: 5")
// collect variant indices
let indices = []
for c in Color {
push(indices, c)
}
assert indices[0] == 0
assert indices[1] == 1
assert indices[2] == 2
println("Indices: 0, 1, 2")
println("All enum iteration assertions passed!")
}
+50
View File
@@ -0,0 +1,50 @@
// enum_tostring.vr — demonstrates built-in enum.to_string()
enum Color {
red
green
blue
}
enum Day {
monday
tuesday
wednesday
thursday
friday
saturday
sunday
}
fn main() {
// Color.to_string()
let c = Color.red
println(c.to_string())
assert c.to_string() == "red"
let g = Color.green
println(g.to_string())
assert g.to_string() == "green"
let b = Color.blue
println(b.to_string())
assert b.to_string() == "blue"
// Day.to_string()
let d = Day.friday
println(d.to_string())
assert d.to_string() == "friday"
let sun = Day.sunday
println(sun.to_string())
assert sun.to_string() == "sunday"
// enum variant literal .to_string()
println(Color.green.to_string())
assert Color.green.to_string() == "green"
println(Day.monday.to_string())
assert Day.monday.to_string() == "monday"
println("All to_string assertions passed!")
}
+62
View File
@@ -0,0 +1,62 @@
// maps.vr — demonstrates map/dictionary features
fn main() {
// create a map using struct literal syntax
let person = { "name": "alice", "age": 30 }
// access with string index
assert person["name"] == "alice"
assert person["age"] == 30
println("person[\"name\"] = alice")
println("person[\"age\"] = 30")
// update with string index
person["age"] = 31
assert person["age"] == 31
println("person[\"age\"] after update = 31")
// add new keys dynamically
person["city"] = "nyc"
assert person["city"] == "nyc"
println("person[\"city\"] = nyc")
// check key existence with has()
assert has(person, "name") == 1
assert has(person, "missing") == 0
println("has(person, \"name\") = 1")
println("has(person, \"missing\") = 0")
// get all keys with keys()
let k = keys(person)
assert len(k) == 3
println("keys count = 3")
// delete a key
delete(person, "city")
assert has(person, "city") == 0
assert len(person) == 2
println("after delete: len = 2")
// len() works on maps
let scores = { "math": 95, "english": 88 }
assert len(scores) == 2
println("scores len = 2")
// nested maps
let data = { "user": { "name": "bob" } }
assert data["user"]["name"] == "bob"
println("nested access = bob")
// maps in arrays
let users = [{ "name": "a" }, { "name": "b" }]
assert users[0]["name"] == "a"
assert users[1]["name"] == "b"
println("maps in arrays OK")
// empty map
let empty = {}
assert len(empty) == 0
println("empty map len = 0")
println("All map assertions passed!")
}
+19
View File
@@ -0,0 +1,19 @@
// maps_debug.vr — debug map keys function
fn main() {
let person = { "name": "alice", "age": 30 }
let k = keys(person)
let n = len(k)
println("len(k) = 2")
if n > 0 {
println("key0")
}
if n > 1 {
println("key1")
}
assert n == 2
println("keys OK")
}
+102 -3
View File
@@ -56,6 +56,10 @@ const op_apush = u8(36)
const op_mkstruct = u8(37) const op_mkstruct = u8(37)
const op_sget = u8(38) const op_sget = u8(38)
const op_sset = u8(39) const op_sset = u8(39)
const op_shas = u8(40) // has(map, "key") -> 1 if key exists, 0 otherwise
const op_sdel = u8(41) // delete(map, "key") -> removes the key
const op_slen = u8(42) // slen(struct) -> number of fields
const op_skeys = u8(43) // skeys(struct) -> array of field name strings
const stack_cap = 65536 const stack_cap = 65536
@@ -350,10 +354,13 @@ fn (mut v Vm) exec() ! {
op_alen { op_alen {
v.ip++ v.ip++
h := v.pop()! h := v.pop()!
if !v.is_arr(h) || !v.valid_arr_handle(h) { if v.is_arr(h) && v.valid_arr_handle(h) {
return error('len() on a non-array value') v.push(v.enc_int(i64(v.arrays[v.hand(h)].len)))!
} else if v.is_struct(h) && v.valid_struct_handle(h) {
v.push(v.enc_int(i64(v.structs[v.hand(h)].fields.len)))!
} else {
return error('len() on a non-array, non-struct value')
} }
v.push(v.enc_int(i64(v.arrays[v.hand(h)].len)))!
} }
op_apush { op_apush {
v.ip++ v.ip++
@@ -433,6 +440,18 @@ fn (mut v Vm) exec() ! {
} }
v.structs[v.hand(h)] = s v.structs[v.hand(h)] = s
} }
op_shas {
v.op_shas()!
}
op_sdel {
v.op_sdel()!
}
op_slen {
v.op_slen()!
}
op_skeys {
v.op_skeys()!
}
else { else {
return error('unknown opcode ${op} at ip ${v.ip}') return error('unknown opcode ${op} at ip ${v.ip}')
} }
@@ -440,6 +459,82 @@ fn (mut v Vm) exec() ! {
} }
} }
// op_shas checks if a struct has a field with the given name.
// stack: struct, "key" → pushes 1 if found, 0 if not.
fn (mut v Vm) op_shas() ! {
v.ip++
name := v.pop()!
h := v.pop()!
if !v.is_struct(h) || !v.valid_struct_handle(h) {
return error('has() on a non-struct value')
}
if !v.is_str(name) || !v.valid_handle(name) {
return error('internal: field name is not a string')
}
fname := v.strings[v.hand(name)]
mut found := false
for f in v.structs[v.hand(h)].fields {
if f.name == fname {
found = true
break
}
}
v.push(v.enc_int(if found { 1 } else { 0 }))!
}
// op_sdel removes a field from a struct.
// stack: struct, "key" → pushes the struct handle back.
fn (mut v Vm) op_sdel() ! {
v.ip++
name := v.pop()!
h := v.pop()!
if !v.is_struct(h) || !v.valid_struct_handle(h) {
return error('delete() on a non-struct value')
}
if !v.is_str(name) || !v.valid_handle(name) {
return error('internal: field name is not a string')
}
fname := v.strings[v.hand(name)]
mut s := v.structs[v.hand(h)]
mut new_fields := []Field{}
for f in s.fields {
if f.name != fname {
new_fields << f
}
}
s.fields = new_fields
v.structs[v.hand(h)] = s
v.push(h)!
}
// op_slen returns the number of fields in a struct.
// stack: struct → pushes field count.
fn (mut v Vm) op_slen() ! {
v.ip++
h := v.pop()!
if !v.is_struct(h) || !v.valid_struct_handle(h) {
return error('len() on a non-struct value')
}
v.push(v.enc_int(i64(v.structs[v.hand(h)].fields.len)))!
}
// op_skeys returns an array of field name strings.
// stack: struct → pushes array handle.
fn (mut v Vm) op_skeys() ! {
v.ip++
h := v.pop()!
if !v.is_struct(h) || !v.valid_struct_handle(h) {
return error('keys() on a non-struct value')
}
mut arr := []i64{}
for f in v.structs[v.hand(h)].fields {
v.strings << f.name
arr << v.mkstr(v.strings.len - 1)
}
v.arrays << arr
v.push(v.mkarr(v.arrays.len - 1))!
}
fn (mut v Vm) read_i64() i64 { fn (mut v Vm) read_i64() i64 {
mut val := u64(0) mut val := u64(0)
for i in 0..8 { for i in 0..8 {
@@ -757,6 +852,10 @@ fn (mut v Vm) trace_op(op u8) {
op_mkstruct { 'mkstruct' } op_mkstruct { 'mkstruct' }
op_sget { 'sget' } op_sget { 'sget' }
op_sset { 'sset' } op_sset { 'sset' }
op_shas { 'shas' }
op_sdel { 'sdel' }
op_slen { 'slen' }
op_skeys { 'skeys' }
else { '??' } else { '??' }
} }
mut s := '' mut s := ''