Refactoring of idl_parser (#4948)
authorVladimir Glavnyy <31897320+vglavnyy@users.noreply.github.com>
Thu, 11 Oct 2018 17:37:47 +0000 (00:37 +0700)
committerWouter van Oortmerssen <aardappel@gmail.com>
Thu, 11 Oct 2018 17:37:47 +0000 (10:37 -0700)
* Refactoring of numbers parser

More accurate parse of float and double.
Hexadecimal floats.
Check "out-of-range" of uint64 fields.
Check correctness of default values and metadata.

* Remove locale-independent code strtod/strtof from PR #4948.

* small optimization

* Add is_(ascii) functions

* is_ascii cleanup

* Fix format conversation

* Refine number parser

* Make code compatible with Android build

* Remove unnecessary suppression of warning C4127

.gitignore
docs/source/Grammar.md
docs/source/Schemas.md
include/flatbuffers/base.h
include/flatbuffers/idl.h
include/flatbuffers/stl_emulation.h
include/flatbuffers/util.h
src/idl_gen_cpp.cpp
src/idl_gen_text.cpp
src/idl_parser.cpp
tests/test.cpp

index db8b067..9e00828 100644 (file)
@@ -109,3 +109,5 @@ dart/.dart_tool/
 dart/build/
 dart/doc/api/
 Cargo.lock
+.corpus**
+.seed**
index bf79596..724137e 100644 (file)
@@ -49,11 +49,26 @@ file_extension_decl = `file_extension` string\_constant `;`
 
 file_identifier_decl = `file_identifier` string\_constant `;`
 
-integer\_constant = `-?[0-9]+` | `true` | `false`
-
-float\_constant = `-?[0-9]+.[0-9]+((e|E)(+|-)?[0-9]+)?`
-
 string\_constant = `\".*?\"`
 
 ident = `[a-zA-Z_][a-zA-Z0-9_]*`
 
+`[:digit:]` = `[0-9]`
+
+`[:xdigit:]` = `[0-9a-fA-F]`
+
+dec\_integer\_constant = `[-+]?[:digit:]+`
+
+hex\_integer\_constant = `[-+]?0[xX][:xdigit:]+`
+
+integer\_constant = dec\_integer\_constant | hex\_integer\_constant
+
+dec\_float\_constant = `[-+]?(([.][:digit:]+)|([:digit:]+[.][:digit:]*)|([:digit:]+))([eE][-+]?[:digit:]+)?`
+
+hex\_float\_constant = `[-+]?0[xX](([.][:xdigit:]+)|([:xdigit:]+[.][:xdigit:]*)|([:xdigit:]+))([pP][-+]?[:digit:]+)`
+
+special\_float\_constant = `[-+]?(nan|inf|infinity)`
+
+float\_constant = decimal\_float\_constant | hexadecimal\_float\_constant | special\_float\_constant
+
+boolean\_constant = `(true|false)` | (integer\_constant ? `true` : `false`)
index a05b002..9647f7c 100644 (file)
@@ -385,6 +385,31 @@ When parsing JSON, it recognizes the following escape codes in strings:
 It also generates these escape codes back again when generating JSON from a
 binary representation.
 
+When parsing numbers, the parser is more flexible than JSON.
+A format of numeric literals is more close to the C/C++.
+According to the [grammar](@ref flatbuffers_grammar), it accepts the following 
+numerical literals:
+
+-   An integer literal can have any number of leading zero `0` digits.
+    Unlike C/C++, the parser ignores a leading zero, not interpreting it as the 
+    beginning of the octal number.
+    The numbers `[081, -00094]` are equal to `[81, -94]`  decimal integers.
+-   The parser accepts unsigned and signed hexadecimal integer numbers.
+    For example: `[0x123, +0x45, -0x67]` are equal to `[291, 69, -103]` decimals.
+-   The format of float-point numbers is fully compatible with C/C++ format.
+    If a modern C++ compiler is used the parser accepts hexadecimal and special 
+    float-point literals as well:
+    `[-1.0, 2., .3e0, 3.e4, 0x21.34p-5, -inf, nan]`.
+    The exponent suffix of hexadecimal float-point number is mandatory.
+    
+    Extended float-point support was tested with:
+    - x64 Windows: `MSVC2015` and higher.
+    - x64 Linux: `LLVM 6.0`, `GCC 4.9` and higher.
+
+-   For compatibility with a JSON lint tool all numeric literals of scalar 
+    fields can be wrapped to quoted string:
+    `"1", "2.0", "0x48A", "0x0C.0Ep-1", "-inf", "true"`.
+
 ## Guidelines
 
 ### Efficiency
index c755d6f..2656f60 100644 (file)
   #endif // __has_include
 #endif // !FLATBUFFERS_HAS_STRING_VIEW
 
+#ifndef FLATBUFFERS_HAS_NEW_STRTOD
+  // Modern (C++11) strtod and strtof functions are available for use.
+  // 1) nan/inf strings as argument of strtod;
+  // 2) hex-float  as argument of  strtod/strtof.
+  #if (defined(_MSC_VER) && _MSC_VER >= 1900) || \
+      (defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 409)) || \
+      (defined(__clang__))
+    #define FLATBUFFERS_HAS_NEW_STRTOD 1
+  #endif
+#endif // !FLATBUFFERS_HAS_NEW_STRTOD
+
 /// @endcond
 
 /// @file
index cf5446a..024f324 100644 (file)
@@ -484,7 +484,11 @@ struct IDLOptions {
 // This encapsulates where the parser is in the current source file.
 struct ParserState {
   ParserState()
-      : cursor_(nullptr), line_start_(nullptr), line_(0), token_(-1) {}
+      : cursor_(nullptr),
+        line_start_(nullptr),
+        line_(0),
+        token_(-1),
+        attr_is_trivial_ascii_string_(true) {}
 
  protected:
   void ResetState(const char *source) {
@@ -508,6 +512,10 @@ struct ParserState {
   int line_;  // the current line being parsed
   int token_;
 
+  // Flag: text in attribute_ is true ASCII string without escape
+  // sequences. Only printable ASCII (without [\t\r\n]).
+  // Used for number-in-string (and base64 string in future).
+  bool attr_is_trivial_ascii_string_;
   std::string attribute_;
   std::vector<std::string> doc_comment_;
 };
@@ -644,7 +652,8 @@ class Parser : public ParserState {
   bool ParseFlexBuffer(const char *source, const char *source_filename,
                        flexbuffers::Builder *builder);
 
-  FLATBUFFERS_CHECKED_ERROR CheckInRange(int64_t val, int64_t min, int64_t max);
+  FLATBUFFERS_CHECKED_ERROR InvalidNumber(const char *number,
+                                          const std::string &msg);
 
   StructDef *LookupStruct(const std::string &id) const;
 
@@ -711,7 +720,7 @@ class Parser : public ParserState {
                                           BaseType req, bool *destmatch);
   FLATBUFFERS_CHECKED_ERROR ParseHash(Value &e, FieldDef* field);
   FLATBUFFERS_CHECKED_ERROR TokenError();
-  FLATBUFFERS_CHECKED_ERROR ParseSingleValue(const std::string *name, Value &e);
+  FLATBUFFERS_CHECKED_ERROR ParseSingleValue(const std::string *name, Value &e, bool check_now);
   FLATBUFFERS_CHECKED_ERROR ParseEnumFromString(Type &type, int64_t *result);
   StructDef *LookupCreateStruct(const std::string &name,
                                 bool create_if_new = true,
index ff537cd..028cc6f 100644 (file)
@@ -37,9 +37,9 @@
 // Not possible if Microsoft Compiler before 2012
 // Possible is the language feature __cpp_alias_templates is defined well
 // Or possible if the C++ std is C+11 or newer
-#if !(defined(_MSC_VER) && _MSC_VER <= 1700 /* MSVC2012 */) \
-  && ((defined(__cpp_alias_templates) && __cpp_alias_templates >= 200704) \
-    || (defined(__cplusplus) && __cplusplus >= 201103L))
+#if (defined(_MSC_VER) && _MSC_VER > 1700 /* MSVC2012 */) \
+    || (defined(__cpp_alias_templates) && __cpp_alias_templates >= 200704) \
+    || (defined(__cplusplus) && __cplusplus >= 201103L)
   #define FLATBUFFERS_TEMPLATES_ALIASES
 #endif
 
@@ -88,12 +88,33 @@ inline void vector_emplace_back(std::vector<T> *vector, V &&data) {
   #endif  // defined(FLATBUFFERS_TEMPLATES_ALIASES)
 #else
   template <typename T> class numeric_limits :
-      public std::numeric_limits<T> {};
+      public std::numeric_limits<T> {
+    public:
+      // Android NDK fix.
+      static T lowest() {
+        return std::numeric_limits<T>::min();
+      }
+  };
+
+  template <> class numeric_limits<float> : 
+      public std::numeric_limits<float> {
+    public:
+      static float lowest() { return -FLT_MAX; }
+  };
+
+  template <> class numeric_limits<double> : 
+      public std::numeric_limits<double> {
+    public:
+      static double lowest() { return -DBL_MAX; }
+  };
 
   template <> class numeric_limits<unsigned long long> {
    public:
     static unsigned long long min() { return 0ULL; }
     static unsigned long long max() { return ~0ULL; }
+    static unsigned long long lowest() {
+      return numeric_limits<unsigned long long>::min();
+    }
   };
 
   template <> class numeric_limits<long long> {
@@ -105,6 +126,9 @@ inline void vector_emplace_back(std::vector<T> *vector, V &&data) {
       return static_cast<long long>(
           (1ULL << ((sizeof(long long) << 3) - 1)) - 1);
     }
+    static long long lowest() {
+      return numeric_limits<long long>::min();
+    }
   };
 #endif  // FLATBUFFERS_CPP98_STL
 
@@ -114,6 +138,7 @@ inline void vector_emplace_back(std::vector<T> *vector, V &&data) {
     template <typename T, typename U> using is_same = std::is_same<T,U>;
     template <typename T> using is_floating_point = std::is_floating_point<T>;
     template <typename T> using is_unsigned = std::is_unsigned<T>;
+    template <typename T> using make_unsigned = std::make_unsigned<T>;
   #else
     // Map C++ TR1 templates defined by stlport.
     template <typename T> using is_scalar = std::tr1::is_scalar<T>;
@@ -121,6 +146,13 @@ inline void vector_emplace_back(std::vector<T> *vector, V &&data) {
     template <typename T> using is_floating_point =
         std::tr1::is_floating_point<T>;
     template <typename T> using is_unsigned = std::tr1::is_unsigned<T>;
+    // Android NDK doesn't have std::make_unsigned or std::tr1::make_unsigned.
+    template<typename T> struct make_unsigned {
+      static_assert(is_unsigned<T>::value, "Specialization not impelented!");
+      using type = T;
+    };
+    template<> struct make_unsigned<char> { using type = unsigned char; };
+    template<> struct make_unsigned<int>  { using type = unsigned int;  };
   #endif  // !FLATBUFFERS_CPP98_STL
 #else
   // MSVC 2010 doesn't support C++11 aliases.
@@ -129,6 +161,7 @@ inline void vector_emplace_back(std::vector<T> *vector, V &&data) {
   template <typename T> struct is_floating_point :
         public std::is_floating_point<T> {};
   template <typename T> struct is_unsigned : public std::is_unsigned<T> {};
+  template <typename T> struct make_unsigned : public std::make_unsigned<T> {};
 #endif  // defined(FLATBUFFERS_TEMPLATES_ALIASES)
 
 #ifndef FLATBUFFERS_CPP98_STL
index cf2949a..01e1b23 100644 (file)
@@ -17,7 +17,7 @@
 #ifndef FLATBUFFERS_UTIL_H_
 #define FLATBUFFERS_UTIL_H_
 
-#include <assert.h>
+#include <errno.h>
 #include <stdint.h>
 #include <stdlib.h>
 #include <fstream>
 
 namespace flatbuffers {
 
+// Avoid `#pragma warning(disable: 4127) // C4127: expression is constant`.
+template<typename T> FLATBUFFERS_CONSTEXPR inline bool IsConstTrue(const T &t) {
+  return !!t;
+}
+
+// @locale-independent functions for ASCII characters set.
+
+// Check that integer scalar is in closed range: (a <= x <= b)
+// using one compare (conditional branch) operator.
+template<typename T> inline bool check_in_range(T x, T a, T b) {
+  // (Hacker's Delight): `a <= x <= b` <=> `(x-a) <={u} (b-a)`.
+  FLATBUFFERS_ASSERT(a <= b);  // static_assert only if 'a' & 'b' templated
+  typedef typename flatbuffers::make_unsigned<T>::type U;
+  return (static_cast<U>(x - a) <= static_cast<U>(b - a));
+}
+
+// Case-insensitive isalpha
+static inline bool is_alpha(char c) {
+  // ASCII only: alpha to upper case => reset bit 0x20 (~0x20 = 0xDF).
+  return check_in_range(c & 0xDF, 'a' & 0xDF, 'z' & 0xDF);
+}
+
+// Check (case-insensitive) that `c` is equal to alpha.
+static inline bool is_alpha_char(char c, char alpha) {
+  FLATBUFFERS_ASSERT(is_alpha(alpha));
+  // ASCII only: alpha to upper case => reset bit 0x20 (~0x20 = 0xDF).
+  return ((c & 0xDF) == (alpha & 0xDF));
+}
+
+// https://en.cppreference.com/w/cpp/string/byte/isxdigit
+// isdigit and isxdigit are the only standard narrow character classification
+// functions that are not affected by the currently installed C locale. although
+// some implementations (e.g. Microsoft in 1252 codepage) may classify
+// additional single-byte characters as digits.
+static inline bool is_digit(char c) { return check_in_range(c, '0', '9'); }
+
+static inline bool is_xdigit(char c) {
+  // Replace by look-up table.
+  return is_digit(c) | check_in_range(c & 0xDF, 'a' & 0xDF, 'f' & 0xDF);
+}
+
+// Case-insensitive isalnum
+static inline bool is_alnum(char c) { return is_alpha(c) || is_digit(c); }
+
+// @end-locale-independent functions for ASCII character set
+
 #ifdef FLATBUFFERS_PREFER_PRINTF
 template<typename T> size_t IntToDigitCount(T t) {
   size_t digit_count = 0;
@@ -158,6 +204,7 @@ template<> inline std::string NumToString<float>(float t) {
 // The returned string length is always xdigits long, prefixed by 0 digits.
 // For example, IntToStringHex(0x23, 8) returns the string "00000023".
 inline std::string IntToStringHex(int i, int xdigits) {
+  FLATBUFFERS_ASSERT(i >= 0);
   // clang-format off
   #ifndef FLATBUFFERS_PREFER_PRINTF
     std::stringstream ss;
@@ -170,28 +217,178 @@ inline std::string IntToStringHex(int i, int xdigits) {
   // clang-format on
 }
 
-// Portable implementation of strtoll().
-inline int64_t StringToInt(const char *str, char **endptr = nullptr,
-                           int base = 10) {
+static inline double strtod_impl(const char *str, char **str_end) {
+  // Result of strtod (printf, etc) depends from current C-locale.
+  return strtod(str, str_end);
+}
+
+static inline float strtof_impl(const char *str, char **str_end) {
+  // Use "strtof" for float and strtod for double to avoid double=>float
+  // rounding problems (see
+  // https://en.cppreference.com/w/cpp/numeric/fenv/feround) or problems with
+  // std::numeric_limits<float>::is_iec559==false. Example:
+  //  for (int mode : { FE_DOWNWARD, FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD }){
+  //    const char *s = "-4e38";
+  //    std::fesetround(mode);
+  //    std::cout << strtof(s, nullptr) << "; " << strtod(s, nullptr) << "; "
+  //              << static_cast<float>(strtod(s, nullptr)) << "\n";
+  //  }
+  // Gives:
+  //  -inf; -4e+38; -inf
+  //  -inf; -4e+38; -inf
+  //  -inf; -4e+38; -3.40282e+38
+  //  -inf; -4e+38; -3.40282e+38
+
   // clang-format off
-  #ifdef _MSC_VER
-    return _strtoi64(str, endptr, base);
+  #ifdef FLATBUFFERS_HAS_NEW_STRTOD
+    return strtof(str, str_end);
   #else
-    return strtoll(str, endptr, base);
-  #endif
+    return static_cast<float>(strtod_impl(str, str_end));
+  #endif // !FLATBUFFERS_HAS_NEW_STRTOD
   // clang-format on
 }
 
-// Portable implementation of strtoull().
-inline uint64_t StringToUInt(const char *str, char **endptr = nullptr,
+// Adaptor for strtoull()/strtoll().
+// Flatbuffers accepts numbers with any count of leading zeros (-009 is -9),
+// while strtoll with base=0 interprets first leading zero as octal prefix.
+// In future, it is possible to add prefixed 0b0101.
+// 1) Checks errno code for overflow condition (out of range).
+// 2) If base <= 0, function try to detect base of number by prefix.
+//
+// Return value (like strtoull and strtoll, but reject partial result):
+// - If successful, an integer value corresponding to the str is returned.
+// - If full string conversion can't be performed, ​0​ is returned.
+// - If the converted value falls out of range of corresponding return type, a
+// range error occurs. In this case value MAX(T)/MIN(T) is returned.
+template<typename T>
+inline T StringToInteger64Impl(const char *const str, const char **endptr,
+                               const int base, const bool check_errno = true) {
+  static_assert(flatbuffers::is_same<T, int64_t>::value ||
+                flatbuffers::is_same<T, uint64_t>::value,
+                "Type T must be either int64_t or uint64_t");
+  FLATBUFFERS_ASSERT(str && endptr);  // endptr must be not null
+  if (base <= 0) {
+    auto s = str;
+    while (*s && !is_digit(*s)) s++;
+    if (s[0] == '0' && is_alpha_char(s[1], 'X'))
+      return StringToInteger64Impl<T>(str, endptr, 16, check_errno);
+    // if a prefix not match, try base=10
+    return StringToInteger64Impl<T>(str, endptr, 10, check_errno);
+  } else {
+    if (check_errno) errno = 0;  // clear thread-local errno
+    // calculate result
+    T result;
+    if (IsConstTrue(flatbuffers::is_same<T, int64_t>::value)) {
+      // clang-format off
+      #ifdef _MSC_VER
+        result = _strtoi64(str, const_cast<char**>(endptr), base);
+      #else
+        result = strtoll(str, const_cast<char**>(endptr), base);
+      #endif
+      // clang-format on
+    } else {  // T is uint64_t
+      // clang-format off
+      #ifdef _MSC_VER
+        result = _strtoui64(str, const_cast<char**>(endptr), base);
+      #else
+        result = strtoull(str, const_cast<char**>(endptr), base);
+      #endif
+      // clang-format on
+
+      // The strtoull accepts negative numbers:
+      // If the minus sign was part of the input sequence, the numeric value
+      // calculated from the sequence of digits is negated as if by unary minus
+      // in the result type, which applies unsigned integer wraparound rules.
+      // Fix this behaviour (except -0).
+      if ((**endptr == '\0') && (0 != result)) {
+        auto s = str;
+        while (*s && !is_digit(*s)) s++;
+        s = (s > str) ? (s - 1) : s;  // step back to one symbol
+        if (*s == '-') {
+          // For unsigned types return max to distinguish from
+          // "no conversion can be performed".
+          result = flatbuffers::numeric_limits<T>::max();
+          // point to the start of string, like errno
+          *endptr = str;
+        }
+      }
+    }
+    // check for overflow
+    if (check_errno && errno) *endptr = str; // point it to start of input
+    // erase partial result, but save an overflow
+    if ((*endptr != str) && (**endptr != '\0')) result = 0;
+    return result;
+  }
+}
+
+// Convert a string to an instance of T.
+// Return value (matched with StringToInteger64Impl and strtod):
+// - If successful, a numeric value corresponding to the str is returned.
+// - If full string conversion can't be performed, ​0​ is returned.
+// - If the converted value falls out of range of corresponding return type, a
+// range error occurs. In this case value MAX(T)/MIN(T) is returned.
+template<typename T> inline bool StringToNumber(const char *s, T *val) {
+  FLATBUFFERS_ASSERT(s && val);
+  const char *end = nullptr;
+  // The errno check isn't needed. strtoll will return MAX/MIN on overlow.
+  const int64_t i = StringToInteger64Impl<int64_t>(s, &end, -1, false);
+  *val = static_cast<T>(i);
+  const auto done = (s != end) && (*end == '\0');
+  if (done) {
+    const int64_t max = flatbuffers::numeric_limits<T>::max();
+    const int64_t min = flatbuffers::numeric_limits<T>::lowest();
+    if (i > max) {
+      *val = static_cast<T>(max);
+      return false;
+    }
+    if (i < min) {
+      // For unsigned types return max to distinguish from
+      // "no conversion can be performed" when 0 is returned.
+      *val = static_cast<T>(flatbuffers::is_unsigned<T>::value ? max : min);
+      return false;
+    }
+  }
+  return done;
+}
+template<> inline bool StringToNumber<int64_t>(const char *s, int64_t *val) {
+  const char *end = s;  // request errno checking
+  *val = StringToInteger64Impl<int64_t>(s, &end, -1);
+  return (s != end) && (*end == '\0');
+}
+template<> inline bool StringToNumber<uint64_t>(const char *s, uint64_t *val) {
+  const char *end = s;  // request errno checking
+  *val = StringToInteger64Impl<uint64_t>(s, &end, -1);
+  return (s != end) && (*end == '\0');
+}
+
+template<> inline bool StringToNumber<double>(const char *s, double *val) {
+  FLATBUFFERS_ASSERT(s && val);
+  char *end = nullptr;
+  *val = strtod_impl(s, &end);
+  auto done = (s != end) && (*end == '\0');
+  if (!done) *val = 0;  // erase partial result
+  return done;
+}
+
+template<> inline bool StringToNumber<float>(const char *s, float *val) {
+  FLATBUFFERS_ASSERT(s && val);
+  char *end = nullptr;
+  *val = strtof_impl(s, &end);
+  auto done = (s != end) && (*end == '\0');
+  if (!done) *val = 0;  // erase partial result
+  return done;
+}
+
+inline int64_t StringToInt(const char *str, const char **endptr = nullptr,
+                           int base = 10) {
+  const char *ep = nullptr;
+  return StringToInteger64Impl<int64_t>(str, endptr ? endptr : &ep, base);
+}
+
+inline uint64_t StringToUInt(const char *str, const char **endptr = nullptr,
                              int base = 10) {
-  // clang-format off
-  #ifdef _MSC_VER
-    return _strtoui64(str, endptr, base);
-  #else
-    return strtoull(str, endptr, base);
-  #endif
-  // clang-format on
+  const char *ep = nullptr;
+  return StringToInteger64Impl<uint64_t>(str, endptr ? endptr : &ep, base);
 }
 
 typedef bool (*LoadFileFunction)(const char *filename, bool binary,
index 1783813..9e39339 100644 (file)
@@ -145,7 +145,7 @@ class CppGenerator : public BaseGenerator {
     std::string guard = file_name_;
     // Remove any non-alpha-numeric characters that may appear in a filename.
     struct IsAlnum {
-      bool operator()(char c) const { return !isalnum(c); }
+      bool operator()(char c) const { return !is_alnum(c); }
     };
     guard.erase(std::remove_if(guard.begin(), guard.end(), IsAlnum()),
                 guard.end());
index 563f690..c129488 100644 (file)
@@ -149,19 +149,23 @@ bool Print<const void *>(const void *val, Type type, int indent,
   return true;
 }
 
+template<typename T> static T GetFieldDefault(const FieldDef &fd) {
+  T val;
+  auto check = StringToNumber(fd.value.constant.c_str(), &val);
+  (void)check;
+  FLATBUFFERS_ASSERT(check);
+  return val;
+}
+
 // Generate text for a scalar field.
-template<typename T> static bool GenField(const FieldDef &fd,
-                                          const Table *table, bool fixed,
-                                          const IDLOptions &opts,
-                                          int indent,
-                                          std::string *_text) {
-  return Print(fixed ?
-    reinterpret_cast<const Struct *>(table)->GetField<T>(fd.value.offset) :
-    table->GetField<T>(fd.value.offset,
-    IsFloat(fd.value.type.base_type) ?
-    static_cast<T>(strtod(fd.value.constant.c_str(), nullptr)) :
-    static_cast<T>(StringToInt(fd.value.constant.c_str()))),
-    fd.value.type, indent, nullptr, opts, _text);
+template<typename T>
+static bool GenField(const FieldDef &fd, const Table *table, bool fixed,
+                     const IDLOptions &opts, int indent, std::string *_text) {
+  return Print(
+      fixed ? reinterpret_cast<const Struct *>(table)->GetField<T>(
+                  fd.value.offset)
+            : table->GetField<T>(fd.value.offset, GetFieldDefault<T>(fd)),
+      fd.value.type, indent, nullptr, opts, _text);
 }
 
 static bool GenStruct(const StructDef &struct_def, const Table *table,
index e686f62..fff320a 100644 (file)
@@ -1,4 +1,4 @@
-/*
+/*
  * Copyright 2014 Google Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -119,57 +119,22 @@ CheckedError Parser::RecurseError() {
                NumToString(FLATBUFFERS_MAX_PARSING_DEPTH) + " reached");
 }
 
-inline std::string OutOfRangeErrorMsg(int64_t val, const std::string &op,
-                                      int64_t limit) {
-  const std::string cause = NumToString(val) + op + NumToString(limit);
-  return "constant does not fit (" + cause + ")";
+CheckedError Parser::InvalidNumber(const char *number, const std::string &msg) {
+  return Error("invalid number: \"" + std::string(number) + "\"" + msg);
 }
-
-// Ensure that integer values we parse fit inside the declared integer type.
-CheckedError Parser::CheckInRange(int64_t val, int64_t min, int64_t max) {
-  if (val < min)
-    return Error(OutOfRangeErrorMsg(val, " < ", min));
-  else if (val > max)
-    return Error(OutOfRangeErrorMsg(val, " > ", max));
-  else
-    return NoError();
-}
-
 // atot: templated version of atoi/atof: convert a string to an instance of T.
 template<typename T>
 inline CheckedError atot(const char *s, Parser &parser, T *val) {
-  int64_t i = StringToInt(s);
-  const int64_t min = flatbuffers::numeric_limits<T>::min();
-  const int64_t max = flatbuffers::numeric_limits<T>::max();
-  *val = (T)i;  // Assign this first to make ASAN happy.
-  return parser.CheckInRange(i, min, max);
-}
-template<>
-inline CheckedError atot<uint64_t>(const char *s, Parser &parser,
-                                   uint64_t *val) {
-  (void)parser;
-  *val = StringToUInt(s);
-  return NoError();
-}
-template<>
-inline CheckedError atot<bool>(const char *s, Parser &parser, bool *val) {
-  (void)parser;
-  *val = 0 != atoi(s);
-  return NoError();
-}
-template<>
-inline CheckedError atot<float>(const char *s, Parser &parser, float *val) {
-  (void)parser;
-  *val = static_cast<float>(strtod(s, nullptr));
-  return NoError();
-}
-template<>
-inline CheckedError atot<double>(const char *s, Parser &parser, double *val) {
-  (void)parser;
-  *val = strtod(s, nullptr);
-  return NoError();
-}
+  auto done = StringToNumber(s, val);
+  if (done) return NoError();
 
+  return parser.InvalidNumber(
+      s, (0 == *val)
+             ? ""
+             : (", constant does not fit [" +
+                NumToString(flatbuffers::numeric_limits<T>::lowest()) + "; " +
+                NumToString(flatbuffers::numeric_limits<T>::max()) + "]"));
+}
 template<>
 inline CheckedError atot<Offset<void>>(const char *s, Parser &parser,
                                        Offset<void> *val) {
@@ -239,8 +204,9 @@ std::string Parser::TokenToStringId(int t) const {
 
 // Parses exactly nibbles worth of hex digits into a number, or error.
 CheckedError Parser::ParseHexNum(int nibbles, uint64_t *val) {
+  FLATBUFFERS_ASSERT(nibbles > 0);
   for (int i = 0; i < nibbles; i++)
-    if (!isxdigit(static_cast<unsigned char>(cursor_[i])))
+    if (!is_xdigit(cursor_[i]))
       return Error("escape code must be followed by " + NumToString(nibbles) +
                    " hex digits");
   std::string target(cursor_, cursor_ + nibbles);
@@ -261,14 +227,15 @@ CheckedError Parser::SkipByteOrderMark() {
   return NoError();
 }
 
-bool IsIdentifierStart(char c) {
-  return isalpha(static_cast<unsigned char>(c)) || c == '_';
+static inline bool IsIdentifierStart(char c) {
+  return is_alpha(c) || (c == '_');
 }
 
 CheckedError Parser::Next() {
   doc_comment_.clear();
   bool seen_newline = cursor_ == source_;
   attribute_.clear();
+  attr_is_trivial_ascii_string_ = true;
   for (;;) {
     char c = *cursor_++;
     token_ = c;
@@ -294,10 +261,6 @@ CheckedError Parser::Next() {
       case ':':
       case ';':
       case '=': return NoError();
-      case '.':
-        if (!isdigit(static_cast<unsigned char>(*cursor_)))
-          return NoError();
-        return Error("floating point constant can\'t start with \".\"");
       case '\"':
       case '\'': {
         int unicode_high_surrogate = -1;
@@ -306,6 +269,7 @@ CheckedError Parser::Next() {
           if (*cursor_ < ' ' && static_cast<signed char>(*cursor_) >= 0)
             return Error("illegal character in string constant");
           if (*cursor_ == '\\') {
+            attr_is_trivial_ascii_string_ = false;  // has escape sequence
             cursor_++;
             if (unicode_high_surrogate != -1 && *cursor_ != 'u') {
               return Error(
@@ -393,6 +357,9 @@ CheckedError Parser::Next() {
               return Error(
                   "illegal Unicode sequence (unpaired high surrogate)");
             }
+            // reset if non-printable
+            attr_is_trivial_ascii_string_ &= check_in_range(*cursor_, ' ', '~');
+
             attribute_ += *cursor_++;
           }
         }
@@ -400,7 +367,8 @@ CheckedError Parser::Next() {
           return Error("illegal Unicode sequence (unpaired high surrogate)");
         }
         cursor_++;
-        if (!opts.allow_non_utf8 && !ValidateUTF8(attribute_)) {
+        if (!attr_is_trivial_ascii_string_ && !opts.allow_non_utf8 &&
+            !ValidateUTF8(attribute_)) {
           return Error("illegal UTF-8 sequence");
         }
         token_ = kTokenStringConstant;
@@ -430,55 +398,69 @@ CheckedError Parser::Next() {
         }
         // fall thru
       default:
-        if (IsIdentifierStart(c)) {
+        const auto has_sign = (c == '+') || (c == '-');
+        // '-'/'+' and following identifier - can be a predefined constant like:
+        // NAN, INF, PI, etc.
+        if (IsIdentifierStart(c) || (has_sign && IsIdentifierStart(*cursor_))) {
           // Collect all chars of an identifier:
           const char *start = cursor_ - 1;
-          while (isalnum(static_cast<unsigned char>(*cursor_)) || *cursor_ == '_')
-            cursor_++;
+          while (IsIdentifierStart(*cursor_) || is_digit(*cursor_)) cursor_++;
           attribute_.append(start, cursor_);
-          token_ = kTokenIdentifier;
+          token_ = has_sign ? kTokenStringConstant : kTokenIdentifier;
           return NoError();
-        } else if (isdigit(static_cast<unsigned char>(c)) || c == '-') {
-          const char *start = cursor_ - 1;
-          if (c == '-' && *cursor_ == '0' &&
-              (cursor_[1] == 'x' || cursor_[1] == 'X')) {
-            ++start;
-            ++cursor_;
-            attribute_.append(&c, &c + 1);
-            c = '0';
-          }
-          if (c == '0' && (*cursor_ == 'x' || *cursor_ == 'X')) {
-            cursor_++;
-            while (isxdigit(static_cast<unsigned char>(*cursor_))) cursor_++;
-            attribute_.append(start + 2, cursor_);
-            attribute_ = NumToString(static_cast<int64_t>(
-                StringToUInt(attribute_.c_str(), nullptr, 16)));
-            token_ = kTokenIntegerConstant;
-            return NoError();
+        }
+
+        auto dot_lvl = (c == '.') ? 0 : 1;  // dot_lvl==0 <=> exactly one '.' seen
+        if (!dot_lvl && !is_digit(*cursor_)) return NoError(); // enum?
+        // Parser accepts hexadecimal-floating-literal (see C++ 5.13.4).
+        if (is_digit(c) || has_sign || !dot_lvl) {
+          const auto start = cursor_ - 1;
+          auto start_digits = !is_digit(c) ? cursor_ : cursor_ - 1;
+          if (!is_digit(c) && is_digit(*cursor_)){
+            start_digits = cursor_; // see digit in cursor_ position
+            c = *cursor_++;
           }
-          while (isdigit(static_cast<unsigned char>(*cursor_))) cursor_++;
-          if (*cursor_ == '.' || *cursor_ == 'e' || *cursor_ == 'E') {
-            if (*cursor_ == '.') {
-              cursor_++;
-              while (isdigit(static_cast<unsigned char>(*cursor_))) cursor_++;
+          // hex-float can't begind with '.'
+          auto use_hex = dot_lvl && (c == '0') && is_alpha_char(*cursor_, 'X');
+          if (use_hex) start_digits = ++cursor_;  // '0x' is the prefix, skip it
+          // Read an integer number or mantisa of float-point number.
+          do {
+            if (use_hex) {
+              while (is_xdigit(*cursor_)) cursor_++;
+            } else {
+              while (is_digit(*cursor_)) cursor_++;
             }
-            // See if this float has a scientific notation suffix. Both JSON
-            // and C++ (through strtod() we use) have the same format:
-            if (*cursor_ == 'e' || *cursor_ == 'E') {
+          } while ((*cursor_ == '.') && (++cursor_) && (--dot_lvl >= 0));
+          // Exponent of float-point number.
+          if ((dot_lvl >= 0) && (cursor_ > start_digits)) {
+            // The exponent suffix of hexadecimal float number is mandatory.
+            if (use_hex && !dot_lvl) start_digits = cursor_;
+            if ((use_hex && is_alpha_char(*cursor_, 'P')) ||
+                is_alpha_char(*cursor_, 'E')) {
+              dot_lvl = 0;  // Emulate dot to signal about float-point number.
               cursor_++;
               if (*cursor_ == '+' || *cursor_ == '-') cursor_++;
-              while (isdigit(static_cast<unsigned char>(*cursor_))) cursor_++;
+              start_digits = cursor_;  // the exponent-part has to have digits
+              // Exponent is decimal integer number
+              while (is_digit(*cursor_)) cursor_++;
+              if (*cursor_ == '.') {
+                cursor_++;  // If see a dot treat it as part of invalid number.
+                dot_lvl = -1;  // Fall thru to Error().
+              }
             }
-            token_ = kTokenFloatConstant;
+          }
+          // Finalize.
+          if ((dot_lvl >= 0) && (cursor_ > start_digits)) {
+            attribute_.append(start, cursor_);
+            token_ = dot_lvl ? kTokenIntegerConstant : kTokenFloatConstant;
+            return NoError();
           } else {
-            token_ = kTokenIntegerConstant;
+            return Error("invalid number: " + std::string(start, cursor_));
           }
-          attribute_.append(start, cursor_);
-          return NoError();
         }
         std::string ch;
         ch = c;
-        if (c < ' ' || c > '~') ch = "code: " + NumToString(c);
+        if (false == check_in_range(c, ' ', '~')) ch = "code: " + NumToString(c);
         return Error("illegal character: " + ch);
     }
   }
@@ -674,7 +656,7 @@ CheckedError Parser::ParseField(StructDef &struct_def) {
         (struct_def.fixed && field->value.constant != "0"))
       return Error(
             "default values currently only supported for scalars in tables");
-    ECHECK(ParseSingleValue(&field->name, field->value));
+    ECHECK(ParseSingleValue(&field->name, field->value, true));
   }
   if (type.enum_def &&
       !type.enum_def->is_union &&
@@ -684,9 +666,20 @@ CheckedError Parser::ParseField(StructDef &struct_def) {
     return Error("default value of " + field->value.constant + " for field " +
                  name + " is not part of enum " + type.enum_def->name);
   }
+  // Append .0 if the value has not it (skip hex and scientific floats).
+  // This suffix needed for generated C++ code.
   if (IsFloat(type.base_type)) {
-    if (!strpbrk(field->value.constant.c_str(), ".eE"))
+    auto &text = field->value.constant;
+    FLATBUFFERS_ASSERT(false == text.empty());
+    auto s = text.c_str();
+    while(*s == ' ') s++;
+    if (*s == '-' || *s == '+') s++;
+    // 1) A float constants (nan, inf, pi, etc) is a kind of identifier.
+    // 2) A float number needn't ".0" at the end if it has exponent.
+    if ((false == IsIdentifierStart(*s)) &&
+        (std::string::npos == field->value.constant.find_first_of(".eEpP"))) {
       field->value.constant += ".0";
+    }
   }
 
   if (type.enum_def && IsScalar(type.base_type) && !struct_def.fixed &&
@@ -915,11 +908,13 @@ CheckedError Parser::ParseAnyValue(Value &val, FieldDef *field,
           (token_ == kTokenIdentifier || token_ == kTokenStringConstant)) {
         ECHECK(ParseHash(val, field));
       } else {
-        ECHECK(ParseSingleValue(field ? &field->name : nullptr, val));
+        ECHECK(ParseSingleValue(field ? &field->name : nullptr, val, false));
       }
       break;
     }
-    default: ECHECK(ParseSingleValue(field ? &field->name : nullptr, val)); break;
+    default:
+      ECHECK(ParseSingleValue(field ? &field->name : nullptr, val, false));
+      break;
   }
   return NoError();
 }
@@ -994,7 +989,8 @@ CheckedError Parser::ParseTable(const StructDef &struct_def, std::string *value,
             ECHECK(parser->SkipAnyJsonValue());
           }
         } else {
-          if (parser->IsIdent("null")) {
+          if (parser->IsIdent("null") &&
+              !IsScalar(field->value.type.base_type)) {
             ECHECK(parser->Next());  // Ignore this field.
           } else {
             Value val = field->value;
@@ -1252,7 +1248,7 @@ CheckedError Parser::ParseMetaData(SymbolTable<Value> *attributes) {
       attributes->Add(name, e);
       if (Is(':')) {
         NEXT();
-        ECHECK(ParseSingleValue(&name, *e));
+        ECHECK(ParseSingleValue(&name, *e, true));
       }
       if (Is(')')) {
         NEXT();
@@ -1264,23 +1260,40 @@ CheckedError Parser::ParseMetaData(SymbolTable<Value> *attributes) {
   return NoError();
 }
 
-CheckedError Parser::TryTypedValue(const std::string *name, int dtoken, bool check, Value &e,
-                                   BaseType req, bool *destmatch) {
+CheckedError Parser::TryTypedValue(const std::string *name, int dtoken,
+                                   bool check, Value &e, BaseType req,
+                                   bool *destmatch) {
   bool match = dtoken == token_;
   if (match) {
+    FLATBUFFERS_ASSERT(*destmatch == false);
     *destmatch = true;
     e.constant = attribute_;
+    // Check token match
     if (!check) {
       if (e.type.base_type == BASE_TYPE_NONE) {
         e.type.base_type = req;
       } else {
-        return Error(std::string("type mismatch: expecting: ") +
-                     kTypeNames[e.type.base_type] +
-                     ", found: " + kTypeNames[req] +
-                     ", name: " + (name ? *name : "") +
-                     ", value: " + e.constant);
+        return Error(
+            std::string("type mismatch: expecting: ") +
+            kTypeNames[e.type.base_type] + ", found: " + kTypeNames[req] +
+            ", name: " + (name ? *name : "") + ", value: " + e.constant);
+      }
+    }
+    // The exponent suffix of hexadecimal float-point number is mandatory.
+    // A hex-integer constant is forbidden as an initializer of float number.
+    if ((kTokenFloatConstant != dtoken) && IsFloat(e.type.base_type)) {
+      const auto &s = e.constant;
+      const auto k = s.find_first_of("0123456789.");
+      if ((std::string::npos != k) && (s.length() > (k + 1)) &&
+          (s.at(k) == '0' && is_alpha_char(s.at(k + 1), 'X')) &&
+          (std::string::npos == s.find_first_of("pP", k + 2))) {
+        return Error(
+            "invalid number, the exponent suffix of hexadecimal "
+            "floating-point literals is mandatory: \"" +
+            s + "\"");
       }
     }
+
     NEXT();
   }
   return NoError();
@@ -1375,20 +1388,29 @@ CheckedError Parser::TokenError() {
   return Error("cannot parse value starting with: " + TokenToStringId(token_));
 }
 
-CheckedError Parser::ParseSingleValue(const std::string *name, Value &e) {
+CheckedError Parser::ParseSingleValue(const std::string *name, Value &e,
+                                      bool check_now) {
   // First see if this could be a conversion function:
   if (token_ == kTokenIdentifier && *cursor_ == '(') {
-    auto functionname = attribute_;
+    // todo: Extract processing of conversion functions to ParseFunction.
+    const auto functionname = attribute_;
+    if (!IsFloat(e.type.base_type)) {
+      return Error(functionname + ": type of argument mismatch, expecting: " +
+                   kTypeNames[BASE_TYPE_DOUBLE] +
+                   ", found: " + kTypeNames[e.type.base_type] +
+                   ", name: " + (name ? *name : "") + ", value: " + e.constant);
+    }
     NEXT();
     EXPECT('(');
-    ECHECK(ParseSingleValue(name, e));
+    ECHECK(Recurse([&]() { return ParseSingleValue(name, e, false); }));
     EXPECT(')');
+    // calculate with double precision
+    double x, y = 0.0;
+    ECHECK(atot(e.constant.c_str(), *this, &x));
+    auto func_match = false;
     // clang-format off
     #define FLATBUFFERS_FN_DOUBLE(name, op) \
-      if (functionname == name) { \
-        auto x = strtod(e.constant.c_str(), nullptr); \
-        e.constant = NumToString(op); \
-      }
+      if (!func_match && functionname == name) { y = op; func_match = true; }
     FLATBUFFERS_FN_DOUBLE("deg", x / kPi * 180);
     FLATBUFFERS_FN_DOUBLE("rad", x * kPi / 180);
     FLATBUFFERS_FN_DOUBLE("sin", sin(x));
@@ -1400,47 +1422,108 @@ CheckedError Parser::ParseSingleValue(const std::string *name, Value &e) {
     // TODO(wvo): add more useful conversion functions here.
     #undef FLATBUFFERS_FN_DOUBLE
     // clang-format on
-    // Then check if this could be a string/identifier enum value:
-  } else if (e.type.base_type != BASE_TYPE_STRING &&
-             e.type.base_type != BASE_TYPE_BOOL &&
-             e.type.base_type != BASE_TYPE_NONE &&
-             (token_ == kTokenIdentifier || token_ == kTokenStringConstant)) {
-    if (IsIdentifierStart(attribute_[0])) {  // Enum value.
+    if (true != func_match) {
+      return Error(std::string("Unknown conversion function: ") + functionname +
+                   ", field name: " + (name ? *name : "") +
+                   ", value: " + e.constant);
+    }
+    e.constant = NumToString(y);
+    return NoError();
+  }
+
+  auto match = false;
+  // clang-format off
+  #define TRY_ECHECK(force, dtoken, check, req)    \
+    if (!match && ((check) || IsConstTrue(force))) \
+    ECHECK(TryTypedValue(name, dtoken, check, e, req, &match))
+  // clang-format on
+
+  if (token_ == kTokenStringConstant || token_ == kTokenIdentifier) {
+    const auto kTokenStringOrIdent = token_;
+    // The string type is a most probable type, check it first.
+    TRY_ECHECK(false, kTokenStringConstant,
+               e.type.base_type == BASE_TYPE_STRING, BASE_TYPE_STRING);
+
+    // avoid escaped and non-ascii in the string
+    if ((token_ == kTokenStringConstant) && IsScalar(e.type.base_type) &&
+        !attr_is_trivial_ascii_string_) {
+      return Error(
+          std::string("type mismatch or invalid value, an initializer of "
+                      "non-string field must be trivial ASCII string: type: ") +
+          kTypeNames[e.type.base_type] + ", name: " + (name ? *name : "") +
+          ", value: " + attribute_);
+    }
+
+    // A boolean as true/false. Boolean as Integer check below.
+    if (!match && IsBool(e.type.base_type)) {
+      auto is_true = attribute_ == "true";
+      if (is_true || attribute_ == "false") {
+        attribute_ = is_true ? "1" : "0";
+        // accepts both kTokenStringConstant and kTokenIdentifier
+        TRY_ECHECK(false, kTokenStringOrIdent, IsBool(e.type.base_type),
+                   BASE_TYPE_BOOL);
+      }
+    }
+    // Check if this could be a string/identifier enum value.
+    // Enum can have only true integer base type.
+    if (!match && IsInteger(e.type.base_type) && !IsBool(e.type.base_type) &&
+        IsIdentifierStart(*attribute_.c_str())) {
       int64_t val;
       ECHECK(ParseEnumFromString(e.type, &val));
       e.constant = NumToString(val);
       NEXT();
-    } else {  // Numeric constant in string.
-      if (IsInteger(e.type.base_type)) {
-        char *end;
-        e.constant = NumToString(StringToInt(attribute_.c_str(), &end));
-        if (*end) return Error("invalid integer: " + attribute_);
-      } else if (IsFloat(e.type.base_type)) {
-        char *end;
-        e.constant = NumToString(strtod(attribute_.c_str(), &end));
-        if (*end) return Error("invalid float: " + attribute_);
-      } else {
-        FLATBUFFERS_ASSERT(0);  // Shouldn't happen, we covered all types.
-        e.constant = "0";
-      }
-      NEXT();
+      match = true;
+    }
+    // float/integer number in string
+    if ((token_ == kTokenStringConstant) && IsScalar(e.type.base_type)) {
+      // remove trailing whitespaces from attribute_
+      auto last = attribute_.find_last_not_of(' ');
+      if (std::string::npos != last)  // has non-whitespace
+        attribute_.resize(last + 1);
     }
+    // Float numbers or nan, inf, pi, etc.
+    TRY_ECHECK(false, kTokenStringOrIdent, IsFloat(e.type.base_type),
+               BASE_TYPE_FLOAT);
+    // An integer constant in string.
+    TRY_ECHECK(false, kTokenStringOrIdent, IsInteger(e.type.base_type),
+               BASE_TYPE_INT);
+    // Unknown tokens will be interpreted as string type.
+    TRY_ECHECK(true, kTokenStringConstant, e.type.base_type == BASE_TYPE_STRING,
+               BASE_TYPE_STRING);
   } else {
-    bool match = false;
-    ECHECK(TryTypedValue(name, kTokenIntegerConstant, IsScalar(e.type.base_type), e,
-                         BASE_TYPE_INT, &match));
-    ECHECK(TryTypedValue(name, kTokenFloatConstant, IsFloat(e.type.base_type), e,
-                         BASE_TYPE_FLOAT, &match));
-    ECHECK(TryTypedValue(name, kTokenStringConstant,
-                         e.type.base_type == BASE_TYPE_STRING, e,
-                         BASE_TYPE_STRING, &match));
-    auto istrue = IsIdent("true");
-    if (istrue || IsIdent("false")) {
-      attribute_ = NumToString(istrue);
-      ECHECK(TryTypedValue(name, kTokenIdentifier, IsBool(e.type.base_type), e,
-                           BASE_TYPE_BOOL, &match));
-    }
-    if (!match) return TokenError();
+    // Try a float number.
+    TRY_ECHECK(false, kTokenFloatConstant, IsFloat(e.type.base_type),
+               BASE_TYPE_FLOAT);
+    // Integer token can init any scalar (integer of float).
+    TRY_ECHECK(true, kTokenIntegerConstant, IsScalar(e.type.base_type),
+               BASE_TYPE_INT);
+  }
+  #undef TRY_ECHECK
+
+  if (!match) return TokenError();
+
+  // The check_now flag must be true when parse a fbs-schema.
+  // This flag forces to check default scalar values or metadata of field.
+  // For JSON parser the flag should be false.
+  // If it is set for JSON each value will be checked twice (see ParseTable).
+  if (check_now && IsScalar(e.type.base_type)) {
+    // "re-pack" an integer scalar to remove any ambiguities like leading zeros
+    // which can be treated as octal-literal (idl_gen_cpp/GenDefaultConstant).
+    const auto repack = IsInteger(e.type.base_type);
+    switch (e.type.base_type) {
+    // clang-format off
+    #define FLATBUFFERS_TD(ENUM, IDLTYPE, \
+            CTYPE, JTYPE, GTYPE, NTYPE, PTYPE, RTYPE) \
+            case BASE_TYPE_ ## ENUM: {\
+                CTYPE val; \
+                ECHECK(atot(e.constant.c_str(), *this, &val)); \
+                if(repack) e.constant = NumToString(val); \
+              break; }
+    FLATBUFFERS_GEN_TYPES_SCALAR(FLATBUFFERS_TD);
+    #undef FLATBUFFERS_TD
+    default: break;
+    // clang-format on
+    }
   }
   return NoError();
 }
@@ -1565,7 +1648,7 @@ CheckedError Parser::ParseEnum(bool is_union, EnumDef **dest) {
       }
       if (Is('=')) {
         NEXT();
-        ev.value = StringToInt(attribute_.c_str());
+        ECHECK(atot(attribute_.c_str(), *this, &ev.value));
         EXPECT(kTokenIntegerConstant);
         if (!opts.proto_mode && prevsize &&
             enum_def->vals.vec[prevsize - 1]->value >= ev.value)
@@ -2451,6 +2534,9 @@ CheckedError Parser::DoParse(const char *source, const char **include_paths,
                                                  ? file_identifier_.c_str()
                                                  : nullptr);
       }
+      // Check that JSON file doesn't contain more objects or IDL directives.
+      // Comments after JSON are allowed.
+      EXPECT(kTokenEof);
     } else if (IsIdent("enum")) {
       ECHECK(ParseEnum(false, nullptr));
     } else if (IsIdent("union")) {
@@ -2606,7 +2692,9 @@ Offset<reflection::Field> FieldDef::Serialize(FlatBufferBuilder *builder,
   return reflection::CreateField(
       *builder, builder->CreateString(name), value.type.Serialize(builder), id,
       value.offset,
+      // Is uint64>max(int64) tested?
       IsInteger(value.type.base_type) ? StringToInt(value.constant.c_str()) : 0,
+      // result may be platform-dependent if underlying is float (not double)
       IsFloat(value.type.base_type) ? strtod(value.constant.c_str(), nullptr)
                                     : 0.0,
       deprecated, required, key, SerializeAttributes(builder, parser),
index a025476..e860e09 100644 (file)
@@ -1,4 +1,4 @@
-/*
+/*
  * Copyright 2014 Google Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
+#include <cmath>
 #include "flatbuffers/flatbuffers.h"
 #include "flatbuffers/idl.h"
 #include "flatbuffers/minireflect.h"
 #include "union_vector/union_vector_generated.h"
 #include "test_assert.h"
 
-// clang-format off
-#ifndef FLATBUFFERS_CPP98_STL
-  #include <random>
-#endif
-
 #include "flatbuffers/flexbuffers.h"
 
 using namespace MyGame::Example;
@@ -1200,7 +1195,6 @@ void TestError_(const char *src, const char *error_substr, const char *file,
 void ErrorTest() {
   // In order they appear in idl_parser.cpp
   TestError("table X { Y:byte; } root_type X; { Y: 999 }", "does not fit");
-  TestError(".0", "floating point");
   TestError("\"\0", "illegal");
   TestError("\"\\q", "escape code");
   TestError("table ///", "documentation");
@@ -1238,25 +1232,42 @@ void ErrorTest() {
   TestError("table X { Y:int; } table X {", "datatype already");
   TestError("struct X (force_align: 7) { Y:int; }", "force_align");
   TestError("{}", "no root");
-  TestError("table X { Y:byte; } root_type X; { Y:1 } { Y:1 }", "one json");
+  TestError("table X { Y:byte; } root_type X; { Y:1 } { Y:1 }", "end of file");
+  TestError("table X { Y:byte; } root_type X; { Y:1 } table Y{ Z:int }",
+            "end of file");
   TestError("root_type X;", "unknown root");
   TestError("struct X { Y:int; } root_type X;", "a table");
   TestError("union X { Y }", "referenced");
   TestError("union Z { X } struct X { Y:int; }", "only tables");
   TestError("table X { Y:[int]; YLength:int; }", "clash");
   TestError("table X { Y:byte; } root_type X; { Y:1, Y:2 }", "more than once");
+  // float to integer conversion is forbidden
+  TestError("table X { Y:int; } root_type X; { Y:1.0 }", "float");
+  TestError("table X { Y:bool; } root_type X; { Y:1.0 }", "float");
 }
 
 template<typename T> T TestValue(const char *json, const char *type_name) {
   flatbuffers::Parser parser;
-
+  parser.builder_.ForceDefaults(true);  // return defaults
+  auto check_default = json ? false : true;
+  if (check_default) { parser.opts.output_default_scalars_in_json = true; }
   // Simple schema.
-  TEST_EQ(parser.Parse(std::string("table X { Y:" + std::string(type_name) +
-                                   "; } root_type X;")
-                           .c_str()),
+  std::string schema =
+      "table X { Y:" + std::string(type_name) + "; } root_type X;";
+  TEST_EQ(parser.Parse(schema.c_str()), true);
+
+  auto done = parser.Parse(check_default ? "{}" : json);
+  TEST_EQ_STR(parser.error_.c_str(), "");
+  TEST_EQ(done, true);
+
+  // Check with print.
+  std::string print_back;
+  parser.opts.indent_step = -1;
+  TEST_EQ(GenerateText(parser, parser.builder_.GetBufferPointer(), &print_back),
           true);
+  // restore value from its default
+  if (check_default) { TEST_EQ(parser.Parse(print_back.c_str()), true); }
 
-  TEST_EQ(parser.Parse(json), true);
   auto root = flatbuffers::GetRoot<flatbuffers::Table>(
       parser.builder_.GetBufferPointer());
   return root->GetField<T>(flatbuffers::FieldIndexToOffset(0), 0);
@@ -1270,17 +1281,44 @@ void ValueTest() {
   TEST_EQ(FloatCompare(TestValue<float>("{ Y:0.0314159e+2 }", "float"),
                        (float)3.14159),
           true);
+  // number in string
+  TEST_EQ(FloatCompare(TestValue<float>("{ Y:\"0.0314159e+2\" }", "float"),
+    (float)3.14159),
+    true);
 
   // Test conversion functions.
   TEST_EQ(FloatCompare(TestValue<float>("{ Y:cos(rad(180)) }", "float"), -1),
           true);
 
+  // int embedded to string
+  TEST_EQ(TestValue<int>("{ Y:\"-876\" }", "int=-123"), -876);
+  TEST_EQ(TestValue<int>("{ Y:\"876\" }", "int=-123"), 876);
+
   // Test negative hex constant.
-  TEST_EQ(TestValue<int>("{ Y:-0x80 }", "int"), -128);
+  TEST_EQ(TestValue<int>("{ Y:-0x8ea0 }", "int=-0x8ea0"), -36512);
+  TEST_EQ(TestValue<int>(nullptr, "int=-0x8ea0"), -36512);
+
+  // positive hex constant
+  TEST_EQ(TestValue<int>("{ Y:0x1abcdef }", "int=0x1"), 0x1abcdef);
+  // with optional '+' sign
+  TEST_EQ(TestValue<int>("{ Y:+0x1abcdef }", "int=+0x1"), 0x1abcdef);
+  // hex in string
+  TEST_EQ(TestValue<int>("{ Y:\"0x1abcdef\" }", "int=+0x1"), 0x1abcdef);
 
   // Make sure we do unsigned 64bit correctly.
   TEST_EQ(TestValue<uint64_t>("{ Y:12335089644688340133 }", "ulong"),
           12335089644688340133ULL);
+
+  // bool in string
+  TEST_EQ(TestValue<bool>("{ Y:\"false\" }", "bool=true"), false);
+  TEST_EQ(TestValue<bool>("{ Y:\"true\" }", "bool=\"true\""), true);
+  TEST_EQ(TestValue<bool>("{ Y:'false' }", "bool=true"), false);
+  TEST_EQ(TestValue<bool>("{ Y:'true' }", "bool=\"true\""), true);
+
+  // check comments before and after json object
+  TEST_EQ(TestValue<int>("/*before*/ { Y:1 } /*after*/", "int"), 1);
+  TEST_EQ(TestValue<int>("//before \n { Y:1 } //after", "int"), 1);
+
 }
 
 void NestedListTest() {
@@ -1337,6 +1375,47 @@ void IntegerOutOfRangeTest() {
             "constant does not fit");
   TestError("table T { F:uint; } root_type T; { F:-1 }",
             "constant does not fit");
+  // Check fixed width aliases
+  TestError("table X { Y:uint8; } root_type X; { Y: -1 }", "does not fit");
+  TestError("table X { Y:uint8; } root_type X; { Y: 256 }", "does not fit");
+  TestError("table X { Y:uint16; } root_type X; { Y: -1 }", "does not fit");
+  TestError("table X { Y:uint16; } root_type X; { Y: 65536 }", "does not fit");
+  TestError("table X { Y:uint32; } root_type X; { Y: -1 }", "");
+  TestError("table X { Y:uint32; } root_type X; { Y: 4294967296 }",
+            "does not fit");
+  TestError("table X { Y:uint64; } root_type X; { Y: -1 }", "");
+  TestError("table X { Y:uint64; } root_type X; { Y: -9223372036854775809 }",
+            "does not fit");
+  TestError("table X { Y:uint64; } root_type X; { Y: 18446744073709551616 }",
+            "does not fit");
+
+  TestError("table X { Y:int8; } root_type X; { Y: -129 }", "does not fit");
+  TestError("table X { Y:int8; } root_type X; { Y: 128 }", "does not fit");
+  TestError("table X { Y:int16; } root_type X; { Y: -32769 }", "does not fit");
+  TestError("table X { Y:int16; } root_type X; { Y: 32768 }", "does not fit");
+  TestError("table X { Y:int32; } root_type X; { Y: -2147483649 }", "");
+  TestError("table X { Y:int32; } root_type X; { Y: 2147483648 }",
+            "does not fit");
+  TestError("table X { Y:int64; } root_type X; { Y: -9223372036854775809 }",
+            "does not fit");
+  TestError("table X { Y:int64; } root_type X; { Y: 9223372036854775808 }",
+            "does not fit");
+  // check out-of-int64 as int8
+  TestError("table X { Y:int8; } root_type X; { Y: -9223372036854775809 }",
+            "does not fit");
+  TestError("table X { Y:int8; } root_type X; { Y: 9223372036854775808 }",
+            "does not fit");
+
+  // Check default values
+  TestError("table X { Y:int64=-9223372036854775809; } root_type X; {}",
+            "does not fit");
+  TestError("table X { Y:int64= 9223372036854775808; } root_type X; {}",
+            "does not fit");
+  TestError("table X { Y:uint64; } root_type X; { Y: -1 }", "");
+  TestError("table X { Y:uint64=-9223372036854775809; } root_type X; {}",
+            "does not fit");
+  TestError("table X { Y:uint64= 18446744073709551616; } root_type X; {}",
+            "does not fit");
 }
 
 void IntegerBoundaryTest() {
@@ -1359,6 +1438,207 @@ void IntegerBoundaryTest() {
   TEST_EQ(TestValue<uint64_t>("{ Y:18446744073709551615 }", "ulong"),
           18446744073709551615U);
   TEST_EQ(TestValue<uint64_t>("{ Y:0 }", "ulong"), 0);
+  TEST_EQ(TestValue<uint64_t>("{ Y: 18446744073709551615 }", "uint64"),
+          18446744073709551615ULL);
+  // check that the default works
+  TEST_EQ(TestValue<uint64_t>(nullptr, "uint64 = 18446744073709551615"),
+          18446744073709551615ULL);
+}
+
+void ValidFloatTest() {
+  const auto infinityf = flatbuffers::numeric_limits<float>::infinity();
+  const auto infinityd = flatbuffers::numeric_limits<double>::infinity();
+  // check rounding to infinity
+  TEST_EQ(TestValue<float>("{ Y:+3.4029e+38 }", "float"), +infinityf);
+  TEST_EQ(TestValue<float>("{ Y:-3.4029e+38 }", "float"), -infinityf);
+  TEST_EQ(TestValue<double>("{ Y:+1.7977e+308 }", "double"), +infinityd);
+  TEST_EQ(TestValue<double>("{ Y:-1.7977e+308 }", "double"), -infinityd);
+
+  TEST_EQ(FloatCompare(TestValue<float>("{ Y:0.0314159e+2 }", "float"),
+                       (float)3.14159),
+          true);
+  // float in string
+  TEST_EQ(FloatCompare(TestValue<float>("{ Y:\" 0.0314159e+2  \" }", "float"),
+                       (float)3.14159),
+          true);
+
+  TEST_EQ(TestValue<float>("{ Y:1 }", "float"), 1.0f);
+  TEST_EQ(TestValue<float>("{ Y:1.0 }", "float"), 1.0f);
+  TEST_EQ(TestValue<float>("{ Y:1. }", "float"), 1.0f);
+  TEST_EQ(TestValue<float>("{ Y:+1. }", "float"), 1.0f);
+  TEST_EQ(TestValue<float>("{ Y:-1. }", "float"), -1.0f);
+  TEST_EQ(TestValue<float>("{ Y:1.e0 }", "float"), 1.0f);
+  TEST_EQ(TestValue<float>("{ Y:1.e+0 }", "float"), 1.0f);
+  TEST_EQ(TestValue<float>("{ Y:1.e-0 }", "float"), 1.0f);
+  TEST_EQ(TestValue<float>("{ Y:0.125 }", "float"), 0.125f);
+  TEST_EQ(TestValue<float>("{ Y:.125 }", "float"), 0.125f);
+  TEST_EQ(TestValue<float>("{ Y:-.125 }", "float"), -0.125f);
+  TEST_EQ(TestValue<float>("{ Y:+.125 }", "float"), +0.125f);
+  TEST_EQ(TestValue<float>("{ Y:5 }", "float"), 5.0f);
+  TEST_EQ(TestValue<float>("{ Y:\"5\" }", "float"), 5.0f);
+
+  #if defined(FLATBUFFERS_HAS_NEW_STRTOD)
+  // Old MSVC versions may have problem with this check.
+  // https://www.exploringbinary.com/visual-c-plus-plus-strtod-still-broken/
+  TEST_EQ(TestValue<double>("{ Y:6.9294956446009195e15 }", "double"),
+    6929495644600920);
+  // check nan's
+  TEST_EQ(std::isnan(TestValue<double>("{ Y:nan }", "double")), true);
+  TEST_EQ(std::isnan(TestValue<float>("{ Y:nan }", "float")), true);
+  TEST_EQ(std::isnan(TestValue<float>("{ Y:\"nan\" }", "float")), true);
+  TEST_EQ(std::isnan(TestValue<float>("{ Y:+nan }", "float")), true);
+  TEST_EQ(std::isnan(TestValue<float>("{ Y:-nan }", "float")), true);
+  TEST_EQ(std::isnan(TestValue<float>(nullptr, "float=nan")), true);
+  TEST_EQ(std::isnan(TestValue<float>(nullptr, "float=-nan")), true);
+  // check inf
+  TEST_EQ(TestValue<float>("{ Y:inf }", "float"), infinityf);
+  TEST_EQ(TestValue<float>("{ Y:\"inf\" }", "float"), infinityf);
+  TEST_EQ(TestValue<float>("{ Y:+inf }", "float"), infinityf);
+  TEST_EQ(TestValue<float>("{ Y:-inf }", "float"), -infinityf);
+  TEST_EQ(TestValue<float>(nullptr, "float=inf"), infinityf);
+  TEST_EQ(TestValue<float>(nullptr, "float=-inf"), -infinityf);
+  TestValue<double>(
+      "{ Y : [0.2, .2, 1.0, -1.0, -2., 2., 1e0, -1e0, 1.0e0, -1.0e0, -3.e2, "
+      "3.0e2] }",
+      "[double]");
+  TestValue<float>(
+      "{ Y : [0.2, .2, 1.0, -1.0, -2., 2., 1e0, -1e0, 1.0e0, -1.0e0, -3.e2, "
+      "3.0e2] }",
+      "[float]");
+
+  // Test binary format of float point.
+  // https://en.cppreference.com/w/cpp/language/floating_literal
+  // 0x11.12p-1 = (1*16^1 + 2*16^0 + 3*16^-1 + 4*16^-2) * 2^-1 =
+  TEST_EQ(TestValue<double>("{ Y:0x12.34p-1 }", "double"), 9.1015625);
+  // hex fraction 1.2 (decimal 1.125) scaled by 2^3, that is 9.0
+  TEST_EQ(TestValue<float>("{ Y:-0x0.2p0 }", "float"), -0.125f);
+  TEST_EQ(TestValue<float>("{ Y:-0x.2p1 }", "float"), -0.25f);
+  TEST_EQ(TestValue<float>("{ Y:0x1.2p3 }", "float"), 9.0f);
+  TEST_EQ(TestValue<float>("{ Y:0x10.1p0 }", "float"), 16.0625f);
+  TEST_EQ(TestValue<double>("{ Y:0x1.2p3 }", "double"), 9.0);
+  TEST_EQ(TestValue<double>("{ Y:0x10.1p0 }", "double"), 16.0625);
+  TEST_EQ(TestValue<double>("{ Y:0xC.68p+2 }", "double"), 49.625);
+  TestValue<double>("{ Y : [0x20.4ep1, +0x20.4ep1, -0x20.4ep1] }", "[double]");
+  TestValue<float>("{ Y : [0x20.4ep1, +0x20.4ep1, -0x20.4ep1] }", "[float]");
+
+#else   // FLATBUFFERS_HAS_NEW_STRTOD
+  TEST_OUTPUT_LINE("FLATBUFFERS_HAS_NEW_STRTOD tests skipped");
+#endif  // FLATBUFFERS_HAS_NEW_STRTOD
+}
+
+void InvalidFloatTest() {
+  auto invalid_msg = "invalid number";
+  auto comma_msg = "expecting: ,";
+  TestError("table T { F:float; } root_type T; { F:1,0 }", "");
+  TestError("table T { F:float; } root_type T; { F:. }", "");
+  TestError("table T { F:float; } root_type T; { F:- }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:+ }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:-. }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:+. }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:.e }", "");
+  TestError("table T { F:float; } root_type T; { F:-e }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:+e }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:-.e }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:+.e }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:-e1 }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:+e1 }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:1.0e+ }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:1.0e- }", invalid_msg);
+  // exponent pP is mandatory for hex-float
+  TestError("table T { F:float; } root_type T; { F:0x0 }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:-0x. }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:0x. }", invalid_msg);
+  // eE not exponent in hex-float!
+  TestError("table T { F:float; } root_type T; { F:0x0.0e+ }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:0x0.0e- }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:0x0.0p }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:0x0.0p+ }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:0x0.0p- }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:0x0.0pa1 }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:0x0.0e+ }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:0x0.0e- }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:0x0.0e+0 }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:0x0.0e-0 }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:0x0.0ep+ }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:0x0.0ep- }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:1.2.3 }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:1.2.e3 }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:1.2e.3 }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:1.2e0.3 }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:1.2e3. }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:1.2e3.0 }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:+-1.0 }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:1.0e+-1 }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:\"1.0e+-1\" }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:1.e0e }", comma_msg);
+  TestError("table T { F:float; } root_type T; { F:0x1.p0e }", comma_msg);
+  TestError("table T { F:float; } root_type T; { F:\" 0x10 \" }", invalid_msg);
+  // floats in string
+  TestError("table T { F:float; } root_type T; { F:\"1,2.\" }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:\"1.2e3.\" }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:\"0x1.p0e\" }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:\"0x1.0\" }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:\" 0x1.0\" }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:\"+ 0\" }", invalid_msg);
+  // disable escapes for "number-in-string"
+  TestError("table T { F:float; } root_type T; { F:\"\\f1.2e3.\" }", "invalid");
+  TestError("table T { F:float; } root_type T; { F:\"\\t1.2e3.\" }", "invalid");
+  TestError("table T { F:float; } root_type T; { F:\"\\n1.2e3.\" }", "invalid");
+  TestError("table T { F:float; } root_type T; { F:\"\\r1.2e3.\" }", "invalid");
+  TestError("table T { F:float; } root_type T; { F:\"4\\x005\" }", "invalid");
+  TestError("table T { F:float; } root_type T; { F:\"\'12\'\" }", invalid_msg);
+  // null is not a number constant!
+  TestError("table T { F:float; } root_type T; { F:\"null\" }", invalid_msg);
+  TestError("table T { F:float; } root_type T; { F:null }", invalid_msg);
+}
+
+template<typename T>
+void NumericUtilsTestInteger(const char *lower, const char *upper) {
+  T x;
+  TEST_EQ(flatbuffers::StringToNumber("1q", &x), false);
+  TEST_EQ(x, 0);
+  TEST_EQ(flatbuffers::StringToNumber(upper, &x), false);
+  TEST_EQ(x, flatbuffers::numeric_limits<T>::max());
+  TEST_EQ(flatbuffers::StringToNumber(lower, &x), false);
+  auto expval = flatbuffers::is_unsigned<T>::value
+                    ? flatbuffers::numeric_limits<T>::max()
+                    : flatbuffers::numeric_limits<T>::lowest();
+  TEST_EQ(x, expval);
+}
+
+template<typename T>
+void NumericUtilsTestFloat(const char *lower, const char *upper) {
+  T f;
+  TEST_EQ(flatbuffers::StringToNumber("1q", &f), false);
+  TEST_EQ(f, 0);
+  TEST_EQ(flatbuffers::StringToNumber(upper, &f), true);
+  TEST_EQ(f, +flatbuffers::numeric_limits<T>::infinity());
+  TEST_EQ(flatbuffers::StringToNumber(lower, &f), true);
+  TEST_EQ(f, -flatbuffers::numeric_limits<T>::infinity());
+}
+
+void NumericUtilsTest() {
+  NumericUtilsTestInteger<uint64_t>("-1", "18446744073709551616");
+  NumericUtilsTestInteger<uint8_t>("-1", "256");
+  NumericUtilsTestInteger<int64_t>("-9223372036854775809",
+                                   "9223372036854775808");
+  NumericUtilsTestInteger<int8_t>("-129", "128");
+  NumericUtilsTestFloat<float>("-3.4029e+38", "+3.4029e+38");
+  NumericUtilsTestFloat<float>("-1.7977e+308", "+1.7977e+308");
+}
+
+void IsAsciiUtilsTest() {
+  char c = -128;
+  for (int cnt = 0; cnt < 256; cnt++) {
+    auto alpha = (('a' <= c) && (c <= 'z')) || (('A' <= c) && (c <= 'Z'));
+    auto dec = (('0' <= c) && (c <= '9'));
+    auto hex = (('a' <= c) && (c <= 'f')) || (('A' <= c) && (c <= 'F'));
+    TEST_EQ(flatbuffers::is_alpha(c), alpha);
+    TEST_EQ(flatbuffers::is_alnum(c), alpha || dec);
+    TEST_EQ(flatbuffers::is_digit(c), dec);
+    TEST_EQ(flatbuffers::is_xdigit(c), dec || hex);
+    c += 1;
+  }
 }
 
 void UnicodeTest() {
@@ -1572,6 +1852,15 @@ void InvalidUTF8Test() {
       // U+10400 "encoded" as U+D801 U+DC00
       "{ F:\"\xED\xA0\x81\xED\xB0\x80\"}",
       "illegal UTF-8 sequence");
+
+  // Check independence of identifier from locale.
+  std::string locale_ident;
+  locale_ident += "table T { F";
+  locale_ident += static_cast<char>(-32); // unsigned 0xE0
+  locale_ident += " :string; }";
+  locale_ident += "root_type T;";
+  locale_ident += "{}";
+  TestError(locale_ident.c_str(), "");
 }
 
 void UnknownFieldsTest() {
@@ -2100,13 +2389,14 @@ int FlatBufferTests() {
   ParseProtoBufAsciiTest();
   TypeAliasesTest();
   EndianSwapTest();
-
   JsonDefaultTest();
-
   FlexBuffersTest();
   UninitializedVectorTest();
   EqualOperatorTest();
-
+  NumericUtilsTest();
+  IsAsciiUtilsTest();
+  ValidFloatTest();
+  InvalidFloatTest();
   return 0;
 }