--- a/src/string.c Fri Dec 27 13:01:31 2024 +0100 +++ b/src/string.c Sat Dec 28 15:06:15 2024 +0100 @@ -839,17 +839,49 @@ } int cx_strtoll_lc(cxstring str, long long *output, int base, const char *groupsep) { - // TODO: replace temporary implementation - (void) groupsep; // unused in temp impl - char *s = malloc(str.length + 1); - memcpy(s, str.ptr, str.length); - s[str.length] = '\0'; - char *e; - errno = 0; - *output = strtoll(s, &e, base); - int r = errno || !(e && *e == '\0'); - free(s); - return r; + // strategy: parse as unsigned, check range, negate if required + bool neg = false; + size_t start_unsigned = 0; + + // trim already, to search for a sign character + str = cx_strtrim(str); + if (str.length == 0) { + errno = EINVAL; + return -1; + } + + // test if we have a negative sign character + if (str.ptr[start_unsigned] == '-') { + neg = true; + start_unsigned++; + // must not be followed by positive sign character + if (str.length == 1 || str.ptr[start_unsigned] == '+') { + errno = EINVAL; + return -1; + } + } + + // now parse the number with strtoull + unsigned long long v; + cxstring ustr = start_unsigned == 0 ? str + : cx_strn(str.ptr + start_unsigned, str.length - start_unsigned); + int ret = cx_strtoull_lc(ustr, &v, base, groupsep); + if (ret != 0) return ret; + if (neg) { + if (v - 1 > LLONG_MAX) { + errno = ERANGE; + return -1; + } + *output = -(long long) v; + return 0; + } else { + if (v > LLONG_MAX) { + errno = ERANGE; + return -1; + } + *output = (long long) v; + return 0; + } } int cx_strtoi8_lc(cxstring str, int8_t *output, int base, const char *groupsep) { @@ -904,16 +936,83 @@ } int cx_strtoull_lc(cxstring str, unsigned long long *output, int base, const char *groupsep) { - // TODO: replace temporary implementation - (void) groupsep; // unused in temp impl - char *s = malloc(str.length + 1); - memcpy(s, str.ptr, str.length); - s[str.length] = '\0'; - char *e; - *output = strtoull(s, &e, base); - int r = !(e && *e == '\0'); - free(s); - return r; + // some sanity checks + str = cx_strtrim(str); + if (str.length == 0) { + errno = EINVAL; + return -1; + } + if (!(base == 2 || base == 8 || base == 10 || base == 16)) { + errno = EINVAL; + return -1; + } + if (groupsep == NULL) groupsep = ""; + + // find the actual start of the number + if (str.ptr[0] == '+') { + str.ptr++; + str.length--; + if (str.length == 0) { + errno = EINVAL; + return -1; + } + } + size_t start = 0; + + // if base is 2 or 16, some leading stuff may appear + if (base == 2) { + if (str.ptr[0] == 'b' || str.ptr[0] == 'B') { + start = 1; + } else if (str.ptr[0] == '0' && str.length > 1) { + if (str.ptr[1] == 'b' || str.ptr[1] == 'B') { + start = 2; + } + } + } else if (base == 16) { + if (str.ptr[0] == 'x' || str.ptr[0] == 'X' || str.ptr[0] == '#') { + start = 1; + } else if (str.ptr[0] == '0' && str.length > 1) { + if (str.ptr[1] == 'x' || str.ptr[1] == 'X') { + start = 2; + } + } + } + + // check if there are digits left + if (start >= str.length) { + errno = EINVAL; + return -1; + } + + // now parse the number + unsigned long long result = 0; + for (size_t i = start; i < str.length; i++) { + // ignore group separators + if (strchr(groupsep, str.ptr[i])) continue; + + // determine the digit value of the character + unsigned char c = str.ptr[i]; + if (c >= 'a') c = 10 + (c - 'a'); + else if (c >= 'A') c = 10 + (c - 'A'); + else if (c >= '0') c = c - '0'; + else c = 255; + if (c >= base) { + errno = EINVAL; + return -1; + } + + // now combine the digit with what we already have + unsigned long right = (result & 0xff) * base + c; + unsigned long long left = (result >> 8) * base + (right >> 8); + if (left > (ULLONG_MAX >> 8)) { + errno = ERANGE; + return -1; + } + result = (left << 8) + (right & 0xff); + } + + *output = result; + return 0; } int cx_strtou8_lc(cxstring str, uint8_t *output, int base, const char *groupsep) {