1
0
mirror of https://github.com/cc65/cc65.git synced 2024-12-22 12:30:41 +00:00
cc65/libsrc/common/_scanf.c

545 lines
14 KiB
C
Raw Normal View History

/*
* _scanf.c
*
* (C) Copyright 2001-2002 Ullrich von Bassewitz (uz@cc65.org)
*
* 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
* is not as elegant as it could be.
*/
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <setjmp.h>
#include <ctype.h>
#include <limits.h>
#include "_scanf.h"
/*****************************************************************************/
/* SetJmp return codes */
/*****************************************************************************/
#define RC_OK 0 /* Regular call */
#define RC_EOF 1 /* EOF reached */
#define RC_NOCONV 2 /* No conversion possible */
/*****************************************************************************/
/* 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 */
/* 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 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] = {
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
};
/*****************************************************************************/
/* Character sets */
/*****************************************************************************/
static void AddCharToSet (unsigned char C)
/* Set the given bit in 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);
asm ("ora %v,x", CharSet);
asm ("sta %v,x", CharSet);
}
static unsigned char IsCharInSet (unsigned char C)
/* Check if the given 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);
asm ("and %v,x", CharSet);
asm ("ldx #$00");
return __AX__;
}
/*****************************************************************************/
/* Code */
/*****************************************************************************/
static void ReadChar (void)
/* Get an input character, count characters */
{
C = D->fin (D);
++D->ccount;
}
static void SkipWhite (void)
/* Skip white space in the input and return the first non white character */
{
while (isspace (C)) {
ReadChar ();
}
}
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;
}
}
static unsigned char HexVal (char C)
/* Convert a digit to a value */
{
if (isdigit (C)) {
return C - '0';
} else {
return C - toupper (C) + ('A' + 10);
}
}
static void 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);
}
/* Read the value */
IntVal = 0;
while (isxdigit (C) && Width-- > 0) {
IntVal = IntVal * Base + HexVal (C);
ReadChar ();
}
/* One more conversion */
++Conversions;
}
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
* - 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;
}
}
}
int _scanf (struct indesc* 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 */
char Start; /* Start of 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_;
/* Initialize variables */
Conversions = 0;
D->ccount = 0;
/* Set up the jump label. The get() routine will use this label when EOF
* is reached.
*/
Result = setjmp (JumpBuf);
if (Result == 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.
*/
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 */
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';
}
++Conversions;
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--) {
*S++ = C;
ReadChar ();
}
} else {
/* Just skip as many chars as given */
while (Width--) {
ReadChar ();
}
}
++Conversions;
break;
case '[':
/* String using characters from a set */
Invert = 0;
/* Clear the set */
memset (CharSet, 0, sizeof (CharSet));
F = *format++;
if (F == '^') {
Invert = 1;
F = *format++;
}
if (F == ']') {
AddCharToSet (']');
F = *format++;
}
/* Read the characters that are part of the set */
while (F != ']' && F != '\0') {
if (*format == '-') {
/* 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) {
AddCharToSet (Start);
if (Start == F) {
break;
}
++Start;
}
/* Get next char after range */
F = *format++;
}
} else {
/* Just a character */
AddCharToSet (F);
/* Get next char */
F = *format++;
}
}
/* Invert the set if requested */
if (Invert) {
for (Start = 0; Start < sizeof (CharSet); ++Start) {
CharSet[Start] ^= 0xFF;
}
}
/* We have the set in CharSet. Read characters and
* store them into a string while they are part of
* the set.
*/
if (!NoAssign) {
S = va_arg (ap, char*);
while (IsCharInSet (C) && Width--) {
*S++ = C;
ReadChar ();
}
*S = '\0';
} else {
while (IsCharInSet (C) && Width--) {
ReadChar ();
}
}
++Conversions;
break;
case 'p':
/* Pointer, format is 0xABCD */
SkipWhite ();
if (C != '0') {
longjmp (JumpBuf, RC_NOCONV);
}
ReadChar ();
if (C != 'x' && C != 'X') {
longjmp (JumpBuf, RC_NOCONV);
}
ReadChar ();
ReadInt (16);
AssignInt ();
break;
case 'n':
/* Store characters consumed so far */
IntVal = D->ccount;
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;
}
}
/* Return the number of conversions */
return Conversions;
}