aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/exess/include/exess/exess.h
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/exess/include/exess/exess.h')
-rw-r--r--subprojects/exess/include/exess/exess.h1633
1 files changed, 1633 insertions, 0 deletions
diff --git a/subprojects/exess/include/exess/exess.h b/subprojects/exess/include/exess/exess.h
new file mode 100644
index 00000000..f327eb2e
--- /dev/null
+++ b/subprojects/exess/include/exess/exess.h
@@ -0,0 +1,1633 @@
+/*
+ Copyright 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.
+*/
+
+#ifndef EXESS_EXESS_H
+#define EXESS_EXESS_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#if defined(_WIN32) && !defined(EXESS_STATIC) && defined(EXESS_INTERNAL)
+# define EXESS_API __declspec(dllexport)
+#elif defined(_WIN32) && !defined(EXESS_STATIC)
+# define EXESS_API __declspec(dllimport)
+#elif defined(__GNUC__)
+# define EXESS_API __attribute__((visibility("default")))
+#else
+# define EXESS_API
+#endif
+
+#ifdef __GNUC__
+# define EXESS_PURE_FUNC __attribute__((pure))
+# define EXESS_CONST_FUNC __attribute__((const))
+#else
+# define EXESS_PURE_FUNC
+# define EXESS_CONST_FUNC
+#endif
+
+#if defined(__clang__) && __clang_major__ >= 7
+# define EXESS_NONNULL _Nonnull
+# define EXESS_NULLABLE _Nullable
+#else
+# define EXESS_NONNULL
+# define EXESS_NULLABLE
+#endif
+
+// Pure API functions have no observable side-effects
+#define EXESS_PURE_API \
+ EXESS_API \
+ EXESS_PURE_FUNC
+
+// Const API functions are pure, and read no memory other than their parameters
+#define EXESS_CONST_API \
+ EXESS_API \
+ EXESS_CONST_FUNC
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ @defgroup exess Exess C API
+ This is the complete public C API of exess.
+ @{
+*/
+
+/// The base URI of XML Schema, `http://www.w3.org/2001/XMLSchema#`
+#define EXESS_XSD_URI "http://www.w3.org/2001/XMLSchema#"
+
+/**
+ @defgroup exess_status Status
+
+ Status codes and return values used for error handling.
+
+ Success and various specific errors are reported by an integer status code,
+ which can be converted to a string to produce friendly error messages.
+ Reading and writing functions return a "result", which has a status code
+ along with a count of bytes read or written.
+
+ @{
+*/
+
+/// Status code to describe errors or other relevant situations
+typedef enum {
+ EXESS_SUCCESS, ///< Success
+ EXESS_EXPECTED_END, ///< Expected end of value
+ EXESS_EXPECTED_BOOLEAN, ///< Expected "false", "true", "0" or "1"
+ EXESS_EXPECTED_INTEGER, ///< Expected an integer value
+ EXESS_EXPECTED_DURATION, ///< Expected a duration starting with 'P'
+ EXESS_EXPECTED_SIGN, ///< Expected '-' or '+'
+ EXESS_EXPECTED_DIGIT, ///< Expected a digit
+ EXESS_EXPECTED_COLON, ///< Expected ':'
+ EXESS_EXPECTED_DASH, ///< Expected '-'
+ EXESS_EXPECTED_TIME_SEP, ///< Expected 'T'
+ EXESS_EXPECTED_TIME_TAG, ///< Expected 'H', 'M', or 'S'
+ EXESS_EXPECTED_DATE_TAG, ///< Expected 'Y', 'M', or 'D'
+ EXESS_EXPECTED_HEX, ///< Expected a hexadecimal character
+ EXESS_EXPECTED_BASE64, ///< Expected a base64 character
+ EXESS_BAD_ORDER, ///< Invalid field order
+ EXESS_BAD_VALUE, ///< Invalid value
+ EXESS_OUT_OF_RANGE, ///< Value out of range for datatype
+ EXESS_NO_SPACE, ///< Insufficient space
+ EXESS_WOULD_REDUCE_PRECISION, ///< Precision reducing coercion required
+ EXESS_WOULD_ROUND, ///< Rounding coercion required
+ EXESS_WOULD_TRUNCATE, ///< Truncating coercion required
+ EXESS_UNSUPPORTED, ///< Unsupported value
+} ExessStatus;
+
+/**
+ Result returned from a read or write function.
+
+ This combines a status code with a byte offset, so it can be used to
+ determine how many characters were read or written, or what error occurred
+ at what character offset.
+*/
+typedef struct {
+ ExessStatus status; ///< Status code
+ size_t count; ///< Number of bytes read or written, excluding null
+} ExessResult;
+
+/**
+ Return a string describing a status code in plain English.
+
+ The returned string is always one sentence, with an uppercase first
+ character, and no trailing period.
+*/
+EXESS_CONST_API
+const char* EXESS_NONNULL
+exess_strerror(ExessStatus status);
+
+/**
+ @}
+ @defgroup exess_numbers Numbers
+ Datatypes for numbers
+ @{
+*/
+
+/**
+ @defgroup exess_decimal Decimal
+
+ An xsd:decimal is a decimal number of arbitrary precision, but this
+ implementation only supports values that fit in a `double`.
+
+ Unlike xsd:double, xsd:decimal is written in numeric form, never in
+ scientific notation. Special infinity and NaN values are not supported.
+ Note that the xsd:decimal representation for some numbers is very long, so
+ xsd:double may be a better choice for values in a wide range.
+
+ Canonical form has no leading "+" sign, and at most 1 leading or trailing
+ zero such that there is at least 1 digit on either side of the decimal
+ point, like "12.34", "-1.0", and "0.0".
+
+ Non-canonical form allows a leading "+", any number of leading and trailing
+ zeros, any number of digits (including zero) on either side of the point,
+ and does not require a decimal point, like "+1", "01", "-.5", "4.", and
+ "42".
+
+ @{
+*/
+
+/// The maximum length of an xsd:decimal string from exess_write_decimal(), 327
+#define EXESS_MAX_DECIMAL_LENGTH 327
+
+/**
+ Read an xsd:decimal string after any leading whitespace.
+
+ Values beyond the range of `decimal` will produce `-INF` or `INF`, and
+ return an error because these are not valid decimal values.
+
+ @param out Set to the parsed value, or NaN on error.
+ @param str String input.
+ @return The `count` of characters read, and a `status` code.
+*/
+EXESS_API
+ExessResult
+exess_read_decimal(double* EXESS_NONNULL out, const char* EXESS_NONNULL str);
+
+/**
+ Write a canonical xsd:decimal string.
+
+ @param value Value to write.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return The `count` of characters in the output, and `status`
+ #EXESS_SUCCESS, or #EXESS_NO_SPACE if the buffer is too small.
+*/
+EXESS_API
+ExessResult
+exess_write_decimal(double value, size_t buf_size, char* EXESS_NULLABLE buf);
+
+/**
+ @}
+ @defgroup exess_double Double
+
+ An xsd:double is an IEEE-754 64-bit floating point number, written in
+ scientific notation.
+
+ Canonical form has no leading "+" sign, at most 1 leading or trailing zero
+ such that there is at least 1 digit on either side of the decimal point, and
+ always has an exponent, like "12.34E56", "-1.0E-2", and "-0.0E0". The
+ special values negative infinity, positive infinity, and not-a-number are
+ written "-INF", "INF", and "NaN", respectively.
+
+ Non-canonical form allows a leading "+", any number of leading and trailing
+ zeros, any number of digits (including zero) on either side of the point,
+ and does not require an exponent or decimal point, like "+1E3", "1E+3",
+ ".5E3", "4.2", and "42".
+
+ @{
+*/
+
+/// The maximum length of a canonical xsd:double string, 24
+#define EXESS_MAX_DOUBLE_LENGTH 24
+
+/**
+ Read an xsd:double string after any leading whitespace.
+
+ Values beyond the range of `double` will produce `-INF` or `INF`.
+
+ @param out Set to the parsed value, or `NAN` on error.
+ @param str String input.
+ @return The `count` of characters read, and a `status` code.
+*/
+EXESS_API
+ExessResult
+exess_read_double(double* EXESS_NONNULL out, const char* EXESS_NONNULL str);
+
+/**
+ Write a canonical xsd:double string.
+
+ Any `double` value is supported. Reading the resulting string with
+ exess_read_double() will produce exactly `value`, except the extra bits in
+ NaNs are not preserved.
+
+ @param value Value to write.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return The `count` of characters in the output and `status` #EXESS_SUCCESS,
+ or #EXESS_NO_SPACE if the buffer is too small.
+*/
+EXESS_API
+ExessResult
+exess_write_double(double value, size_t buf_size, char* EXESS_NULLABLE buf);
+
+/**
+ @}
+ @defgroup exess_float Float
+
+ An xsd:float is an IEEE-754 32-bit floating point number, written in
+ scientific notation.
+
+ The lexical form is the same as xsd:double, the only difference is that the
+ value space of xsd:float is smaller. See @ref exess_double for details.
+
+ @{
+*/
+
+/// The maximum length of a canonical xsd:float string, 15
+#define EXESS_MAX_FLOAT_LENGTH 15
+
+/**
+ Read an xsd:float string after any leading whitespace.
+
+ Values beyond the range of `float` will produce `-INF` or `INF`.
+
+ @param out Set to the parsed value, or `NAN` on error.
+ @param str String input.
+ @return The `count` of characters read, and a `status` code.
+*/
+EXESS_API
+ExessResult
+exess_read_float(float* EXESS_NONNULL out, const char* EXESS_NONNULL str);
+
+/**
+ Write a canonical xsd:float string.
+
+ Any `float` value is supported. Reading the resulting string with
+ exess_read_float() will produce exactly `value`, except the extra bits in
+ NaNs are not preserved.
+
+ @param value Value to write.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return The `count` of characters in the output, and `status`
+ #EXESS_SUCCESS, or #EXESS_NO_SPACE if the buffer is too small.
+*/
+EXESS_API
+ExessResult
+exess_write_float(float value, size_t buf_size, char* EXESS_NULLABLE buf);
+
+/**
+ @}
+ @defgroup exess_boolean Boolean
+
+ An xsd:boolean has only two possible values, canonically written as "false"
+ and "true". The non-canonical forms "0" and "1" are also supported.
+
+ @{
+*/
+
+/// The maximum length of a canonical xsd:boolean string, 5
+#define EXESS_MAX_BOOLEAN_LENGTH 5
+
+/**
+ Read an xsd:boolean 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_boolean(bool* EXESS_NONNULL out, const char* EXESS_NONNULL str);
+
+/**
+ Write a canonical xsd:boolean string.
+
+ @param value Value to write.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return The `count` of characters in the output, and `status`
+ #EXESS_SUCCESS, or #EXESS_NO_SPACE if the buffer is too small.
+*/
+EXESS_API
+ExessResult
+exess_write_boolean(bool value, size_t buf_size, char* EXESS_NULLABLE buf);
+
+/**
+ @}
+ @defgroup exess_long Long
+
+ An xsd:long is a signed 64-bit integer, written in decimal.
+
+ Values range from -9223372036854775808 to 9223372036854775807 inclusive.
+
+ Canonical form has no leading "+" sign and no leading zeros (except for the
+ number "0"), like "-1", "0", and "1234".
+
+ Non-canonical form allows a leading "+" and any number of leading zeros,
+ like "01" and "+0001234".
+
+ @{
+*/
+
+/// The maximum length of a canonical xsd:long string, 20
+#define EXESS_MAX_LONG_LENGTH 20
+
+/**
+ Read an xsd:long string after any leading whitespace.
+
+ @param out Set to the parsed value, or zero on error.
+ @param str String input.
+ @return The `count` of characters read, and a `status` code.
+*/
+EXESS_API
+ExessResult
+exess_read_long(int64_t* EXESS_NONNULL out, const char* EXESS_NONNULL str);
+
+/**
+ Write a canonical xsd:long string.
+
+ @param value Value to write.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return The `count` of characters in the output, and `status`
+ #EXESS_SUCCESS, or #EXESS_NO_SPACE if the buffer is too small.
+*/
+EXESS_API
+ExessResult
+exess_write_long(int64_t value, size_t buf_size, char* EXESS_NULLABLE buf);
+
+/**
+ @}
+ @defgroup exess_int Int
+
+ An xsd:int is a signed 32-bit integer.
+
+ Values range from -2147483648 to 2147483647 inclusive.
+
+ The lexical form is the same as xsd:long, the only difference is that the
+ value space of xsd:int is smaller. See @ref exess_long for details.
+
+ @{
+*/
+
+/// The maximum length of a canonical xsd:int string, 11
+#define EXESS_MAX_INT_LENGTH 11
+
+/**
+ Read an xsd:int string after any leading whitespace.
+
+ @param out Set to the parsed value, or zero on error.
+ @param str String input.
+ @return The `count` of characters read, and a `status` code.
+*/
+EXESS_API
+ExessResult
+exess_read_int(int32_t* EXESS_NONNULL out, const char* EXESS_NONNULL str);
+
+/**
+ Write a canonical xsd:int string.
+
+ @param value Value to write.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return The `count` of characters in the output, and `status`
+ #EXESS_SUCCESS, or #EXESS_NO_SPACE if the buffer is too small.
+*/
+EXESS_API
+ExessResult
+exess_write_int(int32_t value, size_t buf_size, char* EXESS_NULLABLE buf);
+
+/**
+ @}
+ @defgroup exess_short Short
+
+ An xsd:short is a signed 16-bit integer.
+
+ Values range from -32768 to 32767 inclusive.
+
+ The lexical form is the same as xsd:long, the only difference is that the
+ value space of xsd:short is smaller. See @ref exess_long for details.
+
+ @{
+*/
+
+/// The maximum length of a canonical xsd:short string, 6
+#define EXESS_MAX_SHORT_LENGTH 6
+
+/**
+ Read an xsd:short string after any leading whitespace.
+
+ @param out Set to the parsed value, or zero on error.
+ @param str String input.
+ @return The `count` of characters read, and a `status` code.
+*/
+EXESS_API
+ExessResult
+exess_read_short(int16_t* EXESS_NONNULL out, const char* EXESS_NONNULL str);
+
+/**
+ Write a canonical xsd:short string.
+
+ @param value Value to write.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return The `count` of characters in the output, and `status`
+ #EXESS_SUCCESS, or #EXESS_NO_SPACE if the buffer is too small.
+*/
+EXESS_API
+ExessResult
+exess_write_short(int16_t value, size_t buf_size, char* EXESS_NULLABLE buf);
+
+/**
+ @}
+ @defgroup exess_byte Byte
+
+ An xsd:byte is a signed 8-bit integer.
+
+ Values range from -128 to 127 inclusive.
+
+ The lexical form is the same as xsd:long, the only difference is that the
+ value space of xsd:byte is smaller. See @ref exess_long for details.
+
+ @{
+*/
+
+/// The maximum length of a canonical xsd:byte string, 4
+#define EXESS_MAX_BYTE_LENGTH 4
+
+/**
+ Read an xsd:byte string after any leading whitespace.
+
+ @param out Set to the parsed value, or zero on error.
+ @param str String input.
+ @return The `count` of characters read, and a `status` code.
+*/
+EXESS_API
+ExessResult
+exess_read_byte(int8_t* EXESS_NONNULL out, const char* EXESS_NONNULL str);
+
+/**
+ Write a canonical xsd:byte string.
+
+ @param value Value to write.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return The `count` of characters in the output, and `status`
+ #EXESS_SUCCESS, or #EXESS_NO_SPACE if the buffer is too small.
+*/
+EXESS_API
+ExessResult
+exess_write_byte(int8_t value, size_t buf_size, char* EXESS_NULLABLE buf);
+
+/**
+ @}
+ @defgroup exess_ulong Unsigned Long
+
+ An xsd:unsignedLong is an unsigned 64-bit integer, written in decimal.
+
+ Values range from 0 to 18446744073709551615 inclusive.
+
+ Canonical form has no leading "+" sign and no leading zeros (except for the
+ number "0"), like "0", and "1234".
+
+ Non-canonical form allows any number of leading zeros, like "01" and
+ "0001234".
+
+ @{
+*/
+
+/// The maximum length of a canonical xsd:unsignedLong string, 20
+#define EXESS_MAX_ULONG_LENGTH 20
+
+/**
+ Read an xsd:unsignedLong string after any leading whitespace.
+
+ @param out Set to the parsed value, or zero on error.
+ @param str String input.
+ @return The `count` of characters read, and a `status` code.
+*/
+EXESS_API
+ExessResult
+exess_read_ulong(uint64_t* EXESS_NONNULL out, const char* EXESS_NONNULL str);
+
+/**
+ Write a canonical xsd:unsignedLong string.
+
+ @param value Value to write.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return The `count` of characters in the output, and `status`
+ #EXESS_SUCCESS, or #EXESS_NO_SPACE if the buffer is too small.
+*/
+EXESS_API
+ExessResult
+exess_write_ulong(uint64_t value, size_t buf_size, char* EXESS_NULLABLE buf);
+
+/**
+ @}
+ @defgroup exess_uint Unsigned Int
+
+ An xsd:unsignedInt is an unsigned 32-bit integer.
+
+ Values range from 0 to 4294967295 inclusive.
+
+ The lexical form is the same as xsd:unsignedLong, the only difference is
+ that the value space of xsd:unsignedInt is smaller. See @ref exess_ulong
+ for details.
+
+ @{
+*/
+
+/// The maximum length of a canonical xsd:unsignedInt string, 10
+#define EXESS_MAX_UINT_LENGTH 10
+
+/**
+ Read an xsd:unsignedInt string after any leading whitespace.
+
+ @param out Set to the parsed value, or zero on error.
+ @param str String input.
+ @return The `count` of characters read, and a `status` code.
+*/
+EXESS_API
+ExessResult
+exess_read_uint(uint32_t* EXESS_NONNULL out, const char* EXESS_NONNULL str);
+
+/**
+ Write a canonical xsd:unsignedInt string.
+
+ @param value Value to write.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return The `count` of characters in the output, and `status`
+ #EXESS_SUCCESS, or #EXESS_NO_SPACE if the buffer is too small.
+*/
+EXESS_API
+ExessResult
+exess_write_uint(uint32_t value, size_t buf_size, char* EXESS_NULLABLE buf);
+
+/**
+ @}
+ @defgroup exess_ushort Unsigned Short
+
+ An xsd:unsignedShort is an unsigned 16-bit integer.
+
+ Values range from 0 to 65535 inclusive.
+
+ The lexical form is the same as xsd:unsignedLong, the only difference is
+ that the value space of xsd:unsignedShort is smaller. See @ref exess_ulong
+ for details.
+
+ @{
+*/
+
+/// The maximum length of a canonical xsd:unsignedShort string, 5
+#define EXESS_MAX_USHORT_LENGTH 5
+
+/**
+ Read an xsd:unsignedShort string after any leading whitespace.
+
+ @param out Set to the parsed value, or zero on error.
+ @param str String input.
+ @return The `count` of characters read, and a `status` code.
+*/
+EXESS_API
+ExessResult
+exess_read_ushort(uint16_t* EXESS_NONNULL out, const char* EXESS_NONNULL str);
+
+/**
+ Write a canonical xsd:unsignedShort string.
+
+ @param value Value to write.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return The `count` of characters in the output, and `status`
+ #EXESS_SUCCESS, or #EXESS_NO_SPACE if the buffer is too small.
+*/
+EXESS_API
+ExessResult
+exess_write_ushort(uint16_t value, size_t buf_size, char* EXESS_NULLABLE buf);
+
+/**
+ @}
+ @defgroup exess_ubyte Unsigned Byte
+
+ An xsd:unsignedByte is an unsigned 8-bit integer. Values range from 0 to
+ 255 inclusive.
+
+ The lexical form is the same as xsd:unsignedLong, the only difference is
+ that the value space of xsd:unsignedByte is smaller. See @ref exess_ulong
+ for details.
+
+ @{
+*/
+
+/// The maximum length of a canonical xsd:unsignedByte string, 3
+#define EXESS_MAX_UBYTE_LENGTH 3
+
+/**
+ Read an xsd:unsignedByte string after any leading whitespace.
+
+ @param out Set to the parsed value, or zero on error.
+ @param str String input.
+ @return The `count` of characters read, and a `status` code.
+*/
+EXESS_API
+ExessResult
+exess_read_ubyte(uint8_t* EXESS_NONNULL out, const char* EXESS_NONNULL str);
+
+/**
+ Write a canonical xsd:unsignedByte string.
+
+ @param value Value to write.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return The `count` of characters in the output, and `status`
+ #EXESS_SUCCESS, or #EXESS_NO_SPACE if the buffer is too small.
+*/
+EXESS_API
+ExessResult
+exess_write_ubyte(uint8_t value, size_t buf_size, char* EXESS_NULLABLE buf);
+
+/**
+ @}
+ @}
+ @defgroup exess_time_and_date Time and Date
+ Datatypes for dates, times, and durations of time.
+ @{
+*/
+
+/**
+ @defgroup exess_duration Duration
+
+ An xsd:duration is a positive or negative duration of time, written in ISO
+ 8601 format like "PnYnMnDTnHnMnS" where each "n" is a number and fields may
+ be omitted if they are zero.
+
+ All numbers must be integers, except for seconds which may be a decimal. If
+ seconds is a decimal, then at least one digit must follow the decimal point.
+ A negative duration is written with "-" as the first character, for example
+ "-P60D".
+
+ Canonical form omits all zero fields and writes no leading or trailing
+ zeros, except for the zero duration which is written "P0Y", for example
+ "P1DT2H", "PT30M", or "PT4.5S".
+
+ Non-canonical form allows zero fields, leading zeros, and for seconds to be
+ written as a decimal even if it is integer, for example "P06D", "PT7.0S", or
+ "P0Y0M01DT06H00M00S".
+
+ @{
+*/
+
+/// The maximum length of an xsd:duration string from exess_write_duration(), 41
+#define EXESS_MAX_DURATION_LENGTH 41
+
+/**
+ A duration of time (xsd:duration value).
+
+ To save space and to simplify arithmetic, this representation only stores
+ two values: integer months, and decimal seconds (to nanosecond precision).
+ These values are converted to and from the other fields during writing and
+ reading. Years and months are stored as months, and days, hours, minutes,
+ and seconds are stored as seconds.
+
+ The sign of all members must match, so a negative duration has all
+ non-positive members, and a positive duration has all non-negative members.
+*/
+typedef struct {
+ int32_t months;
+ int32_t seconds;
+ int32_t nanoseconds;
+} ExessDuration;
+
+/**
+ Read an xsd:duration string after any leading whitespace.
+
+ @param out Set to the parsed value, or zero on error.
+ @param str String input.
+ @return The `count` of characters read, and a `status` code.
+*/
+EXESS_API
+ExessResult
+exess_read_duration(ExessDuration* EXESS_NONNULL out,
+ const char* EXESS_NONNULL str);
+
+/**
+ Write a canonical xsd:duration string.
+
+ @param value Value to write.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return #EXESS_SUCCESS on success, #EXESS_NO_SPACE if the buffer is too
+ small, or #EXESS_BAD_VALUE if the value is invalid.
+*/
+EXESS_API
+ExessResult
+exess_write_duration(ExessDuration value,
+ size_t buf_size,
+ char* EXESS_NULLABLE buf);
+/**
+ @}
+ @defgroup exess_datetime Datetime
+
+ An xsd:datetime is a date and time in either UTC or local time.
+
+ Strings have the form YYYY-MM-DDTHH:MM:SS with at least 4 year digits
+ (negative or positive), and all other fields positive two-digit integers
+ except seconds which may be a decimal, for example "2001-02-03T12:13:14.56".
+ Nanosecond precision is supported.
+
+ A local datetime has no suffix, a datetime with a time zone is always in
+ UTC, and is written with a "Z" suffix, for example 2001-02-03T12:13:14Z.
+
+ Canonical form only includes a decimal point if the number of seconds is not
+ an integer.
+
+ This implementation supports up to nanosecond resolution.
+
+ @{
+*/
+
+/// The maximum length of an xsd:dateTime string from exess_write_datetime(), 32
+#define EXESS_MAX_DATETIME_LENGTH 32
+
+/**
+ A date and time (xsd:dateTime value).
+
+ This representation follows the syntax, except the UTC flag is stored
+ between the date and time for more efficient packing.
+*/
+typedef struct {
+ int16_t year; ///< Year: any positive or negative value
+ uint8_t month; ///< Month: [1, 12]
+ uint8_t day; ///< Day: [1, 31]
+ uint8_t is_utc; ///< True if this is UTC (not local) time
+ uint8_t hour; ///< Hour: [0, 23]
+ uint8_t minute; ///< Minute: [0, 59]
+ uint8_t second; ///< Second: [0, 59]
+ uint32_t nanosecond; ///< Nanosecond: [0, 999999999]
+} ExessDateTime;
+
+/**
+ Add a duration to a datetime.
+
+ This advances or rewinds the datetime by the given duration, depending on
+ whether the duration is positive or negative.
+
+ If underflow or overflow occur, then this will return an infinite value. A
+ positive infinity has all fields at maximum, and a negative infinity has all
+ fields at minimum, except `is_utc` which is preserved from the input (so
+ infinities are comparable with the values they came from). Since 0 and 255
+ are never valid months, these can be tested for by checking if the year and
+ month are `INT16_MIN` and 0, or `INT16_MAX` and `INT8_MAX`.
+
+ @return `s + d`, or an infinite past or infinite future if underflow or
+ overflow occurs.
+*/
+EXESS_CONST_API
+ExessDateTime
+exess_add_datetime_duration(ExessDateTime s, ExessDuration d);
+
+/**
+ Read an xsd:dateTime string after any leading whitespace.
+
+ @param out Set to the parsed value, or zero on error.
+ @param str String input.
+ @return The `count` of characters read, and a `status` code.
+*/
+EXESS_API
+ExessResult
+exess_read_datetime(ExessDateTime* EXESS_NONNULL out,
+ const char* EXESS_NONNULL str);
+
+/**
+ Write a canonical xsd:datetime string.
+
+ @param value Value to write.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return #EXESS_SUCCESS on success, #EXESS_NO_SPACE if the buffer is too
+ small, or #EXESS_BAD_VALUE if the value is invalid.
+*/
+EXESS_API
+ExessResult
+exess_write_datetime(ExessDateTime value,
+ size_t buf_size,
+ char* EXESS_NULLABLE buf);
+
+/**
+ @}
+ @defgroup exess_timezone Timezones
+
+ Date and time values can have a timezone qualifier suffix. Note that
+ timezone is not a datatype, one only exists as a part of another value.
+
+ Canonical form starts with a sign, followed by two-digit hour and minute
+ offsets separated by a colon, like "-06:00" and "+02:30". The zero offset,
+ UTC, is written "Z".
+
+ Non-canonical form also allows writing UTC as "-00:00" or "+00:00".
+
+ This implementation only supports a resolution of 15 minutes, that is, only
+ offsets at 0, 15, 30, and 45 minutes within an hour.
+
+ @{
+*/
+
+/// A time zone offset for a date or time value
+typedef struct {
+ int8_t quarter_hours; ///< Offset in quarter hours: [-56, 56]
+} ExessTimezone;
+
+/// Sentinel value for local time, `INT8_MAX`
+#define EXESS_LOCAL INT8_MAX
+
+/**
+ @}
+ @defgroup exess_date Date
+ An xsd:date is a year, month, and day, with optional timezone.
+ @{
+*/
+
+/// The maximum length of an xsd:date string from exess_write_date(), 18
+#define EXESS_MAX_DATE_LENGTH 18
+
+/// Date (xsd:date)
+typedef struct {
+ int16_t year;
+ uint8_t month;
+ uint8_t day;
+ ExessTimezone zone;
+} ExessDate;
+
+/**
+ Read an xsd:date string after any leading whitespace.
+
+ @param out Set to the parsed value, or zero on error.
+ @param str String input.
+ @return The `count` of characters read, and a `status` code.
+*/
+EXESS_API
+ExessResult
+exess_read_date(ExessDate* EXESS_NONNULL out, const char* EXESS_NONNULL str);
+
+/**
+ Write a canonical xsd:date string.
+
+ The output is always in canonical form, like `2001-04-12` or
+ `-2001-10-26+02:00`.
+
+ @param value Value to write.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return #EXESS_SUCCESS on success, #EXESS_NO_SPACE if the buffer is too
+ small, or #EXESS_BAD_VALUE if the value is invalid.
+*/
+EXESS_API
+ExessResult
+exess_write_date(ExessDate value, size_t buf_size, char* EXESS_NULLABLE buf);
+
+/**
+ @}
+ @defgroup exess_time Time
+
+ An xsd:time is a time of day, with optional timezone.
+
+ @{
+*/
+
+/// The maximum length of an xsd:time string from exess_write_time(), 24
+#define EXESS_MAX_TIME_LENGTH 24
+
+/// Time (xsd:time)
+typedef struct {
+ ExessTimezone zone; ///< Time zone
+ uint8_t hour; ///< Hour: [0, 23]
+ uint8_t minute; ///< Minute: [0, 59]
+ uint8_t second; ///< Second: [0, 59]
+ uint32_t nanosecond; ///< Nanosecond: [0, 999999999]
+} ExessTime;
+
+/**
+ Read an xsd:time string after any leading whitespace.
+
+ @param out Set to the parsed value, or zero on error.
+ @param str String input.
+ @return The `count` of characters read, and a `status` code.
+*/
+EXESS_API
+ExessResult
+exess_read_time(ExessTime* EXESS_NONNULL out, const char* EXESS_NONNULL str);
+
+/**
+ Write a canonical xsd:time string.
+
+ The output is always in canonical form, like "12:15" or "02:00Z".
+
+ @param value Value to write.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return #EXESS_SUCCESS on success, #EXESS_NO_SPACE if the buffer is too
+ small, or #EXESS_BAD_VALUE if the value is invalid.
+*/
+EXESS_API
+ExessResult
+exess_write_time(ExessTime value, size_t buf_size, char* EXESS_NULLABLE buf);
+
+/**
+ @}
+ @}
+ @defgroup exess_binary Binary
+ Datatypes for arbitrary binary data.
+ @{
+*/
+
+typedef struct {
+ size_t size;
+ void* EXESS_NULLABLE data;
+} ExessBlob;
+
+/**
+ @defgroup exess_base64 Base64
+ An xsd:base64Binary is arbitrary binary data in base64 encoding.
+ @{
+*/
+
+/**
+ Return the maximum number of bytes required to decode `length` bytes of
+ base64.
+
+ The returned value is an upper bound which is only exact for canonical
+ strings.
+
+ @param length The number of input (text) bytes to decode.
+ @return The size of a decoded value in bytes.
+*/
+EXESS_CONST_API
+size_t
+exess_base64_decoded_size(size_t length);
+
+/**
+ Read a binary value from a base64 string.
+
+ Canonical syntax is a multiple of 4 base64 characters, with either 1 or 2
+ trailing "=" characters as necessary, like "Zm9vYg==", with no whitespace.
+ All whitespace is skipped when reading.
+
+ The caller must allocate a large enough buffer to read the value, otherwise
+ an #EXESS_NO_SPACE error will be returned. The required space can be
+ calculated with exess_base64_decoded_size().
+
+ When this is called, the output blob must have the size of the available
+ buffer in bytes, and a pointer to the buffer. On return, the size will be
+ set to the exact size of the decoded data, which may be smaller than the
+ initial available size. Only these first bytes are written, the rest of the
+ buffer is not modified.
+
+ @param out The blob to set to the decoded binary data.
+ @param str String to parse.
+ @return The `count` of characters read, and a `status` code.
+*/
+EXESS_API
+ExessResult
+exess_read_base64(ExessBlob* EXESS_NONNULL out, const char* EXESS_NONNULL str);
+
+/**
+ Write a canonical xsd:base64Binary string.
+
+ The data is always written in canonical form, as a multiple of 4 characters
+ with no whitespace and 1 or 2 trailing "=" characters as padding if
+ necessary.
+
+ @param data_size The size of `data` in bytes.
+ @param data Data to write to a string.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return The `count` of characters in the output, and `status`
+ #EXESS_SUCCESS, or #EXESS_NO_SPACE if the buffer is too small.
+*/
+EXESS_API
+ExessResult
+exess_write_base64(size_t data_size,
+ const void* EXESS_NONNULL data,
+ size_t buf_size,
+ char* EXESS_NULLABLE buf);
+
+/**
+ @}
+ @defgroup exess_hex Hex
+ An xsd:hexBinary is arbitrary binary data in hexadecimal encoding.
+ @{
+*/
+
+/**
+ Return the maximum number of bytes required to decode `length` bytes of hex.
+
+ The returned value is an upper bound which is only exact for canonical
+ strings.
+
+ @param length The number of input (text) bytes to decode.
+ @return The size of a decoded value in bytes.
+*/
+EXESS_CONST_API
+size_t
+exess_hex_decoded_size(size_t length);
+
+/**
+ Read a binary value from a hex string.
+
+ Canonical syntax is an even number of uppercase hexadecimal digits with no
+ whitespace, like "666F6F". Lowercase hexadecimal is also supported, and all
+ whitespace is skipped when reading.
+
+ The caller must allocate a large enough buffer to read the value, otherwise
+ an #EXESS_NO_SPACE error will be returned. The required space can be
+ calculated with exess_hex_decoded_size().
+
+ When this is called, the output blob must have the size of the available
+ buffer in bytes, and a pointer to the buffer. On return, the size will be
+ set to the exact size of the decoded data, which may be smaller than the
+ initial available size. Only these first bytes are written, the rest of the
+ buffer is not modified.
+
+ @param out The blob to set to the decoded binary data.
+ @param str String to parse.
+ @return The `count` of characters read, and a `status` code.
+*/
+EXESS_API
+ExessResult
+exess_read_hex(ExessBlob* EXESS_NONNULL out, const char* EXESS_NONNULL str);
+
+/**
+ Write a canonical xsd:hexBinary string.
+
+ The data is always written in canonical form, as an even number of uppercase
+ hexadecimal digits with no whitespace.
+
+ @param data_size The size of `data` in bytes.
+ @param data Data to write to a string.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return The `count` of characters in the output, and `status`
+ #EXESS_SUCCESS, or #EXESS_NO_SPACE if the buffer is too small.
+*/
+EXESS_API
+ExessResult
+exess_write_hex(size_t data_size,
+ const void* EXESS_NONNULL data,
+ size_t buf_size,
+ char* EXESS_NULLABLE buf);
+
+/**
+ @}
+ @}
+ @defgroup exess_datatypes Datatypes
+ Runtime integer tags for supported datatypes with conversion to/from URIs.
+ @{
+*/
+
+typedef enum {
+ EXESS_NOTHING, ///< Sentinel for unknown datatypes or errors
+ EXESS_BOOLEAN, ///< xsd:boolean (see @ref exess_boolean)
+ EXESS_DECIMAL, ///< xsd:decimal (see @ref exess_decimal)
+ EXESS_DOUBLE, ///< xsd:double (see @ref exess_double)
+ EXESS_FLOAT, ///< xsd:float (see @ref exess_float)
+ EXESS_INTEGER, ///< xsd:integer (see @ref exess_long)
+ EXESS_NON_POSITIVE_INTEGER, ///< xsd:nonPositiveInteger (see @ref exess_long)
+ EXESS_NEGATIVE_INTEGER, ///< xsd:negativeInteger (see @ref exess_long)
+ EXESS_LONG, ///< xsd:long (see @ref exess_long)
+ EXESS_INT, ///< xsd:integer (see @ref exess_int)
+ EXESS_SHORT, ///< xsd:short (see @ref exess_short)
+ EXESS_BYTE, ///< xsd:byte (see @ref exess_byte)
+ EXESS_NON_NEGATIVE_INTEGER, ///< xsd:nonNegativeInteger (see @ref exess_ulong)
+ EXESS_ULONG, ///< xsd:unsignedLong (see @ref exess_ulong)
+ EXESS_UINT, ///< xsd:unsignedInt (see @ref exess_uint)
+ EXESS_USHORT, ///< xsd:unsignedShort (see @ref exess_ushort)
+ EXESS_UBYTE, ///< xsd:unsignedByte (see @ref exess_ubyte)
+ EXESS_POSITIVE_INTEGER, ///< xsd:positiveInteger (see @ref exess_ulong)
+ EXESS_DURATION, ///< xsd:duration (see @ref exess_duration)
+ EXESS_DATETIME, ///< xsd:dateTime (see @ref exess_datetime)
+ EXESS_TIME, ///< xsd:time (see @ref exess_time)
+ EXESS_DATE, ///< xsd:date (see @ref exess_date)
+ EXESS_HEX, ///< xsd:hexBinary (see @ref exess_hex)
+ EXESS_BASE64, ///< xsd:base64Binary (see @ref exess_base64)
+} ExessDatatype;
+
+/**
+ Return the URI for a supported datatype.
+
+ This only returns URIs that start with
+ `http://www.w3.org/2001/XMLSchema#`.
+
+ @param datatype Datatype tag.
+ @return The URI of the datatype, or null for #EXESS_NOTHING.
+*/
+EXESS_CONST_API
+const char* EXESS_NULLABLE
+exess_datatype_uri(ExessDatatype datatype);
+
+/**
+ Return the datatype tag for a datatype URI.
+
+ @return A datatype tag, or #EXESS_NOTHING if the URI is not a supported
+ datatype.
+*/
+EXESS_PURE_API
+ExessDatatype
+exess_datatype_from_uri(const char* EXESS_NONNULL uri);
+
+/**
+ Return whether a datatype has an upper bound on value sizes.
+
+ This returns true for all datatypes except #EXESS_DECIMAL, #EXESS_INTEGER
+ and its half-bounded subtypes #EXESS_NON_POSITIVE_INTEGER,
+ #EXESS_NEGATIVE_INTEGER, #EXESS_NON_NEGATIVE_INTEGER, and
+ #EXESS_POSITIVE_INTEGER, and the binary types #EXESS_HEX and #EXESS_BASE64.
+
+ For bounded datatypes, the maximum length of the string representation is
+ available in `exess_max_lengths` array, or as static constants like
+ #EXESS_MAX_INT_LENGTH.
+
+ @return True if values of the given datatype have a maximum size.
+*/
+EXESS_CONST_API
+bool
+exess_datatype_is_bounded(ExessDatatype datatype);
+
+/**
+ @}
+ @defgroup exess_variant Variant
+ An ExessVariant is a tagged union that can hold any supported datatype.
+ @{
+*/
+
+typedef union {
+ ExessStatus as_status;
+ ExessBlob as_blob;
+ bool as_bool;
+ double as_double;
+ float as_float;
+ int64_t as_long;
+ int32_t as_int;
+ int16_t as_short;
+ int8_t as_byte;
+ uint64_t as_ulong;
+ uint32_t as_uint;
+ uint16_t as_ushort;
+ uint8_t as_ubyte;
+ ExessDuration as_duration;
+ ExessDateTime as_datetime;
+ ExessTime as_time;
+ ExessDate as_date;
+} ExessValue;
+
+/**
+ Any supported value.
+
+ A variant is either nothing, or a value of a specific supported type. The
+ nothing variant has datatype #EXESS_NOTHING.
+
+ The value fields (everything other than datatype) are stored in an anonymous
+ union, only the field corresponding to the datatype is active. This should
+ not be used for type punning, use exess_coerce() for that instead.
+*/
+typedef struct {
+ ExessDatatype datatype;
+ ExessValue value;
+} ExessVariant;
+
+/**
+ @defgroup exess_variant_constructors Constructors
+ @{
+*/
+
+/// Return a nothing (null) variant, with a status code to signal errors
+EXESS_CONST_API
+ExessVariant
+exess_make_nothing(ExessStatus status);
+
+/// Return a boolean variant with the given value
+EXESS_CONST_API
+ExessVariant
+exess_make_boolean(bool value);
+
+/// Return a decimal variant with the given value
+EXESS_CONST_API
+ExessVariant
+exess_make_decimal(double value);
+
+/// Return a double variant with the given value
+EXESS_CONST_API
+ExessVariant
+exess_make_double(double value);
+
+/// Return a float variant with the given value
+EXESS_CONST_API
+ExessVariant
+exess_make_float(float value);
+
+/// Return a long variant with the given value
+EXESS_CONST_API
+ExessVariant
+exess_make_long(int64_t value);
+
+/// Return an int variant with the given value
+EXESS_CONST_API
+ExessVariant
+exess_make_int(int32_t value);
+
+/// Return a short variant with the given value
+EXESS_CONST_API
+ExessVariant
+exess_make_short(int16_t value);
+
+/// Return a byte variant with the given value
+EXESS_CONST_API
+ExessVariant
+exess_make_byte(int8_t value);
+
+/// Return a ulong variant with the given value
+EXESS_CONST_API
+ExessVariant
+exess_make_ulong(uint64_t value);
+
+/// Return a uint variant with the given value
+EXESS_CONST_API
+ExessVariant
+exess_make_uint(uint32_t value);
+
+/// Return a ushort variant with the given value
+EXESS_CONST_API
+ExessVariant
+exess_make_ushort(uint16_t value);
+
+/// Return a ubyte variant with the given value
+EXESS_CONST_API
+ExessVariant
+exess_make_ubyte(uint8_t value);
+
+/// Return a duration variant with the given value
+EXESS_CONST_API
+ExessVariant
+exess_make_duration(ExessDuration value);
+
+/// Return a datetime variant with the given value
+EXESS_CONST_API
+ExessVariant
+exess_make_datetime(ExessDateTime value);
+
+/// Return a time variant with the given value
+EXESS_CONST_API
+ExessVariant
+exess_make_time(ExessTime value);
+
+/// Return a date variant with the given value
+EXESS_CONST_API
+ExessVariant
+exess_make_date(ExessDate value);
+
+/// Return a hex binary variant with the given value
+EXESS_CONST_API
+ExessVariant
+exess_make_hex(ExessBlob value);
+
+/// Return a base64 binary variant with the given value
+EXESS_CONST_API
+ExessVariant
+exess_make_base64(ExessBlob value);
+
+/**
+ @}
+ @defgroup exess_variant_accessors Accessors
+ @{
+*/
+
+/**
+ Return the status of a variant.
+
+ This returns #EXESS_SUCCESS for any valid value, or the stored status for a
+ #EXESS_NOTHING variant.
+*/
+EXESS_PURE_API
+ExessStatus
+exess_get_status(const ExessVariant* EXESS_NONNULL variant);
+
+/// Return a pointer to the value if `variant` is a boolean, otherwise null
+EXESS_PURE_API
+const bool* EXESS_NULLABLE
+exess_get_boolean(const ExessVariant* EXESS_NONNULL variant);
+
+/**
+ Return a pointer to the value if `variant` is a double, otherwise null.
+
+ This will also access the value for #EXESS_DECIMAL.
+*/
+EXESS_PURE_API
+const double* EXESS_NULLABLE
+exess_get_double(const ExessVariant* EXESS_NONNULL variant);
+
+/// Return a pointer to the value if `variant` is a float, otherwise null
+EXESS_PURE_API
+const float* EXESS_NULLABLE
+exess_get_float(const ExessVariant* EXESS_NONNULL variant);
+
+/**
+ Return a pointer to the value if `variant` is a long, otherwise null.
+
+ This will also access the value for #EXESS_INTEGER,
+ #EXESS_NON_POSITIVE_INTEGER, and #EXESS_NEGATIVE_INTEGER.
+*/
+EXESS_PURE_API
+const int64_t* EXESS_NULLABLE
+exess_get_long(const ExessVariant* EXESS_NONNULL variant);
+
+/// Return a pointer to the value if `variant` is an int, otherwise null
+EXESS_PURE_API
+const int32_t* EXESS_NULLABLE
+exess_get_int(const ExessVariant* EXESS_NONNULL variant);
+
+/// Return a pointer to the value if `variant` is a short, otherwise null
+EXESS_PURE_API
+const int16_t* EXESS_NULLABLE
+exess_get_short(const ExessVariant* EXESS_NONNULL variant);
+
+/// Return a pointer to the value if `variant` is a byte, otherwise null
+EXESS_PURE_API
+const int8_t* EXESS_NULLABLE
+exess_get_byte(const ExessVariant* EXESS_NONNULL variant);
+
+/**
+ Return a pointer to the value if `variant` is a ulong, otherwise null.
+
+ This will also access the value for #EXESS_NON_NEGATIVE_INTEGER and
+ #EXESS_POSITIVE_INTEGER.
+*/
+EXESS_PURE_API
+const uint64_t* EXESS_NULLABLE
+exess_get_ulong(const ExessVariant* EXESS_NONNULL variant);
+
+/// Return a pointer to the value if `variant` is a uint, otherwise null
+EXESS_PURE_API
+const uint32_t* EXESS_NULLABLE
+exess_get_uint(const ExessVariant* EXESS_NONNULL variant);
+
+/// Return a pointer to the value if `variant` is a ushort, otherwise null
+EXESS_PURE_API
+const uint16_t* EXESS_NULLABLE
+exess_get_ushort(const ExessVariant* EXESS_NONNULL variant);
+
+/// Return a pointer to the value if `variant` is a ubyte, otherwise null
+EXESS_PURE_API
+const uint8_t* EXESS_NULLABLE
+exess_get_ubyte(const ExessVariant* EXESS_NONNULL variant);
+
+/// Return a pointer to the value if `variant` is a duration, otherwise null
+EXESS_PURE_API
+const ExessDuration* EXESS_NULLABLE
+exess_get_duration(const ExessVariant* EXESS_NONNULL variant);
+
+/// Return a pointer to the value if `variant` is a datetime, otherwise null
+EXESS_PURE_API
+const ExessDateTime* EXESS_NULLABLE
+exess_get_datetime(const ExessVariant* EXESS_NONNULL variant);
+
+/// Return a pointer to the value if `variant` is a time, otherwise null
+EXESS_PURE_API
+const ExessTime* EXESS_NULLABLE
+exess_get_time(const ExessVariant* EXESS_NONNULL variant);
+
+/// Return a pointer to the value if `variant` is a date, otherwise null
+EXESS_PURE_API
+const ExessDate* EXESS_NULLABLE
+exess_get_date(const ExessVariant* EXESS_NONNULL variant);
+
+/// Return a pointer to the value if `variant` is a date, otherwise null
+EXESS_PURE_API
+const ExessBlob* EXESS_NULLABLE
+exess_get_blob(const ExessVariant* EXESS_NONNULL variant);
+
+/**
+ @}
+*/
+
+//
+
+/**
+ Read any supported datatype from a string.
+
+ For reading binary blobs from base64 or hex, the `as_blob` field of `out`
+ must have the size of the available buffer in bytes, and a pointer to the
+ buffer. On return, the size will be set to the exact size of the decoded
+ data, which may be smaller than the initial available size. Only these
+ first bytes are written, the rest of the buffer is not modified.
+
+ @param out Set to the parsed value, or nothing on error.
+ @param datatype The datatype to read the string as.
+ @param str String input.
+ @return The `count` of characters read, and a `status` code.
+*/
+EXESS_API
+ExessResult
+exess_read_variant(ExessVariant* EXESS_NONNULL out,
+ ExessDatatype datatype,
+ const char* EXESS_NONNULL str);
+
+/**
+ Write any supported xsd datatype to a canonical string.
+
+ @param value Value to write.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return The `count` of characters in the output, and `status`
+ #EXESS_SUCCESS, or #EXESS_NO_SPACE if the buffer is too small.
+*/
+EXESS_API
+ExessResult
+exess_write_variant(ExessVariant value,
+ size_t buf_size,
+ char* EXESS_NULLABLE buf);
+
+/**
+ Rewrite a supported xsd datatype in canonical form.
+
+ @param value Input value string.
+ @param datatype Datatype of value.
+ @param buf_size The size of `buf` in bytes.
+ @param buf Output buffer, or null to only measure.
+
+ @return The `count` of characters in the output, and a `status` code. The
+ status may be an error from reading or writing, but the `count` always
+ refers to the number of characters written.
+*/
+EXESS_API
+ExessResult
+exess_write_canonical(const char* EXESS_NONNULL value,
+ ExessDatatype datatype,
+ size_t buf_size,
+ char* EXESS_NULLABLE buf);
+
+/**
+ @defgroup exess_coercion Datatype coercion
+
+ Values can be converted between some datatypes using exess_coerce(). This is
+ particularly useful for reducing the number of datatypes that the application
+ needs to explicitly handle.
+
+ @{
+*/
+
+/**
+ Coercion flags.
+
+ These values are ORed together to enable different kinds of lossy conversion
+ in exess_coerce().
+*/
+typedef enum {
+ /**
+ Only do lossless datatype coercions.
+
+ A lossless coercion is when the value has been perfectly preserved in the
+ target datatype, and coercing it back will result in the same value.
+
+ For some datatype combinations this will always be the case, for example
+ from short to long. For others it will depend on the value, for example
+ only the numbers 0 and 1 coerce to boolean without loss.
+ */
+ EXESS_LOSSLESS = 0u,
+
+ /**
+ Allow datatype coercions that reduce the precision of values.
+
+ This allows coercions that are lossy only in terms of precision, so the
+ resulting value is approximately equal to the original value.
+ Specifically, this allows coercing double to float.
+ */
+ EXESS_REDUCE_PRECISION = 1u << 0u,
+
+ /**
+ Allow datatype coercions that round to the nearest integer.
+
+ This allows coercing floating point numbers to integers by rounding to the
+ nearest integer, with halfway cases rounding towards even (the default
+ IEEE-754 rounding order).
+ */
+ EXESS_ROUND = 1u << 1u,
+
+ /**
+ Allow datatype coercions that truncate significant parts of values.
+
+ This allows coercions that lose data beyond simple precision loss.
+ Specifically, this allows coercing any number to boolean, datetime to
+ date, and datetime to time.
+ */
+ EXESS_TRUNCATE = 1u << 2u,
+} ExessCoercionFlag;
+
+/// Bitwise OR of #ExessCoercionFlag values
+typedef uint32_t ExessCoercionFlags;
+
+/**
+ Coerce a value to another datatype if possible.
+
+ @param value Value to coerce.
+
+ @param datatype Datatype to convert to.
+
+ @param coercions Enabled coercion flags. If this is #EXESS_LOSSLESS (zero),
+ then #EXESS_SUCCESS is only returned if the resulting value can be coerced
+ back to the original type without any loss of data. Otherwise, the lossy
+ coercions enabled by the set bits will be attempted.
+
+ @return #EXESS_SUCCESS on successful conversion, #EXESS_OUT_OF_RANGE if the
+ value is outside the range of the target type,
+ #EXESS_WOULD_REDUCE_PRECISION, #EXESS_WOULD_ROUND, or #EXESS_WOULD_TRUNCATE
+ if the required coercion is not enabled, or #EXESS_UNSUPPORTED if conversion
+ between the types is not supported at all.
+*/
+EXESS_API
+ExessVariant
+exess_coerce(ExessVariant value,
+ ExessDatatype datatype,
+ ExessCoercionFlags coercions);
+
+/**
+ @}
+ @}
+*/
+
+/**
+ The maximum length of the string representation of datatypes.
+
+ For datatypes that do not have such a bound, the value is zero.
+*/
+static const size_t exess_max_lengths[] = {
+ 0, // Unknown
+ EXESS_MAX_BOOLEAN_LENGTH,
+ 0, // decimal
+ EXESS_MAX_DOUBLE_LENGTH,
+ EXESS_MAX_FLOAT_LENGTH,
+ 0, // integer
+ 0, // nonPositiveInteger
+ 0, // negativeInteger
+ EXESS_MAX_LONG_LENGTH,
+ EXESS_MAX_INT_LENGTH,
+ EXESS_MAX_SHORT_LENGTH,
+ EXESS_MAX_BYTE_LENGTH,
+ 0, // nonNegativeInteger
+ EXESS_MAX_ULONG_LENGTH,
+ EXESS_MAX_UINT_LENGTH,
+ EXESS_MAX_USHORT_LENGTH,
+ EXESS_MAX_UBYTE_LENGTH,
+ 0, // positiveInteger
+ EXESS_MAX_DURATION_LENGTH,
+ EXESS_MAX_DATETIME_LENGTH,
+ EXESS_MAX_TIME_LENGTH,
+ EXESS_MAX_DATE_LENGTH,
+ 0, // hexBinary
+ 0, // base64Binary
+};
+
+/**
+ @}
+*/
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // EXESS_EXESS_H