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:

FormPurposeProducerConsumer
VERA TextCanonical sourceAgent (LLM)Compiler
VERA ExplainHuman reviewvera explainHuman 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

CommandDescription
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:

ExampleDescription
hello.veraHello World
fibonacci.veraFibonacci sequence
file_io.veraFile operations
strings.veraString functions
math.veraMath functions
heap.veraHeap allocation
mutex.veraMutexes and threads
tcp_echo_server.veraTCP echo server
tcp_client.veraTCP client
http_get.veraHTTP GET request
http_server.veraHTTP server with routing
http_e2e_test.veraHTTP end-to-end test
json_test.veraJSON parse/serialize tests
dns_test.veraDNS resolution tests
postgres_test.veraPostgreSQL integration tests
env_test.veraEnvironment 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

FormPurposeProducerConsumer
VERA TextCanonical sourceAgent (LLM)Compiler
VERA ExplainHuman reviewvera explainHuman 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/for blocks. Only jumps and labels.
  • No expressionsadd.i64 result, a, b not result = 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 global for 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 count as i64 in one instruction and f64 in 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

TypeDescriptionSize (bytes)
i88-bit integer1
i1616-bit integer2
i3232-bit integer4
i6464-bit integer8
f32IEEE 754 single-precision float4
f64IEEE 754 double-precision float8
ptrPointer (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 division
  • divide_signed.i64 — signed division
  • compare_less.i64 — unsigned less-than
  • compare_signed_less.i64 — signed less-than
  • shift_right.i64 — logical (zero-fill) shift
  • shift_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.

InstructionFormatDescription
addadd.T dest, src1, src2dest = src1 + src2
subtractsubtract.T dest, src1, src2dest = src1 - src2
multiplymultiply.T dest, src1, src2dest = src1 * src2 (low bits of product)
dividedivide.T dest, src1, src2Unsigned division: dest = src1 / src2
divide_signeddivide_signed.T dest, src1, src2Signed division: dest = src1 / src2
remainderremainder.T dest, src1, src2Unsigned remainder: dest = src1 % src2
remainder_signedremainder_signed.T dest, src1, src2Signed remainder: dest = src1 % src2
negatenegate.T dest, srcdest = -src (two's complement)

Division by zero: Traps (program termination with diagnostic). No undefined behavior.

Arithmetic — Floating Point

7 instructions. Applicable types: f32, f64.

InstructionFormatDescription
addadd.T dest, src1, src2Float addition
subtractsubtract.T dest, src1, src2Float subtraction
multiplymultiply.T dest, src1, src2Float multiplication
dividedivide.T dest, src1, src2Float division
negatenegate.T dest, srcFloat negation (sign flip)
square_rootsquare_root.T dest, srcSquare root (hardware instruction on all targets)
fused_multiply_addfused_multiply_add.T dest, src1, src2, src3dest = (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.

InstructionFormatDescription
addadd.ptr dest, base, offsetdest = base + offset. base is ptr, offset is i64 (byte count). Result is ptr.
subtractsubtract.ptr dest, ptr1, ptr2dest = 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.

InstructionFormatDescription
bitwise_andbitwise_and.T dest, src1, src2dest = src1 AND src2
bitwise_orbitwise_or.T dest, src1, src2dest = src1 OR src2
bitwise_xorbitwise_xor.T dest, src1, src2dest = src1 XOR src2
bitwise_notbitwise_not.T dest, srcdest = NOT src (one's complement)
shift_leftshift_left.T dest, src, amountShift left. amount is i8, masked to type width.
shift_rightshift_right.T dest, src, amountLogical shift right (zero-fill).
shift_right_signedshift_right_signed.T dest, src, amountArithmetic shift right (sign-extending).
rotate_leftrotate_left.T dest, src, amountRotate left.
rotate_rightrotate_right.T dest, src, amountRotate 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.

InstructionFormatDescription
compare_equalcompare_equal.T dest, src1, src2dest = (src1 == src2) ? 1 : 0
compare_not_equalcompare_not_equal.T dest, src1, src2dest = (src1 != src2) ? 1 : 0
compare_lesscompare_less.T dest, src1, src2Unsigned: dest = (src1 < src2) ? 1 : 0
compare_less_equalcompare_less_equal.T dest, src1, src2Unsigned: dest = (src1 <= src2) ? 1 : 0
compare_greatercompare_greater.T dest, src1, src2Unsigned: dest = (src1 > src2) ? 1 : 0
compare_greater_equalcompare_greater_equal.T dest, src1, src2Unsigned: dest = (src1 >= src2) ? 1 : 0

Integer Comparisons (signed)

InstructionFormatDescription
compare_signed_lesscompare_signed_less.T dest, src1, src2Signed: dest = (src1 < src2) ? 1 : 0
compare_signed_less_equalcompare_signed_less_equal.T dest, src1, src2Signed: dest = (src1 <= src2) ? 1 : 0
compare_signed_greatercompare_signed_greater.T dest, src1, src2Signed: dest = (src1 > src2) ? 1 : 0
compare_signed_greater_equalcompare_signed_greater_equal.T dest, src1, src2Signed: 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.

InstructionFormatDescription
compare_equalcompare_equal.T dest, src1, src2Ordered equal
compare_not_equalcompare_not_equal.T dest, src1, src2Ordered not equal
compare_lesscompare_less.T dest, src1, src2Ordered less than
compare_less_equalcompare_less_equal.T dest, src1, src2Ordered less or equal
compare_greatercompare_greater.T dest, src1, src2Ordered greater than
compare_greater_equalcompare_greater_equal.T dest, src1, src2Ordered greater or equal
is_nanis_nan.T dest, srcdest = 1 if src is NaN, 0 otherwise

Pointer Comparisons

InstructionFormatDescription
compare_equalcompare_equal.ptr dest, p1, p2dest = (p1 == p2) ? 1 : 0
compare_not_equalcompare_not_equal.ptr dest, p1, p2dest = (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.

InstructionFormatDescription
jumpjump labelUnconditional jump to label.
branch_ifbranch_if cond, label_true, label_falseIf cond (i8) != 0, jump to label_true. Otherwise, jump to label_false.
selectselect.T dest, cond, val_true, val_falsedest = cond ? val_true : val_false. Conditional move, not a branch.
unreachableunreachableMarks 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.

InstructionFormatDescription
loadload.T dest, addrLoad T-sized value from memory at addr (ptr) into dest.
loadload.T dest, addr, offsetLoad from addr + offset. offset is an i64 byte count.
storestore.T addr, valueStore value to memory at addr (ptr).
storestore.T addr, offset, valueStore to addr + offset.
stack_allocatestack_allocate dest, size, alignAllocate 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

InstructionFormatDescription
extendextend.T dest, srcZero-extend src (narrower int) to wider type T.
sign_extendsign_extend.T dest, srcSign-extend src (narrower int) to wider type T.
truncatetruncate.T dest, srcTruncate 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

InstructionFormatDescription
int_to_floatint_to_float.T dest, srcUnsigned integer to float. T is the destination float type.
signed_int_to_floatsigned_int_to_float.T dest, srcSigned integer to float.
float_to_intfloat_to_int.T dest, srcFloat to unsigned integer (truncated toward zero). T is the destination int type.
float_to_signed_intfloat_to_signed_int.T dest, srcFloat to signed integer (truncated toward zero).

Float Width Conversion

InstructionFormatDescription
float_extendfloat_extend.f64 dest, srcf32 to f64 (widen).
float_truncatefloat_truncate.f32 dest, srcf64 to f32 (narrow, may lose precision).

Pointer <-> Integer

InstructionFormatDescription
int_to_pointerint_to_pointer dest, srcInteger (i64) to pointer.
pointer_to_intpointer_to_int dest, srcPointer to integer (i64).

Function Operations

4 instructions.

InstructionFormatDescription
callcall dest, func_name, arg1, arg2, ...Call function, store return value in dest.
call_indirectcall_indirect dest, func_ptr, arg1, arg2, ...Call through function pointer. For callbacks, vtable dispatch.
tail_calltail_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.
returnreturn [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:

OrderingMeaning
relaxedNo ordering guarantees beyond atomicity.
acquireSubsequent reads/writes cannot be reordered before this.
releasePreceding reads/writes cannot be reordered after this.
acquire_releaseBoth acquire and release semantics.
sequentially_consistentTotal order across all seq_cst operations. Strongest guarantee.

Atomic Load / Store

InstructionFormatDescription
atomic_loadatomic_load.T dest, addr, orderingAtomically load from addr.
atomic_storeatomic_store.T addr, value, orderingAtomically 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.

InstructionFormatDescription
atomic_addatomic_add.T dest, addr, value, orderingAtomic add. dest = old.
atomic_subtractatomic_subtract.T dest, addr, value, orderingAtomic subtract. dest = old.
atomic_andatomic_and.T dest, addr, value, orderingAtomic bitwise AND. dest = old.
atomic_oratomic_or.T dest, addr, value, orderingAtomic bitwise OR. dest = old.
atomic_xoratomic_xor.T dest, addr, value, orderingAtomic bitwise XOR. dest = old.
atomic_exchangeatomic_exchange.T dest, addr, value, orderingAtomic swap. dest = old, memory[addr] = value.

Compare-and-Exchange

InstructionFormatDescription
atomic_compare_exchangeatomic_compare_exchange.T dest, addr, expected, desired, success_ordering, failure_orderingIf memory[addr] == expected: memory[addr] = desired. Always: dest = old value at addr. Caller checks success by comparing dest to expected.

Memory Fence

InstructionFormatDescription
fencefence orderingMemory barrier. No memory operand — affects ordering of surrounding operations.

Miscellaneous

4 instructions.

InstructionFormatDescription
copycopy.T dest, srcCopy value from one slot to another. May compile to register move or be eliminated entirely.
constantconstant.T dest, immediateLoad an immediate literal value into a slot.
no_operationno_operationNo operation. May be used for alignment or as a placeholder.
traptrapDebugger 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

CategoryCount
Integer Arithmetic8
Float Arithmetic7
Pointer Arithmetic2
Bitwise / Logical9
Comparison (integer, unsigned)6
Comparison (integer, signed)4
Comparison (float)7
Comparison (pointer)2
Control Flow4
Memory3
Type Conversion10
Function Operations4
Atomic Operations10
Miscellaneous4
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
└─────────────────────────────┘
OffsetSizeFieldDescription
04Magic0x56455241 ("VERA" in ASCII)
42Version MajorSpec major version
62Version MinorSpec minor version
84FlagsBit flags (has_metadata, target_hint, etc.)
124Section CountNumber of sections
164Section Table OffsetByte offset to section table

Section Table Entry

Each section is described by:

SizeFieldDescription
4Section TypeIdentifier (TYPE=1, CODE=2, DATA=3, IMPORT=4, METADATA=5)
4OffsetByte offset from file start
4SizeSection size in bytes
4FlagsSection-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-sectionDescription
Slot Name TableMaps slot indices to string names
Function Name TableMaps function entry points to string names
Label Name TableMaps label offsets to string names
Comment TableMaps instruction offsets to comment strings
Source MappingMaps instruction ranges to original .vera file locations
String PoolDeduplicated string storage referenced by the tables above

Stripping

To strip a VERA binary:

  1. Remove the metadata section.
  2. Update the section table (decrement section count, remove the metadata entry).
  3. Optionally adjust the has_metadata flag 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:

  1. Parse each instruction into opcode + type + operands.
  2. Assign numeric indices to slots (in order of first appearance).
  3. Encode instructions into binary.
  4. 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 InstructionExplanation Output
constant.i64 count, 10Set count to 10 (i64).
add.i64 total, a, bAdd a + b (i64), storing the result in total.
compare_equal.i64 done, i, nCompare i == n (i64), result in done.
compare_signed_less.i64 flag, a, bCompare a < b (signed i64), result in flag.
branch_if done, .end, .contIf done, go to .end; otherwise go to .cont.
load.i64 x, ptr, 0Load i64 from ptr + 0 into x.
store.i64 addr, 8, valStore val at addr + 8.
call result, sqrt, valCall sqrt(val), storing the result in result.
call _, write_stdout, p, nCall write_stdout(p, n).
return totalReturn total.
copy.i64 dst, srcCopy src into dst (i64).
stack_allocate buf, 256, 8Stack-allocate 256 bytes (align 8) as buf.

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_if into "If x is less than zero, handle the negative case."
  • Counting loops: Recognize init + compare + branch_if back as "Repeat for each value of i from 0 to count."
  • Switch/case: Merge multiple compare_equal + branch_if chains into "Depending on cmd: ..."

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:

ModulePurpose
vera:ioFile I/O, console I/O, streams
vera:memoryHeap allocation (allocate, free, copy_memory, zero_memory)
vera:processProcess management, exit codes, environment variables (planned)
vera:threadThread creation, joining, mutexes, sleep
vera:netTCP/UDP sockets — create, connect, bind, listen, accept, send, recv
vera:mathMath functions (sin, cos, exp, log, pow, floor, sqrt, constants) — pure VERA, no primitives
vera:stringString/byte operations (length, find, compare, parse, format, case conversion) — pure VERA, no primitives
vera:binaryBinary encoding/decoding — big-endian and little-endian read/write for i16, i32, i64 — pure VERA, no primitives
vera:net/dnsDNS hostname resolution — resolves hostnames to IPv4 via DNS A record queries over UDP, handles IP passthrough and localhost
vera:envEnvironment variables — get, set, load .env files with optional override
vera:formatString formatting — buffer-append functions for int, float, hex, bool, padding — pure VERA, no primitives
vera:timeTime, 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

PrimitiveSignatureDescription
__vera_sys_write(i32, ptr, i64) -> i64Write bytes to a file descriptor. Returns bytes written.
__vera_sys_read(i32, ptr, i64) -> i64Read bytes from a file descriptor. Returns bytes read.
__vera_sys_open(ptr, i32, i32) -> i32Open a file. Returns file descriptor.
__vera_sys_close(i32) -> i32Close a file descriptor.
__vera_sys_alloc(i64, i64) -> ptrAllocate 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) -> i64Get environment variable (name, name_len, out_buf, out_size). Returns value length or -1.
__vera_sys_setenv(ptr, i64, ptr, i64) -> i32Set environment variable (name, name_len, value, value_len). Returns 0 on success.
__vera_sys_clock() -> i64Monotonic clock in nanoseconds.
__vera_sys_sleep(i64)Sleep for nanoseconds.
__vera_sys_mmap(ptr, i64, i32, i32) -> ptrMemory map (addr hint, size, prot, flags).
__vera_sys_munmap(ptr, i64) -> i32Unmap memory.
__vera_sys_socket(i32, i32, i32) -> i32Create socket (domain, type, protocol). Returns socket fd.
__vera_sys_connect(i32, ptr, i32) -> i32Connect socket to address (fd, sockaddr, addrlen).
__vera_sys_bind(i32, ptr, i32) -> i32Bind socket to address (fd, sockaddr, addrlen).
__vera_sys_listen(i32, i32) -> i32Listen on socket (fd, backlog).
__vera_sys_accept(i32, ptr, ptr) -> i32Accept connection. Returns new socket fd.
__vera_sys_send(i32, ptr, i64, i32) -> i64Send data on socket (fd, buf, len, flags). Returns bytes sent.
__vera_sys_recv(i32, ptr, i64, i32) -> i64Receive data from socket (fd, buf, len, flags). Returns bytes received.
__vera_sys_shutdown(i32, i32) -> i32Shut down socket (fd, how: 0=read, 1=write, 2=both).
__vera_sys_setsockopt(i32, i32, i32, ptr, i32) -> i32Set socket option (fd, level, optname, optval, optlen).
__vera_sys_sockaddr_in(ptr, i32, i32, ptr) -> i32Build IPv4 sockaddr (ip_str, ip_len, port, out_buf). Returns sockaddr size. Compiler builds platform-correct layout.
__vera_sys_poll(i32, i32, i32) -> i32Poll socket for readiness (fd, events, timeout_ms). Events: 1=readable, 2=writable. Returns ready events, 0=timeout, -1=error.
__vera_sys_unlink(ptr) -> i32Delete a file by path. Returns 0 on success.
__vera_sys_seek(i32, i64, i32) -> i64Seek in file (fd, offset, whence). Returns new position.
__vera_sys_stat_size(ptr) -> i64Get file size in bytes by path. Returns -1 on error.
__vera_sys_rename(ptr, ptr) -> i32Rename/move file (old_path, new_path). Returns 0 on success.
__vera_sys_mkdir(ptr, i32) -> i32Create directory (path, mode). Returns 0 on success.
__vera_sys_rmdir(ptr) -> i32Remove empty directory. Returns 0 on success.
__vera_sys_getcwd(ptr, i64) -> i32Get current working directory into buffer. Returns 0 on success.
Threading
__vera_sys_thread_spawn(ptr, ptr) -> i64Create a thread running function pointer with argument. Returns thread handle.
__vera_sys_thread_join(i64) -> i64Wait for thread to finish. Returns the thread's exit value.
__vera_sys_mutex_create() -> i64Create a mutex. Returns mutex handle.
__vera_sys_mutex_lock(i64) -> i32Lock a mutex (blocks until acquired). Returns 0 on success.
__vera_sys_mutex_trylock(i64) -> i32Try to lock a mutex without blocking. Returns 0 if acquired, non-zero if busy.
__vera_sys_mutex_unlock(i64) -> i32Unlock a mutex. Returns 0 on success.
__vera_sys_mutex_destroy(i64) -> i32Destroy 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 syscall instruction with the correct Linux syscall number. Threading maps to clone / futex syscalls.
  • macOS: Emits syscall (x86-64) or svc #0x80 (ARM64) with Darwin syscall numbers. Threading maps to pthread_* via libSystem.
  • Windows: Emits calls to kernel32.dll / ntdll.dll entry points. Threading maps to CreateThread / WaitForSingleObject / CRITICAL_SECTION APIs.

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 emits WSAStartup initialization 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.

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:

  1. Check — Does a package for this functionality already exist in the registry?
  2. Use — If yes, import and use it.
  3. Extend — If it exists but lacks needed functionality, add it and publish a new version.
  4. 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:

  1. Implement — Write the VERA code unit(s) with full metadata (names, comments).
  2. Test — Include verification code units that exercise the functionality.
  3. 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).
  4. Publish — If validation passes, the package is added to the registry.

Quality Gates

The registry enforces quality standards automatically:

GateRequirement
CompilationMust assemble and compile cleanly on all targets (x86-64, ARM64, RISC-V).
VerificationAll test code units must pass.
MetadataAll functions must have named slots and comments. No stripped code.
API stabilityMinor/patch versions must not remove or change existing function signatures.
No duplicationRegistry checks for functional overlap with existing packages and flags for review.
Size budgetPackages 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/client depend 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

CheckDescription
Double-free detectionTracks free calls and warns if the same pointer is freed twice.
Use-after-free detectionWarns if a pointer is dereferenced after its target has been freed.
Leak detectionWarns if allocated memory is never freed on any code path.
Null pointer dereferenceWarns if a pointer that may be null is dereferenced without a prior null check.
Uninitialized slot useError if a slot is read before being written.
Type consistencyError if a slot is used with inconsistent types across instructions.
Unreachable codeWarning for instructions after unconditional jump or return.
Division by zeroWarning 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: LOCK prefix on read-modify-write instructions (LOCK XADD, LOCK CMPXCHG). MFENCE for full memory barrier. x86-64 has a strong memory model (TSO) — most loads/stores are already ordered, so acquire/release orderings 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. DMB for memory barriers. ARM64 has a weak memory model — acquire/release orderings 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 and AMO* (atomic memory operations: AMOADD, AMOSWAP, AMOOR, etc.) with .aq (acquire) and .rl (release) bits. FENCE for 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 + ADDI sequences, which the compiler generates from constant.

Deliberate Exclusions

The following are not in the ISA and will not be added. They belong in the standard library, compiler, or future extensions.

ExcludedReasonWhere It Belongs
sin, cos, tan, exp, log, powMulti-instruction on all targetsvera:math
memcpy, memset, memmoveComplex optimized sequencesvera:memory
malloc, freeAllocator is softwarevera:memory
print, read, file operationsSystem I/Overa:io
SIMD / vector operationsMassive ISA surface area, varies wildly across targetsFuture VERA extension
Exception handling (throw/catch)Complex runtime machineryReturn-code error handling or vera:error
Coroutines / asyncRuntime/scheduler concernvera:thread or future extension
String operationsHigh-level, many implementationsvera:string
Struct/array typesLayout is a higher-level concernAgent knowledge / frontend
clz, ctz, popcntNot universal in hardwareCompiler intrinsics if needed
Virtual dispatch / vtablesOOP runtime concerncall_indirect + data structures
Garbage collectionRuntime concernFuture extension or vera:gc
Overflow-checked arithmeticSynthesizable from existing instructionsCompiler pass
Variadic functionsPlatform ABI nightmareArgument 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:

  1. import "vera:io" pulls in the standard I/O module.
  2. write_stdout is a function from vera:io that calls __vera_sys_write with file descriptor 1.
  3. __vera_sys_write is a compiler primitive — the compiler emits the platform-specific syscall sequence.
  4. Static linking includes only write_stdout and __vera_sys_write (dead code elimination drops the rest of vera: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.

; 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 _main entry 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

PoolRegistersCount
Integer (callee-saved)x19, x20, x21, x22, x23, x24, x25, x26, x27, x2810
Float (callee-saved)d8, d9, d10, d11, d12, d13, d14, d158

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

  1. Build live intervals — For each slot, record the first and last instruction index where it is referenced. Function parameters are live from instruction 0.

  2. 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.

  3. Sort by start — Intervals are sorted by start index, breaking ties by end index.

  4. 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.
  5. 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)
RegionOffset from spSize
Frame pointer + link registersp + 016 bytes (stp x29, x30)
Callee-saved registerssp + 16Variable: ceil(count / 2) * 16 bytes (saved in pairs)
Local slotssp + 16 + cssnum_slots * 8 bytes
Stack-allocate spacesp + 16 + css + slots_sizeSum 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

TypeRegisters
Integer / pointer argumentsx0, x1, ..., x7 (first 8)
Float argumentsd0, 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...):

  1. Load arguments into x0–x7 / d0–d7 from their slots or allocated registers.
  2. Emit bl _vera_<callee_name>.
  3. Store x0 (or d0) 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:

PrimitiveImplementation
__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 a struct sockaddr_in in a caller-provided buffer. Parses the IP string by scanning for dot separators, converting decimal octets with multiplication, and assembling into network byte order. Sets sin_len = 16, sin_family = AF_INET (2), and byte-swaps the port number via the ARM64 rev16 instruction.

  • __vera_sys_stat_size — Allocates a temporary struct stat on the stack (256 bytes), calls _stat, extracts st_size from offset 96 (Darwin ARM64 struct layout), cleans up, and returns the file size.

  • __vera_sys_poll — Builds a pollfd struct on the stack (fd + events + revents), calls _poll, and returns the revents bitmask.

Optimizations

Implemented

OptimizationDescription
Linear scan register allocationKeeps frequently-used slots in callee-saved registers, avoiding memory traffic for the most active values.
Direct register resolutionWhen 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 loadingZero 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 maskingi8 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 pairingSaves and restores use stp/ldp (store/load pair) instructions, writing two 64-bit registers in a single 16-byte-aligned memory operation.
Remainder via msubremainder 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 rorrotate_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 deduplicationWhen multiple imports bring in the same function (same name), only the first copy is emitted.

Not Yet Implemented

OptimizationDescription
Compare-and-branch fusionMerge compare_* + branch_if into cmp + b.cond, eliminating the boolean intermediate slot.
Constant foldingEvaluate constant + constant operations at compile time.
Dead code eliminationRemove unreachable code after unconditional jumps or returns.
Peephole optimizationRewrite short instruction sequences (e.g., eliminate mov x0, x0, merge adjacent ldr/str into ldp/stp).
Strength reductionReplace multiply x, a, 8 with lsl x, a, #3 for power-of-two constants.
Instruction schedulingReorder 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:

EntityAssembly Label PatternExample
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_mainWrapper 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

ModuleDescriptionPrimitives
vera:binaryBinary encoding/decoding (big/little-endian)None (pure VERA)
vera:envEnvironment variables and .env filesYes
vera:formatString formatting and buffer buildingNone (pure VERA)
vera:ioConsole I/O and file operationsYes
vera:mathMathematical functions (f64)None (pure VERA)
vera:memoryHeap allocation and memory operationsYes
vera:netTCP/UDP socket operationsYes
vera:net/dnsDNS hostname resolutionNone (uses vera:net)
vera:stringString and byte sequence operationsNone (pure VERA)
vera:threadThreading and synchronizationYes

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, and vera:binary use 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 buffer
  • offset (i64) — byte offset within the buffer
  • value (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 buffer
  • offset (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 buffer
  • offset (i64) — byte offset within the buffer
  • value (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 buffer
  • offset (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 buffer
  • offset (i64) — byte offset within the buffer
  • value (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 buffer
  • offset (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 buffer
  • offset (i64) — byte offset within the buffer
  • value (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 buffer
  • offset (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 buffer
  • offset (i64) — byte offset within the buffer
  • value (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 buffer
  • offset (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 buffer
  • offset (i64) — byte offset within the buffer
  • value (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 buffer
  • offset (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 name
  • name_len (i64) — length of the variable name
  • out_buf (ptr) — buffer to write the value into
  • out_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 name
  • name_len (i64) — length of the variable name
  • value (ptr) — pointer to the value
  • value_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 path
  • path_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 path
  • path_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 — for int_to_string
  • vera:memory — for copy_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
ParameterTypeDescription
bufptrDestination buffer
offseti64Current write position
buf_sizei64Total buffer capacity
strptrSource bytes to append
leni64Number 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
ParameterTypeDescription
bufptrDestination buffer
offseti64Current write position
buf_sizei64Total buffer capacity
valuei64Signed 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
ParameterTypeDescription
bufptrDestination buffer
offseti64Current write position
buf_sizei64Total buffer capacity
valuei64Unsigned 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
ParameterTypeDescription
bufptrDestination buffer
offseti64Current write position
buf_sizei64Total buffer capacity
valuei64Value 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
ParameterTypeDescription
bufptrDestination buffer
offseti64Current write position
buf_sizei64Total buffer capacity
bytei8Byte 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
ParameterTypeDescription
bufptrDestination buffer
offseti64Current write position
buf_sizei64Total buffer capacity
valuei32Boolean 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
ParameterTypeDescription
bufptrDestination buffer
offseti64Current write position
buf_sizei64Total buffer capacity
valuef64Floating-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
ParameterTypeDescription
bufptrDestination buffer
offseti64Current write position
buf_sizei64Total buffer capacity
strptrString to right-align
leni64Length of string
widthi64Minimum field width
filli8Fill 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
ParameterTypeDescription
bufptrDestination buffer
offseti64Current write position
buf_sizei64Total buffer capacity
strptrString to left-align
leni64Length of string
widthi64Minimum field width
filli8Fill 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:

ConstantValueDescription
FILE_READ1Open for reading
FILE_WRITE2Open for writing
FILE_CREATE4Create if it does not exist
FILE_TRUNCATE8Truncate to zero length if it exists
FILE_APPEND16Writes append to end of file

Seek Origins

ConstantValueDescription
SEEK_START0Seek from beginning of file
SEEK_CURRENT1Seek from current position
SEEK_END2Seek 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 write
  • length (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 write
  • length (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 into
  • max_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 path
  • flags (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 descriptor
  • data (ptr) — pointer to the byte buffer to write
  • length (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 descriptor
  • buffer (ptr) — pointer to the buffer to read into
  • max_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 path
  • new_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 descriptor
  • offset (i64) — byte offset relative to origin
  • origin (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 value
  • b (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 value
  • b (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 clamp
  • lo (f64) — lower bound
  • hi (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 base
  • exponent (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 allocate
  • align (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 free
  • size (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 free
  • size (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 pointer
  • src (ptr) — source pointer
  • count (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 pointer
  • value (i8) — byte value to fill with
  • count (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 pointer
  • count (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.

ConstantValueDescription
AF_INET2IPv4 address family
SOCK_STREAM1TCP (stream) socket type
SOCK_DGRAM2UDP (datagram) socket type
SHUT_READ0Shutdown reading
SHUT_WRITE1Shutdown writing
SHUT_BOTH2Shutdown 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 descriptor
  • ip (ptr) — null-terminated IPv4 address string
  • ip_len (i32) — length of the IP string including the null terminator
  • port (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 descriptor
  • ip (ptr) — null-terminated IPv4 address string
  • ip_len (i32) — length of the IP string including the null terminator
  • port (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 descriptor
  • data (ptr) — pointer to the data to send
  • length (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 descriptor
  • buffer (ptr) — pointer to the buffer to receive into
  • max_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 descriptor
  • how (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 descriptor
  • events (i32) — bitmask of what to check: 1 = readable, 2 = writable, 3 = both
  • timeout_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:

  1. Raw IP addresses (digits and dots only) -- passed through directly without a DNS query.
  2. "localhost" -- returns "127.0.0.1" immediately.
  3. 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 hostname
  • out_buf (ptr) — buffer to write the dotted-decimal IP string into
  • out_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 bytes
  • hostname_len (i64) — length of the hostname
  • server_ip (ptr) — IP address string of the DNS server (e.g., "8.8.8.8")
  • server_ip_len (i64) — length of the server IP string
  • out_buf (ptr) — buffer to write the dotted-decimal IP string into
  • out_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


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 search
  • len (i64) — length of the buffer
  • needle (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 search
  • haystack_len (i64) — length of the buffer
  • needle (ptr) — pointer to the subsequence to find
  • needle_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 buffer
  • b (ptr) — pointer to the second buffer
  • len (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 buffer
  • a_len (i64) — length of the first buffer
  • b (ptr) — pointer to the second buffer
  • b_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 buffer
  • buf_len (i64) — length of the buffer
  • prefix (ptr) — pointer to the prefix
  • prefix_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 string
  • len (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 convert
  • buf (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-place
  • len (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-place
  • len (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 function
  • arg (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 by thread_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 by mutex_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 by mutex_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 by mutex_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 by mutex_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

PackageDescription
pkg:http/clientHTTP/1.1 client (GET and POST)
pkg:http/serverHTTP/1.1 server toolkit
pkg:data/jsonJSON parsing, construction, and serialization
pkg:db/postgresPostgreSQL 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):

OffsetFieldDescription
0tag (i8)Type tag: 0=null, 1=bool, 2=number, 3=string, 4=array, 5=object
8payload (i64 or ptr)Value data or pointer to content
16extra (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 bytes
  • len (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 node
  • val (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 node
  • key (ptr) — pointer to the key string bytes
  • key_len (i64) — length of the key string
  • val (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:

TagType
0null
1bool
2number
3string
4array
5object

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 node
  • out_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 node
  • index (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 node
  • key (ptr) — pointer to the key string to search for
  • key_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 text
  • len (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 node
  • buf (ptr) — caller-allocated output buffer
  • buf_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 by pg_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 handle
  • sql (ptr) — pointer to the SQL statement text
  • sql_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 handle
  • sql (ptr) — pointer to the SQL query text
  • sql_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 by pg_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 by pg_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 handle
  • col (i64) — zero-based column index
  • out_buf (ptr) — caller-allocated buffer for the column name
  • out_size (i64) — size of out_buf in 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 handle
  • row (i64) — zero-based row index
  • col (i64) — zero-based column index
  • out_buf (ptr) — caller-allocated buffer for the cell value
  • out_size (i64) — size of out_buf in 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 handle
  • row (i64) — zero-based row index
  • col (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 terminator
  • port (i32) — TCP port (usually 80)
  • path (ptr) — pointer to path string (e.g., "/index.html")
  • path_len (i64) — length of the path string
  • resp_buf (ptr) — caller-allocated buffer for the raw HTTP response
  • resp_buf_size (i64) — size of resp_buf in 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 terminator
  • port (i32) — TCP port (usually 80)
  • path (ptr) — pointer to request path string
  • path_len (i64) — length of the path string
  • body (ptr) — pointer to request body bytes
  • body_len (i64) — length of the body in bytes
  • content_type (ptr) — pointer to Content-Type header value (e.g., "application/json")
  • ct_len (i64) — length of the content type string
  • resp_buf (ptr) — caller-allocated buffer for the raw HTTP response
  • resp_buf_size (i64) — size of resp_buf in 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 buffer
  • resp_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 buffer
  • resp_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 buffer
  • resp_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 terminator
  • port (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 by http_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 from
  • buf (ptr) — caller-allocated buffer for the request
  • buf_size (i64) — size of buf in 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 request
  • len (i64) — length of the request in bytes

Returns: an integer identifying the HTTP method:

ValueMethod
1GET
2POST
3PUT
4DELETE
0unknown

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 request
  • len (i64) — length of the request in bytes
  • out_buf (ptr) — caller-allocated buffer for the extracted path
  • out_buf_size (i64) — size of out_buf in 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 request
  • len (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 on
  • status (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 string
  • body (ptr) — pointer to the response body bytes
  • body_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 on
  • status (i32) — HTTP status code (400, 404, or 500)

Returns: total bytes sent, or -1 on error.