| /*- | |
| * Copyright (c) 1990, 1993 | |
| * The Regents of the University of California. All rights reserved. | |
| * | |
| * This code is derived from software contributed to Berkeley by | |
| * Chris Torek. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions | |
| * are met: | |
| * 1. Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * 2. Redistributions in binary form must reproduce the above copyright | |
| * notice, this list of conditions and the following disclaimer in the | |
| * documentation and/or other materials provided with the distribution. | |
| * 3. Neither the name of the University nor the names of its contributors | |
| * may be used to endorse or promote products derived from this software | |
| * without specific prior written permission. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
| * SUCH DAMAGE. | |
| */ | |
| #include <ctype.h> | |
| #include <stdlib.h> | |
| #include "local.h" | |
| #define BUF 513 /* Maximum length of numeric string. */ | |
| size_t parsefloat(FILE *fp, char *buf, char *end) { | |
| char *commit, *p; | |
| int infnanpos = 0; | |
| enum { | |
| S_START, S_GOTSIGN, S_INF, S_NAN, S_MAYBEHEX, | |
| S_DIGITS, S_FRAC, S_EXP, S_EXPDIGITS | |
| } state = S_START; | |
| unsigned char c; | |
| int gotmantdig = 0, ishex = 0; | |
| /* | |
| * We set commit = p whenever the string we have read so far | |
| * constitutes a valid representation of a floating point | |
| * number by itself. At some point, the parse will complete | |
| * or fail, and we will ungetc() back to the last commit point. | |
| * To ensure that the file offset gets updated properly, it is | |
| * always necessary to read at least one character that doesn't | |
| * match; thus, we can't short-circuit "infinity" or "nan(...)". | |
| */ | |
| commit = buf - 1; | |
| for (p = buf; p < end; ) { | |
| c = *fp->_p; | |
| reswitch: | |
| switch (state) { | |
| case S_START: | |
| state = S_GOTSIGN; | |
| if (c == '-' || c == '+') | |
| break; | |
| else | |
| goto reswitch; | |
| case S_GOTSIGN: | |
| switch (c) { | |
| case '0': | |
| state = S_MAYBEHEX; | |
| commit = p; | |
| break; | |
| case 'I': | |
| case 'i': | |
| state = S_INF; | |
| break; | |
| case 'N': | |
| case 'n': | |
| state = S_NAN; | |
| break; | |
| default: | |
| state = S_DIGITS; | |
| goto reswitch; | |
| } | |
| break; | |
| case S_INF: | |
| if (infnanpos > 6 || | |
| (c != "nfinity"[infnanpos] && | |
| c != "NFINITY"[infnanpos])) | |
| goto parsedone; | |
| if (infnanpos == 1 || infnanpos == 6) | |
| commit = p; /* inf or infinity */ | |
| infnanpos++; | |
| break; | |
| case S_NAN: | |
| switch (infnanpos) { | |
| case -1: /* XXX kludge to deal with nan(...) */ | |
| goto parsedone; | |
| case 0: | |
| if (c != 'A' && c != 'a') | |
| goto parsedone; | |
| break; | |
| case 1: | |
| if (c != 'N' && c != 'n') | |
| goto parsedone; | |
| else | |
| commit = p; | |
| break; | |
| case 2: | |
| if (c != '(') | |
| goto parsedone; | |
| break; | |
| default: | |
| if (c == ')') { | |
| commit = p; | |
| infnanpos = -2; | |
| } else if (!isalnum(c) && c != '_') | |
| goto parsedone; | |
| break; | |
| } | |
| infnanpos++; | |
| break; | |
| case S_MAYBEHEX: | |
| state = S_DIGITS; | |
| if (c == 'X' || c == 'x') { | |
| ishex = 1; | |
| break; | |
| } else { /* we saw a '0', but no 'x' */ | |
| gotmantdig = 1; | |
| goto reswitch; | |
| } | |
| case S_DIGITS: | |
| if ((ishex && isxdigit(c)) || isdigit(c)) | |
| gotmantdig = 1; | |
| else { | |
| state = S_FRAC; | |
| if (c != '.') | |
| goto reswitch; | |
| } | |
| if (gotmantdig) | |
| commit = p; | |
| break; | |
| case S_FRAC: | |
| if (((c == 'E' || c == 'e') && !ishex) || | |
| ((c == 'P' || c == 'p') && ishex)) { | |
| if (!gotmantdig) | |
| goto parsedone; | |
| else | |
| state = S_EXP; | |
| } else if ((ishex && isxdigit(c)) || isdigit(c)) { | |
| commit = p; | |
| gotmantdig = 1; | |
| } else | |
| goto parsedone; | |
| break; | |
| case S_EXP: | |
| state = S_EXPDIGITS; | |
| if (c == '-' || c == '+') | |
| break; | |
| else | |
| goto reswitch; | |
| case S_EXPDIGITS: | |
| if (isdigit(c)) | |
| commit = p; | |
| else | |
| goto parsedone; | |
| break; | |
| default: | |
| abort(); | |
| } | |
| *p++ = c; | |
| if (--fp->_r > 0) | |
| fp->_p++; | |
| else if (__srefill(fp)) | |
| break; /* EOF */ | |
| } | |
| parsedone: | |
| while (commit < --p) | |
| (void)ungetc(*(unsigned char *)p, fp); | |
| *++commit = '\0'; | |
| return commit - buf; | |
| } | |
| size_t wparsefloat(FILE *fp, wchar_t *buf, wchar_t *end) { | |
| wchar_t *commit, *p; | |
| int infnanpos = 0; | |
| enum { | |
| S_START, S_GOTSIGN, S_INF, S_NAN, S_MAYBEHEX, | |
| S_DIGITS, S_FRAC, S_EXP, S_EXPDIGITS | |
| } state = S_START; | |
| wint_t c; | |
| int gotmantdig = 0, ishex = 0; | |
| /* | |
| * We set commit = p whenever the string we have read so far | |
| * constitutes a valid representation of a floating point | |
| * number by itself. At some point, the parse will complete | |
| * or fail, and we will ungetc() back to the last commit point. | |
| * To ensure that the file offset gets updated properly, it is | |
| * always necessary to read at least one character that doesn't | |
| * match; thus, we can't short-circuit "infinity" or "nan(...)". | |
| */ | |
| commit = buf - 1; | |
| c = WEOF; | |
| for (p = buf; p < end; ) { | |
| if ((c = __fgetwc_unlock(fp)) == WEOF) | |
| break; | |
| reswitch: | |
| switch (state) { | |
| case S_START: | |
| state = S_GOTSIGN; | |
| if (c == '-' || c == '+') | |
| break; | |
| else | |
| goto reswitch; | |
| case S_GOTSIGN: | |
| switch (c) { | |
| case '0': | |
| state = S_MAYBEHEX; | |
| commit = p; | |
| break; | |
| case 'I': | |
| case 'i': | |
| state = S_INF; | |
| break; | |
| case 'N': | |
| case 'n': | |
| state = S_NAN; | |
| break; | |
| default: | |
| state = S_DIGITS; | |
| goto reswitch; | |
| } | |
| break; | |
| case S_INF: | |
| if (infnanpos > 6 || | |
| (c != (wint_t)"nfinity"[infnanpos] && | |
| c != (wint_t)"NFINITY"[infnanpos])) | |
| goto parsedone; | |
| if (infnanpos == 1 || infnanpos == 6) | |
| commit = p; /* inf or infinity */ | |
| infnanpos++; | |
| break; | |
| case S_NAN: | |
| switch (infnanpos) { | |
| case -1: /* XXX kludge to deal with nan(...) */ | |
| goto parsedone; | |
| case 0: | |
| if (c != 'A' && c != 'a') | |
| goto parsedone; | |
| break; | |
| case 1: | |
| if (c != 'N' && c != 'n') | |
| goto parsedone; | |
| else | |
| commit = p; | |
| break; | |
| case 2: | |
| if (c != '(') | |
| goto parsedone; | |
| break; | |
| default: | |
| if (c == ')') { | |
| commit = p; | |
| infnanpos = -2; | |
| } else if (!iswalnum(c) && c != '_') | |
| goto parsedone; | |
| break; | |
| } | |
| infnanpos++; | |
| break; | |
| case S_MAYBEHEX: | |
| state = S_DIGITS; | |
| if (c == 'X' || c == 'x') { | |
| ishex = 1; | |
| break; | |
| } else { /* we saw a '0', but no 'x' */ | |
| gotmantdig = 1; | |
| goto reswitch; | |
| } | |
| case S_DIGITS: | |
| if ((ishex && iswxdigit(c)) || iswdigit(c)) | |
| gotmantdig = 1; | |
| else { | |
| state = S_FRAC; | |
| if (c != L'.') | |
| goto reswitch; | |
| } | |
| if (gotmantdig) | |
| commit = p; | |
| break; | |
| case S_FRAC: | |
| if (((c == 'E' || c == 'e') && !ishex) || | |
| ((c == 'P' || c == 'p') && ishex)) { | |
| if (!gotmantdig) | |
| goto parsedone; | |
| else | |
| state = S_EXP; | |
| } else if ((ishex && iswxdigit(c)) || iswdigit(c)) { | |
| commit = p; | |
| gotmantdig = 1; | |
| } else | |
| goto parsedone; | |
| break; | |
| case S_EXP: | |
| state = S_EXPDIGITS; | |
| if (c == '-' || c == '+') | |
| break; | |
| else | |
| goto reswitch; | |
| case S_EXPDIGITS: | |
| if (iswdigit(c)) | |
| commit = p; | |
| else | |
| goto parsedone; | |
| break; | |
| default: | |
| abort(); | |
| } | |
| *p++ = c; | |
| c = WEOF; | |
| } | |
| parsedone: | |
| if (c != WEOF) | |
| ungetwc(c, fp); | |
| while (commit < --p) | |
| ungetwc(*p, fp); | |
| *++commit = '\0'; | |
| return (int)(commit - buf); | |
| } |