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

Implemented QBind<Val,Val> for direct JsonReader>CborWriter

Fixed a few errors
parent 261558eb
......@@ -40,6 +40,10 @@
#pragma once
#include <QtCore/qglobal.h>
#define Q_PROTECTED_COPY(Class) \
protected: \
Class(const Class &) = default; \
Class &operator=(const Class &) = delete;
#define Q_ENABLE_MOVE_DEFAULT(Class) \
Class ( ) = default; \
Class (Class&& o) = default; \
......@@ -52,7 +56,10 @@
#include <QtCore/qbytearray.h>
// //////////////////////////////////////////////////////////////////////////
// Generic fluent interface for traversing and processing structured value types (serialize, deserialize, construct generic in-memory data structures, etc...)
// Generic fluent interface for traversing structured values and processing them along the way:
// - serializing, deserializing
// - constructing 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)
......@@ -81,6 +88,9 @@ public:
template<typename T> T_ bind( T& t) { if (Q_LIKELY(m_out) && m_out-> _bind(t)) return std::move(m_out) ; return T_ (); }
template<typename T> T_ bind( T&& t) { if (Q_LIKELY(m_out) && m_out-> _bind(t)) return std::move(m_out) ; return T_ (); }
private:
// Val<TResult> innerValue() { return Val<TResult>(/*TResult*/(m_out)); }
// may not compile without a Seq member returning a TResult without side-effect (TItem is not available as in modmed)
T_ m_out = T_(); //!< moved context of current traversal up to TResult that will reference the val itself (be it a QIODevice or QCborValue)
};
......@@ -101,26 +111,30 @@ public:
TImpl* operator->() { return m_out.operator ->(); }
Val<Seq<T_>> item() { if (Q_LIKELY(m_out) && m_out->_item()) return Val<Seq<T_>>(std::move(*this)); return Val<Seq<T_>>(); }
T_ out() { if (Q_LIKELY(m_out) && m_out-> _out()) return std::move(m_out) ; return T_ (); }
/**/ T_ out() { if (Q_LIKELY(m_out) && m_out-> _out()) return std::move(m_out) ; return T_ (); }
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); }
template<typename T> Seq<T_> bind(const T& t) { if (itemValue().bind(t)) return std::move(*this); return Seq<T_>(); } // binding Val<T_> instead of Val<Seq<T_>> requires less code generation
template<typename T> Seq<T_> bind( T& t) { if (itemValue().bind(t)) return std::move(*this); return Seq<T_>(); }
template<typename T> Seq<T_> bind( T&& t) { if (itemValue().bind(t)) return std::move(*this); return Seq<T_>(); }
private:
template<class TResult, typename T> friend class QBind;
Val<T_> itemValue() { if (Q_LIKELY(m_out) && m_out->_item()) { T_ scopedT(m_out); return std::move(Val<T_>(std::move(scopedT))); } return Val<T_>(); } // the m_out copy must be scoped and will be invalid when the original is destroyed
T_ m_out = T_();
};
//! Base class for TResult classes that require a single heap allocation (and lock) but:
//! - are simple to implement and de facto pimpled
//! - provide users with direct access to the fluent interface
//! Base class for TResult classes that require a single heap allocation (and lock)
//! but are simple to implement and de facto pimpled
template<class TResult_, class TImpl_, BindMode Mode_>
class QScopedResult
{
Q_DISABLE_COPY(QScopedResult)
protected:
QScopedResult(const QScopedResult &o) : m(o.m), m_owned(false) {}
QScopedResult &operator=(const QScopedResult &) = delete;
public:
Q_ENABLE_MOVE(QScopedResult, std::swap(m, o.m); std::swap(m_owned, o.m_owned); )
~QScopedResult() { if (m && m_owned) delete m; }
......@@ -149,13 +163,12 @@ protected:
//! Base class for TResult classes that can exhibit high performance (they require no heap allocation and no lock)
//! \warning
//! - users must call .value() prior to using the fluent interface
//! - it is more difficult to implement TResult correctly, especially with internal state (need to understand move semantics)
//! - TResult is not pimpled by default
template<class TResult_, BindMode Mode_>
class QMovedResult
{
Q_DISABLE_COPY(QMovedResult)
Q_PROTECTED_COPY(QMovedResult)
public:
Q_ENABLE_MOVE_DEFAULT(QMovedResult)
......@@ -185,6 +198,10 @@ 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)
}};
// NB: QBind has a structure similar to a Traversable : https://wiki.haskell.org/Typeclassopedia#Traversable
// A QTraverse<Val<TResult>>,T> would be equivalent but would not mandate to actually traverse T to get from Val<TResult> to the actual TResult
// A QFold<TResult,T> would be equivalent to a Foldable allowing to fold T to any TResult without mandating a common Val/Seq structure
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&");
......@@ -291,32 +308,37 @@ struct QBind<TResult, QVector<T>> {
};
// --------------------------------------------------------------------------
// 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");
if (!src || src.null())
return dst.null();
Seq<TSource> srcSequence(src.sequence());
if (srcSequence) {
Seq<TResult> dstSequence(dst.sequence());
if (dstSequence) {
// for (auto srcItem=srcSequence.item(); srcItem /*&& dstSequence*/; srcItem=srcSequence.item()) {
// dstSequence = QBind<Seq<TResult>,Val<Seq<TSource>>>::bind(dstSequence.item(),srcItem);
// }
// QBind specialization for generic Val<TDst>
template<class TSrcResult, class TDst>
struct QBind<TSrcResult, Val<TDst>> { static TSrcResult bind(Val<TSrcResult> src, Val<TDst>& dst) {
static_assert(TSrcResult::Mode==Read ,"cannot read from TSrcResult");
static_assert(TDst ::Mode==Write,"cannot write to TDst" );
Seq<TSrcResult> srcSeq; Seq<TDst> dstSeq;
if ( srcSeq = src.sequence()) {
/**/ dstSeq = dst.sequence();
Val<TSrcResult> srcVal; Val<TDst> dstVal;
while (srcVal = srcSeq.itemValue()) { // using item()'s Val<Seq<TSrcResult> would cause an infinite compiler loop to generate corresponding QBind<Val<Seq<Seq<...>>,_> functions
dstVal = dstSeq.itemValue();
srcSeq = QBind<TSrcResult, Val<TDst>>::bind(std::move(srcVal), dstVal);
}
return dstSequence.out();
/**/ dstSeq.out();
return srcSeq.out();
}
QString srcData; // TODO Use QBindDispatch to avoid losing native types
if (src.bind(srcData))
return dst.bind(srcData);
TSrcResult srcRes;
double d; if (srcRes = src.bind(d)) { dst.bind(d); return srcRes; }
// TODO other native types we do not want to treat as text: bool, qlonglong, qulonglong, float (to optimize CborWriter output)
QString t; if (srcRes = src.bind(t)) { dst.bind(t); return srcRes; }
/**/ dst.null();
return src.null();
return dst.null();
}};
// //////////////////////////////////////////////////////////////////////////
......
......@@ -97,17 +97,20 @@ enum tag{
class CborWriter;
class QCborWriter : public QScopedResult<QCborWriter, CborWriter, BindMode::Write>
{
Q_DISABLE_COPY(QCborWriter)
Q_PROTECTED_COPY(QCborWriter)
public:
Q_ENABLE_MOVE_DEFAULT(QCborWriter)
QCborWriter(QIODevice* io);
private:
template<class T_> friend class Seq;
friend class CborWriter; // uses method below
QCborWriter(CborWriter* outer) : QScopedResult(outer, false) {}
};
class CborWriter : public QMovedResult<CborWriter, BindMode::Write>
{
Q_DISABLE_COPY(CborWriter)
protected:
CborWriter(const CborWriter &o) : QMovedResult(), io(o.io), levels(0) {} // It is important that scoped copies start again from level 0
CborWriter &operator=(const CborWriter &) = delete;
public:
Q_ENABLE_MOVE(CborWriter, std::swap(io, o.io); std::swap(levels, o.levels); )
CborWriter(QIODevice* io) : io(io) { Q_ASSERT(io); }
......
......@@ -55,16 +55,19 @@
class QJsonBuilderImpl;
class QJsonBuilder : public QScopedResult<QJsonBuilder, QJsonBuilderImpl, BindMode::Write>
{
Q_DISABLE_COPY(QJsonBuilder)
Q_PROTECTED_COPY(QJsonBuilder)
public:
Q_ENABLE_MOVE_DEFAULT(QJsonBuilder)
QJsonBuilder(QJsonValue* v);
private:
template<class T_> friend class Seq;
friend class QJsonBuilderImpl; // uses method below
QJsonBuilder(QJsonBuilderImpl* outer) : QScopedResult(outer, false) {}
};
class QJsonBuilderImpl
{
Q_DISABLE_COPY(QJsonBuilderImpl)
QJsonValue* result;
struct Step { const char* key; /* TODO union */ QJsonObject object; QJsonArray array; Step(const char* k=nullptr) : key(k) {}};
QStack<Step> levels = QStack<Step>(); //!< minimal dynamic context to implement out() and ensure actual building in case QJsonBuilderImpl is abandoned
......@@ -75,10 +78,10 @@ protected:
bool _record() { levels.push(Step("" )); return true; }
bool _sequence() { levels.push(Step(nullptr)); return true; }
bool _null() { set(QJsonValue () ); return true; }
bool _bind(const char* s) { set(s ); return true; }
bool _bind(double d) { set(d ); return true; }
bool _bind(bool b) { set(b ); return true; }
bool _null() { set(QJsonValue ()); return true; }
bool _bind(const char* s) { set(s ); return true; }
bool _bind(double d) { set(d ); return true; }
bool _bind(bool b) { set(b ); return true; }
template<typename T> bool _bind(T t, std::enable_if_t<std::is_arithmetic<T>::value>* = nullptr) { return _bind(double(t)); }
......@@ -109,18 +112,21 @@ QJsonBuilder::QJsonBuilder(QJsonValue* v) : QScopedResult(new QJsonBuilderImpl(v
class QJsonWriterImpl;
class QJsonWriter : public QScopedResult<QJsonWriter, QJsonWriterImpl, BindMode::Write>
{
Q_DISABLE_COPY(QJsonWriter)
Q_PROTECTED_COPY(QJsonWriter)
public:
Q_ENABLE_MOVE_DEFAULT(QJsonWriter)
QJsonWriter(QIODevice* io);
private:
template<class T_> friend class Seq;
friend class QJsonWriterImpl; // uses method below
QJsonWriter(QJsonWriterImpl* outer) : QScopedResult(outer, false) {}
};
class QJsonWriterImpl
{
private:
Q_DISABLE_COPY(QJsonWriterImpl)
struct Step { const char* sep; const char* end; };
QIODevice* io;
QStack<Step> levels = QStack<Step>(); //!< minimal dynamic context to implement out() and ensure well-formedness in case TResult is abandoned
public:
......@@ -153,24 +159,27 @@ protected:
template<class T_> friend class Seq; // calls methods below
bool _item() { io->write(levels.last().sep); levels.last().sep = ","; return this; }
bool _out() { io->write(levels.pop() .end) ; return this; }
bool _item() { io->write(levels.last().sep); levels.last().sep = ","; return true; }
bool _out() { io->write(levels.pop() .end) ; return true; }
};
QJsonWriter::QJsonWriter(QIODevice* io) : QScopedResult(new QJsonWriterImpl(io), true) {}
class QJsonReaderImpl;
class QJsonReader : public QScopedResult<QJsonReader, QJsonReaderImpl, BindMode::Read>
{
Q_DISABLE_COPY(QJsonReader)
Q_PROTECTED_COPY(QJsonReader)
public:
Q_ENABLE_MOVE_DEFAULT(QJsonReader)
QJsonReader(QIODevice* io);
private:
template<class T_> friend class Seq;
friend class QJsonReaderImpl; // uses method below
QJsonReader(QJsonReaderImpl* outer) : QScopedResult(outer, false) {}
};
class QJsonReaderImpl
{
Q_DISABLE_COPY(QJsonReaderImpl)
struct Step { int index = -1; const char* end; };
QIODevice* io;
......
......@@ -76,7 +76,7 @@ struct Person
class TextWriter : public QMovedResult<TextWriter, BindMode::Write>
{
Q_DISABLE_COPY(TextWriter)
Q_PROTECTED_COPY(TextWriter)
public:
Q_ENABLE_MOVE_DEFAULT(TextWriter) // for QMovedResult
TextWriter(QIODevice* io) : io(io) { Q_ASSERT(io); }
......@@ -92,13 +92,15 @@ protected:
// 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>(TResult(io)), const_cast<T&>(t)); } // t will not be modified anyway
template<typename T> bool _bind( T& t) { return QBind<TResult,T>::bind(Val<TResult>(TResult(io)), t ); }
template<typename T> bool _bind( T&& t) { return QBind<TResult,T>::bind(Val<TResult>(TResult(io)), t ); }
template<typename T> bool _bind( T&& t) { return QBind<TResult,T>::bind(Val<TResult>(TResult(io)), t ); } // TODO return TResult(*this).bind(t);
template<class T_> friend class Seq; // enables calling methods below through operator->()
bool _item() { io->write(" "); return true; }
bool _out() { io->write("]"); return true; }
private:
template<class T_> friend class Seq;
QIODevice* io = nullptr; // for QMovedResult
};
......@@ -327,7 +329,7 @@ int main()
STOP("T>Cbor",QString::fromUtf8(b.buffer().toHex()))
START {
json.seek(0); b.seek(0); b.buffer().clear();
QCborWriter(&b).bind(QJsonReader(&json).value());
QJsonReader(&json).bind(QCborWriter(&b).value());
}
STOP("Json>Cbor",QString::fromUtf8(b.buffer().toHex()))
}
......
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