llm
Download
368 lines 11.4 KiBTomoModified 2026-08-20 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368

struct Pair{x:Text, y:Text}

func pairwise(strs:[Text] -> func(->Pair?))
    i := 1
    return func(-> Pair?)
        i += 1
        return Pair{strs[i-1] or return none, strs[i] or return none}

func range(first:Int, last:Int -> func(->Int?))
    i := first
    return func(->Int?)
        if i > last
            return none
        i += 1
        return (i-1)

test "pairwise iterator comprehensions"
    >> values := ["A", "B", "C", "D"]
    >> (++: "($(foo.x)$(foo.y))" for foo in pairwise(values))
    assert (++: "($(foo.x)$(foo.y))" for foo in pairwise(values))! == "(AB)(BC)(CD)"
    >> ["$(foo.x)$(foo.y)" for foo in pairwise(values)]
    assert ["$(foo.x)$(foo.y)" for foo in pairwise(values)] == ["AB", "BC", "CD"]

test "pairwise iterator in for loop"
    >> values := ["A", "B", "C", "D"]
    result : @[Text]
    for foo in pairwise(values)
        >> "$(foo.x)$(foo.y)"
        result.insert("$(foo.x)$(foo.y)")
    >> result[]
    assert result[] == ["AB", "BC", "CD"]

test "range iterator"
    >> [i for i in range(5, 10)]
    assert [i for i in range(5, 10)] == [5, 6, 7, 8, 9, 10]
    >> (+: range(5, 10))
    assert (+: range(5, 10))! == 45

test "loop indices are always Int64"
    xs := [10, 20, 30]
    for x at i in xs
        typed : Int64 = i # the index is a native Int64
        assert xs[typed]! == x
        _ : Int = i + 1 # ...and promotes to Int seamlessly

test "integer-count loop variables match the count's type"
    total := 0
    for x in 5
        typed : Int = x # `x` has the count's type
        total += typed
    assert total == 15

test "integer-count loops can have an Int64 index"
    n := 10
    for x at i in n
        _ : Int64 = i # the index is a native Int64
        _ : Int = x # ...and the value is an Int, like the count
        assert Int(i) == x
    big_hits := 0
    for x at i in Int(99999999999999)
        _ : Int64 = i
        _ : Int = x
        big_hits += 1
        if big_hits == 3
            break
    assert big_hits == 3

test "ranges and iterator functions can have an Int64 index"
    # `for x at i in a.to(b)`: i counts 1, 2, 3, ... as Int64; x is the range value
    large := Int(99999999999999)
    for x at step in large.to(large+3)
        _ : Int64 = step
        assert large + Int(step) - 1 == x
    for x at step in Int64(10).to(Int64(13))
        assert Int64(9) + step == x
    # `for x at i in n.onward()`
    total := 0
    for x at i in Int(100).onward()
        total += x
        if i == Int64(3)
            break
    assert total == 100 + 101 + 102
    # `for x at i in iterfn`
    got := ""
    for foo at i in pairwise(["A", "B", "C"])
        got ++= "$(i):$(foo.x)$(foo.y) "
    assert got == "1:AB 2:BC "

test "native Int64 counts are iterable"
    total : Int64 = 0
    for x in Int64(4)
        total += x
    assert total == Int64(10)
    for x at i in Int64(4)
        assert i == x
    ran_else := no
    for x in Int64(0)
        pass
    else
        ran_else = yes
    assert ran_else

test "text iteration indices are Int64 and 1-based"
    out := ""
    for c at i in "abc"
        out ++= "$(i)$(c)"
    assert out == "1a2b3c"

test "comprehensions and reducers support index variables"
    xs := [10, 20, 30]
    t := {"a": 1, "b": 2}
    # list comprehensions
    assert [i*x for x at i in xs] == [10, 40, 90]
    assert [x for x at i in xs if i mod 2 == 1] == [10, 30]
    assert [b for b at a in 3] == [1, 2, 3]
    assert [a for b at a in 5.to(7)] == [Int64(1), Int64(2), Int64(3)]
    # set and table comprehensions
    assert {i*x for x at i in xs} == {10, 40, 90}
    assert {i: x for x at i in xs} == {Int64(1): 10, Int64(2): 20, Int64(3): 30}
    assert {v: k for k, v in t.entries()} == {1: "a", 2: "b"}
    # nested comprehensions (later `for` is the outer loop)
    assert [i*10 + j for x at i in 2 for y at j in 2] == [Int64(11), Int64(21), Int64(12), Int64(22)]
    # reducers
    assert (+: i for x at i in xs)! == Int64(6)
    assert (+: x for x at i in xs if i != 2)! == 40
    assert (+: i for x at i in 2.to(5))! == Int64(10)
    assert (+: v for k, v in t.entries())! == 3
    assert (++: "$(i)$(c)" for c at i in "abc")! == "1a2b3c"
    assert (_max_: i*x for x at i in [30, 20, 10])! == 40

test "the old leading-index loop form is an error"
    for i, x in [10, 20, 30]
        pass
fails_compile "use `at`"

func int_pairs(xs:[Int] -> func(a:&Int, b:&Int -> Bool))
    i := 1
    j := 1
    return func(a:&Int, b:&Int -> Bool)
        j += 1
        if j > xs.length
            i += 1
            j = i + 1
        if j > xs.length
            return no
        a[] = xs[i]!
        b[] = xs[j]!
        return yes

func numbered(names:[Text] -> func(n:&Int64, name:&Text -> Bool))
    i := Int64(0)
    return func(n:&Int64, name:&Text -> Bool)
        i += 1
        if i > Int64(names.length)
            return no
        n[] = i
        name[] = names[i]!
        return yes

test "multi-value iterators yield through & out-parameters"
    xs := [1, 2, 3, 4]
    got := ""
    for a, b in int_pairs(xs)
        got ++= "$(a)$(b) "
    assert got == "12 13 14 23 24 34 "
    # with an `at` counter
    got2 := ""
    for a, b at i in int_pairs(xs)
        got2 ++= "$(i):$(a)$(b) "
    assert got2 == "1:12 2:13 3:14 4:23 5:24 6:34 "
    # heterogeneous yield types
    got3 := ""
    for n, name in numbered(["a", "b"])
        got3 ++= "$(n)$(name) "
    assert got3 == "1a 2b "
    # `_` discards bind nothing
    total := 0
    for a, _ in int_pairs(xs)
        total += a
    assert total == 1 + 1 + 1 + 2 + 2 + 3
    # comprehensions and reducers
    assert [a*10 + b for a, b in int_pairs(xs)] == [12, 13, 14, 23, 24, 34]
    assert (+: a*b for a, b in int_pairs(xs) if a != 1)! == 6 + 8 + 12
    # continue and break
    got4 := ""
    for a, b at i in int_pairs(xs)
        if i == 2
            continue
        if i == 4
            break
        got4 ++= "$(a)$(b) "
    assert got4 == "12 14 "
    # else clause runs when the iterator yields nothing
    ran_else := no
    for a, b in int_pairs([7])
        pass
    else
        ran_else = yes
    assert ran_else

test "multi-value iterator variable counts must match the yield count"
    xs := [1, 2, 3]
    for a in int_pairs(xs)
        pass
fails_compile "yields 2 values per iteration, but this loop has 1 variable"

test "a leading index variable on a multi-value iterator suggests `at`"
    xs := [1, 2, 3]
    for i, a, b in int_pairs(xs)
        pass
fails_compile "bind it with `at`"

test "lockstep iteration over multiple iterables"
    xs := [10, 20, 30]
    ys := ["a", "b", "c"]
    got := ""
    for x, y in xs, ys
        got ++= "$(x)$(y) "
    assert got == "10a 20b 30c "
    # the loop ends when the shortest iterable runs out (either side):
    assert ["$(x)$(y)" for x, y in xs, [1, 2]] == ["101", "202"]
    assert ["$(x)$(y)" for x, y in [1, 2], xs] == ["110", "220"]
    # `at` counts iterations alongside the lockstep values:
    assert ["$(i):$(x)$(y)" for x, y at i in xs, ys] == ["1:10a", "2:20b", "3:30c"]
    # `_` discards a value:
    assert [y for _, y in xs, ys] == ["a", "b", "c"]
    # any iterable kind can participate: counts, ranges, text, tables...
    assert ["$(n)$(x)" for n, x in 5, xs] == ["110", "220", "330"]
    assert ["$(r)$(x)" for r, x in 100.to(200), xs] == ["10010", "10120", "10230"]
    assert ["$(c)$(x)" for c, x in "abc", xs] == ["a10", "b20", "c30"]
    t := {"one": 1, "two": 2}
    assert ["$(k)$(v)$(x)" for k, v, x in t.entries(), xs] == ["one110", "two220"]
    s := {5, 6, 7}
    assert ["$(e)$(x)" for e, x in s, xs] == ["510", "620", "730"]
    # ...including multi-value iterator functions:
    assert ["$(a)$(b)$(y)" for a, b, y in int_pairs([1, 2, 3]), ys] == ["12a", "13b", "23c"]

test "lockstep iterables are evaluated left to right, exactly once"
    order : @[Text]
    first := func(-> [Int])
        order.insert("first")
        return [1, 2]
    second := func(-> [Int])
        order.insert("second")
        return [10, 20]
    for x, y in first(), second()
        pass
    assert order[] == ["first", "second"]

test "lockstep loops support continue, break, and else"
    xs := [10, 20, 30]
    ys := ["a", "b", "c"]
    got := ""
    for x, y at i in xs, ys
        if i == 1
            continue
        if i == 3
            break
        got ++= "$(x)$(y)"
    assert got == "20b"
    ran_else := no
    empty : [Int] = []
    for x, y in xs, empty
        pass
    else
        ran_else = yes
    assert ran_else
    # ...but not when every iterable has values:
    ran_else2 := no
    for x, y in xs, ys
        pass
    else
        ran_else2 = yes
    assert not ran_else2

test "lockstep reducers"
    # dot product, no intermediate list:
    assert (+: x*y for x, y in [1, 2, 3], [4, 5, 6])! == 32

test "reducers inside lambdas capture their iterables"
    xs := [1, 2, 3]
    ys := [10, 20, 30]
    f := func(-> Int)
        return (+: x*2 for x in xs) or 0
    assert f() == 12
    g := func(-> Int)
        return (+: a*b for a, b in xs, ys) or 0
    assert g() == 140

test "lockstep variable counts must match the iterables' total yields"
    t := {"one": 1}
    for k, x in t.entries(), [1, 2]
        pass
fails_compile "`t.entries()` yields 2 values, `[1, 2]` yields 1 value"

test "table.entries() iterates key/value pairs"
    t := {"one": 1, "two": 2, "three": 3}
    got := ""
    for k, v in t.entries()
        got ++= "$k=$v "
    assert got == "one=1 two=2 three=3 "
    # with an `at` counter, in comprehensions, and in reducers:
    assert ["$(i):$(k)$(v)" for k, v at i in t.entries()] == ["1:one1", "2:two2", "3:three3"]
    assert (+: v for _, v in t.entries())! == 6
    assert {v: k for k, v in t.entries()} == {1: "one", 2: "two", 3: "three"}
    # snapshot semantics: mutations after making the iterator aren't seen
    t2 := @{"a": 1}
    iter := t2.entries()
    t2.set("b", 2)
    assert ["$k$v" for k, v in iter] == ["a1"]

test "list.pairs() iterates each unordered pair once (i < j)"
    xs := [10, 20, 30, 40]
    assert ["$a-$b" for a, b in xs.pairs()] == ["10-20", "10-30", "10-40", "20-30", "20-40", "30-40"]
    assert (+: 1 for _, _ in [1, 2, 3].pairs()) == 3
    assert [b for _, b in [5, 6].pairs()] == [6]
    # too few elements for any pair:
    assert ["$a$b" for a, b in [7].pairs()] == []
    ran_else := no
    empty : [Int] = []
    for a, b in empty.pairs()
        pass
    else
        ran_else = yes
    assert ran_else
    # snapshot semantics: mutations after making the iterator aren't seen
    ys := @[1, 2]
    p := ys.pairs()
    ys[1] = 99
    assert ["$a$b" for a, b in p] == ["12"]

test "pairs()/entries() keep snapshot semantics when inlined in for-position"
    # These compile to inline index loops (no closure); mutating the container
    # in the body must still not affect the iteration (the snapshot is stable).
    xs := @[1, 2, 3]
    got := ""
    for a, b in xs.pairs()
        got ++= "$a$b "
        xs[1] = 99
    assert got == "12 13 23 "
    assert xs[1]! == 99
    t := @{"a": 1, "b": 2}
    got2 := ""
    for k, v in t.entries()
        got2 ++= "$k$v "
        t.set("c", 3)
    assert got2 == "a1 b2 "

test "tables can't be iterated directly"
    t := {"one": 1}
    for k, v in t
        pass
fails_compile "Use `.entries()`"

test "sets iterate their elements directly"
    s := {10, 20, 30}
    total := 0
    for x at i in s
        total += x + Int(i)
    assert total == 66
    assert (+: x for x in s)! == 60

test "by-reference variables aren't allowed in lockstep loops"
    xs := @[1, 2, 3]
    for &x, y in xs, [4, 5, 6]
        x[] += y
fails_compile "aren't supported when iterating over multiple values in lockstep"