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"
|