DriftScript
Documentation

Guide

Types

The whole primitive set, and why Vec3 is not one of them.

The primitive set is small and deliberately so. What is not in it matters as much as what is: there are no vector or matrix types in this language, and a consumer with no renderer still has a language.

The primitives

bool, the signed and unsigned integers from 8 to 64 bits, f32 and f64, and String. That is the whole set. A bare number literal is f32, because the engines this language targets compute in single precision, and a literal takes a different width when something gives it one.

// Integers say what they do when a result does not fit. There is no undefined behaviour.
//
// A plain `+` fails rather than producing a value outside the type. `+%` wraps and `+|` saturates,
// and choosing is the point: a health bar saturates, a hash wraps, a count should fail.

fn health(current: u8, healed: u8) -> u8 {
    return current +| healed
}

fn hashStep(accumulator: u32, value: u32) -> u32 {
    return accumulator *% value
}

// Units are erased. `30m` is the number 30; `250ms` is 0.25, because seconds are the base unit.
fn reach() -> f32 {
    return 30m
}

fn beat() -> f32 {
    return 250ms
}
A f32 answer, and the unit that produced it was erased at compile time.

The two widest integers hold what a double holds

i64 and u64 name sixty-four bits of storage and carry fifty-three bits of value, because this backend represents every number as a double and every integer past 2^53 - 1 is either unrepresentable or stands for several at once. A literal outside that range is refused, and arithmetic that would leave it fails the way any other overflow does. The declared width still means what it says wherever a value is stored, which is why a component column declared i64 is a Float64Array on the host side.

Vec3 is not a type here

Vec3, Quat, Mat4, Transform and Color arrive through a linked capability, from whatever maths the host provides. Building them into the language would mean every host had to agree with this engine about what a transform is, and a host with no renderer would carry types it can never use.

// The two prefixes, in one file.
//
// `std/math` is the language's own and is pure, so it links in any host. `drift/scene` is this
// engine's, and it carries the effect of reaching a scene. The prefix in the import is what tells
// you which of the two you are looking at.
//
// `Node` is not a language type either. It arrives with the capability, from whatever the host's
// scene is made of, which is why a consumer with no renderer still has a language.

import { clamp } from "std/math"
import { setRotation } from "drift/scene"

data Spin {
    speed: f32 = 45deg
}

fn turn(spin: Spin, node: Node, dt: f32) {
    let step = math.clamp(spin.speed * dt, 0, 90deg)
    scene.setRotation(node, 0, 1, 0, step)
}
Node is not a language type. It arrives with drift/scene, from whatever the host scene is made of.

Two parameterised types, and no more

T? and Result<T, E> are built in, and you cannot declare your own generic type. Nothing in the language needs one, and the two that exist are about failure rather than about abstraction.