Skip to content
Closed
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
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,10 @@ if(BUILD_TESTING)
target_link_libraries(integer_literal_test ${LIBS} ${CHAISCRIPT_LIBS})
add_test(NAME Integer_Literal_Test COMMAND integer_literal_test)

add_executable(float_literal_test unittests/float_literal_test.cpp)
target_link_libraries(float_literal_test ${LIBS} ${CHAISCRIPT_LIBS})
add_test(NAME Float_Literal_Test COMMAND float_literal_test)

if(MULTITHREAD_SUPPORT_ENABLED)
add_executable(multithreaded_test unittests/multithreaded_test.cpp)
target_link_libraries(multithreaded_test ${LIBS})
Expand Down
84 changes: 27 additions & 57 deletions include/chaiscript/chaiscript_defines.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,19 @@ static_assert(_MSC_FULL_VER >= 190024210, "Visual C++ 2015 Update 3 or later req
#define CHAISCRIPT_DEBUG false
#endif

#include <cmath>
#include <charconv>
#include <memory>
#include <string>
#include <type_traits>

// libc++ (AppleClang, Emscripten) may not support floating-point std::from_chars
#if !defined(CHAISCRIPT_LIBCPP)
#define CHAISCRIPT_HAS_FLOAT_FROM_CHARS 1
#endif

#ifndef CHAISCRIPT_HAS_FLOAT_FROM_CHARS
#include <cstdlib>
#endif

namespace chaiscript {
constexpr static const int version_major = 7;
Expand Down Expand Up @@ -123,67 +133,27 @@ namespace chaiscript {
};

template<typename T>
[[nodiscard]] constexpr auto parse_num(const std::string_view t_str) noexcept -> typename std::enable_if<std::is_integral<T>::value, T>::type {
T t = 0;
for (const auto c : t_str) {
if (c < '0' || c > '9') {
return t;
[[nodiscard]] auto parse_num(const std::string_view t_str) {
T t{};
if constexpr (std::is_floating_point_v<T>) {
#ifdef CHAISCRIPT_HAS_FLOAT_FROM_CHARS
std::from_chars(t_str.data(), t_str.data() + t_str.size(), t);
#else
const std::string tmp(t_str);
if constexpr (std::is_same_v<T, float>) {
t = std::strtof(tmp.c_str(), nullptr);
} else if constexpr (std::is_same_v<T, long double>) {
t = std::strtold(tmp.c_str(), nullptr);
} else {
t = std::strtod(tmp.c_str(), nullptr);
}
t *= 10;
t += c - '0';
#endif
} else {
std::from_chars(t_str.data(), t_str.data() + t_str.size(), t);
}
return t;
}

template<typename T>
[[nodiscard]] auto parse_num(const std::string_view t_str) -> typename std::enable_if<!std::is_integral<T>::value, T>::type {
T t = 0;
T base{};
T decimal_place = 0;
int exponent = 0;

for (const auto c : t_str) {
switch (c) {
case '.':
decimal_place = 10;
break;
case 'e':
case 'E':
exponent = 1;
decimal_place = 0;
base = t;
t = 0;
break;
case '-':
exponent = -1;
break;
case '+':
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (decimal_place < 10) {
t *= 10;
t += static_cast<T>(c - '0');
} else {
t += static_cast<T>(c - '0') / decimal_place;
decimal_place *= 10;
}
break;
default:
break;
}
}
return exponent ? base * std::pow(T(10), t * static_cast<T>(exponent)) : t;
}

struct str_equal {
[[nodiscard]] bool operator()(const std::string &t_lhs, const std::string &t_rhs) const noexcept { return t_lhs == t_rhs; }
template<typename LHS, typename RHS>
Expand Down
10 changes: 5 additions & 5 deletions include/chaiscript/dispatchkit/bootstrap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,17 @@ namespace chaiscript::bootstrap {
m.add(fun([](const Boxed_Number &bn) { return bn.get_as<T>(); }), type);
}

/// Internal function for converting from a string to a value
/// uses ostream operator >> to perform the conversion
template<typename Input>
Input parse_string(const std::string &i) {
if constexpr (!std::is_same<Input, wchar_t>::value && !std::is_same<Input, char16_t>::value && !std::is_same<Input, char32_t>::value) {
if constexpr (std::is_same_v<Input, wchar_t> || std::is_same_v<Input, char16_t> || std::is_same_v<Input, char32_t>) {
throw std::runtime_error("Parsing of wide characters is not yet supported");
} else if constexpr (std::is_arithmetic_v<Input> && !std::is_same_v<Input, char>) {
return parse_num<Input>(i);
} else {
std::stringstream ss(i);
Input t;
ss >> t;
return t;
} else {
throw std::runtime_error("Parsing of wide characters is not yet supported");
}
}

Expand Down
73 changes: 26 additions & 47 deletions include/chaiscript/language/chaiscript_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#define CHAISCRIPT_PARSER_HPP_

#include <cctype>
#include <charconv>
#include <cstring>
#include <exception>
#include <iostream>
Expand Down Expand Up @@ -735,76 +736,54 @@ namespace chaiscript {
bool long_ = false;
bool longlong_ = false;

auto i = t_val.size();

for (; i > 0; --i) {
const char val = t_val[i - 1];

if (val == 'u' || val == 'U') {
while (!t_val.empty()) {
const char c = t_val.back();
if (c == 'u' || c == 'U') {
unsigned_ = true;
} else if (val == 'l' || val == 'L') {
if (long_) {
longlong_ = true;
}

} else if (c == 'l' || c == 'L') {
if (long_) { longlong_ = true; }
long_ = true;
} else {
break;
}
t_val.remove_suffix(1);
}

if (prefixed) {
t_val.remove_prefix(2);
}

unsigned long long uu = 0;
const auto [ptr, ec] = std::from_chars(t_val.data(), t_val.data() + t_val.size(), uu, base);

if (ec != std::errc()) {
return const_var(std::numeric_limits<long long>::max());
}

#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-compare"

#ifdef CHAISCRIPT_CLANG
#pragma GCC diagnostic ignored "-Wtautological-compare"
#pragma GCC diagnostic ignored "-Wtautological-unsigned-zero-compare"
#pragma GCC diagnostic ignored "-Wtautological-type-limit-compare"
#pragma GCC diagnostic ignored "-Wsign-conversion"
#endif

#endif

try {
/// TODO fix this to use from_chars
auto u = std::stoll(std::string(t_val), nullptr, base);

if (!unsigned_ && !long_ && u >= std::numeric_limits<int>::min() && u <= std::numeric_limits<int>::max()) {
return const_var(static_cast<int>(u));
} else if ((unsigned_ || base != 10) && !long_ && u >= std::numeric_limits<unsigned int>::min()
&& u <= std::numeric_limits<unsigned int>::max()) {
return const_var(static_cast<unsigned int>(u));
} else if (!unsigned_ && !longlong_ && u >= std::numeric_limits<long>::min() && u <= std::numeric_limits<long>::max()) {
return const_var(static_cast<long>(u));
} else if ((unsigned_ || base != 10) && !longlong_ && u >= std::numeric_limits<unsigned long>::min()
&& u <= std::numeric_limits<unsigned long>::max()) {
return const_var(static_cast<unsigned long>(u));
} else if (!unsigned_ && u >= std::numeric_limits<long long>::min() && u <= std::numeric_limits<long long>::max()) {
return const_var(static_cast<long long>(u));
} else {
return const_var(static_cast<unsigned long long>(u));
}

} catch (const std::out_of_range &) {
// too big to be signed
try {
/// TODO fix this to use from_chars
auto u = std::stoull(std::string(t_val), nullptr, base);

if (!longlong_ && u >= std::numeric_limits<unsigned long>::min() && u <= std::numeric_limits<unsigned long>::max()) {
return const_var(static_cast<unsigned long>(u));
} else {
return const_var(static_cast<unsigned long long>(u));
}
} catch (const std::out_of_range &) {
// it's just simply too big
return const_var(std::numeric_limits<long long>::max());
}
if (!unsigned_ && !long_ && uu <= static_cast<unsigned long long>(std::numeric_limits<int>::max())) {
return const_var(static_cast<int>(uu));
} else if ((unsigned_ || base != 10) && !long_ && uu <= std::numeric_limits<unsigned int>::max()) {
return const_var(static_cast<unsigned int>(uu));
} else if (!unsigned_ && !longlong_ && uu <= static_cast<unsigned long long>(std::numeric_limits<long>::max())) {
return const_var(static_cast<long>(uu));
} else if ((unsigned_ || base != 10) && !longlong_ && uu <= std::numeric_limits<unsigned long>::max()) {
return const_var(static_cast<unsigned long>(uu));
} else if (!unsigned_ && uu <= static_cast<unsigned long long>(std::numeric_limits<long long>::max())) {
return const_var(static_cast<long long>(uu));
} else {
return const_var(static_cast<unsigned long long>(uu));
}

#ifdef __GNUC__
Expand Down
72 changes: 27 additions & 45 deletions include/chaiscript/utility/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
#include "../chaiscript_defines.hpp"
#include "quick_flat_map.hpp"
#include <cctype>
#include <cmath>
#include <cstdint>
#include <initializer_list>
#include <iostream>
Expand Down Expand Up @@ -503,61 +502,44 @@ namespace chaiscript::json {
}

static JSON parse_number(const std::string &str, size_t &offset) {
std::string val, exp_str;
char c = '\0';
const auto start = offset;
bool isDouble = false;
bool isNegative = false;
std::int64_t exp = 0;
bool isExpNegative = false;
if (offset < str.size() && str.at(offset) == '-') {
isNegative = true;

if (offset < str.size() && str[offset] == '-') { ++offset; }

while (offset < str.size() && str[offset] >= '0' && str[offset] <= '9') { ++offset; }

if (offset < str.size() && str[offset] == '.') {
isDouble = true;
++offset;
while (offset < str.size() && str[offset] >= '0' && str[offset] <= '9') { ++offset; }
}
for (; offset < str.size();) {
c = str.at(offset++);
if (c >= '0' && c <= '9') {
val += c;
} else if (c == '.' && !isDouble) {
val += c;
isDouble = true;
} else {
break;

if (offset < str.size() && (str[offset] == 'e' || str[offset] == 'E')) {
isDouble = true;
++offset;
if (offset < str.size() && (str[offset] == '+' || str[offset] == '-')) { ++offset; }
if (offset >= str.size() || str[offset] < '0' || str[offset] > '9') {
throw std::runtime_error(std::string("JSON ERROR: Number: Expected a number for exponent, found '") + str[offset] + "'");
}
while (offset < str.size() && str[offset] >= '0' && str[offset] <= '9') { ++offset; }
}
if (offset < str.size() && (c == 'E' || c == 'e')) {
c = str.at(offset++);
if (c == '-') {
isExpNegative = true;
} else if (c == '+') {
// do nothing
} else {
--offset;
}

for (; offset < str.size();) {
c = str.at(offset++);
if (c >= '0' && c <= '9') {
exp_str += c;
} else if (!isspace(c) && c != ',' && c != ']' && c != '}') {
throw std::runtime_error(std::string("JSON ERROR: Number: Expected a number for exponent, found '") + c + "'");
} else {
break;
}
if (offset < str.size()) {
const char c = str[offset];
if (!isspace(c) && c != ',' && c != ']' && c != '}') {
throw std::runtime_error(std::string("JSON ERROR: Number: unexpected character '") + c + "'");
}
exp = chaiscript::parse_num<std::int64_t>(exp_str) * (isExpNegative ? -1 : 1);
} else if (offset < str.size() && (!isspace(c) && c != ',' && c != ']' && c != '}')) {
throw std::runtime_error(std::string("JSON ERROR: Number: unexpected character '") + c + "'");
}
--offset;

const auto *const first = str.data() + start;
const auto *const last = str.data() + offset;

const auto numstr = std::string_view(first, static_cast<std::string_view::size_type>(last - first));
if (isDouble) {
return JSON((isNegative ? -1 : 1) * chaiscript::parse_num<double>(val) * std::pow(10, exp));
return JSON(chaiscript::parse_num<double>(numstr));
} else {
if (!exp_str.empty()) {
return JSON((isNegative ? -1 : 1) * static_cast<double>(chaiscript::parse_num<std::int64_t>(val)) * std::pow(10, exp));
} else {
return JSON((isNegative ? -1 : 1) * chaiscript::parse_num<std::int64_t>(val));
}
return JSON(chaiscript::parse_num<std::int64_t>(numstr));
}
}

Expand Down
52 changes: 52 additions & 0 deletions unittests/float_literal_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#include "../static_libs/chaiscript_parser.hpp"
#include "../static_libs/chaiscript_stdlib.hpp"
#include <chaiscript/chaiscript_basic.hpp>

#include <cmath>
#include <iomanip>
#include <iostream>
#include <string>

#define TEST_FLOAT_LITERAL(v) test_float_literal(v, #v)

template<typename T>
bool test_float_literal(const T val, const std::string &str) {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
const T val2 = chai.eval<T>(str);
const bool pass = (val == val2);
std::cout << (pass ? "PASS" : "FAIL") << " (" << str << ") C++="
<< std::setprecision(20) << val << " chai=" << val2 << "\n";
return pass;
}

int main() {
bool success = true;

// Issue #378: scientific notation parsing inaccuracies
success = TEST_FLOAT_LITERAL(1.1e-4) && success;
success = TEST_FLOAT_LITERAL(1.5e+3) && success;
success = TEST_FLOAT_LITERAL(3.14159) && success;
success = TEST_FLOAT_LITERAL(2.718281828459045) && success;
success = TEST_FLOAT_LITERAL(1.0e10) && success;
success = TEST_FLOAT_LITERAL(1.0e-10) && success;
success = TEST_FLOAT_LITERAL(0.1) && success;
success = TEST_FLOAT_LITERAL(0.2) && success;
success = TEST_FLOAT_LITERAL(1.7976931348623157e+308) && success;
success = TEST_FLOAT_LITERAL(2.2250738585072014e-308) && success;

// Float suffix
success = TEST_FLOAT_LITERAL(1.1e-4f) && success;
success = TEST_FLOAT_LITERAL(3.14f) && success;

// Long double suffix
success = TEST_FLOAT_LITERAL(1.1e-4l) && success;
success = TEST_FLOAT_LITERAL(3.14l) && success;

if (success) {
std::cout << "All float literal tests passed.\n";
return 0;
} else {
std::cout << "FLOAT LITERAL TEST FAILURE\n";
return 1;
}
}
Loading