Control Flow
Control flow statements in Axe determine the order and conditions under which code is executed. Axe provides a variety of constructs for managing program flow with clear, predictable semantics.
Conditional Statements
if-elif-else
The if statement executes a block of code if a condition is true. Use elif (else if) for multiple conditions and else for a fallback block.
val age: i32 = 25;
if age < 18 {
println "Minor";
} elif age < 65 {
println "Adult";
} else {
println "Senior";
}
Characteristics:
- Conditions must be of type
bool elifandelseblocks are optional- Multiple
elifbranches can be chained - Each branch is mutually exclusive
- No implicit type coercion of conditions
Boolean Operators
Axe supports standard boolean operators for combining conditions:
val x: i32 = 10;
val y: i32 = 20;
val valid: bool = true;
// AND operator
if x > 5 and y < 25 {
println "Both conditions true";
}
// OR operator
if x > 15 or y > 15 {
println "At least one condition true";
}
// NOT operator
if !valid {
println "Not valid";
}
Short-circuit Evaluation:
andoperator: Returns false immediately if the left operand is falseoroperator: Returns true immediately if the left operand is true- This ensures efficient evaluation and allows safe property access chains
Loops
Loop Statement
The loop statement creates an infinite loop that continues until a break statement is encountered.
mut counter: i32 = 0;
loop {
if counter >= 5 {
break;
}
println counter;
counter = counter + 1;
}
For Loops
Axe provides C-style for loops for iterating a specific number of times:
// Traditional for loop
for mut i = 0; i < 10; i++ {
println i;
}
// Using references
val data: ref StringList = get_data();
for mut i = 0; i < len(deref(data)); i++ {
val item: string = StringList.get(data, i);
println item;
}
For Loop Structure:
for [initialization]; [condition]; [increment] {
[body]
}
- Initialization: Declares and initializes loop variables (e.g.,
mut i = 0) - Condition: Boolean expression evaluated before each iteration
- Increment: Expression executed after each iteration (e.g.,
i++) - Body: Code executed in each iteration
Parallel For Loops
For CPU-intensive operations, Axe supports parallel for loops using OpenMP:
parallel for mut i = 0; i < 1000; i++ {
val result: i32 = expensive_computation(i);
process_result(result);
}
Parallel For Loop Rules:
- Loop variable must be mutable
- Requires OpenMP support (automatically linked when detected)
- Safe for data-parallel algorithms
- Best for computationally intensive iterations
- Avoid side effects and shared state modifications
Break and Continue
Control loop execution with break and continue:
// Using break to exit loop
for mut i = 0; i < 100; i++ {
if should_exit(i) {
break;
}
process(i);
}
// Using continue to skip to next iteration
for mut i = 0; i < 100; i++ {
if skip_this(i) {
continue;
}
process(i);
}
Switch Statements
Switch statements provide efficient multi-way branching based on discrete values:
val status_code: i32 = 404;
switch status_code {
case 200 {
println "OK";
}
case 400 {
println "Bad Request";
}
case 404 {
println "Not Found";
}
case 500 {
println "Server Error";
}
default {
println "Unknown status";
}
}
Switch Characteristics:
- Supports integer and string expressions
- Multiple case branches with associated blocks
- Optional
defaultblock (fallback) - Each case is independent (no fall-through)
- Cases are mutually exclusive
- Efficient for many conditions
Default Case
The default case is optional and executes if no case matches:
val command: string = get_user_input();
switch command {
case "start" {
initialize();
}
case "stop" {
shutdown();
}
default {
println "Unknown command";
}
}
Guard Clauses
Guard clauses are early returns used to exit functions when conditions aren't met, improving code readability:
def validate_input(data: string): bool {
// Guard: check empty
if str_len(data) == 0 {
println "error: empty input";
return false;
}
// Guard: check length
if str_len(data) > 1000 {
println "error: input too long";
return false;
}
// Guard: check format
if !is_valid_format(data) {
println "error: invalid format";
return false;
}
// Main logic here - all guards passed
process_data(data);
return true;
}
Benefits of Guard Clauses:
- Reduces nesting and improves readability
- Makes error cases explicit
- Follows the "fail fast" principle
- Reduces cognitive complexity
Pattern Matching
While Axe doesn't have built-in pattern matching, similar functionality can be achieved with switch statements and conditional checks:
model Result {
success: bool;
value: i32;
error_msg: string;
}
def handle_result(result: Result) {
if result.success {
println concat(str("Success: "), i32_to_string(result.value));
} else {
println concat(str("Error: "), result.error_msg);
}
}
Control Flow Best Practices
1. Minimize Nesting
// Avoid deep nesting
if condition1 {
if condition2 {
if condition3 {
do_something();
}
}
}
// Use guard clauses
if !condition1 {
return;
}
if !condition2 {
return;
}
if !condition3 {
return;
}
do_something();
2. Use Switch for Multiple Cases
// Many if-elif chains
if type == 1 {
handle_type_one();
} elif type == 2 {
handle_type_two();
} elif type == 3 {
handle_type_three();
}
// Use switch
switch type {
case 1 { handle_type_one(); }
case 2 { handle_type_two(); }
case 3 { handle_type_three(); }
}
3. Clear Loop Intent
// Clear loop intention
for mut i = 0; i < items.len; i++ {
val item: string = StringList.get(items, i);
if should_skip(item) {
continue;
}
process(item);
}
4. Parallel Loops Only Where Beneficial
// Use parallel for CPU-intensive work
parallel for mut i = 0; i < 10000; i++ {
val result: f64 = complex_math_operation(i);
store_result(i, result);
}
// Avoid for simple operations
parallel for mut i = 0; i < 10; i++ {
println i; // Too much overhead
}
Performance Considerations
Loop Unrolling
For simple, repetitive operations, the compiler may optimize loops:
// Compiler may optimize this
for mut i = 0; i < items.len; i++ {
process_simple(items.data[i]);
}
Parallel Loop Overhead
Parallel loops have overhead costs. Use them only when:
- Loop body is computationally intensive
- Number of iterations is sufficiently large (typically 100+)
- Data dependencies allow parallelization
Break Performance
Breaking out of loops early can significantly improve performance:
// Early termination for searches
for mut i = 0; i < large_array.len; i++ {
if found_target(large_array.data[i]) {
return i; // Exit early
}
}