mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 14:17:18 +00:00
more
This commit is contained in:
@@ -61,9 +61,9 @@ Quick start:
|
||||
|
||||
## The VuurRaaf language
|
||||
|
||||
A small, V-flavored language. Values are 64-bit integers, strings, or arrays
|
||||
(strings concatenate with `+` and compare with `==`/`!=`; arrays are mutable
|
||||
and compare by identity).
|
||||
A small, V-flavored language. Values are 64-bit integers, strings, arrays, or
|
||||
structs (strings concatenate with `+` and compare with `==`/`!=`; arrays and
|
||||
structs are mutable references that compare by identity).
|
||||
|
||||
```
|
||||
fn sum(items) {
|
||||
@@ -129,6 +129,12 @@ fn main() {
|
||||
println("workday")
|
||||
}
|
||||
}
|
||||
|
||||
let pt = { x: 3, y: 4 } // struct literal: { name: value, ... }
|
||||
println(pt.x) // 3 — field access
|
||||
pt.y = 5 // field assignment
|
||||
let p = { name: "amy", addr: { city: "nyc" } } // nested structs
|
||||
println(p.addr.city) // nyc
|
||||
}
|
||||
```
|
||||
|
||||
@@ -143,6 +149,10 @@ fn main() {
|
||||
- else-if chains: `if a { } else if b { } else { }`
|
||||
- `match`: `match expr { v1 { } v2 { } else { } }` — arms test equality on
|
||||
any comparable value (ints, strings, ...); the `else` arm is optional
|
||||
- structs: literals `{ name: value, ... }` (may nest and may be empty `{}`),
|
||||
field access `a.b` and assignment `a.b = v` (chained: `a[i].b`, `a.b[i]`);
|
||||
structs are mutable references (identity `==`/`!=`), and setting a missing
|
||||
field adds it, so records can be built incrementally
|
||||
- operators: `+ - * / %`, `== != < <= > >=`, `and or not`, unary `-`
|
||||
- statements: `let`, assignment, `if/else`, `match`, `while`, `for`,
|
||||
`break`, `continue`, `return`, `assert`, calls, `print(...)` / `println(...)`
|
||||
@@ -171,7 +181,11 @@ helper:
|
||||
|
||||
Opcodes: `halt push_int push_str load store pop dup add sub mul div mod neg
|
||||
eq ne lt le gt ge and or not jmp jz jnz call ret retv print println assert
|
||||
enter mkarray aget aset alen apush`.
|
||||
enter mkarray aget aset alen apush mkstruct sget sset`.
|
||||
|
||||
Struct opcodes: `mkstruct n` pops `n` (name, value) pairs and pushes a struct
|
||||
handle; `sget "field"` / `sset "field"` read/write a named field (pushing the
|
||||
field name as a string first, exactly like the compiler does).
|
||||
|
||||
## Formats
|
||||
|
||||
|
||||
+25
-1
@@ -189,6 +189,26 @@ pub fn assemble(src string) !obj.Obj {
|
||||
'apush' {
|
||||
o.code << u8(36)
|
||||
}
|
||||
'mkstruct' {
|
||||
o.code << u8(37)
|
||||
o.code << obj.encode_i64(parse_int(arg, 'mkstruct')!)
|
||||
}
|
||||
'sget' {
|
||||
// sget "name" → push_str "name"; sget
|
||||
s := unquote(arg)!
|
||||
o.code << u8(2)
|
||||
o.code << obj.encode_i64(0) // placeholder — rebased by the linker
|
||||
o.relocs << obj.Reloc{ offset: u32(o.code.len) - 8, name: s, kind: 1 }
|
||||
o.code << u8(38)
|
||||
}
|
||||
'sset' {
|
||||
// sset "name" → push_str "name"; sset
|
||||
s := unquote(arg)!
|
||||
o.code << u8(2)
|
||||
o.code << obj.encode_i64(0) // placeholder — rebased by the linker
|
||||
o.relocs << obj.Reloc{ offset: u32(o.code.len) - 8, name: s, kind: 1 }
|
||||
o.code << u8(39)
|
||||
}
|
||||
else {
|
||||
return error('unknown instruction "${op}"')
|
||||
}
|
||||
@@ -258,7 +278,11 @@ fn instr_len(line string) !int {
|
||||
'aset', 'alen', 'apush' {
|
||||
return 1
|
||||
}
|
||||
'push_int', 'push_str', 'load', 'store', 'jmp', 'jz', 'jnz', 'enter', 'mkarray' {
|
||||
'sget', 'sset' {
|
||||
return 10 // push_str (9) + the opcode (1)
|
||||
}
|
||||
'push_int', 'push_str', 'load', 'store', 'jmp', 'jz', 'jnz', 'enter', 'mkarray',
|
||||
'mkstruct' {
|
||||
return 9
|
||||
}
|
||||
'call' {
|
||||
|
||||
@@ -46,6 +46,9 @@ const op_aget = u8(33)
|
||||
const op_aset = u8(34)
|
||||
const op_alen = u8(35)
|
||||
const op_apush = u8(36)
|
||||
const op_mkstruct = u8(37)
|
||||
const op_sget = u8(38)
|
||||
const op_sset = u8(39)
|
||||
|
||||
// compile parses and compiles VuurRaaf source into an object file.
|
||||
pub fn compile(src string) !obj.Obj {
|
||||
@@ -171,6 +174,13 @@ fn (mut g Gen) gen_stmt(st Stmt) ! {
|
||||
g.gen_expr(st.expr)!
|
||||
g.code << op_aset
|
||||
}
|
||||
.field_assign {
|
||||
// a.b = v → a, v, "b" sset (field name on top of the stack)
|
||||
g.gen_expr(st.base)!
|
||||
g.gen_expr(st.expr)!
|
||||
g.emit_field_name(st.target)
|
||||
g.code << op_sset
|
||||
}
|
||||
.if_stmt {
|
||||
else_l := g.new_label()
|
||||
end_l := g.new_label()
|
||||
@@ -384,6 +394,21 @@ fn (mut g Gen) gen_expr(e Expr) ! {
|
||||
g.code << op_mkarray
|
||||
g.code << obj.encode_i64(i64(e.elems.len))
|
||||
}
|
||||
.struct_lit {
|
||||
// for each field: push the name string then the value; mkstruct n
|
||||
// pops the (name, value) pairs and builds the record
|
||||
for f in e.fields {
|
||||
g.emit_field_name(f.name)
|
||||
g.gen_expr(f.val)!
|
||||
}
|
||||
g.code << op_mkstruct
|
||||
g.code << obj.encode_i64(i64(e.fields.len))
|
||||
}
|
||||
.field {
|
||||
g.gen_expr(*e.left)!
|
||||
g.emit_field_name(e.name)
|
||||
g.code << op_sget
|
||||
}
|
||||
.index {
|
||||
g.gen_expr(*e.left)!
|
||||
g.gen_expr(*e.right)!
|
||||
@@ -522,6 +547,14 @@ fn (mut g Gen) gen_binary(e Expr) ! {
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
fn (mut g Gen) emit_field_name(name string) {
|
||||
g.code << op_push_s
|
||||
g.code << obj.encode_i64(0)
|
||||
g.relocs << obj.Reloc{ offset: u32(g.code.len) - 8, name: name, kind: 1 }
|
||||
}
|
||||
|
||||
fn (mut g Gen) intern(s string) int {
|
||||
if s in g.str_map {
|
||||
return g.str_map[s]
|
||||
|
||||
+7
-1
@@ -13,6 +13,8 @@ pub enum TokKind {
|
||||
lbracket
|
||||
rbracket
|
||||
comma
|
||||
dot
|
||||
colon
|
||||
plus
|
||||
minus
|
||||
star
|
||||
@@ -154,12 +156,16 @@ fn (mut l Lexer) next() !Tok {
|
||||
}
|
||||
return Tok{ kind: .dotdot, lit: '..', line: line }
|
||||
}
|
||||
return error('unexpected character "." at line ${line}')
|
||||
return Tok{ kind: .dot, lit: '.', line: line }
|
||||
}
|
||||
`,` {
|
||||
l.advance()
|
||||
return Tok{ kind: .comma, lit: ',', line: line }
|
||||
}
|
||||
`:` {
|
||||
l.advance()
|
||||
return Tok{ kind: .colon, lit: ':', line: line }
|
||||
}
|
||||
`+` {
|
||||
l.advance()
|
||||
return Tok{ kind: .plus, lit: '+', line: line }
|
||||
|
||||
+83
-26
@@ -6,7 +6,7 @@
|
||||
// block := '{' stmt* '}'
|
||||
// stmt := 'let' IDENT '=' expr
|
||||
// | IDENT '=' expr
|
||||
// | postfix '=' expr (a[i] = v)
|
||||
// | postfix '=' expr (a[i] = v, a.b = v)
|
||||
// | 'if' cond block ['else' ('if' ... | block)]
|
||||
// | 'match' expr '{' (expr block | 'else' block)* '}'
|
||||
// | 'while' cond block
|
||||
@@ -25,6 +25,8 @@
|
||||
// add := mul (('*'|'/'|'%') mul)*
|
||||
// mul := ('not'|'-') mul | primary
|
||||
// primary := INT | STR | 'true' | 'false' | IDENT ['(' args ')'] | '(' expr ')'
|
||||
// | '{' [IDENT ':' expr (',' IDENT ':' expr)*] '}' (struct literal)
|
||||
// postfix := primary ('.' IDENT | '[' expr ']')* (field access, indexing)
|
||||
module compiler
|
||||
|
||||
pub enum ExprKind {
|
||||
@@ -33,22 +35,32 @@ pub enum ExprKind {
|
||||
bool_lit
|
||||
ident
|
||||
array_lit
|
||||
struct_lit
|
||||
index
|
||||
field
|
||||
unary
|
||||
binary
|
||||
call
|
||||
}
|
||||
|
||||
// StructField is one `name: value` entry of a struct literal.
|
||||
pub struct StructField {
|
||||
pub mut:
|
||||
name string
|
||||
val Expr
|
||||
}
|
||||
|
||||
pub struct Expr {
|
||||
pub mut:
|
||||
kind ExprKind
|
||||
int_v i64
|
||||
str_v string
|
||||
name string
|
||||
name string // ident/call name, or the field name of a `.field` access
|
||||
op TokKind
|
||||
left &Expr = unsafe { nil }
|
||||
right &Expr = unsafe { nil }
|
||||
elems []Expr
|
||||
fields []StructField // struct_lit: the named fields
|
||||
args []Expr
|
||||
line int
|
||||
}
|
||||
@@ -58,6 +70,7 @@ pub enum StmtKind {
|
||||
let_stmt
|
||||
assign_stmt
|
||||
index_assign
|
||||
field_assign
|
||||
if_stmt
|
||||
match_stmt
|
||||
while_stmt
|
||||
@@ -283,22 +296,33 @@ fn (mut p Parser) parse_stmt() !Stmt {
|
||||
}
|
||||
.ident {
|
||||
p.advance()
|
||||
if p.cur().kind == .assign {
|
||||
p.advance()
|
||||
e := p.parse_expr()!
|
||||
return Stmt{ kind: .assign_stmt, target: t.lit, expr: e, line: t.line }
|
||||
mut e := Expr{}
|
||||
if p.cur().kind == .lparen {
|
||||
// a call statement: foo(args), optionally chained foo().x
|
||||
e = p.parse_call(t)!
|
||||
e = p.parse_postfix_tail(e)!
|
||||
} else {
|
||||
e = p.parse_postfix_tail(Expr{ kind: .ident, name: t.lit, line: t.line })!
|
||||
}
|
||||
if p.cur().kind == .lbracket {
|
||||
// a[i] = v or a[i] (expression statement)
|
||||
e := p.parse_index_chain(t)!
|
||||
if p.cur().kind == .assign {
|
||||
// assignment to an ident, an index, or a field
|
||||
p.advance()
|
||||
rhs := p.parse_expr()!
|
||||
match e.kind {
|
||||
.ident {
|
||||
return Stmt{ kind: .assign_stmt, target: e.name, expr: rhs, line: t.line }
|
||||
}
|
||||
.index {
|
||||
return Stmt{ kind: .index_assign, base: *e.left, idx: *e.right, expr: rhs, line: t.line }
|
||||
}
|
||||
return Stmt{ kind: .expr_stmt, expr: e, line: t.line }
|
||||
.field {
|
||||
return Stmt{ kind: .field_assign, base: *e.left, target: e.name, expr: rhs, line: t.line }
|
||||
}
|
||||
else {
|
||||
return error('cannot assign to this expression (line ${t.line})')
|
||||
}
|
||||
}
|
||||
}
|
||||
e := p.parse_call_or_ident(t)!
|
||||
return Stmt{ kind: .expr_stmt, expr: e, line: t.line }
|
||||
}
|
||||
.kw_print, .kw_println {
|
||||
@@ -352,6 +376,12 @@ fn index_node(base Expr, idx Expr, line int) Expr {
|
||||
return Expr{ kind: .index, left: &b, right: &i, line: line }
|
||||
}
|
||||
|
||||
// field_node builds `base.name`.
|
||||
fn field_node(base Expr, name string, line int) Expr {
|
||||
mut b := base
|
||||
return Expr{ kind: .field, left: &b, name: name, line: line }
|
||||
}
|
||||
|
||||
fn (mut p Parser) parse_expr() !Expr {
|
||||
return p.parse_or()!
|
||||
}
|
||||
@@ -426,29 +456,36 @@ fn (mut p Parser) parse_unary() !Expr {
|
||||
return p.parse_postfix()!
|
||||
}
|
||||
|
||||
// parse_postfix handles indexing: `base[expr]`, possibly chained `a[i][j]`.
|
||||
// parse_postfix handles postfix operators after a primary: indexing
|
||||
// `a[i]` (chainable `a[i][j]`) and field access `a.b` (chainable `a.b.c`),
|
||||
// in any mix: `a[i].b`, `a.b[i]`, ...
|
||||
fn (mut p Parser) parse_postfix() !Expr {
|
||||
mut e := p.parse_primary()!
|
||||
for p.cur().kind == .lbracket {
|
||||
p.advance()
|
||||
idx := p.parse_expr()!
|
||||
p.expect(.rbracket, "']'")!
|
||||
e = index_node(e, idx, e.line)
|
||||
}
|
||||
return e
|
||||
return p.parse_postfix_tail(e)!
|
||||
}
|
||||
|
||||
// parse_index_chain is like parse_postfix but starts from an already-consumed
|
||||
// identifier token (used for statements like `a[i] = v`).
|
||||
fn (mut p Parser) parse_index_chain(t Tok) !Expr {
|
||||
mut e := Expr{ kind: .ident, name: t.lit, line: t.line }
|
||||
for p.cur().kind == .lbracket {
|
||||
// parse_postfix_tail continues a postfix chain from an already-parsed base.
|
||||
// It takes the base by value (Expr only holds pointers to heap-allocated
|
||||
// child nodes) and returns the extended chain.
|
||||
fn (mut p Parser) parse_postfix_tail(e Expr) !Expr {
|
||||
mut cur := e
|
||||
for {
|
||||
if p.cur().kind == .lbracket {
|
||||
p.advance()
|
||||
idx := p.parse_expr()!
|
||||
p.expect(.rbracket, "']'")!
|
||||
e = index_node(e, idx, t.line)
|
||||
cur = index_node(cur, idx, cur.line)
|
||||
continue
|
||||
}
|
||||
return e
|
||||
if p.cur().kind == .dot {
|
||||
p.advance()
|
||||
name := p.expect(.ident, 'field name')!
|
||||
cur = field_node(cur, name.lit, cur.line)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
return cur
|
||||
}
|
||||
|
||||
fn (mut p Parser) parse_primary() !Expr {
|
||||
@@ -492,6 +529,26 @@ fn (mut p Parser) parse_primary() !Expr {
|
||||
p.expect(.rbracket, "']'")!
|
||||
return Expr{ kind: .array_lit, elems: elems, line: t.line }
|
||||
}
|
||||
.lbrace {
|
||||
// struct literal: { name: expr, ... }
|
||||
p.advance()
|
||||
mut fields := []StructField{}
|
||||
if p.cur().kind != .rbrace {
|
||||
for {
|
||||
name := p.expect(.ident, 'field name')!
|
||||
p.expect(.colon, "':'")!
|
||||
val := p.parse_expr()!
|
||||
fields << StructField{ name: name.lit, val: val }
|
||||
if p.cur().kind == .comma {
|
||||
p.advance()
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
p.expect(.rbrace, "'}'")!
|
||||
return Expr{ kind: .struct_lit, fields: fields, line: t.line }
|
||||
}
|
||||
.ident {
|
||||
p.advance()
|
||||
return p.parse_call_or_ident(t)!
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// structs.vr — struct/record values with field access.
|
||||
// vr run examples/structs.vr
|
||||
|
||||
fn make_point(x, y) {
|
||||
return { x: x, y: y }
|
||||
}
|
||||
|
||||
fn dist_sq(p) {
|
||||
return p.x * p.x + p.y * p.y
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// struct literal: { name: value, ... }
|
||||
let p = { name: "bob", age: 30 }
|
||||
println(p) // {name: bob, age: 30}
|
||||
println(p.name) // bob
|
||||
println(p.age) // 30
|
||||
|
||||
// field assignment mutates the record
|
||||
p.age = 31
|
||||
println(p.age) // 31
|
||||
|
||||
// structs are reference values, like arrays
|
||||
let q = p
|
||||
q.age = 40
|
||||
println(p.age) // 40 — p and q share the same record
|
||||
|
||||
// structs flow through functions
|
||||
let pt = make_point(3, 4)
|
||||
println(dist_sq(pt)) // 25
|
||||
|
||||
// structs compose with arrays, in both directions
|
||||
let pts = [{ x: 1, y: 2 }, { x: 10, y: 20 }]
|
||||
println(pts[1].x) // 10
|
||||
pts[0].x = 99
|
||||
println(pts[0].x) // 99
|
||||
|
||||
let person = { name: "amy", addr: { city: "nyc", zip: 10001 } }
|
||||
println(person.addr.city) // nyc
|
||||
person.addr.zip = 90001
|
||||
println(person.addr.zip) // 90001
|
||||
|
||||
let holder = { items: [1, 2, 3] }
|
||||
holder.items[1] = 42
|
||||
println(holder.items) // [1, 42, 3]
|
||||
|
||||
// records can be built incrementally from an empty struct
|
||||
let s = {}
|
||||
s.kind = "point"
|
||||
s.value = 7
|
||||
println(s) // {kind: point, value: 7}
|
||||
|
||||
// structs compare by identity with == / !=
|
||||
let a = { n: 1 }
|
||||
let b = a
|
||||
let c = { n: 1 }
|
||||
assert a == b
|
||||
assert a != c
|
||||
}
|
||||
@@ -185,6 +185,47 @@ fn test_match() {
|
||||
assert hit == 0
|
||||
}
|
||||
|
||||
fn test_structs() {
|
||||
let p = { name: "bob", age: 30 }
|
||||
assert p.name == "bob"
|
||||
assert p.age == 30
|
||||
p.age = 31
|
||||
assert p.age == 31
|
||||
|
||||
// structs are reference values
|
||||
let q = p
|
||||
q.age = 40
|
||||
assert p.age == 40
|
||||
|
||||
// nested structs
|
||||
let person = { name: "amy", addr: { city: "nyc", zip: 10001 } }
|
||||
assert person.addr.city == "nyc"
|
||||
person.addr.zip = 90001
|
||||
assert person.addr.zip == 90001
|
||||
|
||||
// structs in arrays
|
||||
let pts = [{ x: 1, y: 2 }, { x: 10, y: 20 }]
|
||||
assert pts[1].x == 10
|
||||
pts[0].x = 99
|
||||
assert pts[0].x == 99
|
||||
|
||||
// arrays in structs
|
||||
let holder = { items: [1, 2, 3] }
|
||||
holder.items[1] = 42
|
||||
assert holder.items[1] == 42
|
||||
|
||||
// structs passed to and returned from functions
|
||||
let pt = { x: 3, y: 4 }
|
||||
assert pt.x * pt.x + pt.y * pt.y == 25
|
||||
|
||||
// identity comparison
|
||||
let a = { n: 1 }
|
||||
let b = a
|
||||
let c = { n: 1 }
|
||||
assert a == b
|
||||
assert a != c
|
||||
}
|
||||
|
||||
fn test_failing() {
|
||||
// this one is meant to fail — shows up in `vr test` output
|
||||
assert 1 == 2
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
// Stack values are 64-bit tagged integers with two tag bits:
|
||||
// low bits 00 -> encoded number (value = raw << 2)
|
||||
// low bits 01 -> string handle (handle = value >> 2, into v.strings)
|
||||
// low bits 10 -> struct handle (handle = value >> 2, into v.structs)
|
||||
// low bits 11 -> array handle (handle = value >> 2, into v.arrays)
|
||||
// Encoding numbers with a constant shift means no integer ever collides with
|
||||
// a string or array handle.
|
||||
// a string, struct, or array handle.
|
||||
//
|
||||
// Call convention: CALL pushes a frame (retaddr, old bp, argc) and copies the
|
||||
// arguments into the callee's local slots; the callee reserves extra locals
|
||||
@@ -52,14 +53,30 @@ const op_aget = u8(33)
|
||||
const op_aset = u8(34)
|
||||
const op_alen = u8(35)
|
||||
const op_apush = u8(36)
|
||||
const op_mkstruct = u8(37)
|
||||
const op_sget = u8(38)
|
||||
const op_sset = u8(39)
|
||||
|
||||
const stack_cap = 65536
|
||||
|
||||
// Field is one `name: value` entry of a struct value.
|
||||
struct Field {
|
||||
mut:
|
||||
name string
|
||||
val i64
|
||||
}
|
||||
|
||||
struct StructVal {
|
||||
mut:
|
||||
fields []Field
|
||||
}
|
||||
|
||||
struct Vm {
|
||||
mut:
|
||||
code []u8
|
||||
strings []string
|
||||
arrays [][]i64
|
||||
structs []StructVal
|
||||
stack []i64
|
||||
sp int
|
||||
bp int
|
||||
@@ -184,6 +201,9 @@ fn (mut v Vm) exec() ! {
|
||||
if v.is_arr(a) {
|
||||
return error('cannot negate an array')
|
||||
}
|
||||
if v.is_struct(a) {
|
||||
return error('cannot negate a struct')
|
||||
}
|
||||
v.push(v.enc_int(-v.dec_int(a)))!
|
||||
}
|
||||
op_eq {
|
||||
@@ -345,6 +365,74 @@ fn (mut v Vm) exec() ! {
|
||||
v.arrays[v.hand(h)] << val
|
||||
v.push(h)!
|
||||
}
|
||||
op_mkstruct {
|
||||
v.ip++
|
||||
n := int(v.read_i64())
|
||||
mut fields := []Field{len: n}
|
||||
// stack holds (name, value) pairs; pop from the last field back
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
val := v.pop()!
|
||||
name := v.pop()!
|
||||
if !v.is_str(name) || !v.valid_handle(name) {
|
||||
return error('internal: struct field name is not a string')
|
||||
}
|
||||
fields[i] = Field{ name: v.strings[v.hand(name)], val: val }
|
||||
}
|
||||
v.structs << StructVal{ fields: fields }
|
||||
v.push(v.mkstruct_handle(v.structs.len - 1))!
|
||||
}
|
||||
op_sget {
|
||||
v.ip++
|
||||
name := v.pop()!
|
||||
h := v.pop()!
|
||||
if !v.is_struct(h) || !v.valid_struct_handle(h) {
|
||||
return error('field access 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 {
|
||||
v.push(f.val)!
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return error('no field "${fname}" on struct')
|
||||
}
|
||||
}
|
||||
op_sset {
|
||||
v.ip++
|
||||
// stack: [struct, value, "name"] — the name is on top
|
||||
name := v.pop()!
|
||||
val := v.pop()!
|
||||
h := v.pop()!
|
||||
if !v.is_struct(h) || !v.valid_struct_handle(h) {
|
||||
return error('field assignment 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 found := false
|
||||
for i, f in s.fields {
|
||||
if f.name == fname {
|
||||
s.fields[i].val = val
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
// setting a missing field adds it, so records can be built
|
||||
// incrementally from an empty `{}`
|
||||
s.fields << Field{ name: fname, val: val }
|
||||
}
|
||||
v.structs[v.hand(h)] = s
|
||||
}
|
||||
else {
|
||||
return error('unknown opcode ${op} at ip ${v.ip}')
|
||||
}
|
||||
@@ -421,6 +509,10 @@ fn (mut v Vm) is_arr(x i64) bool {
|
||||
return x & 3 == 3
|
||||
}
|
||||
|
||||
fn (mut v Vm) is_struct(x i64) bool {
|
||||
return x & 3 == 2
|
||||
}
|
||||
|
||||
fn (mut v Vm) enc_int(x i64) i64 {
|
||||
return x << 2
|
||||
}
|
||||
@@ -441,6 +533,10 @@ fn (mut v Vm) mkarr(idx int) i64 {
|
||||
return (i64(idx) << 2) | 3
|
||||
}
|
||||
|
||||
fn (mut v Vm) mkstruct_handle(idx int) i64 {
|
||||
return (i64(idx) << 2) | 2
|
||||
}
|
||||
|
||||
fn (mut v Vm) truthy(x i64) bool {
|
||||
return x != 0
|
||||
}
|
||||
@@ -453,6 +549,9 @@ fn (mut v Vm) add(a i64, b i64) !i64 {
|
||||
if v.is_arr(a) || v.is_arr(b) {
|
||||
return error('cannot add arrays with +')
|
||||
}
|
||||
if v.is_struct(a) || v.is_struct(b) {
|
||||
return error('cannot add structs with +')
|
||||
}
|
||||
if v.is_str(a) && v.is_str(b) {
|
||||
return v.alloc_str(v.strings[v.hand(a)] + v.strings[v.hand(b)])
|
||||
}
|
||||
@@ -481,6 +580,9 @@ fn (mut v Vm) arith(a i64, b i64, op string) !i64 {
|
||||
if v.is_arr(a) || v.is_arr(b) {
|
||||
return error('cannot use arrays with "${op}"')
|
||||
}
|
||||
if v.is_struct(a) || v.is_struct(b) {
|
||||
return error('cannot use structs with "${op}"')
|
||||
}
|
||||
x := v.dec_int(a)
|
||||
y := v.dec_int(b)
|
||||
match op {
|
||||
@@ -516,6 +618,13 @@ fn (mut v Vm) cmp(a i64, b i64, op string) !i64 {
|
||||
}
|
||||
return error('cannot order arrays')
|
||||
}
|
||||
if v.is_struct(a) || v.is_struct(b) {
|
||||
// structs compare by identity (handle equality) with ==/!=
|
||||
if op == '==' || op == '!=' {
|
||||
return bool_i64(if op == '==' { a == b } else { a != b })
|
||||
}
|
||||
return error('cannot order structs')
|
||||
}
|
||||
if v.is_str(a) && v.is_str(b) {
|
||||
sa := v.strings[v.hand(a)]
|
||||
sb := v.strings[v.hand(b)]
|
||||
@@ -573,6 +682,21 @@ fn (mut v Vm) val_str(x i64, depth int) string {
|
||||
}
|
||||
return s + ']'
|
||||
}
|
||||
if v.is_struct(x) && v.valid_struct_handle(x) {
|
||||
s := v.structs[v.hand(x)]
|
||||
mut out := '{'
|
||||
limit := if s.fields.len > 20 { 20 } else { s.fields.len }
|
||||
for i in 0..limit {
|
||||
if i > 0 {
|
||||
out += ', '
|
||||
}
|
||||
out += s.fields[i].name + ': ' + v.val_str(s.fields[i].val, depth + 1)
|
||||
}
|
||||
if s.fields.len > limit {
|
||||
out += ', ...'
|
||||
}
|
||||
return out + '}'
|
||||
}
|
||||
return v.dec_int(x).str()
|
||||
}
|
||||
|
||||
@@ -586,6 +710,11 @@ fn (mut v Vm) valid_arr_handle(x i64) bool {
|
||||
return h >= 0 && h < v.arrays.len
|
||||
}
|
||||
|
||||
fn (mut v Vm) valid_struct_handle(x i64) bool {
|
||||
h := v.hand(x)
|
||||
return h >= 0 && h < v.structs.len
|
||||
}
|
||||
|
||||
fn (mut v Vm) trace_op(op u8) {
|
||||
name := match op {
|
||||
op_halt { 'halt' }
|
||||
@@ -625,6 +754,9 @@ fn (mut v Vm) trace_op(op u8) {
|
||||
op_aset { 'aset' }
|
||||
op_alen { 'alen' }
|
||||
op_apush { 'apush' }
|
||||
op_mkstruct { 'mkstruct' }
|
||||
op_sget { 'sget' }
|
||||
op_sset { 'sset' }
|
||||
else { '??' }
|
||||
}
|
||||
mut s := ''
|
||||
|
||||
Reference in New Issue
Block a user