From 1f701200d979d550e13f37e9be3da99b5a1304cb Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 29 Jan 2019 15:41:50 -0800 Subject: [PATCH] go/constant: accept new Go2 number literals This CL introduces go/constant support for the new binary and octal integer literals, hexadecimal floats, and digit separators for all number literals. R=Go1.13 Updates #12711. Updates #19308. Updates #28493. Updates #29008. Change-Id: I7a55f91b8b6373ae6d98ba923b626d33c5552946 Reviewed-on: https://go-review.googlesource.com/c/160239 Reviewed-by: Russ Cox --- src/go/constant/value.go | 36 ++++++++- src/go/constant/value_test.go | 146 +++++++++++++++++++++++++++++++++- 2 files changed, 179 insertions(+), 3 deletions(-) diff --git a/src/go/constant/value.go b/src/go/constant/value.go index 0982243edb..f7efa95404 100644 --- a/src/go/constant/value.go +++ b/src/go/constant/value.go @@ -315,8 +315,9 @@ func makeFloatFromLiteral(lit string) Value { // but it'll take forever to parse as a Rat. lit = "0" } - r, _ := newRat().SetString(lit) - return ratVal{r} + if r, ok := newRat().SetString(lit); ok { + return ratVal{r} + } } // otherwise use floats return makeFloat(f) @@ -380,8 +381,17 @@ func MakeFromLiteral(lit string, tok token.Token, zero uint) Value { panic("MakeFromLiteral called with non-zero last argument") } + // TODO(gri) Remove stripSep and, for token.INT, 0o-octal handling + // below once strconv and math/big can handle separators + // and 0o-octals. + switch tok { case token.INT: + // TODO(gri) remove 0o-special case once strconv and math/big can handle 0o-octals + lit = stripSep(lit) + if len(lit) >= 2 && lit[0] == '0' && (lit[1] == 'o' || lit[1] == 'O') { + lit = "0" + lit[2:] + } if x, err := strconv.ParseInt(lit, 0, 64); err == nil { return int64Val(x) } @@ -390,11 +400,13 @@ func MakeFromLiteral(lit string, tok token.Token, zero uint) Value { } case token.FLOAT: + lit = stripSep(lit) if x := makeFloatFromLiteral(lit); x != nil { return x } case token.IMAG: + lit = stripSep(lit) if n := len(lit); n > 0 && lit[n-1] == 'i' { if im := makeFloatFromLiteral(lit[:n-1]); im != nil { return makeComplex(int64Val(0), im) @@ -420,6 +432,26 @@ func MakeFromLiteral(lit string, tok token.Token, zero uint) Value { return unknownVal{} } +func stripSep(s string) string { + // avoid making a copy if there are no separators (common case) + i := 0 + for i < len(s) && s[i] != '_' { + i++ + } + if i == len(s) { + return s + } + + // make a copy of s without separators + var buf []byte + for i := 0; i < len(s); i++ { + if c := s[i]; c != '_' { + buf = append(buf, c) + } + } + return string(buf) +} + // ---------------------------------------------------------------------------- // Accessors // diff --git a/src/go/constant/value_test.go b/src/go/constant/value_test.go index 68b87eaa55..560712a8f5 100644 --- a/src/go/constant/value_test.go +++ b/src/go/constant/value_test.go @@ -11,7 +11,151 @@ import ( "testing" ) -// TODO(gri) expand this test framework +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`, + + // 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`, +} + +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 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) + } + + xk := x.Kind() + yk := y.Kind() + if xk != yk || xk == Unknown { + t.Errorf("%s: got kind %d != %d", test, xk, yk) + 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