fxlibc/src/libc/stdlib/strto_int.c

97 lines
2.5 KiB
C
Raw Normal View History

#include "stdlib_p.h"
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
/* Parse an integer from a string. Base function for strtol, strtoul, strtoll,
and strtoull. This function:
-> Does not set errno, and instead return the potential error code. Setting
errno would prevent these functions from calling each other as they all
have different ranges, resulting in undue ERANGE.
-> Can parse into both long and long long, depending on what pointer is
non-NULL. */
int strto_int(char const * restrict ptr, char ** restrict endptr, int base,
long *outl, long long *outll, bool use_unsigned)
{
/* Save the value of ptr in endptr now in case the format is invalid */
if(endptr) *endptr = (char *)ptr;
/* Skip initial whitespace */
while(isspace(*ptr)) ptr++;
/* Accept a sign character */
bool negative = false;
if(*ptr == '-') negative = true;
if(*ptr == '-' || *ptr == '+') ptr++;
/* Use unsigned variables as only these have defined overflow */
unsigned long xl = 0;
unsigned long long xll = 0;
int errno_value = 0;
bool valid = false;
/* Read prefixes and determine base */
if((base == 0 || base == 16) && ptr[0]=='0' && tolower(ptr[1])=='x') {
ptr += 2;
base = 16;
}
else if(base == 0 && ptr[0] == '0') {
ptr++;
base = 8;
}
else if(base == 0) {
base = 10;
}
/* Read digits */
while(1) {
int v = -1;
if(isdigit(*ptr)) v = *ptr - '0';
if(islower(*ptr)) v = *ptr - 'a' + 10;
if(v == -1 || v >= base) break;
/* The value is valid as long as there is at least one digit */
valid = true;
/* (x = base*x + v) but with overflow checks */
/*
** TODO FIXME: There is a bug with overflows if the unsigned
** value cannot be represented but the signed value can (which
** is the case only for LONG_MIN and LLONG_MIN)
*/
if(outl) {
if(__builtin_umull_overflow(xl, base, &xl))
errno_value = ERANGE;
if(__builtin_uaddl_overflow(xl, v, &xl))
errno_value = ERANGE;
}
if(outll) {
if(__builtin_umulll_overflow(xll, base, &xll))
errno_value = ERANGE;
if(__builtin_uaddll_overflow(xll, v, &xll))
errno_value = ERANGE;
}
ptr++;
}
/* Handle the sign */
if(negative) {
/* Only -0 can be represented as unsigned */
if(outl && use_unsigned && xl != 0)
errno_value = ERANGE;
if(outll && use_unsigned && xll != 0)
errno_value = ERANGE;
xl = -xl;
xll = -xll;
}
if(outl) *outl = xl;
if(outll) *outll = xll;
if(endptr && valid) *endptr = (char *)ptr;
return errno_value;
}