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