Scanf improvements by Greg King

git-svn-id: svn://svn.cc65.org/cc65/trunk@3377 b7a2c559-68d2-44c3-8de9-860c34a00d81
This commit is contained in:
cuz
2005-02-14 09:19:59 +00:00
parent a4f6f14a6b
commit d406a9f677
8 changed files with 615 additions and 358 deletions

View File

@@ -29,7 +29,6 @@ puts.s
qsort.s qsort.s
realloc.s realloc.s
rewind.s rewind.s
scanf.s
sleep.s sleep.s
strftime.s strftime.s
strtok.s strtok.s

View File

@@ -1,5 +1,6 @@
# -*- makefile -*-
# #
# makefile for CC65 runtime library # makefile for CC65's common library
# #
.SUFFIXES: .o .s .c .SUFFIXES: .o .s .c
@@ -26,9 +27,19 @@ CFLAGS = -Osir -g -T -t $(SYS) --forget-inc-paths -I . -I ../../include
%.o: %.s %.o: %.s
@$(AS) -g -o $@ $(AFLAGS) $< @$(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 # Object files
# From C source-files
C_OBJS = _afailed.o \ C_OBJS = _afailed.o \
_aligned_malloc.o \ _aligned_malloc.o \
_hextab.o \ _hextab.o \
@@ -59,7 +70,6 @@ C_OBJS = _afailed.o \
qsort.o \ qsort.o \
realloc.o \ realloc.o \
rewind.o \ rewind.o \
scanf.o \
sleep.o \ sleep.o \
strftime.o \ strftime.o \
strxfrm.o \ strxfrm.o \
@@ -67,7 +77,7 @@ C_OBJS = _afailed.o \
system.o \ system.o \
timezone.o timezone.o
# From assembly source-files
S_OBJS = _cwd.o \ S_OBJS = _cwd.o \
_fdesc.o \ _fdesc.o \
_file.o \ _file.o \
@@ -134,6 +144,7 @@ S_OBJS = _cwd.o \
raise.o \ raise.o \
remove.o \ remove.o \
rename.o \ rename.o \
scanf.o \
setjmp.o \ setjmp.o \
signal.o \ signal.o \
sigtable.o \ sigtable.o \
@@ -183,10 +194,10 @@ S_OBJS = _cwd.o \
all: $(C_OBJS) $(S_OBJS) all: $(C_OBJS) $(S_OBJS)
clean: clean:
@rm -f *~ @$(RM) *~ *.lst
@rm -f $(C_OBJS:.o=.s) @$(RM) $(C_OBJS:.o=.s)
@rm -f $(C_OBJS) @$(RM) $(C_OBJS)
@rm -f $(S_OBJS) @$(RM) $(S_OBJS)
zap: clean zap: clean

View File

@@ -1,25 +1,38 @@
/* /*
* _scanf.c * _scanf.c
* *
* (C) Copyright 2001-2002 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. It should get * This is the basic layer for all scanf-type functions. It should be
* rewritten in assembler at some time in the future, so most of the code * rewritten in assembly, at some time in the future. So, some of the code
* is not as elegant as it could be. * is not as elegant as it could be.
*/ */
#include <stdio.h>
#include <stdarg.h>
#include <stddef.h> #include <stddef.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h> #include <string.h>
#include <setjmp.h> #include <setjmp.h>
#include <ctype.h>
#include <limits.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" #include "_scanf.h"
extern void __fastcall__ _seterrno (unsigned char code);
#pragma staticlocals(on)
/*****************************************************************************/ /*****************************************************************************/
@@ -28,9 +41,11 @@
#define RC_OK 0 /* Regular call */ enum {
#define RC_EOF 1 /* EOF reached */ RC_OK, /* setjmp() call */
#define RC_NOCONV 2 /* No conversion possible */ RC_NOCONV, /* No conversion possible */
RC_EOF /* EOF reached */
};
@@ -40,22 +55,25 @@
static struct scanfdata* D_; /* Copy of function argument */ 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 va_list ap; /* Copy of function argument */
static jmp_buf JumpBuf; /* Label that is used in case of EOF */ 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 unsigned CharCount; /* Characters read so far */
static int C; /* Character from input */ static int C; /* Character from input */
static unsigned Width; /* Maximum field width */ static unsigned Width; /* Maximum field width */
static long IntVal; /* Converted int value */ static long IntVal; /* Converted int value */
static unsigned Conversions; /* Number of conversions */ static int Assignments; /* Number of assignments */
static unsigned char IntBytes; /* Number of bytes-1 for int conversions */ static unsigned char IntBytes; /* Number of bytes-1 for int conversions */
/* Flags */ /* Flags */
static unsigned char Positive; /* Flag for positive value */ static bool Converted; /* Some object was converted */
static unsigned char NoAssign; /* Supppress assigment */ static bool Positive; /* Flag for positive value */
static unsigned char Invert; /* Do we need to invert the charset? */ static bool NoAssign; /* Suppress assignment */
static unsigned char CharSet[32]; /* 32 * 8 bits = 256 bits */ static bool Invert; /* Do we need to invert the charset? */
static const unsigned char Bits[8] = { static unsigned char CharSet[(1+UCHAR_MAX)/CHAR_BIT];
static const unsigned char Bits[CHAR_BIT] = {
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 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) /* 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 */ /* Set the given bit in the character set */
{ {
asm ("ldy #%o", C); FindBit();
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);
asm ("ora %v,x", CharSet); asm ("ora %v,x", CharSet);
asm ("sta %v,x", CharSet); asm ("sta %v,x", CharSet);
} }
static unsigned char __fastcall__ IsCharInSet (unsigned char C) #pragma optimize (push, off)
/* Check if the given char is part of the character set */
static unsigned char IsCharInSet (void)
/* Check if the char. is part of the character set. */
{ {
asm ("ldy #%o", C); /* Get the character from C. */
asm ("lda (sp),y"); asm ("lda #$00");
asm ("lsr a"); asm ("ldx %v+1", C);
asm ("lsr a"); asm ("bne L1"); /* EOF never is in the set */
asm ("lsr a"); asm ("lda %v", C);
asm ("tax"); FindBit();
asm ("lda (sp),y");
asm ("and #$07");
asm ("tay");
asm ("lda %v,y", Bits);
asm ("and %v,x", CharSet); asm ("and %v,x", CharSet);
asm ("L1:");
asm ("ldx #$00"); asm ("ldx #$00");
return __AX__; return (unsigned char) __AX__;
} }
#pragma optimize (pop)
static void InvertCharSet (void) static void InvertCharSet (void)
@@ -135,10 +172,49 @@ static void InvertCharSet (void)
static void __fastcall__ Error (unsigned char Code) static void PushBack (void)
/* Does a longjmp using the given code */ /* 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"); asm ("sta jmpvec+2");
/* Load D->data into __AX__ */ /* 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 ("lda (ptr1),y");
asm ("tax"); asm ("tax");
asm ("dey"); asm ("dey");
@@ -174,11 +250,11 @@ static void ReadChar (void)
asm ("sta %v", C); asm ("sta %v", C);
asm ("stx %v+1", 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 ("inx");
asm ("bne %g", Done); asm ("beq %g", Done);
asm ("cmp #$FF");
asm ("bne %g", Done);
/* Must bump CharCount. */ /* Must bump CharCount. */
asm ("inc %v", CharCount); asm ("inc %v", CharCount);
@@ -190,13 +266,32 @@ Done:
static void ReadCharWithCheck (void) #pragma optimize (push, off)
/* Get an input char, use longjmp in case of EOF */
static void __fastcall__ Error (unsigned char /* Code */)
/* Does a longjmp using the given code */
{ {
ReadChar (); asm ("pha");
if (C == EOF) { (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); Error (RC_EOF);
} Done:
} }
@@ -204,13 +299,15 @@ static void ReadCharWithCheck (void)
static void SkipWhite (void) static void SkipWhite (void)
/* Skip white space in the input and return the first non white character */ /* Skip white space in the input and return the first non white character */
{ {
while (isspace (C)) { while ((bool) isspace (C)) {
ReadChar (); ReadChar ();
} }
} }
#pragma optimize (push, off)
static void ReadSign (void) static void ReadSign (void)
/* Read an optional sign and skip it. Store 1 in Positive if the value is /* Read an optional sign and skip it. Store 1 in Positive if the value is
* positive, store 0 otherwise. * positive, store 0 otherwise.
@@ -224,6 +321,7 @@ static void ReadSign (void)
asm ("bne %g", NotNeg); asm ("bne %g", NotNeg);
/* Negative value */ /* Negative value */
asm ("sta %v", Converted);
asm ("jsr %v", ReadChar); asm ("jsr %v", ReadChar);
asm ("lda #$00"); /* Flag as negative */ asm ("lda #$00"); /* Flag as negative */
asm ("beq %g", Store); asm ("beq %g", Store);
@@ -232,6 +330,7 @@ static void ReadSign (void)
NotNeg: NotNeg:
asm ("cmp #'+'"); asm ("cmp #'+'");
asm ("bne %g", Pos); asm ("bne %g", Pos);
asm ("sta %v", Converted);
asm ("jsr %v", ReadChar); /* Skip the + sign */ asm ("jsr %v", ReadChar); /* Skip the + sign */
Pos: Pos:
asm ("lda #$01"); /* Flag as positive */ asm ("lda #$01"); /* Flag as positive */
@@ -239,35 +338,59 @@ Store:
asm ("sta %v", Positive); 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 */ /* 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 { static void __fastcall__ ReadInt (unsigned char Base)
return toupper (C) - ('A' - 10); /* 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) static void AssignInt (void)
/* Assign the integer value in Val to the next argument. The function makes /* Assign the integer value in Val to the next argument. The function makes
* several non portable assumptions to reduce code size: * several non-portable assumptions, to reduce code size:
* - int and unsigned types have the same representation * - signed and unsigned types have the same representation.
* - short and int have the same representation. * - short and int have the same representation.
* - all pointer types have the same representation. * - all pointer types have the same representation.
*/ */
{ {
if (!NoAssign) { if (NoAssign == false) {
/* Get the next argument pointer */ /* 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 ("sta ptr1");
asm ("stx ptr1+1"); asm ("stx ptr1+1");
@@ -280,38 +403,16 @@ Loop: asm ("lda %v,y", IntVal);
asm ("dey"); asm ("dey");
asm ("bpl %g", Loop); 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) static void __fastcall__ ScanInt (unsigned char Base)
/* Scan an integer including white space, sign and optional base spec, /* Scan an integer including white space, sign and optional base spec,
* and store it into IntVal. * and store it into IntVal.
@@ -331,10 +432,17 @@ static void __fastcall__ ScanInt (unsigned char Base)
case 'x': case 'x':
case 'X': case 'X':
Base = 16; Base = 16;
Converted = true;
ReadChar (); ReadChar ();
break; break;
default: default:
Base = 8; 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 { } else {
Base = 10; Base = 10;
@@ -345,65 +453,73 @@ static void __fastcall__ ScanInt (unsigned char Base)
ReadInt (Base); ReadInt (Base);
/* Apply the sign */ /* Apply the sign */
if (!Positive) { if (Positive == false) {
IntVal = -IntVal; IntVal = -IntVal;
} }
/* Assign the value to the next argument unless suppressed */ /* Assign the value to the next argument unless suppressed */
AssignInt (); AssignInt ();
/* One more conversion */
++Conversions;
} }
int __fastcall__ _scanf (register struct scanfdata* D, static char GetFormat (void)
register const char* format, va_list ap_) /* 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 (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 /* 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. * types of wrappers to implement the actual ISO xxscanf functions.
*/ */
{ {
register char F; /* Character from format string */ register char* S;
unsigned char Result; /* setjmp result */ bool HaveWidth; /* True if a width was given */
char* S; bool Match; /* True if a character-set has any matches */
unsigned char HaveWidth; /* True if a width was given */ char Start; /* Walks over a range */
char Start; /* Start of range */
/* Place copies of the arguments into global variables. This is not very /* 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 * nice, but on a 6502 platform it gives better code, since the values
* do not have to be passed as parameters. * do not have to be passed as parameters.
*/ */
D_ = D; D_ = D;
format = format_;
ap = ap_; ap = ap_;
/* Initialize variables */ /* Initialize variables */
Conversions = 0; Converted = false;
Assignments = 0;
CharCount = 0; CharCount = 0;
/* Set up the jump label. The get() routine will use this label when EOF /* Set up the jump "label". CheckEnd() will use that label when EOF
* is reached. * is reached. ReadInt() will use it when number-conversion fails.
*/ */
Result = setjmp (JumpBuf); if ((unsigned char) setjmp (JumpBuf) == RC_OK) {
if (Result == RC_OK) {
Again: Again:
/* Get the next input character */ /* Get the next input character */
ReadChar (); ReadChar ();
/* Walk over the format string */ /* Walk over the format string */
while (F = *format++) { while (GetFormat ()) {
/* Check for a conversion */ /* Check for a conversion */
if (F != '%' || *format == '%') { if (F != '%') {
/* %% or any char other than % */
if (F == '%') {
++format;
}
/* Check for a match */ /* Check for a match */
if (isspace (F)) { if ((bool) isspace ((int) F)) {
/* Special white space handling: Any whitespace in the /* Special white space handling: Any whitespace in the
* format string matches any amount of whitespace including * format string matches any amount of whitespace including
@@ -411,46 +527,63 @@ Again:
*/ */
SkipWhite (); SkipWhite ();
continue; continue;
}
} else if (F == C) { Percent:
/* ### Note: The opposite test (C == F)
** would be optimized into buggy code!
*/
if (C != (int) F) {
/* A match. Read the next input character and start over */ /* 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; goto Again;
} else { } else {
/* A mismatch. We will stop scanning the input and return /* A conversion. Skip the percent sign. */
* the number of conversions. /* 0. Check for %% */
*/ if (GetFormat () == '%') {
return Conversions; goto Percent;
} }
} else {
/* A conversion. Skip the percent sign. */
F = *format++;
/* 1. Assignment suppression */ /* 1. Assignment suppression */
if (F == '*') { NoAssign = (F == '*');
F = *format++; if (NoAssign) {
NoAssign = 1; GetFormat ();
} else {
NoAssign = 0;
} }
/* 2. Maximum field width */ /* 2. Maximum field width */
Width = UINT_MAX; Width = UINT_MAX;
HaveWidth = 0; HaveWidth = (bool) isdigit (F);
if (isdigit (F)) { if (HaveWidth) {
HaveWidth = 1;
Width = 0; Width = 0;
do { do {
/* ### Non portable ### */ /* ### Non portable ### */
Width = Width * 10 + (F & 0x0F); Width = Width * 10 + (F & 0x0F);
F = *format++; } while ((bool) isdigit (GetFormat ()));
} while (isdigit (F));
} }
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 */ /* 3. Length modifier */
IntBytes = sizeof(int) - 1; IntBytes = sizeof(int) - 1;
@@ -460,7 +593,7 @@ Again:
IntBytes = sizeof(char) - 1; IntBytes = sizeof(char) - 1;
++format; ++format;
} }
F = *format++; GetFormat ();
break; break;
case 'l': case 'l':
@@ -471,21 +604,20 @@ Again:
/* FALLTHROUGH */ /* FALLTHROUGH */
case 'j': /* intmax_t */ case 'j': /* intmax_t */
IntBytes = sizeof(long) - 1; IntBytes = sizeof(long) - 1;
F = *format++; /* FALLTHROUGH */
break;
case 'z': /* size_t */ case 'z': /* size_t */
case 't': /* ptrdiff_t */ case 't': /* ptrdiff_t */
/* Same size as int */
case 'L': /* long double - ignore this one */ case 'L': /* long double - ignore this one */
F = *format++; GetFormat ();
break;
} }
/* 4. Conversion specifier */ /* 4. Conversion specifier */
switch (F) { switch (F) {
/* 'd' and 'u' conversions are actually the same, since the /* '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. * optionally signed integer.
*/ */
case 'd': /* Optionally signed decimal integer */ case 'd': /* Optionally signed decimal integer */
@@ -509,81 +641,91 @@ Again:
ScanInt (16); ScanInt (16);
break; break;
case 'E':
case 'e':
case 'f':
case 'g':
/* Optionally signed float */
Error (RC_NOCONV);
break;
case 's': case 's':
/* Whitespace terminated string */ /* Whitespace-terminated string */
SkipWhite (); SkipWhite ();
if (!NoAssign) { CheckEnd (); /* Is it an input failure? */
Converted = true; /* No, conversion will succeed */
if (NoAssign == false) {
S = va_arg (ap, char*); S = va_arg (ap, char*);
} }
while (!isspace (C) && Width--) { while (C != EOF
if (!NoAssign) { && (bool) isspace (C) == false
&& ++Width) {
if (NoAssign == false) {
*S++ = C; *S++ = C;
} }
ReadChar (); ReadChar ();
} }
/* Terminate the string just read */ /* Terminate the string just read */
if (!NoAssign) { if (NoAssign == false) {
*S = '\0'; *S = '\0';
++Assignments;
} }
++Conversions;
break; break;
case 'c': case 'c':
/* Fixed length string, NOT zero terminated */ /* Fixed-length string, NOT zero-terminated */
if (!HaveWidth) { if (HaveWidth == false) {
/* No width given, default is 1 */ /* No width given, default is 1 */
Width = 1; Width = ~1u;
} }
if (!NoAssign) { CheckEnd (); /* Is it an input failure? */
Converted = true; /* No, at least 1 char. available */
if (NoAssign == false) {
S = va_arg (ap, char*); S = va_arg (ap, char*);
while (Width--) { /* ## 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; *S++ = C;
ReadCharWithCheck (); ReadChar ();
} }
++Assignments;
} else { } else {
/* Just skip as many chars as given */ /* Just skip as many chars as given */
while (Width--) { while (++Width) {
ReadCharWithCheck (); CheckEnd (); /* Is it a matching failure? */
ReadChar ();
} }
} }
++Conversions;
break; break;
case '[': case '[':
/* String using characters from a set */ /* String using characters from a set */
Invert = 0;
/* Clear the set */ /* Clear the set */
memset (CharSet, 0, sizeof (CharSet)); memset (CharSet, 0, sizeof (CharSet));
F = *format++; /* Skip the left-bracket, and test for inversion. */
if (F == '^') { Invert = (GetFormat () == '^');
Invert = 1; if (Invert) {
F = *format++; GetFormat ();
} }
if (F == ']') { if (F == ']') {
AddCharToSet (']'); /* Empty sets aren't allowed; so, a right-bracket
F = *format++; * at the beginning must be a member of the set.
*/
AddCharToSet (F);
GetFormat ();
} }
/* Read the characters that are part of the set */ /* Read the characters that are part of the set */
while (F != ']' && F != '\0') { while (F != '\0' && F != ']') {
if (*format == '-') { if (*format == '-') { /* Look ahead at next char. */
/* A range. Get start and end, skip the '-' */ /* A range. Get start and end, skip the '-' */
Start = F; Start = F;
F = *++format;
++format; ++format;
if (F == ']') { switch (GetFormat ()) {
case '\0':
case ']':
/* '-' as last char means: include '-' */ /* '-' as last char means: include '-' */
AddCharToSet (Start); AddCharToSet (Start);
AddCharToSet ('-'); AddCharToSet ('-');
} else if (F != '\0') { break;
/* Include all chars in the range */ default:
/* Include all characters
* that are in the range.
*/
while (1) { while (1) {
AddCharToSet (Start); AddCharToSet (Start);
if (Start == F) { if (Start == F) {
@@ -592,15 +734,20 @@ Again:
++Start; ++Start;
} }
/* Get next char after range */ /* Get next char after range */
F = *format++; GetFormat ();
} }
} else { } else {
/* Just a character */ /* Just a character */
AddCharToSet (F); AddCharToSet (F);
/* Get next char */ /* 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 */ /* Invert the set if requested */
if (Invert) { if (Invert) {
@@ -611,76 +758,110 @@ Again:
* store them into a string while they are part of * store them into a string while they are part of
* the set. * the set.
*/ */
if (!NoAssign) { Match = false;
if (NoAssign == false) {
S = va_arg (ap, char*); S = va_arg (ap, char*);
while (IsCharInSet (C) && Width--) { }
while (IsCharInSet () && ++Width) {
if (NoAssign == false) {
*S++ = C; *S++ = C;
}
Match = Converted = true;
ReadChar (); ReadChar ();
} }
/* At least one character must match the set. */
if (Match == false) {
goto NoConv;
}
if (NoAssign == false) {
*S = '\0'; *S = '\0';
} else { ++Assignments;
while (IsCharInSet (C) && Width--) {
ReadChar ();
} }
}
++Conversions;
break; break;
case 'p': case 'p':
/* Pointer, format is 0xABCD */ /* Pointer, general format is 0xABCD.
* %hhp --> zero-page pointer
* %hp --> near pointer
* %lp --> far pointer
*/
SkipWhite (); SkipWhite ();
if (CHAR (C) != '0') { if (CHAR (C) != '0') {
Error (RC_NOCONV); goto NoConv;
}
Converted = true;
ReadChar ();
switch (CHAR (C)) {
case 'x':
case 'X':
break;
default:
goto NoConv;
} }
ReadChar (); ReadChar ();
if (CHAR (C) != 'x' && CHAR (C) != 'X') { ReadInt (16);
Error (RC_NOCONV);
}
ReadChar ();
if (ReadInt (16) != 4) { /* 4 chars expected */
Error (RC_NOCONV);
}
AssignInt (); AssignInt ();
++Conversions;
break; break;
case 'n': case 'n':
/* Store characters consumed so far */ /* Store the number of characters consumed so far
IntVal = CharCount; * (the read-ahead character hasn't been consumed).
*/
IntVal = (long) (CharCount - (C == EOF ? 0u : 1u));
AssignInt (); AssignInt ();
/* Don't count it. */
if (NoAssign == false) {
--Assignments;
}
break; 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: default:
/* Invalid conversion */ /* Invalid specification */
Error (RC_NOCONV); _seterrno (EINVAL);
break; Assignments = EOF;
PushBack ();
return Assignments;
} }
} }
} }
/* Push back the last unused character, provided it is not EOF */
if (C != EOF) {
D->unget (C, D->data);
}
} else { } else {
NoConv:
/* Jump via JumpBuf means an error. If this happens at EOF with no /* Coming here means a failure. If that happens at EOF, with no
* conversions, it is considered an error, otherwise the number * conversion attempts, then it is considered an error; otherwise,
* of conversions is returned (the default behaviour). * the number of assignments is returned (the default behaviour).
*/ */
if (C == EOF && CharCount == 0) { if (C == EOF && Converted == false) {
/* Special case: error */ Assignments = EOF; /* Special case: error */
Conversions = EOF; }
} }
} /* Put the read-ahead character back into the input stream. */
PushBack ();
/* Return the number of conversions */ /* Return the number of conversion-and-assignments. */
return Conversions; return Assignments;
} }

View File

@@ -1,7 +1,7 @@
/* /*
* _scanf.h * _scanf.h
* *
* (C) Copyright 2001 Ullrich von Bassewitz (uz@cc65.org) * (c) Copyright 2004, Ullrich von Bassewitz <uz@cc65.org>
* *
*/ */
@@ -16,6 +16,8 @@
* return EOF if no more data is available. * return EOF if no more data is available.
*/ */
typedef int __fastcall__ (*getfunc) (void* data); 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); typedef int __fastcall__ (*ungetfunc) (int c, void* data);
@@ -27,15 +29,13 @@ typedef int __fastcall__ (*ungetfunc) (int c, void* data);
struct scanfdata { struct scanfdata {
getfunc get; /* Pointer to input routine */ getfunc get; /* Pointer to input routine */
ungetfunc unget; /* Pointer to pushback routine */ ungetfunc unget; /* Pointer to pushback routine */
void* data; /* Pointer to struct. used outside of _scanf() */
/* Fields used outside of _scanf */
void* data; /* Caller data */
}; };
/* Internal scanning routine */ /* 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);

View File

@@ -1,36 +0,0 @@
/*
* scanf.c
*
* Ullrich von Bassewitz (uz@cc65.org), 2004-11-26
*
*/
#include <stdio.h>
/*****************************************************************************/
/* 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);
}

74
libsrc/common/scanf.s Normal file
View File

@@ -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

View File

@@ -1,14 +1,19 @@
; ;
; int __fastcall__ vfscanf (FILE* f, const char* format, va_list ap); ; 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 .export _vfscanf
.import _fgetc, _ungetc .import _fgetc, _ungetc, _ferror
.include "zeropage.inc" .include "zeropage.inc"
.include "_scanf.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) ; int __fastcall__ vfscanf (FILE* f, const char* format, va_list ap)
; /* Standard C function */ ; /* Standard C function */
; { ; {
; struct scanfdata d;
;
; /* Initialize the data struct. We do only need the given file as user data, ; /* 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 ; * because the (getfunc) and (ungetfunc) functions are crafted so that they
; * and ungetc functions. ; * match the standard-I/O fgetc() and ungetc().
; */ ; */
; d.get = (getfunc) fgetc, ; static struct scanfdata d = {
; d.unget = (ungetfunc) ungetc, ; ( getfunc) fgetc,
; d.data = f; ; (ungetfunc) ungetc
; };
; static int count;
; ;
; /* Call the internal function and return the result */ ; d.data = (void*) f;
; return _scanf (&d, format, ap); ;
; /* Call the internal function */
; count = _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, ; Because _scanf() has the same parameter stack as vfscanf(), with f replaced
; we will do exactly that. _scanf will then clean up the stack, so we can jump ; by &d, we will do exactly that. _scanf() then will clean up the stack.
; directly there, no need to return.
; Beware: Since ap is a fastcall parameter, we must not destroy a/x. ; Beware: Since ap is a fastcall parameter, we must not destroy a/x.
; ;
@@ -63,8 +72,27 @@ _vfscanf:
lda #>d lda #>d
sta (sp),y 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 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 #<EOF
tax
rts
; Or, return the result of the scan
L1: lda count
ldx count+1
rts

View File

@@ -2,10 +2,12 @@
; int __fastcall__ vsscanf (const char* str, const char* format, va_list ap); ; int __fastcall__ vsscanf (const char* str, const char* format, va_list ap);
; /* Standard C function */ ; /* Standard C function */
; ;
; Ullrich von Bassewitz, 2004-11-28 ; 2004-11-28, Ullrich von Bassewitz
; 2004-12-21, Greg King
; ;
.export _vsscanf .export _vsscanf
.import popax, __scanf .import popax, __scanf
.importzp sp, ptr1, ptr2 .importzp sp, ptr1, ptr2
@@ -25,14 +27,13 @@
; static int __fastcall__ get (struct sscanfdata* d) ; static int __fastcall__ get (struct sscanfdata* d)
; /* Read a character from the input string and return it */ ; /* Read a character from the input string and return it */
; { ; {
; char C; ; char C = d->str[d->index];
; if (C = d->str[d->index]) { ; if (C == '\0') {
; return EOF;
; }
; /* Increment index only if end not reached */ ; /* Increment index only if end not reached */
; ++d->index; ; ++d->index;
; return C; ; return C;
; } else {
; return EOF;
; }
; } ; }
; ;
@@ -73,17 +74,17 @@
L1: tax ; Save return value L1: tax ; Save return value
tya ; Low byte of index tya ; Low byte of index
ldy #SSCANFDATA::INDEX ldy #SSCANFDATA::INDEX
add #1 add #<1
sta (ptr1),y sta (ptr1),y
iny iny
lda (ptr1),y lda (ptr1),y
adc #$00 adc #>1
sta (ptr1),y sta (ptr1),y
; Return the char just read ; Return the char just read
txa txa
ldx #$00 ldx #>0
rts rts
.endproc .endproc
@@ -110,11 +111,11 @@ L1: tax ; Save return value
ldy #SSCANFDATA::INDEX ldy #SSCANFDATA::INDEX
lda (ptr1),y lda (ptr1),y
sub #1 sub #<1
sta (ptr1),y sta (ptr1),y
iny iny
lda (ptr1),y lda (ptr1),y
sbc #0 sbc #>1
sta (ptr1),y sta (ptr1),y
; Return c ; Return c
@@ -127,15 +128,16 @@ L1: tax ; Save return value
; int __fastcall__ vsscanf (const char* str, const char* format, va_list ap) ; int __fastcall__ vsscanf (const char* str, const char* format, va_list ap)
; /* Standard C function */ ; /* Standard C function */
; { ; {
; struct sscanfdata sd;
; struct scanfdata d;
;
; /* Initialize the data structs. The sscanfdata struct will be passed back ; /* 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; ; static struct sscanfdata sd;
; d.unget = (ungetfunc) unget, ; static const struct scanfdata d = {
; d.data = &sd; ; ( getfunc) get,
; (ungetfunc) unget,
; (void*) &sd
; };
;
; sd.str = str; ; sd.str = str;
; sd.index = 0; ; sd.index = 0;
; ;
@@ -144,10 +146,10 @@ L1: tax ; Save return value
; } ; }
; ;
.data .bss
sd: .tag SSCANFDATA sd: .tag SSCANFDATA
.rodata
d: .addr get d: .addr get
.addr unget .addr unget
.addr sd .addr sd
@@ -177,11 +179,10 @@ d: .addr get
sta sd + SSCANFDATA::INDEX sta sd + SSCANFDATA::INDEX
sta sd + SSCANFDATA::INDEX+1 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 pla
jmp __scanf jmp __scanf
.endproc .endproc