mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 16:07:01 +00:00
Code split
This commit is contained in:
+130
@@ -0,0 +1,130 @@
|
|||||||
|
// ast.v — abstract syntax tree types for the VuurRaaf language.
|
||||||
|
module compiler
|
||||||
|
|
||||||
|
pub enum ExprKind {
|
||||||
|
int_lit
|
||||||
|
str_lit
|
||||||
|
bool_lit
|
||||||
|
ident
|
||||||
|
array_lit
|
||||||
|
struct_lit
|
||||||
|
index
|
||||||
|
field
|
||||||
|
method_call
|
||||||
|
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 // 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 {
|
||||||
|
expr_stmt
|
||||||
|
let_stmt
|
||||||
|
assign_stmt
|
||||||
|
index_assign
|
||||||
|
field_assign
|
||||||
|
if_stmt
|
||||||
|
match_stmt
|
||||||
|
while_stmt
|
||||||
|
for_range_stmt
|
||||||
|
for_in_stmt
|
||||||
|
break_stmt
|
||||||
|
continue_stmt
|
||||||
|
ret_stmt
|
||||||
|
assert_stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchArm is a single `value { body }` arm of a match statement.
|
||||||
|
pub struct MatchArm {
|
||||||
|
pub mut:
|
||||||
|
val Expr
|
||||||
|
body []Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Stmt {
|
||||||
|
pub mut:
|
||||||
|
kind StmtKind
|
||||||
|
target string
|
||||||
|
expr Expr
|
||||||
|
cond Expr
|
||||||
|
base Expr // index_assign: the indexed expression
|
||||||
|
idx Expr // index_assign: the index expression
|
||||||
|
body []Stmt
|
||||||
|
els []Stmt
|
||||||
|
arms []MatchArm // match_stmt: the arms (val + body)
|
||||||
|
has_else bool // match_stmt: a trailing else arm exists
|
||||||
|
els_body []Stmt // match_stmt: body of the else arm
|
||||||
|
has_val bool
|
||||||
|
inclusive bool // for_range_stmt: `..` (false) vs `...` (true)
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructDecl is a `struct Name { a, b }` declaration.
|
||||||
|
pub struct StructDecl {
|
||||||
|
pub mut:
|
||||||
|
name string
|
||||||
|
fields []string
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FnDecl {
|
||||||
|
pub mut:
|
||||||
|
name string
|
||||||
|
recv_name string // method receiver local name ('' for plain functions)
|
||||||
|
recv_type string // method receiver struct type ('' for plain functions)
|
||||||
|
params []string
|
||||||
|
body []Stmt
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ImportDecl {
|
||||||
|
pub mut:
|
||||||
|
path string
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnumDecl is an `enum Name { variant1 variant2 ... }` declaration.
|
||||||
|
pub struct EnumDecl {
|
||||||
|
pub mut:
|
||||||
|
name string
|
||||||
|
variants []string
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConstDecl is a `const NAME = value` declaration.
|
||||||
|
pub struct ConstDecl {
|
||||||
|
pub mut:
|
||||||
|
name string
|
||||||
|
value Expr
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Program {
|
||||||
|
pub mut:
|
||||||
|
fns []FnDecl
|
||||||
|
structs []StructDecl
|
||||||
|
enums []EnumDecl
|
||||||
|
imports []ImportDecl
|
||||||
|
consts []ConstDecl
|
||||||
|
}
|
||||||
@@ -0,0 +1,854 @@
|
|||||||
|
// codegen.v — bytecode code generator for VuurRaaf.
|
||||||
|
//
|
||||||
|
// Compiles a parsed program into a VROBJ object file: flat bytecode plus a
|
||||||
|
// symbol per function and a relocation per call site. Call targets are left as
|
||||||
|
// relocations and resolved by the linker, so functions may live in other files.
|
||||||
|
module compiler
|
||||||
|
|
||||||
|
import obj
|
||||||
|
|
||||||
|
struct Fixup {
|
||||||
|
name string
|
||||||
|
off u32
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoopCtx records where `break` and `continue` should jump while generating
|
||||||
|
// the body of a loop. For `for` loops `continue` targets the increment, not
|
||||||
|
// the condition check, so the loop variable still advances.
|
||||||
|
struct LoopCtx {
|
||||||
|
break_l string
|
||||||
|
continue_l string
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Gen {
|
||||||
|
mut:
|
||||||
|
code []u8
|
||||||
|
strings []string
|
||||||
|
str_map map[string]int
|
||||||
|
symbols []obj.Symbol
|
||||||
|
relocs []obj.Reloc
|
||||||
|
locals map[string]int
|
||||||
|
types map[string]string // local name -> declared struct type ('' = unknown)
|
||||||
|
structs map[string][]string // declared struct name -> field list
|
||||||
|
enums map[string][]string // enum name -> variant list
|
||||||
|
enum_vals map[string]int // 'Enum.variant' -> integer value
|
||||||
|
consts map[string]i64 // constant name -> integer value
|
||||||
|
local_cnt int
|
||||||
|
argc int
|
||||||
|
cur_fn string
|
||||||
|
labels map[string]int
|
||||||
|
fixups []Fixup
|
||||||
|
loops []LoopCtx
|
||||||
|
enter_off u32
|
||||||
|
next_lbl int
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen(prog Program) !obj.Obj {
|
||||||
|
mut g := Gen{}
|
||||||
|
// register enums first so their values are available everywhere
|
||||||
|
for ed in prog.enums {
|
||||||
|
if ed.name in g.enums {
|
||||||
|
return error('duplicate enum declaration "${ed.name}"')
|
||||||
|
}
|
||||||
|
g.enums[ed.name] = ed.variants
|
||||||
|
for i, v in ed.variants {
|
||||||
|
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
|
||||||
|
for sd in prog.structs {
|
||||||
|
if sd.name in g.structs {
|
||||||
|
return error('duplicate struct declaration "${sd.name}"')
|
||||||
|
}
|
||||||
|
g.structs[sd.name] = sd.fields
|
||||||
|
}
|
||||||
|
// compile imported files and merge their objects
|
||||||
|
for imp in prog.imports {
|
||||||
|
imported := compile_file(imp.path)!
|
||||||
|
// merge symbols from the imported object
|
||||||
|
for s in imported.symbols {
|
||||||
|
g.symbols << s
|
||||||
|
}
|
||||||
|
// merge strings
|
||||||
|
for s in imported.strings {
|
||||||
|
g.strings << s
|
||||||
|
}
|
||||||
|
// append imported bytecode and adjust relocations
|
||||||
|
code_off := g.code.len
|
||||||
|
g.code << imported.code
|
||||||
|
for r in imported.relocs {
|
||||||
|
g.relocs << obj.Reloc{ offset: u32(code_off) + r.offset, name: r.name, kind: r.kind }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for fd in prog.fns {
|
||||||
|
g.gen_fn(fd)!
|
||||||
|
}
|
||||||
|
return obj.Obj{
|
||||||
|
symbols: g.symbols
|
||||||
|
strings: g.strings
|
||||||
|
code: g.code
|
||||||
|
relocs: g.relocs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut g Gen) gen_fn(fd FnDecl) ! {
|
||||||
|
// methods compile to functions named `Type.method`; the receiver is the
|
||||||
|
// implicit first argument, so `p.dist(x)` becomes `call Point.dist p, x`
|
||||||
|
sym := if fd.recv_type.len > 0 { '${fd.recv_type}.${fd.name}' } else { fd.name }
|
||||||
|
g.cur_fn = sym
|
||||||
|
g.symbols << obj.Symbol{ name: sym, entry: g.code.len }
|
||||||
|
g.locals.clear()
|
||||||
|
g.types.clear()
|
||||||
|
g.local_cnt = 0
|
||||||
|
g.argc = fd.params.len + if fd.recv_type.len > 0 { 1 } else { 0 }
|
||||||
|
mut next := 0
|
||||||
|
if fd.recv_type.len > 0 {
|
||||||
|
g.locals[fd.recv_name] = 0
|
||||||
|
g.types[fd.recv_name] = fd.recv_type
|
||||||
|
next = 1
|
||||||
|
}
|
||||||
|
for i, p in fd.params {
|
||||||
|
g.locals[p] = i + next
|
||||||
|
}
|
||||||
|
g.local_cnt = g.argc
|
||||||
|
// `enter n` reserves the non-parameter locals; n is patched once the body
|
||||||
|
// has been scanned.
|
||||||
|
g.code << op_enter
|
||||||
|
g.enter_off = u32(g.code.len)
|
||||||
|
g.code << obj.encode_i64(0)
|
||||||
|
for st in fd.body {
|
||||||
|
g.gen_stmt(st)!
|
||||||
|
}
|
||||||
|
g.code << op_ret // trailing return for fall-through
|
||||||
|
obj.patch_i64(mut g.code, g.enter_off, i64(g.local_cnt - g.argc))
|
||||||
|
// resolve intra-function jump targets
|
||||||
|
for f in g.fixups {
|
||||||
|
target := g.labels[f.name] or {
|
||||||
|
return error('internal error: unresolved label ${f.name} in fn ${fd.name}')
|
||||||
|
}
|
||||||
|
obj.patch_i64(mut g.code, f.off, i64(target))
|
||||||
|
}
|
||||||
|
g.fixups.clear()
|
||||||
|
g.labels.clear()
|
||||||
|
g.cur_fn = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut g Gen) gen_stmt(st Stmt) ! {
|
||||||
|
match st.kind {
|
||||||
|
.expr_stmt {
|
||||||
|
g.gen_expr(st.expr)!
|
||||||
|
// print/println already consume their value; everything else
|
||||||
|
// leaves one on the stack that must be discarded
|
||||||
|
if st.expr.kind == .call && (st.expr.name == 'print' || st.expr.name == 'println') {
|
||||||
|
// nothing to discard
|
||||||
|
} else {
|
||||||
|
g.code << op_pop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.let_stmt {
|
||||||
|
g.gen_expr(st.expr)!
|
||||||
|
idx := g.local_cnt
|
||||||
|
g.local_cnt++
|
||||||
|
g.locals[st.target] = idx
|
||||||
|
g.types[st.target] = g.expr_type(st.expr)
|
||||||
|
g.code << op_store
|
||||||
|
g.code << obj.encode_i64(i64(idx))
|
||||||
|
}
|
||||||
|
.assign_stmt {
|
||||||
|
idx := g.locals[st.target] or {
|
||||||
|
return error('unknown variable "${st.target}" at line ${st.line}')
|
||||||
|
}
|
||||||
|
g.gen_expr(st.expr)!
|
||||||
|
g.types[st.target] = g.expr_type(st.expr)
|
||||||
|
g.code << op_store
|
||||||
|
g.code << obj.encode_i64(i64(idx))
|
||||||
|
}
|
||||||
|
.index_assign {
|
||||||
|
// if the index is a string literal, use struct field set (map style)
|
||||||
|
if st.idx.kind == .str_lit {
|
||||||
|
g.gen_expr(st.base)!
|
||||||
|
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 {
|
||||||
|
// 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()
|
||||||
|
g.gen_expr(st.cond)!
|
||||||
|
g.code << op_jz
|
||||||
|
g.code << obj.encode_i64(0)
|
||||||
|
g.fixups << Fixup{ name: else_l, off: u32(g.code.len) - 8 }
|
||||||
|
for s in st.body {
|
||||||
|
g.gen_stmt(s)!
|
||||||
|
}
|
||||||
|
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(else_l)
|
||||||
|
for s in st.els {
|
||||||
|
g.gen_stmt(s)!
|
||||||
|
}
|
||||||
|
g.emit_label(end_l)
|
||||||
|
}
|
||||||
|
.match_stmt {
|
||||||
|
// match x { v1 {..} v2 {..} else {..} } → subject := x; a chain of
|
||||||
|
// equality tests jumping to the matching arm; else falls through.
|
||||||
|
subj_idx := g.new_local()
|
||||||
|
end_l := g.new_label()
|
||||||
|
g.gen_expr(st.expr)!
|
||||||
|
g.emit_store(subj_idx)
|
||||||
|
for i, arm in st.arms {
|
||||||
|
next_l := g.new_label()
|
||||||
|
g.emit_load(subj_idx)
|
||||||
|
g.gen_expr(arm.val)!
|
||||||
|
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 }
|
||||||
|
for s in arm.body {
|
||||||
|
g.gen_stmt(s)!
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
if i == st.arms.len - 1 && !st.has_else {
|
||||||
|
// no else: fall through to the end label
|
||||||
|
g.emit_label(end_l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if st.has_else {
|
||||||
|
for s in st.els_body {
|
||||||
|
g.gen_stmt(s)!
|
||||||
|
}
|
||||||
|
g.emit_label(end_l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.while_stmt {
|
||||||
|
loop_l := g.new_label()
|
||||||
|
end_l := g.new_label()
|
||||||
|
g.emit_label(loop_l)
|
||||||
|
g.gen_expr(st.cond)!
|
||||||
|
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: loop_l }
|
||||||
|
for s in st.body {
|
||||||
|
g.gen_stmt(s)!
|
||||||
|
}
|
||||||
|
g.loops.delete_last()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
.for_range_stmt {
|
||||||
|
// for i in a..b / for i in a...b → i := a; while i <(<=) b { body; i++ }
|
||||||
|
var_idx := g.new_local()
|
||||||
|
bound_idx := g.new_local()
|
||||||
|
loop_l := g.new_label()
|
||||||
|
inc_l := g.new_label()
|
||||||
|
end_l := g.new_label()
|
||||||
|
g.gen_expr(st.expr)!
|
||||||
|
g.gen_expr(st.cond)!
|
||||||
|
g.emit_store(bound_idx)
|
||||||
|
g.emit_store(var_idx)
|
||||||
|
g.emit_label(loop_l)
|
||||||
|
g.emit_load(var_idx)
|
||||||
|
g.emit_load(bound_idx)
|
||||||
|
g.code << if st.inclusive { op_le } else { 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[st.target] or { -1 }
|
||||||
|
prev_t := g.types[st.target] or { '' }
|
||||||
|
g.locals[st.target] = var_idx
|
||||||
|
g.types.delete(st.target)
|
||||||
|
for s in st.body {
|
||||||
|
g.gen_stmt(s)!
|
||||||
|
}
|
||||||
|
if prev >= 0 {
|
||||||
|
g.locals[st.target] = prev
|
||||||
|
} else {
|
||||||
|
g.locals.delete(st.target)
|
||||||
|
}
|
||||||
|
if prev_t.len > 0 {
|
||||||
|
g.types[st.target] = 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)
|
||||||
|
}
|
||||||
|
.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++ }
|
||||||
|
arr_idx := g.new_local()
|
||||||
|
idx_idx := g.new_local()
|
||||||
|
elem_idx := g.new_local()
|
||||||
|
loop_l := g.new_label()
|
||||||
|
inc_l := g.new_label()
|
||||||
|
end_l := g.new_label()
|
||||||
|
g.gen_expr(st.expr)!
|
||||||
|
g.emit_store(arr_idx)
|
||||||
|
g.code << op_push_i
|
||||||
|
g.code << obj.encode_i64(0)
|
||||||
|
g.emit_store(idx_idx)
|
||||||
|
g.emit_label(loop_l)
|
||||||
|
g.emit_load(idx_idx)
|
||||||
|
g.emit_load(arr_idx)
|
||||||
|
g.code << op_alen
|
||||||
|
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 }
|
||||||
|
g.emit_load(arr_idx)
|
||||||
|
g.emit_load(idx_idx)
|
||||||
|
g.code << op_aget
|
||||||
|
g.emit_store(elem_idx)
|
||||||
|
prev := g.locals[st.target] or { -1 }
|
||||||
|
prev_t := g.types[st.target] or { '' }
|
||||||
|
g.locals[st.target] = elem_idx
|
||||||
|
g.types.delete(st.target)
|
||||||
|
for s in st.body {
|
||||||
|
g.gen_stmt(s)!
|
||||||
|
}
|
||||||
|
if prev >= 0 {
|
||||||
|
g.locals[st.target] = prev
|
||||||
|
} else {
|
||||||
|
g.locals.delete(st.target)
|
||||||
|
}
|
||||||
|
if prev_t.len > 0 {
|
||||||
|
g.types[st.target] = prev_t
|
||||||
|
}
|
||||||
|
g.loops.delete_last()
|
||||||
|
g.emit_label(inc_l)
|
||||||
|
g.emit_load(idx_idx)
|
||||||
|
g.code << op_push_i
|
||||||
|
g.code << obj.encode_i64(1)
|
||||||
|
g.code << op_add
|
||||||
|
g.emit_store(idx_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)
|
||||||
|
}
|
||||||
|
.ret_stmt {
|
||||||
|
if st.has_val {
|
||||||
|
g.gen_expr(st.expr)!
|
||||||
|
g.code << op_retv
|
||||||
|
} else {
|
||||||
|
g.code << op_ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.assert_stmt {
|
||||||
|
g.gen_expr(st.expr)!
|
||||||
|
g.code << op_assert
|
||||||
|
}
|
||||||
|
.break_stmt {
|
||||||
|
if g.loops.len == 0 {
|
||||||
|
return error('break outside of a loop (line ${st.line})')
|
||||||
|
}
|
||||||
|
ctx := g.loops[g.loops.len - 1]
|
||||||
|
g.code << op_jmp
|
||||||
|
g.code << obj.encode_i64(0)
|
||||||
|
g.fixups << Fixup{ name: ctx.break_l, off: u32(g.code.len) - 8 }
|
||||||
|
}
|
||||||
|
.continue_stmt {
|
||||||
|
if g.loops.len == 0 {
|
||||||
|
return error('continue outside of a loop (line ${st.line})')
|
||||||
|
}
|
||||||
|
ctx := g.loops[g.loops.len - 1]
|
||||||
|
g.code << op_jmp
|
||||||
|
g.code << obj.encode_i64(0)
|
||||||
|
g.fixups << Fixup{ name: ctx.continue_l, off: u32(g.code.len) - 8 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut g Gen) gen_expr(e Expr) ! {
|
||||||
|
match e.kind {
|
||||||
|
.int_lit {
|
||||||
|
g.code << op_push_i
|
||||||
|
g.code << obj.encode_i64(e.int_v)
|
||||||
|
}
|
||||||
|
.str_lit {
|
||||||
|
// the index is a placeholder; the linker rebases it via a string
|
||||||
|
// relocation so multi-file links keep working
|
||||||
|
g.code << op_push_s
|
||||||
|
g.code << obj.encode_i64(0)
|
||||||
|
g.relocs << obj.Reloc{ offset: u32(g.code.len) - 8, name: e.str_v, kind: 1 }
|
||||||
|
}
|
||||||
|
.array_lit {
|
||||||
|
for el in e.elems {
|
||||||
|
g.gen_expr(el)!
|
||||||
|
}
|
||||||
|
g.code << op_mkarray
|
||||||
|
g.code << obj.encode_i64(i64(e.elems.len))
|
||||||
|
}
|
||||||
|
.struct_lit {
|
||||||
|
// typed literals validate their fields against the declaration
|
||||||
|
// (an undeclared type name is allowed — it may live in another
|
||||||
|
// file, where the same validation applies)
|
||||||
|
if e.name.len > 0 && e.name in g.structs {
|
||||||
|
decl_fields := g.structs[e.name]
|
||||||
|
mut seen := map[string]bool{}
|
||||||
|
for f in e.fields {
|
||||||
|
if f.name !in decl_fields {
|
||||||
|
return error('unknown field "${f.name}" for struct ${e.name} (line ${e.line})')
|
||||||
|
}
|
||||||
|
if f.name in seen {
|
||||||
|
return error('duplicate field "${f.name}" in struct literal (line ${e.line})')
|
||||||
|
}
|
||||||
|
seen[f.name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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 {
|
||||||
|
// check if it's an enum variant (e.g., Color.red)
|
||||||
|
if e.left.kind == .ident {
|
||||||
|
key := '${e.left.name}.${e.name}'
|
||||||
|
if key in g.enum_vals {
|
||||||
|
g.code << op_push_i
|
||||||
|
g.code << obj.encode_i64(i64(g.enum_vals[key]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.gen_expr(*e.left)!
|
||||||
|
g.emit_field_name(e.name)
|
||||||
|
g.code << op_sget
|
||||||
|
}
|
||||||
|
.method_call {
|
||||||
|
// p.dist(x) → call <Type>.dist p, x
|
||||||
|
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)!
|
||||||
|
for a in e.args {
|
||||||
|
g.gen_expr(a)!
|
||||||
|
}
|
||||||
|
g.code << op_call
|
||||||
|
g.code << obj.encode_i64(0) // placeholder — patched by the linker
|
||||||
|
g.relocs << obj.Reloc{ offset: u32(g.code.len) - 8, name: '${recv_t}.${e.name}', kind: 0 }
|
||||||
|
g.code << obj.encode_i64(i64(e.args.len + 1)) // receiver + args
|
||||||
|
}
|
||||||
|
.index {
|
||||||
|
// if the index is a string literal, use struct field access (map style)
|
||||||
|
if e.right.kind == .str_lit {
|
||||||
|
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 {
|
||||||
|
g.code << op_push_i
|
||||||
|
g.code << obj.encode_i64(e.int_v)
|
||||||
|
}
|
||||||
|
.ident {
|
||||||
|
// check if it's a constant
|
||||||
|
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 << 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}')
|
||||||
|
}
|
||||||
|
g.code << op_load
|
||||||
|
g.code << obj.encode_i64(i64(idx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.unary {
|
||||||
|
g.gen_expr(*e.right)!
|
||||||
|
if e.op == .kw_not {
|
||||||
|
g.code << op_not
|
||||||
|
} else {
|
||||||
|
g.code << op_neg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.binary {
|
||||||
|
g.gen_binary(e)!
|
||||||
|
}
|
||||||
|
.call {
|
||||||
|
g.gen_call(e)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut g Gen) gen_call(e Expr) ! {
|
||||||
|
if e.name == 'print' || e.name == 'println' {
|
||||||
|
if e.args.len != 1 {
|
||||||
|
return error('${e.name}() takes exactly one argument (line ${e.line})')
|
||||||
|
}
|
||||||
|
g.gen_expr(e.args[0])!
|
||||||
|
g.code << if e.name == 'print' { op_print } else { op_println }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if e.name == 'len' {
|
||||||
|
if e.args.len != 1 {
|
||||||
|
return error('len() takes exactly one argument (line ${e.line})')
|
||||||
|
}
|
||||||
|
g.gen_expr(e.args[0])!
|
||||||
|
g.code << op_alen
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if e.name == 'push' {
|
||||||
|
if e.args.len != 2 {
|
||||||
|
return error('push() takes exactly two arguments (line ${e.line})')
|
||||||
|
}
|
||||||
|
g.gen_expr(e.args[0])!
|
||||||
|
g.gen_expr(e.args[1])!
|
||||||
|
g.code << op_apush
|
||||||
|
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 {
|
||||||
|
g.gen_expr(a)!
|
||||||
|
}
|
||||||
|
g.code << op_call
|
||||||
|
g.code << obj.encode_i64(0) // placeholder — patched by the linker
|
||||||
|
g.relocs << obj.Reloc{ offset: u32(g.code.len) - 8, name: e.name, kind: 0 }
|
||||||
|
g.code << obj.encode_i64(i64(e.args.len)) // argc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut g Gen) gen_binary(e Expr) ! {
|
||||||
|
match e.op {
|
||||||
|
.kw_and {
|
||||||
|
// a and b → short-circuit: if !a or !b then 0 else 1
|
||||||
|
false_l := g.new_label()
|
||||||
|
end_l := g.new_label()
|
||||||
|
g.gen_expr(*e.left)!
|
||||||
|
g.code << op_jz
|
||||||
|
g.code << obj.encode_i64(0)
|
||||||
|
g.fixups << Fixup{ name: false_l, off: u32(g.code.len) - 8 }
|
||||||
|
g.gen_expr(*e.right)!
|
||||||
|
g.code << op_jz
|
||||||
|
g.code << obj.encode_i64(0)
|
||||||
|
g.fixups << Fixup{ name: false_l, off: u32(g.code.len) - 8 }
|
||||||
|
g.code << op_push_i
|
||||||
|
g.code << obj.encode_i64(1)
|
||||||
|
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(false_l)
|
||||||
|
g.code << op_push_i
|
||||||
|
g.code << obj.encode_i64(0)
|
||||||
|
g.emit_label(end_l)
|
||||||
|
}
|
||||||
|
.kw_or {
|
||||||
|
// a or b → short-circuit: if a or b then 1 else 0
|
||||||
|
true_l := g.new_label()
|
||||||
|
end_l := g.new_label()
|
||||||
|
g.gen_expr(*e.left)!
|
||||||
|
g.code << op_jnz
|
||||||
|
g.code << obj.encode_i64(0)
|
||||||
|
g.fixups << Fixup{ name: true_l, off: u32(g.code.len) - 8 }
|
||||||
|
g.gen_expr(*e.right)!
|
||||||
|
g.code << op_jnz
|
||||||
|
g.code << obj.encode_i64(0)
|
||||||
|
g.fixups << Fixup{ name: true_l, off: u32(g.code.len) - 8 }
|
||||||
|
g.code << op_push_i
|
||||||
|
g.code << obj.encode_i64(0)
|
||||||
|
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(true_l)
|
||||||
|
g.code << op_push_i
|
||||||
|
g.code << obj.encode_i64(1)
|
||||||
|
g.emit_label(end_l)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g.gen_expr(*e.left)!
|
||||||
|
g.gen_expr(*e.right)!
|
||||||
|
op := match e.op {
|
||||||
|
.plus { op_add }
|
||||||
|
.minus { op_sub }
|
||||||
|
.star { op_mul }
|
||||||
|
.slash { op_div }
|
||||||
|
.percent { op_mod }
|
||||||
|
.eq_eq { op_eq }
|
||||||
|
.not_eq { op_ne }
|
||||||
|
.lt { op_lt }
|
||||||
|
.le { op_le }
|
||||||
|
.gt { op_gt }
|
||||||
|
.ge { op_ge }
|
||||||
|
else {
|
||||||
|
return error('unsupported binary operator at line ${e.line}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.code << op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// expr_type returns the declared struct type of an expression when it is
|
||||||
|
// statically knowable: a typed literal `Point{...}`, a copy of a typed
|
||||||
|
// variable, or an enum variant `Enum.variant`. Everything else has no
|
||||||
|
// known type ('').
|
||||||
|
fn (mut g Gen) expr_type(e Expr) string {
|
||||||
|
if e.kind == .struct_lit {
|
||||||
|
return e.name
|
||||||
|
}
|
||||||
|
if e.kind == .ident {
|
||||||
|
return g.types[e.name] or { '' }
|
||||||
|
}
|
||||||
|
// enum variant: Color.red → type is "Color"
|
||||||
|
if e.kind == .field && e.left.kind == .ident {
|
||||||
|
key := '${e.left.name}.${e.name}'
|
||||||
|
if key in g.enum_vals {
|
||||||
|
return e.left.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// (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 {
|
||||||
|
recv := e.left
|
||||||
|
if recv.kind == .ident {
|
||||||
|
t := g.types[recv.name] or { '' }
|
||||||
|
if t.len > 0 {
|
||||||
|
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})')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// 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]
|
||||||
|
}
|
||||||
|
idx := g.strings.len
|
||||||
|
g.strings << s
|
||||||
|
g.str_map[s] = idx
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut g Gen) new_local() int {
|
||||||
|
idx := g.local_cnt
|
||||||
|
g.local_cnt++
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut g Gen) emit_load(idx int) {
|
||||||
|
g.code << op_load
|
||||||
|
g.code << obj.encode_i64(i64(idx))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut g Gen) emit_store(idx int) {
|
||||||
|
g.code << op_store
|
||||||
|
g.code << obj.encode_i64(i64(idx))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut g Gen) new_label() string {
|
||||||
|
g.next_lbl++
|
||||||
|
return 'L${g.next_lbl}'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut g Gen) emit_label(name string) {
|
||||||
|
g.labels[name] = g.code.len
|
||||||
|
}
|
||||||
+1
-901
@@ -1,59 +1,9 @@
|
|||||||
// compiler.v — bytecode code generator for VuurRaaf.
|
// compiler.v — public API for the VuurRaaf compiler.
|
||||||
//
|
|
||||||
// Compiles a parsed program into a VROBJ object file: flat bytecode plus a
|
|
||||||
// symbol per function and a relocation per call site. Call targets are left as
|
|
||||||
// relocations and resolved by the linker, so functions may live in other files.
|
|
||||||
module compiler
|
module compiler
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import obj
|
import obj
|
||||||
|
|
||||||
// opcodes — keep in sync with vm/vm.v and assembler/assembler.v
|
|
||||||
const op_halt = u8(0)
|
|
||||||
const op_push_i = u8(1)
|
|
||||||
const op_push_s = u8(2)
|
|
||||||
const op_load = u8(3)
|
|
||||||
const op_store = u8(4)
|
|
||||||
const op_pop = u8(5)
|
|
||||||
const op_dup = u8(6)
|
|
||||||
const op_add = u8(7)
|
|
||||||
const op_sub = u8(8)
|
|
||||||
const op_mul = u8(9)
|
|
||||||
const op_div = u8(10)
|
|
||||||
const op_mod = u8(11)
|
|
||||||
const op_neg = u8(12)
|
|
||||||
const op_eq = u8(13)
|
|
||||||
const op_ne = u8(14)
|
|
||||||
const op_lt = u8(15)
|
|
||||||
const op_le = u8(16)
|
|
||||||
const op_gt = u8(17)
|
|
||||||
const op_ge = u8(18)
|
|
||||||
const op_and = u8(19)
|
|
||||||
const op_or = u8(20)
|
|
||||||
const op_not = u8(21)
|
|
||||||
const op_jmp = u8(22)
|
|
||||||
const op_jz = u8(23)
|
|
||||||
const op_jnz = u8(24)
|
|
||||||
const op_call = u8(25)
|
|
||||||
const op_ret = u8(26)
|
|
||||||
const op_retv = u8(27)
|
|
||||||
const op_print = u8(28)
|
|
||||||
const op_println = u8(29)
|
|
||||||
const op_assert = u8(30)
|
|
||||||
const op_enter = u8(31)
|
|
||||||
const op_mkarray = u8(32)
|
|
||||||
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 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 {
|
||||||
toks := tokenize(src)!
|
toks := tokenize(src)!
|
||||||
@@ -65,853 +15,3 @@ pub fn compile_file(path string) !obj.Obj {
|
|||||||
src := os.read_file(path)!
|
src := os.read_file(path)!
|
||||||
return compile(src)!
|
return compile(src)!
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
struct Fixup {
|
|
||||||
name string
|
|
||||||
off u32
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoopCtx records where `break` and `continue` should jump while generating
|
|
||||||
// the body of a loop. For `for` loops `continue` targets the increment, not
|
|
||||||
// the condition check, so the loop variable still advances.
|
|
||||||
struct LoopCtx {
|
|
||||||
break_l string
|
|
||||||
continue_l string
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Gen {
|
|
||||||
mut:
|
|
||||||
code []u8
|
|
||||||
strings []string
|
|
||||||
str_map map[string]int
|
|
||||||
symbols []obj.Symbol
|
|
||||||
relocs []obj.Reloc
|
|
||||||
locals map[string]int
|
|
||||||
types map[string]string // local name -> declared struct type ('' = unknown)
|
|
||||||
structs map[string][]string // declared struct name -> field list
|
|
||||||
enums map[string][]string // enum name -> variant list
|
|
||||||
enum_vals map[string]int // 'Enum.variant' -> integer value
|
|
||||||
consts map[string]i64 // constant name -> integer value
|
|
||||||
local_cnt int
|
|
||||||
argc int
|
|
||||||
cur_fn string
|
|
||||||
labels map[string]int
|
|
||||||
fixups []Fixup
|
|
||||||
loops []LoopCtx
|
|
||||||
enter_off u32
|
|
||||||
next_lbl int
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gen(prog Program) !obj.Obj {
|
|
||||||
mut g := Gen{}
|
|
||||||
// register enums first so their values are available everywhere
|
|
||||||
for ed in prog.enums {
|
|
||||||
if ed.name in g.enums {
|
|
||||||
return error('duplicate enum declaration "${ed.name}"')
|
|
||||||
}
|
|
||||||
g.enums[ed.name] = ed.variants
|
|
||||||
for i, v in ed.variants {
|
|
||||||
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
|
|
||||||
for sd in prog.structs {
|
|
||||||
if sd.name in g.structs {
|
|
||||||
return error('duplicate struct declaration "${sd.name}"')
|
|
||||||
}
|
|
||||||
g.structs[sd.name] = sd.fields
|
|
||||||
}
|
|
||||||
// compile imported files and merge their objects
|
|
||||||
for imp in prog.imports {
|
|
||||||
imported := compile_file(imp.path)!
|
|
||||||
// merge symbols from the imported object
|
|
||||||
for s in imported.symbols {
|
|
||||||
g.symbols << s
|
|
||||||
}
|
|
||||||
// merge strings
|
|
||||||
for s in imported.strings {
|
|
||||||
g.strings << s
|
|
||||||
}
|
|
||||||
// append imported bytecode and adjust relocations
|
|
||||||
code_off := g.code.len
|
|
||||||
g.code << imported.code
|
|
||||||
for r in imported.relocs {
|
|
||||||
g.relocs << obj.Reloc{ offset: u32(code_off) + r.offset, name: r.name, kind: r.kind }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for fd in prog.fns {
|
|
||||||
g.gen_fn(fd)!
|
|
||||||
}
|
|
||||||
return obj.Obj{
|
|
||||||
symbols: g.symbols
|
|
||||||
strings: g.strings
|
|
||||||
code: g.code
|
|
||||||
relocs: g.relocs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut g Gen) gen_fn(fd FnDecl) ! {
|
|
||||||
// methods compile to functions named `Type.method`; the receiver is the
|
|
||||||
// implicit first argument, so `p.dist(x)` becomes `call Point.dist p, x`
|
|
||||||
sym := if fd.recv_type.len > 0 { '${fd.recv_type}.${fd.name}' } else { fd.name }
|
|
||||||
g.cur_fn = sym
|
|
||||||
g.symbols << obj.Symbol{ name: sym, entry: g.code.len }
|
|
||||||
g.locals.clear()
|
|
||||||
g.types.clear()
|
|
||||||
g.local_cnt = 0
|
|
||||||
g.argc = fd.params.len + if fd.recv_type.len > 0 { 1 } else { 0 }
|
|
||||||
mut next := 0
|
|
||||||
if fd.recv_type.len > 0 {
|
|
||||||
g.locals[fd.recv_name] = 0
|
|
||||||
g.types[fd.recv_name] = fd.recv_type
|
|
||||||
next = 1
|
|
||||||
}
|
|
||||||
for i, p in fd.params {
|
|
||||||
g.locals[p] = i + next
|
|
||||||
}
|
|
||||||
g.local_cnt = g.argc
|
|
||||||
// `enter n` reserves the non-parameter locals; n is patched once the body
|
|
||||||
// has been scanned.
|
|
||||||
g.code << op_enter
|
|
||||||
g.enter_off = u32(g.code.len)
|
|
||||||
g.code << obj.encode_i64(0)
|
|
||||||
for st in fd.body {
|
|
||||||
g.gen_stmt(st)!
|
|
||||||
}
|
|
||||||
g.code << op_ret // trailing return for fall-through
|
|
||||||
obj.patch_i64(mut g.code, g.enter_off, i64(g.local_cnt - g.argc))
|
|
||||||
// resolve intra-function jump targets
|
|
||||||
for f in g.fixups {
|
|
||||||
target := g.labels[f.name] or {
|
|
||||||
return error('internal error: unresolved label ${f.name} in fn ${fd.name}')
|
|
||||||
}
|
|
||||||
obj.patch_i64(mut g.code, f.off, i64(target))
|
|
||||||
}
|
|
||||||
g.fixups.clear()
|
|
||||||
g.labels.clear()
|
|
||||||
g.cur_fn = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut g Gen) gen_stmt(st Stmt) ! {
|
|
||||||
match st.kind {
|
|
||||||
.expr_stmt {
|
|
||||||
g.gen_expr(st.expr)!
|
|
||||||
// print/println already consume their value; everything else
|
|
||||||
// leaves one on the stack that must be discarded
|
|
||||||
if st.expr.kind == .call && (st.expr.name == 'print' || st.expr.name == 'println') {
|
|
||||||
// nothing to discard
|
|
||||||
} else {
|
|
||||||
g.code << op_pop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.let_stmt {
|
|
||||||
g.gen_expr(st.expr)!
|
|
||||||
idx := g.local_cnt
|
|
||||||
g.local_cnt++
|
|
||||||
g.locals[st.target] = idx
|
|
||||||
g.types[st.target] = g.expr_type(st.expr)
|
|
||||||
g.code << op_store
|
|
||||||
g.code << obj.encode_i64(i64(idx))
|
|
||||||
}
|
|
||||||
.assign_stmt {
|
|
||||||
idx := g.locals[st.target] or {
|
|
||||||
return error('unknown variable "${st.target}" at line ${st.line}')
|
|
||||||
}
|
|
||||||
g.gen_expr(st.expr)!
|
|
||||||
g.types[st.target] = g.expr_type(st.expr)
|
|
||||||
g.code << op_store
|
|
||||||
g.code << obj.encode_i64(i64(idx))
|
|
||||||
}
|
|
||||||
.index_assign {
|
|
||||||
// if the index is a string literal, use struct field set (map style)
|
|
||||||
if st.idx.kind == .str_lit {
|
|
||||||
g.gen_expr(st.base)!
|
|
||||||
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 {
|
|
||||||
// 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()
|
|
||||||
g.gen_expr(st.cond)!
|
|
||||||
g.code << op_jz
|
|
||||||
g.code << obj.encode_i64(0)
|
|
||||||
g.fixups << Fixup{ name: else_l, off: u32(g.code.len) - 8 }
|
|
||||||
for s in st.body {
|
|
||||||
g.gen_stmt(s)!
|
|
||||||
}
|
|
||||||
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(else_l)
|
|
||||||
for s in st.els {
|
|
||||||
g.gen_stmt(s)!
|
|
||||||
}
|
|
||||||
g.emit_label(end_l)
|
|
||||||
}
|
|
||||||
.match_stmt {
|
|
||||||
// match x { v1 {..} v2 {..} else {..} } → subject := x; a chain of
|
|
||||||
// equality tests jumping to the matching arm; else falls through.
|
|
||||||
subj_idx := g.new_local()
|
|
||||||
end_l := g.new_label()
|
|
||||||
g.gen_expr(st.expr)!
|
|
||||||
g.emit_store(subj_idx)
|
|
||||||
for i, arm in st.arms {
|
|
||||||
next_l := g.new_label()
|
|
||||||
g.emit_load(subj_idx)
|
|
||||||
g.gen_expr(arm.val)!
|
|
||||||
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 }
|
|
||||||
for s in arm.body {
|
|
||||||
g.gen_stmt(s)!
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
if i == st.arms.len - 1 && !st.has_else {
|
|
||||||
// no else: fall through to the end label
|
|
||||||
g.emit_label(end_l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if st.has_else {
|
|
||||||
for s in st.els_body {
|
|
||||||
g.gen_stmt(s)!
|
|
||||||
}
|
|
||||||
g.emit_label(end_l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.while_stmt {
|
|
||||||
loop_l := g.new_label()
|
|
||||||
end_l := g.new_label()
|
|
||||||
g.emit_label(loop_l)
|
|
||||||
g.gen_expr(st.cond)!
|
|
||||||
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: loop_l }
|
|
||||||
for s in st.body {
|
|
||||||
g.gen_stmt(s)!
|
|
||||||
}
|
|
||||||
g.loops.delete_last()
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
.for_range_stmt {
|
|
||||||
// for i in a..b / for i in a...b → i := a; while i <(<=) b { body; i++ }
|
|
||||||
var_idx := g.new_local()
|
|
||||||
bound_idx := g.new_local()
|
|
||||||
loop_l := g.new_label()
|
|
||||||
inc_l := g.new_label()
|
|
||||||
end_l := g.new_label()
|
|
||||||
g.gen_expr(st.expr)!
|
|
||||||
g.gen_expr(st.cond)!
|
|
||||||
g.emit_store(bound_idx)
|
|
||||||
g.emit_store(var_idx)
|
|
||||||
g.emit_label(loop_l)
|
|
||||||
g.emit_load(var_idx)
|
|
||||||
g.emit_load(bound_idx)
|
|
||||||
g.code << if st.inclusive { op_le } else { 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[st.target] or { -1 }
|
|
||||||
prev_t := g.types[st.target] or { '' }
|
|
||||||
g.locals[st.target] = var_idx
|
|
||||||
g.types.delete(st.target)
|
|
||||||
for s in st.body {
|
|
||||||
g.gen_stmt(s)!
|
|
||||||
}
|
|
||||||
if prev >= 0 {
|
|
||||||
g.locals[st.target] = prev
|
|
||||||
} else {
|
|
||||||
g.locals.delete(st.target)
|
|
||||||
}
|
|
||||||
if prev_t.len > 0 {
|
|
||||||
g.types[st.target] = 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)
|
|
||||||
}
|
|
||||||
.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++ }
|
|
||||||
arr_idx := g.new_local()
|
|
||||||
idx_idx := g.new_local()
|
|
||||||
elem_idx := g.new_local()
|
|
||||||
loop_l := g.new_label()
|
|
||||||
inc_l := g.new_label()
|
|
||||||
end_l := g.new_label()
|
|
||||||
g.gen_expr(st.expr)!
|
|
||||||
g.emit_store(arr_idx)
|
|
||||||
g.code << op_push_i
|
|
||||||
g.code << obj.encode_i64(0)
|
|
||||||
g.emit_store(idx_idx)
|
|
||||||
g.emit_label(loop_l)
|
|
||||||
g.emit_load(idx_idx)
|
|
||||||
g.emit_load(arr_idx)
|
|
||||||
g.code << op_alen
|
|
||||||
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 }
|
|
||||||
g.emit_load(arr_idx)
|
|
||||||
g.emit_load(idx_idx)
|
|
||||||
g.code << op_aget
|
|
||||||
g.emit_store(elem_idx)
|
|
||||||
prev := g.locals[st.target] or { -1 }
|
|
||||||
prev_t := g.types[st.target] or { '' }
|
|
||||||
g.locals[st.target] = elem_idx
|
|
||||||
g.types.delete(st.target)
|
|
||||||
for s in st.body {
|
|
||||||
g.gen_stmt(s)!
|
|
||||||
}
|
|
||||||
if prev >= 0 {
|
|
||||||
g.locals[st.target] = prev
|
|
||||||
} else {
|
|
||||||
g.locals.delete(st.target)
|
|
||||||
}
|
|
||||||
if prev_t.len > 0 {
|
|
||||||
g.types[st.target] = prev_t
|
|
||||||
}
|
|
||||||
g.loops.delete_last()
|
|
||||||
g.emit_label(inc_l)
|
|
||||||
g.emit_load(idx_idx)
|
|
||||||
g.code << op_push_i
|
|
||||||
g.code << obj.encode_i64(1)
|
|
||||||
g.code << op_add
|
|
||||||
g.emit_store(idx_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)
|
|
||||||
}
|
|
||||||
.ret_stmt {
|
|
||||||
if st.has_val {
|
|
||||||
g.gen_expr(st.expr)!
|
|
||||||
g.code << op_retv
|
|
||||||
} else {
|
|
||||||
g.code << op_ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.assert_stmt {
|
|
||||||
g.gen_expr(st.expr)!
|
|
||||||
g.code << op_assert
|
|
||||||
}
|
|
||||||
.break_stmt {
|
|
||||||
if g.loops.len == 0 {
|
|
||||||
return error('break outside of a loop (line ${st.line})')
|
|
||||||
}
|
|
||||||
ctx := g.loops[g.loops.len - 1]
|
|
||||||
g.code << op_jmp
|
|
||||||
g.code << obj.encode_i64(0)
|
|
||||||
g.fixups << Fixup{ name: ctx.break_l, off: u32(g.code.len) - 8 }
|
|
||||||
}
|
|
||||||
.continue_stmt {
|
|
||||||
if g.loops.len == 0 {
|
|
||||||
return error('continue outside of a loop (line ${st.line})')
|
|
||||||
}
|
|
||||||
ctx := g.loops[g.loops.len - 1]
|
|
||||||
g.code << op_jmp
|
|
||||||
g.code << obj.encode_i64(0)
|
|
||||||
g.fixups << Fixup{ name: ctx.continue_l, off: u32(g.code.len) - 8 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut g Gen) gen_expr(e Expr) ! {
|
|
||||||
match e.kind {
|
|
||||||
.int_lit {
|
|
||||||
g.code << op_push_i
|
|
||||||
g.code << obj.encode_i64(e.int_v)
|
|
||||||
}
|
|
||||||
.str_lit {
|
|
||||||
// the index is a placeholder; the linker rebases it via a string
|
|
||||||
// relocation so multi-file links keep working
|
|
||||||
g.code << op_push_s
|
|
||||||
g.code << obj.encode_i64(0)
|
|
||||||
g.relocs << obj.Reloc{ offset: u32(g.code.len) - 8, name: e.str_v, kind: 1 }
|
|
||||||
}
|
|
||||||
.array_lit {
|
|
||||||
for el in e.elems {
|
|
||||||
g.gen_expr(el)!
|
|
||||||
}
|
|
||||||
g.code << op_mkarray
|
|
||||||
g.code << obj.encode_i64(i64(e.elems.len))
|
|
||||||
}
|
|
||||||
.struct_lit {
|
|
||||||
// typed literals validate their fields against the declaration
|
|
||||||
// (an undeclared type name is allowed — it may live in another
|
|
||||||
// file, where the same validation applies)
|
|
||||||
if e.name.len > 0 && e.name in g.structs {
|
|
||||||
decl_fields := g.structs[e.name]
|
|
||||||
mut seen := map[string]bool{}
|
|
||||||
for f in e.fields {
|
|
||||||
if f.name !in decl_fields {
|
|
||||||
return error('unknown field "${f.name}" for struct ${e.name} (line ${e.line})')
|
|
||||||
}
|
|
||||||
if f.name in seen {
|
|
||||||
return error('duplicate field "${f.name}" in struct literal (line ${e.line})')
|
|
||||||
}
|
|
||||||
seen[f.name] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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 {
|
|
||||||
// check if it's an enum variant (e.g., Color.red)
|
|
||||||
if e.left.kind == .ident {
|
|
||||||
key := '${e.left.name}.${e.name}'
|
|
||||||
if key in g.enum_vals {
|
|
||||||
g.code << op_push_i
|
|
||||||
g.code << obj.encode_i64(i64(g.enum_vals[key]))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g.gen_expr(*e.left)!
|
|
||||||
g.emit_field_name(e.name)
|
|
||||||
g.code << op_sget
|
|
||||||
}
|
|
||||||
.method_call {
|
|
||||||
// p.dist(x) → call <Type>.dist p, x
|
|
||||||
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)!
|
|
||||||
for a in e.args {
|
|
||||||
g.gen_expr(a)!
|
|
||||||
}
|
|
||||||
g.code << op_call
|
|
||||||
g.code << obj.encode_i64(0) // placeholder — patched by the linker
|
|
||||||
g.relocs << obj.Reloc{ offset: u32(g.code.len) - 8, name: '${recv_t}.${e.name}', kind: 0 }
|
|
||||||
g.code << obj.encode_i64(i64(e.args.len + 1)) // receiver + args
|
|
||||||
}
|
|
||||||
.index {
|
|
||||||
// if the index is a string literal, use struct field access (map style)
|
|
||||||
if e.right.kind == .str_lit {
|
|
||||||
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 {
|
|
||||||
g.code << op_push_i
|
|
||||||
g.code << obj.encode_i64(e.int_v)
|
|
||||||
}
|
|
||||||
.ident {
|
|
||||||
// check if it's a constant
|
|
||||||
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 << 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}')
|
|
||||||
}
|
|
||||||
g.code << op_load
|
|
||||||
g.code << obj.encode_i64(i64(idx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.unary {
|
|
||||||
g.gen_expr(*e.right)!
|
|
||||||
if e.op == .kw_not {
|
|
||||||
g.code << op_not
|
|
||||||
} else {
|
|
||||||
g.code << op_neg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.binary {
|
|
||||||
g.gen_binary(e)!
|
|
||||||
}
|
|
||||||
.call {
|
|
||||||
g.gen_call(e)!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut g Gen) gen_call(e Expr) ! {
|
|
||||||
if e.name == 'print' || e.name == 'println' {
|
|
||||||
if e.args.len != 1 {
|
|
||||||
return error('${e.name}() takes exactly one argument (line ${e.line})')
|
|
||||||
}
|
|
||||||
g.gen_expr(e.args[0])!
|
|
||||||
g.code << if e.name == 'print' { op_print } else { op_println }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if e.name == 'len' {
|
|
||||||
if e.args.len != 1 {
|
|
||||||
return error('len() takes exactly one argument (line ${e.line})')
|
|
||||||
}
|
|
||||||
g.gen_expr(e.args[0])!
|
|
||||||
g.code << op_alen
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if e.name == 'push' {
|
|
||||||
if e.args.len != 2 {
|
|
||||||
return error('push() takes exactly two arguments (line ${e.line})')
|
|
||||||
}
|
|
||||||
g.gen_expr(e.args[0])!
|
|
||||||
g.gen_expr(e.args[1])!
|
|
||||||
g.code << op_apush
|
|
||||||
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 {
|
|
||||||
g.gen_expr(a)!
|
|
||||||
}
|
|
||||||
g.code << op_call
|
|
||||||
g.code << obj.encode_i64(0) // placeholder — patched by the linker
|
|
||||||
g.relocs << obj.Reloc{ offset: u32(g.code.len) - 8, name: e.name, kind: 0 }
|
|
||||||
g.code << obj.encode_i64(i64(e.args.len)) // argc
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut g Gen) gen_binary(e Expr) ! {
|
|
||||||
match e.op {
|
|
||||||
.kw_and {
|
|
||||||
// a and b → short-circuit: if !a or !b then 0 else 1
|
|
||||||
false_l := g.new_label()
|
|
||||||
end_l := g.new_label()
|
|
||||||
g.gen_expr(*e.left)!
|
|
||||||
g.code << op_jz
|
|
||||||
g.code << obj.encode_i64(0)
|
|
||||||
g.fixups << Fixup{ name: false_l, off: u32(g.code.len) - 8 }
|
|
||||||
g.gen_expr(*e.right)!
|
|
||||||
g.code << op_jz
|
|
||||||
g.code << obj.encode_i64(0)
|
|
||||||
g.fixups << Fixup{ name: false_l, off: u32(g.code.len) - 8 }
|
|
||||||
g.code << op_push_i
|
|
||||||
g.code << obj.encode_i64(1)
|
|
||||||
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(false_l)
|
|
||||||
g.code << op_push_i
|
|
||||||
g.code << obj.encode_i64(0)
|
|
||||||
g.emit_label(end_l)
|
|
||||||
}
|
|
||||||
.kw_or {
|
|
||||||
// a or b → short-circuit: if a or b then 1 else 0
|
|
||||||
true_l := g.new_label()
|
|
||||||
end_l := g.new_label()
|
|
||||||
g.gen_expr(*e.left)!
|
|
||||||
g.code << op_jnz
|
|
||||||
g.code << obj.encode_i64(0)
|
|
||||||
g.fixups << Fixup{ name: true_l, off: u32(g.code.len) - 8 }
|
|
||||||
g.gen_expr(*e.right)!
|
|
||||||
g.code << op_jnz
|
|
||||||
g.code << obj.encode_i64(0)
|
|
||||||
g.fixups << Fixup{ name: true_l, off: u32(g.code.len) - 8 }
|
|
||||||
g.code << op_push_i
|
|
||||||
g.code << obj.encode_i64(0)
|
|
||||||
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(true_l)
|
|
||||||
g.code << op_push_i
|
|
||||||
g.code << obj.encode_i64(1)
|
|
||||||
g.emit_label(end_l)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
g.gen_expr(*e.left)!
|
|
||||||
g.gen_expr(*e.right)!
|
|
||||||
op := match e.op {
|
|
||||||
.plus { op_add }
|
|
||||||
.minus { op_sub }
|
|
||||||
.star { op_mul }
|
|
||||||
.slash { op_div }
|
|
||||||
.percent { op_mod }
|
|
||||||
.eq_eq { op_eq }
|
|
||||||
.not_eq { op_ne }
|
|
||||||
.lt { op_lt }
|
|
||||||
.le { op_le }
|
|
||||||
.gt { op_gt }
|
|
||||||
.ge { op_ge }
|
|
||||||
else {
|
|
||||||
return error('unsupported binary operator at line ${e.line}')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g.code << op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// expr_type returns the declared struct type of an expression when it is
|
|
||||||
// statically knowable: a typed literal `Point{...}`, a copy of a typed
|
|
||||||
// variable, or an enum variant `Enum.variant`. Everything else has no
|
|
||||||
// known type ('').
|
|
||||||
fn (mut g Gen) expr_type(e Expr) string {
|
|
||||||
if e.kind == .struct_lit {
|
|
||||||
return e.name
|
|
||||||
}
|
|
||||||
if e.kind == .ident {
|
|
||||||
return g.types[e.name] or { '' }
|
|
||||||
}
|
|
||||||
// enum variant: Color.red → type is "Color"
|
|
||||||
if e.kind == .field && e.left.kind == .ident {
|
|
||||||
key := '${e.left.name}.${e.name}'
|
|
||||||
if key in g.enum_vals {
|
|
||||||
return e.left.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// (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 {
|
|
||||||
recv := e.left
|
|
||||||
if recv.kind == .ident {
|
|
||||||
t := g.types[recv.name] or { '' }
|
|
||||||
if t.len > 0 {
|
|
||||||
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})')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// 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]
|
|
||||||
}
|
|
||||||
idx := g.strings.len
|
|
||||||
g.strings << s
|
|
||||||
g.str_map[s] = idx
|
|
||||||
return idx
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut g Gen) new_local() int {
|
|
||||||
idx := g.local_cnt
|
|
||||||
g.local_cnt++
|
|
||||||
return idx
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut g Gen) emit_load(idx int) {
|
|
||||||
g.code << op_load
|
|
||||||
g.code << obj.encode_i64(i64(idx))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut g Gen) emit_store(idx int) {
|
|
||||||
g.code << op_store
|
|
||||||
g.code << obj.encode_i64(i64(idx))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn (mut g Gen) new_label() string {
|
|
||||||
g.next_lbl++
|
|
||||||
return 'L${g.next_lbl}'
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut g Gen) emit_label(name string) {
|
|
||||||
g.labels[name] = g.code.len
|
|
||||||
}
|
|
||||||
|
|||||||
+3
-67
@@ -1,70 +1,6 @@
|
|||||||
// lexer.v — tokenizer for the VuurRaaf source language (.vr).
|
// lexer.v — tokenizer for the VuurRaaf source language (.vr).
|
||||||
module compiler
|
module compiler
|
||||||
|
|
||||||
pub enum TokKind {
|
|
||||||
eof
|
|
||||||
ident
|
|
||||||
int_lit
|
|
||||||
str_lit
|
|
||||||
lparen
|
|
||||||
rparen
|
|
||||||
lbrace
|
|
||||||
rbrace
|
|
||||||
lbracket
|
|
||||||
rbracket
|
|
||||||
comma
|
|
||||||
dot
|
|
||||||
colon
|
|
||||||
plus
|
|
||||||
minus
|
|
||||||
star
|
|
||||||
slash
|
|
||||||
percent
|
|
||||||
eq_eq
|
|
||||||
not_eq
|
|
||||||
lt
|
|
||||||
le
|
|
||||||
gt
|
|
||||||
ge
|
|
||||||
assign
|
|
||||||
plus_eq
|
|
||||||
minus_eq
|
|
||||||
star_eq
|
|
||||||
slash_eq
|
|
||||||
dotdot
|
|
||||||
dotdotdot
|
|
||||||
kw_fn
|
|
||||||
kw_struct
|
|
||||||
kw_let
|
|
||||||
kw_if
|
|
||||||
kw_else
|
|
||||||
kw_while
|
|
||||||
kw_for
|
|
||||||
kw_in
|
|
||||||
kw_match
|
|
||||||
kw_break
|
|
||||||
kw_continue
|
|
||||||
kw_return
|
|
||||||
kw_true
|
|
||||||
kw_false
|
|
||||||
kw_and
|
|
||||||
kw_or
|
|
||||||
kw_not
|
|
||||||
kw_print
|
|
||||||
kw_println
|
|
||||||
kw_assert
|
|
||||||
kw_import
|
|
||||||
kw_enum
|
|
||||||
kw_const
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Tok {
|
|
||||||
pub:
|
|
||||||
kind TokKind
|
|
||||||
lit string
|
|
||||||
line int
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tokenize(src string) ![]Tok {
|
pub fn tokenize(src string) ![]Tok {
|
||||||
mut l := Lexer{ src: src }
|
mut l := Lexer{ src: src }
|
||||||
mut toks := []Tok{}
|
mut toks := []Tok{}
|
||||||
@@ -242,7 +178,7 @@ fn (mut l Lexer) next() !Tok {
|
|||||||
}
|
}
|
||||||
return Tok{ kind: .gt, lit: '>', line: line }
|
return Tok{ kind: .gt, lit: '>', line: line }
|
||||||
}
|
}
|
||||||
`"` {
|
`\"` {
|
||||||
return l.lex_string(line)!
|
return l.lex_string(line)!
|
||||||
}
|
}
|
||||||
`0`...`9` {
|
`0`...`9` {
|
||||||
@@ -310,7 +246,7 @@ fn (mut l Lexer) lex_string(line int) !Tok {
|
|||||||
mut s := ''
|
mut s := ''
|
||||||
for l.pos < l.src.len {
|
for l.pos < l.src.len {
|
||||||
c := l.advance()
|
c := l.advance()
|
||||||
if c == `"` {
|
if c == `\"` {
|
||||||
return Tok{ kind: .str_lit, lit: s, line: line }
|
return Tok{ kind: .str_lit, lit: s, line: line }
|
||||||
}
|
}
|
||||||
if c == `\\` {
|
if c == `\\` {
|
||||||
@@ -325,7 +261,7 @@ fn (mut l Lexer) lex_string(line int) !Tok {
|
|||||||
`t` {
|
`t` {
|
||||||
s += '\t'
|
s += '\t'
|
||||||
}
|
}
|
||||||
`"` {
|
`\"` {
|
||||||
s += '"'
|
s += '"'
|
||||||
}
|
}
|
||||||
`\\` {
|
`\\` {
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// opcodes.v — bytecode opcodes for the VuurRaaf compiler.
|
||||||
|
//
|
||||||
|
// Keep in sync with vm/vm.v and assembler/assembler.v.
|
||||||
|
module compiler
|
||||||
|
|
||||||
|
const op_halt = u8(0)
|
||||||
|
const op_push_i = u8(1)
|
||||||
|
const op_push_s = u8(2)
|
||||||
|
const op_load = u8(3)
|
||||||
|
const op_store = u8(4)
|
||||||
|
const op_pop = u8(5)
|
||||||
|
const op_dup = u8(6)
|
||||||
|
const op_add = u8(7)
|
||||||
|
const op_sub = u8(8)
|
||||||
|
const op_mul = u8(9)
|
||||||
|
const op_div = u8(10)
|
||||||
|
const op_mod = u8(11)
|
||||||
|
const op_neg = u8(12)
|
||||||
|
const op_eq = u8(13)
|
||||||
|
const op_ne = u8(14)
|
||||||
|
const op_lt = u8(15)
|
||||||
|
const op_le = u8(16)
|
||||||
|
const op_gt = u8(17)
|
||||||
|
const op_ge = u8(18)
|
||||||
|
const op_and = u8(19)
|
||||||
|
const op_or = u8(20)
|
||||||
|
const op_not = u8(21)
|
||||||
|
const op_jmp = u8(22)
|
||||||
|
const op_jz = u8(23)
|
||||||
|
const op_jnz = u8(24)
|
||||||
|
const op_call = u8(25)
|
||||||
|
const op_ret = u8(26)
|
||||||
|
const op_retv = u8(27)
|
||||||
|
const op_print = u8(28)
|
||||||
|
const op_println = u8(29)
|
||||||
|
const op_assert = u8(30)
|
||||||
|
const op_enter = u8(31)
|
||||||
|
const op_mkarray = u8(32)
|
||||||
|
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 op_shas = u8(40)
|
||||||
|
const op_sdel = u8(41)
|
||||||
|
const op_slen = u8(42)
|
||||||
|
const op_skeys = u8(43)
|
||||||
@@ -35,134 +35,6 @@
|
|||||||
// ('.' IDENT '(' ... ')' is a method call; everything else field access)
|
// ('.' IDENT '(' ... ')' is a method call; everything else field access)
|
||||||
module compiler
|
module compiler
|
||||||
|
|
||||||
pub enum ExprKind {
|
|
||||||
int_lit
|
|
||||||
str_lit
|
|
||||||
bool_lit
|
|
||||||
ident
|
|
||||||
array_lit
|
|
||||||
struct_lit
|
|
||||||
index
|
|
||||||
field
|
|
||||||
method_call
|
|
||||||
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 // 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 {
|
|
||||||
expr_stmt
|
|
||||||
let_stmt
|
|
||||||
assign_stmt
|
|
||||||
index_assign
|
|
||||||
field_assign
|
|
||||||
if_stmt
|
|
||||||
match_stmt
|
|
||||||
while_stmt
|
|
||||||
for_range_stmt
|
|
||||||
for_in_stmt
|
|
||||||
break_stmt
|
|
||||||
continue_stmt
|
|
||||||
ret_stmt
|
|
||||||
assert_stmt
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchArm is a single `value { body }` arm of a match statement.
|
|
||||||
pub struct MatchArm {
|
|
||||||
pub mut:
|
|
||||||
val Expr
|
|
||||||
body []Stmt
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Stmt {
|
|
||||||
pub mut:
|
|
||||||
kind StmtKind
|
|
||||||
target string
|
|
||||||
expr Expr
|
|
||||||
cond Expr
|
|
||||||
base Expr // index_assign: the indexed expression
|
|
||||||
idx Expr // index_assign: the index expression
|
|
||||||
body []Stmt
|
|
||||||
els []Stmt
|
|
||||||
arms []MatchArm // match_stmt: the arms (val + body)
|
|
||||||
has_else bool // match_stmt: a trailing else arm exists
|
|
||||||
els_body []Stmt // match_stmt: body of the else arm
|
|
||||||
has_val bool
|
|
||||||
inclusive bool // for_range_stmt: `..` (false) vs `...` (true)
|
|
||||||
line int
|
|
||||||
}
|
|
||||||
|
|
||||||
// StructDecl is a `struct Name { a, b }` declaration.
|
|
||||||
pub struct StructDecl {
|
|
||||||
pub mut:
|
|
||||||
name string
|
|
||||||
fields []string
|
|
||||||
line int
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FnDecl {
|
|
||||||
pub mut:
|
|
||||||
name string
|
|
||||||
recv_name string // method receiver local name ('' for plain functions)
|
|
||||||
recv_type string // method receiver struct type ('' for plain functions)
|
|
||||||
params []string
|
|
||||||
body []Stmt
|
|
||||||
line int
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ImportDecl {
|
|
||||||
pub mut:
|
|
||||||
path string
|
|
||||||
line int
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnumDecl is an `enum Name { variant1 variant2 ... }` declaration.
|
|
||||||
pub struct EnumDecl {
|
|
||||||
pub mut:
|
|
||||||
name string
|
|
||||||
variants []string
|
|
||||||
line int
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConstDecl is a `const NAME = value` declaration.
|
|
||||||
pub struct ConstDecl {
|
|
||||||
pub mut:
|
|
||||||
name string
|
|
||||||
value Expr
|
|
||||||
line int
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Program {
|
|
||||||
pub mut:
|
|
||||||
fns []FnDecl
|
|
||||||
structs []StructDecl
|
|
||||||
enums []EnumDecl
|
|
||||||
imports []ImportDecl
|
|
||||||
consts []ConstDecl
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse(toks []Tok) !Program {
|
pub fn parse(toks []Tok) !Program {
|
||||||
mut p := Parser{ toks: toks }
|
mut p := Parser{ toks: toks }
|
||||||
return p.parse_program()
|
return p.parse_program()
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// tokens.v — token types for the VuurRaaf lexer.
|
||||||
|
module compiler
|
||||||
|
|
||||||
|
pub enum TokKind {
|
||||||
|
eof
|
||||||
|
ident
|
||||||
|
int_lit
|
||||||
|
str_lit
|
||||||
|
lparen
|
||||||
|
rparen
|
||||||
|
lbrace
|
||||||
|
rbrace
|
||||||
|
lbracket
|
||||||
|
rbracket
|
||||||
|
comma
|
||||||
|
dot
|
||||||
|
colon
|
||||||
|
plus
|
||||||
|
minus
|
||||||
|
star
|
||||||
|
slash
|
||||||
|
percent
|
||||||
|
eq_eq
|
||||||
|
not_eq
|
||||||
|
lt
|
||||||
|
le
|
||||||
|
gt
|
||||||
|
ge
|
||||||
|
assign
|
||||||
|
plus_eq
|
||||||
|
minus_eq
|
||||||
|
star_eq
|
||||||
|
slash_eq
|
||||||
|
dotdot
|
||||||
|
dotdotdot
|
||||||
|
kw_fn
|
||||||
|
kw_struct
|
||||||
|
kw_let
|
||||||
|
kw_if
|
||||||
|
kw_else
|
||||||
|
kw_while
|
||||||
|
kw_for
|
||||||
|
kw_in
|
||||||
|
kw_match
|
||||||
|
kw_break
|
||||||
|
kw_continue
|
||||||
|
kw_return
|
||||||
|
kw_true
|
||||||
|
kw_false
|
||||||
|
kw_and
|
||||||
|
kw_or
|
||||||
|
kw_not
|
||||||
|
kw_print
|
||||||
|
kw_println
|
||||||
|
kw_assert
|
||||||
|
kw_import
|
||||||
|
kw_enum
|
||||||
|
kw_const
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Tok {
|
||||||
|
pub:
|
||||||
|
kind TokKind
|
||||||
|
lit string
|
||||||
|
line int
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// opcodes.v — bytecode opcodes for the VuurRaaf VM.
|
||||||
|
//
|
||||||
|
// Keep in sync with the compiler and assembler.
|
||||||
|
module vm
|
||||||
|
|
||||||
|
const op_halt = u8(0)
|
||||||
|
const op_push_i = u8(1)
|
||||||
|
const op_push_s = u8(2)
|
||||||
|
const op_load = u8(3)
|
||||||
|
const op_store = u8(4)
|
||||||
|
const op_pop = u8(5)
|
||||||
|
const op_dup = u8(6)
|
||||||
|
const op_add = u8(7)
|
||||||
|
const op_sub = u8(8)
|
||||||
|
const op_mul = u8(9)
|
||||||
|
const op_div = u8(10)
|
||||||
|
const op_mod = u8(11)
|
||||||
|
const op_neg = u8(12)
|
||||||
|
const op_eq = u8(13)
|
||||||
|
const op_ne = u8(14)
|
||||||
|
const op_lt = u8(15)
|
||||||
|
const op_le = u8(16)
|
||||||
|
const op_gt = u8(17)
|
||||||
|
const op_ge = u8(18)
|
||||||
|
const op_and = u8(19)
|
||||||
|
const op_or = u8(20)
|
||||||
|
const op_not = u8(21)
|
||||||
|
const op_jmp = u8(22)
|
||||||
|
const op_jz = u8(23)
|
||||||
|
const op_jnz = u8(24)
|
||||||
|
const op_call = u8(25)
|
||||||
|
const op_ret = u8(26)
|
||||||
|
const op_retv = u8(27)
|
||||||
|
const op_print = u8(28)
|
||||||
|
const op_println = u8(29)
|
||||||
|
const op_assert = u8(30)
|
||||||
|
const op_enter = u8(31)
|
||||||
|
const op_mkarray = u8(32)
|
||||||
|
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 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
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
// ops.v — arithmetic, comparison, and string operations for the VuurRaaf VM.
|
||||||
|
module vm
|
||||||
|
|
||||||
|
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)])
|
||||||
|
}
|
||||||
|
if v.is_str(a) {
|
||||||
|
return v.alloc_str(v.strings[v.hand(a)] + v.num_str(b))
|
||||||
|
}
|
||||||
|
if v.is_str(b) {
|
||||||
|
return v.alloc_str(v.num_str(a) + v.strings[v.hand(b)])
|
||||||
|
}
|
||||||
|
return v.enc_int(v.dec_int(a) + v.dec_int(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) alloc_str(s string) i64 {
|
||||||
|
v.strings << s
|
||||||
|
return v.mkstr(v.strings.len - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) num_str(x i64) string {
|
||||||
|
return v.dec_int(x).str()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) arith(a i64, b i64, op string) !i64 {
|
||||||
|
if v.is_str(a) || v.is_str(b) {
|
||||||
|
return error('cannot use strings with "${op}"')
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
'-' {
|
||||||
|
return v.enc_int(x - y)
|
||||||
|
}
|
||||||
|
'*' {
|
||||||
|
return v.enc_int(x * y)
|
||||||
|
}
|
||||||
|
'/' {
|
||||||
|
if y == 0 {
|
||||||
|
return error('division by zero')
|
||||||
|
}
|
||||||
|
return v.enc_int(x / y)
|
||||||
|
}
|
||||||
|
'%' {
|
||||||
|
if y == 0 {
|
||||||
|
return error('division by zero')
|
||||||
|
}
|
||||||
|
return v.enc_int(x % y)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return error('internal: bad arith op "${op}"')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) cmp(a i64, b i64, op string) !i64 {
|
||||||
|
if v.is_arr(a) || v.is_arr(b) {
|
||||||
|
// arrays compare by identity (handle equality) with ==/!=
|
||||||
|
if op == '==' || op == '!=' {
|
||||||
|
return bool_i64(if op == '==' { a == b } else { a != b })
|
||||||
|
}
|
||||||
|
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)]
|
||||||
|
return bool_i64(match op {
|
||||||
|
'==' { sa == sb }
|
||||||
|
'!=' { sa != sb }
|
||||||
|
'<' { sa < sb }
|
||||||
|
'<=' { sa <= sb }
|
||||||
|
'>' { sa > sb }
|
||||||
|
'>=' { sa >= sb }
|
||||||
|
else { return error('internal: bad cmp op "${op}"') }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if v.is_str(a) || v.is_str(b) {
|
||||||
|
return error('cannot compare a string and a number')
|
||||||
|
}
|
||||||
|
x := v.dec_int(a)
|
||||||
|
y := v.dec_int(b)
|
||||||
|
return bool_i64(match op {
|
||||||
|
'==' { x == y }
|
||||||
|
'!=' { x != y }
|
||||||
|
'<' { x < y }
|
||||||
|
'<=' { x <= y }
|
||||||
|
'>' { x > y }
|
||||||
|
'>=' { x >= y }
|
||||||
|
else { return error('internal: bad cmp op "${op}"') }
|
||||||
|
})
|
||||||
|
}
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
// print.v — value rendering and instruction tracing for the VuurRaaf VM.
|
||||||
|
module vm
|
||||||
|
|
||||||
|
fn (mut v Vm) print_val(x i64) {
|
||||||
|
print(v.val_str(x, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
// val_str renders a value: strings as-is, arrays as [a, b, ...] (with a depth
|
||||||
|
// guard so self-referential arrays cannot hang the printer), numbers as ints.
|
||||||
|
fn (mut v Vm) val_str(x i64, depth int) string {
|
||||||
|
if depth > 16 {
|
||||||
|
return '...'
|
||||||
|
}
|
||||||
|
if v.is_str(x) && v.valid_handle(x) {
|
||||||
|
return v.strings[v.hand(x)]
|
||||||
|
}
|
||||||
|
if v.is_arr(x) && v.valid_arr_handle(x) {
|
||||||
|
a := v.arrays[v.hand(x)]
|
||||||
|
mut s := '['
|
||||||
|
limit := if a.len > 20 { 20 } else { a.len }
|
||||||
|
for i in 0..limit {
|
||||||
|
if i > 0 {
|
||||||
|
s += ', '
|
||||||
|
}
|
||||||
|
s += v.val_str(a[i], depth + 1)
|
||||||
|
}
|
||||||
|
if a.len > limit {
|
||||||
|
s += ', ...'
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) trace_op(op u8) {
|
||||||
|
name := match op {
|
||||||
|
op_halt { 'halt' }
|
||||||
|
op_push_i { 'push_int' }
|
||||||
|
op_push_s { 'push_str' }
|
||||||
|
op_load { 'load' }
|
||||||
|
op_store { 'store' }
|
||||||
|
op_pop { 'pop' }
|
||||||
|
op_dup { 'dup' }
|
||||||
|
op_add { 'add' }
|
||||||
|
op_sub { 'sub' }
|
||||||
|
op_mul { 'mul' }
|
||||||
|
op_div { 'div' }
|
||||||
|
op_mod { 'mod' }
|
||||||
|
op_neg { 'neg' }
|
||||||
|
op_eq { 'eq' }
|
||||||
|
op_ne { 'ne' }
|
||||||
|
op_lt { 'lt' }
|
||||||
|
op_le { 'le' }
|
||||||
|
op_gt { 'gt' }
|
||||||
|
op_ge { 'ge' }
|
||||||
|
op_and { 'and' }
|
||||||
|
op_or { 'or' }
|
||||||
|
op_not { 'not' }
|
||||||
|
op_jmp { 'jmp' }
|
||||||
|
op_jz { 'jz' }
|
||||||
|
op_jnz { 'jnz' }
|
||||||
|
op_call { 'call' }
|
||||||
|
op_ret { 'ret' }
|
||||||
|
op_retv { 'retv' }
|
||||||
|
op_print { 'print' }
|
||||||
|
op_println { 'println' }
|
||||||
|
op_assert { 'assert' }
|
||||||
|
op_enter { 'enter' }
|
||||||
|
op_mkarray { 'mkarray' }
|
||||||
|
op_aget { 'aget' }
|
||||||
|
op_aset { 'aset' }
|
||||||
|
op_alen { 'alen' }
|
||||||
|
op_apush { 'apush' }
|
||||||
|
op_mkstruct { 'mkstruct' }
|
||||||
|
op_sget { 'sget' }
|
||||||
|
op_sset { 'sset' }
|
||||||
|
op_shas { 'shas' }
|
||||||
|
op_sdel { 'sdel' }
|
||||||
|
op_slen { 'slen' }
|
||||||
|
op_skeys { 'skeys' }
|
||||||
|
else { '??' }
|
||||||
|
}
|
||||||
|
mut s := ''
|
||||||
|
for i in 0..v.sp {
|
||||||
|
if i > 0 {
|
||||||
|
s += ' '
|
||||||
|
}
|
||||||
|
if v.is_str(v.stack[i]) && v.valid_handle(v.stack[i]) {
|
||||||
|
s += '"${v.strings[v.hand(v.stack[i])]}"'
|
||||||
|
} else {
|
||||||
|
s += v.val_str(v.stack[i], 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println(' [ip=${v.ip:4}] ${name:-9} stack: [${s}]')
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
// types.v — core types and constants for the VuurRaaf VM.
|
||||||
|
module vm
|
||||||
|
|
||||||
|
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
|
||||||
|
ip int
|
||||||
|
trace bool
|
||||||
|
halted bool
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bool_i64(b bool) i64 {
|
||||||
|
return if b { i64(1) } else { i64(0) }
|
||||||
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
// value.v — tagged value encoding and helpers for the VuurRaaf VM.
|
||||||
|
//
|
||||||
|
// 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)
|
||||||
|
module vm
|
||||||
|
|
||||||
|
fn (mut v Vm) is_str(x i64) bool {
|
||||||
|
return x & 3 == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
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 u64(x) << 2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) dec_int(x i64) i64 {
|
||||||
|
return x >> 2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) hand(x i64) int {
|
||||||
|
return int(x >> 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) mkstr(idx int) i64 {
|
||||||
|
return (u64(idx) << 2) | 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) mkarr(idx int) i64 {
|
||||||
|
return (u64(idx) << 2) | 3
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) mkstruct_handle(idx int) i64 {
|
||||||
|
return (u64(idx) << 2) | 2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) truthy(x i64) bool {
|
||||||
|
return x != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) valid_handle(x i64) bool {
|
||||||
|
h := v.hand(x)
|
||||||
|
return h >= 0 && h < v.strings.len
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut v Vm) valid_arr_handle(x i64) bool {
|
||||||
|
h := v.hand(x)
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -1,13 +1,5 @@
|
|||||||
// vm.v — the VuurRaaf runtime: a small stack-based virtual machine.
|
// vm.v — the VuurRaaf runtime: a small stack-based virtual machine.
|
||||||
//
|
//
|
||||||
// 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, struct, or array handle.
|
|
||||||
//
|
|
||||||
// Call convention: CALL pushes a frame (retaddr, old bp, argc) and copies the
|
// 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
|
// arguments into the callee's local slots; the callee reserves extra locals
|
||||||
// with `enter n` and cleans up with `ret`/`retv`.
|
// with `enter n` and cleans up with `ret`/`retv`.
|
||||||
@@ -15,80 +7,6 @@ module vm
|
|||||||
|
|
||||||
import obj
|
import obj
|
||||||
|
|
||||||
// opcodes — keep in sync with the compiler, assembler, and this interpreter
|
|
||||||
const op_halt = u8(0)
|
|
||||||
const op_push_i = u8(1)
|
|
||||||
const op_push_s = u8(2)
|
|
||||||
const op_load = u8(3)
|
|
||||||
const op_store = u8(4)
|
|
||||||
const op_pop = u8(5)
|
|
||||||
const op_dup = u8(6)
|
|
||||||
const op_add = u8(7)
|
|
||||||
const op_sub = u8(8)
|
|
||||||
const op_mul = u8(9)
|
|
||||||
const op_div = u8(10)
|
|
||||||
const op_mod = u8(11)
|
|
||||||
const op_neg = u8(12)
|
|
||||||
const op_eq = u8(13)
|
|
||||||
const op_ne = u8(14)
|
|
||||||
const op_lt = u8(15)
|
|
||||||
const op_le = u8(16)
|
|
||||||
const op_gt = u8(17)
|
|
||||||
const op_ge = u8(18)
|
|
||||||
const op_and = u8(19)
|
|
||||||
const op_or = u8(20)
|
|
||||||
const op_not = u8(21)
|
|
||||||
const op_jmp = u8(22)
|
|
||||||
const op_jz = u8(23)
|
|
||||||
const op_jnz = u8(24)
|
|
||||||
const op_call = u8(25)
|
|
||||||
const op_ret = u8(26)
|
|
||||||
const op_retv = u8(27)
|
|
||||||
const op_print = u8(28)
|
|
||||||
const op_println = u8(29)
|
|
||||||
const op_assert = u8(30)
|
|
||||||
const op_enter = u8(31)
|
|
||||||
const op_mkarray = u8(32)
|
|
||||||
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 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
|
|
||||||
|
|
||||||
// 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
|
|
||||||
ip int
|
|
||||||
trace bool
|
|
||||||
halted bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// run executes the function named `entry` from the executable `bin` and
|
// run executes the function named `entry` from the executable `bin` and
|
||||||
// returns its return value (0 if it never returns one).
|
// returns its return value (0 if it never returns one).
|
||||||
pub fn run(bin obj.Bin, entry string, trace bool) !i64 {
|
pub fn run(bin obj.Bin, entry string, trace bool) !i64 {
|
||||||
@@ -595,279 +513,3 @@ fn (mut v Vm) ret(with_val bool) ! {
|
|||||||
v.ip = ip
|
v.ip = ip
|
||||||
v.push(retval)!
|
v.push(retval)!
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut v Vm) is_str(x i64) bool {
|
|
||||||
return x & 3 == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
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 u64(x) << 2
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut v Vm) dec_int(x i64) i64 {
|
|
||||||
return x >> 2
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut v Vm) hand(x i64) int {
|
|
||||||
return int(x >> 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut v Vm) mkstr(idx int) i64 {
|
|
||||||
return (u64(idx) << 2) | 1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut v Vm) mkarr(idx int) i64 {
|
|
||||||
return (u64(idx) << 2) | 3
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut v Vm) mkstruct_handle(idx int) i64 {
|
|
||||||
return (u64(idx) << 2) | 2
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut v Vm) truthy(x i64) bool {
|
|
||||||
return x != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bool_i64(b bool) i64 {
|
|
||||||
return if b { i64(1) } else { i64(0) }
|
|
||||||
}
|
|
||||||
|
|
||||||
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)])
|
|
||||||
}
|
|
||||||
if v.is_str(a) {
|
|
||||||
return v.alloc_str(v.strings[v.hand(a)] + v.num_str(b))
|
|
||||||
}
|
|
||||||
if v.is_str(b) {
|
|
||||||
return v.alloc_str(v.num_str(a) + v.strings[v.hand(b)])
|
|
||||||
}
|
|
||||||
return v.enc_int(v.dec_int(a) + v.dec_int(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut v Vm) alloc_str(s string) i64 {
|
|
||||||
v.strings << s
|
|
||||||
return v.mkstr(v.strings.len - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut v Vm) num_str(x i64) string {
|
|
||||||
return v.dec_int(x).str()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut v Vm) arith(a i64, b i64, op string) !i64 {
|
|
||||||
if v.is_str(a) || v.is_str(b) {
|
|
||||||
return error('cannot use strings with "${op}"')
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
'-' {
|
|
||||||
return v.enc_int(x - y)
|
|
||||||
}
|
|
||||||
'*' {
|
|
||||||
return v.enc_int(x * y)
|
|
||||||
}
|
|
||||||
'/' {
|
|
||||||
if y == 0 {
|
|
||||||
return error('division by zero')
|
|
||||||
}
|
|
||||||
return v.enc_int(x / y)
|
|
||||||
}
|
|
||||||
'%' {
|
|
||||||
if y == 0 {
|
|
||||||
return error('division by zero')
|
|
||||||
}
|
|
||||||
return v.enc_int(x % y)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return error('internal: bad arith op "${op}"')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut v Vm) cmp(a i64, b i64, op string) !i64 {
|
|
||||||
if v.is_arr(a) || v.is_arr(b) {
|
|
||||||
// arrays compare by identity (handle equality) with ==/!=
|
|
||||||
if op == '==' || op == '!=' {
|
|
||||||
return bool_i64(if op == '==' { a == b } else { a != b })
|
|
||||||
}
|
|
||||||
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)]
|
|
||||||
return bool_i64(match op {
|
|
||||||
'==' { sa == sb }
|
|
||||||
'!=' { sa != sb }
|
|
||||||
'<' { sa < sb }
|
|
||||||
'<=' { sa <= sb }
|
|
||||||
'>' { sa > sb }
|
|
||||||
'>=' { sa >= sb }
|
|
||||||
else { return error('internal: bad cmp op "${op}"') }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if v.is_str(a) || v.is_str(b) {
|
|
||||||
return error('cannot compare a string and a number')
|
|
||||||
}
|
|
||||||
x := v.dec_int(a)
|
|
||||||
y := v.dec_int(b)
|
|
||||||
return bool_i64(match op {
|
|
||||||
'==' { x == y }
|
|
||||||
'!=' { x != y }
|
|
||||||
'<' { x < y }
|
|
||||||
'<=' { x <= y }
|
|
||||||
'>' { x > y }
|
|
||||||
'>=' { x >= y }
|
|
||||||
else { return error('internal: bad cmp op "${op}"') }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut v Vm) print_val(x i64) {
|
|
||||||
print(v.val_str(x, 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
// val_str renders a value: strings as-is, arrays as [a, b, ...] (with a depth
|
|
||||||
// guard so self-referential arrays cannot hang the printer), numbers as ints.
|
|
||||||
fn (mut v Vm) val_str(x i64, depth int) string {
|
|
||||||
if depth > 16 {
|
|
||||||
return '...'
|
|
||||||
}
|
|
||||||
if v.is_str(x) && v.valid_handle(x) {
|
|
||||||
return v.strings[v.hand(x)]
|
|
||||||
}
|
|
||||||
if v.is_arr(x) && v.valid_arr_handle(x) {
|
|
||||||
a := v.arrays[v.hand(x)]
|
|
||||||
mut s := '['
|
|
||||||
limit := if a.len > 20 { 20 } else { a.len }
|
|
||||||
for i in 0..limit {
|
|
||||||
if i > 0 {
|
|
||||||
s += ', '
|
|
||||||
}
|
|
||||||
s += v.val_str(a[i], depth + 1)
|
|
||||||
}
|
|
||||||
if a.len > limit {
|
|
||||||
s += ', ...'
|
|
||||||
}
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut v Vm) valid_handle(x i64) bool {
|
|
||||||
h := v.hand(x)
|
|
||||||
return h >= 0 && h < v.strings.len
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut v Vm) valid_arr_handle(x i64) bool {
|
|
||||||
h := v.hand(x)
|
|
||||||
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' }
|
|
||||||
op_push_i { 'push_int' }
|
|
||||||
op_push_s { 'push_str' }
|
|
||||||
op_load { 'load' }
|
|
||||||
op_store { 'store' }
|
|
||||||
op_pop { 'pop' }
|
|
||||||
op_dup { 'dup' }
|
|
||||||
op_add { 'add' }
|
|
||||||
op_sub { 'sub' }
|
|
||||||
op_mul { 'mul' }
|
|
||||||
op_div { 'div' }
|
|
||||||
op_mod { 'mod' }
|
|
||||||
op_neg { 'neg' }
|
|
||||||
op_eq { 'eq' }
|
|
||||||
op_ne { 'ne' }
|
|
||||||
op_lt { 'lt' }
|
|
||||||
op_le { 'le' }
|
|
||||||
op_gt { 'gt' }
|
|
||||||
op_ge { 'ge' }
|
|
||||||
op_and { 'and' }
|
|
||||||
op_or { 'or' }
|
|
||||||
op_not { 'not' }
|
|
||||||
op_jmp { 'jmp' }
|
|
||||||
op_jz { 'jz' }
|
|
||||||
op_jnz { 'jnz' }
|
|
||||||
op_call { 'call' }
|
|
||||||
op_ret { 'ret' }
|
|
||||||
op_retv { 'retv' }
|
|
||||||
op_print { 'print' }
|
|
||||||
op_println { 'println' }
|
|
||||||
op_assert { 'assert' }
|
|
||||||
op_enter { 'enter' }
|
|
||||||
op_mkarray { 'mkarray' }
|
|
||||||
op_aget { 'aget' }
|
|
||||||
op_aset { 'aset' }
|
|
||||||
op_alen { 'alen' }
|
|
||||||
op_apush { 'apush' }
|
|
||||||
op_mkstruct { 'mkstruct' }
|
|
||||||
op_sget { 'sget' }
|
|
||||||
op_sset { 'sset' }
|
|
||||||
op_shas { 'shas' }
|
|
||||||
op_sdel { 'sdel' }
|
|
||||||
op_slen { 'slen' }
|
|
||||||
op_skeys { 'skeys' }
|
|
||||||
else { '??' }
|
|
||||||
}
|
|
||||||
mut s := ''
|
|
||||||
for i in 0..v.sp {
|
|
||||||
if i > 0 {
|
|
||||||
s += ' '
|
|
||||||
}
|
|
||||||
if v.is_str(v.stack[i]) && v.valid_handle(v.stack[i]) {
|
|
||||||
s += '"${v.strings[v.hand(v.stack[i])]}"'
|
|
||||||
} else {
|
|
||||||
s += v.val_str(v.stack[i], 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println(' [ip=${v.ip:4}] ${name:-9} stack: [${s}]')
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user