// Fixed-width integer type infos and methods // This file is intended to be used by defining `INTX_C_H__INT_BITS` before including: // // #define INTX_C_H__INT_BITS 32 // #include "intX.c.h" // #include #include #include #include #include #include #include "../util.h" #include "datatypes.h" #include "fail.h" #include "integers.h" #include "text.h" #include "types.h" #ifndef INTX_C_H__INT_BITS #define INTX_C_H__INT_BITS 32 #endif #define CAT_(a, b) a##b #define CAT(a, b) CAT_(a, b) #define STRINGIFY_(s) #s #define STRINGIFY(s) STRINGIFY_(s) #define INT_T CAT(CAT(int, INTX_C_H__INT_BITS), _t) #define UINT_T CAT(CAT(uint, INTX_C_H__INT_BITS), _t) #define OPT_T CAT(CAT(OptionalInt, INTX_C_H__INT_BITS), _t) #define NAME_STR "Int" STRINGIFY(INTX_C_H__INT_BITS) #define NAMESPACED(method_name) CAT(CAT(Int, INTX_C_H__INT_BITS), CAT($, method_name)) static Text_t _int64_to_text(int64_t n) { if (n == INT64_MIN) return Text("-9223372036854775808"); char buf[21] = {[20] = 0}; // Big enough for INT64_MIN + '\0' char *p = &buf[19]; bool negative = n < 0; if (negative) n = -n; // Safe to do because we checked for INT64_MIN earlier do { *(p--) = '0' + (n % 10); n /= 10; } while (n > 0); if (negative) *(p--) = '-'; return Text$from_strn(p + 1, (size_t)(&buf[19] - p)); } public void NAMESPACED(serialize)(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *info) { (void)info, (void)pointers; #if INTX_C_H__INT_BITS < 32 if (fwrite(obj, sizeof(INT_T), 1, out) != sizeof(INT_T)) fail_text(Text("Failed to write whole integer")); #else INT_T i = *(INT_T *)obj; UINT_T z = (UINT_T)((i << 1L) ^ (i >> (INTX_C_H__INT_BITS - 1L))); // Zigzag encode while (z >= 0x80L) { if (fputc((uint8_t)(z | 0x80L), out) == EOF) fail_text(Text("Failed to write full integer")); z >>= 7L; } fputc((uint8_t)z, out); #endif } public void NAMESPACED(deserialize)(FILE *in, void *outval, List_t *pointers, const TypeInfo_t *info) { (void)info, (void)pointers; #if INTX_C_H__INT_BITS < 32 if (fread(outval, sizeof(INT_T), 1, in) != sizeof(INT_T)) fail_text(Text("Failed to read full integer")); #else UINT_T z = 0; for (size_t shift = 0;; shift += 7) { int i = fgetc(in); if (i == EOF) fail_text(Text("Failed to read whole integer")); uint8_t byte = (uint8_t)i; z |= ((UINT_T)(byte & 0x7F)) << shift; if ((byte & 0x80) == 0) break; } *(INT_T *)outval = (INT_T)((z >> 1L) ^ -(z & 1L)); // Zigzag decode #endif } #ifdef __TINYC__ #define __builtin_add_overflow(x, y, result) \ ({ \ *(result) = (x) + (y); \ false; \ }) #endif public Text_t NAMESPACED(as_text)(const void *i, bool colorize, const TypeInfo_t *info) { (void)info; if (!i) return Text(NAME_STR); Text_t text = _int64_to_text((int64_t)(*(INT_T *)i)); return colorize ? Text$concat(Text("\033[35m"), text, Text("\033[m")) : text; } public Text_t NAMESPACED(value_as_text)(INT_T i) { return _int64_to_text((int64_t)i); } public PUREFUNC int32_t NAMESPACED(compare)(const void *x, const void *y, const TypeInfo_t *info) { (void)info; return (*(INT_T *)x > *(INT_T *)y) - (*(INT_T *)x < *(INT_T *)y); } public PUREFUNC bool NAMESPACED(equal)(const void *x, const void *y, const TypeInfo_t *info) { (void)info; return *(INT_T *)x == *(INT_T *)y; } public CONSTFUNC bool NAMESPACED(is_between)(const INT_T x, const INT_T low, const INT_T high) { return (low <= x && x <= high) || (high <= x && x <= low); } public CONSTFUNC INT_T NAMESPACED(clamped)(INT_T x, INT_T min, INT_T max) { return x < min ? min : (x > max ? max : x); } public Text_t NAMESPACED(hex)(INT_T i, Int_t digits_int, bool uppercase, bool prefix) { Int_t as_int = Int$from_int64((int64_t)i); return Int$hex(as_int, digits_int, uppercase, prefix); } public Text_t NAMESPACED(octal)(INT_T i, Int_t digits_int, bool prefix) { Int_t as_int = Int$from_int64((int64_t)i); return Int$octal(as_int, digits_int, prefix); } public List_t NAMESPACED(bits)(INT_T x) { List_t bit_list = (List_t){.data = GC_MALLOC_ATOMIC(sizeof(bool[8 * sizeof(INT_T)])), .atomic = 1, .stride = sizeof(bool), .length = 8 * sizeof(INT_T)}; bool *bits = bit_list.data + sizeof(INT_T) * 8; for (size_t i = 0; i < 8 * sizeof(INT_T); i++) { *(bits--) = x & 1; x >>= 1; } return bit_list; } public bool NAMESPACED(get_bit)(INT_T x, Int_t bit_index) { if (Int$compare_value(bit_index, I(1)) < 0) fail("Invalid bit index (expected 1 or higher): ", bit_index); if (Int$compare_value(bit_index, Int$from_int64(sizeof(INT_T) * 8)) > 0) fail("Bit index is too large! There are only ", (uint64_t)sizeof(INT_T) * 8, " bits, but index is: ", bit_index); return ((x & (INT_T)(1L << (Int64$from_int(bit_index, true) - 1L))) != 0); } typedef struct { OPT_T current, last; INT_T step; } NAMESPACED(Range_t); static OPT_T _next_int(NAMESPACED(Range_t) * info) { OPT_T i = info->current; if (i.has_value) { INT_T next; bool overflow = __builtin_add_overflow(i.value, info->step, &next); if (overflow || (info->last.has_value && (info->step >= 0 ? next > info->last.value : next < info->last.value))) info->current = (OPT_T){.has_value = false}; else info->current = (OPT_T){.has_value = true, .value = next}; } return i; } public #if INTX_C_H__INT_BITS < 64 CONSTFUNC #endif Closure_t NAMESPACED(to)(INT_T first, INT_T last, OPT_T step) { NAMESPACED(Range_t) *range = GC_MALLOC(sizeof(NAMESPACED(Range_t))); range->current = (OPT_T){.has_value = true, .value = first}; range->last = (OPT_T){.has_value = true, .value = last}; range->step = step.has_value ? step.value : (last >= first ? 1 : -1); return (Closure_t){.fn = _next_int, .userdata = range}; } public #if INTX_C_H__INT_BITS < 64 CONSTFUNC #endif Closure_t NAMESPACED(onward)(INT_T first, INT_T step) { NAMESPACED(Range_t) *range = GC_MALLOC(sizeof(NAMESPACED(Range_t))); range->current = (OPT_T){.has_value = true, .value = first}; range->last = (OPT_T){.has_value = false}; range->step = step; return (Closure_t){.fn = _next_int, .userdata = range}; } public PUREFUNC OPT_T NAMESPACED(parse)(Text_t text, OptionalInt_t base, Text_t *remainder) { OptionalInt_t full_int = Int$parse(text, base, remainder); if (full_int.small == 0L) return (OPT_T){.has_value = false}; if (Int$compare_value(full_int, I(NAMESPACED(min))) < 0) { return (OPT_T){.has_value = false}; } if (Int$compare_value(full_int, I(NAMESPACED(max))) > 0) { return (OPT_T){.has_value = false}; } return (OPT_T){.has_value = true, .value = NAMESPACED(from_int)(full_int, true)}; } public PUREFUNC INT_T NAMESPACED(from_num64)(Num_t n, bool truncate) { INT_T i = (INT_T)n; if (!truncate && unlikely((Num_t)i != n)) fail_text( Text$concat(Text("Could not convert Num to an " NAME_STR " without truncation: "), Num$value_as_text(n))); return i; } public PUREFUNC INT_T NAMESPACED(from_num32)(Num32_t n, bool truncate) { INT_T i = (INT_T)n; if (!truncate && unlikely((Num32_t)i != n)) fail_text(Text$concat(Text("Could not convert Num32 to an " NAME_STR " without truncation: "), Num32$value_as_text(n))); return i; } public PUREFUNC INT_T NAMESPACED(from_int)(Int_t i, bool truncate) { if likely (i.small & 1L) { INT_T ret = i.small >> 2L; #if INTX_C_H__INT_BITS < 32 if (!truncate && unlikely((int64_t)ret != (i.small >> 2L))) fail("Integer is too big to fit in an " NAME_STR ": ", i); #endif return ret; } if (!truncate && unlikely(!mpz_fits_slong_p(i.big))) fail("Integer is too big to fit in an " NAME_STR ": ", i); return mpz_get_si(i.big); } #if INTX_C_H__INT_BITS < 64 public PUREFUNC INT_T NAMESPACED(from_int64)(Int64_t i64, bool truncate) { INT_T i = (INT_T)i64; if (!truncate && unlikely((int64_t)i != i64)) fail("Integer is too big to fit in an " NAME_STR ": ", i64); return i; } #elif INTX_C_H__INT_BITS > 64 public CONSTFUNC INT_T NAMESPACED(from_int64)(Int64_t i) { return (INT_T)i; } #endif #if INTX_C_H__INT_BITS < 32 public PUREFUNC INT_T NAMESPACED(from_int32)(Int32_t i32, bool truncate) { INT_T i = (INT_T)i32; if (!truncate && unlikely((int32_t)i != i32)) fail("Integer is too big to fit in an " NAME_STR ": ", i32); return i; } #elif INTX_C_H__INT_BITS > 32 public CONSTFUNC INT_T NAMESPACED(from_int32)(Int32_t i) { return (INT_T)i; } #endif #if INTX_C_H__INT_BITS < 16 public PUREFUNC INT_T NAMESPACED(from_int16)(Int16_t i16, bool truncate) { INT_T i = (INT_T)i16; if (!truncate && unlikely((int16_t)i != i16)) fail("Integer is too big to fit in an " NAME_STR ": ", i16); return i; } #elif INTX_C_H__INT_BITS > 16 public CONSTFUNC INT_T NAMESPACED(from_int16)(Int16_t i) { return (INT_T)i; } #endif #if INTX_C_H__INT_BITS > 8 public CONSTFUNC INT_T NAMESPACED(from_int8)(Int8_t i) { return (INT_T)i; } #endif public CONSTFUNC INT_T NAMESPACED(gcd)(INT_T x, INT_T y) { if (x == 0 || y == 0) return 0; x = NAMESPACED(abs)(x); y = NAMESPACED(abs)(y); while (x != y) { if (x > y) x -= y; else y -= x; } return x; } public const INT_T NAMESPACED(min) = #if INTX_C_H__INT_BITS == 64 INT64_MIN #elif INTX_C_H__INT_BITS == 32 INT32_MIN #elif INTX_C_H__INT_BITS == 16 INT16_MIN #elif INTX_C_H__INT_BITS == 8 INT8_MIN #else #error "Unsupported integer bit width" #endif ; public const INT_T NAMESPACED(max) = #if INTX_C_H__INT_BITS == 64 INT64_MAX #elif INTX_C_H__INT_BITS == 32 INT32_MAX #elif INTX_C_H__INT_BITS == 16 INT16_MAX #elif INTX_C_H__INT_BITS == 8 INT8_MAX #else #error "Unsupported integer bit width" #endif ; public const TypeInfo_t NAMESPACED(info) = { .size = sizeof(INT_T), .align = __alignof__(INT_T), .metamethods = { .compare = NAMESPACED(compare), .as_text = NAMESPACED(as_text), .serialize = NAMESPACED(serialize), .deserialize = NAMESPACED(deserialize), }, }; #undef CAT_ #undef CAT #undef STRINGIFY_ #undef STRINGIFY #undef INT_T #undef UINT_T #undef OPT_T #undef NAME_STR #undef NAMESPACED #undef INTX_C_H__INT_BITS