Browse the documentation

AEL 1.0.0

Control flow

AEL, the Agent Engineering Language, has four ways to direct control: if, while, match and return. Each is a statement, and the check makes sure that every path through a function ends the way its type says.

if and else

fn shipping(weight: u32, express: bool) -> u32 {
    if express {
        return 20;
    } else {
        if weight > 1_000 {
            return 12;
        }
    }
    return 5;
}
  • The condition must be a bool. An integer, text or Option is never a condition.
  • The condition needs no parentheses; the blocks always need braces.
  • else takes a block. To test a further condition, put an if inside that block, as above.
  • Only the chosen block runs.

while

fn sum_to(limit: u64) -> u64 {
    let mut total: u64 = 0;
    let mut n: u64 = 1;
    while n <= limit {
        total = total + n;
        n = n + 1;
    }
    return total;
}
  • The condition is checked before every pass, including the first, so the body may run zero times.
  • There is no break or continue. End a loop through its condition, or leave the function with return.

match

match compares one value against patterns, in order, and runs the block of the first that fits.

enum Reply {
    Empty,
    Text(Str<200>),
    Code(u16, Str<40>),
}

fn status(reply: Reply) -> u16 {
    match reply {
        Reply::Empty => { return 204; }
        Reply::Code(code, _) => { return code; }
        _ => { return 200; }
    }
}
  • The value is evaluated once.
  • The patterns must cover every possible value, and none may repeat another. A pattern after _ can never be reached, so it is refused.
  • An enum needs every variant, or _. A bool needs true and false, or _. Integer patterns, such as 0 or -1, need a final _.
  • You can bind a variant's values to names, as code above, or skip them with _. Binding a value moves it out of the matched value; see Ownership.
  • Each arm is a block. Its names are visible only inside it.
  • Patterns are the wildcard, true and false, integers and enum variants. Structs, text, lists and references are inspected with ordinary code instead, and a pattern cannot carry an extra condition.

Option and Result are enums, so match is how you handle them:

fn port_or_default(configured: Option<u16>) -> u16 {
    match configured {
        Option::Some(port) => { return port; }
        Option::None => { return 8080; }
    }
}

return

  • return value; leaves the function with a value of exactly its result type.
  • A function that returns unit can write return;, or simply reach the end of its body.
  • Every path through a function with a result must end in a return. The check counts a while loop as possibly running zero times, so a return that appears only inside a loop is not enough: add one after the loop.

Statements, not expressions

if and match do not produce values, and a block does not produce the value of its last line. To choose a value, give a binding its value in every branch:

fn fee(member: bool) -> u32 {
    let amount: u32;
    if member {
        amount = 0;
    } else {
        amount = 3;
    }
    return amount;
}

The check makes sure that amount has a value on every path before it is read. Ownership describes the rule.

Loops and waiting

On a microcontroller, one handler runs at a time, until it waits for something. A loop that never waits keeps every other handler waiting, so keep the work between waits bounded. Concurrency describes scheduling.