Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/elfrask/cls/llms.txt

Use this file to discover all available pages before exploring further.

CLS provides a complete set of control-flow constructs designed around explicit, readable syntax. Every branching and looping statement uses mandatory braces — there are no single-line forms. Internally, the interpreter propagates return, break, and continue as typed Flow signals, which means they never accidentally escape their intended scope even when loops are deeply nested.
All control-flow blocks in CLS require curly braces { and }. Single-line forms without braces (like if (x) doSomething();) are not valid.

if / elif / else

The if statement evaluates a boolean condition and branches accordingly. Use elif for additional branches and else as the fallback. The condition must be a Bool (or a value the type checker can widen to Bool); a non-boolean condition will trigger a type-checker warning in strict mode.
if (score >= 90) {
    print("A");
} elif (score >= 80) {
    print("B");
} elif (score >= 70) {
    print("C");
} else {
    print("F");
}
if can also be used as an expression with the then/else form:
var label = if (x > 0) then ("positive") else ("non-positive");

while

while repeats its block as long as the condition remains true. The condition is re-evaluated at the top of every iteration.
var i = 0;
while (i < 5) {
    print(i);
    i = i + 1;
}

loop

loop creates an infinite loop. The only way to exit is with a break statement. Use it when the termination condition is best expressed inside the body rather than at the top.
var n = 0;
loop {
    n = n + 1;
    if (n >= 3) { break; }
}
print("n:", n);   # → n: 3

for (traditional)

The traditional C-style for has three clauses: an initializer, a condition, and an update expression. The initializer may include var to declare a new variable scoped to the loop, or it may be a plain assignment expression.
# With var declaration
for (var k = 0; k < 6; k = k + 1) {
    if (k == 2) { continue; }
    if (k > 4)  { break; }
    print("k:", k);
}
# Output: k: 0  k: 1  k: 3  k: 4

# Without var (uses an existing variable)
for (i = 0; i < 10; i = i + 1) {
    print(i);
}
The update expression (k = k + 1) runs at the end of every iteration, after the body and before the next condition check. The i++ shorthand is also accepted.

for each

for each iterates over any iterable value: arrays, tuples, records, enums, or any object that exposes an iterator protocol.
var arr = [1, 2, 3, 4, 5];

for each item in (arr) {
    print(item);
}
for each works on enums as well. See the Enums page for a dedicated example of iterating over enum variants.

for each with index

Add and <name> after the item binding to receive a zero-based integer index alongside each element:
var arr = [1, 2, 3, 4, 5];

for each item and idx in (arr) {
    print(idx, ":", item);
}
# Output:
# 0 : 1
# 1 : 2
# 2 : 3
# 3 : 4
# 4 : 5
The index variable (idx) is local to the loop body. It counts from 0 and increments by 1 for every iteration regardless of the underlying collection type.

switch / case / default

switch compares an expression against a series of case patterns using equality (==). The first matching case runs its block; if no case matches, the optional default block runs.
switch (value) {
    case ("a") {
        print("option a");
    }
    case ("b") {
        print("option b");
    }
    default {
        print("no match");
    }
}
enum Estado { Apagado, Encendido, EnEspera };

var estado = Estado.Encendido;

switch (estado) {
    case (Estado.Apagado)   { print("off");     }
    case (Estado.Encendido) { print("on");      }
    case (Estado.EnEspera)  { print("standby"); }
}
Patterns are matched by equality. There is no fall-through between cases; each matching block ends automatically. break inside a case exits the switch explicitly.

try / catch / finally

try encloses code that may throw a runtime error. Each catch clause names the captured error as a string variable (the throw message). Multiple catch blocks are allowed. finally, when present, always executes — whether an error was thrown or not.
try {
    var result = riskyOperation();
    print("result:", result);
} catch (e) {
    print("Error:", e);
} finally {
    cleanup();
}
The captured variable e is always a String containing the error message passed to throw(msg). A try/catch also resets the interpreter’s call-stack depth when catching, so no partial stack state leaks out of the error path.
try {
    openFile("data.csv");
} catch (fileErr) {
    print("File error:", fileErr);
} catch (parseErr) {
    print("Parse error:", parseErr);
} finally {
    print("done");
}

break and continue

break exits the nearest enclosing loop or switch block. continue skips the rest of the current loop body and jumps to the next iteration. Both work uniformly across while, loop, for, and for each, including inside nested blocks.
var arr = [1, 2, 3, 4, 5];

# break — stop at 3
for each x in (arr) {
    if (x == 3) { break; }
    print("b:", x);
}
# Output: b: 1  b: 2

# continue — skip 2
for each x in (arr) {
    if (x == 2) { continue; }
    print("c:", x);
}
# Output: c: 1  c: 3  c: 4  c: 5
An inner break inside a nested for each does not propagate to an outer while — each loop captures and clears the Flow signal independently:
var i = 0;
while (i < 3) {
    i = i + 1;
    for each x in (arr) {
        if (x == 2) { break; }   # only exits the for each
    }
    print("while i:", i);        # still runs every iteration
}

return

return exits the current function and optionally yields a value to the caller. A return without a value is valid in void functions.
function buscar(n: int) -> int {
    for each x in ([1, 2, 3, 4, 5]) {
        if (x == n) { return x * 10; }
    }
    return -1;
};

print(buscar(4));   # → 40
print(buscar(9));   # → -1
# Void function — return with no value
function log(msg: String) {
    if (msg == "") { return; }
    print("[LOG]", msg);
};

with

with evaluates an expression, binds the result to a local name, and makes it available exclusively within the block. The binding does not exist outside the braces.
with connection in (openDb("localhost")) {
    connection.query("SELECT * FROM users");
}
# 'connection' is not accessible here
The with syntax is equivalent to:
var connection = openDb("localhost");
# ... block ...
# (connection goes out of scope when the block ends)
It is particularly useful for resources that need to be explicitly scoped — file handles, database connections, or lock guards.

Signal propagation

Internally the CLS interpreter represents return, break, and continue as variants of a Flow signal rather than exceptions. Each loop construct (while, loop, for, for each) inspects the signal at the end of every iteration and clears it when it matches — a break from an inner for each is consumed there and never reaches the outer while. A return propagates upward through the call stack until it hits the function boundary, where it is resolved into the function’s return value. try/catch resets the call-stack depth when it catches an error, ensuring that partial stack state from a thrown path never leaks into the continuation.
The following AST variants (from cls-core/src/frontend/ast.rs) correspond to each control-flow statement:
StatementAST variant
ifStatement::If(IfStatement)
whileStatement::While(WhileStatement)
loopStatement::Loop(Block)
forStatement::For(ForStatement)
for eachStatement::ForEach(ForEachStatement)
switchStatement::Switch(SwitchStatement)
tryStatement::Try(TryStatement)
withStatement::With(WithStatement)
returnStatement::Return(Option<Expression>)
breakStatement::Break
continueStatement::Continue

Build docs developers (and LLMs) love