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 "stdio_impl.h"
44
parsefloat(FILE * f,char * buf,char * end)45 int parsefloat(FILE *f, char *buf, char *end)
46 {
47 char *commit, *p;
48 int infnanpos = 0, decptpos = 0;
49 enum {
50 S_START, S_GOTSIGN, S_INF, S_NAN, S_DONE, S_MAYBEHEX,
51 S_DIGITS, S_DECPT, S_FRAC, S_EXP, S_EXPDIGITS
52 } state = S_START;
53 unsigned char c;
54 int gotmantdig = 0, ishex = 0;
55 const char *decpt = "";
56
57 /*
58 * We set commit = p whenever the string we have read so far
59 * constitutes a valid representation of a floating point
60 * number by itself. At some point, the parse will complete
61 * or fail, and we will ungetc() back to the last commit point.
62 * To ensure that the file offset gets updated properly, it is
63 * always necessary to read at least one character that doesn't
64 * match; thus, we can't short-circuit "infinity" or "nan(...)".
65 */
66 commit = buf - 1;
67 for (p = buf; p < end; ) {
68 c = *f->rpos;
69 reswitch:
70 switch (state) {
71 case S_START:
72 state = S_GOTSIGN;
73 if (c == '-' || c == '+')
74 break;
75 else
76 goto reswitch;
77 case S_GOTSIGN:
78 switch (c) {
79 case '0':
80 state = S_MAYBEHEX;
81 commit = p;
82 break;
83 case 'I':
84 case 'i':
85 state = S_INF;
86 break;
87 case 'N':
88 case 'n':
89 state = S_NAN;
90 break;
91 default:
92 state = S_DIGITS;
93 goto reswitch;
94 }
95 break;
96 case S_INF:
97 if (infnanpos > 6 ||
98 (c != "nfinity"[infnanpos] &&
99 c != "NFINITY"[infnanpos]))
100 goto parsedone;
101 if (infnanpos == 1 || infnanpos == 6)
102 commit = p; /* inf or infinity */
103 infnanpos++;
104 break;
105 case S_NAN:
106 switch (infnanpos) {
107 case 0:
108 if (c != 'A' && c != 'a')
109 goto parsedone;
110 break;
111 case 1:
112 if (c != 'N' && c != 'n')
113 goto parsedone;
114 else
115 commit = p;
116 break;
117 case 2:
118 if (c != '(')
119 goto parsedone;
120 break;
121 default:
122 if (c == ')') {
123 commit = p;
124 infnanpos = -2;
125 } else if (!isalnum(c) && c != '_')
126 goto parsedone;
127 break;
128 }
129 infnanpos++;
130 break;
131 case S_DONE:
132 goto parsedone;
133 case S_MAYBEHEX:
134 state = S_DIGITS;
135 if (c == 'X' || c == 'x') {
136 ishex = 1;
137 break;
138 } else { /* we saw a '0', but no 'x' */
139 gotmantdig = 1;
140 goto reswitch;
141 }
142 case S_DIGITS:
143 if ((ishex && isxdigit(c)) || isdigit(c)) {
144 gotmantdig = 1;
145 } else {
146 state = S_FRAC;
147 if (c != '.')
148 goto reswitch;
149 }
150 if (gotmantdig)
151 commit = p;
152 break;
153 case S_DECPT:
154 if (c == decpt[decptpos]) {
155 if (decpt[++decptpos] == '\0') {
156 /* We read the complete decpt seq. */
157 state = S_FRAC;
158 if (gotmantdig)
159 commit = p;
160 }
161 break;
162 } else if (!decptpos) {
163 /* We didn't read any decpt characters. */
164 state = S_FRAC;
165 goto reswitch;
166 } else {
167 /*
168 * We read part of a multibyte decimal point,
169 * but the rest is invalid, so bail.
170 */
171 goto parsedone;
172 }
173 case S_FRAC:
174 if (((c == 'E' || c == 'e') && !ishex) ||
175 ((c == 'P' || c == 'p') && ishex)) {
176 if (!gotmantdig)
177 goto parsedone;
178 else
179 state = S_EXP;
180 } else if ((ishex && isxdigit(c)) || isdigit(c)) {
181 commit = p;
182 gotmantdig = 1;
183 } else
184 goto parsedone;
185 break;
186 case S_EXP:
187 state = S_EXPDIGITS;
188 if (c == '-' || c == '+')
189 break;
190 else
191 goto reswitch;
192 case S_EXPDIGITS:
193 if (isdigit(c))
194 commit = p;
195 else
196 goto parsedone;
197 break;
198 default:
199 abort();
200 }
201 *p++ = c;
202 if (f->rpos != f->shend)
203 f->rpos++;
204 else
205 break;
206 }
207
208 parsedone:
209 while (commit < --p)
210 --f->rpos;
211 *++commit = '\0';
212 return (commit - buf);
213 }
214