aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2018-06-16 10:26:47 -0400
committerDavid Robillard <d@drobilla.net>2018-11-25 22:12:48 +0100
commita70ee297d71d03c004e78aa1061877f231cfd8d2 (patch)
tree7f0095fcda00b69901d1f133e7c8b4ffc358884b
parent535bc536f0541792b0bab84041e7d82eaa457368 (diff)
downloadserd-a70ee297d71d03c004e78aa1061877f231cfd8d2.tar.gz
serd-a70ee297d71d03c004e78aa1061877f231cfd8d2.tar.bz2
serd-a70ee297d71d03c004e78aa1061877f231cfd8d2.zip
WIP: Add C++ bindings
-rw-r--r--doc/reference.doxygen.in5
-rw-r--r--serd/serd.hpp1029
-rw-r--r--tests/serd_cxx_test.cpp397
-rw-r--r--wscript25
4 files changed, 1448 insertions, 8 deletions
diff --git a/doc/reference.doxygen.in b/doc/reference.doxygen.in
index 2a02e35e..e54553d3 100644
--- a/doc/reference.doxygen.in
+++ b/doc/reference.doxygen.in
@@ -780,7 +780,8 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
-INPUT = @SERD_SRCDIR@/serd/serd.h
+INPUT = @SERD_SRCDIR@/serd/serd.h \
+ @SERD_SRCDIR@/serd/serd.hpp
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@@ -847,7 +848,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/serd.hpp b/serd/serd.hpp
new file mode 100644
index 00000000..bb2d48a1
--- /dev/null
+++ b/serd/serd.hpp
@@ -0,0 +1,1029 @@
+/*
+ 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/serd.h>
+
+#include <cassert>
+#include <memory>
+#include <ostream>
+#include <type_traits>
+#include <utility>
+
+/**
+ @defgroup serdxx Serdxx
+ C++ bindings for Serd, a lightweight RDF syntax library.
+ @{
+*/
+
+namespace serd {
+
+class Node;
+
+/**
+ Return status code.
+*/
+enum class Status {
+ Success = SERD_SUCCESS, /**< No error */
+ Failure = SERD_FAILURE, /**< Non-fatal failure */
+ ErrUnknown = SERD_ERR_UNKNOWN, /**< Unknown error */
+ ErrBadSyntax = SERD_ERR_BAD_SYNTAX, /**< Invalid syntax */
+ ErrBadArg = SERD_ERR_BAD_ARG, /**< Invalid argument */
+ ErrNotFound = SERD_ERR_NOT_FOUND, /**< Not found */
+ ErrIdClash = SERD_ERR_ID_CLASH, /**< Clashing blank node IDs */
+ ErrBadCurie = SERD_ERR_BAD_CURIE, /**< Invalid CURIE */
+ ErrInternal = SERD_ERR_INTERNAL /**< Unexpected internal error */
+};
+
+namespace detail {
+
+template <typename T>
+using FreeFunc = void (*)(T*);
+
+template <typename T>
+using CopyFunc = T* (*)(const T*);
+
+template <typename T>
+void
+no_free(T*)
+{
+}
+
+template <typename T>
+struct Deleter
+{
+ Deleter(FreeFunc<T> free_func) : _free_func(free_func) {}
+
+ void operator()(T* ptr)
+ {
+ if (_free_func) {
+ _free_func(ptr);
+ }
+ }
+
+ FreeFunc<T> _free_func;
+};
+
+/** C++ wrapper for a C object. */
+template <typename T, void FreeFunc(T*)>
+class Wrapper
+{
+public:
+ using CType = T;
+
+ Wrapper(T* ptr) : _ptr(ptr, Deleter<T>(FreeFunc)) {}
+
+ Wrapper(const T* ptr) : _ptr(const_cast<T*>(ptr), nullptr) {}
+
+ Wrapper(Wrapper&& wrapper) = default;
+ Wrapper& operator=(Wrapper&& wrapper) = default;
+
+ Wrapper(const Wrapper&) = delete;
+ Wrapper& operator=(const Wrapper&) = delete;
+
+ bool operator==(const Wrapper&) = delete;
+ bool operator!=(const Wrapper&) = delete;
+
+ operator const T*() const { return _ptr.get(); }
+
+ inline T* c_obj() { return _ptr.get(); }
+ inline const T* c_obj() const { return _ptr.get(); }
+
+protected:
+ Wrapper(std::nullptr_t) : _ptr(nullptr, nullptr) {}
+
+ std::unique_ptr<T, Deleter<T>> _ptr;
+};
+
+template <typename T, T* CopyFunc(const T*), void FreeFunc(T*)>
+class Copyable : public Wrapper<T, FreeFunc>
+{
+public:
+ Copyable(T* ptr) : Wrapper<T, FreeFunc>(ptr) {}
+
+ Copyable(const T* ptr) : Wrapper<T, FreeFunc>(ptr) {}
+
+ Copyable(const Copyable& wrapper)
+ : Wrapper<T, FreeFunc>(CopyFunc(wrapper.c_obj()))
+ {
+ }
+
+ Copyable(Copyable&&) = default;
+ Copyable& operator=(Copyable&&) = default;
+
+ Copyable& operator=(const Copyable& wrapper)
+ {
+ this->_ptr = std::unique_ptr<T, Deleter<T>>(CopyFunc(wrapper.c_obj()),
+ FreeFunc);
+ return *this;
+ }
+
+protected:
+ Copyable(std::nullptr_t) : Wrapper<T, FreeFunc>(nullptr) {}
+};
+
+template <typename Traits>
+size_t
+stream_sink(const void* buf, size_t size, size_t nmemb, void* stream)
+{
+ using Stream = std::basic_ostream<char, Traits>;
+
+ try {
+ Stream* const s = static_cast<Stream*>(stream);
+ s->write(static_cast<const char*>(buf), std::streamsize(size * nmemb));
+ if (s->good()) {
+ return nmemb;
+ }
+ } catch (...) {
+ }
+ return 0;
+}
+
+} // namespace detail
+
+/**
+ 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.
+ The fact that these are interally just pointers is used to avoid adding
+ space overhead for an "is_set" flag, like a generic optional class would.
+*/
+template <typename T>
+class Optional
+{
+public:
+ using CType = typename T::CType;
+
+ Optional() : _value() {}
+ Optional(std::nullptr_t) : _value() {}
+ Optional(const T& value) : _value(value) {}
+ Optional(T&& value) : _value(std::move(value)) {}
+
+ Optional(const Optional&) = default;
+ Optional(Optional&&) = default;
+ Optional& operator=(const Optional&) = default;
+ Optional& operator=(Optional&&) = default;
+
+ void reset() { *this = nullptr; }
+
+ const T& operator*() const
+ {
+ assert(_value.c_obj());
+ return _value;
+ }
+
+ T& operator*()
+ {
+ assert(_value.c_obj());
+ return _value;
+ }
+
+ const T* operator->() const
+ {
+ assert(_value.c_obj());
+ return &_value;
+ }
+
+ T* operator->()
+ {
+ assert(_value.c_obj());
+ 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.c_obj(); }
+
+ bool operator!() const { return !_value.c_obj(); }
+
+ inline CType* ptr() { return _value.c_obj(); }
+ inline const CType* ptr() const { return _value.c_obj(); }
+
+private:
+ T _value;
+};
+
+/**
+ A temporary reference to a string.
+
+ This is used as a function parameter type to allow passing either
+ `std::string` or `const char*` without requiring multiple wrapper
+ definitions or the copying overhead of `std::string`.
+*/
+struct StringRef
+{
+ StringRef(const char* str) : _ptr(str) {}
+ StringRef(const std::string& str) : _ptr(str.c_str()) {}
+
+ StringRef(const StringRef&) = delete;
+ StringRef& operator=(const StringRef&) = delete;
+
+ operator const char*() const { return _ptr; }
+
+private:
+ const char* const _ptr;
+};
+
+using Type = SerdType;
+using NodeFlags = SerdNodeFlags;
+using StyleFlags = SerdStyleFlags;
+using IndexOptions = SerdIndexOptions;
+using Field = SerdField;
+using StatementFlags = SerdStatementFlags;
+
+class Statement;
+
+/**
+ RDF syntax.
+*/
+enum class Syntax {
+ /**
+ Turtle - Terse RDF Triple Language (UTF-8).
+ @see <a href="http://www.w3.org/TeamSubmission/turtle/">Turtle</a>
+ */
+ Turtle = SERD_TURTLE,
+
+ /**
+ NTriples - Line-based RDF triples (ASCII).
+ @see <a href="http://www.w3.org/TR/rdf-testcases#ntriples">NTriples</a>
+ */
+ NTriples = SERD_NTRIPLES,
+
+ /**
+ NQuads - Line-based RDF quads (UTF-8).
+ @see <a href="https://www.w3.org/TR/n-quads/">NQuads</a>
+ */
+ NQuads = SERD_NQUADS,
+
+ /**
+ TriG - Terse RDF quads (UTF-8).
+ @see <a href="https://www.w3.org/TR/trig/">Trig</a>
+ */
+ TriG = SERD_TRIG
+};
+
+/**
+ 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 Style {
+ ascii = SERD_STYLE_ASCII, /**< Escape all non-ASCII characters. */
+};
+
+inline StyleFlags
+operator|(const Style a, const Style b)
+{
+ return (StyleFlags)(int(a) | int(b));
+}
+
+inline StyleFlags
+operator|(const StyleFlags a, const Style b)
+{
+ return (StyleFlags)(int(a) | int(b));
+}
+
+/**
+ Indexing option.
+*/
+enum class IndexOption {
+ SPO = SERD_SPO, /**< Subject, Predicate, Object */
+ SOP = SERD_SOP, /**< Subject, Object, Predicate */
+ OPS = SERD_OPS, /**< Object, Predicate, Subject */
+ OSP = SERD_OSP, /**< Object, Subject, Predicate */
+ PSO = SERD_PSO, /**< Predicate, Subject, Object */
+ POS = SERD_POS /**< Predicate, Object, Subject */
+};
+
+inline IndexOptions
+operator|(const IndexOption a, const IndexOption b)
+{
+ return (IndexOptions)(int(a) | int(b));
+}
+
+inline IndexOptions
+operator|(const IndexOptions a, const IndexOption b)
+{
+ return (IndexOptions)(int(a) | int(b));
+}
+
+/**
+ @name String Utilities
+ @{
+*/
+
+inline const char*
+strerror(const Status status)
+{
+ return serd_strerror((SerdStatus)status);
+}
+
+/**
+ @}
+ @name URI
+ @{
+*/
+
+inline std::string
+file_uri_parse(const StringRef& uri, std::string* hostname = nullptr)
+{
+ char* c_hostname = nullptr;
+ char* c_path = serd_file_uri_parse(uri, &c_hostname);
+ if (hostname && c_hostname) {
+ *hostname = c_hostname;
+ }
+ const std::string path(c_path);
+ free(c_hostname);
+ free(c_path);
+ return path;
+}
+
+/**
+ @}
+ @name Node
+ @{
+*/
+
+class Node : public detail::Copyable<SerdNode, serd_node_copy, serd_node_free>
+{
+public:
+ explicit Node(SerdNode* ptr) : Copyable(ptr) {}
+ explicit Node(const SerdNode* ptr) : Copyable(ptr) {}
+
+ Type type() const { return serd_node_get_type(c_obj()); }
+ const char* c_str() const { return serd_node_get_string(c_obj()); }
+ std::string str() const { return c_str(); }
+ size_t size() const { return serd_node_get_length(c_obj()); }
+ size_t length() const { return serd_node_get_length(c_obj()); }
+ NodeFlags flags() const { return serd_node_get_flags(c_obj()); }
+
+ Optional<Node> datatype() const
+ {
+ return Node(serd_node_get_datatype(c_obj()));
+ }
+
+ Optional<Node> language() const
+ {
+ return Node(serd_node_get_language(c_obj()));
+ }
+
+ bool operator==(const Node& node) const
+ {
+ return serd_node_equals(c_obj(), node.c_obj());
+ }
+
+ bool operator!=(const Node& node) const
+ {
+ return !serd_node_equals(c_obj(), node.c_obj());
+ }
+
+ bool operator<(const Node& node) const
+ {
+ return serd_node_compare(c_obj(), node.c_obj()) < 0;
+ }
+
+ operator std::string() const
+ {
+ return std::string(serd_node_get_string(c_obj()),
+ serd_node_get_length(c_obj()));
+ }
+
+private:
+ friend class Optional<Node>;
+ Node() : Copyable(nullptr) {}
+};
+
+inline std::ostream&
+operator<<(std::ostream& os, const Node& node)
+{
+ return os << node.c_str();
+}
+
+inline Node
+make_string(const StringRef& str)
+{
+ return Node(serd_new_string(str));
+}
+
+inline Node
+make_plain_literal(const StringRef& str, const StringRef& lang)
+{
+ return Node(serd_new_plain_literal(str, lang));
+}
+
+inline Node
+make_typed_literal(const StringRef& str, const Node& datatype)
+{
+ return Node(serd_new_typed_literal(str, datatype.c_obj()));
+}
+
+inline Node
+make_blank(const StringRef& str)
+{
+ return Node(serd_new_blank(str));
+}
+
+inline Node
+make_curie(const StringRef& str)
+{
+ return Node(serd_new_curie(str));
+}
+
+inline Node
+make_uri(const StringRef& str)
+{
+ return Node(serd_new_uri(str));
+}
+
+inline Node
+make_resolved_uri(const StringRef& str, const Node& base)
+{
+ return Node(serd_new_resolved_uri(str, base.c_obj()));
+}
+
+// TODO: serd_node_resolve?
+
+inline Node
+make_file_uri(const StringRef& path)
+{
+ return Node(serd_new_file_uri(path, nullptr));
+}
+
+inline Node
+make_file_uri(const StringRef& path, const StringRef& hostname)
+{
+ return Node(serd_new_file_uri(path, hostname));
+}
+
+inline Node
+make_relative_uri(const StringRef& str,
+ const Node& base,
+ const Optional<Node>& root = {})
+{
+ return Node(serd_new_relative_uri(str, base.c_obj(), root.ptr()));
+}
+
+inline Node
+make_decimal(double d,
+ unsigned frac_digits,
+ const Optional<Node>& datatype = {})
+{
+ return Node(serd_new_decimal(d, frac_digits, datatype.ptr()));
+}
+
+inline Node
+make_integer(int64_t i, const Optional<Node>& datatype = {})
+{
+ return Node(serd_new_integer(i, datatype.ptr()));
+}
+
+inline Node
+make_blob(const void* buf,
+ size_t size,
+ bool wrap_lines,
+ const Optional<Node>& datatype = {})
+{
+ return Node(serd_new_blob(buf, size, wrap_lines, datatype.ptr()));
+}
+
+/**
+ @}
+ @name World
+ @{
+*/
+
+class World : public detail::Wrapper<SerdWorld, serd_world_free>
+{
+public:
+ World() : Wrapper(serd_world_new()) {}
+
+ explicit World(SerdWorld* ptr) : Wrapper(ptr) {}
+
+ Node get_blank() { return Node(serd_world_get_blank(c_obj())); }
+
+ void set_error_sink(SerdErrorSink error_sink, void* handle)
+ {
+ serd_world_set_error_sink(c_obj(), error_sink, handle);
+ }
+
+ // operator SerdWorld*() { return c_obj(); }
+};
+
+/**
+ @}
+ @name Statement
+ @{
+*/
+
+class Statement : public detail::Wrapper<SerdStatement, detail::no_free>
+{
+public:
+ Statement(const SerdStatement* statement) : Wrapper(statement) {}
+
+ Node node(Field field)
+ {
+ return Node(serd_statement_get_node(c_obj(), field));
+ }
+
+ Node subject() const { return Node(serd_statement_get_subject(c_obj())); }
+
+ Node predicate() const
+ {
+ return Node(serd_statement_get_predicate(c_obj()));
+ }
+
+ Node object() const { return Node(serd_statement_get_object(c_obj())); }
+
+ Node graph() const { return Node(serd_statement_get_graph(c_obj())); }
+};
+
+/**
+ @}
+ @name Sink
+ @{
+*/
+
+class Sink
+{
+public:
+ Sink()
+ : _sink(serd_sink_new(this), detail::Deleter<SerdSink>(serd_sink_free))
+ , _is_user(true)
+ {
+ serd_sink_set_base_func(_sink.get(), s_base);
+ serd_sink_set_prefix_func(_sink.get(), s_prefix);
+ serd_sink_set_statement_func(_sink.get(), s_statement);
+ serd_sink_set_end_func(_sink.get(), s_end);
+ }
+
+ explicit Sink(const SerdSink* ptr)
+ : _sink(const_cast<SerdSink*>(ptr), detail::Deleter<SerdSink>(nullptr))
+ , _is_user(false)
+ {
+ }
+
+ virtual ~Sink() = default;
+
+ virtual Status base(const Node& uri)
+ {
+ return _is_user ? Status::Success
+ : Status(serd_sink_set_base(_sink.get(), uri.c_obj()));
+ }
+
+ virtual Status prefix(const Node& name, const Node& uri)
+ {
+ return _is_user ? Status::Success
+ : Status(serd_sink_set_prefix(
+ _sink.get(), name.c_obj(), uri.c_obj()));
+ }
+
+ virtual Status statement(StatementFlags flags, const Statement& statement)
+ {
+ return _is_user ? Status::Success
+ : Status(serd_sink_write_statement(
+ _sink.get(), flags, statement.c_obj()));
+ }
+
+ virtual Status nodes(StatementFlags flags,
+ const Node& subject,
+ const Node& predicate,
+ const Node& object,
+ const Optional<Node>& graph = {})
+ {
+ return _is_user ? Status::Success
+ : Status(serd_sink_write_nodes(_sink.get(),
+ flags,
+ subject.c_obj(),
+ predicate.c_obj(),
+ object.c_obj(),
+ graph.ptr()));
+ }
+
+ virtual Status end(const Node& node)
+ {
+ return _is_user ? Status::Success
+ : Status(serd_sink_end(_sink.get(), node.c_obj()));
+ }
+
+ // operator const SerdSink*() const { return _sink.get(); }
+ // operator SerdSink*() { return _sink.get(); }
+
+ const SerdSink* c_sink() const { return _sink.get(); }
+ SerdSink* c_sink() { return _sink.get(); }
+
+private:
+ static SerdStatus s_base(void* handle, const SerdNode* uri)
+ {
+ return (SerdStatus)((Sink*)handle)->base(Node(uri));
+ }
+
+ static SerdStatus
+ s_prefix(void* handle, const SerdNode* name, const SerdNode* uri)
+ {
+ return (SerdStatus)((Sink*)handle)->prefix(Node(name), Node(uri));
+ }
+
+ static SerdStatus s_statement(void* handle,
+ SerdStatementFlags flags,
+ const SerdStatement* statement)
+ {
+ return (SerdStatus)((Sink*)handle)->statement(flags, statement);
+ }
+
+ static SerdStatus s_end(void* handle, const SerdNode* node)
+ {
+ return (SerdStatus)((Sink*)handle)->end(Node(node));
+ }
+
+ std::unique_ptr<SerdSink, detail::Deleter<SerdSink>> _sink;
+ bool _is_user;
+};
+
+/**
+ @}
+ @name Environment
+ @{
+*/
+
+class Env : public detail::Copyable<SerdEnv, serd_env_copy, serd_env_free>
+{
+public:
+ explicit Env(const Optional<Node>& base = {})
+ : Copyable(serd_env_new(base.ptr()))
+ {
+ }
+
+ /// Return the base URI
+ Node base_uri() const { return Node(serd_env_get_base_uri(c_obj())); }
+
+ /// Set the base URI
+ Status set_base_uri(const Node& uri)
+ {
+ return Status(serd_env_set_base_uri(c_obj(), uri.c_obj()));
+ }
+
+ /// Set a namespace prefix
+ Status set_prefix(const Node& name, const Node& uri)
+ {
+ return Status(serd_env_set_prefix(c_obj(), name.c_obj(), uri.c_obj()));
+ }
+
+ /// Set a namespace prefix
+ Status set_prefix(const StringRef& name, const StringRef& uri)
+ {
+ return Status(serd_env_set_prefix_from_strings(c_obj(), name, uri));
+ }
+
+ /// Qualify `uri` into a CURIE if possible
+ Optional<Node> qualify(const Node& uri) const
+ {
+ return Node(serd_env_qualify(c_obj(), uri.c_obj()));
+ }
+
+ /// Expand `node` into an absolute URI if possible
+ Optional<Node> expand(const Node& node) const
+ {
+ return Node(serd_env_expand(c_obj(), node.c_obj()));
+ }
+
+ /// Send all prefixes to `sink`
+ void send_prefixes(Sink& sink)
+ {
+ serd_env_send_prefixes(c_obj(), sink.c_sink());
+ }
+
+ bool operator==(const Env& env)
+ {
+ return serd_env_equals(c_obj(), env.c_obj());
+ }
+
+ bool operator!=(const Env& env) { return !operator==(env); }
+
+ // operator SerdEnv*() { return c_obj(); }
+};
+
+/**
+ @}
+ @name Reader
+ @{
+*/
+
+class Reader : public detail::Wrapper<SerdReader, serd_reader_free>, public Sink
+{
+public:
+ Reader(World& world, Syntax syntax, const Sink& sink, size_t stack_size)
+ : Wrapper(serd_reader_new(world.c_obj(),
+ SerdSyntax(syntax),
+ sink.c_sink(),
+ stack_size))
+ {
+ }
+
+ void set_strict(bool strict) { serd_reader_set_strict(c_obj(), strict); }
+
+ void add_blank_prefix(const StringRef& prefix)
+ {
+ serd_reader_add_blank_prefix(c_obj(), prefix);
+ }
+
+ SerdStatus start_file(const StringRef& uri, bool bulk = true)
+ {
+ return serd_reader_start_file(c_obj(), uri, bulk);
+ }
+
+ SerdStatus start_stream(SerdReadFunc read_func,
+ SerdStreamErrorFunc error_func,
+ void* stream,
+ const Node& name,
+ size_t page_size)
+ {
+ return serd_reader_start_stream(c_obj(),
+ read_func,
+ error_func,
+ stream,
+ name.c_obj(),
+ page_size);
+ }
+
+ SerdStatus
+ start_string(const StringRef& utf8, const Optional<Node>& name = {})
+ {
+ return serd_reader_start_string(c_obj(), utf8, name.ptr());
+ }
+
+ SerdStatus read_chunk() { return serd_reader_read_chunk(c_obj()); }
+
+ SerdStatus read_document() { return serd_reader_read_document(c_obj()); }
+
+ SerdStatus finish() { return serd_reader_finish(c_obj()); }
+};
+
+/**
+ @}
+ @name Writer
+ @{
+*/
+
+class ByteSink : public detail::Wrapper<SerdByteSink, serd_byte_sink_free>
+{
+public:
+ ByteSink(SerdWriteFunc sink, void* stream, size_t block_size)
+ : Wrapper(serd_byte_sink_new(sink, stream, block_size))
+ {
+ }
+
+ template <typename Traits>
+ ByteSink(std::basic_ostream<char, Traits>& stream)
+ : Wrapper(serd_byte_sink_new(detail::stream_sink<Traits>, &stream, 1))
+ {
+ }
+
+ // operator SerdByteSink*() { return c_obj(); }
+};
+
+class Writer : public detail::Wrapper<SerdWriter, serd_writer_free>, public Sink
+{
+public:
+ Writer(World& world,
+ const Syntax syntax,
+ const StyleFlags style,
+ Env& env,
+ ByteSink& sink)
+ : Wrapper(serd_writer_new(world.c_obj(),
+ SerdSyntax(syntax),
+ style,
+ env.c_obj(),
+ (SerdWriteFunc)serd_byte_sink_write,
+ sink.c_obj()))
+ , Sink(serd_writer_get_sink(c_obj()))
+ {
+ }
+
+ Writer(World& world,
+ const Syntax syntax,
+ const StyleFlags style,
+ Env& env,
+ SerdWriteFunc write_func,
+ void* stream)
+ : Wrapper(serd_writer_new(world.c_obj(),
+ SerdSyntax(syntax),
+ style,
+ env.c_obj(),
+ write_func,
+ stream))
+ , Sink(serd_writer_get_sink(c_obj()))
+ {
+ }
+
+ // FIXME: test
+ template <typename Traits>
+ Writer(World& world,
+ const Syntax syntax,
+ const StyleFlags style,
+ Env& env,
+ std::basic_ostream<char, Traits>& stream)
+ : Wrapper(serd_writer_new(world.c_obj(),
+ SerdSyntax(syntax),
+ style,
+ env.c_obj(),
+ detail::stream_sink<Traits>,
+ &stream))
+ , Sink(serd_writer_get_sink(c_obj()))
+ {
+ }
+
+ Status set_root_uri(const Node& uri)
+ {
+ return Status(serd_writer_set_root_uri(c_obj(), uri.c_obj()));
+ }
+
+ SerdStatus finish() { return serd_writer_finish(c_obj()); }
+};
+
+/**
+ @}
+*/
+
+class Iter : public detail::Copyable<SerdIter, serd_iter_copy, serd_iter_free>
+{
+public:
+ Iter(SerdIter* i) : Copyable(i) {}
+ Iter(const SerdIter* i) : Copyable(i) {}
+
+ Iter& operator++()
+ {
+ serd_iter_next(c_obj());
+ return *this;
+ }
+
+ Statement operator*() const { return Statement(serd_iter_get(c_obj())); }
+
+ bool operator==(const Iter& i)
+ {
+ return serd_iter_equals(c_obj(), i.c_obj());
+ }
+ bool operator!=(const Iter& i)
+ {
+ return !serd_iter_equals(c_obj(), i.c_obj());
+ }
+};
+
+class Range
+ : public detail::Copyable<SerdRange, serd_range_copy, serd_range_free>
+{
+public:
+ Range(SerdRange* r) : Copyable(r) {}
+ Range(const SerdRange* r) : Copyable(r) {}
+
+ Iter begin() const { return Iter(serd_range_begin(c_obj())); }
+ Iter end() const { return Iter(serd_range_end(c_obj())); }
+
+ bool operator==(const Range& i)
+ {
+ return serd_range_equals(c_obj(), i.c_obj());
+ }
+ bool operator!=(const Range& i)
+ {
+ return !serd_range_equals(c_obj(), i.c_obj());
+ }
+};
+
+class Model
+ : public detail::Copyable<SerdModel, serd_model_copy, serd_model_free>
+{
+public:
+ using value_type = Statement;
+ using iterator = Iter;
+ using const_iterator = Iter;
+
+ Model(World& world, SerdIndexOptions indices, SerdModelFlags flags)
+ : Copyable(serd_model_new(world.c_obj(), indices, flags))
+ {
+ }
+
+ Model(World& world, IndexOption indices, SerdModelFlags flags)
+ : Copyable(serd_model_new(world.c_obj(),
+ (SerdIndexOptions)indices,
+ flags))
+ {
+ }
+
+ size_t size() const { return serd_model_size(c_obj()); }
+
+ bool empty() const { return serd_model_empty(c_obj()); }
+
+ void insert(const Statement& s)
+ {
+ serd_model_insert(c_obj(), s.c_obj());
+ }
+
+ void insert(const Node& s,
+ const Node& p,
+ const Node& o,
+ const Optional<Node>& g = {})
+ {
+ serd_model_add(c_obj(), s.c_obj(), p.c_obj(), o.c_obj(), g.ptr());
+ }
+
+ Iter find(const Optional<Node>& s,
+ const Optional<Node>& p,
+ const Optional<Node>& o,
+ const Optional<Node>& g = {})
+ {
+ return Iter(
+ serd_model_find(c_obj(), s.ptr(), p.ptr(), o.ptr(), g.ptr()));
+ }
+
+ Range range(const Optional<Node>& s,
+ const Optional<Node>& p,
+ const Optional<Node>& o,
+ const Optional<Node>& g = {})
+ {
+ return Range(
+ serd_model_range(c_obj(), s.ptr(), p.ptr(), o.ptr(), g.ptr()));
+ }
+
+ Optional<Node> get(const Optional<Node>& s,
+ const Optional<Node>& p,
+ const Optional<Node>& o,
+ const Optional<Node>& g = {})
+ {
+ return Node(
+ serd_model_get(c_obj(), s.ptr(), p.ptr(), o.ptr(), g.ptr()));
+ }
+
+ bool ask(const Optional<Node>& s,
+ const Optional<Node>& p,
+ const Optional<Node>& o,
+ const Optional<Node>& g = {}) const
+ {
+ return serd_model_ask(c_obj(), s.ptr(), p.ptr(), o.ptr(), g.ptr());
+ }
+
+ bool operator==(const Model& model) const
+ {
+ return serd_model_equals(c_obj(), model.c_obj());
+ }
+
+ bool operator!=(const Model& model) const
+ {
+ return !serd_model_equals(c_obj(), model.c_obj());
+ }
+
+ Range all() const { return Range(serd_model_all(c_obj())); }
+
+ iterator begin() const { return iterator(serd_model_begin(c_obj())); }
+
+ iterator end() const { return iterator(serd_model_end(c_obj())); }
+
+ // operator SerdModel*() { return c_obj(); }
+};
+
+class Inserter : public detail::Wrapper<SerdInserter, serd_inserter_free>,
+ public Sink
+{
+public:
+ Inserter(Model& model, Env& env, const Optional<Node>& default_graph = {})
+ : Wrapper(serd_inserter_new(model.c_obj(),
+ env.c_obj(),
+ default_graph.ptr()))
+ , Sink(serd_inserter_get_sink(c_obj()))
+ {
+ }
+};
+
+} // 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..1f5656cc
--- /dev/null
+++ b/tests/serd_cxx_test.cpp
@@ -0,0 +1,397 @@
+/*
+ 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.
+*/
+
+#include "test_utils.h"
+
+#include "serd/serd.hpp"
+
+#include <cassert>
+#include <iostream>
+#include <sstream>
+#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.c_obj();
+
+ // Move construct
+ T moved{std::move(obj)};
+ CHECK(moved.c_obj() == ptr);
+ CHECK(!obj.c_obj());
+
+ // Move assign
+ obj = std::move(moved);
+ CHECK(obj.c_obj() == ptr);
+ CHECK(!moved.c_obj());
+
+ return 0;
+}
+
+template <typename T>
+static int
+test_copy(const T& obj)
+{
+ T copy{obj};
+ CHECK(copy == obj);
+
+ T copy_assigned{obj};
+ copy_assigned = obj;
+ CHECK(copy_assigned == obj);
+
+ return 0;
+}
+
+template <typename T>
+static int
+test_copy_move(const T& obj)
+{
+ T copy{obj};
+ CHECK(copy == obj);
+
+ T moved{std::move(copy)};
+ CHECK(moved == obj);
+ CHECK(copy != obj);
+
+ T copy_assigned{obj};
+ copy_assigned = obj;
+ CHECK(copy_assigned == obj);
+
+ T move_assigned{obj};
+ move_assigned = std::move(copy_assigned);
+ CHECK(move_assigned == obj);
+ CHECK(copy_assigned != obj);
+
+ return 0;
+}
+
+static int
+test_operators()
+{
+ int st = 0;
+
+ serd::World world;
+
+ serd::Model model(world, serd::IndexOption::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;
+ serd::ByteSink byte_sink{stream};
+
+ st |= test_copy_move(
+ serd::Optional<serd::Node>{serd::make_string("hello")});
+ st |= test_move_only(serd::World{});
+ st |= test_move_only(*model.begin());
+ st |= test_copy_move(serd::Env{});
+ // st |= test_move_only(serd::Reader{world, serd::Syntax::Turtle, sink, 4096});
+ st |= test_move_only(serd::ByteSink{stream});
+ 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_copy_move(model.all());
+ // st |= test_move_only(serd::Sink{});
+ // st |= test_move(serd::Statement{});
+
+ st |= test_copy_move(serd::Env{});
+
+ return st;
+}
+
+static int
+test_optional()
+{
+ test_copy_move(serd::Optional<serd::Node>(serd::make_string("node")));
+
+ const serd::Node node = serd::make_string("node");
+ const serd::Node other = serd::make_string("other");
+
+ // Truthiness
+ CHECK(!serd::Optional<serd::Node>());
+ CHECK(!serd::Optional<serd::Node>(nullptr));
+ CHECK(serd::Optional<serd::Node>(node));
+
+ // Comparison and general sanity
+ serd::Optional<serd::Node> optional{node};
+ CHECK(optional);
+ CHECK(optional == node);
+ CHECK(optional != other);
+ CHECK(*optional == node);
+ CHECK(optional->str() == "node");
+ CHECK(optional.ptr() != node.c_obj()); // non-const, must be a copy
+
+ // Reset
+ optional.reset();
+ CHECK(!optional);
+ CHECK(!optional.ptr());
+
+ // Copying and moving
+ serd::Node nonconst = serd::make_string("nonconst");
+ const SerdNode* c_node = nonconst.c_obj();
+
+ optional = nonconst;
+ serd::Optional<serd::Node> copied{optional};
+ CHECK(copied == nonconst);
+ CHECK(copied.ptr() != c_node);
+
+ optional = std::move(nonconst);
+ serd::Optional<serd::Node> moved{std::move(optional)};
+ CHECK(moved.ptr() == c_node);
+ CHECK(!optional);
+
+ serd::Optional<serd::Node> copy_assigned;
+ copy_assigned = optional;
+ CHECK(copy_assigned == optional);
+ CHECK(copy_assigned.ptr() != c_node);
+
+ serd::Optional<serd::Node> move_assigned;
+ move_assigned = std::move(moved);
+ CHECK(move_assigned.ptr() == c_node);
+ CHECK(!optional);
+
+ 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()
+{
+ using namespace serd;
+
+ const auto type = make_uri("http://example.org/Type");
+ const auto base = make_uri("http://example.org/");
+ const auto root = make_uri("http://example.org/");
+
+ CHECK(!test_node(make_string("hello")));
+ CHECK(!test_node(make_plain_literal("hello", "en")));
+ CHECK(!test_node(make_typed_literal("hello", type)));
+ CHECK(!test_node(make_blank("blank")));
+ CHECK(!test_node(make_curie("eg:curie")));
+ CHECK(!test_node(make_uri("http://example.org/thing")));
+ CHECK(!test_node(make_resolved_uri("thing", base)));
+ CHECK(!test_node(make_file_uri("/foo/bar", "host")));
+ CHECK(!test_node(make_file_uri("/foo/bar")));
+ CHECK(!test_node(make_file_uri("/foo/bar", "host")));
+ CHECK(!test_node(make_file_uri("/foo/bar")));
+ CHECK(!test_node(make_relative_uri("http://example.org/a", base)));
+ CHECK(!test_node(make_relative_uri("http://example.org/a", base, root)));
+ CHECK(!test_node(make_decimal(1.2, 7)));
+ CHECK(!test_node(make_decimal(3.4, 7, type)));
+ CHECK(!test_node(make_integer(56)));
+ CHECK(!test_node(make_integer(78, type)));
+ CHECK(!test_node(make_blob("blob", 4, true)));
+ CHECK(!test_node(make_blob("blob", 4, true, type)));
+
+ return 0;
+}
+
+static int
+test_reader()
+{
+ struct Sink : public serd::Sink
+ {
+ serd::Status statement(serd::StatementFlags,
+ const serd::Statement& statement) override
+ {
+ ++n_statements;
+ stream << statement.subject() << " " << statement.predicate() << " "
+ << statement.object() << std::endl;
+ return serd::Status::Success;
+ }
+
+ size_t n_statements{};
+ std::stringstream stream{};
+ };
+
+ Sink sink;
+ serd::World world;
+ serd::Reader reader(world, serd::Syntax::Turtle, sink, 4096);
+
+ reader.start_string("@prefix eg: <http://example.org> ."
+ "eg:s eg:p eg:o1 , eg:o2 .");
+ reader.read_document();
+
+ CHECK(sink.n_statements == 2);
+ CHECK(sink.stream.str() == "eg:s eg:p eg:o1\neg:s eg:p eg:o2\n");
+
+ return 0;
+}
+
+static int
+test_writer()
+{
+ serd::World world;
+ serd::Env env;
+ std::ostringstream stream;
+ serd::ByteSink sink(stream);
+ serd::Writer writer(world, serd::Syntax::Turtle, 0, env, sink);
+
+ writer.base(serd::make_uri("http://drobilla.net/base/"));
+ writer.set_root_uri(serd::make_uri("http://drobilla.net/"));
+ writer.prefix(serd::make_string("eg"),
+ serd::make_uri("http://example.org/"));
+ writer.nodes(0,
+ serd::make_uri("http://drobilla.net/base/s"),
+ serd::make_uri("http://example.org/p"),
+ serd::make_uri("http://drobilla.net/o"));
+
+ writer.finish();
+
+ CHECK(stream.str() == "@base <http://drobilla.net/base/> .\n"
+ "@prefix eg: <http://example.org/> .\n"
+ "\n"
+ "<s>\n"
+ "\teg:p <../o> .\n");
+
+ return 0;
+}
+
+static int
+test_env()
+{
+ serd::Env env;
+
+ const auto base = serd::make_uri("http://drobilla.net/");
+ env.set_base_uri(base);
+ CHECK(env.base_uri() == base);
+
+ env.set_prefix(serd::make_string("eg"),
+ serd::make_uri("http://example.org/"));
+
+ CHECK(env.qualify(serd::make_uri("http://example.org/foo")) ==
+ serd::make_curie("eg:foo"));
+
+ CHECK(env.expand(serd::make_uri("foo")) ==
+ serd::make_uri("http://drobilla.net/foo"));
+
+ serd::Env copied{env};
+ CHECK(copied.qualify(serd::make_uri("http://example.org/foo")) ==
+ serd::make_curie("eg:foo"));
+
+ CHECK(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"));
+
+ CHECK(assigned.qualify(serd::make_uri("http://example.org/foo")) ==
+ serd::make_curie("eg:foo"));
+
+ CHECK(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::IndexOption::SPO | serd::IndexOption::OPS, 0);
+
+ CHECK(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");
+
+ model.insert(s, p, o1);
+ model.insert(s, p, o2);
+
+ CHECK(!model.empty());
+ CHECK(model.size() == 2);
+ CHECK(model.ask(s, p, o1));
+ CHECK(model.ask(s, p, o1));
+ CHECK(!model.ask(s, p, s));
+
+ size_t total_count = 0;
+ for (const auto& statement : model) {
+ CHECK(statement.subject() == s);
+ CHECK(statement.predicate() == p);
+ CHECK(statement.object() == o1 || statement.object() == o2);
+ ++total_count;
+ }
+ CHECK(total_count == 2);
+
+ size_t o1_count = 0;
+ for (const auto& statement : model.range({}, {}, o1)) {
+ CHECK(statement.subject() == s);
+ CHECK(statement.predicate() == p);
+ CHECK(statement.object() == o1);
+ ++o1_count;
+ }
+ CHECK(o1_count == 1);
+
+ size_t o2_count = 0;
+ for (const auto& statement : model.range({}, {}, o2)) {
+ CHECK(statement.subject() == s);
+ CHECK(statement.predicate() == p);
+ CHECK(statement.object() == o2);
+ ++o2_count;
+ }
+ CHECK(o2_count == 1);
+
+ serd::Model copy(model);
+ CHECK(copy == model);
+
+ copy.insert(s, p, s);
+ CHECK(copy != model);
+
+ return 0;
+}
+
+int
+main()
+{
+ int st = 0;
+
+ st |= test_operators();
+ st |= test_optional();
+ st |= test_nodes();
+ st |= test_env();
+ st |= test_reader();
+ st |= test_writer();
+ st |= test_model();
+
+ return st;
+}
diff --git a/wscript b/wscript
index 7ceec45b..4c1bb4e6 100644
--- a/wscript
+++ b/wscript
@@ -21,6 +21,7 @@ out = 'build' # Build directory
def options(ctx):
ctx.load('compiler_c')
+ ctx.load('compiler_cxx')
autowaf.set_options(ctx, test=True)
opt = ctx.get_option_group('Configuration options')
autowaf.add_flags(
@@ -39,8 +40,10 @@ def options(ctx):
def configure(conf):
autowaf.display_header('Serd Configuration')
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,
@@ -126,7 +129,7 @@ lib_source = ['src/base64.c',
def build(bld):
# 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*'))
# Pkgconfig file
autowaf.build_pc(bld, 'SERD', SERD_VERSION, SERD_MAJOR_VERSION, [],
@@ -165,6 +168,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', '-fno-inline-small-functions', '-fno-default-inline'],
'linkflags': [''] if bld.env.NO_COVERAGE else ['--coverage'],
'lib': lib_args['lib'],
'install_path': ''}
@@ -191,6 +195,13 @@ def build(bld):
defines = defines,
**test_args)
+ 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']:
@@ -219,8 +230,8 @@ def build(bld):
bld.install_files('${MANDIR}/man1', 'doc/serdi.1')
bld.add_post_fun(autowaf.run_ldconfig)
- if bld.env.DOCS:
- bld.add_post_fun(lambda ctx: autowaf.make_simple_dox(APPNAME))
+ # if bld.env.DOCS:
+ # bld.add_post_fun(lambda ctx: autowaf.make_simple_dox(APPNAME))
def lint(ctx):
"checks code for style issues"
@@ -531,14 +542,16 @@ def test(ctx):
srcdir = ctx.path.abspath()
os.environ['PATH'] = '.' + os.pathsep + os.getenv('PATH')
- autowaf.pre_test(ctx, APPNAME)
+ dirs = ['.', 'src', 'tests']
+ autowaf.pre_test(ctx, APPNAME, dirs=dirs)
autowaf.run_tests(ctx, APPNAME,
['serd_test',
+ 'serd_cxx_test',
'read_chunk_test',
'nodes_test',
'overflow_test',
'model_test'],
- name='Unit')
+ dirs=dirs, name='Unit')
def test_syntax_io(in_name, expected_name, lang):
in_path = 'tests/good/%s' % in_name
@@ -604,7 +617,7 @@ def test(ctx):
for opts in ['', '-m']:
run_test_suites(ctx, opts)
- autowaf.post_test(ctx, APPNAME)
+ autowaf.post_test(ctx, APPNAME, dirs=dirs)
def posts(ctx):
path = str(ctx.path.abspath())