|
7 | 7 | #include <limits> |
8 | 8 | #include <ctgmath> |
9 | 9 | #include <iomanip> |
| 10 | +#include <charconv> |
10 | 11 | #include <cassert> |
11 | 12 |
|
| 13 | +#include "thirdparty/fast_float/fast_float.h" |
| 14 | + |
| 15 | +// Faster than std::isspace() |
| 16 | +#define IS_SPACE(x) (x == ' ' || x == '\f' || x == '\n' || x == '\r' || x == '\t' || x == '\v') |
| 17 | + |
12 | 18 | namespace libscratchcpp |
13 | 19 | { |
14 | 20 |
|
@@ -61,178 +67,176 @@ extern "C" |
61 | 67 | return (s1.compare(s2) == 0); |
62 | 68 | } |
63 | 69 |
|
64 | | - inline long value_hexToDec(const std::string &s) |
| 70 | + inline double value_hexToDec(const char *s, int n, bool *ok) |
65 | 71 | { |
66 | | - static const std::string digits = "0123456789abcdef"; |
| 72 | + if (ok) |
| 73 | + *ok = false; |
| 74 | + |
| 75 | + // Ignore floats |
| 76 | + const char *p = s; |
67 | 77 |
|
68 | | - for (char c : s) { |
69 | | - if (digits.find(c) == std::string::npos) { |
| 78 | + while (p++ < s + n) { |
| 79 | + if (*p == '.') |
70 | 80 | return 0; |
71 | | - } |
72 | 81 | } |
73 | 82 |
|
74 | | - std::istringstream stream(s); |
75 | | - long ret; |
76 | | - stream >> std::hex >> ret; |
77 | | - return ret; |
| 83 | + double result = 0; |
| 84 | + auto [ptr, ec] = std::from_chars(s, s + n, result, std::chars_format::hex); |
| 85 | + |
| 86 | + if (ec == std::errc{} && ptr == s + n) { |
| 87 | + if (ok) |
| 88 | + *ok = true; |
| 89 | + |
| 90 | + return result; |
| 91 | + } else |
| 92 | + return 0; |
78 | 93 | } |
79 | 94 |
|
80 | | - inline long value_octToDec(const std::string &s) |
| 95 | + inline long value_octToDec(const char *s, int n, bool *ok) |
81 | 96 | { |
82 | | - static const std::string digits = "01234567"; |
| 97 | + if (ok) |
| 98 | + *ok = false; |
83 | 99 |
|
84 | | - for (char c : s) { |
85 | | - if (digits.find(c) == std::string::npos) { |
86 | | - return 0; |
87 | | - } |
88 | | - } |
| 100 | + char *err; |
| 101 | + const double ret = std::strtol(s, &err, 8); |
89 | 102 |
|
90 | | - std::istringstream stream(s); |
91 | | - long ret; |
92 | | - stream >> std::oct >> ret; |
93 | | - return ret; |
| 103 | + if (*err != 0 && !std::isspace(*err)) |
| 104 | + return 0; |
| 105 | + else { |
| 106 | + if (ok) |
| 107 | + *ok = true; |
| 108 | + |
| 109 | + return ret; |
| 110 | + } |
94 | 111 | } |
95 | 112 |
|
96 | | - inline double value_binToDec(const std::string &s) |
| 113 | + inline double value_binToDec(const char *s, int n, bool *ok) |
97 | 114 | { |
98 | | - static const std::string digits = "01"; |
| 115 | + if (ok) |
| 116 | + *ok = false; |
99 | 117 |
|
100 | | - for (char c : s) { |
101 | | - if (digits.find(c) == std::string::npos) { |
102 | | - return 0; |
103 | | - } |
104 | | - } |
| 118 | + char *err; |
| 119 | + const double ret = std::strtol(s, &err, 2); |
| 120 | + |
| 121 | + if (*err != 0 && !std::isspace(*err)) |
| 122 | + return 0; |
| 123 | + else { |
| 124 | + if (ok) |
| 125 | + *ok = true; |
105 | 126 |
|
106 | | - return std::stoi(s, 0, 2); |
| 127 | + return ret; |
| 128 | + } |
107 | 129 | } |
108 | 130 |
|
109 | 131 | inline double value_stringToDouble(const std::string &s, bool *ok = nullptr) |
110 | 132 | { |
111 | 133 | if (ok) |
112 | 134 | *ok = false; |
113 | 135 |
|
114 | | - if (s == "Infinity") { |
115 | | - if (ok) |
116 | | - *ok = true; |
117 | | - return std::numeric_limits<double>::infinity(); |
118 | | - } else if (s == "-Infinity") { |
119 | | - if (ok) |
120 | | - *ok = true; |
121 | | - return -std::numeric_limits<double>::infinity(); |
122 | | - } else if (s == "NaN") { |
| 136 | + if (s.empty()) { |
123 | 137 | if (ok) |
124 | 138 | *ok = true; |
| 139 | + |
125 | 140 | return 0; |
126 | 141 | } |
127 | 142 |
|
128 | | - if (s.size() >= 2 && s[0] == '0') { |
129 | | - std::string sub = s.substr(2, s.size() - 2); |
130 | | - std::transform(sub.begin(), sub.end(), sub.begin(), ::tolower); |
| 143 | + const char *begin = s.data(); |
| 144 | + const char *strEnd = s.data() + s.size(); |
| 145 | + const char *end = strEnd; |
131 | 146 |
|
132 | | - if (s[1] == 'x' || s[1] == 'X') { |
133 | | - return value_hexToDec(sub); |
134 | | - } else if (s[1] == 'o' || s[1] == 'O') { |
135 | | - return value_octToDec(sub); |
136 | | - } else if (s[1] == 'b' || s[1] == 'B') { |
137 | | - return value_binToDec(sub); |
138 | | - } |
139 | | - } |
| 147 | + // Trim leading spaces |
| 148 | + while (begin < end && IS_SPACE(*begin)) |
| 149 | + ++begin; |
140 | 150 |
|
141 | | - static const std::string digits = "0123456789.eE+-"; |
142 | | - const std::string *stringPtr = &s; |
143 | | - bool customStr = false; |
| 151 | + end = begin + 1; |
144 | 152 |
|
145 | | - if (!s.empty() && ((s[0] == ' ') || (s.back() == ' '))) { |
146 | | - std::string *localPtr = new std::string(s); |
147 | | - stringPtr = localPtr; |
148 | | - customStr = true; |
| 153 | + // Trim trailing spaces |
| 154 | + while (end < strEnd && !IS_SPACE(*(end))) |
| 155 | + ++end; |
149 | 156 |
|
150 | | - while (!localPtr->empty() && (localPtr->at(0) == ' ')) |
151 | | - localPtr->erase(0, 1); |
| 157 | + // Only whitespace can be after the end |
| 158 | + const char *p = end; |
152 | 159 |
|
153 | | - while (!localPtr->empty() && (localPtr->back() == ' ')) |
154 | | - localPtr->pop_back(); |
| 160 | + while (p < strEnd) { |
| 161 | + if (!IS_SPACE(*p)) |
| 162 | + return 0; |
| 163 | + |
| 164 | + p++; |
155 | 165 | } |
156 | 166 |
|
157 | | - for (char c : *stringPtr) { |
158 | | - if (digits.find(c) == std::string::npos) { |
159 | | - return 0; |
| 167 | + if (end - begin <= 0) |
| 168 | + return 0; |
| 169 | + |
| 170 | + if (end - begin > 2 && begin[0] == '0') { |
| 171 | + const char prefix = begin[1]; |
| 172 | + const char *sub = begin + 2; |
| 173 | + |
| 174 | + if (prefix == 'x' || prefix == 'X') { |
| 175 | + return value_hexToDec(sub, end - begin - 2, ok); |
| 176 | + } else if (prefix == 'o' || prefix == 'O') { |
| 177 | + return value_octToDec(sub, end - begin - 2, ok); |
| 178 | + } else if (prefix == 'b' || prefix == 'B') { |
| 179 | + return value_binToDec(sub, end - begin - 2, ok); |
160 | 180 | } |
161 | 181 | } |
162 | 182 |
|
163 | | - try { |
| 183 | + // Trim leading zeros |
| 184 | + while (begin < end && (*begin == '0') && !(begin + 1 < end && begin[1] == '.')) |
| 185 | + ++begin; |
| 186 | + |
| 187 | + if (end - begin <= 0) { |
164 | 188 | if (ok) |
165 | 189 | *ok = true; |
166 | 190 |
|
167 | | - // Set locale to C to avoid conversion issues |
168 | | - std::string oldLocale = std::setlocale(LC_NUMERIC, nullptr); |
169 | | - std::setlocale(LC_NUMERIC, "C"); |
170 | | - |
171 | | - double ret = std::stod(*stringPtr); |
| 191 | + return 0; |
| 192 | + } |
172 | 193 |
|
173 | | - // Restore old locale |
174 | | - std::setlocale(LC_NUMERIC, oldLocale.c_str()); |
| 194 | + double ret = 0; |
| 195 | + auto [ptr, ec] = fast_float::from_chars(begin, end, ret, fast_float::chars_format::json); |
175 | 196 |
|
176 | | - if (customStr) |
177 | | - delete stringPtr; |
| 197 | + if (ec == std::errc{} && ptr == end) { |
| 198 | + if (ok) |
| 199 | + *ok = true; |
178 | 200 |
|
179 | 201 | return ret; |
180 | | - } catch (...) { |
181 | | - if (ok) |
182 | | - *ok = false; |
| 202 | + } else |
183 | 203 | return 0; |
184 | | - } |
185 | | - } |
186 | | - |
187 | | - inline long value_stringToLong(const std::string &s, bool *ok = nullptr) |
188 | | - { |
189 | | - if (ok) |
190 | | - *ok = false; |
191 | 204 |
|
| 205 | + // Special values |
192 | 206 | if (s == "Infinity") { |
193 | 207 | if (ok) |
194 | 208 | *ok = true; |
195 | | - return std::numeric_limits<long>::infinity(); |
| 209 | + return std::numeric_limits<double>::infinity(); |
196 | 210 | } else if (s == "-Infinity") { |
197 | 211 | if (ok) |
198 | 212 | *ok = true; |
199 | | - return -std::numeric_limits<long>::infinity(); |
| 213 | + return -std::numeric_limits<double>::infinity(); |
200 | 214 | } else if (s == "NaN") { |
201 | 215 | if (ok) |
202 | 216 | *ok = true; |
203 | | - return 0; |
| 217 | + return std::numeric_limits<double>::quiet_NaN(); |
204 | 218 | } |
205 | 219 |
|
206 | | - if (s.size() >= 2 && s[0] == '0') { |
207 | | - std::string sub = s.substr(2, s.size() - 2); |
208 | | - std::transform(sub.begin(), sub.end(), sub.begin(), ::tolower); |
| 220 | + return 0; |
| 221 | + } |
209 | 222 |
|
210 | | - if (s[1] == 'x' || s[1] == 'X') { |
211 | | - return value_hexToDec(sub); |
212 | | - } else if (s[1] == 'o' || s[1] == 'O') { |
213 | | - return value_octToDec(sub); |
214 | | - } else if (s[1] == 'b' || s[1] == 'B') { |
215 | | - return value_binToDec(sub); |
216 | | - } |
217 | | - } |
| 223 | + inline long value_stringToLong(const std::string &s, bool *ok = nullptr) |
| 224 | + { |
| 225 | + return value_stringToDouble(s, ok); |
| 226 | + } |
218 | 227 |
|
219 | | - static const std::string digits = "0123456789.eE+-"; |
| 228 | + inline bool value_stringIsInt(const char *s, int n) |
| 229 | + { |
| 230 | + const char *p = s; |
220 | 231 |
|
221 | | - for (char c : s) { |
222 | | - if (digits.find(c) == std::string::npos) { |
223 | | - return 0; |
224 | | - } |
225 | | - } |
| 232 | + while (p < s + n) { |
| 233 | + if (*s == '.' || *s == 'e' || *s == 'E') |
| 234 | + return false; |
226 | 235 |
|
227 | | - try { |
228 | | - if (ok) |
229 | | - *ok = true; |
230 | | - return std::stol(s); |
231 | | - } catch (...) { |
232 | | - if (ok) |
233 | | - *ok = false; |
234 | | - return 0; |
| 236 | + p++; |
235 | 237 | } |
| 238 | + |
| 239 | + return true; |
236 | 240 | } |
237 | 241 | } |
238 | 242 |
|
@@ -281,7 +285,7 @@ extern "C" |
281 | 285 | { |
282 | 286 | bool ok; |
283 | 287 |
|
284 | | - if ((str.find_first_of('.') == std::string::npos) && (str.find_first_of('e') == std::string::npos) && (str.find_first_of('E') == std::string::npos)) { |
| 288 | + if (value_stringIsInt(str.c_str(), str.size())) { |
285 | 289 | value_stringToLong(str, &ok); |
286 | 290 | return ok ? 1 : 0; |
287 | 291 | } else { |
|
0 commit comments