The VERA Book
VERA (Virtual Executable Representation for Agents) is a virtual assembly language designed for AI agents to generate directly, bypassing traditional programming languages. It compiles to native machine code.
Why VERA?
Traditional programming inverts the wrong way for an AI-native world:
Human writes → High-level language → Compiler → Machine code
VERA flips this:
Agent writes → VERA Text → Machine code
↓
Human reads ← VERA Explain
The agent is the primary author. The human is the reviewer. VERA provides two representations to serve this model:
| Form | Purpose | Producer | Consumer |
|---|---|---|---|
| VERA Text | Canonical source | Agent (LLM) | Compiler |
| VERA Explain | Human review | vera explain | Human operator |
Key Properties
- ~80 instructions across 14 categories, using full English words (
add,subtract,compare_equal,branch_if) - Named slots instead of registers — the compiler decides physical storage
- Explicit types on every instruction via dot suffix (
.i64,.f32,.ptr) - No structured control flow — only jumps, labels, and two-target branches
- Portable — one source representation targets multiple architectures
- Self-contained binaries — static linking, no runtime dependencies
Positioning
VERA occupies a specific niche between existing representations:
- More abstract than native assembly — no registers, no platform specifics
- Lower level than WebAssembly — no structured control flow requirement, flat memory
- Simpler than LLVM IR — no SSA form, no phi nodes, no undefined behavior
- More LLM-friendly than all of the above — full English words, explicit types everywhere
What's Included
Standard Library (vera:*)
9 modules providing portable building blocks — I/O, memory, strings, math, networking, threading, DNS resolution, environment variables, and binary encoding. Some use compiler primitives for OS access; others are pure VERA with zero primitives.
Packages (pkg:*)
4 community packages built entirely on the standard library — HTTP client/server, JSON parsing/serialization, and a PostgreSQL wire protocol driver.
Toolchain
| Command | Description |
|---|---|
vera run <file> | Interpret a VERA program |
vera check <file> | Parse and validate without execution |
vera explain <file> | Generate English explanation |
vera build <file> | Compile to native binary (darwin-arm64) |
Getting Started
Prerequisites
- Rust toolchain (stable)
- macOS on Apple Silicon (for
vera build— the interpreter works on any platform)
Build from Source
git clone https://github.com/ewrogers/vera-lang.git
cd vera
cargo build
Hello World
Create hello.vera:
import "vera:io"
data greeting: "Hello, World!\n"
function main() -> i32 {
constant.ptr message, greeting
constant.i64 length, 14
call bytes_written, write_stdout, message, length
constant.i32 exit_code, 0
return exit_code
}
Run it with the interpreter:
cargo run -- run hello.vera
Hello, World!
Or compile to a native binary:
cargo run -- build hello.vera -o hello
./hello
CLI Commands
vera run
Interpret a VERA program directly. No compilation step — useful for development and testing.
vera run examples/fibonacci.vera
vera check
Parse and validate a VERA program without executing it. Reports syntax errors and type mismatches.
vera check examples/hello.vera
vera build
Compile to a native binary. Currently targets Darwin ARM64 (macOS Apple Silicon).
vera build examples/hello.vera -o hello
./hello
Set VERA_DEBUG_ASM=1 to preserve the generated assembly file for inspection:
VERA_DEBUG_ASM=1 vera build examples/hello.vera -o hello
# Assembly preserved at /tmp/vera_output.s
vera explain
Generate a natural English description of any VERA program, standard library module, or package:
vera explain examples/hello.vera # Explain a source file
vera explain vera:math # Explain a stdlib module
vera explain pkg:data/json # Explain a package
Example output:
VERA Explain — hello.vera
Dependencies:
- vera:io
Data:
greeting: string "Hello, World!\n" (14 bytes)
Function main() -> i32:
1. Set `message` to `greeting` (ptr).
2. Set `length` to 14 (i64).
3. Call `write_stdout`(`message`, `length`), storing the result in `bytes_written`.
4. Set `exit_code` to 0 (i32).
5. Return `exit_code`.
Examples
The examples/ directory contains working programs demonstrating the standard library and packages:
| Example | Description |
|---|---|
hello.vera | Hello World |
fibonacci.vera | Fibonacci sequence |
file_io.vera | File operations |
strings.vera | String functions |
math.vera | Math functions |
heap.vera | Heap allocation |
mutex.vera | Mutexes and threads |
tcp_echo_server.vera | TCP echo server |
tcp_client.vera | TCP client |
http_get.vera | HTTP GET request |
http_server.vera | HTTP server with routing |
http_e2e_test.vera | HTTP end-to-end test |
json_test.vera | JSON parse/serialize tests |
dns_test.vera | DNS resolution tests |
postgres_test.vera | PostgreSQL integration tests |
env_test.vera | Environment variable tests |
Philosophy and Design Principles
Agent-First, Human-Second
VERA inverts the traditional programming model. In the conventional compile chain:
Human writes → High-level language → Compiler IR → Assembly → Machine code
With agents in the picture, the human-readable source language becomes the bottleneck rather than the enabler. VERA replaces this with:
Agent writes → VERA Text → Machine code
↓
Human reads ← VERA Explain
The agent is the primary author. The human is a reviewer. The agent generates VERA text — a precise, unambiguous, assembly-like format. When a human needs to review the code, vera explain generates a natural English description.
Intent-Based Instructions
VERA instructions express what should happen, not how or where. There are no explicit registers, no calling convention details, no instruction encoding concerns. The compiler decides all physical resource allocation. VERA describes the computation; the compiler maps it to hardware.
Two Representations
| Form | Purpose | Producer | Consumer |
|---|---|---|---|
| VERA Text | Canonical source | Agent (LLM) | Compiler |
| VERA Explain | Human review | vera explain | Human operator |
Not a Programming Language
VERA is deliberately not a programming language. It is an assembly-level representation with:
- No structured control flow — no
if/else/while/forblocks. Only jumps and labels. - No expressions —
add.i64 result, a, bnotresult = a + b. - No implicit behavior — every operation is an explicit instruction.
- No scope — named slots are flat within a function, labels are flat within a function.
- Linear execution — the instruction pointer moves forward unless a jump says otherwise.
Positioning
VERA occupies a specific niche between existing representations:
- More abstract than native assembly — no registers, no platform specifics, portable.
- Lower level than WebAssembly — no structured control flow requirement, no validation stack, flat memory.
- Simpler than LLVM IR — no SSA form, no phi nodes, no undefined behavior semantics, no poison values.
- More LLM-friendly than all of the above — full English words, explicit types everywhere, no implicit state.
Operand Model — Named Slots
Overview
VERA uses named slots instead of registers. A slot is a named storage location scoped to a function. The compiler decides whether each slot lives in a hardware register, on the stack, or is spilled to memory.
Slot Rules
- Slots are declared implicitly on first assignment (when they appear as a destination).
- Slot names are alphanumeric identifiers with underscores:
[a-zA-Z_][a-zA-Z0-9_]* - Slots are scoped to their enclosing function — no global slots (use
globalfor that). - Slots have a fixed type determined by the first instruction that writes to them.
- A slot's type must be consistent across all instructions that reference it. It is an error to use
countasi64in one instruction andf64in another. - Slots can be reassigned (they are not single-assignment).
Why Not Registers
Explicit registers (r0, r1, rax, x0) are architectural concerns. They make the representation non-portable and force the author to do register allocation — a job the compiler does better. Named slots let the agent focus on logic while the compiler handles resource mapping.
Why Not Variables
Slots are not variables in the programming-language sense. There is no scope nesting, no shadowing, no lifetime management at the language level. A slot is simply a name for a value that the compiler maps to physical storage. This keeps VERA at the assembly level.
Type System
Primitive Types
| Type | Description | Size (bytes) |
|---|---|---|
i8 | 8-bit integer | 1 |
i16 | 16-bit integer | 2 |
i32 | 32-bit integer | 4 |
i64 | 64-bit integer | 8 |
f32 | IEEE 754 single-precision float | 4 |
f64 | IEEE 754 double-precision float | 8 |
ptr | Pointer (platform-width, opaque) | 8* |
*ptr is 8 bytes on all currently supported targets (x86-64, ARM64, RISC-V 64). The spec reserves the possibility of 4-byte pointers on future 32-bit targets.
Signedness
Integer types are not inherently signed or unsigned. The bit pattern in an i32 is the same whether it represents 42 or -42. Signedness is a property of the operation, not the type:
divide.i64— unsigned divisiondivide_signed.i64— signed divisioncompare_less.i64— unsigned less-thancompare_signed_less.i64— signed less-thanshift_right.i64— logical (zero-fill) shiftshift_right_signed.i64— arithmetic (sign-extending) shift
This matches hardware reality and follows LLVM IR's design. It avoids the need for separate u32/s32 types.
No Composite Types
VERA has no struct, array, enum, or union types. Structured data is accessed through pointer arithmetic:
; Access field y of Point { x: f64, y: f64 } at pointer p
; x is at offset 0, y is at offset 8
load.f64 y_value, p, 8
Layout knowledge lives in the agent (or a higher-level frontend), not in the ISA. This keeps the type system minimal and the ISA clean.
No Type Inference
Every instruction explicitly states its type via a suffix. There is no implicit type promotion, coercion, or inference. If you need to convert between types, use an explicit conversion instruction.
Instruction Set Architecture
Instruction Format
All instructions follow a consistent format:
opcode.type destination, source1, source2 ; three-operand
opcode.type destination, source ; two-operand (unary)
opcode.type destination, source, immediate ; with immediate
opcode label_or_target ; control flow
- Destination is always first (like ARM/RISC-V, unlike x86 AT&T syntax).
- Type suffix is always present on typed instructions (
.i64,.f32,.ptr). - Operands are either slot names, immediate literals, or labels.
- Comments start with
;and extend to end of line.
Arithmetic — Integer
8 instructions. Applicable types: i8, i16, i32, i64.
| Instruction | Format | Description |
|---|---|---|
add | add.T dest, src1, src2 | dest = src1 + src2 |
subtract | subtract.T dest, src1, src2 | dest = src1 - src2 |
multiply | multiply.T dest, src1, src2 | dest = src1 * src2 (low bits of product) |
divide | divide.T dest, src1, src2 | Unsigned division: dest = src1 / src2 |
divide_signed | divide_signed.T dest, src1, src2 | Signed division: dest = src1 / src2 |
remainder | remainder.T dest, src1, src2 | Unsigned remainder: dest = src1 % src2 |
remainder_signed | remainder_signed.T dest, src1, src2 | Signed remainder: dest = src1 % src2 |
negate | negate.T dest, src | dest = -src (two's complement) |
Division by zero: Traps (program termination with diagnostic). No undefined behavior.
Arithmetic — Floating Point
7 instructions. Applicable types: f32, f64.
| Instruction | Format | Description |
|---|---|---|
add | add.T dest, src1, src2 | Float addition |
subtract | subtract.T dest, src1, src2 | Float subtraction |
multiply | multiply.T dest, src1, src2 | Float multiplication |
divide | divide.T dest, src1, src2 | Float division |
negate | negate.T dest, src | Float negation (sign flip) |
square_root | square_root.T dest, src | Square root (hardware instruction on all targets) |
fused_multiply_add | fused_multiply_add.T dest, src1, src2, src3 | dest = (src1 * src2) + src3, fused (no intermediate rounding) |
Note: add, subtract, multiply, divide, and negate share opcode names with their integer counterparts. The type suffix disambiguates: add.i64 is integer, add.f64 is float.
Why square_root is in the ISA: Every target architecture has a hardware sqrt instruction. It is a single-instruction operation. Other math functions (sin, cos, exp, log) are multi-instruction sequences and belong in the standard library.
Why fused_multiply_add: FMA produces a more accurate result than separate multiply + add (one rounding step instead of two). It is a distinct semantic operation, not just an optimization. All three target architectures have hardware FMA.
Arithmetic — Pointer
2 instructions. Type is always ptr.
| Instruction | Format | Description |
|---|---|---|
add | add.ptr dest, base, offset | dest = base + offset. base is ptr, offset is i64 (byte count). Result is ptr. |
subtract | subtract.ptr dest, ptr1, ptr2 | dest = ptr1 - ptr2. Both inputs are ptr. Result is i64 (byte distance). |
No multiply.ptr or divide.ptr — pointer multiplication/division is meaningless.
Bitwise and Logical
9 instructions. Applicable types: i8, i16, i32, i64.
| Instruction | Format | Description |
|---|---|---|
bitwise_and | bitwise_and.T dest, src1, src2 | dest = src1 AND src2 |
bitwise_or | bitwise_or.T dest, src1, src2 | dest = src1 OR src2 |
bitwise_xor | bitwise_xor.T dest, src1, src2 | dest = src1 XOR src2 |
bitwise_not | bitwise_not.T dest, src | dest = NOT src (one's complement) |
shift_left | shift_left.T dest, src, amount | Shift left. amount is i8, masked to type width. |
shift_right | shift_right.T dest, src, amount | Logical shift right (zero-fill). |
shift_right_signed | shift_right_signed.T dest, src, amount | Arithmetic shift right (sign-extending). |
rotate_left | rotate_left.T dest, src, amount | Rotate left. |
rotate_right | rotate_right.T dest, src, amount | Rotate right. |
Shift amounts are always i8 type and are implicitly masked to the type's bit width (e.g., masked to 0-63 for i64). This matches hardware behavior on all targets.
Comparison
17 instructions. Result type is always i8 (0 = false, 1 = true).
Design: Comparisons produce a boolean result in a named slot. There is no flags register and no implicit condition state. Conditional branches consume the boolean result. The compiler is free to fuse a comparison immediately followed by a branch_if into a single hardware compare-and-branch — this pattern is trivially recognizable.
Integer Comparisons (unsigned)
Applicable types: i8, i16, i32, i64.
| Instruction | Format | Description |
|---|---|---|
compare_equal | compare_equal.T dest, src1, src2 | dest = (src1 == src2) ? 1 : 0 |
compare_not_equal | compare_not_equal.T dest, src1, src2 | dest = (src1 != src2) ? 1 : 0 |
compare_less | compare_less.T dest, src1, src2 | Unsigned: dest = (src1 < src2) ? 1 : 0 |
compare_less_equal | compare_less_equal.T dest, src1, src2 | Unsigned: dest = (src1 <= src2) ? 1 : 0 |
compare_greater | compare_greater.T dest, src1, src2 | Unsigned: dest = (src1 > src2) ? 1 : 0 |
compare_greater_equal | compare_greater_equal.T dest, src1, src2 | Unsigned: dest = (src1 >= src2) ? 1 : 0 |
Integer Comparisons (signed)
| Instruction | Format | Description |
|---|---|---|
compare_signed_less | compare_signed_less.T dest, src1, src2 | Signed: dest = (src1 < src2) ? 1 : 0 |
compare_signed_less_equal | compare_signed_less_equal.T dest, src1, src2 | Signed: dest = (src1 <= src2) ? 1 : 0 |
compare_signed_greater | compare_signed_greater.T dest, src1, src2 | Signed: dest = (src1 > src2) ? 1 : 0 |
compare_signed_greater_equal | compare_signed_greater_equal.T dest, src1, src2 | Signed: dest = (src1 >= src2) ? 1 : 0 |
Note: compare_equal and compare_not_equal are the same for signed and unsigned (equality doesn't depend on signedness interpretation).
Float Comparisons
Applicable types: f32, f64. All are ordered — if either operand is NaN, result is 0 (false), except compare_not_equal where NaN != anything is true. This matches IEEE 754.
| Instruction | Format | Description |
|---|---|---|
compare_equal | compare_equal.T dest, src1, src2 | Ordered equal |
compare_not_equal | compare_not_equal.T dest, src1, src2 | Ordered not equal |
compare_less | compare_less.T dest, src1, src2 | Ordered less than |
compare_less_equal | compare_less_equal.T dest, src1, src2 | Ordered less or equal |
compare_greater | compare_greater.T dest, src1, src2 | Ordered greater than |
compare_greater_equal | compare_greater_equal.T dest, src1, src2 | Ordered greater or equal |
is_nan | is_nan.T dest, src | dest = 1 if src is NaN, 0 otherwise |
Pointer Comparisons
| Instruction | Format | Description |
|---|---|---|
compare_equal | compare_equal.ptr dest, p1, p2 | dest = (p1 == p2) ? 1 : 0 |
compare_not_equal | compare_not_equal.ptr dest, p1, p2 | dest = (p1 != p2) ? 1 : 0 |
No ordering comparisons on pointers. Comparing pointer magnitude is architecture-dependent and semantically questionable. If needed, convert to integer first.
Control Flow
4 instructions. These have no type suffix.
| Instruction | Format | Description |
|---|---|---|
jump | jump label | Unconditional jump to label. |
branch_if | branch_if cond, label_true, label_false | If cond (i8) != 0, jump to label_true. Otherwise, jump to label_false. |
select | select.T dest, cond, val_true, val_false | dest = cond ? val_true : val_false. Conditional move, not a branch. |
unreachable | unreachable | Marks a code path that should never execute. May compile to a trap instruction. |
branch_if takes two targets: This eliminates fall-through semantics. Every basic block ends explicitly with jump, branch_if, return, or unreachable. There is no implicit "continue to next instruction" at the end of a block. This makes the control flow graph unambiguous — critical for correctness when an LLM is generating code.
select is not control flow: It is a conditional move. The compiler may implement it as a hardware CMOV (x86) or CSEL (ARM64), avoiding a branch entirely. It is useful for simple conditional assignments where a branch would be wasteful.
Memory
3 instructions.
| Instruction | Format | Description |
|---|---|---|
load | load.T dest, addr | Load T-sized value from memory at addr (ptr) into dest. |
load | load.T dest, addr, offset | Load from addr + offset. offset is an i64 byte count. |
store | store.T addr, value | Store value to memory at addr (ptr). |
store | store.T addr, offset, value | Store to addr + offset. |
stack_allocate | stack_allocate dest, size, align | Allocate size bytes on the stack with align-byte alignment. Store pointer in dest (ptr). size and align are immediate values. |
Load/store types: i8, i16, i32, i64, f32, f64, ptr.
load.i8 and load.i16 zero-extend into the destination slot. For sign-extension, use load followed by sign_extend.
Alignment: Loads and stores should be naturally aligned (address divisible by type size). Misaligned access behavior is implementation-defined and may trap on some targets.
stack_allocate: Creates a local buffer on the stack. This is necessary for local arrays, temporary structs, or any case where the agent needs an addressable local buffer. The compiler coalesces all stack_allocate calls in a function into the stack frame setup in the function prologue.
Type Conversion
10 instructions.
Integer Width Conversions
| Instruction | Format | Description |
|---|---|---|
extend | extend.T dest, src | Zero-extend src (narrower int) to wider type T. |
sign_extend | sign_extend.T dest, src | Sign-extend src (narrower int) to wider type T. |
truncate | truncate.T dest, src | Truncate src (wider int) to narrower type T. Uses low bits. |
The destination type is given by the suffix. The source type is determined by the slot's established type. Example:
; source is i8, dest is i64
sign_extend.i64 wide_val, narrow_byte
Integer <-> Float Conversions
| Instruction | Format | Description |
|---|---|---|
int_to_float | int_to_float.T dest, src | Unsigned integer to float. T is the destination float type. |
signed_int_to_float | signed_int_to_float.T dest, src | Signed integer to float. |
float_to_int | float_to_int.T dest, src | Float to unsigned integer (truncated toward zero). T is the destination int type. |
float_to_signed_int | float_to_signed_int.T dest, src | Float to signed integer (truncated toward zero). |
Float Width Conversion
| Instruction | Format | Description |
|---|---|---|
float_extend | float_extend.f64 dest, src | f32 to f64 (widen). |
float_truncate | float_truncate.f32 dest, src | f64 to f32 (narrow, may lose precision). |
Pointer <-> Integer
| Instruction | Format | Description |
|---|---|---|
int_to_pointer | int_to_pointer dest, src | Integer (i64) to pointer. |
pointer_to_int | pointer_to_int dest, src | Pointer to integer (i64). |
Function Operations
4 instructions.
| Instruction | Format | Description |
|---|---|---|
call | call dest, func_name, arg1, arg2, ... | Call function, store return value in dest. |
call_indirect | call_indirect dest, func_ptr, arg1, arg2, ... | Call through function pointer. For callbacks, vtable dispatch. |
tail_call | tail_call func_name, arg1, arg2, ... | Tail call. Guaranteed no stack growth. Current frame is replaced. No return value capture — the callee returns directly to this function's caller. |
return | return [value] | Return from function. return with no operand for void functions. |
Multi-return: Functions may return multiple values. Call syntax uses parenthesized destinations:
call (quotient, rem), divmod, a, b
call_indirect: The function pointer slot must be ptr type. The compiler requires a type signature to know the parameter and return types — this is provided through an extern declaration or inferred from context.
No variadic functions: Variadic calling conventions are platform-specific nightmares. VERA does not support them. If variadic behavior is needed, pass a pointer to an argument buffer.
Atomic Operations
10 instructions. For concurrent/multi-threaded programs.
Memory Orderings
Atomic instructions take a memory ordering parameter. VERA follows the C11/C++11 memory model:
| Ordering | Meaning |
|---|---|
relaxed | No ordering guarantees beyond atomicity. |
acquire | Subsequent reads/writes cannot be reordered before this. |
release | Preceding reads/writes cannot be reordered after this. |
acquire_release | Both acquire and release semantics. |
sequentially_consistent | Total order across all seq_cst operations. Strongest guarantee. |
Atomic Load / Store
| Instruction | Format | Description |
|---|---|---|
atomic_load | atomic_load.T dest, addr, ordering | Atomically load from addr. |
atomic_store | atomic_store.T addr, value, ordering | Atomically store to addr. |
Applicable types: i8, i16, i32, i64, ptr.
Atomic Read-Modify-Write
All return the old value (value at addr before the operation) in dest.
| Instruction | Format | Description |
|---|---|---|
atomic_add | atomic_add.T dest, addr, value, ordering | Atomic add. dest = old. |
atomic_subtract | atomic_subtract.T dest, addr, value, ordering | Atomic subtract. dest = old. |
atomic_and | atomic_and.T dest, addr, value, ordering | Atomic bitwise AND. dest = old. |
atomic_or | atomic_or.T dest, addr, value, ordering | Atomic bitwise OR. dest = old. |
atomic_xor | atomic_xor.T dest, addr, value, ordering | Atomic bitwise XOR. dest = old. |
atomic_exchange | atomic_exchange.T dest, addr, value, ordering | Atomic swap. dest = old, memory[addr] = value. |
Compare-and-Exchange
| Instruction | Format | Description |
|---|---|---|
atomic_compare_exchange | atomic_compare_exchange.T dest, addr, expected, desired, success_ordering, failure_ordering | If memory[addr] == expected: memory[addr] = desired. Always: dest = old value at addr. Caller checks success by comparing dest to expected. |
Memory Fence
| Instruction | Format | Description |
|---|---|---|
fence | fence ordering | Memory barrier. No memory operand — affects ordering of surrounding operations. |
Miscellaneous
4 instructions.
| Instruction | Format | Description |
|---|---|---|
copy | copy.T dest, src | Copy value from one slot to another. May compile to register move or be eliminated entirely. |
constant | constant.T dest, immediate | Load an immediate literal value into a slot. |
no_operation | no_operation | No operation. May be used for alignment or as a placeholder. |
trap | trap | Debugger breakpoint. Compiles to INT3 (x86), BKPT (ARM64), EBREAK (RISC-V). Halts execution and signals the debugger. |
Why copy exists: While add.i64 dest, src, 0 would achieve the same result, it obscures intent. An explicit copy tells the compiler "this is a move" and tells the human reader "this is copying a value."
Why constant exists: Loading immediate values is one of the most common operations. On RISC architectures, large constants require multiple hardware instructions (RISC-V needs LUI + ADDI for 32-bit values, more for 64-bit). constant abstracts this.
Instruction Count Summary
| Category | Count |
|---|---|
| Integer Arithmetic | 8 |
| Float Arithmetic | 7 |
| Pointer Arithmetic | 2 |
| Bitwise / Logical | 9 |
| Comparison (integer, unsigned) | 6 |
| Comparison (integer, signed) | 4 |
| Comparison (float) | 7 |
| Comparison (pointer) | 2 |
| Control Flow | 4 |
| Memory | 3 |
| Type Conversion | 10 |
| Function Operations | 4 |
| Atomic Operations | 10 |
| Miscellaneous | 4 |
| Total | ~80 |
With type specialization (each opcode × applicable types), the binary format requires approximately 200-250 distinct encoded forms.
Function Declarations
Syntax
Functions are declared with a name, typed parameters, and an optional return type:
function function_name(param1: type1, param2: type2) -> return_type {
; function body — VERA instructions
}
Void functions omit the return type:
function do_something(x: i64) {
; ...
return
}
Multi-return functions list return types in parentheses:
function divmod(a: i64, b: i64) -> (i64, i64) {
divide.i64 quotient, a, b
remainder.i64 rem, a, b
return quotient, rem
}
Parameters
Function parameters are named slots with declared types. They are the initial slots in the function's scope — the function body can create additional slots by using them as destinations.
Labels
Labels within functions serve as jump targets. They are identifiers followed by a colon:
function example(n: i64) -> i64 {
constant.i64 zero, 0
compare_equal.i64 is_zero, n, zero
branch_if is_zero, .base_case, .recursive_case
.base_case:
return zero
.recursive_case:
constant.i64 one, 1
subtract.i64 prev, n, one
call result, example, prev
add.i64 total, n, result
return total
}
Labels are prefixed with . to visually distinguish them from slot names. Labels are scoped to their enclosing function.
Calling Convention
VERA defines an abstract calling convention. The compiler maps parameters and return values to the platform's ABI:
- x86-64 (System V AMD64): First 6 integer/pointer args in RDI, RSI, RDX, RCX, R8, R9. First 8 float args in XMM0-XMM7. Return in RAX (integer) or XMM0 (float).
- ARM64 (AAPCS64): First 8 integer/pointer args in X0-X7. First 8 float args in V0-V7. Return in X0 or V0.
- RISC-V 64: First 8 integer/pointer args in a0-a7. First 8 float args in fa0-fa7. Return in a0 or fa0.
For internal VERA-to-VERA calls where all call sites are known at link time (static linking), the compiler may use an optimized internal convention.
Data Declarations
Globals
Global variables are declared at the top level of a code unit:
global counter: i64 = 0
global pi: f64 = 3.14159265358979
global buffer_ptr: ptr
Globals without an initializer are zero-initialized.
Static Data
Raw byte data for strings, lookup tables, and other constant data:
data greeting: "Hello, world!\0"
data lookup_table: [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40]
String literals are stored as UTF-8 bytes. The \0 null terminator is explicit — VERA does not add it automatically.
Extern Declarations
Declare functions or globals defined in other code units or compiler primitives:
extern __vera_sys_write(i32, ptr, i64) -> i64
extern __vera_sys_read(i32, ptr, i64) -> i64
extern __vera_sys_alloc(i64, i64) -> ptr
extern __vera_sys_free(ptr, i64, i64)
Extern declarations are primarily used by standard library modules to reference compiler primitives (see Section 11.3). Application-level VERA code typically uses import to pull in standard library or package functionality rather than declaring externs directly.
Imports
Import another VERA code unit:
import "vera:io"
import "vera:memory"
import "utils.vera"
The vera: prefix denotes standard library modules. Bare paths are relative to the importing code unit.
Code Units and Linking
Code Units
Each .vera file is a code unit — the fundamental compilation unit. A code unit contains:
- Import declarations
- Extern declarations
- Global/data declarations
- Function definitions
Static Linking Only
VERA uses static linking exclusively. When a code unit imports another, the imported code is included in the final binary at compile time. There is no dynamic linking, no shared libraries, no runtime loading.
This simplifies the compilation model and guarantees that a compiled VERA binary is self-contained. The compiler resolves all imports transitively and includes only the functions actually referenced (dead code elimination).
Visibility
All functions and globals in a code unit are visible to importers by default. A future revision may add visibility modifiers if needed.
Binary Format
Overview
The VERA binary format is a section-based container designed for compact storage and fast parsing. The metadata section is independently strippable.
File Structure
┌─────────────────────────────┐
│ VERA Header │ Fixed-size header
├─────────────────────────────┤
│ Section Table │ Section offsets and sizes
├─────────────────────────────┤
│ Type Section │ Function signatures, type info
├─────────────────────────────┤
│ Code Section │ Encoded instructions
├─────────────────────────────┤
│ Data Section │ Constants, strings, static data
├─────────────────────────────┤
│ Import Section │ Imported code unit references
├─────────────────────────────┤
│ Metadata Section │ OPTIONAL — names, comments, labels
│ (strippable) │ Source mapping table
└─────────────────────────────┘
Header
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | Magic | 0x56455241 ("VERA" in ASCII) |
| 4 | 2 | Version Major | Spec major version |
| 6 | 2 | Version Minor | Spec minor version |
| 8 | 4 | Flags | Bit flags (has_metadata, target_hint, etc.) |
| 12 | 4 | Section Count | Number of sections |
| 16 | 4 | Section Table Offset | Byte offset to section table |
Section Table Entry
Each section is described by:
| Size | Field | Description |
|---|---|---|
| 4 | Section Type | Identifier (TYPE=1, CODE=2, DATA=3, IMPORT=4, METADATA=5) |
| 4 | Offset | Byte offset from file start |
| 4 | Size | Section size in bytes |
| 4 | Flags | Section-specific flags |
Instruction Encoding
Each instruction is encoded as:
┌──────────┬───────────┬──────────────────┐
│ Opcode │ Type Tag │ Operands │
│ (1 byte) │ (4 bits) │ (variable) │
└──────────┴───────────┴──────────────────┘
- Opcode: 1 byte. Identifies the instruction (0x00 - 0xFF, giving 256 possible opcodes).
- Type tag: 4 bits. Encodes the type suffix (0=i8, 1=i16, 2=i32, 3=i64, 4=f32, 5=f64, 6=ptr, 7-15=reserved).
- Operands: Variable-length. Slot references are encoded as compact indices (1 or 2 bytes using a variable-length encoding). Immediate values use the minimum bytes for their type. Labels are encoded as relative offsets.
Slot Encoding
In the binary format, named slots are replaced by numeric indices. The mapping from name → index is stored in the metadata section. Without metadata, slots are referenced as $0, $1, $2, etc. in disassembly.
Metadata Section
The metadata section is entirely optional. Stripping it is a matter of removing the section and updating the section table. The binary remains fully functional without it.
Contents:
| Sub-section | Description |
|---|---|
| Slot Name Table | Maps slot indices to string names |
| Function Name Table | Maps function entry points to string names |
| Label Name Table | Maps label offsets to string names |
| Comment Table | Maps instruction offsets to comment strings |
| Source Mapping | Maps instruction ranges to original .vera file locations |
| String Pool | Deduplicated string storage referenced by the tables above |
Stripping
To strip a VERA binary:
- Remove the metadata section.
- Update the section table (decrement section count, remove the metadata entry).
- Optionally adjust the
has_metadataflag in the header.
The code, data, type, and import sections are unaffected. The binary executes identically with or without metadata.
Text Format
The text format is what agents write. It is the canonical source representation.
Syntax Summary
; hello.vera — a simple VERA program
import "vera:io"
data hello_str: "Hello, world!\n"
function main() -> i32 {
; Write greeting to standard output
constant.ptr msg, hello_str
constant.i64 len, 14
call result, write_stdout, msg, len
; Exit with success
constant.i32 zero, 0
return zero
}
Lexical Rules
- Comments:
;to end of line. - Identifiers:
[a-zA-Z_][a-zA-Z0-9_]*for slot names, function names, globals. - Labels:
.followed by an identifier, then:at the point of definition. Referenced without the colon. - Type suffixes:
.followed by type name (.i64,.f32,.ptr). - Immediates: Decimal integers (
42,-7), hex integers (0xFF), floating point (3.14,-0.5,1.0e10). - String literals: Double-quoted, with escape sequences (
\n,\t,\0,\\,\"). - Whitespace: Spaces and tabs are insignificant (used for readability). Newlines separate instructions.
Assembly (Text -> Binary)
The assembler performs a trivial 1:1 translation:
- Parse each instruction into opcode + type + operands.
- Assign numeric indices to slots (in order of first appearance).
- Encode instructions into binary.
- Build the metadata section from names, labels, and comments.
This is a single-pass process with a second pass to resolve forward label references.
Disassembly (Binary -> Text)
With metadata: Produces output identical to the original text (minus formatting variations).
Without metadata: Produces output with numeric slot references ($0, $1) and numeric labels.
Explain Format
The explain format is the human-readable representation of VERA code. It is generated by vera explain — a tool that transforms VERA instructions into natural English descriptions. This is the human review layer: it lets non-programmers (or programmers who prefer reading English) verify what agent-generated code does.
Design Goals
- Reads like a numbered procedure, not like code.
- Every instruction gets a step number for easy cross-reference.
- Block labels appear as section headers.
- Data declarations are shown with their string values and byte counts.
- Works on files, standard library modules (
vera:*), and packages (pkg:*).
Usage
vera explain examples/hello.vera # Explain a VERA source file
vera explain vera:math # Explain a standard library module
vera explain pkg:data/json # Explain a community package
Output Structure
The output follows a consistent format:
VERA Explain — <filename or module name>
Dependencies:
- vera:io
Data:
greeting: string "Hello, World!\n" (14 bytes)
Function main() -> i32:
1. Set `message` to `greeting` (ptr).
2. Set `length` to 14 (i64).
3. Call `write_stdout`(`message`, `length`), storing the result in `bytes_written`.
4. Set `exit_code` to 0 (i32).
5. Return `exit_code`.
For functions with labeled blocks, labels appear as indented section headers with step numbers continuing sequentially:
Function fibonacci(n: i64) -> i64:
1. Set `zero` to 0 (i64).
2. Compare `n` == `zero` (i64), result in `is_zero`.
3. If `is_zero`, go to .return_zero; otherwise go to .check_one.
.return_zero:
4. Return `zero`.
.check_one:
5. Compare `n` == `one` (i64), result in `is_one`.
...
Translation Rules
| VERA Instruction | Explanation Output |
|---|---|
constant.i64 count, 10 | Set count to 10 (i64). |
add.i64 total, a, b | Add a + b (i64), storing the result in total. |
compare_equal.i64 done, i, n | Compare i == n (i64), result in done. |
compare_signed_less.i64 flag, a, b | Compare a < b (signed i64), result in flag. |
branch_if done, .end, .cont | If done, go to .end; otherwise go to .cont. |
load.i64 x, ptr, 0 | Load i64 from ptr + 0 into x. |
store.i64 addr, 8, val | Store val at addr + 8. |
call result, sqrt, val | Call sqrt(val), storing the result in result. |
call _, write_stdout, p, n | Call write_stdout(p, n). |
return total | Return total. |
copy.i64 dst, src | Copy src into dst (i64). |
stack_allocate buf, 256, 8 | Stack-allocate 256 bytes (align 8) as buf. |
Navigation
Each instruction receives a sequential step number within its function. Combined with the function name and block label, this gives stable references like "fibonacci, .loop_body, step 12" that map directly to the source code. Step numbers are more meaningful than source line numbers because they count only executable statements, not comments or whitespace.
Future Enhancements
Future versions of vera explain may recognize common instruction patterns and produce higher-level descriptions:
- If/otherwise: Merge
compare + branch_ifinto "Ifxis less than zero, handle the negative case." - Counting loops: Recognize
init + compare + branch_if backas "Repeat for each value ofifrom 0 tocount." - Switch/case: Merge multiple
compare_equal + branch_ifchains into "Depending oncmd: ..."
Standard Library Philosophy
Core Principle
The VERA ISA contains no I/O, no allocation, no system calls. All interaction with the operating system and external environment happens through the standard library.
Standard Library Modules
The standard library is a collection of VERA code units under the vera: namespace:
| Module | Purpose |
|---|---|
vera:io | File I/O, console I/O, streams |
vera:memory | Heap allocation (allocate, free, copy_memory, zero_memory) |
vera:process | Process management, exit codes, environment variables (planned) |
vera:thread | Thread creation, joining, mutexes, sleep |
vera:net | TCP/UDP sockets — create, connect, bind, listen, accept, send, recv |
vera:math | Math functions (sin, cos, exp, log, pow, floor, sqrt, constants) — pure VERA, no primitives |
vera:string | String/byte operations (length, find, compare, parse, format, case conversion) — pure VERA, no primitives |
vera:binary | Binary encoding/decoding — big-endian and little-endian read/write for i16, i32, i64 — pure VERA, no primitives |
vera:net/dns | DNS hostname resolution — resolves hostnames to IPv4 via DNS A record queries over UDP, handles IP passthrough and localhost |
vera:env | Environment variables — get, set, load .env files with optional override |
vera:format | String formatting — buffer-append functions for int, float, hex, bool, padding — pure VERA, no primitives |
vera:time | Time, clocks, timestamps (planned) |
Platform Bridge — Compiler Primitives
At some point, VERA code must talk to the operating system. Syscall conventions are inherently platform-specific (different instruction sequences, different syscall numbers, different argument passing). VERA solves this with compiler-provided primitive functions.
The compiler provides a small, well-defined set of extern functions prefixed with __vera_sys_. These are not real external libraries — the compiler knows how to emit the correct platform-specific instruction sequence for each one. This is analogous to compiler intrinsics in C/C++ or WASI host functions in WebAssembly.
Primitive Functions
| Primitive | Signature | Description |
|---|---|---|
__vera_sys_write | (i32, ptr, i64) -> i64 | Write bytes to a file descriptor. Returns bytes written. |
__vera_sys_read | (i32, ptr, i64) -> i64 | Read bytes from a file descriptor. Returns bytes read. |
__vera_sys_open | (ptr, i32, i32) -> i32 | Open a file. Returns file descriptor. |
__vera_sys_close | (i32) -> i32 | Close a file descriptor. |
__vera_sys_alloc | (i64, i64) -> ptr | Allocate memory (size, alignment). Returns pointer. |
__vera_sys_free | (ptr, i64, i64) | Free memory (pointer, size, alignment). |
__vera_sys_exit | (i32) | Terminate the process with exit code. |
__vera_sys_getenv | (ptr, i64, ptr, i64) -> i64 | Get environment variable (name, name_len, out_buf, out_size). Returns value length or -1. |
__vera_sys_setenv | (ptr, i64, ptr, i64) -> i32 | Set environment variable (name, name_len, value, value_len). Returns 0 on success. |
__vera_sys_clock | () -> i64 | Monotonic clock in nanoseconds. |
__vera_sys_sleep | (i64) | Sleep for nanoseconds. |
__vera_sys_mmap | (ptr, i64, i32, i32) -> ptr | Memory map (addr hint, size, prot, flags). |
__vera_sys_munmap | (ptr, i64) -> i32 | Unmap memory. |
__vera_sys_socket | (i32, i32, i32) -> i32 | Create socket (domain, type, protocol). Returns socket fd. |
__vera_sys_connect | (i32, ptr, i32) -> i32 | Connect socket to address (fd, sockaddr, addrlen). |
__vera_sys_bind | (i32, ptr, i32) -> i32 | Bind socket to address (fd, sockaddr, addrlen). |
__vera_sys_listen | (i32, i32) -> i32 | Listen on socket (fd, backlog). |
__vera_sys_accept | (i32, ptr, ptr) -> i32 | Accept connection. Returns new socket fd. |
__vera_sys_send | (i32, ptr, i64, i32) -> i64 | Send data on socket (fd, buf, len, flags). Returns bytes sent. |
__vera_sys_recv | (i32, ptr, i64, i32) -> i64 | Receive data from socket (fd, buf, len, flags). Returns bytes received. |
__vera_sys_shutdown | (i32, i32) -> i32 | Shut down socket (fd, how: 0=read, 1=write, 2=both). |
__vera_sys_setsockopt | (i32, i32, i32, ptr, i32) -> i32 | Set socket option (fd, level, optname, optval, optlen). |
__vera_sys_sockaddr_in | (ptr, i32, i32, ptr) -> i32 | Build IPv4 sockaddr (ip_str, ip_len, port, out_buf). Returns sockaddr size. Compiler builds platform-correct layout. |
__vera_sys_poll | (i32, i32, i32) -> i32 | Poll socket for readiness (fd, events, timeout_ms). Events: 1=readable, 2=writable. Returns ready events, 0=timeout, -1=error. |
__vera_sys_unlink | (ptr) -> i32 | Delete a file by path. Returns 0 on success. |
__vera_sys_seek | (i32, i64, i32) -> i64 | Seek in file (fd, offset, whence). Returns new position. |
__vera_sys_stat_size | (ptr) -> i64 | Get file size in bytes by path. Returns -1 on error. |
__vera_sys_rename | (ptr, ptr) -> i32 | Rename/move file (old_path, new_path). Returns 0 on success. |
__vera_sys_mkdir | (ptr, i32) -> i32 | Create directory (path, mode). Returns 0 on success. |
__vera_sys_rmdir | (ptr) -> i32 | Remove empty directory. Returns 0 on success. |
__vera_sys_getcwd | (ptr, i64) -> i32 | Get current working directory into buffer. Returns 0 on success. |
| Threading | ||
__vera_sys_thread_spawn | (ptr, ptr) -> i64 | Create a thread running function pointer with argument. Returns thread handle. |
__vera_sys_thread_join | (i64) -> i64 | Wait for thread to finish. Returns the thread's exit value. |
__vera_sys_mutex_create | () -> i64 | Create a mutex. Returns mutex handle. |
__vera_sys_mutex_lock | (i64) -> i32 | Lock a mutex (blocks until acquired). Returns 0 on success. |
__vera_sys_mutex_trylock | (i64) -> i32 | Try to lock a mutex without blocking. Returns 0 if acquired, non-zero if busy. |
__vera_sys_mutex_unlock | (i64) -> i32 | Unlock a mutex. Returns 0 on success. |
__vera_sys_mutex_destroy | (i64) -> i32 | Destroy a mutex and free its resources. Returns 0 on success. |
This is a deliberately small set (~30 primitives). The compiler maps each to the correct implementation:
- Linux: Emits
syscallinstruction with the correct Linux syscall number. Threading maps toclone/futexsyscalls. - macOS: Emits
syscall(x86-64) orsvc #0x80(ARM64) with Darwin syscall numbers. Threading maps topthread_*via libSystem. - Windows: Emits calls to kernel32.dll / ntdll.dll entry points. Threading maps to
CreateThread/WaitForSingleObject/CRITICAL_SECTIONAPIs.
All threading primitives are supported on all three target platforms. The differences are purely in how the compiler emits them — the VERA standard library code is identical across targets.
How the Standard Library Uses Primitives
The standard library modules are written in VERA and call compiler primitives at the lowest level. Example — vera:io implementation:
; vera:io — standard I/O module
extern __vera_sys_write(i32, ptr, i64) -> i64
extern __vera_sys_read(i32, ptr, i64) -> i64
extern __vera_sys_open(ptr, i32, i32) -> i32
extern __vera_sys_close(i32) -> i32
extern __vera_sys_unlink(ptr) -> i32
extern __vera_sys_seek(i32, i64, i32) -> i64
extern __vera_sys_stat_size(ptr) -> i64
extern __vera_sys_rename(ptr, ptr) -> i32
; Well-known file descriptors
global STDIN: i32 = 0
global STDOUT: i32 = 1
global STDERR: i32 = 2
; Portable file open flags (VERA-defined, compiler maps to platform values)
; These can be combined with bitwise_or.
global FILE_READ: i32 = 1 ; Open for reading
global FILE_WRITE: i32 = 2 ; Open for writing
global FILE_CREATE: i32 = 4 ; Create if it does not exist
global FILE_TRUNCATE: i32 = 8 ; Truncate to zero length if it exists
global FILE_APPEND: i32 = 16 ; Writes append to end of file
; Seek origins
global SEEK_START: i32 = 0 ; Offset from beginning of file
global SEEK_CURRENT: i32 = 1 ; Offset from current position
global SEEK_END: i32 = 2 ; Offset from end of file
; --- Console I/O ---
function write_stdout(data: ptr, length: i64) -> i64 {
constant.i32 stdout_fd, 1
call bytes_written, __vera_sys_write, stdout_fd, data, length
return bytes_written
}
function write_stderr(data: ptr, length: i64) -> i64 {
constant.i32 stderr_fd, 2
call bytes_written, __vera_sys_write, stderr_fd, data, length
return bytes_written
}
function read_stdin(buffer: ptr, max_length: i64) -> i64 {
constant.i32 stdin_fd, 0
call bytes_read, __vera_sys_read, stdin_fd, buffer, max_length
return bytes_read
}
; --- File Operations ---
function open_file(path: ptr, flags: i32) -> i32 {
; Default mode 0644 (owner read/write, group/other read)
constant.i32 default_mode, 420
call fd, __vera_sys_open, path, flags, default_mode
return fd
}
function create_file(path: ptr) -> i32 {
; Open for writing, create if missing, truncate if exists
; FILE_WRITE | FILE_CREATE | FILE_TRUNCATE = 2 | 4 | 8 = 14
constant.i32 flags, 14
constant.i32 default_mode, 420
call fd, __vera_sys_open, path, flags, default_mode
return fd
}
function close_file(fd: i32) -> i32 {
call result, __vera_sys_close, fd
return result
}
function write_file(fd: i32, data: ptr, length: i64) -> i64 {
call bytes_written, __vera_sys_write, fd, data, length
return bytes_written
}
function read_file(fd: i32, buffer: ptr, max_length: i64) -> i64 {
call bytes_read, __vera_sys_read, fd, buffer, max_length
return bytes_read
}
function delete_file(path: ptr) -> i32 {
call result, __vera_sys_unlink, path
return result
}
function rename_file(old_path: ptr, new_path: ptr) -> i32 {
call result, __vera_sys_rename, old_path, new_path
return result
}
function file_size(path: ptr) -> i64 {
call size, __vera_sys_stat_size, path
return size
}
function seek_file(fd: i32, offset: i64, origin: i32) -> i64 {
call position, __vera_sys_seek, fd, offset, origin
return position
}
Example — vera:memory implementation:
; vera:memory — heap allocation and memory operations
extern __vera_sys_alloc(i64, i64) -> ptr
extern __vera_sys_free(ptr, i64, i64)
; Allocate `size` bytes with default 8-byte alignment.
function allocate(size: i64) -> ptr {
constant.i64 default_align, 8
call addr, __vera_sys_alloc, size, default_align
return addr
}
; Allocate with explicit alignment (must be power of 2).
function allocate_aligned(size: i64, align: i64) -> ptr {
call addr, __vera_sys_alloc, size, align
return addr
}
; Free a previously allocated block. Caller passes the same size used for allocation.
function free(addr: ptr, size: i64) {
constant.i64 default_align, 8
call _, __vera_sys_free, addr, size, default_align
return
}
function free_aligned(addr: ptr, size: i64, align: i64) {
call _, __vera_sys_free, addr, size, align
return
}
; Copy `count` bytes from `src` to `dest`. Regions must not overlap.
function copy_memory(dest: ptr, src: ptr, count: i64) {
constant.i64 zero, 0
constant.i64 one, 1
copy.i64 i, zero
.copy_loop:
compare_equal.i64 done, i, count
branch_if done, .copy_done, .copy_byte
.copy_byte:
add.ptr src_addr, src, i
add.ptr dest_addr, dest, i
load.i8 byte, src_addr, 0
store.i8 dest_addr, byte
add.i64 i, i, one
jump .copy_loop
.copy_done:
return
}
; Set `count` bytes at `dest` to `value`.
function set_memory(dest: ptr, value: i8, count: i64) {
constant.i64 zero, 0
constant.i64 one, 1
copy.i64 i, zero
.set_loop:
compare_equal.i64 done, i, count
branch_if done, .set_done, .set_byte
.set_byte:
add.ptr addr, dest, i
store.i8 addr, value
add.i64 i, i, one
jump .set_loop
.set_done:
return
}
; Zero `count` bytes at `dest`.
function zero_memory(dest: ptr, count: i64) {
constant.i8 zero_byte, 0
call _, set_memory, dest, zero_byte, count
return
}
Note the design: __vera_sys_alloc / __vera_sys_free are compiler primitives because real allocators require platform-specific features (mmap, VirtualAlloc, page granularity). The vera:memory module provides the high-level API. copy_memory, set_memory, and zero_memory are pure VERA — they work on any target without primitives. A compiler may recognize these patterns and emit optimized sequences (REP MOVSB on x86-64, memcpy intrinsic on ARM64).
Note that free requires the caller to pass the allocation size. This is deliberate — it matches Rust's dealloc(ptr, Layout) model and avoids hidden per-allocation headers. Agents track sizes in their data structures, or the compiler inserts size tracking when safety passes are enabled.
Example — vera:thread implementation:
; vera:thread — threading and synchronization module
extern __vera_sys_thread_spawn(ptr, ptr) -> i64
extern __vera_sys_thread_join(i64) -> i64
extern __vera_sys_mutex_create() -> i64
extern __vera_sys_mutex_lock(i64) -> i32
extern __vera_sys_mutex_trylock(i64) -> i32
extern __vera_sys_mutex_unlock(i64) -> i32
extern __vera_sys_mutex_destroy(i64) -> i32
extern __vera_sys_sleep(i64) -> i32
; --- Thread Operations ---
; Spawn a new thread running the given function with an argument.
; The function must have signature: function(arg: ptr) -> i64
function thread_spawn(func: ptr, arg: ptr) -> i64 {
call handle, __vera_sys_thread_spawn, func, arg
return handle
}
; Wait for a thread to finish and return its exit value.
function thread_join(handle: i64) -> i64 {
call result, __vera_sys_thread_join, handle
return result
}
; Sleep the current thread for the given number of milliseconds.
function thread_sleep(milliseconds: i64) -> i32 {
call result, __vera_sys_sleep, milliseconds
return result
}
; --- Mutex Operations ---
function mutex_create() -> i64 {
call handle, __vera_sys_mutex_create
return handle
}
function mutex_lock(handle: i64) -> i32 {
call result, __vera_sys_mutex_lock, handle
return result
}
; Returns 0 if acquired, non-zero if already locked.
function mutex_trylock(handle: i64) -> i32 {
call result, __vera_sys_mutex_trylock, handle
return result
}
function mutex_unlock(handle: i64) -> i32 {
call result, __vera_sys_mutex_unlock, handle
return result
}
function mutex_destroy(handle: i64) -> i32 {
call result, __vera_sys_mutex_destroy, handle
return result
}
Note the relationship between ISA-level atomics (Section 4.10) and vera:thread: atomics are instructions in the ISA because they map to single hardware instructions on all targets (LOCK prefix on x86-64, LDXR/STXR on ARM64, LR/SC on RISC-V). Threads and mutexes are library functions because they require multi-instruction OS-specific sequences (syscalls, futex operations).
Example — vera:net implementation (TCP/UDP sockets):
; vera:net — networking module
extern __vera_sys_socket(i32, i32, i32) -> i32
extern __vera_sys_connect(i32, ptr, i32) -> i32
extern __vera_sys_bind(i32, ptr, i32) -> i32
extern __vera_sys_listen(i32, i32) -> i32
extern __vera_sys_accept(i32, ptr, ptr) -> i32
extern __vera_sys_send(i32, ptr, i64, i32) -> i64
extern __vera_sys_recv(i32, ptr, i64, i32) -> i64
extern __vera_sys_close(i32) -> i32
extern __vera_sys_shutdown(i32, i32) -> i32
extern __vera_sys_setsockopt(i32, i32, i32, ptr, i32) -> i32
extern __vera_sys_sockaddr_in(ptr, i32, i32, ptr) -> i32
extern __vera_sys_poll(i32, i32, i32) -> i32
; Socket constants (VERA-portable values, compiler maps to platform equivalents)
; AF_INET = 2 (IPv4)
; SOCK_STREAM = 1 (TCP)
; SOCK_DGRAM = 2 (UDP)
; SHUT_READ = 0
; SHUT_WRITE = 1
; SHUT_BOTH = 2
function tcp_socket() -> i32 {
constant.i32 af_inet, 2
constant.i32 sock_stream, 1
constant.i32 proto, 0
call fd, __vera_sys_socket, af_inet, sock_stream, proto
return fd
}
function udp_socket() -> i32 {
constant.i32 af_inet, 2
constant.i32 sock_dgram, 2
constant.i32 proto, 0
call fd, __vera_sys_socket, af_inet, sock_dgram, proto
return fd
}
; Connect to an IPv4 address. ip is a null-terminated string like "127.0.0.1\0".
function tcp_connect(fd: i32, ip: ptr, ip_len: i32, port: i32) -> i32 {
stack_allocate addr_buf, 128, 8
call addr_len, __vera_sys_sockaddr_in, ip, ip_len, port, addr_buf
call result, __vera_sys_connect, fd, addr_buf, addr_len
return result
}
; Bind to an address (use "0.0.0.0\0" for all interfaces).
function tcp_bind(fd: i32, ip: ptr, ip_len: i32, port: i32) -> i32 {
stack_allocate addr_buf, 128, 8
call addr_len, __vera_sys_sockaddr_in, ip, ip_len, port, addr_buf
call result, __vera_sys_bind, fd, addr_buf, addr_len
return result
}
function tcp_listen(fd: i32, backlog: i32) -> i32 {
call result, __vera_sys_listen, fd, backlog
return result
}
function tcp_accept(fd: i32) -> i32 {
constant.ptr null_addr, 0
constant.ptr null_len, 0
call client_fd, __vera_sys_accept, fd, null_addr, null_len
return client_fd
}
function socket_send(fd: i32, data: ptr, length: i64) -> i64 {
constant.i32 flags, 0
call sent, __vera_sys_send, fd, data, length, flags
return sent
}
; Returns bytes received, 0 for EOF, -1 on error.
function socket_recv(fd: i32, buffer: ptr, max_length: i64) -> i64 {
constant.i32 flags, 0
call received, __vera_sys_recv, fd, buffer, max_length, flags
return received
}
function socket_close(fd: i32) -> i32 {
call result, __vera_sys_close, fd
return result
}
function socket_shutdown(fd: i32, how: i32) -> i32 {
call result, __vera_sys_shutdown, fd, how
return result
}
; Poll a socket for readiness.
; events: 1 = readable, 2 = writable, 3 = both.
; timeout_ms: -1 = block forever, 0 = non-blocking check, >0 = wait up to N ms.
; Returns: bitmask of ready events, 0 = timeout, -1 = error.
function socket_poll(fd: i32, events: i32, timeout_ms: i32) -> i32 {
call result, __vera_sys_poll, fd, events, timeout_ms
return result
}
; Enable SO_REUSEADDR (avoids "address already in use" on server restart).
function set_reuseaddr(fd: i32) -> i32 {
constant.i32 sol_socket, 1
constant.i32 so_reuseaddr, 2
stack_allocate opt_buf, 4, 4
constant.i32 one, 1
store.i32 opt_buf, one
constant.i32 opt_len, 4
call result, __vera_sys_setsockopt, fd, sol_socket, so_reuseaddr, opt_buf, opt_len
return result
}
Note: __vera_sys_sockaddr_in is a special primitive — it doesn't map to a single syscall. Instead, the compiler emits platform-correct sockaddr_in struct construction. This abstracts away the differences between POSIX sockaddr_in layout and Winsock's variant, keeping VERA programs fully portable.
Networking primitives map to platform APIs:
- Linux / macOS: POSIX sockets —
socket(),bind(),listen(),accept(),send(),recv(),shutdown(),setsockopt(),poll(),close(). - Windows: Winsock2 —
WSASocket(),bind(),listen(),accept(),send(),recv(),shutdown(),setsockopt(),WSAPoll(),closesocket(). The compiler emitsWSAStartupinitialization at program entry.
This architecture means:
- The standard library is VERA all the way down — agents can read and understand it.
- The only non-VERA boundary is the ~35 compiler primitives.
- Adding a new OS target means teaching the compiler to emit those ~35 primitives. The entire standard library works unchanged.
Example — vera:string implementation (pure VERA, no compiler primitives):
; vera:string — string and byte sequence operations
;
; All functions operate on (ptr, length) byte buffers. VERA has no dedicated
; string type — strings are byte sequences with explicit lengths.
; Pure VERA — no compiler primitives needed.
; Get the length of a null-terminated string (not counting the null byte).
function string_length(buf: ptr) -> i64 {
constant.i64 zero, 0
constant.i64 one, 1
constant.i8 null_byte, 0
copy.i64 i, zero
.loop:
add.ptr addr, buf, i
load.i8 byte, addr, 0
compare_equal.i8 is_null, byte, null_byte
branch_if is_null, .done, .next
.next:
add.i64 i, i, one
jump .loop
.done:
return i
}
; Find the first occurrence of a byte in a buffer.
; Returns the index, or -1 if not found.
function byte_find(buf: ptr, len: i64, needle: i8) -> i64 {
constant.i64 zero, 0
constant.i64 one, 1
constant.i64 neg_one, -1
copy.i64 i, zero
.loop:
compare_equal.i64 at_end, i, len
branch_if at_end, .not_found, .check
.check:
add.ptr addr, buf, i
load.i8 byte, addr, 0
compare_equal.i8 found, byte, needle
branch_if found, .found, .next
.next:
add.i64 i, i, one
jump .loop
.found:
return i
.not_found:
return neg_one
}
; Find the first occurrence of a byte subsequence in a buffer.
; Returns the starting index, or -1 if not found.
function bytes_find(haystack: ptr, haystack_len: i64, needle: ptr, needle_len: i64) -> i64 {
; ... brute-force O(n*m) scan — compare needle at each offset ...
}
; Compare two byte sequences for equality. Returns 1 if equal, 0 if not.
function bytes_equal(a: ptr, b: ptr, len: i64) -> i32 { ... }
; Lexicographic comparison. Returns -1 if a < b, 0 if equal, 1 if a > b.
function bytes_compare(a: ptr, a_len: i64, b: ptr, b_len: i64) -> i32 { ... }
; Check if a buffer starts with a given prefix.
function starts_with(buf: ptr, buf_len: i64, prefix: ptr, prefix_len: i64) -> i32 { ... }
; Parse ASCII decimal digits into an i64. Handles leading sign: "-123" -> -123.
function parse_int(buf: ptr, len: i64) -> i64 { ... }
; Convert an i64 to its ASCII decimal representation.
; Writes digits into buf and returns the number of characters written.
function int_to_string(value: i64, buf: ptr) -> i64 { ... }
; Convert ASCII uppercase letters to lowercase in-place.
function to_lowercase(buf: ptr, len: i64) { ... }
; Convert ASCII lowercase letters to uppercase in-place.
function to_uppercase(buf: ptr, len: i64) { ... }
Note: vera:string is the first standard library module that requires zero compiler primitives. It is implemented entirely in VERA using pointer arithmetic, load/store, and comparisons. This demonstrates that VERA's ISA is sufficient for non-trivial data processing without any platform-specific support.
The module establishes VERA's string convention: all strings are (ptr, len) pairs. There is no dedicated string type. Functions like bytes_find and parse_int are reused by higher-level packages (e.g., pkg:http/client uses them to parse HTTP headers) — avoiding code duplication across the ecosystem.
Example — vera:math implementation (pure VERA, no compiler primitives):
; vera:math — mathematical functions
;
; Pure VERA — no compiler primitives needed. Uses only ISA arithmetic,
; comparisons, and fused_multiply_add for polynomial evaluation.
; --- Constants (accessor functions) ---
function math_pi() -> f64 {
constant.f64 pi, 3.14159265358979323846
return pi
}
; --- Utility ---
function abs_f64(x: f64) -> f64 { ... }
function floor_f64(x: f64) -> f64 { ... } ; float_to_signed_int + adjust
function ceil_f64(x: f64) -> f64 { ... } ; -floor(-x)
function min_f64(a: f64, b: f64) -> f64 { ... }
function max_f64(a: f64, b: f64) -> f64 { ... }
function sqrt_f64(x: f64) -> f64 { ... } ; wraps ISA square_root.f64
; --- Trigonometric (Cody-Waite range reduction + minimax polynomials) ---
function sin_f64(x: f64) -> f64 { ... }
function cos_f64(x: f64) -> f64 { ... }
function tan_f64(x: f64) -> f64 { ... } ; sin/cos
; --- Exponential / Logarithmic ---
function exp_f64(x: f64) -> f64 { ... } ; range reduction via LN2 + polynomial
function log_f64(x: f64) -> f64 { ... } ; mantissa extraction + series
function log2_f64(x: f64) -> f64 { ... }
function log10_f64(x: f64) -> f64 { ... }
function pow_f64(base: f64, exp: f64) -> f64 { ... } ; exp(exp * log(base))
Note: Like vera:string, vera:math requires zero compiler primitives — it is implemented entirely in VERA using the ISA's float arithmetic (add.f64, multiply.f64, divide.f64, square_root.f64) and fused_multiply_add.f64 for high-precision polynomial evaluation via Horner's method. Transcendental functions use standard numerical techniques: Cody-Waite range reduction for sin/cos/exp, minimax polynomial approximations (6-11 terms), and binary exponentiation for 2^k scaling. All results are accurate to 12+ significant digits across the normal input range.
Example — vera:binary implementation (pure VERA, no compiler primitives):
; vera:binary — binary encoding/decoding for multi-byte integers
;
; Provides big-endian (network byte order) and little-endian read/write
; helpers for i16, i32, and i64 values. All write functions return the
; new offset (offset + bytes written) for easy chaining.
; Big-endian (network byte order)
function write_i16_be(buf: ptr, offset: i64, value: i32) -> i64 { ... }
function read_i16_be(buf: ptr, offset: i64) -> i32 { ... }
function write_i32_be(buf: ptr, offset: i64, value: i32) -> i64 { ... }
function read_i32_be(buf: ptr, offset: i64) -> i32 { ... }
function write_i64_be(buf: ptr, offset: i64, value: i64) -> i64 { ... }
function read_i64_be(buf: ptr, offset: i64) -> i64 { ... }
; Little-endian
function write_i16_le(buf: ptr, offset: i64, value: i32) -> i64 { ... }
function read_i16_le(buf: ptr, offset: i64) -> i32 { ... }
function write_i32_le(buf: ptr, offset: i64, value: i32) -> i64 { ... }
function read_i32_le(buf: ptr, offset: i64) -> i32 { ... }
function write_i64_le(buf: ptr, offset: i64, value: i64) -> i64 { ... }
function read_i64_le(buf: ptr, offset: i64) -> i64 { ... }
Note: vera:binary requires zero compiler primitives. It is implemented entirely in VERA using shift_left, shift_right, bitwise_and, bitwise_or, truncate, extend, and load.i8/store.i8. Write functions return the updated offset for easy buffer building (e.g., chaining write_i32_be calls when constructing network protocol messages). The big-endian functions are used by pkg:db/postgres for the PostgreSQL wire protocol.
Example — vera:net/dns implementation (DNS resolution using vera:net, vera:binary, vera:string):
; vera:net/dns — DNS resolution module
;
; Resolves hostnames to IPv4 address strings.
; Three-case resolution: IP passthrough, localhost, DNS A record queries over UDP.
; Public API:
function dns_resolve(hostname: ptr, hostname_len: i64, out_buf: ptr, out_size: i64) -> i64 { ... }
function dns_resolve_with(hostname: ptr, hostname_len: i64, server_ip: ptr, server_ip_len: i64, out_buf: ptr, out_size: i64) -> i64 { ... }
Note: vera:net/dns uses vera:net for UDP sockets (connected UDP pattern), vera:binary for big-endian DNS wire format encoding, and vera:string for hostname parsing. Default nameserver is 8.8.8.8. The module handles raw IP addresses (digits and dots), localhost (returns 127.0.0.1), and actual DNS A record queries. Used by pkg:db/postgres to resolve hostnames in connection strings.
Example — vera:env implementation (environment variables using 2 compiler primitives + pure VERA .env parser):
; vera:env — environment variable operations
;
; Get/set environment variables and load .env files.
; Public API:
function env_get(name: ptr, name_len: i64, out_buf: ptr, out_size: i64) -> i64 { ... }
function env_set(name: ptr, name_len: i64, value: ptr, value_len: i64) -> i32 { ... }
function env_load_file(path: ptr, path_len: i64) -> i32 { ... }
function env_load_file_override(path: ptr, path_len: i64) -> i32 { ... }
Note: vera:env uses two compiler primitives (__vera_sys_getenv, __vera_sys_setenv) for OS environment access. The .env file loader is pure VERA — it reads the file with vera:io, parses KEY=VALUE lines with vera:string, skips # comments and blank lines, and strips surrounding quotes from values. env_load_file preserves existing variables (no overwrite); env_load_file_override overwrites them.
Why Not Built Into the ISA
Keeping system interaction in a library rather than the ISA:
- Keeps the ISA small and stable — the instruction set doesn't change when adding OS support.
- Makes platform ports a library concern, not a spec concern.
- Allows agents to use only what they need (tree-shaking via dead code elimination).
- Follows the Unix philosophy: the kernel (ISA) does one thing well; userspace (stdlib) provides convenience.
Why Not Link Against libc
The alternative to compiler primitives would be declaring extern functions that link against the platform's C library. VERA deliberately avoids this because:
- libc is not universal — musl, glibc, macOS libSystem, and Windows CRT have different behaviors and quirks.
- libc is heavy — statically linking libc adds significant binary size for functionality VERA doesn't need.
- libc has global state — errno, locale, signal handlers. VERA aims for clean, predictable behavior.
- Dependency independence — VERA binaries should be self-contained. No external runtime library required.
The compiler-primitives approach gives VERA the same independence that Go has from libc — direct syscall access without intermediaries.
Package Registry
Core Concept
VERA includes an agent-curated package registry — a shared repository of reusable VERA code units. The distinguishing principle: only agents contribute packages. Humans never contribute directly.
The registry is a DRY (Don't Repeat Yourself) enforcement mechanism. When an agent needs functionality, the workflow is:
- Check — Does a package for this functionality already exist in the registry?
- Use — If yes, import and use it.
- Extend — If it exists but lacks needed functionality, add it and publish a new version.
- Create — If no package exists, implement it, import it, and contribute it upstream.
This creates a self-improving ecosystem where every agent's work benefits all future agents.
Why Agent-Only
Humans write inconsistent code — different styles, naming conventions, documentation quality, error handling patterns. Agents generating VERA produce code that is:
- Consistently formatted — same structure, same patterns, every time.
- Consistently documented — metadata (names, comments) follows conventions.
- Consistently tested — agents generate verification alongside implementation.
- Free of ego — no style debates, no bikeshedding, no abandoned packages.
The registry stays clean and pristine because its contributors are deterministic and convention-following.
Package Naming
Packages use a flat namespace with descriptive, hierarchical names:
import "pkg:compression/zlib"
import "pkg:crypto/sha256"
import "pkg:data/json"
import "pkg:data/csv"
import "pkg:http/client"
import "pkg:http/server"
import "pkg:image/png"
import "pkg:math/matrix"
import "pkg:text/regex"
The pkg: prefix distinguishes community packages from the standard library (vera:).
Versioning
Packages follow semantic versioning (major.minor.patch):
- Patch (1.0.0 -> 1.0.1): Bug fixes. No API changes.
- Minor (1.0.0 -> 1.1.0): New functionality added. Backward compatible.
- Major (1.0.0 -> 2.0.0): Breaking API changes.
Import syntax supports version pinning:
import "pkg:data/json" ; latest compatible version
import "pkg:data/json@2" ; latest 2.x.x
import "pkg:data/json@2.3" ; latest 2.3.x
import "pkg:data/json@2.3.1" ; exact version
All versions remain available in the registry. Older VERA code using @1 continues to work even after @2 is published.
Package Structure
A package is a collection of VERA code units with a manifest:
pkg:data/json/
├── package.vera.toml ; manifest (name, version, description, dependencies)
├── json.vera ; main code unit (public API)
├── parser.vera ; internal parser implementation
├── serializer.vera ; internal serializer implementation
└── tests/
├── parse_test.vera ; verification code units
└── serialize_test.vera
The manifest declares:
[package]
name = "data/json"
version = "1.2.0"
description = "JSON parsing and serialization"
[dependencies]
"vera:memory" = "*"
"vera:string" = "*"
"pkg:data/utf8" = "1"
Contribution Workflow
When an agent contributes to the registry:
- Implement — Write the VERA code unit(s) with full metadata (names, comments).
- Test — Include verification code units that exercise the functionality.
- Validate — The registry runs automated checks:
- Assembles and compiles for all supported targets.
- Runs verification code units.
- Checks for slot naming consistency and metadata completeness.
- Checks that new versions don't break existing API contracts (for minor/patch).
- Publish — If validation passes, the package is added to the registry.
Quality Gates
The registry enforces quality standards automatically:
| Gate | Requirement |
|---|---|
| Compilation | Must assemble and compile cleanly on all targets (x86-64, ARM64, RISC-V). |
| Verification | All test code units must pass. |
| Metadata | All functions must have named slots and comments. No stripped code. |
| API stability | Minor/patch versions must not remove or change existing function signatures. |
| No duplication | Registry checks for functional overlap with existing packages and flags for review. |
| Size budget | Packages should be focused. A package doing too many things is split into multiple packages. |
Discovery
Agents discover packages by querying the registry API:
- Search by functionality: "I need JSON parsing" ->
pkg:data/json - Search by category: "What data format packages exist?" -> list of
pkg:data/* - Dependency graph: "What does
pkg:http/clientdepend on?" -> transitive dependency list
The registry maintains a machine-readable index optimized for agent queries, not human browsing (though a human-readable view may exist for oversight).
Compiler Safety Passes
Overview
Inspired by Rust's compile-time safety guarantees, the VERA compiler may implement safety checking passes. These are compiler features, not ISA features — they analyze VERA code and reject or warn about unsafe patterns.
Potential Safety Checks
| Check | Description |
|---|---|
| Double-free detection | Tracks free calls and warns if the same pointer is freed twice. |
| Use-after-free detection | Warns if a pointer is dereferenced after its target has been freed. |
| Leak detection | Warns if allocated memory is never freed on any code path. |
| Null pointer dereference | Warns if a pointer that may be null is dereferenced without a prior null check. |
| Uninitialized slot use | Error if a slot is read before being written. |
| Type consistency | Error if a slot is used with inconsistent types across instructions. |
| Unreachable code | Warning for instructions after unconditional jump or return. |
| Division by zero | Warning if the divisor may be zero without a prior check. |
Severity Levels
- Error: Code is rejected. Cannot compile. (Type consistency, uninitialized use.)
- Warning: Code compiles but may be incorrect. (Double-free, use-after-free, leaks.)
- Hint: Suggestion for improvement. (Unreachable code.)
Safety checks may be configured by the user (enable/disable individual checks, treat warnings as errors, etc.).
Target Architectures
x86-64
- ABI: System V AMD64 (Linux, macOS) or Microsoft x64 (Windows).
- Register mapping: The compiler maps VERA slots to RAX, RBX, RCX, RDX, RSI, RDI, R8-R15 (16 general-purpose registers). Float slots use XMM0-XMM15.
- Instruction fusion:
compare_* + branch_if->CMP + Jcc.constant + use-> immediate operand encoding.load/store with offset-> memory operand with displacement. - Atomics:
LOCKprefix on read-modify-write instructions (LOCK XADD,LOCK CMPXCHG).MFENCEfor full memory barrier. x86-64 has a strong memory model (TSO) — most loads/stores are already ordered, soacquire/releaseorderings are free. - Notable: x86-64 has variable-length instruction encoding, complex addressing modes, and CISC heritage. The compiler can exploit these for compact code.
ARM64 (AArch64)
- ABI: AAPCS64.
- Register mapping: VERA slots map to X0-X30 (31 general-purpose registers). Float slots use V0-V31 (NEON/FP registers).
- Instruction fusion:
compare_* + branch_if->CMP + B.cond. ARM64 has more registers than x86-64, so fewer spills. - Atomics: Load-acquire (
LDAR), store-release (STLR), and exclusive load/store pairs (LDXR/STXR) for read-modify-write.DMBfor memory barriers. ARM64 has a weak memory model —acquire/releaseorderings emit real barrier instructions. - Notable: ARM64 is fixed-width (4 bytes per instruction), load/store architecture. VERA's explicit load/store model maps naturally.
RISC-V 64 (RV64GC)
- ABI: Standard RISC-V calling convention.
- Register mapping: VERA slots map to x0-x31 (32 general-purpose registers, x0 is hardwired to zero). Float slots use f0-f31.
- Instruction fusion:
compare_* + branch_if-> fused compare-and-branch (BEQ,BLT, etc. — RISC-V natively supports this pattern). - Atomics: A-extension provides
LR/SC(load-reserved / store-conditional) pairs andAMO*(atomic memory operations:AMOADD,AMOSWAP,AMOOR, etc.) with.aq(acquire) and.rl(release) bits.FENCEfor memory barriers. Weak memory model similar to ARM64. - Notable: RISC-V has the most registers and the most direct mapping from VERA's compare-and-branch pattern. Large constants require
LUI + ADDIsequences, which the compiler generates fromconstant.
Deliberate Exclusions
The following are not in the ISA and will not be added. They belong in the standard library, compiler, or future extensions.
| Excluded | Reason | Where It Belongs |
|---|---|---|
sin, cos, tan, exp, log, pow | Multi-instruction on all targets | vera:math |
memcpy, memset, memmove | Complex optimized sequences | vera:memory |
malloc, free | Allocator is software | vera:memory |
print, read, file operations | System I/O | vera:io |
| SIMD / vector operations | Massive ISA surface area, varies wildly across targets | Future VERA extension |
Exception handling (throw/catch) | Complex runtime machinery | Return-code error handling or vera:error |
| Coroutines / async | Runtime/scheduler concern | vera:thread or future extension |
| String operations | High-level, many implementations | vera:string |
| Struct/array types | Layout is a higher-level concern | Agent knowledge / frontend |
clz, ctz, popcnt | Not universal in hardware | Compiler intrinsics if needed |
| Virtual dispatch / vtables | OOP runtime concern | call_indirect + data structures |
| Garbage collection | Runtime concern | Future extension or vera:gc |
| Overflow-checked arithmetic | Synthesizable from existing instructions | Compiler pass |
| Variadic functions | Platform ABI nightmare | Argument buffer pattern |
Examples
Hello World
The simplest complete VERA program — output a string to the console. Demonstrates the platform bridge in action.
; hello.vera — the simplest VERA program
import "vera:io"
data greeting: "Hello, World!\n"
function main() -> i32 {
constant.ptr message, greeting
constant.i64 length, 14
call bytes_written, write_stdout, message, length
constant.i32 exit_code, 0
return exit_code
}
What happens at compile time:
import "vera:io"pulls in the standard I/O module.write_stdoutis a function fromvera:iothat calls__vera_sys_writewith file descriptor 1.__vera_sys_writeis a compiler primitive — the compiler emits the platform-specific syscall sequence.- Static linking includes only
write_stdoutand__vera_sys_write(dead code elimination drops the rest ofvera:io).
Compiled output on Linux x86-64 (conceptually):
; greeting lives in .rodata
; main:
lea rsi, [rip + greeting] ; message pointer
mov edx, 14 ; length
mov edi, 1 ; stdout fd (from write_stdout)
mov eax, 1 ; syscall number for write (from __vera_sys_write)
syscall
xor eax, eax ; return 0
ret
The agent wrote 6 lines of VERA. The compiler inlined write_stdout, resolved the primitive, and emitted ~7 native instructions. No libc. No runtime. Self-contained binary.
Explain output (with metadata):
main takes no arguments and returns an exit code.
Write the greeting "Hello, World!" (14 bytes) to the console. Return exit code 0.
Control Flow Patterns
VERA has no structured control flow. All high-level patterns decompose into the same primitives: compare_*, branch_if, jump, and labels.
If / Else
; if (x > 10) { do_something() } else { do_other() }
constant.i64 threshold, 10
compare_signed_greater.i64 above, x, threshold
branch_if above, .then, .else
.then:
call _, do_something
jump .end
.else:
call _, do_other
jump .end
.end:
Explain: If x is greater than 10, do something. Otherwise, do other.
If / Else If / Else
constant.i64 zero, 0
constant.i64 hundred, 100
compare_signed_less.i64 is_negative, x, zero
branch_if is_negative, .negative, .check_large
.check_large:
compare_signed_greater.i64 is_large, x, hundred
branch_if is_large, .large, .normal
.negative:
call _, handle_negative
jump .end
.large:
call _, handle_large
jump .end
.normal:
call _, handle_normal
jump .end
.end:
Explain: If x is negative, handle the negative case. If x is greater than 100, handle the large case. Otherwise, handle the normal case.
While Loop
; while (count > 0) { count = process(count) }
.while_check:
constant.i64 zero, 0
compare_signed_greater.i64 has_more, count, zero
branch_if has_more, .while_body, .while_end
.while_body:
call count, process, count
jump .while_check
.while_end:
Explain: Repeat the following while count is greater than 0: process count and update it with the result.
For Loop (counting)
; for (i = 0; i < length; i++) { process(array[i]) }
constant.i64 index, 0
constant.i64 one, 1
constant.i64 element_size, 8
.for_check:
compare_signed_less.i64 in_bounds, index, length
branch_if in_bounds, .for_body, .for_end
.for_body:
multiply.i64 byte_offset, index, element_size
add.ptr element_addr, array, byte_offset
load.i64 element, element_addr, 0
call _, process, element
add.i64 index, index, one
jump .for_check
.for_end:
Explain: For each index from 0 up to length: load the element from the array at that position and process it.
Do-While Loop
; do { result = try_operation() } while (result == RETRY)
constant.i32 retry_code, -1
.do_body:
call result, try_operation
compare_equal.i32 should_retry, result, retry_code
branch_if should_retry, .do_body, .do_end
.do_end:
Explain: Try the operation. If the result indicates retry, try again. Otherwise, continue.
Switch / Case
; switch (command) { case 1: create(); case 2: delete(); case 3: update(); default: error(); }
constant.i32 one, 1
constant.i32 two, 2
constant.i32 three, 3
compare_equal.i32 is_create, command, one
branch_if is_create, .case_create, .check_delete
.check_delete:
compare_equal.i32 is_delete, command, two
branch_if is_delete, .case_delete, .check_update
.check_update:
compare_equal.i32 is_update, command, three
branch_if is_update, .case_update, .case_default
.case_create:
call _, create
jump .switch_end
.case_delete:
call _, delete
jump .switch_end
.case_update:
call _, update
jump .switch_end
.case_default:
call _, error
jump .switch_end
.switch_end:
Explain: Depending on the command: if 1, create. If 2, delete. If 3, update. Otherwise, report an error.
Nested Loops (2D array traversal)
constant.i64 row, 0
constant.i64 col, 0
constant.i64 one, 1
constant.i64 col_stride, 8
.row_check:
compare_signed_less.i64 row_ok, row, num_rows
branch_if row_ok, .col_setup, .done
.col_setup:
constant.i64 col, 0
.col_check:
compare_signed_less.i64 col_ok, col, num_cols
branch_if col_ok, .inner_body, .row_advance
.inner_body:
multiply.i64 row_offset, row, num_cols
add.i64 linear_index, row_offset, col
multiply.i64 byte_offset, linear_index, col_stride
add.ptr cell_addr, matrix, byte_offset
load.i64 cell, cell_addr, 0
call _, process_cell, row, col, cell
add.i64 col, col, one
jump .col_check
.row_advance:
add.i64 row, row, one
jump .row_check
.done:
Explain: For each row from 0 to num_rows, and for each col from 0 to num_cols: load the cell value from the matrix and process it with its row and column position.
Fibonacci
; fib.vera — compute the nth Fibonacci number iteratively
function fibonacci(n: i64) -> i64 {
constant.i64 zero, 0
constant.i64 one, 1
; Base case: n == 0
compare_equal.i64 is_zero, n, zero
branch_if is_zero, .return_zero, .check_one
.return_zero:
return zero
.check_one:
; Base case: n == 1
compare_equal.i64 is_one, n, one
branch_if is_one, .return_one, .loop_setup
.return_one:
return one
.loop_setup:
copy.i64 previous, zero ; fib(0)
copy.i64 current, one ; fib(1)
constant.i64 index, 2
.loop_body:
add.i64 next, previous, current
copy.i64 previous, current
copy.i64 current, next
add.i64 index, index, one
compare_signed_less_equal.i64 continue, index, n
branch_if continue, .loop_body, .done
.done:
return current
}
Explain output (with metadata):
fibonacci takes a number n and returns a number.
If n is equal to 0, return 0. If n is equal to 1, return 1.
Otherwise, set previous to 0 and current to 1. Starting from 2, repeat the following for each index up to n: compute the next value by adding previous and current, then shift current to previous and the next value to current.
When finished, return current.
Point Distance
; point.vera — compute distance between two 2D points
; Layout: Point { x: f64 (offset 0), y: f64 (offset 8) }
function point_distance(p1: ptr, p2: ptr) -> f64 {
; Load coordinates
load.f64 x1, p1, 0
load.f64 y1, p1, 8
load.f64 x2, p2, 0
load.f64 y2, p2, 8
; Compute differences
subtract.f64 delta_x, x2, x1
subtract.f64 delta_y, y2, y1
; distance = sqrt(dx^2 + dy^2)
multiply.f64 dx_squared, delta_x, delta_x
fused_multiply_add.f64 sum_of_squares, delta_y, delta_y, dx_squared
square_root.f64 distance, sum_of_squares
return distance
}
Explain output (with metadata):
point_distance takes two point pointers p1 and p2 and returns a decimal number.
Load the x and y coordinates from both points. Compute delta_x as the difference between the x coordinates, and delta_y as the difference between the y coordinates. Square both deltas, add them together, and take the square root to get the distance. Return the distance.
Linked List Search
; list.vera — search a linked list for a value
; Layout: Node { value: i64 (offset 0), next: ptr (offset 8) }
function list_contains(head: ptr, target: i64) -> i8 {
constant.ptr null, 0
copy.ptr current, head
.check_node:
; If current is null, not found
compare_equal.ptr is_null, current, null
branch_if is_null, .not_found, .compare_value
.compare_value:
load.i64 value, current, 0
compare_equal.i64 found, value, target
branch_if found, .found, .advance
.advance:
load.ptr current, current, 8
jump .check_node
.found:
constant.i8 yes, 1
return yes
.not_found:
constant.i8 no, 0
return no
}
Explain output (with metadata):
list_contains takes a list head pointer and a target number. Returns whether the target was found.
Start at the head of the list. For each node: if the current pointer is null, the target was not found — return 0. Otherwise, load the value from the current node. If the value is equal to the target, it was found — return 1. Otherwise, advance current to the next node and check again.
Array Sum with Early Exit
; array.vera — sum array elements, stop if sum exceeds a limit
function bounded_sum(array: ptr, length: i64, limit: i64) -> i64 {
constant.i64 sum, 0
constant.i64 index, 0
constant.i64 one, 1
constant.i64 element_size, 8
; Handle empty array
compare_equal.i64 is_empty, length, index
branch_if is_empty, .done, .loop_body
.loop_body:
; Load element
multiply.i64 byte_offset, index, element_size
add.ptr element_addr, array, byte_offset
load.i64 element, element_addr, 0
; Add to sum
add.i64 sum, sum, element
; Check limit
compare_signed_greater.i64 exceeded, sum, limit
branch_if exceeded, .done, .increment
.increment:
add.i64 index, index, one
compare_signed_less.i64 more, index, length
branch_if more, .loop_body, .done
.done:
return sum
}
Explain output (with metadata):
bounded_sum takes an array pointer, a length, and a limit. Returns the sum.
Start with a sum of 0. If the array is empty, return 0 immediately.
For each element in the array: add it to the running sum. If the sum exceeds the limit, stop early and return the current sum. Otherwise, move to the next element. When all elements have been processed, return the sum.
Atomic Counter
; counter.vera — thread-safe atomic counter operations
global counter_value: i64 = 0
function counter_increment() -> i64 {
; Atomically add 1 to the counter, return the old value
constant.ptr addr, counter_value
constant.i64 one, 1
atomic_add.i64 old_value, addr, one, sequentially_consistent
return old_value
}
function counter_compare_and_set(expected: i64, desired: i64) -> i8 {
; Try to set counter to desired if it currently equals expected
constant.ptr addr, counter_value
atomic_compare_exchange.i64 actual, addr, expected, desired, sequentially_consistent, relaxed
compare_equal.i64 success, actual, expected
return success
}
Explain output (with metadata):
counter_increment atomically adds 1 to the global counter_value and returns the value it had before the increment. This is safe to call from multiple threads simultaneously.
counter_compare_and_set takes an expected value and a desired value. It atomically checks whether counter_value currently equals expected. If so, it sets it to desired and returns 1 (success). If not, it leaves the counter unchanged and returns 0 (failure).
Appendix A: Opcode Quick Reference
Arithmetic (Integer)
add subtract multiply divide divide_signed remainder remainder_signed negate
Arithmetic (Float)
add subtract multiply divide negate square_root fused_multiply_add
Pointer Arithmetic
add.ptr subtract.ptr
Bitwise
bitwise_and bitwise_or bitwise_xor bitwise_not shift_left shift_right shift_right_signed rotate_left rotate_right
Comparison (Unsigned)
compare_equal compare_not_equal compare_less compare_less_equal compare_greater compare_greater_equal
Comparison (Signed)
compare_signed_less compare_signed_less_equal compare_signed_greater compare_signed_greater_equal
Comparison (Float)
compare_equal compare_not_equal compare_less compare_less_equal compare_greater compare_greater_equal is_nan
Control Flow
jump branch_if select unreachable
Memory
load store stack_allocate
Type Conversion
extend sign_extend truncate int_to_float signed_int_to_float float_to_int float_to_signed_int float_extend float_truncate int_to_pointer pointer_to_int
Functions
call call_indirect tail_call return
Atomic
atomic_load atomic_store atomic_add atomic_subtract atomic_and atomic_or atomic_xor atomic_exchange atomic_compare_exchange fence
Miscellaneous
copy constant no_operation trap
Reference Compiler
Note: This section documents the Darwin ARM64 reference implementation. It describes one approach to compiling VERA, not the only valid approach. Other compilers may make different trade-offs. Nothing in this section is normative — it is provided for transparency and as a guide for implementers.
Overview
The reference compiler targets macOS on Apple Silicon (ARM64/AArch64). It is implemented in approximately 2,000 lines of Rust and produces standalone native binaries with no runtime dependencies beyond the system linker.
┌──────────┐ ┌─────────┐ ┌──────────────┐ ┌──────────┐ ┌──────────┐
│ .vera │ ──> │ Parser │ ──> │ Import │ ──> │ ARM64 │ ──> │ cc │
│ source │ │ (AST) │ │ Resolution │ │ Codegen │ │ (asm + │
│ │ │ │ │ (vera: pkg:) │ │ (.s file)│ │ link) │
└──────────┘ └─────────┘ └──────────────┘ └──────────┘ └──────────┘
│
┌────▼─────┐
│ Native │
│ Mach-O │
│ binary │
└──────────┘
The compiler emits ARM64 assembly text (.s file), then invokes the system C compiler (cc, which is Clang on macOS) to assemble and link. The resulting binary links against libSystem for libc functions used by compiler primitives. No other libraries are required.
Compilation Pipeline
The pipeline has five stages:
1. Parse. vera_parser::parse() transforms VERA text into a Program AST containing functions, data declarations, globals, imports, and extern declarations. This is a single-pass lexer and parser.
2. Resolve stdlib imports. vera_stdlib::resolve_all_imports() scans the import list for vera:* entries (e.g., vera:io, vera:net). Each standard library module is embedded in the compiler binary as source text. The resolver parses the module and merges its functions, data, globals, and externs into the program.
3. Resolve package imports. vera_pkg::resolve_all_imports() does the same for pkg:* entries (e.g., pkg:http/client). Package modules may themselves import vera:* modules, which are resolved recursively.
4. Emit ARM64 assembly. Arm64Codegen::emit_program() traverses the fully-resolved program and emits a complete ARM64 assembly file. This includes:
- Data declarations in
__DATA,__data - Global variables with type-appropriate directives (
.byte,.short,.long,.quad) - Function prologues, instruction-by-instruction translation, and epilogues
- A
_mainentry point wrapper that calls_vera_main - Inline implementations of all compiler primitives (
__vera_sys_*)
5. Assemble and link. The compiler invokes cc -arch arm64 -o <output> <temp>.s. The assembler produces a Mach-O object, and the linker resolves references to libSystem functions (_write, _malloc, _pthread_create, etc.). The temporary .s file is deleted after successful compilation.
Register Allocation
The compiler uses a linear scan register allocator. This is a middle ground between trivial stack-only allocation (every slot on the stack) and full graph-coloring (optimal but complex).
Available Registers
| Pool | Registers | Count |
|---|---|---|
| Integer (callee-saved) | x19, x20, x21, x22, x23, x24, x25, x26, x27, x28 | 10 |
| Float (callee-saved) | d8, d9, d10, d11, d12, d13, d14, d15 | 8 |
Only callee-saved registers are used for slot allocation. This means function calls never clobber allocated slot values — no save/restore around calls is needed.
Registers x0–x7 and d0–d7 are reserved for argument passing (AAPCS64 ABI). Registers x8–x18 are scratch registers used internally by the codegen for temporaries (loads, stores, address computation, and primitive calls).
Algorithm
-
Build live intervals — For each slot, record the first and last instruction index where it is referenced. Function parameters are live from instruction 0.
-
Extend for loops — Backward branch edges are detected (jump/branch_if targets that precede the branch instruction). Any slot whose live interval overlaps a loop body has its interval extended to cover the full loop range.
-
Sort by start — Intervals are sorted by start index, breaking ties by end index.
-
Greedy assignment — Walk the sorted intervals. For each new interval:
- Expire any active interval that ended before this one starts, returning its register to the free pool.
- If a register is available in the appropriate pool (integer or float), assign it.
- If no register is available, consider spilling: find the active interval with the longest remaining live range. If it is longer than the current interval, evict it and assign the freed register to the current interval.
- If the current interval is the longest, it stays on the stack.
-
Record callee-saved usage — Only registers that were actually assigned need to be saved and restored in the function prologue and epilogue.
Spilled Slots
Slots that do not receive a register live in their stack slot. The codegen loads them into scratch registers (x8–x10, d0) before use and stores them back after writes.
Stack Frame Layout
Every VERA function gets a fixed-size stack frame allocated in the prologue:
Higher addresses
┌─────────────────────────────────┐
│ stack_allocate space │ ← sp + 16 + css + slots_size
├─────────────────────────────────┤
│ Local slots (8 bytes each) │ ← sp + 16 + css
├─────────────────────────────────┤
│ Callee-saved register saves │ ← sp + 16
├─────────────────────────────────┤
│ x29 (frame ptr) / x30 (LR) │ ← sp
└─────────────────────────────────┘
Lower addresses (sp)
| Region | Offset from sp | Size |
|---|---|---|
| Frame pointer + link register | sp + 0 | 16 bytes (stp x29, x30) |
| Callee-saved registers | sp + 16 | Variable: ceil(count / 2) * 16 bytes (saved in pairs) |
| Local slots | sp + 16 + css | num_slots * 8 bytes |
| Stack-allocate space | sp + 16 + css + slots_size | Sum of all stack_allocate sizes (each 8-byte aligned) |
The total frame size is rounded up to 16-byte alignment per AAPCS64 requirements.
Stack allocations (stack_allocate buf, 1024, 8) receive consecutive regions in the stack-allocate space. The compiler computes the pointer to each allocation at code generation time as a fixed offset from sp.
Every slot occupies exactly 8 bytes regardless of its VERA type (i8, i16, i32, i64, f32, f64, ptr). This simplifies layout computation at the cost of some stack memory. Register-allocated slots still have stack slots reserved (for potential spill paths), but they are not actively used unless the slot is spilled.
Calling Convention
The compiler follows AAPCS64 (ARM64 Procedure Call Standard).
Parameter Passing
| Type | Registers |
|---|---|
| Integer / pointer arguments | x0, x1, ..., x7 (first 8) |
| Float arguments | d0, d1, ..., d7 (first 8) |
The compiler looks up the callee's function signature to determine which arguments are float versus integer. This ensures correct register assignment — for example, if parameter 2 is f64, it goes in the next available d-register, not an x-register.
If an argument is already in its destination register (because the slot was register-allocated to the same ABI register), the move is elided.
Return Values
- Integer / pointer returns:
x0 - Float returns:
d0
Function Calls
For a VERA-to-VERA call (call dest, func_name, args...):
- Load arguments into x0–x7 / d0–d7 from their slots or allocated registers.
- Emit
bl _vera_<callee_name>. - Store
x0(ord0) into the destination slot or register.
Tail Calls
tail_call func, args... emits the full epilogue (restore callee-saved registers, restore x29/x30, deallocate frame) followed by b _vera_<func> instead of bl. This reuses the caller's return address, enabling unbounded mutual recursion without stack growth.
Indirect Calls
call_indirect dest, fptr, args... loads the function pointer into a register, loads arguments into ABI registers, and emits blr <reg> (branch-and-link through register).
Platform Bridge
Compiler primitives (__vera_sys_*) are recognized by name during code generation. Instead of emitting a bl _vera_<name>, the compiler emits the platform-appropriate implementation inline.
On Darwin ARM64, most primitives map directly to libSystem function calls:
| Primitive | Implementation |
|---|---|
__vera_sys_write(fd, buf, count) | bl _write |
__vera_sys_read(fd, buf, count) | bl _read |
__vera_sys_close(fd) | bl _close |
__vera_sys_alloc(size, align) | bl _malloc |
__vera_sys_free(ptr, size, align) | bl _free |
__vera_sys_exit(code) | bl _exit |
__vera_sys_getenv(name, len, buf, size) | Null-terminate name, bl _getenv, bl _strlen, copy to buf |
__vera_sys_setenv(name, nlen, val, vlen) | Null-terminate both, bl _setenv with overwrite=1 |
__vera_sys_sleep(ns) | Convert to microseconds, bl _usleep |
__vera_sys_socket(domain, type, proto) | bl _socket |
__vera_sys_bind(fd, addr, len) | bl _bind |
__vera_sys_listen(fd, backlog) | bl _listen |
__vera_sys_accept(fd, addr, len) | bl _accept |
__vera_sys_connect(fd, addr, len) | bl _connect |
__vera_sys_send(fd, buf, len, flags) | bl _send |
__vera_sys_recv(fd, buf, len, flags) | bl _recv |
__vera_sys_shutdown(fd, how) | bl _shutdown |
__vera_sys_setsockopt(fd, ...) | bl _setsockopt |
__vera_sys_thread_spawn(func, arg) | bl _pthread_create (with wrapper setup) |
__vera_sys_thread_join(handle) | bl _pthread_join |
__vera_sys_mutex_create() | Allocate 64 bytes, bl _pthread_mutex_init |
__vera_sys_mutex_lock(handle) | bl _pthread_mutex_lock |
__vera_sys_mutex_unlock(handle) | bl _pthread_mutex_unlock |
__vera_sys_mutex_destroy(handle) | bl _pthread_mutex_destroy, then free |
Some primitives require multi-instruction sequences:
-
__vera_sys_open— Translates VERA portable file flags (READ=1, WRITE=2, CREATE=4, TRUNCATE=8, APPEND=16) to macOS POSIX flags (O_RDONLY=0, O_WRONLY=1, O_RDWR=2, O_CREAT=0x200, O_TRUNC=0x400, O_APPEND=8) using bitfield extraction and conditional select instructions. -
__vera_sys_sockaddr_in— Constructs astruct sockaddr_inin a caller-provided buffer. Parses the IP string by scanning for dot separators, converting decimal octets with multiplication, and assembling into network byte order. Setssin_len = 16,sin_family = AF_INET (2), and byte-swaps the port number via the ARM64rev16instruction. -
__vera_sys_stat_size— Allocates a temporarystruct staton the stack (256 bytes), calls_stat, extractsst_sizefrom offset 96 (Darwin ARM64 struct layout), cleans up, and returns the file size. -
__vera_sys_poll— Builds apollfdstruct on the stack (fd + events + revents), calls_poll, and returns the revents bitmask.
Optimizations
Implemented
| Optimization | Description |
|---|---|
| Linear scan register allocation | Keeps frequently-used slots in callee-saved registers, avoiding memory traffic for the most active values. |
| Direct register resolution | When an operand is already in the correct register (allocated or ABI position), the codegen skips the load-from-stack step entirely, eliminating redundant ldr/str pairs. |
| Efficient immediate loading | Zero uses mov reg, #0. Small values (0–65535) use a single mov. Negative values close to zero use movn. Larger constants use minimal movz/movk chains, emitting only non-zero 16-bit halfwords. |
| Type-width masking | i8 and i16 results are masked with and to their correct width after operations. i32 uses the w-register view (automatic upper-32 zero extension). i64 needs no masking. The common 64-bit case incurs zero overhead. |
| Callee-saved register pairing | Saves and restores use stp/ldp (store/load pair) instructions, writing two 64-bit registers in a single 16-byte-aligned memory operation. |
| Remainder via msub | remainder r, a, b emits sdiv tmp, a, b followed by msub r, tmp, b, a (r = a - (a/b)*b) since ARM64 has no dedicated modulo instruction. |
| Rotate left via ror | rotate_left r, a, n emits neg tmp, n followed by ror r, a, tmp (rotate right by 64-n), since ARM64 only provides rotate right. |
| Function deduplication | When multiple imports bring in the same function (same name), only the first copy is emitted. |
Not Yet Implemented
| Optimization | Description |
|---|---|
| Compare-and-branch fusion | Merge compare_* + branch_if into cmp + b.cond, eliminating the boolean intermediate slot. |
| Constant folding | Evaluate constant + constant operations at compile time. |
| Dead code elimination | Remove unreachable code after unconditional jumps or returns. |
| Peephole optimization | Rewrite short instruction sequences (e.g., eliminate mov x0, x0, merge adjacent ldr/str into ldp/stp). |
| Strength reduction | Replace multiply x, a, 8 with lsl x, a, #3 for power-of-two constants. |
| Instruction scheduling | Reorder independent instructions to reduce pipeline stalls. |
Debug Support
VERA_DEBUG_ASM environment variable — When set to any value, the temporary assembly file is preserved instead of being deleted after compilation:
VERA_DEBUG_ASM=1 vera build examples/hello.vera -o hello
# Assembly preserved at /tmp/vera_output.s
Naming Conventions in Generated Assembly
The generated assembly uses consistent naming to make it easy to correlate with VERA source:
| Entity | Assembly Label Pattern | Example |
|---|---|---|
| VERA function | _vera_<name> | _vera_fibonacci |
| Block label | _vera_<func>_<label> | _vera_fibonacci_loop_body |
| Data declaration | _data_<name> | _data_greeting |
| Global variable | _global_<name> | _global_counter |
| Entry point | _main | Wrapper that calls _vera_main |
The _vera_ prefix on all VERA functions prevents collisions with libc symbols (e.g., a VERA function named write becomes _vera_write, avoiding conflict with _write from libSystem).
Standard Library
The VERA standard library provides portable building blocks under the vera: namespace. Import any module with:
import "vera:io"
Modules
| Module | Description | Primitives |
|---|---|---|
| vera:binary | Binary encoding/decoding (big/little-endian) | None (pure VERA) |
| vera:env | Environment variables and .env files | Yes |
| vera:format | String formatting and buffer building | None (pure VERA) |
| vera:io | Console I/O and file operations | Yes |
| vera:math | Mathematical functions (f64) | None (pure VERA) |
| vera:memory | Heap allocation and memory operations | Yes |
| vera:net | TCP/UDP socket operations | Yes |
| vera:net/dns | DNS hostname resolution | None (uses vera:net) |
| vera:string | String and byte sequence operations | None (pure VERA) |
| vera:thread | Threading and synchronization | Yes |
Design Principles
- Minimal primitives: Only ~35 compiler-provided functions (
__vera_sys_*) bridge to the OS. Everything else is pure VERA. - Pure VERA modules:
vera:string,vera:math, andvera:binaryuse zero primitives — they are implemented entirely in VERA using the ISA's arithmetic, memory, and comparison instructions. - Flat (ptr, len) convention: Strings are byte buffers with explicit lengths. No dedicated string type, no null terminators (except when interfacing with C-style APIs).
vera:binary
Binary encoding/decoding for multi-byte integers.
import "vera:binary"
Overview
Provides big-endian (network byte order) and little-endian read/write helpers for i16, i32, and i64 values. All write functions return the new offset (offset + bytes written) for easy chaining, allowing sequential writes without manual offset arithmetic.
Dependencies: None (pure VERA)
Functions
Big-Endian (Network Byte Order)
write_i16_be
function write_i16_be(buf: ptr, offset: i64, value: i32) -> i64
Write a 16-bit integer in big-endian byte order at buf + offset.
Parameters:
buf(ptr) — destination bufferoffset(i64) — byte offset within the buffervalue(i32) — the 16-bit value to write (low 16 bits used)
Returns: offset + 2 (the new offset for chaining)
read_i16_be
function read_i16_be(buf: ptr, offset: i64) -> i32
Read a 16-bit big-endian integer from buf + offset.
Parameters:
buf(ptr) — source bufferoffset(i64) — byte offset within the buffer
Returns: the 16-bit value as an i32
write_i32_be
function write_i32_be(buf: ptr, offset: i64, value: i32) -> i64
Write a 32-bit integer in big-endian byte order at buf + offset.
Parameters:
buf(ptr) — destination bufferoffset(i64) — byte offset within the buffervalue(i32) — the 32-bit value to write
Returns: offset + 4 (the new offset for chaining)
read_i32_be
function read_i32_be(buf: ptr, offset: i64) -> i32
Read a 32-bit big-endian integer from buf + offset.
Parameters:
buf(ptr) — source bufferoffset(i64) — byte offset within the buffer
Returns: the 32-bit value
write_i64_be
function write_i64_be(buf: ptr, offset: i64, value: i64) -> i64
Write a 64-bit integer in big-endian byte order at buf + offset.
Parameters:
buf(ptr) — destination bufferoffset(i64) — byte offset within the buffervalue(i64) — the 64-bit value to write
Returns: offset + 8 (the new offset for chaining)
read_i64_be
function read_i64_be(buf: ptr, offset: i64) -> i64
Read a 64-bit big-endian integer from buf + offset.
Parameters:
buf(ptr) — source bufferoffset(i64) — byte offset within the buffer
Returns: the 64-bit value
Little-Endian
write_i16_le
function write_i16_le(buf: ptr, offset: i64, value: i32) -> i64
Write a 16-bit integer in little-endian byte order at buf + offset.
Parameters:
buf(ptr) — destination bufferoffset(i64) — byte offset within the buffervalue(i32) — the 16-bit value to write (low 16 bits used)
Returns: offset + 2 (the new offset for chaining)
read_i16_le
function read_i16_le(buf: ptr, offset: i64) -> i32
Read a 16-bit little-endian integer from buf + offset.
Parameters:
buf(ptr) — source bufferoffset(i64) — byte offset within the buffer
Returns: the 16-bit value as an i32
write_i32_le
function write_i32_le(buf: ptr, offset: i64, value: i32) -> i64
Write a 32-bit integer in little-endian byte order at buf + offset.
Parameters:
buf(ptr) — destination bufferoffset(i64) — byte offset within the buffervalue(i32) — the 32-bit value to write
Returns: offset + 4 (the new offset for chaining)
read_i32_le
function read_i32_le(buf: ptr, offset: i64) -> i32
Read a 32-bit little-endian integer from buf + offset.
Parameters:
buf(ptr) — source bufferoffset(i64) — byte offset within the buffer
Returns: the 32-bit value
write_i64_le
function write_i64_le(buf: ptr, offset: i64, value: i64) -> i64
Write a 64-bit integer in little-endian byte order at buf + offset.
Parameters:
buf(ptr) — destination bufferoffset(i64) — byte offset within the buffervalue(i64) — the 64-bit value to write
Returns: offset + 8 (the new offset for chaining)
read_i64_le
function read_i64_le(buf: ptr, offset: i64) -> i64
Read a 64-bit little-endian integer from buf + offset.
Parameters:
buf(ptr) — source bufferoffset(i64) — byte offset within the buffer
Returns: the 64-bit value
vera:env
Environment variables and .env file loading.
import "vera:env"
Overview
Get and set environment variables, and load .env files. File loading parses KEY=VALUE lines, skipping comments (#) and blank lines. Optionally strips surrounding quotes (single or double) from values. Handles both LF and CRLF line endings.
Dependencies: vera:io, vera:memory, vera:string
Functions
env_get
function env_get(name: ptr, name_len: i64, out_buf: ptr, out_size: i64) -> i64
Get the value of an environment variable. If the value is longer than out_size, only out_size bytes are copied but the full length is still returned (the caller can detect truncation by comparing the return value to out_size).
Parameters:
name(ptr) — pointer to the variable namename_len(i64) — length of the variable nameout_buf(ptr) — buffer to write the value intoout_size(i64) — size of the output buffer
Returns: length of the value, or -1 if the variable is not set
env_set
function env_set(name: ptr, name_len: i64, value: ptr, value_len: i64) -> i32
Set an environment variable. Overwrites the value if the variable is already set.
Parameters:
name(ptr) — pointer to the variable namename_len(i64) — length of the variable namevalue(ptr) — pointer to the valuevalue_len(i64) — length of the value
Returns: 0 on success, -1 on error
env_load_file
function env_load_file(path: ptr, path_len: i64) -> i32
Load a .env file. Variables that are already set in the environment are not overwritten. The parser:
- Skips blank lines and lines starting with
# - Splits each line on the first
=character - Strips surrounding single or double quotes from values
Parameters:
path(ptr) — pointer to the file pathpath_len(i64) — length of the file path
Returns: 0 on success, -1 if the file cannot be read
env_load_file_override
function env_load_file_override(path: ptr, path_len: i64) -> i32
Load a .env file. Variables that are already set in the environment are overwritten. Otherwise identical to env_load_file.
Parameters:
path(ptr) — pointer to the file pathpath_len(i64) — length of the file path
Returns: 0 on success, -1 if the file cannot be read
vera:format
String formatting and buffer building. Every function takes (buf, offset, buf_size, ...) and returns the new offset. Bounds-checked — writes are clamped to buf_size.
import "vera:format"
Overview
vera:format provides an append-based string formatting API. Each fmt_* function writes formatted data into a buffer starting at the given offset and returns the updated offset. This pattern allows chaining calls to build complex strings incrementally.
The module is pure VERA — it uses zero compiler primitives and is built entirely on vera:string and vera:memory.
Dependencies
vera:string— forint_to_stringvera:memory— forcopy_memory
Functions
fmt_str
Append raw bytes to the buffer.
function fmt_str(buf: ptr, offset: i64, buf_size: i64, str: ptr, len: i64) -> i64
| Parameter | Type | Description |
|---|---|---|
buf | ptr | Destination buffer |
offset | i64 | Current write position |
buf_size | i64 | Total buffer capacity |
str | ptr | Source bytes to append |
len | i64 | Number of bytes to append |
Returns: New offset after the appended bytes. If appending would exceed buf_size, only the bytes that fit are written.
fmt_int
Append a signed i64 as decimal text.
function fmt_int(buf: ptr, offset: i64, buf_size: i64, value: i64) -> i64
| Parameter | Type | Description |
|---|---|---|
buf | ptr | Destination buffer |
offset | i64 | Current write position |
buf_size | i64 | Total buffer capacity |
value | i64 | Signed integer to format |
Returns: New offset. Examples: 42 → "42", -7 → "-7", 0 → "0".
fmt_uint
Append an unsigned i64 as decimal text.
function fmt_uint(buf: ptr, offset: i64, buf_size: i64, value: i64) -> i64
| Parameter | Type | Description |
|---|---|---|
buf | ptr | Destination buffer |
offset | i64 | Current write position |
buf_size | i64 | Total buffer capacity |
value | i64 | Unsigned integer to format |
Returns: New offset. Treats the value as unsigned — no sign prefix.
fmt_hex
Append an i64 as lowercase hexadecimal (no 0x prefix).
function fmt_hex(buf: ptr, offset: i64, buf_size: i64, value: i64) -> i64
| Parameter | Type | Description |
|---|---|---|
buf | ptr | Destination buffer |
offset | i64 | Current write position |
buf_size | i64 | Total buffer capacity |
value | i64 | Value to format as hex |
Returns: New offset. Examples: 255 → "ff", 57005 → "dead", 0 → "0".
fmt_char
Append a single byte.
function fmt_char(buf: ptr, offset: i64, buf_size: i64, byte: i8) -> i64
| Parameter | Type | Description |
|---|---|---|
buf | ptr | Destination buffer |
offset | i64 | Current write position |
buf_size | i64 | Total buffer capacity |
byte | i8 | Byte value to append |
Returns: New offset (offset + 1), or offset if the buffer is full.
fmt_bool
Append "true" or "false".
function fmt_bool(buf: ptr, offset: i64, buf_size: i64, value: i32) -> i64
| Parameter | Type | Description |
|---|---|---|
buf | ptr | Destination buffer |
offset | i64 | Current write position |
buf_size | i64 | Total buffer capacity |
value | i32 | Boolean value (0 = false, non-zero = true) |
Returns: New offset after appending "true" (4 bytes) or "false" (5 bytes).
fmt_float
Append an f64 as decimal with 6 fractional digits.
function fmt_float(buf: ptr, offset: i64, buf_size: i64, value: f64) -> i64
| Parameter | Type | Description |
|---|---|---|
buf | ptr | Destination buffer |
offset | i64 | Current write position |
buf_size | i64 | Total buffer capacity |
value | f64 | Floating-point value to format |
Returns: New offset. Format: [-]integer.fraction with exactly 6 fractional digits. Examples: 3.14159265 → "3.141592", -0.5 → "-0.500000", 0.0 → "0.000000".
fmt_pad_left
Right-align: prepend fill bytes to reach width, then append str.
function fmt_pad_left(buf: ptr, offset: i64, buf_size: i64, str: ptr, len: i64, width: i64, fill: i8) -> i64
| Parameter | Type | Description |
|---|---|---|
buf | ptr | Destination buffer |
offset | i64 | Current write position |
buf_size | i64 | Total buffer capacity |
str | ptr | String to right-align |
len | i64 | Length of string |
width | i64 | Minimum field width |
fill | i8 | Fill byte (e.g., ' ' or '0') |
Returns: New offset. If len >= width, appends str with no padding.
fmt_pad_right
Left-align: append str, then fill bytes to reach width.
function fmt_pad_right(buf: ptr, offset: i64, buf_size: i64, str: ptr, len: i64, width: i64, fill: i8) -> i64
| Parameter | Type | Description |
|---|---|---|
buf | ptr | Destination buffer |
offset | i64 | Current write position |
buf_size | i64 | Total buffer capacity |
str | ptr | String to left-align |
len | i64 | Length of string |
width | i64 | Minimum field width |
fill | i8 | Fill byte (e.g., ' ' or '0') |
Returns: New offset. If len >= width, appends str with no padding.
Usage Pattern
import "vera:format"
import "vera:memory"
import "vera:io"
function main() -> i32 {
; Allocate a 256-byte buffer
call buf, allocate, 256
constant.i64 buf_size, 256
constant.i64 zero, 0
; Build a formatted string
copy.i64 off, zero
constant.ptr hello, _greeting
constant.i64 hello_len, 7
call off, fmt_str, buf, off, buf_size, hello, hello_len
constant.i64 value, 42
call off, fmt_int, buf, off, buf_size, value
constant.i8 newline, 10
call off, fmt_char, buf, off, buf_size, newline
; Write the result
call _, write_stdout, buf, off
; Clean up
call _, free, buf, buf_size
constant.i32 exit_code, 0
return exit_code
}
data _greeting: "Hello, "
vera:io
Console I/O and file operations.
import "vera:io"
Overview
Provides console I/O and file operations. All functions are built on compiler primitives (__vera_sys_*) which the compiler maps to platform-specific syscalls.
Dependencies: None (uses primitives directly)
Constants
File Open Flags
Combine with bitwise_or when calling open_file:
| Constant | Value | Description |
|---|---|---|
FILE_READ | 1 | Open for reading |
FILE_WRITE | 2 | Open for writing |
FILE_CREATE | 4 | Create if it does not exist |
FILE_TRUNCATE | 8 | Truncate to zero length if it exists |
FILE_APPEND | 16 | Writes append to end of file |
Seek Origins
| Constant | Value | Description |
|---|---|---|
SEEK_START | 0 | Seek from beginning of file |
SEEK_CURRENT | 1 | Seek from current position |
SEEK_END | 2 | Seek from end of file |
Functions
Console I/O
write_stdout
function write_stdout(data: ptr, length: i64) -> i64
Write bytes to standard output (fd 1).
Parameters:
data(ptr) — pointer to the byte buffer to writelength(i64) — number of bytes to write
Returns: number of bytes actually written
write_stderr
function write_stderr(data: ptr, length: i64) -> i64
Write bytes to standard error (fd 2).
Parameters:
data(ptr) — pointer to the byte buffer to writelength(i64) — number of bytes to write
Returns: number of bytes actually written
read_stdin
function read_stdin(buffer: ptr, max_length: i64) -> i64
Read bytes from standard input (fd 0).
Parameters:
buffer(ptr) — pointer to the buffer to read intomax_length(i64) — maximum number of bytes to read
Returns: number of bytes actually read
File Operations
open_file
function open_file(path: ptr, flags: i32) -> i32
Open a file with the specified flags. The path must be a null-terminated string. Uses default file permissions 0644 (owner read/write, group/other read).
Parameters:
path(ptr) — null-terminated file pathflags(i32) — combination of file open flags (see Constants above)
Returns: file descriptor on success, or a negative value on error
create_file
function create_file(path: ptr) -> i32
Create a new file (or truncate an existing one) for writing. Equivalent to opening with FILE_WRITE | FILE_CREATE | FILE_TRUNCATE (flags = 14). The path must be null-terminated.
Parameters:
path(ptr) — null-terminated file path
Returns: file descriptor on success, or a negative value on error
close_file
function close_file(fd: i32) -> i32
Close an open file descriptor.
Parameters:
fd(i32) — file descriptor to close
Returns: 0 on success, or a negative value on error
write_file
function write_file(fd: i32, data: ptr, length: i64) -> i64
Write bytes to an open file.
Parameters:
fd(i32) — file descriptordata(ptr) — pointer to the byte buffer to writelength(i64) — number of bytes to write
Returns: number of bytes actually written
read_file
function read_file(fd: i32, buffer: ptr, max_length: i64) -> i64
Read bytes from an open file into a buffer.
Parameters:
fd(i32) — file descriptorbuffer(ptr) — pointer to the buffer to read intomax_length(i64) — maximum number of bytes to read
Returns: number of bytes actually read
delete_file
function delete_file(path: ptr) -> i32
Delete a file from the filesystem. The path must be null-terminated.
Parameters:
path(ptr) — null-terminated file path
Returns: 0 on success, or a negative value on error
rename_file
function rename_file(old_path: ptr, new_path: ptr) -> i32
Rename or move a file. Both paths must be null-terminated.
Parameters:
old_path(ptr) — null-terminated current file pathnew_path(ptr) — null-terminated new file path
Returns: 0 on success, or a negative value on error
file_size
function file_size(path: ptr) -> i64
Get the size of a file in bytes. The path must be null-terminated.
Parameters:
path(ptr) — null-terminated file path
Returns: file size in bytes, or a negative value on error
seek_file
function seek_file(fd: i32, offset: i64, origin: i32) -> i64
Reposition the read/write offset of an open file.
Parameters:
fd(i32) — file descriptoroffset(i64) — byte offset relative tooriginorigin(i32) — seek origin: 0 = from start, 1 = from current position, 2 = from end
Returns: the new absolute position in the file, or a negative value on error
vera:math
Mathematical functions for f64 floating-point arithmetic.
import "vera:math"
Overview
Provides constants, utility functions, and transcendental functions for f64 floating-point arithmetic. All functions operate on IEEE 754 double-precision values. Trigonometric functions use Cody-Waite range reduction and minimax polynomial approximations. Exponential and logarithmic functions use Horner-form polynomial evaluation with FMA instructions.
Dependencies: None (pure VERA)
Functions
Constants
math_pi
function math_pi() -> f64
Returns the constant pi (3.14159265358979323846).
math_e
function math_e() -> f64
Returns Euler's number e (2.71828182845904523536).
math_ln2
function math_ln2() -> f64
Returns the natural logarithm of 2 (0.69314718055994530942).
math_ln10
function math_ln10() -> f64
Returns the natural logarithm of 10 (2.30258509299404568402).
Utility
abs_f64
function abs_f64(x: f64) -> f64
Absolute value of a float.
Parameters:
x(f64) — input value
Returns: |x|
floor_f64
function floor_f64(x: f64) -> f64
Round toward negative infinity. Returns NaN unchanged. Values with magnitude >= 2^53 are returned unchanged (already integers at f64 precision).
Parameters:
x(f64) — input value
Returns: the largest integer value not greater than x
ceil_f64
function ceil_f64(x: f64) -> f64
Round toward positive infinity. Implemented as -floor(-x).
Parameters:
x(f64) — input value
Returns: the smallest integer value not less than x
round_f64
function round_f64(x: f64) -> f64
Round to nearest integer (half rounds up). Implemented as floor(x + 0.5).
Parameters:
x(f64) — input value
Returns: the nearest integer to x
min_f64
function min_f64(a: f64, b: f64) -> f64
Minimum of two floats.
Parameters:
a(f64) — first valueb(f64) — second value
Returns: the smaller of a and b
max_f64
function max_f64(a: f64, b: f64) -> f64
Maximum of two floats.
Parameters:
a(f64) — first valueb(f64) — second value
Returns: the larger of a and b
clamp_f64
function clamp_f64(x: f64, lo: f64, hi: f64) -> f64
Clamp x to the range [lo, hi]. Equivalent to min(max(x, lo), hi).
Parameters:
x(f64) — value to clamplo(f64) — lower boundhi(f64) — upper bound
Returns: x clamped to [lo, hi]
sqrt_f64
function sqrt_f64(x: f64) -> f64
Square root. Wraps the ISA's square_root.f64 instruction.
Parameters:
x(f64) — input value (must be >= 0)
Returns: the square root of x
degrees_to_radians
function degrees_to_radians(deg: f64) -> f64
Convert degrees to radians.
Parameters:
deg(f64) — angle in degrees
Returns: angle in radians
radians_to_degrees
function radians_to_degrees(rad: f64) -> f64
Convert radians to degrees.
Parameters:
rad(f64) — angle in radians
Returns: angle in degrees
Trigonometric
sin_f64
function sin_f64(x: f64) -> f64
Sine of x (radians). Uses Cody-Waite range reduction to [-pi/4, pi/4] and minimax polynomial kernels. Returns NaN unchanged.
Parameters:
x(f64) — angle in radians
Returns: sin(x)
cos_f64
function cos_f64(x: f64) -> f64
Cosine of x (radians). Uses the same range reduction as sin_f64. Returns NaN unchanged.
Parameters:
x(f64) — angle in radians
Returns: cos(x)
tan_f64
function tan_f64(x: f64) -> f64
Tangent of x (radians). Computed as sin(x) / cos(x).
Parameters:
x(f64) — angle in radians
Returns: tan(x)
Exponential / Logarithmic
exp_f64
function exp_f64(x: f64) -> f64
Exponential function e^x. Uses range reduction via x = k * ln2 + r and a minimax polynomial for exp(r). Handles overflow (x > ~709.78 returns +infinity) and underflow (x < ~-745.13 returns 0). Returns NaN unchanged.
Parameters:
x(f64) — exponent
Returns: e^x
log_f64
function log_f64(x: f64) -> f64
Natural logarithm of x. Decomposes x into mantissa and exponent, then uses a Horner-form polynomial on s = (m-1)/(m+1). Returns NaN for negative inputs, -infinity for zero, and NaN unchanged.
Parameters:
x(f64) — input value (must be > 0 for a meaningful result)
Returns: ln(x)
log2_f64
function log2_f64(x: f64) -> f64
Base-2 logarithm of x. Computed as log(x) * (1/ln2).
Parameters:
x(f64) — input value (must be > 0 for a meaningful result)
Returns: log2(x)
log10_f64
function log10_f64(x: f64) -> f64
Base-10 logarithm of x. Computed as log(x) * (1/ln10).
Parameters:
x(f64) — input value (must be > 0 for a meaningful result)
Returns: log10(x)
Power
pow_f64
function pow_f64(base: f64, exponent: f64) -> f64
Raise base to the power of exponent. Computed as exp(exponent * log(base)) for positive bases. Handles special cases:
- Any base raised to 0 returns 1.0 (including NaN^0).
- 1.0 raised to any exponent returns 1.0.
- 0.0 raised to a positive exponent returns 0.0; to a negative exponent returns +infinity.
- Negative bases with integer exponents use sign parity; non-integer exponents return NaN.
Parameters:
base(f64) — the baseexponent(f64) — the exponent
Returns: base^exponent
vera:memory
Heap allocation and memory operations.
import "vera:memory"
Overview
Provides heap allocation (allocate/free) and bulk memory operations (copy, zero, set). All functions are built on compiler primitives. The allocator implementation lives in the compiler/runtime, not in VERA code. __vera_sys_alloc/__vera_sys_free use platform-appropriate strategies (mmap+freelist on Linux/macOS, VirtualAlloc+HeapAlloc on Windows).
Dependencies: None (uses primitives directly)
Functions
Allocation
allocate
function allocate(size: i64) -> ptr
Allocate size bytes of heap memory with default alignment (8 bytes).
Parameters:
size(i64) — number of bytes to allocate
Returns: pointer to the allocated block, or null (0) on failure
allocate_aligned
function allocate_aligned(size: i64, align: i64) -> ptr
Allocate size bytes with a specific alignment. Alignment must be a power of 2.
Parameters:
size(i64) — number of bytes to allocatealign(i64) — alignment in bytes (must be a power of 2)
Returns: pointer to the allocated block, or null (0) on failure
free
function free(addr: ptr, size: i64)
Free a previously allocated block. The caller must pass the same size that was used for the original allocate call.
Parameters:
addr(ptr) — pointer to the block to freesize(i64) — size of the block (must match the allocation size)
free_aligned
function free_aligned(addr: ptr, size: i64, align: i64)
Free a block that was allocated with allocate_aligned. The caller must pass the same size and alignment used for allocation.
Parameters:
addr(ptr) — pointer to the block to freesize(i64) — size of the block (must match the allocation size)align(i64) — alignment of the block (must match the allocation alignment)
Bulk Memory Operations
copy_memory
function copy_memory(dest: ptr, src: ptr, count: i64)
Copy count bytes from src to dest. The source and destination regions must not overlap.
Parameters:
dest(ptr) — destination pointersrc(ptr) — source pointercount(i64) — number of bytes to copy
set_memory
function set_memory(dest: ptr, value: i8, count: i64)
Set count bytes at dest to value.
Parameters:
dest(ptr) — destination pointervalue(i8) — byte value to fill withcount(i64) — number of bytes to set
zero_memory
function zero_memory(dest: ptr, count: i64)
Zero count bytes at dest. Equivalent to set_memory(dest, 0, count).
Parameters:
dest(ptr) — destination pointercount(i64) — number of bytes to zero
vera:net
TCP/UDP socket operations.
import "vera:net"
Overview
Provides portable socket operations for TCP and UDP clients and servers. All functions are built on compiler primitives (__vera_sys_*) which the compiler maps to platform-specific networking APIs (POSIX sockets on Linux/macOS, Winsock on Windows).
Dependencies: None (uses primitives directly)
Constants
These are VERA-portable values. The compiler maps them to platform equivalents.
| Constant | Value | Description |
|---|---|---|
AF_INET | 2 | IPv4 address family |
SOCK_STREAM | 1 | TCP (stream) socket type |
SOCK_DGRAM | 2 | UDP (datagram) socket type |
SHUT_READ | 0 | Shutdown reading |
SHUT_WRITE | 1 | Shutdown writing |
SHUT_BOTH | 2 | Shutdown both reading and writing |
Functions
Socket Creation
tcp_socket
function tcp_socket() -> i32
Create a TCP (stream) socket using IPv4.
Returns: socket file descriptor, or -1 on error
udp_socket
function udp_socket() -> i32
Create a UDP (datagram) socket using IPv4.
Returns: socket file descriptor, or -1 on error
Client Operations
tcp_connect
function tcp_connect(fd: i32, ip: ptr, ip_len: i32, port: i32) -> i32
Connect a socket to an IPv4 address and port. The IP address must be a null-terminated string (e.g., "127.0.0.1\0").
Parameters:
fd(i32) — socket file descriptorip(ptr) — null-terminated IPv4 address stringip_len(i32) — length of the IP string including the null terminatorport(i32) — port number to connect to
Returns: 0 on success, -1 on error
Server Operations
tcp_bind
function tcp_bind(fd: i32, ip: ptr, ip_len: i32, port: i32) -> i32
Bind a socket to an IPv4 address and port. The IP address must be a null-terminated string; use "0.0.0.0\0" to bind to all interfaces.
Parameters:
fd(i32) — socket file descriptorip(ptr) — null-terminated IPv4 address stringip_len(i32) — length of the IP string including the null terminatorport(i32) — port number to bind to
Returns: 0 on success, -1 on error
tcp_listen
function tcp_listen(fd: i32, backlog: i32) -> i32
Start listening for incoming connections on a bound socket.
Parameters:
fd(i32) — socket file descriptor (must be bound)backlog(i32) — maximum length of the pending connection queue
Returns: 0 on success, -1 on error
tcp_accept
function tcp_accept(fd: i32) -> i32
Accept an incoming connection on a listening socket. Blocks until a connection arrives.
Parameters:
fd(i32) — listening socket file descriptor
Returns: new client socket file descriptor, or -1 on error
Data Transfer
socket_send
function socket_send(fd: i32, data: ptr, length: i64) -> i64
Send data on a connected socket.
Parameters:
fd(i32) — socket file descriptordata(ptr) — pointer to the data to sendlength(i64) — number of bytes to send
Returns: number of bytes sent, or -1 on error
socket_recv
function socket_recv(fd: i32, buffer: ptr, max_length: i64) -> i64
Receive data from a connected socket into a buffer.
Parameters:
fd(i32) — socket file descriptorbuffer(ptr) — pointer to the buffer to receive intomax_length(i64) — maximum number of bytes to receive
Returns: number of bytes received, 0 for EOF (peer closed connection), or -1 on error
Socket Management
socket_close
function socket_close(fd: i32) -> i32
Close a socket.
Parameters:
fd(i32) — socket file descriptor
Returns: 0 on success, or a negative value on error
socket_shutdown
function socket_shutdown(fd: i32, how: i32) -> i32
Shut down part of a socket connection.
Parameters:
fd(i32) — socket file descriptorhow(i32) — shutdown mode: 0 = stop reading, 1 = stop writing, 2 = stop both
Returns: 0 on success, or a negative value on error
socket_poll
function socket_poll(fd: i32, events: i32, timeout_ms: i32) -> i32
Poll a socket for readiness.
Parameters:
fd(i32) — socket file descriptorevents(i32) — bitmask of what to check: 1 = readable, 2 = writable, 3 = bothtimeout_ms(i32) — milliseconds to wait (-1 = block forever, 0 = non-blocking check)
Returns: bitmask of ready events (1 = readable, 2 = writable), 0 on timeout, -1 on error
set_reuseaddr
function set_reuseaddr(fd: i32) -> i32
Enable SO_REUSEADDR on a socket. Useful for servers to avoid "address already in use" errors when restarting.
Parameters:
fd(i32) — socket file descriptor
Returns: 0 on success
vera:net/dns
DNS hostname resolution.
import "vera:net/dns"
Overview
Resolves hostnames to IPv4 address strings using DNS A record queries over UDP. Handles three cases:
- Raw IP addresses (digits and dots only) -- passed through directly without a DNS query.
- "localhost" -- returns
"127.0.0.1"immediately. - Hostnames -- performs a DNS A record query via UDP to a nameserver and parses the response.
Uses connected UDP sockets for DNS communication. The default nameserver is 8.8.8.8 (Google Public DNS). A 5-second timeout is applied when waiting for DNS responses.
Dependencies: vera:net, vera:binary, vera:string, vera:memory
Functions
dns_resolve
function dns_resolve(hostname: ptr, hostname_len: i64, out_buf: ptr, out_size: i64) -> i64
Resolve a hostname to an IPv4 address string using the default DNS server (8.8.8.8).
Parameters:
hostname(ptr) — pointer to the hostname bytes (not necessarily null-terminated)hostname_len(i64) — length of the hostnameout_buf(ptr) — buffer to write the dotted-decimal IP string intoout_size(i64) — size of the output buffer
Returns: length of the IP string written to out_buf, or -1 on failure
dns_resolve_with
function dns_resolve_with(hostname: ptr, hostname_len: i64, server_ip: ptr, server_ip_len: i64, out_buf: ptr, out_size: i64) -> i64
Resolve a hostname to an IPv4 address string using a specific DNS server.
Parameters:
hostname(ptr) — pointer to the hostname byteshostname_len(i64) — length of the hostnameserver_ip(ptr) — IP address string of the DNS server (e.g.,"8.8.8.8")server_ip_len(i64) — length of the server IP stringout_buf(ptr) — buffer to write the dotted-decimal IP string intoout_size(i64) — size of the output buffer
Returns: length of the IP string written to out_buf, or -1 on failure
vera:string
String and byte sequence operations.
import "vera:string"
Overview
All functions operate on (ptr, length) byte buffers. VERA has no dedicated string type — strings are byte sequences with explicit lengths. Null terminators are only relevant when interfacing with C-style APIs.
Dependencies: None (pure VERA)
Functions
Length
string_length
function string_length(buf: ptr) -> i64
Get the length of a null-terminated string (not counting the null byte). Scans forward from buf until a zero byte is found.
Parameters:
buf(ptr) — pointer to a null-terminated string
Returns: number of bytes before the null terminator
Search
byte_find
function byte_find(buf: ptr, len: i64, needle: i8) -> i64
Find the first occurrence of a single byte in a buffer.
Parameters:
buf(ptr) — pointer to the buffer to searchlen(i64) — length of the bufferneedle(i8) — byte value to search for
Returns: index of the first occurrence, or -1 if not found
bytes_find
function bytes_find(haystack: ptr, haystack_len: i64, needle: ptr, needle_len: i64) -> i64
Find the first occurrence of a byte subsequence in a buffer. If the needle is empty, returns 0.
Parameters:
haystack(ptr) — pointer to the buffer to searchhaystack_len(i64) — length of the bufferneedle(ptr) — pointer to the subsequence to findneedle_len(i64) — length of the subsequence
Returns: starting index of the first match, or -1 if not found
Comparison
bytes_equal
function bytes_equal(a: ptr, b: ptr, len: i64) -> i32
Compare two byte sequences for equality.
Parameters:
a(ptr) — pointer to the first bufferb(ptr) — pointer to the second bufferlen(i64) — number of bytes to compare
Returns: 1 if all bytes are equal, 0 otherwise
bytes_compare
function bytes_compare(a: ptr, a_len: i64, b: ptr, b_len: i64) -> i32
Lexicographic comparison of two byte sequences. Compares byte-by-byte up to the shorter length; if all compared bytes are equal, the shorter sequence is considered "less".
Parameters:
a(ptr) — pointer to the first buffera_len(i64) — length of the first bufferb(ptr) — pointer to the second bufferb_len(i64) — length of the second buffer
Returns: -1 if a < b, 0 if equal, 1 if a > b
starts_with
function starts_with(buf: ptr, buf_len: i64, prefix: ptr, prefix_len: i64) -> i32
Check if a buffer starts with a given prefix.
Parameters:
buf(ptr) — pointer to the bufferbuf_len(i64) — length of the bufferprefix(ptr) — pointer to the prefixprefix_len(i64) — length of the prefix
Returns: 1 if buf[0..prefix_len) equals prefix, 0 otherwise
Parsing
parse_int
function parse_int(buf: ptr, len: i64) -> i64
Parse ASCII decimal digits into an i64. Stops at the first non-digit character. Handles an optional leading sign: "-123" yields -123, "+456" yields 456.
Parameters:
buf(ptr) — pointer to the ASCII stringlen(i64) — length of the string
Returns: the parsed integer value (0 for empty input)
Formatting
int_to_string
function int_to_string(value: i64, buf: ptr) -> i64
Convert an i64 to its ASCII decimal representation. Writes the digits into buf and returns the number of characters written. Handles negative numbers (writes a leading '-'). The buffer must be at least 20 bytes.
Parameters:
value(i64) — the integer to convertbuf(ptr) — output buffer (must be at least 20 bytes)
Returns: number of characters written
Case Conversion
to_lowercase
function to_lowercase(buf: ptr, len: i64)
Convert ASCII uppercase letters (A-Z) to lowercase in-place. Non-ASCII bytes and non-letter bytes are left unchanged.
Parameters:
buf(ptr) — pointer to the buffer to modify in-placelen(i64) — length of the buffer
to_uppercase
function to_uppercase(buf: ptr, len: i64)
Convert ASCII lowercase letters (a-z) to uppercase in-place. Non-ASCII bytes and non-letter bytes are left unchanged.
Parameters:
buf(ptr) — pointer to the buffer to modify in-placelen(i64) — length of the buffer
vera:thread
Threading and synchronization.
import "vera:thread"
Overview
Provides thread creation/joining, mutexes, and sleep. All functions are built on compiler primitives (__vera_sys_*) which the compiler maps to platform-specific threading APIs (pthreads on Linux/macOS, Win32 threads on Windows).
Dependencies: None (uses primitives directly)
Functions
Thread Operations
thread_spawn
function thread_spawn(func: ptr, arg: ptr) -> i64
Spawn a new thread running the given function with an argument. The function must have the signature function(arg: ptr) -> i64.
Parameters:
func(ptr) — pointer to the thread entry functionarg(ptr) — argument to pass to the function
Returns: a thread handle used for joining
thread_join
function thread_join(handle: i64) -> i64
Wait for a thread to finish and return its exit value.
Parameters:
handle(i64) — thread handle returned bythread_spawn
Returns: the thread's return value
thread_sleep
function thread_sleep(milliseconds: i64) -> i32
Sleep the current thread for the given number of milliseconds.
Parameters:
milliseconds(i64) — duration to sleep
Returns: 0 on success
Mutex Operations
mutex_create
function mutex_create() -> i64
Create a new mutex.
Returns: a mutex handle
mutex_lock
function mutex_lock(handle: i64) -> i32
Lock a mutex. Blocks until the mutex is acquired.
Parameters:
handle(i64) — mutex handle returned bymutex_create
Returns: 0 on success
mutex_trylock
function mutex_trylock(handle: i64) -> i32
Try to lock a mutex without blocking.
Parameters:
handle(i64) — mutex handle returned bymutex_create
Returns: 0 if acquired, non-zero if the mutex is already locked
mutex_unlock
function mutex_unlock(handle: i64) -> i32
Unlock a mutex.
Parameters:
handle(i64) — mutex handle returned bymutex_create
Returns: 0 on success
mutex_destroy
function mutex_destroy(handle: i64) -> i32
Destroy a mutex and free its resources.
Parameters:
handle(i64) — mutex handle returned bymutex_create
Returns: 0 on success
Packages
VERA packages provide higher-level functionality under the pkg: namespace. They are built entirely on the standard library and contain no compiler primitives.
import "pkg:http/client"
Available Packages
| Package | Description |
|---|---|
| pkg:http/client | HTTP/1.1 client (GET and POST) |
| pkg:http/server | HTTP/1.1 server toolkit |
| pkg:data/json | JSON parsing, construction, and serialization |
| pkg:db/postgres | PostgreSQL wire protocol v3 client |
Design Principles
- Built on stdlib: Packages use only
vera:*modules — no direct compiler primitives. - Agent-contributed: The package registry is maintained by agents, ensuring consistent quality and conventions.
- Static linking: Packages are linked at compile time. No runtime dependencies.
pkg:data/json
JSON parsing, construction, and serialization.
import "pkg:data/json"
Overview
Provides a complete JSON library for parsing, constructing, serializing, and querying JSON values. All values are heap-allocated 24-byte nodes with a type tag, payload, and extra field. Numbers are integer-only (i64) in this version.
Value layout (24 bytes):
| Offset | Field | Description |
|---|---|---|
| 0 | tag (i8) | Type tag: 0=null, 1=bool, 2=number, 3=string, 4=array, 5=object |
| 8 | payload (i64 or ptr) | Value data or pointer to content |
| 16 | extra (i64) | Auxiliary data (e.g., string length, array count) |
Dependencies: vera:memory, vera:string
Construction
json_null
function json_null() -> ptr
Create a JSON null value.
Returns: pointer to a new null node.
json_bool
function json_bool(v: i32) -> ptr
Create a JSON boolean value.
Parameters:
v(i32) — 1 for true, 0 for false
Returns: pointer to a new boolean node.
json_number
function json_number(v: i64) -> ptr
Create a JSON number value (integer-only).
Parameters:
v(i64) — the integer value
Returns: pointer to a new number node.
json_string
function json_string(data: ptr, len: i64) -> ptr
Create a JSON string value. The string data is copied to the heap.
Parameters:
data(ptr) — pointer to the string byteslen(i64) — length of the string in bytes
Returns: pointer to a new string node.
json_array_new
function json_array_new() -> ptr
Create an empty JSON array. The array starts with an initial capacity of 4 and grows automatically when elements are pushed.
Returns: pointer to a new array node.
json_array_push
function json_array_push(arr: ptr, val: ptr)
Append a value to a JSON array. The array buffer is automatically doubled in capacity if full.
Parameters:
arr(ptr) — pointer to the array nodeval(ptr) — pointer to the value node to append
json_object_new
function json_object_new() -> ptr
Create an empty JSON object. The object starts with an initial capacity of 4 entries and grows automatically.
Returns: pointer to a new object node.
json_object_set
function json_object_set(obj: ptr, key: ptr, key_len: i64, val: ptr)
Set a key-value pair in a JSON object. If the key already exists, the old value is freed and replaced. The key is copied to the heap.
Parameters:
obj(ptr) — pointer to the object nodekey(ptr) — pointer to the key string byteskey_len(i64) — length of the key stringval(ptr) — pointer to the value node to associate with the key
Type Inspection
json_type
function json_type(value: ptr) -> i8
Get the type tag of a JSON value.
Parameters:
value(ptr) — pointer to a JSON node
Returns: the type tag:
| Tag | Type |
|---|---|
| 0 | null |
| 1 | bool |
| 2 | number |
| 3 | string |
| 4 | array |
| 5 | object |
Value Access
json_get_bool
function json_get_bool(value: ptr) -> i32
Extract the boolean value from a JSON bool node.
Parameters:
value(ptr) — pointer to a boolean JSON node
Returns: 1 for true, 0 for false, or -1 if the node is not a boolean.
json_get_number
function json_get_number(value: ptr) -> i64
Extract the integer value from a JSON number node.
Parameters:
value(ptr) — pointer to a number JSON node
Returns: the integer value, or 0 if the node is not a number.
json_get_string
function json_get_string(value: ptr, out_len: ptr) -> ptr
Get a pointer to the string data inside a JSON string node.
Parameters:
value(ptr) — pointer to a string JSON nodeout_len(ptr) — pointer to an i64 where the string length will be written
Returns: pointer to the string data, or null if the node is not a string. The length is written to out_len (0 if not a string).
json_array_length
function json_array_length(value: ptr) -> i64
Get the number of elements in a JSON array.
Parameters:
value(ptr) — pointer to an array JSON node
Returns: the element count, or -1 if the node is not an array.
json_array_get
function json_array_get(value: ptr, index: i64) -> ptr
Get an element from a JSON array by index.
Parameters:
value(ptr) — pointer to an array JSON nodeindex(i64) — zero-based index of the element
Returns: pointer to the element node, or null if the node is not an array or the index is out of bounds.
json_object_get
function json_object_get(value: ptr, key: ptr, key_len: i64) -> ptr
Look up a value in a JSON object by key. Performs a linear scan comparing keys by byte equality.
Parameters:
value(ptr) — pointer to an object JSON nodekey(ptr) — pointer to the key string to search forkey_len(i64) — length of the key string
Returns: pointer to the value node, or null if the key was not found or the node is not an object.
Parsing and Serialization
json_parse
function json_parse(buf: ptr, len: i64) -> ptr
Parse a JSON document from a byte buffer. Supports all JSON types: strings (with escape sequences), numbers (integer portion), booleans, null, arrays, and objects.
Parameters:
buf(ptr) — pointer to the JSON textlen(i64) — length of the JSON text in bytes
Returns: pointer to the root JSON node, or null on parse error.
json_serialize
function json_serialize(value: ptr, buf: ptr, buf_size: i64) -> i64
Serialize a JSON value tree into a byte buffer. Produces compact JSON (no whitespace). Strings are escaped according to JSON rules (\", \\, \n, \r, \t).
Parameters:
value(ptr) — pointer to the root JSON nodebuf(ptr) — caller-allocated output bufferbuf_size(i64) — size of the output buffer in bytes
Returns: number of bytes written to buf, or 0 if the value is null.
Memory
json_free
function json_free(value: ptr)
Recursively free a JSON value and all memory it owns. For strings, frees the string data. For arrays, frees each element recursively, then the element buffer. For objects, frees each key and value recursively, then the entry buffer. Safe to call with a null pointer.
Parameters:
value(ptr) — pointer to the JSON node to free (or null)
pkg:db/postgres
PostgreSQL wire protocol v3 client.
import "pkg:db/postgres"
Overview
Minimal PostgreSQL driver for CRUD operations using the simple query protocol. Connects via TCP, authenticates with cleartext password, executes SQL queries, and returns structured result sets. Hostnames are resolved via vera:net/dns, so both IP addresses and domain names are supported.
Connection string format: postgres://user:password@host:port/database
Limitations (v1):
- Cleartext password authentication only (no MD5/SCRAM)
- No TLS/SSL
- No prepared statements (simple query protocol only)
- All values returned as text strings
Dependencies: vera:net, vera:net/dns, vera:memory, vera:string, vera:binary
Functions
pg_connect
function pg_connect(url: ptr, url_len: i64) -> ptr
Connect to a PostgreSQL server. Parses the connection string, resolves the hostname via DNS, opens a TCP connection, sends a StartupMessage, and handles password authentication.
Parameters:
url(ptr) — pointer to the connection string (e.g.,"postgres://user:pass@host:5432/dbname")url_len(i64) — length of the connection string in bytes
Returns: an opaque connection handle (ptr), or null on failure.
pg_close
function pg_close(conn: ptr)
Close a PostgreSQL connection. Sends a Terminate message to the server, closes the TCP socket, and frees all connection memory.
Parameters:
conn(ptr) — the connection handle returned bypg_connect
pg_exec
function pg_exec(conn: ptr, sql: ptr, sql_len: i64) -> i64
Execute a non-SELECT SQL statement (INSERT, UPDATE, DELETE, CREATE, DROP, etc.) using the simple query protocol.
Parameters:
conn(ptr) — the connection handlesql(ptr) — pointer to the SQL statement textsql_len(i64) — length of the SQL statement in bytes
Returns: the number of affected rows, or -1 on error. For DDL statements (CREATE, DROP), returns 0 on success.
pg_query
function pg_query(conn: ptr, sql: ptr, sql_len: i64) -> ptr
Execute a SELECT statement and return a result set. The caller must call pg_result_free when done with the result.
Parameters:
conn(ptr) — the connection handlesql(ptr) — pointer to the SQL query textsql_len(i64) — length of the SQL query in bytes
Returns: an opaque result set handle (ptr), or null on error.
pg_result_rows
function pg_result_rows(result: ptr) -> i64
Get the number of rows in a result set.
Parameters:
result(ptr) — the result set handle returned bypg_query
Returns: the number of rows.
pg_result_columns
function pg_result_columns(result: ptr) -> i64
Get the number of columns in a result set.
Parameters:
result(ptr) — the result set handle returned bypg_query
Returns: the number of columns.
pg_result_column_name
function pg_result_column_name(result: ptr, col: i64, out_buf: ptr, out_size: i64) -> i64
Get a column name by index. Copies the name into the caller-provided buffer.
Parameters:
result(ptr) — the result set handlecol(i64) — zero-based column indexout_buf(ptr) — caller-allocated buffer for the column nameout_size(i64) — size ofout_bufin bytes
Returns: the number of bytes written to out_buf, or -1 if col is out of range.
pg_result_get
function pg_result_get(result: ptr, row: i64, col: i64, out_buf: ptr, out_size: i64) -> i64
Get a cell value as a string. Copies the value into the caller-provided buffer.
Parameters:
result(ptr) — the result set handlerow(i64) — zero-based row indexcol(i64) — zero-based column indexout_buf(ptr) — caller-allocated buffer for the cell valueout_size(i64) — size ofout_bufin bytes
Returns: bytes written to out_buf, 0 for an empty string, or -1 for SQL NULL.
pg_result_is_null
function pg_result_is_null(result: ptr, row: i64, col: i64) -> i32
Check if a cell value is SQL NULL.
Parameters:
result(ptr) — the result set handlerow(i64) — zero-based row indexcol(i64) — zero-based column index
Returns: 1 if the value is NULL, 0 if not NULL, or -1 if the indices are out of range.
pg_result_free
function pg_result_free(result: ptr)
Free a result set and all associated memory. Must be called when you are done reading query results.
Parameters:
result(ptr) — the result set handle to free
Example
import "pkg:db/postgres"
import "vera:memory"
import "vera:io"
data conn_str: "postgres://myuser:mypass@localhost:5432/mydb"
data select_sql: "SELECT id, name FROM users"
data insert_sql: "INSERT INTO users (name) VALUES ('alice')"
function main() -> i32 {
; Connect
constant.ptr p_url, conn_str
constant.i64 url_len, 44
call conn, pg_connect, p_url, url_len
; Execute a write statement
constant.ptr p_insert, insert_sql
constant.i64 insert_len, 41
call affected, pg_exec, conn, p_insert, insert_len
; Execute a query
constant.ptr p_select, select_sql
constant.i64 select_len, 26
call result, pg_query, conn, p_select, select_len
; Read results
call num_rows, pg_result_rows, result
call num_cols, pg_result_columns, result
; Iterate rows
constant.i64 zero, 0
constant.i64 one, 1
copy.i64 row, zero
stack_allocate val_buf, 256, 8
constant.i64 val_size, 256
.row_loop:
compare_signed_greater_equal.i64 done, row, num_rows
branch_if done, .cleanup, .read_row
.read_row:
call len, pg_result_get, result, row, zero, val_buf, val_size
call _, print, val_buf, len
add.i64 row, row, one
jump .row_loop
.cleanup:
; Free result set and close connection
call _, pg_result_free, result
call _, pg_close, conn
constant.i32 exit_ok, 0
return exit_ok
}
pkg:http/client
HTTP/1.1 client over raw TCP sockets.
import "pkg:http/client"
Overview
Provides HTTP GET and POST over TCP. Supports hostnames (resolved via DNS), IP addresses, and "localhost". Built on vera:net for sockets, vera:net/dns for hostname resolution, vera:memory for buffer operations, and vera:string for byte scanning and parsing.
Dependencies: vera:net, vera:net/dns, vera:memory, vera:string
Functions
http_get
function http_get(host: ptr, host_len: i32, port: i32, path: ptr, path_len: i64, resp_buf: ptr, resp_buf_size: i64) -> i64
Perform an HTTP/1.1 GET request.
Parameters:
host(ptr) — pointer to null-terminated host string (IP or hostname, e.g.,"example.com\0")host_len(i32) — length of the host string, including the null terminatorport(i32) — TCP port (usually 80)path(ptr) — pointer to path string (e.g.,"/index.html")path_len(i64) — length of the path stringresp_buf(ptr) — caller-allocated buffer for the raw HTTP responseresp_buf_size(i64) — size ofresp_bufin bytes
Returns: total bytes written to resp_buf, or -1 on error.
http_post
function http_post(host: ptr, host_len: i32, port: i32, path: ptr, path_len: i64, body: ptr, body_len: i64, content_type: ptr, ct_len: i64, resp_buf: ptr, resp_buf_size: i64) -> i64
Perform an HTTP/1.1 POST request.
Parameters:
host(ptr) — pointer to null-terminated host string (IP, hostname, or"localhost")host_len(i32) — length of the host string, including the null terminatorport(i32) — TCP port (usually 80)path(ptr) — pointer to request path stringpath_len(i64) — length of the path stringbody(ptr) — pointer to request body bytesbody_len(i64) — length of the body in bytescontent_type(ptr) — pointer to Content-Type header value (e.g.,"application/json")ct_len(i64) — length of the content type stringresp_buf(ptr) — caller-allocated buffer for the raw HTTP responseresp_buf_size(i64) — size ofresp_bufin bytes
Returns: total bytes written to resp_buf, or -1 on error.
http_parse_status
function http_parse_status(resp: ptr, resp_len: i64) -> i32
Extract the HTTP status code from a raw response.
Expects the response to start with "HTTP/1.x NNN ...". Parses the 3-digit status code starting at byte offset 9.
Parameters:
resp(ptr) — pointer to the raw HTTP response bufferresp_len(i64) — length of the response in bytes
Returns: the 3-digit HTTP status code (e.g., 200), or -1 on parse error.
http_find_body
function http_find_body(resp: ptr, resp_len: i64) -> i64
Find the byte offset where the response body begins.
Searches for the \r\n\r\n header/body separator and returns the offset immediately after it.
Parameters:
resp(ptr) — pointer to the raw HTTP response bufferresp_len(i64) — length of the response in bytes
Returns: byte offset where the body starts, or -1 if the separator was not found.
http_get_content_length
function http_get_content_length(resp: ptr, resp_len: i64) -> i64
Parse the Content-Length header value from a raw HTTP response.
Searches for the "Content-Length: " header and parses the decimal integer that follows it.
Parameters:
resp(ptr) — pointer to the raw HTTP response bufferresp_len(i64) — length of the response in bytes
Returns: the content length as an integer, or -1 if the header was not found.
Example
import "pkg:http/client"
import "vera:memory"
data host: "example.com\0"
data path: "/"
; In a function:
call buf, allocate, buf_size
call resp_len, http_get, p_host, host_len, port, p_path, path_len, buf, buf_size
call status, http_parse_status, buf, resp_len
call body_off, http_find_body, buf, resp_len
pkg:http/server
HTTP/1.1 server over raw TCP sockets.
import "pkg:http/server"
Overview
Provides a simple HTTP server toolkit. Binds to an address, accepts connections, parses incoming HTTP requests, and sends HTTP responses. Built on vera:net for sockets, vera:memory for buffer operations, and vera:string for byte scanning and parsing.
Dependencies: vera:net, vera:memory, vera:string
Functions
http_server_start
function http_server_start(ip: ptr, ip_len: i32, port: i32) -> i32
Create a TCP server socket, bind to the given address, and start listening for connections. Enables address reuse (SO_REUSEADDR) so the server can restart quickly. Uses a listen backlog of 128.
Parameters:
ip(ptr) — pointer to null-terminated IP string (e.g.,"0.0.0.0\0")ip_len(i32) — length of the IP string, including the null terminatorport(i32) — TCP port to listen on
Returns: the server socket file descriptor, or -1 on error.
http_server_accept
function http_server_accept(server_fd: i32) -> i32
Accept an incoming client connection. Blocks until a client connects.
Parameters:
server_fd(i32) — the server socket returned byhttp_server_start
Returns: the client socket file descriptor, or -1 on error.
http_read_request
function http_read_request(client_fd: i32, buf: ptr, buf_size: i64) -> i64
Read an HTTP request from a client socket. Reads until the end-of-headers marker (\r\n\r\n) is found and any Content-Length body bytes have been received, or buf_size bytes have been read.
Parameters:
client_fd(i32) — the client socket to read frombuf(ptr) — caller-allocated buffer for the requestbuf_size(i64) — size ofbufin bytes
Returns: total bytes read (headers + body), or -1 on error (client disconnected before sending any data).
http_request_method
function http_request_method(buf: ptr, len: i64) -> i32
Extract the HTTP method from a request buffer. Parses the first token of the request line.
Parameters:
buf(ptr) — pointer to the raw HTTP requestlen(i64) — length of the request in bytes
Returns: an integer identifying the HTTP method:
| Value | Method |
|---|---|
| 1 | GET |
| 2 | POST |
| 3 | PUT |
| 4 | DELETE |
| 0 | unknown |
http_request_path
function http_request_path(buf: ptr, len: i64, out_buf: ptr, out_buf_size: i64) -> i64
Extract the request path from an HTTP request. Parses "METHOD /path HTTP/1.1\r\n..." and copies the path into out_buf. If the path is longer than out_buf_size, it is clamped.
Parameters:
buf(ptr) — pointer to the raw HTTP requestlen(i64) — length of the request in bytesout_buf(ptr) — caller-allocated buffer for the extracted pathout_buf_size(i64) — size ofout_bufin bytes
Returns: length of the path copied into out_buf, or -1 on parse error.
http_request_body
function http_request_body(buf: ptr, len: i64) -> i64
Find the byte offset where the request body begins. Searches for the \r\n\r\n separator and returns the offset immediately after it.
Parameters:
buf(ptr) — pointer to the raw HTTP requestlen(i64) — length of the request in bytes
Returns: byte offset where the body starts, or -1 if headers are not complete.
http_send_response
function http_send_response(client_fd: i32, status: i32, content_type: ptr, ct_len: i64, body: ptr, body_len: i64) -> i64
Send an HTTP response with status line, headers, and body. Automatically includes Content-Type, Content-Length, and Connection: close headers. Supported status codes: 200, 400, 404, 500.
Parameters:
client_fd(i32) — the client socket to send onstatus(i32) — HTTP status code (200, 400, 404, or 500)content_type(ptr) — pointer to Content-Type value string (e.g.,"text/html")ct_len(i64) — length of the content type stringbody(ptr) — pointer to the response body bytesbody_len(i64) — length of the body in bytes
Returns: total bytes sent (headers + body), or -1 on error.
http_send_error
function http_send_error(client_fd: i32, status: i32) -> i64
Send a canned error response with a plain text body. Supports status codes 400, 404, and 500. Any unrecognized code defaults to 500.
Parameters:
client_fd(i32) — the client socket to send onstatus(i32) — HTTP status code (400, 404, or 500)
Returns: total bytes sent, or -1 on error.