# Chapter 5: Control flow

Loup's control flow is close to C's and TypeScript's, with a few rules that catch people out: conditions always take parentheses, bodies always take braces, there is no `?:` operator, and loop variables are immutable unless you ask otherwise. This chapter covers conditionals, the loop forms, `match`, scoped resources with `with`, and exceptions, and points out where Loup 1.25.1 does not yet do what the specification says.

## Conditionals

```loup
@mode(host.native)

func describe(n: int): string {
    if (n < 0) {
        return "negative";
    } else if (n == 0) {
        return "zero";
    } else {
        return "positive";
    }
}

func main(): void {
    println(describe(-3));
    println(describe(0));
}
```

*Listing 5-1: `if`, `else if`, `else`*

```text
negative
zero
```

The parentheses around a condition are part of the syntax (ruling PAREN-1), and so are the braces around each branch, even for a single statement. Leaving out the parentheses, as you would in Rust, Go or Swift, gives a message that says exactly what to do:

```loup,does_not_compile
@mode(host.native)

func main(): void {
    let load = 7;
    if load > 3 {
        println("busy");
    }
}
```

```text
main.lphc:5:8: error[E0001]: E_CONDITION_PARENS: the condition of `if` must be in parentheses; add them: `if (...) {`
```

`while` conditions and `for` headers need parentheses for the same reason. A branch without braces, `if (load > 3) println("busy");`, is `error[E0002]: expected `{``.

### `if` is an expression

An `if` with an `else` produces a value, so it can sit on the right of a `let`. Loup has no ternary operator; this is the replacement:

```loup,does_not_compile
@mode(host.native)

func main(): void {
    let load = 7;
    let state = load > 3 ? "busy" : "idle";
    println(state);
}
```

```text
main.lphc:5:28: error[E0003]: expected `;`
```

The message is unhelpful because the parser does not know `?` in this position. Write an `if` expression:

```loup
#~ @mode(host.native)
#~ func main(): void {
let load = 7;
let state = if (load > 3) { "busy" } else { "idle" };
println(state);
#~ }
```

*Listing 5-2: An `if` expression instead of `?:`*

```text
busy
```

Each branch is a block whose last expression is its value; both branches must produce the same type. This is also how you initialize a `let` whose value depends on a condition.

## Loops

Loup has four loop forms: `while`, `do … while`, the counted C-style `for`, and `for … in` over anything iterable.

```loup
#~ @mode(host.native)
func main(): void {
    var attempts = 0;
    while (attempts < 3) {
        attempts = attempts + 1;
    }

    var polls = 0;
    do {
        polls = polls + 1;
    } while (polls < 2);

    for (var i = 0; i < 3; i = i + 1) {
        print(i);
    }
    println("");

    for (i in 0..3) {
        print(i);
    }
    println("");

    for (host in ["alpha", "beta"]) {
        println(i"ping {$ host $}");
    }
    println(i"{$ attempts $} attempts, {$ polls $} polls");
}
```

*Listing 5-3: The four loop forms*

```text
012
012
ping alpha
ping beta
3 attempts, 2 polls
```

The range `start..end` excludes `end`, and it is not a value: it is allowed only as the source of a `for` loop or a comprehension. `let r = 0..3;` is `E_RANGE_FORM_MISPLACED`. There is no inclusive range; write `0..n + 1`.

`break` leaves the innermost loop and `continue` starts its next iteration. To leave an outer loop, label it:

```loup
#~ @mode(host.native)
#~ func main(): void {
let grid = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
var found = -1;
outer: for (row in grid) {
    for (cell in row) {
        if (cell * cell > 30) {
            found = cell;
            break outer;
        }
    }
}
println(found);
#~ }
```

*Listing 5-4: Breaking out of nested loops with a label*

```text
6
```

### Loop variables are immutable

The variable a `for … in` loop binds is a `let`. Assigning to it is an error:

```loup,does_not_compile
@mode(host.native)

func main(): void {
    let prices = [100, 250];
    for (p in prices) {
        p = p + p // 10;
        println(p);
    }
}
```

```text
main.lphc:6:9: error[E0001]: cannot assign to immutable binding 'p'
```

Declare it `var` if you want a scratch copy you can change. Each iteration gets a fresh copy, so assigning to it never changes the list:

```loup
#~ @mode(host.native)
#~ func main(): void {
let prices = [100, 250];
for (var p in prices) {
    p = p + p // 10;
    println(p);
}
println(prices);
#~ }
```

*Listing 5-5: A `var` loop variable*

```text
110
275
[100, 250]
```

To build a changed list, use a comprehension: `[p + p // 10 for p in prices]` (chapter 6).

### Changing a collection while iterating over it

In C++, appending to a `std::vector` while iterating over it can invalidate the iterator. The Loup specification makes the equivalent a compile-time error: a `for (x in items)` loop borrows `items` for the whole body, and mutating `items` inside is `E_BORROW_LOOP_MUTATION` (Language Reference §1-6.4). Stage0 1.25.1 does not check this yet, and the loop sees elements appended while it runs:

```loup,not_desired_behavior
@mode(host.native)

func main(): void {
    var orderLines = [1200, 800, 1500];
    var visited = 0;
    for (amount in orderLines) {
        visited = visited + 1;
        if (amount > 1000) {
            orderLines.push(amount // 100);   # an insurance line for large orders
        }
    }
    println(i"visited {$ visited $} lines: {$ orderLines $}");
}
```

```text
visited 5 lines: [1200, 800, 1500, 12, 15]
```

The loop was meant to visit the three original lines; it visited the two new ones too. If a new line had itself been over 1000, the loop would never end. Collect the additions separately and append them after the loop, which is also what the specified rule will require:

```loup
#~ @mode(host.native)
#~ func main(): void {
var orderLines = [1200, 800, 1500];
var insurance: list[int] = [];
for (amount in orderLines) {
    if (amount > 1000) {
        insurance.push(amount // 100);
    }
}
orderLines = orderLines + insurance;
println(orderLines);
#~ }
```

*Listing 5-6: Collecting changes, then applying them*

```text
[1200, 800, 1500, 12, 15]
```

## `match`

`match` compares a value against patterns and evaluates the first arm that matches. There are no parentheses around the value being matched, and arms are separated by commas:

```loup
#~ @mode(host.native)
func classify(status: int): string {
    return match status {
        200 => "ok",
        404 => "not found",
        code if (code >= 500) => i"server error {$ code $}",
        _ => "other",
    };
}

func main(): void {
    println(classify(200));
    println(classify(503));
    println(classify(302));
}
```

*Listing 5-7: Literal patterns, a guard, and a wildcard*

```text
ok
server error 503
other
```

A plain name such as `code` matches anything and binds it; `if (…)` after it is a guard; `_` matches anything without binding. Alternatives such as `301 | 302 =>` are not supported in 1.25.1; use a guard (`code if (code == 301 or code == 302)`).

A `match` must cover every possible value. That matters most for enums, where adding a variant makes every `match` that forgot it fail to compile:

```loup,does_not_compile
@mode(host.native)

enum Light { Red, Amber, Green }

func action(light: Light): string {
    return match light {
        Light.Red => "stop",
        Light.Green => "go",
    };
}

func main(): void {
    println(action(Light.Amber));
}
```

```text
main.lphc:6:12: error[E0001]: Non-exhaustive match: missing variants Amber
```

Add the missing arm (`Light.Amber => "slow"`) or a `_` arm. Chapter 8 covers enums, destructuring patterns, and `Option` and `Result`.

## Scoped resources with `with`

`with (name = expr) { … }` runs a block with a resource and guarantees that the resource is told when the block ends, normally or by an exception. The resource implements the `Scoped[T]` interface: `scope_enter()` returns the value bound to `name`, and `scope_exit(error)` receives `None` or `Some(exception)` (Language Reference §1-6.6). Returning `false` from `scope_exit` lets an exception continue on its way.

```loup
@mode(host.native)

class Transaction implements Scoped[Transaction] {
    pub let name: string;

    new(name: string) { this.name = name; }

    pub func scope_enter(): Transaction {
        println(i"begin {$ this.name $}");
        return this;
    }

    pub func scope_exit(error: Option[Exception]): bool {
        match error {
            None => println(i"commit {$ this.name $}"),
            Some(e) => println(i"rollback {$ this.name $}: {$ e.message $}"),
        }
        return false;
    }
}

func transfer(amount: int): void {
    with (tx = Transaction(i"transfer {$ amount $}")) {
        if (amount > 1000) {
            throw ValueError("amount over limit");
        }
        println(i"moved {$ amount $}");
    }
}

func main(): void {
    transfer(50);
    try {
        transfer(5000);
    } catch (ValueError as e) {
        println(i"caught: {$ e.message $}");
    }
}
```

*Listing 5-8: A transaction that commits or rolls back*

```text
begin transfer 50
moved 50
commit transfer 50
begin transfer 5000
rollback transfer 5000: amount over limit
caught: amount over limit
```

This is the Loup equivalent of Python's context managers and C#'s `using`. Native programs use it for files, locks and connections whose release must not wait for a reference count to reach zero.

> ⚠ **Planned.** `defer stmt;` and `defer { … }`, which run cleanup when the enclosing block exits (Language Reference §1-6.5), parse in Loup 1.25.1 but do not work yet: the interpreter skips the deferred code entirely, and a native build refuses the program (`E-BUILD-NATIVE: … an unsupported operation`). Use `with` or `try`/`finally` for cleanup until `defer` lands.

## Exceptions

`throw` raises an exception; `try`/`catch`/`finally` handles it. The catch clause names the exception type, then `as`, then the binding:

```loup
@mode(host.native)

class ConfigError : Exception {
    new(msg: string) { super(msg); }
}

func loadPort(text: string): int {
    try {
        let n = text.toInt();
        if (n <= 0) {
            throw ConfigError(i"port must be positive, got {$ n $}");
        }
        return n;
    } catch (ConfigError as e) {
        println(i"config error: {$ e.message $}");
        return 8080;
    } finally {
        println("checked port");
    }
}

func main(): void {
    println(loadPort("9000"));
    println(loadPort("-1"));
}
```

*Listing 5-9: `try`, `catch`, `finally`*

```text
checked port
9000
config error: port must be positive, got -1
checked port
8080
```

`finally` runs on every path out of the `try`, including the `return` inside it, which is why "checked port" prints before the caller prints the value. Chapter 12 covers the exception hierarchy, `catch (A | B as e)`, chaining with `throw … from e`, `Result` values and the `?` operator, and what an uncaught exception looks like in a native binary.

## Summary

- Conditions take parentheses (`E_CONDITION_PARENS`) and bodies take braces. `if` with `else` is an expression and replaces the ternary operator, which does not exist.
- Loops: `while`, `do … while`, `for (var i = 0; …; …)`, and `for (x in xs)`. `start..end` excludes `end` and is only valid as a loop source.
- A `for … in` variable is immutable; write `for (var x in xs)` for a mutable copy.
- Mutating a list inside a loop over it should be `E_BORROW_LOOP_MUTATION`; 1.25.1 does not check it and the loop sees the new elements, so collect changes and apply them afterwards.
- `match` is exhaustive, supports literals, bindings, guards and `_`; `|` alternatives are not supported yet.
- `with (x = resource) { … }` calls `scope_enter` and `scope_exit` on every exit path. `defer` is ⚠ planned: it parses but does not run.
- `catch (Type as e)` catches by type; `finally` always runs. Chapter 6 covers collections.
