From a07d76b264d2cf19ff283146717e96d5d747907b Mon Sep 17 00:00:00 2001 From: "J.-F. Cap" Date: Sat, 16 Mar 2019 16:46:57 +0100 Subject: [PATCH] use custom string to bigint reader in place of strtol for better compatibility with default janet number reader --- src/core/bigint.c | 29 ++------------ src/core/strtod.c | 98 +++++++++++++++++++++++++++++++++++++++++++++ src/include/janet.h | 2 + test/suite6.janet | 17 ++++---- 4 files changed, 113 insertions(+), 33 deletions(-) diff --git a/src/core/bigint.c b/src/core/bigint.c index a256e97b..dd2ce9b5 100644 --- a/src/core/bigint.c +++ b/src/core/bigint.c @@ -83,29 +83,6 @@ static const JanetAbstractType bi_uint64_type = { uint64_unmarshal }; -static int parse_int64(const char *str, bi_int64 *box) { - char *endptr; - int base = ((str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) || - (str[0] == '-' && str[1] == '0' && (str[2] == 'x' || str[2] == 'X'))) ? 16 : 10; - errno = 0; - *box = (bi_int64)strtoll(str, &endptr, base); - if ((errno == ERANGE && (*box == LLONG_MAX || *box == LLONG_MIN)) || - (errno != 0 && *box == 0) || - (endptr == str)) return 0; - return 1; -} - -static int parse_uint64(const char *str, bi_uint64 *box) { - char *endptr; - int base = (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) ? 16 : 10; - errno = 0; - *box = (bi_int64)strtoull(str, &endptr, base); - if ((errno == ERANGE && (*box == ULLONG_MAX)) || - (errno != 0 && *box == 0) || - (endptr == str)) return 0; - return 1; -} - static bi_int64 check_bi_int64(Janet x) { switch (janet_type(x)) { @@ -117,7 +94,8 @@ static bi_int64 check_bi_int64(Janet x) { } case JANET_STRING: { bi_int64 value; - if (parse_int64((const char *)janet_unwrap_string(x), &value)) + const uint8_t *str = janet_unwrap_string(x); + if (janet_scan_int64(str, janet_string_length(str), &value)) return value; break; } @@ -142,7 +120,8 @@ static bi_uint64 check_bi_uint64(Janet x) { } case JANET_STRING: { bi_uint64 value; - if (parse_uint64((const char *)janet_unwrap_string(x), &value)) + const uint8_t *str = janet_unwrap_string(x); + if (janet_scan_uint64(str, janet_string_length(str), &value)) return value; break; } diff --git a/src/core/strtod.c b/src/core/strtod.c index 615c76a3..b199de7c 100644 --- a/src/core/strtod.c +++ b/src/core/strtod.c @@ -361,3 +361,101 @@ error: free(mant.digits); return 1; } + +#ifdef JANET_BIGINT + +static int scan_bigint( + const uint8_t *str, + int32_t len, + uint64_t *out, + int *neg +) { + const uint8_t *end = str + len; + int seenadigit = 0; + int base = 10; + *neg = 0; + *out = 0; + uint64_t accum = 0; + /* len max is INT64_MAX in base 2 with _ between each bits */ + /* '2r' + 64 bits + 63 _ + sign = 130 => 150 for some leading */ + /* zeros */ + if (len > 150) return 0; + /* Get sign */ + if (str >= end) return 0; + if (*str == '-') { + *neg = 1; + str++; + } else if (*str == '+') { + str++; + } + /* Check for leading 0x or digit digit r */ + if (str + 1 < end && str[0] == '0' && str[1] == 'x') { + base = 16; + str += 2; + } else if (str + 1 < end && + str[0] >= '0' && str[0] <= '9' && + str[1] == 'r') { + base = str[0] - '0'; + str += 2; + } else if (str + 2 < end && + str[0] >= '0' && str[0] <= '9' && + str[1] >= '0' && str[1] <= '9' && + str[2] == 'r') { + base = 10 * (str[0] - '0') + (str[1] - '0'); + if (base < 2 || base > 36) return 0; + str += 3; + } + + /* Skip leading zeros */ + while (str < end && *str == '0') { + seenadigit = 1; + str++; + } + /* Parse significant digits */ + while (str < end) { + if (*str == '_') { + if (!seenadigit) return 0; + } else { + int digit = digit_lookup[*str & 0x7F]; + if (*str > 127 || digit >= base) return 0; + if (accum > (UINT64_MAX - digit) / base) return 0; + accum = accum * base + digit; + seenadigit = 1; + } + str++; + } + + if (!seenadigit) return 0; + *out = accum; + return 1; +} + +int janet_scan_int64(const uint8_t *str, int32_t len, int64_t *out) { + int neg; + uint64_t bi; + if (scan_bigint(str, len, &bi, &neg)) { + if (neg && bi <= 0x8000000000000000UL) { + *out = -bi; + return 1; + } + if (!neg && bi <= 0x7FFFFFFFFFFFFFFFUL) { + *out = bi; + return 1; + } + } + return 0; +} + +int janet_scan_uint64(const uint8_t *str, int32_t len, uint64_t *out) { + int neg; + uint64_t bi; + if (scan_bigint(str, len, &bi, &neg)) { + if (!neg) { + *out = bi; + return 1; + } + } + return 0; +} + +#endif diff --git a/src/include/janet.h b/src/include/janet.h index 36bc4a80..496e8458 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -1338,6 +1338,8 @@ JANET_API Janet janet_bigint_int64(int64_t x); JANET_API Janet janet_bigint_uint64(uint64_t x); JANET_API int64_t janet_checkbigint_int64(Janet x); JANET_API uint64_t janet_checkbigint_uint64(Janet x); +JANET_API int janet_scan_int64(const uint8_t *str, int32_t len, int64_t *out); +JANET_API int janet_scan_uint64(const uint8_t *str, int32_t len, uint64_t *out); #endif diff --git a/test/suite6.janet b/test/suite6.janet index 3d855fb8..a8e1b50a 100644 --- a/test/suite6.janet +++ b/test/suite6.janet @@ -21,7 +21,7 @@ (import test/helper :prefix "" :exit true) (start-suite 6) -# some tests for bigint +# some tests for bigint (def i64 bigint/int64) (def u64 bigint/uint64) @@ -34,8 +34,9 @@ # max double we can convert to int (2^53) (def b (u64 0x1fffffffffffff)) (def b (u64 (math/pow 2 53))) - # from string - (def c (u64 "0xffffffffffffffff")) + # from string + (def c (u64 "0xffff_ffff_ffff_ffff")) + (def c (u64 "32rvv_vv_vv_vv")) (def d (u64 "123456789")))) (assert-no-error @@ -47,7 +48,7 @@ (def b (i64 0x1fffffffffffff)) (def b (i64 (math/pow 2 53))) # from string - (def c (i64 "0x7fffffffffffffff")) + (def c (i64 "0x7fff_ffff_ffff_ffff")) (def d (i64 "123456789")))) (assert-error @@ -58,15 +59,15 @@ (def b (u64 (+ (math/pow 2 53) 1))) # out of range 65 bits (def c (u64 "0x1ffffffffffffffff")) - # just to big + # just to big (def d (u64 "123456789123456789123456789")))) -(assert (:== (:/ (u64 "0xffffffffffffffff") 8 2) "0xfffffffffffffff") "bigint operations") +(assert (:== (:/ (u64 "0xffff_ffff_ffff_ffff") 8 2) "0xfffffffffffffff") "bigint operations") (assert (let [a (u64 0xff)] (:== (:+ a a a a) (:* a 2 2))) "bigint operations") (assert-error "trap INT64_MIN / -1" - (:/ (bigint/int64 "-0x8000000000000000") -1)) + (:/ (bigint/int64 "-0x8000_0000_0000_0000") -1)) # in place operators (assert (let [a (u64 1e10)] (:+! a 1000000 "1000000" "0xffff") (:== a 10002065535)) "in place operators") @@ -79,7 +80,7 @@ (set (t 2) "1000") (set (t 3) (t 0)) (set (t 4) (u64 1000)) - (and + (and (:== (t 0) (t 1)) (:== (t 1) (t 2)) (:== (t 2) (t 3))