aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoralandonovan <adonovan@google.com>2020-11-11 12:03:41 -0500
committerGitHub <noreply@github.com>2020-11-11 12:03:41 -0500
commit3b7e02ecde8b80808c06c8284ce9f85c101fcebd (patch)
tree1168e8456ab49c55ff2f47c9b98a7a0feff69204
parentdff0ae5b48201537496c261571cf1f08725ec804 (diff)
downloadstarlark-go-3b7e02ecde8b80808c06c8284ce9f85c101fcebd.tar.gz
starlark: bring floating-point into spec compliance (#313)
This change makes go.starlark.net's floating-point implementation match the proposed spec (see https://github.com/bazelbuild/starlark/pull/119), and thus much more closely match the behavior of the Java implementation. The major changes are: - Float values are totally ordered; NaN compares greater than +Inf. - The string form of a finite float value always contains an exponent or a decimal point, so they are self-evidently not int values. - Operations that would cause a large integer to become rounded to an infinite float are now an error. The resolve.AllowFloat boolean, and the corresponding -float command-line flag, now have no effect. Floating point support is always enabled.
-rw-r--r--cmd/starlark/starlark.go2
-rw-r--r--go.mod2
-rw-r--r--go.sum4
-rw-r--r--resolve/resolve.go11
-rw-r--r--resolve/resolve_test.go3
-rw-r--r--resolve/testdata/resolve.star7
-rw-r--r--starlark/eval.go106
-rw-r--r--starlark/eval_test.go1
-rw-r--r--starlark/int.go10
-rw-r--r--starlark/library.go41
-rw-r--r--starlark/testdata/assign.star7
-rw-r--r--starlark/testdata/bool.star8
-rw-r--r--starlark/testdata/builtins.star2
-rw-r--r--starlark/testdata/float.star355
-rw-r--r--starlark/testdata/int.star202
-rw-r--r--starlark/testdata/json.star3
-rw-r--r--starlark/testdata/misc.star2
-rw-r--r--starlark/testdata/string.star288
-rw-r--r--starlark/value.go100
-rw-r--r--starlarkstruct/struct_test.go1
20 files changed, 755 insertions, 400 deletions
diff --git a/cmd/starlark/starlark.go b/cmd/starlark/starlark.go
index af4f71f..31555f1 100644
--- a/cmd/starlark/starlark.go
+++ b/cmd/starlark/starlark.go
@@ -35,7 +35,7 @@ func init() {
flag.BoolVar(&compile.Disassemble, "disassemble", compile.Disassemble, "show disassembly during compilation of each function")
// non-standard dialect flags
- flag.BoolVar(&resolve.AllowFloat, "float", resolve.AllowFloat, "allow floating-point numbers")
+ flag.BoolVar(&resolve.AllowFloat, "float", resolve.AllowFloat, "obsolete; no effect")
flag.BoolVar(&resolve.AllowSet, "set", resolve.AllowSet, "allow set data type")
flag.BoolVar(&resolve.AllowLambda, "lambda", resolve.AllowLambda, "allow lambda expressions")
flag.BoolVar(&resolve.AllowNestedDef, "nesteddef", resolve.AllowNestedDef, "allow nested def statements")
diff --git a/go.mod b/go.mod
index 87e6d96..e6aa6ba 100644
--- a/go.mod
+++ b/go.mod
@@ -6,5 +6,5 @@ require (
github.com/chzyer/logex v1.1.10 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
- golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect
+ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 // indirect
)
diff --git a/go.sum b/go.sum
index a969512..7d77852 100644
--- a/go.sum
+++ b/go.sum
@@ -4,5 +4,5 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5O
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
-golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/resolve/resolve.go b/resolve/resolve.go
index e4b6752..26d74b4 100644
--- a/resolve/resolve.go
+++ b/resolve/resolve.go
@@ -100,7 +100,7 @@ const doesnt = "this Starlark dialect does not "
var (
AllowNestedDef = false // allow def statements within function bodies
AllowLambda = false // allow lambda expressions
- AllowFloat = false // allow floating point literals, the 'float' built-in, and x / y
+ AllowFloat = false // obsolete; no effect
AllowSet = false // allow the 'set' built-in
AllowGlobalReassign = false // allow reassignment to top-level names; also, allow if/for/while at top-level
AllowRecursion = false // allow while statements and recursive functions
@@ -418,9 +418,6 @@ func (r *resolver) useToplevel(use use) (bind *Binding) {
r.predeclared[id.Name] = bind // save it
} else if r.isUniversal(id.Name) {
// use of universal name
- if !AllowFloat && id.Name == "float" {
- r.errorf(id.NamePos, doesnt+"support floating point")
- }
if !AllowSet && id.Name == "set" {
r.errorf(id.NamePos, doesnt+"support sets")
}
@@ -636,9 +633,6 @@ func (r *resolver) expr(e syntax.Expr) {
r.use(e)
case *syntax.Literal:
- if !AllowFloat && e.Token == syntax.FLOAT {
- r.errorf(e.TokenPos, doesnt+"support floating point")
- }
case *syntax.ListExpr:
for _, x := range e.List {
@@ -713,9 +707,6 @@ func (r *resolver) expr(e syntax.Expr) {
r.expr(e.X)
case *syntax.BinaryExpr:
- if !AllowFloat && e.Op == syntax.SLASH {
- r.errorf(e.OpPos, doesnt+"support floating point (use //)")
- }
r.expr(e.X)
r.expr(e.Y)
diff --git a/resolve/resolve_test.go b/resolve/resolve_test.go
index 090dbc5..cb2c672 100644
--- a/resolve/resolve_test.go
+++ b/resolve/resolve_test.go
@@ -15,7 +15,6 @@ import (
)
func setOptions(src string) {
- resolve.AllowFloat = option(src, "float")
resolve.AllowGlobalReassign = option(src, "globalreassign")
resolve.AllowLambda = option(src, "lambda")
resolve.AllowNestedDef = option(src, "nesteddef")
@@ -38,7 +37,7 @@ func TestResolve(t *testing.T) {
continue
}
- // A chunk may set options by containing e.g. "option:float".
+ // A chunk may set options by containing e.g. "option:nesteddef".
setOptions(chunk.Source)
if err := resolve.File(f, isPredeclared, isUniversal); err != nil {
diff --git a/resolve/testdata/resolve.star b/resolve/testdata/resolve.star
index 4f1a270..8399bf3 100644
--- a/resolve/testdata/resolve.star
+++ b/resolve/testdata/resolve.star
@@ -307,12 +307,7 @@ def h(kwargs, a, **kwargs): pass ### "duplicate parameter: kwargs"
def i(*x, **x): pass ### "duplicate parameter: x"
---
-# No floating point
-a = float("3.141") ### `dialect does not support floating point`
-b = 1 / 2 ### `dialect does not support floating point \(use //\)`
-c = 3.141 ### `dialect does not support floating point`
----
-# Floating point support (option:float)
+# Floating-point support is now standard.
a = float("3.141")
b = 1 / 2
c = 3.141
diff --git a/starlark/eval.go b/starlark/eval.go
index c17b8b7..633fc30 100644
--- a/starlark/eval.go
+++ b/starlark/eval.go
@@ -713,14 +713,22 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
case Int:
return x.Add(y), nil
case Float:
- return x.Float() + y, nil
+ xf, err := x.finiteFloat()
+ if err != nil {
+ return nil, err
+ }
+ return xf + y, nil
}
case Float:
switch y := y.(type) {
case Float:
return x + y, nil
case Int:
- return x + y.Float(), nil
+ yf, err := y.finiteFloat()
+ if err != nil {
+ return nil, err
+ }
+ return x + yf, nil
}
case *List:
if y, ok := y.(*List); ok {
@@ -745,14 +753,22 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
case Int:
return x.Sub(y), nil
case Float:
- return x.Float() - y, nil
+ xf, err := x.finiteFloat()
+ if err != nil {
+ return nil, err
+ }
+ return xf - y, nil
}
case Float:
switch y := y.(type) {
case Float:
return x - y, nil
case Int:
- return x - y.Float(), nil
+ yf, err := y.finiteFloat()
+ if err != nil {
+ return nil, err
+ }
+ return x - yf, nil
}
}
@@ -763,7 +779,11 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
case Int:
return x.Mul(y), nil
case Float:
- return x.Float() * y, nil
+ xf, err := x.finiteFloat()
+ if err != nil {
+ return nil, err
+ }
+ return xf * y, nil
case String:
return stringRepeat(y, x)
case *List:
@@ -780,7 +800,11 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
case Float:
return x * y, nil
case Int:
- return x * y.Float(), nil
+ yf, err := y.finiteFloat()
+ if err != nil {
+ return nil, err
+ }
+ return x * yf, nil
}
case String:
if y, ok := y.(Int); ok {
@@ -804,30 +828,40 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
case syntax.SLASH:
switch x := x.(type) {
case Int:
+ xf, err := x.finiteFloat()
+ if err != nil {
+ return nil, err
+ }
switch y := y.(type) {
case Int:
- yf := y.Float()
+ yf, err := y.finiteFloat()
+ if err != nil {
+ return nil, err
+ }
if yf == 0.0 {
- return nil, fmt.Errorf("real division by zero")
+ return nil, fmt.Errorf("floating-point division by zero")
}
- return x.Float() / yf, nil
+ return xf / yf, nil
case Float:
if y == 0.0 {
- return nil, fmt.Errorf("real division by zero")
+ return nil, fmt.Errorf("floating-point division by zero")
}
- return x.Float() / y, nil
+ return xf / y, nil
}
case Float:
switch y := y.(type) {
case Float:
if y == 0.0 {
- return nil, fmt.Errorf("real division by zero")
+ return nil, fmt.Errorf("floating-point division by zero")
}
return x / y, nil
case Int:
- yf := y.Float()
+ yf, err := y.finiteFloat()
+ if err != nil {
+ return nil, err
+ }
if yf == 0.0 {
- return nil, fmt.Errorf("real division by zero")
+ return nil, fmt.Errorf("floating-point division by zero")
}
return x / yf, nil
}
@@ -843,10 +877,14 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
}
return x.Div(y), nil
case Float:
+ xf, err := x.finiteFloat()
+ if err != nil {
+ return nil, err
+ }
if y == 0.0 {
return nil, fmt.Errorf("floored division by zero")
}
- return floor((x.Float() / y)), nil
+ return floor(xf / y), nil
}
case Float:
switch y := y.(type) {
@@ -856,7 +894,10 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
}
return floor(x / y), nil
case Int:
- yf := y.Float()
+ yf, err := y.finiteFloat()
+ if err != nil {
+ return nil, err
+ }
if yf == 0.0 {
return nil, fmt.Errorf("floored division by zero")
}
@@ -874,23 +915,31 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
}
return x.Mod(y), nil
case Float:
+ xf, err := x.finiteFloat()
+ if err != nil {
+ return nil, err
+ }
if y == 0 {
- return nil, fmt.Errorf("float modulo by zero")
+ return nil, fmt.Errorf("floating-point modulo by zero")
}
- return x.Float().Mod(y), nil
+ return xf.Mod(y), nil
}
case Float:
switch y := y.(type) {
case Float:
if y == 0.0 {
- return nil, fmt.Errorf("float modulo by zero")
+ return nil, fmt.Errorf("floating-point modulo by zero")
}
return x.Mod(y), nil
case Int:
if y.Sign() == 0 {
- return nil, fmt.Errorf("float modulo by zero")
+ return nil, fmt.Errorf("floating-point modulo by zero")
}
- return x.Mod(y.Float()), nil
+ yf, err := y.finiteFloat()
+ if err != nil {
+ return nil, err
+ }
+ return x.Mod(yf), nil
}
case String:
return interpolate(string(x), y)
@@ -1497,20 +1546,7 @@ func interpolate(format string, x Value) (Value, error) {
if !ok {
return nil, fmt.Errorf("%%%c format requires float, not %s", c, arg.Type())
}
- switch c {
- case 'e':
- fmt.Fprintf(buf, "%e", f)
- case 'f':
- fmt.Fprintf(buf, "%f", f)
- case 'g':
- fmt.Fprintf(buf, "%g", f)
- case 'E':
- fmt.Fprintf(buf, "%E", f)
- case 'F':
- fmt.Fprintf(buf, "%F", f)
- case 'G':
- fmt.Fprintf(buf, "%G", f)
- }
+ Float(f).format(buf, c)
case 'c':
switch arg := arg.(type) {
case Int:
diff --git a/starlark/eval_test.go b/starlark/eval_test.go
index a57ccad..13876a7 100644
--- a/starlark/eval_test.go
+++ b/starlark/eval_test.go
@@ -24,7 +24,6 @@ import (
// A test may enable non-standard options by containing (e.g.) "option:recursion".
func setOptions(src string) {
- resolve.AllowFloat = option(src, "float")
resolve.AllowGlobalReassign = option(src, "globalreassign")
resolve.LoadBindsGlobally = option(src, "loadbindsglobally")
resolve.AllowLambda = option(src, "lambda")
diff --git a/starlark/int.go b/starlark/int.go
index c40532a..397be86 100644
--- a/starlark/int.go
+++ b/starlark/int.go
@@ -193,6 +193,16 @@ func (i Int) Float() Float {
return Float(iSmall)
}
+// finiteFloat returns the finite float value nearest i,
+// or an error if the magnitude is too large.
+func (i Int) finiteFloat() (Float, error) {
+ f := i.Float()
+ if math.IsInf(float64(f), 0) {
+ return 0, fmt.Errorf("int too large to convert to float")
+ }
+ return f, nil
+}
+
func (x Int) Sign() int {
xSmall, xBig := x.get()
if xBig != nil {
diff --git a/starlark/library.go b/starlark/library.go
index 9036877..e9e2f94 100644
--- a/starlark/library.go
+++ b/starlark/library.go
@@ -12,6 +12,7 @@ package starlark
import (
"errors"
"fmt"
+ "math"
"math/big"
"os"
"sort"
@@ -46,7 +47,7 @@ func init() {
"dir": NewBuiltin("dir", dir),
"enumerate": NewBuiltin("enumerate", enumerate),
"fail": NewBuiltin("fail", fail),
- "float": NewBuiltin("float", float), // requires resolve.AllowFloat
+ "float": NewBuiltin("float", float),
"getattr": NewBuiltin("getattr", getattr),
"hasattr": NewBuiltin("hasattr", hasattr),
"hash": NewBuiltin("hash", hash),
@@ -330,13 +331,39 @@ func float(thread *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error
return Float(0.0), nil
}
case Int:
- return x.Float(), nil
+ return x.finiteFloat()
case Float:
return x, nil
case String:
- f, err := strconv.ParseFloat(string(x), 64)
+ if x == "" {
+ return nil, fmt.Errorf("float: empty string")
+ }
+ // +/- NaN or Inf or Infinity (case insensitive)?
+ s := string(x)
+ switch x[len(x)-1] {
+ case 'y', 'Y':
+ if strings.EqualFold(s, "infinity") || strings.EqualFold(s, "+infinity") {
+ return inf, nil
+ } else if strings.EqualFold(s, "-infinity") {
+ return neginf, nil
+ }
+ case 'f', 'F':
+ if strings.EqualFold(s, "inf") || strings.EqualFold(s, "+inf") {
+ return inf, nil
+ } else if strings.EqualFold(s, "-inf") {
+ return neginf, nil
+ }
+ case 'n', 'N':
+ if strings.EqualFold(s, "nan") || strings.EqualFold(s, "+nan") || strings.EqualFold(s, "-nan") {
+ return nan, nil
+ }
+ }
+ f, err := strconv.ParseFloat(s, 64)
+ if math.IsInf(f, 0) {
+ return nil, fmt.Errorf("floating-point number too large")
+ }
if err != nil {
- return nil, nameErr(b, err)
+ return nil, fmt.Errorf("invalid float literal: %s", s)
}
return Float(f), nil
default:
@@ -344,6 +371,12 @@ func float(thread *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error
}
}
+var (
+ inf = Float(math.Inf(+1))
+ neginf = Float(math.Inf(-1))
+ nan = Float(math.NaN())
+)
+
// https://github.com/google/starlark-go/blob/master/doc/spec.md#getattr
func getattr(thread *Thread, b *Builtin, args Tuple, kwargs []Tuple) (Value, error) {
var object, dflt Value
diff --git a/starlark/testdata/assign.star b/starlark/testdata/assign.star
index 38108f9..64d60fb 100644
--- a/starlark/testdata/assign.star
+++ b/starlark/testdata/assign.star
@@ -232,14 +232,11 @@ assert.fails(lambda: tuple, "global variable tuple referenced before assignment"
tuple = ()
---
-# option:float option:set
-# Same as above, but set and float are dialect-specific;
+# option:set
+# Same as above, but set is dialect-specific;
# we shouldn't notice any difference.
load("assert.star", "assert")
-float = 1.0
-assert.eq(type(float), "float")
-
set = [1, 2, 3]
assert.eq(type(set), "list")
diff --git a/starlark/testdata/bool.star b/starlark/testdata/bool.star
index 78ef825..6c084a3 100644
--- a/starlark/testdata/bool.star
+++ b/starlark/testdata/bool.star
@@ -1,5 +1,4 @@
# Tests of Starlark 'bool'
-# option:float
load("assert.star", "assert")
@@ -9,6 +8,13 @@ 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("")],
diff --git a/starlark/testdata/builtins.star b/starlark/testdata/builtins.star
index 2385c16..8dcee46 100644
--- a/starlark/testdata/builtins.star
+++ b/starlark/testdata/builtins.star
@@ -1,5 +1,5 @@
# Tests of Starlark built-in functions
-# option:float option:set
+# option:set
load("assert.star", "assert")
diff --git a/starlark/testdata/float.star b/starlark/testdata/float.star
index 4d7a269..b4df38d 100644
--- a/starlark/testdata/float.star
+++ b/starlark/testdata/float.star
@@ -1,5 +1,5 @@
# Tests of Starlark 'float'
-# option:float option:set
+# option:set
load("assert.star", "assert")
@@ -7,6 +7,20 @@ load("assert.star", "assert")
# - 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")
@@ -16,10 +30,117 @@ assert.eq(type(1.234e10), "float")
assert.eq(type(1.234e+10), "float")
assert.eq(type(1.234e-10), "float")
-# truth
-assert.true(123.0)
-assert.true(-1.0)
-assert.true(not 0.0)
+# 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)
@@ -56,9 +177,9 @@ 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, "real division by zero")
-assert.fails(lambda: 1.0 / 0.0, "real division by zero")
-assert.fails(lambda: 1 / 0.0, "real division by zero")
+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)
@@ -90,64 +211,69 @@ 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, "float modulo by zero")
-assert.fails(lambda: 1.0 % 0.0, "float modulo by zero")
-assert.fails(lambda: 1 % 0.0, "float modulo by zero")
+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")
-# nan
-nan = float("NaN")
-def isnan(x): return x != x
-assert.true(nan != nan)
-assert.true(not (nan == nan))
-
-# ordered comparisons with NaN
-assert.true(not nan < nan)
-assert.true(not nan > nan)
-assert.true(not nan <= nan)
-assert.true(not nan >= nan)
-assert.true(not nan == nan) # use explicit operator, not assert.ne
-assert.true(nan != nan)
-assert.true(not nan < 0)
-assert.true(not nan > 0)
-assert.true(not [nan] < [nan])
-assert.true(not [nan] > [nan])
-
-# Even a value containing NaN is not equal to itself.
-nanlist = [nan]
-assert.true(not nanlist < nanlist)
-assert.true(not nanlist > nanlist)
-assert.ne(nanlist, nanlist)
-
-# Since NaN values never compare equal,
-# a dict may have any number of NaN keys.
-nandict = {nan: 1, nan: 2, nan: 3}
-assert.eq(len(nandict), 3)
-assert.eq(str(nandict), "{NaN: 1, NaN: 2, NaN: 3}")
-assert.true(nan not in nandict)
-assert.eq(nandict.get(nan, None), None)
+# -- 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
-inf = float("Inf")
-neginf = float("-Inf")
-assert.true(isnan(+inf / +inf))
-assert.true(isnan(+inf / -inf))
-assert.true(isnan(-inf / +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)
-assert.eq(float(int("2" + "0" * 308)), inf) # 2e308 is too large to represent as a float
-assert.eq(float(int("-2" + "0" * 308)), -inf)
# 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
@@ -178,25 +304,75 @@ assert.eq(int(1e100), int("10000000000000000159028911097599180468360808563945281
assert.fails(lambda: int(inf), "cannot convert.*infinity")
assert.fails(lambda: int(nan), "cannot convert.*NaN")
-# float conversion
+# -- 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.eq(float("1.1"), 1.1)
-assert.fails(lambda: float("1.1abc"), "invalid syntax")
-assert.fails(lambda: float("1e100.0"), "invalid syntax")
-assert.fails(lambda: float("1e1000"), "out of range")
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.true(isnan(float("NaN")))
-assert.fails(lambda: float("+NaN"), "invalid syntax")
-assert.fails(lambda: float("-NaN"), "invalid syntax")
+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.
@@ -214,16 +390,67 @@ def checkhash():
checkhash()
# string formatting
-assert.eq("%s" % 123.45e67, "1.2345e+69")
-assert.eq("%r" % 123.45e67, "1.2345e+69")
-assert.eq("%e" % 123.45e67, "1.234500e+69")
-assert.eq("%f" % 123.45e67, "1234500000000000033987094856609369647752433474509923447907937257783296.000000")
-assert.eq("%g" % 123.45e67, "1.2345e+69")
+
+# %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("%f" % 123, "123.000000")
-assert.eq("%g" % 123, "123")
+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
diff --git a/starlark/testdata/int.star b/starlark/testdata/int.star
index c987fd1..a2e6716 100644
--- a/starlark/testdata/int.star
+++ b/starlark/testdata/int.star
@@ -1,5 +1,4 @@
# Tests of Starlark 'int'
-# option:float
load("assert.star", "assert")
@@ -21,72 +20,72 @@ 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)
+ 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)
+ 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)
+ 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)
+ 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()
@@ -94,56 +93,58 @@ compound()
# 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.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(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) # 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", 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", 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("+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) # 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) # 1*7*7 + 2*7 + 3
assert.eq(int("-123", 7), -66)
assert.eq(int("12", 16), 18)
assert.eq(int("-12", 16), -18)
@@ -160,6 +161,7 @@ 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")
+
# int from string, auto detect base
assert.eq(int("123", 0), 123)
assert.eq(int("+123", 0), +123)
@@ -170,10 +172,12 @@ 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") # 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")
@@ -185,14 +189,14 @@ assert.fails(lambda: int("0x-4", 16), "invalid literal with base 16: 0x-4")
# 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(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 | 0 ^ 1, 1) # check | and ^ operators precedence
assert.eq(~1, -2)
-assert.eq(~-2, 1)
+assert.eq(~(-2), 1)
assert.eq(~0, -1)
assert.eq(1 << 2, 4)
assert.eq(2 >> 1, 1)
@@ -202,30 +206,30 @@ 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)
+ 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(maxint64 + 1), "9223372036854775808")
assert.eq(str(minint64), "-9223372036854775808")
-assert.eq(str(minint64-1), "-9223372036854775809")
+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(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")
@@ -235,11 +239,11 @@ 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(" ".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.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
index 615d70b..37c9070 100644
--- a/starlark/testdata/json.star
+++ b/starlark/testdata/json.star
@@ -1,5 +1,4 @@
# Tests of json module.
-# option:float
load("assert.star", "assert")
load("json.star", "json")
@@ -28,7 +27,7 @@ assert.eq(json.encode("\x80"), '"\\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(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
diff --git a/starlark/testdata/misc.star b/starlark/testdata/misc.star
index 0cb973a..84ef84c 100644
--- a/starlark/testdata/misc.star
+++ b/starlark/testdata/misc.star
@@ -36,8 +36,6 @@
# tuple slice
# interpolate with %c, %%
-# option:float
-
load("assert.star", "assert")
# Ordered comparisons require values of the same type.
diff --git a/starlark/testdata/string.star b/starlark/testdata/string.star
index 1d78dd7..859f645 100644
--- a/starlark/testdata/string.star
+++ b/starlark/testdata/string.star
@@ -1,10 +1,10 @@
# Tests of Starlark 'string'
-# option:float option:set
+# option:set
load("assert.star", "assert")
# raw string literals:
-assert.eq(r'a\bc', "a\\bc")
+assert.eq(r"a\bc", "a\\bc")
# truth
assert.true("abc")
@@ -12,7 +12,7 @@ assert.true(chr(0))
assert.true(not "")
# str + str
-assert.eq("a"+"b"+"c", "abc")
+assert.eq("a" + "b" + "c", "abc")
# str * int, int * str
assert.eq("abc" * 0, "")
@@ -24,26 +24,26 @@ 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")
+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)
+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.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"), 65)
assert.eq(ord("Й"), 1049)
assert.eq(ord("😿"), 0x1F63F)
-assert.eq(ord("Й"[1:]), 0xFFFD) # = Unicode replacement character
+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
+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()), "codepoints")
@@ -62,17 +62,21 @@ assert.eq(list("".codepoints()), [])
# string.elem_ords
assert.eq(type("abcЙ😿".elem_ords()), "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("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()), [])
# string.elems
assert.eq(type("abcЙ😿".elems()), "elems")
assert.eq(str("abcЙ😿".elems()), '"abcЙ😿".elems()')
-assert.eq(list("abcЙ😿".elems()),
- ["a", "b", "c", "\xd0", "\x99", "\xf0", "\x9f", "\x98", "\xbf"])
-assert.eq(list(("A" + "😿Z"[1:]).elems()),
- ["A", "\x9f", "\x98", "\xbf", "Z"])
+assert.eq(
+ list("abcЙ😿".elems()),
+ ["a", "b", "c", "\xd0", "\x99", "\xf0", "\x9f", "\x98", "\xbf"],
+)
+assert.eq(
+ list(("A" + "😿Z"[1:]).elems()),
+ ["A", "\x9f", "\x98", "\xbf", "Z"],
+)
assert.eq(list("".elems()), [])
# indexing, x[i]
@@ -90,7 +94,10 @@ assert.fails(lambda: "abc"[4], "out of range")
# x[i] = ...
x2 = "abc"
-def f(): x2[1] = 'B'
+
+def f():
+ x2[1] = "B"
+
assert.fails(f, "string.*does not support.*assignment")
# slicing, x[i:j]
@@ -115,6 +122,7 @@ assert.eq("abc"[:3], "abc")
assert.eq("abc"[:4], "abc")
assert.eq("abc"[1:2], "b")
assert.eq("abc"[2:1], "")
+
# non-unit strides
assert.eq("abcd"[0:4:1], "abcd")
assert.eq("abcd"[::2], "ac")
@@ -139,7 +147,7 @@ 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.eq("hello", "he" + "llo")
assert.ne("hello", "Hello")
# hash must follow java.lang.String.hashCode.
@@ -157,12 +165,13 @@ assert.eq(gothash, wanthash)
# 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("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, "α")
@@ -181,31 +190,31 @@ 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.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.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.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.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")
+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"])
@@ -242,44 +251,46 @@ 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("-", 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('--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("--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'])
+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")
@@ -305,23 +316,26 @@ 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".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')
+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))
+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")
@@ -346,71 +360,77 @@ 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')
+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))
+ 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
+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)
+ 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(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
+
# The Java implementation does not correctly reject the following cases:
# (See Google Issue b/34385336)
-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
+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')
+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
diff --git a/starlark/value.go b/starlark/value.go
index eb0e84e..8d1b88a 100644
--- a/starlark/value.go
+++ b/starlark/value.go
@@ -385,10 +385,47 @@ func (x Bool) CompareSameType(op syntax.Token, y_ Value, depth int) (bool, error
// Float is the type of a Starlark float.
type Float float64
-func (f Float) String() string { return strconv.FormatFloat(float64(f), 'g', 6, 64) }
-func (f Float) Type() string { return "float" }
-func (f Float) Freeze() {} // immutable
-func (f Float) Truth() Bool { return f != 0.0 }
+func (f Float) String() string {
+ var buf strings.Builder
+ f.format(&buf, 'g')
+ return buf.String()
+}
+
+func (f Float) format(buf *strings.Builder, conv byte) {
+ ff := float64(f)
+ if !isFinite(ff) {
+ if math.IsInf(ff, +1) {
+ buf.WriteString("+inf")
+ } else if math.IsInf(ff, -1) {
+ buf.WriteString("-inf")
+ } else {
+ buf.WriteString("nan")
+ }
+ return
+ }
+
+ // %g is the default format used by str.
+ // It uses the minimum precision to avoid ambiguity,
+ // and always includes a '.' or an 'e' so that the value
+ // is self-evidently a float, not an int.
+ if conv == 'g' || conv == 'G' {
+ s := strconv.FormatFloat(ff, conv, -1, 64)
+ buf.WriteString(s)
+ // Ensure result always has a decimal point if no exponent.
+ // "123" -> "123.0"
+ if strings.IndexByte(s, conv-'g'+'e') < 0 && strings.IndexByte(s, '.') < 0 {
+ buf.WriteString(".0")
+ }
+ return
+ }
+
+ // %[eEfF] use 6-digit precision
+ buf.WriteString(strconv.FormatFloat(ff, conv, 6, 64))
+}
+
+func (f Float) Type() string { return "float" }
+func (f Float) Freeze() {} // immutable
+func (f Float) Truth() Bool { return f != 0.0 }
func (f Float) Hash() (uint32, error) {
// Equal float and int values must yield the same hash.
// TODO(adonovan): opt: if f is non-integral, and thus not equal
@@ -409,27 +446,34 @@ func isFinite(f float64) bool {
func (x Float) CompareSameType(op syntax.Token, y_ Value, depth int) (bool, error) {
y := y_.(Float)
- switch op {
- case syntax.EQL:
- return x == y, nil
- case syntax.NEQ:
- return x != y, nil
- case syntax.LE:
- return x <= y, nil
- case syntax.LT:
- return x < y, nil
- case syntax.GE:
- return x >= y, nil
- case syntax.GT:
- return x > y, nil
+ return threeway(op, floatCmp(x, y)), nil
+}
+
+// floatCmp performs a three-valued comparison on floats,
+// which are totally ordered with NaN > +Inf.
+func floatCmp(x, y Float) int {
+ if x > y {
+ return +1
+ } else if x < y {
+ return -1
+ } else if x == y {
+ return 0
}
- panic(op)
+
+ // At least one operand is NaN.
+ if x == x {
+ return -1 // y is NaN
+ } else if y == y {
+ return +1 // x is NaN
+ }
+ return 0 // both NaN
}
func (f Float) rational() *big.Rat { return new(big.Rat).SetFloat64(float64(f)) }
// AsFloat returns the float64 value closest to x.
-// The f result is undefined if x is not a float or int.
+// The f result is undefined if x is not a float or Int.
+// The result may be infinite if x is a very large Int.
func AsFloat(x Value) (f float64, ok bool) {
switch x := x.(type) {
case Float:
@@ -1199,11 +1243,10 @@ func CompareDepth(op syntax.Token, x, y Value, depth int) (bool, error) {
switch x := x.(type) {
case Int:
if y, ok := y.(Float); ok {
- if y != y {
- return false, nil // y is NaN
- }
var cmp int
- if !math.IsInf(float64(y), 0) {
+ if y != y {
+ cmp = -1 // y is NaN
+ } else if !math.IsInf(float64(y), 0) {
cmp = x.rational().Cmp(y.rational()) // y is finite
} else if y > 0 {
cmp = -1 // y is +Inf
@@ -1214,16 +1257,15 @@ func CompareDepth(op syntax.Token, x, y Value, depth int) (bool, error) {
}
case Float:
if y, ok := y.(Int); ok {
- if x != x {
- return false, nil // x is NaN
- }
var cmp int
- if !math.IsInf(float64(x), 0) {
+ if x != x {
+ cmp = +1 // x is NaN
+ } else if !math.IsInf(float64(x), 0) {
cmp = x.rational().Cmp(y.rational()) // x is finite
} else if x > 0 {
- cmp = -1 // x is +Inf
+ cmp = +1 // x is +Inf
} else {
- cmp = +1 // x is -Inf
+ cmp = -1 // x is -Inf
}
return threeway(op, cmp), nil
}
diff --git a/starlarkstruct/struct_test.go b/starlarkstruct/struct_test.go
index 8e6a93d..5de7020 100644
--- a/starlarkstruct/struct_test.go
+++ b/starlarkstruct/struct_test.go
@@ -19,7 +19,6 @@ func init() {
// The tests make extensive use of these not-yet-standard features.
resolve.AllowLambda = true
resolve.AllowNestedDef = true
- resolve.AllowFloat = true
resolve.AllowSet = true
}