diff options
Diffstat (limited to 'subprojects/exess/src/coerce.c')
-rw-r--r-- | subprojects/exess/src/coerce.c | 422 |
1 files changed, 422 insertions, 0 deletions
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 <d@drobilla.net> + + 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 <math.h> +#include <stdint.h> + +/* 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; +} |