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

WIP: test Read+Write between buffers, generic and specific data structures

parent 9650ab4b
......@@ -27,4 +27,5 @@ SOURCES += \
main.cpp
HEADERS += \
QCborWriter.hpp
QCborWriter.hpp \
QJsonReader.hpp
/****************************************************************************
* **
* ** 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 <type_traits>
#include <QtCore/qiodevice.h>
class QJsonReader
{
class Impl;
Q_DISABLE_COPY(QJsonReader) // but not swap
public:
Q_ENABLE_SWAP(QJsonReader, std::swap(m, o.m);)
using TResult = QJsonReader;
using TValue = QJsonReader::Impl*;
using TSequence = QJsonReader::Impl*;
static const BindMode Mode = BindMode::Read;
QJsonReader(QIODevice* io) : m(new Impl(io)) {}
~QJsonReader() { if (m) delete m; }
Val<QJsonReader> begin() { auto beforeMove = m; return std::move(Val<QJsonReader>(beforeMove, std::move(*this))); }
private:
Impl* m = nullptr;
class Impl {
struct Level { bool isFirst; const char* end; };
QIODevice* io = nullptr;
QStack<Level> levels = QStack<Level>(); //!< minimal dynamic context to implement out()
char nextChar = '\0'; //!< minimal dynamic context to parse
public:
Impl(QIODevice* io) : io(io) {}
protected:
// static "interface" implementation
template<class T_> friend class Val;
Impl* sequence() { levels.push(Level{true,"]"});
return get('[', "ntf-.0123456789[{\"") ? this : nullptr; }
bool null() { if ( !get('n', "[{\"ntf-.0123456789")) return false;
if (nextChar == 'u') getChar(); else { next("[{\""); return false; }
if (nextChar == 'l') getChar(); else { next("[{\""); return false; }
if (nextChar == 'l') getChar(); else { next("[{\""); return false; }
return true; }
bool bind(QByteArray& s) { if (!get('"')) {
return false;
}
s.clear();
for (char read = getCharInString();
read != '\0';
read = getCharInString()) {
s.push_back(read);
}
return get('"'); }
// Natively supported overloads
bool bind( double& n) { qlonglong ll=0; double d=0;
// roughly:
// m_stream >> ll;
bool isNegative = false;
if (nextChar == '-') {
getChar();
isNegative = true;
}
int digit = getDigit();
if (digit < 0 || 9 < digit) {
return false; // do not accept no digit otherwise we may accept an empty mantissa or string!
}
do {
// TODO detect overflow
ll=ll*10+digit; d=d*10+digit;
digit = getDigit();
}
while (0 <= digit && digit <= 9); // accept many leading '0' which is more permissive than JSON
if (nextChar == '.') {
// roughly
// d = ll;
// m_stream >> decimal // except it would eat exponent
// d += decimal
getChar();
double decimal=.1;
for (int digit = getDigit(); 0 <= digit && digit <= 9; digit = getDigit(), decimal/=10) {
d+=decimal*digit;
}
}
if (nextChar == 'e' || nextChar == 'E') {
getChar();
qlonglong exponent = 0;
bool isNegativeExponent = false;
if (nextChar == '-') {
getChar();
isNegativeExponent = true;
}
if (nextChar == '+') {
getChar();
}
for (int digit = getDigit(); 0 <= digit && digit <= 9; digit = getDigit()) {
exponent=exponent*10+digit; // TODO detect overflow
}
// TODO detect overflow
d *=10^(isNegativeExponent ? -exponent : exponent);
}
// TODO if (!next(",]}") {}
n = isNegative ? -d : d;
return true; }
// This dispatch would be more simple with C++17 constexpr if
template<typename T> void bind(T t) {
QBind<TResult,T>::bind(Val<TResult>(this, TResult()), t); // Val<TResult> prevents QBind<TResult,T>() from getting out() of an outer Seq for instance
}
// static "interface" implementation
template<class T_> friend class Seq;
Impl* item() { Level& level = levels.last();
if (!level.isFirst && !get(',',level.end)) {
return nullptr;
}
level.isFirst = false;
return this; }
Impl* out() { auto level = levels.pop();
while (get(',', level.end)) {
skipItem();
}
return get(*level.end) ? this : nullptr; }
private:
int getDigit() {
return '0' <= nextChar && nextChar <= '9' ? (getChar())-'0' : -1;
}
char getCharInString()
{
if (nextChar == '\0' || nextChar == '"') {
return '\0';
}
if (nextChar == '\\') {
getChar();
bool isHex; unsigned hex;
switch (getChar())
{
case'\\': return '\\';
case '"': return '"';
case 'b': return '\b';
case 'f': return '\f';
case 'n': return '\n';
case 'r': return '\r';
case 't': return '\t';
case '/': return '/';
case 'u': {
QByteArray read;
read.push_back(getChar());
read.push_back(getChar());
read.push_back(getChar());
read.push_back(getChar());
hex = read.toUInt(&isHex, 16);
if (isHex) {
return hex;
}
return '?';
}
default : return '?';
}
}
else {
return getChar();
}
}
char getChar() {
if (!io->getChar(&nextChar)) {
nextChar = '\0';
}
return nextChar;
}
char next(const char* ends) {
while (nextChar != '\0' && (nextChar <= ' ' || !strchr(ends, nextChar))) {
getChar();
}
return nextChar;
}
bool get(char expected) {
return getChar() == expected;
}
bool get(char expected, const char* others) {
QByteArray valid(others);
valid.append(expected);
if (next(valid) != expected) {
return false;
}
return get(expected);
}
bool skipItem() {
QByteArray ba; double d;
return
( sequence() && out() )
|| bind(ba)
|| bind(d)
|| null()
;
}
};
};
......@@ -68,6 +68,7 @@ public:
Seq<T_> sequence() { auto val = Q_LIKELY(m_val) ? m_val->sequence() : nullptr; return Seq<T_>(val, std::move(m_out)); }
/**/T_ null() { if (Q_LIKELY(m_val) ) m_val-> null() ; return std::move(m_out) ; }
template<typename T>
T_ bind(T t) { if (Q_LIKELY(m_val) ) m_val-> bind(t) ; return std::move(m_out) ; }
private:
......@@ -114,7 +115,7 @@ struct QBind
}
};
// QBind partial specializations (generic on TResult, not always on TResult::Mode)
// QBind partial specializations (generic on TResult, usually not on TResult::Mode for dynamically-sized or builtin types)
#include <QtCore/qstring.h>
......@@ -143,12 +144,35 @@ struct QBind<TResult, std::nullptr_t> { static TResult bind(Val<TResult> value,
return value.null();
}};
// QBind specialization for generic Val<TSrc>
#include <QtCore/qjsonvalue.h>
template<class TResult>
struct QBind<TResult, QJsonValue> { static TResult bind(Val<TResult> dst, QJsonValue src) {
static_assert(TResult::Mode==Write,"cannot write to TResult");
if (src.isNull ()) return dst.null();
if (src.isArray ()) return dst.sequence().bind(src.toArray ());
if (src.isDouble()) return dst .bind(src.toDouble());
if (src.isString()) return dst .bind(src.toString());
}};
#include <QtCore/qjsonarray.h>
template<class TResult>
struct QBind<TResult, QJsonArray> { static TResult bind(Val<TResult> dst, QJsonArray src) {
static_assert(TResult::Mode==Write,"cannot write to TResult");
auto dstSequence(dst.sequence());
for (auto&& item : src) {
dstSequence = dstSequence.bind(item);
}
return dstSequence;
}};
// QBind specialization for generic Val<TSource>
template<class TDst, class TSrc>
struct QBind<TDst, Val<TSrc>> { static TDst bind(Val<TDst> dst, Val<TSrc> src) {
static_assert(TSrc::Mode==Read ,"cannot read from TSrc");
static_assert(TDst::Mode==Write,"cannot write to TDst");
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)
......@@ -280,11 +304,28 @@ private:
// Tests and Benchmarks
// NB: It is not possible to use QBENCHMARK to item qDebug because it installs a specific handler
struct Person
{
QString firstName; QString lastName; double height;
template<class TResult>
TResult bind(Val<TResult> value) {
return value
.sequence()
.bind(firstName)
.bind(lastName)
.bind(height)
; // automagically closes all opened structures
}
};
#include <QtCore/qelapsedtimer.h>
#include <QtCore/qfile.h>
#include <QtCore/qbuffer.h>
#include <QtCore/qdebug.h>
#include <QtCore/qjsonvalue.h>
#include "QJsonReader.hpp"
#include "QCborWriter.hpp"
#define GROUP(group) { \
......@@ -420,7 +461,34 @@ int main()
STOP("QWritable>Cbor",QString::fromUtf8(b.buffer().toHex()));
}
GROUP_STOP
#if 0
GROUP("Read+Write")
{
// Person p;
// START {
// b.seek(0); b.buffer()="[\"John\",\"Doe\",42]";
// QJsonReader(&b).begin().bind(p);
// }
// STOP("Json>struct",QString("[%1,%2,%3]").arg(p.firstName,p.lastName,QString::number(p.height)))
// START {
// b.seek(0); b.buffer().clear();
// QJsonWriter(&b).begin().bind(p);
// }
// STOP("struct>Json",b.buffer())
// QJsonValue v;
// START {
// b.seek(0); b.buffer().clear();
// QJsonReader(&b).begin().bind(v);
// }
// STOP("Json>JsonValue",v.toString());
// START {
// b.seek(0); b.buffer().clear();
// QCborWriter(&b).begin().bind(v);
// }
// STOP("JsonValue>Cbor",QString::fromUtf8(b.buffer().toHex()));
}
GROUP_STOP
#endif
return fclose(samples);
return fclose(results);
}
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