This commit is contained in:
allexanderbergmns
2026-08-24 15:55:23 +02:00
parent fc0ff47459
commit 265e667912
8 changed files with 414 additions and 48 deletions
+33
View File
@@ -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
View File
@@ -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 }
+98 -41
View File
@@ -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,24 +35,34 @@ 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
op TokKind
left &Expr = unsafe { nil }
right &Expr = unsafe { nil }
elems []Expr
args []Expr
line int
kind ExprKind
int_v i64
str_v 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
}
pub enum StmtKind {
@@ -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()
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 == .assign {
// assignment to an ident, an index, or a field
p.advance()
e := p.parse_expr()!
return Stmt{ kind: .assign_stmt, target: t.lit, expr: e, 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 {
p.advance()
rhs := p.parse_expr()!
return Stmt{ kind: .index_assign, base: *e.left, idx: *e.right, expr: rhs, line: t.line }
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 }
}
.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})')
}
}
return Stmt{ kind: .expr_stmt, expr: e, 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 {
p.advance()
idx := p.parse_expr()!
p.expect(.rbracket, "']'")!
e = index_node(e, idx, t.line)
// 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, "']'")!
cur = index_node(cur, idx, cur.line)
continue
}
if p.cur().kind == .dot {
p.advance()
name := p.expect(.ident, 'field name')!
cur = field_node(cur, name.lit, cur.line)
continue
}
break
}
return e
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)!