Skip to content

Commit

Permalink
Merge pull request #254 from bbbgan/fix_parse_number
Browse files Browse the repository at this point in the history
  • Loading branch information
qicosmos authored Apr 12, 2024
2 parents cc352a9 + 7d20997 commit d009665
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 41 deletions.
17 changes: 13 additions & 4 deletions iguana/detail/charconv.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,36 @@

#include "dragonbox_to_chars.h"
#include "fast_float.h"
#include "iguana/define.h"
#include "itoa.hpp"


namespace iguana {
template <typename T>
struct is_char_type
: std::disjunction<std::is_same<T, char>, std::is_same<T, wchar_t>,
std::is_same<T, char16_t>, std::is_same<T, char32_t>> {};

namespace detail {
template <typename U>

// check_number==true: check if the string [first, last) is a legal number
template <bool check_number = true, typename U>
std::pair<const char *, std::errc> from_chars(const char *first,
const char *last,
U &value) noexcept {
const char *last, U &value) {
using T = std::decay_t<U>;
if constexpr (std::is_floating_point_v<T>) {
auto [p, ec] = fast_float::from_chars(first, last, value);
if constexpr (check_number) {
if (p != last || ec != std::errc{})
IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); }
}
return {p, ec};
}
else {
auto [p, ec] = std::from_chars(first, last, value);
if constexpr (check_number) {
if (p != last || ec != std::errc{})
IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); }
}
return {p, ec};
}
}
Expand Down
8 changes: 3 additions & 5 deletions iguana/json_reader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
if (size == 0)
IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); }
const auto start = &*it;
auto [p, ec] = detail::from_chars(start, start + size, value);
if (ec != std::errc{} || *p == '.')
auto [p, ec] = detail::from_chars<false>(start, start + size, value);
if (ec != std::errc{} || !can_follow_number(*p))
IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); }
it += (p - &*it);
}
Expand All @@ -82,9 +82,7 @@ IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
buffer[i] = *it++;
++i;
}
auto [p, ec] = detail::from_chars(buffer, buffer + i, value);
if (ec != std::errc{})
IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); }
detail::from_chars(buffer, buffer + i, value);
}
}

Expand Down
29 changes: 25 additions & 4 deletions iguana/json_util.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ class numeric_str {
if (val_.empty())
IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); }
T res;
auto [_, ec] =
detail::from_chars(val_.data(), val_.data() + val_.size(), res);
if (ec != std::errc{})
IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); }
detail::from_chars(val_.data(), val_.data() + val_.size(), res);
return res;
}

Expand Down Expand Up @@ -214,4 +211,28 @@ IGUANA_INLINE bool is_numeric(char c) noexcept {
return static_cast<bool>(is_num[static_cast<unsigned int>(c)]);
}

// '\t' '\r' '\n' '"' '}' ']' ',' ' ' '\0'
IGUANA_INLINE bool can_follow_number(char c) noexcept {
static constexpr int can_follow_num[256] = {
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, // 2
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, // 5
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F
};
return static_cast<bool>(can_follow_num[static_cast<unsigned int>(c)]);
}

} // namespace iguana
6 changes: 1 addition & 5 deletions iguana/xml_reader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
#include "detail/utf.hpp"
#include "xml_util.hpp"


namespace iguana {
namespace detail {

Expand Down Expand Up @@ -34,10 +33,7 @@ IGUANA_INLINE void parse_value(U &&value, It &&begin, It &&end) {
else if constexpr (num_v<T>) {
auto size = std::distance(begin, end);
const auto start = &*begin;
auto [p, ec] = detail::from_chars(start, start + size, value);
if (ec != std::errc{})
IGUANA_UNLIKELY
throw std::runtime_error("Failed to parse number");
detail::from_chars(start, start + size, value);
}
else if constexpr (char_v<T>) {
if (static_cast<size_t>(std::distance(begin, end)) != 1)
Expand Down
6 changes: 1 addition & 5 deletions iguana/yaml_reader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
#include "detail/utf.hpp"
#include "yaml_util.hpp"


namespace iguana {

template <typename T, typename It, std::enable_if_t<refletable_v<T>, int> = 0>
Expand Down Expand Up @@ -114,10 +113,7 @@ IGUANA_INLINE void parse_value(U &value, It &&value_begin, It &&value_end) {
IGUANA_UNLIKELY { return; }
auto size = std::distance(value_begin, value_end);
const auto start = &*value_begin;
auto [p, ec] = detail::from_chars(start, start + size, value);
if (ec != std::errc{})
IGUANA_UNLIKELY
throw std::runtime_error("Failed to parse number");
detail::from_chars(start, start + size, value);
}

// string_view should be used for string with ' " ?
Expand Down
33 changes: 16 additions & 17 deletions test/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,23 +180,22 @@ struct nest_t {
};
REFLECTION(nest_t, name, value, var, var2);

struct point_t1 {
int x;
int y;
};
REFLECTION(point_t1, x, y);

TEST_CASE("test double to int") {
point_t p{1, 0.45};
std::string s;
iguana::to_json(p, s);
std::cout << s << std::endl;
point_t1 p2;
CHECK_THROWS(iguana::from_json(p2, s));

point_t p3;
iguana::from_json(p3, s);
CHECK(p.y == p3.y);
TEST_CASE("test throw while parsing an illegal number") {
{
std::string str{"[0,1.0]"};
std::vector<int> test{};
CHECK_THROWS(iguana::from_json(test, str.begin(), str.end()));
}
{
std::string str{"1A"};
int test{};
CHECK_THROWS(iguana::from_json(test, str.begin(), str.end()));
}
{
std::string str{"1.0"};
int test{};
CHECK_THROWS(iguana::from_json(test, str.begin(), str.end()));
}
}

TEST_CASE("test variant") {
Expand Down
25 changes: 25 additions & 0 deletions test/test_xml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,31 @@ TEST_CASE("test smart_ptr") {
validator(cont1);
}

TEST_CASE("test throw while parsing an illegal number") {
{
std::string str = R"(
<Contents_t>
<vec>42A</vec>
<vec_s>15</vec_s>
<b>test</b>
</Contents_t>
)";
Contents_t cont;
CHECK_THROWS(iguana::from_xml(cont, str));
}
{
std::string str = R"(
<Contents_t>
<vec>42</vec>
<vec_s>15.7</vec_s>
<b>test</b>
</Contents_t>
)";
Contents_t cont;
CHECK_THROWS(iguana::from_xml(cont, str));
}
}

struct next_obj_t {
int x;
int y;
Expand Down
1 change: 0 additions & 1 deletion test/unit_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@ TEST_CASE("test parse item seq container") {
std::array<int, 3> test{};
CHECK_THROWS(iguana::from_json(test, str.begin(), str.end()));
}

{
std::string str{"[0,1,2"};
std::list<int> test{};
Expand Down

0 comments on commit d009665

Please sign in to comment.