diff options
Diffstat (limited to 'subprojects/exess/src/canonical.c')
-rw-r--r-- | subprojects/exess/src/canonical.c | 309 |
1 files changed, 309 insertions, 0 deletions
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 <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 "read_utils.h" +#include "string_utils.h" +#include "write_utils.h" + +#include "exess/exess.h" + +#include <stdbool.h> +#include <stddef.h> + +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); +} |