Language reference

A complete guide to code-lang syntax and semantics. All examples can be run in the REPL or saved to a .cl file.

Comments

# single-line comment

/* multi-line
   comment */

Variables

let declares a mutable variable. const declares a constant — reassignment is a runtime error.

let age = 25;
const PI = 3.14159;

age = 26;      # ok
PI  = 3;       # error: cannot reassign constant

Types

TypeLiteral exampleNotes
Integer42, -764-bit signed
Float3.14, -0.564-bit IEEE 754
String"hello"UTF-8, double-quoted
Char'a'Single character, single-quoted
Booleantrue, false
NullnullAbsence of value
Array[1, "x", true]Mixed types allowed
Hash{ "k": 1 }Any type as key or value
Functionfn(x) { x * 2 }First-class value
StructPoint { x: 1 }Typed object with defaults

Operators

CategoryOperators
Arithmetic+ - * / % · ** power · // floor division
Comparison== != < > <= >=
Logical&& || ! — short-circuit evaluation
Compound assign+= -= *= /= %=
Increment / decrement++ -- prefix and postfix
String concat+ — works between strings
2 ** 8;       # 256
17 // 5;      # 3  (floor division)
10 % 3;       # 1

let n = 5;
n++;          # n is now 6
n += 10;      # n is now 16

Control flow

if / elseif / else

Branches are expressions — the last evaluated value is the result of the whole block.
let score = 85;

if (score >= 90) {
    "A"
} elseif (score >= 80) {
    "B"
} else {
    "C"
};

while

let i = 0;
while (i < 5) {
    i += 1;
};

for

for (let i = 0; i < 5; i++) {
    if (i == 2) { continue; };
    if (i == 4) { break; };
};

break and continue work in both while and for.

Functions

Functions are values. Assign them with let or const. Return early with return — the last expression in a block is also returned implicitly.

let add = fn(a, b) {
    return a + b;
};

let square = fn(x) { x * x };   # implicit return

add(3, 4);      # 7
square(9);      # 81

Closures

Functions close over the enclosing scope and capture variables by reference.

let make_adder = fn(n) {
    return fn(x) { x + n };
};

let add5 = make_adder(5);
add5(10);   # 15
add5(20);   # 25

Recursion

let fib = fn(n) {
    if (n <= 1) { return n; };
    return fib(n - 1) + fib(n - 2);
};

fib(10);   # 55

Higher-order functions

map, filter, and reduce are not in the stdlib yet because they need evaluator access to call function values. Use for loops in the meantime.
let apply = fn(f, x) { f(x) };

apply(fn(n) { n * 2 }, 7);   # 14

Arrays

let nums = [1, 2, 3, 4, 5];

nums[0];          # 1
nums[2] = 99;     # mutate in place
nums[-1];         # last element (if supported)

let mixed = [1, "hello", true, [2, 3]];

See the arrays module for slice, sort, zip, flatten, and 15 more operations.

Hashes

let person = { "name": "Alice", "age": 30 };

person["name"];        # Alice
person.name;           # same — dot access works too
person["role"] = "admin";   # add or update key

Keys can be any type. See the hash module for keys, values, merge, and more.

Structs

Structs define a named type with default field values. Instantiate with TypeName { fields } — any field not provided gets its default.

struct User {
    name: "Guest",
    role: "user",
    active: true,
}

let admin = User { name: "Walon", role: "admin" };
let guest = User {};

admin.name;    # Walon
guest.name;    # Guest
guest.active;  # true

Modules

Import stdlib

import "math";
import "strings";

math.sqrt(16);              # 4.0
math.clamp(150, 0, 100);    # 100

strings.split("a,b,c", ",");   # ["a", "b", "c"]
strings.to_upper("hello");     # HELLO

Import a .cl file

Import any .cl file by its path (without the extension). Everything declared at the top level in that file becomes a field on the resulting module object.

# utils.cl
const VERSION = "1.0";
let double = fn(x) { x * 2 };

# main.cl
import "utils";
utils.double(5);    # 10
utils.VERSION;      # 1.0

See all 12 built-in modules in the standard library reference.