From d406a9f6773d6df6b94742fd8c54f1be1439cdb9 Mon Sep 17 00:00:00 2001 From: cuz Date: Mon, 14 Feb 2005 09:19:59 +0000 Subject: [PATCH] Scanf improvements by Greg King git-svn-id: svn://svn.cc65.org/cc65/trunk@3377 b7a2c559-68d2-44c3-8de9-860c34a00d81 --- libsrc/common/.cvsignore | 1 - libsrc/common/Makefile | 25 +- libsrc/common/_scanf.c | 701 ++++++++++++++++++++++++--------------- libsrc/common/_scanf.h | 17 +- libsrc/common/scanf.c | 36 -- libsrc/common/scanf.s | 74 +++++ libsrc/common/vfscanf.s | 60 +++- libsrc/common/vsscanf.s | 47 +-- 8 files changed, 609 insertions(+), 352 deletions(-) delete mode 100644 libsrc/common/scanf.c create mode 100644 libsrc/common/scanf.s diff --git a/libsrc/common/.cvsignore b/libsrc/common/.cvsignore index 89274f351..62c0d2b40 100644 --- a/libsrc/common/.cvsignore +++ b/libsrc/common/.cvsignore @@ -29,7 +29,6 @@ puts.s qsort.s realloc.s rewind.s -scanf.s sleep.s strftime.s strtok.s diff --git a/libsrc/common/Makefile b/libsrc/common/Makefile index 9f44723f4..d3ea1d5c2 100644 --- a/libsrc/common/Makefile +++ b/libsrc/common/Makefile @@ -1,5 +1,6 @@ +# -*- makefile -*- # -# makefile for CC65 runtime library +# makefile for CC65's common library # .SUFFIXES: .o .s .c @@ -26,9 +27,19 @@ CFLAGS = -Osir -g -T -t $(SYS) --forget-inc-paths -I . -I ../../include %.o: %.s @$(AS) -g -o $@ $(AFLAGS) $< +#-------------------------------------------------------------------------- +# Rules to help us see what code the compiler and assembler make. + +%.s : %.c + @$(CC) $(CFLAGS) -S $< + +%.lst : %.s + @$(AS) $(AFLAGS) -l -o /dev/null $< + #-------------------------------------------------------------------------- # Object files +# From C source-files C_OBJS = _afailed.o \ _aligned_malloc.o \ _hextab.o \ @@ -59,7 +70,6 @@ C_OBJS = _afailed.o \ qsort.o \ realloc.o \ rewind.o \ - scanf.o \ sleep.o \ strftime.o \ strxfrm.o \ @@ -67,7 +77,7 @@ C_OBJS = _afailed.o \ system.o \ timezone.o - +# From assembly source-files S_OBJS = _cwd.o \ _fdesc.o \ _file.o \ @@ -134,6 +144,7 @@ S_OBJS = _cwd.o \ raise.o \ remove.o \ rename.o \ + scanf.o \ setjmp.o \ signal.o \ sigtable.o \ @@ -183,10 +194,10 @@ S_OBJS = _cwd.o \ all: $(C_OBJS) $(S_OBJS) clean: - @rm -f *~ - @rm -f $(C_OBJS:.o=.s) - @rm -f $(C_OBJS) - @rm -f $(S_OBJS) + @$(RM) *~ *.lst + @$(RM) $(C_OBJS:.o=.s) + @$(RM) $(C_OBJS) + @$(RM) $(S_OBJS) zap: clean diff --git a/libsrc/common/_scanf.c b/libsrc/common/_scanf.c index 036a6eb87..5475e185c 100644 --- a/libsrc/common/_scanf.c +++ b/libsrc/common/_scanf.c @@ -1,25 +1,38 @@ /* * _scanf.c * - * (C) Copyright 2001-2002 Ullrich von Bassewitz (uz@cc65.org) + * (c) Copyright 2001-2005, Ullrich von Bassewitz + * 2005-01-24, Greg King * - * This is the basic layer for all scanf type functions. It should get - * rewritten in assembler at some time in the future, so most of the code + * 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 -#include #include +#include +#include +#include #include #include -#include #include +#include + +#include +/* _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" +extern void __fastcall__ _seterrno (unsigned char code); + +#pragma staticlocals(on) + /*****************************************************************************/ @@ -28,9 +41,11 @@ -#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 */ +}; @@ -40,22 +55,25 @@ -static struct scanfdata* 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 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 unsigned Conversions; /* Number of conversions */ -static unsigned char IntBytes; /* Number of bytes-1 for int 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 Invert; /* Do we need to invert the charset? */ -static unsigned char CharSet[32]; /* 32 * 8 bits = 256 bits */ -static const unsigned char Bits[8] = { +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 }; @@ -76,43 +94,62 @@ static const unsigned char Bits[8] = { -static void __fastcall__ AddCharToSet (unsigned char C) -/* Set the given bit in the character set */ +/* 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 ("ldy #%o", C); - asm ("lda (sp),y"); + asm ("pha"); + asm ("lsr a"); /* Divide by CHAR_BIT */ asm ("lsr a"); asm ("lsr a"); - asm ("lsr a"); - asm ("tax"); - asm ("lda (sp),y"); - asm ("and #$07"); - asm ("tay"); + 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); } -static unsigned char __fastcall__ IsCharInSet (unsigned char C) -/* Check if the given char is part of the character set */ +#pragma optimize (push, off) + +static unsigned char IsCharInSet (void) +/* Check if the char. is part of the character set. */ { - asm ("ldy #%o", C); - asm ("lda (sp),y"); - asm ("lsr a"); - asm ("lsr a"); - asm ("lsr a"); - asm ("tax"); - asm ("lda (sp),y"); - asm ("and #$07"); - asm ("tay"); - asm ("lda %v,y", Bits); + /* 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 __AX__; + return (unsigned char) __AX__; } +#pragma optimize (pop) + static void InvertCharSet (void) @@ -135,10 +172,49 @@ static void InvertCharSet (void) -static void __fastcall__ Error (unsigned char Code) -/* Does a longjmp using the given code */ +static void PushBack (void) +/* Push back the last (unused) character, provided it is not EOF. */ { - longjmp (JumpBuf, Code); + /* 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: } @@ -161,7 +237,7 @@ static void ReadChar (void) asm ("sta jmpvec+2"); /* Load D->data into __AX__ */ - asm ("ldy #%b", offsetof (struct scanfdata, data)+1); + asm ("ldy #%b", offsetof (struct scanfdata, data) + 1); asm ("lda (ptr1),y"); asm ("tax"); asm ("dey"); @@ -174,11 +250,11 @@ static void ReadChar (void) asm ("sta %v", C); asm ("stx %v+1", C); - /* If C is not EOF, bump the character counter. */ + /* If C is EOF, don't bump the character counter. + * Only the high-byte needs to be checked. + */ asm ("inx"); - asm ("bne %g", Done); - asm ("cmp #$FF"); - asm ("bne %g", Done); + asm ("beq %g", Done); /* Must bump CharCount. */ asm ("inc %v", CharCount); @@ -190,13 +266,32 @@ Done: -static void ReadCharWithCheck (void) -/* Get an input char, use longjmp in case of EOF */ +#pragma optimize (push, off) + +static void __fastcall__ Error (unsigned char /* Code */) +/* Does a longjmp using the given code */ { - ReadChar (); - if (C == EOF) { - Error (RC_EOF); - } + 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: } @@ -204,13 +299,15 @@ static void ReadCharWithCheck (void) static void SkipWhite (void) /* Skip white space in the input and return the first non white character */ { - while (isspace (C)) { + 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. @@ -224,6 +321,7 @@ static void ReadSign (void) asm ("bne %g", NotNeg); /* Negative value */ + asm ("sta %v", Converted); asm ("jsr %v", ReadChar); asm ("lda #$00"); /* Flag as negative */ asm ("beq %g", Store); @@ -232,6 +330,7 @@ static void ReadSign (void) 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 */ @@ -239,35 +338,59 @@ 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 */ { + return (bool) isdigit (C) ? + C - '0' : + (char) tolower ((int) C) - ('a' - 10); +} - if (isdigit (C)) { - return C - '0'; - } else { - return toupper (C) - ('A' - 10); + + +static void __fastcall__ ReadInt (unsigned char Base) +/* Read an integer, and store it into IntVal. */ +{ + 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 (); } + + /* If we didn't convert anything, it's a failure. */ + if (CharCount == 0) { + Error (RC_NOCONV); + } + + /* 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) { + if (NoAssign == false) { /* Get the next argument pointer */ - __AX__ = (unsigned) va_arg (ap, void*); + (void*) __AX__ = va_arg (ap, void*); - /* Store the argument pointer into ptr1 */ + /* Put the argument pointer into the zero-page. */ asm ("sta ptr1"); asm ("stx ptr1+1"); @@ -280,38 +403,16 @@ Loop: asm ("lda %v,y", IntVal); asm ("dey"); asm ("bpl %g", Loop); + /* Another assignment */ + asm ("inc %v", Assignments); + asm ("bne %g", Done); + asm ("inc %v+1", Assignments); +Done: } } -static unsigned char ReadInt (unsigned char Base) -/* Read an integer and store it into IntVal. Returns the number of chars - * converted. Does NOT bump Conversions. - */ -{ - unsigned char Val; - unsigned char CharCount = 0; - - /* Read the integer value */ - IntVal = 0; - while (isxdigit (C) && Width-- > 0 && (Val = HexVal (C)) < Base) { - ++CharCount; - IntVal = IntVal * Base + Val; - ReadChar (); - } - - /* If we didn't convert anything, it's an error */ - if (CharCount == 0) { - Error (RC_NOCONV); - } - - /* Return the number of characters converted */ - return CharCount; -} - - - static void __fastcall__ ScanInt (unsigned char Base) /* Scan an integer including white space, sign and optional base spec, * and store it into IntVal. @@ -331,10 +432,17 @@ static void __fastcall__ ScanInt (unsigned char Base) 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; @@ -345,65 +453,73 @@ static void __fastcall__ ScanInt (unsigned char Base) ReadInt (Base); /* Apply the sign */ - if (!Positive) { + if (Positive == false) { IntVal = -IntVal; } /* Assign the value to the next argument unless suppressed */ AssignInt (); +} - /* One more conversion */ - ++Conversions; + + +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 __fastcall__ _scanf (register struct scanfdata* D, - register 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. */ { - register char F; /* Character from format string */ - unsigned char Result; /* setjmp result */ - char* S; - unsigned char HaveWidth; /* True if a width was given */ - char Start; /* Start of range */ + 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; + 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); - 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++) { + while (GetFormat ()) { /* Check for a conversion */ - if (F != '%' || *format == '%') { - - /* %% or any char other than % */ - if (F == '%') { - ++format; - } + if (F != '%') { /* Check for a match */ - if (isspace (F)) { + if ((bool) isspace ((int) F)) { /* Special white space handling: Any whitespace in the * format string matches any amount of whitespace including @@ -411,46 +527,63 @@ Again: */ SkipWhite (); continue; + } - } else if (F == C) { - - /* A match. Read the next input character and start over */ - goto Again; - - } else { +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 conversions. - */ - return Conversions; + /* 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. */ - F = *format++; + /* 0. Check for %% */ + if (GetFormat () == '%') { + goto Percent; + } /* 1. Assignment suppression */ - if (F == '*') { - F = *format++; - NoAssign = 1; - } else { - NoAssign = 0; + NoAssign = (F == '*'); + if (NoAssign) { + GetFormat (); } /* 2. Maximum field width */ Width = UINT_MAX; - HaveWidth = 0; - if (isdigit (F)) { - HaveWidth = 1; - Width = 0; + HaveWidth = (bool) isdigit (F); + if (HaveWidth) { + Width = 0; do { /* ### Non portable ### */ Width = Width * 10 + (F & 0x0F); - F = *format++; - } while (isdigit (F)); + } 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; @@ -460,7 +593,7 @@ Again: IntBytes = sizeof(char) - 1; ++format; } - F = *format++; + GetFormat (); break; case 'l': @@ -471,21 +604,20 @@ Again: /* FALLTHROUGH */ case 'j': /* intmax_t */ IntBytes = sizeof(long) - 1; - F = *format++; - break; + /* FALLTHROUGH */ case 'z': /* size_t */ case 't': /* ptrdiff_t */ + /* Same size as int */ + case 'L': /* long double - ignore this one */ - F = *format++; - break; + GetFormat (); } /* 4. Conversion specifier */ switch (F) { - /* 'd' and 'u' conversions are actually the same, since the - * standard says that evene the 'u' modifier allows an + * standard says that even the 'u' modifier allows an * optionally signed integer. */ case 'd': /* Optionally signed decimal integer */ @@ -507,100 +639,115 @@ Again: case 'X': /* Optionally signed hexadecimal integer */ ScanInt (16); - break; - - case 'E': - case 'e': - case 'f': - case 'g': - /* Optionally signed float */ - Error (RC_NOCONV); - break; + break; case 's': - /* Whitespace terminated string */ + /* Whitespace-terminated string */ SkipWhite (); - if (!NoAssign) { - S = va_arg (ap, char*); + CheckEnd (); /* Is it an input failure? */ + Converted = true; /* No, conversion will succeed */ + if (NoAssign == false) { + S = va_arg (ap, char*); } - while (!isspace (C) && Width--) { - if (!NoAssign) { - *S++ = C; - } - ReadChar (); - } - /* Terminate the string just read */ - if (!NoAssign) { - *S = '\0'; - } - ++Conversions; - break; + 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) { - /* No width given, default is 1 */ - Width = 1; - } - if (!NoAssign) { - S = va_arg (ap, char*); - while (Width--) { + /* 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; - ReadCharWithCheck (); + ReadChar (); } - } else { + ++Assignments; + } else { /* Just skip as many chars as given */ - while (Width--) { - ReadCharWithCheck (); + while (++Width) { + CheckEnd (); /* Is it a matching failure? */ + ReadChar (); } } - ++Conversions; break; case '[': - /* String using characters from a set */ - Invert = 0; + /* String using characters from a set */ /* Clear the set */ memset (CharSet, 0, sizeof (CharSet)); - F = *format++; - if (F == '^') { - Invert = 1; - F = *format++; + /* Skip the left-bracket, and test for inversion. */ + Invert = (GetFormat () == '^'); + if (Invert) { + GetFormat (); } if (F == ']') { - AddCharToSet (']'); - F = *format++; + /* 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 != ']' && F != '\0') { - if (*format == '-') { + while (F != '\0' && F != ']') { + if (*format == '-') { /* Look ahead at next char. */ /* A range. Get start and end, skip the '-' */ Start = F; - F = *++format; ++format; - if (F == ']') { - /* '-' as last char means: include '-' */ - AddCharToSet (Start); - AddCharToSet ('-'); - } else if (F != '\0') { - /* Include all chars in the range */ - while (1) { + switch (GetFormat ()) { + case '\0': + case ']': + /* '-' as last char means: include '-' */ AddCharToSet (Start); - if (Start == F) { - break; + AddCharToSet ('-'); + break; + default: + /* Include all characters + * that are in the range. + */ + while (1) { + AddCharToSet (Start); + if (Start == F) { + break; + } + ++Start; } - ++Start; - } - /* Get next char after range */ - F = *format++; + /* Get next char after range */ + GetFormat (); } } else { /* Just a character */ AddCharToSet (F); /* Get next char */ - F = *format++; + 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) { @@ -611,76 +758,110 @@ Again: * store them into a string while they are part of * the set. */ - if (!NoAssign) { - S = va_arg (ap, char*); - while (IsCharInSet (C) && Width--) { + Match = false; + if (NoAssign == false) { + S = va_arg (ap, char*); + } + while (IsCharInSet () && ++Width) { + if (NoAssign == false) { *S++ = C; - ReadChar (); - } - *S = '\0'; - } else { - while (IsCharInSet (C) && Width--) { - ReadChar (); - } + } + Match = Converted = true; + ReadChar (); } - ++Conversions; + /* At least one character must match the set. */ + if (Match == false) { + goto NoConv; + } + if (NoAssign == false) { + *S = '\0'; + ++Assignments; + } break; case 'p': - /* Pointer, format is 0xABCD */ + /* Pointer, general format is 0xABCD. + * %hhp --> zero-page pointer + * %hp --> near pointer + * %lp --> far pointer + */ SkipWhite (); if (CHAR (C) != '0') { - Error (RC_NOCONV); + goto NoConv; } + Converted = true; ReadChar (); - if (CHAR (C) != 'x' && CHAR (C) != 'X') { - Error (RC_NOCONV); - } + switch (CHAR (C)) { + case 'x': + case 'X': + break; + default: + goto NoConv; + } ReadChar (); - if (ReadInt (16) != 4) { /* 4 chars expected */ - Error (RC_NOCONV); - } + ReadInt (16); AssignInt (); - ++Conversions; break; case 'n': - /* Store characters consumed so far */ - IntVal = CharCount; - AssignInt (); + /* 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; - default: - /* Invalid conversion */ - Error (RC_NOCONV); - 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 */ - /* Push back the last unused character, provided it is not EOF */ - if (C != EOF) { - D->unget (C, D->data); - } + /* 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: - /* Jump via JumpBuf means an error. If this happens at EOF with no - * conversions, it is considered an error, otherwise the number - * of conversions is returned (the default behaviour). + /* 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 && CharCount == 0) { - /* Special case: error */ - Conversions = EOF; - } - + 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; +} diff --git a/libsrc/common/_scanf.h b/libsrc/common/_scanf.h index 0dbf0e01b..f8aad5be3 100644 --- a/libsrc/common/_scanf.h +++ b/libsrc/common/_scanf.h @@ -1,7 +1,7 @@ /* * _scanf.h * - * (C) Copyright 2001 Ullrich von Bassewitz (uz@cc65.org) + * (c) Copyright 2004, Ullrich von Bassewitz * */ @@ -16,26 +16,26 @@ * return EOF if no more data is available. */ typedef int __fastcall__ (*getfunc) (void* data); + +/* Type of the function that is called to put back unused data */ typedef int __fastcall__ (*ungetfunc) (int c, void* data); /* Control structure passed to the low level worker function. * Beware: This structure is mirrored in the _scanf.inc assembler include - * file, so check this when altering the structure. + * file, so check this when altering the structure. */ struct scanfdata { - getfunc get; /* Pointer to input routine */ - ungetfunc unget; /* Pointer to pushback routine */ - - /* Fields used outside of _scanf */ - void* data; /* Caller data */ + getfunc get; /* Pointer to input routine */ + ungetfunc unget; /* Pointer to pushback routine */ + void* data; /* Pointer to struct. used outside of _scanf() */ }; /* Internal scanning routine */ -int __fastcall__ _scanf (struct scanfdata* d, const char* format, va_list ap); +int __fastcall__ _scanf (const struct scanfdata* d, const char* format, va_list ap); @@ -44,4 +44,3 @@ int __fastcall__ _scanf (struct scanfdata* d, const char* format, va_list ap); - diff --git a/libsrc/common/scanf.c b/libsrc/common/scanf.c deleted file mode 100644 index d92174afc..000000000 --- a/libsrc/common/scanf.c +++ /dev/null @@ -1,36 +0,0 @@ -/* - * scanf.c - * - * Ullrich von Bassewitz (uz@cc65.org), 2004-11-26 - * - */ - - - -#include - - - -/*****************************************************************************/ -/* Code */ -/*****************************************************************************/ - - - -int scanf (const char* format, ...) -/* Standard C function */ -{ - va_list ap; - - /* Setup for variable arguments */ - va_start (ap, format); - - /* Call vfscanf(). Since we know that va_end won't do anything, we will - * save the call and return the value directly. - */ - return vfscanf (stdin, format, ap); -} - - - - diff --git a/libsrc/common/scanf.s b/libsrc/common/scanf.s new file mode 100644 index 000000000..b8b89d212 --- /dev/null +++ b/libsrc/common/scanf.s @@ -0,0 +1,74 @@ +; +; int scanf(const char* Format, ...); +; +; 2000-12-01, Ullrich von Bassewitz +; 2004-12-31, Greg King +; + + .export _scanf + + .import _stdin, pushax, addysp, _vfscanf + .import sp:zp, ptr1:zp + + .macpack generic + +; ---------------------------------------------------------------------------- +; Code +; +_scanf: + sty ArgSize ; Number of argument bytes passed in .Y + +; We are using a (hopefully) clever trick here to reduce code size. On entry, +; the stack pointer points to the last pushed argument of the variable +; argument list. Adding the number of argument bytes, would result in a +; pointer that points _above_ the Format argument. +; Because we have to push stdin anyway, we will do that here, so: +; +; * we will save the subtraction of 2 (__fixargs__) later; +; * we will have the address of the Format argument which needs to +; be pushed next. + + lda _stdin + ldx _stdin+1 + jsr pushax + +; Now, calculate the va_list pointer, which does point to Format. + + lda sp + ldx sp+1 + add ArgSize + bcc @L1 + inx +@L1: sta ptr1 + stx ptr1+1 + +; Push a copy of Format. + + ldy #1 + lda (ptr1),y + tax + dey + lda (ptr1),y + jsr pushax + +; Load va_list [last and __fastcall__ argument to vfscanf()]. + + lda ptr1 + ldx ptr1+1 + +; Call vfscanf(). + + jsr _vfscanf + +; Clean up the stack. We will return what we got from vfscanf(). + + ldy ArgSize + jmp addysp + +; ---------------------------------------------------------------------------- +; Data +; + .bss +ArgSize: + .res 1 ; Number of argument bytes + diff --git a/libsrc/common/vfscanf.s b/libsrc/common/vfscanf.s index 057a62cc3..44b05ef83 100644 --- a/libsrc/common/vfscanf.s +++ b/libsrc/common/vfscanf.s @@ -1,14 +1,19 @@ ; ; int __fastcall__ vfscanf (FILE* f, const char* format, va_list ap); ; -; Ullrich von Bassewitz, 2004-11-27 +; 2004-11-27, Ullrich von Bassewitz +; 2004-12-21, Greg King ; .export _vfscanf - .import _fgetc, _ungetc + .import _fgetc, _ungetc, _ferror .include "zeropage.inc" .include "_scanf.inc" + .include "stdio.inc" + + +count := ptr3 ; Result of scan ; ---------------------------------------------------------------------------- @@ -25,23 +30,27 @@ d: .addr _fgetc ; GET ; int __fastcall__ vfscanf (FILE* f, const char* format, va_list ap) ; /* Standard C function */ ; { -; struct scanfdata d; -; ; /* Initialize the data struct. We do only need the given file as user data, -; * since the get and ungetc are crafted so they match the standard fgetc -; * and ungetc functions. +; * because the (getfunc) and (ungetfunc) functions are crafted so that they +; * match the standard-I/O fgetc() and ungetc(). ; */ -; d.get = (getfunc) fgetc, -; d.unget = (ungetfunc) ungetc, -; d.data = f; +; static struct scanfdata d = { +; ( getfunc) fgetc, +; (ungetfunc) ungetc +; }; +; static int count; +; +; d.data = (void*) f; +; +; /* Call the internal function */ +; count = _scanf (&d, format, ap); ; -; /* Call the internal function and return the result */ -; return _scanf (&d, format, ap); +; /* And, return the result */ +; return ferror (f) ? EOF : count; ; } ; -; Since _scanf has the same parameter stack as vfscanf, with f replaced by &d, -; we will do exactly that. _scanf will then clean up the stack, so we can jump -; directly there, no need to return. +; Because _scanf() has the same parameter stack as vfscanf(), with f replaced +; by &d, we will do exactly that. _scanf() then will clean up the stack. ; Beware: Since ap is a fastcall parameter, we must not destroy a/x. ; @@ -63,8 +72,27 @@ _vfscanf: lda #>d sta (sp),y -; Restore the low byte of ap and jump to the _scanf function +; Restore the low byte of ap, and call the _scanf function pla - jmp __scanf + jsr __scanf + sta count + stx count+1 + +; Return -1 if there was a read error during the scan + + lda d + SCANFDATA::DATA ; Get f + ldx d + SCANFDATA::DATA+1 + jsr _ferror + tay + beq L1 + lda #str[d->index]) { -; /* Increment index only if end not reached */ -; ++d->index; -; return C; -; } else { +; char C = d->str[d->index]; +; if (C == '\0') { ; return EOF; ; } +; /* Increment index only if end not reached */ +; ++d->index; +; return C; ; } ; @@ -73,17 +74,17 @@ L1: tax ; Save return value tya ; Low byte of index ldy #SSCANFDATA::INDEX - add #1 + add #<1 sta (ptr1),y iny lda (ptr1),y - adc #$00 + adc #>1 sta (ptr1),y ; Return the char just read txa - ldx #$00 + ldx #>0 rts .endproc @@ -110,11 +111,11 @@ L1: tax ; Save return value ldy #SSCANFDATA::INDEX lda (ptr1),y - sub #1 + sub #<1 sta (ptr1),y iny lda (ptr1),y - sbc #0 + sbc #>1 sta (ptr1),y ; Return c @@ -127,15 +128,16 @@ L1: tax ; Save return value ; int __fastcall__ vsscanf (const char* str, const char* format, va_list ap) ; /* Standard C function */ ; { -; struct sscanfdata sd; -; struct scanfdata d; -; ; /* Initialize the data structs. The sscanfdata struct will be passed back -; * to the get and unget functions by _scanf. +; * to the get and unget functions by _scanf(). ; */ -; d.get = (getfunc) get; -; d.unget = (ungetfunc) unget, -; d.data = &sd; +; static struct sscanfdata sd; +; static const struct scanfdata d = { +; ( getfunc) get, +; (ungetfunc) unget, +; (void*) &sd +; }; +; ; sd.str = str; ; sd.index = 0; ; @@ -144,10 +146,10 @@ L1: tax ; Save return value ; } ; -.data - +.bss sd: .tag SSCANFDATA +.rodata d: .addr get .addr unget .addr sd @@ -177,11 +179,10 @@ d: .addr get sta sd + SSCANFDATA::INDEX sta sd + SSCANFDATA::INDEX+1 -; Restore the low byte of ap and jump to _scanf which will cleanup the stacl +; Restore the low byte of ap, and jump to _scanf() which will clean up the stack pla jmp __scanf .endproc - -- 2.39.5