aboutsummaryrefslogtreecommitdiff
path: root/starlark/testdata
diff options
context:
space:
mode:
Diffstat (limited to 'starlark/testdata')
-rw-r--r--starlark/testdata/assign.star354
-rw-r--r--starlark/testdata/benchmark.star62
-rw-r--r--starlark/testdata/bool.star62
-rw-r--r--starlark/testdata/builtins.star225
-rw-r--r--starlark/testdata/bytes.star159
-rw-r--r--starlark/testdata/control.star64
-rw-r--r--starlark/testdata/dict.star248
-rw-r--r--starlark/testdata/float.star504
-rw-r--r--starlark/testdata/function.star323
-rw-r--r--starlark/testdata/int.star260
-rw-r--r--starlark/testdata/json.star147
-rw-r--r--starlark/testdata/list.star276
-rw-r--r--starlark/testdata/misc.star139
-rw-r--r--starlark/testdata/module.star17
-rw-r--r--starlark/testdata/paths.star250
-rw-r--r--starlark/testdata/recursion.star43
-rw-r--r--starlark/testdata/set.star118
-rw-r--r--starlark/testdata/string.star472
-rw-r--r--starlark/testdata/tuple.star55
19 files changed, 3778 insertions, 0 deletions
diff --git a/starlark/testdata/assign.star b/starlark/testdata/assign.star
new file mode 100644
index 0000000..7f579f0
--- /dev/null
+++ b/starlark/testdata/assign.star
@@ -0,0 +1,354 @@
+# Tests of Starlark assignment.
+
+# This is a "chunked" file: each "---" effectively starts a new file.
+
+# tuple assignment
+load("assert.star", "assert")
+
+() = () # empty ok
+
+a, b, c = 1, 2, 3
+assert.eq(a, 1)
+assert.eq(b, 2)
+assert.eq(c, 3)
+
+(d, e, f,) = (1, 2, 3) # trailing comma ok
+---
+(a, b, c) = 1 ### "got int in sequence assignment"
+---
+(a, b) = () ### "too few values to unpack"
+---
+(a, b) = (1,) ### "too few values to unpack"
+---
+(a, b, c) = (1, 2) ### "too few values to unpack"
+---
+(a, b) = (1, 2, 3) ### "too many values to unpack"
+---
+() = 1 ### "got int in sequence assignment"
+---
+() = (1,) ### "too many values to unpack"
+---
+() = (1, 2) ### "too many values to unpack"
+---
+# list assignment
+load("assert.star", "assert")
+
+[] = [] # empty ok
+
+[a, b, c] = [1, 2, 3]
+assert.eq(a, 1)
+assert.eq(b, 2)
+assert.eq(c, 3)
+
+[d, e, f,] = [1, 2, 3] # trailing comma ok
+---
+[a, b, c] = 1 ### "got int in sequence assignment"
+---
+[a, b] = [] ### "too few values to unpack"
+---
+[a, b] = [1] ### "too few values to unpack"
+---
+[a, b, c] = [1, 2] ### "too few values to unpack"
+---
+[a, b] = [1, 2, 3] ### "too many values to unpack"
+---
+[] = 1 ### "got int in sequence assignment"
+---
+[] = [1] ### "too many values to unpack"
+---
+[] = [1, 2] ### "too many values to unpack"
+---
+# list-tuple assignment
+load("assert.star", "assert")
+
+# empty ok
+[] = ()
+() = []
+
+[a, b, c] = (1, 2, 3)
+assert.eq(a, 1)
+assert.eq(b, 2)
+assert.eq(c, 3)
+
+[a2, b2, c2] = 1, 2, 3 # bare tuple ok
+
+(d, e, f) = [1, 2, 3]
+assert.eq(d, 1)
+assert.eq(e, 2)
+assert.eq(f, 3)
+
+[g, h, (i, j)] = (1, 2, [3, 4])
+assert.eq(g, 1)
+assert.eq(h, 2)
+assert.eq(i, 3)
+assert.eq(j, 4)
+
+(k, l, [m, n]) = [1, 2, (3, 4)]
+assert.eq(k, 1)
+assert.eq(l, 2)
+assert.eq(m, 3)
+assert.eq(n, 4)
+
+---
+# misc assignment
+load("assert.star", "assert")
+
+def assignment():
+ a = [1, 2, 3]
+ a[1] = 5
+ assert.eq(a, [1, 5, 3])
+ a[-2] = 2
+ assert.eq(a, [1, 2, 3])
+ assert.eq("%d %d" % (5, 7), "5 7")
+ x={}
+ x[1] = 2
+ x[1] += 3
+ assert.eq(x[1], 5)
+ def f12(): x[(1, "abc", {})] = 1
+ assert.fails(f12, "unhashable type: dict")
+
+assignment()
+
+---
+# augmented assignment
+
+load("assert.star", "assert")
+
+def f():
+ x = 1
+ x += 1
+ assert.eq(x, 2)
+ x *= 3
+ assert.eq(x, 6)
+f()
+
+---
+# effects of evaluating LHS occur only once
+
+load("assert.star", "assert")
+
+count = [0] # count[0] is the number of calls to f
+
+def f():
+ count[0] += 1
+ return count[0]
+
+x = [1, 2, 3]
+x[f()] += 1
+
+assert.eq(x, [1, 3, 3]) # sole call to f returned 1
+assert.eq(count[0], 1) # f was called only once
+
+---
+# Order of evaluation.
+
+load("assert.star", "assert")
+
+calls = []
+
+def f(name, result):
+ calls.append(name)
+ return result
+
+# The right side is evaluated before the left in an ordinary assignment.
+calls.clear()
+f("array", [0])[f("index", 0)] = f("rhs", 0)
+assert.eq(calls, ["rhs", "array", "index"])
+
+calls.clear()
+f("lhs1", [0])[0], f("lhs2", [0])[0] = f("rhs1", 0), f("rhs2", 0)
+assert.eq(calls, ["rhs1", "rhs2", "lhs1", "lhs2"])
+
+# Left side is evaluated first (and only once) in an augmented assignment.
+calls.clear()
+f("array", [0])[f("index", 0)] += f("addend", 1)
+assert.eq(calls, ["array", "index", "addend"])
+
+---
+# global referenced before assignment
+
+def f():
+ return g ### "global variable g referenced before assignment"
+
+f()
+
+g = 1
+
+---
+# Free variables are captured by reference, so this is ok.
+load("assert.star", "assert")
+
+def f():
+ def g():
+ return outer
+ outer = 1
+ return g()
+
+assert.eq(f(), 1)
+
+---
+load("assert.star", "assert")
+
+printok = [False]
+
+# This program should resolve successfully but fail dynamically.
+# However, the Java implementation currently reports the dynamic
+# error at the x=1 statement (b/33975425). I think we need to simplify
+# the resolver algorithm to what we have implemented.
+def use_before_def():
+ print(x) # dynamic error: local var referenced before assignment
+ printok[0] = True
+ x = 1 # makes 'x' local
+
+assert.fails(use_before_def, 'local variable x referenced before assignment')
+assert.true(not printok[0]) # execution of print statement failed
+
+---
+x = [1]
+x.extend([2]) # ok
+
+def f():
+ x += [4] ### "local variable x referenced before assignment"
+
+f()
+
+---
+
+z += 3 ### "global variable z referenced before assignment"
+
+---
+load("assert.star", "assert")
+
+# It's ok to define a global that shadows a built-in...
+list = []
+assert.eq(type(list), "list")
+
+# ...but then all uses refer to the global,
+# even if they occur before the binding use.
+# See github.com/google/skylark/issues/116.
+assert.fails(lambda: tuple, "global variable tuple referenced before assignment")
+tuple = ()
+
+---
+# option:set
+# Same as above, but set is dialect-specific;
+# we shouldn't notice any difference.
+load("assert.star", "assert")
+
+set = [1, 2, 3]
+assert.eq(type(set), "list")
+
+# As in Python 2 and Python 3,
+# all 'in x' expressions in a comprehension are evaluated
+# in the comprehension's lexical block, except the first,
+# which is resolved in the outer block.
+x = [[1, 2]]
+assert.eq([x for x in x for y in x],
+ [[1, 2], [1, 2]])
+
+---
+# A comprehension establishes a single new lexical block,
+# not one per 'for' clause.
+x = [1, 2]
+_ = [x for _ in [3] for x in x] ### "local variable x referenced before assignment"
+
+---
+load("assert.star", "assert")
+
+# assign singleton sequence to 1-tuple
+(x,) = (1,)
+assert.eq(x, 1)
+(y,) = [1]
+assert.eq(y, 1)
+
+# assign 1-tuple to variable
+z = (1,)
+assert.eq(type(z), "tuple")
+assert.eq(len(z), 1)
+assert.eq(z[0], 1)
+
+# assign value to parenthesized variable
+(a) = 1
+assert.eq(a, 1)
+
+---
+# assignment to/from fields.
+load("assert.star", "assert", "freeze")
+
+hf = hasfields()
+hf.x = 1
+assert.eq(hf.x, 1)
+hf.x = [1, 2]
+hf.x += [3, 4]
+assert.eq(hf.x, [1, 2, 3, 4])
+freeze(hf)
+def setX(hf):
+ hf.x = 2
+def setY(hf):
+ hf.y = 3
+assert.fails(lambda: setX(hf), "cannot set field on a frozen hasfields")
+assert.fails(lambda: setY(hf), "cannot set field on a frozen hasfields")
+
+---
+# destucturing assignment in a for loop.
+load("assert.star", "assert")
+
+def f():
+ res = []
+ for (x, y), z in [(["a", "b"], 3), (["c", "d"], 4)]:
+ res.append((x, y, z))
+ return res
+assert.eq(f(), [("a", "b", 3), ("c", "d", 4)])
+
+def g():
+ a = {}
+ for i, a[i] in [("one", 1), ("two", 2)]:
+ pass
+ return a
+assert.eq(g(), {"one": 1, "two": 2})
+
+---
+# parenthesized LHS in augmented assignment (success)
+# option:globalreassign
+load("assert.star", "assert")
+
+a = 5
+(a) += 3
+assert.eq(a, 8)
+
+---
+# parenthesized LHS in augmented assignment (error)
+
+(a) += 5 ### "global variable a referenced before assignment"
+
+---
+# option:globalreassign
+load("assert.star", "assert")
+assert = 1
+load("assert.star", "assert")
+
+---
+# option:globalreassign option:loadbindsglobally
+load("assert.star", "assert")
+assert = 1
+load("assert.star", "assert")
+
+---
+# option:loadbindsglobally
+_ = assert ### "global variable assert referenced before assignment"
+load("assert.star", "assert")
+
+---
+_ = assert ### "local variable assert referenced before assignment"
+load("assert.star", "assert")
+
+---
+def f(): assert.eq(1, 1) # forward ref OK
+load("assert.star", "assert")
+f()
+
+---
+# option:loadbindsglobally
+def f(): assert.eq(1, 1) # forward ref OK
+load("assert.star", "assert")
+f()
diff --git a/starlark/testdata/benchmark.star b/starlark/testdata/benchmark.star
new file mode 100644
index 0000000..b02868d
--- /dev/null
+++ b/starlark/testdata/benchmark.star
@@ -0,0 +1,62 @@
+# Benchmarks of Starlark execution
+
+def bench_range_construction(b):
+ for _ in range(b.n):
+ range(200)
+
+def bench_range_iteration(b):
+ for _ in range(b.n):
+ for x in range(200):
+ pass
+
+# Make a 2-level call tree of 100 * 100 calls.
+def bench_calling(b):
+ list = range(100)
+
+ def g():
+ for x in list:
+ pass
+
+ def f():
+ for x in list:
+ g()
+
+ for _ in range(b.n):
+ f()
+
+# Measure overhead of calling a trivial built-in method.
+emptydict = {}
+range1000 = range(1000)
+
+def bench_builtin_method(b):
+ for _ in range(b.n):
+ for _ in range1000:
+ emptydict.get(None)
+
+def bench_int(b):
+ for _ in range(b.n):
+ a = 0
+ for _ in range1000:
+ a += 1
+
+def bench_bigint(b):
+ for _ in range(b.n):
+ a = 1 << 31 # maxint32 + 1
+ for _ in range1000:
+ a += 1
+
+def bench_gauss(b):
+ # Sum of arithmetic series. All results fit in int32.
+ for _ in range(b.n):
+ acc = 0
+ for x in range(92000):
+ acc += x
+
+def bench_mix(b):
+ "Benchmark of a simple mix of computation (for, if, arithmetic, comprehension)."
+ for _ in range(b.n):
+ x = 0
+ for i in range(50):
+ if i:
+ x += 1
+ a = [x for x in range(i)]
diff --git a/starlark/testdata/bool.star b/starlark/testdata/bool.star
new file mode 100644
index 0000000..6c084a3
--- /dev/null
+++ b/starlark/testdata/bool.star
@@ -0,0 +1,62 @@
+# Tests of Starlark 'bool'
+
+load("assert.star", "assert")
+
+# truth
+assert.true(True)
+assert.true(not False)
+assert.true(not not True)
+assert.true(not not 1 >= 1)
+
+# precedence of not
+assert.true(not not 2 > 1)
+# assert.true(not (not 2) > 1) # TODO(adonovan): fix: gives error for False > 1.
+# assert.true(not ((not 2) > 1)) # TODO(adonovan): fix
+# assert.true(not ((not (not 2)) > 1)) # TODO(adonovan): fix
+# assert.true(not not not (2 > 1))
+
+# bool conversion
+assert.eq(
+ [bool(), bool(1), bool(0), bool("hello"), bool("")],
+ [False, True, False, True, False],
+)
+
+# comparison
+assert.true(None == None)
+assert.true(None != False)
+assert.true(None != True)
+assert.eq(1 == 1, True)
+assert.eq(1 == 2, False)
+assert.true(False == False)
+assert.true(True == True)
+
+# ordered comparison
+assert.true(False < True)
+assert.true(False <= True)
+assert.true(False <= False)
+assert.true(True > False)
+assert.true(True >= False)
+assert.true(True >= True)
+
+# conditional expression
+assert.eq(1 if 3 > 2 else 0, 1)
+assert.eq(1 if "foo" else 0, 1)
+assert.eq(1 if "" else 0, 0)
+
+# short-circuit evaluation of 'and' and 'or':
+# 'or' yields the first true operand, or the last if all are false.
+assert.eq(0 or "" or [] or 0, 0)
+assert.eq(0 or "" or [] or 123 or 1 // 0, 123)
+assert.fails(lambda : 0 or "" or [] or 0 or 1 // 0, "division by zero")
+
+# 'and' yields the first false operand, or the last if all are true.
+assert.eq(1 and "a" and [1] and 123, 123)
+assert.eq(1 and "a" and [1] and 0 and 1 // 0, 0)
+assert.fails(lambda : 1 and "a" and [1] and 123 and 1 // 0, "division by zero")
+
+# Built-ins that want a bool want an actual bool, not a truth value.
+# See github.com/bazelbuild/starlark/issues/30
+assert.eq(''.splitlines(True), [])
+assert.fails(lambda: ''.splitlines(1), 'got int, want bool')
+assert.fails(lambda: ''.splitlines("hello"), 'got string, want bool')
+assert.fails(lambda: ''.splitlines(0.0), 'got float, want bool')
diff --git a/starlark/testdata/builtins.star b/starlark/testdata/builtins.star
new file mode 100644
index 0000000..c6591b8
--- /dev/null
+++ b/starlark/testdata/builtins.star
@@ -0,0 +1,225 @@
+# Tests of Starlark built-in functions
+# option:set
+
+load("assert.star", "assert")
+
+# len
+assert.eq(len([1, 2, 3]), 3)
+assert.eq(len((1, 2, 3)), 3)
+assert.eq(len({1: 2}), 1)
+assert.fails(lambda: len(1), "int.*has no len")
+
+# and, or
+assert.eq(123 or "foo", 123)
+assert.eq(0 or "foo", "foo")
+assert.eq(123 and "foo", "foo")
+assert.eq(0 and "foo", 0)
+none = None
+_1 = none and none[0] # rhs is not evaluated
+_2 = (not none) or none[0] # rhs is not evaluated
+
+# any, all
+assert.true(all([]))
+assert.true(all([1, True, "foo"]))
+assert.true(not all([1, True, ""]))
+assert.true(not any([]))
+assert.true(any([0, False, "foo"]))
+assert.true(not any([0, False, ""]))
+
+# in
+assert.true(3 in [1, 2, 3])
+assert.true(4 not in [1, 2, 3])
+assert.true(3 in (1, 2, 3))
+assert.true(4 not in (1, 2, 3))
+assert.fails(lambda: 3 in "foo", "in.*requires string as left operand")
+assert.true(123 in {123: ""})
+assert.true(456 not in {123:""})
+assert.true([] not in {123: ""})
+
+# sorted
+assert.eq(sorted([42, 123, 3]), [3, 42, 123])
+assert.eq(sorted([42, 123, 3], reverse=True), [123, 42, 3])
+assert.eq(sorted(["wiz", "foo", "bar"]), ["bar", "foo", "wiz"])
+assert.eq(sorted(["wiz", "foo", "bar"], reverse=True), ["wiz", "foo", "bar"])
+assert.fails(lambda: sorted([1, 2, None, 3]), "int < NoneType not implemented")
+assert.fails(lambda: sorted([1, "one"]), "string < int not implemented")
+# custom key function
+assert.eq(sorted(["two", "three", "four"], key=len),
+ ["two", "four", "three"])
+assert.eq(sorted(["two", "three", "four"], key=len, reverse=True),
+ ["three", "four", "two"])
+assert.fails(lambda: sorted([1, 2, 3], key=None), "got NoneType, want callable")
+# sort is stable
+pairs = [(4, 0), (3, 1), (4, 2), (2, 3), (3, 4), (1, 5), (2, 6), (3, 7)]
+assert.eq(sorted(pairs, key=lambda x: x[0]),
+ [(1, 5),
+ (2, 3), (2, 6),
+ (3, 1), (3, 4), (3, 7),
+ (4, 0), (4, 2)])
+assert.fails(lambda: sorted(1), 'sorted: for parameter iterable: got int, want iterable')
+
+# reversed
+assert.eq(reversed([1, 144, 81, 16]), [16, 81, 144, 1])
+
+# set
+assert.contains(set([1, 2, 3]), 1)
+assert.true(4 not in set([1, 2, 3]))
+assert.eq(len(set([1, 2, 3])), 3)
+assert.eq(sorted([x for x in set([1, 2, 3])]), [1, 2, 3])
+
+# dict
+assert.eq(dict([(1, 2), (3, 4)]), {1: 2, 3: 4})
+assert.eq(dict([(1, 2), (3, 4)], foo="bar"), {1: 2, 3: 4, "foo": "bar"})
+assert.eq(dict({1:2, 3:4}), {1: 2, 3: 4})
+assert.eq(dict({1:2, 3:4}.items()), {1: 2, 3: 4})
+
+# range
+assert.eq("range", type(range(10)))
+assert.eq("range(10)", str(range(0, 10, 1)))
+assert.eq("range(1, 10)", str(range(1, 10)))
+assert.eq(range(0, 5, 10), range(0, 5, 11))
+assert.eq("range(0, 10, -1)", str(range(0, 10, -1)))
+assert.fails(lambda: {range(10): 10}, "unhashable: range")
+assert.true(bool(range(1, 2)))
+assert.true(not(range(2, 1))) # an empty range is false
+assert.eq([x*x for x in range(5)], [0, 1, 4, 9, 16])
+assert.eq(list(range(5)), [0, 1, 2, 3, 4])
+assert.eq(list(range(-5)), [])
+assert.eq(list(range(2, 5)), [2, 3, 4])
+assert.eq(list(range(5, 2)), [])
+assert.eq(list(range(-2, -5)), [])
+assert.eq(list(range(-5, -2)), [-5, -4, -3])
+assert.eq(list(range(2, 10, 3)), [2, 5, 8])
+assert.eq(list(range(10, 2, -3)), [10, 7, 4])
+assert.eq(list(range(-2, -10, -3)), [-2, -5, -8])
+assert.eq(list(range(-10, -2, 3)), [-10, -7, -4])
+assert.eq(list(range(10, 2, -1)), [10, 9, 8, 7, 6, 5, 4, 3])
+assert.eq(list(range(5)[1:]), [1, 2, 3, 4])
+assert.eq(len(range(5)[1:]), 4)
+assert.eq(list(range(5)[:2]), [0, 1])
+assert.eq(list(range(10)[1:]), [1, 2, 3, 4, 5, 6, 7, 8, 9])
+assert.eq(list(range(10)[1:9:2]), [1, 3, 5, 7])
+assert.eq(list(range(10)[1:10:2]), [1, 3, 5, 7, 9])
+assert.eq(list(range(10)[1:11:2]), [1, 3, 5, 7, 9])
+assert.eq(list(range(10)[::-2]), [9, 7, 5, 3, 1])
+assert.eq(list(range(0, 10, 2)[::2]), [0, 4, 8])
+assert.eq(list(range(0, 10, 2)[::-2]), [8, 4, 0])
+# range() is limited by the width of the Go int type (int32 or int64).
+assert.fails(lambda: range(1<<64), "... out of range .want value in signed ..-bit range")
+assert.eq(len(range(0x7fffffff)), 0x7fffffff) # O(1)
+# Two ranges compare equal if they denote the same sequence:
+assert.eq(range(0), range(2, 1, 3)) # []
+assert.eq(range(0, 3, 2), range(0, 4, 2)) # [0, 2]
+assert.ne(range(1, 10), range(2, 10))
+assert.fails(lambda: range(0) < range(0), "range < range not implemented")
+# <number> in <range>
+assert.contains(range(3), 1)
+assert.contains(range(3), 2.0) # acts like 2
+assert.fails(lambda: True in range(3), "requires integer.*not bool") # bools aren't numbers
+assert.fails(lambda: "one" in range(10), "requires integer.*not string")
+assert.true(4 not in range(4))
+assert.true(1e15 not in range(4)) # too big for int32
+assert.true(1e100 not in range(4)) # too big for int64
+# https://github.com/google/starlark-go/issues/116
+assert.fails(lambda: range(0, 0, 2)[:][0], "index 0 out of range: empty range")
+
+# list
+assert.eq(list("abc".elems()), ["a", "b", "c"])
+assert.eq(sorted(list({"a": 1, "b": 2})), ['a', 'b'])
+
+# min, max
+assert.eq(min(5, -2, 1, 7, 3), -2)
+assert.eq(max(5, -2, 1, 7, 3), 7)
+assert.eq(min([5, -2, 1, 7, 3]), -2)
+assert.eq(min("one", "two", "three", "four"), "four")
+assert.eq(max("one", "two", "three", "four"), "two")
+assert.fails(min, "min requires at least one positional argument")
+assert.fails(lambda: min(1), "not iterable")
+assert.fails(lambda: min([]), "empty")
+assert.eq(min(5, -2, 1, 7, 3, key=lambda x: x*x), 1) # min absolute value
+assert.eq(min(5, -2, 1, 7, 3, key=lambda x: -x), 7) # min negated value
+
+# enumerate
+assert.eq(enumerate("abc".elems()), [(0, "a"), (1, "b"), (2, "c")])
+assert.eq(enumerate([False, True, None], 42), [(42, False), (43, True), (44, None)])
+
+# zip
+assert.eq(zip(), [])
+assert.eq(zip([]), [])
+assert.eq(zip([1, 2, 3]), [(1,), (2,), (3,)])
+assert.eq(zip("".elems()), [])
+assert.eq(zip("abc".elems(),
+ list("def".elems()),
+ "hijk".elems()),
+ [("a", "d", "h"), ("b", "e", "i"), ("c", "f", "j")])
+z1 = [1]
+assert.eq(zip(z1), [(1,)])
+z1.append(2)
+assert.eq(zip(z1), [(1,), (2,)])
+assert.fails(lambda: zip(z1, 1), "zip: argument #2 is not iterable: int")
+z1.append(3)
+
+# dir for builtin_function_or_method
+assert.eq(dir(None), [])
+assert.eq(dir({})[:3], ["clear", "get", "items"]) # etc
+assert.eq(dir(1), [])
+assert.eq(dir([])[:3], ["append", "clear", "extend"]) # etc
+
+# hasattr, getattr, dir
+# hasfields is an application-defined type defined in eval_test.go.
+hf = hasfields()
+assert.eq(dir(hf), [])
+assert.true(not hasattr(hf, "x"))
+assert.fails(lambda: getattr(hf, "x"), "no .x field or method")
+assert.eq(getattr(hf, "x", 42), 42)
+hf.x = 1
+assert.true(hasattr(hf, "x"))
+assert.eq(getattr(hf, "x"), 1)
+assert.eq(hf.x, 1)
+hf.x = 2
+assert.eq(getattr(hf, "x"), 2)
+assert.eq(hf.x, 2)
+# built-in types can have attributes (methods) too.
+myset = set([])
+assert.eq(dir(myset), ["union"])
+assert.true(hasattr(myset, "union"))
+assert.true(not hasattr(myset, "onion"))
+assert.eq(str(getattr(myset, "union")), "<built-in method union of set value>")
+assert.fails(lambda: getattr(myset, "onion"), "no .onion field or method")
+assert.eq(getattr(myset, "onion", 42), 42)
+
+# dir returns a new, sorted, mutable list
+assert.eq(sorted(dir("")), dir("")) # sorted
+dir("").append("!") # mutable
+assert.true("!" not in dir("")) # new
+
+# error messages should suggest spelling corrections
+hf.one = 1
+hf.two = 2
+hf.three = 3
+hf.forty_five = 45
+assert.fails(lambda: hf.One, 'no .One field.*did you mean .one')
+assert.fails(lambda: hf.oone, 'no .oone field.*did you mean .one')
+assert.fails(lambda: hf.FortyFive, 'no .FortyFive field.*did you mean .forty_five')
+assert.fails(lambda: hf.trhee, 'no .trhee field.*did you mean .three')
+assert.fails(lambda: hf.thirty, 'no .thirty field or method$') # no suggestion
+
+# spell check in setfield too
+def setfield(): hf.noForty_Five = 46 # "no" prefix => SetField returns NoSuchField
+assert.fails(setfield, 'no .noForty_Five field.*did you mean .forty_five')
+
+# repr
+assert.eq(repr(1), "1")
+assert.eq(repr("x"), '"x"')
+assert.eq(repr(["x", 1]), '["x", 1]')
+
+# fail
+---
+fail() ### `fail: $`
+x = 1//0 # unreachable
+---
+fail(1) ### `fail: 1`
+---
+fail(1, 2, 3) ### `fail: 1 2 3`
+---
+fail(1, 2, 3, sep="/") ### `fail: 1/2/3`
diff --git a/starlark/testdata/bytes.star b/starlark/testdata/bytes.star
new file mode 100644
index 0000000..d500403
--- /dev/null
+++ b/starlark/testdata/bytes.star
@@ -0,0 +1,159 @@
+# Tests of 'bytes' (immutable byte strings).
+
+load("assert.star", "assert")
+
+# bytes(string) -- UTF-k to UTF-8 transcoding with U+FFFD replacement
+hello = bytes("hello, 世界")
+goodbye = bytes("goodbye")
+empty = bytes("")
+nonprinting = bytes("\t\n\x7F\u200D") # TAB, NEWLINE, DEL, ZERO_WIDTH_JOINER
+assert.eq(bytes("hello, 世界"[:-1]), b"hello, 世��")
+
+# bytes(iterable of int) -- construct from numeric byte values
+assert.eq(bytes([65, 66, 67]), b"ABC")
+assert.eq(bytes((65, 66, 67)), b"ABC")
+assert.eq(bytes([0xf0, 0x9f, 0x98, 0xbf]), b"😿")
+assert.fails(lambda: bytes([300]),
+ "at index 0, 300 out of range .want value in unsigned 8-bit range")
+assert.fails(lambda: bytes([b"a"]),
+ "at index 0, got bytes, want int")
+assert.fails(lambda: bytes(1), "want string, bytes, or iterable of ints")
+
+# literals
+assert.eq(b"hello, 世界", hello)
+assert.eq(b"goodbye", goodbye)
+assert.eq(b"", empty)
+assert.eq(b"\t\n\x7F\u200D", nonprinting)
+assert.ne("abc", b"abc")
+assert.eq(b"\012\xff\u0400\U0001F63F", b"\n\xffЀ😿") # see scanner tests for more
+assert.eq(rb"\r\n\t", b"\\r\\n\\t") # raw
+
+# type
+assert.eq(type(hello), "bytes")
+
+# len
+assert.eq(len(hello), 13)
+assert.eq(len(goodbye), 7)
+assert.eq(len(empty), 0)
+assert.eq(len(b"A"), 1)
+assert.eq(len(b"Ѐ"), 2)
+assert.eq(len(b"世"), 3)
+assert.eq(len(b"😿"), 4)
+
+# truth
+assert.true(hello)
+assert.true(goodbye)
+assert.true(not empty)
+
+# str(bytes) does UTF-8 to UTF-k transcoding.
+# TODO(adonovan): specify.
+assert.eq(str(hello), "hello, 世界")
+assert.eq(str(hello[:-1]), "hello, 世��") # incomplete UTF-8 encoding => U+FFFD
+assert.eq(str(goodbye), "goodbye")
+assert.eq(str(empty), "")
+assert.eq(str(nonprinting), "\t\n\x7f\u200d")
+assert.eq(str(b"\xED\xB0\x80"), "���") # UTF-8 encoding of unpaired surrogate => U+FFFD x 3
+
+# repr
+assert.eq(repr(hello), r'b"hello, 世界"')
+assert.eq(repr(hello[:-1]), r'b"hello, 世\xe7\x95"') # (incomplete UTF-8 encoding )
+assert.eq(repr(goodbye), 'b"goodbye"')
+assert.eq(repr(empty), 'b""')
+assert.eq(repr(nonprinting), 'b"\\t\\n\\x7f\\u200d"')
+
+# equality
+assert.eq(hello, hello)
+assert.ne(hello, goodbye)
+assert.eq(b"goodbye", goodbye)
+
+# ordered comparison
+assert.lt(b"abc", b"abd")
+assert.lt(b"abc", b"abcd")
+assert.lt(b"\x7f", b"\x80") # bytes compare as uint8, not int8
+
+# bytes are dict-hashable
+dict = {hello: 1, goodbye: 2}
+dict[b"goodbye"] = 3
+assert.eq(len(dict), 2)
+assert.eq(dict[goodbye], 3)
+
+# hash(bytes) is 32-bit FNV-1a.
+assert.eq(hash(b""), 0x811c9dc5)
+assert.eq(hash(b"a"), 0xe40c292c)
+assert.eq(hash(b"ab"), 0x4d2505ca)
+assert.eq(hash(b"abc"), 0x1a47e90b)
+
+# indexing
+assert.eq(goodbye[0], b"g")
+assert.eq(goodbye[-1], b"e")
+assert.fails(lambda: goodbye[100], "out of range")
+
+# slicing
+assert.eq(goodbye[:4], b"good")
+assert.eq(goodbye[4:], b"bye")
+assert.eq(goodbye[::2], b"gobe")
+assert.eq(goodbye[3:4], b"d") # special case: len=1
+assert.eq(goodbye[4:4], b"") # special case: len=0
+
+# bytes in bytes
+assert.eq(b"bc" in b"abcd", True)
+assert.eq(b"bc" in b"dcab", False)
+assert.fails(lambda: "bc" in b"dcab", "requires bytes or int as left operand, not string")
+
+# int in bytes
+assert.eq(97 in b"abc", True) # 97='a'
+assert.eq(100 in b"abc", False) # 100='d'
+assert.fails(lambda: 256 in b"abc", "int in bytes: 256 out of range")
+assert.fails(lambda: -1 in b"abc", "int in bytes: -1 out of range")
+
+# ord TODO(adonovan): specify
+assert.eq(ord(b"a"), 97)
+assert.fails(lambda: ord(b"ab"), "ord: bytes has length 2, want 1")
+assert.fails(lambda: ord(b""), "ord: bytes has length 0, want 1")
+
+# repeat (bytes * int)
+assert.eq(goodbye * 3, b"goodbyegoodbyegoodbye")
+assert.eq(3 * goodbye, b"goodbyegoodbyegoodbye")
+
+# elems() returns an iterable value over 1-byte substrings.
+assert.eq(type(hello.elems()), "bytes.elems")
+assert.eq(str(hello.elems()), "b\"hello, 世界\".elems()")
+assert.eq(list(hello.elems()), [104, 101, 108, 108, 111, 44, 32, 228, 184, 150, 231, 149, 140])
+assert.eq(bytes([104, 101, 108, 108, 111, 44, 32, 228, 184, 150, 231, 149, 140]), hello)
+assert.eq(list(goodbye.elems()), [103, 111, 111, 100, 98, 121, 101])
+assert.eq(list(empty.elems()), [])
+assert.eq(bytes(hello.elems()), hello) # bytes(iterable) is dual to bytes.elems()
+
+# x[i] = ...
+def f():
+ b"abc"[1] = b"B"
+
+assert.fails(f, "bytes.*does not support.*assignment")
+
+# TODO(adonovan): the specification is not finalized in many areas:
+# - chr, ord functions
+# - encoding/decoding bytes to string.
+# - methods: find, index, split, etc.
+#
+# Summary of string operations (put this in spec).
+#
+# string to number:
+# - bytes[i] returns numeric value of ith byte.
+# - ord(string) returns numeric value of sole code point in string.
+# - ord(string[i]) is not a useful operation: fails on non-ASCII; see below.
+# Q. Perhaps ord should return the first (not sole) code point? Then it becomes a UTF-8 decoder.
+# Perhaps ord(string, index=int) should apply the index and relax the len=1 check.
+# - string.codepoint() iterates over 1-codepoint substrings.
+# - string.codepoint_ords() iterates over numeric values of code points in string.
+# - string.elems() iterates over 1-element (UTF-k code) substrings.
+# - string.elem_ords() iterates over numeric UTF-k code values.
+# - string.elem_ords()[i] returns numeric value of ith element (UTF-k code).
+# - string.elems()[i] returns substring of a single element (UTF-k code).
+# - int(string) parses string as decimal (or other) numeric literal.
+#
+# number to string:
+# - chr(int) returns string, UTF-k encoding of Unicode code point (like Python).
+# Redundant with '%c' % int (which Python2 calls 'unichr'.)
+# - bytes(chr(int)) returns byte string containing UTF-8 encoding of one code point.
+# - bytes([int]) returns 1-byte string (with regrettable list allocation).
+# - str(int) - format number as decimal.
diff --git a/starlark/testdata/control.star b/starlark/testdata/control.star
new file mode 100644
index 0000000..554ab25
--- /dev/null
+++ b/starlark/testdata/control.star
@@ -0,0 +1,64 @@
+# Tests of Starlark control flow
+
+load("assert.star", "assert")
+
+def controlflow():
+ # elif
+ x = 0
+ if True:
+ x=1
+ elif False:
+ assert.fail("else of true")
+ else:
+ assert.fail("else of else of true")
+ assert.true(x)
+
+ x = 0
+ if False:
+ assert.fail("then of false")
+ elif True:
+ x = 1
+ else:
+ assert.fail("else of true")
+ assert.true(x)
+
+ x = 0
+ if False:
+ assert.fail("then of false")
+ elif False:
+ assert.fail("then of false")
+ else:
+ x = 1
+ assert.true(x)
+controlflow()
+
+def loops():
+ y = ""
+ for x in [1, 2, 3, 4, 5]:
+ if x == 2:
+ continue
+ if x == 4:
+ break
+ y = y + str(x)
+ return y
+assert.eq(loops(), "13")
+
+# return
+g = 123
+def f(x):
+ for g in (1, 2, 3):
+ if g == x:
+ return g
+assert.eq(f(2), 2)
+assert.eq(f(4), None) # falling off end => return None
+assert.eq(g, 123) # unchanged by local use of g in function
+
+# infinite sequences
+def fib(n):
+ seq = []
+ for x in fibonacci: # fibonacci is an infinite iterable defined in eval_test.go
+ if len(seq) == n:
+ break
+ seq.append(x)
+ return seq
+assert.eq(fib(10), [0, 1, 1, 2, 3, 5, 8, 13, 21, 34])
diff --git a/starlark/testdata/dict.star b/starlark/testdata/dict.star
new file mode 100644
index 0000000..1aeb1e7
--- /dev/null
+++ b/starlark/testdata/dict.star
@@ -0,0 +1,248 @@
+# Tests of Starlark 'dict'
+
+load("assert.star", "assert", "freeze")
+
+# literals
+assert.eq({}, {})
+assert.eq({"a": 1}, {"a": 1})
+assert.eq({"a": 1,}, {"a": 1})
+
+# truth
+assert.true({False: False})
+assert.true(not {})
+
+# dict + dict is no longer supported.
+assert.fails(lambda: {"a": 1} + {"b": 2}, 'unknown binary op: dict \\+ dict')
+
+# dict comprehension
+assert.eq({x: x*x for x in range(3)}, {0: 0, 1: 1, 2: 4})
+
+# dict.pop
+x6 = {"a": 1, "b": 2}
+assert.eq(x6.pop("a"), 1)
+assert.eq(str(x6), '{"b": 2}')
+assert.fails(lambda: x6.pop("c"), "pop: missing key")
+assert.eq(x6.pop("c", 3), 3)
+assert.eq(x6.pop("c", None), None) # default=None tests an edge case of UnpackArgs
+assert.eq(x6.pop("b"), 2)
+assert.eq(len(x6), 0)
+
+# dict.popitem
+x7 = {"a": 1, "b": 2}
+assert.eq([x7.popitem(), x7.popitem()], [("a", 1), ("b", 2)])
+assert.fails(x7.popitem, "empty dict")
+assert.eq(len(x7), 0)
+
+# dict.keys, dict.values
+x8 = {"a": 1, "b": 2}
+assert.eq(x8.keys(), ["a", "b"])
+assert.eq(x8.values(), [1, 2])
+
+# equality
+assert.eq({"a": 1, "b": 2}, {"a": 1, "b": 2})
+assert.eq({"a": 1, "b": 2,}, {"a": 1, "b": 2})
+assert.eq({"a": 1, "b": 2}, {"b": 2, "a": 1})
+
+# insertion order is preserved
+assert.eq(dict([("a", 0), ("b", 1), ("c", 2), ("b", 3)]).keys(), ["a", "b", "c"])
+assert.eq(dict([("b", 0), ("a", 1), ("b", 2), ("c", 3)]).keys(), ["b", "a", "c"])
+assert.eq(dict([("b", 0), ("a", 1), ("b", 2), ("c", 3)])["b"], 2)
+# ...even after rehashing (which currently occurs after key 'i'):
+small = dict([("a", 0), ("b", 1), ("c", 2)])
+small.update([("d", 4), ("e", 5), ("f", 6), ("g", 7), ("h", 8), ("i", 9), ("j", 10), ("k", 11)])
+assert.eq(small.keys(), ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"])
+
+# Duplicate keys are not permitted in dictionary expressions (see b/35698444).
+# (Nor in keyword args to function calls---checked by resolver.)
+assert.fails(lambda: {"aa": 1, "bb": 2, "cc": 3, "bb": 4}, 'duplicate key: "bb"')
+
+# Check that even with many positional args, keyword collisions are detected.
+assert.fails(lambda: dict({'b': 3}, a=4, **dict(a=5)), 'dict: duplicate keyword arg: "a"')
+assert.fails(lambda: dict({'a': 2, 'b': 3}, a=4, **dict(a=5)), 'dict: duplicate keyword arg: "a"')
+# positional/keyword arg key collisions are ok
+assert.eq(dict((['a', 2], ), a=4), {'a': 4})
+assert.eq(dict((['a', 2], ['a', 3]), a=4), {'a': 4})
+
+# index
+def setIndex(d, k, v):
+ d[k] = v
+
+x9 = {}
+assert.fails(lambda: x9["a"], 'key "a" not in dict')
+x9["a"] = 1
+assert.eq(x9["a"], 1)
+assert.eq(x9, {"a": 1})
+assert.fails(lambda: setIndex(x9, [], 2), 'unhashable type: list')
+freeze(x9)
+assert.fails(lambda: setIndex(x9, "a", 3), 'cannot insert into frozen hash table')
+
+x9a = {}
+x9a[1, 2] = 3 # unparenthesized tuple is allowed here
+assert.eq(x9a.keys()[0], (1, 2))
+
+# dict.get
+x10 = {"a": 1}
+assert.eq(x10.get("a"), 1)
+assert.eq(x10.get("b"), None)
+assert.eq(x10.get("a", 2), 1)
+assert.eq(x10.get("b", 2), 2)
+
+# dict.clear
+x11 = {"a": 1}
+assert.contains(x11, "a")
+assert.eq(x11["a"], 1)
+x11.clear()
+assert.fails(lambda: x11["a"], 'key "a" not in dict')
+assert.true("a" not in x11)
+freeze(x11)
+assert.fails(x11.clear, "cannot clear frozen hash table")
+
+# dict.setdefault
+x12 = {"a": 1}
+assert.eq(x12.setdefault("a"), 1)
+assert.eq(x12["a"], 1)
+assert.eq(x12.setdefault("b"), None)
+assert.eq(x12["b"], None)
+assert.eq(x12.setdefault("c", 2), 2)
+assert.eq(x12["c"], 2)
+assert.eq(x12.setdefault("c", 3), 2)
+assert.eq(x12["c"], 2)
+freeze(x12)
+assert.eq(x12.setdefault("a", 1), 1) # no change, no error
+assert.fails(lambda: x12.setdefault("d", 1), "cannot insert into frozen hash table")
+
+# dict.update
+x13 = {"a": 1}
+x13.update(a=2, b=3)
+assert.eq(x13, {"a": 2, "b": 3})
+x13.update([("b", 4), ("c", 5)])
+assert.eq(x13, {"a": 2, "b": 4, "c": 5})
+x13.update({"c": 6, "d": 7})
+assert.eq(x13, {"a": 2, "b": 4, "c": 6, "d": 7})
+freeze(x13)
+assert.fails(lambda: x13.update({"a": 8}), "cannot insert into frozen hash table")
+
+# dict as a sequence
+#
+# for loop
+x14 = {1:2, 3:4}
+def keys(dict):
+ keys = []
+ for k in dict: keys.append(k)
+ return keys
+assert.eq(keys(x14), [1, 3])
+#
+# comprehension
+assert.eq([x for x in x14], [1, 3])
+#
+# varargs
+def varargs(*args): return args
+x15 = {"one": 1}
+assert.eq(varargs(*x15), ("one",))
+
+# kwargs parameter does not alias the **kwargs dict
+def kwargs(**kwargs): return kwargs
+x16 = kwargs(**x15)
+assert.eq(x16, x15)
+x15["two"] = 2 # mutate
+assert.ne(x16, x15)
+
+# iterator invalidation
+def iterator1():
+ dict = {1:1, 2:1}
+ for k in dict:
+ dict[2*k] = dict[k]
+assert.fails(iterator1, "insert.*during iteration")
+
+def iterator2():
+ dict = {1:1, 2:1}
+ for k in dict:
+ dict.pop(k)
+assert.fails(iterator2, "delete.*during iteration")
+
+def iterator3():
+ def f(d):
+ d[3] = 3
+ dict = {1:1, 2:1}
+ _ = [f(dict) for x in dict]
+assert.fails(iterator3, "insert.*during iteration")
+
+# This assignment is not a modification-during-iteration:
+# the sequence x should be completely iterated before
+# the assignment occurs.
+def f():
+ x = {1:2, 2:4}
+ a, x[0] = x
+ assert.eq(a, 1)
+ assert.eq(x, {1: 2, 2: 4, 0: 2})
+f()
+
+# Regression test for a bug in hashtable.delete
+def test_delete():
+ d = {}
+
+ # delete tail first
+ d["one"] = 1
+ d["two"] = 2
+ assert.eq(str(d), '{"one": 1, "two": 2}')
+ d.pop("two")
+ assert.eq(str(d), '{"one": 1}')
+ d.pop("one")
+ assert.eq(str(d), '{}')
+
+ # delete head first
+ d["one"] = 1
+ d["two"] = 2
+ assert.eq(str(d), '{"one": 1, "two": 2}')
+ d.pop("one")
+ assert.eq(str(d), '{"two": 2}')
+ d.pop("two")
+ assert.eq(str(d), '{}')
+
+ # delete middle
+ d["one"] = 1
+ d["two"] = 2
+ d["three"] = 3
+ assert.eq(str(d), '{"one": 1, "two": 2, "three": 3}')
+ d.pop("two")
+ assert.eq(str(d), '{"one": 1, "three": 3}')
+ d.pop("three")
+ assert.eq(str(d), '{"one": 1}')
+ d.pop("one")
+ assert.eq(str(d), '{}')
+
+test_delete()
+
+# Regression test for github.com/google/starlark-go/issues/128.
+assert.fails(lambda: dict(None), 'got NoneType, want iterable')
+assert.fails(lambda: {}.update(None), 'got NoneType, want iterable')
+
+---
+# Verify position of an "unhashable key" error in a dict literal.
+
+_ = {
+ "one": 1,
+ ["two"]: 2, ### "unhashable type: list"
+ "three": 3,
+}
+
+---
+# Verify position of a "duplicate key" error in a dict literal.
+
+_ = {
+ "one": 1,
+ "one": 1, ### `duplicate key: "one"`
+ "three": 3,
+}
+
+---
+# Verify position of an "unhashable key" error in a dict comprehension.
+
+_ = {
+ k: v ### "unhashable type: list"
+ for k, v in [
+ ("one", 1),
+ (["two"], 2),
+ ("three", 3),
+ ]
+}
diff --git a/starlark/testdata/float.star b/starlark/testdata/float.star
new file mode 100644
index 0000000..b4df38d
--- /dev/null
+++ b/starlark/testdata/float.star
@@ -0,0 +1,504 @@
+# Tests of Starlark 'float'
+# option:set
+
+load("assert.star", "assert")
+
+# TODO(adonovan): more tests:
+# - precision
+# - limits
+
+# type
+assert.eq(type(0.0), "float")
+
+# truth
+assert.true(123.0)
+assert.true(-1.0)
+assert.true(not 0.0)
+assert.true(-1.0e-45)
+assert.true(float("NaN"))
+
+# not iterable
+assert.fails(lambda: len(0.0), 'has no len')
+assert.fails(lambda: [x for x in 0.0], 'float value is not iterable')
+
+# literals
+assert.eq(type(1.234), "float")
+assert.eq(type(1e10), "float")
+assert.eq(type(1e+10), "float")
+assert.eq(type(1e-10), "float")
+assert.eq(type(1.234e10), "float")
+assert.eq(type(1.234e+10), "float")
+assert.eq(type(1.234e-10), "float")
+
+# int/float equality
+assert.eq(0.0, 0)
+assert.eq(0, 0.0)
+assert.eq(1.0, 1)
+assert.eq(1, 1.0)
+assert.true(1.23e45 != 1229999999999999973814869011019624571608236031)
+assert.true(1.23e45 == 1229999999999999973814869011019624571608236032)
+assert.true(1.23e45 != 1229999999999999973814869011019624571608236033)
+assert.true(1229999999999999973814869011019624571608236031 != 1.23e45)
+assert.true(1229999999999999973814869011019624571608236032 == 1.23e45)
+assert.true(1229999999999999973814869011019624571608236033 != 1.23e45)
+
+# loss of precision
+p53 = 1<<53
+assert.eq(float(p53-1), p53-1)
+assert.eq(float(p53+0), p53+0)
+assert.eq(float(p53+1), p53+0) #
+assert.eq(float(p53+2), p53+2)
+assert.eq(float(p53+3), p53+4) #
+assert.eq(float(p53+4), p53+4)
+assert.eq(float(p53+5), p53+4) #
+assert.eq(float(p53+6), p53+6)
+assert.eq(float(p53+7), p53+8) #
+assert.eq(float(p53+8), p53+8)
+
+assert.true(float(p53+1) != p53+1) # comparisons are exact
+assert.eq(float(p53+1) - (p53+1), 0) # arithmetic entails rounding
+
+assert.fails(lambda: {123.0: "f", 123: "i"}, "duplicate key: 123")
+
+# equal int/float values have same hash
+d = {123.0: "x"}
+d[123] = "y"
+assert.eq(len(d), 1)
+assert.eq(d[123.0], "y")
+
+# literals (mostly covered by scanner tests)
+assert.eq(str(0.), "0.0")
+assert.eq(str(.0), "0.0")
+assert.true(5.0 != 4.999999999999999)
+assert.eq(5.0, 4.9999999999999999) # both literals denote 5.0
+assert.eq(1.23e45, 1.23 * 1000000000000000000000000000000000000000000000)
+assert.eq(str(1.23e-45 - (1.23 / 1000000000000000000000000000000000000000000000)), "-1.5557538194652854e-61")
+
+nan = float("NaN")
+inf = float("+Inf")
+neginf = float("-Inf")
+negzero = (-1e-323 / 10)
+
+# -- arithmetic --
+
+# +float, -float
+assert.eq(+(123.0), 123.0)
+assert.eq(-(123.0), -123.0)
+assert.eq(-(-(123.0)), 123.0)
+assert.eq(+(inf), inf)
+assert.eq(-(inf), neginf)
+assert.eq(-(neginf), inf)
+assert.eq(str(-(nan)), "nan")
+# +
+assert.eq(1.2e3 + 5.6e7, 5.60012e+07)
+assert.eq(1.2e3 + 1, 1201)
+assert.eq(1 + 1.2e3, 1201)
+assert.eq(str(1.2e3 + nan), "nan")
+assert.eq(inf + 0, inf)
+assert.eq(inf + 1, inf)
+assert.eq(inf + inf, inf)
+assert.eq(str(inf + neginf), "nan")
+# -
+assert.eq(1.2e3 - 5.6e7, -5.59988e+07)
+assert.eq(1.2e3 - 1, 1199)
+assert.eq(1 - 1.2e3, -1199)
+assert.eq(str(1.2e3 - nan), "nan")
+assert.eq(inf - 0, inf)
+assert.eq(inf - 1, inf)
+assert.eq(str(inf - inf), "nan")
+assert.eq(inf - neginf, inf)
+# *
+assert.eq(1.5e6 * 2.2e3, 3.3e9)
+assert.eq(1.5e6 * 123, 1.845e+08)
+assert.eq(123 * 1.5e6, 1.845e+08)
+assert.eq(str(1.2e3 * nan), "nan")
+assert.eq(str(inf * 0), "nan")
+assert.eq(inf * 1, inf)
+assert.eq(inf * inf, inf)
+assert.eq(inf * neginf, neginf)
+# %
+assert.eq(100.0 % 7.0, 2)
+assert.eq(100.0 % -7.0, -5) # NB: different from Go / Java
+assert.eq(-100.0 % 7.0, 5) # NB: different from Go / Java
+assert.eq(-100.0 % -7.0, -2)
+assert.eq(-100.0 % 7, 5)
+assert.eq(100 % 7.0, 2)
+assert.eq(str(1.2e3 % nan), "nan")
+assert.eq(str(inf % 1), "nan")
+assert.eq(str(inf % inf), "nan")
+assert.eq(str(inf % neginf), "nan")
+# /
+assert.eq(str(100.0 / 7.0), "14.285714285714286")
+assert.eq(str(100 / 7.0), "14.285714285714286")
+assert.eq(str(100.0 / 7), "14.285714285714286")
+assert.eq(str(100.0 / nan), "nan")
+# //
+assert.eq(100.0 // 7.0, 14)
+assert.eq(100 // 7.0, 14)
+assert.eq(100.0 // 7, 14)
+assert.eq(100.0 // -7.0, -15)
+assert.eq(100 // -7.0, -15)
+assert.eq(100.0 // -7, -15)
+assert.eq(str(1 // neginf), "-0.0")
+assert.eq(str(100.0 // nan), "nan")
+
+# addition
+assert.eq(0.0 + 1.0, 1.0)
+assert.eq(1.0 + 1.0, 2.0)
+assert.eq(1.25 + 2.75, 4.0)
+assert.eq(5.0 + 7.0, 12.0)
+assert.eq(5.1 + 7, 12.1) # float + int
+assert.eq(7 + 5.1, 12.1) # int + float
+
+# subtraction
+assert.eq(5.0 - 7.0, -2.0)
+assert.eq(5.1 - 7.1, -2.0)
+assert.eq(5.5 - 7, -1.5)
+assert.eq(5 - 7.5, -2.5)
+assert.eq(0.0 - 1.0, -1.0)
+
+# multiplication
+assert.eq(5.0 * 7.0, 35.0)
+assert.eq(5.5 * 2.5, 13.75)
+assert.eq(5.5 * 7, 38.5)
+assert.eq(5 * 7.1, 35.5)
+
+# real division (like Python 3)
+# The / operator is available only when the 'fp' dialect option is enabled.
+assert.eq(100.0 / 8.0, 12.5)
+assert.eq(100.0 / -8.0, -12.5)
+assert.eq(-100.0 / 8.0, -12.5)
+assert.eq(-100.0 / -8.0, 12.5)
+assert.eq(98.0 / 8.0, 12.25)
+assert.eq(98.0 / -8.0, -12.25)
+assert.eq(-98.0 / 8.0, -12.25)
+assert.eq(-98.0 / -8.0, 12.25)
+assert.eq(2.5 / 2.0, 1.25)
+assert.eq(2.5 / 2, 1.25)
+assert.eq(5 / 4.0, 1.25)
+assert.eq(5 / 4, 1.25)
+assert.fails(lambda: 1.0 / 0, "floating-point division by zero")
+assert.fails(lambda: 1.0 / 0.0, "floating-point division by zero")
+assert.fails(lambda: 1 / 0.0, "floating-point division by zero")
+
+# floored division
+assert.eq(100.0 // 8.0, 12.0)
+assert.eq(100.0 // -8.0, -13.0)
+assert.eq(-100.0 // 8.0, -13.0)
+assert.eq(-100.0 // -8.0, 12.0)
+assert.eq(98.0 // 8.0, 12.0)
+assert.eq(98.0 // -8.0, -13.0)
+assert.eq(-98.0 // 8.0, -13.0)
+assert.eq(-98.0 // -8.0, 12.0)
+assert.eq(2.5 // 2.0, 1.0)
+assert.eq(2.5 // 2, 1.0)
+assert.eq(5 // 4.0, 1.0)
+assert.eq(5 // 4, 1)
+assert.eq(type(5 // 4), "int")
+assert.fails(lambda: 1.0 // 0, "floored division by zero")
+assert.fails(lambda: 1.0 // 0.0, "floored division by zero")
+assert.fails(lambda: 1 // 0.0, "floored division by zero")
+
+# remainder
+assert.eq(100.0 % 8.0, 4.0)
+assert.eq(100.0 % -8.0, -4.0)
+assert.eq(-100.0 % 8.0, 4.0)
+assert.eq(-100.0 % -8.0, -4.0)
+assert.eq(98.0 % 8.0, 2.0)
+assert.eq(98.0 % -8.0, -6.0)
+assert.eq(-98.0 % 8.0, 6.0)
+assert.eq(-98.0 % -8.0, -2.0)
+assert.eq(2.5 % 2.0, 0.5)
+assert.eq(2.5 % 2, 0.5)
+assert.eq(5 % 4.0, 1.0)
+assert.fails(lambda: 1.0 % 0, "floating-point modulo by zero")
+assert.fails(lambda: 1.0 % 0.0, "floating-point modulo by zero")
+assert.fails(lambda: 1 % 0.0, "floating-point modulo by zero")
+
+# floats cannot be used as indices, even if integral
+assert.fails(lambda: "abc"[1.0], "want int")
+assert.fails(lambda: ["A", "B", "C"].insert(1.0, "D"), "want int")
+assert.fails(lambda: range(3)[1.0], "got float, want int")
+
+# -- comparisons --
+# NaN
+assert.true(nan == nan) # \
+assert.true(nan >= nan) # unlike Python
+assert.true(nan <= nan) # /
+assert.true(not (nan > nan))
+assert.true(not (nan < nan))
+assert.true(not (nan != nan)) # unlike Python
+# Sort is stable: 0.0 and -0.0 are equal, but they are not permuted.
+# Similarly 1 and 1.0.
+assert.eq(
+ str(sorted([inf, neginf, nan, 1e300, -1e300, 1.0, -1.0, 1, -1, 1e-300, -1e-300, 0, 0.0, negzero, 1e-300, -1e-300])),
+ "[-inf, -1e+300, -1.0, -1, -1e-300, -1e-300, 0, 0.0, -0.0, 1e-300, 1e-300, 1.0, 1, 1e+300, +inf, nan]")
+
+# Sort is stable, and its result contains no adjacent x, y such that y > x.
+# Note: Python's reverse sort is unstable; see https://bugs.python.org/issue36095.
+assert.eq(str(sorted([7, 3, nan, 1, 9])), "[1, 3, 7, 9, nan]")
+assert.eq(str(sorted([7, 3, nan, 1, 9], reverse=True)), "[nan, 9, 7, 3, 1]")
+
+# All NaN values compare equal. (Identical objects compare equal.)
+nandict = {nan: 1}
+nandict[nan] = 2
+assert.eq(len(nandict), 1) # (same as Python)
+assert.eq(nandict[nan], 2) # (same as Python)
+assert.fails(lambda: {nan: 1, nan: 2}, "duplicate key: nan")
+
+nandict[float('nan')] = 3 # a distinct NaN object
+assert.eq(str(nandict), "{nan: 3}") # (Python: {nan: 2, nan: 3})
+
+assert.eq(str({inf: 1, neginf: 2}), "{+inf: 1, -inf: 2}")
+
+# zero
+assert.eq(0.0, negzero)
+
+# inf
+assert.eq(+inf / +inf, nan)
+assert.eq(+inf / -inf, nan)
+assert.eq(-inf / +inf, nan)
+assert.eq(0.0 / +inf, 0.0)
+assert.eq(0.0 / -inf, 0.0)
+assert.true(inf > -inf)
+assert.eq(inf, -neginf)
+# TODO(adonovan): assert inf > any finite number, etc.
+
+# negative zero
+negz = -0
+assert.eq(negz, 0)
+
+# min/max ordering with NaN (the greatest float value)
+assert.eq(max([1, nan, 3]), nan)
+assert.eq(max([nan, 2, 3]), nan)
+assert.eq(min([1, nan, 3]), 1)
+assert.eq(min([nan, 2, 3]), 2)
+
+# float/float comparisons
+fltmax = 1.7976931348623157e+308 # approx
+fltmin = 4.9406564584124654e-324 # approx
+assert.lt(-inf, -fltmax)
+assert.lt(-fltmax, -1.0)
+assert.lt(-1.0, -fltmin)
+assert.lt(-fltmin, 0.0)
+assert.lt(0, fltmin)
+assert.lt(fltmin, 1.0)
+assert.lt(1.0, fltmax)
+assert.lt(fltmax, inf)
+
+# int/float comparisons
+assert.eq(0, 0.0)
+assert.eq(1, 1.0)
+assert.eq(-1, -1.0)
+assert.ne(-1, -1.0 + 1e-7)
+assert.lt(-2, -2 + 1e-15)
+
+# int conversion (rounds towards zero)
+assert.eq(int(100.1), 100)
+assert.eq(int(100.0), 100)
+assert.eq(int(99.9), 99)
+assert.eq(int(-99.9), -99)
+assert.eq(int(-100.0), -100)
+assert.eq(int(-100.1), -100)
+assert.eq(int(1e100), int("10000000000000000159028911097599180468360808563945281389781327557747838772170381060813469985856815104"))
+assert.fails(lambda: int(inf), "cannot convert.*infinity")
+assert.fails(lambda: int(nan), "cannot convert.*NaN")
+
+# -- float() function --
+assert.eq(float(), 0.0)
+# float(bool)
+assert.eq(float(False), 0.0)
+assert.eq(float(True), 1.0)
+# float(int)
+assert.eq(float(0), 0.0)
+assert.eq(float(1), 1.0)
+assert.eq(float(123), 123.0)
+assert.eq(float(123 * 1000000 * 1000000 * 1000000 * 1000000 * 1000000), 1.23e+32)
+# float(float)
+assert.eq(float(1.1), 1.1)
+assert.fails(lambda: float(None), "want number or string")
+assert.ne(False, 0.0) # differs from Python
+assert.ne(True, 1.0)
+# float(string)
+assert.eq(float("1.1"), 1.1)
+assert.fails(lambda: float("1.1abc"), "invalid float literal")
+assert.fails(lambda: float("1e100.0"), "invalid float literal")
+assert.fails(lambda: float("1e1000"), "floating-point number too large")
+assert.eq(float("-1.1"), -1.1)
+assert.eq(float("+1.1"), +1.1)
+assert.eq(float("+Inf"), inf)
+assert.eq(float("-Inf"), neginf)
+assert.eq(float("NaN"), nan)
+assert.eq(float("NaN"), nan)
+assert.eq(float("+NAN"), nan)
+assert.eq(float("-nan"), nan)
+assert.eq(str(float("Inf")), "+inf")
+assert.eq(str(float("+INF")), "+inf")
+assert.eq(str(float("-inf")), "-inf")
+assert.eq(str(float("+InFiniTy")), "+inf")
+assert.eq(str(float("-iNFiniTy")), "-inf")
+assert.fails(lambda: float("one point two"), "invalid float literal: one point two")
+assert.fails(lambda: float("1.2.3"), "invalid float literal: 1.2.3")
+assert.fails(lambda: float(123 << 500 << 500 << 50), "int too large to convert to float")
+assert.fails(lambda: float(-123 << 500 << 500 << 50), "int too large to convert to float")
+assert.fails(lambda: float(str(-123 << 500 << 500 << 50)), "floating-point number too large")
+
+# -- implicit float(int) conversions --
+assert.fails(lambda: (1<<500<<500<<500) + 0.0, "int too large to convert to float")
+assert.fails(lambda: 0.0 + (1<<500<<500<<500), "int too large to convert to float")
+assert.fails(lambda: (1<<500<<500<<500) - 0.0, "int too large to convert to float")
+assert.fails(lambda: 0.0 - (1<<500<<500<<500), "int too large to convert to float")
+assert.fails(lambda: (1<<500<<500<<500) * 1.0, "int too large to convert to float")
+assert.fails(lambda: 1.0 * (1<<500<<500<<500), "int too large to convert to float")
+assert.fails(lambda: (1<<500<<500<<500) / 1.0, "int too large to convert to float")
+assert.fails(lambda: 1.0 / (1<<500<<500<<500), "int too large to convert to float")
+assert.fails(lambda: (1<<500<<500<<500) // 1.0, "int too large to convert to float")
+assert.fails(lambda: 1.0 // (1<<500<<500<<500), "int too large to convert to float")
+assert.fails(lambda: (1<<500<<500<<500) % 1.0, "int too large to convert to float")
+assert.fails(lambda: 1.0 % (1<<500<<500<<500), "int too large to convert to float")
+
+
+# -- int function --
+assert.eq(int(0.0), 0)
+assert.eq(int(1.0), 1)
+assert.eq(int(1.1), 1)
+assert.eq(int(0.9), 0)
+assert.eq(int(-1.1), -1.0)
+assert.eq(int(-1.0), -1.0)
+assert.eq(int(-0.9), 0.0)
+assert.eq(int(1.23e+32), 123000000000000004979083645550592)
+assert.eq(int(-1.23e-32), 0)
+assert.eq(int(1.23e-32), 0)
+assert.fails(lambda: int(float("+Inf")), "cannot convert float infinity to integer")
+assert.fails(lambda: int(float("-Inf")), "cannot convert float infinity to integer")
+assert.fails(lambda: int(float("NaN")), "cannot convert float NaN to integer")
+
+
+# hash
+# Check that equal float and int values have the same internal hash.
+def checkhash():
+ for a in [1.23e100, 1.23e10, 1.23e1, 1.23,
+ 1, 4294967295, 8589934591, 9223372036854775807]:
+ for b in [a, -a, 1/a, -1/a]:
+ f = float(b)
+ i = int(b)
+ if f == i:
+ fh = {f: None}
+ ih = {i: None}
+ if fh != ih:
+ assert.true(False, "{%v: None} != {%v: None}: hashes vary" % fh, ih)
+checkhash()
+
+# string formatting
+
+# %d
+assert.eq("%d" % 0, "0")
+assert.eq("%d" % 0.0, "0")
+assert.eq("%d" % 123, "123")
+assert.eq("%d" % 123.0, "123")
+assert.eq("%d" % 1.23e45, "1229999999999999973814869011019624571608236032")
+# (see below for '%d' % NaN/Inf)
+assert.eq("%d" % negzero, "0")
+assert.fails(lambda: "%d" % float("NaN"), "cannot convert float NaN to integer")
+assert.fails(lambda: "%d" % float("+Inf"), "cannot convert float infinity to integer")
+assert.fails(lambda: "%d" % float("-Inf"), "cannot convert float infinity to integer")
+
+# %e
+assert.eq("%e" % 0, "0.000000e+00")
+assert.eq("%e" % 0.0, "0.000000e+00")
+assert.eq("%e" % 123, "1.230000e+02")
+assert.eq("%e" % 123.0, "1.230000e+02")
+assert.eq("%e" % 1.23e45, "1.230000e+45")
+assert.eq("%e" % -1.23e-45, "-1.230000e-45")
+assert.eq("%e" % nan, "nan")
+assert.eq("%e" % inf, "+inf")
+assert.eq("%e" % neginf, "-inf")
+assert.eq("%e" % negzero, "-0.000000e+00")
+assert.fails(lambda: "%e" % "123", "requires float, not str")
+# %f
+assert.eq("%f" % 0, "0.000000")
+assert.eq("%f" % 0.0, "0.000000")
+assert.eq("%f" % 123, "123.000000")
+assert.eq("%f" % 123.0, "123.000000")
+# Note: Starlark/Java emits 1230000000000000000000000000000000000000000000.000000. Why?
+assert.eq("%f" % 1.23e45, "1229999999999999973814869011019624571608236032.000000")
+assert.eq("%f" % -1.23e-45, "-0.000000")
+assert.eq("%f" % nan, "nan")
+assert.eq("%f" % inf, "+inf")
+assert.eq("%f" % neginf, "-inf")
+assert.eq("%f" % negzero, "-0.000000")
+assert.fails(lambda: "%f" % "123", "requires float, not str")
+# %g
+assert.eq("%g" % 0, "0.0")
+assert.eq("%g" % 0.0, "0.0")
+assert.eq("%g" % 123, "123.0")
+assert.eq("%g" % 123.0, "123.0")
+assert.eq("%g" % 1.110, "1.11")
+assert.eq("%g" % 1e5, "100000.0")
+assert.eq("%g" % 1e6, "1e+06") # Note: threshold of scientific notation is 1e17 in Starlark/Java
+assert.eq("%g" % 1.23e45, "1.23e+45")
+assert.eq("%g" % -1.23e-45, "-1.23e-45")
+assert.eq("%g" % nan, "nan")
+assert.eq("%g" % inf, "+inf")
+assert.eq("%g" % neginf, "-inf")
+assert.eq("%g" % negzero, "-0.0")
+# str uses %g
+assert.eq(str(0.0), "0.0")
+assert.eq(str(123.0), "123.0")
+assert.eq(str(1.23e45), "1.23e+45")
+assert.eq(str(-1.23e-45), "-1.23e-45")
+assert.eq(str(nan), "nan")
+assert.eq(str(inf), "+inf")
+assert.eq(str(neginf), "-inf")
+assert.eq(str(negzero), "-0.0")
+assert.fails(lambda: "%g" % "123", "requires float, not str")
+
+i0 = 1
+f0 = 1.0
+assert.eq(type(i0), "int")
+assert.eq(type(f0), "float")
+
+ops = {
+ '+': lambda x, y: x + y,
+ '-': lambda x, y: x - y,
+ '*': lambda x, y: x * y,
+ '/': lambda x, y: x / y,
+ '//': lambda x, y: x // y,
+ '%': lambda x, y: x % y,
+}
+
+# Check that if either argument is a float, so too is the result.
+def checktypes():
+ want = set("""
+int + int = int
+int + float = float
+float + int = float
+float + float = float
+int - int = int
+int - float = float
+float - int = float
+float - float = float
+int * int = int
+int * float = float
+float * int = float
+float * float = float
+int / int = float
+int / float = float
+float / int = float
+float / float = float
+int // int = int
+int // float = float
+float // int = float
+float // float = float
+int % int = int
+int % float = float
+float % int = float
+float % float = float
+"""[1:].splitlines())
+ for opname in ("+", "-", "*", "/", "%"):
+ for x in [i0, f0]:
+ for y in [i0, f0]:
+ op = ops[opname]
+ got = "%s %s %s = %s" % (type(x), opname, type(y), type(op(x, y)))
+ assert.contains(want, got)
+checktypes()
diff --git a/starlark/testdata/function.star b/starlark/testdata/function.star
new file mode 100644
index 0000000..737df26
--- /dev/null
+++ b/starlark/testdata/function.star
@@ -0,0 +1,323 @@
+# Tests of Starlark 'function'
+# option:set
+
+# TODO(adonovan):
+# - add some introspection functions for looking at function values
+# and test that functions have correct position, free vars, names of locals, etc.
+# - move the hard-coded tests of parameter passing from eval_test.go to here.
+
+load("assert.star", "assert", "freeze")
+
+# Test lexical scope and closures:
+def outer(x):
+ def inner(y):
+ return x + x + y # multiple occurrences of x should create only 1 freevar
+ return inner
+
+z = outer(3)
+assert.eq(z(5), 11)
+assert.eq(z(7), 13)
+z2 = outer(4)
+assert.eq(z2(5), 13)
+assert.eq(z2(7), 15)
+assert.eq(z(5), 11)
+assert.eq(z(7), 13)
+
+# Function name
+assert.eq(str(outer), '<function outer>')
+assert.eq(str(z), '<function inner>')
+assert.eq(str(str), '<built-in function str>')
+assert.eq(str("".startswith), '<built-in method startswith of string value>')
+
+# Stateful closure
+def squares():
+ x = [0]
+ def f():
+ x[0] += 1
+ return x[0] * x[0]
+ return f
+
+sq = squares()
+assert.eq(sq(), 1)
+assert.eq(sq(), 4)
+assert.eq(sq(), 9)
+assert.eq(sq(), 16)
+
+# Freezing a closure
+sq2 = freeze(sq)
+assert.fails(sq2, "frozen list")
+
+# recursion detection, simple
+def fib(x):
+ if x < 2:
+ return x
+ return fib(x-2) + fib(x-1)
+assert.fails(lambda: fib(10), "function fib called recursively")
+
+# recursion detection, advanced
+#
+# A simplistic recursion check that looks for repeated calls to the
+# same function value will not detect recursion using the Y
+# combinator, which creates a new closure at each step of the
+# recursion. To truly prohibit recursion, the dynamic check must look
+# for repeated calls of the same syntactic function body.
+Y = lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args)))
+fibgen = lambda fib: lambda x: (x if x<2 else fib(x-1)+fib(x-2))
+fib2 = Y(fibgen)
+assert.fails(lambda: [fib2(x) for x in range(10)], "function lambda called recursively")
+
+# However, this stricter check outlaws many useful programs
+# that are still bounded, and creates a hazard because
+# helper functions such as map below cannot be used to
+# call functions that themselves use map:
+def map(f, seq): return [f(x) for x in seq]
+def double(x): return x+x
+assert.eq(map(double, [1, 2, 3]), [2, 4, 6])
+assert.eq(map(double, ["a", "b", "c"]), ["aa", "bb", "cc"])
+def mapdouble(x): return map(double, x)
+assert.fails(lambda: map(mapdouble, ([1, 2, 3], ["a", "b", "c"])),
+ 'function map called recursively')
+# With the -recursion option it would yield [[2, 4, 6], ["aa", "bb", "cc"]].
+
+# call of function not through its name
+# (regression test for parsing suffixes of primary expressions)
+hf = hasfields()
+hf.x = [len]
+assert.eq(hf.x[0]("abc"), 3)
+def f():
+ return lambda: 1
+assert.eq(f()(), 1)
+assert.eq(["abc"][0][0].upper(), "A")
+
+# functions may be recursively defined,
+# so long as they don't dynamically recur.
+calls = []
+def yin(x):
+ calls.append("yin")
+ if x:
+ yang(False)
+
+def yang(x):
+ calls.append("yang")
+ if x:
+ yin(False)
+
+yin(True)
+assert.eq(calls, ["yin", "yang"])
+
+calls.clear()
+yang(True)
+assert.eq(calls, ["yang", "yin"])
+
+
+# builtin_function_or_method use identity equivalence.
+closures = set(["".count for _ in range(10)])
+assert.eq(len(closures), 10)
+
+---
+# Default values of function parameters are mutable.
+load("assert.star", "assert", "freeze")
+
+def f(x=[0]):
+ return x
+
+assert.eq(f(), [0])
+
+f().append(1)
+assert.eq(f(), [0, 1])
+
+# Freezing a function value freezes its parameter defaults.
+freeze(f)
+assert.fails(lambda: f().append(2), "cannot append to frozen list")
+
+---
+# This is a well known corner case of parsing in Python.
+load("assert.star", "assert")
+
+f = lambda x: 1 if x else 0
+assert.eq(f(True), 1)
+assert.eq(f(False), 0)
+
+x = True
+f2 = (lambda x: 1) if x else 0
+assert.eq(f2(123), 1)
+
+tf = lambda: True, lambda: False
+assert.true(tf[0]())
+assert.true(not tf[1]())
+
+---
+# Missing parameters are correctly reported
+# in functions of more than 64 parameters.
+# (This tests a corner case of the implementation:
+# we avoid a map allocation for <64 parameters)
+
+load("assert.star", "assert")
+
+def f(a, b, c, d, e, f, g, h,
+ i, j, k, l, m, n, o, p,
+ q, r, s, t, u, v, w, x,
+ y, z, A, B, C, D, E, F,
+ G, H, I, J, K, L, M, N,
+ O, P, Q, R, S, T, U, V,
+ W, X, Y, Z, aa, bb, cc, dd,
+ ee, ff, gg, hh, ii, jj, kk, ll,
+ mm):
+ pass
+
+assert.fails(lambda: f(
+ 1, 2, 3, 4, 5, 6, 7, 8,
+ 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32,
+ 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48,
+ 49, 50, 51, 52, 53, 54, 55, 56,
+ 57, 58, 59, 60, 61, 62, 63, 64), "missing 1 argument \\(mm\\)")
+
+assert.fails(lambda: f(
+ 1, 2, 3, 4, 5, 6, 7, 8,
+ 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32,
+ 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48,
+ 49, 50, 51, 52, 53, 54, 55, 56,
+ 57, 58, 59, 60, 61, 62, 63, 64, 65,
+ mm = 100), 'multiple values for parameter "mm"')
+
+---
+# Regression test for github.com/google/starlark-go/issues/21,
+# which concerns dynamic checks.
+# Related: https://github.com/bazelbuild/starlark/issues/21,
+# which concerns static checks.
+
+load("assert.star", "assert")
+
+def f(*args, **kwargs):
+ return args, kwargs
+
+assert.eq(f(x=1, y=2), ((), {"x": 1, "y": 2}))
+assert.fails(lambda: f(x=1, **dict(x=2)), 'multiple values for parameter "x"')
+
+def g(x, y):
+ return x, y
+
+assert.eq(g(1, y=2), (1, 2))
+assert.fails(lambda: g(1, y=2, **{'y': 3}), 'multiple values for parameter "y"')
+
+---
+# Regression test for a bug in CALL_VAR_KW.
+
+load("assert.star", "assert")
+
+def f(a, b, x, y):
+ return a+b+x+y
+
+assert.eq(f(*("a", "b"), **dict(y="y", x="x")) + ".", 'abxy.')
+---
+# Order of evaluation of function arguments.
+# Regression test for github.com/google/skylark/issues/135.
+load("assert.star", "assert")
+
+r = []
+
+def id(x):
+ r.append(x)
+ return x
+
+def f(*args, **kwargs):
+ return (args, kwargs)
+
+y = f(id(1), id(2), x=id(3), *[id(4)], **dict(z=id(5)))
+assert.eq(y, ((1, 2, 4), dict(x=3, z=5)))
+
+# This matches Python2 and Starlark-in-Java, but not Python3 [1 2 4 3 6].
+# *args and *kwargs are evaluated last.
+# (Python[23] also allows keyword arguments after *args.)
+# See github.com/bazelbuild/starlark#13 for spec change.
+assert.eq(r, [1, 2, 3, 4, 5])
+
+---
+# option:recursion
+# See github.com/bazelbuild/starlark#170
+load("assert.star", "assert")
+
+def a():
+ list = []
+ def b(n):
+ list.append(n)
+ if n > 0:
+ b(n - 1) # recursive reference to b
+
+ b(3)
+ return list
+
+assert.eq(a(), [3, 2, 1, 0])
+
+def c():
+ list = []
+ x = 1
+ def d():
+ list.append(x) # this use of x observes both assignments
+ d()
+ x = 2
+ d()
+ return list
+
+assert.eq(c(), [1, 2])
+
+def e():
+ def f():
+ return x # forward reference ok: x is a closure cell
+ x = 1
+ return f()
+
+assert.eq(e(), 1)
+
+---
+load("assert.star", "assert")
+
+def e():
+ x = 1
+ def f():
+ print(x) # this reference to x fails
+ x = 3 # because this assignment makes x local to f
+ f()
+
+assert.fails(e, "local variable x referenced before assignment")
+
+def f():
+ def inner():
+ return x
+ if False:
+ x = 0
+ return x # fails (x is an uninitialized cell of this function)
+
+assert.fails(f, "local variable x referenced before assignment")
+
+def g():
+ def inner():
+ return x # fails (x is an uninitialized cell of the enclosing function)
+ if False:
+ x = 0
+ return inner()
+
+assert.fails(g, "local variable x referenced before assignment")
+
+---
+# A trailing comma is allowed in any function definition or call.
+# This reduces the need to edit neighboring lines when editing defs
+# or calls splayed across multiple lines.
+
+def a(x,): pass
+def b(x, y=None, ): pass
+def c(x, y=None, *args, ): pass
+def d(x, y=None, *args, z=None, ): pass
+def e(x, y=None, *args, z=None, **kwargs, ): pass
+
+a(1,)
+b(1, y=2, )
+#c(1, *[], )
+#d(1, *[], z=None, )
+#e(1, *[], z=None, *{}, )
diff --git a/starlark/testdata/int.star b/starlark/testdata/int.star
new file mode 100644
index 0000000..46c0ad0
--- /dev/null
+++ b/starlark/testdata/int.star
@@ -0,0 +1,260 @@
+# Tests of Starlark 'int'
+
+load("assert.star", "assert")
+
+# basic arithmetic
+assert.eq(0 - 1, -1)
+assert.eq(0 + 1, +1)
+assert.eq(1 + 1, 2)
+assert.eq(5 + 7, 12)
+assert.eq(5 * 7, 35)
+assert.eq(5 - 7, -2)
+
+# int boundaries
+maxint64 = (1 << 63) - 1
+minint64 = -1 << 63
+maxint32 = (1 << 31) - 1
+minint32 = -1 << 31
+assert.eq(maxint64, 9223372036854775807)
+assert.eq(minint64, -9223372036854775808)
+assert.eq(maxint32, 2147483647)
+assert.eq(minint32, -2147483648)
+
+# truth
+def truth():
+ assert.true(not 0)
+ for m in [1, maxint32]: # Test small/big ranges
+ assert.true(123 * m)
+ assert.true(-1 * m)
+
+truth()
+
+# floored division
+# (For real division, see float.star.)
+def division():
+ for m in [1, maxint32]: # Test small/big ranges
+ assert.eq((100 * m) // (7 * m), 14)
+ assert.eq((100 * m) // (-7 * m), -15)
+ assert.eq((-100 * m) // (7 * m), -15) # NB: different from Go/Java
+ assert.eq((-100 * m) // (-7 * m), 14) # NB: different from Go/Java
+ assert.eq((98 * m) // (7 * m), 14)
+ assert.eq((98 * m) // (-7 * m), -14)
+ assert.eq((-98 * m) // (7 * m), -14)
+ assert.eq((-98 * m) // (-7 * m), 14)
+
+division()
+
+# remainder
+def remainder():
+ for m in [1, maxint32]: # Test small/big ranges
+ assert.eq((100 * m) % (7 * m), 2 * m)
+ assert.eq((100 * m) % (-7 * m), -5 * m) # NB: different from Go/Java
+ assert.eq((-100 * m) % (7 * m), 5 * m) # NB: different from Go/Java
+ assert.eq((-100 * m) % (-7 * m), -2 * m)
+ assert.eq((98 * m) % (7 * m), 0)
+ assert.eq((98 * m) % (-7 * m), 0)
+ assert.eq((-98 * m) % (7 * m), 0)
+ assert.eq((-98 * m) % (-7 * m), 0)
+
+remainder()
+
+# compound assignment
+def compound():
+ x = 1
+ x += 1
+ assert.eq(x, 2)
+ x -= 3
+ assert.eq(x, -1)
+ x *= 39
+ assert.eq(x, -39)
+ x //= 4
+ assert.eq(x, -10)
+ x /= -2
+ assert.eq(x, 5)
+ x %= 3
+ assert.eq(x, 2)
+
+ # use resolve.AllowBitwise to enable the ops:
+ x = 2
+ x &= 1
+ assert.eq(x, 0)
+ x |= 2
+ assert.eq(x, 2)
+ x ^= 3
+ assert.eq(x, 1)
+ x <<= 2
+ assert.eq(x, 4)
+ x >>= 2
+ assert.eq(x, 1)
+
+compound()
+
+# int conversion
+# See float.star for float-to-int conversions.
+# We follow Python 3 here, but I can't see the method in its madness.
+# int from bool/int/float
+assert.fails(int, "missing argument") # int()
+assert.eq(int(False), 0)
+assert.eq(int(True), 1)
+assert.eq(int(3), 3)
+assert.eq(int(3.1), 3)
+assert.fails(lambda: int(3, base = 10), "non-string with explicit base")
+assert.fails(lambda: int(True, 10), "non-string with explicit base")
+
+# int from string, base implicitly 10
+assert.eq(int("100000000000000000000"), 10000000000 * 10000000000)
+assert.eq(int("-100000000000000000000"), -10000000000 * 10000000000)
+assert.eq(int("123"), 123)
+assert.eq(int("-123"), -123)
+assert.eq(int("0123"), 123) # not octal
+assert.eq(int("-0123"), -123)
+assert.fails(lambda: int("0x12"), "invalid literal with base 10")
+assert.fails(lambda: int("-0x12"), "invalid literal with base 10")
+assert.fails(lambda: int("0o123"), "invalid literal.*base 10")
+assert.fails(lambda: int("-0o123"), "invalid literal.*base 10")
+
+# int from string, explicit base
+assert.eq(int("0"), 0)
+assert.eq(int("00"), 0)
+assert.eq(int("0", base = 10), 0)
+assert.eq(int("00", base = 10), 0)
+assert.eq(int("0", base = 8), 0)
+assert.eq(int("00", base = 8), 0)
+assert.eq(int("-0"), 0)
+assert.eq(int("-00"), 0)
+assert.eq(int("-0", base = 10), 0)
+assert.eq(int("-00", base = 10), 0)
+assert.eq(int("-0", base = 8), 0)
+assert.eq(int("-00", base = 8), 0)
+assert.eq(int("+0"), 0)
+assert.eq(int("+00"), 0)
+assert.eq(int("+0", base = 10), 0)
+assert.eq(int("+00", base = 10), 0)
+assert.eq(int("+0", base = 8), 0)
+assert.eq(int("+00", base = 8), 0)
+assert.eq(int("11", base = 9), 10)
+assert.eq(int("-11", base = 9), -10)
+assert.eq(int("10011", base = 2), 19)
+assert.eq(int("-10011", base = 2), -19)
+assert.eq(int("123", 8), 83)
+assert.eq(int("-123", 8), -83)
+assert.eq(int("0123", 8), 83) # redundant zeros permitted
+assert.eq(int("-0123", 8), -83)
+assert.eq(int("00123", 8), 83)
+assert.eq(int("-00123", 8), -83)
+assert.eq(int("0o123", 8), 83)
+assert.eq(int("-0o123", 8), -83)
+assert.eq(int("123", 7), 66) # 1*7*7 + 2*7 + 3
+assert.eq(int("-123", 7), -66)
+assert.eq(int("12", 16), 18)
+assert.eq(int("-12", 16), -18)
+assert.eq(int("0x12", 16), 18)
+assert.eq(int("-0x12", 16), -18)
+assert.eq(0x1000000000000001 * 0x1000000000000001, 0x1000000000000002000000000000001)
+assert.eq(int("1010", 2), 10)
+assert.eq(int("111111101", 2), 509)
+assert.eq(int("0b0101", 0), 5)
+assert.eq(int("0b0101", 2), 5) # prefix is redundant with explicit base
+assert.eq(int("0b00000", 0), 0)
+assert.eq(1111111111111111 * 1111111111111111, 1234567901234567654320987654321)
+assert.fails(lambda: int("0x123", 8), "invalid literal.*base 8")
+assert.fails(lambda: int("-0x123", 8), "invalid literal.*base 8")
+assert.fails(lambda: int("0o123", 16), "invalid literal.*base 16")
+assert.fails(lambda: int("-0o123", 16), "invalid literal.*base 16")
+assert.fails(lambda: int("0x110", 2), "invalid literal.*base 2")
+
+# Base prefix is honored only if base=0, or if the prefix matches the explicit base.
+# See https://github.com/google/starlark-go/issues/337
+assert.fails(lambda: int("0b0"), "invalid literal.*base 10")
+assert.eq(int("0b0", 0), 0)
+assert.eq(int("0b0", 2), 0)
+assert.eq(int("0b0", 16), 0xb0)
+assert.eq(int("0x0b0", 16), 0xb0)
+assert.eq(int("0x0b0", 0), 0xb0)
+assert.eq(int("0x0b0101", 16), 0x0b0101)
+
+# int from string, auto detect base
+assert.eq(int("123", 0), 123)
+assert.eq(int("+123", 0), +123)
+assert.eq(int("-123", 0), -123)
+assert.eq(int("0x12", 0), 18)
+assert.eq(int("+0x12", 0), +18)
+assert.eq(int("-0x12", 0), -18)
+assert.eq(int("0o123", 0), 83)
+assert.eq(int("+0o123", 0), +83)
+assert.eq(int("-0o123", 0), -83)
+assert.fails(lambda: int("0123", 0), "invalid literal.*base 0") # valid in Python 2.7
+assert.fails(lambda: int("-0123", 0), "invalid literal.*base 0")
+
+# github.com/google/starlark-go/issues/108
+assert.fails(lambda: int("0Oxa", 8), "invalid literal with base 8: 0Oxa")
+
+# follow-on bugs to issue 108
+assert.fails(lambda: int("--4"), "invalid literal with base 10: --4")
+assert.fails(lambda: int("++4"), "invalid literal with base 10: \\+\\+4")
+assert.fails(lambda: int("+-4"), "invalid literal with base 10: \\+-4")
+assert.fails(lambda: int("0x-4", 16), "invalid literal with base 16: 0x-4")
+
+# bitwise union (int|int), intersection (int&int), XOR (int^int), unary not (~int),
+# left shift (int<<int), and right shift (int>>int).
+# use resolve.AllowBitwise to enable the ops.
+# TODO(adonovan): this is not yet in the Starlark spec,
+# but there is consensus that it should be.
+assert.eq(1 | 2, 3)
+assert.eq(3 | 6, 7)
+assert.eq((1 | 2) & (2 | 4), 2)
+assert.eq(1 ^ 2, 3)
+assert.eq(2 ^ 2, 0)
+assert.eq(1 | 0 ^ 1, 1) # check | and ^ operators precedence
+assert.eq(~1, -2)
+assert.eq(~(-2), 1)
+assert.eq(~0, -1)
+assert.eq(1 << 2, 4)
+assert.eq(2 >> 1, 1)
+assert.fails(lambda: 2 << -1, "negative shift count")
+assert.fails(lambda: 1 << 512, "shift count too large")
+
+# comparisons
+# TODO(adonovan): test: < > == != etc
+def comparisons():
+ for m in [1, maxint32 / 2, maxint32]: # Test small/big ranges
+ assert.lt(-2 * m, -1 * m)
+ assert.lt(-1 * m, 0 * m)
+ assert.lt(0 * m, 1 * m)
+ assert.lt(1 * m, 2 * m)
+ assert.true(2 * m >= 2 * m)
+ assert.true(2 * m > 1 * m)
+ assert.true(1 * m >= 1 * m)
+ assert.true(1 * m > 0 * m)
+ assert.true(0 * m >= 0 * m)
+ assert.true(0 * m > -1 * m)
+ assert.true(-1 * m >= -1 * m)
+ assert.true(-1 * m > -2 * m)
+
+comparisons()
+
+# precision
+assert.eq(str(maxint64), "9223372036854775807")
+assert.eq(str(maxint64 + 1), "9223372036854775808")
+assert.eq(str(minint64), "-9223372036854775808")
+assert.eq(str(minint64 - 1), "-9223372036854775809")
+assert.eq(str(minint64 * minint64), "85070591730234615865843651857942052864")
+assert.eq(str(maxint32 + 1), "2147483648")
+assert.eq(str(minint32 - 1), "-2147483649")
+assert.eq(str(minint32 * minint32), "4611686018427387904")
+assert.eq(str(minint32 | maxint32), "-1")
+assert.eq(str(minint32 & minint32), "-2147483648")
+assert.eq(str(minint32 ^ maxint32), "-1")
+assert.eq(str(minint32 // -1), "2147483648")
+
+# string formatting
+assert.eq("%o %x %d" % (0o755, 0xDEADBEEF, 42), "755 deadbeef 42")
+nums = [-95, -1, 0, +1, +95]
+assert.eq(" ".join(["%o" % x for x in nums]), "-137 -1 0 1 137")
+assert.eq(" ".join(["%d" % x for x in nums]), "-95 -1 0 1 95")
+assert.eq(" ".join(["%i" % x for x in nums]), "-95 -1 0 1 95")
+assert.eq(" ".join(["%x" % x for x in nums]), "-5f -1 0 1 5f")
+assert.eq(" ".join(["%X" % x for x in nums]), "-5F -1 0 1 5F")
+assert.eq("%o %x %d" % (123, 123, 123), "173 7b 123")
+assert.eq("%o %x %d" % (123.1, 123.1, 123.1), "173 7b 123") # non-int operands are acceptable
+assert.fails(lambda: "%d" % True, "cannot convert bool to int")
diff --git a/starlark/testdata/json.star b/starlark/testdata/json.star
new file mode 100644
index 0000000..7c7b316
--- /dev/null
+++ b/starlark/testdata/json.star
@@ -0,0 +1,147 @@
+# Tests of json module.
+
+load("assert.star", "assert")
+load("json.star", "json")
+
+assert.eq(dir(json), ["decode", "encode", "indent"])
+
+# Some of these cases were inspired by github.com/nst/JSONTestSuite.
+
+## json.encode
+
+assert.eq(json.encode(None), "null")
+assert.eq(json.encode(True), "true")
+assert.eq(json.encode(False), "false")
+assert.eq(json.encode(-123), "-123")
+assert.eq(json.encode(12345*12345*12345*12345*12345*12345), "3539537889086624823140625")
+assert.eq(json.encode(float(12345*12345*12345*12345*12345*12345)), "3.539537889086625e+24")
+assert.eq(json.encode(12.345e67), "1.2345e+68")
+assert.eq(json.encode("hello"), '"hello"')
+assert.eq(json.encode([1, 2, 3]), "[1,2,3]")
+assert.eq(json.encode((1, 2, 3)), "[1,2,3]")
+assert.eq(json.encode(range(3)), "[0,1,2]") # a built-in iterable
+assert.eq(json.encode(dict(x = 1, y = "two")), '{"x":1,"y":"two"}')
+assert.eq(json.encode(dict(y = "two", x = 1)), '{"x":1,"y":"two"}') # key, not insertion, order
+assert.eq(json.encode(struct(x = 1, y = "two")), '{"x":1,"y":"two"}') # a user-defined HasAttrs
+assert.eq(json.encode("😹"[:1]), '"\\ufffd"') # invalid UTF-8 -> replacement char
+
+def encode_error(expr, error):
+ assert.fails(lambda: json.encode(expr), error)
+
+encode_error(float("NaN"), "json.encode: cannot encode non-finite float nan")
+encode_error({1: "two"}, "dict has int key, want string")
+encode_error(len, "cannot encode builtin_function_or_method as JSON")
+encode_error(struct(x=[1, {"x": len}]), # nested failure
+ 'in field .x: at list index 1: in dict key "x": cannot encode...')
+encode_error(struct(x=[1, {"x": len}]), # nested failure
+ 'in field .x: at list index 1: in dict key "x": cannot encode...')
+encode_error({1: 2}, 'dict has int key, want string')
+
+## json.decode
+
+assert.eq(json.decode("null"), None)
+assert.eq(json.decode("true"), True)
+assert.eq(json.decode("false"), False)
+assert.eq(json.decode("-123"), -123)
+assert.eq(json.decode("-0"), -0)
+assert.eq(json.decode("3539537889086624823140625"), 3539537889086624823140625)
+assert.eq(json.decode("3539537889086624823140625.0"), float(3539537889086624823140625))
+assert.eq(json.decode("3.539537889086625e+24"), 3.539537889086625e+24)
+assert.eq(json.decode("0e+1"), 0)
+assert.eq(json.decode("-0.0"), -0.0)
+assert.eq(json.decode(
+ "-0.000000000000000000000000000000000000000000000000000000000000000000000000000001"),
+ -0.000000000000000000000000000000000000000000000000000000000000000000000000000001)
+assert.eq(json.decode('[]'), [])
+assert.eq(json.decode('[1]'), [1])
+assert.eq(json.decode('[1,2,3]'), [1, 2, 3])
+assert.eq(json.decode('{"one": 1, "two": 2}'), dict(one=1, two=2))
+assert.eq(json.decode('{"foo\\u0000bar": 42}'), {"foo\x00bar": 42})
+assert.eq(json.decode('"\\ud83d\\ude39\\ud83d\\udc8d"'), "😹💍")
+assert.eq(json.decode('"\\u0123"'), 'ģ')
+assert.eq(json.decode('"\x7f"'), "\x7f")
+
+def decode_error(expr, error):
+ assert.fails(lambda: json.decode(expr), error)
+
+decode_error('truefalse',
+ "json.decode: at offset 4, unexpected character 'f' after value")
+
+decode_error('"abc', "unclosed string literal")
+decode_error('"ab\\gc"', "invalid character 'g' in string escape code")
+decode_error("'abc'", "unexpected character '\\\\''")
+
+decode_error("1.2.3", "invalid number: 1.2.3")
+decode_error("+1", "unexpected character '\\+'")
+decode_error("-abc", "invalid number: -")
+decode_error("-", "invalid number: -")
+decode_error("-00", "invalid number: -00")
+decode_error("00", "invalid number: 00")
+decode_error("--1", "invalid number: --1")
+decode_error("-+1", "invalid number: -\\+1")
+decode_error("1e1e1", "invalid number: 1e1e1")
+decode_error("0123", "invalid number: 0123")
+decode_error("000.123", "invalid number: 000.123")
+decode_error("-0123", "invalid number: -0123")
+decode_error("-000.123", "invalid number: -000.123")
+decode_error("0x123", "unexpected character 'x' after value")
+
+decode_error('[1, 2 ', "unexpected end of file")
+decode_error('[1, 2, ', "unexpected end of file")
+decode_error('[1, 2, ]', "unexpected character ']'")
+decode_error('[1, 2, }', "unexpected character '}'")
+decode_error('[1, 2}', "got '}', want ',' or ']'")
+
+decode_error('{"one": 1', "unexpected end of file")
+decode_error('{"one" 1', "after object key, got '1', want ':'")
+decode_error('{"one": 1 "two": 2', "in object, got '\"', want ',' or '}'")
+decode_error('{"one": 1,', "unexpected end of file")
+decode_error('{"one": 1, }', "unexpected character '}'")
+decode_error('{"one": 1]', "in object, got ']', want ',' or '}'")
+
+def codec(x):
+ return json.decode(json.encode(x))
+
+# string round-tripping
+strings = [
+ "😿", # U+1F63F CRYING_CAT_FACE
+ "🐱‍👤", # CAT FACE + ZERO WIDTH JOINER + BUST IN SILHOUETTE
+]
+assert.eq(codec(strings), strings)
+
+# codepoints is a string with every 16-bit code point.
+codepoints = ''.join(['%c' % c for c in range(65536)])
+assert.eq(codec(codepoints), codepoints)
+
+# number round-tripping
+numbers = [
+ 0, 1, -1, +1, 1.23e45, -1.23e-45,
+ 3539537889086624823140625,
+ float(3539537889086624823140625),
+]
+assert.eq(codec(numbers), numbers)
+
+## json.indent
+
+s = json.encode(dict(x = 1, y = ["one", "two"]))
+
+assert.eq(json.indent(s), '''{
+ "x": 1,
+ "y": [
+ "one",
+ "two"
+ ]
+}''')
+
+assert.eq(json.decode(json.indent(s)), {"x": 1, "y": ["one", "two"]})
+
+assert.eq(json.indent(s, prefix='¶', indent='–––'), '''{
+¶–––"x": 1,
+¶–––"y": [
+¶––––––"one",
+¶––––––"two"
+¶–––]
+¶}''')
+
+assert.fails(lambda: json.indent("!@#$%^& this is not json"), 'invalid character')
+---
diff --git a/starlark/testdata/list.star b/starlark/testdata/list.star
new file mode 100644
index 0000000..526a962
--- /dev/null
+++ b/starlark/testdata/list.star
@@ -0,0 +1,276 @@
+# Tests of Starlark 'list'
+
+load("assert.star", "assert", "freeze")
+
+# literals
+assert.eq([], [])
+assert.eq([1], [1])
+assert.eq([1], [1])
+assert.eq([1, 2], [1, 2])
+assert.ne([1, 2, 3], [1, 2, 4])
+
+# truth
+assert.true([0])
+assert.true(not [])
+
+# indexing, x[i]
+abc = list("abc".elems())
+assert.fails(lambda: abc[-4], "list index -4 out of range \\[-3:2]")
+assert.eq(abc[-3], "a")
+assert.eq(abc[-2], "b")
+assert.eq(abc[-1], "c")
+assert.eq(abc[0], "a")
+assert.eq(abc[1], "b")
+assert.eq(abc[2], "c")
+assert.fails(lambda: abc[3], "list index 3 out of range \\[-3:2]")
+
+# x[i] = ...
+x3 = [0, 1, 2]
+x3[1] = 2
+x3[2] += 3
+assert.eq(x3, [0, 2, 5])
+
+def f2():
+ x3[3] = 4
+
+assert.fails(f2, "out of range")
+freeze(x3)
+
+def f3():
+ x3[0] = 0
+
+assert.fails(f3, "cannot assign to element of frozen list")
+assert.fails(x3.clear, "cannot clear frozen list")
+
+# list + list
+assert.eq([1, 2, 3] + [3, 4, 5], [1, 2, 3, 3, 4, 5])
+assert.fails(lambda: [1, 2] + (3, 4), "unknown.*list \\+ tuple")
+assert.fails(lambda: (1, 2) + [3, 4], "unknown.*tuple \\+ list")
+
+# list * int, int * list
+assert.eq(abc * 0, [])
+assert.eq(abc * -1, [])
+assert.eq(abc * 1, abc)
+assert.eq(abc * 3, ["a", "b", "c", "a", "b", "c", "a", "b", "c"])
+assert.eq(0 * abc, [])
+assert.eq(-1 * abc, [])
+assert.eq(1 * abc, abc)
+assert.eq(3 * abc, ["a", "b", "c", "a", "b", "c", "a", "b", "c"])
+
+# list comprehensions
+assert.eq([2 * x for x in [1, 2, 3]], [2, 4, 6])
+assert.eq([2 * x for x in [1, 2, 3] if x > 1], [4, 6])
+assert.eq(
+ [(x, y) for x in [1, 2] for y in [3, 4]],
+ [(1, 3), (1, 4), (2, 3), (2, 4)],
+)
+assert.eq([(x, y) for x in [1, 2] if x == 2 for y in [3, 4]], [(2, 3), (2, 4)])
+assert.eq([2 * x for x in (1, 2, 3)], [2, 4, 6])
+assert.eq([x for x in "abc".elems()], ["a", "b", "c"])
+assert.eq([x for x in {"a": 1, "b": 2}], ["a", "b"])
+assert.eq([(y, x) for x, y in {1: 2, 3: 4}.items()], [(2, 1), (4, 3)])
+
+# corner cases of parsing:
+assert.eq([x for x in range(12) if x % 2 == 0 if x % 3 == 0], [0, 6])
+assert.eq([x for x in [1, 2] if lambda: None], [1, 2])
+assert.eq([x for x in [1, 2] if (lambda: 3 if True else 4)], [1, 2])
+
+# list function
+assert.eq(list(), [])
+assert.eq(list("ab".elems()), ["a", "b"])
+
+# A list comprehension defines a separate lexical block,
+# whether at top-level...
+a = [1, 2]
+b = [a for a in [3, 4]]
+assert.eq(a, [1, 2])
+assert.eq(b, [3, 4])
+
+# ...or local to a function.
+def listcompblock():
+ c = [1, 2]
+ d = [c for c in [3, 4]]
+ assert.eq(c, [1, 2])
+ assert.eq(d, [3, 4])
+
+listcompblock()
+
+# list.pop
+x4 = [1, 2, 3, 4, 5]
+assert.fails(lambda: x4.pop(-6), "index -6 out of range \\[-5:4]")
+assert.fails(lambda: x4.pop(6), "index 6 out of range \\[-5:4]")
+assert.eq(x4.pop(), 5)
+assert.eq(x4, [1, 2, 3, 4])
+assert.eq(x4.pop(1), 2)
+assert.eq(x4, [1, 3, 4])
+assert.eq(x4.pop(0), 1)
+assert.eq(x4, [3, 4])
+assert.eq(x4.pop(-2), 3)
+assert.eq(x4, [4])
+assert.eq(x4.pop(-1), 4)
+assert.eq(x4, [])
+
+# TODO(adonovan): test uses of list as sequence
+# (for loop, comprehension, library functions).
+
+# x += y for lists is equivalent to x.extend(y).
+# y may be a sequence.
+# TODO: Test that side-effects of 'x' occur only once.
+def list_extend():
+ a = [1, 2, 3]
+ b = a
+ a = a + [4] # creates a new list
+ assert.eq(a, [1, 2, 3, 4])
+ assert.eq(b, [1, 2, 3]) # b is unchanged
+
+ a = [1, 2, 3]
+ b = a
+ a += [4] # updates a (and thus b) in place
+ assert.eq(a, [1, 2, 3, 4])
+ assert.eq(b, [1, 2, 3, 4]) # alias observes the change
+
+ a = [1, 2, 3]
+ b = a
+ a.extend([4]) # updates existing list
+ assert.eq(a, [1, 2, 3, 4])
+ assert.eq(b, [1, 2, 3, 4]) # alias observes the change
+
+list_extend()
+
+# Unlike list.extend(iterable), list += iterable makes its LHS name local.
+a_list = []
+
+def f4():
+ a_list += [1] # binding use => a_list is a local var
+
+assert.fails(f4, "local variable a_list referenced before assignment")
+
+# list += <not iterable>
+def f5():
+ x = []
+ x += 1
+
+assert.fails(f5, "unknown binary op: list \\+ int")
+
+# frozen list += iterable
+def f6():
+ x = []
+ freeze(x)
+ x += [1]
+
+assert.fails(f6, "cannot apply \\+= to frozen list")
+
+# list += hasfields (hasfields is not iterable but defines list+hasfields)
+def f7():
+ x = []
+ x += hasfields()
+ return x
+
+assert.eq(f7(), 42) # weird, but exercises a corner case in list+=x.
+
+# append
+x5 = [1, 2, 3]
+x5.append(4)
+x5.append("abc")
+assert.eq(x5, [1, 2, 3, 4, "abc"])
+
+# extend
+x5a = [1, 2, 3]
+x5a.extend("abc".elems()) # string
+x5a.extend((True, False)) # tuple
+assert.eq(x5a, [1, 2, 3, "a", "b", "c", True, False])
+
+# list.insert
+def insert_at(index):
+ x = list(range(3))
+ x.insert(index, 42)
+ return x
+
+assert.eq(insert_at(-99), [42, 0, 1, 2])
+assert.eq(insert_at(-2), [0, 42, 1, 2])
+assert.eq(insert_at(-1), [0, 1, 42, 2])
+assert.eq(insert_at(0), [42, 0, 1, 2])
+assert.eq(insert_at(1), [0, 42, 1, 2])
+assert.eq(insert_at(2), [0, 1, 42, 2])
+assert.eq(insert_at(3), [0, 1, 2, 42])
+assert.eq(insert_at(4), [0, 1, 2, 42])
+
+# list.remove
+def remove(v):
+ x = [3, 1, 4, 1]
+ x.remove(v)
+ return x
+
+assert.eq(remove(3), [1, 4, 1])
+assert.eq(remove(1), [3, 4, 1])
+assert.eq(remove(4), [3, 1, 1])
+assert.fails(lambda: [3, 1, 4, 1].remove(42), "remove: element not found")
+
+# list.index
+bananas = list("bananas".elems())
+assert.eq(bananas.index("a"), 1) # bAnanas
+assert.fails(lambda: bananas.index("d"), "value not in list")
+
+# start
+assert.eq(bananas.index("a", -1000), 1) # bAnanas
+assert.eq(bananas.index("a", 0), 1) # bAnanas
+assert.eq(bananas.index("a", 1), 1) # bAnanas
+assert.eq(bananas.index("a", 2), 3) # banAnas
+assert.eq(bananas.index("a", 3), 3) # banAnas
+assert.eq(bananas.index("b", 0), 0) # Bananas
+assert.eq(bananas.index("n", -3), 4) # banaNas
+assert.fails(lambda: bananas.index("n", -2), "value not in list")
+assert.eq(bananas.index("s", -2), 6) # bananaS
+assert.fails(lambda: bananas.index("b", 1), "value not in list")
+
+# start, end
+assert.eq(bananas.index("s", -1000, 7), 6) # bananaS
+assert.fails(lambda: bananas.index("s", -1000, 6), "value not in list")
+assert.fails(lambda: bananas.index("d", -1000, 1000), "value not in list")
+
+# slicing, x[i:j:k]
+assert.eq(bananas[6::-2], list("snnb".elems()))
+assert.eq(bananas[5::-2], list("aaa".elems()))
+assert.eq(bananas[4::-2], list("nnb".elems()))
+assert.eq(bananas[99::-2], list("snnb".elems()))
+assert.eq(bananas[100::-2], list("snnb".elems()))
+# TODO(adonovan): many more tests
+
+# iterator invalidation
+def iterator1():
+ list = [0, 1, 2]
+ for x in list:
+ list[x] = 2 * x
+ return list
+
+assert.fails(iterator1, "assign to element.* during iteration")
+
+def iterator2():
+ list = [0, 1, 2]
+ for x in list:
+ list.remove(x)
+
+assert.fails(iterator2, "remove.*during iteration")
+
+def iterator3():
+ list = [0, 1, 2]
+ for x in list:
+ list.append(3)
+
+assert.fails(iterator3, "append.*during iteration")
+
+def iterator4():
+ list = [0, 1, 2]
+ for x in list:
+ list.extend([3, 4])
+
+assert.fails(iterator4, "extend.*during iteration")
+
+def iterator5():
+ def f(x):
+ x.append(4)
+
+ list = [1, 2, 3]
+ _ = [f(list) for x in list]
+
+assert.fails(iterator5, "append.*during iteration")
diff --git a/starlark/testdata/misc.star b/starlark/testdata/misc.star
new file mode 100644
index 0000000..e7e0c06
--- /dev/null
+++ b/starlark/testdata/misc.star
@@ -0,0 +1,139 @@
+# Miscellaneous tests of Starlark evaluation.
+# This is a "chunked" file: each "---" effectively starts a new file.
+
+# TODO(adonovan): move these tests into more appropriate files.
+# TODO(adonovan): test coverage:
+# - stmts: pass; if cond fail; += and failures;
+# for x fail; for x not iterable; for can't assign; for
+# error in loop body
+# - subassign fail
+# - x[i]=x fail in both operands; frozen x; list index not int; boundscheck
+# - x.f = ...
+# - failure in list expr [...]; tuple expr; dict expr (bad key)
+# - cond expr semantics; failures
+# - x[i] failures in both args; dict and iterator key and range checks;
+# unhandled operand types
+# - +: list/list, int/int, string/string, tuple+tuple, dict/dict;
+# - * and ** calls: various errors
+# - call of non-function
+# - slice x[ijk]
+# - comprehension: unhashable dict key;
+# scope of vars (local and toplevel); noniterable for clause
+# - unknown unary op
+# - ordering of values
+# - freeze, transitivity of its effect.
+# - add an application-defined type to the environment so we can test it.
+# - even more:
+#
+# eval
+# pass statement
+# assign to tuple l-value -- illegal
+# assign to list l-value -- illegal
+# assign to field
+# tuple + tuple
+# call with *args, **kwargs
+# slice with step
+# tuple slice
+# interpolate with %c, %%
+
+load("assert.star", "assert")
+
+# Ordered comparisons require values of the same type.
+assert.fails(lambda: None < None, "not impl")
+assert.fails(lambda: None < False, "not impl")
+assert.fails(lambda: False < list, "not impl")
+assert.fails(lambda: list < {}, "not impl")
+assert.fails(lambda: {} < (lambda: None), "not impl")
+assert.fails(lambda: (lambda: None) < 0, "not impl")
+assert.fails(lambda: 0 < [], "not impl")
+assert.fails(lambda: [] < "", "not impl")
+assert.fails(lambda: "" < (), "not impl")
+# Except int < float:
+assert.lt(1, 2.0)
+assert.lt(2.0, 3)
+
+---
+# cyclic data structures
+load("assert.star", "assert")
+
+cyclic = [1, 2, 3] # list cycle
+cyclic[1] = cyclic
+assert.eq(str(cyclic), "[1, [...], 3]")
+assert.fails(lambda: cyclic < cyclic, "maximum recursion")
+assert.fails(lambda: cyclic == cyclic, "maximum recursion")
+cyclic2 = [1, 2, 3]
+cyclic2[1] = cyclic2
+assert.fails(lambda: cyclic2 == cyclic, "maximum recursion")
+
+cyclic3 = [1, [2, 3]] # list-list cycle
+cyclic3[1][0] = cyclic3
+assert.eq(str(cyclic3), "[1, [[...], 3]]")
+cyclic4 = {"x": 1}
+cyclic4["x"] = cyclic4
+assert.eq(str(cyclic4), "{\"x\": {...}}")
+cyclic5 = [0, {"x": 1}] # list-dict cycle
+cyclic5[1]["x"] = cyclic5
+assert.eq(str(cyclic5), "[0, {\"x\": [...]}]")
+assert.eq(str(cyclic5), "[0, {\"x\": [...]}]")
+assert.fails(lambda: cyclic5 == cyclic5 ,"maximum recursion")
+cyclic6 = [0, {"x": 1}]
+cyclic6[1]["x"] = cyclic6
+assert.fails(lambda: cyclic5 == cyclic6, "maximum recursion")
+
+---
+# regression
+load("assert.star", "assert")
+
+# was a parse error:
+assert.eq(("ababab"[2:]).replace("b", "c"), "acac")
+assert.eq("ababab"[2:].replace("b", "c"), "acac")
+
+# test parsing of line continuation, at toplevel and in expression.
+three = 1 + \
+ 2
+assert.eq(1 + \
+ 2, three)
+
+---
+# A regression test for error position information.
+
+_ = {}.get(1, default=2) ### "get: unexpected keyword arguments"
+
+---
+# Load exposes explicitly declared globals from other modules.
+load('assert.star', 'assert', 'freeze')
+assert.eq(str(freeze), '<built-in function freeze>')
+
+---
+# Load does not expose pre-declared globals from other modules.
+# See github.com/google/skylark/issues/75.
+load('assert.star', 'assert', 'matches') ### "matches not found in module"
+
+---
+# Load does not expose universals accessible in other modules.
+load('assert.star', 'len') ### "len not found in module"
+
+
+---
+# Test plus folding optimization.
+load('assert.star', 'assert')
+
+s = "s"
+l = [4]
+t = (4,)
+
+assert.eq("a" + "b" + "c", "abc")
+assert.eq("a" + "b" + s + "c", "absc")
+assert.eq(() + (1,) + (2, 3), (1, 2, 3))
+assert.eq(() + (1,) + t + (2, 3), (1, 4, 2, 3))
+assert.eq([] + [1] + [2, 3], [1, 2, 3])
+assert.eq([] + [1] + l + [2, 3], [1, 4, 2, 3])
+
+assert.fails(lambda: "a" + "b" + 1 + "c", "unknown binary op: string \\+ int")
+assert.fails(lambda: () + () + 1 + (), "unknown binary op: tuple \\+ int")
+assert.fails(lambda: [] + [] + 1 + [], "unknown binary op: list \\+ int")
+
+
+
+---
+load('assert.star', 'froze') ### `name froze not found .*did you mean freeze`
diff --git a/starlark/testdata/module.star b/starlark/testdata/module.star
new file mode 100644
index 0000000..6aac2e2
--- /dev/null
+++ b/starlark/testdata/module.star
@@ -0,0 +1,17 @@
+# Tests of Module.
+
+load("assert.star", "assert")
+
+assert.eq(type(assert), "module")
+assert.eq(str(assert), '<module "assert">')
+assert.eq(dir(assert), ["contains", "eq", "fail", "fails", "lt", "ne", "true"])
+assert.fails(lambda : {assert: None}, "unhashable: module")
+
+def assignfield():
+ assert.foo = None
+
+assert.fails(assignfield, "can't assign to .foo field of module")
+
+# no such field
+assert.fails(lambda : assert.nonesuch, "module has no .nonesuch field or method$")
+assert.fails(lambda : assert.falls, "module has no .falls field or method .did you mean .fails\\?")
diff --git a/starlark/testdata/paths.star b/starlark/testdata/paths.star
new file mode 100644
index 0000000..cf8a3c4
--- /dev/null
+++ b/starlark/testdata/paths.star
@@ -0,0 +1,250 @@
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Skylib module containing file path manipulation functions.
+
+NOTE: The functions in this module currently only support paths with Unix-style
+path separators (forward slash, "/"); they do not handle Windows-style paths
+with backslash separators or drive letters.
+"""
+
+# This file is in the Bazel build language dialect of Starlark,
+# so declarations of 'fail' and 'struct' are required to make
+# it compile in the core language.
+def fail(msg):
+ print(msg)
+
+struct = dict
+
+def _basename(p):
+ """Returns the basename (i.e., the file portion) of a path.
+
+ Note that if `p` ends with a slash, this function returns an empty string.
+ This matches the behavior of Python's `os.path.basename`, but differs from
+ the Unix `basename` command (which would return the path segment preceding
+ the final slash).
+
+ Args:
+ p: The path whose basename should be returned.
+
+ Returns:
+ The basename of the path, which includes the extension.
+ """
+ return p.rpartition("/")[-1]
+
+def _dirname(p):
+ """Returns the dirname of a path.
+
+ The dirname is the portion of `p` up to but not including the file portion
+ (i.e., the basename). Any slashes immediately preceding the basename are not
+ included, unless omitting them would make the dirname empty.
+
+ Args:
+ p: The path whose dirname should be returned.
+
+ Returns:
+ The dirname of the path.
+ """
+ prefix, sep, _ = p.rpartition("/")
+ if not prefix:
+ return sep
+ else:
+ # If there are multiple consecutive slashes, strip them all out as Python's
+ # os.path.dirname does.
+ return prefix.rstrip("/")
+
+def _is_absolute(path):
+ """Returns `True` if `path` is an absolute path.
+
+ Args:
+ path: A path (which is a string).
+
+ Returns:
+ `True` if `path` is an absolute path.
+ """
+ return path.startswith("/") or (len(path) > 2 and path[1] == ":")
+
+def _join(path, *others):
+ """Joins one or more path components intelligently.
+
+ This function mimics the behavior of Python's `os.path.join` function on POSIX
+ platform. It returns the concatenation of `path` and any members of `others`,
+ inserting directory separators before each component except the first. The
+ separator is not inserted if the path up until that point is either empty or
+ already ends in a separator.
+
+ If any component is an absolute path, all previous components are discarded.
+
+ Args:
+ path: A path segment.
+ *others: Additional path segments.
+
+ Returns:
+ A string containing the joined paths.
+ """
+ result = path
+
+ for p in others:
+ if _is_absolute(p):
+ result = p
+ elif not result or result.endswith("/"):
+ result += p
+ else:
+ result += "/" + p
+
+ return result
+
+def _normalize(path):
+ """Normalizes a path, eliminating double slashes and other redundant segments.
+
+ This function mimics the behavior of Python's `os.path.normpath` function on
+ POSIX platforms; specifically:
+
+ - If the entire path is empty, "." is returned.
+ - All "." segments are removed, unless the path consists solely of a single
+ "." segment.
+ - Trailing slashes are removed, unless the path consists solely of slashes.
+ - ".." segments are removed as long as there are corresponding segments
+ earlier in the path to remove; otherwise, they are retained as leading ".."
+ segments.
+ - Single and double leading slashes are preserved, but three or more leading
+ slashes are collapsed into a single leading slash.
+ - Multiple adjacent internal slashes are collapsed into a single slash.
+
+ Args:
+ path: A path.
+
+ Returns:
+ The normalized path.
+ """
+ if not path:
+ return "."
+
+ if path.startswith("//") and not path.startswith("///"):
+ initial_slashes = 2
+ elif path.startswith("/"):
+ initial_slashes = 1
+ else:
+ initial_slashes = 0
+ is_relative = (initial_slashes == 0)
+
+ components = path.split("/")
+ new_components = []
+
+ for component in components:
+ if component in ("", "."):
+ continue
+ if component == "..":
+ if new_components and new_components[-1] != "..":
+ # Only pop the last segment if it isn't another "..".
+ new_components.pop()
+ elif is_relative:
+ # Preserve leading ".." segments for relative paths.
+ new_components.append(component)
+ else:
+ new_components.append(component)
+
+ path = "/".join(new_components)
+ if not is_relative:
+ path = ("/" * initial_slashes) + path
+
+ return path or "."
+
+def _relativize(path, start):
+ """Returns the portion of `path` that is relative to `start`.
+
+ Because we do not have access to the underlying file system, this
+ implementation differs slightly from Python's `os.path.relpath` in that it
+ will fail if `path` is not beneath `start` (rather than use parent segments to
+ walk up to the common file system root).
+
+ Relativizing paths that start with parent directory references only works if
+ the path both start with the same initial parent references.
+
+ Args:
+ path: The path to relativize.
+ start: The ancestor path against which to relativize.
+
+ Returns:
+ The portion of `path` that is relative to `start`.
+ """
+ segments = _normalize(path).split("/")
+ start_segments = _normalize(start).split("/")
+ if start_segments == ["."]:
+ start_segments = []
+ start_length = len(start_segments)
+
+ if (path.startswith("/") != start.startswith("/") or
+ len(segments) < start_length):
+ fail("Path '%s' is not beneath '%s'" % (path, start))
+
+ for ancestor_segment, segment in zip(start_segments, segments):
+ if ancestor_segment != segment:
+ fail("Path '%s' is not beneath '%s'" % (path, start))
+
+ length = len(segments) - start_length
+ result_segments = segments[-length:]
+ return "/".join(result_segments)
+
+def _replace_extension(p, new_extension):
+ """Replaces the extension of the file at the end of a path.
+
+ If the path has no extension, the new extension is added to it.
+
+ Args:
+ p: The path whose extension should be replaced.
+ new_extension: The new extension for the file. The new extension should
+ begin with a dot if you want the new filename to have one.
+
+ Returns:
+ The path with the extension replaced (or added, if it did not have one).
+ """
+ return _split_extension(p)[0] + new_extension
+
+def _split_extension(p):
+ """Splits the path `p` into a tuple containing the root and extension.
+
+ Leading periods on the basename are ignored, so
+ `path.split_extension(".bashrc")` returns `(".bashrc", "")`.
+
+ Args:
+ p: The path whose root and extension should be split.
+
+ Returns:
+ A tuple `(root, ext)` such that the root is the path without the file
+ extension, and `ext` is the file extension (which, if non-empty, contains
+ the leading dot). The returned tuple always satisfies the relationship
+ `root + ext == p`.
+ """
+ b = _basename(p)
+ last_dot_in_basename = b.rfind(".")
+
+ # If there is no dot or the only dot in the basename is at the front, then
+ # there is no extension.
+ if last_dot_in_basename <= 0:
+ return (p, "")
+
+ dot_distance_from_end = len(b) - last_dot_in_basename
+ return (p[:-dot_distance_from_end], p[-dot_distance_from_end:])
+
+paths = struct(
+ basename = _basename,
+ dirname = _dirname,
+ is_absolute = _is_absolute,
+ join = _join,
+ normalize = _normalize,
+ relativize = _relativize,
+ replace_extension = _replace_extension,
+ split_extension = _split_extension,
+)
diff --git a/starlark/testdata/recursion.star b/starlark/testdata/recursion.star
new file mode 100644
index 0000000..3368614
--- /dev/null
+++ b/starlark/testdata/recursion.star
@@ -0,0 +1,43 @@
+# Tests of Starlark recursion and while statement.
+
+# This is a "chunked" file: each "---" effectively starts a new file.
+
+# option:recursion
+
+load("assert.star", "assert")
+
+def sum(n):
+ r = 0
+ while n > 0:
+ r += n
+ n -= 1
+ return r
+
+def fib(n):
+ if n <= 1:
+ return 1
+ return fib(n-1) + fib(n-2)
+
+def while_break(n):
+ r = 0
+ while n > 0:
+ if n == 5:
+ break
+ r += n
+ n -= 1
+ return r
+
+def while_continue(n):
+ r = 0
+ while n > 0:
+ if n % 2 == 0:
+ n -= 1
+ continue
+ r += n
+ n -= 1
+ return r
+
+assert.eq(fib(5), 8)
+assert.eq(sum(5), 5+4+3+2+1)
+assert.eq(while_break(10), 40)
+assert.eq(while_continue(10), 25)
diff --git a/starlark/testdata/set.star b/starlark/testdata/set.star
new file mode 100644
index 0000000..bca4144
--- /dev/null
+++ b/starlark/testdata/set.star
@@ -0,0 +1,118 @@
+# Tests of Starlark 'set'
+# option:set
+
+# Sets are not a standard part of Starlark, so the features
+# tested in this file must be enabled in the application by setting
+# resolve.AllowSet. (All sets are created by calls to the 'set'
+# built-in or derived from operations on existing sets.)
+# The semantics are subject to change as the spec evolves.
+
+# TODO(adonovan): support set mutation:
+# - del set[k]
+# - set.remove
+# - set.update
+# - set.clear
+# - set += iterable, perhaps?
+# Test iterator invalidation.
+
+load("assert.star", "assert")
+
+# literals
+# Parser does not currently support {1, 2, 3}.
+# TODO(adonovan): add test to syntax/testdata/errors.star.
+
+# set comprehensions
+# Parser does not currently support {x for x in y}.
+# See syntax/testdata/errors.star.
+
+# set constructor
+assert.eq(type(set()), "set")
+assert.eq(list(set()), [])
+assert.eq(type(set([1, 3, 2, 3])), "set")
+assert.eq(list(set([1, 3, 2, 3])), [1, 3, 2])
+assert.eq(type(set("hello".elems())), "set")
+assert.eq(list(set("hello".elems())), ["h", "e", "l", "o"])
+assert.eq(list(set(range(3))), [0, 1, 2])
+assert.fails(lambda : set(1), "got int, want iterable")
+assert.fails(lambda : set(1, 2, 3), "got 3 arguments")
+assert.fails(lambda : set([1, 2, {}]), "unhashable type: dict")
+
+# truth
+assert.true(not set())
+assert.true(set([False]))
+assert.true(set([1, 2, 3]))
+
+x = set([1, 2, 3])
+y = set([3, 4, 5])
+
+# set + any is not defined
+assert.fails(lambda : x + y, "unknown.*: set \\+ set")
+
+# set | set (use resolve.AllowBitwise to enable it)
+assert.eq(list(set("a".elems()) | set("b".elems())), ["a", "b"])
+assert.eq(list(set("ab".elems()) | set("bc".elems())), ["a", "b", "c"])
+assert.fails(lambda : set() | [], "unknown binary op: set | list")
+assert.eq(type(x | y), "set")
+assert.eq(list(x | y), [1, 2, 3, 4, 5])
+assert.eq(list(x | set([5, 1])), [1, 2, 3, 5])
+assert.eq(list(x | set((6, 5, 4))), [1, 2, 3, 6, 5, 4])
+
+# set.union (allows any iterable for right operand)
+assert.eq(list(set("a".elems()).union("b".elems())), ["a", "b"])
+assert.eq(list(set("ab".elems()).union("bc".elems())), ["a", "b", "c"])
+assert.eq(set().union([]), set())
+assert.eq(type(x.union(y)), "set")
+assert.eq(list(x.union(y)), [1, 2, 3, 4, 5])
+assert.eq(list(x.union([5, 1])), [1, 2, 3, 5])
+assert.eq(list(x.union((6, 5, 4))), [1, 2, 3, 6, 5, 4])
+assert.fails(lambda : x.union([1, 2, {}]), "unhashable type: dict")
+
+# intersection, set & set (use resolve.AllowBitwise to enable it)
+assert.eq(list(set("a".elems()) & set("b".elems())), [])
+assert.eq(list(set("ab".elems()) & set("bc".elems())), ["b"])
+
+# symmetric difference, set ^ set (use resolve.AllowBitwise to enable it)
+assert.eq(set([1, 2, 3]) ^ set([4, 5, 3]), set([1, 2, 4, 5]))
+
+def test_set_augmented_assign():
+ x = set([1, 2, 3])
+ x &= set([2, 3])
+ assert.eq(x, set([2, 3]))
+ x |= set([1])
+ assert.eq(x, set([1, 2, 3]))
+ x ^= set([4, 5, 3])
+ assert.eq(x, set([1, 2, 4, 5]))
+
+test_set_augmented_assign()
+
+# len
+assert.eq(len(x), 3)
+assert.eq(len(y), 3)
+assert.eq(len(x | y), 5)
+
+# str
+assert.eq(str(set([1])), "set([1])")
+assert.eq(str(set([2, 3])), "set([2, 3])")
+assert.eq(str(set([3, 2])), "set([3, 2])")
+
+# comparison
+assert.eq(x, x)
+assert.eq(y, y)
+assert.true(x != y)
+assert.eq(set([1, 2, 3]), set([3, 2, 1]))
+assert.fails(lambda : x < y, "set < set not implemented")
+
+# iteration
+assert.true(type([elem for elem in x]), "list")
+assert.true(list([elem for elem in x]), [1, 2, 3])
+
+def iter():
+ list = []
+ for elem in x:
+ list.append(elem)
+ return list
+
+assert.eq(iter(), [1, 2, 3])
+
+# sets are not indexable
+assert.fails(lambda : x[0], "unhandled.*operation")
diff --git a/starlark/testdata/string.star b/starlark/testdata/string.star
new file mode 100644
index 0000000..b317d1a
--- /dev/null
+++ b/starlark/testdata/string.star
@@ -0,0 +1,472 @@
+# Tests of Starlark 'string'
+# option:set
+
+load("assert.star", "assert")
+
+# raw string literals:
+assert.eq(r"a\bc", "a\\bc")
+
+# truth
+assert.true("abc")
+assert.true(chr(0))
+assert.true(not "")
+
+# str + str
+assert.eq("a" + "b" + "c", "abc")
+
+# str * int, int * str
+assert.eq("abc" * 0, "")
+assert.eq("abc" * -1, "")
+assert.eq("abc" * 1, "abc")
+assert.eq("abc" * 5, "abcabcabcabcabc")
+assert.eq(0 * "abc", "")
+assert.eq(-1 * "abc", "")
+assert.eq(1 * "abc", "abc")
+assert.eq(5 * "abc", "abcabcabcabcabc")
+assert.fails(lambda: 1.0 * "abc", "unknown.*float \\* str")
+assert.fails(lambda: "abc" * (1000000 * 1000000), "repeat count 1000000000000 too large")
+assert.fails(lambda: "abc" * 1000000 * 1000000, "excessive repeat \\(3000000 \\* 1000000 elements")
+
+# len
+assert.eq(len("Hello, 世界!"), 14)
+assert.eq(len("𐐷"), 4) # U+10437 has a 4-byte UTF-8 encoding (and a 2-code UTF-16 encoding)
+
+# chr & ord
+assert.eq(chr(65), "A") # 1-byte UTF-8 encoding
+assert.eq(chr(1049), "Й") # 2-byte UTF-8 encoding
+assert.eq(chr(0x1F63F), "😿") # 4-byte UTF-8 encoding
+assert.fails(lambda: chr(-1), "Unicode code point -1 out of range \\(<0\\)")
+assert.fails(lambda: chr(0x110000), "Unicode code point U\\+110000 out of range \\(>0x10FFFF\\)")
+assert.eq(ord("A"), 0x41)
+assert.eq(ord("Й"), 0x419)
+assert.eq(ord("世"), 0x4e16)
+assert.eq(ord("😿"), 0x1F63F)
+assert.eq(ord("Й"[1:]), 0xFFFD) # = Unicode replacement character
+assert.fails(lambda: ord("abc"), "string encodes 3 Unicode code points, want 1")
+assert.fails(lambda: ord(""), "string encodes 0 Unicode code points, want 1")
+assert.fails(lambda: ord("😿"[1:]), "string encodes 3 Unicode code points, want 1") # 3 x 0xFFFD
+
+# string.codepoint_ords
+assert.eq(type("abcЙ😿".codepoint_ords()), "string.codepoints")
+assert.eq(str("abcЙ😿".codepoint_ords()), '"abcЙ😿".codepoint_ords()')
+assert.eq(list("abcЙ😿".codepoint_ords()), [97, 98, 99, 1049, 128575])
+assert.eq(list(("A" + "😿Z"[1:]).codepoint_ords()), [ord("A"), 0xFFFD, 0xFFFD, 0xFFFD, ord("Z")])
+assert.eq(list("".codepoint_ords()), [])
+assert.fails(lambda: "abcЙ😿".codepoint_ords()[2], "unhandled index") # not indexable
+assert.fails(lambda: len("abcЙ😿".codepoint_ords()), "no len") # unknown length
+
+# string.codepoints
+assert.eq(type("abcЙ😿".codepoints()), "string.codepoints")
+assert.eq(str("abcЙ😿".codepoints()), '"abcЙ😿".codepoints()')
+assert.eq(list("abcЙ😿".codepoints()), ["a", "b", "c", "Й", "😿"])
+assert.eq(list(("A" + "😿Z"[1:]).codepoints()), ["A", "�", "�", "�", "Z"])
+assert.eq(list("".codepoints()), [])
+assert.fails(lambda: "abcЙ😿".codepoints()[2], "unhandled index") # not indexable
+assert.fails(lambda: len("abcЙ😿".codepoints()), "no len") # unknown length
+
+# string.elem_ords
+assert.eq(type("abcЙ😿".elem_ords()), "string.elems")
+assert.eq(str("abcЙ😿".elem_ords()), '"abcЙ😿".elem_ords()')
+assert.eq(list("abcЙ😿".elem_ords()), [97, 98, 99, 208, 153, 240, 159, 152, 191])
+assert.eq(list(("A" + "😿Z"[1:]).elem_ords()), [65, 159, 152, 191, 90])
+assert.eq(list("".elem_ords()), [])
+assert.eq("abcЙ😿".elem_ords()[2], 99) # indexable
+assert.eq(len("abcЙ😿".elem_ords()), 9) # known length
+
+# string.elems (1-byte substrings, which are invalid text)
+assert.eq(type("abcЙ😿".elems()), "string.elems")
+assert.eq(str("abcЙ😿".elems()), '"abcЙ😿".elems()')
+assert.eq(
+ repr(list("abcЙ😿".elems())),
+ r'["a", "b", "c", "\xd0", "\x99", "\xf0", "\x9f", "\x98", "\xbf"]',
+)
+assert.eq(
+ repr(list(("A" + "😿Z"[1:]).elems())),
+ r'["A", "\x9f", "\x98", "\xbf", "Z"]',
+)
+assert.eq(list("".elems()), [])
+assert.eq("abcЙ😿".elems()[2], "c") # indexable
+assert.eq(len("abcЙ😿".elems()), 9) # known length
+
+# indexing, x[i]
+assert.eq("Hello, 世界!"[0], "H")
+assert.eq(repr("Hello, 世界!"[7]), r'"\xe4"') # (invalid text)
+assert.eq("Hello, 世界!"[13], "!")
+assert.fails(lambda: "abc"[-4], "out of range")
+assert.eq("abc"[-3], "a")
+assert.eq("abc"[-2], "b")
+assert.eq("abc"[-1], "c")
+assert.eq("abc"[0], "a")
+assert.eq("abc"[1], "b")
+assert.eq("abc"[2], "c")
+assert.fails(lambda: "abc"[4], "out of range")
+
+# x[i] = ...
+def f():
+ "abc"[1] = "B"
+
+assert.fails(f, "string.*does not support.*assignment")
+
+# slicing, x[i:j]
+assert.eq("abc"[:], "abc")
+assert.eq("abc"[-4:], "abc")
+assert.eq("abc"[-3:], "abc")
+assert.eq("abc"[-2:], "bc")
+assert.eq("abc"[-1:], "c")
+assert.eq("abc"[0:], "abc")
+assert.eq("abc"[1:], "bc")
+assert.eq("abc"[2:], "c")
+assert.eq("abc"[3:], "")
+assert.eq("abc"[4:], "")
+assert.eq("abc"[:-4], "")
+assert.eq("abc"[:-3], "")
+assert.eq("abc"[:-2], "a")
+assert.eq("abc"[:-1], "ab")
+assert.eq("abc"[:0], "")
+assert.eq("abc"[:1], "a")
+assert.eq("abc"[:2], "ab")
+assert.eq("abc"[:3], "abc")
+assert.eq("abc"[:4], "abc")
+assert.eq("abc"[1:2], "b")
+assert.eq("abc"[2:1], "")
+assert.eq(repr("😿"[:1]), r'"\xf0"') # (invalid text)
+
+# non-unit strides
+assert.eq("abcd"[0:4:1], "abcd")
+assert.eq("abcd"[::2], "ac")
+assert.eq("abcd"[1::2], "bd")
+assert.eq("abcd"[4:0:-1], "dcb")
+assert.eq("banana"[7::-2], "aaa")
+assert.eq("banana"[6::-2], "aaa")
+assert.eq("banana"[5::-2], "aaa")
+assert.eq("banana"[4::-2], "nnb")
+assert.eq("banana"[::-1], "ananab")
+assert.eq("banana"[None:None:-2], "aaa")
+assert.fails(lambda: "banana"[1.0::], "invalid start index: got float, want int")
+assert.fails(lambda: "banana"[:"":], "invalid end index: got string, want int")
+assert.fails(lambda: "banana"[:"":True], "invalid slice step: got bool, want int")
+
+# in, not in
+assert.true("oo" in "food")
+assert.true("ox" not in "food")
+assert.true("" in "food")
+assert.true("" in "")
+assert.fails(lambda: 1 in "", "requires string as left operand")
+assert.fails(lambda: "" in 1, "unknown binary op: string in int")
+
+# ==, !=
+assert.eq("hello", "he" + "llo")
+assert.ne("hello", "Hello")
+
+# hash must follow java.lang.String.hashCode.
+wanthash = {
+ "": 0,
+ "\0" * 100: 0,
+ "hello": 99162322,
+ "world": 113318802,
+ "Hello, 世界!": 417292677,
+}
+gothash = {s: hash(s) for s in wanthash}
+assert.eq(gothash, wanthash)
+
+# TODO(adonovan): ordered comparisons
+
+# string % tuple formatting
+assert.eq("A %d %x Z" % (123, 456), "A 123 1c8 Z")
+assert.eq("A %(foo)d %(bar)s Z" % {"foo": 123, "bar": "hi"}, "A 123 hi Z")
+assert.eq("%s %r" % ("hi", "hi"), 'hi "hi"') # TODO(adonovan): use ''-quotation
+assert.eq("%%d %d" % 1, "%d 1")
+assert.fails(lambda: "%d %d" % 1, "not enough arguments for format string")
+assert.fails(lambda: "%d %d" % (1, 2, 3), "too many arguments for format string")
+assert.fails(lambda: "" % 1, "too many arguments for format string")
+
+# %c
+assert.eq("%c" % 65, "A")
+assert.eq("%c" % 0x3b1, "α")
+assert.eq("%c" % "A", "A")
+assert.eq("%c" % "α", "α")
+assert.fails(lambda: "%c" % "abc", "requires a single-character string")
+assert.fails(lambda: "%c" % "", "requires a single-character string")
+assert.fails(lambda: "%c" % 65.0, "requires int or single-character string")
+assert.fails(lambda: "%c" % 10000000, "requires a valid Unicode code point")
+assert.fails(lambda: "%c" % -1, "requires a valid Unicode code point")
+# TODO(adonovan): more tests
+
+# str.format
+assert.eq("a{}b".format(123), "a123b")
+assert.eq("a{}b{}c{}d{}".format(1, 2, 3, 4), "a1b2c3d4")
+assert.eq("a{{b".format(), "a{b")
+assert.eq("a}}b".format(), "a}b")
+assert.eq("a{{b}}c".format(), "a{b}c")
+assert.eq("a{x}b{y}c{}".format(1, x = 2, y = 3), "a2b3c1")
+assert.fails(lambda: "a{z}b".format(x = 1), "keyword z not found")
+assert.fails(lambda: "{-1}".format(1), "keyword -1 not found")
+assert.fails(lambda: "{-0}".format(1), "keyword -0 not found")
+assert.fails(lambda: "{+0}".format(1), "keyword \\+0 not found")
+assert.fails(lambda: "{+1}".format(1), "keyword \\+1 not found") # starlark-go/issues/114
+assert.eq("{0000000000001}".format(0, 1), "1")
+assert.eq("{012}".format(*range(100)), "12") # decimal, despite leading zeros
+assert.fails(lambda: "{0,1} and {1}".format(1, 2), "keyword 0,1 not found")
+assert.fails(lambda: "a{123}b".format(), "tuple index out of range")
+assert.fails(lambda: "a{}b{}c".format(1), "tuple index out of range")
+assert.eq("a{010}b".format(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10), "a10b") # index is decimal
+assert.fails(lambda: "a{}b{1}c".format(1, 2), "cannot switch from automatic field numbering to manual")
+assert.eq("a{!s}c".format("b"), "abc")
+assert.eq("a{!r}c".format("b"), r'a"b"c')
+assert.eq("a{x!r}c".format(x = "b"), r'a"b"c')
+assert.fails(lambda: "{x!}".format(x = 1), "unknown conversion")
+assert.fails(lambda: "{x!:}".format(x = 1), "unknown conversion")
+assert.fails(lambda: "{a.b}".format(1), "syntax x.y is not supported")
+assert.fails(lambda: "{a[0]}".format(1), "syntax a\\[i\\] is not supported")
+assert.fails(lambda: "{ {} }".format(1), "nested replacement fields not supported")
+assert.fails(lambda: "{{}".format(1), "single '}' in format")
+assert.fails(lambda: "{}}".format(1), "single '}' in format")
+assert.fails(lambda: "}}{".format(1), "unmatched '{' in format")
+assert.fails(lambda: "}{{".format(1), "single '}' in format")
+
+# str.split, str.rsplit
+assert.eq("a.b.c.d".split("."), ["a", "b", "c", "d"])
+assert.eq("a.b.c.d".rsplit("."), ["a", "b", "c", "d"])
+assert.eq("a.b.c.d".split(".", -1), ["a", "b", "c", "d"])
+assert.eq("a.b.c.d".rsplit(".", -1), ["a", "b", "c", "d"])
+assert.eq("a.b.c.d".split(".", 0), ["a.b.c.d"])
+assert.eq("a.b.c.d".rsplit(".", 0), ["a.b.c.d"])
+assert.eq("a.b.c.d".split(".", 1), ["a", "b.c.d"])
+assert.eq("a.b.c.d".rsplit(".", 1), ["a.b.c", "d"])
+assert.eq("a.b.c.d".split(".", 2), ["a", "b", "c.d"])
+assert.eq("a.b.c.d".rsplit(".", 2), ["a.b", "c", "d"])
+assert.eq(" ".split("."), [" "])
+assert.eq(" ".rsplit("."), [" "])
+
+# {,r}split on white space:
+assert.eq(" a bc\n def \t ghi".split(), ["a", "bc", "def", "ghi"])
+assert.eq(" a bc\n def \t ghi".split(None), ["a", "bc", "def", "ghi"])
+assert.eq(" a bc\n def \t ghi".split(None, 0), ["a bc\n def \t ghi"])
+assert.eq(" a bc\n def \t ghi".rsplit(None, 0), [" a bc\n def \t ghi"])
+assert.eq(" a bc\n def \t ghi".split(None, 1), ["a", "bc\n def \t ghi"])
+assert.eq(" a bc\n def \t ghi".rsplit(None, 1), [" a bc\n def", "ghi"])
+assert.eq(" a bc\n def \t ghi".split(None, 2), ["a", "bc", "def \t ghi"])
+assert.eq(" a bc\n def \t ghi".rsplit(None, 2), [" a bc", "def", "ghi"])
+assert.eq(" a bc\n def \t ghi".split(None, 3), ["a", "bc", "def", "ghi"])
+assert.eq(" a bc\n def \t ghi".rsplit(None, 3), [" a", "bc", "def", "ghi"])
+assert.eq(" a bc\n def \t ghi".split(None, 4), ["a", "bc", "def", "ghi"])
+assert.eq(" a bc\n def \t ghi".rsplit(None, 4), ["a", "bc", "def", "ghi"])
+assert.eq(" a bc\n def \t ghi".rsplit(None, 5), ["a", "bc", "def", "ghi"])
+
+assert.eq(" a bc\n def \t ghi ".split(None, 0), ["a bc\n def \t ghi "])
+assert.eq(" a bc\n def \t ghi ".rsplit(None, 0), [" a bc\n def \t ghi"])
+assert.eq(" a bc\n def \t ghi ".split(None, 1), ["a", "bc\n def \t ghi "])
+assert.eq(" a bc\n def \t ghi ".rsplit(None, 1), [" a bc\n def", "ghi"])
+
+# Observe the algorithmic difference when splitting on spaces versus other delimiters.
+assert.eq("--aa--bb--cc--".split("-", 0), ["--aa--bb--cc--"]) # contrast this
+assert.eq(" aa bb cc ".split(None, 0), ["aa bb cc "]) # with this
+assert.eq("--aa--bb--cc--".rsplit("-", 0), ["--aa--bb--cc--"]) # ditto this
+assert.eq(" aa bb cc ".rsplit(None, 0), [" aa bb cc"]) # and this
+
+#
+assert.eq("--aa--bb--cc--".split("-", 1), ["", "-aa--bb--cc--"])
+assert.eq("--aa--bb--cc--".rsplit("-", 1), ["--aa--bb--cc-", ""])
+assert.eq(" aa bb cc ".split(None, 1), ["aa", "bb cc "])
+assert.eq(" aa bb cc ".rsplit(None, 1), [" aa bb", "cc"])
+
+#
+assert.eq("--aa--bb--cc--".split("-", -1), ["", "", "aa", "", "bb", "", "cc", "", ""])
+assert.eq("--aa--bb--cc--".rsplit("-", -1), ["", "", "aa", "", "bb", "", "cc", "", ""])
+assert.eq(" aa bb cc ".split(None, -1), ["aa", "bb", "cc"])
+assert.eq(" aa bb cc ".rsplit(None, -1), ["aa", "bb", "cc"])
+assert.eq(" ".split(None), [])
+assert.eq(" ".rsplit(None), [])
+
+assert.eq("localhost:80".rsplit(":", 1)[-1], "80")
+
+# str.splitlines
+assert.eq("\nabc\ndef".splitlines(), ["", "abc", "def"])
+assert.eq("\nabc\ndef".splitlines(True), ["\n", "abc\n", "def"])
+assert.eq("\nabc\ndef\n".splitlines(), ["", "abc", "def"])
+assert.eq("\nabc\ndef\n".splitlines(True), ["\n", "abc\n", "def\n"])
+assert.eq("".splitlines(), []) #
+assert.eq("".splitlines(True), []) #
+assert.eq("a".splitlines(), ["a"])
+assert.eq("a".splitlines(True), ["a"])
+assert.eq("\n".splitlines(), [""])
+assert.eq("\n".splitlines(True), ["\n"])
+assert.eq("a\n".splitlines(), ["a"])
+assert.eq("a\n".splitlines(True), ["a\n"])
+assert.eq("a\n\nb".splitlines(), ["a", "", "b"])
+assert.eq("a\n\nb".splitlines(True), ["a\n", "\n", "b"])
+assert.eq("a\nb\nc".splitlines(), ["a", "b", "c"])
+assert.eq("a\nb\nc".splitlines(True), ["a\n", "b\n", "c"])
+assert.eq("a\nb\nc\n".splitlines(), ["a", "b", "c"])
+assert.eq("a\nb\nc\n".splitlines(True), ["a\n", "b\n", "c\n"])
+
+# str.{,l,r}strip
+assert.eq(" \tfoo\n ".strip(), "foo")
+assert.eq(" \tfoo\n ".lstrip(), "foo\n ")
+assert.eq(" \tfoo\n ".rstrip(), " \tfoo")
+assert.eq(" \tfoo\n ".strip(""), "foo")
+assert.eq(" \tfoo\n ".lstrip(""), "foo\n ")
+assert.eq(" \tfoo\n ".rstrip(""), " \tfoo")
+assert.eq("blah.h".strip("b.h"), "la")
+assert.eq("blah.h".lstrip("b.h"), "lah.h")
+assert.eq("blah.h".rstrip("b.h"), "bla")
+
+# str.count
+assert.eq("banana".count("a"), 3)
+assert.eq("banana".count("a", 2), 2)
+assert.eq("banana".count("a", -4, -2), 1)
+assert.eq("banana".count("a", 1, 4), 2)
+assert.eq("banana".count("a", 0, -100), 0)
+
+# str.{starts,ends}with
+assert.true("foo".endswith("oo"))
+assert.true(not "foo".endswith("x"))
+assert.true("foo".startswith("fo"))
+assert.true(not "foo".startswith("x"))
+assert.fails(lambda: "foo".startswith(1), "got int.*want string")
+
+#
+assert.true("abc".startswith(("a", "A")))
+assert.true("ABC".startswith(("a", "A")))
+assert.true(not "ABC".startswith(("b", "B")))
+assert.fails(lambda: "123".startswith((1, 2)), "got int, for element 0")
+assert.fails(lambda: "123".startswith(["3"]), "got list")
+
+#
+assert.true("abc".endswith(("c", "C")))
+assert.true("ABC".endswith(("c", "C")))
+assert.true(not "ABC".endswith(("b", "B")))
+assert.fails(lambda: "123".endswith((1, 2)), "got int, for element 0")
+assert.fails(lambda: "123".endswith(["3"]), "got list")
+
+# start/end
+assert.true("abc".startswith("bc", 1))
+assert.true(not "abc".startswith("b", 999))
+assert.true("abc".endswith("ab", None, -1))
+assert.true(not "abc".endswith("b", None, -999))
+
+# str.replace
+assert.eq("banana".replace("a", "o", 1), "bonana")
+assert.eq("banana".replace("a", "o"), "bonono")
+# TODO(adonovan): more tests
+
+# str.{,r}find
+assert.eq("foofoo".find("oo"), 1)
+assert.eq("foofoo".find("ox"), -1)
+assert.eq("foofoo".find("oo", 2), 4)
+assert.eq("foofoo".rfind("oo"), 4)
+assert.eq("foofoo".rfind("ox"), -1)
+assert.eq("foofoo".rfind("oo", 1, 4), 1)
+assert.eq("foofoo".find(""), 0)
+assert.eq("foofoo".rfind(""), 6)
+
+# str.{,r}partition
+assert.eq("foo/bar/wiz".partition("/"), ("foo", "/", "bar/wiz"))
+assert.eq("foo/bar/wiz".rpartition("/"), ("foo/bar", "/", "wiz"))
+assert.eq("foo/bar/wiz".partition("."), ("foo/bar/wiz", "", ""))
+assert.eq("foo/bar/wiz".rpartition("."), ("", "", "foo/bar/wiz"))
+assert.fails(lambda: "foo/bar/wiz".partition(""), "empty separator")
+assert.fails(lambda: "foo/bar/wiz".rpartition(""), "empty separator")
+
+assert.eq("?".join(["foo", "a/b/c.go".rpartition("/")[0]]), "foo?a/b")
+
+# str.is{alpha,...}
+def test_predicates():
+ predicates = ["alnum", "alpha", "digit", "lower", "space", "title", "upper"]
+ table = {
+ "Hello, World!": "title",
+ "hello, world!": "lower",
+ "base64": "alnum lower",
+ "HAL-9000": "upper",
+ "Catch-22": "title",
+ "": "",
+ "\n\t\r": "space",
+ "abc": "alnum alpha lower",
+ "ABC": "alnum alpha upper",
+ "123": "alnum digit",
+ "DŽLJ": "alnum alpha upper",
+ "DžLj": "alnum alpha",
+ "Dž Lj": "title",
+ "džlj": "alnum alpha lower",
+ }
+ for str, want in table.items():
+ got = " ".join([name for name in predicates if getattr(str, "is" + name)()])
+ if got != want:
+ assert.fail("%r matched [%s], want [%s]" % (str, got, want))
+
+test_predicates()
+
+# Strings are not iterable.
+# ok
+assert.eq(len("abc"), 3) # len
+assert.true("a" in "abc") # str in str
+assert.eq("abc"[1], "b") # indexing
+
+# not ok
+def for_string():
+ for x in "abc":
+ pass
+
+def args(*args):
+ return args
+
+assert.fails(lambda: args(*"abc"), "must be iterable, not string") # varargs
+assert.fails(lambda: list("abc"), "got string, want iterable") # list(str)
+assert.fails(lambda: tuple("abc"), "got string, want iterable") # tuple(str)
+assert.fails(lambda: set("abc"), "got string, want iterable") # set(str)
+assert.fails(lambda: set() | "abc", "unknown binary op: set | string") # set union
+assert.fails(lambda: enumerate("ab"), "got string, want iterable") # enumerate
+assert.fails(lambda: sorted("abc"), "got string, want iterable") # sorted
+assert.fails(lambda: [].extend("bc"), "got string, want iterable") # list.extend
+assert.fails(lambda: ",".join("abc"), "got string, want iterable") # string.join
+assert.fails(lambda: dict(["ab"]), "not iterable .*string") # dict
+assert.fails(for_string, "string value is not iterable") # for loop
+assert.fails(lambda: [x for x in "abc"], "string value is not iterable") # comprehension
+assert.fails(lambda: all("abc"), "got string, want iterable") # all
+assert.fails(lambda: any("abc"), "got string, want iterable") # any
+assert.fails(lambda: reversed("abc"), "got string, want iterable") # reversed
+assert.fails(lambda: zip("ab", "cd"), "not iterable: string") # zip
+
+# str.join
+assert.eq(",".join([]), "")
+assert.eq(",".join(["a"]), "a")
+assert.eq(",".join(["a", "b"]), "a,b")
+assert.eq(",".join(["a", "b", "c"]), "a,b,c")
+assert.eq(",".join(("a", "b", "c")), "a,b,c")
+assert.eq("".join(("a", "b", "c")), "abc")
+assert.fails(lambda: "".join(None), "got NoneType, want iterable")
+assert.fails(lambda: "".join(["one", 2]), "join: in list, want string, got int")
+
+# TODO(adonovan): tests for: {,r}index
+
+# str.capitalize
+assert.eq("hElLo, WoRlD!".capitalize(), "Hello, world!")
+assert.eq("por qué".capitalize(), "Por qué")
+assert.eq("¿Por qué?".capitalize(), "¿por qué?")
+
+# str.lower
+assert.eq("hElLo, WoRlD!".lower(), "hello, world!")
+assert.eq("por qué".lower(), "por qué")
+assert.eq("¿Por qué?".lower(), "¿por qué?")
+assert.eq("LJUBOVIĆ".lower(), "ljubović")
+assert.true("dženan ljubović".islower())
+
+# str.upper
+assert.eq("hElLo, WoRlD!".upper(), "HELLO, WORLD!")
+assert.eq("por qué".upper(), "POR QUÉ")
+assert.eq("¿Por qué?".upper(), "¿POR QUÉ?")
+assert.eq("ljubović".upper(), "LJUBOVIĆ")
+assert.true("DŽENAN LJUBOVIĆ".isupper())
+
+# str.title
+assert.eq("hElLo, WoRlD!".title(), "Hello, World!")
+assert.eq("por qué".title(), "Por Qué")
+assert.eq("¿Por qué?".title(), "¿Por Qué?")
+assert.eq("ljubović".title(), "Ljubović")
+assert.true("Dženan Ljubović".istitle())
+assert.true(not "DŽenan LJubović".istitle())
+
+# method spell check
+assert.fails(lambda: "".starts_with, "no .starts_with field.*did you mean .startswith")
+assert.fails(lambda: "".StartsWith, "no .StartsWith field.*did you mean .startswith")
+assert.fails(lambda: "".fin, "no .fin field.*.did you mean .find")
diff --git a/starlark/testdata/tuple.star b/starlark/testdata/tuple.star
new file mode 100644
index 0000000..f306133
--- /dev/null
+++ b/starlark/testdata/tuple.star
@@ -0,0 +1,55 @@
+# Tests of Starlark 'tuple'
+
+load("assert.star", "assert")
+
+# literal
+assert.eq((), ())
+assert.eq((1), 1)
+assert.eq((1,), (1,))
+assert.ne((1), (1,))
+assert.eq((1, 2), (1, 2))
+assert.eq((1, 2, 3, 4, 5), (1, 2, 3, 4, 5))
+assert.ne((1, 2, 3), (1, 2, 4))
+
+# truth
+assert.true((False,))
+assert.true((False, False))
+assert.true(not ())
+
+# indexing, x[i]
+assert.eq(("a", "b")[0], "a")
+assert.eq(("a", "b")[1], "b")
+
+# slicing, x[i:j]
+assert.eq("abcd"[0:4:1], "abcd")
+assert.eq("abcd"[::2], "ac")
+assert.eq("abcd"[1::2], "bd")
+assert.eq("abcd"[4:0:-1], "dcb")
+banana = tuple("banana".elems())
+assert.eq(banana[7::-2], tuple("aaa".elems()))
+assert.eq(banana[6::-2], tuple("aaa".elems()))
+assert.eq(banana[5::-2], tuple("aaa".elems()))
+assert.eq(banana[4::-2], tuple("nnb".elems()))
+
+# tuple
+assert.eq(tuple(), ())
+assert.eq(tuple("abc".elems()), ("a", "b", "c"))
+assert.eq(tuple(["a", "b", "c"]), ("a", "b", "c"))
+assert.eq(tuple([1]), (1,))
+assert.fails(lambda: tuple(1), "got int, want iterable")
+
+# tuple * int, int * tuple
+abc = tuple("abc".elems())
+assert.eq(abc * 0, ())
+assert.eq(abc * -1, ())
+assert.eq(abc * 1, abc)
+assert.eq(abc * 3, ("a", "b", "c", "a", "b", "c", "a", "b", "c"))
+assert.eq(0 * abc, ())
+assert.eq(-1 * abc, ())
+assert.eq(1 * abc, abc)
+assert.eq(3 * abc, ("a", "b", "c", "a", "b", "c", "a", "b", "c"))
+assert.fails(lambda: abc * (1000000 * 1000000), "repeat count 1000000000000 too large")
+assert.fails(lambda: abc * 1000000 * 1000000, "excessive repeat \\(3000000 \\* 1000000 elements")
+
+# TODO(adonovan): test use of tuple as sequence
+# (for loop, comprehension, library functions).