Commit 5b22c131 authored by EXT Arnaud Clère's avatar EXT Arnaud Clère
Browse files

Isolated the 200 core lines of QBind in QBind.hpp

Grouped QJson* support in QJson.hpp
Kept use cases and benchmarks in main.cpp
parent 88ea9759
/****************************************************************************
* **
* ** Copyright (C) 2017 MinMaxMedical.
* ** All rights reserved.
* ** Contact: MinMaxMedical <InCAS@MinMaxMedical.com>
* **
* ** This file is part of the modmedLog module.
* **
* ** $QT_BEGIN_LICENSE:BSD$
* ** You may use this file under the terms of the BSD license as follows:
* **
* ** "Redistribution and use in source and binary forms, with or without
* ** modification, are permitted provided that the following conditions are
* ** met:
* ** * Redistributions of source code must retain the above copyright
* ** notice, this list of conditions and the following disclaimer.
* ** * Redistributions in binary form must reproduce the above copyright
* ** notice, this list of conditions and the following disclaimer in
* ** the documentation and/or other materials provided with the
* ** distribution.
* ** * Neither the name of MinMaxMedical S.A.S. and its Subsidiary(-ies) nor
* ** the names of its contributors may be used to endorse or promote
* ** products derived from this software without specific prior written
* ** permission.
* **
* ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
* ** $QT_END_LICENSE$
* **
* ****************************************************************************/
#pragma once
#include <QtCore/qglobal.h>
#define Q_ENABLE_SWAP(Class, Statements) \
Class ( ) = default; \
Class (Class&& o) noexcept { Statements } \
Class& operator=(Class&& o) noexcept { if (this!=&o) { Statements } return *this; }
// //////////////////////////////////////////////////////////////////////////
// Generic fluent interface for traversing and processing structured value types (serialize, deserialize, construct generic in-memory data structures, etc...)
// See Person::bind() definition below for an example
enum BindMode { Read, Write }; //!< drives QBind<TResult,T>() traversal and processing (the design would support others like Append)
template<class T_> class Rec; //!< a Record data structure (not defined in this Proof-Of-Concept for the sake of simplicity)
template<class T_> class Seq; //!< a Sequence data structure defined below
template<class T_> class Val //!< a choice of sequence(), null(), or any value with a textual representation
{
Q_DISABLE_COPY(Val) // but not swap
public:
Q_ENABLE_SWAP(Val, std::swap(m_val, o.m_val); std::swap(m_out, o.m_out);)
// T_ can be either a TResult or any combination of nested Seq or Rec like T_ = Rec<Seq<TResult>
using TResult = typename T_::TResult; //!< the start and end point of the traversal as well as the associated processing (see QJsonWriter below for an example)
using TValue = typename TResult::TValue;
static const BindMode Mode = TResult::Mode;
Val(TValue val, T_&& out) { std::swap(m_val, val); std::swap(m_out, out); }
operator bool() { return bool(m_val); } //!< to drive QBind<TResult,T>() traversal
Seq<T_> sequence() { auto seq = Q_LIKELY(m_val) ? m_val->sequence() : nullptr ; return Seq<T_>(seq, seq ? std::move(m_out) : T_()); }
/**/T_ null() { auto n = Q_LIKELY(m_val) ? m_val-> null() : false ; return n ? std::move(m_out) : T_() ; }
template<typename T> T_ bind(const T& t) { auto v = Q_LIKELY(m_val) ? m_val->bind(t) : false ; return v ? std::move(m_out) : T_() ; }
template<typename T> T_ bind( T& t) { auto v = Q_LIKELY(m_val) ? m_val->bind(t) : false ; return v ? std::move(m_out) : T_() ; }
template<typename T> T_ bind( T&& t) { auto v = Q_LIKELY(m_val) ? m_val->bind(t) : false ; return v ? std::move(m_out) : T_() ; }
private:
TValue m_val = TValue(); //!< gives semantic to the traversal (nullptr if already moved)
T_ m_out = T_ (); //!< moved context of current traversal up to TResult that will reference the val itself (be it a QIODevice or QCborValue)
};
template<class T_> class Seq
{
Q_DISABLE_COPY(Seq) // but not swap
public:
Q_ENABLE_SWAP(Seq, std::swap(m_seq, o.m_seq); std::swap(m_out, o.m_out);)
using TResult = typename T_::TResult;
using TValue = typename TResult::TValue;
using TSequence = typename TResult::TSequence;
static const BindMode Mode = TResult::Mode;
Seq(TSequence seq, T_&& out) { std::swap(m_seq, seq); std::swap(m_out, out); }
operator bool() { return m_seq; } //!< to drive QBind<TResult,T>() traversal
Val<Seq<T_>> item() { auto v = Q_LIKELY(m_seq) ? m_seq->item() : nullptr; return Val<Seq<T_>>(v, v ? std::move(*this) : Seq<T_>()); }
T_ out() { if (Q_LIKELY(m_seq) ) m_seq->out () ; return std::move(m_out) ; }
operator TResult() { return out(); /* recursively calls operator TResult() as mandated by T_ */ }
TResult result() { return operator TResult(); }
// Shortcuts
template<typename T> Seq<T_> bind(const T& t) { return item().bind(t); }
template<typename T> Seq<T_> bind( T& t) { return item().bind(t); }
template<typename T> Seq<T_> bind( T&& t) { return item().bind(t); }
private:
TSequence m_seq = TSequence();
T_ m_out = T_ ();
};
// //////////////////////////////////////////////////////////////////////////
// QBind<TResult,T>
#include <type_traits>
template<class TResult, typename T>
struct QBind { static TResult bind(Val<TResult> value, T& t) {
return t.bind(std::move(value)); // In case of error, define a T::bind(Val<TResult>) method or an external QBind<TResult,T>::bind(Val<TResult>,T)
}};
template<class TResult, typename T>
struct QBind<TResult, const T> { static TResult bind(Val<TResult> value, const T& t) {
static_assert(TResult::Mode==Write,"Cannot Read from TResult into const T&");
return t.bind(std::move(value)); // In case of error, define a T::bind(Val<TResult>) method or an external QBind<TResult,T>::bind(Val<TResult>,T)
}};
// --------------------------------------------------------------------------
// QBind partial specializations (generic on TResult, usually not on TResult::Mode for dynamically-sized or builtin types)
#include <cstddef>
template<class TResult>
struct QBind<TResult, std::nullptr_t> { static TResult bind(Val<TResult> value, std::nullptr_t&) {
return value.null();
}};
#include <QtCore/qstring.h>
template<class TResult>
struct QBind<TResult, const QString> { static TResult bind(Val<TResult> dst, const QString& src) {
static_assert(TResult::Mode==Write,"Cannot Read from TResult into const QString&");
return dst.bind(src.toUtf8().constData());
}};
template<class TResult>
struct QBind<TResult, QString> {
static TResult bind(Val<TResult> dst, QString& src, std::enable_if_t<TResult::Mode==Write>* = nullptr) {
return dst.bind(src.toUtf8().constData());
}
static TResult bind(Val<TResult> src, QString& dst, std::enable_if_t<TResult::Mode==Read >* = nullptr) {
QByteArray ba;
auto result = src.bind(ba);
if (result)
dst = QString::fromUtf8(ba);
return result;
}
};
#include <QtCore/qvector.h>
template<class TResult, typename T>
struct QBind<TResult, const QVector<T>> { static TResult bind(Val<TResult> dst, const QVector<T>& src) {
static_assert(TResult::Mode==Write,"Cannot Read from TResult into const QVector<T>&");
auto s(dst.sequence());
for (auto&& t : src) {
s = s.bind(t);
}
return s;
}};
template<class TResult, typename T>
struct QBind<TResult, QVector<T>> {
static TResult bind(Val<TResult> dst, QVector<T>& src, std::enable_if_t<TResult::Mode==Write>* = nullptr) {
auto s(dst.sequence());
for (auto&& t : src) {
s = s.bind(t);
}
return s;
}
static TResult bind(Val<TResult> src, QVector<T>& dst, std::enable_if_t<TResult::Mode==Read >* = nullptr) {
auto s(src.sequence());
for (auto i = s.item(); i; i = s.item()) {
T t;
s = i.bind(t);
if (s)
dst.push_back(t);
}
return s;
}
};
// --------------------------------------------------------------------------
// QBind specialization for generic Val<TSource>
template<class TResult, class TSource>
struct QBind<TResult, Val<TSource>> { static TResult bind(Val<TResult> dst, Val<TSource>& src) {
static_assert(TSource::Mode==Read ,"cannot read from TSource");
static_assert(TResult::Mode==Write,"cannot write to TResult");
auto srcNull(src.null());
if (srcNull)
return dst.null();
auto srcSequence(src.sequence());
if (srcSequence) {
auto dstSequence(dst.sequence());
for (auto srcItem(srcSequence.item()); srcItem && dstSequence; srcItem=srcSequence.item()) {
dstSequence = dstSequence.bind(srcItem);
}
return dstSequence;
}
QString srcData; // TODO Use QBindDispatch to avoid losing native types
if (src.bind(srcData))
return dst.bind(srcData);
else
return dst.null();
}};
// //////////////////////////////////////////////////////////////////////////
// Deferred Write bind
#include <memory>
#include <cstddef>
template<class TResult>
struct IWritable
{
virtual ~IWritable() = default;
virtual TResult writeTo(Val<TResult>&& v) const = 0;
};
//! A shared const copy of T that can be bound to a TResult where Mode == Write
//! \see https://stackoverflow.com/questions/18856824/ad-hoc-polymorphism-and-heterogeneous-containers-with-value-semantics
template<class TResult>
class QWritable
{
std::shared_ptr<const IWritable<TResult>> m_writable; //!< Copyable, moveable
public:
template<typename T>
QWritable(T t) : m_writable(std::make_shared<const Copy<TResult, T>>(std::move(t))) {}
QWritable( ) : m_writable(std::make_shared<const Copy<TResult,std::nullptr_t>>(nullptr )) {}
TResult bind(Val<TResult> v) { return m_writable->writeTo(std::move(v)); }
template<class TOtherResult> operator QWritable<TOtherResult>() { return *this; }
private:
template<class TRes, typename T>
struct Copy : IWritable<TRes> {
Copy(T&& t) : m_t(std::move(t)) {}
TRes writeTo(Val<TRes>&& v) const override { return v.bind(m_t); }
template<class TOtherRes> operator Copy<TOtherRes,T>() { return *this; }
private:
const T m_t;
};
};
......@@ -28,4 +28,5 @@ SOURCES += \
HEADERS += \
QCborWriter.hpp \
QJsonReader.hpp
QJson.hpp \
QBind.hpp
......@@ -89,6 +89,9 @@ enum tag{
} // cbor
// //////////////////////////////////////////////////////////////////////////
// QBind<QCborWriter,T> support
class QCborWriter
{
class Impl;
......
......@@ -40,7 +40,85 @@
#pragma once
#include <type_traits>
#include <QtCore/qiodevice.h>
#include <QtCore/qstack.h>
// //////////////////////////////////////////////////////////////////////////
// QBind<QJson*,T> support
class QJsonWriter
{
class Impl;
Q_DISABLE_COPY(QJsonWriter) // but not swap
public:
Q_ENABLE_SWAP(QJsonWriter, std::swap(m, o.m); std::swap(m_owned, o.m_owned); )
using TResult = QJsonWriter;
using TValue = QJsonWriter::Impl*;
using TSequence = QJsonWriter::Impl*;
static const BindMode Mode = BindMode::Write;
QJsonWriter(QIODevice* io) : m(new Impl(io)), m_owned(true) {}
~QJsonWriter() { if (m && m_owned) delete m; }
operator bool() { return m; } //!< to drive QBind<TResult,T>() traversal
Val<QJsonWriter> begin() { auto beforeMove = m; return std::move(Val<QJsonWriter>(beforeMove, std::move(*this))); }
// Shortcuts
Seq<QJsonWriter> sequence() { return begin().sequence(); }
template<typename T> QJsonWriter bind(const T& t) { return begin().bind(t); }
template<typename T> QJsonWriter bind( T&& t) { return begin().bind(t); }
private:
friend class Impl; // uses method below
QJsonWriter(Impl* m) : m(m), m_owned(false) {}
Impl* m = nullptr;
bool m_owned = true;
class Impl {
private:
struct Level { const char* sep; const char* end; };
QIODevice* io;
QStack<Level> levels = QStack<Level>(); //!< minimal dynamic context to implement out() and ensure well-formedness in case TResult is abandoned
public:
Impl(QIODevice* io) : io(io) { Q_ASSERT(io); }
~Impl() { for (auto&& level : levels) io->write(level.end); }
protected:
template<class T_> friend class Val; // calls methods below
Impl* sequence() { levels.push(Level{"","]"});
io->write( "[" );
return this; }
bool null() { io->write( "null" );
return true; }
bool bind(const char* s) { io->write( "\"" );
io->write( s ); // TODO escape
io->write( "\"" );
return true; }
// Natively supported overloads
bool bind( float n) { io->write(QByteArray::number(n, 'g', std::numeric_limits< float>::max_digits10)); return true; }
bool bind( double n) { io->write(QByteArray::number(n, 'g', std::numeric_limits<double>::max_digits10)); return true; }
bool bind(qulonglong n) { io->write(QByteArray::number(n )); return true; }
bool bind( qlonglong n) { io->write(QByteArray::number(n )); return true; }
// This dispatch would be more simple with C++17 constexpr if
template<typename T> bool bind(T t, std::enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value>* = nullptr) { return bind(qulonglong(t)); }
template<typename T> bool bind(T t, std::enable_if_t<std::is_integral<T>::value && std:: is_signed<T>::value>* = nullptr) { return bind( qlonglong(t)); }
// Val<TResult> prevents QBind from getting out() of an outer Seq for instance
template<typename T> bool bind(const T& t) { return QBind<TResult,T>::bind(Val<TResult>(this, TResult(this)), const_cast<T&>(t)); } // t will not be modified anyway
template<typename T> bool bind( T& t) { return QBind<TResult,T>::bind(Val<TResult>(this, TResult(this)), t ); }
template<typename T> bool bind( T&& t) { return QBind<TResult,T>::bind(Val<TResult>(this, TResult(this)), t ); }
template<class T_> friend class Seq; // calls methods below
Impl* item() { io->write(levels.last().sep); levels.last().sep = ","; return this; }
Impl* out() { io->write(levels.pop() .end) ; return this; }
};
};
class QJsonReader
{
......@@ -250,3 +328,115 @@ private:
}
};
};
// //////////////////////////////////////////////////////////////////////////
// QBind<TResult,QJson*> support
#include <QtCore/qjsonvalue.h>
#include <QtCore/qjsonarray.h>
template<class TResult>
struct QBind<TResult, const QJsonValue> { static TResult bind(Val<TResult> dst, const QJsonValue& src) {
static_assert(TResult::Mode==Write,"Cannot Read from TResult into const QJsonValue&");
if (src.isArray ()) return dst.bind(src.toArray ());
if (src.isDouble()) return dst.bind(src.toDouble());
if (src.isString()) return dst.bind(src.toString());
return dst.null();
}};
template<class TResult>
struct QBind<TResult, QJsonValue> {
static TResult bind(Val<TResult> dst, QJsonValue& src, std::enable_if_t<TResult::Mode==Write>* = nullptr) {
if (src.isArray ()) return dst.bind(src.toArray ());
if (src.isDouble()) return dst.bind(src.toDouble());
if (src.isString()) return dst.bind(src.toString());
return dst.null();
}
static TResult bind(Val<TResult> src, QJsonValue& dst, std::enable_if_t<TResult::Mode==Read >* = nullptr) {
auto s(src.sequence());
if (s) {
QJsonArray array;
for (auto i = s.item(); i; i = s.item()) {
QJsonValue v;
s = i.bind(v);
if (s)
array.push_back(v);
}
dst = array;
return s;
}
double d; QString t;
auto
result = src.bind(d); if (result) { dst = QJsonValue(d); return result; }
result = src.bind(t); if (result) { dst = QJsonValue(t); return result; }
dst = QJsonValue(); return src.null();
}
};
template<class TResult>
struct QBind<TResult, const QJsonValueRef> { static TResult bind(Val<TResult> dst, const QJsonValueRef& src) {
static_assert(TResult::Mode==Write,"Cannot Read from TResult into const QJsonValueRef&");
if (src.isArray ()) return dst.bind(src.toArray ());
if (src.isDouble()) return dst.bind(src.toDouble());
if (src.isString()) return dst.bind(src.toString());
return dst.null();
}};
template<class TResult>
struct QBind<TResult, QJsonValueRef> {
static TResult bind(Val<TResult> dst, QJsonValueRef& src, std::enable_if_t<TResult::Mode==Write>* = nullptr) {
if (src.isArray ()) return dst.bind(src.toArray ());
if (src.isDouble()) return dst.bind(src.toDouble());
if (src.isString()) return dst.bind(src.toString());
return dst.null();
}
static TResult bind(Val<TResult> src, QJsonValueRef& dst, std::enable_if_t<TResult::Mode==Read >* = nullptr) {
auto s(src.sequence());
if (s) {
QJsonArray array;
for (auto i = s.item(); i; i = s.item()) {
QJsonValueRef v;
s = i.bind(v);
if (s)
array.push_back(v);
}
dst = array;
return s;
}
double d; QString t;
auto
result = src.bind(d); if (result) { dst = QJsonValueRef(d); return result; }
result = src.bind(t); if (result) { dst = QJsonValueRef(t); return result; }
dst = QJsonValueRef(); return src.null();
}
};
#include <QtCore/qjsonarray.h>
template<class TResult>
struct QBind<TResult, const QJsonArray> { static TResult bind(Val<TResult> dst, const QJsonArray& src) {
static_assert(TResult::Mode==Write,"Cannot Read from TResult into const QJsonArray&");
auto s(dst.sequence());
for (auto&& item : src) {
s = s.bind(item);
}
return s;
}};
template<class TResult>
struct QBind<TResult, QJsonArray> {
static TResult bind(Val<TResult> dst, QJsonArray& src, std::enable_if_t<TResult::Mode==Write>* = nullptr) {
auto s(dst.sequence());
for (auto item : src) {
s = s.bind(item);
}
return s;
}
static TResult bind(Val<TResult> src, QJsonArray& dst, std::enable_if_t<TResult::Mode==Read >* = nullptr) {
auto s(src.sequence());
for (auto i = s.item(); i; i = s.item()) {
QJsonValue v;
s = i.bind(v);
if (s)
dst.push_back(v);
}
return s;
}
};
This diff is collapsed.
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment