I noticed that OpenBSD's fscanf doesn't yet support hex float strings,
which are standardized in C99. I am using them in my application (which
I would like to support OpenBSD), since the "%a" format specifier is a
convenient way to preserve the exact floating point value.

strtod already supports parsing hex floats, so it is just the scanner
in __svfscanf that needed changes.

The implementation reuses the PFXOK and NZDIGITS flags from CT_INT
scanning and follows similar logic to CT_INT. This required allocating
new flag values for DPTOK and EXPOK.

I did my best to follow style(9), but since the indentation level of this
switch is so high, I found it difficult wrap lines nicely. I noticed that
several existing lines broke the "space around binary operators" rule if
the added space would require unnatural wrapping, so I did the same here.

I wasn't sure which comments I should carry over from the CT_INT case
(for example, above `case 'x':`), or if any of the other changes require
additional comments. Please let me know if they do.

diff --git lib/libc/stdio/vfscanf.c lib/libc/stdio/vfscanf.c
index 5fb55d99e61..87134a9ef86 100644
--- lib/libc/stdio/vfscanf.c
+++ lib/libc/stdio/vfscanf.c
@@ -66,19 +66,19 @@
 
 /*
  * The following are used in numeric conversions only:
- * SIGNOK, HAVESIGN, NDIGITS, DPTOK, and EXPOK are for floating point;
- * SIGNOK, HAVESIGN, NDIGITS, PFXOK, and NZDIGITS are for integral.
+ * DPTOK and EXPOK are for floating point;
+ * SIGNOK, HAVESIGN, NDIGITS, PFXOK, and NZDIGITS are for integral and floating
+ * point.
  */
 #define        SIGNOK          0x01000 /* +/- is (still) legal */
 #define        HAVESIGN        0x02000 /* sign detected */
 #define        NDIGITS         0x04000 /* no digits detected */
-
-#define        DPTOK           0x08000 /* (float) decimal point is still legal 
*/
-#define        EXPOK           0x10000 /* (float) exponent (e+3, etc) still 
legal */
-
 #define        PFXOK           0x08000 /* 0x prefix is (still) legal */
 #define        NZDIGITS        0x10000 /* no zero digits detected */
 
+#define        DPTOK           0x20000 /* (float) decimal point is still legal 
*/
+#define        EXPOK           0x40000 /* (float) exponent (e+3, etc) still 
legal */
+
 /*
  * Conversion types.
  */
@@ -770,7 +770,8 @@ literal:
                                width = sizeof(buf) - 2;
                        width++;
 #endif
-                       flags |= SIGNOK | NDIGITS | DPTOK | EXPOK;
+                       flags |= SIGNOK | NDIGITS | NZDIGITS | DPTOK | EXPOK;
+                       base = 10;
                        for (p = buf; width; width--) {
                                c = *fp->_p;
                                /*
@@ -779,15 +780,36 @@ literal:
                                 */
                                switch (c) {
 
-                               case '0': case '1': case '2': case '3':
+                               case '0':
+                                       if ((flags&(NZDIGITS|NDIGITS|DPTOK)) ==
+                                           (NZDIGITS|NDIGITS|DPTOK))
+                                               flags |= PFXOK;
+                                       else
+                                               flags &= ~PFXOK;
+                                       flags &=
+                                           ~(SIGNOK | NZDIGITS | NDIGITS);
+                                       goto fok;
+
+                               case '1': case '2': case '3':
                                case '4': case '5': case '6': case '7':
                                case '8': case '9':
-                                       flags &= ~(SIGNOK | NDIGITS);
+                                       flags &= ~(SIGNOK | PFXOK | NDIGITS);
+                                       goto fok;
+
+                               /* letters ok iff hex */
+                               case 'A': case 'B': case 'C':
+                               case 'D': case 'F':
+                               case 'a': case 'b': case 'c':
+                               case 'd': case 'f':
+                                       if (base == 10)
+                                               break;  /* not legal here */
+                                       flags &= ~(SIGNOK | PFXOK | NDIGITS);
                                        goto fok;
 
                                case '+': case '-':
                                        if (flags & SIGNOK) {
                                                flags &= ~SIGNOK;
+                                               flags |= HAVESIGN;
                                                goto fok;
                                        }
                                        break;
@@ -799,11 +821,35 @@ literal:
                                        break;
                                case 'e': case 'E':
                                        /* no exponent without some digits */
-                                       if ((flags&(NDIGITS|EXPOK)) == EXPOK) {
+                                       if (base == 10 &&
+                                           (flags&(NDIGITS|EXPOK)) == EXPOK) {
                                                flags =
                                                    (flags & ~(EXPOK|DPTOK)) |
                                                    SIGNOK | NDIGITS;
                                                goto fok;
+                                       } else if (base == 16) {
+                                               flags &=
+                                                   ~(SIGNOK|PFXOK|NDIGITS);
+                                               goto fok;
+                                       }
+                                       break;
+                               case 'p': case 'P':
+                                       /* no exponent without some digits */
+                                       if (base == 16 &&
+                                           (flags&(NDIGITS|EXPOK)) == EXPOK) {
+                                               flags =
+                                                   (flags & ~(EXPOK|DPTOK)) |
+                                                   SIGNOK | NDIGITS;
+                                               base = 10;
+                                               goto fok;
+                                       }
+                                       break;
+                               case 'x': case 'X':
+                                       if ((flags & PFXOK) && p ==
+                                           buf + 1 + !!(flags & HAVESIGN)) {
+                                               base = 16;
+                                               flags &= ~PFXOK;
+                                               goto fok;
                                        }
                                        break;
                                }

Reply via email to