Exess¶
Installation¶
This is a brief overview of building and installing from source on the command
line using meson. Building requires a relatively modern C compiler (GCC,
Clang, and MSVC are tested), meson, and its dependencies Python and (by
default) ninja. It can also generate projects for popular IDEs, see the
meson configure --backend
option for details.
Configuring¶
The build is configured with the setup command, which creates a new build
directory with the given name:
meson setup build
The environment variables CC and CC_LD, if set, give the C compiler and
linker during setup, but otherwise it’s best to configure via meson options.
After setup, enter the build directory (and stay):
cd build
From here, the build configuration can be inspected:
meson configure
Options can be set by passing C-style “define” options to configure:
meson configure -Dc_args="-march=native" -Dprefix="/opt/mypackage/"
Building¶
Once configured, the compile command will build everything, and the test
command will run all tests:
meson compile
meson test
Installing¶
A compiled project can be installed with the install command:
meson install
You may need to acquire root permissions to install to a system-wide prefix.
For packaging, the installation may be staged to a directory using the
DESTDIR environment variable or the meson install --destdir option:
DESTDIR=/tmp/mypackage/ meson install
meson install --destdir=/tmp/mypackage/
Description¶
Exess is a simple C library for reading and writing values as strings.
It provides portable locale-independent functions for converting common number,
date, time, and binary datatypes. Values are preserved exactly wherever
possible, so, for example, a float written to a string will be read back as
exactly the original value on any system.
The supported datatypes, defined by the XSD specification, are explicitly compatible with many standards (like XML, RDF, and ISO 8601), and most are incidentally compatible with many others (like C and JSON).
The library is straightforward to use and doesn’t depend on an allocator, any other blocking functions, the current locale, or any other shared mutable state, making it safe to use in almost any context.
Supported Datatypes¶
Most of the XSD datatypes are implemented, omitting some XML-specific and recurring Gregorian calendar datatypes:
boolean, like “false”, “true”, “0”, or “1”.decimal, like “1.234”.floatanddouble, like “4.2E1” or “4.2e1”.The unbounded integers
integer,nonPositiveInteger,negativeInteger,nonNegativeInteger, andnonPositiveInteger, like “56” or “-78”.The constrained integers
long,int,short,byte,unsignedLong,unsignedInt,unsignedShort, andunsignedByte, like “-90” or “12”.duration, like “P1Y2M3DT4H5M”, and its totally-ordered derivativesyearMonthDuration(like “P6Y7M”) anddayTimeDuration(like “P8DT9H”).dateTime, like “2001-01-30T14:30:45”, and its totally-ordered derivativedateTimeStamp(like “1929-10-24T09:30:00-04:00”).time, like “12:30:00.000000001”.date, like “2001-12-31”.hexBinary, like “EC5355”.base64Binary, like “Zm9vYmFy”.
Precision¶
Time-related datatypes have nanosecond precision.
Precision of decimals and unbounded integers is limited only by the provided buffer size. These arbitrary-precision values are opaque, but can be coerced to simpler datatypes for use.
Dependencies¶
Exess has no dependencies except for the C standard library, specifically the
functions ceil, ldexp, llrint, llrintf, memcmp, memcpy, memset,
nextafter, strcmp, strncmp, trunc, and truncf.
Usage¶
To use exess,
the compiler must be configured to add the versioned include directory to the include path,
and to link with the corresponding library.
The pkg-config package exess-0 describes the required options:
pkg-config --cflags --libs exess-0
The API can then be used by including exess/exess.h:
#include <exess/exess.h>
If package support isn’t available,
arguments like -I/usr/include/exess-0 -lexess-0 must be added to the compiler command manually.
Reading Values¶
Each supported type has a read function that takes a string to read, and a pointer to an output value.
It reads the value after skipping any leading whitespace,
then returns an ExessResult with a status code and the count of characters read.
For example:
int32_t v = 0;
ExessResult r = exess_read_int("1234", &v);
if (!r.status) {
printf("Read %zu bytes as %d\n", r.count, v);
}
If there was a syntax error,
the status gives the specific problem,
and the count gives the offset of the error.
Note that read functions may stop at unrecognized non-whitespace characters and return successfully. This allows values to be read directly from any syntactic context (for example, a quoted field in a document string), but requires the caller to check that the read ended where expected. Proper error detection requires checking the returned count as well, for example:
bool is_null_terminated_int(const char* string) {
int32_t v = 0;
ExessResult r = exess_read_int(string, &v);
return !r.status && !string[r.count];
}
Writing Values¶
The corresponding write function takes a value to write,
a buffer size in bytes, and a buffer to write to.
It returns an ExessResult,
with a status code and the count of characters written,
not including the trailing null byte.
char s[12] = {0};
ExessResult r = exess_write_int(1234, sizeof(s), s);
if (!r.status) {
printf("Write error: %s\n", exess_strerror(r.status));
}
Allocating Strings¶
Exess never allocates memory, the calling code is responsible for providing a large enough output buffer.
For datatypes with a bounded length,
the array exess_max_lengths,
indexed by the ExessDatatype,
contains the length of the longest possible string value of that datatype (at most 41),
or zero if the datatype is unbounded.
For larger values, the required length can be determined by passing a zero output size to the write function.
Then, EXESS_NO_SPACE is returned with the count of characters required.
This can be used to precisely allocate memory for the string,
taking care to allocate an extra byte for the null terminator.
For example:
ExessResult r = exess_write_int(1234, 0, NULL);
char* s = (char*)calloc(r.count + 1, 1);
r = exess_write_int(1234, r.count + 1, s);
Note that for some types, measuring the output can be about as expensive as actually writing the value. For example, it requires binary to decimal conversion for floating point numbers. So, it’s usually best to avoid measuring twice by writing immediately to a sufficiently large buffer, then copying the result to the final destination.
Generic Values¶
The fundamental read and write functions all have similar semantics but different type signatures. A generic API that works with opaque buffers is also provided, which can be used to read and write any supported datatype without explicitly handling each case.
Any value can be read with exess_read_value() and written with exess_write_value(),
which work similarly to their typed counterparts,
except they take a datatype, size, and pointer to a buffer rather than a value.
ExessDatatype enumerates all of the supported datatypes.
Unbounded Numbers¶
There are six unbounded number types:
decimal,
integer,
nonPositiveInteger,
negativeInteger,
nonNegativeInteger,
and positiveInteger.
Both exess_read_value() and exess_write_value() support reading and writing a subset of these types,
but “big” numbers aren’t supported.
Values are stored in the largest corresponding native type:
double, int64_t, or uint64_t.
If the value doesn’t fit,
then exess_read_value() will return an EXESS_OUT_OF_RANGE error.
Canonical Writing¶
Since values are usually written in canonical form,
strings can be converted to canonical form by first reading, then writing again.
If the parsed value itself isn’t required,
then exess_write_canonical() can be used to do this more efficiently.
For example, this will print 12:
char s[4] = {0};
ExessVariableResult r = {EXESS_SUCCESS, 0, 0};
r = exess_write_canonical(EXESS_INT, "+12", sizeof(s), s);
if (!r.status) {
printf("%s\n", s);
}
This is particularly useful for unbounded datatypes, since values are transformed one character at a time, avoiding value conversion, machine limits, and the need for a temporary value buffer.
Exess C API¶
Symbols¶
Basic preprocessor symbols and enumerations.
-
EXESS_XSD_URI¶
“http://www.w3.org/2001/XMLSchema#”
The base URI of XML Schema.
-
enum ExessOrder¶
The result of comparing two values.
This follows the usual strcmp() convention, but includes a distinction between comparable values that are strictly less/greater than one another, and incomparable values that are arbitrarily chosen to be less/greater than one another by this implementation.
-
enumerator EXESS_ORDER_STRICTLY_LESS¶
Comparable, first is lesser.
-
enumerator EXESS_ORDER_MAYBE_LESS¶
Incomparable, first may be lesser.
-
enumerator EXESS_ORDER_EQUAL¶
Equal values.
-
enumerator EXESS_ORDER_MAYBE_GREATER¶
Incomparable, first may be greater.
-
enumerator EXESS_ORDER_STRICTLY_GREATER¶
Comparable, first is greater.
-
enumerator EXESS_ORDER_STRICTLY_LESS¶
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.
-
enum ExessStatus¶
Status code to describe errors or other relevant situations.
-
enumerator EXESS_SUCCESS¶
Success.
-
enumerator EXESS_LOSS¶
Lossy operation.
-
enumerator EXESS_EXPECTED_BOOLEAN¶
Expected “false”, “true”, “0” or “1”.
-
enumerator EXESS_EXPECTED_INTEGER¶
Expected an integer value.
-
enumerator EXESS_EXPECTED_DURATION¶
Expected a duration starting with ‘P’.
-
enumerator EXESS_EXPECTED_SIGN¶
Expected ‘-’ or ‘+’.
-
enumerator EXESS_EXPECTED_DIGIT¶
Expected a digit.
-
enumerator EXESS_EXPECTED_ZERO¶
Expected ‘0’.
-
enumerator EXESS_EXPECTED_COLON¶
Expected ‘:’.
-
enumerator EXESS_EXPECTED_DASH¶
Expected ‘-‘.
-
enumerator EXESS_EXPECTED_TIME_SEP¶
Expected ‘T’.
-
enumerator EXESS_EXPECTED_TIME_TAG¶
Expected ‘H’, ‘M’, or ‘S’.
-
enumerator EXESS_EXPECTED_DATE_TAG¶
Expected ‘Y’, ‘M’, or ‘D’.
-
enumerator EXESS_EXPECTED_SECOND_TAG¶
Expected ‘S’.
-
enumerator EXESS_EXPECTED_HEX¶
Expected a hexadecimal character.
-
enumerator EXESS_EXPECTED_BASE64¶
Expected a base64 character.
-
enumerator EXESS_BAD_ORDER¶
Invalid field order.
-
enumerator EXESS_BAD_VALUE¶
Invalid value.
-
enumerator EXESS_OUT_OF_RANGE¶
Value out of range.
-
enumerator EXESS_NO_SPACE¶
Insufficient space.
-
enumerator EXESS_UNSUPPORTED¶
Unsupported value.
-
enumerator EXESS_SUCCESS¶
-
struct ExessResult¶
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.
-
ExessStatus status¶
Status code.
-
size_t count¶
Number of bytes read or written, excluding null.
-
ExessStatus status¶
-
struct ExessVariableResult¶
Result returned from a function that reads and writes.
This is like
ExessResultbut includes separate read and write counts. This allows the caller to know both how many bytes were read from the input and how many bytes were written to the output.-
ExessStatus status¶
Status code.
-
size_t read_count¶
Number of bytes read.
-
size_t write_count¶
Number of bytes written, excluding null.
-
ExessStatus status¶
-
const char *exess_strerror(ExessStatus status)¶
Return a string describing a status code in plain language.
The returned string is always one sentence, with an uppercase first character, and no trailing period.
Fixed Type API¶
Statically type-safe API for working with values of a known fixed type.
Numbers¶
decimal¶
A decimal is a decimal number of arbitrary precision, but this implementation only supports values that fit in a double.
Unlike double, decimal is written in numeric form, never in scientific notation. Special infinity and NaN values aren’t supported. Note that the decimal representation for some numbers is very long, so 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 doesn’t require a decimal point, like “+1”, “01”, “-.5”, “4.”, and “42”.
-
ExessVariableResult exess_read_decimal(const char *str, size_t out_size, void *out)¶
Read a
decimalstring after any leading whitespace.The high-precision value format is opaque to the user, but can be written or converted to other types.
- Parameters:
str – String to read.
out_size – Size of
outin bytes.out – Parsed value.
- Returns:
The
read_countof characters read,write_countof bytes written, and astatus. Thestatuswill beExessStatus.EXESS_LOSSif an approximate value was successfully read by dropping significant digits.
-
ExessResult exess_write_decimal(size_t value_size, const void *value, size_t buf_size, char *buf)¶
Write a canonical
decimalstring.- Parameters:
value_size – The size of
valuein bytes.value – Value to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
The
countof characters in the output, and astatus(ExessStatus.EXESS_SUCCESS, orExessStatus.EXESS_NO_SPACEif the buffer is too small).
integer¶
An integer is a decimal with no decimal point or fractional component.
-
ExessVariableResult exess_read_integer(const char *str, size_t out_size, void *out)¶
Read a
integerstring after any leading whitespace.The high-precision value format is opaque to the user, but can be written or converted to other types.
- Parameters:
str – String to read.
out_size – Size of
outin bytes.out – Parsed value.
- Returns:
The
read_countof characters read,write_countof bytes written, and astatus. Thestatuswill beExessStatus.EXESS_LOSSif an approximate value was successfully read by dropping significant digits.
-
ExessResult exess_write_integer(size_t value_size, const void *value, size_t buf_size, char *buf)¶
Write a canonical
integerstring.- Parameters:
value_size – The size of
valuein bytes.value – Value to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
The
countof characters in the output, and astatus(ExessStatus.EXESS_SUCCESS, orExessStatus.EXESS_NO_SPACEif the buffer is too small).
double¶
A 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 doesn’t require an exponent or decimal point, like “+1E3”, “1E+3”, “.5E3”, “4.2”, and “42”.
-
EXESS_MAX_DOUBLE_LENGTH¶
24U
The maximum length of a canonical
doublestring.
-
ExessResult exess_read_double(const char *str, double *out)¶
Read a
doublestring after any leading whitespace.Values too large (in absolute terms) to store will produce infinity, values too small will produce zero, and insignificant digits may be discarded. Note that these cases are considered successful since the value space of
doublehas limited precision by definition.- Parameters:
str – String to read.
out – Parsed value, or NaN on error.
- Returns:
The
countof characters read, and astatus.
-
ExessResult exess_write_double(double value, size_t buf_size, char *buf)¶
Write a canonical
doublestring.Any
doublevalue is supported. Reading the resulting string withexess_read_double()will produce exactlyvalue, except the extra bits in NaNs aren’t preserved.- Parameters:
value – Value to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
The
countof characters in the output, and astatus(ExessStatus.EXESS_SUCCESS, orExessStatus.EXESS_NO_SPACEif the buffer is too small).
float¶
A float is an IEEE-754 32-bit floating point number, written in scientific notation.
The lexical form is the same as double, but the value space has less precision.
-
EXESS_MAX_FLOAT_LENGTH¶
15U
The maximum length of a canonical
floatstring.
-
ExessResult exess_read_float(const char *str, float *out)¶
Read a
floatstring after any leading whitespace.Values too large (in absolute terms) to store will produce infinity, values too small will produce zero, and insignificant digits may be discarded. Note that these cases are considered successful since the value space of
floathas limited precision by definition.- Parameters:
str – String to read.
out – Parsed value, or NaN on error.
- Returns:
The
countof characters read, and astatus.
-
ExessResult exess_write_float(float value, size_t buf_size, char *buf)¶
Write a canonical
floatstring.Any
floatvalue is supported. Reading the resulting string withexess_read_float()will produce exactlyvalue, except the extra bits in NaNs aren’t preserved.- Parameters:
value – Value to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
The
countof characters in the output, and astatus(ExessStatus.EXESS_SUCCESS, orExessStatus.EXESS_NO_SPACEif the buffer is too small).
boolean¶
A boolean has only two possible values, canonically written as “false” and “true”.
The non-canonical forms “0” and “1” are also supported.
-
EXESS_MAX_BOOLEAN_LENGTH¶
5U
The maximum length of a canonical
booleanstring.
-
ExessResult exess_read_boolean(const char *str, bool *out)¶
Read a
booleanstring after any leading whitespace.- Parameters:
str – String to read.
out – Parsed value, or false on error.
- Returns:
The
countof characters read, and astatus.
-
ExessResult exess_write_boolean(bool value, size_t buf_size, char *buf)¶
Write a canonical
booleanstring.- Parameters:
value – Value to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
The
countof characters in the output, and astatus(ExessStatus.EXESS_SUCCESS, orExessStatus.EXESS_NO_SPACEif the buffer is too small).
long¶
A 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”.
-
EXESS_MAX_LONG_LENGTH¶
20U
The maximum length of a canonical
longstring.
-
ExessResult exess_read_long(const char *str, int64_t *out)¶
Read a
longstring after any leading whitespace.- Parameters:
str – String to read.
out – Parsed value, or zero on error.
- Returns:
The
countof characters read, and astatus.
-
ExessResult exess_write_long(int64_t value, size_t buf_size, char *buf)¶
Write a canonical
longstring.- Parameters:
value – Value to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
The
countof characters in the output, and astatus(ExessStatus.EXESS_SUCCESS, orExessStatus.EXESS_NO_SPACEif the buffer is too small).
int¶
An int is a signed 32-bit integer.
The lexical form is the same as long, but values range from -2147483648 to 2147483647 inclusive.
-
EXESS_MAX_INT_LENGTH¶
11U
The maximum length of a canonical
intstring.
-
ExessResult exess_read_int(const char *str, int32_t *out)¶
Read an
intstring after any leading whitespace.- Parameters:
str – String to read.
out – Parsed value, or zero on error.
- Returns:
The
countof characters read, and astatus.
-
ExessResult exess_write_int(int32_t value, size_t buf_size, char *buf)¶
Write a canonical
intstring.- Parameters:
value – Value to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
The
countof characters in the output, and astatus(ExessStatus.EXESS_SUCCESS, orExessStatus.EXESS_NO_SPACEif the buffer is too small).
short¶
A short is a signed 16-bit integer.
The lexical form is the same as long, but values range from -32768 to 32767 inclusive.
-
EXESS_MAX_SHORT_LENGTH¶
6U
The maximum length of a canonical
shortstring.
-
ExessResult exess_read_short(const char *str, int16_t *out)¶
Read a
shortstring after any leading whitespace.- Parameters:
str – String to read.
out – Parsed value, or zero on error.
- Returns:
The
countof characters read, and astatus.
-
ExessResult exess_write_short(int16_t value, size_t buf_size, char *buf)¶
Write a canonical
shortstring.- Parameters:
value – Value to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
The
countof characters in the output, and astatus(ExessStatus.EXESS_SUCCESS, orExessStatus.EXESS_NO_SPACEif the buffer is too small).
byte¶
A byte is a signed 8-bit integer.
The lexical form is the same as long, but values range from -128 to 127 inclusive.
-
EXESS_MAX_BYTE_LENGTH¶
4U
The maximum length of a canonical
bytestring.
-
ExessResult exess_read_byte(const char *str, int8_t *out)¶
Read a
bytestring after any leading whitespace.- Parameters:
str – String to read.
out – Parsed value, or zero on error.
- Returns:
The
countof characters read, and astatus.
-
ExessResult exess_write_byte(int8_t value, size_t buf_size, char *buf)¶
Write a canonical
bytestring.- Parameters:
value – Value to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
The
countof characters in the output, and astatus(ExessStatus.EXESS_SUCCESS, orExessStatus.EXESS_NO_SPACEif the buffer is too small).
unsignedLong¶
An 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 a leading “+”, a leading “-” for zero, and any number of leading zeros, like “01” and “0001234”.
-
EXESS_MAX_ULONG_LENGTH¶
20U
The maximum length of a canonical
unsignedLongstring.
-
ExessResult exess_read_ulong(const char *str, uint64_t *out)¶
Read an
unsignedLongstring after any leading whitespace.- Parameters:
str – String to read.
out – Parsed value, or zero on error.
- Returns:
The
countof characters read, and astatus.
-
ExessResult exess_write_ulong(uint64_t value, size_t buf_size, char *buf)¶
Write a canonical
unsignedLongstring.- Parameters:
value – Value to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
The
countof characters in the output, and astatus(ExessStatus.EXESS_SUCCESS, orExessStatus.EXESS_NO_SPACEif the buffer is too small).
unsignedInt¶
An unsignedInt is an unsigned 32-bit integer.
The lexical form is the same as unsignedLong, but values range from 0 to 4294967295 inclusive.
-
EXESS_MAX_UINT_LENGTH¶
10U
The maximum length of a canonical
unsignedIntstring.
-
ExessResult exess_read_uint(const char *str, uint32_t *out)¶
Read an
unsignedIntstring after any leading whitespace.- Parameters:
str – String to read.
out – Parsed value, or zero on error.
- Returns:
The
countof characters read, and astatus.
-
ExessResult exess_write_uint(uint32_t value, size_t buf_size, char *buf)¶
Write a canonical
unsignedIntstring.- Parameters:
value – Value to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
The
countof characters in the output, and astatus(ExessStatus.EXESS_SUCCESS, orExessStatus.EXESS_NO_SPACEif the buffer is too small).
unsignedShort¶
An unsignedShort is an unsigned 16-bit integer.
The lexical form is the same as unsignedLong, but values range from 0 to 65535 inclusive.
-
EXESS_MAX_USHORT_LENGTH¶
5U
The maximum length of a canonical
unsignedShortstring.
-
ExessResult exess_read_ushort(const char *str, uint16_t *out)¶
Read an
unsignedShortstring after any leading whitespace.- Parameters:
str – String to read.
out – Parsed value, or zero on error.
- Returns:
The
countof characters read, and astatus.
-
ExessResult exess_write_ushort(uint16_t value, size_t buf_size, char *buf)¶
Write a canonical
unsignedShortstring.- Parameters:
value – Value to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
The
countof characters in the output, and astatus(ExessStatus.EXESS_SUCCESS, orExessStatus.EXESS_NO_SPACEif the buffer is too small).
unsignedByte¶
An unsignedByte is an unsigned 8-bit integer.
The lexical form is the same as unsignedLong, but values range from 0 to 255 inclusive.
-
EXESS_MAX_UBYTE_LENGTH¶
3U
The maximum length of a canonical
unsignedBytestring.
-
ExessResult exess_read_ubyte(const char *str, uint8_t *out)¶
Read an
unsignedBytestring after any leading whitespace.- Parameters:
str – String to read.
out – Parsed value, or zero on error.
- Returns:
The
countof characters read, and astatus.
-
ExessResult exess_write_ubyte(uint8_t value, size_t buf_size, char *buf)¶
Write a canonical
unsignedBytestring.- Parameters:
value – Value to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
The
countof characters in the output, and astatus(ExessStatus.EXESS_SUCCESS, orExessStatus.EXESS_NO_SPACEif the buffer is too small).
Time and Date¶
Timezone Offsets¶
Some time and date values can have a timezone qualifier suffix.
A timezone isn’t a datatype unto itself, but 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.
-
EXESS_TIMEZONE_LOCAL¶
((
ExessTimezone)127U)Sentinel value for local time.
-
EXESS_TIMEZONE_UTC¶
((
ExessTimezone)0U)Sentinel value for UTC time.
-
typedef int8_t ExessTimezone¶
A time zone offset in quarter hours.
This is stored in a single byte for compactness in other structures. Valid values are from -56 to 56 inclusive.
-
ExessTimezone exess_timezone(int8_t hours, int8_t minutes)¶
Construct a time zone offset from hours and minutes.
This is a convenience constructor that handles the conversion from hours and minutes to the quarter-hour offset used in exess. The sign of both values must be the same. Hours can be from -14 to 14 inclusive, and minutes can only be -45, -30, -15, 0, 15, 30, or 45.
- Returns:
A time zone offset in quarter hours, or
EXESS_TIMEZONE_LOCALif the parameters are invalid or not supported.
duration¶
A 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’s integer, for example “P06D”, “PT7.0S”, or “P0Y0M01DT06H00M00S”.
-
EXESS_MAX_DURATION_LENGTH¶
41U
The maximum length of a
durationstring fromexess_write_duration()
-
struct ExessDuration¶
Duration of time.
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.
-
int32_t months¶
Number of months.
-
int32_t seconds¶
Number of seconds.
-
int32_t nanoseconds¶
Number of nanoseconds.
-
int32_t months¶
-
ExessOrder exess_compare_duration(ExessDuration lhs, ExessDuration rhs)¶
Compare two durations.
Loosely speaking, a duration is considered less than another if it’s a shorter duration of time. However, note that two durations may not be comparable, since the relation between fields, such as the number of days in a month, varies at different times. Strictly speaking, two durations are comparable if adding them to any
dateTimewould produce results with the same ordering.The
ExessDurationrepresentation condenses all fields into two values: months and seconds. When the two values are incomparable, a month is considered greater than any number of seconds.Values with only months, or only seconds, are always comparable. Applications are encouraged to always use such values and never carry seconds into months or vice-versa.
- Returns:
Less than, equal to, or greater than zero if
lhsis less than, equal to, or greater thanrhs, respectively. Comparable and incomparable cases may also be distinguished, seeExessOrderfor details.
-
ExessResult exess_read_duration(const char *str, ExessDuration *out)¶
Read a
durationstring after any leading whitespace.- Parameters:
str – String to read.
out – Parsed value, or zero on error.
- Returns:
The
countof characters read, and astatus.
-
ExessResult exess_write_duration(ExessDuration value, size_t buf_size, char *buf)¶
Write a canonical
durationstring.- Parameters:
value – Value to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
ExessStatus.EXESS_SUCCESSon success,ExessStatus.EXESS_NO_SPACEif the buffer is too small, orExessStatus.EXESS_BAD_VALUEif the value is invalid.
dateTime¶
A dateTime is a date and time, with an optional timezone offset.
Strings have the form YYYY-MM-DDTHH:MM:SS with an optional timezone suffix, 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” or “2001-02-03T12:13:14.56-06:00”
Canonical form only includes a decimal point if the number of seconds isn’t an integer. This implementation supports up to nanosecond resolution. Midnight at the end of the day, like “1999-12-31T24:00:00” is a valid value, equivalent to the start of the next day, like “2000-01-01T00:00:00”, which is the canonical form.
-
EXESS_MAX_DATE_TIME_LENGTH¶
37U
The maximum length of a
dateTimestring fromexess_write_date_time()
-
struct ExessDateTime¶
Date and time.
This representation follows the syntax, except the timezone offset is stored between the date and time for more efficient packing.
-
int16_t year¶
Year: any positive or negative value.
-
uint8_t month¶
Month: [1, 12].
-
uint8_t day¶
Day: [1, 31].
-
ExessTimezone zone¶
Timezone offset in quarter hours.
-
uint8_t hour¶
Hour: [0, 24].
-
uint8_t minute¶
Minute: [0, 59].
-
uint8_t second¶
Second: [0, 59].
-
uint32_t nanosecond¶
Nanosecond: [0, 999999999].
-
int16_t year¶
-
ExessDateTime exess_add_date_time_duration(ExessDateTime s, ExessDuration d)¶
Add a duration to a dateTime.
This advances or rewinds 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
zonewhich 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 areINT16_MINand 0, orINT16_MAXandINT8_MAX.- Returns:
s + d, or an infinite past or infinite future if underflow or overflow occurs.
-
ExessOrder exess_compare_date_time(ExessDateTime lhs, ExessDateTime rhs)¶
Compare two dateTimes.
Note that local and zoned dateTimes times may not be comparable. When the values aren’t comparable, this function arbitrarily chooses the local time to be lesser.
- Returns:
Less than, equal to, or greater than zero if
lhsis less than, equal to, or greater thanrhs, respectively. Comparable and incomparable cases may also be distinguished, seeExessOrderfor details.
-
ExessDateTime exess_date_time_to_utc(ExessDateTime datetime)¶
Convert a dateTime to UTC.
- Returns:
The input converted to UTC if it has a timezone, otherwise the unmodified input.
-
ExessResult exess_read_date_time(const char *str, ExessDateTime *out)¶
Read a dateTime string after any leading whitespace.
- Parameters:
str – String to read.
out – Parsed value, or zero on error.
- Returns:
The
countof characters read, and astatus.
-
ExessResult exess_write_date_time(ExessDateTime value, size_t buf_size, char *buf)¶
Write a dateTime string.
The written string is in canonical form, except that midnight at the end of the day (24:00:00) is written as-is. Note that this differs from the other write functions, which always write canonical form.
- Parameters:
value – Value to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
ExessStatus.EXESS_SUCCESSon success,ExessStatus.EXESS_NO_SPACEif the buffer is too small, orExessStatus.EXESS_BAD_VALUEif the value is invalid.
date¶
A date is a year, month, and day, with an optional timezone offset.
Strings have the form YYYY-MM-DD with an optional timezone suffix, at least 4 year digits (negative or positive), and exactly 2 digits for both month and day. For example, “2001-02-03” or “2001-02-03-06:00”.
Canonical form has no leading zeros for the year.
-
EXESS_MAX_DATE_LENGTH¶
18U
The maximum length of a
datestring fromexess_write_date()
-
struct ExessDate¶
Date.
-
int16_t year¶
Year.
-
uint8_t month¶
Month: [1, 12].
-
uint8_t day¶
Day: [1, 31].
-
ExessTimezone zone¶
Timezone offset in quarter hours.
-
int16_t year¶
-
ExessOrder exess_compare_date(ExessDate lhs, ExessDate rhs)¶
Compare two dates.
Note that local and zoned dates may not be comparable. See
exess_compare_date_time()for details.- Returns:
Less than, equal to, or greater than zero if
lhsis less than, equal to, or greater thanrhs, respectively. Comparable and incomparable cases may also be distinguished, seeExessOrderfor details.
-
ExessResult exess_read_date(const char *str, ExessDate *out)¶
Read a
datestring after any leading whitespace.- Parameters:
str – String to read.
out – Parsed value, or zero on error.
- Returns:
The
countof characters read, and astatus.
-
ExessResult exess_write_date(ExessDate value, size_t buf_size, char *buf)¶
Write a canonical
datestring.The output is always in canonical form, like
2001-04-12or-2001-10-26+02:00.- Parameters:
value – Value to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
ExessStatus.EXESS_SUCCESSon success,ExessStatus.EXESS_NO_SPACEif the buffer is too small, orExessStatus.EXESS_BAD_VALUEif the value is invalid.
time¶
A time is a time of day, with an optional timezone offset.
Strings have the form HH:MM:SS with an optional timezone suffix, where seconds may be a decimal value, for example, “12:13:14”, “12:13:14.56”, or “12:13:14.56-07:00”.
Canonical form only includes a decimal point if the number of seconds isn’t an integer. This implementation supports up to nanosecond resolution. Note that midnight at the end of the day (like “24:00:00”) is a valid lexical form, but unlike dateTime, 24 isn’t a valid hour value. Such forms will be read as 00:00:00.
-
EXESS_MAX_TIME_LENGTH¶
24U
The maximum length of a
timestring fromexess_write_time()
-
struct ExessTime¶
Time.
-
ExessTimezone zone¶
Timezone offset in quarter hours.
-
uint8_t hour¶
Hour: [0, 23].
-
uint8_t minute¶
Minute: [0, 59].
-
uint8_t second¶
Second: [0, 59].
-
uint32_t nanosecond¶
Nanosecond: [0, 999999999].
-
ExessTimezone zone¶
-
ExessOrder exess_compare_time(ExessTime lhs, ExessTime rhs)¶
Compare two times.
Note that local and zoned times may not be comparable. See
exess_compare_date_time()for details.- Returns:
Less than, equal to, or greater than zero if
lhsis less than, equal to, or greater thanrhs, respectively. Comparable and incomparable cases may also be distinguished, seeExessOrderfor details.
-
ExessResult exess_read_time(const char *str, ExessTime *out)¶
Read a
timestring after any leading whitespace.- Parameters:
str – String to read.
out – Parsed value, or zero on error.
- Returns:
The
countof characters read, and astatus.
-
ExessResult exess_write_time(ExessTime value, size_t buf_size, char *buf)¶
Write a canonical
timestring.The output is always in canonical form, like “12:15” or “02:00Z”.
- Parameters:
value – Value to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
ExessStatus.EXESS_SUCCESSon success,ExessStatus.EXESS_NO_SPACEif the buffer is too small, orExessStatus.EXESS_BAD_VALUEif the value is invalid.
Binary¶
base64Binary¶
A base64Binary is arbitrary binary data in base64 encoding.
Strings consist of characters from the base64 alphabet ([0-9], [A-Z], [a-z], “+”, “/”, and “=”) with whitespace (tab, newline, carriage return, or space) allowed anywhere. Strings are padded to a multiple of four non-whitespace characters with with trailing “=” characters. For example, the base64 encoding of the string “data” is “ZGF0YQo=”.
Canonical form contains no whitespace.
-
size_t exess_decoded_base64_size(size_t length)¶
Return the maximum number of bytes required to decode
lengthbytes of base64.The returned value is an upper bound which is only exact for canonical strings.
- Parameters:
length – Number of input (text) bytes to decode.
- Returns:
The maximum size of a decoded value in bytes.
-
ExessVariableResult exess_read_base64(const char *str, size_t out_size, void *out)¶
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
ExessStatus.EXESS_NO_SPACEerror will be returned. The required space can be calculated withexess_decoded_base64_size().When this is called,
outmust point to a buffer of at leastout_sizebytes. The returned result contains the exact size of the decoded data, which may be smaller thanout_size. Only these first bytes are written, the rest of the buffer isn’t modified.- Parameters:
str – String to read.
out_size – Size of
outin bytes.out – Decoded binary data.
- Returns:
The
read_countof characters read,write_countof bytes written, and astatus.
-
ExessResult exess_write_base64(size_t data_size, const void *data, size_t buf_size, char *buf)¶
Write a canonical
base64Binarystring.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.
- Parameters:
data_size – Size of
datain bytes.data – Data to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
The
countof characters in the output, and astatus(ExessStatus.EXESS_SUCCESS, orExessStatus.EXESS_NO_SPACEif the buffer is too small).
hexBinary¶
A hexBinary is arbitrary binary data in hexadecimal encoding.
Strings consist of an even number of hexadecimal characters ([0-9], [A-F], and [a-f]). Note that, unlike base64Binary, whitespace between characters isn’t allowed.
-
size_t exess_decoded_hex_size(size_t length)¶
Return the maximum number of bytes required to decode
lengthbytes of hex.The returned value is an upper bound which is only exact for canonical strings.
- Parameters:
length – Number of input (text) bytes to decode.
- Returns:
The size of a decoded value in bytes.
-
ExessVariableResult exess_read_hex(const char *str, size_t out_size, void *out)¶
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
ExessStatus.EXESS_NO_SPACEerror will be returned. The required space can be calculated withexess_decoded_hex_size().When this is called,
outmust point to a buffer of at leastout_sizebytes. The returned result contains the exact size of the decoded data, which may be smaller thanout_size. Only these first bytes are written, the rest of the buffer isn’t modified.- Parameters:
str – String to parse.
out_size – Size of
outin bytes.out – Decoded binary data.
- Returns:
The
read_countof characters read,write_countof bytes written, and astatus.
-
ExessResult exess_write_hex(size_t data_size, const void *data, size_t buf_size, char *buf)¶
Write a canonical
hexBinarystring.The data is always written in canonical form, as an even number of uppercase hexadecimal digits with no whitespace.
- Parameters:
data_size – Size of
datain bytes.data – Data to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
The
countof characters in the output, and astatus(ExessStatus.EXESS_SUCCESS, orExessStatus.EXESS_NO_SPACEif the buffer is too small).
Dynamic Type API¶
API for working with general values of a type given at runtime.
Datatypes¶
Integer identifiers for datatypes, and utility functions to access their properties.
-
EXESS_MAX_DATATYPE¶
26U
The unsigned value of the largest
ExessDatatypeenumerator.
-
enum ExessDatatype¶
A supported datatype, or zero.
-
enumerator EXESS_NOTHING¶
Sentinel for unknown datatypes or errors.
-
enumerator EXESS_ULONG¶
-
enumerator EXESS_UINT¶
-
enumerator EXESS_USHORT¶
-
enumerator EXESS_UBYTE¶
-
enumerator EXESS_NON_POSITIVE_INTEGER¶
integer<= 0
-
enumerator EXESS_NEGATIVE_INTEGER¶
integer< 0
-
enumerator EXESS_NON_NEGATIVE_INTEGER¶
integer>= 0
-
enumerator EXESS_POSITIVE_INTEGER¶
integer> 0
-
enumerator EXESS_BASE64¶
-
enumerator EXESS_NOTHING¶
-
ExessDatatype exess_datatype_from_name(const char *name)¶
Return the datatype tag for a datatype name.
- Parameters:
name – The camel-case name of a supported datatype (its URI fragment), like “unsignedLong” or “dateTime”.
- Returns:
A datatype tag, or
ExessDatatype.EXESS_NOTHINGif no such datatype is known.
-
ExessDatatype exess_datatype_from_uri(const char *uri)¶
Return the datatype tag for a datatype URI.
- Parameters:
uri – Absolute URI for a supported datatype, like “http://www.w3.org/2001/XMLSchema#unsignedLong” or “http://www.w3.org/2001/XMLSchema#dateTime”.
- Returns:
A datatype tag, or
ExessDatatype.EXESS_NOTHINGif no such datatype is known.
-
const char *exess_datatype_name(ExessDatatype datatype)¶
Return the name of a datatype, or null.
This returns the suffix of the URI that would be returned by
exess_datatype_uri().- Parameters:
datatype – Datatype tag.
- Returns:
The name of the datatype, or null for
ExessDatatype.EXESS_NOTHING.
-
const char *exess_datatype_uri(ExessDatatype datatype)¶
Return the URI for a datatype, or null.
This only returns URIs that start with “http://www.w3.org/2001/XMLSchema#”.
- Parameters:
datatype – Datatype tag.
- Returns:
The URI of the datatype, or null for
ExessDatatype.EXESS_NOTHING.
-
uint8_t exess_max_lengths[EXESS_MAX_DATATYPE + 1U]¶
Array of maximum string lengths, indexed by datatype.
Each entry is the maximum length of a string with the indexed datatype, or zero if the datatype is unknown or unbounded. For example:
unsigned four_bytes = exess_max_lengths[EXESS_BYTE];
The unbounded types are
ExessDatatype.EXESS_DECIMAL,ExessDatatype.EXESS_INTEGERand its half-bounded subtypesExessDatatype.EXESS_NON_POSITIVE_INTEGER,ExessDatatype.EXESS_NEGATIVE_INTEGER,ExessDatatype.EXESS_NON_NEGATIVE_INTEGER, andExessDatatype.EXESS_POSITIVE_INTEGER, and the binary typesExessDatatype.EXESS_HEXandExessDatatype.EXESS_BASE64.
-
uint8_t exess_value_sizes[EXESS_MAX_DATATYPE + 1U]¶
Array of binary value sizes, indexed by datatype.
Each entry is the size of the binary representation of the indexed datatype (not a string length), or zero for values that can be arbitrarily large.
Generic Values¶
A generic interface for reading and writing binary values.
-
EXESS_ALIGN¶
Attribute to align a buffer for any supported value.
-
EXESS_MAX_FIXED_SIZE¶
12U
The maximum size of a supported fixed-size value in bytes.
-
ExessOrder exess_compare_value(ExessDatatype lhs_datatype, size_t lhs_size, const void *lhs_value, ExessDatatype rhs_datatype, size_t rhs_size, const void *rhs_value)¶
Compare two values.
- Returns:
Less than, equal to, or greater than zero if the left-hand value is less than, equal to, or greater than the right-hand value, respectively. Comparable and incomparable cases may also be distinguished, see
ExessOrderfor details.
-
ExessVariableResult exess_read_value(ExessDatatype datatype, const char *str, size_t out_size, void *out)¶
Read any supported datatype from a string.
Note that
outmust be suitably aligned for the datatype being read, it will be dereferenced directly as a pointer to the value type. A 64-bit aligned buffer will be suitably aligned for any datatype.- Parameters:
str – String to read.
datatype – Datatype to read the string as.
out_size – Size of
outin bytes.out – Parsed value on success.
- Returns:
The
read_countfromstr,write_counttoout(both in bytes), and astatus.
-
ExessResult exess_write_value(ExessDatatype datatype, size_t value_size, const void *value, size_t buf_size, char *buf)¶
Write any supported datatype to a canonical string.
Note that
valuemust be suitably aligned for the datatype being written, it will be dereferenced directly as a pointer to the value type. A 64-bit aligned buffer will be suitably aligned for any datatype.- Parameters:
datatype – Datatype of
value.value_size – Size of
valuein bytes.value – Value to write.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
The
countof characters in the output, and astatus(ExessStatus.EXESS_SUCCESS, orExessStatus.EXESS_NO_SPACEif the buffer is too small).
Canonical Form¶
Rewriting generic strings in canonical form.
-
ExessVariableResult exess_write_canonical(ExessDatatype datatype, const char *str, size_t buf_size, char *buf)¶
Rewrite a supported datatype in canonical form.
- Parameters:
datatype – Datatype of value.
str – Input value string.
buf_size – Size of
bufin bytes.buf – Output buffer, or null to only measure.
- Returns:
The
read_countfromstr,write_counttobuf(both in bytes), and astatus. The status may be an error from reading or writing.
Coercion¶
Coercing values to different datatypes.
-
typedef uint32_t ExessCoercions¶
Bitwise OR of
ExessCoercionflags.If this is zero, then only lossless coercions will be performed. 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.
-
enum ExessCoercion¶
Coercion flags.
These values are ORed together to enable different kinds of lossy conversion.
-
enumerator EXESS_APPROXIMATE¶
Allow 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.
-
enumerator EXESS_ROUND¶
Allow 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).
-
enumerator EXESS_TRUNCATE¶
Allow coercions that truncate significant parts of values.
Specifically, this allows coercing any number to boolean, and dateTime to date or time.
-
enumerator EXESS_APPROXIMATE¶
-
ExessResult exess_coerce_value(ExessCoercions coercions, ExessDatatype in_datatype, size_t in_size, const void *in, ExessDatatype out_datatype, size_t out_size, void *out)¶
Coerce a value to another datatype if possible.
- Parameters:
coercions – Enabled coercion flags. If this is zero, then
ExessStatus.EXESS_SUCCESSis 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.in_datatype – Datatype of
in.in_size – Size of
inin bytes.in – Input value to coerce.
out_datatype – Datatype to convert to.
out_size – Size of
outin bytes.out – Coerced value on success.
- Returns:
The
countof bytes written, and astatus:ExessStatus.EXESS_SUCCESSon successful conversion,ExessStatus.EXESS_OUT_OF_RANGEif the value is outside the range of the target type,ExessStatus.EXESS_LOSSif the required coercion isn’t enabled, orExessStatus.EXESS_UNSUPPORTEDif conversion between the types isn’t supported at all.