blob: 81a5d8c861390d27188737715537b3a33332bb55 [file] [log] [blame]
khenaidoo5fc5cea2021-08-11 17:39:16 -04001// 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
7// parseNumberValue parses a number from the input and returns a Token object.
8func (d *Decoder) parseNumberValue() (Token, bool) {
9 in := d.in
10 num := parseNumber(in)
11 if num.size == 0 {
12 return Token{}, false
13 }
14 numAttrs := num.kind
15 if num.neg {
16 numAttrs |= isNegative
17 }
18 strSize := num.size
19 last := num.size - 1
20 if num.kind == numFloat && (d.in[last] == 'f' || d.in[last] == 'F') {
21 strSize = last
22 }
23 tok := Token{
24 kind: Scalar,
25 attrs: numberValue,
26 pos: len(d.orig) - len(d.in),
27 raw: d.in[:num.size],
28 str: string(d.in[:strSize]),
29 numAttrs: numAttrs,
30 }
31 d.consume(num.size)
32 return tok, true
33}
34
35const (
36 numDec uint8 = (1 << iota) / 2
37 numHex
38 numOct
39 numFloat
40)
41
42// number is the result of parsing out a valid number from parseNumber. It
43// contains data for doing float or integer conversion via the strconv package
44// in conjunction with the input bytes.
45type number struct {
46 kind uint8
47 neg bool
48 size int
49}
50
51// parseNumber constructs a number object from given input. It allows for the
52// following patterns:
Joey Armstrongba3d9d12024-01-15 14:22:11 -050053//
54// integer: ^-?([1-9][0-9]*|0[xX][0-9a-fA-F]+|0[0-7]*)
55// float: ^-?((0|[1-9][0-9]*)?([.][0-9]*)?([eE][+-]?[0-9]+)?[fF]?)
56//
khenaidoo5fc5cea2021-08-11 17:39:16 -040057// It also returns the number of parsed bytes for the given number, 0 if it is
58// not a number.
59func parseNumber(input []byte) number {
60 kind := numDec
61 var size int
62 var neg bool
63
64 s := input
65 if len(s) == 0 {
66 return number{}
67 }
68
69 // Optional -
70 if s[0] == '-' {
71 neg = true
72 s = s[1:]
73 size++
74 if len(s) == 0 {
75 return number{}
76 }
77 }
78
79 // C++ allows for whitespace and comments in between the negative sign and
80 // the rest of the number. This logic currently does not but is consistent
81 // with v1.
82
83 switch {
84 case s[0] == '0':
85 if len(s) > 1 {
86 switch {
87 case s[1] == 'x' || s[1] == 'X':
88 // Parse as hex number.
89 kind = numHex
90 n := 2
91 s = s[2:]
92 for len(s) > 0 && (('0' <= s[0] && s[0] <= '9') ||
93 ('a' <= s[0] && s[0] <= 'f') ||
94 ('A' <= s[0] && s[0] <= 'F')) {
95 s = s[1:]
96 n++
97 }
98 if n == 2 {
99 return number{}
100 }
101 size += n
102
103 case '0' <= s[1] && s[1] <= '7':
104 // Parse as octal number.
105 kind = numOct
106 n := 2
107 s = s[2:]
108 for len(s) > 0 && '0' <= s[0] && s[0] <= '7' {
109 s = s[1:]
110 n++
111 }
112 size += n
113 }
114
115 if kind&(numHex|numOct) > 0 {
116 if len(s) > 0 && !isDelim(s[0]) {
117 return number{}
118 }
119 return number{kind: kind, neg: neg, size: size}
120 }
121 }
122 s = s[1:]
123 size++
124
125 case '1' <= s[0] && s[0] <= '9':
126 n := 1
127 s = s[1:]
128 for len(s) > 0 && '0' <= s[0] && s[0] <= '9' {
129 s = s[1:]
130 n++
131 }
132 size += n
133
134 case s[0] == '.':
135 // Set kind to numFloat to signify the intent to parse as float. And
136 // that it needs to have other digits after '.'.
137 kind = numFloat
138
139 default:
140 return number{}
141 }
142
143 // . followed by 0 or more digits.
144 if len(s) > 0 && s[0] == '.' {
145 n := 1
146 s = s[1:]
147 // If decimal point was before any digits, it should be followed by
148 // other digits.
149 if len(s) == 0 && kind == numFloat {
150 return number{}
151 }
152 for len(s) > 0 && '0' <= s[0] && s[0] <= '9' {
153 s = s[1:]
154 n++
155 }
156 size += n
157 kind = numFloat
158 }
159
160 // e or E followed by an optional - or + and 1 or more digits.
161 if len(s) >= 2 && (s[0] == 'e' || s[0] == 'E') {
162 kind = numFloat
163 s = s[1:]
164 n := 1
165 if s[0] == '+' || s[0] == '-' {
166 s = s[1:]
167 n++
168 if len(s) == 0 {
169 return number{}
170 }
171 }
172 for len(s) > 0 && '0' <= s[0] && s[0] <= '9' {
173 s = s[1:]
174 n++
175 }
176 size += n
177 }
178
179 // Optional suffix f or F for floats.
180 if len(s) > 0 && (s[0] == 'f' || s[0] == 'F') {
181 kind = numFloat
182 s = s[1:]
183 size++
184 }
185
186 // Check that next byte is a delimiter or it is at the end.
187 if len(s) > 0 && !isDelim(s[0]) {
188 return number{}
189 }
190
191 return number{kind: kind, neg: neg, size: size}
192}