/* * Copyright (c) 2022 Calvin Rose & contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef JANET_AMALG #include "features.h" #include #include "util.h" #endif #include #include #include #include #include /* Conditional compilation */ #ifdef JANET_INT_TYPES #define MAX_INT_IN_DBL 9007199254740992ULL /* 2^53 */ static int it_s64_get(void *p, Janet key, Janet *out); static int it_u64_get(void *p, Janet key, Janet *out); static Janet janet_int64_next(void *p, Janet key); static Janet janet_uint64_next(void *p, Janet key); static int32_t janet_int64_hash(void *p1, size_t size) { (void) size; int32_t *words = p1; return words[0] ^ words[1]; } static int janet_int64_compare(void *p1, void *p2) { int64_t x = *((int64_t *)p1); int64_t y = *((int64_t *)p2); return x == y ? 0 : x < y ? -1 : 1; } static int janet_uint64_compare(void *p1, void *p2) { uint64_t x = *((uint64_t *)p1); uint64_t y = *((uint64_t *)p2); return x == y ? 0 : x < y ? -1 : 1; } static void int64_marshal(void *p, JanetMarshalContext *ctx) { janet_marshal_abstract(ctx, p); janet_marshal_int64(ctx, *((int64_t *)p)); } static void *int64_unmarshal(JanetMarshalContext *ctx) { int64_t *p = janet_unmarshal_abstract(ctx, sizeof(int64_t)); p[0] = janet_unmarshal_int64(ctx); return p; } static void it_s64_tostring(void *p, JanetBuffer *buffer) { char str[32]; sprintf(str, "%" PRId64, *((int64_t *)p)); janet_buffer_push_cstring(buffer, str); } static void it_u64_tostring(void *p, JanetBuffer *buffer) { char str[32]; sprintf(str, "%" PRIu64, *((uint64_t *)p)); janet_buffer_push_cstring(buffer, str); } const JanetAbstractType janet_s64_type = { "core/s64", NULL, NULL, it_s64_get, NULL, int64_marshal, int64_unmarshal, it_s64_tostring, janet_int64_compare, janet_int64_hash, janet_int64_next, JANET_ATEND_NEXT }; const JanetAbstractType janet_u64_type = { "core/u64", NULL, NULL, it_u64_get, NULL, int64_marshal, int64_unmarshal, it_u64_tostring, janet_uint64_compare, janet_int64_hash, janet_uint64_next, JANET_ATEND_NEXT }; int64_t janet_unwrap_s64(Janet x) { switch (janet_type(x)) { default: break; case JANET_NUMBER : { double dbl = janet_unwrap_number(x); if (fabs(dbl) <= MAX_INT_IN_DBL) return (int64_t)dbl; break; } case JANET_STRING: { int64_t value; const uint8_t *str = janet_unwrap_string(x); if (janet_scan_int64(str, janet_string_length(str), &value)) return value; break; } case JANET_ABSTRACT: { void *abst = janet_unwrap_abstract(x); if (janet_abstract_type(abst) == &janet_s64_type || (janet_abstract_type(abst) == &janet_u64_type)) return *(int64_t *)abst; break; } } janet_panicf("bad s64 initializer: %t", x); return 0; } uint64_t janet_unwrap_u64(Janet x) { switch (janet_type(x)) { default: break; case JANET_NUMBER : { double dbl = janet_unwrap_number(x); /* Allow negative values to be cast to "wrap around". * This let's addition and subtraction work as expected. */ if (fabs(dbl) <= MAX_INT_IN_DBL) return (uint64_t)dbl; break; } case JANET_STRING: { uint64_t value; const uint8_t *str = janet_unwrap_string(x); if (janet_scan_uint64(str, janet_string_length(str), &value)) return value; break; } case JANET_ABSTRACT: { void *abst = janet_unwrap_abstract(x); if (janet_abstract_type(abst) == &janet_s64_type || (janet_abstract_type(abst) == &janet_u64_type)) return *(uint64_t *)abst; break; } } janet_panicf("bad u64 initializer: %t", x); return 0; } JanetIntType janet_is_int(Janet x) { if (!janet_checktype(x, JANET_ABSTRACT)) return JANET_INT_NONE; const JanetAbstractType *at = janet_abstract_type(janet_unwrap_abstract(x)); return (at == &janet_s64_type) ? JANET_INT_S64 : ((at == &janet_u64_type) ? JANET_INT_U64 : JANET_INT_NONE); } Janet janet_wrap_s64(int64_t x) { int64_t *box = janet_abstract(&janet_s64_type, sizeof(int64_t)); *box = (int64_t)x; return janet_wrap_abstract(box); } Janet janet_wrap_u64(uint64_t x) { uint64_t *box = janet_abstract(&janet_u64_type, sizeof(uint64_t)); *box = (uint64_t)x; return janet_wrap_abstract(box); } JANET_CORE_FN(cfun_it_s64_new, "(int/s64 value)", "Create a boxed signed 64 bit integer from a string value.") { janet_fixarity(argc, 1); return janet_wrap_s64(janet_unwrap_s64(argv[0])); } JANET_CORE_FN(cfun_it_u64_new, "(int/u64 value)", "Create a boxed unsigned 64 bit integer from a string value.") { janet_fixarity(argc, 1); return janet_wrap_u64(janet_unwrap_u64(argv[0])); } JANET_CORE_FN(cfun_to_number, "(int/to-number value)", "Convert an int/u64 or int/s64 to a number. Fails if the number is out of range for an int32.") { janet_fixarity(argc, 1); if (janet_type(argv[0]) == JANET_ABSTRACT) { void *abst = janet_unwrap_abstract(argv[0]); if (janet_abstract_type(abst) == &janet_s64_type) { int64_t value = *((int64_t *)abst); if (value > JANET_INTMAX_INT64) { janet_panicf("cannot convert %q to a number, must be in the range [%q, %q]", argv[0], janet_wrap_number(JANET_INTMIN_DOUBLE), janet_wrap_number(JANET_INTMAX_DOUBLE)); } if (value < -JANET_INTMAX_INT64) { janet_panicf("cannot convert %q to a number, must be in the range [%q, %q]", argv[0], janet_wrap_number(JANET_INTMIN_DOUBLE), janet_wrap_number(JANET_INTMAX_DOUBLE)); } return janet_wrap_number((double)value); } if (janet_abstract_type(abst) == &janet_u64_type) { uint64_t value = *((uint64_t *)abst); if (value > JANET_INTMAX_INT64) { janet_panicf("cannot convert %q to a number, must be in the range [%q, %q]", argv[0], janet_wrap_number(JANET_INTMIN_DOUBLE), janet_wrap_number(JANET_INTMAX_DOUBLE)); } return janet_wrap_number((double)value); } } janet_panicf("expected int/u64 or int/s64, got %q", argv[0]); } JANET_CORE_FN(cfun_to_bytes, "(int/to-bytes value &opt endianness buffer)", "Write the bytes of an `int/s64` or `int/u64` into a buffer.\n" "The `buffer` parameter specifies an existing buffer to write to, if unset a new buffer will be created.\n" "Returns the modified buffer.\n" "The `endianness` paramater indicates the byte order:\n" "- `nil` (unset): system byte order\n" "- `:le`: little-endian, least significant byte first\n" "- `:be`: big-endian, most significant byte first\n") { janet_arity(argc, 1, 3); if (janet_is_int(argv[0]) == JANET_INT_NONE) { janet_panicf("int/to-bytes: expected an int/s64 or int/u64, got %q", argv[0]); } int reverse = 0; if (argc > 1 && !janet_checktype(argv[1], JANET_NIL)) { JanetKeyword endianness_kw = janet_getkeyword(argv, 1); if (!janet_cstrcmp(endianness_kw, "le")) { #if JANET_BIG_ENDIAN reverse = 1; #endif } else if (!janet_cstrcmp(endianness_kw, "be")) { #if JANET_LITTLE_ENDIAN reverse = 1; #endif } else { janet_panicf("int/to-bytes: expected endianness :le, :be or nil, got %v", argv[1]); } } JanetBuffer *buffer = NULL; if (argc > 2 && !janet_checktype(argv[2], JANET_NIL)) { if (!janet_checktype(argv[2], JANET_BUFFER)) { janet_panicf("int/to-bytes: expected buffer or nil, got %q", argv[2]); } buffer = janet_unwrap_buffer(argv[2]); janet_buffer_extra(buffer, 8); } else { buffer = janet_buffer(8); } uint8_t *bytes = janet_unwrap_abstract(argv[0]); if (reverse) { for (int i = 0; i < 8; ++i) { buffer->data[buffer->count + 7 - i] = bytes[i]; } } else { memcpy(buffer->data + buffer->count, bytes, 8); } buffer->count += 8; return janet_wrap_buffer(buffer); } /* * Code to support polymorphic comparison. * int/u64 and int/s64 support a "compare" method that allows * comparison to each other, and to Janet numbers, using the * "compare" "compare<" ... functions. * In the following code explicit casts are sometimes used to help * make it clear when int/float conversions are happening. */ static int compare_double_double(double x, double y) { return (x < y) ? -1 : ((x > y) ? 1 : 0); } static int compare_int64_double(int64_t x, double y) { if (isnan(y)) { return 0; // clojure and python do this } else if ((y > (- ((double) MAX_INT_IN_DBL))) && (y < ((double) MAX_INT_IN_DBL))) { double dx = (double) x; return compare_double_double(dx, y); } else if (y > ((double) INT64_MAX)) { return -1; } else if (y < ((double) INT64_MIN)) { return 1; } else { int64_t yi = (int64_t) y; return (x < yi) ? -1 : ((x > yi) ? 1 : 0); } } static int compare_uint64_double(uint64_t x, double y) { if (isnan(y)) { return 0; // clojure and python do this } else if (y < 0) { return 1; } else if ((y >= 0) && (y < ((double) MAX_INT_IN_DBL))) { double dx = (double) x; return compare_double_double(dx, y); } else if (y > ((double) UINT64_MAX)) { return -1; } else { uint64_t yi = (uint64_t) y; return (x < yi) ? -1 : ((x > yi) ? 1 : 0); } } static Janet cfun_it_s64_compare(int32_t argc, Janet *argv) { janet_fixarity(argc, 2); if (janet_is_int(argv[0]) != JANET_INT_S64) janet_panic("compare method requires int/s64 as first argument"); int64_t x = janet_unwrap_s64(argv[0]); switch (janet_type(argv[1])) { default: break; case JANET_NUMBER : { double y = janet_unwrap_number(argv[1]); return janet_wrap_number(compare_int64_double(x, y)); } case JANET_ABSTRACT: { void *abst = janet_unwrap_abstract(argv[1]); if (janet_abstract_type(abst) == &janet_s64_type) { int64_t y = *(int64_t *)abst; return janet_wrap_number((x < y) ? -1 : (x > y ? 1 : 0)); } else if (janet_abstract_type(abst) == &janet_u64_type) { // comparing signed to unsigned -- be careful! uint64_t y = *(uint64_t *)abst; if (x < 0) { return janet_wrap_number(-1); } else if (y > INT64_MAX) { return janet_wrap_number(-1); } else { int64_t y2 = (int64_t) y; return janet_wrap_number((x < y2) ? -1 : (x > y2 ? 1 : 0)); } } break; } } return janet_wrap_nil(); } static Janet cfun_it_u64_compare(int32_t argc, Janet *argv) { janet_fixarity(argc, 2); if (janet_is_int(argv[0]) != JANET_INT_U64) // is this needed? janet_panic("compare method requires int/u64 as first argument"); uint64_t x = janet_unwrap_u64(argv[0]); switch (janet_type(argv[1])) { default: break; case JANET_NUMBER : { double y = janet_unwrap_number(argv[1]); return janet_wrap_number(compare_uint64_double(x, y)); } case JANET_ABSTRACT: { void *abst = janet_unwrap_abstract(argv[1]); if (janet_abstract_type(abst) == &janet_u64_type) { uint64_t y = *(uint64_t *)abst; return janet_wrap_number((x < y) ? -1 : (x > y ? 1 : 0)); } else if (janet_abstract_type(abst) == &janet_s64_type) { // comparing unsigned to signed -- be careful! int64_t y = *(int64_t *)abst; if (y < 0) { return janet_wrap_number(1); } else if (x > INT64_MAX) { return janet_wrap_number(1); } else { int64_t x2 = (int64_t) x; return janet_wrap_number((x2 < y) ? -1 : (x2 > y ? 1 : 0)); } } break; } } return janet_wrap_nil(); } /* * In C, signed arithmetic overflow is undefined behvior * but unsigned arithmetic overflow is twos complement * * Reference: * https://en.cppreference.com/w/cpp/language/ub * http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html * * This means OPMETHOD & OPMETHODINVERT must always use * unsigned arithmetic internally, regardless of the true type. * This will not affect the end result (property of twos complement). */ #define OPMETHOD(T, type, name, oper) \ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ janet_arity(argc, 2, -1); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = janet_unwrap_##type(argv[0]); \ for (int32_t i = 1; i < argc; i++) \ /* This avoids undefined behavior. See above for why. */ \ *box = (T) ((uint64_t) (*box)) oper ((uint64_t) janet_unwrap_##type(argv[i])); \ return janet_wrap_abstract(box); \ } \ #define OPMETHODINVERT(T, type, name, oper) \ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ janet_fixarity(argc, 2); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = janet_unwrap_##type(argv[1]); \ /* This avoids undefined behavior. See above for why. */ \ *box = (T) ((uint64_t) *box) oper ((uint64_t) janet_unwrap_##type(argv[0])); \ return janet_wrap_abstract(box); \ } \ #define DIVMETHOD(T, type, name, oper) \ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ janet_arity(argc, 2, -1); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = janet_unwrap_##type(argv[0]); \ for (int32_t i = 1; i < argc; i++) { \ T value = janet_unwrap_##type(argv[i]); \ if (value == 0) janet_panic("division by zero"); \ *box oper##= value; \ } \ return janet_wrap_abstract(box); \ } \ #define DIVMETHODINVERT(T, type, name, oper) \ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ janet_fixarity(argc, 2); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = janet_unwrap_##type(argv[1]); \ T value = janet_unwrap_##type(argv[0]); \ if (value == 0) janet_panic("division by zero"); \ *box oper##= value; \ return janet_wrap_abstract(box); \ } \ #define DIVMETHOD_SIGNED(T, type, name, oper) \ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ janet_arity(argc, 2, -1); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = janet_unwrap_##type(argv[0]); \ for (int32_t i = 1; i < argc; i++) { \ T value = janet_unwrap_##type(argv[i]); \ if (value == 0) janet_panic("division by zero"); \ if ((value == -1) && (*box == INT64_MIN)) janet_panic("INT64_MIN divided by -1"); \ *box oper##= value; \ } \ return janet_wrap_abstract(box); \ } \ #define DIVMETHODINVERT_SIGNED(T, type, name, oper) \ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ janet_fixarity(argc, 2); \ T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ *box = janet_unwrap_##type(argv[1]); \ T value = janet_unwrap_##type(argv[0]); \ if (value == 0) janet_panic("division by zero"); \ if ((value == -1) && (*box == INT64_MIN)) janet_panic("INT64_MIN divided by -1"); \ *box oper##= value; \ return janet_wrap_abstract(box); \ } \ static Janet cfun_it_s64_mod(int32_t argc, Janet *argv) { janet_fixarity(argc, 2); int64_t *box = janet_abstract(&janet_s64_type, sizeof(int64_t)); int64_t op1 = janet_unwrap_s64(argv[0]); int64_t op2 = janet_unwrap_s64(argv[1]); int64_t x = op1 % op2; *box = (op1 > 0) ? ((op2 > 0) ? x : (0 == x ? x : x + op2)) : ((op2 > 0) ? (0 == x ? x : x + op2) : x); return janet_wrap_abstract(box); } OPMETHOD(int64_t, s64, add, +) OPMETHOD(int64_t, s64, sub, -) OPMETHODINVERT(int64_t, s64, subi, -) OPMETHOD(int64_t, s64, mul, *) DIVMETHOD_SIGNED(int64_t, s64, div, /) DIVMETHOD_SIGNED(int64_t, s64, rem, %) DIVMETHODINVERT_SIGNED(int64_t, s64, divi, /) OPMETHOD(int64_t, s64, and, &) OPMETHOD(int64_t, s64, or, |) OPMETHOD(int64_t, s64, xor, ^) OPMETHOD(int64_t, s64, lshift, <<) OPMETHOD(int64_t, s64, rshift, >>) OPMETHOD(uint64_t, u64, add, +) OPMETHOD(uint64_t, u64, sub, -) OPMETHODINVERT(uint64_t, u64, subi, -) OPMETHOD(uint64_t, u64, mul, *) DIVMETHOD(uint64_t, u64, div, /) DIVMETHOD(uint64_t, u64, mod, %) DIVMETHODINVERT(uint64_t, u64, divi, /) OPMETHOD(uint64_t, u64, and, &) OPMETHOD(uint64_t, u64, or, |) OPMETHOD(uint64_t, u64, xor, ^) OPMETHOD(uint64_t, u64, lshift, <<) OPMETHOD(uint64_t, u64, rshift, >>) #undef OPMETHOD #undef DIVMETHOD #undef DIVMETHOD_SIGNED #undef COMPMETHOD static JanetMethod it_s64_methods[] = { {"+", cfun_it_s64_add}, {"r+", cfun_it_s64_add}, {"-", cfun_it_s64_sub}, {"r-", cfun_it_s64_subi}, {"*", cfun_it_s64_mul}, {"r*", cfun_it_s64_mul}, {"/", cfun_it_s64_div}, {"r/", cfun_it_s64_divi}, {"mod", cfun_it_s64_mod}, {"rmod", cfun_it_s64_mod}, {"%", cfun_it_s64_rem}, {"r%", cfun_it_s64_rem}, {"&", cfun_it_s64_and}, {"r&", cfun_it_s64_and}, {"|", cfun_it_s64_or}, {"r|", cfun_it_s64_or}, {"^", cfun_it_s64_xor}, {"r^", cfun_it_s64_xor}, {"<<", cfun_it_s64_lshift}, {">>", cfun_it_s64_rshift}, {"compare", cfun_it_s64_compare}, {NULL, NULL} }; static JanetMethod it_u64_methods[] = { {"+", cfun_it_u64_add}, {"r+", cfun_it_u64_add}, {"-", cfun_it_u64_sub}, {"r-", cfun_it_u64_subi}, {"*", cfun_it_u64_mul}, {"r*", cfun_it_u64_mul}, {"/", cfun_it_u64_div}, {"r/", cfun_it_u64_divi}, {"mod", cfun_it_u64_mod}, {"rmod", cfun_it_u64_mod}, {"%", cfun_it_u64_mod}, {"r%", cfun_it_u64_mod}, {"&", cfun_it_u64_and}, {"r&", cfun_it_u64_and}, {"|", cfun_it_u64_or}, {"r|", cfun_it_u64_or}, {"^", cfun_it_u64_xor}, {"r^", cfun_it_u64_xor}, {"<<", cfun_it_u64_lshift}, {">>", cfun_it_u64_rshift}, {"compare", cfun_it_u64_compare}, {NULL, NULL} }; static Janet janet_int64_next(void *p, Janet key) { (void) p; return janet_nextmethod(it_s64_methods, key); } static Janet janet_uint64_next(void *p, Janet key) { (void) p; return janet_nextmethod(it_u64_methods, key); } static int it_s64_get(void *p, Janet key, Janet *out) { (void) p; if (!janet_checktype(key, JANET_KEYWORD)) return 0; return janet_getmethod(janet_unwrap_keyword(key), it_s64_methods, out); } static int it_u64_get(void *p, Janet key, Janet *out) { (void) p; if (!janet_checktype(key, JANET_KEYWORD)) return 0; return janet_getmethod(janet_unwrap_keyword(key), it_u64_methods, out); } /* Module entry point */ void janet_lib_inttypes(JanetTable *env) { JanetRegExt it_cfuns[] = { JANET_CORE_REG("int/s64", cfun_it_s64_new), JANET_CORE_REG("int/u64", cfun_it_u64_new), JANET_CORE_REG("int/to-number", cfun_to_number), JANET_CORE_REG("int/to-bytes", cfun_to_bytes), JANET_REG_END }; janet_core_cfuns_ext(env, NULL, it_cfuns); janet_register_abstract_type(&janet_s64_type); janet_register_abstract_type(&janet_u64_type); } #endif