blob: a2e08f96756348dd6db1705756b4f3a828d219b2 [file] [log] [blame]
Joe Tsai27c2a762018-08-01 16:48:18 -07001// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package text
6
7import (
8 "bytes"
9 "io"
10 "math"
11 "regexp"
12 "strconv"
13 "strings"
14
Damien Neile89e6242019-05-13 23:55:40 -070015 "google.golang.org/protobuf/internal/errors"
Joe Tsai27c2a762018-08-01 16:48:18 -070016)
17
Herbie Ong84f09602019-01-17 19:31:47 -080018// marshalNumber encodes v as either a Bool, Int, Uint, Float32, or Float64.
Joe Tsai27c2a762018-08-01 16:48:18 -070019func (p *encoder) marshalNumber(v Value) error {
20 var err error
21 p.out, err = appendNumber(p.out, v)
22 return err
23}
24func appendNumber(out []byte, v Value) ([]byte, error) {
25 if len(v.raw) > 0 {
26 switch v.Type() {
Herbie Ong84f09602019-01-17 19:31:47 -080027 case Bool, Int, Uint, Float32, Float64:
Joe Tsai27c2a762018-08-01 16:48:18 -070028 return append(out, v.raw...), nil
29 }
30 }
31 switch v.Type() {
32 case Bool:
33 if b, _ := v.Bool(); b {
34 return append(out, "true"...), nil
35 } else {
36 return append(out, "false"...), nil
37 }
38 case Int:
39 return strconv.AppendInt(out, int64(v.num), 10), nil
40 case Uint:
41 return strconv.AppendUint(out, uint64(v.num), 10), nil
Herbie Ong84f09602019-01-17 19:31:47 -080042 case Float32:
43 return appendFloat(out, v, 32)
44 case Float64:
45 return appendFloat(out, v, 64)
Joe Tsai27c2a762018-08-01 16:48:18 -070046 default:
47 return nil, errors.New("invalid type %v, expected bool or number", v.Type())
48 }
49}
50
Herbie Ong84f09602019-01-17 19:31:47 -080051func appendFloat(out []byte, v Value, bitSize int) ([]byte, error) {
52 switch n := math.Float64frombits(v.num); {
53 case math.IsNaN(n):
54 return append(out, "nan"...), nil
55 case math.IsInf(n, +1):
56 return append(out, "inf"...), nil
57 case math.IsInf(n, -1):
58 return append(out, "-inf"...), nil
59 default:
60 return strconv.AppendFloat(out, n, 'g', -1, bitSize), nil
61 }
62}
63
Joe Tsai27c2a762018-08-01 16:48:18 -070064// These regular expressions were derived by reverse engineering the C++ code
65// in tokenizer.cc and text_format.cc.
66var (
67 literals = map[string]interface{}{
68 // These exact literals are the ones supported in C++.
69 // In C++, a 1-bit unsigned integers is also allowed to represent
70 // a boolean. This is handled in Value.Bool.
71 "t": true,
72 "true": true,
73 "True": true,
74 "f": false,
75 "false": false,
76 "False": false,
77
78 // C++ permits "-nan" and the case-insensitive variants of these.
79 // However, Go continues to be case-sensitive.
80 "nan": math.NaN(),
81 "inf": math.Inf(+1),
82 "-inf": math.Inf(-1),
83 }
84 literalRegexp = regexp.MustCompile("^-?[a-zA-Z]+")
85 intRegexp = regexp.MustCompile("^-?([1-9][0-9]*|0[xX][0-9a-fA-F]+|0[0-7]*)")
86 floatRegexp = regexp.MustCompile("^-?((0|[1-9][0-9]*)?([.][0-9]*)?([eE][+-]?[0-9]+)?[fF]?)")
87)
88
Herbie Ong84f09602019-01-17 19:31:47 -080089// unmarshalNumber decodes a Bool, Int, Uint, or Float64 from the input.
Joe Tsai27c2a762018-08-01 16:48:18 -070090func (p *decoder) unmarshalNumber() (Value, error) {
91 v, n, err := consumeNumber(p.in)
92 p.consume(n)
93 return v, err
94}
95func consumeNumber(in []byte) (Value, int, error) {
96 if len(in) == 0 {
97 return Value{}, 0, io.ErrUnexpectedEOF
98 }
99 if n := matchWithDelim(literalRegexp, in); n > 0 {
100 if v, ok := literals[string(in[:n])]; ok {
101 return rawValueOf(v, in[:n:n]), n, nil
102 }
103 }
104 if n := matchWithDelim(floatRegexp, in); n > 0 {
105 if bytes.ContainsAny(in[:n], ".eEfF") {
106 s := strings.TrimRight(string(in[:n]), "fF")
Herbie Ong84f09602019-01-17 19:31:47 -0800107 // Always decode float as 64-bit.
Joe Tsai27c2a762018-08-01 16:48:18 -0700108 f, err := strconv.ParseFloat(s, 64)
109 if err != nil {
110 return Value{}, 0, err
111 }
112 return rawValueOf(f, in[:n:n]), n, nil
113 }
114 }
115 if n := matchWithDelim(intRegexp, in); n > 0 {
116 if in[0] == '-' {
117 v, err := strconv.ParseInt(string(in[:n]), 0, 64)
118 if err != nil {
119 return Value{}, 0, err
120 }
121 return rawValueOf(v, in[:n:n]), n, nil
122 } else {
123 v, err := strconv.ParseUint(string(in[:n]), 0, 64)
124 if err != nil {
125 return Value{}, 0, err
126 }
127 return rawValueOf(v, in[:n:n]), n, nil
128 }
129 }
130 return Value{}, 0, newSyntaxError("invalid %q as number or bool", errRegexp.Find(in))
131}