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

Moved errorReport to *Impl to improve it

Removed optimizations from *Result shortcut methods (no noticeable perf
improvement)
parent 2ece7f66
......@@ -56,13 +56,7 @@ protected: \
#include <QtCore/qbytearray.h>
// //////////////////////////////////////////////////////////////////////////
// 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)
// Error handling helpers
static const char* qBindExpectedNull = "Expected null" ;
static const char* qBindExpectedSequence = "Expected sequence";
......@@ -73,19 +67,29 @@ static const char* qBindExpectedDecimal = "Expected decimal" ;
static const char* qBindExpectedBoolean = "Expected boolean" ;
static const char* qBindIgnoredCharacter = "Ignored character";
template<class TResult>
template<class T>
class ScopedChoice //!< for accurate error reporting
{
Q_DISABLE_COPY(ScopedChoice)
public:
ScopedChoice(TResult& r) : r(r) { r.setChoice(true ); }
~ScopedChoice() { r.setChoice(false); }
ScopedChoice(T& t) : t(t) { if(t) t->setChoice(true ); }
~ScopedChoice() { if(t) t->setChoice(false); }
private:
TResult& r;
T& t;
};
// //////////////////////////////////////////////////////////////////////////
// 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)
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)
......@@ -105,12 +109,6 @@ 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)
friend class ScopedChoice<Val<TResult>>;
void setChoice(bool v) { m_out.setChoice(v); }
T_ m_out = T_(); //!< moved context of current traversal up to TResult that will reference the val itself (be it a QIODevice or QCborValue)
};
......@@ -138,12 +136,10 @@ public:
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:
protected:
template<class TResult, typename T> friend class QBind;
Val<T_> itemValue(int min= 1) { if (Q_LIKELY(m_out) && m_out->_item(min)) { 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
void setChoice(bool v) { m_out.setChoice(v); }
private:
T_ m_out = T_();
};
......@@ -165,29 +161,24 @@ public:
using TImpl = TImpl_ ;
static constexpr BindMode Mode = Mode_;
template<class TResult> friend class ScopedChoice;
operator bool() { return m; } //!< to drive QBind<TResult,T>() traversal
TImpl* operator->() { Q_ASSERT_X(m,Q_FUNC_INFO,"check operator bool() before calling operator->()"); return m; }
void reportError(const char* error) { if (!isChoice) this->reportError(error); }
Val<TResult> value() { return Val<TResult>(std::move(*static_cast<TResult*>(this))); }
// Shortcuts
/**/ Seq<TResult> sequence() { /* return value().sequence(); */ if (Q_LIKELY(m) && m->_sequence()) return Seq<TResult>(std::move(*static_cast<TResult*>(this))); return Seq<TResult>(); }
/**/ TResult null() { /* return value(). null(); */ if (Q_LIKELY(m) && m-> _null()) return std::move(*static_cast<TResult*>(this)) ; return TResult (); }
template<typename T> TResult bind(const T& t) { /* return value(). bind(t); */ if (Q_LIKELY(m) && m-> _bind(t)) return std::move(*static_cast<TResult*>(this)) ; return TResult (); }
template<typename T> TResult bind( T& t) { /* return value(). bind(t); */ if (Q_LIKELY(m) && m-> _bind(t)) return std::move(*static_cast<TResult*>(this)) ; return TResult (); }
template<typename T> TResult bind( T&& t) { /* return value(). bind(t); */ if (Q_LIKELY(m) && m-> _bind(t)) return std::move(*static_cast<TResult*>(this)) ; return TResult (); }
/**/ Seq<TResult> sequence() { return value().sequence(); }
/**/ TResult null() { return value(). null(); }
template<typename T> TResult bind(const T& t) { return value(). bind(t); }
template<typename T> TResult bind( T& t) { return value(). bind(t); }
template<typename T> TResult bind( T&& t) { return value(). bind(t); }
void setChoice(bool v) { isChoice = v; }
void reportError(const char* error) { if (m) m->reportError(error); }
protected:
QScopedResult(TImpl* result, bool owned) : m(result), m_owned(owned) {}
TImpl* m = nullptr;
bool m_owned = true ; //!< for nested QBind only
bool isChoice = false ;
};
//! Base class for TResult classes that can exhibit high performance (they require no heap allocation and no lock)
......@@ -211,14 +202,16 @@ public:
Val<TResult> value() { return Val<TResult>(std::move(*static_cast<TResult*>(this))); }
// Shortcuts
/**/ Seq<TResult> sequence() { /* return value().sequence(); */ TResult* r=static_cast<TResult*>(this); if (Q_LIKELY(*r) && r->_sequence()) return Seq<TResult>(std::move(*r)); return Seq<TResult>(); }
/**/ TResult null() { /* return value(). null(); */ TResult* r=static_cast<TResult*>(this); if (Q_LIKELY(*r) && r-> _null()) return std::move(*r) ; return TResult (); }
template<typename T> TResult bind(const T& t) { /* return value(). bind(t); */ TResult* r=static_cast<TResult*>(this); if (Q_LIKELY(*r) && r-> _bind(t)) return std::move(*r) ; return TResult (); }
template<typename T> TResult bind( T& t) { /* return value(). bind(t); */ TResult* r=static_cast<TResult*>(this); if (Q_LIKELY(*r) && r-> _bind(t)) return std::move(*r) ; return TResult (); }
template<typename T> TResult bind( T&& t) { /* return value(). bind(t); */ TResult* r=static_cast<TResult*>(this); if (Q_LIKELY(*r) && r-> _bind(t)) return std::move(*r) ; return TResult (); }
void reportError(const char*) { /* a TResult with BindMode::Write will not encounter bind mismatches but only write errors independent from the current state */ }
void setChoice(bool) { /* not implemented */ }
/**/ Seq<TResult> sequence() { return value().sequence(); }
/**/ TResult null() { return value(). null(); }
template<typename T> TResult bind(const T& t) { return value(). bind(t); }
template<typename T> TResult bind( T& t) { return value(). bind(t); }
template<typename T> TResult bind( T&& t) { return value(). bind(t); }
void reportError(const char*) {} // a TResult with BindMode::Write will not encounter bind mismatches but only write errors independent from the current state
protected:
friend class ScopedChoice<Val<TResult>>;
void setChoice(bool) {} // not implemented like reportError
};
#include <QtCore/qbuffer.h>
......@@ -399,10 +392,10 @@ struct QBind<TResult, QVector<T>> {
// C++17 constexpr if is required to handle situations where one of these definitions is not used
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(0); i; i = s.item(0)) {
Val<Seq<TResult>> i;
while (i = s.item(0)) {
T t;
s = i.bind(t);
if (s)
if (s = i.bind(t))
dst.push_back(t);
}
return s;
......
......@@ -67,14 +67,15 @@ private:
class QJsonBuilderImpl
{
Q_DISABLE_COPY(QJsonBuilderImpl)
QJsonValue* value;
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
public:
QJsonBuilderImpl(QJsonValue* v) : value(v) { Q_ASSERT(v); }
protected:
friend class ScopedChoice<Val<QJsonBuilder>>;
void setChoice(bool) {} // not implemented like reportError
friend class QScopedResult<QJsonBuilder, QJsonBuilderImpl, BindMode::Write>; // for slight optimization of fluent interface shortcuts
void reportError(const char*) {} // a TResult with BindMode::Write will not encounter bind mismatches but only write errors independent from the current state
template<class T_> friend class Val; // calls methods below
bool _record() { levels.push(Step("" )); return true; }
......@@ -108,6 +109,10 @@ private:
levels.last().array.append(v);
}
}
QJsonValue* value;
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
};
QJsonBuilder::QJsonBuilder(QJsonValue* v) : QScopedResult(new QJsonBuilderImpl(v), true) {}
......@@ -129,8 +134,6 @@ class JsonVisitorImpl
public:
JsonVisitorImpl(const QJsonValue* v) : value(v) { Q_ASSERT(v); }
void reportError(const char* error) { errors.append(Error{ error, currentPath() }); }
struct Error { const char* error; QByteArray path; template<class T> T bind(Val<T> value) { return value.bind(QByteArray(error)+' '+path); } };
QVector<Error> errors;
......@@ -149,7 +152,12 @@ public:
return path;
}
protected:
friend class ScopedChoice<Val<QJsonVisitor>>;
void setChoice(bool v) { isChoice=v; }
friend class QScopedResult<QJsonVisitor, JsonVisitorImpl, BindMode::Read>;
void reportError(const char* error) { if (!isChoice) errors.append(Error{ error, currentPath() }); }
template<class T_> friend class Val; // calls methods below
bool _null() { if (current()->isNull ()) { return true; } reportError(qBindExpectedNull ); return false; }
......@@ -167,7 +175,7 @@ protected:
template<class T_> friend class Seq; // calls methods below
bool _item(const char* key) { steps.last().key=key; if (!(steps.last().item = current(1)->toObject().value(QString::fromUtf8(steps.last().key))).isUndefined()) { return true; } return false; }
bool _item( int min= 1) { steps.last().idx++ ; if (!(steps.last().item = current(1)->toArray (). at( steps.last().idx )).isUndefined()) { return true; } if (min<=0) reportError(qBindExpectedItem); return false; }
bool _item( int min= 1) { steps.last().idx++ ; if (!(steps.last().item = current(1)->toArray (). at( steps.last().idx )).isUndefined()) { return true; } if (0<min) reportError(qBindExpectedItem); return false; }
bool _out(int max=-1) { Q_UNUSED(max); steps.pop(); return true; }
private:
const QJsonValue* current(unsigned outer=0) const { return steps.size()-outer <= 0 ? value : &(steps[steps.size()-outer-1].item); }
......@@ -175,6 +183,7 @@ private:
const QJsonValue* value;
struct Step { const char* key=nullptr; int idx=-1; QJsonValue item; Step() = default; };
QStack<Step> steps = QStack<Step>();
bool isChoice = false;
};
QJsonVisitor::QJsonVisitor(const QJsonValue* v) : QScopedResult(new JsonVisitorImpl(v), true) {}
......@@ -193,16 +202,16 @@ private:
class QJsonWriterImpl
{
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:
QJsonWriterImpl(QIODevice* io) : io(io) { Q_ASSERT(io); }
~QJsonWriterImpl() { for (auto&& level : levels) io->write(level.end); }
protected:
friend class ScopedChoice<Val<QJsonWriter>>;
void setChoice(bool) { /* not implemented like reportError */ }
friend class QScopedResult<QJsonWriter, QJsonWriterImpl, BindMode::Write>;
void reportError(const char*) { /* a TResult with BindMode::Write will not encounter bind mismatches but only write errors independent from the current state */ }
template<class T_> friend class Val; // calls methods below
bool _sequence() { levels.push(Step{"","]"});
......@@ -232,6 +241,11 @@ protected:
bool _item(int min= 1) { Q_UNUSED(min); return io->write(levels.last().sep)==strlen(levels.last().sep) ; levels.last().sep = ","; }
bool _out(int max=-1) { Q_UNUSED(max); return io->write(levels.pop() .end) ; }
private:
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
};
QJsonWriter::QJsonWriter(QIODevice* io) : QScopedResult(new QJsonWriterImpl(io), true) {}
......@@ -256,15 +270,19 @@ class QJsonReaderImpl
QIODevice* io;
int line = 0, column = 0, index = -1;
QStack<Step> levels = QStack<Step>(); //!< dynamic context required to implement item() and report meaningful errors
bool isChoice = false;
public:
QJsonReaderImpl(QIODevice* io) : io(io) { Q_ASSERT(io); }
void reportError(const char* error) { errors.append(Error{ error, line, column, index }); }
struct Error { const char* error; int line; int column; int index; template<class T> T bind(Val<T> value) { QByteArray ba(error); ba.append(' ').append(QByteArray::number(line)).append(':').append(QByteArray::number(column)); return value.bind(ba.constData()); } };
QVector<Error> errors;
protected:
friend class ScopedChoice<Val<QJsonReader>>;
void setChoice(bool v) { isChoice=v; }
friend class QScopedResult<QJsonReader, QJsonReaderImpl, BindMode::Read>;
void reportError(const char* error) { if (!isChoice) errors.append(Error{ error, line, column, index }); }
template<class T_> friend class Val; // calls methods below
bool _sequence() { if (get('[', "[{\"ntf-0123456789.")) {
......@@ -313,7 +331,7 @@ protected:
bool _bind( double& n) { qlonglong ll=0; double d=0; int digit;
bool isNegative = get('-', "[{\"0123456789.");
if ((digit = getDigit()) < 0) {
reportError("Expected digit");
reportError(qBindExpectedDecimal);
return false; // do not accept no digit otherwise we may accept an empty mantissa or string!
}
do { // TODO detect overflow
......@@ -371,6 +389,7 @@ protected:
_any();
}
return get(*level.end, "}"); }
private:
int getDigit(int base = 10) { Q_ASSERT(0<base);
int digit;
......@@ -420,7 +439,7 @@ private:
while (!(nextChar() == expected || strchr(validChars, nextChar()) || nextChar() == '\0')) {
char ignored = getChar();
if (!isspace(ignored)) {
reportError(qBindIgnoredCharacter);
errors.append(Error{ qBindIgnoredCharacter, line, column, index });
}
}
}
......@@ -468,7 +487,7 @@ struct QBind<TResult, QJsonValue> {
QString t; if (r = src.bind(t)) { dst = QJsonValue(t); return r; }
QJsonArray a; if (r = src.bind(a)) { dst = a ; return r; }
}
if (!(r = src.null())) src->reportError("Expected bool|double|QString|QJsonArray|null");
if (!(r = src.null())) r.reportError("Expected bool|double|QString|QJsonArray|null");
/**/ dst = QJsonValue();
return r;
}
......@@ -499,8 +518,7 @@ struct QBind<TResult, QJsonArray> {
Val<Seq<TResult>> i;
while (i = s.item(0)) { // s = s.bind(v) would assign s an invalid Seq<TResult> on the last item
QJsonValue v;
s = i.bind(v);
if (s)
if (s = i.bind(v))
dst.append(v);
}
return s;
......
Markdown is supported
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