Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Overview

Vox is a value-oriented language for reusable packages and executable scripts. It is pure by default, keeps side effects explicit, and aims to stay readable in small files.

This page is a quick guide. Use the specification for exact grammar and full semantic rules.

What A File Can Be

Reusable Vox files start with a package header:

  • package demo.math; for reusable code

Executable scripts may either start with a header or omit it:

  • script demo.main; for a named executable entrypoint
  • evil script demo.main; for a named executable entrypoint that may perform side effects
  • no header for an anonymous executable script

Packages may be imported by other Vox files. Scripts may declare param inputs and may end with one trailing expression that becomes the script result. Declarations inside scripts are local to that script. Anonymous scripts cannot be imported or compiled as libraries; they can only be executed directly.

Basic Declarations

package demo.math;

public import math;

val defaultScale = 2.0;

public fun clamp01(x: Float): Float {
    if (x < 0.0) {
        0.0
    } else if (x > 1.0) {
        1.0
    } else {
        x
    }
}

fun scale(x: Float, factor: Float = defaultScale): Float = x * factor;

Rules to remember:

  • val creates an immutable binding.
  • var allows local reassignment inside a block or script.
  • fun declares a function.
  • public exports a package declaration or re-exports an import.
  • declarations are private by default.
  • function parameters and return types use name: Type.
  • default argument values use =.
  • packages are order-independent declaration graphs with top-level val and fun declarations;
  • scripts execute in source order, except that script function headers are visible throughout the script.

The exact modules, types, and host functions available to import depend on the host application embedding Vox.

Expressions And Control Flow

Blocks return their last expression, so small functions often read naturally:

fun sum(values: List[Float]): Float {
    var total = 0.0;

    for (value in values) {
        total += value;
    }

    total
}

Common expression forms:

  • if is an expression.
  • return is available when an early exit is clearer.
  • lambdas use x -> x * 2 or (x: Float) -> x * 2.
  • tuples use (a, b).
  • lists use [1, 2, 3].
  • records use { name = "vox", version = 1 }.
  • value.with { field = next } copies an immutable value with selected changes; write field: Type = next when an explicit replacement type hint is useful.
  • value.fun(args) calls a function as a method — sugar for fun(value, args).
val i: Int = 1;

fun add(x: Int, y: Int): Int = x + y;

val result: Int = i.add(2);  // sugar for add(i, 2)

For external libraries, struct methods from trait implementations are also available via this syntax.

Method resolution order: fields, then methods, then qualified names. Defining more than one applicable method with the same name for a receiver type is a compile-time error. This includes conflicts between built-in methods, visible first-parameter functions, and trait methods implemented by the receiver.

Nullability

Nullable types use ?:

fun findUser(id: Int): { name: String }? {
    if (id == 1) {
        { name = "vox" }
    } else {
        null
    }
}

val name = findUser(2)?.name ?: "unknown";

Useful operators:

  • ?. accesses a nullable receiver safely.
  • ?: provides a fallback when the left side is null.
  • !! unwraps a nullable value and fails at runtime if it is null.

Effects And econ

Pure code is the default. Mark a function evil when it performs observable side effects such as I/O.

evil fun readText(path: String): String {
    host.readText(path)
}

fun cachedText(path: String): Econ[String] {
    econ[String] {
        readText(path)
    }
}

econ[T] { ... } is a built-in intrinsic that creates a pure handle to a cached snapshot of an effectful computation. Pure code can pass the handle around without re-running the effect.

Use snapshot.update() to refresh an Econ[T]. The call re-runs the original econ block, stores the new snapshot, and returns the refreshed T value.

Scripts

Scripts use the same declaration syntax as packages, plus param inputs and an optional trailing result expression. The script ...; header is optional for a pure script that is meant to be executed directly.

script demo.main;

param value: Float;
param factor: Float = 2.0;

fun scale(x: Float): Float = x * factor;

scale(value)

The same script can be written anonymously:

param value: Float;
param factor: Float = 2.0;

fun scale(x: Float): Float = x * factor;

scale(value)

Script values and statements are processed in source order:

script demo.counter;

var b = 1;
val a = b;
b = 2;

a

This script returns 1, because a receives the value of b at the point where a is declared. It is not a live alias to b.

Script functions are visible throughout the script:

script demo.functions;

val total = even(4) + odd(3);

fun even(value: Int): Int = value;
fun odd(value: Int): Int = value;

total

Use scripts for entrypoints and one-off execution. Use packages for code you want to import elsewhere.

Documentation Comments

Vox uses /// for documentation comments, similar to Rust. Doc comments annotate declarations and are shown in editor hover:

/// Computes the greatest common divisor of two integers.
/// Uses the Euclidean algorithm.
fun gcd(a: Int, b: Int): Int {
    if (b == 0) {
        a
    } else {
        gcd(b, a % b)
    }
}

/// The default scaling factor.
val defaultScale: Float = 2.0;   /// Applied to all coordinate-transforms.

Doc comments come in two forms:

  • Head docstrings: /// lines that appear directly before a declaration (val, var, fun, import, param). These describe the declaration they precede.
  • Body docstrings: /// inside a function body provide additional documentation for the function. A /// on the same line as a value declaration (after the ;) is a body docstring for that value.

A package or named script header may also be preceded by /// lines to document the module:

/// Geometry utilities for 2D and 3D coordinate transforms.
package geo.transform;

Important: Every /// comment must annotate either a val/var, fun, import, param, or a package/named script header. The language server will raise a warning if a doc comment is not attached to any declaration.