mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 14:57:18 +00:00
353 lines
7.5 KiB
V
353 lines
7.5 KiB
V
// assembler.v — assembles VuurRaaf assembly (.vasm) into VROBJ object files.
|
|
//
|
|
// Assembly syntax:
|
|
// ; comment
|
|
// .global main ; export the label as a linkable symbol
|
|
// main: ; label definition (local unless .global)
|
|
// push_int 42
|
|
// push_str "hello"
|
|
// call helper 1 ; call <symbol-or-label> <argc>
|
|
// println
|
|
// halt
|
|
//
|
|
// Jumps (jmp/jz/jnz) target local labels and are resolved here; calls to
|
|
// exported symbols become relocations resolved by the linker.
|
|
module assembler
|
|
|
|
import os
|
|
import obj
|
|
|
|
pub fn assemble(src string) !obj.Obj {
|
|
lines := src.split_into_lines()
|
|
// pass 1: collect labels, globals, and code offsets
|
|
mut labels := map[string]int{}
|
|
mut globals := map[string]bool{}
|
|
mut offset := 0
|
|
for raw in lines {
|
|
line := clean(raw)
|
|
if line.len == 0 {
|
|
continue
|
|
}
|
|
if line.starts_with('.global') {
|
|
name := line.all_after('.global').trim_space()
|
|
if name.len == 0 {
|
|
return error('`.global` needs a name')
|
|
}
|
|
globals[name] = true
|
|
continue
|
|
}
|
|
if line.ends_with(':') {
|
|
name := line[..line.len - 1].trim_space()
|
|
if name in labels {
|
|
return error('duplicate label "${name}"')
|
|
}
|
|
labels[name] = offset
|
|
continue
|
|
}
|
|
offset += instr_len(line)!
|
|
}
|
|
// pass 2: emit code
|
|
mut o := obj.Obj{}
|
|
for raw in lines {
|
|
line := clean(raw)
|
|
if line.len == 0 || line.starts_with('.global') || line.ends_with(':') {
|
|
continue
|
|
}
|
|
parts := split_line(line)!
|
|
op := parts[0]
|
|
arg := if parts.len > 1 { parts[1] } else { '' }
|
|
match op {
|
|
'halt' {
|
|
o.code << u8(0)
|
|
}
|
|
'push_int' {
|
|
val := parse_int(arg, 'push_int')!
|
|
o.code << u8(1)
|
|
o.code << obj.encode_i64(val)
|
|
}
|
|
'push_str' {
|
|
s := unquote(arg)!
|
|
o.code << u8(2)
|
|
o.code << obj.encode_i64(0) // placeholder — rebased by the linker
|
|
o.relocs << obj.Reloc{ offset: u32(o.code.len) - 8, name: s, kind: 1 }
|
|
}
|
|
'load' {
|
|
o.code << u8(3)
|
|
o.code << obj.encode_i64(parse_int(arg, 'load')!)
|
|
}
|
|
'store' {
|
|
o.code << u8(4)
|
|
o.code << obj.encode_i64(parse_int(arg, 'store')!)
|
|
}
|
|
'pop' {
|
|
o.code << u8(5)
|
|
}
|
|
'dup' {
|
|
o.code << u8(6)
|
|
}
|
|
'add' {
|
|
o.code << u8(7)
|
|
}
|
|
'sub' {
|
|
o.code << u8(8)
|
|
}
|
|
'mul' {
|
|
o.code << u8(9)
|
|
}
|
|
'div' {
|
|
o.code << u8(10)
|
|
}
|
|
'mod' {
|
|
o.code << u8(11)
|
|
}
|
|
'neg' {
|
|
o.code << u8(12)
|
|
}
|
|
'eq' {
|
|
o.code << u8(13)
|
|
}
|
|
'ne' {
|
|
o.code << u8(14)
|
|
}
|
|
'lt' {
|
|
o.code << u8(15)
|
|
}
|
|
'le' {
|
|
o.code << u8(16)
|
|
}
|
|
'gt' {
|
|
o.code << u8(17)
|
|
}
|
|
'ge' {
|
|
o.code << u8(18)
|
|
}
|
|
'and' {
|
|
o.code << u8(19)
|
|
}
|
|
'or' {
|
|
o.code << u8(20)
|
|
}
|
|
'not' {
|
|
o.code << u8(21)
|
|
}
|
|
'jmp' {
|
|
o.code << u8(22)
|
|
o.code << obj.encode_i64(i64(label(labels, arg)!))
|
|
}
|
|
'jz' {
|
|
o.code << u8(23)
|
|
o.code << obj.encode_i64(i64(label(labels, arg)!))
|
|
}
|
|
'jnz' {
|
|
o.code << u8(24)
|
|
o.code << obj.encode_i64(i64(label(labels, arg)!))
|
|
}
|
|
'call' {
|
|
o.code << u8(25)
|
|
o.code << obj.encode_i64(0) // placeholder for the target
|
|
if arg in labels && arg !in globals {
|
|
obj.patch_i64(mut o.code, u32(o.code.len) - 8, i64(labels[arg]))
|
|
} else {
|
|
// reference to an exported symbol — resolved at link time
|
|
o.relocs << obj.Reloc{ offset: u32(o.code.len) - 8, name: arg, kind: 0 }
|
|
}
|
|
argc := if parts.len > 2 { parse_int(parts[2], 'call argc')! } else { i64(0) }
|
|
o.code << obj.encode_i64(argc)
|
|
}
|
|
'ret' {
|
|
o.code << u8(26)
|
|
}
|
|
'retv' {
|
|
o.code << u8(27)
|
|
}
|
|
'print' {
|
|
o.code << u8(28)
|
|
}
|
|
'println' {
|
|
o.code << u8(29)
|
|
}
|
|
'assert' {
|
|
o.code << u8(30)
|
|
}
|
|
'enter' {
|
|
o.code << u8(31)
|
|
o.code << obj.encode_i64(parse_int(arg, 'enter')!)
|
|
}
|
|
'mkarray' {
|
|
o.code << u8(32)
|
|
o.code << obj.encode_i64(parse_int(arg, 'mkarray')!)
|
|
}
|
|
'aget' {
|
|
o.code << u8(33)
|
|
}
|
|
'aset' {
|
|
o.code << u8(34)
|
|
}
|
|
'alen' {
|
|
o.code << u8(35)
|
|
}
|
|
'apush' {
|
|
o.code << u8(36)
|
|
}
|
|
'mkstruct' {
|
|
o.code << u8(37)
|
|
o.code << obj.encode_i64(parse_int(arg, 'mkstruct')!)
|
|
}
|
|
'sget' {
|
|
// sget "name" → push_str "name"; sget
|
|
s := unquote(arg)!
|
|
o.code << u8(2)
|
|
o.code << obj.encode_i64(0) // placeholder — rebased by the linker
|
|
o.relocs << obj.Reloc{ offset: u32(o.code.len) - 8, name: s, kind: 1 }
|
|
o.code << u8(38)
|
|
}
|
|
'sset' {
|
|
// sset "name" → push_str "name"; sset
|
|
s := unquote(arg)!
|
|
o.code << u8(2)
|
|
o.code << obj.encode_i64(0) // placeholder — rebased by the linker
|
|
o.relocs << obj.Reloc{ offset: u32(o.code.len) - 8, name: s, kind: 1 }
|
|
o.code << u8(39)
|
|
}
|
|
else {
|
|
return error('unknown instruction "${op}"')
|
|
}
|
|
}
|
|
}
|
|
// export the globals as symbols
|
|
for name in globals.keys() {
|
|
entry := labels[name] or { return error('.global "${name}" is not a defined label') }
|
|
o.symbols << obj.Symbol{ name: name, entry: entry }
|
|
}
|
|
return o
|
|
}
|
|
|
|
pub fn assemble_file(path string) !obj.Obj {
|
|
src := os.read_file(path)!
|
|
return assemble(src)!
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
fn clean(line string) string {
|
|
// strip ';' comments and trim
|
|
if idx := line.index(';') {
|
|
return line[..idx].trim_space()
|
|
}
|
|
return line.trim_space()
|
|
}
|
|
|
|
// split_line splits an assembly line into whitespace-separated fields while
|
|
// keeping a double-quoted string (which may contain spaces) as a single field.
|
|
fn split_line(line string) ![]string {
|
|
mut parts := []string{}
|
|
mut i := 0
|
|
for i < line.len {
|
|
for i < line.len && (line[i] == ` ` || line[i] == `\t`) {
|
|
i++
|
|
}
|
|
if i >= line.len {
|
|
break
|
|
}
|
|
if line[i] == `"` {
|
|
// scan to the closing quote
|
|
start := i
|
|
i++
|
|
for i < line.len && line[i] != `"` {
|
|
i++
|
|
}
|
|
i++
|
|
parts << line[start..if i <= line.len { i } else { line.len }]
|
|
} else {
|
|
start := i
|
|
for i < line.len && line[i] != ` ` && line[i] != `\t` {
|
|
i++
|
|
}
|
|
parts << line[start..i]
|
|
}
|
|
}
|
|
return parts
|
|
}
|
|
|
|
fn instr_len(line string) !int {
|
|
parts := split_line(line)!
|
|
op := parts[0]
|
|
match op {
|
|
'halt', 'pop', 'dup', 'add', 'sub', 'mul', 'div', 'mod', 'neg', 'eq', 'ne', 'lt', 'le',
|
|
'gt', 'ge', 'and', 'or', 'not', 'ret', 'retv', 'print', 'println', 'assert', 'aget',
|
|
'aset', 'alen', 'apush' {
|
|
return 1
|
|
}
|
|
'sget', 'sset' {
|
|
return 10 // push_str (9) + the opcode (1)
|
|
}
|
|
'push_int', 'push_str', 'load', 'store', 'jmp', 'jz', 'jnz', 'enter', 'mkarray',
|
|
'mkstruct' {
|
|
return 9
|
|
}
|
|
'call' {
|
|
return 17 // opcode + target(8) + argc(8)
|
|
}
|
|
else {
|
|
return error('unknown instruction "${op}"')
|
|
}
|
|
}
|
|
}
|
|
|
|
fn label(labels map[string]int, name string) !int {
|
|
return labels[name] or { return error('unknown label "${name}"') }
|
|
}
|
|
|
|
fn parse_int(s string, what string) !i64 {
|
|
if s.len == 0 {
|
|
return error('${what}: bad integer "${s}"')
|
|
}
|
|
start := if s[0] == `-` { 1 } else { 0 }
|
|
if start >= s.len {
|
|
return error('${what}: bad integer "${s}"')
|
|
}
|
|
for i in start..s.len {
|
|
if s[i] < `0` || s[i] > `9` {
|
|
return error('${what}: bad integer "${s}"')
|
|
}
|
|
}
|
|
return s.i64()
|
|
}
|
|
|
|
fn unquote(s string) !string {
|
|
if s.len < 2 || s[0] != `"` || s[s.len - 1] != `"` {
|
|
return error('expected a string literal, got "${s}"')
|
|
}
|
|
inner := s[1..s.len - 1]
|
|
mut out := ''
|
|
mut i := 0
|
|
for i < inner.len {
|
|
c := inner[i]
|
|
if c == `\\` && i + 1 < inner.len {
|
|
i++
|
|
match inner[i] {
|
|
`n` {
|
|
out += '\n'
|
|
}
|
|
`t` {
|
|
out += '\t'
|
|
}
|
|
`"` {
|
|
out += '"'
|
|
}
|
|
`\\` {
|
|
out += '\\'
|
|
}
|
|
else {
|
|
out += '\\${inner[i].ascii_str()}'
|
|
}
|
|
}
|
|
} else {
|
|
out += c.ascii_str()
|
|
}
|
|
i++
|
|
}
|
|
return out
|
|
}
|
|
|