blob: 7a1be93839b4abc24432f1d3b6b74d5a481cbcf1 [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
15 "google.golang.org/proto/internal/errors"
16)
17
18// marshalNumber encodes v as either a Bool, Int, Uint, or Float.
19func (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() {
27 case Bool, Int, Uint, Float:
28 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
42 case Float:
43 switch n := math.Float64frombits(v.num); {
44 case math.IsNaN(n):
45 return append(out, "nan"...), nil
46 case math.IsInf(n, +1):
47 return append(out, "inf"...), nil
48 case math.IsInf(n, -1):
49 return append(out, "-inf"...), nil
50 default:
51 return strconv.AppendFloat(out, n, 'g', -1, 64), nil
52 }
53 default:
54 return nil, errors.New("invalid type %v, expected bool or number", v.Type())
55 }
56}
57
58// These regular expressions were derived by reverse engineering the C++ code
59// in tokenizer.cc and text_format.cc.
60var (
61 literals = map[string]interface{}{
62 // These exact literals are the ones supported in C++.
63 // In C++, a 1-bit unsigned integers is also allowed to represent
64 // a boolean. This is handled in Value.Bool.
65 "t": true,
66 "true": true,
67 "True": true,
68 "f": false,
69 "false": false,
70 "False": false,
71
72 // C++ permits "-nan" and the case-insensitive variants of these.
73 // However, Go continues to be case-sensitive.
74 "nan": math.NaN(),
75 "inf": math.Inf(+1),
76 "-inf": math.Inf(-1),
77 }
78 literalRegexp = regexp.MustCompile("^-?[a-zA-Z]+")
79 intRegexp = regexp.MustCompile("^-?([1-9][0-9]*|0[xX][0-9a-fA-F]+|0[0-7]*)")
80 floatRegexp = regexp.MustCompile("^-?((0|[1-9][0-9]*)?([.][0-9]*)?([eE][+-]?[0-9]+)?[fF]?)")
81)
82
83// unmarshalNumber decodes a Bool, Int, Uint, or Float from the input.
84func (p *decoder) unmarshalNumber() (Value, error) {
85 v, n, err := consumeNumber(p.in)
86 p.consume(n)
87 return v, err
88}
89func consumeNumber(in []byte) (Value, int, error) {
90 if len(in) == 0 {
91 return Value{}, 0, io.ErrUnexpectedEOF
92 }
93 if n := matchWithDelim(literalRegexp, in); n > 0 {
94 if v, ok := literals[string(in[:n])]; ok {
95 return rawValueOf(v, in[:n:n]), n, nil
96 }
97 }
98 if n := matchWithDelim(floatRegexp, in); n > 0 {
99 if bytes.ContainsAny(in[:n], ".eEfF") {
100 s := strings.TrimRight(string(in[:n]), "fF")
101 f, err := strconv.ParseFloat(s, 64)
102 if err != nil {
103 return Value{}, 0, err
104 }
105 return rawValueOf(f, in[:n:n]), n, nil
106 }
107 }
108 if n := matchWithDelim(intRegexp, in); n > 0 {
109 if in[0] == '-' {
110 v, err := strconv.ParseInt(string(in[:n]), 0, 64)
111 if err != nil {
112 return Value{}, 0, err
113 }
114 return rawValueOf(v, in[:n:n]), n, nil
115 } else {
116 v, err := strconv.ParseUint(string(in[:n]), 0, 64)
117 if err != nil {
118 return Value{}, 0, err
119 }
120 return rawValueOf(v, in[:n:n]), n, nil
121 }
122 }
123 return Value{}, 0, newSyntaxError("invalid %q as number or bool", errRegexp.Find(in))
124}