src/string.c

changeset 1061
c7d23892eab5
parent 1052
e997862a57d8
child 1063
e453e717876e
--- 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) {

mercurial