diff options
-rw-r--r-- | .clang-tidy | 22 | ||||
-rw-r--r-- | doc/reference.doxygen.in | 13 | ||||
-rw-r--r-- | serd/detail/Copyable.hpp | 150 | ||||
-rw-r--r-- | serd/detail/Flags.hpp | 76 | ||||
-rw-r--r-- | serd/detail/Optional.hpp | 127 | ||||
-rw-r--r-- | serd/detail/StringView.hpp | 204 | ||||
-rw-r--r-- | serd/detail/Wrapper.hpp | 127 | ||||
-rw-r--r-- | serd/serd.hpp | 1653 | ||||
-rw-r--r-- | tests/serd_cxx_test.cpp | 828 | ||||
-rw-r--r-- | wscript | 53 |
10 files changed, 3244 insertions, 9 deletions
diff --git a/.clang-tidy b/.clang-tidy index 6c239a72..e453ebbf 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,19 +1,41 @@ Checks: > *, + -*-deprecated-headers, -*-magic-numbers, + -*-non-private-member-variables-in-classes, -*-uppercase-literal-suffix, -android-cloexec-fopen, -bugprone-branch-clone, -bugprone-suspicious-string-compare, + -cert-dcl50-cpp, -clang-analyzer-alpha.*, -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, -clang-analyzer-valist.Uninitialized, + -clang-diagnostic-*, + -cppcoreguidelines-macro-usage, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-pro-type-reinterpret-cast, + -cppcoreguidelines-pro-type-vararg, + -fuchsia-default-argument*, + -fuchsia-multiple-inheritance, + -fuchsia-overloaded-operator, + -google-explicit-constructor, -google-readability-todo, + -google-runtime-references, + -hicpp-explicit-conversions, -hicpp-multiway-paths-covered, + -hicpp-no-array-decay, -hicpp-signed-bitwise, + -hicpp-vararg, -llvm-header-guard, + -modernize-use-nullptr, + -modernize-use-trailing-return-type, + -modernize-use-using, + -performance-unnecessary-value-param, -readability-else-after-return, -readability-implicit-bool-conversion, + -readability-named-parameter, WarningsAsErrors: '*' HeaderFilterRegex: '.*' FormatStyle: file diff --git a/doc/reference.doxygen.in b/doc/reference.doxygen.in index a30e5b8a..28c2f8e0 100644 --- a/doc/reference.doxygen.in +++ b/doc/reference.doxygen.in @@ -521,7 +521,7 @@ CASE_SENSE_NAMES = YES # scope will be hidden. # The default value is: NO. -HIDE_SCOPE_NAMES = NO +HIDE_SCOPE_NAMES = YES # If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will # append additional text to a page's title, such as Class Reference. If set to @@ -670,7 +670,7 @@ SHOW_FILES = NO # Folder Tree View (if specified). # The default value is: YES. -SHOW_NAMESPACES = NO +SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from @@ -782,6 +782,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 @@ -807,7 +808,7 @@ INPUT_ENCODING = UTF-8 # *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, # *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. -FILE_PATTERNS = +FILE_PATTERNS = *.h *.hpp # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. @@ -849,7 +850,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 @@ -1143,7 +1144,7 @@ HTML_EXTRA_FILES = # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_COLORSTYLE_HUE = 160 +HTML_COLORSTYLE_HUE = 120 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A @@ -1151,7 +1152,7 @@ HTML_COLORSTYLE_HUE = 160 # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_SAT = 32 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 diff --git a/serd/detail/Copyable.hpp b/serd/detail/Copyable.hpp new file mode 100644 index 00000000..df5305d4 --- /dev/null +++ b/serd/detail/Copyable.hpp @@ -0,0 +1,150 @@ +/* + 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> + +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/serd/detail/Flags.hpp b/serd/detail/Flags.hpp new file mode 100644 index 00000000..637b3aec --- /dev/null +++ b/serd/detail/Flags.hpp @@ -0,0 +1,76 @@ +/* + 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(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(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..24a5acce --- /dev/null +++ b/serd/detail/Optional.hpp @@ -0,0 +1,127 @@ +/* + 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 <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..dab3aecd --- /dev/null +++ b/serd/detail/StringView.hpp @@ -0,0 +1,204 @@ +/* + 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(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)}; + } + + int compare(StringView rhs) const noexcept + { + const size_type len = std::min(size(), rhs.size()); + const int cmp = strncmp(data(), rhs.data(), len); + if (cmp) { + return cmp; + } else if (size() == rhs.size()) { + return 0; + } else if (size() < rhs.size()) { + return -1; + } + return 1; + } + + 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 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/serd/detail/Wrapper.hpp b/serd/detail/Wrapper.hpp new file mode 100644 index 00000000..ad38b121 --- /dev/null +++ b/serd/detail/Wrapper.hpp @@ -0,0 +1,127 @@ +/* + 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> + +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> +{ + 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)) {} + 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/serd/serd.hpp b/serd/serd.hpp new file mode 100644 index 00000000..ee133462 --- /dev/null +++ b/serd/serd.hpp @@ -0,0 +1,1653 @@ +/* + 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 <functional> +#include <iostream> +#include <map> +#include <memory> +#include <sstream> +#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; + +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)); +} + +/// @copydoc serd_strtod +inline double +strtod(StringView str, size_t* end = nullptr) +{ + return serd_strtod(str.c_str(), end); +} + +/** + @} + @name Base64 + @{ +*/ + +/** + Encode `size` bytes of `buf` into `str`, which must be large enough + + @param buf Input binary data (vector-like container of bytes). + @param wrap_lines Wrap lines at 76 characters to conform to RFC 2045. + @return A base64 encoded representation of the data in `buf`. +*/ +template <typename Container> +inline std::string +base64_encode(const Container& buf, bool wrap_lines = false) +{ + 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; +} + +/** + 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) +{ + 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; +} + +/** + @} + @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 = 1) + : BasicWrapper(serd_byte_sink_new_function(s_write, this, block_size)) + , _write_func(std::move(write_func)) + { + } + + 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(hicpp-explicit-conversions) + NodeWrapper(const NodeWrapper<C>& node) : Base{node} + { + } + + 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; + + 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(c_str(), length()); + } + + explicit operator StringView() const + { + return StringView(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_substring(str.data(), str.length())); +} + +/// @copydoc serd_new_plain_literal +inline Node +make_plain_literal(StringView str, StringView lang) +{ + return Node(serd_new_plain_literal(str.data(), lang.data())); +} + +/// Create a new typed literal node from `str` +inline Node +make_typed_literal(StringView str, const NodeView& datatype) +{ + return Node(serd_new_typed_literal(str.data(), datatype.cobj())); +} + +/// @copydoc serd_new_blank +inline Node +make_blank(StringView str) +{ + return Node(serd_new_simple_node(SERD_BLANK, str.data(), str.length())); +} + +/// @copydoc serd_new_curie +inline Node +make_curie(StringView str) +{ + return Node(serd_new_simple_node(SERD_CURIE, str.data(), str.length())); +} + +/// @copydoc serd_new_uri +inline Node +make_uri(StringView str) +{ + return Node(serd_new_simple_node(SERD_URI, str.data(), str.length())); +} + +/// @copydoc serd_new_resolved_uri +inline Node +make_resolved_uri(StringView str, const NodeView& base) +{ + return Node(serd_new_resolved_uri(str.data(), base.cobj())); +} + +/// 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.data(), nullptr)); +} + +/// @copydoc serd_new_file_uri +inline Node +make_file_uri(StringView path, StringView hostname) +{ + return Node(serd_new_file_uri(path.data(), hostname.data())); +} + +/// @copydoc serd_new_relative_uri +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())); +} + +/// @copydoc serd_new_decimal +inline Node +make_decimal(double d, + unsigned max_precision, + unsigned max_frac_digits, + Optional<NodeView> datatype = {}) +{ + return Node( + serd_new_decimal(d, max_precision, max_frac_digits, 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_boolean +inline Node +make_boolean(bool b) +{ + return Node(serd_new_boolean(b)); +} + +/// @copydoc serd_new_blob +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; + } + + 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_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}; + } + + /// Return URI as a string + std::string string() const { return serialise(nullptr, nullptr); } + + /// Return URI as a string relative to `base` + std::string relative_string(const URI& base) const + { + return serialise(base.cobj(), nullptr); + } + + /** + 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 + { + return serialise(base.cobj(), root.cobj()); + } + + const SerdURI* cobj() const { return &_uri; } + +private: + static Component make_component(const SerdStringView slice) + { + return slice.buf ? Component{slice.buf, slice.len} : Component{}; + } + + std::string serialise(const SerdURI* base, const SerdURI* root) const + { + std::ostringstream ss; + ByteSink byte_sink{ss}; + + serd_uri_serialise_relative( + cobj(), base, root, ByteSink::s_write, &byte_sink); + + return ss.str(); + } + + SerdURI _uri; +}; + +inline std::ostream& +operator<<(std::ostream& os, const URI& uri) +{ + ByteSink byte_sink{os}; + serd_uri_serialise(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> + 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)} + { + } + + Cursor(StringView name, const unsigned line, const unsigned col) + : Cursor(make_string(name), line, col) + { + } + + 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 cxx_fields; + for (size_t i = 0; i < entry->n_fields; ++i) { + cxx_fields.emplace(entry->fields[i].key, + entry->fields[i].value); + } + + return static_cast<SerdStatus>( + self->_log_func(entry->domain, + static_cast<LogLevel>(entry->level), + cxx_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 IterWrapper; + +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(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(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(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, nullptr)) + { + serd_sink_set_event_func(cobj(), s_event); + } + + // 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 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, const NodeView& uri) + { + return Status(serd_env_set_prefix_from_strings(cobj(), + name.c_str(), + uri.c_str())); + } + + /// Set a namespace prefix + Status set_prefix(StringView name, StringView uri) + { + return Status(serd_env_set_prefix_from_strings(cobj(), + name.c_str(), + uri.c_str())); + } + + /// 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(nullptr), detail::Ownership::owned}) + { + } + + explicit Env(const NodeView& base) + : EnvWrapper(EnvWrapper::UPtr{serd_env_new(base.cobj()), + detail::Ownership::owned}) + { + } +}; + +/** + @} + @name Reader + @{ +*/ + +class Reader : public detail::BasicWrapper<SerdReader, serd_reader_free> +{ +public: + Reader(World& world, + Syntax syntax, + ReaderFlags flags, + SinkView sink, + size_t stack_size) + : BasicWrapper(serd_reader_new(world.cobj(), + SerdSyntax(syntax), + SerdReaderFlags(flags), + 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{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_get_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/tests/serd_cxx_test.cpp b/tests/serd_cxx_test.cpp new file mode 100644 index 00000000..ae346e01 --- /dev/null +++ b/tests/serd_cxx_test.cpp @@ -0,0 +1,828 @@ +/* + 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{"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, {}, 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()); + } else if (node.language()) { + return test_node(*node.language()); + } + return 0; +} + +static int +test_string() +{ + assert(!strcmp(serd::strerror(serd::Status::err_unknown), "Unknown error")); + assert(serd::strtod("4.0") == 4.0); + + static const char* const buf = "36.0"; + size_t end = 0; + const double num = serd::strtod(buf, &end); + + assert(num == 36.0); + assert(end == 4); + + 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; +} + +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; +} + +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_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, 17, 7))); + assert(!test_node(serd::make_decimal(3.4, 17, 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 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_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"); + + assert(resolved.string() == + "http://example.org/base/relative/path?query#fragment"); + 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::file_uri_parse("file:///foo/%20bar"); + assert(local_file_uri == "/foo/ bar"); + + auto hostname = std::string(); + auto host_file_uri = serd::file_uri_parse("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::Reader reader(world, serd::Syntax::Turtle, {}, 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() == "eg:s eg:p b1\n" + "b1 eg:p2 eg: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() == "eg:s eg:p eg:o3\n" + "eg:s eg: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* writer_test_doc = "@base <http://drobilla.net/base/> .\n" + "@prefix eg: <http://example.org/> .\n" + "\n" + "<s>\n" + "\teg:p [\n" + "\t\teg:p2 <../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); + writer = serd::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{"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, 15> 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; +} @@ -28,10 +28,12 @@ post_tags = ['Hacking', 'RDF', 'Serd'] def options(ctx): ctx.load('compiler_c') + ctx.load('compiler_cxx') opt = ctx.configuration_options() ctx.add_flags( opt, {'no-utils': 'do not build command line utilities', + 'no-cxx': 'do not build C++ bindings', 'stack-check': 'include runtime stack sanity checks', 'static': 'build static library', 'no-shared': 'do not build shared library', @@ -43,11 +45,18 @@ def options(ctx): def configure(conf): conf.load('compiler_c', cache=True) + + if not Options.options.no_cxx: + conf.load('compiler_cxx', cache=True) + conf.load('autowaf', cache=True) if not autowaf.set_c_lang(conf, 'c11', mandatory=False): autowaf.set_c_lang(conf, 'c99') + if 'COMPILER_CXX' in conf.env: + autowaf.set_cxx_lang(conf, 'c++11') + if Options.options.strict: # Check for programs used by lint target conf.find_program("flake8", var="FLAKE8", mandatory=False) @@ -94,6 +103,26 @@ def configure(conf): ], }) + autowaf.add_compiler_flags(conf.env, 'cxx', { + 'clang': [ + '-Wno-documentation-unknown-command', + ], + 'gcc': [ + '-Wno-multiple-inheritance', + ], + 'msvc': [ + '/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 + ] + }) + conf.env.update({ 'BUILD_UTILS': not Options.options.no_utils, 'BUILD_SHARED': not Options.options.no_shared, @@ -198,9 +227,14 @@ lib_source = ['src/base64.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, [], @@ -243,6 +277,7 @@ def build(bld): coverage_flags = [''] if bld.env.NO_COVERAGE else ['--coverage'] test_args = {'includes': ['.', './src'], 'cflags': coverage_flags, + 'cxxflags': coverage_flags, 'linkflags': coverage_flags, 'lib': lib_args['lib'], 'install_path': ''} @@ -283,6 +318,15 @@ def build(bld): defines = defines, **test_args) + # C++ API test + if 'COMPILER_CXX' in bld.env: + 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: obj = bld(features = 'c cprogram', @@ -348,7 +392,7 @@ def lint(ctx): if "CLANG_TIDY" in ctx.env and "clang" in ctx.env.CC[0]: Logs.info("Running clang-tidy") - sources = glob.glob('src/*.c') + glob.glob('tests/*.c') + sources = glob.glob('src/*.c') + glob.glob('tests/*.c*') sources = list(map(os.path.abspath, sources)) procs = [] for source in sources: @@ -720,6 +764,9 @@ def test(tst): check(['./statement_test']) check(['./terse_write_test']) + if 'COMPILER_CXX' in tst.env: + check(['./serd_cxx_test']) + def test_syntax_io(check, in_name, check_name, lang): in_path = 'tests/good/%s' % in_name out_path = in_path + '.io' |