Prechádzať zdrojové kódy

Adding more tests for double and int.
Adding support for hex and oct in integers.

Samuel Jaffe 9 rokov pred
rodič
commit
66dbd3768a
4 zmenil súbory, kde vykonal 186 pridanie a 40 odobranie
  1. 74 0
      json_binder_value_double.t.h
  2. 29 4
      json_binder_value_int.t.h
  3. 53 6
      json_common.cpp
  4. 30 30
      json_common.hpp

+ 74 - 0
json_binder_value_double.t.h

@@ -29,4 +29,78 @@ public:
     value_binder<double> binder{};
     TS_ASSERT_THROWS_NOTHING(parse(bind(out, binder), data, allow_all));
   }
+  
+  void test_parse_throws_on_no_data() {
+    char data[] = "";
+    double out = 0.0;
+    value_binder<double> binder{};
+    TS_ASSERT_THROWS(parse(bind(out, binder), data, allow_all),
+                     json::json_numeric_exception);
+  }
+
+  void test_parse_throws_on_non_numeric_data() {
+    char data[] = "one half";
+    double out = 0.0;
+    value_binder<double> binder{};
+    TS_ASSERT_THROWS(parse(bind(out, binder), data, allow_all),
+                     json::json_numeric_exception);
+  }
+  
+  void test_parse_throws_on_incorrectly_terminated_data_with_flag() {
+    char data[] = "5.0boo";
+    double out = 0.0;
+    value_binder<double> binder{};
+    TS_ASSERT_THROWS(parse(bind(out, binder), data,
+                           disable_concatenated_json_bodies),
+                     json::malformed_json_exception);
+    TS_ASSERT_EQUALS(out, 5.0);
+  }
+  
+  void test_parses_double_max_scientific_within_five_decimal_places() {
+    char data[] = "1.79769e+308";
+    double out = 0.0;
+    value_binder<double> binder{};
+    TS_ASSERT_THROWS_NOTHING(parse(bind(out, binder), data, allow_all));
+    TS_ASSERT_DELTA(out, std::numeric_limits<double>::max(), 1E303);
+  }
+
+  void test_throws_exception_on_number_out_of_range_max() {
+    char data[] = "1.8e+308";
+    double out = 0.0;
+    value_binder<double> binder{};
+    TS_ASSERT_THROWS(parse(bind(out, binder), data, allow_all),
+                     json::json_numeric_width_exception);
+  }
+
+  void test_parses_double_lowest_scientific_within_five_decimal_places() {
+    char data[] = "-1.79769e+308";
+    double out = 0.0;
+    value_binder<double> binder{};
+    TS_ASSERT_THROWS_NOTHING(parse(bind(out, binder), data, allow_all));
+    TS_ASSERT_DELTA(out, std::numeric_limits<double>::lowest(), 1E303);
+  }
+  
+  void test_throws_exception_on_number_out_of_range_lowest() {
+    char data[] = "-1.8e+308";
+    double out = 0.0;
+    value_binder<double> binder{};
+    TS_ASSERT_THROWS(parse(bind(out, binder), data, allow_all),
+                     json::json_numeric_width_exception);
+  }
+  
+  void test_parses_double_min_scientific_within_five_decimal_places() {
+    char data[] = "2.22508e-308"; // 2.22507e-308 is min according to cppreference
+    double out = 0.0;
+    value_binder<double> binder{};
+    TS_ASSERT_THROWS_NOTHING(parse(bind(out, binder), data, allow_all));
+    TS_ASSERT_DELTA(out, std::numeric_limits<double>::min(), 1E-303);
+  }
+  
+  void test_throws_exception_on_number_out_of_range_min() {
+    char data[] = "2e-308";
+    double out = 0.0;
+    value_binder<double> binder{};
+    TS_ASSERT_THROWS(parse(bind(out, binder), data, allow_all),
+                     json::json_numeric_width_exception);
+  }
 };

+ 29 - 4
json_binder_value_int.t.h

@@ -80,15 +80,32 @@ public:
     value_binder<int> binder{};
     TS_ASSERT_THROWS_NOTHING(parse(bind(out, binder), data, allow_all));
   }
-  
-  void test_parsing_short_will_error_over_narrowing() {
-    char data[] = "100000";
+
+  void test_signed_int_under_min_throws() {
+    char data[] = "-2147483649";
+    int out = 0;
+    value_binder<int> binder{};
+    TS_ASSERT_THROWS(parse(bind(out, binder), data, allow_all),
+                     json::json_numeric_width_exception);
+  }
+
+  void test_parsing_short_will_error_over_narrowing_max() {
+    char data[] = "32768";
     short out = 0;
     value_binder<short> binder{};
     TS_ASSERT_THROWS(parse(bind(out, binder), data, allow_all),
                      json::json_numeric_width_exception);
     TS_ASSERT_EQUALS(out, 0);
   }
+  
+  void test_parsing_short_will_error_over_narrowing_min() {
+    char data[] = "-32769";
+    short out = 0;
+    value_binder<short> binder{};
+    TS_ASSERT_THROWS(parse(bind(out, binder), data, allow_all),
+                     json::json_numeric_width_exception);
+  }
+
 
   void test_double_to_int_throws() {
     char data[] = "2.0";
@@ -114,5 +131,13 @@ public:
     TS_ASSERT_THROWS(parse(bind(out, binder), data,
                            disable_concatenated_json_bodies),
                      json::malformed_json_exception);
-  }  
+  }
+  
+  void test_parse_hexadecimal_number() {
+    char data[] = "0xF";
+    int out = 0;
+    value_binder<int> binder{};
+    parse(bind(out, binder), data, allow_all);
+    TS_ASSERT_EQUALS(out, 15);
+  }
 };

+ 53 - 6
json_common.cpp

@@ -6,6 +6,7 @@
 //
 
 #include "json_common.hpp"
+#include <map>
 
 namespace json {
   namespace helper {
@@ -19,35 +20,81 @@ namespace json {
       }
     }
     
+    namespace {
+      std::map<numeric_token_info::parse_state, uint_jt> bases{
+        { numeric_token_info::decimal,     10 },
+        { numeric_token_info::octal,        8 },
+        { numeric_token_info::hexadecimal, 16 }
+      };
+      std::map<numeric_token_info::parse_state, std::string> allowed{
+        { numeric_token_info::decimal,     "0123456789" },
+        { numeric_token_info::octal,       "01234567" },
+        { numeric_token_info::hexadecimal, "0123456789aAbBcCdDeEfF" }
+      };
+      std::map<char, int_jt> values{
+        { '0',  0 }, { '1',  1 }, { '2',  2 }, { '3',  3 },
+        { '4',  4 }, { '5',  5 }, { '6',  6 }, { '7',  7 },
+        { '8',  8 }, { '9',  9 }, { 'a', 10 }, { 'A', 10 },
+        { 'b', 11 }, { 'B', 11 }, { 'c', 12 }, { 'C', 12 },
+        { 'd', 13 }, { 'D', 13 }, { 'e', 14 }, { 'E', 14 },
+        { 'f', 15 }, { 'F', 15 }
+      };
+      std::map<numeric_token_info::parse_state, uint_jt> thresholds{
+        { numeric_token_info::decimal,     UINT_JT_MAX / 10 },
+        { numeric_token_info::octal,       UINT_JT_MAX /  8 },
+        { numeric_token_info::hexadecimal, UINT_JT_MAX / 16 }
+      };
+      std::map<numeric_token_info::parse_state, int_jt> last_digits{
+        { numeric_token_info::decimal,     INT_JT_MAX % 10 },
+        { numeric_token_info::octal,       INT_JT_MAX %  8 },
+        { numeric_token_info::hexadecimal, INT_JT_MAX % 16 }
+      };
+
+    }
+
+    // TODO - parse hex and octal
     numeric_token_info::numeric_token_info(char const * start)
     : val(0)
+    , base(decimal)
     , it(start)
     , end(start)
     , is_double(false)
     , is_negative(*start == '-') {
       if ( is_negative ) { ++it; ++end; }
+      if ( *end == '0' ) {
+        base = octal;
+        ++end;
+        if (strchr("xX", *end)) {
+          base = hexadecimal;
+          ++end;
+        }
+      }
       for (char c = *end;
-           strchr(",]}", c) == NULL && !isspace(c);
+           !strchr(",]}", c) && !isspace(c);
            c = *++end) {
-        is_double |= !isdigit(*end);
+        is_double |= !strchr(allowed[base].c_str(), c);
       }
       
+      if ( is_negative && base != decimal ) {
+        throw json_numeric_exception{"Only decimal numbers can be recorded as negative"};
+      }
       if (end == it) {
         throw unterminated_json_exception("Expected any token, got nothing");
       }
     }
     
     numeric_state numeric_token_info::parse_numeric() {
-      static uint_jt const threshold = (UINT_JT_MAX / 10);
+      uint_jt const threshold = thresholds[base];
+      uint_jt const last_digit = last_digits[base];
       val = 0;
       for (char c = *it; it != end; c = *++it) {
-        int_jt digit = static_cast<int_jt>(c - '0');
+        int_jt digit = values[c];
         if (val > threshold ||
             ( val == threshold && ((it + 1) < end ||
-              digit > INT_JT_MAX_LAST_DIGIT))) {
+              digit > last_digit))) {
           return DOUBLE;
         }
-        val = (10 * val) + digit;
+        val = (bases[base] * val) + digit;
       }
       return INTEGER;
     }

+ 30 - 30
json_common.hpp

@@ -41,7 +41,14 @@ namespace json {
   public json_numeric_exception {
     using json_numeric_exception::json_numeric_exception;
   };
-
+  
+  template <typename T = json::int_jt>
+  struct numeric_limits {
+    static constexpr const T max{std::numeric_limits<T>::max()};
+    static constexpr const T min{std::numeric_limits<T>::min()};
+    static constexpr const uint_jt over{uint_jt{max}+1};
+  };
+  
   namespace {
     const constexpr json::int_jt INT_JT_MAX = std::numeric_limits<json::int_jt>::max();
     const constexpr json::int_jt INT_JT_MAX_LAST_DIGIT = (INT_JT_MAX % 10);
@@ -69,11 +76,12 @@ namespace json { namespace helper {
   };
   
   struct numeric_token_info {
+    enum parse_state { decimal, octal, hexadecimal };
     numeric_token_info(char const * start);
     numeric_state parse_numeric();
     
     uint_jt val;
-    
+    parse_state base;
     char const * it;
     char const * end;
     bool is_double;
@@ -127,36 +135,28 @@ namespace json { namespace helper {
   template <typename J>
   void parse_numeric(J & json, char const * & data) {
     numeric_token_info info = data;
-    
-    if ( info.is_double || info.parse_numeric() == DOUBLE ) {
-      if (std::is_integral<J>::value) {
-        throw json::json_numeric_exception{"Expected integer, got double"};
-      }
-      helper::parse_double(json, data);
-    } else {
-      uint_jt const val = info.val;
-      
-      if (fls(val) > std::numeric_limits<J>::digits + info.is_negative) {
-        throw json::json_numeric_width_exception{"Integer width too small for parsed value"};
-      }
-
-      if (info.is_negative) {
-        if (!std::is_signed<J>::value) {
-          throw json::json_numeric_exception{"Expected signed integer"};
-        }
-        if (val == INT_JT_OVER) {
-          json = (J) INT_JT_MIN; // Cast suppresses error message, narrowing check above makes safe
-        } else {
-          json = -int_jt(val);
-        }
-      } else if (val <= uint_jt(INT_JT_MAX)) {
-        json = int_jt(val);
-      } else if (std::is_signed<J>::value) {
-        throw json::json_numeric_exception{"Expected unsigned integer"};
+    if ( info.is_negative && !std::is_signed<J>::value ) {
+      throw json_numeric_exception{"Expected signed integer"};
+    } else if ( info.is_double || info.parse_numeric() == DOUBLE ) {
+      throw json_numeric_exception{"Expected integer, got double"};
+    } else if (info.base == numeric_token_info::decimal &&
+               (fls(info.val) > std::numeric_limits<J>::digits + info.is_negative
+               || info.val > json::numeric_limits<J>::over)) {
+      throw json_numeric_width_exception{"Integer width too small for parsed value"};
+    } else if (info.is_negative) {
+      if (info.val == json::numeric_limits<J>::over) {
+        json = json::numeric_limits<J>::min;
       } else {
-        json = val;
+        json = -int_jt(info.val);
       }
-      data = info.it;
+    } else if (info.val <= uint_jt(INT_JT_MAX)) {
+      json = int_jt(info.val);
+    } else if (std::is_signed<J>::value &&
+               info.base == numeric_token_info::decimal) {
+      throw json_numeric_exception{"Expected unsigned integer"};
+    } else {
+      json = info.val;
     }
+    data = info.it;
   }
 } }