Commit f781b951 authored by EXT Arnaud Clère's avatar EXT Arnaud Clère

Added Cbor support and tests

parent c4ab7ec5
......@@ -62,6 +62,8 @@ public:
QUtf8String(const QByteArray& o) : QByteArray(o) {}
QUtf8String() : QByteArray() {}
};
#include <QtCore/qmetatype.h>
Q_DECLARE_METATYPE(QUtf8String)
using QName = const char*; //!< String literal explicitely utf8 encoded
......
......@@ -176,7 +176,6 @@ private:
};
// --------------------------------------------------------------------------
#if QT_VERSION>=QT_VERSION_CHECK(5,12,0)
#include <QtCore/qcborstream.h>
#include <QtCore/qstack.h>
......@@ -331,14 +330,14 @@ public:
/**/ Seq<Cursor> sequence(quint32* s=nullptr) { return Cursor(this).value().sequence ( s); }
template<typename T> Cursor bind ( T&& t) { return Cursor(this).value().bind(std::forward<T>(t)); }
protected:
bool _sequence(quint32* s=nullptr) { Q_UNUSED(s); levels.push(Step(nullptr)); return true; }
bool _record (quint32* s=nullptr) { Q_UNUSED(s); levels.push(Step("" )); return true; }
bool _null ( ) { set(QCborValue( )); return true; }
bool _bind ( const char* s) { set(QCborValue(s)); return true; }
bool _bind ( bool& b) { set(QCborValue(b)); return true; }
bool _bind ( double& d) { set(QCborValue(d)); return true; }
bool _bind ( quint64& n) { double d(n); return _bind(d); }
bool _bind ( qint64& n) { double d(n); return _bind(d); }
bool _sequence(quint32* =nullptr) { levels.push(Step(nullptr)); return true; }
bool _record (quint32* =nullptr) { levels.push(Step("" )); return true; }
bool _null ( ) { set(QCborValue( )); return true; }
bool _bind ( const char* s) { set(QCborValue(s)); return true; }
bool _bind ( bool& b) { set(QCborValue(b)); return true; }
bool _bind ( double& d) { set(QCborValue(d)); return true; }
bool _bind ( quint64& n) { double d(n); return _bind(d); }
bool _bind ( qint64& n) { double d(n); return _bind(d); }
bool _item(QName n) { levels.last().key=n ; return true; }
bool _item( ) { levels.last().key=nullptr; return true; }
......@@ -358,7 +357,166 @@ private:
QCborValue* cbor;
struct Step { QUtf8String key; /* TODO union */ QCborMap object; QCborArray 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 QCborBuilderImpl is abandoned
QStack<Step> levels = QStack<Step>(); //!< minimal dynamic context to implement out() and ensure actual building in case QCborBuilder is abandoned
};
#endif
#include <QtCore/qvector.h>
class QCborVisitor : public IReader
{
Q_DISABLE_COPY(QCborVisitor)
public:
QCborVisitor(const QCborValue* v) : cbor(v) { Q_ASSERT(v); }
struct Step { const char* key=nullptr; int idx=-1; QCborValue item; Step() = default; };
struct Error { const char* error; QUtf8String path; template<class T> T bind(Val<T>&& value) { return value.bind(QUtf8String(error)+' '+path); } };
QVector<Error> errors;
QUtf8String currentPath() {
QUtf8String path;
Q_FOREACH(Step s, steps) {
if (s.key) { path.append('{').append(s.key); }
else { path.append('[').append(QUtf8String::number(s.idx)); }
}
return path;
}
// Shortcuts
/**/ Val<Cursor> value ( ) { return Cursor(this).value(); }
/**/ Seq<Cursor> sequence(quint32* s=nullptr) { return Cursor(this).value().sequence ( s); }
template<typename T> Cursor bind ( T&& t) { return Cursor(this).value().bind(std::forward<T>(t)); }
protected:
bool _sequence(quint32* =nullptr) { if (current()->isArray ()) { steps.push(Step()); return true; } _reportError(qBindExpectedSequence); return false; }
bool _record (quint32* =nullptr) { if (current()->isMap ()) { steps.push(Step()); return true; } _reportError(qBindExpectedRecord ); return false; }
bool _null ( ) { if (current()->isNull ()) { return true; } _reportError(qBindExpectedNull ); return false; }
bool _bind ( QUtf8String& u) { QString s; if (_bind(s) ) { u = s.toUtf8() ; return true; } return false; }
bool _bind ( QString& v) { if (current()->isString ()) { v = current()->toString (); return true; } _reportError(qBindExpectedText ); return false; }
bool _bind ( QByteArray& v) { if (current()->isByteArray()) { v = current()->toByteArray(); return true; } _reportError(qBindExpectedText ); return false; }
bool _bind ( bool& v) { if (current()->isBool ()) { v = current()->toBool (); return true; } _reportError(qBindExpectedBoolean ); return false; }
bool _bind ( qint64& t) { if (current()->isInteger ()) { t = current()->toInteger (); return true; } return false; }
bool _bind ( quint64& t) { if (current()->isInteger ()) { t = current()->toInteger (); return true; } return false; } // TODO full quint64 range
bool _bind ( float& v) { double d; if (_bind(d) ) { v = float(d) ; return true; } return false; }
bool _bind ( double& v) { if (current()->isDouble ()) { v = current()->toDouble (); return true; } _reportError(qBindExpectedDecimal ); return false; }
bool _item(QUtf8String& k) { steps.last().key=k; return !(steps.last().item = current(1)->toMap ().value(QString::fromUtf8(steps.last().key))).isUndefined(); }
bool _item( ) { steps.last().idx++; return !(steps.last().item = current(1)->toArray(). at( steps.last().idx )).isUndefined(); }
bool _out ( ) { steps.pop() ; return true; }
bool _isOk() { return true; }
void _setChoice(bool v) { isChoice=v; }
void _reportError(const char* e) { if (!isChoice) errors.append(Error{ e, currentPath() }); }
private:
const QCborValue* current(unsigned outer=0) const { return unsigned(steps.size())-outer <= 0 ? cbor : &(steps[unsigned(steps.size())-outer-1].item); }
const QCborValue* cbor;
QStack<Step> steps = QStack<Step>();
bool isChoice = false;
};
// //////////////////////////////////////////////////////////////////////////
// QBind<QCbor*,_> support
#include <QtCore/qjsonvalue.h>
#include <QtCore/qjsonarray.h>
#include <QtCore/qjsonobject.h>
template<class TResult> //=Cursor
struct QBind<QCborValue, TResult> {
static TResult bind(Val<TResult>&& v, QCborValue&& j) {
if (v->mode()==Write) {
if (j.isMap ()) return v.bind(j.toMap ());
if (j.isArray ()) return v.bind(j.toArray ());
if (j.isBool ()) return v.bind(j.toBool ());
if (j.isInteger ()) return v.bind(j.toInteger ());
if (j.isDouble ()) return v.bind(j.toDouble ());
if (j.isString ()) return v.bind(j.toString ());
if (j.isByteArray()) return v.bind(j.toByteArray());
return v.null();
}
else { Q_ASSERT_X(false, Q_FUNC_INFO, "Unsupported v->mode()"); return v.null(); }
}
static TResult bind(Val<TResult>&& v, QCborValue& j) {
if (v->mode()==Write) {
return bind(std::move(v),std::move(j));
}
else if (v->mode()==Read) {
TResult r;
{
QScopedChoice<Val<TResult>> choice(v);
QCborArray a; if ((r = v.bind(a))) { j = a ; return r; }
QCborMap o; if ((r = v.bind(o))) { j = o ; return r; }
QString t; if ((r = v.bind(t))) { j = QCborValue( t ); return r; }
bool b; if ((r = v.bind(b))) { j = QCborValue( b ); return r; }
qint64 i; if ((r = v.bind(i))) { j = QCborValue( i ); return r; }
quint64 u; if ((r = v.bind(u))) { j = QCborValue(double(u)); return r; } // FIXME range
double d; if ((r = v.bind(d))) { j = QCborValue( d ); return r; }
QByteArray y; if ((r = v.bind(y))) { j = QCborValue( y ); return r; }
}
if (!(r = v.null())) r.reportError("Expected bool|double|QString|QCborArray|QCborOnject|null");
/**/ j = QCborValue( ); return r;
}
else { Q_ASSERT_X(!v, Q_FUNC_INFO, "Unsupported v->mode()"); return v.null(); }
}
};
template<class TResult> //=Cursor
struct QBind<QCborArray, TResult> {
static TResult bind(Val<TResult>&& v, QCborArray&& j) {
if (v->mode()==Write) {
quint32 size=quint32(j.size());
auto s(v.sequence(&size));
for (QCborValue item : j) {
s = s.bind(item);
}
return s;
}
else { Q_ASSERT_X(false, Q_FUNC_INFO, "Unsupported v->mode()"); return v.null(); }
}
static TResult bind(Val<TResult>&& v, QCborArray& j) {
if (v->mode()==Write) {
return bind(std::move(v),std::move(j));
}
else if (v->mode()==Read) {
auto s(v.sequence());
Val<Seq<TResult>> i;
while ((i = s.item())) {
QCborValue json;
if ((s = i.bind(json)))
j.append(json);
}
return s;
}
else { Q_ASSERT_X(!v, Q_FUNC_INFO, "Unsupported v->mode()"); return v.null(); }
}
};
template<class TResult> //=Cursor
struct QBind<QCborMap, TResult> {
static TResult bind(Val<TResult>&& v, QCborMap&& j) {
if (v->mode()==Write) {
quint32 size=quint32(j.size());
auto s(v.record(&size));
for (QCborValue key : j.keys()) {
s = s.bind(key.toString().toUtf8().constData(),QCborValue(j[key]));
}
return s;
}
else { Q_ASSERT_X(false, Q_FUNC_INFO, "Unsupported v->mode()"); return v.null(); }
}
static TResult bind(Val<TResult>&& v, QCborMap& j) {
if (v->mode()==Write) {
return bind(std::move(v),std::move(j));
}
else if (v->mode()==Read) {
auto s(v.record());
QUtf8String k; Val<Rec<TResult>> i;
while ((i = s.item(k))) {
QCborValue json;
if ((s = i.bind(json)))
j.insert(QString::fromUtf8(k),json);
}
return s;
}
else { Q_ASSERT_X(!v, Q_FUNC_INFO, "Unsupported v->mode()"); return v.null(); }
}
};
......@@ -92,7 +92,7 @@ private:
QJsonValue* json;
struct Step { QUtf8String key; /* TODO union */ QJsonObject object; QJsonArray array; Step(const char* k=nullptr) : key(k) {} };
QStack<Step> steps = QStack<Step>(); //!< minimal dynamic context to implement out() and ensure actual building in case QJsonBuilderImpl is abandoned
QStack<Step> steps = QStack<Step>(); //!< minimal dynamic context to implement out() and ensure actual building in case QJsonBuilder is abandoned
};
// --------------------------------------------------------------------------
......
This diff is collapsed.
......@@ -155,9 +155,8 @@ public:
#include <QtCore/qdebug.h>
#include <QtCore/qjsonvalue.h>
#include <QtCore/qjsondocument.h>
#if QT_VERSION>=QT_VERSION_CHECK(5,12,0)
#include <QtCore/qcborstream.h>
#endif
#include <QtGui/qcolor.h>
// NB: It is not possible to use QBENCHMARK to evaluate qDebug because it installs a specific handler
......@@ -169,7 +168,7 @@ public:
groupWarmup=(previousTotal<0.01 || std::abs(groupTotal-previousTotal)*100/previousTotal > 1.); \
previousTotal=groupTotal; \
groupTotal=0.; \
if (previousTotal<0.01) { fprintf(results,"group "); fprintf(samples, "group |%s\n", group); } else fprintf(results,"%-10s",group);
if (previousTotal<0.01) { fprintf(results,"group "); fprintf(samples, "group |%s\n", group); } else fprintf(results,"%-12s",group);
#define GROUP_STOP \
if (previousTotal<0.01) fprintf(results,"|total(usecs)|variation(%%)\n"); else fprintf(results,"|%12.1f|%5.1f\n", groupTotal, previousTotal>=0.01 ? std::abs(groupTotal-previousTotal)*100/previousTotal : 0); \
......@@ -203,11 +202,10 @@ int main(int argc, char *argv[])
// Temporary buffers
QString s;
QBuffer b;
QBuffer b; b.open(QIODevice::ReadWrite);
QVariant v;
b.open(QIODevice::ReadWrite);
QDataStream d(&b);
GROUP("<builtin")
GROUP("builtin>")//========================================================
{
START {
s.clear();
......@@ -264,7 +262,6 @@ int main(int argc, char *argv[])
;
}
STOP("Cbor",QString::fromUtf8(b.buffer().toHex()));
#if QT_VERSION>=QT_VERSION_CHECK(5,12,0)
START {
b.seek(0); b.buffer().clear();
QCborStreamWriter s(&b);
......@@ -282,7 +279,6 @@ int main(int argc, char *argv[])
s.endArray();
}
STOP("QCborStream",QString::fromUtf8(b.buffer().toHex()));
#endif
START {
b.seek(0); b.buffer().clear();
QDataStream d(&b);
......@@ -326,7 +322,7 @@ int main(int argc, char *argv[])
STOP("Writables>Json",QString::fromUtf8(b.buffer()));
}
GROUP_STOP;
GROUP("<doubles")
GROUP("doubles>")//========================================================
{
START {
s.clear();
......@@ -356,7 +352,7 @@ int main(int argc, char *argv[])
QCborWriter(&b).bind(transform);
}
STOP("Cbor",QString::fromUtf8(b.buffer().toHex()));
#if QT_VERSION>=QT_VERSION_CHECK(5,12,0)
START {
b.seek(0); b.buffer().clear();
QCborStreamWriter s(&b);
......@@ -368,7 +364,6 @@ int main(int argc, char *argv[])
s.endArray();
}
STOP("QCborStream",QString::fromUtf8(b.buffer().toHex()));
#endif
START {
b.seek(0); b.buffer().clear();
QDataStream d(&b);
......@@ -401,7 +396,7 @@ int main(int argc, char *argv[])
STOP("Writable>Json",QString::fromUtf8(b.buffer()));
}
GROUP_STOP
GROUP("<Person")
GROUP("Person>")//=========================================================
{
START {
s.clear();
......@@ -434,7 +429,7 @@ int main(int argc, char *argv[])
QCborWriter(&b).bind(person);
}
STOP("Cbor",QString::fromUtf8(b.buffer().toHex()));
#if QT_VERSION>=QT_VERSION_CHECK(5,12,0)
START {
b.seek(0); b.buffer().clear();
QCborStreamWriter s(&b);
......@@ -456,7 +451,6 @@ int main(int argc, char *argv[])
s.endMap();
}
STOP("QCborStream",QString::fromUtf8(b.buffer().toHex()));
#endif
START {
b.seek(0); b.buffer().clear();
QDataStream d(&b);
......@@ -486,151 +480,219 @@ int main(int argc, char *argv[])
STOP("Writable>Json",QString::fromUtf8(b.buffer()));
}
GROUP_STOP
GROUP("Read+Write")
GROUP("Person<>Json")//====================================================
{
QBuffer json;
json.open(QIODevice::ReadOnly);
json.buffer() = "_{ \"names\": [ _\"John\" _, \"Doe\"] , \"age\"_:_null, \"height\":_1.75, \"phones\": [ \"+44 1234567\" , \"+44 2345678\" ], \"\":\"superfluous item\" _} ";
// 0 22 41 55 70
QJsonValue v;
#if QT_VERSION>=QT_VERSION_CHECK(5,12,0)
QCborValue c;
#endif
QBuffer json; json.open(QIODevice::ReadOnly);
json.buffer() =
"_{ \"names\": [ _\"John\" _, \"Doe\"] , \"age\"_:_null, \"height\":_1.75, \"phones\": [ \"+44 1234567\" , \"+44 2345678\" ], \"\":\"superfluous item\" _} ";
Person p;
QVector<QJsonReader ::Error> jsonReaderErrors;
QBuffer roundtrip; roundtrip.open(QIODevice::ReadWrite);
QJsonValue jv;
QCborValue cv;
QVector<QJsonReader ::Error> readerErrors;
QVector<QJsonVisitor::Error> visitorErrors;
//---------------------------------------------------------------------
START {
json.seek(0); b.seek(0); b.buffer().clear();
json.seek(0); p = {};
QJsonReader r(&json);
r.bind(QCborWriter(&b).value());
jsonReaderErrors = r.errors;
r.bind(p);
readerErrors = r.errors;
}
STOP("Json>Cbor",QString::fromUtf8(b.buffer().toHex()))
STOP("Json>P",QString::fromUtf8(Text(p)+Text(readerErrors)))
START {
json.seek(0); v = QJsonValue();
QJsonReader r(&json);
r.bind(v);
jsonReaderErrors = r.errors;
roundtrip.seek(0);
QJsonWriter(&roundtrip).bind(p);
}
STOP("Json>JsonValue",QString::fromUtf8(QJsonDocument(v.toObject()).toJson()+Text(jsonReaderErrors)));
STOP("P>Json",QString::fromUtf8(roundtrip.buffer()))
START {
json.seek(0); p = {};
QJsonReader r(&json);
roundtrip.seek(0); p = {};
QJsonReader r(&roundtrip);
r.bind(p);
jsonReaderErrors = r.errors;
readerErrors = r.errors;
}
STOP("Json>T",QString::fromUtf8(Text(p)+Text(jsonReaderErrors)))
QBuffer roundtripJson;
roundtripJson.open(QIODevice::ReadWrite);
STOP("Json>P",QString::fromUtf8(Text(p)+Text(readerErrors)))
//---------------------------------------------------------------------
START {
roundtripJson.seek(0);
QJsonWriter(&roundtripJson).bind(p);
jv = QJsonValue();
QJsonBuilder(&jv).bind(p);
}
STOP("T>Json",QString::fromUtf8(roundtripJson.buffer()))
START {
v = QJsonValue();
QJsonBuilder(&v).bind(p);
}
STOP("T>JsonValue",QString::fromUtf8(QJsonDocument(v.toObject()).toJson()));
QVector<QJsonVisitor::Error> jsonVisitorErrors;
STOP("P>JsonValue",QString::fromUtf8(QJsonDocument(jv.toObject()).toJson()));
START {
p = {};
QJsonVisitor r(&v);
QJsonVisitor r(&jv);
r.bind(p);
jsonVisitorErrors = r.errors;
visitorErrors = r.errors;
}
STOP("JsonValue>T",QString::fromUtf8(Text(p)+Text(jsonVisitorErrors)))
STOP("JsonValue>P",QString::fromUtf8(Text(p)+Text(visitorErrors)))
//---------------------------------------------------------------------
START {
b.seek(0); b.buffer().clear();
QCborWriter(&b).bind(p);
roundtrip.seek(0); b.seek(0); b.buffer().clear();
QJsonReader r(&roundtrip);
r.bind(QCborWriter(&b).value());
readerErrors = r.errors;
}
STOP("Json>Cbor",QString::fromUtf8(b.buffer().toHex()))
START {
roundtrip.seek(0); jv = QJsonValue();
QJsonReader r(&roundtrip);
r.bind(jv);
readerErrors = r.errors;
}
STOP("T>Cbor",QString::fromUtf8(b.buffer().toHex()))
#if QT_VERSION>=QT_VERSION_CHECK(5,12,0)
QCborStreamReader r(b.buffer());
STOP("Json>JsonValue",QString::fromUtf8(QJsonDocument(jv.toObject()).toJson()+Text(readerErrors)));
}
GROUP_STOP
GROUP("Person<>Cbor")//====================================================
{
QBuffer cbor; cbor.open(QIODevice::ReadOnly);
cbor.buffer() = QByteArray::fromHex(QByteArray(
"A5" // map(5)
"65" "6E616D6573" // text(5) "names"
"82" // array(2)
"64" "4A6F686E" // text(4) "John"
"63" "446F65" // text(3) "Doe"
"63" "616765" // text(3) "age"
"12" // unsigned(18)
"66" "686569676874" // text(6) "height"
"F6" // primitive(22)
"66" "70686F6E6573" // text(6) "phones"
"82" // array(2)
"6B" "2B34342031323334353637" // text(11) "+44 1234567"
"6B" "2B34342032333435363738" // text(11) "+44 2345678"
"60" // text(0) ""
"70" "7375706572666C756F7573206974656D"// text(16) "superfluous item"
));
Person p;
QBuffer roundtrip; roundtrip.open(QIODevice::ReadWrite);
QJsonValue jv;
QCborValue cv;
QVector<QCborReader ::Error> readerErrors;
QVector<QCborVisitor::Error> visitorErrors;
QCborStreamReader reader(cbor.buffer());
bool parsed;
//---------------------------------------------------------------------
START {
p = {};
r.reset();
if (r.isMap() &&
r.enterContainer() &&
r.hasNext() &&
r.isString() &&
r.readString().data=="names" &&
r.readString().status==QCborStreamReader::EndOfString &&
r.isArray() &&
r.enterContainer() &&
r.hasNext() &&
r.isString())
p.firstName=r.readString().data;
if (r.readString().status==QCborStreamReader::EndOfString &&
r.hasNext() &&
r.isString())
p.lastName=r.readString().data;
if (r.readString().status==QCborStreamReader::EndOfString &&
!r.hasNext() &&
r.leaveContainer() &&
r.hasNext() &&
r.isString() &&
r.readString().data=="height" &&
r.readString().status==QCborStreamReader::EndOfString &&
r.isDouble())
p.height=r.toDouble();
if (r.next() &&
r.hasNext() &&
r.isString() &&
r.readString().data=="age" &&
r.readString().status==QCborStreamReader::EndOfString &&
!r.isUnsignedInteger() &&
r.isNegativeInteger())
p.age=int(r.toInteger());
if (r.next() &&
r.hasNext() &&
r.isString() &&
r.readString().data=="phones" &&
r.readString().status==QCborStreamReader::EndOfString &&
r.isArray() &&
r.enterContainer() &&
r.hasNext() &&
r.isString())
p.phones.append(r.readString().data);
if (//r.readString().status==QCborStreamReader::EndOfString &&
r.hasNext() &&
r.isString())
p.phones.append(r.readString().data);
if (//r.readString().status==QCborStreamReader::EndOfString &&
!r.hasNext() &&
r.leaveContainer() &&
r.hasNext() &&
r.isString() &&
r.readString().data=="comments" &&
r.readString().status==QCborStreamReader::EndOfString &&
r.isString())
p.comments=r.readString().data;
parsed =
r.readString().status==QCborStreamReader::EndOfString &&
!r.hasNext() &&
r.leaveContainer();
r.reset();
reader.reset(); p = {};
QCborReader r(&reader);
r.bind(p);
readerErrors = r.errors;
}
STOP("QCborStream>T",QString(Text(p)+Text(parsed)))
STOP("Cbor>P",QString(Text(p)+Text(readerErrors)))
START {
p = {};
r.reset();
QCborReader(&r).bind(p);
roundtrip.seek(0);
QCborWriter(&roundtrip).bind(p);
}
STOP("Cbor>T",QString(Text(p)/*+Text(jsonVisitorErrors)*/))
STOP("P>Cbor",QString::fromUtf8(roundtrip.buffer().toHex()))
START {
b.seek(0); b.buffer().clear();
QCborBuilder(&c).bind(p);
roundtrip.seek(0); reader.setDevice(&roundtrip); p = {};
QCborReader r(&reader);
r.bind(p);
readerErrors = r.errors;
}
STOP("T>CborValue",QString::fromUtf8(c.toCbor().toHex()))
#endif
STOP("Cbor>P",QString(Text(p)+Text(readerErrors)))
//---------------------------------------------------------------------
START {
b.seek(0); b.buffer().clear();
QCborWriter(&b).bind(v);
p = {};
reader.reset();
if (reader.isMap() &&
reader.enterContainer() &&
reader.hasNext() &&
reader.isString() &&
reader.readString().data=="names" &&
reader.readString().status==QCborStreamReader::EndOfString &&
reader.isArray() &&
reader.enterContainer() &&
reader.hasNext() &&
reader.isString())
p.firstName=reader.readString().data;
if (reader.readString().status==QCborStreamReader::EndOfString &&
reader.hasNext() &&
reader.isString())
p.lastName=reader.readString().data;
if (reader.readString().status==QCborStreamReader::EndOfString &&
!reader.hasNext() &&
reader.leaveContainer() &&
reader.hasNext() &&
reader.isString() &&
reader.readString().data=="height" &&
reader.readString().status==QCborStreamReader::EndOfString &&
reader.isDouble())
p.height=reader.toDouble();
if (reader.next() &&
reader.hasNext() &&
reader.isString() &&
reader.readString().data=="age" &&
reader.readString().status==QCborStreamReader::EndOfString &&
!reader.isUnsignedInteger() &&