| // 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 text |
| |
| import ( |
| "bytes" |
| "io" |
| "math" |
| "strconv" |
| |
| "google.golang.org/protobuf/internal/errors" |
| ) |
| |
| // marshalNumber encodes v as either a Bool, Int, Uint, Float32, or Float64. |
| func (p *encoder) marshalNumber(v Value) error { |
| var err error |
| p.out, err = appendNumber(p.out, v) |
| return err |
| } |
| func appendNumber(out []byte, v Value) ([]byte, error) { |
| if len(v.raw) > 0 { |
| switch v.Type() { |
| case Bool, Int, Uint, Float32, Float64: |
| return append(out, v.raw...), nil |
| } |
| } |
| switch v.Type() { |
| case Bool: |
| if b, _ := v.Bool(); b { |
| return append(out, "true"...), nil |
| } else { |
| return append(out, "false"...), nil |
| } |
| case Int: |
| return strconv.AppendInt(out, int64(v.num), 10), nil |
| case Uint: |
| return strconv.AppendUint(out, uint64(v.num), 10), nil |
| case Float32: |
| return appendFloat(out, v, 32) |
| case Float64: |
| return appendFloat(out, v, 64) |
| default: |
| return nil, errors.New("invalid type %v, expected bool or number", v.Type()) |
| } |
| } |
| |
| func appendFloat(out []byte, v Value, bitSize int) ([]byte, error) { |
| switch n := math.Float64frombits(v.num); { |
| case math.IsNaN(n): |
| return append(out, "nan"...), nil |
| case math.IsInf(n, +1): |
| return append(out, "inf"...), nil |
| case math.IsInf(n, -1): |
| return append(out, "-inf"...), nil |
| default: |
| return strconv.AppendFloat(out, n, 'g', -1, bitSize), nil |
| } |
| } |
| |
| // These regular expressions were derived by reverse engineering the C++ code |
| // in tokenizer.cc and text_format.cc. |
| var ( |
| literals = map[string]interface{}{ |
| // These exact literals are the ones supported in C++. |
| // In C++, a 1-bit unsigned integers is also allowed to represent |
| // a boolean. This is handled in Value.Bool. |
| "t": true, |
| "true": true, |
| "True": true, |
| "f": false, |
| "false": false, |
| "False": false, |
| |
| // C++ permits "-nan" and the case-insensitive variants of these. |
| // However, Go continues to be case-sensitive. |
| "nan": math.NaN(), |
| "inf": math.Inf(+1), |
| "-inf": math.Inf(-1), |
| } |
| ) |
| |
| // unmarshalNumber decodes a Bool, Int, Uint, or Float64 from the input. |
| func (p *decoder) unmarshalNumber() (Value, error) { |
| v, n, err := consumeNumber(p.in) |
| p.consume(n) |
| return v, err |
| } |
| |
| func consumeNumber(in []byte) (Value, int, error) { |
| if len(in) == 0 { |
| return Value{}, 0, io.ErrUnexpectedEOF |
| } |
| if v, n := matchLiteral(in); n > 0 { |
| return rawValueOf(v, in[:n]), n, nil |
| } |
| |
| num, ok := parseNumber(in) |
| if !ok { |
| return Value{}, 0, newSyntaxError("invalid %q as number or bool", errRegexp.Find(in)) |
| } |
| |
| if num.typ == numFloat { |
| f, err := strconv.ParseFloat(string(num.value), 64) |
| if err != nil { |
| return Value{}, 0, err |
| } |
| return rawValueOf(f, in[:num.size]), num.size, nil |
| } |
| |
| if num.neg { |
| v, err := strconv.ParseInt(string(num.value), 0, 64) |
| if err != nil { |
| return Value{}, 0, err |
| } |
| return rawValueOf(v, num.value), num.size, nil |
| } |
| v, err := strconv.ParseUint(string(num.value), 0, 64) |
| if err != nil { |
| return Value{}, 0, err |
| } |
| return rawValueOf(v, num.value), num.size, nil |
| } |
| |
| func matchLiteral(in []byte) (interface{}, int) { |
| switch in[0] { |
| case 't', 'T': |
| rest := in[1:] |
| if len(rest) == 0 || isDelim(rest[0]) { |
| return true, 1 |
| } |
| if n := matchStringWithDelim("rue", rest); n > 0 { |
| return true, 4 |
| } |
| case 'f', 'F': |
| rest := in[1:] |
| if len(rest) == 0 || isDelim(rest[0]) { |
| return false, 1 |
| } |
| if n := matchStringWithDelim("alse", rest); n > 0 { |
| return false, 5 |
| } |
| case 'n': |
| if n := matchStringWithDelim("nan", in); n > 0 { |
| return math.NaN(), 3 |
| } |
| case 'i': |
| if n := matchStringWithDelim("inf", in); n > 0 { |
| return math.Inf(1), 3 |
| } |
| case '-': |
| if n := matchStringWithDelim("-inf", in); n > 0 { |
| return math.Inf(-1), 4 |
| } |
| } |
| return nil, 0 |
| } |
| |
| func matchStringWithDelim(s string, b []byte) int { |
| if !bytes.HasPrefix(b, []byte(s)) { |
| return 0 |
| } |
| |
| n := len(s) |
| if n < len(b) && !isDelim(b[n]) { |
| return 0 |
| } |
| return n |
| } |
| |
| type numType uint8 |
| |
| const ( |
| numDec numType = (1 << iota) / 2 |
| numHex |
| numOct |
| numFloat |
| ) |
| |
| // number is the result of parsing out a valid number from parseNumber. It |
| // contains data for doing float or integer conversion via the strconv package. |
| type number struct { |
| typ numType |
| neg bool |
| // Size of input taken up by the number. This may not be the same as |
| // len(number.value). |
| size int |
| // Bytes for doing strconv.Parse{Float,Int,Uint} conversion. |
| value []byte |
| } |
| |
| // parseNumber constructs a number object from given input. It allows for the |
| // following patterns: |
| // integer: ^-?([1-9][0-9]*|0[xX][0-9a-fA-F]+|0[0-7]*) |
| // float: ^-?((0|[1-9][0-9]*)?([.][0-9]*)?([eE][+-]?[0-9]+)?[fF]?) |
| func parseNumber(input []byte) (number, bool) { |
| var size int |
| var neg bool |
| typ := numDec |
| |
| s := input |
| if len(s) == 0 { |
| return number{}, false |
| } |
| |
| // Optional - |
| if s[0] == '-' { |
| neg = true |
| s = s[1:] |
| size++ |
| if len(s) == 0 { |
| return number{}, false |
| } |
| } |
| |
| // C++ allows for whitespace and comments in between the negative sign and |
| // the rest of the number. This logic currently does not but is consistent |
| // with v1. |
| |
| switch { |
| case s[0] == '0': |
| if len(s) > 1 { |
| switch { |
| case s[1] == 'x' || s[1] == 'X': |
| // Parse as hex number. |
| typ = numHex |
| n := 2 |
| s = s[2:] |
| for len(s) > 0 && (('0' <= s[0] && s[0] <= '9') || |
| ('a' <= s[0] && s[0] <= 'f') || |
| ('A' <= s[0] && s[0] <= 'F')) { |
| s = s[1:] |
| n++ |
| } |
| if n == 2 { |
| return number{}, false |
| } |
| size += n |
| |
| case '0' <= s[1] && s[1] <= '7': |
| // Parse as octal number. |
| typ = numOct |
| n := 2 |
| s = s[2:] |
| for len(s) > 0 && '0' <= s[0] && s[0] <= '7' { |
| s = s[1:] |
| n++ |
| } |
| size += n |
| } |
| |
| if typ&(numHex|numOct) > 0 { |
| if len(s) > 0 && !isDelim(s[0]) { |
| return number{}, false |
| } |
| return number{ |
| typ: typ, |
| size: size, |
| neg: neg, |
| value: input[:size], |
| }, true |
| } |
| } |
| s = s[1:] |
| size++ |
| |
| case '1' <= s[0] && s[0] <= '9': |
| n := 1 |
| s = s[1:] |
| for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { |
| s = s[1:] |
| n++ |
| } |
| size += n |
| |
| case s[0] == '.': |
| // Handled below. |
| |
| default: |
| return number{}, false |
| } |
| |
| // . followed by 0 or more digits. |
| if len(s) > 0 && s[0] == '.' { |
| typ = numFloat |
| n := 1 |
| s = s[1:] |
| for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { |
| s = s[1:] |
| n++ |
| } |
| size += n |
| } |
| |
| // e or E followed by an optional - or + and 1 or more digits. |
| if len(s) >= 2 && (s[0] == 'e' || s[0] == 'E') { |
| typ = numFloat |
| s = s[1:] |
| n := 1 |
| if s[0] == '+' || s[0] == '-' { |
| s = s[1:] |
| n++ |
| if len(s) == 0 { |
| return number{}, false |
| } |
| } |
| for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { |
| s = s[1:] |
| n++ |
| } |
| size += n |
| } |
| |
| // At this point, input[:size] contains a valid number that can be converted |
| // via strconv.Parse{Float,Int,Uint}. |
| value := input[:size] |
| |
| // Optional suffix f or F for floats. |
| if len(s) > 0 && (s[0] == 'f' || s[0] == 'F') { |
| typ = numFloat |
| s = s[1:] |
| size++ |
| } |
| |
| // Check that next byte is a delimiter or it is at the end. |
| if len(s) > 0 && !isDelim(s[0]) { |
| return number{}, false |
| } |
| |
| return number{ |
| typ: typ, |
| size: size, |
| neg: neg, |
| value: value, |
| }, true |
| } |