aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2018-06-16 10:26:47 -0400
committerDavid Robillard <d@drobilla.net>2019-04-13 19:48:23 +0200
commit7e15cdbd8ce83771098a90d70b086f833ab0941f (patch)
treea1ea18e9647c9e8dd84818ac6690da584407c996
parent5878f2aeb5e069947761e0c9629b7741b63d78f9 (diff)
downloadserd-7e15cdbd8ce83771098a90d70b086f833ab0941f.tar.gz
serd-7e15cdbd8ce83771098a90d70b086f833ab0941f.tar.bz2
serd-7e15cdbd8ce83771098a90d70b086f833ab0941f.zip
WIP: Add C++ bindings
-rw-r--r--doc/reference.doxygen.in3
-rw-r--r--serd/detail/Copyable.hpp101
-rw-r--r--serd/detail/Flags.hpp73
-rw-r--r--serd/detail/Optional.hpp129
-rw-r--r--serd/detail/StringView.hpp148
-rw-r--r--serd/detail/Wrapper.hpp87
-rw-r--r--serd/serd.hpp1456
-rw-r--r--tests/serd_cxx_test.cpp526
-rw-r--r--wscript21
9 files changed, 2541 insertions, 3 deletions
diff --git a/doc/reference.doxygen.in b/doc/reference.doxygen.in
index 8e11f865..f4d80c95 100644
--- a/doc/reference.doxygen.in
+++ b/doc/reference.doxygen.in
@@ -804,6 +804,7 @@ WARN_LOGFILE =
# Note: If this tag is empty the current directory is searched.
INPUT = @SERD_SRCDIR@/serd/serd.h \
+ @SERD_SRCDIR@/serd/serd.hpp \
@SERD_SRCDIR@/doc/mainpage.md
# This tag can be used to specify the character encoding of the source files
@@ -871,7 +872,7 @@ EXCLUDE_PATTERNS =
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories use the pattern */test/*
-EXCLUDE_SYMBOLS =
+EXCLUDE_SYMBOLS = detail
# The EXAMPLE_PATH tag can be used to specify one or more files or directories
# that contain example code fragments that are included (see the \include
diff --git a/serd/detail/Copyable.hpp b/serd/detail/Copyable.hpp
new file mode 100644
index 00000000..225fc18c
--- /dev/null
+++ b/serd/detail/Copyable.hpp
@@ -0,0 +1,101 @@
+/*
+ Copyright 2019 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 "serd/serd.h"
+
+#include <cstddef>
+#include <memory>
+#include <type_traits>
+
+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*), void Free(Mutable<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*),
+ void Free(typename std::remove_const<T>::type*)>
+typename std::enable_if<!std::is_const<T>::value, T>::type*
+copy(const T* ptr)
+{
+ return Copy(ptr); // Making a mutable wrapper, copy
+}
+
+/// 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 Copyable : public Wrapper<T, Free>
+{
+public:
+ explicit Copyable(T* ptr) : Wrapper<T, Free>(ptr) {}
+
+ Copyable(const Copyable& wrapper)
+ : Wrapper<T, Free>(copy<T, Copy, Free>(wrapper.cobj()))
+ {
+ }
+
+ template <class U, void UFree(Mutable<U>*)>
+ explicit Copyable(const Copyable<U, Copy, Equals, UFree>& wrapper)
+ : Wrapper<T, Free>(copy<T, Copy, Free>(wrapper.cobj()))
+ {
+ }
+
+ Copyable(Copyable&&) noexcept = default;
+ Copyable& operator=(Copyable&&) noexcept = default;
+ ~Copyable() noexcept = default;
+
+ Copyable& operator=(const Copyable& wrapper)
+ {
+ this->_ptr = std::unique_ptr<T, Deleter<T, Free>>(
+ copy<T, Copy, Free>(wrapper.cobj()));
+ return *this;
+ }
+
+ template <class U, void UFree(Mutable<U>*)>
+ bool operator==(const Copyable<U, Copy, Equals, UFree>& wrapper) const
+ {
+ return Equals(this->cobj(), wrapper.cobj());
+ }
+
+ template <class U, void UFree(Mutable<U>*)>
+ bool operator!=(const Copyable<U, Copy, Equals, UFree>& wrapper) const
+ {
+ return !operator==(wrapper);
+ }
+
+protected:
+ explicit Copyable(std::nullptr_t) : Wrapper<T, Free>(nullptr) {}
+};
+
+} // namespace detail
+} // namespace serd
+
+#endif // SERD_DETAIL_COPYABLE_HPP
diff --git a/serd/detail/Flags.hpp b/serd/detail/Flags.hpp
new file mode 100644
index 00000000..bae2da96
--- /dev/null
+++ b/serd/detail/Flags.hpp
@@ -0,0 +1,73 @@
+/*
+ Copyright 2019 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 "serd/serd.h"
+
+#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(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};
+ }
+
+ // NOLINTNEXTLINE(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/serd/detail/Optional.hpp b/serd/detail/Optional.hpp
new file mode 100644
index 00000000..5ca7f83f
--- /dev/null
+++ b/serd/detail/Optional.hpp
@@ -0,0 +1,129 @@
+/*
+ Copyright 2019 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 "serd/serd.h"
+
+#include <cassert>
+#include <cstddef>
+#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(hicpp-explicit-conversions, modernize-pass-by-value)
+ Optional(const T& value) : _value(value) {}
+
+ // NOLINTNEXTLINE(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(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/serd/detail/StringView.hpp b/serd/detail/StringView.hpp
new file mode 100644
index 00000000..0915daed
--- /dev/null
+++ b/serd/detail/StringView.hpp
@@ -0,0 +1,148 @@
+/*
+ Copyright 2019 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 "serd/serd.h"
+
+#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 : _str{nullptr}, _len{0U} {}
+
+ constexpr StringView(const char* const str, const size_t len) noexcept
+ : _str{str}, _len{len}
+ {
+ }
+
+ // NOLINTNEXTLINE(hicpp-explicit-conversions)
+ StringView(const char* const str) noexcept
+ : _str{str}, _len{str ? strlen(str) : 0}
+ {
+ }
+
+ // NOLINTNEXTLINE(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[](size_t pos) const { return _str[pos]; }
+
+ const char& at(size_t pos) const
+ {
+ if (pos >= size()) {
+ throw std::out_of_range("serd::StringView::at pos");
+ }
+
+ return _str[pos];
+ }
+
+ StringView substr(size_t pos, size_t n = npos) const
+ {
+ if (pos > size()) {
+ throw std::out_of_range("serd::StringView::substr pos");
+ }
+
+ return StringView{data() + pos, std::min(size() - pos, n)};
+ }
+
+ 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);
+ }
+
+ // NOLINTNEXTLINE(hicpp-explicit-conversions)
+ explicit operator std::string() const { return str(); }
+
+ // NOLINTNEXTLINE(hicpp-explicit-conversions)
+ explicit operator const char*() const { return _str; }
+
+private:
+ const char* const _str{};
+ const size_t _len{};
+};
+
+inline bool
+operator==(const detail::StringView& lhs, const std::string& rhs)
+{
+ return lhs.length() == rhs.length() && rhs == lhs.c_str();
+}
+
+inline bool
+operator==(const detail::StringView& lhs, const char* rhs)
+{
+ return !strncmp(lhs.c_str(), rhs, lhs.length());
+}
+
+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/serd/detail/Wrapper.hpp b/serd/detail/Wrapper.hpp
new file mode 100644
index 00000000..5048dfd1
--- /dev/null
+++ b/serd/detail/Wrapper.hpp
@@ -0,0 +1,87 @@
+/*
+ Copyright 2019 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 "serd/serd.h"
+
+#include <cstddef>
+#include <memory>
+
+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;
+
+/// Callable deleter for a C object, noopt for const pointers
+template <typename T, void Free(Mutable<T>*)>
+struct Deleter
+{
+ 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*)
+ {
+ }
+};
+
+/// Generic C++ wrapper for a C object
+template <typename T, void FreeFunc(Mutable<T>*)>
+class Wrapper
+{
+public:
+ using CType = T;
+
+ explicit Wrapper(T* ptr) : _ptr(ptr) {}
+
+ Wrapper(Wrapper&& wrapper) noexcept = default;
+ Wrapper& operator=(Wrapper&& 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<T, FreeFunc>> _ptr;
+};
+
+} // namespace detail
+} // namespace serd
+
+#endif // SERD_DETAIL_WRAPPER_HPP
diff --git a/serd/serd.hpp b/serd/serd.hpp
new file mode 100644
index 00000000..5084e1ad
--- /dev/null
+++ b/serd/serd.hpp
@@ -0,0 +1,1456 @@
+/*
+ Copyright 2018 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 <functional>
+#include <iostream>
+#include <memory>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+/**
+ @defgroup serdxx Serdxx
+ C++ bindings for Serd, a lightweight RDF syntax library.
+ @{
+*/
+
+namespace serd {
+namespace detail {
+
+static inline size_t
+stream_sink(const void* buf, size_t size, size_t nmemb, void* stream) noexcept
+{
+ assert(size == 1);
+ try {
+ auto* const s = static_cast<std::ostream*>(stream);
+ s->write(static_cast<const char*>(buf), std::streamsize(nmemb));
+ if (s->good()) {
+ return nmemb;
+ }
+ } catch (...) {
+ }
+ return 0;
+}
+
+} // namespace detail
+
+template <typename T>
+using Optional = detail::Optional<T>;
+
+using StringView = detail::StringView;
+
+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;
+}
+
+/// Return status code
+enum class Status {
+ success = SERD_SUCCESS, ///< No error
+ failure = SERD_FAILURE, ///< Non-fatal failure
+ err_unknown = SERD_ERR_UNKNOWN, ///< Unknown error
+ err_bad_syntax = SERD_ERR_BAD_SYNTAX, ///< Invalid syntax
+ err_bad_arg = SERD_ERR_BAD_ARG, ///< Invalid argument
+ err_bad_iter = SERD_ERR_BAD_ITER, ///< Use of invalidated iterator
+ err_not_found = SERD_ERR_NOT_FOUND, ///< Not found
+ err_id_clash = SERD_ERR_ID_CLASH, ///< Clashing blank node IDs
+ err_bad_curie = SERD_ERR_BAD_CURIE, ///< Invalid CURIE
+ err_internal = SERD_ERR_INTERNAL, ///< Unexpected internal error
+ err_overflow = SERD_ERR_OVERFLOW, ///< Stack overflow
+ err_invalid = SERD_ERR_INVALID ///< Invalid data
+};
+
+/// RDF syntax type
+enum class Syntax {
+ Turtle = SERD_TURTLE, ///< Terse RDF Triple Language
+ NTriples = SERD_NTRIPLES, ///< Line-based RDF triples
+ NQuads = SERD_NQUADS, ///< Line-based RDF quads (NTriples with graphs)
+ TriG = SERD_TRIG ///< Terse RDF quads (Turtle with graphs)
+};
+
+/// Flags indicating inline abbreviation information for a statement
+enum class StatementFlag {
+ empty_S = SERD_EMPTY_S, ///< Empty blank node subject
+ anon_S = SERD_ANON_S, ///< Start of anonymous subject
+ anon_O = SERD_ANON_O, ///< Start of anonymous object
+ list_S = SERD_LIST_S, ///< Start of list subject
+ list_O = SERD_LIST_O, ///< Start of list object
+ terse_S = SERD_TERSE_S, ///< Terse serialisation of new subject
+ terse_O = SERD_TERSE_O ///< Terse serialisation of new object
+};
+
+using StatementFlags = detail::Flags<StatementFlag>;
+
+/// Flags that control style for a model serialisation
+enum class SerialisationFlag {
+ no_inline_objects = SERD_NO_INLINE_OBJECTS, ///< Disable object inlining
+};
+
+using SerialisationFlags = detail::Flags<SerialisationFlag>;
+
+/**
+ Type of a syntactic RDF node
+
+ This is more precise than the type of an abstract RDF node. An abstract
+ node is either a resource, literal, or blank. In syntax there are two ways
+ to refer to a resource (by URI or CURIE) and two ways to refer to a blank
+ (by ID or anonymously). Anonymous (inline) blank nodes are expressed using
+ SerdStatementFlags rather than this type.
+*/
+enum class NodeType {
+ /**
+ The type of a nonexistent node
+
+ This type is useful as a sentinel, but is never emitted by the reader.
+ */
+ nothing = SERD_NOTHING,
+
+ /**
+ Literal value
+
+ A literal optionally has either a language, or a datatype (not both).
+ */
+ literal = SERD_LITERAL,
+
+ /**
+ URI (absolute or relative)
+
+ Value is an unquoted URI string, which is either a relative reference
+ with respect to the current base URI (e.g. "foo/bar"), or an absolute
+ URI (e.g. "http://example.org/foo").
+ @see <a href="http://tools.ietf.org/html/rfc3986">RFC3986</a>.
+ */
+ URI = SERD_URI,
+
+ /**
+ CURIE, a shortened URI
+
+ Value is an unquoted CURIE string relative to the current environment,
+ e.g. "rdf:type".
+ @see <a href="http://www.w3.org/TR/curie">CURIE Syntax 1.0</a>
+ */
+ CURIE = SERD_CURIE,
+
+ /**
+ A blank node
+
+ Value is a blank node ID, e.g. "id3", which is meaningful only within
+ this serialisation.
+ @see <a href="http://www.w3.org/TeamSubmission/turtle#nodeID">Turtle
+ <tt>nodeID</tt></a>
+ */
+ blank = SERD_BLANK
+};
+
+/// Flags indicating certain string properties relevant to serialisation
+enum class NodeFlag {
+ has_newline = SERD_HAS_NEWLINE, ///< Contains line breaks
+ has_quote = SERD_HAS_QUOTE, ///< Contains quotes
+ has_datatype = SERD_HAS_DATATYPE, ///< Literal node has datatype
+ has_language = SERD_HAS_LANGUAGE ///< Literal node has language
+};
+
+using NodeFlags = detail::Flags<NodeFlag>;
+
+/// Field in a statement
+enum class Field {
+ subject = SERD_SUBJECT, ///< Subject
+ predicate = SERD_PREDICATE, ///< Predicate ("key")
+ object = SERD_OBJECT, ///< Object ("value")
+ graph = SERD_GRAPH ///< Graph ("context")
+};
+
+/// Indexing option
+enum class ModelFlag {
+ index_SPO = SERD_INDEX_SPO, ///< Subject, Predicate, Object
+ index_SOP = SERD_INDEX_SOP, ///< Subject, Object, Predicate
+ index_OPS = SERD_INDEX_OPS, ///< Object, Predicate, Subject
+ index_OSP = SERD_INDEX_OSP, ///< Object, Subject, Predicate
+ index_PSO = SERD_INDEX_PSO, ///< Predicate, Subject, Object
+ index_POS = SERD_INDEX_POS, ///< Predicate, Object, Subject
+ index_graphs = SERD_INDEX_GRAPHS, ///< Support multiple graphs in model
+ store_cursors = SERD_STORE_CURSORS ///< Store original cursor of statements
+};
+
+using ModelFlags = detail::Flags<ModelFlag>;
+
+/// Log message level
+enum class LogLevel {
+ info = SERD_LOG_LEVEL_INFO, ///< Normal informative message
+ warning = SERD_LOG_LEVEL_WARNING, ///< Warning
+ error = SERD_LOG_LEVEL_ERROR ///< Error
+};
+
+/**
+ Syntax style options
+
+ The style of the writer output can be controlled by ORing together
+ values from this enumeration. Note that some options are only supported
+ for some syntaxes (e.g. NTriples does not support abbreviation and is
+ always ASCII).
+*/
+enum class WriterFlag {
+ ascii = SERD_WRITE_ASCII, ///< Escape all non-ASCII characters
+ terse = SERD_WRITE_TERSE ///< Write terser output without newlines
+};
+
+using WriterFlags = detail::Flags<WriterFlag>;
+
+/**
+ @name String Utilities
+ @{
+*/
+
+inline const char*
+strerror(const Status status)
+{
+ return serd_strerror(static_cast<SerdStatus>(status));
+}
+
+/**
+ @}
+ @name Node
+ @{
+*/
+
+template <typename CObj>
+using NodeHandle = detail::
+ Copyable<CObj, serd_node_copy, serd_node_equals, serd_node_free>;
+
+template <typename CObj>
+class NodeBase;
+
+using Node = NodeBase<SerdNode>;
+using NodeView = NodeBase<const SerdNode>;
+
+template <typename CObj>
+class NodeBase : public NodeHandle<CObj>
+{
+public:
+ using Base = NodeHandle<CObj>;
+ using Base::cobj;
+
+ explicit NodeBase(CObj* ptr) : Base(ptr) {}
+
+ template <typename C>
+ // NOLINTNEXTLINE(hicpp-explicit-conversions)
+ NodeBase(const NodeBase<C>& node) : Base{node}
+ {
+ }
+
+ NodeType type() const { return NodeType(serd_node_get_type(cobj())); }
+ const char* c_str() const { return serd_node_get_string(cobj()); }
+ StringView str() const { return StringView{c_str(), length()}; }
+ size_t size() const { return serd_node_get_length(cobj()); }
+ size_t length() const { return serd_node_get_length(cobj()); }
+
+ NodeFlags flags() const { return NodeFlags(serd_node_get_flags(cobj())); }
+
+ Optional<NodeView> datatype() const;
+ Optional<NodeView> language() const;
+
+ Node resolve(NodeView base) const
+ {
+ return Node(serd_node_resolve(cobj(), base.cobj()));
+ }
+
+ bool operator<(NodeView node) const
+ {
+ return serd_node_compare(cobj(), node.cobj()) < 0;
+ }
+
+ explicit operator std::string() const
+ {
+ return std::string(serd_node_get_string(cobj()),
+ serd_node_get_length(cobj()));
+ }
+
+ explicit operator StringView() const
+ {
+ return StringView(serd_node_get_string(cobj()),
+ serd_node_get_length(cobj()));
+ }
+
+ 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>
+NodeBase<CObj>::datatype() const
+{
+ return NodeView(serd_node_get_datatype(cobj()));
+}
+
+template <typename CObj>
+Optional<NodeView>
+NodeBase<CObj>::language() const
+{
+ return NodeView(serd_node_get_language(cobj()));
+}
+
+inline std::ostream&
+operator<<(std::ostream& os, const NodeView& node)
+{
+ return os << node.c_str();
+}
+
+inline Node
+make_string(StringView str)
+{
+ return Node(serd_new_string(str.data()));
+}
+
+inline Node
+make_plain_literal(StringView str, StringView lang)
+{
+ return Node(serd_new_plain_literal(str.data(), lang.data()));
+}
+
+inline Node
+make_typed_literal(StringView str, const NodeView& datatype)
+{
+ return Node(serd_new_typed_literal(str.data(), datatype.cobj()));
+}
+
+inline Node
+make_blank(StringView str)
+{
+ return Node(serd_new_blank(str.data()));
+}
+
+inline Node
+make_curie(StringView str)
+{
+ return Node(serd_new_curie(str.data()));
+}
+
+inline Node
+make_uri(StringView str)
+{
+ return Node(serd_new_uri(str.data()));
+}
+
+inline Node
+make_resolved_uri(StringView str, const NodeView& base)
+{
+ return Node(serd_new_resolved_uri(str.data(), base.cobj()));
+}
+
+inline Node
+make_file_uri(StringView path)
+{
+ return Node(serd_new_file_uri(path.data(), nullptr));
+}
+
+inline Node
+make_file_uri(StringView path, StringView hostname)
+{
+ return Node(serd_new_file_uri(path.data(), hostname.data()));
+}
+
+inline Node
+make_relative_uri(StringView str,
+ const NodeView& base,
+ Optional<NodeView> root = {})
+{
+ return Node(serd_new_relative_uri(str.data(), base.cobj(), root.cobj()));
+}
+
+inline Node
+make_decimal(double d, unsigned frac_digits, Optional<NodeView> datatype = {})
+{
+ return Node(serd_new_decimal(d, frac_digits, datatype.cobj()));
+}
+
+inline Node
+make_integer(int64_t i, Optional<NodeView> datatype = {})
+{
+ return Node(serd_new_integer(i, datatype.cobj()));
+}
+
+inline Node
+make_blob(const void* buf,
+ size_t size,
+ bool wrap_lines,
+ Optional<NodeView> datatype = {})
+{
+ return Node(serd_new_blob(buf, size, wrap_lines, datatype.cobj()));
+}
+
+/**
+ @}
+ @name URI
+ @{
+*/
+
+inline std::string
+file_uri_parse(StringView uri, std::string* hostname = nullptr)
+{
+ char* c_hostname = nullptr;
+ char* c_path = serd_file_uri_parse(uri.data(), &c_hostname);
+ if (hostname && c_hostname) {
+ *hostname = c_hostname;
+ }
+ const std::string path(c_path);
+ serd_free(c_hostname);
+ serd_free(c_path);
+ return path;
+}
+
+/**
+ 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_URI_NULL)
+ {
+ serd_uri_parse(str.data(), &_uri);
+ }
+
+ explicit URI(const NodeView& node) : _uri(SERD_URI_NULL)
+ {
+ serd_uri_parse(node.c_str(), &_uri);
+ }
+
+ explicit URI(const SerdURI& uri) : _uri(uri) {}
+
+ Component scheme() const { return make_component(_uri.scheme); }
+ Component authority() const { return make_component(_uri.authority); }
+ Component path_base() const { return make_component(_uri.path_base); }
+ 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
+ {
+ SerdURI resolved = SERD_URI_NULL;
+ serd_uri_resolve(&_uri, &base._uri, &resolved);
+ return URI{resolved};
+ }
+
+ const SerdURI* cobj() const { return &_uri; }
+
+private:
+ static Component make_component(const SerdStringView slice)
+ {
+ return slice.buf ? Component{slice.buf, slice.len} : Component{};
+ }
+
+ SerdURI _uri;
+};
+
+inline std::ostream&
+operator<<(std::ostream& os, const URI& uri)
+{
+ serd_uri_serialise(uri.cobj(), detail::stream_sink, &os);
+ return os;
+}
+
+/**
+ @}
+ @name Cursor
+ @{
+*/
+
+template <typename CObj>
+using CursorHandle = detail::
+ Copyable<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* const cursor) : Base(cursor) {}
+
+ template <typename C>
+ // NOLINTNEXTLINE(hicpp-explicit-conversions)
+ CursorWrapper(const CursorWrapper<C>& cursor) : CursorWrapper{cursor}
+ {
+ }
+
+ NodeView name() const { return NodeView(serd_cursor_get_name(cobj())); }
+ unsigned line() const { return serd_cursor_get_line(cobj()); }
+ unsigned column() const { return serd_cursor_get_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(const NodeView& name, const unsigned line, const unsigned col)
+ : CursorData{Node{name}}
+ , CursorWrapper{serd_cursor_new(name_node.cobj(), line, col)}
+ {
+ }
+
+ template <typename C>
+ // NOLINTNEXTLINE(hicpp-explicit-conversions)
+ Cursor(const CursorWrapper<C>& 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
+ @{
+*/
+
+/// A message description
+struct Message
+{
+ Status status; ///< Status code
+ LogLevel level; ///< Log level
+ Optional<CursorView> cursor; ///< Origin of message
+ std::string string; ///< Message string
+};
+
+/**
+ @}
+ @name Event Handlers
+ @{
+*/
+
+/**
+ @}
+ @name World
+ @{
+*/
+
+class World : public detail::Wrapper<SerdWorld, serd_world_free>
+{
+public:
+ using MessageSink = std::function<Status(const Message&)>;
+
+ World() : Wrapper(serd_world_new()) {}
+
+ explicit World(SerdWorld* ptr) : Wrapper(ptr) {}
+
+ NodeView get_blank() { return NodeView(serd_world_get_blank(cobj())); }
+
+ void set_message_sink(MessageSink msg_sink)
+ {
+ _msg_sink = std::move(msg_sink);
+ serd_world_set_message_sink(cobj(), s_message_sink, this);
+ }
+
+ Status log(const SerdMessage* msg)
+ {
+ return static_cast<Status>(serd_world_log(cobj(), msg));
+ }
+
+ SERD_LOG_FUNC(5, 6)
+ Status log(const Status status,
+ const LogLevel level,
+ const SerdCursor* const cursor,
+ const char* const fmt,
+ ...)
+ {
+ va_list args;
+ va_start(args, fmt);
+ const SerdMessage msg = {static_cast<SerdStatus>(status),
+ static_cast<SerdLogLevel>(level),
+ cursor,
+ fmt,
+ &args};
+ const SerdStatus st = serd_world_log(cobj(), &msg);
+ va_end(args);
+ return static_cast<Status>(st);
+ }
+
+private:
+ static std::string format(const char* fmt, va_list* args) noexcept
+ {
+ va_list args_copy;
+ va_copy(args_copy, *args);
+ const int n_bytes = vsnprintf(nullptr, 0, fmt, args_copy);
+ va_end(args_copy);
+
+#if __cplusplus >= 201703L
+ std::string result(n_bytes, '\0');
+ vsnprintf(result.data(), n_bytes + 1, 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_message_sink(void* handle, const SerdMessage* msg) noexcept
+ {
+ const auto* const self = static_cast<const World*>(handle);
+ try {
+ Message message{static_cast<Status>(msg->status),
+ static_cast<LogLevel>(msg->level),
+ CursorView{msg->cursor},
+ format(msg->fmt, msg->args)};
+ return static_cast<SerdStatus>(self->_msg_sink(message));
+ } catch (...) {
+ return SERD_ERR_INTERNAL;
+ }
+ }
+
+ MessageSink _msg_sink;
+};
+
+/**
+ @}
+ @name Statement
+ @{
+*/
+
+template <typename CObj>
+using StatementHandle = detail::Copyable<CObj,
+ serd_statement_copy,
+ serd_statement_equals,
+ serd_statement_free>;
+
+template <typename CObj>
+class StatementBase;
+
+using StatementView = StatementBase<const SerdStatement>;
+
+/// Extra data managed by mutable (user created) Statement
+struct StatementData
+{
+ Node _subject;
+ Node _predicate;
+ Node _object;
+ Node _graph;
+ Optional<Cursor> _cursor;
+};
+
+template <typename CObj>
+class StatementBase : public StatementHandle<CObj>
+{
+public:
+ using Base = StatementHandle<CObj>;
+ using Base::cobj;
+
+ explicit StatementBase(CObj* statement) : Base{statement} {}
+
+ template <typename C>
+ // NOLINTNEXTLINE(hicpp-explicit-conversions)
+ StatementBase(const StatementBase<C>& statement) : Base{statement}
+ {
+ }
+
+ NodeView node(Field field) const
+ {
+ return NodeView(
+ serd_statement_get_node(cobj(), static_cast<SerdField>(field)));
+ }
+
+ NodeView subject() const
+ {
+ return NodeView(serd_statement_get_subject(cobj()));
+ }
+
+ NodeView predicate() const
+ {
+ return NodeView(serd_statement_get_predicate(cobj()));
+ }
+
+ NodeView object() const
+ {
+ return NodeView(serd_statement_get_object(cobj()));
+ }
+
+ NodeView graph() const
+ {
+ return NodeView(serd_statement_get_graph(cobj()));
+ }
+
+ Optional<CursorView> cursor() const
+ {
+ return CursorView(serd_statement_get_cursor(cobj()));
+ }
+
+ StatementBase* operator->() { return this; }
+ const StatementBase* operator->() const { return this; }
+};
+
+class Statement : public StatementData, public StatementBase<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>{}}
+ , StatementBase{serd_statement_new(_subject.cobj(),
+ _predicate.cobj(),
+ _object.cobj(),
+ _graph.cobj(),
+ _cursor.cobj())}
+ {
+ }
+
+ template <typename C>
+ // NOLINTNEXTLINE(hicpp-explicit-conversions)
+ Statement(const StatementBase<C>& statement)
+ : StatementData{statement.subject(),
+ statement.predicate(),
+ statement.object(),
+ statement.graph(),
+ statement.cursor() ? *statement.cursor()
+ : Optional<Cursor>{}}
+ , StatementBase{statement}
+ {
+ }
+};
+
+/**
+ @}
+ @name Sink
+ @{
+*/
+
+/**
+ Sink function for base URI changes
+
+ Called whenever the base URI of the serialisation changes.
+*/
+using BaseFunc = std::function<Status(NodeView)>;
+
+/**
+ Sink function for namespace definitions
+
+ Called whenever a prefix is defined in the serialisation.
+*/
+using PrefixFunc = std::function<Status(NodeView name, NodeView uri)>;
+
+/**
+ Sink function for statements
+
+ Called for every RDF statement in the serialisation.
+*/
+using StatementFunc = std::function<Status(StatementFlags, StatementView)>;
+
+/**
+ Sink function for anonymous node end markers
+
+ This is called to indicate that the given anonymous node will no longer be
+ referred to by any future statements (the anonymous serialisation of the
+ node is finished).
+*/
+using EndFunc = std::function<Status(NodeView)>;
+
+template <typename CSink>
+class SinkBase : public detail::Wrapper<CSink, serd_sink_free>
+{
+public:
+ explicit SinkBase(CSink* ptr) : detail::Wrapper<CSink, serd_sink_free>(ptr)
+ {
+ }
+
+ template <typename C>
+ // NOLINTNEXTLINE(hicpp-explicit-conversions)
+ SinkBase(const SinkBase<C>& sink)
+ : detail::Wrapper<CSink, serd_sink_free>(sink)
+ {
+ }
+
+ Status base(const NodeView& uri) const
+ {
+ return Status(serd_sink_write_base(this->cobj(), uri.cobj()));
+ }
+
+ Status prefix(const NodeView& name, const NodeView& uri) const
+ {
+ return Status(
+ serd_sink_write_prefix(this->cobj(), name.cobj(), uri.cobj()));
+ }
+
+ Status statement(StatementFlags flags, StatementView statement) const
+ {
+ return Status(serd_sink_write_statement(
+ this->cobj(), flags, statement.cobj()));
+ }
+
+ 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()));
+ }
+
+ Status end(const NodeView& node) const
+ {
+ return Status(serd_sink_write_end(this->cobj(), node.cobj()));
+ }
+
+private:
+ static SerdStatus s_base(void* handle, const SerdNode* uri) noexcept
+ {
+ auto* const sink = static_cast<const SinkBase*>(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
+ {
+ auto* const sink = static_cast<const SinkBase*>(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
+ {
+ auto* const sink = static_cast<const SinkBase*>(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
+ {
+ auto* const sink = static_cast<const SinkBase*>(handle);
+ return sink->_end_func ? SerdStatus(sink->_end_func(NodeView(node)))
+ : SERD_SUCCESS;
+ }
+};
+
+class SinkView : public SinkBase<const SerdSink>
+{
+public:
+ explicit SinkView(const SerdSink* ptr) : SinkBase<const SerdSink>(ptr) {}
+
+ // NOLINTNEXTLINE(hicpp-explicit-conversions)
+ SinkView(const SinkBase<SerdSink>& sink)
+ : SinkBase<const SerdSink>(sink.cobj())
+ {
+ }
+};
+
+class Sink : public SinkBase<SerdSink>
+{
+public:
+ Sink() : SinkBase(serd_sink_new(this, nullptr)) {}
+
+ // EnvRef env() const { return serd_sink_get_env(cobj()); }
+
+ Status set_base_func(BaseFunc base_func)
+ {
+ _base_func = std::move(base_func);
+ return Status(serd_sink_set_base_func(cobj(), s_base));
+ }
+
+ Status set_prefix_func(PrefixFunc prefix_func)
+ {
+ _prefix_func = std::move(prefix_func);
+ return Status(serd_sink_set_prefix_func(cobj(), s_prefix));
+ }
+
+ Status set_statement_func(StatementFunc statement_func)
+ {
+ _statement_func = std::move(statement_func);
+ return Status(serd_sink_set_statement_func(cobj(), s_statement));
+ }
+
+ Status set_end_func(EndFunc end_func)
+ {
+ _end_func = std::move(end_func);
+ return Status(serd_sink_set_end_func(cobj(), s_end));
+ }
+
+private:
+ static SerdStatus s_base(void* handle, const SerdNode* uri) noexcept
+ {
+ 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
+ {
+ 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
+ {
+ 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
+ {
+ auto* const sink = static_cast<const Sink*>(handle);
+ return sink->_end_func ? SerdStatus(sink->_end_func(NodeView(node)))
+ : SERD_SUCCESS;
+ }
+
+ BaseFunc _base_func;
+ PrefixFunc _prefix_func;
+ StatementFunc _statement_func;
+ EndFunc _end_func;
+};
+
+/**
+ @}
+ @name Environment
+ @{
+*/
+
+template <typename CObj>
+using EnvHandle =
+ detail::Copyable<CObj, serd_env_copy, serd_env_equals, serd_env_free>;
+
+template <typename CObj>
+class EnvWrapper : public EnvHandle<CObj>
+{
+public:
+ using Base = EnvHandle<CObj>;
+ using Base::cobj;
+
+ explicit EnvWrapper(CObj* ptr) : Base(ptr) {}
+
+ /// Return the base URI
+ NodeView base_uri() const
+ {
+ return NodeView(serd_env_get_base_uri(cobj()));
+ }
+
+ /// 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(serd_env_new(nullptr)) {}
+ explicit Env(const NodeView& base) : EnvWrapper(serd_env_new(base.cobj()))
+ {
+ }
+ // explicit Env(SerdEnv* ptr) : EnvWrapper(ptr) {}
+ // explicit Env(const SerdEnv* ptr) : EnvWrapper(serd_env_copy(ptr)) {}
+ // explicit Env(const EnvRef& ref) : EnvWrapper(serd_env_copy(ref.cobj()))
+ // {}
+
+ /// Set the base URI
+ Status set_base_uri(const NodeView& uri)
+ {
+ return Status(serd_env_set_base_uri(cobj(), uri.cobj()));
+ }
+
+ /// Set a namespace prefix
+ Status set_prefix(const NodeView& name, const NodeView& uri)
+ {
+ return Status(serd_env_set_prefix(cobj(), name.cobj(), uri.cobj()));
+ }
+
+ /// Set a namespace prefix
+ Status set_prefix(StringView name, StringView uri)
+ {
+ return Status(serd_env_set_prefix_from_strings(
+ cobj(), name.data(), uri.data()));
+ }
+};
+
+/**
+ @}
+ @name Reader
+ @{
+*/
+
+class Reader : public detail::Wrapper<SerdReader, serd_reader_free>
+{
+public:
+ Reader(World& world, Syntax syntax, SinkView sink, size_t stack_size)
+ : Wrapper(serd_reader_new(world.cobj(),
+ SerdSyntax(syntax),
+ sink.cobj(),
+ stack_size))
+ {
+ }
+
+ void set_strict(bool strict) { serd_reader_set_strict(cobj(), strict); }
+
+ void add_blank_prefix(StringView prefix)
+ {
+ serd_reader_add_blank_prefix(cobj(), prefix.data());
+ }
+
+ Status start_file(StringView uri, bool bulk = true)
+ {
+ return Status(serd_reader_start_file(cobj(), uri.data(), bulk));
+ }
+
+ Status start_file(const NodeView& uri, bool bulk = true)
+ {
+ if (uri.type() != NodeType::URI) {
+ return Status::err_bad_arg;
+ }
+
+ return Status(start_file(file_uri_parse(uri.str()), bulk));
+ }
+
+ Status start_stream(FILE* stream,
+ Optional<NodeView> name = {},
+ size_t page_size = 1)
+ {
+ return Status(serd_reader_start_stream(
+ cobj(),
+ reinterpret_cast<SerdReadFunc>(fread),
+ reinterpret_cast<SerdStreamErrorFunc>(ferror),
+ stream,
+ name.cobj(),
+ page_size));
+ }
+
+ Status start_stream(std::istream& stream,
+ Optional<NodeView> name = {},
+ size_t page_size = 1)
+ {
+ return Status(serd_reader_start_stream(cobj(),
+ s_stream_read,
+ s_stream_error,
+ &stream,
+ name.cobj(),
+ page_size));
+ }
+
+ Status start_string(StringView utf8, Optional<NodeView> name = {})
+ {
+ return Status(
+ serd_reader_start_string(cobj(), utf8.data(), name.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
+ @{
+*/
+
+/**
+ 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 TextSink
+{
+public:
+ explicit TextSink(WriteFunc write_func) : _write_func(std::move(write_func))
+ {
+ }
+
+ explicit TextSink(std::ostream& stream)
+ : _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<TextSink*>(sink);
+
+ try {
+ return self->_write_func(static_cast<const char*>(buf), nmemb);
+ } catch (...) {
+ }
+ return 0;
+ }
+
+private:
+ WriteFunc _write_func;
+};
+
+class Writer : public detail::Wrapper<SerdWriter, serd_writer_free>
+{
+public:
+ Writer(World& world,
+ const Syntax syntax,
+ const WriterFlags flags,
+ Env& env,
+ WriteFunc sink)
+ : Wrapper(serd_writer_new(world.cobj(),
+ SerdSyntax(syntax),
+ flags,
+ env.cobj(),
+ TextSink::s_write,
+ &_text_sink))
+ , _text_sink(std::move(sink))
+ {
+ }
+
+ Writer(World& world,
+ const Syntax syntax,
+ const WriterFlags flags,
+ Env& env,
+ std::ostream& stream)
+ : Wrapper(serd_writer_new(world.cobj(),
+ SerdSyntax(syntax),
+ flags,
+ env.cobj(),
+ TextSink::s_write,
+ &_text_sink))
+ , _text_sink(stream)
+ {
+ }
+
+ SinkView sink() { return SinkView{serd_writer_get_sink(cobj())}; }
+
+ // EnvView env() { return EnvView{serd_writer_get_env(cobj())}; }
+
+ Status set_root_uri(const NodeView& uri)
+ {
+ return Status(serd_writer_set_root_uri(cobj(), uri.cobj()));
+ }
+
+ SerdStatus finish() { return serd_writer_finish(cobj()); }
+
+private:
+ TextSink _text_sink;
+};
+
+/**
+ @}
+*/
+
+template <typename CObj>
+using IterHandle = detail::
+ Copyable<CObj, serd_iter_copy, serd_iter_equals, serd_iter_free>;
+
+template <typename CObj>
+class IterBase : public IterHandle<CObj>
+{
+public:
+ using Base = IterHandle<CObj>;
+ using Base::cobj;
+
+ explicit IterBase(CObj* ptr) : Base(ptr) {}
+
+ // template <typename = std::enable_if<!std::is_const<CObj>::value>>
+ // IterBase(const IterBase<const CObj, detail::no_free>& other) :
+ // Base(other)
+ // {
+ // }
+
+ StatementView operator*() const
+ {
+ return StatementView(serd_iter_get(cobj()));
+ }
+
+ StatementView operator->() const
+ {
+ return StatementView{serd_iter_get(cobj())};
+ }
+};
+
+using IterView = IterBase<const SerdIter>;
+
+class Iter : public IterBase<SerdIter>
+{
+public:
+ explicit Iter(SerdIter* ptr) : IterBase(ptr) {}
+ explicit Iter(const SerdIter* ptr) : IterBase(serd_iter_copy(ptr)) {}
+ explicit Iter(IterView ref) : IterBase(serd_iter_copy(ref.cobj())) {}
+
+ IterBase& operator++()
+ {
+ serd_iter_next(this->cobj());
+ return *this;
+ }
+};
+
+class Range : public detail::Copyable<SerdRange,
+ serd_range_copy,
+ serd_range_equals,
+ serd_range_free>
+{
+public:
+ using Base = detail::Copyable<SerdRange,
+ serd_range_copy,
+ serd_range_equals,
+ serd_range_free>;
+
+ explicit Range(SerdRange* r) : Copyable(r) {}
+ // explicit Range(const SerdRange* r) : Copyable(r) {}
+
+ Iter begin() const { return Iter(serd_range_begin(cobj())); }
+ Iter end() const { return Iter(serd_range_end(cobj())); }
+
+ Status serialise(SinkView sink, SerialisationFlags flags = {})
+ {
+ return Status(serd_range_serialise(cobj(), sink.cobj(), flags));
+ }
+};
+
+using ModelHandle = detail::Copyable<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()));
+ }
+
+ 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())); }
+
+ iterator end() const { return iterator(serd_model_end(cobj())); }
+
+private:
+ friend class detail::Optional<Model>;
+ explicit Model(std::nullptr_t) : Copyable(nullptr) {}
+};
+
+class Inserter : public detail::Wrapper<SerdInserter, serd_inserter_free>
+{
+public:
+ Inserter(Model& model, Env& env, Optional<NodeView> default_graph = {})
+ : Wrapper(serd_inserter_new(model.cobj(),
+ env.cobj(),
+ default_graph.cobj()))
+ {
+ }
+
+ SinkView sink() { return SinkView{serd_inserter_get_sink(cobj())}; }
+};
+
+} // namespace serd
+
+/**
+ @}
+*/
+
+#endif // SERD_SERD_HPP
diff --git a/tests/serd_cxx_test.cpp b/tests/serd_cxx_test.cpp
new file mode 100644
index 00000000..c751a394
--- /dev/null
+++ b/tests/serd_cxx_test.cpp
@@ -0,0 +1,526 @@
+/*
+ Copyright 2018 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"
+
+#include <array>
+#include <cassert>
+#include <cstddef>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <type_traits>
+#include <utility>
+
+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(bugprone-use-after-move)
+
+ // Move assign
+ obj = std::move(moved);
+ assert(obj.cobj() == ptr);
+ assert(!moved.cobj()); // NOLINT(bugprone-use-after-move)
+
+ 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(bugprone-use-after-move)
+
+ 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(bugprone-use-after-move)
+
+ return 0;
+}
+
+static int
+test_operators()
+{
+ int st = 0;
+
+ serd::World world;
+
+ serd::Model model(world, serd::ModelFlag::index_SPO);
+ model.insert(serd::make_uri("http://example.org/s"),
+ serd::make_uri("http://example.org/p"),
+ serd::make_uri("http://example.org/o"));
+
+ 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, 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(bugprone-use-after-move)
+
+ 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());
+ } else if (node.language()) {
+ return test_node(*node.language());
+ }
+ 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(!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_resolved_uri("thing", base)));
+ 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_relative_uri("http://example.org/a", base)));
+ assert(!test_node(
+ serd::make_relative_uri("http://example.org/a", base, root)));
+ assert(!test_node(serd::make_decimal(1.2, 7)));
+ assert(!test_node(serd::make_decimal(3.4, 7, type)));
+ assert(!test_node(serd::make_integer(56)));
+ assert(!test_node(serd::make_integer(78, type)));
+ assert(!test_node(serd::make_blob("blob", 4, true)));
+ assert(!test_node(serd::make_blob("blob", 4, true, type)));
+
+ return 0;
+}
+
+static int
+test_uri()
+{
+ const auto no_authority = serd::URI("file:/path");
+ 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_base().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_base().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_base() == "/base/");
+ assert(resolved.path() == "relative/path");
+ assert(resolved.query() == "query");
+ assert(resolved.fragment() == "#fragment");
+
+ std::ostringstream ss;
+ ss << resolved;
+ assert(ss.str() == "http://example.org/base/relative/path?query#fragment");
+
+ return 0;
+}
+
+static int
+test_reader()
+{
+ size_t n_statements{};
+ std::stringstream stream{};
+ serd::Sink sink;
+
+ 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;
+ });
+
+ serd::World world;
+ serd::Reader reader(world, serd::Syntax::Turtle, sink, 4096);
+
+ // Read from string
+ reader.start_string("@prefix eg: <http://example.org> ."
+ "eg:s eg:p eg:o1 , eg:o2 .");
+ reader.read_document();
+
+ assert(n_statements == 2);
+ assert(stream.str() == "eg:s eg:p eg:o1\neg:s eg:p eg:o2\n");
+
+ // Read from stream
+ std::stringstream ss("eg:s eg:p eg:o3 , eg:o4 .");
+ reader.start_stream(ss);
+ reader.read_document();
+
+ assert(n_statements == 4);
+ assert(stream.str() == "eg:s eg:p eg:o1\n"
+ "eg:s eg:p eg:o2\n"
+ "eg:s eg:p eg:o3\n"
+ "eg:s eg:p eg:o4\n");
+
+ return 0;
+}
+
+static void
+write_test_doc(serd::Writer& writer)
+{
+ const auto& sink = writer.sink();
+ sink.base(serd::make_uri("http://drobilla.net/base/"));
+ // sink.set_root_uri(serd::make_uri("http://drobilla.net/"));
+ sink.prefix(serd::make_string("eg"), serd::make_uri("http://example.org/"));
+ sink.write({},
+ serd::make_uri("http://drobilla.net/base/s"),
+ serd::make_uri("http://example.org/p"),
+ serd::make_uri("http://drobilla.net/o"));
+
+ writer.finish();
+}
+
+static const char* writer_test_doc = "@base <http://drobilla.net/base/> .\n"
+ "@prefix eg: <http://example.org/> .\n"
+ "\n"
+ "<s>\n"
+ "\teg:p <../o> .\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);
+
+ 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;
+
+ 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://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"));
+
+ 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(s, p, o2);
+
+ assert(!model.empty());
+ assert(model.size() == 2);
+ assert(model.ask(s, p, o1));
+ assert(model.ask(s, p, o1));
+ 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);
+
+ serd::Model copy(model);
+ assert(copy == model);
+
+ copy.insert(s, p, s);
+ assert(copy != model);
+
+ return 0;
+}
+
+static int
+test_log()
+{
+ serd::World world;
+ world.set_message_sink([](const serd::Message& msg) {
+ assert(msg.status == serd::Status::err_bad_arg);
+ assert(msg.level == serd::LogLevel::error);
+ assert(!msg.cursor);
+ assert(msg.string == "bad argument to something: 42\n");
+ return serd::Status::success;
+ });
+
+ world.log(serd::Status::err_bad_arg,
+ serd::LogLevel::error,
+ nullptr,
+ "bad argument to %s: %d\n",
+ "something",
+ 42);
+
+ return 0;
+}
+
+int
+main()
+{
+ using TestFunc = int (*)();
+
+ constexpr std::array<TestFunc, 10> tests{{test_operators,
+ test_optional,
+ test_nodes,
+ test_uri,
+ test_env,
+ test_reader,
+ test_writer_ostream,
+ test_writer_string_sink,
+ test_model,
+ test_log}};
+
+ int failed = 0;
+ for (const auto& test : tests) {
+ failed += test();
+ }
+
+ if (failed == 0) {
+ std::cerr << "All " << tests.size() << " tests passed" << std::endl;
+ } else {
+ std::cerr << failed << "/" << tests.size() << " tests failed"
+ << std::endl;
+ }
+
+ return failed;
+}
diff --git a/wscript b/wscript
index 5a4c1f64..df544bfc 100644
--- a/wscript
+++ b/wscript
@@ -22,6 +22,7 @@ out = 'build' # Build directory
def options(ctx):
ctx.load('compiler_c')
+ ctx.load('compiler_cxx')
opt = ctx.configuration_options()
ctx.add_flags(
opt,
@@ -36,8 +37,10 @@ def options(ctx):
def configure(conf):
conf.load('compiler_c', cache=True)
+ conf.load('compiler_cxx', cache=True)
conf.load('autowaf', cache=True)
autowaf.set_c_lang(conf, 'c99')
+ autowaf.set_cxx_lang(conf, 'c++11')
conf.env.update({
'BUILD_UTILS': not Options.options.no_utils,
@@ -116,9 +119,14 @@ lib_source = ['src/base64.c',
'src/zix/hash.c']
def build(bld):
- # C Headers
+ # Main C and C++ headers
includedir = '${INCLUDEDIR}/serd-%s/serd' % SERD_MAJOR_VERSION
- bld.install_files(includedir, bld.path.ant_glob('serd/*.h'))
+ bld.install_files(includedir, bld.path.ant_glob('serd/*.h*'))
+
+ # C++ detail headers
+ includedir = '${INCLUDEDIR}/serd-%s/serd' % SERD_MAJOR_VERSION
+ bld.install_files(includedir + '/detail',
+ bld.path.ant_glob('serd/detail/*.hpp'))
# Pkgconfig file
autowaf.build_pc(bld, 'SERD', SERD_VERSION, SERD_MAJOR_VERSION, [],
@@ -158,6 +166,7 @@ def build(bld):
if bld.env.BUILD_TESTS:
test_args = {'includes': ['.', './src'],
'cflags': [''] if bld.env.NO_COVERAGE else ['--coverage'],
+ 'cxxflags': [''] if bld.env.NO_COVERAGE else ['--coverage', '-fno-inline'],
'linkflags': [''] if bld.env.NO_COVERAGE else ['--coverage'],
'lib': lib_args['lib'],
'install_path': ''}
@@ -194,6 +203,13 @@ def build(bld):
obj.cflags += bld.env.PTHREAD_CFLAGS
obj.linkflags += bld.env.PTHREAD_LINKFLAGS
+ bld(features = 'cxx cxxprogram',
+ source = 'tests/serd_cxx_test.cpp',
+ use = 'libserd_profiled',
+ target = 'serd_cxx_test',
+ defines = defines,
+ **test_args)
+
# Utilities
if bld.env.BUILD_UTILS:
for i in ['serdi', 'serd_validate']:
@@ -522,6 +538,7 @@ def test(tst):
check(['./nodes_test'])
check(['./overflow_test'])
check(['./serd_test'])
+ check(['./serd_cxx_test'])
check(['./terse_write_test'])
check(['./read_chunk_test'])