Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the number parsing #27

Merged
merged 7 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion qnc/qncliterals.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ constexpr auto operator ""_L1(const char *str, std::size_t len)
return QLatin1String{str, static_cast<qsizetype>(len)};
}

#else // QT_VERSION < QT_VERSION_CHECK(6,4,0)
#else // QT_VERSION < QT_VERSION_CHECK(6,0,0)

constexpr auto operator ""_L1(const char ch)
{
Expand All @@ -91,6 +91,11 @@ inline auto operator ""_ba(const char *str, std::size_t len)
return QByteArray{str, static_cast<compat::lentype>(len)};
}

inline auto operator ""_s(const char16_t *str, std::size_t len)
{
return QString::fromUtf16(str, static_cast<compat::lentype>(len));
}

#endif // QT_VERSION < QT_VERSION_CHECK(6,4,0)

constexpr auto operator ""_baview(const char *str, std::size_t len)
Expand Down
94 changes: 94 additions & 0 deletions qnc/qncparse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,97 @@
* Copyright (C) 2023 Mathias Hasselmann
*/
#include "qncparse.h"

// STL headers
#include <cmath>

#if QT_VERSION_MAJOR < 6
using QByteArrayView = QByteArray;
#endif // QT_VERSION_MAJOR < 6

namespace qnc::detail {

template <>
void parse(const QByteArrayView &text, bool &value, bool &isValid)
{
const auto &trimmedText = text.trimmed();

if (trimmedText.compare("true", Qt::CaseInsensitive) == 0
|| trimmedText.compare("yes", Qt::CaseInsensitive) == 0
|| trimmedText.compare("on", Qt::CaseInsensitive) == 0
|| trimmedText.compare("enabled", Qt::CaseInsensitive) == 0) {
value = true;
isValid = true;
} else if (trimmedText.compare("false", Qt::CaseInsensitive) == 0
|| trimmedText.compare("no", Qt::CaseInsensitive) == 0
|| trimmedText.compare("off", Qt::CaseInsensitive) == 0
|| trimmedText.compare("disabled", Qt::CaseInsensitive) == 0) {
value = false;
isValid = true;
} else if (const auto &number = qnc::parse<int>(trimmedText)) {
value = (number != 0);
isValid = true;
} else {
isValid = false;
}
}

template <>
void parse(const QByteArrayView &text, long double &value, bool &isValid)
{
if (Q_UNLIKELY(text.compare("+nan", Qt::CaseInsensitive) == 0)
|| Q_UNLIKELY(text.compare("-nan", Qt::CaseInsensitive) == 0)) {
isValid = false; // reject nan with sign to align with QString::toFloat() and ::toDouble()
} else if (Q_UNLIKELY(text.compare("+inf", Qt::CaseInsensitive) == 0)
|| Q_UNLIKELY(text.compare("inf", Qt::CaseInsensitive) == 0)) {
value = qInf(); // parse infinitity early so that it can be distinguished from out-of-range error
isValid = true;
} else if (Q_UNLIKELY(text.compare("-inf", Qt::CaseInsensitive) == 0)) {
value = -qInf(); // parse infinitity early so that it can be distinguished from out-of-range error
isValid = true;
} else {
const auto first = text.constData();
auto last = static_cast<char *>(nullptr);
const auto result = value = std::strtold(first, &last);

if (Q_UNLIKELY(last == nullptr)
|| Q_UNLIKELY(last == first)
|| Q_UNLIKELY(last[0] != '\0')
|| Q_UNLIKELY(std::isinf(result))) {
isValid = false;
} else {
value = result;
isValid = true;
}
}
}

#if QT_VERSION_MAJOR < 6

template <> void parse(const QLatin1String &text, long double &value, bool &isValid) { parse(QByteArray{text.data(), text.size()}, value, isValid); }
template <> void parse(const QLatin1String &text, bool &value, bool &isValid) { parse(QByteArray{text.data(), text.size()}, value, isValid); }

#else // QT_VERSION_MAJOR >= 6

template <> void parse(const QByteArray &text, long double &value, bool &isValid) { parse(QByteArrayView{text}, value, isValid); }
template <> void parse(const QByteArray &text, bool &value, bool &isValid) { parse(QByteArrayView{text}, value, isValid); }
template <> void parse(const QLatin1String &text, long double &value, bool &isValid) { parse(QByteArrayView{text}, value, isValid); }
template <> void parse(const QLatin1String &text, bool &value, bool &isValid) { parse(QByteArrayView{text}, value, isValid); }

#endif // QT_VERSION_MAJOR >= 6

template <> void parse(const QStringView &text, long double &value, bool &isValid) { parse(text.toLatin1(), value, isValid); }
template <> void parse(const QStringView &text, bool &value, bool &isValid) { parse(text.toLatin1(), value, isValid); }
template <> void parse(const QString &text, long double &value, bool &isValid) { parse(text.toLatin1(), value, isValid); }
template <> void parse(const QString &text, bool &value, bool &isValid) { parse(text.toLatin1(), value, isValid); }

#if QT_VERSION_MAJOR >= 6

template <> void parse(const QAnyStringView &text, long double &value, bool &isValid) { parse(text.toString(), value, isValid); }
template <> void parse(const QAnyStringView &text, bool &value, bool &isValid) { parse(text.toString(), value, isValid); }
template <> void parse(const QUtf8StringView &text, long double &value, bool &isValid) { parse(text.toString(), value, isValid); }
template <> void parse(const QUtf8StringView &text, bool &value, bool &isValid) { parse(text.toString(), value, isValid); }

#endif // QT_VERSION_MAJOR >= 6

} // namespace qnc::detail
66 changes: 47 additions & 19 deletions qnc/qncparse.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,48 +8,76 @@
#include <QString>

// STL headers
#include <limits>
#include <optional>

namespace qnc {

namespace detail {

template <class S, typename T> auto parseMethod(const T &) = delete;
template <class S> auto parseMethod(const short &) { return &S::toShort; }
template <class S> auto parseMethod(const ushort &) { return &S::toUShort; }
template <class S> auto parseMethod(const int &) { return &S::toInt; }
template <class S> auto parseMethod(const uint &) { return &S::toUInt; }
template <class S> auto parseMethod(const long &) { return &S::toLong; }
template <class S> auto parseMethod(const ulong &) { return &S::toULong; }
template <class S> auto parseMethod(const qlonglong &) { return &S::toLongLong; }
template <class S> auto parseMethod(const qulonglong &) { return &S::toULongLong; }
template <class S> auto parseMethod(const float &) { return &S::toFloat; }
template <class S> auto parseMethod(const double &) { return &S::toDouble; }

template <class S, typename T>
using HasBase = std::is_same<T (S::* )(bool *, int) const, decltype(detail::parseMethod<S>(T{}))>;
void parseIndirect(const S &text, T &value, bool &isValid, int base)
{
auto genericValue = text.toInt(&isValid, base);

if (Q_LIKELY(isValid)) {
if (Q_LIKELY(genericValue >= std::numeric_limits<T>::min())
&& Q_LIKELY(genericValue <= std::numeric_limits<T>::max()))
value = static_cast<T>(genericValue);
else
isValid = false;
}
}

template <class S, typename T, typename ...Args>
void parse(const S &, T &, bool &, Args...) = delete;

template <class S> void parse(const S &text, bool &value, bool &isValid);
template <class S> void parse(const S &text, qint8 &value, bool &isValid, int base = 10) { parseIndirect(text, value, isValid, base); }
template <class S> void parse(const S &text, quint8 &value, bool &isValid, int base = 10) { parseIndirect(text, value, isValid, base); }
template <class S> void parse(const S &text, short &value, bool &isValid, int base = 10) { value = text.toShort (&isValid, base); }
template <class S> void parse(const S &text, ushort &value, bool &isValid, int base = 10) { value = text.toUShort (&isValid, base); }
template <class S> void parse(const S &text, int &value, bool &isValid, int base = 10) { value = text.toInt (&isValid, base); }
template <class S> void parse(const S &text, uint &value, bool &isValid, int base = 10) { value = text.toUInt (&isValid, base); }
template <class S> void parse(const S &text, long &value, bool &isValid, int base = 10) { value = text.toLong (&isValid, base); }
template <class S> void parse(const S &text, ulong &value, bool &isValid, int base = 10) { value = text.toULong (&isValid, base); }
template <class S> void parse(const S &text, qlonglong &value, bool &isValid, int base = 10) { value = text.toLongLong (&isValid, base); }
template <class S> void parse(const S &text, qulonglong &value, bool &isValid, int base = 10) { value = text.toULongLong (&isValid, base); }
template <class S> void parse(const S &text, float &value, bool &isValid) { value = text.toFloat (&isValid); }
template <class S> void parse(const S &text, double &value, bool &isValid) { value = text.toDouble (&isValid); }
template <class S> void parse(const S &text, long double &value, bool &isValid);

template <typename T, class StringLike, typename ...Args>
std::optional<T> parse(const StringLike &text, Args &&...args)
{
auto value = T{};
auto isValid = false;
const auto parser = detail::parseMethod<StringLike>(T{});
const auto value = (text.*parser)(&isValid, std::forward<Args>(args)...);

parse(text, value, isValid, std::forward<Args>(args)...);

if (Q_LIKELY(isValid))
return value;

return {};
}

} // namespace detail
template <typename T, typename Char, std::size_t N, typename ...Args>
std::optional<T> parse(const Char (&str)[N], Args &&...args)
{
const auto it = std::char_traits<Char>::find(str, N, Char(0));
const auto end = it ? it : std::end(str);

return parse<T>(QStringView{str, end}, std::forward<Args>(args)...);
}

template <typename T, class StringLike, typename = std::enable_if_t<detail::HasBase<StringLike, T>::value>>
std::optional<T> parse(const StringLike &text, int base = 10) { return detail::parse<T>(text, base); }
} // namespace detail

template <typename T, class StringLike, typename = std::enable_if_t<!detail::HasBase<StringLike, T>::value>>
template <typename T, class StringLike>
std::optional<T> parse(const StringLike &text) { return detail::parse<T>(text); }

template <typename T, class StringLike>
std::optional<T> parse(const StringLike &text, int base) { return detail::parse<T>(text, base); }

} // namespace qnc

#endif // QNC_QNCPARSE_H
10 changes: 9 additions & 1 deletion tests/auto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,17 @@ function(add_testcase SOURCE_FILENAME) # [SOURCES...]
add_test(NAME "${TESTCASE_NAME}" COMMAND ${TEST_RUNNER} "$<TARGET_FILE:${TESTCASE_NAME}>")
endfunction()

qnc_add_library(
QncTestSuport STATIC
qnctestsupport.cpp
qnctestsupport.h
)

target_link_libraries(QncTestSuport PUBLIC Qt::Test)

add_testcase(tst_httpparser.cpp LIBRARIES QncHttp)
add_testcase(tst_mdnsmessages.cpp LIBRARIES QncMdns)
add_testcase(tst_mdnsresolver.cpp LIBRARIES QncMdns)
add_testcase(tst_qncparse.cpp LIBRARIES QncCore)
add_testcase(tst_qncparse.cpp LIBRARIES QncCore QncTestSuport)
add_testcase(tst_ssdpresolver.cpp LIBRARIES QncSsdp)
add_testcase(tst_xmlparser.cpp LIBRARIES QncXml)
77 changes: 77 additions & 0 deletions tests/auto/qnctestsupport.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* QtNetworkCrumbs - Some networking toys for Qt
* Copyright (C) 2019-2024 Mathias Hasselmann
*/
#include "qnctestsupport.h"

// Qt headers
#include <QTest>

// STL headers
#include <iomanip>
#include <sstream>

namespace qnc::tests {
namespace {

[[nodiscard]] std::string toStdString(long double value)
{
const auto p = std::numeric_limits<long double>::digits;
return (std::ostringstream{} << std::setprecision(p) << value).str();
}

[[nodiscard]] QString toString(long double value)
{
return QString::fromStdString(toStdString(value));
}

} // namespace

bool initialize()
{
return QMetaType::registerConverter<long double, QString>(&toString);
}

} // namespace qnc::tests

[[nodiscard]] char *QTest::toString(const long double &t)
{
return qstrdup(qnc::tests::toStdString(t).c_str());
}

[[nodiscard]] bool QTest::qCompare(long double const &t1, long double const &t2,
const char *actual, const char *expected,
const char *file, int line)
{
auto success = false;

if (std::isnan(t1)) {
success = std::isnan(t2);
} else if (std::isinf(t1) && t1 > 0) {
success = std::isinf(t2) && t2 > 0;
} else if (std::isinf(t1) && t1 < 0) {
success = std::isinf(t2) && t2 < 0;
} else {
success = qFuzzyCompare(t1, t2);
}

#if QT_VERSION_MAJOR < 6

return compare_helper(success, "Compared numbers are not the same",
toString(t1), toString(t2), actual, expected, file, line);

#else // QT_VERSION_MAJOR >= 6

return reportResult(success,
[t1] { return toString(t1); },
[t2] { return toString(t2); },
actual, expected, ComparisonOperation::Equal,
file, line);

#endif // QT_VERSION_MAJOR >= 6
}

QDebug operator<<(QDebug debug, long double value)
{
const auto _ = QDebugStateSaver{debug};
return debug.noquote() << qnc::tests::toString(value);
}
69 changes: 69 additions & 0 deletions tests/auto/qnctestsupport.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/* QtNetworkCrumbs - Some networking toys for Qt
* Copyright (C) 2019-2024 Mathias Hasselmann
*/
#ifndef QNC_QNCTESTSUPPORT_H
#define QNC_QNCTESTSUPPORT_H

// Qt headers
#include <QMetaType>

// STL headers
#include <cmath>

class QDebug;

Q_DECLARE_METATYPE(long double)

namespace QTest {

[[nodiscard]] char *toString(const long double &t);
[[nodiscard]] bool qCompare(long double const &t1, long double const &t2,
const char *actual, const char *expected,
const char *file, int line);

[[nodiscard]] inline bool qCompare(long double const &t1, const double &t2,
const char *actual, const char *expected,
const char *file, int line)
{
return qCompare(t1, static_cast<long double>(t2), actual, expected, file, line);
}

[[nodiscard]] inline bool qCompare(long double const &t1, const float &t2,
const char *actual, const char *expected,
const char *file, int line)
{
return qCompare(t1, static_cast<long double>(t2), actual, expected, file, line);
}

[[nodiscard]] inline bool qCompare(const double &t1, const long double &t2,
const char *actual, const char *expected,
const char *file, int line)
{
return qCompare(static_cast<long double>(t1), t2, actual, expected, file, line);
}

[[nodiscard]] inline bool qCompare(float const &t1, const long double &t2,
const char *actual, const char *expected,
const char *file, int line)
{
return qCompare(static_cast<long double>(t1), t2, actual, expected, file, line);
}

} // namespace QTest

[[nodiscard]] inline bool qFuzzyCompare(long double p1, long double p2)
{
if constexpr (sizeof(long double) != sizeof(double)) {
return (std::abs(p1 - p2) * 10000000000000000. <= std::min(std::abs(p1), std::abs(p2)));
} else {
return qFuzzyCompare(static_cast<double>(p1), static_cast<double>(p2));
}
}

QDebug operator<<(QDebug debug, long double value);

namespace qnc::tests {
bool initialize();
} // namespace qnc::tests

#endif // QNC_QNCTESTSUPPORT_H
Loading
Loading