aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/exess/bindings
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2021-02-25 10:27:59 -0500
committerDavid Robillard <d@drobilla.net>2021-03-08 23:23:05 -0500
commitc4821c8e6bf1f81c6ea31e11ebc0fc1666e9337b (patch)
treea62995534f5f606ac2f8bae22d525532b824cb5e /subprojects/exess/bindings
parent6bcd18ae60482790b645a345f718e7099250f261 (diff)
downloadserd-c4821c8e6bf1f81c6ea31e11ebc0fc1666e9337b.tar.gz
serd-c4821c8e6bf1f81c6ea31e11ebc0fc1666e9337b.tar.bz2
serd-c4821c8e6bf1f81c6ea31e11ebc0fc1666e9337b.zip
Add exess from git@gitlab.com:drobilla/exess.git 4638b1f
Diffstat (limited to 'subprojects/exess/bindings')
-rw-r--r--subprojects/exess/bindings/cpp/include/exess/exess.hpp707
-rw-r--r--subprojects/exess/bindings/cpp/meson.build54
-rw-r--r--subprojects/exess/bindings/cpp/test/.clang-tidy17
-rw-r--r--subprojects/exess/bindings/cpp/test/meson.build8
-rw-r--r--subprojects/exess/bindings/cpp/test/test_exess_hpp.cpp221
5 files changed, 1007 insertions, 0 deletions
diff --git a/subprojects/exess/bindings/cpp/include/exess/exess.hpp b/subprojects/exess/bindings/cpp/include/exess/exess.hpp
new file mode 100644
index 00000000..7aa6fc5b
--- /dev/null
+++ b/subprojects/exess/bindings/cpp/include/exess/exess.hpp
@@ -0,0 +1,707 @@
+/*
+ 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_HPP
+#define EXESS_EXESS_HPP
+
+#include "exess/exess.h"
+
+#include <cstdint>
+#include <exception>
+#include <iostream>
+#include <string>
+#include <type_traits>
+#include <typeinfo>
+#include <vector>
+
+namespace exess {
+
+/**
+ @defgroup exesspp Exess C++ API
+ This is the C++ wrapper for the exess API.
+ @{
+*/
+
+constexpr const char* EXESS_NONNULL const xsd_uri =
+ "http://www.w3.org/2001/XMLSchema#";
+
+// FIXME: Reorganize
+using Datatype = ExessDatatype;
+using Result = ExessResult;
+
+using Duration = ExessDuration;
+using DateTime = ExessDateTime;
+using Time = ExessTime;
+using Date = ExessDate;
+
+/**
+ @defgroup exesspp_status Status
+ @copydoc exess_status
+ @{
+*/
+
+/// @copydoc ExessStatus
+enum class Status {
+ success, ///< @copydoc EXESS_SUCCESS
+ expected_end, ///< @copydoc EXESS_EXPECTED_END
+ expected_boolean, ///< @copydoc EXESS_EXPECTED_BOOLEAN
+ expected_integer, ///< @copydoc EXESS_EXPECTED_INTEGER
+ expected_duration, ///< @copydoc EXESS_EXPECTED_DURATION
+ expected_sign, ///< @copydoc EXESS_EXPECTED_SIGN
+ expected_digit, ///< @copydoc EXESS_EXPECTED_DIGIT
+ expected_colon, ///< @copydoc EXESS_EXPECTED_COLON
+ expected_dash, ///< @copydoc EXESS_EXPECTED_DASH
+ expected_time_sep, ///< @copydoc EXESS_EXPECTED_TIME_SEP
+ expected_time_tag, ///< @copydoc EXESS_EXPECTED_TIME_TAG
+ expected_date_tag, ///< @copydoc EXESS_EXPECTED_DATE_TAG
+ expected_hex, ///< @copydoc EXESS_EXPECTED_HEX
+ expected_base64, ///< @copydoc EXESS_EXPECTED_BASE64
+ bad_order, ///< @copydoc EXESS_BAD_ORDER
+ bad_value, ///< @copydoc EXESS_BAD_VALUE
+ out_of_range, ///< @copydoc EXESS_OUT_OF_RANGE
+ no_space, ///< @copydoc EXESS_NO_SPACE
+ would_reduce_precision, ///< @copydoc EXESS_WOULD_REDUCE_PRECISION
+ would_round, ///< @copydoc EXESS_WOULD_ROUND
+ would_truncate, ///< @copydoc EXESS_WOULD_TRUNCATE
+ unsupported, ///< @copydoc EXESS_UNSUPPORTED
+};
+
+/// @copydoc exess_strerror
+inline const char* EXESS_NONNULL
+strerror(const Status status)
+{
+ return exess_strerror(static_cast<ExessStatus>(status));
+}
+
+inline std::ostream&
+operator<<(std::ostream& stream, const Status status)
+{
+ return stream << strerror(status);
+}
+
+/**
+ @}
+
+ Datatype Traits
+*/
+
+namespace detail {
+
+template<class T>
+struct DatatypeTraits {};
+
+template<>
+struct DatatypeTraits<bool> {
+ static constexpr auto datatype = EXESS_BOOLEAN;
+ static constexpr auto read_function = exess_read_boolean;
+ static constexpr auto write_function = exess_write_boolean;
+};
+
+template<>
+struct DatatypeTraits<double> {
+ static constexpr auto datatype = EXESS_DOUBLE;
+ static constexpr auto read_function = exess_read_double;
+ static constexpr auto write_function = exess_write_double;
+};
+
+template<>
+struct DatatypeTraits<float> {
+ static constexpr auto datatype = EXESS_FLOAT;
+ static constexpr auto read_function = exess_read_float;
+ static constexpr auto write_function = exess_write_float;
+};
+
+template<>
+struct DatatypeTraits<int64_t> {
+ static constexpr auto datatype = EXESS_LONG;
+ static constexpr auto read_function = exess_read_long;
+ static constexpr auto write_function = exess_write_long;
+};
+
+template<>
+struct DatatypeTraits<int32_t> {
+ static constexpr auto datatype = EXESS_INT;
+ static constexpr auto read_function = exess_read_int;
+ static constexpr auto write_function = exess_write_int;
+};
+
+template<>
+struct DatatypeTraits<int16_t> {
+ static constexpr auto datatype = EXESS_SHORT;
+ static constexpr auto read_function = exess_read_short;
+ static constexpr auto write_function = exess_write_short;
+};
+
+template<>
+struct DatatypeTraits<int8_t> {
+ static constexpr auto datatype = EXESS_BYTE;
+ static constexpr auto read_function = exess_read_byte;
+ static constexpr auto write_function = exess_write_byte;
+};
+
+template<>
+struct DatatypeTraits<uint64_t> {
+ static constexpr auto datatype = EXESS_ULONG;
+ static constexpr auto read_function = exess_read_ulong;
+ static constexpr auto write_function = exess_write_ulong;
+};
+
+template<>
+struct DatatypeTraits<uint32_t> {
+ static constexpr auto datatype = EXESS_UINT;
+ static constexpr auto read_function = exess_read_uint;
+ static constexpr auto write_function = exess_write_uint;
+};
+
+template<>
+struct DatatypeTraits<uint16_t> {
+ static constexpr auto datatype = EXESS_USHORT;
+ static constexpr auto read_function = exess_read_ushort;
+ static constexpr auto write_function = exess_write_ushort;
+};
+
+template<>
+struct DatatypeTraits<uint8_t> {
+ static constexpr auto datatype = EXESS_UBYTE;
+ static constexpr auto read_function = exess_read_ubyte;
+ static constexpr auto write_function = exess_write_ubyte;
+};
+
+template<>
+struct DatatypeTraits<Duration> {
+ static constexpr auto datatype = EXESS_DURATION;
+ static constexpr auto read_function = exess_read_duration;
+ static constexpr auto write_function = exess_write_duration;
+};
+
+template<>
+struct DatatypeTraits<DateTime> {
+ static constexpr auto datatype = EXESS_DATETIME;
+ static constexpr auto read_function = exess_read_datetime;
+ static constexpr auto write_function = exess_write_datetime;
+};
+
+template<>
+struct DatatypeTraits<Time> {
+ static constexpr auto datatype = EXESS_TIME;
+ static constexpr auto read_function = exess_read_time;
+ static constexpr auto write_function = exess_write_time;
+};
+
+template<>
+struct DatatypeTraits<Date> {
+ static constexpr auto datatype = EXESS_DATE;
+ static constexpr auto read_function = exess_read_date;
+ static constexpr auto write_function = exess_write_date;
+};
+
+} // namespace detail
+
+/**
+ @defgroup exesspp_read_write Reading and Writing
+ @{
+*/
+
+/**
+ Read a value from a string.
+
+ @param out Set to the parsed value.
+ @param str String input.
+ @return The `count` of characters read, and a `status` code.
+*/
+template<class T>
+inline Result
+read(T* EXESS_NONNULL out, const char* EXESS_NONNULL str)
+{
+ return detail::DatatypeTraits<T>::read_function(out, str);
+}
+
+/**
+ Write a value 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`
+ #Status::success, or #Status::no_space if the buffer is too small.
+*/
+template<class T>
+inline Result
+write(const T& value, const size_t buf_size, char* EXESS_NONNULL buf)
+{
+ return detail::DatatypeTraits<T>::write_function(value, buf_size, buf);
+}
+
+/**
+ Return a value as a string.
+
+ This is a wrapper for write() that allocates a new string of the appropriate
+ length, writes the value to it, and returns it.
+
+ @param value The value to convert to a string.
+ @return The value as a string, or the empty string on error.
+*/
+template<class T>
+inline std::string
+to_string(const T& value)
+{
+ auto r = detail::DatatypeTraits<T>::write_function(value, 0, nullptr);
+ if (r.status) {
+ return {};
+ }
+
+ // In C++17, std::string::data() allows mutable access
+#if __cplusplus >= 201703L
+ std::string string(r.count, ' ');
+ r = detail::DatatypeTraits<T>::write_function(
+ value, r.count + 1, string.data());
+
+ // Before, we had to allocate somewhere else
+#else
+ std::vector<char> buf(r.count + 1, '\0');
+ r = detail::DatatypeTraits<T>::write_function(value, r.count + 1, buf.data());
+
+ std::string string(buf.data());
+#endif
+
+ return r.status ? "" : string;
+}
+
+/**
+ @}
+*/
+
+// TODO: hex, base64
+
+struct Variant : ExessVariant {
+ explicit Variant(const Status status) noexcept
+ : ExessVariant{}
+ {
+ datatype = EXESS_NOTHING;
+ value.as_status = static_cast<ExessStatus>(status);
+ }
+
+ explicit Variant(const bool v) noexcept
+ : ExessVariant{}
+ {
+ datatype = EXESS_BOOLEAN;
+ value.as_bool = v;
+ }
+
+ explicit Variant(const double v) noexcept
+ : ExessVariant{}
+ {
+ datatype = EXESS_DOUBLE;
+ value.as_double = v;
+ }
+
+ explicit Variant(const float v) noexcept
+ : ExessVariant{}
+ {
+ datatype = EXESS_FLOAT;
+ value.as_float = v;
+ }
+
+ explicit Variant(const int64_t v) noexcept
+ : ExessVariant{}
+ {
+ datatype = EXESS_LONG;
+ value.as_long = v;
+ }
+
+ explicit Variant(const int32_t v) noexcept
+ : ExessVariant{}
+ {
+ datatype = EXESS_INT;
+ value.as_int = v;
+ }
+
+ explicit Variant(const int16_t v) noexcept
+ : ExessVariant{}
+ {
+ datatype = EXESS_SHORT;
+ value.as_short = v;
+ }
+
+ explicit Variant(const int8_t v) noexcept
+ : ExessVariant{}
+ {
+ datatype = EXESS_BYTE;
+ value.as_byte = v;
+ }
+
+ explicit Variant(const uint64_t v) noexcept
+ : ExessVariant{}
+ {
+ datatype = EXESS_ULONG;
+ value.as_ulong = v;
+ }
+
+ explicit Variant(const uint32_t v) noexcept
+ : ExessVariant{}
+ {
+ datatype = EXESS_UINT;
+ value.as_uint = v;
+ }
+
+ explicit Variant(const uint16_t v) noexcept
+ : ExessVariant{}
+ {
+ datatype = EXESS_USHORT;
+ value.as_ushort = v;
+ }
+
+ explicit Variant(const uint8_t v) noexcept
+ : ExessVariant{}
+ {
+ datatype = EXESS_UBYTE;
+ value.as_ubyte = v;
+ }
+
+ explicit Variant(const Duration v) noexcept
+ : ExessVariant{}
+ {
+ datatype = EXESS_DURATION;
+ value.as_duration = v;
+ }
+
+ explicit Variant(const DateTime v) noexcept
+ : ExessVariant{}
+ {
+ datatype = EXESS_DATETIME;
+ value.as_datetime = v;
+ }
+
+ explicit Variant(const Time v) noexcept
+ : ExessVariant{}
+ {
+ datatype = EXESS_TIME;
+ value.as_time = v;
+ }
+
+ explicit Variant(const Date v) noexcept
+ : ExessVariant{}
+ {
+ datatype = EXESS_DATE;
+ value.as_date = v;
+ }
+};
+
+inline Variant
+make_decimal(const double value) noexcept
+{
+ Variant result{value};
+ result.datatype = EXESS_DECIMAL;
+ return result;
+}
+
+inline Variant
+make_integer(const int64_t value) noexcept
+{
+ Variant result{value};
+ result.datatype = EXESS_INTEGER;
+ return result;
+}
+
+inline Variant
+make_non_positive_integer(const int64_t value) noexcept
+{
+ if (value > 0) {
+ return Variant{Status::out_of_range};
+ }
+
+ Variant result{value};
+ result.datatype = EXESS_NON_POSITIVE_INTEGER;
+ return result;
+}
+
+inline Variant
+make_negative_integer(const int64_t value) noexcept
+{
+ if (value >= 0) {
+ return Variant{Status::out_of_range};
+ }
+
+ Variant result{value};
+ result.datatype = EXESS_NEGATIVE_INTEGER;
+ return result;
+}
+
+inline Variant
+make_non_negative_integer(const uint64_t value) noexcept
+{
+ Variant result{value};
+ result.datatype = EXESS_NON_NEGATIVE_INTEGER;
+ return result;
+}
+
+inline Variant
+make_positive_integer(const uint64_t value) noexcept
+{
+ if (value == 0) {
+ return Variant{Status::out_of_range};
+ }
+
+ Variant result{value};
+ result.datatype = EXESS_POSITIVE_INTEGER;
+ return result;
+}
+
+// enum class Datatype {
+// NOTHING, ///< Sentinel for unknown datatypes or errors
+// BOOLEAN, ///< xsd:boolean (see @ref exess_boolean)
+// DECIMAL, ///< xsd:decimal (see @ref exess_decimal)
+// DOUBLE, ///< xsd:double (see @ref exess_double)
+// FLOAT, ///< xsd:float (see @ref exess_float)
+// INTEGER, ///< xsd:integer (see @ref exess_long)
+// NON_POSITIVE_INTEGER, ///< xsd:nonPositiveInteger (see @ref exess_long)
+// NEGATIVE_INTEGER, ///< xsd:negativeInteger (see @ref exess_long)
+// LONG, ///< xsd:long (see @ref exess_long)
+// INT, ///< xsd:integer (see @ref exess_int)
+// SHORT, ///< xsd:short (see @ref exess_short)
+// BYTE, ///< xsd:byte (see @ref exess_byte)
+// NON_NEGATIVE_INTEGER, ///< xsd:nonNegativeInteger (see @ref exess_ulong)
+// ULONG, ///< xsd:unsignedLong (see @ref exess_ulong)
+// UINT, ///< xsd:unsignedInt (see @ref exess_uint)
+// USHORT, ///< xsd:unsignedShort (see @ref exess_ushort)
+// UBYTE, ///< xsd:unsignedByte (see @ref exess_ubyte)
+// POSITIVE_INTEGER, ///< xsd:positiveInteger (see @ref exess_ulong)
+// DURATION, ///< xsd:duration (see @ref exess_duration)
+// DATETIME, ///< xsd:dateTime (see @ref exess_datetime)
+// TIME, ///< xsd:time (see @ref exess_time)
+// DATE, ///< xsd:date (see @ref exess_date)
+// HEX, ///< xsd:hexBinary (see @ref exess_hex)
+// BASE64, ///< xsd:base64Binary (see @ref exess_base64)
+// }
+
+//
+
+/**
+ Return a pointer to the value of `variant` if it has type `T`.
+
+ This is safe to call on any variant, and will only return a pointer to the
+ value if the variant has the matching type and the value is valid to read.
+
+ @return A pointer to the value in `variant`, or null.
+*/
+template<class T>
+constexpr const T* EXESS_NULLABLE
+get_if(const Variant& variant) noexcept
+{
+ return (variant.datatype == detail::DatatypeTraits<T>::datatype)
+ ? reinterpret_cast<const T*>(&variant.value)
+ : nullptr;
+}
+
+/**
+ Return a pointer to the Status value in `variant` if it exists.
+*/
+template<>
+constexpr const Status* EXESS_NULLABLE
+get_if<Status>(const Variant& variant) noexcept
+{
+ return variant.datatype == EXESS_NOTHING
+ ? reinterpret_cast<const Status*>(&variant.value.as_status)
+ : nullptr;
+}
+
+/**
+ Return a pointer to the value of `variant` if it is a `double`.
+
+ This specialization works for both #EXESS_DECIMAL and #EXESS_DOUBLE values.
+*/
+template<>
+constexpr const double* EXESS_NULLABLE
+get_if<double>(const Variant& variant) noexcept
+{
+ return (variant.datatype == EXESS_DECIMAL || variant.datatype == EXESS_DOUBLE)
+ ? &variant.value.as_double
+ : nullptr;
+}
+
+/**
+ Return a pointer to the value of `variant` if it is an `int64_t` (long).
+
+ This specialization works for #EXESS_INTEGER, #EXESS_NON_POSITIVE_INTEGER,
+ #EXESS_NEGATIVE_INTEGER, and #EXESS_LONG values.
+*/
+template<>
+constexpr const int64_t* EXESS_NULLABLE
+get_if<int64_t>(const Variant& variant) noexcept
+{
+ return (variant.datatype >= EXESS_INTEGER && variant.datatype <= EXESS_LONG)
+ ? &variant.value.as_long
+ : nullptr;
+}
+
+/**
+ Return a pointer to the value of `variant` if it is a `uint64_t` (ulong).
+
+ This specialization works for #EXESS_NON_NEGATIVE_INTEGER, #EXESS_ULONG, and
+ #EXESS_POSITIVE_INTEGER values.
+*/
+template<>
+constexpr const uint64_t* EXESS_NULLABLE
+get_if<uint64_t>(const Variant& variant) noexcept
+{
+ return (variant.datatype == EXESS_NON_NEGATIVE_INTEGER ||
+ variant.datatype == EXESS_ULONG ||
+ variant.datatype == EXESS_POSITIVE_INTEGER)
+ ? &variant.value.as_ulong
+ : nullptr;
+}
+
+/**
+ Return the value of a variant with the given type.
+*/
+template<class T>
+const T&
+get(const Variant& variant)
+{
+ if (const T* const pointer = get_if<T>(variant)) {
+ return *pointer;
+ }
+
+ if (variant.datatype == EXESS_NOTHING) {
+ throw std::runtime_error{"Empty exess::Variant access"};
+ }
+
+ throw std::runtime_error{
+ std::string("Bad exess::Variant access: ") +
+ std::string(exess_datatype_uri(variant.datatype) + strlen(xsd_uri)) +
+ " from " + typeid(T).name()};
+}
+
+template<class T>
+constexpr size_t
+max_length()
+{
+ return 0u;
+}
+
+template<>
+constexpr size_t
+max_length<bool>()
+{
+ return EXESS_MAX_BOOLEAN_LENGTH;
+}
+
+template<>
+constexpr size_t
+max_length<double>()
+{
+ return EXESS_MAX_DOUBLE_LENGTH;
+}
+
+template<>
+constexpr size_t
+max_length<float>()
+{
+ return EXESS_MAX_FLOAT_LENGTH;
+}
+
+template<>
+constexpr size_t
+max_length<int64_t>()
+{
+ return EXESS_MAX_LONG_LENGTH;
+}
+
+template<>
+constexpr size_t
+max_length<int32_t>()
+{
+ return EXESS_MAX_INT_LENGTH;
+}
+
+template<>
+constexpr size_t
+max_length<int16_t>()
+{
+ return EXESS_MAX_SHORT_LENGTH;
+}
+
+template<>
+constexpr size_t
+max_length<int8_t>()
+{
+ return EXESS_MAX_BYTE_LENGTH;
+}
+
+template<>
+constexpr size_t
+max_length<uint64_t>()
+{
+ return EXESS_MAX_ULONG_LENGTH;
+}
+
+template<>
+constexpr size_t
+max_length<uint32_t>()
+{
+ return EXESS_MAX_UINT_LENGTH;
+}
+
+template<>
+constexpr size_t
+max_length<uint16_t>()
+{
+ return EXESS_MAX_USHORT_LENGTH;
+}
+
+template<>
+constexpr size_t
+max_length<uint8_t>()
+{
+ return EXESS_MAX_UBYTE_LENGTH;
+}
+
+template<>
+constexpr size_t
+max_length<Duration>()
+{
+ return EXESS_MAX_DURATION_LENGTH;
+}
+
+template<>
+constexpr size_t
+max_length<DateTime>()
+{
+ return EXESS_MAX_DATETIME_LENGTH;
+}
+
+template<>
+constexpr size_t
+max_length<Time>()
+{
+ return EXESS_MAX_TIME_LENGTH;
+}
+
+template<>
+constexpr size_t
+max_length<Date>()
+{
+ return EXESS_MAX_DATE_LENGTH;
+}
+
+/**
+ @}
+*/
+
+} // namespace exess
+
+#endif // EXESS_EXESS_HPP
diff --git a/subprojects/exess/bindings/cpp/meson.build b/subprojects/exess/bindings/cpp/meson.build
new file mode 100644
index 00000000..61e3616e
--- /dev/null
+++ b/subprojects/exess/bindings/cpp/meson.build
@@ -0,0 +1,54 @@
+add_languages(['cpp'])
+cpp = meson.get_compiler('cpp')
+
+# Set ultra strict warnings for developers, if requested
+if get_option('strict')
+ if cpp.get_id() == 'clang'
+ cpp_suppressions = [
+ '-Wno-c++14-extensions',
+ '-Wno-c++98-compat',
+ '-Wno-c++98-compat-pedantic',
+ '-Wno-documentation-unknown-command',
+ '-Wno-nullability-extension',
+ '-Wno-padded',
+ ]
+
+ elif cpp.get_id() == 'gcc'
+ cpp_suppressions = [
+ '-Wno-inline',
+ '-Wno-padded',
+ '-Wno-switch-default',
+ '-Wno-unused-const-variable',
+ '-Wno-useless-cast',
+ ]
+
+ elif cpp.get_id() == 'msvc'
+ cpp_suppressions = [
+ '/wd4028', # formal parameter different from declaration
+ '/wd4204', # nonstandard extension: non-constant aggregate initializer
+ '/wd4706', # assignment within conditional expression
+ '/wd4710', # function not inlined
+ '/wd4711', # function selected for automatic inline expansion
+ '/wd4820', # padding added after data member
+ '/wd5045', # will insert Spectre mitigation
+ ]
+ endif
+
+else
+ if cpp.get_id() == 'clang'
+ cpp_suppressions = [
+ '-Wno-nullability-extension',
+ ]
+ endif
+endif
+
+exess_cpp_args = cc.get_supported_arguments(cpp_suppressions)
+
+cpp_headers = ['include/exess/exess.hpp']
+cpp_header_files = files(cpp_headers)
+
+exess_hpp_dep = declare_dependency(
+ dependencies: exess_dep,
+ include_directories: include_directories('include'))
+
+subdir('test')
diff --git a/subprojects/exess/bindings/cpp/test/.clang-tidy b/subprojects/exess/bindings/cpp/test/.clang-tidy
new file mode 100644
index 00000000..a0307839
--- /dev/null
+++ b/subprojects/exess/bindings/cpp/test/.clang-tidy
@@ -0,0 +1,17 @@
+Checks: >
+ *,
+ -*-magic-numbers,
+ -*-uppercase-literal-suffix,
+ -clang-analyzer-nullability.NullableDereferenced,
+ -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
+ -cppcoreguidelines-pro-type-reinterpret-cast,
+ -fuchsia-default-arguments-calls,
+ -fuchsia-overloaded-operator,
+ -hicpp-no-array-decay,
+ -llvmlibc-*,
+ -misc-non-private-member-variables-in-classes,
+ -modernize-unary-static-assert,
+ -modernize-use-trailing-return-type,
+WarningsAsErrors: '*'
+HeaderFilterRegex: '.*'
+FormatStyle: file
diff --git a/subprojects/exess/bindings/cpp/test/meson.build b/subprojects/exess/bindings/cpp/test/meson.build
new file mode 100644
index 00000000..e04c21c9
--- /dev/null
+++ b/subprojects/exess/bindings/cpp/test/meson.build
@@ -0,0 +1,8 @@
+test('exess_hpp',
+ executable('test_exess_hpp',
+ 'test_exess_hpp.cpp',
+ cpp_args: exess_cpp_args + prog_args,
+ dependencies: [exess_hpp_dep],
+ include_directories: include_directories('../../../test',
+ '../../../src')),
+ suite: 'cpp')
diff --git a/subprojects/exess/bindings/cpp/test/test_exess_hpp.cpp b/subprojects/exess/bindings/cpp/test/test_exess_hpp.cpp
new file mode 100644
index 00000000..e940f4b1
--- /dev/null
+++ b/subprojects/exess/bindings/cpp/test/test_exess_hpp.cpp
@@ -0,0 +1,221 @@
+/*
+ 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.
+*/
+
+#undef NDEBUG
+
+#include "float_test_data.h"
+
+#include "exess/exess.h"
+#include "exess/exess.hpp"
+
+#include <cassert>
+#include <cstdint>
+#include <cstdlib>
+#include <iostream>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+
+namespace exess {
+namespace {
+
+void
+test_status()
+{
+ std::ostringstream ss;
+ ss << Status::no_space;
+
+ std::cerr << ss.str();
+ assert(ss.str() == "Insufficient space");
+}
+
+void
+test_read()
+{
+ bool a_bool = false;
+ assert(!read(&a_bool, "true").status);
+ assert(a_bool == true);
+
+ double a_double = 0.0;
+ assert(!read(&a_double, "4.2E16").status);
+ assert(double_matches(a_double, 42000000000000000.0));
+
+ float a_float = 0.0f;
+ assert(!read(&a_float, "4.2E7").status);
+ assert(float_matches(a_float, 42000000.0f));
+
+ int64_t a_long = 0;
+ assert(!read(&a_long, "-4200000000").status);
+ assert(a_long == -4200000000);
+
+ int32_t a_int = 0;
+ assert(!read(&a_int, "-42000").status);
+ assert(a_int == -42000);
+
+ int16_t a_short = 0;
+ assert(!read(&a_short, "-420").status);
+ assert(a_short == -420);
+
+ int8_t a_byte = 0;
+ assert(!read(&a_byte, "-42").status);
+ assert(a_byte == -42);
+
+ uint64_t a_ulong = 0u;
+ assert(!read(&a_ulong, "4200000000").status);
+ assert(a_ulong == 4200000000);
+
+ uint32_t a_uint = 0u;
+ assert(!read(&a_uint, "42000").status);
+ assert(a_uint == 42000);
+
+ uint16_t a_ushort = 0u;
+ assert(!read(&a_ushort, "420").status);
+ assert(a_ushort == 420);
+
+ uint8_t a_ubyte = 0u;
+ assert(!read(&a_ubyte, "42").status);
+ assert(a_ubyte == 42);
+}
+
+void
+test_to_string()
+{
+ assert(to_string(true) == "true");
+ assert(to_string(42000000000000000.0) == "4.2E16");
+ assert(to_string(42000000.0f) == "4.2E7");
+ assert(to_string(int64_t(-4200000000)) == "-4200000000");
+ assert(to_string(int32_t(-42000)) == "-42000");
+ assert(to_string(int16_t(-420)) == "-420");
+ assert(to_string(int8_t(-42)) == "-42");
+ assert(to_string(uint64_t(4200000000u)) == "4200000000");
+ assert(to_string(uint32_t(42000u)) == "42000");
+ assert(to_string(uint16_t(420u)) == "420");
+ assert(to_string(uint8_t(42u)) == "42");
+}
+
+void
+test_max_length()
+{
+ static_assert(max_length<bool>() == EXESS_MAX_BOOLEAN_LENGTH, "");
+ static_assert(max_length<double>() == EXESS_MAX_DOUBLE_LENGTH, "");
+ static_assert(max_length<float>() == EXESS_MAX_FLOAT_LENGTH, "");
+ static_assert(max_length<int64_t>() == EXESS_MAX_LONG_LENGTH, "");
+ static_assert(max_length<int32_t>() == EXESS_MAX_INT_LENGTH, "");
+ static_assert(max_length<int16_t>() == EXESS_MAX_SHORT_LENGTH, "");
+ static_assert(max_length<int8_t>() == EXESS_MAX_BYTE_LENGTH, "");
+ static_assert(max_length<uint64_t>() == EXESS_MAX_ULONG_LENGTH, "");
+ static_assert(max_length<uint32_t>() == EXESS_MAX_UINT_LENGTH, "");
+ static_assert(max_length<uint16_t>() == EXESS_MAX_USHORT_LENGTH, "");
+ static_assert(max_length<uint8_t>() == EXESS_MAX_UBYTE_LENGTH, "");
+ static_assert(max_length<Duration>() == EXESS_MAX_DURATION_LENGTH, "");
+ static_assert(max_length<DateTime>() == EXESS_MAX_DATETIME_LENGTH, "");
+ static_assert(max_length<Date>() == EXESS_MAX_DATE_LENGTH, "");
+ static_assert(max_length<Time>() == EXESS_MAX_TIME_LENGTH, "");
+}
+
+template<class T>
+void
+check_get_throws(const Variant& variant)
+{
+ bool caught = false;
+
+ try {
+ get<T>(variant);
+ } catch (const std::runtime_error&) {
+ caught = true;
+ }
+
+ assert(caught);
+}
+
+void
+test_variant()
+{
+ const auto a_nothing = Variant{Status::success};
+ const auto a_bool = Variant{true};
+ const auto a_decimal = make_decimal(1.2);
+ const auto a_double = Variant{3.4};
+ const auto a_float = Variant{5.6f};
+ const auto a_integer = make_integer(7);
+ const auto a_non_positive_integer = make_non_positive_integer(-8);
+ const auto a_negative_integer = make_negative_integer(-9);
+ const auto a_long = Variant{int64_t(10)};
+ const auto a_int = Variant{int32_t(11)};
+ const auto a_short = Variant{int16_t(12)};
+ const auto a_byte = Variant{int8_t(13)};
+ const auto a_non_negative_integer = make_non_negative_integer(14u);
+ const auto a_ulong = Variant{uint64_t(15u)};
+ const auto a_uint = Variant{uint32_t(16u)};
+ const auto a_ushort = Variant{uint16_t(17u)};
+ const auto a_ubyte = Variant{uint8_t(18u)};
+ const auto a_positive_integer = make_positive_integer(19u);
+
+ try {
+ assert(get<Status>(a_nothing) == Status::success);
+ assert(get<bool>(a_bool) == true);
+ assert(double_matches(get<double>(a_decimal), 1.2));
+ assert(double_matches(get<double>(a_double), 3.4));
+ assert(float_matches(get<float>(a_float), 5.6f));
+ assert(get<int64_t>(a_integer) == 7);
+ assert(get<int64_t>(a_non_positive_integer) == -8);
+ assert(get<int64_t>(a_negative_integer) == -9);
+ assert(get<int64_t>(a_long) == 10);
+ assert(get<int32_t>(a_int) == 11);
+ assert(get<int16_t>(a_short) == 12);
+ assert(get<int8_t>(a_byte) == 13);
+ assert(get<uint64_t>(a_non_negative_integer) == 14u);
+ assert(get<uint64_t>(a_ulong) == 15u);
+ assert(get<uint32_t>(a_uint) == 16u);
+ assert(get<uint16_t>(a_ushort) == 17u);
+ assert(get<uint8_t>(a_ubyte) == 18u);
+ assert(get<uint64_t>(a_positive_integer) == 19u);
+ } catch (const std::runtime_error&) {
+ abort();
+ }
+
+ check_get_throws<int>(a_nothing);
+ check_get_throws<int>(a_bool);
+ check_get_throws<int>(a_double);
+ check_get_throws<int>(a_float);
+ check_get_throws<int>(a_integer);
+ check_get_throws<int>(a_non_positive_integer);
+ check_get_throws<int>(a_negative_integer);
+ check_get_throws<int>(a_long);
+ check_get_throws<bool>(a_int);
+ check_get_throws<int>(a_short);
+ check_get_throws<int>(a_byte);
+ check_get_throws<int>(a_non_negative_integer);
+ check_get_throws<int>(a_ulong);
+ check_get_throws<int>(a_uint);
+ check_get_throws<int>(a_ushort);
+ check_get_throws<int>(a_ubyte);
+ check_get_throws<int>(a_positive_integer);
+}
+
+} // namespace
+} // namespace exess
+
+int
+main()
+{
+ exess::test_status();
+ exess::test_read();
+ exess::test_to_string();
+ exess::test_max_length();
+ exess::test_variant();
+
+ return 0;
+}