mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 15:17:19 +00:00
225 lines
7.9 KiB
Markdown
225 lines
7.9 KiB
Markdown
# vuurraaf/v
|
|
|
|
A complete toolchain for **VuurRaaf**, written in V from scratch: a compiler,
|
|
an assembler, a linker, and a stack-based runtime. Everything — including the
|
|
object file format and the virtual machine — lives in this repository.
|
|
|
|
```
|
|
.vr --compiler--> .vobj --linker--> .vbin --vm--> output
|
|
.vasm --assembler--> .vobj
|
|
```
|
|
|
|
## Build
|
|
|
|
Requires [V](https://vlang.io) (`v` in your PATH).
|
|
|
|
```bash
|
|
v -o bin/vr . # build the toolchain
|
|
./bin/vr up # or rebuild from inside the toolchain
|
|
./bin/vr symlink # optionally symlink bin/vr into your PATH
|
|
```
|
|
|
|
> Note: build with `v .` from the project root. Building via an explicit file
|
|
> or path argument makes V pick tcc without the Boehm GC, a combination that
|
|
> miscompiles this codebase (`vr up` already does the right thing).
|
|
|
|
## Usage
|
|
|
|
```
|
|
vr compile <file.vr> [-o out.vobj] source -> object
|
|
vr assemble <file.vasm> [-o out.vobj] assembly -> object
|
|
vr link <a.vobj> [more.vobj ...] [-o out] objects -> executable (.vbin)
|
|
vr run <file.vr|file.vbin> compile+link+run, or run a binary
|
|
vr debug <file.vr|file.vbin> run with an instruction trace
|
|
vr test <file.vr> run every test_* function
|
|
vr bench <file.vr> [iterations] benchmark main()
|
|
vr clean remove .vobj/.vbin artifacts
|
|
vr up rebuild bin/vr
|
|
vr symlink link bin/vr into your PATH
|
|
vr config [set <key> <value>] toolchain config (outdir, verbose)
|
|
vr info | loader | alloc | version | help
|
|
```
|
|
|
|
Quick start:
|
|
|
|
```bash
|
|
./bin/vr run examples/hello.vr # run a program
|
|
./bin/vr test examples/tests.vr # run the tests (one fails on purpose)
|
|
./bin/vr debug examples/hello.vr # watch every bytecode instruction
|
|
|
|
# the assembler path
|
|
./bin/vr assemble examples/math.vasm -o math.vobj
|
|
./bin/vr link math.vobj -o math.vbin
|
|
./bin/vr run math.vbin
|
|
|
|
# multi-file programs (functions in one file may call functions in another)
|
|
./bin/vr compile examples/lib.vr -o lib.vobj
|
|
./bin/vr compile examples/use_lib.vr -o use_lib.vobj
|
|
./bin/vr link lib.vobj use_lib.vobj -o use_lib.vbin
|
|
./bin/vr run use_lib.vbin
|
|
```
|
|
|
|
## The VuurRaaf language
|
|
|
|
A small, V-flavored language. Values are 64-bit integers, strings, arrays, or
|
|
structs (strings concatenate with `+` and compare with `==`/`!=`; arrays and
|
|
structs are mutable references that compare by identity).
|
|
|
|
```
|
|
fn sum(items) {
|
|
let total = 0
|
|
for x in items { // iterate an array
|
|
total = total + x
|
|
}
|
|
return total
|
|
}
|
|
|
|
fn main() {
|
|
let x = 6 * 7
|
|
assert x == 42
|
|
let big = x > 40 and x < 50 // and / or / not, short-circuiting
|
|
if big {
|
|
println("x is big")
|
|
} else {
|
|
println("x is small")
|
|
}
|
|
|
|
let a = [10, 20, 30]
|
|
a[1] = 99 // index assignment
|
|
push(a, 40) // grow in place
|
|
println(a) // [10, 99, 30, 40]
|
|
println(len(a)) // 4
|
|
println(sum(a)) // 179
|
|
|
|
for i in 0..5 { ... } // 0 1 2 3 4 (exclusive ..)
|
|
for i in 1...3 { ... } // 1 2 3 (inclusive ...)
|
|
|
|
for i in 0..10 {
|
|
if i == 2 {
|
|
continue // skip this iteration
|
|
}
|
|
if i == 5 {
|
|
break // leave the loop early
|
|
}
|
|
}
|
|
|
|
let grid = [[1, 2], [3, 4]] // nested arrays
|
|
println(grid[1][0]) // 3
|
|
|
|
let i = 100
|
|
for i in 0..3 { ... } // loop vars are scoped to the loop
|
|
println(i) // 100
|
|
|
|
if score >= 90 { // else-if chains
|
|
grade = "A"
|
|
} else if score >= 80 {
|
|
grade = "B"
|
|
} else {
|
|
grade = "F"
|
|
}
|
|
|
|
match day { // match on any comparable value
|
|
"sat" {
|
|
println("weekend")
|
|
}
|
|
"sun" {
|
|
println("weekend")
|
|
}
|
|
else { // optional fallback arm
|
|
println("workday")
|
|
}
|
|
}
|
|
|
|
let pt = { x: 3, y: 4 } // struct literal: { name: value, ... }
|
|
println(pt.x) // 3 — field access
|
|
pt.y = 5 // field assignment
|
|
let p = { name: "amy", addr: { city: "nyc" } } // nested structs
|
|
println(p.addr.city) // nyc
|
|
}
|
|
```
|
|
|
|
- functions: `fn name(a, b) { ... }` with `return expr`
|
|
- variables: `let name = expr`, reassignment `name = expr`
|
|
- arrays: `[e1, e2, ...]`, indexing `a[i]` (read and write), `len(a)`,
|
|
`push(a, v)`; array literals may nest
|
|
- for loops: `for x in arr { }` and ranges `for i in 0..10 { }` /
|
|
`for i in 0...10 { }`; loop variables are scoped to the loop body
|
|
- `break` / `continue` inside `while` and `for` loops (in `for` loops
|
|
`continue` advances the loop variable / iterator first)
|
|
- else-if chains: `if a { } else if b { } else { }`
|
|
- `match`: `match expr { v1 { } v2 { } else { } }` — arms test equality on
|
|
any comparable value (ints, strings, ...); the `else` arm is optional
|
|
- structs: literals `{ name: value, ... }` (may nest and may be empty `{}`),
|
|
field access `a.b` and assignment `a.b = v` (chained: `a[i].b`, `a.b[i]`);
|
|
structs are mutable references (identity `==`/`!=`), and setting a missing
|
|
field adds it, so records can be built incrementally
|
|
- operators: `+ - * / %`, `== != < <= > >=`, `and or not`, unary `-`
|
|
- statements: `let`, assignment, `if/else`, `match`, `while`, `for`,
|
|
`break`, `continue`, `return`, `assert`, calls, `print(...)` / `println(...)`
|
|
- comments: `//`
|
|
|
|
## Assembly
|
|
|
|
`.vasm` files talk to the VM directly. Labels, `.global` exports, and the full
|
|
opcode set:
|
|
|
|
```
|
|
; comment
|
|
.global main
|
|
main:
|
|
push_int 42
|
|
call helper 1 ; call <target> <argc>
|
|
println
|
|
halt
|
|
|
|
.global helper
|
|
helper:
|
|
enter 0 ; reserve extra locals (args were copied in by `call`)
|
|
load 0
|
|
retv
|
|
```
|
|
|
|
Opcodes: `halt push_int push_str load store pop dup add sub mul div mod neg
|
|
eq ne lt le gt ge and or not jmp jz jnz call ret retv print println assert
|
|
enter mkarray aget aset alen apush mkstruct sget sset`.
|
|
|
|
Struct opcodes: `mkstruct n` pops `n` (name, value) pairs and pushes a struct
|
|
handle; `sget "field"` / `sset "field"` read/write a named field (pushing the
|
|
field name as a string first, exactly like the compiler does).
|
|
|
|
## Formats
|
|
|
|
- **VROBJ** (`.vobj`) — linker input: bytecode, exported symbols (function
|
|
name -> code offset), string constants, and relocations (call sites and
|
|
string references).
|
|
- **VRBIN** (`.vbin`) — the executable: function table, string table, bytecode.
|
|
|
|
## Architecture
|
|
|
|
| module | role |
|
|
|--------------|-------------------------------------------------------------|
|
|
| `compiler/` | lexer, recursive-descent parser, bytecode codegen (VROBJ) |
|
|
| `assembler/` | `.vasm` -> VROBJ |
|
|
| `linker/` | resolves relocations, rebases strings, emits VRBIN |
|
|
| `vm/` | stack VM: tagged values, call frames, string/array heaps |
|
|
| `obj/` | VROBJ/VRBIN binary formats |
|
|
| `bin/` | small standalone tools: `tl_alloc.v`, `tl_loader.v` |
|
|
|
|
The VM is a stack machine with 64-bit tagged values using two tag bits:
|
|
numbers are stored shifted left by two, string handles end in `01`, array
|
|
handles in `11` — so no integer ever collides with a heap handle. Arrays live
|
|
in a growable heap (`mkarray`/`aget`/`aset`/`alen`/`apush`). Calls push a
|
|
frame (return address, base pointer, argc), copy arguments into local slots,
|
|
and reserve extra locals with `enter n`. `vr debug` prints every instruction
|
|
with the stack contents (arrays rendered as `[1, 2, ...]`).
|
|
|
|
## Repository layout
|
|
|
|
```
|
|
main.v CLI entry point (vr <command> ...)
|
|
v.mod module definition
|
|
compiler/ assembler/ linker/ vm/ obj/ the toolchain itself
|
|
bin/ built binary + standalone tools
|
|
examples/ runnable examples (hello, lib, asm, tests, fib)
|
|
```
|