]> git.sur5r.net Git - cc65/blobdiff - libsrc/common/_scanf.c
Removed (pretty inconsistently used) tab chars from source code base.
[cc65] / libsrc / common / _scanf.c
index e7d7389dc0e7bc9a23d144d6946ab1a079459869..108fc6095e6694129b44a0a838e2114b596071de 100644 (file)
 /*
  * _scanf.c
  *
- * (C) Copyright 2001 Ullrich von Bassewitz (uz@cc65.org)
+ * (c) Copyright 2001-2005, Ullrich von Bassewitz <uz@cc65.org>
+ * 2005-01-24, Greg King <gngking@erols.com>
  *
- * This is the basic layer for all scanf type functions.
+ * This is the basic layer for all scanf-type functions.  It should be
+ * rewritten in assembly, at some time in the future.  So, some of the code
+ * is not as elegant as it could be.
  */
 
 
 
-#include <stdio.h>
+#include <stddef.h>
 #include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
 #include <setjmp.h>
-#include <ctype.h>
 #include <limits.h>
+#include <errno.h>
+
+#include <ctype.h>
+/* _scanf() can give EOF to these functions.  But, the macroes can't
+** understand it; so, they are removed.
+*/
+#undef isspace
+#undef isxdigit
 
 #include "_scanf.h"
 
+#pragma static-locals(on)
+
 
 
 /*****************************************************************************/
-/*                           SetJmp return codes                            */
+/*                            SetJmp return codes                            */
 /*****************************************************************************/
 
 
 
-#define RC_OK          0               /* Regular call */
-#define RC_EOF         1               /* EOF reached */
-#define RC_NOCONV      2               /* No conversion possible */
+enum {
+    RC_OK,                              /* setjmp() call */
+    RC_NOCONV,                          /* No conversion possible */
+    RC_EOF                              /* EOF reached */
+};
 
 
 
 /*****************************************************************************/
-/*                                  Data                                    */
+/*                                   Data                                    */
 /*****************************************************************************/
 
 
 
-static struct indesc*  D;              /* Copy of function argument */
-static va_list         ap;             /* Copy of function argument */
-static jmp_buf                 JumpBuf;        /* Label that is used in case of EOF */
-static char                    C;              /* Character from input */
-static unsigned                Width;          /* Maximum field width */
-static long                    IntVal;         /* Converted int value */
-static unsigned                Conversions;    /* Number of conversions */
+static const char*    format;           /* Copy of function argument */
+static const struct scanfdata* D_;      /* Copy of function argument */
+static va_list        ap;               /* Copy of function argument */
+static jmp_buf        JumpBuf;          /* "Label" that is used for failures */
+static char           F;                /* Character from format string */
+static unsigned       CharCount;        /* Characters read so far */
+static int            C;                /* Character from input */
+static unsigned       Width;            /* Maximum field width */
+static long           IntVal;           /* Converted int value */
+static int            Assignments;      /* Number of assignments */
+static unsigned char  IntBytes;         /* Number of bytes-1 for int conversions */
 
 /* Flags */
-static unsigned char   Positive;       /* Flag for positive value */
-static unsigned char   NoAssign;       /* Supppress assigment */
-static unsigned char   IsShort;        /* Short type */
-static unsigned char   IsLong;         /* Long type */
+static bool           Converted;        /* Some object was converted */
+static bool           Positive;         /* Flag for positive value */
+static bool           NoAssign;         /* Suppress assignment */
+static bool           Invert;           /* Do we need to invert the charset? */
+static unsigned char  CharSet[(1+UCHAR_MAX)/CHAR_BIT];
+static const unsigned char Bits[CHAR_BIT] = {
+    0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
+};
+
+/* We need C to be 16 bits since we cannot check for EOF otherwise.
+ * Unfortunately, this causes the code to be quite larger, even if for most
+ * purposes, checking the low byte would be enough, since if C is EOF, the
+ * low byte will not match any useful character anyway (at least for the
+ * supported platforms - I know that this is not portable). So the following
+ * macro is used to access just the low byte of C.
+ */
+#define CHAR(c)         (*((unsigned char*)&(c)))
 
 
 
 /*****************************************************************************/
-/*                             Character sets                               */
+/*                              Character sets                               */
 /*****************************************************************************/
 
 
 
+/* We don't want the optimizer to ruin our "perfect" ;-)
+ * assembly code!
+ */
+#pragma optimize (push, off)
+
+static unsigned FindBit (void)
+/* Locate the character's bit in the charset array.
+ * < .A - Argument character
+ * > .X - Offset of the byte in the character-set mask
+ * > .A - Bit-mask
+ */
+{
+    asm ("pha");
+    asm ("lsr a");              /* Divide by CHAR_BIT */
+    asm ("lsr a");
+    asm ("lsr a");
+    asm ("tax");                /* Byte's offset */
+    asm ("pla");
+    asm ("and #%b", CHAR_BIT-1);
+    asm ("tay");                /* Bit's offset */
+    asm ("lda %v,y", Bits);
+    return (unsigned) __AX__;
+}
+
+#pragma optimize (pop)
+
+
+static void __fastcall__ AddCharToSet (unsigned char /* C */)
+/* Set the given bit in the character set */
+{
+    FindBit();
+    asm ("ora %v,x", CharSet);
+    asm ("sta %v,x", CharSet);
+}
+
+
+
+#pragma optimize (push, off)
+
+static unsigned char IsCharInSet (void)
+/* Check if the char. is part of the character set. */
+{
+    /* Get the character from C. */
+    asm ("lda #$00");
+    asm ("ldx %v+1", C);
+    asm ("bne L1");                     /* EOF never is in the set */
+    asm ("lda %v", C);
+    FindBit();
+    asm ("and %v,x", CharSet);
+    asm ("L1:");
+    asm ("ldx #$00");
+    return (unsigned char) __AX__;
+}
+
+#pragma optimize (pop)
+
+
+
+static void InvertCharSet (void)
+/* Invert the character set */
+{
+    asm ("ldy #%b", sizeof (CharSet) - 1);
+    asm ("L1:");
+    asm ("lda %v,y", CharSet);
+    asm ("eor #$FF");
+    asm ("sta %v,y", CharSet);
+    asm ("dey");
+    asm ("bpl L1");
+}
+
+
+
 /*****************************************************************************/
-/*                                  Code                                    */
+/*                                   Code                                    */
 /*****************************************************************************/
 
 
 
+static void PushBack (void)
+/* Push back the last (unused) character, provided it is not EOF. */
+{
+    /* Get the character from C. */
+    /* Only the high-byte needs to be checked for EOF. */
+    asm ("ldx %v+1", C);
+    asm ("bne %g", Done);
+    asm ("lda %v", C);
+
+    /* Put unget()'s first argument on the stack. */
+    asm ("jsr pushax");
+
+    /* Copy D into the zero-page. */
+    (const struct scanfdata*) __AX__ = D_;
+    asm ("sta ptr1");
+    asm ("stx ptr1+1");
+
+    /* Copy the unget vector to jmpvec. */
+    asm ("ldy #%b", offsetof (struct scanfdata, unget));
+    asm ("lda (ptr1),y");
+    asm ("sta jmpvec+1");
+    asm ("iny");
+    asm ("lda (ptr1),y");
+    asm ("sta jmpvec+2");
+
+    /* Load D->data into __AX__. */
+    asm ("ldy #%b", offsetof (struct scanfdata, data) + 1);
+    asm ("lda (ptr1),y");
+    asm ("tax");
+    asm ("dey");
+    asm ("lda (ptr1),y");
+
+    /* Call the unget routine. */
+    asm ("jsr jmpvec");
+
+    /* Take back that character's count. */
+    asm ("lda %v", CharCount);
+    asm ("bne %g", Yank);
+    asm ("dec %v+1", CharCount);
+Yank:
+    asm ("dec %v", CharCount);
+
+Done:
+    ;
+}
+
+
+
 static void ReadChar (void)
 /* Get an input character, count characters */
 {
-    C = D->fin (D);
-    ++D->ccount;
+    /* Move D to ptr1 */
+    asm ("lda %v", D_);
+    asm ("ldx %v+1", D_);
+    asm ("sta ptr1");
+    asm ("stx ptr1+1");
+
+    /* Copy the get vector to jmpvec */
+    asm ("ldy #%b", offsetof (struct scanfdata, get));
+    asm ("lda (ptr1),y");
+    asm ("sta jmpvec+1");
+    asm ("iny");
+    asm ("lda (ptr1),y");
+    asm ("sta jmpvec+2");
+
+    /* Load D->data into __AX__ */
+    asm ("ldy #%b", offsetof (struct scanfdata, data) + 1);
+    asm ("lda (ptr1),y");
+    asm ("tax");
+    asm ("dey");
+    asm ("lda (ptr1),y");
+
+    /* Call the get routine */
+    asm ("jsr jmpvec");
+
+    /* Assign the result to C */
+    asm ("sta %v", C);
+    asm ("stx %v+1", C);
+
+    /* If C is EOF, don't bump the character counter.
+     * Only the high-byte needs to be checked.
+     */
+    asm ("inx");
+    asm ("beq %g", Done);
+
+    /* Must bump CharCount. */
+    asm ("inc %v", CharCount);
+    asm ("bne %g", Done);
+    asm ("inc %v+1", CharCount);
+
+Done:
+    ;
+}
+
+
+
+#pragma optimize (push, off)
+
+static void __fastcall__ Error (unsigned char /* Code */)
+/* Does a longjmp using the given code */
+{
+    asm ("pha");
+    (char*) __AX__ = JumpBuf;
+    asm ("jsr pushax");
+    asm ("pla");
+    asm ("ldx #>0");
+    asm ("jmp %v", longjmp);
+}
+
+#pragma optimize (pop)
+
+
+
+static void CheckEnd (void)
+/* Stop a scan if it prematurely reaches the end of a string or a file. */
+{
+    /* Only the high-byte needs to be checked for EOF. */
+    asm ("ldx %v+1", C);
+    asm ("beq %g", Done);
+
+        Error (RC_EOF);
+Done:
+    ;
 }
 
 
@@ -76,343 +300,568 @@ static void ReadChar (void)
 static void SkipWhite (void)
 /* Skip white space in the input and return the first non white character */
 {
-    while (isspace (C)) {
-       ReadChar ();
+    while ((bool) isspace (C)) {
+        ReadChar ();
     }
 }
 
 
 
+#pragma optimize (push, off)
+
 static void ReadSign (void)
 /* Read an optional sign and skip it. Store 1 in Positive if the value is
  * positive, store 0 otherwise.
  */
 {
-    switch (C) {
-       case '-':
-                   ReadChar ();
-           Positive = 0;
-       case '+':
-           ReadChar ();
-           /* FALLTHROUGH */
-       default:
-           Positive = 1;
-    }
+    /* We can ignore the high byte of C here, since if it is EOF, the lower
+     * byte won't match anyway.
+     */
+    asm ("lda %v", C);
+    asm ("cmp #'-'");
+    asm ("bne %g", NotNeg);
+
+    /* Negative value */
+    asm ("sta %v", Converted);
+    asm ("jsr %v", ReadChar);
+    asm ("lda #$00");           /* Flag as negative */
+    asm ("beq %g", Store);
+
+    /* Positive value */
+NotNeg:
+    asm ("cmp #'+'");
+    asm ("bne %g", Pos);
+    asm ("sta %v", Converted);
+    asm ("jsr %v", ReadChar);   /* Skip the + sign */
+Pos:
+    asm ("lda #$01");           /* Flag as positive */
+Store:
+    asm ("sta %v", Positive);
 }
 
+#pragma optimize (pop)
 
 
-static unsigned char HexVal (char C)
+
+static unsigned char __fastcall__ HexVal (char C)
 /* Convert a digit to a value */
 {
-
-    if (isdigit (C)) {
-       return C - '0';
-    } else {
-       return C - toupper (C) + ('A' + 10);
-    }
+    return (bool) isdigit (C) ?
+        C - '0' :
+        (char) tolower ((int) C) - ('a' - 10);
 }
 
 
 
-static void ReadInt (unsigned char Base)
-/* Read an integer and store it into IntVal */
+static void __fastcall__ ReadInt (unsigned char Base)
+/* Read an integer, and store it into IntVal. */
 {
-    /* Value must start with a digit */
-    if (!isdigit (C)) {
-       longjmp (JumpBuf, RC_NOCONV);
+    unsigned char Val, CharCount = 0;
+
+    /* Read the integer value */
+    IntVal = 0L;
+    while ((bool) isxdigit (C) && ++Width != 0
+           && (Val = HexVal ((char) C)) < Base) {
+        ++CharCount;
+        IntVal = IntVal * (long) Base + (long) Val;
+        ReadChar ();
     }
 
-    /* Read the value */
-    IntVal = 0;
-    while (isxdigit (C) && Width-- > 0) {
-       printf ("ReadInt: '%c'\n", C);
-               IntVal = IntVal * Base + HexVal (C);
-       ReadChar ();
+    /* If we didn't convert anything, it's a failure. */
+    if (CharCount == 0) {
+        Error (RC_NOCONV);
     }
 
-    /* One more conversion */
-    ++Conversions;
+    /* Another conversion */
+    Converted = true;
 }
 
 
 
 static void AssignInt (void)
 /* Assign the integer value in Val to the next argument. The function makes
- * several non portable assumptions to reduce code size:
- *   - int and unsigned types have the same representation
+ * several non-portable assumptions, to reduce code size:
+ *   - signed and unsigned types have the same representation.
  *   - short and int have the same representation.
  *   - all pointer types have the same representation.
  */
 {
-    if (!NoAssign) {
-       /* Get the next argument pointer */
-       void* P = va_arg (ap, void*);
-
-       /* Assign to the converted value */
-       if (IsLong) {
-           *(long*)P = IntVal;
-       } else {
-           *(int*)P = (int) IntVal;
-       }
+    if (NoAssign == false) {
+
+        /* Get the next argument pointer */
+        (void*) __AX__ = va_arg (ap, void*);
+
+        /* Put the argument pointer into the zero-page. */
+        asm ("sta ptr1");
+        asm ("stx ptr1+1");
+
+        /* Get the number of bytes-1 to copy */
+        asm ("ldy %v", IntBytes);
+
+        /* Assign the integer value */
+Loop:   asm ("lda %v,y", IntVal);
+        asm ("sta (ptr1),y");
+        asm ("dey");
+        asm ("bpl %g", Loop);
+
+        /* Another assignment */
+        asm ("inc %v", Assignments);
+        asm ("bne %g", Done);
+        asm ("inc %v+1", Assignments);
+Done:   ;
+    }
+}
+
+
+
+static void __fastcall__ ScanInt (unsigned char Base)
+/* Scan an integer including white space, sign and optional base spec,
+ * and store it into IntVal.
+ */
+{
+    /* Skip whitespace */
+    SkipWhite ();
+
+    /* Read an optional sign */
+    ReadSign ();
+
+    /* If Base is unknown (zero), figure it out */
+    if (Base == 0) {
+        if (CHAR (C) == '0') {
+            ReadChar ();
+            switch (CHAR (C)) {
+                case 'x':
+                case 'X':
+                    Base = 16;
+                    Converted = true;
+                    ReadChar ();
+                    break;
+                default:
+                    Base = 8;
+
+                    /* Restart at the beginning of the number because it might
+                     * be only a single zero digit (which already was read).
+                     */
+                    PushBack ();
+                    C = '0';
+            }
+        } else {
+            Base = 10;
+        }
     }
+
+    /* Read the integer value */
+    ReadInt (Base);
+
+    /* Apply the sign */
+    if (Positive == false) {
+        IntVal = -IntVal;
+    }
+
+    /* Assign the value to the next argument unless suppressed */
+    AssignInt ();
+}
+
+
+
+static char GetFormat (void)
+/* Pick up the next character from the format string. */
+{
+/*  return (F = *format++); */
+    (const char*) __AX__ = format;
+    asm ("sta regsave");
+    asm ("stx regsave+1");
+    ++format;
+    asm ("ldy #0");
+    asm ("lda (regsave),y");
+    asm ("ldx #>0");
+    return (F = (char) __AX__);
 }
 
 
 
-int _scanf (struct indesc* D_, const char* format, va_list ap_)
+int __fastcall__ _scanf (const struct scanfdata* D,
+                         const char* format_, va_list ap_)
 /* This is the routine used to do the actual work. It is called from several
  * types of wrappers to implement the actual ISO xxscanf functions.
  */
 {
-    char         F;            /* Character from format string */
-    unsigned char Result;      /* setjmp result */
-    char*        S;
-    unsigned char Base;                /* Integer base in %i */
-    unsigned char HaveWidth;   /* True if a width was given */
+    register char* S;
+             bool  HaveWidth;   /* True if a width was given */
+             bool  Match;       /* True if a character-set has any matches */
+             char  Start;       /* Walks over a range */
 
     /* Place copies of the arguments into global variables. This is not very
      * nice, but on a 6502 platform it gives better code, since the values
      * do not have to be passed as parameters.
      */
-    D  = D_;
-    ap = ap_;
+    D_     = D;
+    format = format_;
+    ap     = ap_;
 
     /* Initialize variables */
-    Conversions = 0;
-    D->ccount   = 0;
+    Converted   = false;
+    Assignments = 0;
+    CharCount   = 0;
 
-    /* Set up the jump label. The get() routine will use this label when EOF
-     * is reached.
+    /* Set up the jump "label".  CheckEnd() will use that label when EOF
+     * is reached.  ReadInt() will use it when number-conversion fails.
      */
-    Result = setjmp (JumpBuf);
-    printf ("Result = %u\n", Result);
-    if (Result == RC_OK) {
-
+    if ((unsigned char) setjmp (JumpBuf) == RC_OK) {
 Again:
-               /* Get the next input character */
-       ReadChar ();
-
-       /* Walk over the format string */
-       while (F = *format++) {
-
-           /* Check for a conversion */
-           if (F != '%' || *format == '%') {
-
-               /* %% or any char other than % */
-               if (F == '%') {
-                   ++format;
-               }
-
-               /* Check for a match */
-               if (isspace (F)) {
-
-                   /* Special white space handling: Any whitespace matches
-                    * any amount of whitespace including none(!). So this
-                    * match will never fail.
-                    */
-                   SkipWhite ();
-                   continue;
-
-               } else if (F != C) {
-
-                   /* A mismatch. We will stop scanning the input and return
-                    * the number of conversions.
-                    */
-                   printf ("F = '%c', C = '%c' --> mismatch\n", F, C);
-                   return Conversions;
-
-               } else {
-
-                   /* A match. Read the next input character and start over */
-                   goto Again;
-
-               }
-
-           } else {
-
-               /* A conversion. Skip the percent sign. */
-               F = *format++;
-
-               /* Initialize variables */
-               NoAssign    = 0;
-               IsShort     = 0;
-               IsLong      = 0;
-               Width       = UINT_MAX;
-               HaveWidth   = 0;
-
-               /* Check for flags. */
-               while (1) {
-                   if (isdigit (F)) {
-                       HaveWidth = 1;
-                       Width     = 0;
-                       do {
-                           /* ### Non portable ### */
-                           Width = Width * 10 + (F & 0x0F);
-                           F = *format++;
-                       } while (isdigit (F));
-                   } else {
-                       switch (F) {
-                           case '*':   NoAssign = 1;   break;
-                           case 'h':   IsShort = 1;    break;
-                           case 'l':
-                           case 'L':   IsLong = 1;     break;
-                           default:    goto FlagsDone;
-                       }
-                       F = *format++;
-                   }
-               }
-FlagsDone:
-
-               /* Check for the actual conversion character */
-               printf ("F = '%c'\n", F);
-               switch (F) {
-
-                   case 'D':
-                       IsLong = 1;
-                   case 'd':
-                       /* Optionally signed decimal integer */
-                       SkipWhite ();
-                       ReadSign ();
-                       ReadInt (10);
-                       if (!Positive) {
-                           IntVal = -IntVal;
-                       }
-                       AssignInt ();
-                       break;
-
-                   case 'i':
-                       /* Optionally signed integer with a base */
-                       SkipWhite ();
-                       ReadSign ();
-                       if (C == '0') {
-                           ReadChar ();
-                           switch (C) {
-                               case 'x':
-                               case 'X':
-                                   Base = 16;
-                                   ReadChar();
-                                   break;
-                               default:
-                                   Base = 8;
-                           }
-                       } else {
-                           Base = 10;
-                       }
-                       ReadInt (Base);
-                       if (!Positive) {
-                           IntVal = -IntVal;
-                       }
-                       AssignInt ();
-                       break;
-
-                   case 'o':
-                       /* Unsigned octal integer */
-                       SkipWhite ();
-                       ReadInt (8);
-                       AssignInt ();
-                       break;
-
-                   case 'u':
-                       /* Unsigned decimal integer */
-                       SkipWhite ();
-                       ReadInt (10);
-                       AssignInt ();
-                       break;
-
-                   case 'x':
-                   case 'X':
-                       /* Unsigned hexadecimal integer */
-                       SkipWhite ();
-                       ReadInt (16);
-                       AssignInt ();
-                       break;
-
-                   case 'E':
-                   case 'e':
-                   case 'f':
-                   case 'g':
-                       /* Optionally signed float */
-                       longjmp (JumpBuf, RC_NOCONV);
-                       break;
-
-                   case 's':
-                       /* Whitespace terminated string */
-                       SkipWhite ();
-                       if (!NoAssign) {
-                           S = va_arg (ap, char*);
-                       }
-                               while (!isspace (C) && Width--) {
-                           if (!NoAssign) {
-                               *S++ = C;
-                           }
-                           ReadChar ();
-                       }
-                       /* Terminate the string just read */
-                       if (!NoAssign) {
-                           *S = '\0';
-                       }
-                       break;
-
-                   case 'c':
-                       /* Fixed length string, NOT zero terminated */
-                       if (!HaveWidth) {
-                           /* No width given, default is 1 */
-                           Width = 1;
-                       }
-                       if (!NoAssign) {
-                           S = va_arg (ap, char*);
-                       }
-                       while (Width--) {
-                           if (!NoAssign) {
-                               *S++ = C;
-                           }
-                           ReadChar ();
-                       }
-                       ++Conversions;
-                       break;
-
-                   case '[':
-                       /* String using characters from a set */
-                       break;
-
-                   case 'p':
-                       /* Pointer */
-                       break;
-
-                   case 'n':
-                       /* Store characters consumed so far */
-                       IntVal = D->ccount;
-                       IsLong = 0;
-                       AssignInt ();
-                       break;
-
-                   default:
-                       /* Invalid conversion */
-                       longjmp (JumpBuf, RC_NOCONV);
-                       break;
-
-               }
-
-               /* Skip the format char */
-               goto Again;
-
-           }
-
-       }
-
-    } else if (Result == RC_EOF) {
-
-       /* Jump via JumpBuf means EOF on input */
-       if (D->ccount == 0) {
-           /* Special case: error */
-           return -1;
-       }
 
+        /* Get the next input character */
+        ReadChar ();
+
+        /* Walk over the format string */
+        while (GetFormat ()) {
+
+            /* Check for a conversion */
+            if (F != '%') {
+
+                /* Check for a match */
+                if ((bool) isspace ((int) F)) {
+
+                    /* Special white space handling: Any whitespace in the
+                     * format string matches any amount of whitespace including
+                     * none(!). So this match will never fail.
+                     */
+                    SkipWhite ();
+                    continue;
+                }
+
+Percent:
+                /* ### Note:  The opposite test (C == F)
+                ** would be optimized into buggy code!
+                */
+                if (C != (int) F) {
+
+                    /* A mismatch -- we will stop scanning the input,
+                     * and return the number of assigned conversions.
+                     */
+                    goto NoConv;
+                }
+
+                /* A match -- get the next input character, and continue. */
+                goto Again;
+
+            } else {
+
+                /* A conversion. Skip the percent sign. */
+                /* 0. Check for %% */
+                if (GetFormat () == '%') {
+                    goto Percent;
+                }
+
+                /* 1. Assignment suppression */
+                NoAssign = (F == '*');
+                if (NoAssign) {
+                    GetFormat ();
+                }
+
+                /* 2. Maximum field width */
+                Width     = UINT_MAX;
+                HaveWidth = (bool) isdigit (F);
+                if (HaveWidth) {
+                    Width = 0;
+                    do {
+                        /* ### Non portable ### */
+                        Width = Width * 10 + (F & 0x0F);
+                    } while ((bool) isdigit (GetFormat ()));
+                }
+                if (Width == 0) {
+                    /* Invalid specification */
+                    /* Note:  This method of leaving the function might seem
+                     * to be crude, but it optimizes very well because
+                     * the four exits can share this code.
+                     */
+                    _seterrno (EINVAL);
+                    Assignments = EOF;
+                    PushBack ();
+                    return Assignments;
+                }
+                /* Increment-and-test makes better code than test-and-decrement
+                 * does.  So, change the width into a form that can be used in
+                 * that way.
+                 */
+                Width = ~Width;
+
+                /* 3. Length modifier */
+                IntBytes = sizeof(int) - 1;
+                switch (F) {
+                    case 'h':
+                        if (*format == 'h') {
+                            IntBytes = sizeof(char) - 1;
+                            ++format;
+                        }
+                        GetFormat ();
+                        break;
+
+                    case 'l':
+                        if (*format == 'l') {
+                            /* Treat long long as long */
+                            ++format;
+                        }
+                        /* FALLTHROUGH */
+                    case 'j':   /* intmax_t */
+                        IntBytes = sizeof(long) - 1;
+                        /* FALLTHROUGH */
+
+                    case 'z':   /* size_t */
+                    case 't':   /* ptrdiff_t */
+                        /* Same size as int */
+
+                    case 'L':   /* long double - ignore this one */
+                        GetFormat ();
+                }
+
+                /* 4. Conversion specifier */
+                switch (F) {
+                    /* 'd' and 'u' conversions are actually the same, since the
+                     * standard says that even the 'u' modifier allows an
+                     * optionally signed integer.
+                     */
+                    case 'd':   /* Optionally signed decimal integer */
+                    case 'u':
+                        ScanInt (10);
+                        break;
+
+                    case 'i':
+                        /* Optionally signed integer with a base */
+                        ScanInt (0);
+                        break;
+
+                    case 'o':
+                        /* Optionally signed octal integer */
+                        ScanInt (8);
+                        break;
+
+                    case 'x':
+                    case 'X':
+                        /* Optionally signed hexadecimal integer */
+                        ScanInt (16);
+                        break;
+
+                    case 's':
+                        /* Whitespace-terminated string */
+                        SkipWhite ();
+                        CheckEnd ();       /* Is it an input failure? */
+                        Converted = true;  /* No, conversion will succeed */
+                        if (NoAssign == false) {
+                            S = va_arg (ap, char*);
+                        }
+                        while (C != EOF
+                               && (bool) isspace (C) == false
+                               && ++Width) {
+                            if (NoAssign == false) {
+                                *S++ = C;
+                            }
+                            ReadChar ();
+                        }
+                        /* Terminate the string just read */
+                        if (NoAssign == false) {
+                            *S = '\0';
+                            ++Assignments;
+                        }
+                        break;
+
+                    case 'c':
+                        /* Fixed-length string, NOT zero-terminated */
+                        if (HaveWidth == false) {
+                            /* No width given, default is 1 */
+                            Width = ~1u;
+                        }
+                        CheckEnd ();       /* Is it an input failure? */
+                        Converted = true;  /* No, at least 1 char. available */
+                        if (NoAssign == false) {
+                            S = va_arg (ap, char*);
+                            /* ## This loop is convenient for us, but it isn't
+                             * standard C.  The standard implies that a failure
+                             * shouldn't put anything into the array argument.
+                             */
+                            while (++Width) {
+                                CheckEnd ();  /* Is it a matching failure? */
+                                *S++ = C;
+                                ReadChar ();
+                            }
+                            ++Assignments;
+                        } else {
+                            /* Just skip as many chars as given */
+                            while (++Width) {
+                                CheckEnd ();  /* Is it a matching failure? */
+                                ReadChar ();
+                            }
+                        }
+                        break;
+
+                    case '[':
+                        /* String using characters from a set */
+                        /* Clear the set */
+                        memset (CharSet, 0, sizeof (CharSet));
+                        /* Skip the left-bracket, and test for inversion. */
+                        Invert = (GetFormat () == '^');
+                        if (Invert) {
+                            GetFormat ();
+                        }
+                        if (F == ']') {
+                            /* Empty sets aren't allowed; so, a right-bracket
+                             * at the beginning must be a member of the set.
+                             */
+                            AddCharToSet (F);
+                            GetFormat ();
+                        }
+                        /* Read the characters that are part of the set */
+                        while (F != '\0' && F != ']') {
+                            if (*format == '-') {  /* Look ahead at next char. */
+                                /* A range. Get start and end, skip the '-' */
+                                Start = F;
+                                ++format;
+                                switch (GetFormat ()) {
+                                    case '\0':
+                                    case ']':
+                                        /* '-' as last char means:  include '-' */
+                                        AddCharToSet (Start);
+                                        AddCharToSet ('-');
+                                        break;
+                                    default:
+                                        /* Include all characters
+                                         * that are in the range.
+                                         */
+                                        while (1) {
+                                            AddCharToSet (Start);
+                                            if (Start == F) {
+                                                break;
+                                            }
+                                            ++Start;
+                                        }
+                                        /* Get next char after range */
+                                        GetFormat ();
+                                }
+                            } else {
+                                /* Just a character */
+                                AddCharToSet (F);
+                                /* Get next char */
+                                GetFormat ();
+                            }
+                        }
+                        /* Don't go beyond the end of the format string. */
+                        /* (Maybe, this should mean an invalid specification.) */
+                        if (F == '\0') {
+                            --format;
+                        }
+
+                        /* Invert the set if requested */
+                        if (Invert) {
+                            InvertCharSet ();
+                        }
+
+                        /* We have the set in CharSet. Read characters and
+                         * store them into a string while they are part of
+                         * the set.
+                         */
+                        Match = false;
+                        if (NoAssign == false) {
+                            S = va_arg (ap, char*);
+                        }
+                        while (IsCharInSet () && ++Width) {
+                            if (NoAssign == false) {
+                                *S++ = C;
+                            }
+                            Match = Converted = true;
+                            ReadChar ();
+                        }
+                        /* At least one character must match the set. */
+                        if (Match == false) {
+                            goto NoConv;
+                        }
+                        if (NoAssign == false) {
+                            *S = '\0';
+                            ++Assignments;
+                        }
+                        break;
+
+                    case 'p':
+                        /* Pointer, general format is 0xABCD.
+                         * %hhp --> zero-page pointer
+                         * %hp --> near pointer
+                         * %lp --> far pointer
+                         */
+                        SkipWhite ();
+                        if (CHAR (C) != '0') {
+                            goto NoConv;
+                        }
+                        Converted = true;
+                        ReadChar ();
+                        switch (CHAR (C)) {
+                            case 'x':
+                            case 'X':
+                                break;
+                            default:
+                                goto NoConv;
+                        }
+                        ReadChar ();
+                        ReadInt (16);
+                        AssignInt ();
+                        break;
+
+                    case 'n':
+                        /* Store the number of characters consumed so far
+                         * (the read-ahead character hasn't been consumed).
+                         */
+                        IntVal = (long) (CharCount - (C == EOF ? 0u : 1u));
+                        AssignInt ();
+                        /* Don't count it. */
+                        if (NoAssign == false) {
+                            --Assignments;
+                        }
+                        break;
+
+                    case 'S':
+                    case 'C':
+                        /* Wide characters */
+
+                    case 'a':
+                    case 'A':
+                    case 'e':
+                    case 'E':
+                    case 'f':
+                    case 'F':
+                    case 'g':
+                    case 'G':
+                        /* Optionally signed float */
+
+                        /* Those 2 groups aren't implemented. */
+                        _seterrno (ENOSYS);
+                        Assignments = EOF;
+                        PushBack ();
+                        return Assignments;
+
+                    default:
+                        /* Invalid specification */
+                        _seterrno (EINVAL);
+                        Assignments = EOF;
+                        PushBack ();
+                        return Assignments;
+                }
+            }
+        }
+    } else {
+NoConv:
+
+        /* Coming here means a failure. If that happens at EOF, with no
+         * conversion attempts, then it is considered an error; otherwise,
+         * the number of assignments is returned (the default behaviour).
+         */
+        if (C == EOF && Converted == false) {
+            Assignments = EOF;  /* Special case:  error */
+        }
     }
 
-    /* Return the number of conversions */
-    return Conversions;
+    /* Put the read-ahead character back into the input stream. */
+    PushBack ();
+
+    /* Return the number of conversion-and-assignments. */
+    return Assignments;
 }