llm
Download
254 lines 7.9 KiBTomoModified 2026-09-29 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
test "table literal, lookup, and iteration"
    >> t := {"one": 1, "two": 2}
    >> t["one"]
    >> t["two"]
    >> t["???"]
    assert t == {"one": 1, "two": 2}
    assert t["one"] == 1
    assert t["two"] == 2
    assert t["???"] == none
    assert t["one"]! == 1
    assert t["???"] or -1 == -1

    >> t_str := ""
    for k,v in t.entries()
        t_str ++= "($k=$v)"
    >> t_str
    >> t.length
    >> t.keys
    >> t.values
    assert t_str == "(one=1)(two=2)"
    assert t.length == 2
    assert t.fallback == none
    assert t.keys == ["one", "two"]
    assert t.values == [1, 2]

test "fallback tables"
    >> t := {"one": 1, "two": 2}
    >> t2 := {"three": 3; fallback=t}
    >> t2["one"]
    >> t2["three"]
    >> t2.length
    >> t2.fallback
    assert t2 == {"three": 3; fallback={"one": 1, "two": 2}}
    assert t2["one"] == 1
    assert t2["three"] == 3
    assert t2["???"] == none
    assert t2.length == 1
    assert t2.fallback == {"one": 1, "two": 2}

    >> t2_str := ""
    for k,v in t2.entries()
        t2_str ++= "($k=$v)"
    >> t2_str
    assert t2_str == "(three=3)"

test "table comprehensions"
    assert {i: 10*i for i in 5} == {1: 10, 2: 20, 3: 30, 4: 40, 5: 50}
    assert {i: 10*i for i in 5 if i mod 2 != 0} == {1: 10, 3: 30, 5: 50}
    assert {x: 10*x for x in y if x > 1 for y in [3, 4, 5] if y < 5} == {2: 20, 3: 30, 4: 40}

test "heap-allocated table remove"
    >> t3 := @{1: 10, 2: 20, 3: 30}
    t3.remove(3)
    >> t3[]
    assert t3[] == {1: 10, 2: 20}

test "plain table and fallback lookup"
    >> plain := {1: 10, 2: 20, 3: 30}
    >> plain[2]!
    >> plain[456] or -999
    >> plain.has(2)
    >> plain.has(456)
    assert plain[2]! == 20
    assert plain[2]! == 20
    assert plain[456] or -999 == -999
    assert plain.has(2)
    assert not plain.has(456)

    >> fallback := {4: 40; fallback=plain}
    >> fallback.has(1)
    >> fallback[1] or -999
    assert fallback.has(1)
    assert fallback[1] or -999 == 10

test "mutating a heap-allocated table"
    >> t4 := &{"one": 1}
    t4["one"] = 999
    t4["two"] = 222
    >> t4[]
    assert t4[] == {"one": 999, "two": 222}

test "table equality and comparison"
    >> {1: 1, 2: 2} == {2: 2, 1: 1}
    assert {1: 1, 2: 2} == {2: 2, 1: 1}
    assert {1: 1, 2: 2} != {1: 1, 2: 999}
    assert ({1: 1, 2: 2} <> {2: 2, 1: 1}) == Int32(0)
    >> ints : [{Int:Int}] = [{}, {0: 0}, {99: 99}, {1: 1, 2: 2, 3: 3}, {1: 1, 99: 99, 3: 3}, {1: 1, 2: -99, 3: 3}, {1: 1, 99: -99, 3: 4}]
    >> ints.sorted()
    assert ints.sorted() == [{}, {0: 0}, {1: 1, 2: -99, 3: 3}, {1: 1, 2: 2, 3: 3}, {1: 1, 99: 99, 3: 3}, {1: 1, 99: -99, 3: 4}, {99: 99}]

test "default values"
    # Default values:
    >> counter := &{"x": 10; default=0}
    >> counter["x"]
    >> counter["y"]
    >> counter.has("x")
    >> counter.has("y")
    assert counter["x"] == 10
    assert counter["y"] == 0
    assert counter.has("x")
    assert not counter.has("y")
    counter["y"] += 1
    >> counter["y"]
    assert counter["y"] == 1

    # A default in the type annotation lets an empty table index
    # non-optionally (needed because empty literals can't infer their types):
    empty : &{Text:Int; default=0} = &{}
    assert empty["missing"] == 0
    empty["a"] += 1
    empty["a"] += 1
    assert empty["a"] == 2
    # Terse `= default` type form is equivalent:
    terse : &{Text:Int = 0} = &{}
    terse["z"] += 5
    assert terse["z"] == 5

test "defaults are file-scoped"
    # A `default=` expression is part of the table's *type*, and it is stored
    # unevaluated and recompiled wherever the table is indexed -- which can be
    # in another function, a lambda body, or an index of a temporary that has
    # no variable attached. So it resolves at file scope, where names compile
    # to symbols that exist at all of those places:
    scoped : {Int:Int; default=FILE_DEFAULT} = {1: 1}
    assert scoped[1] == 1
    assert scoped[999] == 100

    # Across a function boundary, indexing a temporary, and on a struct field:
    assert lookup_missing({1: 1}) == 100
    assert make_defaulted()[999] == 100
    assert Counts{{1: 1}}.counts[999] == 100

test "defaults inside closures"
    # Nothing has to be captured for the default: a file-scope value compiles
    # to the same symbol inside a lambda's generated function as outside it.
    from_type : {Int:Int; default=FILE_DEFAULT} = {1: 1}
    by_type := func(k:Int -> Int) from_type[k]
    assert by_type(1) == 1
    assert by_type(999) == 100

    from_literal := {1: 1; default=FILE_DEFAULT}
    by_literal := func(k:Int -> Int) from_literal[k]
    assert by_literal(999) == 100

    # Through two levels of nesting, and out past the declaring frame:
    nested := func(-> func(k:Int -> Int))
        inner : {Int:Int; default=FILE_DEFAULT} = {}
        return func(k:Int -> Int) inner[k]
    assert nested()(999) == 100

    # The assigning form (`Table$get_or_setdefault`) behaves the same:
    accumulator : @{Int:Int; default=FILE_DEFAULT} = @{}
    bump := func()
        accumulator[9] += 1
    bump()
    bump()
    >> accumulator
    assert accumulator[9] == 102

test "default on value but not type is rejected"
    # The default would be silently dropped (indexing would become optional),
    # so this is a compile error pointing at the type-annotation form:
    t : &{Int:Int} = &{; default=0}
    >> t[9]
fails_compile "default value, but the type"

test "set operations on tables"
    # Set operations
    >> a := {"A":1, "B":2, "C":3}
    >> b := {"B":2, "C":30, "D":40}
    >> a.with(b)
    >> a.intersection(b)
    >> a.difference(b)
    >> a.without(b)
    assert a.with(b) == {"A":1, "B":2, "C":30, "D":40}
    assert a.with(b) == a ++ b
    assert a.intersection(b) == {"B":2}
    assert a.difference(b) == {"A":1, "D":40}
    assert a.without(b) == {"A":1, "C":3}

test "set operations on sets"
    # Set operations with sets
    >> a := {"A", "B", "C"}
    >> b := {"B", "C", "D"}
    >> a.with(b)
    >> a.intersection(b)
    >> a.difference(b)
    >> a.without(b)
    assert a.with(b) == {"A", "B", "C", "D"}
    assert a.with(b) == a ++ b
    assert a.intersection(b) == {"B", "C"}
    assert a.difference(b) == {"A", "D"}
    assert a.without(b) == {"A"}

test "force-unwrapping a missing key panics"
    t := {1: 10, 2: 20}
    _ := t[999]!
fails "This key was not found in the table: 999"

test "table values must match the declared type"
    t : {Text:Int} = {"a": "b"}
fails_compile "I expected a Int here, but this is a Text"

test "continue and break work in table loops"
    t := {"a": 1, "b": 2, "c": 3}
    n := 0
    for k, v in t.entries()
        if k == "a"
            continue
        n += v
        if n >= 5
            break
    assert n == 5

test "a table's .length is usable as an Int64 value"
    t := {"a": 1, "b": 2}
    assert "$(t.length)" == "2"
    assert t.length == Int64(2)

func table_arg_length(t:{Int:Int} -> Int)
    return t.length

# As with lists, a comprehension in a table literal yields entries rather than
# being one, so an argument check has to look through it:
test "a table comprehension is accepted directly as an argument"
    assert table_arg_length({i: 10*i for i in 3}) == 3
    assert table_arg_length({i: 10*i for i in 5 if i > 2}) == 3


FILE_DEFAULT := 100

struct Counts{counts:{Int:Int; default=FILE_DEFAULT}}

func lookup_missing(t:{Int:Int; default=FILE_DEFAULT} -> Int)
    return t[999]

func make_defaulted(-> {Int:Int; default=FILE_DEFAULT})
    return {1: 1}

test "a default can't be a local variable"
    # The default would have to be recompiled at every index, including ones in
    # scopes where this local doesn't exist:
    local_default := 100
    assert local_default == 100
    bad : {Int:Int; default=local_default} = {}
    >> bad[1]
fails_compile "can only use file-scope values"

test "a default can't refer to the table it belongs to"
    # `recursive` is a local (and isn't bound yet either), so this is caught
    # rather than silently failing to compile:
    recursive : {Int:Int; default=recursive[0]} = {}
    >> recursive[1]
fails_compile "can only use file-scope values"