From c4821c8e6bf1f81c6ea31e11ebc0fc1666e9337b Mon Sep 17 00:00:00 2001 From: David Robillard Date: Thu, 25 Feb 2021 10:27:59 -0500 Subject: Add exess from git@gitlab.com:drobilla/exess.git 4638b1f --- subprojects/exess/src/.clang-tidy | 12 + subprojects/exess/src/attributes.h | 30 ++ subprojects/exess/src/base64.c | 163 ++++++++++ subprojects/exess/src/bigint.c | 605 +++++++++++++++++++++++++++++++++++ subprojects/exess/src/bigint.h | 118 +++++++ subprojects/exess/src/boolean.c | 69 ++++ subprojects/exess/src/byte.c | 45 +++ subprojects/exess/src/canonical.c | 309 ++++++++++++++++++ subprojects/exess/src/coerce.c | 422 ++++++++++++++++++++++++ subprojects/exess/src/datatype.c | 79 +++++ subprojects/exess/src/date.c | 112 +++++++ subprojects/exess/src/date_utils.h | 65 ++++ subprojects/exess/src/datetime.c | 265 +++++++++++++++ subprojects/exess/src/decimal.c | 267 ++++++++++++++++ subprojects/exess/src/decimal.h | 63 ++++ subprojects/exess/src/digits.c | 243 ++++++++++++++ subprojects/exess/src/digits.h | 38 +++ subprojects/exess/src/double.c | 53 +++ subprojects/exess/src/duration.c | 322 +++++++++++++++++++ subprojects/exess/src/exess_config.h | 79 +++++ subprojects/exess/src/float.c | 44 +++ subprojects/exess/src/hex.c | 131 ++++++++ subprojects/exess/src/ieee_float.h | 63 ++++ subprojects/exess/src/int.c | 45 +++ subprojects/exess/src/int_math.c | 78 +++++ subprojects/exess/src/int_math.h | 72 +++++ subprojects/exess/src/long.c | 110 +++++++ subprojects/exess/src/macros.h | 31 ++ subprojects/exess/src/read_utils.c | 51 +++ subprojects/exess/src/read_utils.h | 77 +++++ subprojects/exess/src/scientific.c | 125 ++++++++ subprojects/exess/src/scientific.h | 35 ++ subprojects/exess/src/short.c | 45 +++ subprojects/exess/src/soft_float.c | 161 ++++++++++ subprojects/exess/src/soft_float.h | 71 ++++ subprojects/exess/src/strerror.c | 70 ++++ subprojects/exess/src/string_utils.h | 74 +++++ subprojects/exess/src/strtod.c | 405 +++++++++++++++++++++++ subprojects/exess/src/strtod.h | 33 ++ subprojects/exess/src/time.c | 172 ++++++++++ subprojects/exess/src/time_utils.h | 35 ++ subprojects/exess/src/timezone.c | 150 +++++++++ subprojects/exess/src/timezone.h | 59 ++++ subprojects/exess/src/ubyte.c | 45 +++ subprojects/exess/src/uint.c | 45 +++ subprojects/exess/src/ulong.c | 94 ++++++ subprojects/exess/src/ushort.c | 45 +++ subprojects/exess/src/variant.c | 431 +++++++++++++++++++++++++ subprojects/exess/src/warnings.h | 46 +++ subprojects/exess/src/write_utils.c | 50 +++ subprojects/exess/src/write_utils.h | 85 +++++ subprojects/exess/src/year.c | 98 ++++++ 52 files changed, 6435 insertions(+) create mode 100644 subprojects/exess/src/.clang-tidy create mode 100644 subprojects/exess/src/attributes.h create mode 100644 subprojects/exess/src/base64.c create mode 100644 subprojects/exess/src/bigint.c create mode 100644 subprojects/exess/src/bigint.h create mode 100644 subprojects/exess/src/boolean.c create mode 100644 subprojects/exess/src/byte.c create mode 100644 subprojects/exess/src/canonical.c create mode 100644 subprojects/exess/src/coerce.c create mode 100644 subprojects/exess/src/datatype.c create mode 100644 subprojects/exess/src/date.c create mode 100644 subprojects/exess/src/date_utils.h create mode 100644 subprojects/exess/src/datetime.c create mode 100644 subprojects/exess/src/decimal.c create mode 100644 subprojects/exess/src/decimal.h create mode 100644 subprojects/exess/src/digits.c create mode 100644 subprojects/exess/src/digits.h create mode 100644 subprojects/exess/src/double.c create mode 100644 subprojects/exess/src/duration.c create mode 100644 subprojects/exess/src/exess_config.h create mode 100644 subprojects/exess/src/float.c create mode 100644 subprojects/exess/src/hex.c create mode 100644 subprojects/exess/src/ieee_float.h create mode 100644 subprojects/exess/src/int.c create mode 100644 subprojects/exess/src/int_math.c create mode 100644 subprojects/exess/src/int_math.h create mode 100644 subprojects/exess/src/long.c create mode 100644 subprojects/exess/src/macros.h create mode 100644 subprojects/exess/src/read_utils.c create mode 100644 subprojects/exess/src/read_utils.h create mode 100644 subprojects/exess/src/scientific.c create mode 100644 subprojects/exess/src/scientific.h create mode 100644 subprojects/exess/src/short.c create mode 100644 subprojects/exess/src/soft_float.c create mode 100644 subprojects/exess/src/soft_float.h create mode 100644 subprojects/exess/src/strerror.c create mode 100644 subprojects/exess/src/string_utils.h create mode 100644 subprojects/exess/src/strtod.c create mode 100644 subprojects/exess/src/strtod.h create mode 100644 subprojects/exess/src/time.c create mode 100644 subprojects/exess/src/time_utils.h create mode 100644 subprojects/exess/src/timezone.c create mode 100644 subprojects/exess/src/timezone.h create mode 100644 subprojects/exess/src/ubyte.c create mode 100644 subprojects/exess/src/uint.c create mode 100644 subprojects/exess/src/ulong.c create mode 100644 subprojects/exess/src/ushort.c create mode 100644 subprojects/exess/src/variant.c create mode 100644 subprojects/exess/src/warnings.h create mode 100644 subprojects/exess/src/write_utils.c create mode 100644 subprojects/exess/src/write_utils.h create mode 100644 subprojects/exess/src/year.c (limited to 'subprojects/exess/src') diff --git a/subprojects/exess/src/.clang-tidy b/subprojects/exess/src/.clang-tidy new file mode 100644 index 00000000..8023398e --- /dev/null +++ b/subprojects/exess/src/.clang-tidy @@ -0,0 +1,12 @@ +Checks: > + *, + -*-magic-numbers, + -*-uppercase-literal-suffix, + -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, + -hicpp-multiway-paths-covered, + -llvm-header-guard, + -llvmlibc-*, + -misc-no-recursion, +WarningsAsErrors: '*' +HeaderFilterRegex: '.*' +FormatStyle: file diff --git a/subprojects/exess/src/attributes.h b/subprojects/exess/src/attributes.h new file mode 100644 index 00000000..1575113b --- /dev/null +++ b/subprojects/exess/src/attributes.h @@ -0,0 +1,30 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef EXESS_ATTRIBUTES_H +#define EXESS_ATTRIBUTES_H + +#ifdef __GNUC__ +# define EXESS_I_PURE_FUNC __attribute__((pure)) +# define EXESS_I_CONST_FUNC __attribute__((const)) +# define EXESS_I_MALLOC_FUNC __attribute__((malloc)) +#else +# define EXESS_I_PURE_FUNC +# define EXESS_I_CONST_FUNC +# define EXESS_I_MALLOC_FUNC +#endif + +#endif // EXESS_ATTRIBUTES_H diff --git a/subprojects/exess/src/base64.c b/subprojects/exess/src/base64.c new file mode 100644 index 00000000..de64ee68 --- /dev/null +++ b/subprojects/exess/src/base64.c @@ -0,0 +1,163 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "macros.h" +#include "read_utils.h" +#include "string_utils.h" +#include "write_utils.h" + +#include "exess/exess.h" + +#include +#include +#include + +// Map a 6-bit base64 group to a base64 digit +static inline uint8_t +map(const unsigned group) +{ + assert(group < 64); + + // See . + static const uint8_t b64_map[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + return b64_map[group]; +} + +// Unmap a base64 digit to the numeric value used for decoding +static inline uint8_t +unmap(const uint8_t in) +{ + /* Table indexed by encoded characters that contains the numeric value used + for decoding, shifted up by 47 to be in the range of printable ASCII. A + '$' is a placeholder for characters not in the base64 alphabet. */ + static const uint8_t b64_unmap[] = + "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$m$$$ncdefghijkl$$$$$$" + "$/0123456789:;<=>?@ABCDEFGH$$$$$$IJKLMNOPQRSTUVWXYZ[\\]^_`ab$$$$" + "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" + "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"; + + return (uint8_t)(b64_unmap[in] - 47u); +} + +static char +next_char(const char* const str, size_t* const i) +{ + *i += skip_whitespace(str + *i); + + return str[*i]; +} + +size_t +exess_base64_decoded_size(const size_t length) +{ + return (length * 3) / 4 + 2; +} + +ExessResult +exess_read_base64(ExessBlob* const out, const char* const str) +{ + uint8_t* const uout = (uint8_t*)out->data; + const uint8_t* const ustr = (const uint8_t*)str; + size_t i = 0u; + size_t o = 0u; + + while (str[i]) { + // Skip leading whitespace + i += skip_whitespace(str + i); + if (!str[i]) { + break; + } + + // Read next chunk of 4 input characters + uint8_t in[] = "===="; + for (size_t j = 0; j < 4; ++j) { + const char c = next_char(str, &i); + if (!is_base64(c)) { + return result(EXESS_EXPECTED_BASE64, i); + } + + in[j] = ustr[i++]; + } + + if (in[0] == '=' || in[1] == '=' || (in[2] == '=' && in[3] != '=')) { + return result(EXESS_BAD_VALUE, i); + } + + const size_t n_bytes = 1u + (in[2] != '=') + (in[3] != '='); + if (o + n_bytes > out->size) { + return result(EXESS_NO_SPACE, i); + } + + const uint8_t a1 = (uint8_t)(unmap(in[0]) << 2u); + const uint8_t a2 = unmap(in[1]) >> 4u; + + uout[o++] = a1 | a2; + + if (in[2] != '=') { + const uint8_t b1 = (uint8_t)(unmap(in[1]) << 4u) & 0xF0u; + const uint8_t b2 = unmap(in[2]) >> 2u; + + uout[o++] = b1 | b2; + } + + if (in[3] != '=') { + const uint8_t c1 = (uint8_t)(unmap(in[2]) << 6u) & 0xC0u; + const uint8_t c2 = unmap(in[3]); + + uout[o++] = c1 | c2; + } + } + + out->size = o; + return result(EXESS_SUCCESS, i); +} + +ExessResult +exess_write_base64(const size_t data_size, + const void* const data, + const size_t buf_size, + char* const buf) +{ + const size_t length = (data_size + 2) / 3 * 4; + if (!buf) { + return result(EXESS_SUCCESS, length); + } + + if (buf_size < length + 1) { + return result(EXESS_NO_SPACE, 0); + } + + uint8_t* const out = (uint8_t*)buf; + + size_t o = 0; + for (size_t i = 0; i < data_size; i += 3, o += 4) { + uint8_t in[4] = {0, 0, 0, 0}; + const size_t n_in = MIN(3, data_size - i); + memcpy(in, (const uint8_t*)data + i, n_in); + + out[o] = map(in[0] >> 2u); + out[o + 1] = map(((in[0] & 0x03u) << 4u) | ((in[1] & 0xF0u) >> 4u)); + out[o + 2] = + ((n_in > 1u) ? map(((in[1] & 0x0Fu) << 2u) | ((in[2] & 0xC0u) >> 6u)) + : '='); + + out[o + 3] = ((n_in > 2u) ? map(in[2] & 0x3Fu) : '='); + } + + return end_write(EXESS_SUCCESS, buf_size, buf, o); +} diff --git a/subprojects/exess/src/bigint.c b/subprojects/exess/src/bigint.c new file mode 100644 index 00000000..8c8a95c8 --- /dev/null +++ b/subprojects/exess/src/bigint.c @@ -0,0 +1,605 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "bigint.h" +#include "macros.h" + +#include "int_math.h" + +#include +#include +#include +#include + +typedef uint64_t Hugit; + +static const uint32_t bigit_mask = ~(uint32_t)0; +static const uint64_t carry_mask = (uint64_t) ~(uint32_t)0 << 32u; + +typedef struct { + unsigned bigits; + unsigned bits; +} Offset; + +static inline Offset +make_offset(const unsigned i) +{ + const unsigned bigits = i / BIGINT_BIGIT_BITS; + const unsigned bits = i - bigits * BIGINT_BIGIT_BITS; + + const Offset offset = {bigits, bits}; + return offset; +} + +#ifndef NDEBUG +static inline bool +exess_bigint_is_clamped(const ExessBigint* num) +{ + return num->n_bigits == 0 || num->bigits[num->n_bigits - 1]; +} +#endif + +void +exess_bigint_shift_left(ExessBigint* num, const unsigned amount) +{ + assert(exess_bigint_is_clamped(num)); + if (amount == 0 || num->n_bigits == 0) { + return; + } + + const Offset offset = make_offset(amount); + + assert(num->n_bigits + offset.bigits < BIGINT_MAX_BIGITS); + num->n_bigits += offset.bigits + (bool)offset.bits; + + if (offset.bits == 0) { // Simple bigit-aligned shift + for (unsigned i = num->n_bigits - 1; i >= offset.bigits; --i) { + num->bigits[i] = num->bigits[i - offset.bigits]; + } + } else { // Bigit + sub-bigit bit offset shift + const unsigned right_shift = BIGINT_BIGIT_BITS - offset.bits; + for (unsigned i = num->n_bigits - offset.bigits - 1; i > 0; --i) { + num->bigits[i + offset.bigits] = + (num->bigits[i] << offset.bits) | (num->bigits[i - 1] >> right_shift); + } + + num->bigits[offset.bigits] = num->bigits[0] << offset.bits; + } + + // Zero LSBs + for (unsigned i = 0; i < offset.bigits; ++i) { + num->bigits[i] = 0; + } + + exess_bigint_clamp(num); + assert(exess_bigint_is_clamped(num)); +} + +void +exess_bigint_zero(ExessBigint* num) +{ + static const ExessBigint zero = {{0}, 0}; + + *num = zero; +} + +void +exess_bigint_set(ExessBigint* num, const ExessBigint* value) +{ + *num = *value; +} + +void +exess_bigint_set_u32(ExessBigint* num, const uint32_t value) +{ + exess_bigint_zero(num); + + num->bigits[0] = value; + num->n_bigits = (bool)value; +} + +void +exess_bigint_clamp(ExessBigint* num) +{ + while (num->n_bigits > 0 && num->bigits[num->n_bigits - 1] == 0) { + --num->n_bigits; + } +} + +void +exess_bigint_set_u64(ExessBigint* num, const uint64_t value) +{ + exess_bigint_zero(num); + + num->bigits[0] = (Bigit)(value & bigit_mask); + num->bigits[1] = (Bigit)(value >> BIGINT_BIGIT_BITS); + num->n_bigits = num->bigits[1] ? 2u : num->bigits[0] ? 1u : 0u; +} + +void +exess_bigint_set_pow10(ExessBigint* num, const unsigned exponent) +{ + exess_bigint_set_u32(num, 1); + exess_bigint_multiply_pow10(num, exponent); +} + +static uint32_t +read_u32(const char* const str, uint32_t* result, uint32_t* n_digits) +{ + static const size_t uint32_digits10 = 9; + + *result = *n_digits = 0; + + uint32_t i = 0; + for (; str[i] && *n_digits < uint32_digits10; ++i) { + if (str[i] >= '0' && str[i] <= '9') { + *result = *result * 10u + (unsigned)(str[i] - '0'); + *n_digits += 1; + } else if (str[i] != '.') { + break; + } + } + + return i; +} + +void +exess_bigint_set_decimal_string(ExessBigint* num, const char* const str) +{ + exess_bigint_zero(num); + + uint32_t pos = 0; + uint32_t n_digits = 0; + uint32_t n_read = 0; + uint32_t word = 0; + while ((n_read = read_u32(str + pos, &word, &n_digits))) { + exess_bigint_multiply_u32(num, (uint32_t)POW10[n_digits]); + exess_bigint_add_u32(num, word); + pos += n_read; + } + + exess_bigint_clamp(num); +} + +void +exess_bigint_set_hex_string(ExessBigint* num, const char* const str) +{ + exess_bigint_zero(num); + + // Read digits from right to left until we run off the beginning + const int length = (int)strlen(str); + char digit_buf[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; + int i = length - 8; + for (; i >= 0; i -= 8) { + memcpy(digit_buf, str + i, 8); + num->bigits[num->n_bigits++] = (Bigit)strtoll(digit_buf, NULL, 16); + } + + // Read leftovers into MSB if necessary + if (i > -8) { + memset(digit_buf, 0, sizeof(digit_buf)); + memcpy(digit_buf, str, 8u + (unsigned)i); + num->bigits[num->n_bigits++] = (Bigit)strtoll(digit_buf, NULL, 16); + } + + exess_bigint_clamp(num); +} + +void +exess_bigint_multiply_u32(ExessBigint* num, const uint32_t factor) +{ + switch (factor) { + case 0: + exess_bigint_zero(num); + return; + case 1: + return; + default: + break; + } + + Hugit carry = 0; + for (unsigned i = 0; i < num->n_bigits; ++i) { + const Hugit p = (Hugit)factor * num->bigits[i]; + const Hugit hugit = p + (carry & bigit_mask); + + num->bigits[i] = (Bigit)(hugit & bigit_mask); + + carry = (hugit >> 32u) + (carry >> 32u); + } + + for (; carry; carry >>= 32u) { + assert(num->n_bigits + 1 <= BIGINT_MAX_BIGITS); + num->bigits[num->n_bigits++] = (Bigit)carry; + } +} + +void +exess_bigint_multiply_u64(ExessBigint* num, const uint64_t factor) +{ + switch (factor) { + case 0: + exess_bigint_zero(num); + return; + case 1: + return; + default: + break; + } + + const Hugit f_lo = factor & bigit_mask; + const Hugit f_hi = factor >> 32u; + + Hugit carry = 0; + for (unsigned i = 0; i < num->n_bigits; ++i) { + const Hugit p_lo = f_lo * num->bigits[i]; + const Hugit p_hi = f_hi * num->bigits[i]; + const Hugit hugit = p_lo + (carry & bigit_mask); + + num->bigits[i] = (Bigit)(hugit & bigit_mask); + carry = p_hi + (hugit >> 32u) + (carry >> 32u); + } + + for (; carry; carry >>= 32u) { + assert(num->n_bigits + 1 <= BIGINT_MAX_BIGITS); + num->bigits[num->n_bigits++] = (Bigit)(carry & bigit_mask); + } +} + +void +exess_bigint_multiply_pow10(ExessBigint* num, const unsigned exponent) +{ + /* To reduce multiplication, we exploit 10^e = (2*5)^e = 2^e * 5^e to + factor out an exponentiation by 5 instead of 10. So, we first multiply + by 5^e (hard), then by 2^e (just a single left shift). */ + + // 5^27, the largest power of 5 that fits in 64 bits + static const uint64_t pow5_27 = 7450580596923828125ull; + + // Powers of 5 up to 5^13, the largest that fits in 32 bits + static const uint32_t pow5[] = { + 1, + 5, + 5 * 5, + 5 * 5 * 5, + 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + }; + + if (exponent == 0 || num->n_bigits == 0) { + return; + } + + // Multiply by 5^27 until e < 27 so we can switch to 32 bits + unsigned e = exponent; + while (e >= 27) { + exess_bigint_multiply_u64(num, pow5_27); + e -= 27; + } + + // Multiply by 5^13 until e < 13 so we have only one multiplication left + while (e >= 13) { + exess_bigint_multiply_u32(num, pow5[13]); + e -= 13; + } + + // Multiply by the final 5^e (which may be zero, making this a noop) + exess_bigint_multiply_u32(num, pow5[e]); + + // Finally multiply by 2^e + exess_bigint_shift_left(num, exponent); +} + +int +exess_bigint_compare(const ExessBigint* lhs, const ExessBigint* rhs) +{ + if (lhs->n_bigits < rhs->n_bigits) { + return -1; + } + + if (lhs->n_bigits > rhs->n_bigits) { + return 1; + } + + for (int i = (int)lhs->n_bigits - 1; i >= 0; --i) { + const Bigit bigit_l = lhs->bigits[i]; + const Bigit bigit_r = rhs->bigits[i]; + if (bigit_l < bigit_r) { + return -1; + } + + if (bigit_l > bigit_r) { + return 1; + } + } + + return 0; +} + +int +exess_bigint_plus_compare(const ExessBigint* l, + const ExessBigint* p, + const ExessBigint* c) +{ + assert(exess_bigint_is_clamped(l)); + assert(exess_bigint_is_clamped(p)); + assert(exess_bigint_is_clamped(c)); + + if (l->n_bigits < p->n_bigits) { + return exess_bigint_plus_compare(p, l, c); + } + + if (l->n_bigits + 1 < c->n_bigits) { + return -1; + } + + if (l->n_bigits > c->n_bigits) { + return 1; + } + + if (p->n_bigits < l->n_bigits && l->n_bigits < c->n_bigits) { + return -1; + } + + Hugit borrow = 0; + for (int i = (int)c->n_bigits - 1; i >= 0; --i) { + const Bigit ai = l->bigits[i]; + const Bigit bi = p->bigits[i]; + const Bigit ci = c->bigits[i]; + const Hugit sum = (Hugit)ai + bi; + + if (sum > ci + borrow) { + return 1; + } + + if ((borrow += ci - sum) > 1) { + return -1; + } + + borrow <<= 32u; + } + + return borrow ? -1 : 0; +} + +void +exess_bigint_add_u32(ExessBigint* lhs, const uint32_t rhs) +{ + if (lhs->n_bigits == 0) { + exess_bigint_set_u32(lhs, rhs); + return; + } + + Hugit sum = (Hugit)lhs->bigits[0] + rhs; + Bigit carry = (Bigit)(sum >> 32u); + + lhs->bigits[0] = (Bigit)(sum & bigit_mask); + + unsigned i = 1; + for (; carry; ++i) { + assert(carry == 0 || carry == 1); + + sum = (Hugit)carry + lhs->bigits[i]; + lhs->bigits[i] = (Bigit)(sum & bigit_mask); + carry = (Bigit)((sum & carry_mask) >> 32u); + } + + lhs->n_bigits = MAX(i, lhs->n_bigits); + assert(exess_bigint_is_clamped(lhs)); +} + +void +exess_bigint_add(ExessBigint* lhs, const ExessBigint* rhs) +{ + assert(MAX(lhs->n_bigits, rhs->n_bigits) + 1 <= BIGINT_MAX_BIGITS); + + bool carry = 0; + unsigned i = 0; + for (; i < rhs->n_bigits; ++i) { + const Hugit sum = (Hugit)lhs->bigits[i] + rhs->bigits[i] + carry; + + lhs->bigits[i] = (Bigit)(sum & bigit_mask); + carry = (sum & carry_mask) >> 32u; + } + + for (; carry; ++i) { + const Hugit sum = (Hugit)lhs->bigits[i] + carry; + + lhs->bigits[i] = (Bigit)(sum & bigit_mask); + carry = (sum & carry_mask) >> 32u; + } + + lhs->n_bigits = MAX(i, lhs->n_bigits); + assert(exess_bigint_is_clamped(lhs)); +} + +void +exess_bigint_subtract(ExessBigint* lhs, const ExessBigint* rhs) +{ + assert(exess_bigint_is_clamped(lhs)); + assert(exess_bigint_is_clamped(rhs)); + assert(exess_bigint_compare(lhs, rhs) >= 0); + + bool borrow = 0; + unsigned i = 0; + for (i = 0; i < rhs->n_bigits; ++i) { + const Bigit l = lhs->bigits[i]; + const Bigit r = rhs->bigits[i]; + + lhs->bigits[i] = l - r - borrow; + borrow = l < r || (l == r && borrow); + } + + for (; borrow; ++i) { + const Bigit l = lhs->bigits[i]; + + lhs->bigits[i] -= borrow; + + borrow = l == 0; + } + + exess_bigint_clamp(lhs); +} + +static unsigned +exess_bigint_leading_zeros(const ExessBigint* num) +{ + return 32 * (BIGINT_MAX_BIGITS - num->n_bigits) + + exess_clz32(num->bigits[num->n_bigits - 1]); +} + +// EXESS_I_PURE_FUNC +static Bigit +exess_bigint_left_shifted_bigit_i(const ExessBigint* num, + const Offset amount, + const unsigned index) +{ + /* assert(exess_bigint_is_clamped(num)); */ + if (amount.bigits == 0 && amount.bits == 0) { + return num->bigits[index]; + } + + if (index < amount.bigits) { + return 0; + } + + if (amount.bits == 0) { // Simple bigit-aligned shift + return num->bigits[index - amount.bigits]; + } + + if (index == amount.bigits) { // Last non-zero bigit + return num->bigits[0] << amount.bits; + } + + // Bigit + sub-bigit bit offset shift + const unsigned right_shift = BIGINT_BIGIT_BITS - amount.bits; + return (num->bigits[index - amount.bigits] << amount.bits) | + (num->bigits[index - amount.bigits - 1] >> right_shift); +} + +Bigit +exess_bigint_left_shifted_bigit(const ExessBigint* num, + const unsigned amount, + const unsigned index) +{ + return exess_bigint_left_shifted_bigit_i(num, make_offset(amount), index); +} + +void +exess_bigint_subtract_left_shifted(ExessBigint* lhs, + const ExessBigint* rhs, + const unsigned amount) +{ + assert(exess_bigint_is_clamped(lhs)); + assert(exess_bigint_is_clamped(rhs)); +#ifndef NDEBUG + { + ExessBigint check_rhs = *rhs; + exess_bigint_shift_left(&check_rhs, amount); + assert(exess_bigint_compare(lhs, &check_rhs) >= 0); + } +#endif + + const Offset offset = make_offset(amount); + const unsigned r_n_bigits = rhs->n_bigits + offset.bigits + (bool)offset.bits; + + bool borrow = 0; + unsigned i = 0; + for (i = 0; i < r_n_bigits; ++i) { + const Bigit l = lhs->bigits[i]; + const Bigit r = exess_bigint_left_shifted_bigit_i(rhs, offset, i); + + lhs->bigits[i] = l - r - borrow; + borrow = l < r || ((l == r) && borrow); + } + + for (; borrow; ++i) { + const Bigit l = lhs->bigits[i]; + + lhs->bigits[i] -= borrow; + + borrow = l == 0; + } + + exess_bigint_clamp(lhs); +} + +uint32_t +exess_bigint_divmod(ExessBigint* lhs, const ExessBigint* rhs) +{ + assert(exess_bigint_is_clamped(lhs)); + assert(exess_bigint_is_clamped(rhs)); + assert(rhs->n_bigits > 0); + if (lhs->n_bigits < rhs->n_bigits) { + return 0; + } + + uint32_t result = 0; + const Bigit r0 = rhs->bigits[rhs->n_bigits - 1]; + const unsigned rlz = exess_bigint_leading_zeros(rhs); + + // Shift and subtract until the LHS does not have more bigits + int big_steps = 0; + while (lhs->n_bigits > rhs->n_bigits) { + const unsigned llz = exess_bigint_leading_zeros(lhs); + const unsigned shift = rlz - llz - 1; + + result += 1u << shift; + exess_bigint_subtract_left_shifted(lhs, rhs, shift); + ++big_steps; + } + + // Handle simple termination cases + int cmp = exess_bigint_compare(lhs, rhs); + if (cmp < 0) { + return result; + } + + if (cmp > 0 && lhs->n_bigits == 1) { + assert(rhs->n_bigits == 1); + const Bigit l0 = lhs->bigits[lhs->n_bigits - 1]; + + lhs->bigits[lhs->n_bigits - 1] = l0 % r0; + lhs->n_bigits -= (lhs->bigits[lhs->n_bigits - 1] == 0); + return result + l0 / r0; + } + + // Both now have the same number of digits, finish with subtraction + int final_steps = 0; + for (; cmp >= 0; cmp = exess_bigint_compare(lhs, rhs)) { + const unsigned llz = exess_bigint_leading_zeros(lhs); + if (rlz == llz) { + // Both have the same number of leading zeros, just subtract + exess_bigint_subtract(lhs, rhs); + return result + 1; + } + + const unsigned shift = rlz - llz - 1; + result += 1u << shift; + exess_bigint_subtract_left_shifted(lhs, rhs, shift); + ++final_steps; + } + + return result; +} diff --git a/subprojects/exess/src/bigint.h b/subprojects/exess/src/bigint.h new file mode 100644 index 00000000..088052f6 --- /dev/null +++ b/subprojects/exess/src/bigint.h @@ -0,0 +1,118 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef EXESS_BIGINT_H +#define EXESS_BIGINT_H + +#include "attributes.h" + +#include +#include +#include + +typedef uint32_t Bigit; + +/* We need enough precision for any double, the "largest" of which (using + absolute exponents) is the smallest subnormal ~= 5e-324. This is 1076 bits + long, but we need a bit more space for arithmetic. This is absurd, but such + is decimal. These are only used on the stack so it doesn't hurt too much. +*/ + +#define BIGINT_MAX_SIGNIFICANT_BITS 1280u +#define BIGINT_BIGIT_BITS 32u +#define BIGINT_MAX_BIGITS (BIGINT_MAX_SIGNIFICANT_BITS / BIGINT_BIGIT_BITS) + +typedef struct { + Bigit bigits[BIGINT_MAX_BIGITS]; + unsigned n_bigits; +} ExessBigint; + +void +exess_bigint_zero(ExessBigint* num); + +size_t +exess_bigint_print_hex(FILE* stream, const ExessBigint* num); + +void +exess_bigint_clamp(ExessBigint* num); + +void +exess_bigint_shift_left(ExessBigint* num, unsigned amount); + +void +exess_bigint_set(ExessBigint* num, const ExessBigint* value); + +void +exess_bigint_set_u32(ExessBigint* num, uint32_t value); + +void +exess_bigint_set_u64(ExessBigint* num, uint64_t value); + +void +exess_bigint_set_pow10(ExessBigint* num, unsigned exponent); + +void +exess_bigint_set_decimal_string(ExessBigint* num, const char* str); + +void +exess_bigint_set_hex_string(ExessBigint* num, const char* str); + +void +exess_bigint_multiply_u32(ExessBigint* num, uint32_t factor); + +void +exess_bigint_multiply_u64(ExessBigint* num, uint64_t factor); + +void +exess_bigint_multiply_pow10(ExessBigint* num, unsigned exponent); + +EXESS_I_PURE_FUNC +int +exess_bigint_compare(const ExessBigint* lhs, const ExessBigint* rhs); + +void +exess_bigint_add_u32(ExessBigint* lhs, uint32_t rhs); + +void +exess_bigint_add(ExessBigint* lhs, const ExessBigint* rhs); + +void +exess_bigint_subtract(ExessBigint* lhs, const ExessBigint* rhs); + +EXESS_I_PURE_FUNC +Bigit +exess_bigint_left_shifted_bigit(const ExessBigint* num, + unsigned amount, + unsigned index); + +/// Faster implementation of exess_bigint_subtract(lhs, rhs << amount) +void +exess_bigint_subtract_left_shifted(ExessBigint* lhs, + const ExessBigint* rhs, + unsigned amount); + +/// Faster implementation of exess_bigint_compare(l + p, c) +EXESS_I_PURE_FUNC +int +exess_bigint_plus_compare(const ExessBigint* l, + const ExessBigint* p, + const ExessBigint* c); + +/// Divide and set `lhs` to modulo +uint32_t +exess_bigint_divmod(ExessBigint* lhs, const ExessBigint* rhs); + +#endif // EXESS_BIGINT_H diff --git a/subprojects/exess/src/boolean.c b/subprojects/exess/src/boolean.c new file mode 100644 index 00000000..32657da9 --- /dev/null +++ b/subprojects/exess/src/boolean.c @@ -0,0 +1,69 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "read_utils.h" +#include "write_utils.h" + +#include "exess/exess.h" + +#include +#include + +ExessResult +exess_read_boolean(bool* const out, const char* const str) +{ + size_t i = skip_whitespace(str); + ExessResult r = {EXESS_EXPECTED_BOOLEAN, i}; + + *out = false; + + switch (str[i]) { + case '0': + return end_read(EXESS_SUCCESS, str, ++i); + + case '1': + *out = true; + return end_read(EXESS_SUCCESS, str, ++i); + + case 't': + if (!strncmp(str + i, "true", 4)) { + *out = true; + return end_read(EXESS_SUCCESS, str, i + 4u); + } + break; + + case 'f': + if (!strncmp(str + i, "false", 5)) { + return end_read(EXESS_SUCCESS, str, i + 5u); + } + break; + + default: + break; + } + + return end_read(r.status, str, r.count); +} + +ExessResult +exess_write_boolean(const bool value, const size_t buf_size, char* const buf) +{ + return end_write(EXESS_SUCCESS, + buf_size, + buf, + value ? write_string(4, "true", buf_size, buf, 0) + : write_string(5, "false", buf_size, buf, 0)); +} diff --git a/subprojects/exess/src/byte.c b/subprojects/exess/src/byte.c new file mode 100644 index 00000000..4754d82d --- /dev/null +++ b/subprojects/exess/src/byte.c @@ -0,0 +1,45 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "read_utils.h" + +#include "exess/exess.h" + +#include +#include + +ExessResult +exess_read_byte(int8_t* const out, const char* const str) +{ + int64_t long_out = 0; + const ExessResult r = exess_read_long(&long_out, str); + if (r.status) { + return r; + } + + if (long_out < INT8_MIN || long_out > INT8_MAX) { + return result(EXESS_OUT_OF_RANGE, r.count); + } + + *out = (int8_t)long_out; + return r; +} + +ExessResult +exess_write_byte(const int8_t value, const size_t buf_size, char* const buf) +{ + return exess_write_long(value, buf_size, buf); +} diff --git a/subprojects/exess/src/canonical.c b/subprojects/exess/src/canonical.c new file mode 100644 index 00000000..f70aaecc --- /dev/null +++ b/subprojects/exess/src/canonical.c @@ -0,0 +1,309 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "read_utils.h" +#include "string_utils.h" +#include "write_utils.h" + +#include "exess/exess.h" + +#include +#include + +typedef enum { + EXESS_NEGATIVE, + EXESS_ZERO, + EXESS_POSITIVE, +} ExessIntegerKind; + +/// Return true iff `c` is "+" or "-" +static inline bool +is_sign(const int c) +{ + return c == '+' || c == '-'; +} + +/// Return true iff `c` is "0" +static inline bool +is_zero(const int c) +{ + return c == '0'; +} + +/// Return true iff `c` is "." +static inline bool +is_point(const int c) +{ + return c == '.'; +} + +// Scan forwards as long as `pred` returns true for characters +static inline size_t +scan(bool (*pred)(const int), const char* const str, size_t i) +{ + while (pred(str[i])) { + ++i; + } + + return i; +} + +// Skip the next character if `pred` returns true for it +static inline size_t +skip(bool (*pred)(const int), const char* const str, const size_t i) +{ + return i + (pred(str[i]) ? 1 : 0); +} + +static ExessResult +write_decimal(const char* const str, const size_t buf_size, char* const buf) +{ + size_t i = 0; + + const size_t sign = scan(is_space, str, i); // Sign + const size_t leading = skip(is_sign, str, sign); // First digit + if (str[leading] != '.' && !is_digit(str[leading])) { + return result(EXESS_EXPECTED_DIGIT, sign); + } + + const size_t first = scan(is_zero, str, leading); // First non-zero + const size_t point = scan(is_digit, str, first); // Decimal point + size_t last = scan(is_digit, str, skip(is_point, str, point)); // Last digit + const size_t end = scan(is_space, str, last); // Last non-space + + const ExessStatus st = is_end(str[end]) ? EXESS_SUCCESS : EXESS_EXPECTED_END; + + // Ignore trailing zeros + if (str[point] == '.') { + while (str[last - 1] == '0') { + --last; + } + } + + // Add leading sign only if the number is negative + size_t o = 0; + if (str[sign] == '-') { + o += write_char('-', buf_size, buf, o); + } + + // Handle zero as a special case (no non-zero digits to copy) + if (first == last) { + o += write_string(3, "0.0", buf_size, buf, o); + return result(EXESS_SUCCESS, o); + } + + // Add leading zero if needed to have at least one digit before the point + if (str[first] == '.') { + o += write_char('0', buf_size, buf, o); + } + + // Add digits + o += write_string(last - first, str + first, buf_size, buf, o); + + if (str[point] != '.') { + // Add missing decimal suffix + o += write_string(2, ".0", buf_size, buf, o); + } else if (point == last - 1) { + // Add missing trailing zero after point + o += write_char('0', buf_size, buf, o); + } + + return result(st, o); +} + +static ExessResult +write_integer(const char* const str, + const size_t buf_size, + char* const buf, + ExessIntegerKind* const kind) +{ + const size_t sign = scan(is_space, str, 0); // Sign + const size_t leading = skip(is_sign, str, sign); // First digit + if (!is_digit(str[leading])) { + return result(EXESS_EXPECTED_DIGIT, sign); + } + + const size_t first = scan(is_zero, str, leading); // First non-zero + const size_t last = scan(is_digit, str, first); // Last digit + const size_t end = scan(is_space, str, last); // Last non-space + + const ExessStatus st = is_end(str[end]) ? EXESS_SUCCESS : EXESS_EXPECTED_END; + + // Handle zero as a special case (no non-zero digits to copy) + size_t o = 0; + if (first == last) { + o += write_char('0', buf_size, buf, o); + *kind = EXESS_ZERO; + return result(EXESS_SUCCESS, o); + } + + // Add leading sign only if the number is negative + if (str[sign] == '-') { + *kind = EXESS_NEGATIVE; + o += write_char('-', buf_size, buf, o); + } else { + *kind = EXESS_POSITIVE; + } + + // Add digits + o += write_string(last - first, str + first, buf_size, buf, o); + + return result(st, o); +} + +static ExessResult +write_hex(const char* const str, const size_t buf_size, char* const buf) +{ + size_t i = 0; + size_t o = 0; + + for (; str[i]; ++i) { + if (is_hexdig(str[i])) { + o += write_char(str[i], buf_size, buf, o); + } else if (!is_space(str[i])) { + return result(EXESS_EXPECTED_HEX, o); + } + } + + if (o == 0 || o % 2 != 0) { + return result(EXESS_EXPECTED_HEX, o); + } + + return result(EXESS_SUCCESS, o); +} + +static ExessResult +write_base64(const char* const str, const size_t buf_size, char* const buf) +{ + size_t i = 0; + size_t o = 0; + + for (; str[i]; ++i) { + if (is_base64(str[i])) { + o += write_char(str[i], buf_size, buf, o); + } else if (!is_space(str[i])) { + return result(EXESS_EXPECTED_BASE64, o); + } + } + + if (o == 0 || o % 4 != 0) { + return result(EXESS_EXPECTED_BASE64, o); + } + + return result(EXESS_SUCCESS, o); +} + +static ExessResult +write_bounded(const char* const str, + const ExessDatatype datatype, + const size_t buf_size, + char* const buf) +{ + ExessVariant variant = {EXESS_NOTHING, {EXESS_SUCCESS}}; + const ExessResult r = exess_read_variant(&variant, datatype, str); + + return r.status ? r : exess_write_variant(variant, buf_size, buf); +} + +ExessResult +exess_write_canonical(const char* const str, + const ExessDatatype datatype, + const size_t buf_size, + char* const buf) +{ + ExessIntegerKind kind = EXESS_ZERO; + ExessResult r = {EXESS_UNSUPPORTED, 0}; + + switch (datatype) { + case EXESS_NOTHING: + break; + + case EXESS_BOOLEAN: + r = write_bounded(str, datatype, buf_size, buf); + break; + + case EXESS_DECIMAL: + r = write_decimal(str, buf_size, buf); + break; + + case EXESS_DOUBLE: + case EXESS_FLOAT: + r = write_bounded(str, datatype, buf_size, buf); + break; + + case EXESS_INTEGER: + r = write_integer(str, buf_size, buf, &kind); + break; + + case EXESS_NON_POSITIVE_INTEGER: + r = write_integer(str, buf_size, buf, &kind); + if (kind == EXESS_POSITIVE) { + r.status = EXESS_BAD_VALUE; + } + break; + + case EXESS_NEGATIVE_INTEGER: + r = write_integer(str, buf_size, buf, &kind); + if (kind == EXESS_ZERO || kind == EXESS_POSITIVE) { + r.status = EXESS_BAD_VALUE; + } + break; + + case EXESS_LONG: + case EXESS_INT: + case EXESS_SHORT: + case EXESS_BYTE: + r = write_bounded(str, datatype, buf_size, buf); + break; + + case EXESS_NON_NEGATIVE_INTEGER: + r = write_integer(str, buf_size, buf, &kind); + if (kind == EXESS_NEGATIVE) { + r.status = EXESS_BAD_VALUE; + } + break; + + case EXESS_ULONG: + case EXESS_UINT: + case EXESS_USHORT: + case EXESS_UBYTE: + r = write_bounded(str, datatype, buf_size, buf); + break; + + case EXESS_POSITIVE_INTEGER: + r = write_integer(str, buf_size, buf, &kind); + if (kind == EXESS_NEGATIVE || kind == EXESS_ZERO) { + r.status = EXESS_BAD_VALUE; + } + break; + + case EXESS_DURATION: + case EXESS_DATETIME: + case EXESS_TIME: + case EXESS_DATE: + r = write_bounded(str, datatype, buf_size, buf); + break; + + case EXESS_HEX: + r = write_hex(str, buf_size, buf); + break; + + case EXESS_BASE64: + r = write_base64(str, buf_size, buf); + } + + return end_write(r.status, buf_size, buf, r.count); +} diff --git a/subprojects/exess/src/coerce.c b/subprojects/exess/src/coerce.c new file mode 100644 index 00000000..1a0c35dc --- /dev/null +++ b/subprojects/exess/src/coerce.c @@ -0,0 +1,422 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "exess/exess.h" + +#include +#include + +/* Limits for the range of integers that can be exactly represented in floating + point types. Note that these limits are one less than the largest value, + since values larger than that may round to it which causes problems with + perfect round-tripping. For example, 16777217 when parsed as a float will + result in 1.6777216E7, which a "lossless" coercion would then convert to + 16777216. */ + +#define MAX_FLOAT_INT 16777215 +#define MAX_DOUBLE_INT 9007199254740991L + +static ExessVariant +coerce_long_in_range(const ExessVariant variant, + const int64_t min, + const int64_t max) +{ + const ExessVariant result = exess_coerce(variant, EXESS_LONG, EXESS_LOSSLESS); + if (result.datatype == EXESS_LONG) { + if (result.value.as_long < min || result.value.as_long > max) { + return exess_make_nothing(EXESS_OUT_OF_RANGE); + } + } + + return result; +} + +static ExessVariant +coerce_to_long(const ExessVariant variant, const ExessCoercionFlags coercions) +{ + switch (variant.datatype) { + case EXESS_NOTHING: + return variant; + + case EXESS_BOOLEAN: + return exess_make_long(variant.value.as_bool); + + case EXESS_DECIMAL: + case EXESS_DOUBLE: + if (!(coercions & (ExessCoercionFlags)EXESS_ROUND) && + variant.value.as_double > trunc(variant.value.as_double)) { + return exess_make_nothing(EXESS_WOULD_ROUND); + } + + if (variant.value.as_double < (double)-MAX_DOUBLE_INT || + variant.value.as_double > (double)MAX_DOUBLE_INT) { + return exess_make_nothing(EXESS_OUT_OF_RANGE); + } + + return exess_make_long(lrint(variant.value.as_double)); + + case EXESS_FLOAT: + if (!(coercions & (ExessCoercionFlags)EXESS_ROUND) && + variant.value.as_float > truncf(variant.value.as_float)) { + return exess_make_nothing(EXESS_WOULD_ROUND); + } + + if (variant.value.as_float < (float)-MAX_FLOAT_INT || + variant.value.as_float > (float)MAX_FLOAT_INT) { + return exess_make_nothing(EXESS_OUT_OF_RANGE); + } + + return exess_make_long(lrintf(variant.value.as_float)); + + case EXESS_INTEGER: + case EXESS_NON_POSITIVE_INTEGER: + case EXESS_NEGATIVE_INTEGER: + case EXESS_LONG: + return exess_make_long(variant.value.as_long); + + case EXESS_INT: + return exess_make_long(variant.value.as_int); + + case EXESS_SHORT: + return exess_make_long(variant.value.as_short); + + case EXESS_BYTE: + return exess_make_long(variant.value.as_byte); + + case EXESS_NON_NEGATIVE_INTEGER: + return exess_make_long(variant.value.as_long); + + case EXESS_ULONG: + if (variant.value.as_ulong > INT64_MAX) { + return exess_make_nothing(EXESS_OUT_OF_RANGE); + } + + return exess_make_long((int64_t)variant.value.as_ulong); + + case EXESS_UINT: + return exess_make_long(variant.value.as_uint); + + case EXESS_USHORT: + return exess_make_long(variant.value.as_ushort); + + case EXESS_UBYTE: + return exess_make_long(variant.value.as_ubyte); + + case EXESS_POSITIVE_INTEGER: + if (variant.value.as_ulong > INT64_MAX) { + return exess_make_nothing(EXESS_OUT_OF_RANGE); + } + + return exess_make_long((int64_t)variant.value.as_ulong); + + case EXESS_DURATION: + case EXESS_DATETIME: + case EXESS_TIME: + case EXESS_DATE: + case EXESS_HEX: + case EXESS_BASE64: + break; + } + + return exess_make_nothing(EXESS_UNSUPPORTED); +} + +static ExessVariant +coerce_ulong_in_range(const ExessVariant variant, const uint64_t max) +{ + const ExessVariant result = + exess_coerce(variant, EXESS_ULONG, EXESS_LOSSLESS); + + if (result.datatype == EXESS_ULONG) { + if (variant.value.as_ulong > max) { + return exess_make_nothing(EXESS_OUT_OF_RANGE); + } + } + + return result; +} + +static ExessVariant +coerce_to_ulong(const ExessVariant value, const ExessCoercionFlags coercions) +{ + switch (value.datatype) { + case EXESS_NOTHING: + return value; + + case EXESS_BOOLEAN: + return exess_make_ulong(value.value.as_bool); + + case EXESS_DECIMAL: + case EXESS_DOUBLE: + if (!(coercions & (ExessCoercionFlags)EXESS_ROUND) && + value.value.as_double > trunc(value.value.as_double)) { + return exess_make_nothing(EXESS_WOULD_ROUND); + } + + if (value.value.as_double < 0.0 || + value.value.as_double > (double)MAX_DOUBLE_INT) { + return exess_make_nothing(EXESS_OUT_OF_RANGE); + } + + return exess_make_ulong((uint64_t)lrint(value.value.as_double)); + + case EXESS_FLOAT: + if (!(coercions & (ExessCoercionFlags)EXESS_ROUND) && + value.value.as_float > truncf(value.value.as_float)) { + return exess_make_nothing(EXESS_WOULD_ROUND); + } + + if (value.value.as_float < 0.0f || + value.value.as_float > (float)MAX_FLOAT_INT) { + return exess_make_nothing(EXESS_OUT_OF_RANGE); + } + + return exess_make_ulong((uint64_t)lrintf(value.value.as_float)); + + case EXESS_INTEGER: + case EXESS_NON_POSITIVE_INTEGER: + case EXESS_NEGATIVE_INTEGER: + case EXESS_LONG: + if (value.value.as_long < 0) { + return exess_make_nothing(EXESS_OUT_OF_RANGE); + } + + return exess_make_ulong((uint64_t)value.value.as_long); + + case EXESS_INT: + if (value.value.as_int < 0) { + return exess_make_nothing(EXESS_OUT_OF_RANGE); + } + + return exess_make_ulong((uint64_t)value.value.as_int); + + case EXESS_SHORT: + if (value.value.as_short < 0) { + return exess_make_nothing(EXESS_OUT_OF_RANGE); + } + + return exess_make_ulong((uint64_t)value.value.as_short); + + case EXESS_BYTE: + if (value.value.as_byte < 0) { + return exess_make_nothing(EXESS_OUT_OF_RANGE); + } + + return exess_make_ulong((uint64_t)value.value.as_byte); + + case EXESS_NON_NEGATIVE_INTEGER: + case EXESS_ULONG: + return exess_make_ulong(value.value.as_ulong); + + case EXESS_UINT: + return exess_make_ulong(value.value.as_uint); + + case EXESS_USHORT: + return exess_make_ulong(value.value.as_ushort); + + case EXESS_UBYTE: + return exess_make_ulong(value.value.as_ubyte); + + case EXESS_POSITIVE_INTEGER: + return exess_make_ulong(value.value.as_ulong); + + case EXESS_DURATION: + case EXESS_DATETIME: + case EXESS_TIME: + case EXESS_DATE: + case EXESS_HEX: + case EXESS_BASE64: + break; + } + + return exess_make_nothing(EXESS_UNSUPPORTED); +} + +ExessVariant +exess_coerce(const ExessVariant value, + const ExessDatatype datatype, + const ExessCoercionFlags coercions) +{ + if (datatype == value.datatype) { + return value; + } + + ExessVariant result = value; + + switch (datatype) { + case EXESS_NOTHING: + break; + + case EXESS_BOOLEAN: + result = exess_coerce(value, EXESS_LONG, coercions); + if (result.datatype == EXESS_LONG) { + if (!(coercions & (ExessCoercionFlags)EXESS_TRUNCATE) && + result.value.as_long != 0 && result.value.as_long != 1) { + return exess_make_nothing(EXESS_WOULD_TRUNCATE); + } + + return exess_make_boolean(result.value.as_long != 0); + } + break; + + case EXESS_DECIMAL: + // FIXME + + case EXESS_DOUBLE: + if (value.datatype == EXESS_DECIMAL) { + return exess_make_double(value.value.as_double); + } + + if (value.datatype == EXESS_FLOAT) { + return exess_make_double((double)value.value.as_float); + } + + result = coerce_long_in_range(value, -MAX_DOUBLE_INT, MAX_DOUBLE_INT); + if (result.datatype == EXESS_LONG) { + return exess_make_double((double)result.value.as_long); + } + + break; + + case EXESS_FLOAT: + if (value.datatype == EXESS_DECIMAL || value.datatype == EXESS_DOUBLE) { + if (!(coercions & (ExessCoercionFlags)EXESS_REDUCE_PRECISION)) { + return exess_make_nothing(EXESS_WOULD_REDUCE_PRECISION); + } + + return exess_make_float((float)result.value.as_double); + } else { + result = coerce_long_in_range(value, -MAX_FLOAT_INT, MAX_FLOAT_INT); + if (result.datatype == EXESS_LONG) { + return exess_make_float((float)result.value.as_long); + } + } + + break; + + case EXESS_INTEGER: + result = coerce_to_long(value, coercions); + break; + + case EXESS_NON_POSITIVE_INTEGER: + result = coerce_to_long(value, coercions); + if (result.datatype == EXESS_LONG && result.value.as_long > 0) { + return exess_make_nothing(EXESS_OUT_OF_RANGE); + } + + break; + + case EXESS_NEGATIVE_INTEGER: + result = coerce_to_long(value, coercions); + if (result.datatype == EXESS_LONG && result.value.as_long >= 0) { + return exess_make_nothing(EXESS_OUT_OF_RANGE); + } + break; + + case EXESS_LONG: + return coerce_to_long(value, coercions); + + case EXESS_INT: + result = coerce_long_in_range(value, INT32_MIN, INT32_MAX); + break; + + case EXESS_SHORT: + result = coerce_long_in_range(value, INT16_MIN, INT16_MAX); + break; + + case EXESS_BYTE: + result = coerce_long_in_range(value, INT8_MIN, INT8_MAX); + break; + + case EXESS_NON_NEGATIVE_INTEGER: + case EXESS_ULONG: + result = coerce_to_ulong(value, coercions); + break; + + case EXESS_UINT: + result = coerce_ulong_in_range(value, UINT32_MAX); + break; + + case EXESS_USHORT: + result = coerce_ulong_in_range(value, UINT16_MAX); + break; + + case EXESS_UBYTE: + result = coerce_ulong_in_range(value, UINT8_MAX); + break; + + case EXESS_POSITIVE_INTEGER: + result = coerce_to_ulong(value, coercions); + if (result.datatype == EXESS_ULONG && result.value.as_ulong == 0u) { + return exess_make_nothing(EXESS_OUT_OF_RANGE); + } + break; + + case EXESS_DURATION: + case EXESS_DATETIME: + return exess_make_nothing(EXESS_UNSUPPORTED); + + case EXESS_TIME: + if (value.datatype != EXESS_DATETIME) { + return exess_make_nothing(EXESS_UNSUPPORTED); + } + + if (coercions & (ExessCoercionFlags)EXESS_TRUNCATE) { + const ExessTime time = { + {value.value.as_datetime.is_utc ? 0 : EXESS_LOCAL}, + value.value.as_datetime.hour, + value.value.as_datetime.minute, + value.value.as_datetime.second, + value.value.as_datetime.nanosecond}; + + return exess_make_time(time); + } + + return exess_make_nothing(EXESS_WOULD_TRUNCATE); + + case EXESS_DATE: + if (value.datatype != EXESS_DATETIME) { + return exess_make_nothing(EXESS_UNSUPPORTED); + } + + if (coercions & (ExessCoercionFlags)EXESS_TRUNCATE) { + const ExessDate date = { + value.value.as_datetime.year, + value.value.as_datetime.month, + value.value.as_datetime.day, + {value.value.as_datetime.is_utc ? 0 : EXESS_LOCAL}}; + return exess_make_date(date); + } + + return exess_make_nothing(EXESS_WOULD_TRUNCATE); + + case EXESS_HEX: + return (value.datatype == EXESS_BASE64) + ? exess_make_hex(value.value.as_blob) + : exess_make_nothing(EXESS_UNSUPPORTED); + + case EXESS_BASE64: + return (value.datatype == EXESS_HEX) + ? exess_make_base64(value.value.as_blob) + : exess_make_nothing(EXESS_UNSUPPORTED); + } + + if (result.datatype != EXESS_NOTHING) { + result.datatype = datatype; + } + + return result; +} diff --git a/subprojects/exess/src/datatype.c b/subprojects/exess/src/datatype.c new file mode 100644 index 00000000..c7789597 --- /dev/null +++ b/subprojects/exess/src/datatype.c @@ -0,0 +1,79 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "exess/exess.h" + +#include +#include + +#define N_DATATYPES 24 + +static const char* EXESS_NONNULL const uris[N_DATATYPES + 1] = { + NULL, // + EXESS_XSD_URI "boolean", // + EXESS_XSD_URI "decimal", // + EXESS_XSD_URI "double", // + EXESS_XSD_URI "float", // + EXESS_XSD_URI "integer", // + EXESS_XSD_URI "nonPositiveInteger", // + EXESS_XSD_URI "negativeInteger", // + EXESS_XSD_URI "long", // + EXESS_XSD_URI "int", // + EXESS_XSD_URI "short", // + EXESS_XSD_URI "byte", // + EXESS_XSD_URI "nonNegativeInteger", // + EXESS_XSD_URI "unsignedLong", // + EXESS_XSD_URI "unsignedInt", // + EXESS_XSD_URI "unsignedShort", // + EXESS_XSD_URI "unsignedByte", // + EXESS_XSD_URI "positiveInteger", // + EXESS_XSD_URI "duration", // + EXESS_XSD_URI "datetime", // + EXESS_XSD_URI "time", // + EXESS_XSD_URI "date", // + EXESS_XSD_URI "hexBinary", // + EXESS_XSD_URI "base64Binary", // +}; + +const char* +exess_datatype_uri(const ExessDatatype datatype) +{ + return (datatype > EXESS_NOTHING && datatype <= EXESS_BASE64) ? uris[datatype] + : NULL; +} + +ExessDatatype +exess_datatype_from_uri(const char* const uri) +{ + static const size_t xsd_len = sizeof(EXESS_XSD_URI) - 1; + + if (!strncmp(uri, EXESS_XSD_URI, xsd_len)) { + const char* const name = uri + xsd_len; + for (size_t i = 1; i < N_DATATYPES; ++i) { + if (!strcmp(name, uris[i] + xsd_len)) { + return (ExessDatatype)i; + } + } + } + + return EXESS_NOTHING; +} + +bool +exess_datatype_is_bounded(const ExessDatatype datatype) +{ + return (datatype < N_DATATYPES) ? exess_max_lengths[datatype] != 0 : false; +} diff --git a/subprojects/exess/src/date.c b/subprojects/exess/src/date.c new file mode 100644 index 00000000..77b336f7 --- /dev/null +++ b/subprojects/exess/src/date.c @@ -0,0 +1,112 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "date_utils.h" +#include "read_utils.h" +#include "timezone.h" +#include "write_utils.h" + +#include "exess/exess.h" + +#include +#include + +ExessResult +read_date_numbers(ExessDate* const out, const char* const str) +{ + // Read year at the beginning + size_t i = skip_whitespace(str); + ExessResult r = read_year_number(&out->year, str + i); + if (r.status) { + return result(r.status, i + r.count); + } + + // Read year-month delimiter + i += r.count; + if (str[i] != '-') { + return result(EXESS_EXPECTED_DASH, i); + } + + // Read month + ++i; + if ((r = read_two_digit_number(&out->month, 1, 12, str + i)).status) { + return result(r.status, i + r.count); + } + + // Read month-day delimiter + i += r.count; + if (str[i] != '-') { + return result(EXESS_EXPECTED_DASH, i); + } + + // Read day + ++i; + if ((r = read_two_digit_number(&out->day, 1, 31, str + i)).status) { + return result(r.status, i + r.count); + } + + // Check that day is in range + i += r.count; + if (out->day > days_in_month(out->year, out->month)) { + return result(EXESS_OUT_OF_RANGE, i); + } + + return result(EXESS_SUCCESS, i); +} + +ExessResult +exess_read_date(ExessDate* const out, const char* const str) +{ + memset(out, 0, sizeof(*out)); + + // Read YYYY-MM-DD numbers + size_t i = skip_whitespace(str); + ExessResult r = read_date_numbers(out, str + i); + + i += r.count; + if (r.status || is_end(str[i])) { + out->zone.quarter_hours = EXESS_LOCAL; + return result(r.status, i); + } + + // Read timezone + r = exess_read_timezone(&out->zone, str + i); + + return result(r.status, i + r.count); +} + +ExessResult +exess_write_date(const ExessDate value, const size_t buf_size, char* const buf) +{ + if (value.month < 1 || value.month > 12 || value.day < 1 || value.day > 31) { + return end_write(EXESS_BAD_VALUE, buf_size, buf, 0); + } + + ExessResult r = write_year_number(value.year, buf_size, buf); + size_t o = r.count; + if (r.status) { + return end_write(r.status, buf_size, buf, o); + } + + o += write_char('-', buf_size, buf, o); + o += write_two_digit_number(value.month, buf_size, buf, o); + o += write_char('-', buf_size, buf, o); + o += write_two_digit_number(value.day, buf_size, buf, o); + + r = write_timezone(value.zone, buf_size, buf, o); + + return end_write(r.status, buf_size, buf, o + r.count); +} diff --git a/subprojects/exess/src/date_utils.h b/subprojects/exess/src/date_utils.h new file mode 100644 index 00000000..b864925e --- /dev/null +++ b/subprojects/exess/src/date_utils.h @@ -0,0 +1,65 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef EXESS_DATE_UTILS_H +#define EXESS_DATE_UTILS_H + +#include "exess/exess.h" + +#include +#include +#include + +static inline bool +is_leap_year(const int64_t year) +{ + if (year % 4) { + return false; + } + + if (year % 100) { + return true; + } + + if (year % 400) { + return false; + } + + return true; +} + +static inline uint8_t +days_in_month(const int16_t year, const uint8_t month) +{ + return month == 2u ? (is_leap_year(year) ? 29u : 28u) + : (uint8_t)(30u + (month + (month / 8u)) % 2u); +} + +ExessResult +read_year_number(int16_t* out, const char* str); + +ExessResult +write_year_number(int16_t value, size_t buf_size, char* buf); + +/// Read YYYY-MM-DD date numbers without a timezone +ExessResult +read_date_numbers(ExessDate* out, const char* str); + +EXESS_CONST_FUNC +size_t +exess_timezone_string_length(ExessTimezone value); + +#endif // EXESS_DATE_UTILS_H diff --git a/subprojects/exess/src/datetime.c b/subprojects/exess/src/datetime.c new file mode 100644 index 00000000..0fe56afe --- /dev/null +++ b/subprojects/exess/src/datetime.c @@ -0,0 +1,265 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "date_utils.h" +#include "read_utils.h" +#include "string_utils.h" +#include "time_utils.h" +#include "write_utils.h" + +#include "exess/exess.h" + +#include +#include +#include + +static inline ExessDateTime +infinite_future(const bool is_utc) +{ + const ExessDateTime result = {INT16_MAX, + UINT8_MAX, + UINT8_MAX, + is_utc, + UINT8_MAX, + UINT8_MAX, + UINT8_MAX, + UINT32_MAX}; + + return result; +} + +static inline ExessDateTime +infinite_past(const bool is_utc) +{ + const ExessDateTime result = {INT16_MIN, 0, 0, is_utc, 0, 0, 0, 0}; + + return result; +} + +static int32_t +modulo(const int32_t a, const int32_t low, const int32_t high) +{ + return ((a - low) % (high - low)) + low; +} + +static int32_t +quotient(const int32_t a, const int32_t low, const int32_t high) +{ + return (a - low) / (high - low); +} + +ExessDateTime +exess_add_datetime_duration(const ExessDateTime s, const ExessDuration d) +{ + /* + See . + This algorithm is modified to support subtraction when d is negative. + */ + + const int32_t d_year = d.months / 12; + const int32_t d_month = d.months % 12; + const int32_t d_day = d.seconds / (24 * 60 * 60); + const int32_t d_hour = d.seconds / 60 / 60 % 24; + const int32_t d_minute = d.seconds / 60 % 60; + const int32_t d_second = d.seconds % 60; + const int32_t d_nanosecond = d.nanoseconds; + + ExessDateTime e = {0, 0u, 0u, s.is_utc, 0u, 0u, 0u, 0u}; + int32_t temp = 0; + int32_t carry = 0; + + // Months (may be modified additionally below) + temp = s.month + d_month; + if (temp <= 0) { + e.month = (uint8_t)(12 + modulo(temp, 1, 13)); + carry = quotient(temp, 1, 13) - 1; + } else { + e.month = (uint8_t)modulo(temp, 1, 13); + carry = quotient(temp, 1, 13); + } + + // Years (may be modified additionally below) + temp = s.year + d_year + carry; + if (temp > INT16_MAX) { + return infinite_future(s.is_utc); + } + + if (temp < INT16_MIN) { + return infinite_past(s.is_utc); + } + + e.year = (int16_t)temp; + + // Nanoseconds + temp = (int32_t)s.nanosecond + d_nanosecond; + if (temp < 0) { + e.nanosecond = (uint32_t)(1000000000 + (temp % 1000000000)); + carry = temp / 1000000000 - 1; + } else { + e.nanosecond = (uint32_t)(temp % 1000000000); + carry = temp / 1000000000; + } + + // Seconds + temp = s.second + d_second + carry; + if (temp < 0) { + e.second = (uint8_t)(60 + (temp % 60)); + carry = temp / 60 - 1; + } else { + e.second = (uint8_t)(temp % 60); + carry = temp / 60; + } + + // Minutes + temp = s.minute + d_minute + carry; + if (temp < 0) { + e.minute = (uint8_t)(60 + (temp % 60)); + carry = temp / 60 - 1; + } else { + e.minute = (uint8_t)(temp % 60); + carry = temp / 60; + } + + // Hours + temp = s.hour + d_hour + carry; + if (temp < 0) { + e.hour = (uint8_t)(24 + (temp % 24)); + carry = temp / 24 - 1; + } else { + e.hour = (uint8_t)(temp % 24); + carry = temp / 24; + } + + /* + Carry days into months and years as necessary. Note that the algorithm in + the spec first clamps here, but we don't because no such datetime should + exist (exess_read_datetime refuses to read them) + */ + int32_t day = s.day + d_day + carry; + while (day < 1 || day > days_in_month(e.year, e.month)) { + if (day < 1) { + if (e.month == 1) { + if (e.year == INT16_MIN) { + return infinite_past(s.is_utc); + } + + --e.year; + e.month = 12; + day += days_in_month(e.year, e.month); + } else { + --e.month; + day += days_in_month(e.year, e.month); + } + } else { + day -= days_in_month(e.year, e.month); + if (++e.month > 12) { + if (e.year == INT16_MAX) { + return infinite_future(s.is_utc); + } + + ++e.year; + e.month = (uint8_t)modulo(e.month, 1, 13); + } + } + } + + e.day = (uint8_t)day; + + return e; +} + +ExessResult +exess_read_datetime(ExessDateTime* const out, const char* const str) +{ + out->year = 0; + out->month = 0; + out->day = 0; + + // Read date + ExessDate date = {0, 0u, 0u, {EXESS_LOCAL}}; + const ExessResult dr = read_date_numbers(&date, str); + if (dr.status) { + return dr; + } + + size_t i = dr.count; + if (str[i] != 'T') { + return result(EXESS_EXPECTED_TIME_SEP, i); + } + + ++i; + + // Read time + ExessTime time = {{INT8_MAX}, 0u, 0u, 0u, 0u}; + const ExessResult tr = exess_read_time(&time, str + i); + if (tr.status) { + return result(tr.status, i + tr.count); + } + + i += tr.count; + + const ExessDateTime datetime = {date.year, + date.month, + date.day, + time.zone.quarter_hours != EXESS_LOCAL, + time.hour, + time.minute, + time.second, + time.nanosecond}; + + if (datetime.is_utc) { + const ExessDuration tz_duration = { + 0u, -time.zone.quarter_hours * 15 * 60, 0}; + + *out = exess_add_datetime_duration(datetime, tz_duration); + } else { + *out = datetime; + } + + return result(EXESS_SUCCESS, i); +} + +ExessResult +exess_write_datetime(const ExessDateTime value, + const size_t buf_size, + char* const buf) +{ + const ExessTimezone local = {EXESS_LOCAL}; + const ExessDate date = {value.year, value.month, value.day, local}; + const ExessTimezone zone = {value.is_utc ? 0 : EXESS_LOCAL}; + const ExessTime time = { + zone, value.hour, value.minute, value.second, value.nanosecond}; + + if (!in_range(value.month, 1, 12) || !in_range(value.day, 1, 31) || + !in_range(value.hour, 0, 24) || !in_range(value.minute, 0, 59) || + !in_range(value.second, 0, 59) || value.nanosecond > 999999999) { + return end_write(EXESS_BAD_VALUE, buf_size, buf, 0); + } + + // Write date + ExessResult dr = exess_write_date(date, buf_size, buf); + if (dr.status) { + return end_write(dr.status, buf_size, buf, dr.count); + } + + // Write time delimiter + size_t o = dr.count + write_char('T', buf_size, buf, dr.count); + + // Write time with timezone + const ExessResult tr = write_time(time, buf_size, buf, o); + + return end_write(tr.status, buf_size, buf, o + tr.count); +} diff --git a/subprojects/exess/src/decimal.c b/subprojects/exess/src/decimal.c new file mode 100644 index 00000000..a8ce8ad5 --- /dev/null +++ b/subprojects/exess/src/decimal.c @@ -0,0 +1,267 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "decimal.h" +#include "digits.h" +#include "read_utils.h" +#include "string_utils.h" +#include "strtod.h" +#include "warnings.h" +#include "write_utils.h" + +#include "exess/exess.h" + +#include +#include +#include + +#include + +typedef enum { + EXESS_POINT_AFTER, ///< Decimal point is after all significant digits + EXESS_POINT_BEFORE, ///< Decimal point is before all significant digits + EXESS_POINT_BETWEEN, ///< Decimal point is between significant digits +} ExessPointLocation; + +typedef struct { + ExessPointLocation point_loc; ///< Location of decimal point + unsigned n_zeros_before; ///< Number of extra zeros before point + unsigned n_zeros_after; ///< Number of extra zeros after point +} DecimalMetrics; + +static DecimalMetrics +decimal_metrics(const ExessDecimalDouble count) +{ + const int expt = + count.expt >= 0 ? (count.expt - (int)count.n_digits + 1) : count.expt; + + DecimalMetrics metrics = {EXESS_POINT_AFTER, 0u, 0u}; + + if (count.expt >= (int)count.n_digits - 1) { + metrics.point_loc = EXESS_POINT_AFTER; + metrics.n_zeros_before = (unsigned)count.expt - (count.n_digits - 1u); + metrics.n_zeros_after = 1u; + } else if (count.expt < 0) { + metrics.point_loc = EXESS_POINT_BEFORE; + metrics.n_zeros_before = 1u; + metrics.n_zeros_after = (unsigned)(-expt - 1); + } else { + metrics.point_loc = EXESS_POINT_BETWEEN; + } + + return metrics; +} + +static ExessNumberKind +number_kind(const double d) +{ + EXESS_DISABLE_CONVERSION_WARNINGS + const int fpclass = fpclassify(d); + const bool is_negative = signbit(d); + EXESS_RESTORE_WARNINGS + + switch (fpclass) { + case FP_ZERO: + return is_negative ? EXESS_NEGATIVE_ZERO : EXESS_POSITIVE_ZERO; + case FP_INFINITE: + return is_negative ? EXESS_NEGATIVE_INFINITY : EXESS_POSITIVE_INFINITY; + case FP_NORMAL: + case FP_SUBNORMAL: + return is_negative ? EXESS_NEGATIVE : EXESS_POSITIVE; + default: + break; + } + + return EXESS_NAN; +} + +ExessDecimalDouble +exess_measure_decimal(const double d, const unsigned max_precision) +{ + ExessDecimalDouble value = {number_kind(d), 0, 0, {0}}; + + if (value.kind != EXESS_NEGATIVE && value.kind != EXESS_POSITIVE) { + return value; + } + + // Get decimal digits + const double abs_d = fabs(d); + const ExessDigitCount count = + exess_digits(abs_d, value.digits, max_precision); + + assert(count.count == 1 || value.digits[count.count - 1] != '0'); + + value.n_digits = count.count; + value.expt = count.expt; + + return value; +} + +ExessDecimalDouble +exess_measure_float(const float f) +{ + return exess_measure_decimal((double)f, FLT_DECIMAL_DIG); +} + +ExessDecimalDouble +exess_measure_double(const double d) +{ + return exess_measure_decimal(d, DBL_DECIMAL_DIG); +} + +static size_t +exess_decimal_double_string_length(const ExessDecimalDouble decimal) +{ + switch (decimal.kind) { + case EXESS_NEGATIVE: + break; + case EXESS_NEGATIVE_INFINITY: + return 0; + case EXESS_NEGATIVE_ZERO: + return 4; + case EXESS_POSITIVE_ZERO: + return 3; + case EXESS_POSITIVE: + break; + case EXESS_POSITIVE_INFINITY: + case EXESS_NAN: + return 0; + } + + const DecimalMetrics metrics = decimal_metrics(decimal); + const unsigned n_zeros = metrics.n_zeros_before + metrics.n_zeros_after; + const bool is_negative = decimal.kind == EXESS_NEGATIVE; + + return is_negative + decimal.n_digits + 1 + n_zeros; +} + +static size_t +copy_digits(char* const dest, const char* const src, const size_t n) +{ + memcpy(dest, src, n); + return n; +} + +static size_t +set_zeros(char* const dest, const size_t n) +{ + memset(dest, '0', n); + return n; +} + +static ExessResult +read_decimal_number(double* const out, const char* const str) +{ + *out = (double)NAN; + + if (str[0] == '+' || str[0] == '-') { + if (str[1] != '.' && !is_digit(str[1])) { + return result(EXESS_EXPECTED_DIGIT, 1); + } + } else if (str[0] != '.' && !is_digit(str[0])) { + return result(EXESS_EXPECTED_DIGIT, 0); + } + + const size_t i = skip_whitespace(str); + ExessDecimalDouble in = {EXESS_NAN, 0u, 0, {0}}; + const ExessResult r = parse_decimal(&in, str + i); + + if (!r.status) { + *out = parsed_double_to_double(in); + } + + return result(r.status, i + r.count); +} + +ExessResult +exess_read_decimal(double* const out, const char* const str) +{ + const size_t i = skip_whitespace(str); + const ExessResult r = read_decimal_number(out, str + i); + + return end_read(r.status, str, i + r.count); +} + +ExessResult +exess_write_decimal_double(const ExessDecimalDouble decimal, + const size_t buf_size, + char* const buf) +{ + if (!buf) { + return result(EXESS_SUCCESS, exess_decimal_double_string_length(decimal)); + } + + size_t i = 0; + if (buf_size < 3) { + return end_write(EXESS_NO_SPACE, buf_size, buf, 0); + } + + switch (decimal.kind) { + case EXESS_NEGATIVE: + buf[i++] = '-'; + break; + case EXESS_NEGATIVE_INFINITY: + return end_write(EXESS_BAD_VALUE, buf_size, buf, 0); + case EXESS_NEGATIVE_ZERO: + return write_special(4, "-0.0", buf_size, buf); + case EXESS_POSITIVE_ZERO: + return write_special(3, "0.0", buf_size, buf); + case EXESS_POSITIVE: + break; + case EXESS_POSITIVE_INFINITY: + case EXESS_NAN: + return end_write(EXESS_BAD_VALUE, buf_size, buf, 0); + } + + const DecimalMetrics metrics = decimal_metrics(decimal); + const unsigned n_zeros = metrics.n_zeros_before + metrics.n_zeros_after; + if (buf_size - i <= decimal.n_digits + 1 + n_zeros) { + return end_write(EXESS_NO_SPACE, buf_size, buf, 0); + } + + if (metrics.point_loc == EXESS_POINT_AFTER) { + i += copy_digits(buf + i, decimal.digits, decimal.n_digits); + i += set_zeros(buf + i, metrics.n_zeros_before); + buf[i++] = '.'; + buf[i++] = '0'; + } else if (metrics.point_loc == EXESS_POINT_BEFORE) { + buf[i++] = '0'; + buf[i++] = '.'; + i += set_zeros(buf + i, metrics.n_zeros_after); + i += copy_digits(buf + i, decimal.digits, decimal.n_digits); + } else { + assert(metrics.point_loc == EXESS_POINT_BETWEEN); + assert(decimal.expt >= -1); + + const size_t n_before = (size_t)decimal.expt + 1u; + const size_t n_after = decimal.n_digits - n_before; + + i += copy_digits(buf + i, decimal.digits, n_before); + buf[i++] = '.'; + memcpy(buf + i, decimal.digits + n_before, n_after); + i += n_after; + } + + return end_write(EXESS_SUCCESS, buf_size, buf, i); +} + +ExessResult +exess_write_decimal(const double value, const size_t n, char* const buf) +{ + const ExessDecimalDouble decimal = exess_measure_double(value); + + return exess_write_decimal_double(decimal, n, buf); +} diff --git a/subprojects/exess/src/decimal.h b/subprojects/exess/src/decimal.h new file mode 100644 index 00000000..7c5aa963 --- /dev/null +++ b/subprojects/exess/src/decimal.h @@ -0,0 +1,63 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef EXESS_DECIMAL_H +#define EXESS_DECIMAL_H + +#include "exess/exess.h" + +#include + +// Define C11 numeric constants if the compiler hasn't already +#ifndef FLT_DECIMAL_DIG +# define FLT_DECIMAL_DIG 9 +#endif +#ifndef DBL_DECIMAL_DIG +# define DBL_DECIMAL_DIG 17 +#endif + +typedef enum { + EXESS_NEGATIVE, + EXESS_NEGATIVE_INFINITY, + EXESS_NEGATIVE_ZERO, + EXESS_POSITIVE_ZERO, + EXESS_POSITIVE, + EXESS_POSITIVE_INFINITY, + EXESS_NAN, +} ExessNumberKind; + +typedef struct { + ExessNumberKind kind; ///< Kind of number + int expt; ///< Power of 10 exponent + unsigned n_digits; ///< Number of significant digits + char digits[DBL_DECIMAL_DIG + 2]; ///< Significant digits +} ExessDecimalDouble; + +ExessDecimalDouble +exess_measure_decimal(double d, unsigned max_precision); + +ExessDecimalDouble +exess_measure_float(float f); + +ExessDecimalDouble +exess_measure_double(double d); + +ExessResult +exess_write_decimal_double(ExessDecimalDouble decimal, + size_t buf_size, + char* buf); + +#endif // EXESS_DECIMAL_H diff --git a/subprojects/exess/src/digits.c b/subprojects/exess/src/digits.c new file mode 100644 index 00000000..dd303269 --- /dev/null +++ b/subprojects/exess/src/digits.c @@ -0,0 +1,243 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "digits.h" + +#include "bigint.h" +#include "ieee_float.h" +#include "soft_float.h" +#include "warnings.h" + +#include +#include +#include +#include +#include + +/* + This is more or less just an implementation of the classic rational number + based floating point print routine ("Dragon4"). See "How to Print + Floating-Point Numbers Accurately" by Guy L. Steele Jr. and Jon L White for + the canonical source. The basic idea is to find a big rational between 1 and + 10 where value = (numer / denom) * 10^e, then continuously divide it to + generate decimal digits. + + Unfortunately, this algorithm requires pretty massive bigints to work + correctly for all doubles, and isn't particularly fast. Something like + Grisu3 could be added to improve performance, but that has the annoying + property of needing a more precise fallback in some cases, meaning it would + only add more code, not replace any. Since this is already a pretty + ridiculous amount of code, I'll hold off on this until it becomes a problem, + or somebody comes up with a better algorithm. +*/ + +/// Return true if the number is within the lower boundary +static bool +within_lower(const ExessBigint* const numer, + const ExessBigint* const d_lower, + const bool is_even) +{ + return is_even ? exess_bigint_compare(numer, d_lower) <= 0 + : exess_bigint_compare(numer, d_lower) < 0; +} + +/// Return true if the number is within the upper boundary +static bool +within_upper(const ExessBigint* const numer, + const ExessBigint* const denom, + const ExessBigint* const d_upper, + const bool is_even) +{ + return is_even ? exess_bigint_plus_compare(numer, d_upper, denom) >= 0 + : exess_bigint_plus_compare(numer, d_upper, denom) > 0; +} + +/** + Find values so that 0.1 <= numer/denom < 1 or 1 <= numer/denom < 10. + + @param significand Double significand. + @param exponent Double exponent (base 2). + @param decimal_power Decimal exponent (log10 of the double). + @param[out] numer Numerator of rational number. + @param[out] denom Denominator of rational number. + @param[out] delta Distance to the lower and upper boundaries. +*/ +static void +calculate_initial_values(const uint64_t significand, + const int exponent, + const int decimal_power, + const bool lower_is_closer, + ExessBigint* const numer, + ExessBigint* const denom, + ExessBigint* const delta) +{ + /* Use a common denominator of 2^1 so that boundary distance is an integer. + If the lower boundary is closer, we need to scale everything but the + lower boundary to compensate, so add another factor of two here (this is + faster than shifting them again later as in the paper). */ + const unsigned lg_denom = 1u + lower_is_closer; + + if (exponent >= 0) { + // delta = 2^e + exess_bigint_set_u32(delta, 1); + exess_bigint_shift_left(delta, (unsigned)exponent); + + // numer = f * 2^e + exess_bigint_set_u64(numer, significand); + exess_bigint_shift_left(numer, (unsigned)exponent + lg_denom); + + // denom = 10^d + exess_bigint_set_pow10(denom, (unsigned)decimal_power); + exess_bigint_shift_left(denom, lg_denom); + } else if (decimal_power >= 0) { + // delta = 2^e, which is just 1 here since 2^-e is in the denominator + exess_bigint_set_u32(delta, 1); + + // numer = f + exess_bigint_set_u64(numer, significand); + exess_bigint_shift_left(numer, lg_denom); + + // denom = 10^d * 2^-e + exess_bigint_set_pow10(denom, (unsigned)decimal_power); + exess_bigint_shift_left(denom, (unsigned)-exponent + lg_denom); + } else { + // delta = 10^d + exess_bigint_set_pow10(delta, (unsigned)-decimal_power); + + // numer = f * 10^-d + exess_bigint_set(numer, delta); + exess_bigint_multiply_u64(numer, significand); + exess_bigint_shift_left(numer, lg_denom); + + // denom = 2^-exponent + exess_bigint_set_u32(denom, 1); + exess_bigint_shift_left(denom, (unsigned)-exponent + lg_denom); + } +} + +#ifndef NDEBUG +static bool +check_initial_values(const ExessBigint* const numer, + const ExessBigint* const denom, + const ExessBigint* const d_upper) +{ + ExessBigint upper = *numer; + exess_bigint_add(&upper, d_upper); + assert(exess_bigint_compare(&upper, denom) >= 0); + + const uint32_t div = exess_bigint_divmod(&upper, denom); + assert(div >= 1 && div < 10); + return true; +} +#endif + +static unsigned +emit_digits(ExessBigint* const numer, + const ExessBigint* const denom, + ExessBigint* const d_lower, + ExessBigint* const d_upper, + const bool is_even, + char* const buffer, + const size_t max_digits) +{ + unsigned length = 0; + for (size_t i = 0; i < max_digits; ++i) { + // Emit the next digit + const uint32_t digit = exess_bigint_divmod(numer, denom); + assert(digit <= 9); + buffer[length++] = (char)('0' + digit); + + // Check for termination + const bool within_low = within_lower(numer, d_lower, is_even); + const bool within_high = within_upper(numer, denom, d_upper, is_even); + if (!within_low && !within_high) { + exess_bigint_multiply_u32(numer, 10); + exess_bigint_multiply_u32(d_lower, 10); + if (d_lower != d_upper) { + exess_bigint_multiply_u32(d_upper, 10); + } + } else { + if (!within_low || (within_high && exess_bigint_plus_compare( + numer, numer, denom) >= 0)) { + // In high only, or halfway and the next digit is > 5, round up + assert(buffer[length - 1] != '9'); + buffer[length - 1]++; + } + + break; + } + } + + return length; +} + +ExessDigitCount +exess_digits(const double d, char* const buf, const unsigned max_digits) +{ + EXESS_DISABLE_CONVERSION_WARNINGS + assert(isfinite(d) && fpclassify(d) != FP_ZERO); + EXESS_RESTORE_WARNINGS + + const ExessSoftFloat value = soft_float_from_double(d); + const int power = (int)lrint(log10(d)); + const bool is_even = !(value.f & 1u); + const bool lower_is_closer = double_lower_boundary_is_closer(d); + + // Calculate initial values so that v = (numer / denom) * 10^power + ExessBigint numer; + ExessBigint denom; + ExessBigint d_lower; + calculate_initial_values( + value.f, value.e, power, lower_is_closer, &numer, &denom, &d_lower); + + ExessBigint d_upper_storage; + ExessBigint* d_upper = NULL; + if (lower_is_closer) { + // Scale upper boundary to account for the closer lower boundary + // (the numerator and denominator were already scaled above) + d_upper_storage = d_lower; + d_upper = &d_upper_storage; + exess_bigint_shift_left(d_upper, 1); + } else { + d_upper = &d_lower; // Boundaries are the same, reuse the lower + } + + // Scale if necessary to make 1 <= (numer + delta) / denom < 10 + ExessDigitCount count = {0, 0}; + if (within_upper(&numer, &denom, d_upper, is_even)) { + count.expt = power; + } else { + count.expt = power - 1; + exess_bigint_multiply_u32(&numer, 10); + exess_bigint_multiply_u32(&d_lower, 10); + if (d_upper != &d_lower) { + exess_bigint_multiply_u32(d_upper, 10); + } + } + + // Write digits to output + assert(check_initial_values(&numer, &denom, d_upper)); + count.count = + emit_digits(&numer, &denom, &d_lower, d_upper, is_even, buf, max_digits); + + // Trim trailing zeros + while (count.count > 1 && buf[count.count - 1] == '0') { + buf[--count.count] = 0; + } + + buf[count.count] = '\0'; + return count; +} diff --git a/subprojects/exess/src/digits.h b/subprojects/exess/src/digits.h new file mode 100644 index 00000000..0753a0af --- /dev/null +++ b/subprojects/exess/src/digits.h @@ -0,0 +1,38 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef EXESS_DIGITS_H +#define EXESS_DIGITS_H + +typedef struct { + unsigned count; ///< Number of digits + int expt; ///< Power of 10 exponent +} ExessDigitCount; + +/** + Write significant digits digits for `d` into `buf`. + + Writes only significant digits, without any leading or trailing zeros. The + actual number is given by the exponent in the return value. + + @param d The number to convert to digits, must be finite and non-zero. + @param buf The output buffer at least `max_digits` long. + @param max_digits The maximum number of digits to write. +*/ +ExessDigitCount +exess_digits(double d, char* buf, unsigned max_digits); + +#endif // EXESS_DIGITS_H diff --git a/subprojects/exess/src/double.c b/subprojects/exess/src/double.c new file mode 100644 index 00000000..2934010d --- /dev/null +++ b/subprojects/exess/src/double.c @@ -0,0 +1,53 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "decimal.h" +#include "read_utils.h" +#include "scientific.h" +#include "strtod.h" +#include "write_utils.h" + +#include "exess/exess.h" + +#include +#include + +ExessResult +exess_read_double(double* const out, const char* const str) +{ + *out = (double)NAN; + + const size_t i = skip_whitespace(str); + ExessDecimalDouble in = {EXESS_NAN, 0u, 0, {0}}; + const ExessResult r = parse_double(&in, str + i); + + if (!r.status) { + *out = parsed_double_to_double(in); + } + + return result(r.status, i + r.count); +} + +ExessResult +exess_write_double(const double value, const size_t buf_size, char* const buf) +{ + const ExessDecimalDouble decimal = exess_measure_double(value); + const ExessResult r = + buf ? exess_write_scientific(decimal, buf_size, buf) + : result(EXESS_SUCCESS, exess_scientific_string_length(decimal)); + + return end_write(r.status, buf_size, buf, r.count); +} diff --git a/subprojects/exess/src/duration.c b/subprojects/exess/src/duration.c new file mode 100644 index 00000000..0a753fd3 --- /dev/null +++ b/subprojects/exess/src/duration.c @@ -0,0 +1,322 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "read_utils.h" +#include "string_utils.h" +#include "time_utils.h" +#include "write_utils.h" + +#include "exess/exess.h" + +#include +#include +#include +#include +#include + +typedef enum { YEAR, MONTH, DAY, HOUR, MINUTE, SECOND } Field; + +static ExessStatus +set_field(ExessDuration* const out, + const Field current_field, + const Field field, + const uint32_t value) +{ + if (value >= INT32_MAX) { + return EXESS_OUT_OF_RANGE; + } + + if (field < current_field) { + return EXESS_BAD_ORDER; + } + + switch (field) { + case YEAR: + out->months = (int32_t)(12 * lrint(value)); + break; + case MONTH: + out->months = (int32_t)(out->months + lrint(value)); + break; + case DAY: + out->seconds = (int32_t)(24 * 60 * 60 * lrint(value)); + break; + case HOUR: + out->seconds = (int32_t)(out->seconds + 60 * 60 * lrint(value)); + break; + case MINUTE: + out->seconds = (int32_t)(out->seconds + 60 * lrint(value)); + break; + case SECOND: + out->seconds = (int32_t)(out->seconds + lrint(value)); + break; + } + + return EXESS_SUCCESS; +} + +static ExessResult +read_date(ExessDuration* const out, const Field field, const char* const str) +{ + uint32_t value = 0; + ExessResult r = exess_read_uint(&value, str); + if (r.status > EXESS_EXPECTED_END) { + return r; + } + + size_t i = r.count; + switch (str[i]) { + case 'Y': + if ((r.status = set_field(out, field, YEAR, value))) { + return r; + } + + ++i; + if (str[i] != 'T' && !is_end(str[i])) { + r = read_date(out, MONTH, str + i); + i += r.count; + } + break; + + case 'M': + if ((r.status = set_field(out, field, MONTH, value))) { + return r; + } + + ++i; + if (str[i] != 'T' && !is_end(str[i])) { + r = read_date(out, DAY, str + i); + i += r.count; + } + break; + + case 'D': + if ((r.status = set_field(out, field, DAY, value))) { + return r; + } + + ++i; + break; + + default: + return result(EXESS_EXPECTED_DATE_TAG, i); + } + + return result(r.status, i); +} + +static ExessResult +read_time(ExessDuration* const out, const Field field, const char* const str) +{ + uint32_t value = 0; + ExessResult r = exess_read_uint(&value, str); + if (r.status > EXESS_EXPECTED_END) { + return r; + } + + size_t i = r.count; + ExessResult next = {EXESS_SUCCESS, 0}; + switch (str[i]) { + case '.': { + if (!is_digit(str[++i])) { + return result(EXESS_EXPECTED_DIGIT, i); + } + + uint32_t nanoseconds = 0; + + r = read_nanoseconds(&nanoseconds, str + i); + i += r.count; + + if (str[i] != 'S') { + return result(EXESS_EXPECTED_TIME_TAG, i); + } + + r.status = set_field(out, field, SECOND, value); + out->nanoseconds = (int32_t)nanoseconds; + + break; + } + + case 'H': + r.status = set_field(out, field, HOUR, value); + if (!is_end(str[i + 1])) { + next = read_time(out, MINUTE, str + i + 1); + } + break; + + case 'M': + r.status = set_field(out, field, MINUTE, value); + if (!is_end(str[i + 1])) { + next = read_time(out, SECOND, str + i + 1); + } + break; + + case 'S': + r.status = set_field(out, field, SECOND, value); + break; + + default: + return result(EXESS_EXPECTED_TIME_TAG, i); + } + + if (r.status) { + return r; + } + + return result(next.status, i + 1 + next.count); +} + +ExessResult +exess_read_duration(ExessDuration* const out, const char* const str) +{ + memset(out, 0, sizeof(*out)); + + size_t i = skip_whitespace(str); + bool is_negative = false; + if (str[i] == '-') { + is_negative = true; + ++i; + } + + if (str[i] != 'P') { + return result(EXESS_EXPECTED_DURATION, i); + } + + ++i; + + if (str[i] != 'T') { + ExessResult r = read_date(out, YEAR, str + i); + if (r.status) { + return result(r.status, i + r.count); + } + + i += r.count; + + if (!is_end(str[i]) && str[i] != 'T') { + return result(EXESS_EXPECTED_TIME_SEP, i); + } + } + + if (str[i] == 'T') { + ++i; + + ExessResult r = read_time(out, HOUR, str + i); + if (r.status) { + return result(r.status, i + r.count); + } + + i += r.count; + } + + if (is_negative) { + out->months = -out->months; + out->seconds = -out->seconds; + out->nanoseconds = -out->nanoseconds; + } + + return end_read(EXESS_SUCCESS, str, i); +} + +static size_t +write_int_field(ExessResult* r, + const uint32_t value, + const char tag, + const size_t buf_size, + char* const buf, + const size_t i) +{ + if (!r->status) { + if (value == 0) { + *r = result(EXESS_SUCCESS, 0); + } else if (!buf) { + *r = exess_write_uint(value, buf_size, buf); + ++r->count; + } else { + *r = exess_write_uint(value, buf_size - i, buf + i); + if (!r->status) { + buf[i + r->count++] = tag; + } + } + } + + return r->count; +} + +ExessResult +exess_write_duration(const ExessDuration value, + const size_t buf_size, + char* const buf) +{ + // Write zero as a special case + size_t i = 0; + if (value.months == 0 && value.seconds == 0 && value.nanoseconds == 0) { + i += write_string(3, "P0Y", buf_size, buf, i); + return end_write(EXESS_SUCCESS, buf_size, buf, i); + } + + if (value.months == INT32_MIN || value.seconds == INT32_MIN) { + return end_write(EXESS_OUT_OF_RANGE, buf_size, buf, 0); + } + + const bool is_negative = + (value.months < 0 || value.seconds < 0 || value.nanoseconds < 0); + + if (is_negative && + (value.months > 0 || value.seconds > 0 || value.nanoseconds > 0)) { + return end_write(EXESS_BAD_VALUE, buf_size, buf, 0); + } + + // Write duration prefix + if (value.months < 0 || value.seconds < 0 || value.nanoseconds < 0) { + i += write_string(2, "-P", buf_size, buf, i); + } else { + i += write_char('P', buf_size, buf, i); + } + + const uint32_t abs_years = (uint32_t)(abs(value.months) / 12); + const uint32_t abs_months = (uint32_t)(abs(value.months) % 12); + const uint32_t abs_days = (uint32_t)(abs(value.seconds) / (24 * 60 * 60)); + const uint32_t abs_hours = (uint32_t)(abs(value.seconds) / 60 / 60 % 24); + const uint32_t abs_minutes = (uint32_t)(abs(value.seconds) / 60 % 60); + const uint8_t abs_seconds = (uint8_t)(abs(value.seconds) % 60); + const uint32_t abs_nanoseconds = (uint32_t)abs(value.nanoseconds); + + // Write date segments if present + ExessResult r = result(EXESS_SUCCESS, 0); + i += write_int_field(&r, abs_years, 'Y', buf_size, buf, i); + i += write_int_field(&r, abs_months, 'M', buf_size, buf, i); + i += write_int_field(&r, abs_days, 'D', buf_size, buf, i); + + // Write time segments if present + const bool has_time = abs_hours + abs_minutes + abs_seconds + abs_nanoseconds; + if (has_time && !r.status) { + i += write_char('T', buf_size, buf, i); + i += write_int_field(&r, abs_hours, 'H', buf_size, buf, i); + i += write_int_field(&r, abs_minutes, 'M', buf_size, buf, i); + + if (abs_seconds != 0 || abs_nanoseconds != 0) { + r = write_digits(abs_seconds, buf_size, buf, i); + i += r.count; + + if (!r.status && abs_nanoseconds > 0) { + i += write_nanoseconds(abs_nanoseconds, buf_size, buf, i); + } + + i += write_char('S', buf_size, buf, i); + } + } + + return end_write(r.status, buf_size, buf, i); +} diff --git a/subprojects/exess/src/exess_config.h b/subprojects/exess/src/exess_config.h new file mode 100644 index 00000000..4325484a --- /dev/null +++ b/subprojects/exess/src/exess_config.h @@ -0,0 +1,79 @@ +/* + Copyright 2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/* + Configuration header that defines reasonable defaults at compile time. + + This allows compile-time configuration from the command line (typically via + the build system) while still allowing the source to be built without any + configuration. The build system can define EXESS_NO_DEFAULT_CONFIG to disable + defaults, in which case it must define things like HAVE_FEATURE to enable + features. The design here ensures that compiler warnings or + include-what-you-use will catch any mistakes. +*/ + +#ifndef EXESS_CONFIG_H +#define EXESS_CONFIG_H + +// Define version unconditionally so a warning will catch a mismatch +#define EXESS_VERSION "0.0.1" + +#if !defined(EXESS_NO_DEFAULT_CONFIG) + +// GCC and clang: __builtin_clz() +# ifndef HAVE_BUILTIN_CLZ +# if defined(__has_builtin) +# if __has_builtin(__builtin_clz) +# define HAVE_BUILTIN_CLZ 1 +# else +# define HAVE_BUILTIN_CLZ 0 +# endif +# elif defined(__GNUC__) +# define HAVE_BUILTIN_CLZ 1 +# else +# define HAVE_BUILTIN_CLZ 0 +# endif +# endif + +// GCC and clang: __builtin_clz() +# ifndef HAVE_BUILTIN_CLZLL +# if defined(__has_builtin) +# if __has_builtin(__builtin_clzll) +# define HAVE_BUILTIN_CLZLL 1 +# else +# define HAVE_BUILTIN_CLZLL 0 +# endif +# elif defined(__GNUC__) +# define HAVE_BUILTIN_CLZLL 1 +# else +# define HAVE_BUILTIN_CLZLL 0 +# endif +# endif + +#endif // !defined(EXESS_NO_DEFAULT_CONFIG) + +/* + Make corresponding USE_FEATURE defines based on the HAVE_FEATURE defines from + above or the command line. The code checks for these using #if (not #ifdef), + so there will be an undefined warning if it checks for an unknown feature, + and this header is always required by any code that checks for features, even + if the build system defines them all. +*/ + +#define USE_BUILTIN_CLZ HAVE_BUILTIN_CLZ +#define USE_BUILTIN_CLZLL HAVE_BUILTIN_CLZLL + +#endif // EXESS_CONFIG_H diff --git a/subprojects/exess/src/float.c b/subprojects/exess/src/float.c new file mode 100644 index 00000000..bcc6d49a --- /dev/null +++ b/subprojects/exess/src/float.c @@ -0,0 +1,44 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "decimal.h" +#include "read_utils.h" +#include "scientific.h" + +#include "exess/exess.h" + +#include +#include + +ExessResult +exess_read_float(float* const out, const char* const str) +{ + double value = (double)NAN; + const ExessResult r = exess_read_double(&value, str); + + *out = (float)value; + + return r; +} + +ExessResult +exess_write_float(const float value, const size_t buf_size, char* const buf) +{ + const ExessDecimalDouble decimal = exess_measure_float(value); + + return buf ? exess_write_scientific(decimal, buf_size, buf) + : result(EXESS_SUCCESS, exess_scientific_string_length(decimal)); +} diff --git a/subprojects/exess/src/hex.c b/subprojects/exess/src/hex.c new file mode 100644 index 00000000..bd9de4d6 --- /dev/null +++ b/subprojects/exess/src/hex.c @@ -0,0 +1,131 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "read_utils.h" +#include "string_utils.h" +#include "write_utils.h" + +#include "exess/exess.h" + +#include +#include + +/// Hex encoding table +static const char hex_map[] = "0123456789ABCDEF"; + +static inline uint8_t +decode_nibble(const char c) +{ + if (is_digit(c)) { + return (uint8_t)(c - '0'); + } + + if (c >= 'A' && c <= 'F') { + return (uint8_t)(10 + c - 'A'); + } + + if (c >= 'a' && c <= 'f') { + return (uint8_t)(10 + c - 'a'); + } + + return UINT8_MAX; +} + +static char +next_char(const char* const str, size_t* const i) +{ + *i += skip_whitespace(str + *i); + + return str[*i]; +} + +size_t +exess_hex_decoded_size(const size_t length) +{ + return length / 2; +} + +ExessResult +exess_read_hex(ExessBlob* const out, const char* const str) +{ + uint8_t* const uout = (uint8_t*)out->data; + size_t i = 0u; + size_t o = 0u; + + while (str[i]) { + const char hi_char = next_char(str, &i); + if (!hi_char) { + break; + } + + ++i; + + const uint8_t hi = decode_nibble(hi_char); + if (hi == UINT8_MAX) { + return result(EXESS_EXPECTED_HEX, i); + } + + const char lo_char = next_char(str, &i); + if (!lo_char) { + return result(EXESS_EXPECTED_HEX, i); + } + + ++i; + + const uint8_t lo = decode_nibble(lo_char); + if (lo == UINT8_MAX) { + return result(EXESS_EXPECTED_HEX, i); + } + + if (o >= out->size) { + return result(EXESS_NO_SPACE, i); + } + + uout[o++] = (uint8_t)(hi << 4u) | lo; + } + + out->size = o; + return result(EXESS_SUCCESS, i); +} + +ExessResult +exess_write_hex(const size_t data_size, + const void* const data, + const size_t buf_size, + char* const buf) +{ + const size_t length = 2 * data_size; + if (!buf) { + return result(EXESS_SUCCESS, length); + } + + if (buf_size < length + 1) { + return result(EXESS_NO_SPACE, 0); + } + + const uint8_t* const in = (const uint8_t*)data; + size_t o = 0u; + + for (size_t i = 0; i < data_size; ++i) { + const uint8_t hi = (in[i] & 0xF0u) >> 4u; + const uint8_t lo = (in[i] & 0x0Fu); + + buf[o++] = hex_map[hi]; + buf[o++] = hex_map[lo]; + } + + return end_write(EXESS_SUCCESS, buf_size, buf, o); +} diff --git a/subprojects/exess/src/ieee_float.h b/subprojects/exess/src/ieee_float.h new file mode 100644 index 00000000..5c70bf4b --- /dev/null +++ b/subprojects/exess/src/ieee_float.h @@ -0,0 +1,63 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef EXESS_IEEE_FLOAT_H +#define EXESS_IEEE_FLOAT_H + +#include +#include +#include +#include + +static const unsigned dbl_physical_mant_dig = DBL_MANT_DIG - 1u; +static const uint64_t dbl_mant_mask = 0x000FFFFFFFFFFFFFull; +static const uint64_t dbl_expt_mask = 0x7FF0000000000000ul; +static const uint64_t dbl_hidden_bit = 0x0010000000000000ul; +static const int dbl_expt_bias = 0x3FF + DBL_MANT_DIG - 1; +static const int dbl_subnormal_expt = -0x3FF - DBL_MANT_DIG + 2; + +/// Return the raw representation of a float +static inline uint32_t +float_to_rep(const float d) +{ + uint32_t rep = 0; + memcpy(&rep, &d, sizeof(rep)); + return rep; +} + +/// Return the raw representation of a double +static inline uint64_t +double_to_rep(const double d) +{ + uint64_t rep = 0; + memcpy(&rep, &d, sizeof(rep)); + return rep; +} + +/// Return true if the lower boundary is closer than the upper boundary +static inline bool +double_lower_boundary_is_closer(const double d) +{ + const uint64_t rep = double_to_rep(d); + const uint64_t mant = rep & dbl_mant_mask; + const uint64_t expt = rep & dbl_expt_mask; + const bool is_subnormal = expt == 0; + + // True when f = 2^(p-1) (except for the smallest normal) + return !is_subnormal && mant == 0; +} + +#endif // EXESS_IEEE_FLOAT_H diff --git a/subprojects/exess/src/int.c b/subprojects/exess/src/int.c new file mode 100644 index 00000000..232cbcc4 --- /dev/null +++ b/subprojects/exess/src/int.c @@ -0,0 +1,45 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "read_utils.h" + +#include "exess/exess.h" + +#include +#include + +ExessResult +exess_read_int(int32_t* const out, const char* const str) +{ + int64_t long_out = 0; + const ExessResult r = exess_read_long(&long_out, str); + if (r.status) { + return r; + } + + if (long_out < INT32_MIN || long_out > INT32_MAX) { + return result(EXESS_OUT_OF_RANGE, r.count); + } + + *out = (int32_t)long_out; + return r; +} + +ExessResult +exess_write_int(const int32_t value, const size_t buf_size, char* const buf) +{ + return exess_write_long(value, buf_size, buf); +} diff --git a/subprojects/exess/src/int_math.c b/subprojects/exess/src/int_math.c new file mode 100644 index 00000000..ce263b43 --- /dev/null +++ b/subprojects/exess/src/int_math.c @@ -0,0 +1,78 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "int_math.h" + +#include "exess_config.h" + +#include + +unsigned +exess_clz32(const uint32_t i) +{ + assert(i != 0); + +#if USE_BUILTIN_CLZ + return (unsigned)__builtin_clz(i); +#else + unsigned n = 32u; + uint32_t bits = i; + for (unsigned s = 16; s > 0; s >>= 1) { + const uint32_t left = bits >> s; + if (left) { + n -= s; + bits = left; + } + } + return n - bits; +#endif +} + +unsigned +exess_clz64(const uint64_t i) +{ + assert(i != 0); + +#if USE_BUILTIN_CLZLL + return (unsigned)__builtin_clzll(i); +#else + return i & 0xFFFFFFFF00000000 ? exess_clz32((uint32_t)(i >> 32u)) + : 32u + exess_clz32(i & 0xFFFFFFFF); +#endif +} + +uint64_t +exess_ilog2(const uint64_t i) +{ + assert(i != 0); + return (64u - exess_clz64(i | 1u)) - 1u; +} + +uint64_t +exess_ilog10(const uint64_t i) +{ + // See https://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 + const uint64_t log2 = exess_ilog2(i); + const uint64_t t = (log2 + 1u) * 1233u >> 12u; + + return t - (i < POW10[t]) + (i == 0); +} + +uint8_t +exess_num_digits(const uint64_t i) +{ + return i == 0u ? 1u : (uint8_t)(exess_ilog10(i) + 1u); +} diff --git a/subprojects/exess/src/int_math.h b/subprojects/exess/src/int_math.h new file mode 100644 index 00000000..a4a57140 --- /dev/null +++ b/subprojects/exess/src/int_math.h @@ -0,0 +1,72 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef EXESS_INTMATH_H +#define EXESS_INTMATH_H + +#include "attributes.h" + +#include + +static const int uint64_digits10 = 19; + +static const uint64_t POW10[] = {1ull, + 10ull, + 100ull, + 1000ull, + 10000ull, + 100000ull, + 1000000ull, + 10000000ull, + 100000000ull, + 1000000000ull, + 10000000000ull, + 100000000000ull, + 1000000000000ull, + 10000000000000ull, + 100000000000000ull, + 1000000000000000ull, + 10000000000000000ull, + 100000000000000000ull, + 1000000000000000000ull, + 10000000000000000000ull}; + +/// Return the number of leading zeros in `i` +EXESS_I_CONST_FUNC +unsigned +exess_clz32(uint32_t i); + +/// Return the number of leading zeros in `i` +EXESS_I_CONST_FUNC +unsigned +exess_clz64(uint64_t i); + +/// Return the log base 2 of `i` +EXESS_I_CONST_FUNC +uint64_t +exess_ilog2(uint64_t i); + +/// Return the log base 10 of `i` +EXESS_I_CONST_FUNC +uint64_t +exess_ilog10(uint64_t i); + +/// Return the number of decimal digits required to represent `i` +EXESS_I_CONST_FUNC +uint8_t +exess_num_digits(uint64_t i); + +#endif // EXESS_INTMATH_H diff --git a/subprojects/exess/src/long.c b/subprojects/exess/src/long.c new file mode 100644 index 00000000..2fbcdf59 --- /dev/null +++ b/subprojects/exess/src/long.c @@ -0,0 +1,110 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "int_math.h" +#include "read_utils.h" +#include "write_utils.h" + +#include "exess/exess.h" + +#include +#include +#include + +ExessResult +exess_read_long(int64_t* const out, const char* const str) +{ + *out = 0; + + // Read leading sign if present + size_t i = skip_whitespace(str); + int sign = 1; + if (str[i] == '-') { + sign = -1; + ++i; + } else if (str[i] == '+') { + ++i; + } + + // Read digits + uint64_t magnitude = 0; + ExessResult r = exess_read_ulong(&magnitude, str + i); + if (r.status > EXESS_EXPECTED_END) { + return result(r.status, i + r.count); + } + + i += r.count; + + if (sign > 0) { + if (magnitude > (uint64_t)INT64_MAX) { + return result(EXESS_OUT_OF_RANGE, i); + } + + *out = (int64_t)magnitude; + return end_read(EXESS_SUCCESS, str, i); + } + + const uint64_t min_magnitude = (uint64_t)(-(INT64_MIN + 1)) + 1; + if (magnitude > min_magnitude) { + return result(EXESS_OUT_OF_RANGE, i); + } + + if (magnitude == min_magnitude) { + *out = INT64_MIN; + } else { + *out = -(int64_t)magnitude; + } + + return end_read(r.status, str, i); +} + +static size_t +exess_long_string_length(const int64_t value) +{ + if (value == INT64_MIN) { + return 20; + } + + if (value < 0) { + return 1u + exess_num_digits((uint64_t)-value); + } + + return exess_num_digits((uint64_t)value); +} + +ExessResult +exess_write_long(const int64_t value, const size_t buf_size, char* const buf) +{ + if (!buf) { + return result(EXESS_SUCCESS, exess_long_string_length(value)); + } + + if (value == INT64_MIN) { + return end_write( + EXESS_SUCCESS, + buf_size, + buf, + write_string(20, "-9223372036854775808", buf_size, buf, 0)); + } + + const bool is_negative = value < 0; + const uint64_t abs_value = (uint64_t)(is_negative ? -value : value); + + size_t i = (is_negative) ? write_char('-', buf_size, buf, 0) : 0; + ExessResult r = write_digits(abs_value, buf_size, buf, i); + + return end_write(r.status, buf_size, buf, i + r.count); +} diff --git a/subprojects/exess/src/macros.h b/subprojects/exess/src/macros.h new file mode 100644 index 00000000..80ed68f5 --- /dev/null +++ b/subprojects/exess/src/macros.h @@ -0,0 +1,31 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef EXESS_MACROS_H +#define EXESS_MACROS_H + +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#define MAX(x, y) ((x) > (y) ? (x) : (y)) +#define CLAMP(x, l, h) MAX(l, MIN(h, x)) + +#define SET_IF(pointer, value) \ + do { \ + if (pointer) { \ + *(pointer) = (value); \ + } \ + } while (0) + +#endif // EXESS_MACROS_H diff --git a/subprojects/exess/src/read_utils.c b/subprojects/exess/src/read_utils.c new file mode 100644 index 00000000..823fe97f --- /dev/null +++ b/subprojects/exess/src/read_utils.c @@ -0,0 +1,51 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "read_utils.h" +#include "string_utils.h" + +#include "exess/exess.h" + +ExessResult +read_two_digit_number(uint8_t* const out, + const uint8_t min_value, + const uint8_t max_value, + const char* const str) +{ + size_t i = 0; + + // Read digits + size_t d = 0; + for (; d < 2; ++d, ++i) { + if (is_digit(str[i])) { + *out = (uint8_t)((*out * 10) + (str[i] - '0')); + } else { + break; + } + } + + // Ensure there are exactly the expected number of digits + if (d != 2) { + return result(EXESS_EXPECTED_DIGIT, i); + } + + // Ensure value is in range + if (*out < min_value || *out > max_value) { + return result(EXESS_OUT_OF_RANGE, i); + } + + return result(EXESS_SUCCESS, i); +} diff --git a/subprojects/exess/src/read_utils.h b/subprojects/exess/src/read_utils.h new file mode 100644 index 00000000..3f7e1f56 --- /dev/null +++ b/subprojects/exess/src/read_utils.h @@ -0,0 +1,77 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef EXESS_READ_UTILS_H +#define EXESS_READ_UTILS_H + +#include "string_utils.h" + +#include "exess/exess.h" + +#include +#include +#include + +static inline size_t +skip_whitespace(const char* const str) +{ + size_t i = 0; + while (is_space(str[i])) { + ++i; + } + + return i; +} + +static inline bool +is_end(const char c) +{ + switch (c) { + case '\0': + case ' ': + case '\f': + case '\n': + case '\r': + case '\t': + case '\v': + return true; + default: + break; + } + + return false; +} + +static inline ExessResult +result(const ExessStatus status, const size_t count) +{ + const ExessResult r = {status, count}; + return r; +} + +ExessResult +read_two_digit_number(uint8_t* out, + uint8_t min_value, + uint8_t max_value, + const char* str); + +static inline ExessResult +end_read(const ExessStatus status, const char* str, const size_t i) +{ + return result((status || is_end(str[i])) ? status : EXESS_EXPECTED_END, i); +} + +#endif // EXESS_READ_UTILS_H diff --git a/subprojects/exess/src/scientific.c b/subprojects/exess/src/scientific.c new file mode 100644 index 00000000..3e441a86 --- /dev/null +++ b/subprojects/exess/src/scientific.c @@ -0,0 +1,125 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "scientific.h" +#include "decimal.h" +#include "int_math.h" +#include "read_utils.h" +#include "write_utils.h" + +#include "exess/exess.h" + +#include +#include + +size_t +exess_scientific_string_length(const ExessDecimalDouble value) +{ + switch (value.kind) { + case EXESS_NEGATIVE: + break; + case EXESS_NEGATIVE_INFINITY: + return 4; + case EXESS_NEGATIVE_ZERO: + return 6; + case EXESS_POSITIVE_ZERO: + return 5; + case EXESS_POSITIVE: + break; + case EXESS_POSITIVE_INFINITY: + case EXESS_NAN: + return 3; + } + + const unsigned n_expt_digits = + (unsigned)exess_num_digits((unsigned)abs(value.expt)); + + return ((value.kind == EXESS_NEGATIVE) + // Sign + value.n_digits + 1 + // Digits and point + (value.n_digits <= 1) + // Added '0' after point + 1 + // 'E' + (value.expt < 0) + // Exponent sign + n_expt_digits); // Exponent digits +} + +ExessResult +exess_write_scientific(const ExessDecimalDouble value, + const size_t n, + char* const buf) +{ + size_t i = 0; + + if (n < 4) { + return result(EXESS_NO_SPACE, 0); + } + + switch (value.kind) { + case EXESS_NEGATIVE: + buf[i++] = '-'; + break; + case EXESS_NEGATIVE_INFINITY: + return write_special(4, "-INF", n, buf); + case EXESS_NEGATIVE_ZERO: + return write_special(6, "-0.0E0", n, buf); + case EXESS_POSITIVE_ZERO: + return write_special(5, "0.0E0", n, buf); + case EXESS_POSITIVE: + break; + case EXESS_POSITIVE_INFINITY: + return write_special(3, "INF", n, buf); + case EXESS_NAN: + return write_special(3, "NaN", n, buf); + } + + if (n - i <= value.n_digits + 1) { + buf[0] = '\0'; + return result(EXESS_NO_SPACE, 0); + } + + // Write mantissa, with decimal point after the first (normal form) + buf[i++] = value.digits[0]; + buf[i++] = '.'; + if (value.n_digits > 1) { + memcpy(buf + i, value.digits + 1, value.n_digits - 1); + i += value.n_digits - 1; + } else { + buf[i++] = '0'; + } + + // Write exponent + + const unsigned n_expt_digits = exess_num_digits((unsigned)abs(value.expt)); + + if (n - i <= 1u + (value.expt < 0) + n_expt_digits) { + buf[0] = '\0'; + return result(EXESS_NO_SPACE, 0); + } + + buf[i++] = 'E'; + if (value.expt < 0) { + buf[i++] = '-'; + } + + unsigned abs_expt = (unsigned)abs(value.expt); + char* s = buf + i + n_expt_digits; + + *s-- = '\0'; + do { + *s-- = (char)('0' + (abs_expt % 10)); + } while ((abs_expt /= 10) > 0); + + return result(EXESS_SUCCESS, i + n_expt_digits); +} diff --git a/subprojects/exess/src/scientific.h b/subprojects/exess/src/scientific.h new file mode 100644 index 00000000..96c3096a --- /dev/null +++ b/subprojects/exess/src/scientific.h @@ -0,0 +1,35 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef EXESS_SCIENTIFIC_H +#define EXESS_SCIENTIFIC_H + +#include "decimal.h" + +#include "exess/exess.h" + +#include + +EXESS_CONST_FUNC +size_t +exess_scientific_string_length(ExessDecimalDouble value); + +ExessResult +exess_write_scientific(ExessDecimalDouble value, + size_t n, + char* EXESS_NONNULL buf); + +#endif // EXESS_SCIENTIFIC_H diff --git a/subprojects/exess/src/short.c b/subprojects/exess/src/short.c new file mode 100644 index 00000000..9241b75a --- /dev/null +++ b/subprojects/exess/src/short.c @@ -0,0 +1,45 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "read_utils.h" + +#include "exess/exess.h" + +#include +#include + +ExessResult +exess_read_short(int16_t* const out, const char* const str) +{ + int64_t long_out = 0; + const ExessResult r = exess_read_long(&long_out, str); + if (r.status) { + return r; + } + + if (long_out < INT16_MIN || long_out > INT16_MAX) { + return result(EXESS_OUT_OF_RANGE, r.count); + } + + *out = (int16_t)long_out; + return r; +} + +ExessResult +exess_write_short(const int16_t value, const size_t buf_size, char* const buf) +{ + return exess_write_long(value, buf_size, buf); +} diff --git a/subprojects/exess/src/soft_float.c b/subprojects/exess/src/soft_float.c new file mode 100644 index 00000000..bea9a176 --- /dev/null +++ b/subprojects/exess/src/soft_float.c @@ -0,0 +1,161 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "soft_float.h" + +#include "ieee_float.h" +#include "int_math.h" + +#include +#include +#include + +/// 10^k for k = min_dec_expt, min_dec_expt + dec_expt_step, ..., max_dec_expt +static const ExessSoftFloat soft_pow10[] = { + {0xFA8FD5A0081C0288, -1220}, {0xBAAEE17FA23EBF76, -1193}, + {0x8B16FB203055AC76, -1166}, {0xCF42894A5DCE35EA, -1140}, + {0x9A6BB0AA55653B2D, -1113}, {0xE61ACF033D1A45DF, -1087}, + {0xAB70FE17C79AC6CA, -1060}, {0xFF77B1FCBEBCDC4F, -1034}, + {0xBE5691EF416BD60C, -1007}, {0x8DD01FAD907FFC3C, -980}, + {0xD3515C2831559A83, -954}, {0x9D71AC8FADA6C9B5, -927}, + {0xEA9C227723EE8BCB, -901}, {0xAECC49914078536D, -874}, + {0x823C12795DB6CE57, -847}, {0xC21094364DFB5637, -821}, + {0x9096EA6F3848984F, -794}, {0xD77485CB25823AC7, -768}, + {0xA086CFCD97BF97F4, -741}, {0xEF340A98172AACE5, -715}, + {0xB23867FB2A35B28E, -688}, {0x84C8D4DFD2C63F3B, -661}, + {0xC5DD44271AD3CDBA, -635}, {0x936B9FCEBB25C996, -608}, + {0xDBAC6C247D62A584, -582}, {0xA3AB66580D5FDAF6, -555}, + {0xF3E2F893DEC3F126, -529}, {0xB5B5ADA8AAFF80B8, -502}, + {0x87625F056C7C4A8B, -475}, {0xC9BCFF6034C13053, -449}, + {0x964E858C91BA2655, -422}, {0xDFF9772470297EBD, -396}, + {0xA6DFBD9FB8E5B88F, -369}, {0xF8A95FCF88747D94, -343}, + {0xB94470938FA89BCF, -316}, {0x8A08F0F8BF0F156B, -289}, + {0xCDB02555653131B6, -263}, {0x993FE2C6D07B7FAC, -236}, + {0xE45C10C42A2B3B06, -210}, {0xAA242499697392D3, -183}, + {0xFD87B5F28300CA0E, -157}, {0xBCE5086492111AEB, -130}, + {0x8CBCCC096F5088CC, -103}, {0xD1B71758E219652C, -77}, + {0x9C40000000000000, -50}, {0xE8D4A51000000000, -24}, + {0xAD78EBC5AC620000, 3}, {0x813F3978F8940984, 30}, + {0xC097CE7BC90715B3, 56}, {0x8F7E32CE7BEA5C70, 83}, + {0xD5D238A4ABE98068, 109}, {0x9F4F2726179A2245, 136}, + {0xED63A231D4C4FB27, 162}, {0xB0DE65388CC8ADA8, 189}, + {0x83C7088E1AAB65DB, 216}, {0xC45D1DF942711D9A, 242}, + {0x924D692CA61BE758, 269}, {0xDA01EE641A708DEA, 295}, + {0xA26DA3999AEF774A, 322}, {0xF209787BB47D6B85, 348}, + {0xB454E4A179DD1877, 375}, {0x865B86925B9BC5C2, 402}, + {0xC83553C5C8965D3D, 428}, {0x952AB45CFA97A0B3, 455}, + {0xDE469FBD99A05FE3, 481}, {0xA59BC234DB398C25, 508}, + {0xF6C69A72A3989F5C, 534}, {0xB7DCBF5354E9BECE, 561}, + {0x88FCF317F22241E2, 588}, {0xCC20CE9BD35C78A5, 614}, + {0x98165AF37B2153DF, 641}, {0xE2A0B5DC971F303A, 667}, + {0xA8D9D1535CE3B396, 694}, {0xFB9B7CD9A4A7443C, 720}, + {0xBB764C4CA7A44410, 747}, {0x8BAB8EEFB6409C1A, 774}, + {0xD01FEF10A657842C, 800}, {0x9B10A4E5E9913129, 827}, + {0xE7109BFBA19C0C9D, 853}, {0xAC2820D9623BF429, 880}, + {0x80444B5E7AA7CF85, 907}, {0xBF21E44003ACDD2D, 933}, + {0x8E679C2F5E44FF8F, 960}, {0xD433179D9C8CB841, 986}, + {0x9E19DB92B4E31BA9, 1013}, {0xEB96BF6EBADF77D9, 1039}, + {0xAF87023B9BF0EE6B, 1066}}; + +ExessSoftFloat +soft_float_from_double(const double d) +{ + assert(d >= 0.0); + + const uint64_t rep = double_to_rep(d); + const uint64_t frac = rep & dbl_mant_mask; + const int expt = (int)((rep & dbl_expt_mask) >> dbl_physical_mant_dig); + + if (expt == 0) { // Subnormal + ExessSoftFloat v = {frac, dbl_subnormal_expt}; + return v; + } + + const ExessSoftFloat v = {frac + dbl_hidden_bit, expt - dbl_expt_bias}; + return v; +} + +double +soft_float_to_double(const ExessSoftFloat v) +{ + return ldexp((double)v.f, v.e); +} + +ExessSoftFloat +soft_float_normalize(ExessSoftFloat value) +{ + const unsigned amount = exess_clz64(value.f); + + value.f <<= amount; + value.e -= (int)amount; + + return value; +} + +ExessSoftFloat +soft_float_multiply(const ExessSoftFloat lhs, const ExessSoftFloat rhs) +{ + static const uint64_t mask = 0xFFFFFFFF; + static const uint64_t round = 1u << 31u; + + const uint64_t l0 = lhs.f >> 32u; + const uint64_t l1 = lhs.f & mask; + const uint64_t r0 = rhs.f >> 32u; + const uint64_t r1 = rhs.f & mask; + const uint64_t l0r0 = l0 * r0; + const uint64_t l1r0 = l1 * r0; + const uint64_t l0r1 = l0 * r1; + const uint64_t l1r1 = l1 * r1; + const uint64_t mid = (l1r1 >> 32u) + (l0r1 & mask) + (l1r0 & mask) + round; + + const ExessSoftFloat r = {l0r0 + (l0r1 >> 32u) + (l1r0 >> 32u) + (mid >> 32u), + lhs.e + rhs.e + 64}; + + return r; +} + +ExessSoftFloat +soft_float_exact_pow10(const int expt) +{ + static const ExessSoftFloat table[8] = {{0xA000000000000000, -60}, + {0xC800000000000000, -57}, + {0xFA00000000000000, -54}, + {0x9C40000000000000, -50}, + {0xC350000000000000, -47}, + {0xF424000000000000, -44}, + {0x9896800000000000, -40}}; + + assert(expt > 0); + assert(expt < dec_expt_step); + return table[expt - 1]; +} + +ExessSoftFloat +soft_float_pow10_under(const int exponent, int* pow10_exponent) +{ + assert(exponent >= min_dec_expt); + assert(exponent < max_dec_expt + dec_expt_step); + + const int cache_offset = -min_dec_expt; + const int index = (exponent + cache_offset) / dec_expt_step; + + *pow10_exponent = min_dec_expt + index * dec_expt_step; + + assert(*pow10_exponent <= exponent); + assert(exponent < *pow10_exponent + dec_expt_step); + + return soft_pow10[index]; +} diff --git a/subprojects/exess/src/soft_float.h b/subprojects/exess/src/soft_float.h new file mode 100644 index 00000000..b766ae5e --- /dev/null +++ b/subprojects/exess/src/soft_float.h @@ -0,0 +1,71 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef EXESS_SOFT_FLOAT_H +#define EXESS_SOFT_FLOAT_H + +#include "attributes.h" + +#include + +typedef struct { + uint64_t f; ///< Significand + int e; ///< Exponent +} ExessSoftFloat; + +static const int min_dec_expt = -348; +static const int max_dec_expt = 340; +static const int dec_expt_step = 8; + +/// Convert `d` to a soft float +EXESS_I_CONST_FUNC +ExessSoftFloat +soft_float_from_double(double d); + +/// Convert `v` to a double +double +soft_float_to_double(ExessSoftFloat v); + +/// Normalize `value` so the MSb of its significand is 1 +EXESS_I_CONST_FUNC +ExessSoftFloat +soft_float_normalize(ExessSoftFloat value); + +/// Multiply `lhs` by `rhs` and return the result +EXESS_I_CONST_FUNC +ExessSoftFloat +soft_float_multiply(ExessSoftFloat lhs, ExessSoftFloat rhs); + +/// Return exactly 10^e for e in [0...dec_expt_step] +EXESS_I_CONST_FUNC +ExessSoftFloat +soft_float_exact_pow10(int expt); + +/** + Return a cached power of 10 with exponent not greater than `max_exponent`. + + Valid only for `max_exponent` values from min_dec_expt to max_dec_expt + + dec_expt_step. The returned power's exponent is a multiple of + dec_expt_step. + + @param max_exponent Maximum decimal exponent of the result. + @param[out] pow10_exponent Set to the decimal exponent of the result. + @return A cached power of 10 as a soft float. +*/ +ExessSoftFloat +soft_float_pow10_under(int max_exponent, int* pow10_exponent); + +#endif // EXESS_SOFT_FLOAT_H diff --git a/subprojects/exess/src/strerror.c b/subprojects/exess/src/strerror.c new file mode 100644 index 00000000..b2167cac --- /dev/null +++ b/subprojects/exess/src/strerror.c @@ -0,0 +1,70 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "exess/exess.h" + +const char* +exess_strerror(ExessStatus status) +{ + switch (status) { + case EXESS_SUCCESS: + return "Success"; + case EXESS_EXPECTED_END: + return "Expected end of value"; + case EXESS_EXPECTED_BOOLEAN: + return "Expected \"false\", \"true\", \"0\" or \"1\""; + case EXESS_EXPECTED_INTEGER: + return "Expected an integer value"; + case EXESS_EXPECTED_DURATION: + return "Expected a duration starting with 'P'"; + case EXESS_EXPECTED_SIGN: + return "Expected '-' or '+'"; + case EXESS_EXPECTED_DIGIT: + return "Expected a digit"; + case EXESS_EXPECTED_COLON: + return "Expected ':'"; + case EXESS_EXPECTED_DASH: + return "Expected '-'"; + case EXESS_EXPECTED_TIME_SEP: + return "Expected 'T'"; + case EXESS_EXPECTED_TIME_TAG: + return "Expected 'H', 'M', or 'S'"; + case EXESS_EXPECTED_DATE_TAG: + return "Expected 'Y', 'M', or 'D'"; + case EXESS_EXPECTED_HEX: + return "Expected a hexadecimal character"; + case EXESS_EXPECTED_BASE64: + return "Expected a base64 character"; + case EXESS_BAD_ORDER: + return "Invalid field order"; + case EXESS_BAD_VALUE: + return "Invalid value"; + case EXESS_OUT_OF_RANGE: + return "Value outside valid range"; + case EXESS_NO_SPACE: + return "Insufficient space"; + case EXESS_WOULD_REDUCE_PRECISION: + return "Precision reducing coercion required"; + case EXESS_WOULD_ROUND: + return "Rounding coercion required"; + case EXESS_WOULD_TRUNCATE: + return "Truncating coercion required"; + case EXESS_UNSUPPORTED: + return "Unsupported value"; + } + + return "Unknown error"; +} diff --git a/subprojects/exess/src/string_utils.h b/subprojects/exess/src/string_utils.h new file mode 100644 index 00000000..8acb2ffd --- /dev/null +++ b/subprojects/exess/src/string_utils.h @@ -0,0 +1,74 @@ +/* + Copyright 2011-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef EXESS_STRING_UTILS_H +#define EXESS_STRING_UTILS_H + +#include + +/// Return true if `c` lies within [`min`...`max`] (inclusive) +static inline bool +in_range(const int c, const int min, const int max) +{ + return (c >= min && c <= max); +} + +/// Return true if `c` is a whitespace character +static inline bool +is_space(const int c) +{ + switch (c) { + case ' ': + case '\f': + case '\n': + case '\r': + case '\t': + case '\v': + return true; + default: + return false; + } +} + +/// ALPHA ::= [A-Za-z] +static inline bool +is_alpha(const int c) +{ + return in_range(c, 'A', 'Z') || in_range(c, 'a', 'z'); +} + +/// DIGIT ::= [0-9] +static inline bool +is_digit(const int c) +{ + return in_range(c, '0', '9'); +} + +/// HEXDIG ::= DIGIT | "A" | "B" | "C" | "D" | "E" | "F" +static inline bool +is_hexdig(const int c) +{ + return is_digit(c) || in_range(c, 'A', 'F'); +} + +/// BASE64 ::= ALPHA | DIGIT | "+" | "/" | "=" +static inline bool +is_base64(const int c) +{ + return is_alpha(c) || is_digit(c) || c == '+' || c == '/' || c == '='; +} + +#endif // EXESS_STRING_UTILS_H diff --git a/subprojects/exess/src/strtod.c b/subprojects/exess/src/strtod.c new file mode 100644 index 00000000..55a0b082 --- /dev/null +++ b/subprojects/exess/src/strtod.c @@ -0,0 +1,405 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "strtod.h" +#include "bigint.h" +#include "decimal.h" +#include "ieee_float.h" +#include "int_math.h" +#include "macros.h" +#include "read_utils.h" +#include "soft_float.h" +#include "string_utils.h" + +#include +#include +#include +#include +#include +#include + +static inline int +read_sign(const char** const sptr) +{ + if (**sptr == '-') { + ++(*sptr); + return -1; + } + + if (**sptr == '+') { + ++(*sptr); + } + + return 1; +} + +ExessResult +parse_decimal(ExessDecimalDouble* const out, const char* const str) +{ + memset(out, 0, sizeof(*out)); + + // Read leading sign if necessary + const char* s = str; + const int sign = read_sign(&s); + int n_leading_before = 0; + + out->kind = (sign < 0) ? EXESS_NEGATIVE : EXESS_POSITIVE; + + // Check that the first character is valid + if (*s != '.' && !is_digit(*s)) { + return result(EXESS_EXPECTED_DIGIT, (size_t)(s - str)); + } + + // Skip leading zeros before decimal point + while (*s == '0') { + ++s; + ++n_leading_before; + } + + // Skip leading zeros after decimal point + int n_leading_after = 0; // Zeros skipped after decimal point + bool after_point = false; // True if we are after the decimal point + if (*s == '.') { + after_point = true; + for (++s; *s == '0'; ++s) { + ++n_leading_after; + } + } + + // Read significant digits of the mantissa into a 64-bit integer + uint64_t frac = 0; // Fraction value (ignoring decimal point) + int n_before = 0; // Number of digits before decimal point + int n_after = 0; // Number of digits after decimal point + for (; out->n_digits < DBL_DECIMAL_DIG + 1; ++s) { + if (is_digit(*s)) { + frac = (frac * 10) + (unsigned)(*s - '0'); + n_before += !after_point; + n_after += after_point; + out->digits[out->n_digits++] = *s; + } else if (*s == '.' && !after_point) { + after_point = true; + } else { + break; + } + } + + // Skip extra digits + int n_extra_before = 0; + int n_extra_after = 0; + for (;; ++s) { + if (*s == '.' && !after_point) { + after_point = true; + } else if (is_digit(*s)) { + n_extra_before += !after_point; + n_extra_after += after_point; + } else { + break; + } + } + + // Calculate final output exponent + out->expt = n_extra_before - n_after - n_leading_after; + + if (out->n_digits == 0) { + out->kind = + out->kind == EXESS_NEGATIVE ? EXESS_NEGATIVE_ZERO : EXESS_POSITIVE_ZERO; + } + + return result(EXESS_SUCCESS, (size_t)(s - str)); +} + +ExessResult +parse_double(ExessDecimalDouble* const out, const char* const str) +{ + memset(out, 0, sizeof(*out)); + + // Handle non-numeric special cases + + if (!strcmp(str, "NaN")) { + out->kind = EXESS_NAN; + return result(EXESS_SUCCESS, 3u); + } + + if (!strcmp(str, "-INF")) { + out->kind = EXESS_NEGATIVE_INFINITY; + return result(EXESS_SUCCESS, 4u); + } + + if (!strcmp(str, "INF")) { + out->kind = EXESS_POSITIVE_INFINITY; + return result(EXESS_SUCCESS, 3u); + } + + if (!strcmp(str, "+INF")) { + out->kind = EXESS_POSITIVE_INFINITY; + return result(EXESS_SUCCESS, 4u); + } + + // Read mantissa as a decimal + const ExessResult r = parse_decimal(out, str); + if (r.status) { + return r; + } + + const char* s = str + r.count; + + // Read exponent + int abs_expt = 0; + int expt_sign = 1; + if (*s == 'e' || *s == 'E') { + ++s; + + if (*s != '-' && *s != '+' && !is_digit(*s)) { + return result(EXESS_EXPECTED_DIGIT, (size_t)(s - str)); + } + + expt_sign = read_sign(&s); + while (is_digit(*s)) { + abs_expt = (abs_expt * 10) + (*s++ - '0'); + } + } + + // Calculate final output exponent + out->expt += expt_sign * abs_expt; + + if (out->n_digits == 0) { + out->kind = out->kind < EXESS_POSITIVE_ZERO ? EXESS_NEGATIVE_ZERO + : EXESS_POSITIVE_ZERO; + } + + return result(EXESS_SUCCESS, (size_t)(s - str)); +} + +static uint64_t +normalize(ExessSoftFloat* value, const uint64_t error) +{ + const int original_e = value->e; + + *value = soft_float_normalize(*value); + + assert(value->e <= original_e); + return error << (unsigned)(original_e - value->e); +} + +/** + Return the error added by floating point multiplication. + + Should be l + r + l*r/(2^64) + 0.5, but we short the denominator to 63 due + to lack of precision, which effectively rounds up. +*/ +static inline uint64_t +product_error(const uint64_t lerror, + const uint64_t rerror, + const uint64_t half_ulp) +{ + return lerror + rerror + ((lerror * rerror) >> 63u) + half_ulp; +} + +/** + Guess the binary floating point value for decimal input. + + @param significand Significand from the input. + @param expt10 Decimal exponent from the input. + @param n_digits Number of decimal digits in the significand. + @param[out] guess Either the exact number, or its predecessor. + @return True if `guess` is correct. +*/ +static bool +sftod(const uint64_t significand, + const int expt10, + const int n_digits, + ExessSoftFloat* const guess) +{ + assert(expt10 <= max_dec_expt); + assert(expt10 >= min_dec_expt); + + /* The general idea here is to try and find a power of 10 that we can + multiply by the significand to get the number. We get one from the + cache which is possibly too small, then multiply by another power of 10 + to make up the difference if necessary. For example, with a target + power of 10^70, if we get 10^68 from the cache, then we multiply again + by 10^2. This, as well as normalization, accumulates error, which is + tracked throughout to know if we got the precise number. */ + + // Use a common denominator of 2^3 to avoid fractions + static const unsigned lg_denom = 3; + static const uint64_t denom = 1u << 3u; + static const uint64_t half_ulp = 4u; + + // Start out with just the significand, and no error + ExessSoftFloat input = {significand, 0}; + uint64_t error = normalize(&input, 0); + + // Get a power of 10 that takes us most of the way without overshooting + int cached_expt10 = 0; + ExessSoftFloat pow10 = soft_float_pow10_under(expt10, &cached_expt10); + + // Get an exact fixup power if necessary + const int d_expt10 = expt10 - cached_expt10; + if (d_expt10) { + input = soft_float_multiply(input, soft_float_exact_pow10(d_expt10)); + if (d_expt10 > uint64_digits10 - n_digits) { + error += half_ulp; // Product does not fit in an integer + } + } + + // Multiply the significand by the power, normalize, and update the error + input = soft_float_multiply(input, pow10); + error = normalize(&input, product_error(error, half_ulp, half_ulp)); + + // Get the effective number of significant bits from the order of magnitude + const int magnitude = 64 + input.e; + const int real_magnitude = magnitude - dbl_subnormal_expt; + const unsigned n_significant_bits = + (unsigned)MAX(0, MIN(real_magnitude, DBL_MANT_DIG)); + + // Calculate the number of "extra" bits of precision we have + assert(n_significant_bits <= 64); + unsigned n_extra_bits = 64u - n_significant_bits; + if (n_extra_bits + lg_denom >= 64u) { + // Very small subnormal where extra * denom does not fit in an integer + // Shift right (and accumulate some more error) to compensate + const unsigned amount = (n_extra_bits + lg_denom) - 63; + + input.f >>= amount; + input.e += (int)amount; + error = product_error((error >> amount) + 1u, half_ulp, half_ulp); + n_extra_bits -= amount; + } + + // Calculate boundaries for the extra bits (with the common denominator) + assert(n_extra_bits < 64); + const uint64_t extra_mask = (1ull << n_extra_bits) - 1u; + const uint64_t extra_bits = (input.f & extra_mask) * denom; + const uint64_t middle = (1ull << (n_extra_bits - 1u)) * denom; + const uint64_t low = middle - error; + const uint64_t high = middle + error; + + // Round to nearest representable double + guess->f = (input.f >> n_extra_bits) + (extra_bits >= high); + guess->e = input.e + (int)n_extra_bits; + + // Too inaccurate if the extra bits are within the error around the middle + return extra_bits <= low || extra_bits >= high; +} + +static int +compare_buffer(const char* buf, const int expt, const ExessSoftFloat upper) +{ + ExessBigint buf_bigint; + exess_bigint_set_decimal_string(&buf_bigint, buf); + + ExessBigint upper_bigint; + exess_bigint_set_u64(&upper_bigint, upper.f); + + if (expt >= 0) { + exess_bigint_multiply_pow10(&buf_bigint, (unsigned)expt); + } else { + exess_bigint_multiply_pow10(&upper_bigint, (unsigned)-expt); + } + + if (upper.e > 0) { + exess_bigint_shift_left(&upper_bigint, (unsigned)upper.e); + } else { + exess_bigint_shift_left(&buf_bigint, (unsigned)-upper.e); + } + + return exess_bigint_compare(&buf_bigint, &upper_bigint); +} + +double +parsed_double_to_double(const ExessDecimalDouble in) +{ + static const int n_exact_pow10 = sizeof(POW10) / sizeof(POW10[0]); + static const unsigned max_exact_int_digits = 15; // Digits that fit exactly + static const int max_decimal_power = 309; // Max finite power + static const int min_decimal_power = -324; // Min non-zero power + + switch (in.kind) { + case EXESS_NEGATIVE: + break; + case EXESS_NEGATIVE_INFINITY: + return (double)-INFINITY; + case EXESS_NEGATIVE_ZERO: + return -0.0; + case EXESS_POSITIVE_ZERO: + return 0.0; + case EXESS_POSITIVE: + break; + case EXESS_POSITIVE_INFINITY: + return (double)INFINITY; + case EXESS_NAN: + return (double)NAN; + } + + uint64_t frac = 0; + for (unsigned i = 0u; i < in.n_digits; ++i) { + if (is_digit(in.digits[i])) { + frac = (frac * 10) + (unsigned)(in.digits[i] - '0'); + } + } + + const int expt = in.expt; + const int result_power = (int)in.n_digits + expt; + + // Return early for simple exact cases + + const int sign = in.kind >= EXESS_POSITIVE_ZERO ? 1 : -1; + + if (result_power > max_decimal_power) { + return sign * (double)INFINITY; + } + + if (result_power < min_decimal_power) { + return sign * 0.0; + } + + if (in.n_digits < max_exact_int_digits) { + if (expt < 0 && -expt < n_exact_pow10) { + return sign * ((double)frac / (double)POW10[-expt]); + } + + if (expt >= 0 && expt < n_exact_pow10) { + return sign * ((double)frac * (double)POW10[expt]); + } + } + + // Try to guess the number using only soft floating point (fast path) + ExessSoftFloat guess = {0, 0}; + const bool exact = sftod(frac, expt, (int)in.n_digits, &guess); + const double g = soft_float_to_double(guess); + if (exact) { + return sign * g; + } + + // Not sure, guess is either the number or its predecessor (rare slow path) + // Compare it with the buffer using bigints to find out which + const ExessSoftFloat upper = {guess.f * 2 + 1, guess.e - 1}; + const int cmp = compare_buffer(in.digits, in.expt, upper); + if (cmp < 0) { + return sign * g; + } + + if (cmp > 0) { + return sign * nextafter(g, (double)INFINITY); + } + + if ((guess.f & 1u) == 0) { + return sign * g; // Round towards even + } + + return sign * nextafter(g, (double)INFINITY); // Round odd up +} diff --git a/subprojects/exess/src/strtod.h b/subprojects/exess/src/strtod.h new file mode 100644 index 00000000..7fdf7d6a --- /dev/null +++ b/subprojects/exess/src/strtod.h @@ -0,0 +1,33 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef EXESS_STRTOD_H +#define EXESS_STRTOD_H + +#include "decimal.h" + +#include "exess/exess.h" + +ExessResult +parse_decimal(ExessDecimalDouble* out, const char* str); + +ExessResult +parse_double(ExessDecimalDouble* out, const char* str); + +double +parsed_double_to_double(ExessDecimalDouble in); + +#endif // EXESS_STRTOD_H diff --git a/subprojects/exess/src/time.c b/subprojects/exess/src/time.c new file mode 100644 index 00000000..8c7bc12c --- /dev/null +++ b/subprojects/exess/src/time.c @@ -0,0 +1,172 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "read_utils.h" +#include "string_utils.h" +#include "time_utils.h" +#include "timezone.h" +#include "write_utils.h" + +#include "exess/exess.h" + +#include +#include +#include +#include +#include + +ExessResult +read_nanoseconds(uint32_t* const out, const char* const str) +{ + size_t i = 0; + char frac_digits[10] = {'0', '0', '0', '0', '0', '0', '0', '0', '0', 0}; + for (unsigned j = 0u; j < 9u && is_digit(str[i]); ++j) { + frac_digits[j] = str[i++]; + } + + return result(exess_read_uint(out, frac_digits).status, i); +} + +ExessResult +exess_read_time(ExessTime* const out, const char* const str) +{ + memset(out, 0, sizeof(*out)); + + // Read hour + size_t i = skip_whitespace(str); + ExessResult r = read_two_digit_number(&out->hour, 0, 24, str + i); + if (r.status) { + return result(r.status, i + r.count); + } + + // Read hour-minute delimiter + i += r.count; + if (str[i] != ':') { + return result(EXESS_EXPECTED_COLON, i); + } + + // Read minute + ++i; + r = read_two_digit_number(&out->minute, 0, 59, str + i); + if (r.status) { + return result(r.status, i + r.count); + } + + // Read minute-second delimiter + i += r.count; + if (str[i] != ':') { + return result(EXESS_EXPECTED_COLON, i); + } + + // Read second + ++i; + r = read_two_digit_number(&out->second, 0, 59, str + i); + i += r.count; + if (r.status) { + return result(r.status, i); + } + + // Read nanoseconds if present + if (str[i] == '.') { + ++i; + r = read_nanoseconds(&out->nanosecond, str + i); + i += r.count; + } + + // Read timezone if present + if (!is_end(str[i])) { + r = exess_read_timezone(&out->zone, str + i); + i += r.count; + } else { + out->zone.quarter_hours = EXESS_LOCAL; + } + + return end_read(r.status, str, i); +} + +size_t +write_nanoseconds(const uint32_t nanosecond, + const size_t buf_size, + char* const buf, + const size_t i) +{ + assert(nanosecond <= 999999999); + + if (nanosecond == 0) { + return 0; + } + + char frac_digits[10] = {'0', '0', '0', '0', '0', '0', '0', '0', '0', 0}; + + // Write digits right to left, but replace trailing zeros with null + uint32_t remaining = nanosecond; + uint32_t n_trailing = 0; + bool wrote_nonzero = false; + for (uint32_t j = 0; remaining > 0; ++j) { + const char digit = (char)('0' + (remaining % 10)); + if (!wrote_nonzero && digit == '0') { + frac_digits[8 - j] = '\0'; + ++n_trailing; + } else { + frac_digits[8 - j] = digit; + } + + wrote_nonzero = wrote_nonzero || digit != '0'; + remaining /= 10; + } + + size_t n = write_char('.', buf_size, buf, i); + + n += write_string(9 - n_trailing, frac_digits, buf_size, buf, i + n); + + return n; +} + +ExessResult +write_time(const ExessTime value, + const size_t buf_size, + char* const buf, + const size_t offset) +{ + if (value.hour > 24 || value.minute > 59 || value.second > 59 || + value.nanosecond > 999999999 || + (value.hour == 24 && + (value.minute != 0 || value.second != 0 || value.nanosecond != 0))) { + return result(EXESS_BAD_VALUE, 0); + } + + size_t o = offset; + + // Write integral hour, minute, and second + o += write_two_digit_number(value.hour, buf_size, buf, o); + o += write_char(':', buf_size, buf, o); + o += write_two_digit_number(value.minute, buf_size, buf, o); + o += write_char(':', buf_size, buf, o); + o += write_two_digit_number(value.second, buf_size, buf, o); + o += write_nanoseconds(value.nanosecond, buf_size, buf, o); + + const ExessResult r = write_timezone(value.zone, buf_size, buf, o); + + return result(r.status, o - offset + r.count); +} + +ExessResult +exess_write_time(const ExessTime value, const size_t buf_size, char* const buf) +{ + const ExessResult r = write_time(value, buf_size, buf, 0); + + return end_write(r.status, buf_size, buf, r.count); +} diff --git a/subprojects/exess/src/time_utils.h b/subprojects/exess/src/time_utils.h new file mode 100644 index 00000000..32e4b6df --- /dev/null +++ b/subprojects/exess/src/time_utils.h @@ -0,0 +1,35 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef EXESS_TIME_UTILS_H +#define EXESS_TIME_UTILS_H + +#include +#include + +/// Read fractional digits as an integer number of nanoseconds +ExessResult +read_nanoseconds(uint32_t* out, const char* str); + +/// Write nanoseconds as fractional digits +size_t +write_nanoseconds(uint32_t nanosecond, size_t buf_size, char* buf, size_t i); + +/// Write a complete time with timezone suffix if necessary +ExessResult +write_time(ExessTime value, size_t buf_size, char* buf, size_t offset); + +#endif // EXESS_TIME_UTILS_H diff --git a/subprojects/exess/src/timezone.c b/subprojects/exess/src/timezone.c new file mode 100644 index 00000000..717f0d08 --- /dev/null +++ b/subprojects/exess/src/timezone.c @@ -0,0 +1,150 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "timezone.h" +#include "date_utils.h" +#include "read_utils.h" +#include "string_utils.h" +#include "write_utils.h" + +#include "exess/exess.h" + +#include +#include + +ExessResult +exess_read_timezone(ExessTimezone* const out, const char* const str) +{ + out->quarter_hours = INT8_MAX; + + // Start at the beginning (no whitespace skipping here) + size_t i = 0; + + // Handle UTC special case + if (str[i] == 'Z') { + out->quarter_hours = 0; + return result(EXESS_SUCCESS, i + 1); + } + + // Read leading sign (required) + int sign = 1; + switch (str[i]) { + case '+': + ++i; + break; + case '-': + sign = -1; + ++i; + break; + default: + return result(EXESS_EXPECTED_SIGN, i); + } + + const char h0 = str[i]; + if (!is_digit(h0)) { + return result(EXESS_EXPECTED_DIGIT, i); + } + + const char h1 = str[++i]; + if (!is_digit(h1)) { + return result(EXESS_EXPECTED_DIGIT, i); + } + + ++i; + + const int8_t hour = (int8_t)(sign * (10 * (h0 - '0') + (h1 - '0'))); + if (hour > 14 || hour < -14) { + return result(EXESS_OUT_OF_RANGE, i); + } + + if (str[i] != ':') { + return result(EXESS_EXPECTED_COLON, i); + } + + const char m0 = str[++i]; + if (!is_digit(m0)) { + return result(EXESS_EXPECTED_DIGIT, i); + } + + const char m1 = str[++i]; + if (!is_digit(m1)) { + return result(EXESS_EXPECTED_DIGIT, i); + } + + const int8_t minute = (int8_t)(sign * (10 * (m0 - '0') + (m1 - '0'))); + + ++i; + + if (minute % 15) { + return result(EXESS_UNSUPPORTED, i); + } + + if (minute > 59 || minute < -59) { + return result(EXESS_OUT_OF_RANGE, i); + } + + const int8_t quarters = (int8_t)(4 * hour + minute / 15); + if (quarters < -56 || quarters > 56) { + return result(EXESS_OUT_OF_RANGE, i); + } + + out->quarter_hours = quarters; + + return result(EXESS_SUCCESS, i); +} + +size_t +exess_timezone_string_length(const ExessTimezone value) +{ + return value.quarter_hours != EXESS_LOCAL ? (value.quarter_hours == 0) ? 1 : 6 + : 0; +} + +ExessResult +write_timezone(const ExessTimezone value, + const size_t buf_size, + char* const buf, + size_t o) +{ + if (value.quarter_hours == EXESS_LOCAL) { + return result(EXESS_SUCCESS, 0); + } + + if (value.quarter_hours < -56 || value.quarter_hours > 56) { + return result(EXESS_BAD_VALUE, 0); + } + + if (!buf) { + return result(EXESS_SUCCESS, exess_timezone_string_length(value)); + } + + if (value.quarter_hours == 0) { + write_char('Z', buf_size, buf, o); + return result(EXESS_SUCCESS, 1); + } + + const uint8_t abs_quarters = (uint8_t)abs(value.quarter_hours); + const uint8_t abs_hour = abs_quarters / 4; + const uint8_t abs_minute = (uint8_t)(15u * (abs_quarters % 4u)); + + size_t n = 0; + n += write_char(value.quarter_hours < 0 ? '-' : '+', buf_size, buf, o + n); + n += write_two_digit_number(abs_hour, buf_size, buf, o + n); + n += write_char(':', buf_size, buf, o + n); + n += write_two_digit_number(abs_minute, buf_size, buf, o + n); + + return result(EXESS_SUCCESS, n); +} diff --git a/subprojects/exess/src/timezone.h b/subprojects/exess/src/timezone.h new file mode 100644 index 00000000..b10c2483 --- /dev/null +++ b/subprojects/exess/src/timezone.h @@ -0,0 +1,59 @@ +/* + Copyright 2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef EXESS_TIMEZONE_H +#define EXESS_TIMEZONE_H + +#include "exess/exess.h" + +#include + +/// The maximum length of a canonical timezone string, 6 +#define EXESS_MAX_TIMEZONE_LENGTH 6 + +/** + Read a timezone string after any leading whitespace. + + @param out Set to the parsed value, or false on error. + @param str String input. + @return The `count` of characters read, and a `status` code. +*/ +EXESS_API +ExessResult +exess_read_timezone(ExessTimezone* EXESS_NONNULL out, + const char* EXESS_NONNULL str); + +/** + Write a canonical timezone suffix. + + The output is always in canonical form, either `Z` for UTC or a signed hour + and minute offset with leading zeros, like `-05:30` or `+14:00`. + + @param value Value to write. + @param buf_size The size of `buf` in bytes. + @param buf Output buffer, or null to only measure. + @param o The current write offset in `buf` + + @return #EXESS_SUCCESS on success, #EXESS_NO_SPACE if the buffer is too + small, or #EXESS_BAD_VALUE if the value is invalid. +*/ +ExessResult +write_timezone(ExessTimezone value, + size_t buf_size, + char* EXESS_NULLABLE buf, + size_t o); + +#endif // EXESS_TIMEZONE_H diff --git a/subprojects/exess/src/ubyte.c b/subprojects/exess/src/ubyte.c new file mode 100644 index 00000000..f520832a --- /dev/null +++ b/subprojects/exess/src/ubyte.c @@ -0,0 +1,45 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "read_utils.h" + +#include "exess/exess.h" + +#include +#include + +ExessResult +exess_read_ubyte(uint8_t* const out, const char* const str) +{ + uint64_t long_out = 0; + const ExessResult r = exess_read_ulong(&long_out, str); + if (r.status) { + return r; + } + + if (long_out > UINT8_MAX) { + return result(EXESS_OUT_OF_RANGE, r.count); + } + + *out = (uint8_t)long_out; + return r; +} + +ExessResult +exess_write_ubyte(const uint8_t value, const size_t buf_size, char* const buf) +{ + return exess_write_ulong(value, buf_size, buf); +} diff --git a/subprojects/exess/src/uint.c b/subprojects/exess/src/uint.c new file mode 100644 index 00000000..dad46b48 --- /dev/null +++ b/subprojects/exess/src/uint.c @@ -0,0 +1,45 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "read_utils.h" + +#include "exess/exess.h" + +#include +#include + +ExessResult +exess_read_uint(uint32_t* const out, const char* const str) +{ + uint64_t long_out = 0; + const ExessResult r = exess_read_ulong(&long_out, str); + if (r.status && r.status != EXESS_EXPECTED_END) { + return r; + } + + if (long_out > UINT32_MAX) { + return result(EXESS_OUT_OF_RANGE, r.count); + } + + *out = (uint32_t)long_out; + return r; +} + +ExessResult +exess_write_uint(const uint32_t value, const size_t buf_size, char* const buf) +{ + return exess_write_ulong(value, buf_size, buf); +} diff --git a/subprojects/exess/src/ulong.c b/subprojects/exess/src/ulong.c new file mode 100644 index 00000000..3d9e00c9 --- /dev/null +++ b/subprojects/exess/src/ulong.c @@ -0,0 +1,94 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "exess/exess.h" +#include "int_math.h" +#include "read_utils.h" +#include "string_utils.h" +#include "write_utils.h" + +#include +#include + +ExessResult +exess_read_ulong(uint64_t* const out, const char* const str) +{ + *out = 0; + + // Ensure the first character is a digit + size_t i = skip_whitespace(str); + if (!is_digit(str[i])) { + return result(EXESS_EXPECTED_DIGIT, i); + } + + // Skip leading zeros + int n_zeroes = 0; + while (str[i] == '0') { + ++i; + ++n_zeroes; + } + + // Read digits + for (; is_digit(str[i]); ++i) { + const uint64_t next = (*out * 10u) + (uint64_t)(str[i] - '0'); + if (next < *out) { + *out = 0; + return result(EXESS_OUT_OF_RANGE, i); + } + + *out = next; + } + + return end_read(EXESS_SUCCESS, str, i); +} + +ExessResult +write_digits(const uint64_t value, + const size_t buf_size, + char* const buf, + const size_t i) +{ + const uint8_t n_digits = exess_num_digits(value); + if (!buf) { + return result(EXESS_SUCCESS, n_digits); + } + + if (i + n_digits >= buf_size) { + return end_write(EXESS_NO_SPACE, buf_size, buf, 0); + } + + // Point s to the end + char* s = buf + i + n_digits - 1u; + + // Write integer part (right to left) + uint64_t remaining = value; + do { + *s-- = (char)('0' + (remaining % 10)); + } while ((remaining /= 10) > 0); + + return result(EXESS_SUCCESS, n_digits); +} + +ExessResult +exess_write_ulong(const uint64_t value, const size_t buf_size, char* const buf) +{ + if (!buf) { + return result(EXESS_SUCCESS, exess_num_digits(value)); + } + + const ExessResult r = write_digits(value, buf_size, buf, 0); + return end_write(r.status, buf_size, buf, r.count); +} diff --git a/subprojects/exess/src/ushort.c b/subprojects/exess/src/ushort.c new file mode 100644 index 00000000..fc4d2c7e --- /dev/null +++ b/subprojects/exess/src/ushort.c @@ -0,0 +1,45 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "read_utils.h" + +#include "exess/exess.h" + +#include +#include + +ExessResult +exess_read_ushort(uint16_t* const out, const char* const str) +{ + uint64_t long_out = 0; + const ExessResult r = exess_read_ulong(&long_out, str); + if (r.status) { + return r; + } + + if (long_out > UINT16_MAX) { + return result(EXESS_OUT_OF_RANGE, r.count); + } + + *out = (uint16_t)long_out; + return r; +} + +ExessResult +exess_write_ushort(const uint16_t value, const size_t buf_size, char* const buf) +{ + return exess_write_ulong(value, buf_size, buf); +} diff --git a/subprojects/exess/src/variant.c b/subprojects/exess/src/variant.c new file mode 100644 index 00000000..d8977abd --- /dev/null +++ b/subprojects/exess/src/variant.c @@ -0,0 +1,431 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "read_utils.h" +#include "write_utils.h" + +#include "exess/exess.h" + +#include +#include +#include + +// Constructors + +ExessVariant +exess_make_nothing(const ExessStatus status) +{ + ExessVariant v = {EXESS_NOTHING, .value.as_status = status}; + return v; +} + +ExessVariant +exess_make_boolean(const bool value) +{ + ExessVariant v = {EXESS_BOOLEAN, .value.as_bool = value}; + return v; +} + +ExessVariant +exess_make_decimal(const double value) +{ + ExessVariant v = {EXESS_DECIMAL, .value.as_double = value}; + return v; +} + +ExessVariant +exess_make_double(const double value) +{ + ExessVariant v = {EXESS_DOUBLE, .value.as_double = value}; + return v; +} + +ExessVariant +exess_make_float(const float value) +{ + ExessVariant v = {EXESS_FLOAT, .value.as_float = value}; + return v; +} + +ExessVariant +exess_make_long(const int64_t value) +{ + ExessVariant v = {EXESS_LONG, .value.as_long = value}; + return v; +} + +ExessVariant +exess_make_int(const int32_t value) +{ + ExessVariant v = {EXESS_INT, .value.as_int = value}; + return v; +} + +ExessVariant +exess_make_short(const int16_t value) +{ + ExessVariant v = {EXESS_SHORT, .value.as_short = value}; + return v; +} + +ExessVariant +exess_make_byte(const int8_t value) +{ + ExessVariant v = {EXESS_BYTE, .value.as_byte = value}; + return v; +} + +ExessVariant +exess_make_ulong(const uint64_t value) +{ + ExessVariant v = {EXESS_ULONG, .value.as_ulong = value}; + return v; +} + +ExessVariant +exess_make_uint(const uint32_t value) +{ + ExessVariant v = {EXESS_UINT, .value.as_uint = value}; + return v; +} + +ExessVariant +exess_make_ushort(const uint16_t value) +{ + ExessVariant v = {EXESS_USHORT, .value.as_ushort = value}; + return v; +} + +ExessVariant +exess_make_ubyte(const uint8_t value) +{ + ExessVariant v = {EXESS_UBYTE, .value.as_ubyte = value}; + return v; +} + +ExessVariant +exess_make_duration(const ExessDuration value) +{ + ExessVariant v = {EXESS_DURATION, .value.as_duration = value}; + return v; +} + +ExessVariant +exess_make_datetime(const ExessDateTime value) +{ + ExessVariant v = {EXESS_DATETIME, .value.as_datetime = value}; + return v; +} + +ExessVariant +exess_make_time(const ExessTime value) +{ + ExessVariant v = {EXESS_TIME, .value.as_time = value}; + return v; +} + +ExessVariant +exess_make_date(const ExessDate value) +{ + ExessVariant v = {EXESS_DATE, .value.as_date = value}; + return v; +} + +ExessVariant +exess_make_hex(const ExessBlob blob) +{ + ExessVariant v = {EXESS_HEX, .value.as_blob = blob}; + return v; +} + +ExessVariant +exess_make_base64(const ExessBlob blob) +{ + ExessVariant v = {EXESS_BASE64, .value.as_blob = blob}; + return v; +} + +// Accessors + +ExessStatus +exess_get_status(const ExessVariant* const variant) +{ + return variant->datatype == EXESS_NOTHING ? variant->value.as_status + : EXESS_SUCCESS; +} +const bool* +exess_get_boolean(const ExessVariant* const variant) +{ + return variant->datatype == EXESS_BOOLEAN ? &variant->value.as_bool : NULL; +} + +const double* +exess_get_double(const ExessVariant* const variant) +{ + return (variant->datatype == EXESS_DECIMAL || + variant->datatype == EXESS_DOUBLE) + ? &variant->value.as_double + : NULL; +} + +const float* +exess_get_float(const ExessVariant* const variant) +{ + return variant->datatype == EXESS_FLOAT ? &variant->value.as_float : NULL; +} + +const int64_t* +exess_get_long(const ExessVariant* const variant) +{ + return (variant->datatype >= EXESS_INTEGER && variant->datatype <= EXESS_LONG) + ? &variant->value.as_long + : NULL; +} + +const int32_t* +exess_get_int(const ExessVariant* const variant) +{ + return variant->datatype == EXESS_INT ? &variant->value.as_int : NULL; +} + +const int16_t* +exess_get_short(const ExessVariant* const variant) +{ + return variant->datatype == EXESS_SHORT ? &variant->value.as_short : NULL; +} + +const int8_t* +exess_get_byte(const ExessVariant* const variant) +{ + return variant->datatype == EXESS_BYTE ? &variant->value.as_byte : NULL; +} + +const uint64_t* +exess_get_ulong(const ExessVariant* const variant) +{ + return (variant->datatype == EXESS_NON_NEGATIVE_INTEGER || + variant->datatype == EXESS_ULONG || + variant->datatype == EXESS_POSITIVE_INTEGER) + ? &variant->value.as_ulong + : NULL; +} + +const uint32_t* +exess_get_uint(const ExessVariant* const variant) +{ + return variant->datatype == EXESS_UINT ? &variant->value.as_uint : NULL; +} + +const uint16_t* +exess_get_ushort(const ExessVariant* const variant) +{ + return variant->datatype == EXESS_USHORT ? &variant->value.as_ushort : NULL; +} + +const uint8_t* +exess_get_ubyte(const ExessVariant* const variant) +{ + return variant->datatype == EXESS_UBYTE ? &variant->value.as_ubyte : NULL; +} + +const ExessBlob* +exess_get_blob(const ExessVariant* const variant) +{ + return (variant->datatype == EXESS_HEX || variant->datatype == EXESS_BASE64) + ? &variant->value.as_blob + : NULL; +} + +const ExessDuration* +exess_get_duration(const ExessVariant* const variant) +{ + return variant->datatype == EXESS_DURATION ? &variant->value.as_duration + : NULL; +} + +const ExessDateTime* +exess_get_datetime(const ExessVariant* const variant) +{ + return variant->datatype == EXESS_DATETIME ? &variant->value.as_datetime + : NULL; +} + +const ExessTime* +exess_get_time(const ExessVariant* const variant) +{ + return variant->datatype == EXESS_TIME ? &variant->value.as_time : NULL; +} + +const ExessDate* +exess_get_date(const ExessVariant* const variant) +{ + return variant->datatype == EXESS_DATE ? &variant->value.as_date : NULL; +} + +// Reading and Writing + +ExessResult +exess_read_variant(ExessVariant* const out, + ExessDatatype datatype, + const char* const str) +{ + ExessResult r = {EXESS_UNSUPPORTED, 0}; + + out->datatype = datatype; + + switch (datatype) { + case EXESS_NOTHING: + break; + case EXESS_BOOLEAN: + return exess_read_boolean(&out->value.as_bool, str); + case EXESS_DECIMAL: + return exess_read_decimal(&out->value.as_double, str); + case EXESS_DOUBLE: + return exess_read_double(&out->value.as_double, str); + case EXESS_FLOAT: + return exess_read_float(&out->value.as_float, str); + case EXESS_INTEGER: + return exess_read_long(&out->value.as_long, str); + + case EXESS_NON_POSITIVE_INTEGER: + if (!(r = exess_read_long(&out->value.as_long, str)).status) { + if (out->value.as_long > 0) { + return result(EXESS_OUT_OF_RANGE, r.count); + } + } + break; + + case EXESS_NEGATIVE_INTEGER: + if (!(r = exess_read_long(&out->value.as_long, str)).status) { + if (out->value.as_long >= 0) { + return result(EXESS_OUT_OF_RANGE, r.count); + } + } + break; + + case EXESS_LONG: + return exess_read_long(&out->value.as_long, str); + case EXESS_INT: + return exess_read_int(&out->value.as_int, str); + case EXESS_SHORT: + return exess_read_short(&out->value.as_short, str); + case EXESS_BYTE: + return exess_read_byte(&out->value.as_byte, str); + case EXESS_NON_NEGATIVE_INTEGER: + case EXESS_ULONG: + return exess_read_ulong(&out->value.as_ulong, str); + case EXESS_UINT: + return exess_read_uint(&out->value.as_uint, str); + case EXESS_USHORT: + return exess_read_ushort(&out->value.as_ushort, str); + case EXESS_UBYTE: + return exess_read_ubyte(&out->value.as_ubyte, str); + + case EXESS_POSITIVE_INTEGER: + if (!(r = exess_read_ulong(&out->value.as_ulong, str)).status) { + if (out->value.as_ulong <= 0) { + return result(EXESS_OUT_OF_RANGE, r.count); + } + } + break; + + case EXESS_DURATION: + return exess_read_duration(&out->value.as_duration, str); + case EXESS_DATETIME: + return exess_read_datetime(&out->value.as_datetime, str); + case EXESS_TIME: + return exess_read_time(&out->value.as_time, str); + case EXESS_DATE: + return exess_read_date(&out->value.as_date, str); + case EXESS_HEX: + return exess_read_hex(&out->value.as_blob, str); + case EXESS_BASE64: + return exess_read_base64(&out->value.as_blob, str); + } + + return r; +} + +ExessResult +exess_write_variant(const ExessVariant variant, + const size_t buf_size, + char* const buf) +{ + if (buf_size > 0) { + buf[0] = '\0'; + } + + switch (variant.datatype) { + case EXESS_NOTHING: + break; + case EXESS_BOOLEAN: + return exess_write_boolean(variant.value.as_bool, buf_size, buf); + case EXESS_DECIMAL: + return exess_write_decimal(variant.value.as_double, buf_size, buf); + case EXESS_DOUBLE: + return exess_write_double(variant.value.as_double, buf_size, buf); + case EXESS_FLOAT: + return exess_write_float(variant.value.as_float, buf_size, buf); + case EXESS_INTEGER: + case EXESS_NON_POSITIVE_INTEGER: + case EXESS_NEGATIVE_INTEGER: + case EXESS_LONG: + return exess_write_long(variant.value.as_long, buf_size, buf); + case EXESS_INT: + return exess_write_int(variant.value.as_int, buf_size, buf); + case EXESS_SHORT: + return exess_write_short(variant.value.as_short, buf_size, buf); + case EXESS_BYTE: + return exess_write_byte(variant.value.as_byte, buf_size, buf); + case EXESS_NON_NEGATIVE_INTEGER: + case EXESS_ULONG: + return exess_write_ulong(variant.value.as_ulong, buf_size, buf); + case EXESS_UINT: + return exess_write_uint(variant.value.as_uint, buf_size, buf); + case EXESS_USHORT: + return exess_write_ushort(variant.value.as_ushort, buf_size, buf); + case EXESS_UBYTE: + return exess_write_ubyte(variant.value.as_ubyte, buf_size, buf); + case EXESS_POSITIVE_INTEGER: + return exess_write_ulong(variant.value.as_ulong, buf_size, buf); + case EXESS_DURATION: + return exess_write_duration(variant.value.as_duration, buf_size, buf); + case EXESS_DATETIME: + return exess_write_datetime(variant.value.as_datetime, buf_size, buf); + case EXESS_TIME: + return exess_write_time(variant.value.as_time, buf_size, buf); + case EXESS_DATE: + return exess_write_date(variant.value.as_date, buf_size, buf); + case EXESS_HEX: + if (variant.value.as_blob.data) { + return exess_write_hex(variant.value.as_blob.size, + (void*)variant.value.as_blob.data, + buf_size, + buf); + } + break; + case EXESS_BASE64: + if (variant.value.as_blob.data) { + return exess_write_base64(variant.value.as_blob.size, + (void*)variant.value.as_blob.data, + buf_size, + buf); + } + break; + } + + return end_write(EXESS_BAD_VALUE, buf_size, buf, 0); +} diff --git a/subprojects/exess/src/warnings.h b/subprojects/exess/src/warnings.h new file mode 100644 index 00000000..2b1f6587 --- /dev/null +++ b/subprojects/exess/src/warnings.h @@ -0,0 +1,46 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef EXESS_WARNINGS_H +#define EXESS_WARNINGS_H + +#if defined(__clang__) + +# define EXESS_DISABLE_CONVERSION_WARNINGS \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wconversion\"") \ + _Pragma("clang diagnostic ignored \"-Wdouble-promotion\"") + +# define EXESS_RESTORE_WARNINGS _Pragma("clang diagnostic pop") + +#elif defined(__GNUC__) + +# define EXESS_DISABLE_CONVERSION_WARNINGS \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wconversion\"") \ + _Pragma("GCC diagnostic ignored \"-Wfloat-conversion\"") \ + _Pragma("GCC diagnostic ignored \"-Wdouble-promotion\"") + +# define EXESS_RESTORE_WARNINGS _Pragma("GCC diagnostic pop") + +#else + +# define EXESS_DISABLE_CONVERSION_WARNINGS +# define EXESS_RESTORE_WARNINGS + +#endif + +#endif // EXESS_WARNINGS_H diff --git a/subprojects/exess/src/write_utils.c b/subprojects/exess/src/write_utils.c new file mode 100644 index 00000000..fdac0754 --- /dev/null +++ b/subprojects/exess/src/write_utils.c @@ -0,0 +1,50 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "write_utils.h" +#include "read_utils.h" + +#include "exess/exess.h" + +#include + +size_t +write_two_digit_number(const uint8_t value, + const size_t buf_size, + char* const buf, + const size_t i) +{ + if (buf_size >= i + 1) { + buf[i] = (char)((value >= 10) ? ('0' + value / 10) : '0'); + buf[i + 1] = (char)('0' + (value % 10)); + } + + return 2; +} + +ExessResult +write_special(const size_t string_length, + const char* const string, + const size_t buf_size, + char* const buf) +{ + if (buf_size < string_length + 1) { + return end_write(EXESS_NO_SPACE, buf_size, buf, 0); + } + + memcpy(buf, string, string_length + 1); + return result(EXESS_SUCCESS, string_length); +} diff --git a/subprojects/exess/src/write_utils.h b/subprojects/exess/src/write_utils.h new file mode 100644 index 00000000..ea941743 --- /dev/null +++ b/subprojects/exess/src/write_utils.h @@ -0,0 +1,85 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef EXESS_WRITE_UTILS_H +#define EXESS_WRITE_UTILS_H + +#include "exess/exess.h" + +#include +#include +#include + +static inline size_t +write_char(const char c, size_t buf_size, char* const buf, const size_t i) +{ + if (buf && buf_size >= i + 1) { + buf[i] = c; + } + + return 1; +} + +static inline size_t +write_string(const size_t len, + const char* str, + const size_t buf_size, + char* const buf, + const size_t i) +{ + if (buf && buf_size >= i + len + 1) { + memcpy(buf + i, str, len); + buf[i + len] = 0; + } + + return len; +} + +static inline ExessResult +end_write(const ExessStatus status, + const size_t buf_size, + char* const buf, + const size_t i) +{ + ExessResult r = {status, status > EXESS_EXPECTED_END ? 0 : i}; + + if (buf) { + if (!status && i >= buf_size) { + r.status = EXESS_NO_SPACE; + r.count = 0; + } + + if (r.count < buf_size) { + buf[r.count] = '\0'; + } + } + + return r; +} + +ExessResult +write_digits(uint64_t value, size_t buf_size, char* buf, size_t i); + +size_t +write_two_digit_number(uint8_t value, size_t buf_size, char* buf, size_t i); + +ExessResult +write_special(size_t string_length, + const char* string, + size_t buf_size, + char* buf); + +#endif // EXESS_WRITE_UTILS_H diff --git a/subprojects/exess/src/year.c b/subprojects/exess/src/year.c new file mode 100644 index 00000000..e268ff80 --- /dev/null +++ b/subprojects/exess/src/year.c @@ -0,0 +1,98 @@ +/* + Copyright 2019-2021 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "date_utils.h" +#include "int_math.h" +#include "macros.h" +#include "read_utils.h" +#include "write_utils.h" + +#include "exess/exess.h" + +#include +#include +#include + +ExessResult +read_year_number(int16_t* const out, const char* const str) +{ + *out = 0; + + // Read leading sign if present + size_t i = 0; + int sign = 1; + if (str[i] == '-') { + sign = -1; + ++i; + } + + // Read digits + uint64_t magnitude = 0; + ExessResult r = exess_read_ulong(&magnitude, str + i); + if (r.status > EXESS_EXPECTED_END) { + return result(r.status, i + r.count); + } + + i += r.count; + + if (sign > 0) { + if (magnitude > (uint16_t)INT16_MAX) { + return result(EXESS_OUT_OF_RANGE, i); + } + + *out = (int16_t)magnitude; + } else { + const uint16_t min_magnitude = (uint16_t)(-(INT16_MIN + 1)) + 1; + if (magnitude > min_magnitude) { + return result(EXESS_OUT_OF_RANGE, i); + } + + if (magnitude == min_magnitude) { + *out = INT16_MIN; + } else { + *out = (int16_t)-magnitude; + } + } + + return result(r.count >= 4 ? EXESS_SUCCESS : EXESS_EXPECTED_DIGIT, i); +} + +ExessResult +write_year_number(const int16_t value, const size_t buf_size, char* const buf) +{ + const uint32_t abs_year = (uint32_t)abs(value); + const uint8_t n_digits = exess_num_digits(abs_year); + const bool is_negative = value < 0; + + if (!buf) { + return result(EXESS_SUCCESS, is_negative + MAX(4u, n_digits)); + } + + // Write sign + size_t i = 0; + if (is_negative) { + i += write_char('-', buf_size, buf, i); + } + + // Write leading zeros to ensure we have at least 4 year digits + for (size_t j = n_digits; j < 4; ++j) { + i += write_char('0', buf_size, buf, i); + } + + const ExessResult yr = exess_write_uint(abs_year, buf_size - i, buf + i); + + return end_write(yr.status, buf_size, buf, i + yr.count); +} -- cgit v1.2.1