diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 13:15:26 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 13:15:26 +0000 |
commit | 82539ad8d59729fb45b0bb0edda8f2bddb719eb1 (patch) | |
tree | 58f0b58e6f44f0e04d4a6373132cf426fa835fa7 /src/go/constant | |
parent | Initial commit. (diff) | |
download | golang-1.17-upstream.tar.xz golang-1.17-upstream.zip |
Adding upstream version 1.17.13.upstream/1.17.13upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/go/constant')
-rw-r--r-- | src/go/constant/example_test.go | 180 | ||||
-rw-r--r-- | src/go/constant/value.go | 1403 | ||||
-rw-r--r-- | src/go/constant/value_test.go | 729 |
3 files changed, 2312 insertions, 0 deletions
diff --git a/src/go/constant/example_test.go b/src/go/constant/example_test.go new file mode 100644 index 0000000..6443ee6 --- /dev/null +++ b/src/go/constant/example_test.go @@ -0,0 +1,180 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package constant_test + +import ( + "fmt" + "go/constant" + "go/token" + "math" + "sort" +) + +func Example_complexNumbers() { + // Create the complex number 2.3 + 5i. + ar := constant.MakeFloat64(2.3) + ai := constant.MakeImag(constant.MakeInt64(5)) + a := constant.BinaryOp(ar, token.ADD, ai) + + // Compute (2.3 + 5i) * 11. + b := constant.MakeUint64(11) + c := constant.BinaryOp(a, token.MUL, b) + + // Convert c into a complex128. + Ar, exact := constant.Float64Val(constant.Real(c)) + if !exact { + fmt.Printf("Could not represent real part %s exactly as float64\n", constant.Real(c)) + } + Ai, exact := constant.Float64Val(constant.Imag(c)) + if !exact { + fmt.Printf("Could not represent imaginary part %s as exactly as float64\n", constant.Imag(c)) + } + C := complex(Ar, Ai) + + fmt.Println("literal", 25.3+55i) + fmt.Println("go/constant", c) + fmt.Println("complex128", C) + + // Output: + // + // Could not represent real part 25.3 exactly as float64 + // literal (25.3+55i) + // go/constant (25.3 + 55i) + // complex128 (25.299999999999997+55i) +} + +func ExampleBinaryOp() { + // 11 / 0.5 + a := constant.MakeUint64(11) + b := constant.MakeFloat64(0.5) + c := constant.BinaryOp(a, token.QUO, b) + fmt.Println(c) + + // Output: 22 +} + +func ExampleUnaryOp() { + vs := []constant.Value{ + constant.MakeBool(true), + constant.MakeFloat64(2.7), + constant.MakeUint64(42), + } + + for i, v := range vs { + switch v.Kind() { + case constant.Bool: + vs[i] = constant.UnaryOp(token.NOT, v, 0) + + case constant.Float: + vs[i] = constant.UnaryOp(token.SUB, v, 0) + + case constant.Int: + // Use 16-bit precision. + // This would be equivalent to ^uint16(v). + vs[i] = constant.UnaryOp(token.XOR, v, 16) + } + } + + for _, v := range vs { + fmt.Println(v) + } + + // Output: + // + // false + // -2.7 + // 65493 +} + +func ExampleCompare() { + vs := []constant.Value{ + constant.MakeString("Z"), + constant.MakeString("bacon"), + constant.MakeString("go"), + constant.MakeString("Frame"), + constant.MakeString("defer"), + constant.MakeFromLiteral(`"a"`, token.STRING, 0), + } + + sort.Slice(vs, func(i, j int) bool { + // Equivalent to vs[i] <= vs[j]. + return constant.Compare(vs[i], token.LEQ, vs[j]) + }) + + for _, v := range vs { + fmt.Println(constant.StringVal(v)) + } + + // Output: + // + // Frame + // Z + // a + // bacon + // defer + // go +} + +func ExampleSign() { + zero := constant.MakeInt64(0) + one := constant.MakeInt64(1) + negOne := constant.MakeInt64(-1) + + mkComplex := func(a, b constant.Value) constant.Value { + b = constant.MakeImag(b) + return constant.BinaryOp(a, token.ADD, b) + } + + vs := []constant.Value{ + negOne, + mkComplex(zero, negOne), + mkComplex(one, negOne), + mkComplex(negOne, one), + mkComplex(negOne, negOne), + zero, + mkComplex(zero, zero), + one, + mkComplex(zero, one), + mkComplex(one, one), + } + + for _, v := range vs { + fmt.Printf("% d %s\n", constant.Sign(v), v) + } + + // Output: + // + // -1 -1 + // -1 (0 + -1i) + // -1 (1 + -1i) + // -1 (-1 + 1i) + // -1 (-1 + -1i) + // 0 0 + // 0 (0 + 0i) + // 1 1 + // 1 (0 + 1i) + // 1 (1 + 1i) +} + +func ExampleVal() { + maxint := constant.MakeInt64(math.MaxInt64) + fmt.Printf("%v\n", constant.Val(maxint)) + + e := constant.MakeFloat64(math.E) + fmt.Printf("%v\n", constant.Val(e)) + + b := constant.MakeBool(true) + fmt.Printf("%v\n", constant.Val(b)) + + b = constant.Make(false) + fmt.Printf("%v\n", constant.Val(b)) + + // Output: + // + // 9223372036854775807 + // 6121026514868073/2251799813685248 + // true + // false +} diff --git a/src/go/constant/value.go b/src/go/constant/value.go new file mode 100644 index 0000000..78cb3f8 --- /dev/null +++ b/src/go/constant/value.go @@ -0,0 +1,1403 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package constant implements Values representing untyped +// Go constants and their corresponding operations. +// +// A special Unknown value may be used when a value +// is unknown due to an error. Operations on unknown +// values produce unknown values unless specified +// otherwise. +// +package constant + +import ( + "fmt" + "go/token" + "math" + "math/big" + "math/bits" + "strconv" + "strings" + "sync" + "unicode/utf8" +) + +// Kind specifies the kind of value represented by a Value. +type Kind int + +const ( + // unknown values + Unknown Kind = iota + + // non-numeric values + Bool + String + + // numeric values + Int + Float + Complex +) + +// A Value represents the value of a Go constant. +type Value interface { + // Kind returns the value kind. + Kind() Kind + + // String returns a short, quoted (human-readable) form of the value. + // For numeric values, the result may be an approximation; + // for String values the result may be a shortened string. + // Use ExactString for a string representing a value exactly. + String() string + + // ExactString returns an exact, quoted (human-readable) form of the value. + // If the Value is of Kind String, use StringVal to obtain the unquoted string. + ExactString() string + + // Prevent external implementations. + implementsValue() +} + +// ---------------------------------------------------------------------------- +// Implementations + +// Maximum supported mantissa precision. +// The spec requires at least 256 bits; typical implementations use 512 bits. +const prec = 512 + +// TODO(gri) Consider storing "error" information in an unknownVal so clients +// can provide better error messages. For instance, if a number is +// too large (incl. infinity), that could be recorded in unknownVal. +// See also #20583 and #42695 for use cases. + +// Representation of values: +// +// Values of Int and Float Kind have two different representations each: int64Val +// and intVal, and ratVal and floatVal. When possible, the "smaller", respectively +// more precise (for Floats) representation is chosen. However, once a Float value +// is represented as a floatVal, any subsequent results remain floatVals (unless +// explicitly converted); i.e., no attempt is made to convert a floatVal back into +// a ratVal. The reasoning is that all representations but floatVal are mathematically +// exact, but once that precision is lost (by moving to floatVal), moving back to +// a different representation implies a precision that's not actually there. + +type ( + unknownVal struct{} + boolVal bool + stringVal struct { + // Lazy value: either a string (l,r==nil) or an addition (l,r!=nil). + mu sync.Mutex + s string + l, r *stringVal + } + int64Val int64 // Int values representable as an int64 + intVal struct{ val *big.Int } // Int values not representable as an int64 + ratVal struct{ val *big.Rat } // Float values representable as a fraction + floatVal struct{ val *big.Float } // Float values not representable as a fraction + complexVal struct{ re, im Value } +) + +func (unknownVal) Kind() Kind { return Unknown } +func (boolVal) Kind() Kind { return Bool } +func (*stringVal) Kind() Kind { return String } +func (int64Val) Kind() Kind { return Int } +func (intVal) Kind() Kind { return Int } +func (ratVal) Kind() Kind { return Float } +func (floatVal) Kind() Kind { return Float } +func (complexVal) Kind() Kind { return Complex } + +func (unknownVal) String() string { return "unknown" } +func (x boolVal) String() string { return strconv.FormatBool(bool(x)) } + +// String returns a possibly shortened quoted form of the String value. +func (x *stringVal) String() string { + const maxLen = 72 // a reasonable length + s := strconv.Quote(x.string()) + if utf8.RuneCountInString(s) > maxLen { + // The string without the enclosing quotes is greater than maxLen-2 runes + // long. Remove the last 3 runes (including the closing '"') by keeping + // only the first maxLen-3 runes; then add "...". + i := 0 + for n := 0; n < maxLen-3; n++ { + _, size := utf8.DecodeRuneInString(s[i:]) + i += size + } + s = s[:i] + "..." + } + return s +} + +// string constructs and returns the actual string literal value. +// If x represents an addition, then it rewrites x to be a single +// string, to speed future calls. This lazy construction avoids +// building different string values for all subpieces of a large +// concatenation. See golang.org/issue/23348. +func (x *stringVal) string() string { + x.mu.Lock() + if x.l != nil { + x.s = strings.Join(reverse(x.appendReverse(nil)), "") + x.l = nil + x.r = nil + } + s := x.s + x.mu.Unlock() + + return s +} + +// reverse reverses x in place and returns it. +func reverse(x []string) []string { + n := len(x) + for i := 0; i+i < n; i++ { + x[i], x[n-1-i] = x[n-1-i], x[i] + } + return x +} + +// appendReverse appends to list all of x's subpieces, but in reverse, +// and returns the result. Appending the reversal allows processing +// the right side in a recursive call and the left side in a loop. +// Because a chain like a + b + c + d + e is actually represented +// as ((((a + b) + c) + d) + e), the left-side loop avoids deep recursion. +// x must be locked. +func (x *stringVal) appendReverse(list []string) []string { + y := x + for y.r != nil { + y.r.mu.Lock() + list = y.r.appendReverse(list) + y.r.mu.Unlock() + + l := y.l + if y != x { + y.mu.Unlock() + } + l.mu.Lock() + y = l + } + s := y.s + if y != x { + y.mu.Unlock() + } + return append(list, s) +} + +func (x int64Val) String() string { return strconv.FormatInt(int64(x), 10) } +func (x intVal) String() string { return x.val.String() } +func (x ratVal) String() string { return rtof(x).String() } + +// String returns a decimal approximation of the Float value. +func (x floatVal) String() string { + f := x.val + + // Don't try to convert infinities (will not terminate). + if f.IsInf() { + return f.String() + } + + // Use exact fmt formatting if in float64 range (common case): + // proceed if f doesn't underflow to 0 or overflow to inf. + if x, _ := f.Float64(); f.Sign() == 0 == (x == 0) && !math.IsInf(x, 0) { + return fmt.Sprintf("%.6g", x) + } + + // Out of float64 range. Do approximate manual to decimal + // conversion to avoid precise but possibly slow Float + // formatting. + // f = mant * 2**exp + var mant big.Float + exp := f.MantExp(&mant) // 0.5 <= |mant| < 1.0 + + // approximate float64 mantissa m and decimal exponent d + // f ~ m * 10**d + m, _ := mant.Float64() // 0.5 <= |m| < 1.0 + d := float64(exp) * (math.Ln2 / math.Ln10) // log_10(2) + + // adjust m for truncated (integer) decimal exponent e + e := int64(d) + m *= math.Pow(10, d-float64(e)) + + // ensure 1 <= |m| < 10 + switch am := math.Abs(m); { + case am < 1-0.5e-6: + // The %.6g format below rounds m to 5 digits after the + // decimal point. Make sure that m*10 < 10 even after + // rounding up: m*10 + 0.5e-5 < 10 => m < 1 - 0.5e6. + m *= 10 + e-- + case am >= 10: + m /= 10 + e++ + } + + return fmt.Sprintf("%.6ge%+d", m, e) +} + +func (x complexVal) String() string { return fmt.Sprintf("(%s + %si)", x.re, x.im) } + +func (x unknownVal) ExactString() string { return x.String() } +func (x boolVal) ExactString() string { return x.String() } +func (x *stringVal) ExactString() string { return strconv.Quote(x.string()) } +func (x int64Val) ExactString() string { return x.String() } +func (x intVal) ExactString() string { return x.String() } + +func (x ratVal) ExactString() string { + r := x.val + if r.IsInt() { + return r.Num().String() + } + return r.String() +} + +func (x floatVal) ExactString() string { return x.val.Text('p', 0) } + +func (x complexVal) ExactString() string { + return fmt.Sprintf("(%s + %si)", x.re.ExactString(), x.im.ExactString()) +} + +func (unknownVal) implementsValue() {} +func (boolVal) implementsValue() {} +func (*stringVal) implementsValue() {} +func (int64Val) implementsValue() {} +func (ratVal) implementsValue() {} +func (intVal) implementsValue() {} +func (floatVal) implementsValue() {} +func (complexVal) implementsValue() {} + +func newInt() *big.Int { return new(big.Int) } +func newRat() *big.Rat { return new(big.Rat) } +func newFloat() *big.Float { return new(big.Float).SetPrec(prec) } + +func i64toi(x int64Val) intVal { return intVal{newInt().SetInt64(int64(x))} } +func i64tor(x int64Val) ratVal { return ratVal{newRat().SetInt64(int64(x))} } +func i64tof(x int64Val) floatVal { return floatVal{newFloat().SetInt64(int64(x))} } +func itor(x intVal) ratVal { return ratVal{newRat().SetInt(x.val)} } +func itof(x intVal) floatVal { return floatVal{newFloat().SetInt(x.val)} } +func rtof(x ratVal) floatVal { return floatVal{newFloat().SetRat(x.val)} } +func vtoc(x Value) complexVal { return complexVal{x, int64Val(0)} } + +func makeInt(x *big.Int) Value { + if x.IsInt64() { + return int64Val(x.Int64()) + } + return intVal{x} +} + +func makeRat(x *big.Rat) Value { + a := x.Num() + b := x.Denom() + if smallInt(a) && smallInt(b) { + // ok to remain fraction + return ratVal{x} + } + // components too large => switch to float + return floatVal{newFloat().SetRat(x)} +} + +var floatVal0 = floatVal{newFloat()} + +func makeFloat(x *big.Float) Value { + // convert -0 + if x.Sign() == 0 { + return floatVal0 + } + if x.IsInf() { + return unknownVal{} + } + // No attempt is made to "go back" to ratVal, even if possible, + // to avoid providing the illusion of a mathematically exact + // representation. + return floatVal{x} +} + +func makeComplex(re, im Value) Value { + if re.Kind() == Unknown || im.Kind() == Unknown { + return unknownVal{} + } + return complexVal{re, im} +} + +func makeFloatFromLiteral(lit string) Value { + if f, ok := newFloat().SetString(lit); ok { + if smallFloat(f) { + // ok to use rationals + if f.Sign() == 0 { + // Issue 20228: If the float underflowed to zero, parse just "0". + // Otherwise, lit might contain a value with a large negative exponent, + // such as -6e-1886451601. As a float, that will underflow to 0, + // but it'll take forever to parse as a Rat. + lit = "0" + } + if r, ok := newRat().SetString(lit); ok { + return ratVal{r} + } + } + // otherwise use floats + return makeFloat(f) + } + return nil +} + +// Permit fractions with component sizes up to maxExp +// before switching to using floating-point numbers. +const maxExp = 4 << 10 + +// smallInt reports whether x would lead to "reasonably"-sized fraction +// if converted to a *big.Rat. +func smallInt(x *big.Int) bool { + return x.BitLen() < maxExp +} + +// smallFloat64 reports whether x would lead to "reasonably"-sized fraction +// if converted to a *big.Rat. +func smallFloat64(x float64) bool { + if math.IsInf(x, 0) { + return false + } + _, e := math.Frexp(x) + return -maxExp < e && e < maxExp +} + +// smallFloat reports whether x would lead to "reasonably"-sized fraction +// if converted to a *big.Rat. +func smallFloat(x *big.Float) bool { + if x.IsInf() { + return false + } + e := x.MantExp(nil) + return -maxExp < e && e < maxExp +} + +// ---------------------------------------------------------------------------- +// Factories + +// MakeUnknown returns the Unknown value. +func MakeUnknown() Value { return unknownVal{} } + +// MakeBool returns the Bool value for b. +func MakeBool(b bool) Value { return boolVal(b) } + +// MakeString returns the String value for s. +func MakeString(s string) Value { return &stringVal{s: s} } + +// MakeInt64 returns the Int value for x. +func MakeInt64(x int64) Value { return int64Val(x) } + +// MakeUint64 returns the Int value for x. +func MakeUint64(x uint64) Value { + if x < 1<<63 { + return int64Val(int64(x)) + } + return intVal{newInt().SetUint64(x)} +} + +// MakeFloat64 returns the Float value for x. +// If x is -0.0, the result is 0.0. +// If x is not finite, the result is an Unknown. +func MakeFloat64(x float64) Value { + if math.IsInf(x, 0) || math.IsNaN(x) { + return unknownVal{} + } + if smallFloat64(x) { + return ratVal{newRat().SetFloat64(x + 0)} // convert -0 to 0 + } + return floatVal{newFloat().SetFloat64(x + 0)} +} + +// MakeFromLiteral returns the corresponding integer, floating-point, +// imaginary, character, or string value for a Go literal string. The +// tok value must be one of token.INT, token.FLOAT, token.IMAG, +// token.CHAR, or token.STRING. The final argument must be zero. +// If the literal string syntax is invalid, the result is an Unknown. +func MakeFromLiteral(lit string, tok token.Token, zero uint) Value { + if zero != 0 { + panic("MakeFromLiteral called with non-zero last argument") + } + + switch tok { + case token.INT: + if x, err := strconv.ParseInt(lit, 0, 64); err == nil { + return int64Val(x) + } + if x, ok := newInt().SetString(lit, 0); ok { + return intVal{x} + } + + case token.FLOAT: + if x := makeFloatFromLiteral(lit); x != nil { + return x + } + + case token.IMAG: + if n := len(lit); n > 0 && lit[n-1] == 'i' { + if im := makeFloatFromLiteral(lit[:n-1]); im != nil { + return makeComplex(int64Val(0), im) + } + } + + case token.CHAR: + if n := len(lit); n >= 2 { + if code, _, _, err := strconv.UnquoteChar(lit[1:n-1], '\''); err == nil { + return MakeInt64(int64(code)) + } + } + + case token.STRING: + if s, err := strconv.Unquote(lit); err == nil { + return MakeString(s) + } + + default: + panic(fmt.Sprintf("%v is not a valid token", tok)) + } + + return unknownVal{} +} + +// ---------------------------------------------------------------------------- +// Accessors +// +// For unknown arguments the result is the zero value for the respective +// accessor type, except for Sign, where the result is 1. + +// BoolVal returns the Go boolean value of x, which must be a Bool or an Unknown. +// If x is Unknown, the result is false. +func BoolVal(x Value) bool { + switch x := x.(type) { + case boolVal: + return bool(x) + case unknownVal: + return false + default: + panic(fmt.Sprintf("%v not a Bool", x)) + } +} + +// StringVal returns the Go string value of x, which must be a String or an Unknown. +// If x is Unknown, the result is "". +func StringVal(x Value) string { + switch x := x.(type) { + case *stringVal: + return x.string() + case unknownVal: + return "" + default: + panic(fmt.Sprintf("%v not a String", x)) + } +} + +// Int64Val returns the Go int64 value of x and whether the result is exact; +// x must be an Int or an Unknown. If the result is not exact, its value is undefined. +// If x is Unknown, the result is (0, false). +func Int64Val(x Value) (int64, bool) { + switch x := x.(type) { + case int64Val: + return int64(x), true + case intVal: + return x.val.Int64(), false // not an int64Val and thus not exact + case unknownVal: + return 0, false + default: + panic(fmt.Sprintf("%v not an Int", x)) + } +} + +// Uint64Val returns the Go uint64 value of x and whether the result is exact; +// x must be an Int or an Unknown. If the result is not exact, its value is undefined. +// If x is Unknown, the result is (0, false). +func Uint64Val(x Value) (uint64, bool) { + switch x := x.(type) { + case int64Val: + return uint64(x), x >= 0 + case intVal: + return x.val.Uint64(), x.val.IsUint64() + case unknownVal: + return 0, false + default: + panic(fmt.Sprintf("%v not an Int", x)) + } +} + +// Float32Val is like Float64Val but for float32 instead of float64. +func Float32Val(x Value) (float32, bool) { + switch x := x.(type) { + case int64Val: + f := float32(x) + return f, int64Val(f) == x + case intVal: + f, acc := newFloat().SetInt(x.val).Float32() + return f, acc == big.Exact + case ratVal: + return x.val.Float32() + case floatVal: + f, acc := x.val.Float32() + return f, acc == big.Exact + case unknownVal: + return 0, false + default: + panic(fmt.Sprintf("%v not a Float", x)) + } +} + +// Float64Val returns the nearest Go float64 value of x and whether the result is exact; +// x must be numeric or an Unknown, but not Complex. For values too small (too close to 0) +// to represent as float64, Float64Val silently underflows to 0. The result sign always +// matches the sign of x, even for 0. +// If x is Unknown, the result is (0, false). +func Float64Val(x Value) (float64, bool) { + switch x := x.(type) { + case int64Val: + f := float64(int64(x)) + return f, int64Val(f) == x + case intVal: + f, acc := newFloat().SetInt(x.val).Float64() + return f, acc == big.Exact + case ratVal: + return x.val.Float64() + case floatVal: + f, acc := x.val.Float64() + return f, acc == big.Exact + case unknownVal: + return 0, false + default: + panic(fmt.Sprintf("%v not a Float", x)) + } +} + +// Val returns the underlying value for a given constant. Since it returns an +// interface, it is up to the caller to type assert the result to the expected +// type. The possible dynamic return types are: +// +// x Kind type of result +// ----------------------------------------- +// Bool bool +// String string +// Int int64 or *big.Int +// Float *big.Float or *big.Rat +// everything else nil +// +func Val(x Value) interface{} { + switch x := x.(type) { + case boolVal: + return bool(x) + case *stringVal: + return x.string() + case int64Val: + return int64(x) + case intVal: + return x.val + case ratVal: + return x.val + case floatVal: + return x.val + default: + return nil + } +} + +// Make returns the Value for x. +// +// type of x result Kind +// ---------------------------- +// bool Bool +// string String +// int64 Int +// *big.Int Int +// *big.Float Float +// *big.Rat Float +// anything else Unknown +// +func Make(x interface{}) Value { + switch x := x.(type) { + case bool: + return boolVal(x) + case string: + return &stringVal{s: x} + case int64: + return int64Val(x) + case *big.Int: + return makeInt(x) + case *big.Rat: + return makeRat(x) + case *big.Float: + return makeFloat(x) + default: + return unknownVal{} + } +} + +// BitLen returns the number of bits required to represent +// the absolute value x in binary representation; x must be an Int or an Unknown. +// If x is Unknown, the result is 0. +func BitLen(x Value) int { + switch x := x.(type) { + case int64Val: + u := uint64(x) + if x < 0 { + u = uint64(-x) + } + return 64 - bits.LeadingZeros64(u) + case intVal: + return x.val.BitLen() + case unknownVal: + return 0 + default: + panic(fmt.Sprintf("%v not an Int", x)) + } +} + +// Sign returns -1, 0, or 1 depending on whether x < 0, x == 0, or x > 0; +// x must be numeric or Unknown. For complex values x, the sign is 0 if x == 0, +// otherwise it is != 0. If x is Unknown, the result is 1. +func Sign(x Value) int { + switch x := x.(type) { + case int64Val: + switch { + case x < 0: + return -1 + case x > 0: + return 1 + } + return 0 + case intVal: + return x.val.Sign() + case ratVal: + return x.val.Sign() + case floatVal: + return x.val.Sign() + case complexVal: + return Sign(x.re) | Sign(x.im) + case unknownVal: + return 1 // avoid spurious division by zero errors + default: + panic(fmt.Sprintf("%v not numeric", x)) + } +} + +// ---------------------------------------------------------------------------- +// Support for assembling/disassembling numeric values + +const ( + // Compute the size of a Word in bytes. + _m = ^big.Word(0) + _log = _m>>8&1 + _m>>16&1 + _m>>32&1 + wordSize = 1 << _log +) + +// Bytes returns the bytes for the absolute value of x in little- +// endian binary representation; x must be an Int. +func Bytes(x Value) []byte { + var t intVal + switch x := x.(type) { + case int64Val: + t = i64toi(x) + case intVal: + t = x + default: + panic(fmt.Sprintf("%v not an Int", x)) + } + + words := t.val.Bits() + bytes := make([]byte, len(words)*wordSize) + + i := 0 + for _, w := range words { + for j := 0; j < wordSize; j++ { + bytes[i] = byte(w) + w >>= 8 + i++ + } + } + // remove leading 0's + for i > 0 && bytes[i-1] == 0 { + i-- + } + + return bytes[:i] +} + +// MakeFromBytes returns the Int value given the bytes of its little-endian +// binary representation. An empty byte slice argument represents 0. +func MakeFromBytes(bytes []byte) Value { + words := make([]big.Word, (len(bytes)+(wordSize-1))/wordSize) + + i := 0 + var w big.Word + var s uint + for _, b := range bytes { + w |= big.Word(b) << s + if s += 8; s == wordSize*8 { + words[i] = w + i++ + w = 0 + s = 0 + } + } + // store last word + if i < len(words) { + words[i] = w + i++ + } + // remove leading 0's + for i > 0 && words[i-1] == 0 { + i-- + } + + return makeInt(newInt().SetBits(words[:i])) +} + +// Num returns the numerator of x; x must be Int, Float, or Unknown. +// If x is Unknown, or if it is too large or small to represent as a +// fraction, the result is Unknown. Otherwise the result is an Int +// with the same sign as x. +func Num(x Value) Value { + switch x := x.(type) { + case int64Val, intVal: + return x + case ratVal: + return makeInt(x.val.Num()) + case floatVal: + if smallFloat(x.val) { + r, _ := x.val.Rat(nil) + return makeInt(r.Num()) + } + case unknownVal: + break + default: + panic(fmt.Sprintf("%v not Int or Float", x)) + } + return unknownVal{} +} + +// Denom returns the denominator of x; x must be Int, Float, or Unknown. +// If x is Unknown, or if it is too large or small to represent as a +// fraction, the result is Unknown. Otherwise the result is an Int >= 1. +func Denom(x Value) Value { + switch x := x.(type) { + case int64Val, intVal: + return int64Val(1) + case ratVal: + return makeInt(x.val.Denom()) + case floatVal: + if smallFloat(x.val) { + r, _ := x.val.Rat(nil) + return makeInt(r.Denom()) + } + case unknownVal: + break + default: + panic(fmt.Sprintf("%v not Int or Float", x)) + } + return unknownVal{} +} + +// MakeImag returns the Complex value x*i; +// x must be Int, Float, or Unknown. +// If x is Unknown, the result is Unknown. +func MakeImag(x Value) Value { + switch x.(type) { + case unknownVal: + return x + case int64Val, intVal, ratVal, floatVal: + return makeComplex(int64Val(0), x) + default: + panic(fmt.Sprintf("%v not Int or Float", x)) + } +} + +// Real returns the real part of x, which must be a numeric or unknown value. +// If x is Unknown, the result is Unknown. +func Real(x Value) Value { + switch x := x.(type) { + case unknownVal, int64Val, intVal, ratVal, floatVal: + return x + case complexVal: + return x.re + default: + panic(fmt.Sprintf("%v not numeric", x)) + } +} + +// Imag returns the imaginary part of x, which must be a numeric or unknown value. +// If x is Unknown, the result is Unknown. +func Imag(x Value) Value { + switch x := x.(type) { + case unknownVal: + return x + case int64Val, intVal, ratVal, floatVal: + return int64Val(0) + case complexVal: + return x.im + default: + panic(fmt.Sprintf("%v not numeric", x)) + } +} + +// ---------------------------------------------------------------------------- +// Numeric conversions + +// ToInt converts x to an Int value if x is representable as an Int. +// Otherwise it returns an Unknown. +func ToInt(x Value) Value { + switch x := x.(type) { + case int64Val, intVal: + return x + + case ratVal: + if x.val.IsInt() { + return makeInt(x.val.Num()) + } + + case floatVal: + // avoid creation of huge integers + // (Existing tests require permitting exponents of at least 1024; + // allow any value that would also be permissible as a fraction.) + if smallFloat(x.val) { + i := newInt() + if _, acc := x.val.Int(i); acc == big.Exact { + return makeInt(i) + } + + // If we can get an integer by rounding up or down, + // assume x is not an integer because of rounding + // errors in prior computations. + + const delta = 4 // a small number of bits > 0 + var t big.Float + t.SetPrec(prec - delta) + + // try rounding down a little + t.SetMode(big.ToZero) + t.Set(x.val) + if _, acc := t.Int(i); acc == big.Exact { + return makeInt(i) + } + + // try rounding up a little + t.SetMode(big.AwayFromZero) + t.Set(x.val) + if _, acc := t.Int(i); acc == big.Exact { + return makeInt(i) + } + } + + case complexVal: + if re := ToFloat(x); re.Kind() == Float { + return ToInt(re) + } + } + + return unknownVal{} +} + +// ToFloat converts x to a Float value if x is representable as a Float. +// Otherwise it returns an Unknown. +func ToFloat(x Value) Value { + switch x := x.(type) { + case int64Val: + return i64tor(x) // x is always a small int + case intVal: + if smallInt(x.val) { + return itor(x) + } + return itof(x) + case ratVal, floatVal: + return x + case complexVal: + if Sign(x.im) == 0 { + return ToFloat(x.re) + } + } + return unknownVal{} +} + +// ToComplex converts x to a Complex value if x is representable as a Complex. +// Otherwise it returns an Unknown. +func ToComplex(x Value) Value { + switch x := x.(type) { + case int64Val, intVal, ratVal, floatVal: + return vtoc(x) + case complexVal: + return x + } + return unknownVal{} +} + +// ---------------------------------------------------------------------------- +// Operations + +// is32bit reports whether x can be represented using 32 bits. +func is32bit(x int64) bool { + const s = 32 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 +} + +// is63bit reports whether x can be represented using 63 bits. +func is63bit(x int64) bool { + const s = 63 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 +} + +// UnaryOp returns the result of the unary expression op y. +// The operation must be defined for the operand. +// If prec > 0 it specifies the ^ (xor) result size in bits. +// If y is Unknown, the result is Unknown. +// +func UnaryOp(op token.Token, y Value, prec uint) Value { + switch op { + case token.ADD: + switch y.(type) { + case unknownVal, int64Val, intVal, ratVal, floatVal, complexVal: + return y + } + + case token.SUB: + switch y := y.(type) { + case unknownVal: + return y + case int64Val: + if z := -y; z != y { + return z // no overflow + } + return makeInt(newInt().Neg(big.NewInt(int64(y)))) + case intVal: + return makeInt(newInt().Neg(y.val)) + case ratVal: + return makeRat(newRat().Neg(y.val)) + case floatVal: + return makeFloat(newFloat().Neg(y.val)) + case complexVal: + re := UnaryOp(token.SUB, y.re, 0) + im := UnaryOp(token.SUB, y.im, 0) + return makeComplex(re, im) + } + + case token.XOR: + z := newInt() + switch y := y.(type) { + case unknownVal: + return y + case int64Val: + z.Not(big.NewInt(int64(y))) + case intVal: + z.Not(y.val) + default: + goto Error + } + // For unsigned types, the result will be negative and + // thus "too large": We must limit the result precision + // to the type's precision. + if prec > 0 { + z.AndNot(z, newInt().Lsh(big.NewInt(-1), prec)) // z &^= (-1)<<prec + } + return makeInt(z) + + case token.NOT: + switch y := y.(type) { + case unknownVal: + return y + case boolVal: + return !y + } + } + +Error: + panic(fmt.Sprintf("invalid unary operation %s%v", op, y)) +} + +func ord(x Value) int { + switch x.(type) { + default: + // force invalid value into "x position" in match + // (don't panic here so that callers can provide a better error message) + return -1 + case unknownVal: + return 0 + case boolVal, *stringVal: + return 1 + case int64Val: + return 2 + case intVal: + return 3 + case ratVal: + return 4 + case floatVal: + return 5 + case complexVal: + return 6 + } +} + +// match returns the matching representation (same type) with the +// smallest complexity for two values x and y. If one of them is +// numeric, both of them must be numeric. If one of them is Unknown +// or invalid (say, nil) both results are that value. +// +func match(x, y Value) (_, _ Value) { + switch ox, oy := ord(x), ord(y); { + case ox < oy: + x, y = match0(x, y) + case ox > oy: + y, x = match0(y, x) + } + return x, y +} + +// match0 must only be called by match. +// Invariant: ord(x) < ord(y) +func match0(x, y Value) (_, _ Value) { + // Prefer to return the original x and y arguments when possible, + // to avoid unnecessary heap allocations. + + switch y.(type) { + case intVal: + switch x1 := x.(type) { + case int64Val: + return i64toi(x1), y + } + case ratVal: + switch x1 := x.(type) { + case int64Val: + return i64tor(x1), y + case intVal: + return itor(x1), y + } + case floatVal: + switch x1 := x.(type) { + case int64Val: + return i64tof(x1), y + case intVal: + return itof(x1), y + case ratVal: + return rtof(x1), y + } + case complexVal: + return vtoc(x), y + } + + // force unknown and invalid values into "x position" in callers of match + // (don't panic here so that callers can provide a better error message) + return x, x +} + +// BinaryOp returns the result of the binary expression x op y. +// The operation must be defined for the operands. If one of the +// operands is Unknown, the result is Unknown. +// BinaryOp doesn't handle comparisons or shifts; use Compare +// or Shift instead. +// +// To force integer division of Int operands, use op == token.QUO_ASSIGN +// instead of token.QUO; the result is guaranteed to be Int in this case. +// Division by zero leads to a run-time panic. +// +func BinaryOp(x_ Value, op token.Token, y_ Value) Value { + x, y := match(x_, y_) + + switch x := x.(type) { + case unknownVal: + return x + + case boolVal: + y := y.(boolVal) + switch op { + case token.LAND: + return x && y + case token.LOR: + return x || y + } + + case int64Val: + a := int64(x) + b := int64(y.(int64Val)) + var c int64 + switch op { + case token.ADD: + if !is63bit(a) || !is63bit(b) { + return makeInt(newInt().Add(big.NewInt(a), big.NewInt(b))) + } + c = a + b + case token.SUB: + if !is63bit(a) || !is63bit(b) { + return makeInt(newInt().Sub(big.NewInt(a), big.NewInt(b))) + } + c = a - b + case token.MUL: + if !is32bit(a) || !is32bit(b) { + return makeInt(newInt().Mul(big.NewInt(a), big.NewInt(b))) + } + c = a * b + case token.QUO: + return makeRat(big.NewRat(a, b)) + case token.QUO_ASSIGN: // force integer division + c = a / b + case token.REM: + c = a % b + case token.AND: + c = a & b + case token.OR: + c = a | b + case token.XOR: + c = a ^ b + case token.AND_NOT: + c = a &^ b + default: + goto Error + } + return int64Val(c) + + case intVal: + a := x.val + b := y.(intVal).val + c := newInt() + switch op { + case token.ADD: + c.Add(a, b) + case token.SUB: + c.Sub(a, b) + case token.MUL: + c.Mul(a, b) + case token.QUO: + return makeRat(newRat().SetFrac(a, b)) + case token.QUO_ASSIGN: // force integer division + c.Quo(a, b) + case token.REM: + c.Rem(a, b) + case token.AND: + c.And(a, b) + case token.OR: + c.Or(a, b) + case token.XOR: + c.Xor(a, b) + case token.AND_NOT: + c.AndNot(a, b) + default: + goto Error + } + return makeInt(c) + + case ratVal: + a := x.val + b := y.(ratVal).val + c := newRat() + switch op { + case token.ADD: + c.Add(a, b) + case token.SUB: + c.Sub(a, b) + case token.MUL: + c.Mul(a, b) + case token.QUO: + c.Quo(a, b) + default: + goto Error + } + return makeRat(c) + + case floatVal: + a := x.val + b := y.(floatVal).val + c := newFloat() + switch op { + case token.ADD: + c.Add(a, b) + case token.SUB: + c.Sub(a, b) + case token.MUL: + c.Mul(a, b) + case token.QUO: + c.Quo(a, b) + default: + goto Error + } + return makeFloat(c) + + case complexVal: + y := y.(complexVal) + a, b := x.re, x.im + c, d := y.re, y.im + var re, im Value + switch op { + case token.ADD: + // (a+c) + i(b+d) + re = add(a, c) + im = add(b, d) + case token.SUB: + // (a-c) + i(b-d) + re = sub(a, c) + im = sub(b, d) + case token.MUL: + // (ac-bd) + i(bc+ad) + ac := mul(a, c) + bd := mul(b, d) + bc := mul(b, c) + ad := mul(a, d) + re = sub(ac, bd) + im = add(bc, ad) + case token.QUO: + // (ac+bd)/s + i(bc-ad)/s, with s = cc + dd + ac := mul(a, c) + bd := mul(b, d) + bc := mul(b, c) + ad := mul(a, d) + cc := mul(c, c) + dd := mul(d, d) + s := add(cc, dd) + re = add(ac, bd) + re = quo(re, s) + im = sub(bc, ad) + im = quo(im, s) + default: + goto Error + } + return makeComplex(re, im) + + case *stringVal: + if op == token.ADD { + return &stringVal{l: x, r: y.(*stringVal)} + } + } + +Error: + panic(fmt.Sprintf("invalid binary operation %v %s %v", x_, op, y_)) +} + +func add(x, y Value) Value { return BinaryOp(x, token.ADD, y) } +func sub(x, y Value) Value { return BinaryOp(x, token.SUB, y) } +func mul(x, y Value) Value { return BinaryOp(x, token.MUL, y) } +func quo(x, y Value) Value { return BinaryOp(x, token.QUO, y) } + +// Shift returns the result of the shift expression x op s +// with op == token.SHL or token.SHR (<< or >>). x must be +// an Int or an Unknown. If x is Unknown, the result is x. +// +func Shift(x Value, op token.Token, s uint) Value { + switch x := x.(type) { + case unknownVal: + return x + + case int64Val: + if s == 0 { + return x + } + switch op { + case token.SHL: + z := i64toi(x).val + return makeInt(z.Lsh(z, s)) + case token.SHR: + return x >> s + } + + case intVal: + if s == 0 { + return x + } + z := newInt() + switch op { + case token.SHL: + return makeInt(z.Lsh(x.val, s)) + case token.SHR: + return makeInt(z.Rsh(x.val, s)) + } + } + + panic(fmt.Sprintf("invalid shift %v %s %d", x, op, s)) +} + +func cmpZero(x int, op token.Token) bool { + switch op { + case token.EQL: + return x == 0 + case token.NEQ: + return x != 0 + case token.LSS: + return x < 0 + case token.LEQ: + return x <= 0 + case token.GTR: + return x > 0 + case token.GEQ: + return x >= 0 + } + panic(fmt.Sprintf("invalid comparison %v %s 0", x, op)) +} + +// Compare returns the result of the comparison x op y. +// The comparison must be defined for the operands. +// If one of the operands is Unknown, the result is +// false. +// +func Compare(x_ Value, op token.Token, y_ Value) bool { + x, y := match(x_, y_) + + switch x := x.(type) { + case unknownVal: + return false + + case boolVal: + y := y.(boolVal) + switch op { + case token.EQL: + return x == y + case token.NEQ: + return x != y + } + + case int64Val: + y := y.(int64Val) + switch op { + case token.EQL: + return x == y + case token.NEQ: + return x != y + case token.LSS: + return x < y + case token.LEQ: + return x <= y + case token.GTR: + return x > y + case token.GEQ: + return x >= y + } + + case intVal: + return cmpZero(x.val.Cmp(y.(intVal).val), op) + + case ratVal: + return cmpZero(x.val.Cmp(y.(ratVal).val), op) + + case floatVal: + return cmpZero(x.val.Cmp(y.(floatVal).val), op) + + case complexVal: + y := y.(complexVal) + re := Compare(x.re, token.EQL, y.re) + im := Compare(x.im, token.EQL, y.im) + switch op { + case token.EQL: + return re && im + case token.NEQ: + return !re || !im + } + + case *stringVal: + xs := x.string() + ys := y.(*stringVal).string() + switch op { + case token.EQL: + return xs == ys + case token.NEQ: + return xs != ys + case token.LSS: + return xs < ys + case token.LEQ: + return xs <= ys + case token.GTR: + return xs > ys + case token.GEQ: + return xs >= ys + } + } + + panic(fmt.Sprintf("invalid comparison %v %s %v", x_, op, y_)) +} diff --git a/src/go/constant/value_test.go b/src/go/constant/value_test.go new file mode 100644 index 0000000..91ad0b0 --- /dev/null +++ b/src/go/constant/value_test.go @@ -0,0 +1,729 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package constant + +import ( + "fmt" + "go/token" + "math" + "math/big" + "strings" + "testing" +) + +var intTests = []string{ + // 0-octals + `0_123 = 0123`, + `0123_456 = 0123456`, + + // decimals + `1_234 = 1234`, + `1_234_567 = 1234567`, + + // hexadecimals + `0X_0 = 0`, + `0X_1234 = 0x1234`, + `0X_CAFE_f00d = 0xcafef00d`, + + // octals + `0o0 = 0`, + `0o1234 = 01234`, + `0o01234567 = 01234567`, + + `0O0 = 0`, + `0O1234 = 01234`, + `0O01234567 = 01234567`, + + `0o_0 = 0`, + `0o_1234 = 01234`, + `0o0123_4567 = 01234567`, + + `0O_0 = 0`, + `0O_1234 = 01234`, + `0O0123_4567 = 01234567`, + + // binaries + `0b0 = 0`, + `0b1011 = 0xb`, + `0b00101101 = 0x2d`, + + `0B0 = 0`, + `0B1011 = 0xb`, + `0B00101101 = 0x2d`, + + `0b_0 = 0`, + `0b10_11 = 0xb`, + `0b_0010_1101 = 0x2d`, +} + +// The RHS operand may be a floating-point quotient n/d of two integer values n and d. +var floatTests = []string{ + // decimal floats + `1_2_3. = 123.`, + `0_123. = 123.`, + + `0_0e0 = 0.`, + `1_2_3e0 = 123.`, + `0_123e0 = 123.`, + + `0e-0_0 = 0.`, + `1_2_3E+0 = 123.`, + `0123E1_2_3 = 123e123`, + + `0.e+1 = 0.`, + `123.E-1_0 = 123e-10`, + `01_23.e123 = 123e123`, + + `.0e-1 = .0`, + `.123E+10 = .123e10`, + `.0123E123 = .0123e123`, + + `1_2_3.123 = 123.123`, + `0123.01_23 = 123.0123`, + + `1e-1000000000 = 0`, + `1e+1000000000 = ?`, + `6e5518446744 = ?`, + `-6e5518446744 = ?`, + + // hexadecimal floats + `0x0.p+0 = 0.`, + `0Xdeadcafe.p-10 = 0xdeadcafe/1024`, + `0x1234.P84 = 0x1234000000000000000000000`, + + `0x.1p-0 = 1/16`, + `0X.deadcafep4 = 0xdeadcafe/0x10000000`, + `0x.1234P+12 = 0x1234/0x10`, + + `0x0p0 = 0.`, + `0Xdeadcafep+1 = 0x1bd5b95fc`, + `0x1234P-10 = 0x1234/1024`, + + `0x0.0p0 = 0.`, + `0Xdead.cafep+1 = 0x1bd5b95fc/0x10000`, + `0x12.34P-10 = 0x1234/0x40000`, + + `0Xdead_cafep+1 = 0xdeadcafep+1`, + `0x_1234P-10 = 0x1234p-10`, + + `0X_dead_cafe.p-10 = 0xdeadcafe.p-10`, + `0x12_34.P1_2_3 = 0x1234.p123`, +} + +var imagTests = []string{ + `1_234i = 1234i`, + `1_234_567i = 1234567i`, + + `0.i = 0i`, + `123.i = 123i`, + `0123.i = 123i`, + + `0.e+1i = 0i`, + `123.E-1_0i = 123e-10i`, + `01_23.e123i = 123e123i`, + + `1e-1000000000i = 0i`, + `1e+1000000000i = ?`, + `6e5518446744i = ?`, + `-6e5518446744i = ?`, +} + +func testNumbers(t *testing.T, kind token.Token, tests []string) { + for _, test := range tests { + a := strings.Split(test, " = ") + if len(a) != 2 { + t.Errorf("invalid test case: %s", test) + continue + } + + x := MakeFromLiteral(a[0], kind, 0) + var y Value + if a[1] == "?" { + y = MakeUnknown() + } else { + if i := strings.Index(a[1], "/"); i >= 0 && kind == token.FLOAT { + n := MakeFromLiteral(a[1][:i], token.INT, 0) + d := MakeFromLiteral(a[1][i+1:], token.INT, 0) + y = BinaryOp(n, token.QUO, d) + } else { + y = MakeFromLiteral(a[1], kind, 0) + } + if y.Kind() == Unknown { + panic(fmt.Sprintf("invalid test case: %s %d", test, y.Kind())) + } + } + + xk := x.Kind() + yk := y.Kind() + if xk != yk { + t.Errorf("%s: got kind %d != %d", test, xk, yk) + continue + } + + if yk == Unknown { + continue + } + + if !Compare(x, token.EQL, y) { + t.Errorf("%s: %s != %s", test, x, y) + } + } +} + +// TestNumbers verifies that differently written literals +// representing the same number do have the same value. +func TestNumbers(t *testing.T) { + testNumbers(t, token.INT, intTests) + testNumbers(t, token.FLOAT, floatTests) + testNumbers(t, token.IMAG, imagTests) +} + +var opTests = []string{ + // unary operations + `+ 0 = 0`, + `+ ? = ?`, + `- 1 = -1`, + `- ? = ?`, + `^ 0 = -1`, + `^ ? = ?`, + + `! true = false`, + `! false = true`, + `! ? = ?`, + + // etc. + + // binary operations + `"" + "" = ""`, + `"foo" + "" = "foo"`, + `"" + "bar" = "bar"`, + `"foo" + "bar" = "foobar"`, + + `0 + 0 = 0`, + `0 + 0.1 = 0.1`, + `0 + 0.1i = 0.1i`, + `0.1 + 0.9 = 1`, + `1e100 + 1e100 = 2e100`, + `? + 0 = ?`, + `0 + ? = ?`, + + `0 - 0 = 0`, + `0 - 0.1 = -0.1`, + `0 - 0.1i = -0.1i`, + `1e100 - 1e100 = 0`, + `? - 0 = ?`, + `0 - ? = ?`, + + `0 * 0 = 0`, + `1 * 0.1 = 0.1`, + `1 * 0.1i = 0.1i`, + `1i * 1i = -1`, + `? * 0 = ?`, + `0 * ? = ?`, + `0 * 1e+1000000000 = ?`, + + `0 / 0 = "division_by_zero"`, + `10 / 2 = 5`, + `5 / 3 = 5/3`, + `5i / 3i = 5/3`, + `? / 0 = ?`, + `0 / ? = ?`, + `0 * 1e+1000000000i = ?`, + + `0 % 0 = "runtime_error:_integer_divide_by_zero"`, // TODO(gri) should be the same as for / + `10 % 3 = 1`, + `? % 0 = ?`, + `0 % ? = ?`, + + `0 & 0 = 0`, + `12345 & 0 = 0`, + `0xff & 0xf = 0xf`, + `? & 0 = ?`, + `0 & ? = ?`, + + `0 | 0 = 0`, + `12345 | 0 = 12345`, + `0xb | 0xa0 = 0xab`, + `? | 0 = ?`, + `0 | ? = ?`, + + `0 ^ 0 = 0`, + `1 ^ -1 = -2`, + `? ^ 0 = ?`, + `0 ^ ? = ?`, + + `0 &^ 0 = 0`, + `0xf &^ 1 = 0xe`, + `1 &^ 0xf = 0`, + // etc. + + // shifts + `0 << 0 = 0`, + `1 << 10 = 1024`, + `0 >> 0 = 0`, + `1024 >> 10 == 1`, + `? << 0 == ?`, + `? >> 10 == ?`, + // etc. + + // comparisons + `false == false = true`, + `false == true = false`, + `true == false = false`, + `true == true = true`, + + `false != false = false`, + `false != true = true`, + `true != false = true`, + `true != true = false`, + + `"foo" == "bar" = false`, + `"foo" != "bar" = true`, + `"foo" < "bar" = false`, + `"foo" <= "bar" = false`, + `"foo" > "bar" = true`, + `"foo" >= "bar" = true`, + + `0 == 0 = true`, + `0 != 0 = false`, + `0 < 10 = true`, + `10 <= 10 = true`, + `0 > 10 = false`, + `10 >= 10 = true`, + + `1/123456789 == 1/123456789 == true`, + `1/123456789 != 1/123456789 == false`, + `1/123456789 < 1/123456788 == true`, + `1/123456788 <= 1/123456789 == false`, + `0.11 > 0.11 = false`, + `0.11 >= 0.11 = true`, + + `? == 0 = false`, + `? != 0 = false`, + `? < 10 = false`, + `? <= 10 = false`, + `? > 10 = false`, + `? >= 10 = false`, + + `0 == ? = false`, + `0 != ? = false`, + `0 < ? = false`, + `10 <= ? = false`, + `0 > ? = false`, + `10 >= ? = false`, + + // etc. +} + +func TestOps(t *testing.T) { + for _, test := range opTests { + a := strings.Split(test, " ") + i := 0 // operator index + + var x, x0 Value + switch len(a) { + case 4: + // unary operation + case 5: + // binary operation + x, x0 = val(a[0]), val(a[0]) + i = 1 + default: + t.Errorf("invalid test case: %s", test) + continue + } + + op, ok := optab[a[i]] + if !ok { + panic("missing optab entry for " + a[i]) + } + + y, y0 := val(a[i+1]), val(a[i+1]) + + got := doOp(x, op, y) + want := val(a[i+3]) + if !eql(got, want) { + t.Errorf("%s: got %s; want %s", test, got, want) + continue + } + + if x0 != nil && !eql(x, x0) { + t.Errorf("%s: x changed to %s", test, x) + continue + } + + if !eql(y, y0) { + t.Errorf("%s: y changed to %s", test, y) + continue + } + } +} + +func eql(x, y Value) bool { + _, ux := x.(unknownVal) + _, uy := y.(unknownVal) + if ux || uy { + return ux == uy + } + return Compare(x, token.EQL, y) +} + +// ---------------------------------------------------------------------------- +// String tests + +var xxx = strings.Repeat("x", 68) +var issue14262 = `"بموجب الشروط التالية نسب المصنف — يجب عليك أن تنسب العمل بالطريقة التي تحددها المؤلف أو المرخص (ولكن ليس بأي حال من الأحوال أن توحي وتقترح بتحول أو استخدامك للعمل). المشاركة على قدم المساواة — إذا كنت يعدل ، والتغيير ، أو الاستفادة من هذا العمل ، قد ينتج عن توزيع العمل إلا في ظل تشابه او تطابق فى واحد لهذا الترخيص."` + +var stringTests = []struct { + input, short, exact string +}{ + // Unknown + {"", "unknown", "unknown"}, + {"0x", "unknown", "unknown"}, + {"'", "unknown", "unknown"}, + {"1f0", "unknown", "unknown"}, + {"unknown", "unknown", "unknown"}, + + // Bool + {"true", "true", "true"}, + {"false", "false", "false"}, + + // String + {`""`, `""`, `""`}, + {`"foo"`, `"foo"`, `"foo"`}, + {`"` + xxx + `xx"`, `"` + xxx + `xx"`, `"` + xxx + `xx"`}, + {`"` + xxx + `xxx"`, `"` + xxx + `...`, `"` + xxx + `xxx"`}, + {`"` + xxx + xxx + `xxx"`, `"` + xxx + `...`, `"` + xxx + xxx + `xxx"`}, + {issue14262, `"بموجب الشروط التالية نسب المصنف — يجب عليك أن تنسب العمل بالطريقة ال...`, issue14262}, + + // Int + {"0", "0", "0"}, + {"-1", "-1", "-1"}, + {"12345", "12345", "12345"}, + {"-12345678901234567890", "-12345678901234567890", "-12345678901234567890"}, + {"12345678901234567890", "12345678901234567890", "12345678901234567890"}, + + // Float + {"0.", "0", "0"}, + {"-0.0", "0", "0"}, + {"10.0", "10", "10"}, + {"2.1", "2.1", "21/10"}, + {"-2.1", "-2.1", "-21/10"}, + {"1e9999", "1e+9999", "0x.f8d4a9da224650a8cb2959e10d985ad92adbd44c62917e608b1f24c0e1b76b6f61edffeb15c135a4b601637315f7662f325f82325422b244286a07663c9415d2p+33216"}, + {"1e-9999", "1e-9999", "0x.83b01ba6d8c0425eec1b21e96f7742d63c2653ed0a024cf8a2f9686df578d7b07d7a83d84df6a2ec70a921d1f6cd5574893a7eda4d28ee719e13a5dce2700759p-33215"}, + {"2.71828182845904523536028747135266249775724709369995957496696763", "2.71828", "271828182845904523536028747135266249775724709369995957496696763/100000000000000000000000000000000000000000000000000000000000000"}, + {"0e9999999999", "0", "0"}, // issue #16176 + {"-6e-1886451601", "0", "0"}, // issue #20228 + + // Complex + {"0i", "(0 + 0i)", "(0 + 0i)"}, + {"-0i", "(0 + 0i)", "(0 + 0i)"}, + {"10i", "(0 + 10i)", "(0 + 10i)"}, + {"-10i", "(0 + -10i)", "(0 + -10i)"}, + {"1e9999i", "(0 + 1e+9999i)", "(0 + 0x.f8d4a9da224650a8cb2959e10d985ad92adbd44c62917e608b1f24c0e1b76b6f61edffeb15c135a4b601637315f7662f325f82325422b244286a07663c9415d2p+33216i)"}, +} + +func TestString(t *testing.T) { + for _, test := range stringTests { + x := val(test.input) + if got := x.String(); got != test.short { + t.Errorf("%s: got %q; want %q as short string", test.input, got, test.short) + } + if got := x.ExactString(); got != test.exact { + t.Errorf("%s: got %q; want %q as exact string", test.input, got, test.exact) + } + } +} + +// ---------------------------------------------------------------------------- +// Support functions + +func val(lit string) Value { + if len(lit) == 0 { + return MakeUnknown() + } + + switch lit { + case "?": + return MakeUnknown() + case "true": + return MakeBool(true) + case "false": + return MakeBool(false) + } + + if i := strings.IndexByte(lit, '/'); i >= 0 { + // assume fraction + a := MakeFromLiteral(lit[:i], token.INT, 0) + b := MakeFromLiteral(lit[i+1:], token.INT, 0) + return BinaryOp(a, token.QUO, b) + } + + tok := token.INT + switch first, last := lit[0], lit[len(lit)-1]; { + case first == '"' || first == '`': + tok = token.STRING + lit = strings.ReplaceAll(lit, "_", " ") + case first == '\'': + tok = token.CHAR + case last == 'i': + tok = token.IMAG + default: + if !strings.HasPrefix(lit, "0x") && strings.ContainsAny(lit, "./Ee") { + tok = token.FLOAT + } + } + + return MakeFromLiteral(lit, tok, 0) +} + +var optab = map[string]token.Token{ + "!": token.NOT, + + "+": token.ADD, + "-": token.SUB, + "*": token.MUL, + "/": token.QUO, + "%": token.REM, + + "<<": token.SHL, + ">>": token.SHR, + + "&": token.AND, + "|": token.OR, + "^": token.XOR, + "&^": token.AND_NOT, + + "==": token.EQL, + "!=": token.NEQ, + "<": token.LSS, + "<=": token.LEQ, + ">": token.GTR, + ">=": token.GEQ, +} + +func panicHandler(v *Value) { + switch p := recover().(type) { + case nil: + // nothing to do + case string: + *v = MakeString(p) + case error: + *v = MakeString(p.Error()) + default: + panic(p) + } +} + +func doOp(x Value, op token.Token, y Value) (z Value) { + defer panicHandler(&z) + + if x == nil { + return UnaryOp(op, y, 0) + } + + switch op { + case token.EQL, token.NEQ, token.LSS, token.LEQ, token.GTR, token.GEQ: + return MakeBool(Compare(x, op, y)) + case token.SHL, token.SHR: + s, _ := Int64Val(y) + return Shift(x, op, uint(s)) + default: + return BinaryOp(x, op, y) + } +} + +// ---------------------------------------------------------------------------- +// Other tests + +var fracTests = []string{ + "0", + "1", + "-1", + "1.2", + "-0.991", + "2.718281828", + "3.14159265358979323e-10", + "1e100", + "1e1000", +} + +func TestFractions(t *testing.T) { + for _, test := range fracTests { + x := val(test) + // We don't check the actual numerator and denominator because they + // are unlikely to be 100% correct due to floatVal rounding errors. + // Instead, we compute the fraction again and compare the rounded + // result. + q := BinaryOp(Num(x), token.QUO, Denom(x)) + got := q.String() + want := x.String() + if got != want { + t.Errorf("%s: got quotient %s, want %s", x, got, want) + } + } +} + +var bytesTests = []string{ + "0", + "1", + "123456789", + "123456789012345678901234567890123456789012345678901234567890", +} + +func TestBytes(t *testing.T) { + for _, test := range bytesTests { + x := val(test) + bytes := Bytes(x) + + // special case 0 + if Sign(x) == 0 && len(bytes) != 0 { + t.Errorf("%s: got %v; want empty byte slice", test, bytes) + } + + if n := len(bytes); n > 0 && bytes[n-1] == 0 { + t.Errorf("%s: got %v; want no leading 0 byte", test, bytes) + } + + if got := MakeFromBytes(bytes); !eql(got, x) { + t.Errorf("%s: got %s; want %s (bytes = %v)", test, got, x, bytes) + } + } +} + +func TestUnknown(t *testing.T) { + u := MakeUnknown() + var values = []Value{ + u, + MakeBool(false), // token.ADD ok below, operation is never considered + MakeString(""), + MakeInt64(1), + MakeFromLiteral("''", token.CHAR, 0), + MakeFromLiteral("-1234567890123456789012345678901234567890", token.INT, 0), + MakeFloat64(1.2), + MakeImag(MakeFloat64(1.2)), + } + for _, val := range values { + x, y := val, u + for i := range [2]int{} { + if i == 1 { + x, y = y, x + } + if got := BinaryOp(x, token.ADD, y); got.Kind() != Unknown { + t.Errorf("%s + %s: got %s; want %s", x, y, got, u) + } + if got := Compare(x, token.EQL, y); got { + t.Errorf("%s == %s: got true; want false", x, y) + } + } + } +} + +func TestMakeFloat64(t *testing.T) { + var zero float64 + for _, arg := range []float64{ + -math.MaxFloat32, + -10, + -0.5, + -zero, + zero, + 1, + 10, + 123456789.87654321e-23, + 1e10, + math.MaxFloat64, + } { + val := MakeFloat64(arg) + if val.Kind() != Float { + t.Errorf("%v: got kind = %d; want %d", arg, val.Kind(), Float) + } + + // -0.0 is mapped to 0.0 + got, exact := Float64Val(val) + if !exact || math.Float64bits(got) != math.Float64bits(arg+0) { + t.Errorf("%v: got %v (exact = %v)", arg, got, exact) + } + } + + // infinity + for sign := range []int{-1, 1} { + arg := math.Inf(sign) + val := MakeFloat64(arg) + if val.Kind() != Unknown { + t.Errorf("%v: got kind = %d; want %d", arg, val.Kind(), Unknown) + } + } +} + +type makeTestCase struct { + kind Kind + arg, want interface{} +} + +func dup(k Kind, x interface{}) makeTestCase { return makeTestCase{k, x, x} } + +func TestMake(t *testing.T) { + for _, test := range []makeTestCase{ + {Bool, false, false}, + {String, "hello", "hello"}, + + {Int, int64(1), int64(1)}, + {Int, big.NewInt(10), int64(10)}, + {Int, new(big.Int).Lsh(big.NewInt(1), 62), int64(1 << 62)}, + dup(Int, new(big.Int).Lsh(big.NewInt(1), 63)), + + {Float, big.NewFloat(0), floatVal0.val}, + dup(Float, big.NewFloat(2.0)), + dup(Float, big.NewRat(1, 3)), + } { + val := Make(test.arg) + got := Val(val) + if val.Kind() != test.kind || got != test.want { + t.Errorf("got %v (%T, kind = %d); want %v (%T, kind = %d)", + got, got, val.Kind(), test.want, test.want, test.kind) + } + } +} + +func BenchmarkStringAdd(b *testing.B) { + for size := 1; size <= 65536; size *= 4 { + b.Run(fmt.Sprint(size), func(b *testing.B) { + b.ReportAllocs() + n := int64(0) + for i := 0; i < b.N; i++ { + x := MakeString(strings.Repeat("x", 100)) + y := x + for j := 0; j < size-1; j++ { + y = BinaryOp(y, token.ADD, x) + } + n += int64(len(StringVal(y))) + } + if n != int64(b.N)*int64(size)*100 { + b.Fatalf("bad string %d != %d", n, int64(b.N)*int64(size)*100) + } + }) + } +} + +var bitLenTests = []struct { + val int64 + want int +}{ + {0, 0}, + {1, 1}, + {-16, 5}, + {1 << 61, 62}, + {1 << 62, 63}, + {-1 << 62, 63}, + {-1 << 63, 64}, +} + +func TestBitLen(t *testing.T) { + for _, test := range bitLenTests { + if got := BitLen(MakeInt64(test.val)); got != test.want { + t.Errorf("%v: got %v, want %v", test.val, got, test.want) + } + } +} |