Language catalog

CX Features

A living checklist of what the experimental compiler supports today. These features work in examples, but syntax and behavior may still change.

Program ShapeFunctions and DataControl FlowTypes With TeethExpressions and OperatorsStandard LibraryTooling

Feature set

Program Shape

Modules, imports, aliases, and C declarations describe what a CX file sees.

Point Distance

module app.main;

import std.core;
import c.stdio as io;
from c.math import sqrt;

struct Point {
    x: double;
    y: double;
}

extension Point {
    fn distance(other: Point) -> double {
        let dx = self.x - other.x;
        let dy = self.y - other.y;
        return sqrt(dx * dx + dy * dy);
    }
}

fn main() -> int {
    let points = Vec<Point>.create();
    points.add(Point(0, 0));
    points.add(Point(3, 4));

    foreach index, point in points {
        let zero = Point(0, 0);
        let d = point.distance(zero);
        io.printf(
            "point %d = %.2f\n",
            index,
            d
        );
    }

    points.free();
    return 0;
}

test "point distance" {
    let a = Point(0, 0);
    let b = Point(3, 4);
    expect(a.distance(b) == 5);
}

Modules and Imports

module app.main;

import std.core;
import c.stdio as io;
from c.math import sqrt;

C Declarations

module c.stdio;

declare <stdio.h> {
    type FILE = opaque;
    const EOF: int;
    fn printf(format: const char*, ...) -> int;
}

Type Aliases

type Bytes = Slice<u8>;
type IntVec = Vec<int>;
type CompareFn = fn(int, int) -> int;

Feature set

Functions and Data

CX keeps C's explicit data model, then adds syntax that removes repetitive boilerplate.

Functions

fn add(a: int, b: int) -> int {
    return a + b;
}

Structs and Initializers

struct Point {
    x: double;
    y: double;
}

let p: Point = Point { x: 10.0, y: 20.0 };
let q: Point = Point(1.0, 2.0);

Static and Instance Methods

extension Point {
    static fn origin() -> Point {
        return Point(0.0, 0.0);
    }

    fn distance(other: Point) -> double {
        let dx = self.x - other.x;
        let dy = self.y - other.y;
        return sqrt(dx * dx + dy * dy);
    }
}

Feature set

Control Flow

Common C-style control flow stays recognizable, with foreach and match for higher-level code.

If, While, For

if (value > 0) {
    total += value;
} else if (value < 0) {
    total -= value;
}

while (total < 100) {
    total += 1;
}

for (let i: int = 0; i < 10; i += 1) {
    total += i;
}

Foreach

foreach value in values {
    printf("%d\n", value);
}

foreach index, value in values {
    printf("%d: %d\n", index, value);
}

foreach key => value in map {
    printf("%d -> %d\n", key, value);
}

Switch

switch (surface) {
    case SHINY_SURFACE: {
        return 1;
    }
    default: {
        return 0;
    }
}

Feature set

Types With Teeth

Generics, type wrappers, tagged unions, interfaces, and requirements make reusable native code easier to write.

Generics

let numbers: Vec<int> = Vec<int>.with_capacity(16);
numbers.add(10);
numbers.add(20);

let names: Vec<StringView> = Vec<StringView>.create();

Type Wrappers

type Stack<T> using Vec<T> {
    expose static with_capacity -> Self;
    expose add as push;
    expose pop;
    expose free;
}

extension Stack<T> {
    fn peek() -> Option<T> {
        if (self.length == 0) {
            return Option.none<T>();
        }

        return Option.some<T>(self.data[self.length - 1]);
    }
}

Tagged Unions and Match

union Expr {
    Number: int;
    Binary: BinaryExpr;
}

fn eval(expr: Expr*) -> int {
    match expr {
        Number: n => return n;
        Binary: b => return eval_binary(&b);
        _ => return 0;
    }
}

Interfaces

interface Allocator {
    fn allocate(size: usize, align: usize) -> void*;
    fn resize(ptr: void*, old_size: usize, new_size: usize, align: usize) -> void*;
    fn free(ptr: void*, size: usize, align: usize) -> void;
}

let arena: ArenaAllocator = ArenaAllocator.create(4096);
let allocator: Allocator = arena;
let memory: void* = allocator.allocate(128, 8);

Requirements

requires Equal<T> {
    static fn equals(left: T, right: T) -> bool;
}

struct HashSet<T> where T: Hash<T> + Equal<T> {
    entries: Vec<T>;
}

Constrained Extensions

requires Disposable<T> {
    fn free(self: Self*) -> void;
}

extension Option<T>
where T: Disposable<T> {
    fn free() -> void {
        if (self.has_value) {
            self.value.free();
            self.has_value = false;
        }
    }
}

Feature set

Expressions and Operators

Most C-compatible expressions stay intact, while CX adds a few semantic conveniences.

Pointer Field Lowering

fn read(point: Point*) -> double {
    return point.x + point.y;
}

Null and Bool Literals

let node: Node* = null;
let ok: bool = true;

if (node == null) {
    return false;
}

Three-way Compare

requires Compare<T> {
    static fn compare(left: T, right: T) -> int;
}

let order: int = left <=> right;

Lambdas

let compare: fn(int, int) -> int =
    fn(left: int, right: int) => left <=> right;

Feature set

Standard Library

The current core library stays small, but it covers the primitives needed for practical examples.

Options and Results

let parsed: Option<int> = text.parse_int();
if (parsed.has_value) {
    printf("%d\n", parsed.value);
}

let file: Result<File, Error> = File.open("data.txt");

Strings and Builders

let text: StringView = StringView.from_cstr(" hi ");
let trimmed: StringView = text.trim();

let builder: StringBuilder = StringBuilder.create();
builder.append_cstr("hello");
builder.append_char(' ');
builder.append_cstr("cx");

Collections

let map: HashMap<StringView, int> = HashMap<StringView, int>.create();
map.put(StringView.from_cstr("/health"), 200);

let stack: Stack<int> = Stack<int>.create();
stack.push(42);

Bitmap

let bitmap: Bitmap = Bitmap.create(640, 480);
bitmap.clear(Color32 { r: 16, g: 16, b: 16, a: 255 });
*bitmap.pixel(10, 10) = Color32 { r: 255, g: 0, b: 0, a: 255 };
bitmap.save("image.bmp");
bitmap.free();

Feature set

Tooling

The compiler and CLI are part of the language experience, not an afterthought.

Project Config

name = "raytracer"
kind = "exe"
sources = ["examples/raytracer.cx"]
output = "build/bin/raytracer.exe"
c_output = "build/c/raytracer.c"
cc = "gcc"
cc_args = ["-O2", "-lm"]

CLI Commands

cx init
cx run
cx test
cx test --std
cx transpile examples/raytracer.cx

Test Blocks

test "parse int" {
    let value: Option<int> = StringView.from_cstr("42").parse_int();
    expect(value.has_value);
    expect(value.value == 42);
}