The cweet programming languge aims to be a safer, more expressive alternative to the C programming language.

Statement Expressions

Statement expressions allow us to rewrite a sequence of statements computing a value as an expression.

// if-else as expressions.
var max = if (a > b) a else b;

// A block of code as an expression.
var sum: [100][100]int = do {
    var r = undefined;
    loop for (var i; 0 ..^ r.len[0]) {
        loop for (var j; 0 ..^ r.len[1]) {
            r[i][j] = i+j;
        }
    }
    r; // value of the block.
};

Type Inference

Types of function parameters and return value has to be explicit. Types of other values are inferred.

fn foo(): int
{
    var y = undefined;
    with (
        var primes = [2, 3, 5, 7, 11];
        y = 0;
    ) loop for (var p; primes[..]) {
        y += p;
    }
    return y;
}

Scope Guards

Scope guards allow code that deallocates resources to be close to code that allocates the resource and ensures proper cleanup.

fn copy(from, to: []char): void
{
    var f1 = open(from, "r");
    scope (exit) { close(f1); }
    var f2 = open(to, "w");
    scope (exit) { close(f2); }
    with (
        var buf: [1024]char = undefined;
        var n = undefined;
    ) loop while (
        do {
            n = read(f1, buf[..]);
            n > 0;
        }
    ) {
        write(f2, buf[..^n]);
    }
}

Lambda Syntax

Lambda syntax allows writing short-lived functions without giving them a name. Lambda functions can access their environment if they can be inlined at the caller.

alias string_proc = :&fn(: []char): void;

fn with_input_string(inline f: string_proc): void
{
    var s = readline();
    scope (exit) { free(s); }
    f(s);
}

fn main(): int
{
    with_input_string(\s => do {
        import io (print, println);
        print("Hello ");
        println(s);
    });
    return 0;
}

Sum Types and Pattern Matching

Sum types provide tagged unions in the language ensuring that the programmer does not incorrectly access invalid fields.

// Tagged discriminative Unions.
type NetStatus = choice {
    Connected{ip: [4]byte},
    Disconnected
};

// match expression.
var desc = match (status) {
    Connected{...} => "connected",
    Disconnected   => "disconnected",
};

Error Handling

An expression that has type T|Error (T or Error) may produce a value of type T or cause an error.

extern fn sqrt(a: double): double|Error;

fn len(v: [2]double): double
{
    return sqrt(
        v[0]*v[0] +
        v[1]*v[1]
    )!; // Ignore error.
}

fn maxsqrt(a, b: double): double|Error
{
    // Propagate error.
    var aa = sqrt(a)^;
    var bb = sqrt(b)^;
    return if (aa > bb) aa else bb;
}

fn maxsqrt1(a, b: double): double
{
    // Handle errors.
    try {
        var aa = sqrt(a)^;
        var bb = sqrt(b)^;
        return if (aa > bb) aa else bb;
    } catch {
        _ => panic("sqrt of a negative number.");
    }
}

Modules

Modules provide abstraction and parameterization over abstractions.

// Typed allocators.
interface Allocator {
    type t; // Type to allocate
    fn f(): *t; // Return Null on failure.
}

module Mallocator(X: Type) {
    alias t = X.t;
    fn f(): *t {
        alias cast = repr.to(:t).f;
        return cast(malloc(X.sizeOf()));
    }
}

module PoolAllocator(X: Type, S: Size):
       Allocator with (t = X.t) { // Hides pool.
    alias t = X.t;
    var pool: [S.v]t = undefined;
    var next: size = 0;
    fn f(): *t {
        when (next >= S.v) { return Null; }
        var r = &pool[next];
        ++next;
        return r;
    }
}

module FallbackAllocator(
   P: Allocator,
   F: Allocator with (t = P.t)
) {
    alias t = P.t;
    fn f(): *t {
        var p = P.f();
        when (p != Null) { return p; }
        return F.f();
    }
}

// Create alloc function.
alias alloc = FallbackAllocator(
    PoolAllocator(:int, 1024),
    Mallocator(:int)
).f;