From 718e67b5c26516fd71298e9ebe511c27c13a41c5 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 28 Sep 2023 19:48:44 +0200
Subject: [PATCH] fix #195: Fix string to double conversion (space before
 number)

---
 include/scratchcpp/value.h          | 134 +++++++++++++++++++++-------
 test/scratch_classes/value_test.cpp |  38 ++++++--
 2 files changed, 133 insertions(+), 39 deletions(-)

diff --git a/include/scratchcpp/value.h b/include/scratchcpp/value.h
index ff1cbced..b9b0d39e 100644
--- a/include/scratchcpp/value.h
+++ b/include/scratchcpp/value.h
@@ -257,24 +257,7 @@ class LIBSCRATCHCPP_EXPORT Value
                     case Type::Integer:
                         return std::to_string(m_intValue);
                     case Type::Double: {
-                        std::stringstream stream;
-                        stream << m_doubleValue;
-                        std::string s = stream.str();
-                        std::size_t index;
-
-                        for (int i = 0; i < 2; i++) {
-                            if (i == 0)
-                                index = s.find("e+");
-                            else
-                                index = s.find("e-");
-
-                            if (index != std::string::npos) {
-                                while ((s.size() >= index + 3) && (s[index + 2] == '0'))
-                                    s.erase(index + 2, 1);
-                            }
-                        }
-
-                        return s;
+                        return doubleToString(m_doubleValue);
                     }
                     case Type::Bool:
                         return m_boolValue ? "true" : "false";
@@ -471,6 +454,26 @@ class LIBSCRATCHCPP_EXPORT Value
                 std::string m_stringValue;
         };
 
+        // -1 - error
+        // 0 - is string
+        // 1 - is long
+        // 2 - is double
+        static int checkString(const std::string &str, long *longValue, double *doubleValue)
+        {
+            if (!longValue || !doubleValue)
+                return -1;
+
+            bool ok;
+
+            if ((str.find_first_of('.') == std::string::npos) && (str.find_first_of('e') == std::string::npos) && (str.find_first_of('E') == std::string::npos)) {
+                *longValue = stringToLong(str, &ok);
+                return ok ? 1 : 0;
+            } else {
+                *doubleValue = stringToDouble(str, &ok);
+                return ok ? 2 : 0;
+            }
+        }
+
         void initString(const std::string &str)
         {
             if (str.empty())
@@ -486,23 +489,21 @@ class LIBSCRATCHCPP_EXPORT Value
                 return;
             }
 
-            bool ok;
-            bool isLong = false;
             long l;
             double d;
-            if ((str.find_first_of('.') == std::string::npos) && (str.find_first_of('e') == std::string::npos) && (str.find_first_of('E') == std::string::npos)) {
-                l = stringToLong(str, &ok);
-                isLong = true;
-            } else
-                d = stringToDouble(str, &ok);
-            if (ok) {
-                if (isLong) {
+            int type = checkString(str, &l, &d);
+
+            switch (type) {
+                case 1:
                     *this = l;
                     m_type = Type::Integer;
-                } else {
+                    break;
+                case 2:
                     *this = d;
                     m_type = Type::Double;
-                }
+                    break;
+                default:
+                    break;
             }
         }
 
@@ -532,9 +533,30 @@ class LIBSCRATCHCPP_EXPORT Value
                         }
                 }
             } else {
-                if (v1.isString() || v2.isString())
-                    return stringsEqual(v1.toUtf16(), v2.toUtf16());
-                else if (v1.isNumber() || v2.isNumber())
+                if (v1.isString() || v2.isString()) {
+                    if (static_cast<int>(v1.m_type) < 0 || static_cast<int>(v2.m_type) < 0)
+                        return stringsEqual(v1.toUtf16(), v2.toUtf16());
+
+                    long l1, l2;
+                    double d1, d2;
+                    int type1 = checkString(v1.toString(), &l1, &d1);
+                    int type2 = checkString(v2.toString(), &l2, &d2);
+
+                    if (type1 == 1)
+                        d1 = l1;
+
+                    if (type2 == 1)
+                        d2 = l2;
+
+                    if (type1 > 0 && type2 > 0)
+                        return d1 == d2;
+                    else if (type2 > 0)
+                        return v1.toString() == doubleToString(d2);
+                    else if (type1 > 0)
+                        return doubleToString(d1) == v2.toString();
+                    else
+                        return stringsEqual(v1.toUtf16(), v2.toUtf16());
+                } else if (v1.isNumber() || v2.isNumber())
                     return v1.toDouble() == v2.toDouble();
                 else if (v1.isBool() || v2.isBool())
                     return ((v1.m_type != Type::NaN && v2.m_type != Type::NaN) && (v1.toBool() == v2.toBool()));
@@ -711,15 +733,37 @@ class LIBSCRATCHCPP_EXPORT Value
             }
 
             static const std::string digits = "0123456789.eE+-";
-            for (char c : s) {
+            const std::string *stringPtr = &s;
+            bool customStr = false;
+
+            if (!s.empty() && ((s[0] == ' ') || (s.back() == ' '))) {
+                std::string *localPtr = new std::string(s);
+                stringPtr = localPtr;
+                customStr = true;
+
+                while (!localPtr->empty() && (localPtr->at(0) == ' '))
+                    localPtr->erase(0, 1);
+
+                while (!localPtr->empty() && (localPtr->back() == ' '))
+                    localPtr->pop_back();
+            }
+
+            for (char c : *stringPtr) {
                 if (digits.find(c) == std::string::npos) {
                     return 0;
                 }
             }
+
             try {
                 if (ok)
                     *ok = true;
-                return std::stod(s);
+
+                double ret = std::stod(*stringPtr);
+
+                if (customStr)
+                    delete stringPtr;
+
+                return ret;
             } catch (...) {
                 if (ok)
                     *ok = false;
@@ -764,6 +808,28 @@ class LIBSCRATCHCPP_EXPORT Value
                 return 0;
             }
         }
+
+        static std::string doubleToString(double v)
+        {
+            std::stringstream stream;
+            stream << v;
+            std::string s = stream.str();
+            std::size_t index;
+
+            for (int i = 0; i < 2; i++) {
+                if (i == 0)
+                    index = s.find("e+");
+                else
+                    index = s.find("e-");
+
+                if (index != std::string::npos) {
+                    while ((s.size() >= index + 3) && (s[index + 2] == '0'))
+                        s.erase(index + 2, 1);
+                }
+            }
+
+            return s;
+        }
 };
 
 } // namespace libscratchcpp
diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp
index 3657d799..e522e760 100644
--- a/test/scratch_classes/value_test.cpp
+++ b/test/scratch_classes/value_test.cpp
@@ -1787,16 +1787,44 @@ TEST(ValueTest, EqualityOperators)
     {
         Value v1 = 5.25;
         Value v2 = "5.25";
-        Value v3 = "5.26";
+        Value v3 = " 5.25";
+        Value v4 = "5.25 ";
+        Value v5 = " 5.25 ";
+        Value v6 = "  5.25  ";
+        Value v7 = "5.26";
 
         ASSERT_TRUE(v1 == v2);
         ASSERT_FALSE(v1 != v2);
 
-        ASSERT_FALSE(v1 == v3);
-        ASSERT_TRUE(v1 != v3);
+        ASSERT_TRUE(v1 == v3);
+        ASSERT_FALSE(v1 != v3);
 
-        ASSERT_FALSE(v2 == v3);
-        ASSERT_TRUE(v2 != v3);
+        ASSERT_TRUE(v1 == v4);
+        ASSERT_FALSE(v1 != v4);
+
+        ASSERT_TRUE(v1 == v5);
+        ASSERT_FALSE(v1 != v5);
+
+        ASSERT_TRUE(v1 == v6);
+        ASSERT_FALSE(v1 != v6);
+
+        ASSERT_FALSE(v1 == v7);
+        ASSERT_TRUE(v1 != v7);
+
+        ASSERT_FALSE(v2 == v7);
+        ASSERT_TRUE(v2 != v7);
+
+        ASSERT_FALSE(v3 == v7);
+        ASSERT_TRUE(v3 != v7);
+
+        ASSERT_FALSE(v4 == v7);
+        ASSERT_TRUE(v4 != v7);
+
+        ASSERT_FALSE(v5 == v7);
+        ASSERT_TRUE(v5 != v7);
+
+        ASSERT_FALSE(v6 == v7);
+        ASSERT_TRUE(v6 != v7);
     }
 
     {
