aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2018-06-16 10:26:47 -0400
committerDavid Robillard <d@drobilla.net>2021-03-08 23:36:35 -0500
commitdc5ccf0913fe716059bb633d8a183cbddc634a7a (patch)
treee2bd280213c648e6a4ff838bd344981f6a60bf4c
parent0c347c9701af4595a68bb37eb7c69b5db2d452f8 (diff)
downloadserd-dc5ccf0913fe716059bb633d8a183cbddc634a7a.tar.gz
serd-dc5ccf0913fe716059bb633d8a183cbddc634a7a.tar.bz2
serd-dc5ccf0913fe716059bb633d8a183cbddc634a7a.zip
WIP: Add C++ bindings
-rw-r--r--.includes.imp2
-rw-r--r--bindings/cpp/include/.clang-tidy19
-rw-r--r--bindings/cpp/include/serd/detail/Copyable.hpp153
-rw-r--r--bindings/cpp/include/serd/detail/Flags.hpp80
-rw-r--r--bindings/cpp/include/serd/detail/Optional.hpp126
-rw-r--r--bindings/cpp/include/serd/detail/StringView.hpp236
-rw-r--r--bindings/cpp/include/serd/detail/Wrapper.hpp141
-rw-r--r--bindings/cpp/include/serd/serd.hpp1665
-rw-r--r--bindings/cpp/meson.build75
-rw-r--r--bindings/cpp/serdxx.pc.in10
-rw-r--r--bindings/cpp/test/.clang-tidy29
-rw-r--r--bindings/cpp/test/test_serd_hpp.cpp837
-rw-r--r--meson.build9
-rw-r--r--meson_options.txt3
14 files changed, 3385 insertions, 0 deletions
diff --git a/.includes.imp b/.includes.imp
index 07b654dd..3f87fee5 100644
--- a/.includes.imp
+++ b/.includes.imp
@@ -3,4 +3,6 @@
{ "symbol": [ "uint32_t", "private", "<stdint.h>", "public" ] },
{ "symbol": [ "uint64_t", "private", "<stdint.h>", "public" ] },
{ "symbol": [ "uint8_t", "private", "<stdint.h>", "public" ] },
+
+ { "symbol": [ "std::ostringstream", "private", "<sstream>", "public" ] }
]
diff --git a/bindings/cpp/include/.clang-tidy b/bindings/cpp/include/.clang-tidy
new file mode 100644
index 00000000..9bb976d2
--- /dev/null
+++ b/bindings/cpp/include/.clang-tidy
@@ -0,0 +1,19 @@
+Checks: >
+ *,
+ -*-non-private-member-variables-in-classes,
+ -*-uppercase-literal-suffix,
+ -cert-dcl50-cpp,
+ -clang-diagnostic-unused-macros,
+ -cppcoreguidelines-pro-bounds-pointer-arithmetic,
+ -fuchsia-default-arguments-declarations,
+ -fuchsia-multiple-inheritance,
+ -fuchsia-overloaded-operator,
+ -hicpp-named-parameter,
+ -llvmlibc-*,
+ -modernize-use-trailing-return-type,
+ -readability-else-after-return,
+ -readability-implicit-bool-conversion,
+ -readability-named-parameter,
+WarningsAsErrors: '*'
+HeaderFilterRegex: '.*'
+FormatStyle: file
diff --git a/bindings/cpp/include/serd/detail/Copyable.hpp b/bindings/cpp/include/serd/detail/Copyable.hpp
new file mode 100644
index 00000000..36eb5042
--- /dev/null
+++ b/bindings/cpp/include/serd/detail/Copyable.hpp
@@ -0,0 +1,153 @@
+/*
+ Copyright 2019-2020 David Robillard <http://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 SERD_DETAIL_COPYABLE_HPP
+#define SERD_DETAIL_COPYABLE_HPP
+
+#include "serd/detail/Wrapper.hpp"
+
+#include <cstddef>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+namespace serd {
+namespace detail {
+
+/// Copy function for a C object
+template<class T>
+using CopyFunc = T* (*)(const T*);
+
+template<class T, Mutable<T>* Copy(const T*)>
+typename std::enable_if<std::is_const<T>::value, T>::type*
+copy(const T* ptr)
+{
+ return ptr; // Making a view (const reference), do not copy
+}
+
+template<class T, Mutable<T>* Copy(const T*)>
+typename std::enable_if<!std::is_const<T>::value, T>::type*
+copy(const T* ptr)
+{
+ return Copy(ptr); // Making a mutable wrapper, copy
+}
+
+template<class T,
+ Mutable<T>* Copy(const T*),
+ bool Equals(const T*, const T*),
+ void Free(Mutable<T>*)>
+class BasicCopyable : public Wrapper<T, BasicDeleter<T, Free>>
+{
+public:
+ using Deleter = BasicDeleter<T, Free>;
+ using Base = Wrapper<T, Deleter>;
+
+ explicit BasicCopyable(T* ptr)
+ : Base{ptr}
+ {}
+
+ BasicCopyable(const BasicCopyable& wrapper)
+ : Base(copy<T, Copy>(wrapper.cobj()))
+ {}
+
+ template<class U, void UFree(Mutable<U>*)>
+ explicit BasicCopyable(const BasicCopyable<U, Copy, Equals, UFree>& wrapper)
+ : Base(copy<T, Copy>(wrapper.cobj()))
+ {}
+
+ BasicCopyable(BasicCopyable&&) noexcept = default;
+ ~BasicCopyable() noexcept = default;
+
+ BasicCopyable& operator=(BasicCopyable&&) noexcept = default;
+
+ BasicCopyable& operator=(const BasicCopyable& wrapper)
+ {
+ if (&wrapper != this) {
+ this->_ptr = std::unique_ptr<T, Deleter>(copy<T, Copy>(wrapper.cobj()));
+ }
+ return *this;
+ }
+
+ template<class U>
+ bool operator==(const BasicCopyable<U, Copy, Equals, Free>& wrapper) const
+ {
+ return Equals(this->cobj(), wrapper.cobj());
+ }
+
+ template<class U>
+ bool operator!=(const BasicCopyable<U, Copy, Equals, Free>& wrapper) const
+ {
+ return !operator==(wrapper);
+ }
+};
+
+/// Generic C++ wrapper for a copyable C object
+template<class T,
+ Mutable<T>* Copy(const T*),
+ bool Equals(const T*, const T*),
+ void Free(Mutable<T>*)>
+class DynamicCopyable : public Wrapper<T, DynamicDeleter<T, Free>>
+{
+public:
+ using Deleter = DynamicDeleter<T, Free>;
+ using Base = Wrapper<T, Deleter>;
+ using UPtr = typename Base::UPtr;
+
+ explicit DynamicCopyable(UPtr ptr)
+ : Base(std::move(ptr))
+ {}
+
+ DynamicCopyable(const DynamicCopyable& wrapper)
+ : Base(Copy(wrapper.cobj()), Ownership::owned)
+ {}
+
+ DynamicCopyable(DynamicCopyable&&) noexcept = default;
+ DynamicCopyable& operator=(DynamicCopyable&&) noexcept = default;
+
+ ~DynamicCopyable() noexcept = default;
+
+ DynamicCopyable& operator=(const DynamicCopyable& wrapper)
+ {
+ if (&wrapper != this) {
+ this->_ptr =
+ std::unique_ptr<T, Deleter>(Copy(wrapper.cobj()), Ownership::owned);
+ }
+
+ return *this;
+ }
+
+ template<class U>
+ bool operator==(const DynamicCopyable<U, Copy, Equals, Free>& wrapper) const
+ {
+ return Equals(this->cobj(), wrapper.cobj());
+ }
+
+ template<class U>
+ bool operator!=(const DynamicCopyable<U, Copy, Equals, Free>& wrapper) const
+ {
+ return !operator==(wrapper);
+ }
+
+protected:
+ explicit DynamicCopyable(std::nullptr_t)
+ : Base(nullptr)
+ {}
+};
+
+} // namespace detail
+} // namespace serd
+
+#endif // SERD_DETAIL_COPYABLE_HPP
diff --git a/bindings/cpp/include/serd/detail/Flags.hpp b/bindings/cpp/include/serd/detail/Flags.hpp
new file mode 100644
index 00000000..115aa543
--- /dev/null
+++ b/bindings/cpp/include/serd/detail/Flags.hpp
@@ -0,0 +1,80 @@
+/*
+ Copyright 2019-2020 David Robillard <http://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 SERD_DETAIL_FLAGS_HPP
+#define SERD_DETAIL_FLAGS_HPP
+
+#include <type_traits>
+
+namespace serd {
+namespace detail {
+
+/**
+ Type-safe bit flags
+
+ This is a minimal interface for a type-safe bit flags field, which only
+ allows values from the given enum to be set.
+
+ @tparam Flag Enum class of flag values.
+*/
+template<typename Flag>
+class Flags
+{
+public:
+ static_assert(std::is_enum<Flag>::value, "");
+
+ using FlagUnderlyingType = typename std::underlying_type<Flag>::type;
+ using Value = typename std::make_unsigned<FlagUnderlyingType>::type;
+
+ constexpr Flags() noexcept
+ : _value(0)
+ {}
+
+ constexpr explicit Flags(const Value value) noexcept
+ : _value{value}
+ {}
+
+ // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
+ constexpr Flags(const Flag f) noexcept
+ : _value(static_cast<Value>(f))
+ {}
+
+ constexpr Flags operator|(const Flag rhs) const noexcept
+ {
+ return Flags{_value | static_cast<Value>(rhs)};
+ }
+
+ constexpr Flags operator|(const Flags rhs) const noexcept
+ {
+ return Flags{_value | rhs._value};
+ }
+
+ constexpr bool operator==(const Flag rhs) const noexcept
+ {
+ return _value == static_cast<Value>(rhs);
+ }
+
+ // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
+ constexpr operator Value() const noexcept { return _value; }
+
+private:
+ Value _value{};
+};
+
+} // namespace detail
+} // namespace serd
+
+#endif // SERD_DETAIL_FLAGS_HPP
diff --git a/bindings/cpp/include/serd/detail/Optional.hpp b/bindings/cpp/include/serd/detail/Optional.hpp
new file mode 100644
index 00000000..0f5b316b
--- /dev/null
+++ b/bindings/cpp/include/serd/detail/Optional.hpp
@@ -0,0 +1,126 @@
+/*
+ Copyright 2019-2020 David Robillard <http://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 SERD_DETAIL_OPTIONAL_HPP
+#define SERD_DETAIL_OPTIONAL_HPP
+
+#include <cassert>
+#include <type_traits>
+#include <utility>
+
+namespace serd {
+namespace detail {
+
+struct ConstructNullOptional {};
+
+/**
+ A simple optional wrapper around a wrapped type with a pointer-like API
+
+ This works like a typical optional type, but only works with Wrapper types,
+ and exploits the fact that these are interally just pointers to avoid adding
+ space overhead for an "is_set" flag, like a generic optional class would.
+
+ Types must explicitly opt-in to being optional by providing a constructor
+ that takes a single ContructNullOptional argument. This constructor should
+ only be used by the Optional implementation, which guarantees that such an
+ object will not be used except by calling its cobj() method.
+*/
+template<typename T>
+class Optional
+{
+public:
+ using CType = typename T::CType;
+
+ Optional()
+ : _value(nullptr)
+ {}
+
+ // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
+ Optional(T value)
+ : _value(std::move(value))
+ {}
+
+ template<
+ typename U,
+ typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
+ // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
+ Optional(U&& value)
+ : _value(std::forward<U>(value))
+ {}
+
+ void reset() { _value = T{nullptr}; }
+
+ const T& operator*() const
+ {
+ assert(_value.cobj());
+ return _value;
+ }
+
+ T& operator*()
+ {
+ assert(_value.cobj());
+ return _value;
+ }
+
+ const T* operator->() const
+ {
+ assert(_value.cobj());
+ return &_value;
+ }
+
+ T* operator->()
+ {
+ assert(_value.cobj());
+ return &_value;
+ }
+
+ bool operator==(const Optional& optional)
+ {
+ return (!*this && !optional) ||
+ (*this && optional && _value == optional._value);
+ }
+
+ bool operator!=(const Optional& optional) { return !operator==(optional); }
+
+ explicit operator bool() const { return _value.cobj(); }
+ bool operator!() const { return !_value.cobj(); }
+
+ inline CType* cobj() { return _value.cobj(); }
+ inline const CType* cobj() const { return _value.cobj(); }
+
+private:
+ T _value;
+};
+
+} // namespace detail
+
+template<class T>
+constexpr detail::Optional<T>
+make_optional(T&& value)
+{
+ return detail::Optional<T>{std::forward<T>(value)};
+}
+
+template<class T, class... Args>
+constexpr detail::Optional<T>
+make_optional(Args&&... args)
+{
+ return detail::Optional<T>{std::forward<Args>(args)...};
+}
+
+} // namespace serd
+
+#endif // SERD_DETAIL_OPTIONAL_HPP
diff --git a/bindings/cpp/include/serd/detail/StringView.hpp b/bindings/cpp/include/serd/detail/StringView.hpp
new file mode 100644
index 00000000..54d8fb84
--- /dev/null
+++ b/bindings/cpp/include/serd/detail/StringView.hpp
@@ -0,0 +1,236 @@
+/*
+ Copyright 2019-2020 David Robillard <http://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 SERD_DETAIL_STRINGVIEW_HPP
+#define SERD_DETAIL_STRINGVIEW_HPP
+
+#include <algorithm>
+#include <cstddef>
+#include <cstring>
+#include <ostream>
+#include <stdexcept>
+#include <string>
+
+namespace serd {
+namespace detail {
+
+/**
+ Immutable slice of a string.
+
+ This is a minimal implementation that is compatible with std::string_view
+ and std::string for most basic use cases. This could be replaced with
+ std::string_view once C++17 support can be relied on.
+*/
+class StringView
+{
+public:
+ using char_type = char;
+ using size_type = size_t;
+ using traits_type = std::char_traits<char>;
+ using value_type = char;
+ using pointer = value_type*;
+ using const_pointer = const value_type*;
+ using reference = value_type&;
+ using const_reference = const value_type&;
+ using iterator = const char*;
+ using const_iterator = const char*;
+
+ static constexpr size_type npos = size_t(-1);
+
+ constexpr StringView() noexcept = default;
+
+ constexpr StringView(const char* const str, const size_t len) noexcept
+ : _str{str}
+ , _len{len}
+ {}
+
+ // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
+ StringView(const char* const str) noexcept
+ : _str{str}
+ , _len{str ? strlen(str) : 0}
+ {}
+
+ // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
+ StringView(const std::string& str) noexcept
+ : _str{str.c_str()}
+ , _len{str.length()}
+ {}
+
+ constexpr size_t size() const { return _len; }
+ constexpr size_t length() const { return _len; }
+ constexpr bool empty() const { return _len == 0; }
+ constexpr const char* c_str() const { return _str; }
+ constexpr const char* data() const { return _str; }
+ constexpr const char& front() const { return _str[0]; }
+ constexpr const char& back() const { return _str[_len - 1]; }
+
+ constexpr const_iterator begin() const { return _str; }
+ constexpr const_iterator end() const { return _str + _len; }
+ constexpr const_iterator cbegin() const { return begin(); }
+ constexpr const_iterator cend() const { return end(); }
+
+ constexpr const char& operator[](const size_t pos) const { return _str[pos]; }
+
+ const char& at(const size_t pos) const
+ {
+ if (pos >= size()) {
+ throw std::out_of_range("serd::StringView::at pos");
+ }
+
+ return _str[pos];
+ }
+
+ StringView substr(const size_t pos) const
+ {
+ if (pos > size()) {
+ throw std::out_of_range("serd::StringView::substr pos");
+ }
+
+ return StringView{data() + pos, size() - pos};
+ }
+
+ StringView substr(const size_t pos, const size_t n) const
+ {
+ if (pos > size()) {
+ throw std::out_of_range("serd::StringView::substr pos");
+ }
+
+ return StringView{data() + pos, std::min(size() - pos, n)};
+ }
+
+ int compare(StringView rhs) const noexcept
+ {
+ if (!data() && !rhs.data()) {
+ return 0;
+ }
+
+ if (!data()) {
+ return -1;
+ }
+
+ if (!rhs.data()) {
+ return 1;
+ }
+
+ const size_type len = std::min(size(), rhs.size());
+ const int cmp = strncmp(data(), rhs.data(), len);
+
+ if (cmp) {
+ return cmp;
+ }
+
+ if (size() == rhs.size()) {
+ return 0;
+ }
+
+ if (size() < rhs.size()) {
+ return -1;
+ }
+
+ return 1;
+ }
+
+ template<class Alloc = std::allocator<char>>
+ std::basic_string<char, traits_type, Alloc> str() const
+ {
+ return std::basic_string<char, traits_type, Alloc>(data(), size(), Alloc{});
+ }
+
+ template<class Alloc = std::allocator<char>>
+ std::basic_string<char, traits_type, Alloc> str(const Alloc& alloc) const
+ {
+ return std::basic_string<char, traits_type, Alloc>(data(), size(), alloc);
+ }
+
+ explicit operator std::string() const { return str(); }
+
+ explicit operator const char*() const { return _str; }
+
+ // NOLINTNEXTLINE(hicpp-explicit-conversions)
+ operator const SerdStringView() const { return SerdStringView{_str, _len}; }
+
+private:
+ const char* const _str{};
+ const size_t _len{};
+};
+
+inline bool
+operator==(const detail::StringView& lhs, const detail::StringView& rhs)
+{
+ return !lhs.compare(rhs);
+}
+
+inline bool
+operator==(const detail::StringView& lhs, const std::string& rhs)
+{
+ return lhs.length() == rhs.length() &&
+ !strncmp(lhs.c_str(), rhs.c_str(), lhs.length());
+}
+
+inline bool
+operator==(const detail::StringView& lhs, const char* rhs)
+{
+ return !strncmp(lhs.c_str(), rhs, lhs.length());
+}
+
+inline bool
+operator!=(const detail::StringView& lhs, const detail::StringView& rhs)
+{
+ return lhs.compare(rhs);
+}
+
+inline bool
+operator!=(const detail::StringView& lhs, const std::string& rhs)
+{
+ return lhs.length() != rhs.length() ||
+ !!strncmp(lhs.c_str(), rhs.c_str(), lhs.length());
+}
+
+inline bool
+operator!=(const detail::StringView& lhs, const char* rhs)
+{
+ return !!strncmp(lhs.c_str(), rhs, lhs.length());
+}
+
+inline bool
+operator<(const detail::StringView& lhs, const detail::StringView& rhs)
+{
+ return lhs.compare(rhs) < 0;
+}
+
+inline bool
+operator<(const detail::StringView& lhs, const std::string& rhs)
+{
+ return lhs.c_str() < StringView(rhs);
+}
+
+inline bool
+operator<(const detail::StringView& lhs, const char* rhs)
+{
+ return strncmp(lhs.c_str(), rhs, lhs.length()) < 0;
+}
+
+inline std::ostream&
+operator<<(std::ostream& os, const StringView& str)
+{
+ os.write(str.data(), std::streamsize(str.size()));
+ return os;
+}
+
+} // namespace detail
+} // namespace serd
+
+#endif // SERD_DETAIL_STRINGVIEW_HPP
diff --git a/bindings/cpp/include/serd/detail/Wrapper.hpp b/bindings/cpp/include/serd/detail/Wrapper.hpp
new file mode 100644
index 00000000..10395ec4
--- /dev/null
+++ b/bindings/cpp/include/serd/detail/Wrapper.hpp
@@ -0,0 +1,141 @@
+/*
+ Copyright 2019-2020 David Robillard <http://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 SERD_DETAIL_WRAPPER_HPP
+#define SERD_DETAIL_WRAPPER_HPP
+
+#include <cstddef>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+namespace serd {
+namespace detail {
+
+template<typename T>
+class Optional;
+
+/// Free function for a C object
+template<typename T>
+using FreeFunc = void (*)(T*);
+
+template<class T>
+using Mutable = typename std::remove_const<T>::type;
+
+/**
+ Simple overhead-free deleter for a C object.
+
+ Can be used with const or mutable pointers, but only mutable pointers will
+ be freed. This makes it simple to wrap APIs where constness conveys
+ ownership, but can not handle unowned mutable pointers.
+*/
+template<typename T, void Free(Mutable<T>*)>
+struct BasicDeleter {
+ template<typename = std::enable_if<!std::is_const<T>::value>>
+ void operator()(typename std::remove_const<T>::type* ptr)
+ {
+ Free(ptr);
+ }
+
+ template<typename = std::enable_if<std::is_const<T>::value>>
+ void operator()(const T*)
+ {}
+};
+
+/// Ownership for `DynamicDeleter`
+enum class Ownership { owned, view };
+
+/**
+ Deleter for a C object that can handle dynamic ownership.
+
+ Unlike `BasicDeleter`, this can be used to handle non-owned references to
+ mutable objects, at the cost of an extra word for tracking the ownership
+ (since constness in the type can't convey this information).
+*/
+template<typename T, void Free(Mutable<T>*)>
+struct DynamicDeleter : BasicDeleter<T, Free> {
+ // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
+ DynamicDeleter(Ownership ownership)
+ : _ownership{ownership}
+ {}
+
+ void operator()(T* ptr)
+ {
+ if (_ownership == Ownership::owned) {
+ BasicDeleter<T, Free>::operator()(ptr);
+ }
+ }
+
+private:
+ Ownership _ownership;
+};
+
+/// Generic C++ wrapper for a C object
+template<typename T, class Deleter>
+class Wrapper
+{
+public:
+ using CType = T;
+ using UPtr = std::unique_ptr<T, Deleter>;
+
+ explicit Wrapper(T* ptr)
+ : _ptr(ptr, Deleter{})
+ {}
+
+ Wrapper(T* ptr, Deleter deleter)
+ : _ptr(ptr, std::move(deleter))
+ {}
+
+ explicit Wrapper(UPtr ptr)
+ : _ptr(std::move(ptr))
+ {}
+
+ Wrapper(Wrapper&&) noexcept = default;
+ Wrapper& operator=(Wrapper&&) noexcept = default;
+
+ Wrapper(const Wrapper&) = delete;
+ Wrapper& operator=(const Wrapper&) = delete;
+
+ ~Wrapper() = default;
+
+ T* cobj() { return _ptr.get(); }
+ const T* cobj() const { return _ptr.get(); }
+
+protected:
+ friend class detail::Optional<T>;
+
+ explicit Wrapper(std::nullptr_t)
+ : _ptr(nullptr)
+ {}
+
+ void reset() { _ptr.reset(); }
+
+ std::unique_ptr<T, Deleter> _ptr;
+};
+
+template<typename T, void Free(Mutable<T>*)>
+class BasicWrapper : public Wrapper<T, BasicDeleter<T, Free>>
+{
+public:
+ explicit BasicWrapper(T* ptr)
+ : Wrapper<T, BasicDeleter<T, Free>>{ptr}
+ {}
+};
+
+} // namespace detail
+} // namespace serd
+
+#endif // SERD_DETAIL_WRAPPER_HPP
diff --git a/bindings/cpp/include/serd/serd.hpp b/bindings/cpp/include/serd/serd.hpp
new file mode 100644
index 00000000..3e90e6b1
--- /dev/null
+++ b/bindings/cpp/include/serd/serd.hpp
@@ -0,0 +1,1665 @@
+/*
+ Copyright 2019-2020 David Robillard <http://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.
+*/
+
+/// @file serd.hpp C++ API for Serd, a lightweight RDF syntax library
+
+#ifndef SERD_SERD_HPP
+#define SERD_SERD_HPP
+
+#include "serd/detail/Copyable.hpp"
+#include "serd/detail/Flags.hpp"
+#include "serd/detail/Optional.hpp"
+#include "serd/detail/StringView.hpp"
+#include "serd/detail/Wrapper.hpp"
+
+#include "serd/serd.h"
+
+#include <cassert>
+#include <cstdarg>
+#include <cstdint>
+#include <cstdio>
+#include <functional>
+#include <map>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+/**
+ @defgroup serdxx Serdxx
+ C++ bindings for Serd, a lightweight RDF syntax library.
+
+ @ingroup serd
+ @{
+*/
+
+/**
+ Serd C++ API namespace.
+*/
+namespace serd {
+
+template<typename T>
+using Optional = detail::Optional<T>;
+
+using StringView = detail::StringView;
+
+using URIView = SerdURIView;
+
+template<typename Flag>
+inline constexpr
+ typename std::enable_if<std::is_enum<Flag>::value, detail::Flags<Flag>>::type
+ operator|(const Flag lhs, const Flag rhs) noexcept
+{
+ return detail::Flags<Flag>{lhs} | rhs;
+}
+
+/// @copydoc SerdStatus
+enum class Status {
+ success = SERD_SUCCESS, ///< @copydoc SERD_SUCCESS
+ failure = SERD_FAILURE, ///< @copydoc SERD_FAILURE
+ err_unknown = SERD_ERR_UNKNOWN, ///< @copydoc SERD_ERR_UNKNOWN
+ err_bad_syntax = SERD_ERR_BAD_SYNTAX, ///< @copydoc SERD_ERR_BAD_SYNTAX
+ err_bad_arg = SERD_ERR_BAD_ARG, ///< @copydoc SERD_ERR_BAD_ARG
+ err_bad_iter = SERD_ERR_BAD_ITER, ///< @copydoc SERD_ERR_BAD_ITER
+ err_not_found = SERD_ERR_NOT_FOUND, ///< @copydoc SERD_ERR_NOT_FOUND
+ err_id_clash = SERD_ERR_ID_CLASH, ///< @copydoc SERD_ERR_ID_CLASH
+ err_bad_curie = SERD_ERR_BAD_CURIE, ///< @copydoc SERD_ERR_BAD_CURIE
+ err_internal = SERD_ERR_INTERNAL, ///< @copydoc SERD_ERR_INTERNAL
+ err_overflow = SERD_ERR_OVERFLOW, ///< @copydoc SERD_ERR_OVERFLOW
+ err_invalid = SERD_ERR_INVALID, ///< @copydoc SERD_ERR_INVALID
+ err_no_data = SERD_ERR_NO_DATA, ///< @copydoc SERD_ERR_NO_DATA
+ err_bad_write = SERD_ERR_BAD_WRITE, ///< @copydoc SERD_ERR_BAD_WRITE
+ err_bad_call = SERD_ERR_BAD_CALL ///< @copydoc SERD_ERR_BAD_CALL
+};
+
+/// @copydoc SerdSyntax
+enum class Syntax {
+ empty = SERD_SYNTAX_EMPTY, ///< @copydoc SERD_SYNTAX_EMPTY
+ Turtle = SERD_TURTLE, ///< @copydoc SERD_TURTLE
+ NTriples = SERD_NTRIPLES, ///< @copydoc SERD_NTRIPLES
+ NQuads = SERD_NQUADS, ///< @copydoc SERD_NQUADS
+ TriG = SERD_TRIG ///< @copydoc SERD_TRIG
+};
+
+/// @copydoc SerdStatementFlag
+enum class StatementFlag {
+ empty_S = SERD_EMPTY_S, ///< @copydoc SERD_EMPTY_S
+ anon_S = SERD_ANON_S, ///< @copydoc SERD_ANON_S
+ anon_O = SERD_ANON_O, ///< @copydoc SERD_ANON_O
+ list_S = SERD_LIST_S, ///< @copydoc SERD_LIST_S
+ list_O = SERD_LIST_O, ///< @copydoc SERD_LIST_O
+ terse_S = SERD_TERSE_S, ///< @copydoc SERD_TERSE_S
+ terse_O = SERD_TERSE_O ///< @copydoc SERD_TERSE_O
+};
+
+/// Bitwise OR of #StatementFlag values
+using StatementFlags = detail::Flags<StatementFlag>;
+
+/// @copydoc SerdSerialisationFlag
+enum class SerialisationFlag {
+ no_inline_objects = SERD_NO_INLINE_OBJECTS ///< Disable object inlining
+};
+
+/// Bitwise OR of #SerialisationFlag values
+using SerialisationFlags = detail::Flags<SerialisationFlag>;
+
+/// @copydoc SerdNodeType
+enum class NodeType {
+ literal = SERD_LITERAL, ///< @copydoc SERD_LITERAL
+ URI = SERD_URI, ///< @copydoc SERD_URI
+ CURIE = SERD_CURIE, ///< @copydoc SERD_CURIE
+ blank = SERD_BLANK ///< @copydoc SERD_BLANK
+};
+
+/// @copydoc SerdNodeFlag
+enum class NodeFlag {
+ has_newline = SERD_HAS_NEWLINE, ///< @copydoc SERD_HAS_NEWLINE
+ has_quote = SERD_HAS_QUOTE, ///< @copydoc SERD_HAS_QUOTE
+ has_datatype = SERD_HAS_DATATYPE, ///< @copydoc SERD_HAS_DATATYPE
+ has_language = SERD_HAS_LANGUAGE ///< @copydoc SERD_HAS_LANGUAGE
+};
+
+/// Bitwise OR of #NodeFlag values
+using NodeFlags = detail::Flags<NodeFlag>;
+
+/// @copydoc SerdField
+enum class Field {
+ subject = SERD_SUBJECT, ///< @copydoc SERD_SUBJECT
+ predicate = SERD_PREDICATE, ///< @copydoc SERD_PREDICATE
+ object = SERD_OBJECT, ///< @copydoc SERD_OBJECT
+ graph = SERD_GRAPH ///< @copydoc SERD_GRAPH
+};
+
+/// @copydoc SerdModelFlag
+enum class ModelFlag {
+ index_SPO = SERD_INDEX_SPO, ///< @copydoc SERD_INDEX_SPO
+ index_SOP = SERD_INDEX_SOP, ///< @copydoc SERD_INDEX_SOP
+ index_OPS = SERD_INDEX_OPS, ///< @copydoc SERD_INDEX_OPS
+ index_OSP = SERD_INDEX_OSP, ///< @copydoc SERD_INDEX_OSP
+ index_PSO = SERD_INDEX_PSO, ///< @copydoc SERD_INDEX_PSO
+ index_POS = SERD_INDEX_POS, ///< @copydoc SERD_INDEX_POS
+ index_graphs = SERD_INDEX_GRAPHS, ///< @copydoc SERD_INDEX_GRAPHS
+ store_cursors = SERD_STORE_CURSORS ///< @copydoc SERD_STORE_CURSORS
+};
+
+/// Bitwise OR of #ModelFlag values
+using ModelFlags = detail::Flags<ModelFlag>;
+
+/// @copydoc SerdLogLevel
+enum class LogLevel {
+ emerg = SERD_LOG_LEVEL_EMERG, ///< @copydoc SERD_LOG_LEVEL_EMERG
+ alert = SERD_LOG_LEVEL_ALERT, ///< @copydoc SERD_LOG_LEVEL_ALERT
+ crit = SERD_LOG_LEVEL_CRIT, ///< @copydoc SERD_LOG_LEVEL_CRIT
+ err = SERD_LOG_LEVEL_ERR, ///< @copydoc SERD_LOG_LEVEL_ERR
+ warning = SERD_LOG_LEVEL_WARNING, ///< @copydoc SERD_LOG_LEVEL_WARNING
+ notice = SERD_LOG_LEVEL_NOTICE, ///< @copydoc SERD_LOG_LEVEL_NOTICE
+ info = SERD_LOG_LEVEL_INFO, ///< @copydoc SERD_LOG_LEVEL_INFO
+ debug = SERD_LOG_LEVEL_DEBUG ///< @copydoc SERD_LOG_LEVEL_DEBUG
+};
+
+/// @copydoc SerdReaderFlag
+enum class ReaderFlag {
+ lax = SERD_READ_LAX ///< @copydoc SERD_READ_LAX
+};
+
+/// @copydoc SerdReaderFlags
+using ReaderFlags = detail::Flags<ReaderFlag>;
+
+/// @copydoc SerdWriterFlag
+enum class WriterFlag {
+ ascii = SERD_WRITE_ASCII, ///< @copydoc SERD_WRITE_ASCII
+ terse = SERD_WRITE_TERSE, ///< @copydoc SERD_WRITE_TERSE
+ lax = SERD_WRITE_LAX ///< @copydoc SERD_WRITE_LAX
+};
+
+/// @copydoc SerdWriterFlags
+using WriterFlags = detail::Flags<WriterFlag>;
+
+/**
+ @name String Utilities
+ @{
+*/
+
+/// @copydoc serd_strerror
+inline const char*
+strerror(const Status status)
+{
+ return serd_strerror(static_cast<SerdStatus>(status));
+}
+
+/**
+ @}
+ @name Base64
+ @{
+*/
+
+/**
+ Encode `size` bytes of `buf` into `str`, which must be large enough
+
+ @param buf Input binary data (vector-like container of bytes).
+ @return A base64 encoded representation of the data in `buf`.
+*/
+template<typename Container>
+inline std::string
+base64_encode(const Container& buf)
+{
+#if 0
+ const size_t length{serd_base64_encoded_length(buf.size(), wrap_lines)};
+ std::string str(length + 1, '\0');
+
+ serd_base64_encode(&str.at(0), buf.data(), buf.size(), wrap_lines);
+ return str;
+#endif
+
+ (void)buf;
+ return ""; // FIXME
+}
+
+// FIXME
+#if 0
+/**
+ Decode a base64 string
+
+ Container must be a vector-like container of bytes.
+
+ @param str Base64 string to decode.
+ @return The decoded data represented in `str`.
+*/
+template<typename Container = std::vector<uint8_t>>
+inline Container
+base64_decode(StringView str)
+{
+# if 0
+ size_t size{serd_base64_decoded_size(str.length())};
+ Container buf(size, 0);
+
+ serd_base64_decode(&buf.at(0), &size, str.c_str(), str.length());
+ buf.resize(size);
+ return buf;
+# endif
+ (void)str;
+ return {}; // FIXME
+}
+#endif
+
+/**
+ @}
+ @name Byte Source
+ @{
+*/
+
+class ByteSource
+ : public detail::BasicWrapper<SerdByteSource, serd_byte_source_free>
+{
+public:
+ explicit ByteSource(std::istream& stream)
+ : BasicWrapper(
+ serd_byte_source_new_function(s_read, s_error, this, nullptr, 1))
+ , _stream(&stream)
+ {}
+
+ explicit ByteSource(const std::string& string)
+ : BasicWrapper(serd_byte_source_new_string(string.c_str(), nullptr))
+ , _stream(nullptr)
+ {}
+
+ ByteSource(const serd::ByteSource&) = delete;
+ ByteSource& operator=(const serd::ByteSource&) = delete;
+
+ ByteSource(serd::ByteSource&&) = delete;
+ ByteSource& operator=(serd::ByteSource&&) = delete;
+
+ ~ByteSource() = default;
+
+ static inline size_t s_read(void* buf,
+ size_t size,
+ size_t nmemb,
+ void* source) noexcept
+ {
+ assert(size == 1);
+ auto* self = static_cast<ByteSource*>(source);
+
+ try {
+ self->_stream->read(static_cast<char*>(buf),
+ static_cast<std::streamsize>(nmemb));
+ if (self->_stream->fail()) {
+ return 0;
+ }
+ } catch (...) {
+ }
+ return nmemb;
+ }
+
+ static inline int s_error(void* source) noexcept
+ {
+ auto* self = static_cast<ByteSource*>(source);
+
+ return static_cast<int>(self->_stream->fail());
+ }
+
+private:
+ std::istream* _stream;
+};
+
+/**
+ @}
+ @name Byte Sink
+ @{
+*/
+
+/**
+ Sink function for string output
+
+ Similar semantics to `SerdWriteFunc` (and in turn `fwrite`), but takes char*
+ for convenience and may set errno for more informative error reporting than
+ supported by `SerdStreamErrorFunc`.
+
+ @return Number of elements (bytes) written, which is short on error.
+*/
+using WriteFunc = std::function<size_t(const char*, size_t)>;
+
+class ByteSink : public detail::BasicWrapper<SerdByteSink, serd_byte_sink_free>
+{
+public:
+ ByteSink(WriteFunc write_func, const size_t block_size)
+ : BasicWrapper(serd_byte_sink_new_function(s_write, this, block_size))
+ , _write_func(std::move(write_func))
+ {}
+
+ explicit ByteSink(WriteFunc write_func)
+ : ByteSink(write_func, 1)
+ {}
+
+ explicit ByteSink(std::ostream& stream)
+ : BasicWrapper(serd_byte_sink_new_function(s_write, this, 1))
+ , _write_func([&](const char* str, size_t len) {
+ stream.write(str, std::streamsize(len));
+ return stream.good() ? len : size_t(0);
+ })
+ {}
+
+ static inline size_t s_write(const void* buf,
+ size_t size,
+ size_t nmemb,
+ void* sink) noexcept
+ {
+ assert(size == 1);
+ auto* self = static_cast<ByteSink*>(sink);
+
+ try {
+ return self->_write_func(static_cast<const char*>(buf), nmemb);
+ } catch (...) {
+ }
+ return 0;
+ }
+
+private:
+ WriteFunc _write_func;
+};
+
+/**
+ @}
+ @name Syntax Utilities
+ @{
+*/
+
+/// @copydoc serd_syntax_by_name
+inline Syntax
+syntax_by_name(StringView name)
+{
+ return Syntax(serd_syntax_by_name(name.c_str()));
+}
+
+/// @copydoc serd_guess_syntax
+inline Syntax
+guess_syntax(StringView filename)
+{
+ return Syntax(serd_guess_syntax(filename.c_str()));
+}
+
+/// @copydoc serd_syntax_has_graphs
+inline bool
+syntax_has_graphs(const Syntax syntax)
+{
+ return serd_syntax_has_graphs(static_cast<SerdSyntax>(syntax));
+}
+
+/**
+ @}
+ @name Node
+ @{
+*/
+
+template<typename CObj>
+using NodeHandle =
+ detail::BasicCopyable<CObj, serd_node_copy, serd_node_equals, serd_node_free>;
+
+template<typename CObj>
+class NodeWrapper;
+
+using Node = NodeWrapper<SerdNode>;
+using NodeView = NodeWrapper<const SerdNode>;
+
+template<typename CObj>
+class NodeWrapper : public NodeHandle<CObj>
+{
+public:
+ using Base = NodeHandle<CObj>;
+ using Base::cobj;
+
+ explicit NodeWrapper(CObj* ptr)
+ : Base(ptr)
+ {}
+
+ template<typename C>
+ // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
+ NodeWrapper(const NodeWrapper<C>& node)
+ : Base{node}
+ {}
+
+ explicit NodeWrapper(const bool b)
+ : NodeWrapper{serd_new_boolean(b)}
+ {}
+
+ explicit NodeWrapper(const double d)
+ : NodeWrapper{serd_new_double(d)}
+ {}
+
+ explicit NodeWrapper(const float f)
+ : NodeWrapper{serd_new_float(f)}
+ {}
+
+ explicit NodeWrapper(const int64_t i)
+ : NodeWrapper{serd_new_integer(i, nullptr)}
+ {}
+
+ NodeType type() const { return NodeType(serd_node_type(cobj())); }
+ const char* c_str() const { return serd_node_string(cobj()); }
+ StringView str() const { return StringView{c_str(), length()}; }
+ size_t size() const { return serd_node_length(cobj()); }
+ size_t length() const { return serd_node_length(cobj()); }
+
+ NodeFlags flags() const { return NodeFlags(serd_node_flags(cobj())); }
+
+ Optional<NodeView> datatype() const;
+ Optional<NodeView> language() const;
+
+ SerdURIView uri_view() const { return serd_parse_uri(c_str()); }
+
+ Node resolve(NodeView base) const
+ {
+ return Node(
+ serd_new_parsed_uri(serd_resolve_uri(uri_view(), base.uri_view())));
+ }
+
+ bool operator<(NodeView node) const
+ {
+ return serd_node_compare(cobj(), node.cobj()) < 0;
+ }
+
+ explicit operator std::string() const
+ {
+ return std::string(c_str(), length());
+ }
+
+ operator StringView() const { return StringView(c_str(), length()); }
+ operator SerdStringView() const { return SerdStringView{c_str(), length()}; }
+
+ const char* begin() const { return c_str(); }
+ const char* end() const { return c_str() + length(); }
+ bool empty() const { return length() == 0; }
+};
+
+template<typename CObj>
+Optional<NodeView>
+NodeWrapper<CObj>::datatype() const
+{
+ return NodeView(serd_node_datatype(cobj()));
+}
+
+template<typename CObj>
+Optional<NodeView>
+NodeWrapper<CObj>::language() const
+{
+ return NodeView(serd_node_language(cobj()));
+}
+
+inline std::ostream&
+operator<<(std::ostream& os, const NodeView& node)
+{
+ return os << node.c_str();
+}
+
+/// Create a new plain literal node with no language from `str`
+inline Node
+make_string(StringView str)
+{
+ return Node(serd_new_string(str));
+}
+
+/// @copydoc serd_new_plain_literal
+inline Node
+make_plain_literal(StringView str, StringView lang)
+{
+ return Node(serd_new_plain_literal(str, lang));
+}
+
+/// Create a new typed literal node from `str`
+inline Node
+make_typed_literal(StringView str, const StringView datatype)
+{
+ return Node(serd_new_typed_literal(str, datatype));
+}
+
+/// @copydoc serd_new_blank
+inline Node
+make_blank(StringView str)
+{
+ return Node(serd_new_blank(str));
+}
+
+/// @copydoc serd_new_curie
+inline Node
+make_curie(StringView str)
+{
+ return Node(serd_new_curie(str));
+}
+
+/// @copydoc serd_new_uri
+inline Node
+make_uri(StringView str)
+{
+ return Node(serd_new_uri(str));
+}
+
+/// @copydoc serd_new_parsed_uri
+inline Node
+make_uri(URIView uri)
+{
+ return Node(serd_new_parsed_uri(uri));
+}
+
+/// Create a new file URI node from a local filesystem path
+inline Node
+make_file_uri(StringView path)
+{
+ return Node(serd_new_file_uri(path, SERD_EMPTY_STRING()));
+}
+
+/// @copydoc serd_new_file_uri
+inline Node
+make_file_uri(StringView path, StringView hostname)
+{
+ return Node(serd_new_file_uri(path, hostname));
+}
+
+/// @copydoc serd_new_boolean
+inline Node
+make_boolean(const bool b)
+{
+ return Node(serd_new_boolean(b));
+}
+
+/// @copydoc serd_new_decimal
+inline Node
+make_decimal(double d, Optional<NodeView> datatype = {})
+{
+ return Node(serd_new_decimal(d, datatype.cobj()));
+}
+
+/// @copydoc serd_new_double
+inline Node
+make_double(double d)
+{
+ return Node(serd_new_double(d));
+}
+
+/// @copydoc serd_new_float
+inline Node
+make_float(float f)
+{
+ return Node(serd_new_float(f));
+}
+
+/// @copydoc serd_new_integer
+inline Node
+make_integer(int64_t i, Optional<NodeView> datatype = {})
+{
+ return Node(serd_new_integer(i, datatype.cobj()));
+}
+
+/// @copydoc serd_new_blob
+inline Node
+make_blob(const void* buf, size_t size, Optional<NodeView> datatype = {})
+{
+ return Node(serd_new_blob(buf, size, datatype.cobj()));
+}
+
+template<class T>
+T
+get(NodeView node);
+
+/// @copydoc serd_get_boolean
+template<>
+bool
+get<bool>(NodeView node)
+{
+ return serd_get_boolean(node.cobj());
+}
+
+/// @copydoc serd_get_double
+template<>
+double
+get<double>(NodeView node)
+{
+ return serd_get_double(node.cobj());
+}
+
+/// @copydoc serd_get_float
+template<>
+float
+get<float>(NodeView node)
+{
+ return serd_get_float(node.cobj());
+}
+
+/// @copydoc serd_get_integer
+template<>
+int64_t
+get<int64_t>(NodeView node)
+{
+ return serd_get_integer(node.cobj());
+}
+
+/**
+ @}
+ @name URI
+ @{
+*/
+
+inline std::string
+parse_file_uri(StringView uri, std::string* hostname = nullptr)
+{
+ char* c_hostname = nullptr;
+ char* c_path = serd_parse_file_uri(uri.data(), &c_hostname);
+ if (hostname && c_hostname) {
+ *hostname = c_hostname;
+ }
+
+ std::string path(c_path);
+ serd_free(c_hostname);
+ serd_free(c_path);
+ return path;
+}
+
+inline bool
+uri_string_has_scheme(StringView uri)
+{
+ return serd_uri_string_has_scheme(uri.c_str());
+}
+
+/**
+ A parsed URI
+
+ This directly refers to slices in other strings, it does not own any memory
+ itself. Thus, URIs can be parsed and/or resolved against a base URI
+ in-place without allocating memory.
+*/
+class URI
+{
+public:
+ /**
+ Component of a URI.
+
+ Note that there is a distinction between a component being non-present
+ and present but empty. For example, "file:///path" has an empty
+ authority, while "file:/path" has no authority. A non-present component
+ has its `data()` pointer set to null, while an empty component has a
+ data pointer, but length zero.
+ */
+ using Component = StringView;
+
+ explicit URI(StringView str)
+ : _uri{serd_parse_uri(str.data())}
+ {}
+
+ explicit URI(const SerdURIView& uri)
+ : _uri(uri)
+ {}
+
+ Component scheme() const { return make_component(_uri.scheme); }
+ Component authority() const { return make_component(_uri.authority); }
+ Component path_prefix() const { return make_component(_uri.path_prefix); }
+ Component path() const { return make_component(_uri.path); }
+ Component query() const { return make_component(_uri.query); }
+ Component fragment() const { return make_component(_uri.fragment); }
+
+ /// Return this URI resolved against `base`
+ URI resolve(const URI& base) const
+ {
+ return URI{serd_resolve_uri(_uri, base._uri)};
+ }
+
+ /// Return URI as a string
+ std::string string() const
+ {
+ std::ostringstream ss;
+ ByteSink byte_sink{ss};
+
+ serd_write_uri(_uri, ByteSink::s_write, &byte_sink);
+ return ss.str();
+ }
+
+ /// Return URI as a string relative to `base`
+ std::string relative_string(const URI& base) const
+ {
+ std::ostringstream ss;
+ ByteSink byte_sink{ss};
+
+ const SerdURIView rel = serd_relative_uri(_uri, base._uri);
+
+ serd_write_uri(rel, ByteSink::s_write, &byte_sink);
+ return ss.str();
+ }
+
+ /**
+ Return URI as a string relative to `base` but constrained to `root`
+
+ The returned URI string is relative iff this URI is a child of `base`
+ and `root`. The `root` must be a prefix of `base` and can be used keep
+ up-references ("../") within a certain namespace.
+ */
+ std::string relative_string(const URI& base, const URI& root) const
+ {
+ if (serd_uri_is_within(_uri, root._uri)) {
+ return relative_string(base);
+ }
+
+ return string();
+ }
+
+ const SerdURIView* cobj() const { return &_uri; }
+
+private:
+ static Component make_component(const SerdStringView slice)
+ {
+ return slice.buf ? Component{slice.buf, slice.len} : Component{};
+ }
+
+ SerdURIView _uri;
+};
+
+inline std::ostream&
+operator<<(std::ostream& os, const URI& uri)
+{
+ ByteSink byte_sink{os};
+ serd_write_uri(*uri.cobj(), ByteSink::s_write, &byte_sink);
+ return os;
+}
+
+/**
+ @}
+ @name Cursor
+ @{
+*/
+
+template<typename CObj>
+using CursorHandle = detail::
+ BasicCopyable<CObj, serd_cursor_copy, serd_cursor_equals, serd_cursor_free>;
+
+template<typename CObj>
+class CursorWrapper : public CursorHandle<CObj>
+{
+public:
+ using Base = CursorHandle<CObj>;
+ using Base::cobj;
+
+ explicit CursorWrapper(CObj* cursor)
+ : Base(cursor)
+ {}
+
+ template<typename C>
+ // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
+ CursorWrapper(const CursorWrapper<C>& cursor)
+ : Base{cursor.cobj()}
+ {}
+
+ NodeView name() const { return NodeView(serd_cursor_name(cobj())); }
+ unsigned line() const { return serd_cursor_line(cobj()); }
+ unsigned column() const { return serd_cursor_column(cobj()); }
+};
+
+using CursorView = CursorWrapper<const SerdCursor>;
+
+/// Extra data managed by mutable (user created) Cursor
+struct CursorData {
+ Node name_node;
+};
+
+class Cursor
+ : private CursorData
+ , public CursorWrapper<SerdCursor>
+{
+public:
+ Cursor(NodeView name, const unsigned line, const unsigned col)
+ : CursorData{Node{name}}
+ , CursorWrapper{serd_cursor_new(name_node.cobj(), line, col)}
+ {}
+
+ // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
+ Cursor(const CursorView& cursor)
+ : Cursor(cursor.name(), cursor.line(), cursor.column())
+ {}
+
+private:
+ friend class detail::Optional<Cursor>;
+ friend class Statement;
+
+ explicit Cursor(std::nullptr_t)
+ : CursorData{Node{nullptr}}
+ , CursorWrapper{nullptr}
+ {}
+};
+
+/**
+ @}
+ @name Logging
+ @{
+*/
+
+/**
+ @}
+ @name World
+ @{
+*/
+
+using LogFields = std::map<StringView, StringView>;
+
+using LogFunc =
+ std::function<Status(StringView, LogLevel, LogFields, std::string)>;
+
+class World : public detail::BasicWrapper<SerdWorld, serd_world_free>
+{
+public:
+ World()
+ : BasicWrapper(serd_world_new())
+ {}
+
+ NodeView get_blank() { return NodeView(serd_world_get_blank(cobj())); }
+
+ void set_message_func(LogFunc log_func)
+ {
+ _log_func = std::move(log_func);
+ serd_world_set_log_func(cobj(), s_log_func, this);
+ }
+
+ SERD_LOG_FUNC(5, 6)
+ Status log(StringView domain,
+ const LogLevel level,
+ const LogFields& fields,
+ const char* const fmt,
+ ...)
+ {
+ va_list args;
+ va_start(args, fmt);
+
+ std::vector<SerdLogField> c_fields(fields.size());
+ size_t index = 0;
+ for (const auto& f : fields) {
+ c_fields[index].key = f.first.c_str();
+ c_fields[index].value = f.second.c_str();
+ ++index;
+ }
+
+ const SerdStatus st = serd_world_vlogf(cobj(),
+ domain.c_str(),
+ static_cast<SerdLogLevel>(level),
+ fields.size(),
+ c_fields.data(),
+ fmt,
+ args);
+
+ va_end(args);
+ return static_cast<Status>(st);
+ }
+
+private:
+ SERD_LOG_FUNC(1, 0)
+ static std::string format(const char* fmt, va_list args) noexcept
+ {
+ va_list args_copy;
+ va_copy(args_copy, args);
+
+ const auto n_bytes =
+ static_cast<unsigned>(vsnprintf(nullptr, 0, fmt, args_copy));
+
+ va_end(args_copy);
+
+#if __cplusplus >= 201703L
+ std::string result(n_bytes, '\0');
+ vsnprintf(result.data(), n_bytes + 1u, fmt, args);
+#else
+ std::vector<char> str(n_bytes + 1u, '\0');
+ vsnprintf(str.data(), n_bytes + 1u, fmt, args);
+ std::string result(str.data(), size_t(n_bytes));
+#endif
+ return result;
+ }
+
+ static SerdStatus s_log_func(void* handle, const SerdLogEntry* entry) noexcept
+ {
+ const auto* const self = static_cast<const World*>(handle);
+ try {
+ LogFields fields;
+ for (size_t i = 0; i < entry->n_fields; ++i) {
+ fields.emplace(entry->fields[i].key, entry->fields[i].value);
+ }
+
+ return static_cast<SerdStatus>(
+ self->_log_func(entry->domain,
+ static_cast<LogLevel>(entry->level),
+ fields,
+ format(entry->fmt, *entry->args)));
+ } catch (...) {
+ return SERD_ERR_INTERNAL;
+ }
+ }
+
+ LogFunc _log_func{};
+};
+
+/**
+ @}
+ @name Statement
+ @{
+*/
+
+template<typename CObj>
+using StatementHandle = detail::BasicCopyable<CObj,
+ serd_statement_copy,
+ serd_statement_equals,
+ serd_statement_free>;
+
+template<typename CObj>
+class StatementWrapper;
+
+using StatementView = StatementWrapper<const SerdStatement>;
+
+/// Extra data managed by mutable (user created) Statement
+struct StatementData {
+ Node _subject;
+ Node _predicate;
+ Node _object;
+ Optional<Node> _graph;
+ Optional<Cursor> _cursor;
+};
+
+template<typename CObj>
+class StatementWrapper : public StatementHandle<CObj>
+{
+public:
+ using Base = StatementHandle<CObj>;
+ using Base::cobj;
+
+ explicit StatementWrapper(CObj* statement)
+ : Base{statement}
+ {}
+
+ template<typename C>
+ // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
+ StatementWrapper(const StatementWrapper<C>& statement)
+ : Base{statement}
+ {}
+
+ /// @copydoc serd_statement_node
+ NodeView node(Field field) const
+ {
+ return NodeView(serd_statement_node(cobj(), static_cast<SerdField>(field)));
+ }
+
+ /// @copydoc serd_statement_subject
+ NodeView subject() const { return NodeView(serd_statement_subject(cobj())); }
+
+ /// @copydoc serd_statement_predicate
+ NodeView predicate() const
+ {
+ return NodeView(serd_statement_predicate(cobj()));
+ }
+
+ /// @copydoc serd_statement_object
+ NodeView object() const { return NodeView(serd_statement_object(cobj())); }
+
+ /// @copydoc serd_statement_graph
+ Optional<NodeView> graph() const
+ {
+ return NodeView{serd_statement_graph(cobj())};
+ }
+
+ /// @copydoc serd_statement_cursor
+ Optional<CursorView> cursor() const
+ {
+ return CursorView(serd_statement_cursor(cobj()));
+ }
+
+private:
+ template<typename CIter>
+ friend class IterWrapper;
+
+ StatementWrapper()
+ : Base{nullptr}
+ {}
+};
+
+class Statement
+ : public StatementData
+ , public StatementWrapper<SerdStatement>
+{
+public:
+ Statement(const NodeView& s,
+ const NodeView& p,
+ const NodeView& o,
+ const NodeView& g,
+ Optional<CursorView> cursor = {})
+ : StatementData{s, p, o, g, cursor ? *cursor : Optional<Cursor>{}}
+ , StatementWrapper{serd_statement_new(_subject.cobj(),
+ _predicate.cobj(),
+ _object.cobj(),
+ _graph.cobj(),
+ _cursor.cobj())}
+ {}
+
+ Statement(const NodeView& s,
+ const NodeView& p,
+ const NodeView& o,
+ Optional<CursorView> cursor = {})
+ : StatementData{s, p, o, {}, cursor ? *cursor : Optional<Cursor>{}}
+ , StatementWrapper{serd_statement_new(_subject.cobj(),
+ _predicate.cobj(),
+ _object.cobj(),
+ nullptr,
+ _cursor.cobj())}
+ {}
+
+ // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
+ Statement(const StatementView& statement)
+ : StatementData{statement.subject(),
+ statement.predicate(),
+ statement.object(),
+ statement.graph() ? *statement.graph() : Optional<Node>{},
+ statement.cursor() ? *statement.cursor()
+ : Optional<Cursor>{}}
+ , StatementWrapper{statement}
+ {}
+};
+
+/**
+ @}
+ @name Sink
+ @{
+*/
+
+/// @copydoc SerdBaseFunc
+using BaseFunc = std::function<Status(NodeView)>;
+
+/// @copydoc SerdPrefixFunc
+using PrefixFunc = std::function<Status(NodeView name, NodeView uri)>;
+
+/// @copydoc SerdStatementFunc
+using StatementFunc = std::function<Status(StatementFlags, StatementView)>;
+
+/// @copydoc SerdEndFunc
+using EndFunc = std::function<Status(NodeView)>;
+
+template<typename CSink>
+class SinkWrapper
+ : public detail::Wrapper<CSink, detail::BasicDeleter<CSink, serd_sink_free>>
+{
+public:
+ explicit SinkWrapper(CSink* ptr)
+ : detail::Wrapper<CSink, detail::BasicDeleter<CSink, serd_sink_free>>(ptr)
+ {}
+
+ /// @copydoc serd_sink_write_base
+ Status base(const NodeView& uri) const
+ {
+ return Status(serd_sink_write_base(this->cobj(), uri.cobj()));
+ }
+
+ /// @copydoc serd_sink_write_prefix
+ Status prefix(NodeView name, const NodeView& uri) const
+ {
+ return Status(
+ serd_sink_write_prefix(this->cobj(), name.cobj(), uri.cobj()));
+ }
+
+ /// @copydoc serd_sink_write_statement
+ Status statement(StatementFlags flags, StatementView statement) const
+ {
+ return Status(
+ serd_sink_write_statement(this->cobj(), flags, statement.cobj()));
+ }
+
+ /// @copydoc serd_sink_write
+ Status write(StatementFlags flags,
+ const NodeView& subject,
+ const NodeView& predicate,
+ const NodeView& object,
+ Optional<NodeView> graph = {}) const
+ {
+ return Status(serd_sink_write(this->cobj(),
+ flags,
+ subject.cobj(),
+ predicate.cobj(),
+ object.cobj(),
+ graph.cobj()));
+ }
+
+ /// @copydoc serd_sink_write_end
+ Status end(const NodeView& node) const
+ {
+ return Status(serd_sink_write_end(this->cobj(), node.cobj()));
+ }
+};
+
+class SinkView : public SinkWrapper<const SerdSink>
+{
+public:
+ explicit SinkView(const SerdSink* ptr)
+ : SinkWrapper<const SerdSink>(ptr)
+ {}
+
+ // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
+ SinkView(const SinkWrapper<SerdSink>& sink)
+ : SinkWrapper<const SerdSink>(sink.cobj())
+ {}
+};
+
+class Sink : public SinkWrapper<SerdSink>
+{
+public:
+ Sink()
+ : SinkWrapper(serd_sink_new(this, s_event, nullptr))
+ {}
+
+ // EnvView env() const { return serd_sink_get_env(cobj()); }
+
+ void set_base_func(BaseFunc base_func) { _base_func = std::move(base_func); }
+
+ void set_prefix_func(PrefixFunc prefix_func)
+ {
+ _prefix_func = std::move(prefix_func);
+ }
+
+ void set_statement_func(StatementFunc statement_func)
+ {
+ _statement_func = std::move(statement_func);
+ }
+
+ void set_end_func(EndFunc end_func) { _end_func = std::move(end_func); }
+
+private:
+ static SerdStatus s_base(void* handle, const SerdNode* uri) noexcept
+ {
+ const auto* const sink = static_cast<const Sink*>(handle);
+ return sink->_base_func ? SerdStatus(sink->_base_func(NodeView(uri)))
+ : SERD_SUCCESS;
+ }
+
+ static SerdStatus s_prefix(void* handle,
+ const SerdNode* name,
+ const SerdNode* uri) noexcept
+ {
+ const auto* const sink = static_cast<const Sink*>(handle);
+ return sink->_prefix_func
+ ? SerdStatus(sink->_prefix_func(NodeView(name), NodeView(uri)))
+ : SERD_SUCCESS;
+ }
+
+ static SerdStatus s_statement(void* handle,
+ SerdStatementFlags flags,
+ const SerdStatement* statement) noexcept
+ {
+ const auto* const sink = static_cast<const Sink*>(handle);
+ return sink->_statement_func
+ ? SerdStatus(sink->_statement_func(StatementFlags(flags),
+ StatementView(statement)))
+ : SERD_SUCCESS;
+ }
+
+ static SerdStatus s_end(void* handle, const SerdNode* node) noexcept
+ {
+ const auto* const sink = static_cast<const Sink*>(handle);
+ return sink->_end_func ? SerdStatus(sink->_end_func(NodeView(node)))
+ : SERD_SUCCESS;
+ }
+
+ static SerdStatus s_event(void* handle, const SerdEvent* event) noexcept
+ {
+ const auto* const sink = static_cast<const Sink*>(handle);
+
+ switch (event->type) {
+ case SERD_BASE:
+ return sink->_base_func
+ ? SerdStatus(sink->_base_func(NodeView(event->base.uri)))
+ : SERD_SUCCESS;
+ case SERD_PREFIX:
+ return sink->_prefix_func
+ ? SerdStatus(sink->_prefix_func(NodeView(event->prefix.name),
+ NodeView(event->prefix.uri)))
+ : SERD_SUCCESS;
+ case SERD_STATEMENT:
+ return sink->_statement_func
+ ? SerdStatus(sink->_statement_func(
+ StatementFlags(event->statement.flags),
+ StatementView(event->statement.statement)))
+ : SERD_SUCCESS;
+ case SERD_END:
+ return sink->_end_func
+ ? SerdStatus(sink->_end_func(NodeView(event->end.node)))
+ : SERD_SUCCESS;
+ }
+
+ return SERD_SUCCESS;
+ }
+
+ BaseFunc _base_func{};
+ PrefixFunc _prefix_func{};
+ StatementFunc _statement_func{};
+ EndFunc _end_func{};
+};
+
+/**
+ @}
+ @name Environment
+ @{
+*/
+
+template<typename CObj>
+using EnvHandle =
+ detail::DynamicCopyable<CObj, serd_env_copy, serd_env_equals, serd_env_free>;
+
+template<typename CObj>
+class EnvWrapper : public EnvHandle<CObj>
+{
+public:
+ using Base = EnvHandle<CObj>;
+ using UPtr = typename Base::UPtr;
+
+ using Base::cobj;
+
+ explicit EnvWrapper(UPtr ptr)
+ : Base(std::move(ptr))
+ {}
+
+ /// Return the base URI
+ NodeView base_uri() const { return NodeView(serd_env_base_uri(cobj())); }
+
+ /// Set the base URI
+ Status set_base_uri(const StringView& uri)
+ {
+ return Status(serd_env_set_base_uri(cobj(), uri));
+ }
+
+ /// Set a namespace prefix
+ Status set_prefix(StringView name, StringView uri)
+ {
+ return Status(serd_env_set_prefix(cobj(), name, uri));
+ }
+
+ /// Qualify `uri` into a CURIE if possible
+ Optional<Node> qualify(const NodeView& uri) const
+ {
+ return Node(serd_env_qualify(cobj(), uri.cobj()));
+ }
+
+ /// Expand `node` into an absolute URI if possible
+ Optional<Node> expand(const NodeView& node) const
+ {
+ return Node(serd_env_expand(cobj(), node.cobj()));
+ }
+
+ /// Send all prefixes to `sink`
+ void write_prefixes(SinkView sink) const
+ {
+ serd_env_write_prefixes(cobj(), sink.cobj());
+ }
+};
+
+using EnvView = EnvWrapper<const SerdEnv>;
+
+class Env : public EnvWrapper<SerdEnv>
+{
+public:
+ Env()
+ : EnvWrapper(EnvWrapper::UPtr{serd_env_new(SERD_EMPTY_STRING()),
+ detail::Ownership::owned})
+ {}
+
+ explicit Env(const NodeView& base)
+ : EnvWrapper(EnvWrapper::UPtr{serd_env_new(base), detail::Ownership::owned})
+ {}
+};
+
+/**
+ @}
+ @name Reader
+ @{
+*/
+
+class Reader : public detail::BasicWrapper<SerdReader, serd_reader_free>
+{
+public:
+ Reader(World& world,
+ const Syntax syntax,
+ const ReaderFlags flags,
+ Env& env,
+ SinkView sink,
+ size_t stack_size)
+ : BasicWrapper(serd_reader_new(world.cobj(),
+ SerdSyntax(syntax),
+ flags,
+ env.cobj(),
+ sink.cobj(),
+ stack_size))
+ {}
+
+ void add_blank_prefix(StringView prefix)
+ {
+ serd_reader_add_blank_prefix(cobj(), prefix.data());
+ }
+
+ Status start(ByteSource& byte_source)
+ {
+ return Status(serd_reader_start(cobj(), byte_source.cobj()));
+ }
+
+ Status read_chunk() { return Status(serd_reader_read_chunk(cobj())); }
+
+ Status read_document() { return Status(serd_reader_read_document(cobj())); }
+
+ Status finish() { return Status(serd_reader_finish(cobj())); }
+
+private:
+ static inline size_t s_stream_read(void* buf,
+ size_t size,
+ size_t nmemb,
+ void* stream) noexcept
+ {
+ assert(size == 1);
+ try {
+ auto* const s = static_cast<std::istream*>(stream);
+ s->read(static_cast<char*>(buf), std::streamsize(nmemb));
+ if (s->good()) {
+ return nmemb;
+ }
+ } catch (...) {
+ }
+
+ return 0;
+ }
+
+ static inline int s_stream_error(void* stream) noexcept
+ {
+ try {
+ auto* const s = static_cast<std::istream*>(stream);
+ return (!(s->good()));
+ } catch (...) {
+ }
+
+ return 1;
+ }
+};
+
+/**
+ @}
+ @name Byte Streams
+ @{
+*/
+
+/**
+ @}
+ @name Writer
+ @{
+*/
+
+struct WriterData {
+ ByteSink _byte_sink;
+};
+
+class Writer
+ : public WriterData
+ , public detail::BasicWrapper<SerdWriter, serd_writer_free>
+{
+public:
+ Writer(World& world,
+ const Syntax syntax,
+ const WriterFlags flags,
+ Env& env,
+ WriteFunc sink)
+ : WriterData{ByteSink{sink}}
+ , BasicWrapper(serd_writer_new(world.cobj(),
+ SerdSyntax(syntax),
+ flags,
+ env.cobj(),
+ _byte_sink.cobj()))
+ {}
+
+ Writer(World& world,
+ const Syntax syntax,
+ const WriterFlags flags,
+ Env& env,
+ std::ostream& stream)
+ : WriterData{ByteSink{stream}}
+ , BasicWrapper(serd_writer_new(world.cobj(),
+ SerdSyntax(syntax),
+ flags,
+ env.cobj(),
+ _byte_sink.cobj()))
+ {}
+
+ SinkView sink() { return SinkView{serd_writer_sink(cobj())}; }
+
+ Status set_root_uri(const NodeView& uri)
+ {
+ return Status(serd_writer_set_root_uri(cobj(), uri.cobj()));
+ }
+
+ Status finish() { return Status(serd_writer_finish(cobj())); }
+};
+
+/**
+ @}
+*/
+
+template<typename CObj>
+using IterHandle = detail::
+ DynamicCopyable<CObj, serd_iter_copy, serd_iter_equals, serd_iter_free>;
+
+template<typename CObj>
+class IterWrapper : public IterHandle<CObj>
+{
+public:
+ using Base = IterHandle<CObj>;
+ using UPtr = typename Base::UPtr;
+ using Base::cobj;
+
+ explicit IterWrapper(CObj* ptr, detail::Ownership ownership)
+ : Base(UPtr{ptr, ownership})
+ {}
+
+ IterWrapper(IterWrapper&&) noexcept = default;
+ IterWrapper(const IterWrapper&) = default;
+
+ IterWrapper& operator=(IterWrapper&&) noexcept = default;
+ IterWrapper& operator=(const IterWrapper&) = default;
+
+ ~IterWrapper() = default;
+
+ const StatementView& operator*() const
+ {
+ _statement = StatementView{serd_iter_get(cobj())};
+ return _statement;
+ }
+
+ const StatementView* operator->() const
+ {
+ _statement = StatementView{serd_iter_get(cobj())};
+ return &_statement;
+ }
+
+protected:
+ mutable StatementView _statement{};
+};
+
+using IterView = IterWrapper<const SerdIter>;
+
+class Iter : public IterWrapper<SerdIter>
+{
+public:
+ explicit Iter(SerdIter* ptr, detail::Ownership ownership)
+ : IterWrapper(ptr, ownership)
+ {}
+
+ Iter(Iter&&) = default;
+ Iter(const Iter&) = default;
+
+ Iter& operator=(Iter&&) = default;
+ Iter& operator=(const Iter&) = default;
+
+ ~Iter() = default;
+
+ Iter& operator++()
+ {
+ serd_iter_next(this->cobj());
+ return *this;
+ }
+};
+
+class Range
+ : public detail::BasicCopyable<SerdRange,
+ serd_range_copy,
+ serd_range_equals,
+ serd_range_free>
+{
+public:
+ using Base = detail::BasicCopyable<SerdRange,
+ serd_range_copy,
+ serd_range_equals,
+ serd_range_free>;
+
+ explicit Range(SerdRange* r)
+ : BasicCopyable(r)
+ , _begin{serd_range_begin(r), detail::Ownership::view}
+ , _end{serd_range_end(r), detail::Ownership::view}
+ {}
+
+ const Iter& begin() const { return _begin; }
+ Iter& begin() { return _begin; }
+ const Iter& end() const { return _end; }
+
+ Status serialise(SinkView sink, SerialisationFlags flags = {})
+ {
+ return Status(serd_range_serialise(cobj(), sink.cobj(), flags));
+ }
+
+private:
+ Iter _begin;
+ Iter _end;
+};
+
+using ModelHandle = detail::
+ BasicCopyable<SerdModel, serd_model_copy, serd_model_equals, serd_model_free>;
+
+class Model : public ModelHandle
+{
+public:
+ using Base = ModelHandle;
+ using value_type = Statement;
+ using iterator = Iter;
+ using const_iterator = Iter;
+
+ Model(World& world, ModelFlags flags)
+ : Base(serd_model_new(world.cobj(), flags))
+ {}
+
+ Model(World& world, ModelFlag flag)
+ : Base(serd_model_new(world.cobj(), ModelFlags(flag)))
+ {}
+
+ size_t size() const { return serd_model_size(cobj()); }
+
+ bool empty() const { return serd_model_empty(cobj()); }
+
+ void insert(StatementView s) { serd_model_insert(cobj(), s.cobj()); }
+
+ void insert(const NodeView& s,
+ const NodeView& p,
+ const NodeView& o,
+ Optional<NodeView> g = {})
+ {
+ serd_model_add(cobj(), s.cobj(), p.cobj(), o.cobj(), g.cobj());
+ }
+
+ Iter find(Optional<NodeView> s,
+ Optional<NodeView> p,
+ Optional<NodeView> o,
+ Optional<NodeView> g = {}) const
+ {
+ return Iter(serd_model_find(cobj(), s.cobj(), p.cobj(), o.cobj(), g.cobj()),
+ detail::Ownership::owned);
+ }
+
+ Range range(Optional<NodeView> s,
+ Optional<NodeView> p,
+ Optional<NodeView> o,
+ Optional<NodeView> g = {}) const
+ {
+ return Range(
+ serd_model_range(cobj(), s.cobj(), p.cobj(), o.cobj(), g.cobj()));
+ }
+
+ Optional<NodeView> get(Optional<NodeView> s,
+ Optional<NodeView> p,
+ Optional<NodeView> o,
+ Optional<NodeView> g = {}) const
+ {
+ return NodeView(
+ serd_model_get(cobj(), s.cobj(), p.cobj(), o.cobj(), g.cobj()));
+ }
+
+ Optional<StatementView> get_statement(Optional<NodeView> s,
+ Optional<NodeView> p,
+ Optional<NodeView> o,
+ Optional<NodeView> g = {}) const
+ {
+ return StatementView(
+ serd_model_get_statement(cobj(), s.cobj(), p.cobj(), o.cobj(), g.cobj()));
+ }
+
+ bool ask(Optional<NodeView> s,
+ Optional<NodeView> p,
+ Optional<NodeView> o,
+ Optional<NodeView> g = {}) const
+ {
+ return serd_model_ask(cobj(), s.cobj(), p.cobj(), o.cobj(), g.cobj());
+ }
+
+ size_t count(Optional<NodeView> s,
+ Optional<NodeView> p,
+ Optional<NodeView> o,
+ Optional<NodeView> g = {}) const
+ {
+ return serd_model_count(cobj(), s.cobj(), p.cobj(), o.cobj(), g.cobj());
+ }
+
+ Range all() const { return Range(serd_model_all(cobj())); }
+
+ iterator begin() const
+ {
+ return iterator(serd_model_begin(cobj()), detail::Ownership::owned);
+ }
+
+ iterator end() const
+ {
+ return iterator(serd_iter_copy(serd_model_end(cobj())),
+ detail::Ownership::owned);
+ }
+
+private:
+ friend class detail::Optional<Model>;
+ explicit Model(std::nullptr_t)
+ : BasicCopyable(nullptr)
+ {}
+};
+
+class Inserter : public SinkWrapper<SerdSink>
+{
+public:
+ Inserter(Model& model, Env& env, Optional<NodeView> default_graph = {})
+ : SinkWrapper(
+ serd_inserter_new(model.cobj(), env.cobj(), default_graph.cobj()))
+ {}
+};
+
+} // namespace serd
+
+/**
+ @}
+*/
+
+#endif // SERD_SERD_HPP
diff --git a/bindings/cpp/meson.build b/bindings/cpp/meson.build
new file mode 100644
index 00000000..627e537c
--- /dev/null
+++ b/bindings/cpp/meson.build
@@ -0,0 +1,75 @@
+versioned_cpp_name = 'serdxx' + version_suffix
+
+cpp_prog_args = []
+
+# Set ultra strict warnings for developers, if requested
+if get_option('strict')
+ cpp_warnings = all_cpp_warnings
+ if cpp.get_id() == 'clang'
+ cpp_warnings += [
+ '-Wno-documentation-unknown-command',
+ '-Wno-format-nonliteral',
+ '-Wno-nullability-extension',
+ '-Wno-padded',
+ ]
+ elif cpp.get_id() == 'gcc'
+ cpp_warnings += [
+ '-Wno-abi-tag',
+ '-Wno-float-equal',
+ '-Wno-inline',
+ '-Wno-multiple-inheritance',
+ '-Wno-padded',
+ '-Wno-suggest-attribute=pure',
+ '-Wno-switch-default',
+ '-Wno-unused-const-variable',
+ ]
+ elif cpp.get_id() == 'msvc'
+ cpp_warnings += [
+ '/wd4355', # 'this' used in base member initializer list
+ '/wd4571', # structured exceptions are no longer caught
+ '/wd4623', # default constructor implicitly deleted
+ '/wd4625', # copy constructor implicitly deleted
+ '/wd4626', # assignment operator implicitly deleted
+ '/wd4710', # function not inlined
+ '/wd4868', # may not enforce left-to-right evaluation order
+ '/wd5026', # move constructor implicitly deleted
+ '/wd5027', # move assignment operator implicitly deleted
+ ]
+ endif
+
+ cpp_prog_args = cpp.get_supported_arguments(cpp_warnings)
+
+endif
+
+cpp_headers = [
+ 'include/serd/serd.hpp',
+ 'include/serd/detail/Copyable.hpp',
+ 'include/serd/detail/Flags.hpp',
+ 'include/serd/detail/Optional.hpp',
+ 'include/serd/detail/StringView.hpp',
+ 'include/serd/detail/Wrapper.hpp',
+]
+
+serdxx_dep = declare_dependency(
+ include_directories: include_directories(['include']),
+ link_with: libserd)
+
+pkg.generate(
+ description: 'C++ bindings for serd',
+ filebase: versioned_cpp_name,
+ name: 'Serdxx',
+ subdirs: [versioned_cpp_name],
+ version: meson.project_version())
+
+# Install headers to a versioned include directory
+install_headers(cpp_headers, subdir: versioned_cpp_name / 'serd')
+
+cpp_test_args = cpp.get_supported_arguments(['-Wno-float-equal'])
+
+test('bindings',
+ executable('test_serd_hpp',
+ 'test/test_serd_hpp.cpp',
+ include_directories: include_directories(['include']),
+ cpp_args: cpp_prog_args + cpp_test_args,
+ dependencies: [serd_dep, serdxx_dep]),
+ suite: 'cpp')
diff --git a/bindings/cpp/serdxx.pc.in b/bindings/cpp/serdxx.pc.in
new file mode 100644
index 00000000..47b8bf11
--- /dev/null
+++ b/bindings/cpp/serdxx.pc.in
@@ -0,0 +1,10 @@
+prefix=@PREFIX@
+exec_prefix=@EXEC_PREFIX@
+libdir=@LIBDIR@
+includedir=@INCLUDEDIR@
+
+Name: Serdxx
+Version: @SERD_VERSION@
+Description: C++ bindings for serd, a lightweight RDF syntax library
+Requires: serd-@SERD_MAJOR_VERSION@
+Cflags: -I${includedir}/serdxx-@SERD_MAJOR_VERSION@
diff --git a/bindings/cpp/test/.clang-tidy b/bindings/cpp/test/.clang-tidy
new file mode 100644
index 00000000..4998205c
--- /dev/null
+++ b/bindings/cpp/test/.clang-tidy
@@ -0,0 +1,29 @@
+Checks: >
+ *,
+ -*-magic-numbers,
+ -*-non-private-member-variables-in-classes,
+ -*-uppercase-literal-suffix,
+ -cert-dcl50-cpp,
+ -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
+ -cppcoreguidelines-pro-bounds-pointer-arithmetic,
+ -cppcoreguidelines-pro-type-vararg,
+ -fuchsia-default-arguments-calls,
+ -fuchsia-default-arguments-declarations,
+ -fuchsia-multiple-inheritance,
+ -fuchsia-overloaded-operator,
+ -google-readability-todo,
+ -google-runtime-references,
+ -hicpp-named-parameter,
+ -hicpp-no-array-decay,
+ -hicpp-signed-bitwise,
+ -hicpp-vararg,
+ -llvmlibc-*,
+ -misc-no-recursion,
+ -modernize-use-trailing-return-type,
+ -performance-unnecessary-value-param,
+ -readability-else-after-return,
+ -readability-implicit-bool-conversion,
+ -readability-named-parameter,
+WarningsAsErrors: '*'
+HeaderFilterRegex: '*.hpp'
+FormatStyle: file
diff --git a/bindings/cpp/test/test_serd_hpp.cpp b/bindings/cpp/test/test_serd_hpp.cpp
new file mode 100644
index 00000000..24e663fa
--- /dev/null
+++ b/bindings/cpp/test/test_serd_hpp.cpp
@@ -0,0 +1,837 @@
+/*
+ Copyright 2018-2020 David Robillard <http://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 "serd/serd.h"
+#include "serd/serd.hpp"
+
+// TODO: Figure out how to make iwyu not suggest adding these
+#include "serd/detail/Copyable.hpp"
+#include "serd/detail/Flags.hpp"
+#include "serd/detail/Optional.hpp"
+#include "serd/detail/StringView.hpp"
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <cstdint>
+#include <cstring>
+#include <fstream> // IWYU pragma: keep
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+template<typename T>
+static int
+test_move_only(T&& obj)
+{
+ static_assert(!std::is_copy_constructible<T>::value, "");
+ static_assert(!std::is_copy_assignable<T>::value, "");
+
+ const auto* const ptr = obj.cobj();
+
+ // Move construct
+ T moved{std::forward<T>(obj)};
+ assert(moved.cobj() == ptr);
+ assert(!obj.cobj()); // NOLINT
+
+ // Move assign
+ obj = std::move(moved);
+ assert(obj.cobj() == ptr);
+ assert(!moved.cobj()); // NOLINT
+
+ return 0;
+}
+
+template<typename T>
+static int
+test_copy_move(const T& obj)
+{
+ T copy{obj};
+ assert(copy == obj);
+
+ T moved{std::move(copy)};
+ assert(moved == obj);
+ assert(copy != obj); // NOLINT
+
+ T copy_assigned{obj};
+ copy_assigned = obj;
+ assert(copy_assigned == obj);
+
+ T move_assigned{obj};
+ move_assigned = std::move(copy_assigned);
+ assert(move_assigned == obj);
+ assert(copy_assigned != obj); // NOLINT
+
+ return 0;
+}
+
+static int
+test_operators()
+{
+ int st = 0;
+
+ serd::World world;
+
+ serd::Model model(
+ world, serd::ModelFlag::index_SPO | serd::ModelFlag::store_cursors);
+ model.insert(serd::Statement{serd::make_uri("http://example.org/s"),
+ serd::make_uri("http://example.org/p"),
+ serd::make_uri("http://example.org/o"),
+ serd::Cursor{serd::make_uri("test.ttl"), 1, 1}});
+
+ serd::Sink sink;
+ serd::Env env;
+
+ std::ostringstream stream;
+
+ // st |= test_move_only(serd::World{});
+ st |= test_copy_move(serd::Statement{*model.begin()});
+ st |= test_copy_move(
+ serd::Cursor{serd::make_uri("http://example.org/doc"), 1, 2});
+ st |= test_copy_move(model.begin()->cursor());
+ st |= test_copy_move(serd::Env{});
+ st |= test_move_only(
+ serd::Reader{world, serd::Syntax::Turtle, {}, env, sink, 4096});
+ st |= test_copy_move(model.begin());
+ st |= test_copy_move(model.all());
+ // Sink
+ st |= test_copy_move(model);
+ // st |= test_move_only(serd::Inserter{model, env});
+ // st |= test_move_only(serd::Sink{});
+
+ st |= test_copy_move(serd::Env{});
+
+ return st;
+}
+
+template<typename Value>
+static int
+test_optional(const Value& value, const Value& other)
+{
+ test_copy_move(value);
+
+ // Truthiness
+ assert(!serd::Optional<Value>());
+ // assert(!serd::Optional<Value>(nullptr));
+ assert(serd::Optional<Value>(value));
+
+ // Comparison and general sanity
+ serd::Optional<Value> optional{value};
+ assert(optional);
+ assert(optional == value);
+ assert(optional != other);
+ assert(*optional == value);
+ assert(optional.cobj() != value.cobj()); // non-const, must be a copy
+
+ // Reset
+ optional.reset();
+ assert(!optional);
+ assert(!optional.cobj());
+
+ // Copying and moving
+ Value nonconst = value;
+ const auto* c_ptr = nonconst.cobj();
+
+ optional = nonconst;
+ serd::Optional<Value> copied{optional};
+ assert(copied == nonconst);
+ assert(copied.cobj() != c_ptr);
+
+ optional = std::move(nonconst);
+ serd::Optional<Value> moved{std::move(optional)};
+ assert(moved.cobj() == c_ptr);
+ assert(!optional); // NOLINT
+
+ serd::Optional<Value> copy_assigned;
+ copy_assigned = optional;
+ assert(copy_assigned == optional);
+ assert(copy_assigned.cobj() != c_ptr);
+
+ serd::Optional<Value> move_assigned;
+ move_assigned = std::move(moved);
+ assert(move_assigned.cobj() == c_ptr);
+ assert(!optional);
+
+ serd::Optional<Value> nullopt_assigned;
+ nullopt_assigned = {};
+ assert(!nullopt_assigned.cobj());
+
+ return 0;
+}
+
+static int
+test_optional()
+{
+ test_optional(serd::make_string("value"), serd::make_string("other"));
+
+ {
+ serd::World world;
+
+ serd::Model value(world, serd::ModelFlag::index_SPO);
+ value.insert(serd::make_uri("http://example.org/s1"),
+ serd::make_uri("http://example.org/p1"),
+ serd::make_uri("http://example.org/o1"));
+
+ serd::Model other(world, serd::ModelFlag::index_SPO);
+ value.insert(serd::make_uri("http://example.org/s2"),
+ serd::make_uri("http://example.org/p2"),
+ serd::make_uri("http://example.org/o2"));
+
+ test_optional(value, other);
+ }
+
+ return 0;
+}
+
+static int
+test_node(const serd::Node& node)
+{
+ test_copy_move(node);
+
+ if (node.datatype()) {
+ return test_node(*node.datatype());
+ }
+
+ if (node.language()) {
+ return test_node(*node.language());
+ }
+
+ return 0;
+}
+
+static int
+test_string()
+{
+ assert(!strcmp(serd::strerror(serd::Status::err_unknown), "Unknown error"));
+
+ return 0;
+}
+
+static int
+test_stringview()
+{
+ const serd::StringView hello{"hello"};
+
+ assert(hello.front() == 'h');
+ assert(hello.back() == 'o');
+
+ assert(*hello.begin() == 'h');
+ assert(*hello.end() == '\0');
+ assert(*(hello.end() - 1) == 'o');
+ assert(*hello.cbegin() == 'h');
+ assert(*hello.cend() == '\0');
+ assert(*(hello.cend() - 1) == 'o');
+
+ assert(hello[0] == 'h');
+ assert(hello[1] == 'e');
+ assert(hello.at(0) == 'h');
+ assert(hello.at(1) == 'e');
+ assert(hello.substr(2) == "llo");
+
+ assert(hello.str() == "hello");
+ assert(std::string(hello) == "hello");
+ assert(!strcmp(static_cast<const char*>(hello), "hello"));
+
+ std::stringstream ss;
+ ss << hello;
+ assert(ss.str() == "hello");
+
+ bool threw = false;
+ try {
+ hello.at(6);
+ } catch (const std::out_of_range&) {
+ threw = true;
+ }
+ assert(threw);
+
+ try {
+ hello.substr(6);
+ } catch (const std::out_of_range&) {
+ threw = true;
+ }
+ assert(threw);
+
+ assert(serd::StringView{} == serd::StringView{}); // NOLINT
+ assert(hello == "hello");
+ assert(hello == std::string{"hello"});
+ assert(hello == serd::StringView{"hello"});
+
+ assert(hello != "world");
+ assert(hello != std::string{"world"});
+ assert(hello != serd::StringView{"world"});
+
+ assert(serd::StringView{"a"}.compare(serd::StringView{"ab"}) < 0);
+ assert(serd::StringView{"ab"}.compare(serd::StringView{"a"}) > 0);
+ assert(serd::StringView{"ab"}.compare(serd::StringView{"ab"}) == 0);
+
+ assert(hello < serd::StringView{"world"});
+ assert(hello < std::string{"world"});
+ assert(hello < "world");
+
+ assert(!(hello < serd::StringView{"apple"}));
+ assert(!(hello < std::string{"apple"}));
+ assert(!(hello < "apple"));
+
+ return 0;
+}
+
+// FIXME
+#if 0
+static int
+test_base64()
+{
+ const std::vector<uint8_t> data{1, 1, 2, 3, 5};
+
+ const auto encoded = serd::base64_encode(data);
+ const auto decoded = serd::base64_decode(encoded);
+
+ assert(decoded == data);
+
+ return 0;
+}
+#endif
+
+static int
+test_syntax()
+{
+ assert(serd::syntax_by_name("Turtle") == serd::Syntax::Turtle);
+ assert(serd::guess_syntax("foo.trig") == serd::Syntax::TriG);
+ assert(!serd::syntax_has_graphs(serd::Syntax::NTriples));
+ return 0;
+}
+
+static int
+test_nodes()
+{
+ const auto type = serd::make_uri("http://example.org/Type");
+ const auto base = serd::make_uri("http://example.org/");
+ const auto root = serd::make_uri("http://example.org/");
+
+ assert(base.type() == serd::NodeType::URI);
+ assert(base.str() == "http://example.org/");
+ assert(base.size() == strlen("http://example.org/"));
+ assert(base == root);
+ assert(base < type);
+ assert(!base.empty());
+ assert(std::count(base.begin(), base.end(), '/') == 3);
+
+ const auto relative = serd::make_uri("rel/uri");
+ const auto resolved = relative.resolve(base);
+ assert(static_cast<std::string>(resolved) == "http://example.org/rel/uri");
+ assert(static_cast<serd::StringView>(resolved) ==
+ "http://example.org/rel/uri");
+
+ const auto string = serd::make_string("hello\n\"world\"");
+ assert(string.flags() ==
+ (serd::NodeFlag::has_newline | serd::NodeFlag::has_quote));
+
+ const auto number = serd::make_integer(42);
+ assert(number.flags() == serd::NodeFlag::has_datatype);
+ assert(number.datatype() ==
+ serd::make_uri("http://www.w3.org/2001/XMLSchema#integer"));
+
+ const auto tagged = serd::make_plain_literal("hallo", "de");
+ assert(tagged.flags() == serd::NodeFlag::has_language);
+ assert(tagged.language() == serd::make_string("de"));
+
+ assert(!test_node(serd::make_string("hello")));
+ assert(!test_node(serd::make_plain_literal("hello", "en")));
+ assert(!test_node(serd::make_typed_literal("hello", type)));
+ assert(!test_node(serd::make_blank("blank")));
+ assert(!test_node(serd::make_curie("eg:curie")));
+ assert(!test_node(serd::make_uri("http://example.org/thing")));
+ assert(!test_node(serd::make_file_uri("/foo/bar", "host")));
+ assert(!test_node(serd::make_file_uri("/foo/bar")));
+ assert(!test_node(serd::make_file_uri("/foo/bar", "host")));
+ assert(!test_node(serd::make_file_uri("/foo/bar")));
+ assert(!test_node(serd::make_decimal(1.2)));
+ assert(!test_node(serd::make_decimal(3.4, type)));
+ assert(!test_node(serd::make_integer(56)));
+ assert(!test_node(serd::make_integer(78, type)));
+ assert(!test_node(serd::make_blob("blob", 4)));
+ assert(!test_node(serd::make_blob("blob", 4, type)));
+
+ assert(serd::get<bool>(serd::make_boolean(true)) == true);
+ assert(serd::get<bool>(serd::make_boolean(false)) == false);
+ assert(serd::get<double>(serd::make_double(1.5)) == 1.5);
+ assert(serd::get<double>(serd::make_double(-2.5)) == -2.5);
+ assert(serd::get<float>(serd::make_float(1.2f)) == 1.2f);
+ assert(serd::get<float>(serd::make_float(-2.5f)) == -2.5f);
+ assert(serd::get<int64_t>(serd::make_integer(12)) == 12);
+ assert(serd::get<int64_t>(serd::make_integer(-34)) == -34);
+
+ return 0;
+}
+
+static int
+test_uri()
+{
+ const auto uri = serd::make_uri("file:/path");
+ const auto no_authority = serd::URI(uri);
+ assert(no_authority.scheme() == "file");
+ assert(!no_authority.authority().data());
+ assert(no_authority.path() == "/path");
+
+ const auto empty_authority = serd::URI("file:///path");
+ assert(empty_authority.scheme() == "file");
+ assert(empty_authority.authority().data());
+ assert(empty_authority.authority().empty());
+ assert(empty_authority.path() == "/path");
+
+ const auto base = serd::URI("http://example.org/base/");
+ assert(base.scheme() == "http");
+ assert(base.authority() == "example.org");
+ assert(!base.path_prefix().data());
+ assert(base.path() == "/base/");
+ assert(!base.query().data());
+ assert(!base.fragment().data());
+
+ const auto rel = serd::URI("relative/path?query#fragment");
+ assert(!rel.scheme().data());
+ assert(!rel.authority().data());
+ assert(!rel.path_prefix().data());
+ assert(rel.path() == "relative/path");
+ assert(rel.query() == "query");
+ assert(rel.fragment() == "#fragment");
+
+ const auto resolved = rel.resolve(base);
+ assert(resolved.scheme() == "http");
+ assert(resolved.authority() == "example.org");
+ assert(resolved.path_prefix() == "/base/");
+ assert(resolved.path() == "relative/path");
+ assert(resolved.query() == "query");
+ assert(resolved.fragment() == "#fragment");
+
+ assert(resolved.string() ==
+ "http://example.org/base/relative/path?query#fragment");
+ std::cerr << resolved.relative_string(base) << std::endl;
+ assert(resolved.relative_string(base) == "relative/path?query#fragment");
+
+ const auto domain = serd::URI("http://example.org/");
+ assert(domain.relative_string(resolved) == "../../");
+ assert(domain.relative_string(resolved, base) == domain.string());
+
+ auto local_file_uri = serd::parse_file_uri("file:///foo/%20bar");
+ assert(local_file_uri == "/foo/ bar");
+
+ auto hostname = std::string();
+ auto host_file_uri = serd::parse_file_uri("file://host/foo", &hostname);
+ assert(hostname == "host");
+ assert(host_file_uri == "/foo");
+
+ assert(serd::uri_string_has_scheme("http://example.org/"));
+ assert(!serd::uri_string_has_scheme("foo/bar"));
+
+ std::ostringstream ss;
+ ss << resolved;
+ assert(ss.str() == "http://example.org/base/relative/path?query#fragment");
+
+ return 0;
+}
+
+static int
+test_reader()
+{
+ serd::Optional<serd::Node> base_uri;
+ serd::Optional<serd::Node> ns_name;
+ serd::Optional<serd::Node> ns_uri;
+ serd::Optional<serd::Node> ended_node;
+ size_t n_statements{};
+ std::stringstream stream{};
+ serd::Sink sink;
+
+ sink.set_base_func([&](serd::NodeView uri) {
+ base_uri = uri;
+ return serd::Status::success;
+ });
+
+ sink.set_prefix_func([&](serd::NodeView name, serd::NodeView uri) {
+ ns_name = name;
+ ns_uri = uri;
+ return serd::Status::success;
+ });
+
+ sink.set_statement_func(
+ [&](const serd::StatementFlags, const serd::Statement& statement) {
+ ++n_statements;
+ stream << statement.subject() << " " << statement.predicate() << " "
+ << statement.object() << std::endl;
+ return serd::Status::success;
+ });
+
+ sink.set_end_func([&](serd::NodeView node) {
+ ended_node = node;
+ return serd::Status::success;
+ });
+
+ serd::World world;
+ serd::Env env;
+ serd::Reader reader(world, serd::Syntax::Turtle, {}, env, sink, 4096);
+
+ const std::string input("@base <http://example.org/base> ."
+ "@prefix eg: <http://example.org/> ."
+ "eg:s eg:p [ eg:p2 eg:o2 ] .");
+
+ // Read from string
+ serd::ByteSource string_source(input);
+ reader.start(string_source);
+ reader.read_document();
+
+ assert(n_statements == 2);
+ assert(stream.str() == "http://example.org/s http://example.org/p b1\n"
+ "b1 http://example.org/p2 http://example.org/o2\n");
+
+ assert(base_uri == serd::make_uri("http://example.org/base"));
+ assert(ns_name == serd::make_string("eg"));
+ assert(ns_uri == serd::make_uri("http://example.org/"));
+
+ // Read from C++ stream
+ std::stringstream ss("eg:s eg:p eg:o3 , _:blank .");
+ serd::ByteSource byte_source(ss);
+ stream.str("");
+ reader.add_blank_prefix("prefix_");
+ reader.start(byte_source);
+ assert(reader.read_chunk() == serd::Status::success);
+ assert(reader.read_chunk() != serd::Status::success);
+
+ assert(n_statements == 4);
+ assert(stream.str() ==
+ "http://example.org/s http://example.org/p http://example.org/o3\n"
+ "http://example.org/s http://example.org/p prefix_blank\n");
+
+ assert(reader.finish() == serd::Status::success);
+
+ return 0;
+}
+
+static serd::Status
+write_test_doc(serd::Writer& writer)
+{
+ const auto& sink = writer.sink();
+
+ const auto blank = serd::make_blank("b1");
+ sink.base(serd::make_uri("http://drobilla.net/base/"));
+ sink.prefix(serd::make_string("eg"), serd::make_uri("http://example.org/"));
+ sink.write(serd::StatementFlag::anon_O,
+ serd::make_uri("http://drobilla.net/base/s"),
+ serd::make_uri("http://example.org/p"),
+ blank);
+ sink.statement({},
+ serd::Statement(blank,
+ serd::make_uri("http://example.org/p2"),
+ serd::make_uri("http://drobilla.net/o")));
+ sink.end(blank);
+
+ return writer.finish();
+}
+
+static const char* const writer_test_doc =
+ "@base <http://drobilla.net/base/> .\n"
+ "@prefix eg: <http://example.org/> .\n"
+ "\n"
+ "<http://drobilla.net/base/s>\n"
+ "\t<http://example.org/p> [\n"
+ "\t\t<http://example.org/p2> <http://drobilla.net/o>\n"
+ "\t] .\n";
+
+static int
+test_writer_ostream()
+{
+ serd::World world;
+ serd::Env env;
+
+ {
+ std::ostringstream stream;
+ serd::Writer writer(world, serd::Syntax::Turtle, {}, env, stream);
+
+ write_test_doc(writer);
+ assert(stream.str() == writer_test_doc);
+ }
+
+ {
+ std::ofstream bad_file("/does/not/exist");
+ bad_file.clear();
+ bad_file.exceptions(std::ofstream::badbit);
+ serd::Writer writer(world, serd::Syntax::Turtle, {}, env, bad_file);
+ const serd::Status st =
+ writer.sink().base(serd::make_uri("http://drobilla.net/base/"));
+ assert(st == serd::Status::err_bad_write);
+ }
+
+ return 0;
+}
+
+static int
+test_writer_string_sink()
+{
+ serd::World world;
+ serd::Env env;
+ std::string output;
+ serd::Writer writer(world,
+ serd::Syntax::Turtle,
+ {},
+ env,
+ [&output](const char* str, size_t len) {
+ output += str;
+ return len;
+ });
+
+ write_test_doc(writer);
+ assert(output == writer_test_doc);
+
+ return 0;
+}
+
+static int
+test_env()
+{
+ serd::Env env{serd::make_uri("http://example.org/")};
+ assert(env.base_uri() == serd::make_uri("http://example.org/"));
+
+ env = serd::Env{};
+
+ const auto base = serd::make_uri("http://drobilla.net/");
+ env.set_base_uri(base);
+ assert(env.base_uri() == base);
+
+ env.set_prefix(serd::make_string("eg"),
+ serd::make_uri("http://drobilla.net/"));
+ env.set_prefix("eg", serd::make_uri("http://example.org/"));
+
+ assert(env.qualify(serd::make_uri("http://example.org/foo")) ==
+ serd::make_curie("eg:foo"));
+
+ assert(env.expand(serd::make_uri("foo")) ==
+ serd::make_uri("http://drobilla.net/foo"));
+
+ serd::Env copied{env};
+ assert(copied.qualify(serd::make_uri("http://example.org/foo")) ==
+ serd::make_curie("eg:foo"));
+
+ assert(copied.expand(serd::make_uri("foo")) ==
+ serd::make_uri("http://drobilla.net/foo"));
+
+ serd::Env assigned;
+ assigned = env;
+ auto curie = env.qualify(serd::make_uri("http://example.org/foo"));
+
+ assert(assigned.qualify(serd::make_uri("http://example.org/foo")) ==
+ serd::make_curie("eg:foo"));
+
+ assert(assigned.expand(serd::make_uri("foo")) ==
+ serd::make_uri("http://drobilla.net/foo"));
+
+ serd::Sink sink;
+ serd::Optional<serd::Node> ns_name;
+ serd::Optional<serd::Node> ns_uri;
+
+ sink.set_prefix_func([&](serd::NodeView name, serd::NodeView uri) {
+ ns_name = name;
+ ns_uri = uri;
+ return serd::Status::success;
+ });
+
+ env.write_prefixes(sink);
+ assert(ns_name == serd::make_string("eg"));
+ assert(ns_uri == serd::make_uri("http://example.org/"));
+
+ return 0;
+}
+
+static int
+test_statement()
+{
+ const auto s = serd::make_uri("http://example.org/s");
+ const auto p = serd::make_uri("http://example.org/p");
+ const auto o = serd::make_uri("http://example.org/o");
+ const auto g = serd::make_uri("http://example.org/g");
+ const auto cur = serd::Cursor{serd::make_string("test"), 42, 53};
+
+ const auto t_statement = serd::Statement{s, p, o};
+
+ assert(t_statement.subject() == s);
+ assert(t_statement.predicate() == p);
+ assert(t_statement.object() == o);
+ assert(!t_statement.graph());
+ assert(!t_statement.cursor());
+
+ const auto q_statement = serd::Statement{s, p, o, g, cur};
+ assert(q_statement.subject() == s);
+ assert(q_statement.predicate() == p);
+ assert(q_statement.object() == o);
+ assert(q_statement.graph() == g);
+ assert(q_statement.cursor() == cur);
+
+ assert(q_statement.node(serd::Field::subject) == s);
+ assert(q_statement.node(serd::Field::predicate) == p);
+ assert(q_statement.node(serd::Field::object) == o);
+ assert(q_statement.node(serd::Field::graph) == g);
+
+ return 0;
+}
+
+static int
+test_model()
+{
+ serd::World world;
+ serd::Model model(world,
+ serd::ModelFlag::index_SPO | serd::ModelFlag::index_OPS);
+
+ assert(model.empty());
+
+ const auto s = serd::make_uri("http://example.org/s");
+ const auto p = serd::make_uri("http://example.org/p");
+ const auto o1 = serd::make_uri("http://example.org/o1");
+ const auto o2 = serd::make_uri("http://example.org/o2");
+
+ serd::NodeView b = world.get_blank();
+ auto r = b.resolve(s);
+
+ model.insert(s, p, o1);
+ model.insert(serd::Statement{s, p, o2});
+
+ assert(!model.empty());
+ assert(model.size() == 2);
+ assert(model.ask(s, p, o1));
+ assert(model.count(s, p, o1) == 1);
+ assert(!model.ask(s, p, s));
+
+ size_t total_count = 0;
+ for (const auto& statement : model) {
+ assert(statement.subject() == s);
+ assert(statement.predicate() == p);
+ assert(statement.object() == o1 || statement.object() == o2);
+ ++total_count;
+ }
+ assert(total_count == 2);
+
+ size_t o1_count = 0;
+ for (const auto& statement : model.range({}, {}, o1)) {
+ assert(statement.subject() == s);
+ assert(statement.predicate() == p);
+ assert(statement.object() == o1);
+ ++o1_count;
+ }
+ assert(o1_count == 1);
+
+ size_t o2_count = 0;
+ for (const auto& statement : model.range({}, {}, o2)) {
+ assert(statement.subject() == s);
+ assert(statement.predicate() == p);
+ assert(statement.object() == o2);
+ ++o2_count;
+ }
+ assert(o2_count == 1);
+
+ assert(model.get({}, p, o1) == s);
+
+ const auto statement = model.get_statement(s, p, {});
+ assert(statement);
+ assert(statement->subject() == s);
+ assert(statement->predicate() == p);
+ assert(statement->object() == o1);
+
+ const auto iter = model.find(s, p, {});
+ assert(iter->subject() == s);
+ assert(iter->predicate() == p);
+ assert(iter->object() == o1);
+
+ serd::Model copy(model);
+ assert(copy == model);
+
+ copy.insert(s, p, s);
+ assert(copy != model);
+
+ return 0;
+}
+
+static int
+test_log()
+{
+ serd::World world;
+ bool called = false;
+ world.set_message_func([&called](const serd::StringView domain,
+ const serd::LogLevel level,
+ const serd::LogFields& fields,
+ const std::string& msg) {
+ assert(domain == "test");
+ assert(fields.at("TEST_EXTRA") == "extra field");
+ assert(level == serd::LogLevel::err);
+ assert(msg == "bad argument to something: 42\n");
+ called = true;
+ return serd::Status::success;
+ });
+
+ const auto success = world.log("test",
+ serd::LogLevel::err,
+ {{"TEST_EXTRA", "extra field"}},
+ "bad argument to %s: %d\n",
+ "something",
+ 42);
+
+ assert(called);
+ assert(success == serd::Status::success);
+
+ world.set_message_func([](const serd::StringView,
+ const serd::LogLevel,
+ const serd::LogFields&,
+ const std::string&) -> serd::Status {
+ throw std::runtime_error("error");
+ });
+
+ const auto failure = world.log("test", serd::LogLevel::err, {}, "failure");
+ assert(failure == serd::Status::err_internal);
+
+ return 0;
+}
+
+int
+main()
+{
+ using TestFunc = int (*)();
+
+ constexpr std::array<TestFunc, 14> tests{{test_operators,
+ test_optional,
+ test_nodes,
+ test_string,
+ test_stringview,
+ // test_base64,
+ test_syntax,
+ test_uri,
+ test_env,
+ test_reader,
+ test_writer_ostream,
+ test_writer_string_sink,
+ test_statement,
+ test_model,
+ test_log}};
+
+ int failed = 0;
+ for (const auto& test : tests) {
+ failed += test();
+ }
+
+ std::cerr << "Failed " << failed << " tests" << std::endl;
+
+ return failed;
+}
diff --git a/meson.build b/meson.build
index 35aeb2ad..81415fe5 100644
--- a/meson.build
+++ b/meson.build
@@ -19,6 +19,10 @@ versioned_name = 'serd' + version_suffix
pkg = import('pkgconfig')
cc = meson.get_compiler('c')
+if add_languages('cpp', required: get_option('bindings_cpp'))
+ cpp = meson.get_compiler('cpp')
+endif
+
# Set ultra strict warnings for developers, if requested
if get_option('strict')
subdir('meson')
@@ -194,6 +198,11 @@ if get_option('tests')
subdir('test')
endif
+# C++ bindings
+if is_variable('cpp')
+ subdir('bindings/cpp')
+endif
+
if meson.version().version_compare('>=0.53.0')
summary('Tests', get_option('tests'), bool_yn: true)
summary('Utilities', get_option('utils'), bool_yn: true)
diff --git a/meson_options.txt b/meson_options.txt
index 4cca6ffb..2a565b5c 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,3 +1,6 @@
+option('bindings_cpp', type: 'feature', value: 'auto', yield: true,
+ description: 'Build C++ bindings')
+
option('docs', type: 'feature', value: 'auto', yield: true,
description: 'Build documentation')