This chapter covers NanoLang's control flow constructs: conditionals (if/else and cond), loops (while and for), and common patterns.
5.1 if/else Expressions
The if/else construct is used for conditional execution of statements.
Basic if Statement
fn check_positive(x: int) -> void {
if (> x 0) {
(println "Positive")
}
}
shadow check_positive {
(check_positive 5)
(check_positive -3)
}
if/else Statement
fn describe_number(x: int) -> void {
if (> x 0) {
(println "Positive")
} else {
(println "Not positive")
}
}
shadow describe_number {
(describe_number 5)
(describe_number -3)
}
if/else if/else Chain
fn classify_number(x: int) -> void {
if x > 0 {
(println "Positive")
} else if x < 0 {
(println "Negative")
} else {
(println "Zero")
}
}
shadow classify_number {
(classify_number 5)
(classify_number -3)
(classify_number 0)
}
NanoLang supports else if chaining for multi-way conditionals. You can also nest if inside else blocks for the same effect.
Expressions vs Statements
**When to use if/else:**
- For side effects (printing, modifying variables)
- When you don't need a return value
- For control flow in procedures
fn example_statement(x: int) -> void {
if (> x 10) {
(println "Big")
set x (* x 2) # Side effect
}
}
shadow example_statement {
(example_statement 15)
}
5.2 cond Multi-way Branches
For expressions that **return a value**, use cond instead of if/else.
Basic cond Syntax
fn sign(x: int) -> int {
return (cond
((< x 0) -1)
((> x 0) 1)
(else 0)
)
}
shadow sign {
assert (== (sign 5) 1)
assert (== (sign -3) -1)
assert (== (sign 0) 0)
}
**Syntax:** (cond (test result) (test result) ... (else default))
When to Use cond
Use cond when:
- You need to return a value based on conditions
- You want expression-style conditionals
- You're choosing between multiple alternatives
fn grade_to_points(grade: string) -> int {
return (cond
((== grade "A") 4)
((== grade "B") 3)
((== grade "C") 2)
((== grade "D") 1)
(else 0)
)
}
shadow grade_to_points {
assert (== (grade_to_points "A") 4)
assert (== (grade_to_points "C") 2)
assert (== (grade_to_points "F") 0)
}
Multiple Tests
Each test is evaluated in order until one matches:
fn categorize_age(age: int) -> string {
return (cond
((< age 13) "child")
((< age 20) "teenager")
((< age 65) "adult")
(else "senior")
)
}
shadow categorize_age {
assert (== (categorize_age 10) "child")
assert (== (categorize_age 15) "teenager")
assert (== (categorize_age 30) "adult")
assert (== (categorize_age 70) "senior")
}
else Clause
The else clause provides a default value:
fn is_vowel(c: int) -> bool {
return (cond
((== c 97) true) # 'a'
((== c 101) true) # 'e'
((== c 105) true) # 'i'
((== c 111) true) # 'o'
((== c 117) true) # 'u'
(else false)
)
}
shadow is_vowel {
assert (is_vowel 97) # 'a'
assert (not (is_vowel 98)) # 'b'
}
Pattern Matching with cond
cond is useful for pattern-like matching:
fn day_type(day: string) -> string {
return (cond
((or (== day "Saturday") (== day "Sunday")) "weekend")
((== day "Friday") "almost weekend")
(else "weekday")
)
}
shadow day_type {
assert (== (day_type "Monday") "weekday")
assert (== (day_type "Friday") "almost weekend")
assert (== (day_type "Saturday") "weekend")
}
Nested cond
You can nest cond expressions:
fn tax_bracket(income: int, married: bool) -> float {
return (cond
(married (cond
((< income 20000) 0.10)
((< income 50000) 0.15)
(else 0.25)
))
(else (cond
((< income 10000) 0.10)
((< income 40000) 0.15)
(else 0.25)
))
)
}
shadow tax_bracket {
assert (== (tax_bracket 15000 true) 0.10)
assert (== (tax_bracket 15000 false) 0.15)
}
5.3 while Loops
The while loop repeats code while a condition is true.
Basic while Loop
fn count_to_n(n: int) -> void {
let mut i: int = 1
while (<= i n) {
(println (int_to_string i))
set i (+ i 1)
}
}
shadow count_to_n {
(count_to_n 3)
}
**Syntax:** while (condition) { body }
Loop Conditions
The condition is checked before each iteration:
fn sum_to_n(n: int) -> int {
let mut sum: int = 0
let mut i: int = 1
while (<= i n) {
set sum (+ sum i)
set i (+ i 1)
}
return sum
}
shadow sum_to_n {
assert (== (sum_to_n 5) 15) # 1+2+3+4+5
assert (== (sum_to_n 0) 0)
assert (== (sum_to_n 10) 55)
}
Breaking Out of Loops
Use early returns to exit a loop:
fn find_first_negative(arr: array<int>) -> int {
let mut i: int = 0
while (< i (array_length arr)) {
let val: int = (array_get arr i)
if (< val 0) {
return i # Exit loop and function
}
set i (+ i 1)
}
return -1 # Not found
}
shadow find_first_negative {
assert (== (find_first_negative [1, 2, -3, 4]) 2)
assert (== (find_first_negative [1, 2, 3]) -1)
}
Infinite Loops
Be careful: loops without a way to exit will run forever:
# ❌ Don't do this (infinite loop):
# fn infinite() -> void {
# while true {
# (println "Forever!")
# }
# }
Common while Loop Patterns
**Counter pattern:**
fn factorial(n: int) -> int {
let mut result: int = 1
let mut i: int = 1
while (<= i n) {
set result (* result i)
set i (+ i 1)
}
return result
}
shadow factorial {
assert (== (factorial 5) 120)
}
**Accumulator pattern:**
fn sum_array(arr: array<int>) -> int {
let mut sum: int = 0
let mut i: int = 0
while (< i (array_length arr)) {
set sum (+ sum (array_get arr i))
set i (+ i 1)
}
return sum
}
shadow sum_array {
assert (== (sum_array [1, 2, 3, 4, 5]) 15)
}
**Search pattern:**
fn contains(arr: array<int>, target: int) -> bool {
let mut i: int = 0
while (< i (array_length arr)) {
if (== (array_get arr i) target) {
return true
}
set i (+ i 1)
}
return false
}
shadow contains {
assert (contains [1, 2, 3, 4, 5] 3)
assert (not (contains [1, 2, 3] 9))
}
5.4 for-in-range Loops
The for loop uses range iteration for counting.
Basic for Loop
fn print_numbers() -> void {
for i in (range 0 5) {
(println (int_to_string i))
}
}
shadow print_numbers {
(print_numbers)
}
**Syntax:** for var in (range start end) { body }
**Components:**
1. **var** - Loop variable name (immutable within loop)
2. **range** - Built-in function: (range start end)
3. **start** - First value (inclusive)
4. **end** - Last value (exclusive)
5. **body** - Code to execute each iteration
**Range behavior:**
(range 0 5)yields: 0, 1, 2, 3, 4(range 1 4)yields: 1, 2, 3(range 0 0)yields nothing (no iterations)
Range Iteration
Common pattern: iterate from 0 to n-1:
fn sum_range(n: int) -> int {
let mut sum: int = 0
for i in (range 0 n) {
set sum (+ sum i)
}
return sum
}
shadow sum_range {
assert (== (sum_range 5) 10) # 0+1+2+3+4
assert (== (sum_range 0) 0)
}
Loop Variables
The loop variable is automatically declared and scoped to the loop:
fn use_loop_variable() -> int {
let mut result: int = 0
for i in (range 1 11) {
set result (+ result i)
}
# i is not accessible here
return result
}
shadow use_loop_variable {
assert (== (use_loop_variable) 55) # 1+2+3+...+10
}
Counting Backwards
For descending ranges, use a while loop (range only goes forward):
fn countdown(n: int) -> void {
let mut i: int = n
while (> i 0) {
(println (int_to_string i))
set i (- i 1)
}
}
shadow countdown {
(countdown 3)
}
Array Iteration
Iterate over array indices with range:
fn process_array(arr: array<int>) -> int {
let mut sum: int = 0
for i in (range 0 (array_length arr)) {
set sum (+ sum (array_get arr i))
}
return sum
}
shadow process_array {
assert (== (process_array [10, 20, 30]) 60)
}
Custom Step Sizes
For non-unit steps, use while loops:
fn sum_evens(n: int) -> int {
let mut sum: int = 0
let mut i: int = 0
while (< i n) {
set sum (+ sum i)
set i (+ i 2) # Step by 2
}
return sum
}
shadow sum_evens {
assert (== (sum_evens 10) 20) # 0+2+4+6+8
}
5.5 Loop Patterns & Idioms
Common patterns you'll use frequently.
Accumulation
Building up a result:
fn multiply_array(arr: array<int>) -> int {
let mut product: int = 1
for i in (range 0 (array_length arr)) {
set product (* product (array_get arr i))
}
return product
}
shadow multiply_array {
assert (== (multiply_array [2, 3, 4]) 24)
assert (== (multiply_array [1, 1, 1]) 1)
}
Iteration
Processing each element:
fn print_array(arr: array<int>) -> void {
for i in (range 0 (array_length arr)) {
(println (int_to_string (array_get arr i)))
}
}
shadow print_array {
(print_array [1, 2, 3])
}
Filtering
Counting elements that match a condition:
fn count_positives(arr: array<int>) -> int {
let mut count: int = 0
for i in (range 0 (array_length arr)) {
if (> (array_get arr i) 0) {
set count (+ count 1)
}
}
return count
}
shadow count_positives {
assert (== (count_positives [1, -2, 3, -4, 5]) 3)
}
Finding
Searching for an element:
fn find_index(arr: array<int>, target: int) -> int {
for i in (range 0 (array_length arr)) {
if (== (array_get arr i) target) {
return i
}
}
return -1
}
shadow find_index {
assert (== (find_index [10, 20, 30] 20) 1)
assert (== (find_index [10, 20, 30] 99) -1)
}
Transforming
Building a new result based on each element:
fn double_sum(arr: array<int>) -> int {
let mut sum: int = 0
for i in (range 0 (array_length arr)) {
let doubled: int = (* (array_get arr i) 2)
set sum (+ sum doubled)
}
return sum
}
shadow double_sum {
assert (== (double_sum [1, 2, 3]) 12) # (1*2)+(2*2)+(3*2)
}
Nested Loops
Processing multi-dimensional data:
fn multiplication_table(n: int) -> void {
for i in (range 1 (+ n 1)) {
for j in (range 1 (+ n 1)) {
let product: int = (* i j)
(println (int_to_string product))
}
}
}
shadow multiplication_table {
(multiplication_table 3)
}
Best Practices
**1. Choose the right loop**
# Use for when you know the iteration count
fn count_up(n: int) -> void {
for i in (range 0 n) {
(println (int_to_string i))
}
}
# Use while when the condition is complex
fn read_until_negative(arr: array<int>) -> int {
let mut i: int = 0
while (and (< i (array_length arr)) (>= (array_get arr i) 0)) {
set i (+ i 1)
}
return i
}
shadow count_up { (count_up 3) }
shadow read_until_negative {
assert (== (read_until_negative [1, 2, -1, 4]) 2)
}
**2. Keep loop bodies simple**
Good: Extract complex logic into functions
fn is_prime(n: int) -> bool {
if (< n 2) { return false }
for i in (range 2 n) {
if (== (% n i) 0) {
return false
}
}
return true
}
fn count_primes(arr: array<int>) -> int {
let mut count: int = 0
for i in (range 0 (array_length arr)) {
if (is_prime (array_get arr i)) {
set count (+ count 1)
}
}
return count
}
shadow is_prime {
assert (is_prime 7)
assert (not (is_prime 4))
}
shadow count_primes {
assert (== (count_primes [2, 3, 4, 5, 6, 7]) 4)
}
**3. Avoid off-by-one errors**
# ✅ Correct: < for array indices
fn correct_iteration(arr: array<int>) -> int {
let mut sum: int = 0
for i in (range 0 (array_length arr)) {
set sum (+ sum (array_get arr i))
}
return sum
}
# ❌ Wrong: <= would go past end
# fn wrong_iteration(arr: array<int>) -> int {
# let mut sum: int = 0
# for i in (range 0 (+ (array_length arr) 1)) {
# set sum (+ sum (array_get arr i)) # Error on last iteration
# }
# return sum
# }
shadow correct_iteration {
assert (== (correct_iteration [1, 2, 3]) 6)
}
Summary
In this chapter, you learned:
- ✅
if/elsefor statements (side effects) - ✅
condfor expressions (return values) - ✅
whileloops for condition-based iteration - ✅
forloops for counted iteration - ✅ Common loop patterns (accumulation, filtering, finding)
Practice Exercises
# 1. Find maximum using for loop
fn find_max(arr: array<int>) -> int {
let mut max: int = (array_get arr 0)
for i in (range 1 (array_length arr)) {
let val: int = (array_get arr i)
if (> val max) {
set max val
}
}
return max
}
shadow find_max {
assert (== (find_max [1, 5, 3, 9, 2]) 9)
}
# 2. Check if all elements are positive
fn all_positive(arr: array<int>) -> bool {
for i in (range 0 (array_length arr)) {
if (<= (array_get arr i) 0) {
return false
}
}
return true
}
shadow all_positive {
assert (all_positive [1, 2, 3])
assert (not (all_positive [1, -2, 3]))
}
# 3. Compute GCD using while loop
fn gcd(a: int, b: int) -> int {
let mut x: int = a
let mut y: int = b
while (!= y 0) {
let temp: int = y
set y (% x y)
set x temp
}
return x
}
shadow gcd {
assert (== (gcd 48 18) 6)
assert (== (gcd 100 50) 50)
}
---
**Previous:** Chapter 4: Functions
**Next:** Chapter 6: Collections