1 /*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1990, 1993
5 * The Regents of the University of California. All rights reserved.
6 *
7 * Copyright (c) 2011 The FreeBSD Foundation
8 * All rights reserved.
9 * Portions of this software were developed by David Chisnall
10 * under sponsorship from the FreeBSD Foundation.
11 *
12 * This code is derived from software contributed to Berkeley by
13 * Chris Torek.
14 *
15 * Redistribution and use in source and binary forms, with or without
16 * modification, are permitted provided that the following conditions
17 * are met:
18 * 1. Redistributions of source code must retain the above copyright
19 * notice, this list of conditions and the following disclaimer.
20 * 2. Redistributions in binary form must reproduce the above copyright
21 * notice, this list of conditions and the following disclaimer in the
22 * documentation and/or other materials provided with the distribution.
23 * 3. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
26 *
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
38 */
39
40 #include <ctype.h>
41 #include <stdlib.h>
42 #include <stdio.h>
43 #include "shgetc.h"
44 #include "stdio_impl.h"
45
parsefloat(FILE * f,char * buf,char * end)46 int parsefloat(FILE *f, char *buf, char *end)
47 {
48 char *commit, *p;
49 int infnanpos = 0, decptpos = 0;
50 enum {
51 S_START, S_GOTSIGN, S_INF, S_NAN, S_DONE, S_MAYBEHEX,
52 S_DIGITS, S_DECPT, S_FRAC, S_EXP, S_EXPDIGITS
53 } state = S_START;
54 unsigned char c;
55 int gotmantdig = 0, ishex = 0;
56 const char *decpt = "";
57
58 /*
59 * We set commit = p whenever the string we have read so far
60 * constitutes a valid representation of a floating point
61 * number by itself. At some point, the parse will complete
62 * or fail, and we will ungetc() back to the last commit point.
63 * To ensure that the file offset gets updated properly, it is
64 * always necessary to read at least one character that doesn't
65 * match; thus, we can't short-circuit "infinity" or "nan(...)".
66 */
67 commit = buf - 1;
68 for (p = buf; p < end; ) {
69 // When file buffer has no content to read, it need to refill buf again.
70 if (shgetc(f) < 0) {
71 break;
72 } else {
73 shunget(f);
74 }
75 c = *f->rpos;
76 reswitch:
77 switch (state) {
78 case S_START:
79 state = S_GOTSIGN;
80 if (c == '-' || c == '+')
81 break;
82 else
83 goto reswitch;
84 case S_GOTSIGN:
85 switch (c) {
86 case '0':
87 state = S_MAYBEHEX;
88 commit = p;
89 break;
90 case 'I':
91 case 'i':
92 state = S_INF;
93 break;
94 case 'N':
95 case 'n':
96 state = S_NAN;
97 break;
98 default:
99 state = S_DIGITS;
100 goto reswitch;
101 }
102 break;
103 case S_INF:
104 if (infnanpos > 6 ||
105 (c != "nfinity"[infnanpos] &&
106 c != "NFINITY"[infnanpos]))
107 goto parsedone;
108 if (infnanpos == 1 || infnanpos == 6)
109 commit = p; /* inf or infinity */
110 infnanpos++;
111 break;
112 case S_NAN:
113 switch (infnanpos) {
114 case 0:
115 if (c != 'A' && c != 'a')
116 goto parsedone;
117 break;
118 case 1:
119 if (c != 'N' && c != 'n')
120 goto parsedone;
121 else
122 commit = p;
123 break;
124 case 2:
125 if (c != '(')
126 goto parsedone;
127 break;
128 default:
129 if (c == ')') {
130 commit = p;
131 infnanpos = -2;
132 } else if (!isalnum(c) && c != '_')
133 goto parsedone;
134 break;
135 }
136 infnanpos++;
137 break;
138 case S_DONE:
139 goto parsedone;
140 case S_MAYBEHEX:
141 state = S_DIGITS;
142 if (c == 'X' || c == 'x') {
143 ishex = 1;
144 break;
145 } else { /* we saw a '0', but no 'x' */
146 gotmantdig = 1;
147 goto reswitch;
148 }
149 case S_DIGITS:
150 if ((ishex && isxdigit(c)) || isdigit(c)) {
151 gotmantdig = 1;
152 } else {
153 state = S_FRAC;
154 if (c != '.')
155 goto reswitch;
156 }
157 if (gotmantdig)
158 commit = p;
159 break;
160 case S_DECPT:
161 if (c == decpt[decptpos]) {
162 if (decpt[++decptpos] == '\0') {
163 /* We read the complete decpt seq. */
164 state = S_FRAC;
165 if (gotmantdig)
166 commit = p;
167 }
168 break;
169 } else if (!decptpos) {
170 /* We didn't read any decpt characters. */
171 state = S_FRAC;
172 goto reswitch;
173 } else {
174 /*
175 * We read part of a multibyte decimal point,
176 * but the rest is invalid, so bail.
177 */
178 goto parsedone;
179 }
180 case S_FRAC:
181 if (((c == 'E' || c == 'e') && !ishex) ||
182 ((c == 'P' || c == 'p') && ishex)) {
183 if (!gotmantdig)
184 goto parsedone;
185 else
186 state = S_EXP;
187 } else if ((ishex && isxdigit(c)) || isdigit(c)) {
188 commit = p;
189 gotmantdig = 1;
190 } else
191 goto parsedone;
192 break;
193 case S_EXP:
194 state = S_EXPDIGITS;
195 if (c == '-' || c == '+')
196 break;
197 else
198 goto reswitch;
199 case S_EXPDIGITS:
200 if (isdigit(c))
201 commit = p;
202 else
203 goto parsedone;
204 break;
205 default:
206 abort();
207 }
208 *p++ = c;
209 // When file buffer has no content to read, it need to refill buf again.
210 if (shgetc(f) < 0) {
211 break;
212 } else {
213 shunget(f);
214 }
215 f->rpos++;
216 }
217
218 parsedone:
219 while (commit < --p)
220 shunget(f);
221 *++commit = '\0';
222 return (commit - buf);
223 }
224