From e18e9a988e42e59c1468c59190ceaced15d01d0f Mon Sep 17 00:00:00 2001 From: bumbread Date: Sun, 5 Jun 2022 14:13:27 +1100 Subject: [PATCH] Complete implementation of strto* and ato* functions --- code/errno.c | 4 ++ code/stdlib/conv.c | 166 ++++++++++++++++++++++++++++++--------------- inc/errno.h | 2 +- test/test.c | 43 ++++++++++-- 4 files changed, 155 insertions(+), 60 deletions(-) create mode 100644 code/errno.c diff --git a/code/errno.c b/code/errno.c new file mode 100644 index 0000000..cdb2993 --- /dev/null +++ b/code/errno.c @@ -0,0 +1,4 @@ + +#include + +int errno; diff --git a/code/stdlib/conv.c b/code/stdlib/conv.c index f40392d..aebe2ff 100644 --- a/code/stdlib/conv.c +++ b/code/stdlib/conv.c @@ -9,6 +9,14 @@ // TODO: i made a function that parses longs and i'm fucken // tired. someone make float parsing :kekw: +// Call me weak if you want but I'm actually too lazy to type +// them out every time, also they take up a lot of horiz space. +typedef long long int intll; +typedef long int intl; +typedef unsigned long long int intull; +typedef unsigned long int intul; +typedef unsigned int intu; + #define inrange(start, c, end) ((start) <= (c) && (c) <= (end)) static bool isbase(int c, int base) { @@ -43,92 +51,144 @@ static long todigitl(int c) { return val; } -long -strtol(const char *restrict nptr, char **restrict endptr, int inbase) { - if(!inrange(0, inbase, 36)) { - *endptr = NULL; - return 0; +static intull strto_generic( + const char *restrict nptr, + char **restrict endptr, + int inbase, + intl *coefptr, + intull int_max +) { + const char *restrict str = nptr; + intull value = 0; + int digits_read = 0; + bool is_signed = (coefptr != NULL); + // Find max{abs(int)}. Signed integers have negative, + // whose absolute value is 1 bigger than int_max. + intull int_abs_max = int_max; + if(is_signed) { + ++int_abs_max; } + if(!inrange(0, inbase, 36)) { + goto finish; + } + intull base = (intull)inbase; // Skip space on the beginning - while(isspace(*nptr)) { - ++nptr; + while(isspace(*str)) { + ++str; } // Parse sign - long coef = 1; - if(*nptr == '-') { - coef = -1; - ++nptr; + intl coef = 1; + if(is_signed) { + if(*str == '-') { + coef = -1; + ++str; + } } - if(*nptr == '+') { - ++nptr; + if(*str == '+') { + ++str; } - unsigned long base = (unsigned long)inbase; - unsigned long value = 0; // See if we need to parse base in C-like format - if(*nptr == '0' && *(nptr+1) == 'x') { - ++nptr; + // then set the base accordingly + if(*str == '0' && (*(str+1) == 'x' || *(str+1) == 'X')) { + ++str; if(base == 16 || base == 0) { - ++nptr; + ++str; base = 16; } else { - value = 0; - goto end; + goto finish; } } - else if(*nptr == '0') { - ++nptr; + else if(*str == '0') { + ++str; + ++digits_read; if(base == 8 || base == 0) { base = 8; } } - while(isbase(*nptr, (int)base)) { - unsigned long digit = (unsigned long)todigitl(*nptr); - if(value > (ULONG_MAX - digit)/base) { - errno = ERANGE; - value = 0; - goto end; + // Parse the string of digits in the given base. If the value + // exceeds abs(int_min) we exit with range error. + while(isbase(*str, (int)base)) { + intull digit = (intull)todigitl(*str); + if(value > (int_abs_max - digit)/base) { + goto error_out_of_range; } value = base*value + digit; - ++nptr; + ++str; + ++digits_read; } - unsigned long max_modulo = (unsigned long)LONG_MAX+1; - if(value > max_modulo) { - errno = ERANGE; + // We only allow the modulo of value equal to abs(int_min) if it is + // preceeded by the minus sign. + if(is_signed) { + if(value == int_abs_max && coef != -1) { + goto error_out_of_range; + } + } + goto finish; +error_out_of_range: + // Skip the remainder of the subject string + while(isbase(*str, (int)base)) { + ++str; + } + errno = ERANGE; + value = int_max; + goto finish; +finish: + // If no conversion is performed we return the value of 0 and *endptr + // is set to the nptr. + bool conv_performed = (digits_read > 0); + if(!conv_performed) { value = 0; - goto end; } - if(value == max_modulo) { - if(coef == 1) { - errno = ERANGE; - value = 0; - goto end; + if(endptr != NULL) { + if(!conv_performed) { + *endptr = (char *)nptr; } else { - value = LONG_MIN; - coef = 1; + *endptr = (char *)str; } } -end: - if(endptr != NULL) { - *endptr = (char *)nptr; + *coefptr = coef; + return value; +} + +intl strtol(const char *restrict nptr, char **restrict endptr, int base) { + intull int_max = (intull)LONG_MAX; + intl coef; + intull modulo = strto_generic(nptr, endptr, base, &coef, int_max); + intl value; + if(modulo == int_max) { + value = LONG_MIN; } - return coef*(long)value; + else { + value = coef * (intl)modulo; + } + return value; } -long long -strtoll(const char *restrict nptr, char **restrict endptr, int base) { - return 0; +intll strtoll(const char *restrict nptr, char **restrict endptr, int base) { + intull int_max = (intull)LLONG_MAX; + intl coef; + intull modulo = strto_generic(nptr, endptr, base, &coef, int_max); + intll value; + if(modulo == int_max) { + value = LLONG_MIN; + } + else { + value = (intll)coef * (intll)modulo; + } + return value; } -unsigned long -strtoul(const char *restrict nptr, char **restrict endptr, int base) { - return 0; +intul strtoul(const char *restrict nptr, char **restrict endptr, int base) { + intull int_max = (intull)ULONG_MAX; + intull value = strto_generic(nptr, endptr, base, NULL, int_max); + return (intul)value; } -unsigned long long -strtoull(const char *restrict nptr, char **restrict endptr, int base) { - return 0; +intull strtoull(const char *restrict nptr, char **restrict endptr, int base) { + intull int_max = (intull)ULLONG_MAX; + return strto_generic(nptr, endptr, base, NULL, int_max); } double atof(const char *nptr) { diff --git a/inc/errno.h b/inc/errno.h index a1783de..50f3328 100644 --- a/inc/errno.h +++ b/inc/errno.h @@ -5,4 +5,4 @@ #define ERANGE 3 // TODO: make it thread-local -int errno; +extern int errno; diff --git a/test/test.c b/test/test.c index d895fce..6e69e5f 100644 --- a/test/test.c +++ b/test/test.c @@ -1,13 +1,44 @@ #include #include +#include +#include +#include +#include int main(int argc, char** argv) { - /*for (int i = 0; i < argv; i++) { - printf("[%d] = %s\n", argv[i]); - }*/ - - for (char c = 'a'; c != 'z'; ++c) { - assert(isupper(toupper(c))); + const char *p = "10 200000000000000000000000000000 30 -40 junk"; + // printf("Parsing '%s':\n", p); + + for (;;) + { + // errno can be set to any non-zero value by a library function call + // regardless of whether there was an error, so it needs to be cleared + // in order to check the error set by strtol + errno = 0; + char *end; + const long i = strtol(p, &end, 10); + if (p == end) + break; + + const bool range_error = errno == ERANGE; + // printf("Extracted '%.*s', strtol returned %ld.", (int)(end-p), p, i); + p = end; + + // if (range_error) + // printf(" Range error occurred."); + + // putchar('\n'); } + + // printf("Unextracted leftover: '%s'\n\n", p); + + // parsing without error handling + long v1 = strtol("1010", NULL, 2); + long v2 = strtol("12", NULL, 8); + long v3 = strtol("A", NULL, 16); + long v4 = strtol("junk", NULL, 36); + long v5 = strtol("012", NULL, 0); + long v6 = strtol("0xA", NULL, 0); + long v7 = strtol("junk", NULL, 0); return 0; }