number.go 3.3 KB

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