Tzopilotl
Docs
GitHub

Tzopilotl by Example

6. Ranges and For Loops

6.1 Range Expressions

-- Ranges use (start..end) syntax with inclusive bounds
let r = (1..5);          -- Range from 1 to 5 inclusive
r println;                -- (1..5)
r length println;         -- 5
r toArray println;        -- [1, 2, 3, 4, 5]

-- Stepped ranges: (start,next..end)
let evens = (0,2..10);    -- step inferred as next - start = 2
evens toArray println;   -- [0, 2, 4, 6, 8, 10]

-- Descending ranges infer step automatically
let down = (5..1);        -- step inferred as -1
down toArray println;    -- [5, 4, 3, 2, 1]

-- Descending with explicit step
let odds = (9,7..1);     -- step = 7 - 9 = -2
odds toArray println;    -- [9, 7, 5, 3, 1]

-- Infinite ranges
let nat = (1..);          -- 1, 2, 3, 4, ... (no end)
let by3 = (0,3..);        -- 0, 3, 6, 9, ... (stepped, no end)

6.2 For Loops over Ranges

-- Basic for loop: for (variable : iterable) { body }
for (i : (1..5)) {
    i println;
}
-- Output: 1 2 3 4 5 (each on its own line)

-- Stepped range
for (i : (0,2..10)) {
    i println;
}
-- Output: 0 2 4 6 8 10

-- Descending range (step inferred as -1)
for (i : (5..1)) {
    i println;
}
-- Output: 5 4 3 2 1

-- Descending with explicit step
for (i : (10,7..1)) {
    i println;
}
-- Output: 10 7 4 1

Range Optimization

When a range literal appears directly in a for loop (e.g., for (i : (1..100))), the compiler emits an inline counter loop. No Range object is allocated and no array is materialized.

6.3 For Loops over Arrays and Lists

-- For loop over an array
let arr = [10, 20, 30];
for (x : arr) {
    x println;
}
-- Output: 10 20 30

-- For loop over a list
let lst = List(1, 2, 3);
for (x : lst) {
    x println;
}
-- Output: 1 2 3

6.4 Range Built-in Functions

let r = (1..5);

-- length: number of elements in the range
r length println;       -- 5

-- toArray: materialize into an [Int]
r toArray println;      -- [1, 2, 3, 4, 5]

-- toList: create a lazy List<Int>
r toList println;       -- List(1, 2, 3, 4, 5)

-- toList on an infinite range produces an infinite lazy list
let nat = (1..);
let nats = nat toList;
nats println;           -- List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...)

6.5 Fraction Ranges

Ranges also support fraction endpoints and steps. Use fraction literals like 1/2 (no spaces around /).

-- Basic fraction range (step defaults to 1)
for (i : (1/2..5/2)) {
    i println;
}
-- Output: 1/2 3/2 5/2

-- Stepped fraction range: (start,next..end)
for (i : (1/4, 1/2..2/1)) {
    i println;
}
-- Output: 1/4 1/2 3/4 1/1 5/4 3/2 7/4 2/1

-- Descending fraction range (step inferred)
for (i : (3/1..1/2)) {
    i println;
}
-- Output: 3/1 2/1 1/1

-- Fraction range as value
let fr = (1/4, 3/4..3/1);
fr length println;      -- 6
fr toArray println;     -- [1/4, 3/4, 5/4, 7/4, 9/4, 11/4]
fr toList println;      -- List(1/4, 3/4, 5/4, 7/4, 9/4, 11/4)

-- Infinite fraction range
let flinf = (1/2..) toList;
flinf println;          -- List(1/2, 3/2, 5/2, 7/2, 9/2, ...)

6.6 Indexing Ranges

Ranges are indexable with [], without materializing an array. Finite ranges index cyclically, just like arrays: the index wraps modulo the range's length, so negative indices count from the end. Infinite ranges have no length to wrap by; they use the absolute value of the index instead.

-- Finite ranges: cyclic indexing, like arrays
let r = (0..9);
r[3] println;           -- 3
r[-1] println;          -- 9 (last element)
r[13] println;          -- 3 (wraps around)

-- Works with stepped and descending ranges
(0,2..10)[3] println;  -- 6
(5..1)[1] println;     -- 4

-- Infinite ranges: the absolute value of the index
let nat = (0..);
nat[5] println;         -- 5
nat[-3] println;        -- 3

-- Fraction ranges index the same way
(1/2..5/2)[1] println; -- 3/2

-- Indexing by a collection of indices, like arrays
(0..9)[[3, 1, 2]] println;   -- [3, 1, 2]

-- Explicit @ auto-map over containers of ranges
[(0..9), (10..19)] @ [2] println;  -- [2, 12]