| /* |
| __ _____ _____ _____ |
| __| | __| | | | JSON for Modern C++ |
| | | |__ | | | | | | version 2.1.1 |
| |_____|_____|_____|_|___| https://github.com/nlohmann/json |
| |
| Licensed under the MIT License <http://opensource.org/licenses/MIT>. |
| Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>. |
| |
| Permission is hereby granted, free of charge, to any person obtaining a copy |
| of this software and associated documentation files (the "Software"), to deal |
| in the Software without restriction, including without limitation the rights |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| copies of the Software, and to permit persons to whom the Software is |
| furnished to do so, subject to the following conditions: |
| |
| The above copyright notice and this permission notice shall be included in all |
| copies or substantial portions of the Software. |
| |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| SOFTWARE. |
| */ |
| |
| #ifndef NLOHMANN_JSON_HPP |
| #define NLOHMANN_JSON_HPP |
| |
| #include <algorithm> // all_of, copy, fill, find, for_each, generate_n, none_of, remove, reverse, transform |
| #include <array> // array |
| #include <cassert> // assert |
| #include <ciso646> // and, not, or |
| #include <clocale> // lconv, localeconv |
| #include <cmath> // isfinite, labs, ldexp, signbit |
| #include <cstddef> // nullptr_t, ptrdiff_t, size_t |
| #include <cstdint> // int64_t, uint64_t |
| #include <cstdlib> // abort, strtod, strtof, strtold, strtoul, strtoll, strtoull |
| #include <cstring> // memcpy, strlen |
| #include <forward_list> // forward_list |
| #include <functional> // function, hash, less |
| #include <initializer_list> // initializer_list |
| #include <iomanip> // hex |
| #include <iosfwd> // istream, ostream |
| #include <iterator> // advance, begin, back_inserter, bidirectional_iterator_tag, distance, end, inserter, iterator, iterator_traits, next, random_access_iterator_tag, reverse_iterator |
| #include <limits> // numeric_limits |
| #include <locale> // locale |
| #include <map> // map |
| #include <memory> // addressof, allocator, allocator_traits, unique_ptr |
| #include <numeric> // accumulate |
| #include <sstream> // stringstream |
| #include <string> // getline, stoi, string, to_string |
| #include <type_traits> // add_pointer, conditional, decay, enable_if, false_type, integral_constant, is_arithmetic, is_base_of, is_const, is_constructible, is_convertible, is_default_constructible, is_enum, is_floating_point, is_integral, is_nothrow_move_assignable, is_nothrow_move_constructible, is_pointer, is_reference, is_same, is_scalar, is_signed, remove_const, remove_cv, remove_pointer, remove_reference, true_type, underlying_type |
| #include <utility> // declval, forward, make_pair, move, pair, swap |
| #include <valarray> // valarray |
| #include <vector> // vector |
| |
| // exclude unsupported compilers |
| #if defined(__clang__) |
| #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 |
| #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" |
| #endif |
| #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) |
| #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40900 |
| #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" |
| #endif |
| #endif |
| |
| // disable float-equal warnings on GCC/clang |
| #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wfloat-equal" |
| #endif |
| |
| // disable documentation warnings on clang |
| #if defined(__clang__) |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wdocumentation" |
| #endif |
| |
| // allow for portable deprecation warnings |
| #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) |
| #define JSON_DEPRECATED __attribute__((deprecated)) |
| #elif defined(_MSC_VER) |
| #define JSON_DEPRECATED __declspec(deprecated) |
| #else |
| #define JSON_DEPRECATED |
| #endif |
| |
| // allow to disable exceptions |
| #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && not defined(JSON_NOEXCEPTION) |
| #define JSON_THROW(exception) throw exception |
| #define JSON_TRY try |
| #define JSON_CATCH(exception) catch(exception) |
| #else |
| #define JSON_THROW(exception) std::abort() |
| #define JSON_TRY if(true) |
| #define JSON_CATCH(exception) if(false) |
| #endif |
| |
| // manual branch prediction |
| #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) |
| #define JSON_LIKELY(x) __builtin_expect(!!(x), 1) |
| #define JSON_UNLIKELY(x) __builtin_expect(!!(x), 0) |
| #else |
| #define JSON_LIKELY(x) x |
| #define JSON_UNLIKELY(x) x |
| #endif |
| |
| // cpp language standard detection |
| #if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 |
| #define JSON_HAS_CPP_17 |
| #define JSON_HAS_CPP_14 |
| #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) |
| #define JSON_HAS_CPP_14 |
| #endif |
| |
| /*! |
| @brief namespace for Niels Lohmann |
| @see https://github.com/nlohmann |
| @since version 1.0.0 |
| */ |
| namespace nlohmann |
| { |
| template<typename = void, typename = void> |
| struct adl_serializer; |
| |
| // forward declaration of basic_json (required to split the class) |
| template<template<typename U, typename V, typename... Args> class ObjectType = |
| std::map, |
| template<typename U, typename... Args> class ArrayType = std::vector, |
| class StringType = std::string, class BooleanType = bool, |
| class NumberIntegerType = std::int64_t, |
| class NumberUnsignedType = std::uint64_t, |
| class NumberFloatType = double, |
| template<typename U> class AllocatorType = std::allocator, |
| template<typename T, typename SFINAE = void> class JSONSerializer = |
| adl_serializer> |
| class basic_json; |
| |
| // Ugly macros to avoid uglier copy-paste when specializing basic_json |
| // This is only temporary and will be removed in 3.0 |
| |
| #define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ |
| template<template<typename, typename, typename...> class ObjectType, \ |
| template<typename, typename...> class ArrayType, \ |
| class StringType, class BooleanType, class NumberIntegerType, \ |
| class NumberUnsignedType, class NumberFloatType, \ |
| template<typename> class AllocatorType, \ |
| template<typename, typename = void> class JSONSerializer> |
| |
| #define NLOHMANN_BASIC_JSON_TPL \ |
| basic_json<ObjectType, ArrayType, StringType, BooleanType, \ |
| NumberIntegerType, NumberUnsignedType, NumberFloatType, \ |
| AllocatorType, JSONSerializer> |
| |
| |
| /*! |
| @brief unnamed namespace with internal helper functions |
| |
| This namespace collects some functions that could not be defined inside the |
| @ref basic_json class. |
| |
| @since version 2.1.0 |
| */ |
| namespace detail |
| { |
| //////////////// |
| // exceptions // |
| //////////////// |
| |
| /*! |
| @brief general exception of the @ref basic_json class |
| |
| This class is an extension of `std::exception` objects with a member @a id for |
| exception ids. It is used as the base class for all exceptions thrown by the |
| @ref basic_json class. This class can hence be used as "wildcard" to catch |
| exceptions. |
| |
| Subclasses: |
| - @ref parse_error for exceptions indicating a parse error |
| - @ref invalid_iterator for exceptions indicating errors with iterators |
| - @ref type_error for exceptions indicating executing a member function with |
| a wrong type |
| - @ref out_of_range for exceptions indicating access out of the defined range |
| - @ref other_error for exceptions indicating other library errors |
| |
| @internal |
| @note To have nothrow-copy-constructible exceptions, we internally use |
| `std::runtime_error` which can cope with arbitrary-length error messages. |
| Intermediate strings are built with static functions and then passed to |
| the actual constructor. |
| @endinternal |
| |
| @liveexample{The following code shows how arbitrary library exceptions can be |
| caught.,exception} |
| |
| @since version 3.0.0 |
| */ |
| class exception : public std::exception |
| { |
| public: |
| /// returns the explanatory string |
| const char* what() const noexcept override |
| { |
| return m.what(); |
| } |
| |
| /// the id of the exception |
| const int id; |
| |
| protected: |
| exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} |
| |
| static std::string name(const std::string& ename, int id_) |
| { |
| return "[json.exception." + ename + "." + std::to_string(id_) + "] "; |
| } |
| |
| private: |
| /// an exception object as storage for error messages |
| std::runtime_error m; |
| }; |
| |
| /*! |
| @brief exception indicating a parse error |
| |
| This excpetion is thrown by the library when a parse error occurs. Parse errors |
| can occur during the deserialization of JSON text, CBOR, MessagePack, as well |
| as when using JSON Patch. |
| |
| Member @a byte holds the byte index of the last read character in the input |
| file. |
| |
| Exceptions have ids 1xx. |
| |
| name / id | example message | description |
| ------------------------------ | --------------- | ------------------------- |
| json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position. |
| json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. |
| json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. |
| json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. |
| json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. |
| json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number wihtout a leading `0`. |
| json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. |
| json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. |
| json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number. |
| json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. |
| json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xf8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. |
| json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. |
| |
| @note For an input with n bytes, 1 is the index of the first character and n+1 |
| is the index of the terminating null byte or the end of file. This also |
| holds true when reading a byte vector (CBOR or MessagePack). |
| |
| @liveexample{The following code shows how a `parse_error` exception can be |
| caught.,parse_error} |
| |
| @sa @ref exception for the base class of the library exceptions |
| @sa @ref invalid_iterator for exceptions indicating errors with iterators |
| @sa @ref type_error for exceptions indicating executing a member function with |
| a wrong type |
| @sa @ref out_of_range for exceptions indicating access out of the defined range |
| @sa @ref other_error for exceptions indicating other library errors |
| |
| @since version 3.0.0 |
| */ |
| class parse_error : public exception |
| { |
| public: |
| /*! |
| @brief create a parse error exception |
| @param[in] id_ the id of the exception |
| @param[in] byte_ the byte index where the error occurred (or 0 if the |
| position cannot be determined) |
| @param[in] what_arg the explanatory string |
| @return parse_error object |
| */ |
| static parse_error create(int id_, std::size_t byte_, const std::string& what_arg) |
| { |
| std::string w = exception::name("parse_error", id_) + "parse error" + |
| (byte_ != 0 ? (" at " + std::to_string(byte_)) : "") + |
| ": " + what_arg; |
| return parse_error(id_, byte_, w.c_str()); |
| } |
| |
| /*! |
| @brief byte index of the parse error |
| |
| The byte index of the last read character in the input file. |
| |
| @note For an input with n bytes, 1 is the index of the first character and |
| n+1 is the index of the terminating null byte or the end of file. |
| This also holds true when reading a byte vector (CBOR or MessagePack). |
| */ |
| const std::size_t byte; |
| |
| private: |
| parse_error(int id_, std::size_t byte_, const char* what_arg) |
| : exception(id_, what_arg), byte(byte_) {} |
| }; |
| |
| /*! |
| @brief exception indicating errors with iterators |
| |
| This exception is thrown if iterators passed to a library function do not match |
| the expected semantics. |
| |
| Exceptions have ids 2xx. |
| |
| name / id | example message | description |
| ----------------------------------- | --------------- | ------------------------- |
| json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. |
| json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. |
| json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from. |
| json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid. |
| json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid. |
| json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range. |
| json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key. |
| json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. |
| json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. |
| json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. |
| json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to. |
| json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container. |
| json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered. |
| json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin(). |
| |
| @liveexample{The following code shows how an `invalid_iterator` exception can be |
| caught.,invalid_iterator} |
| |
| @sa @ref exception for the base class of the library exceptions |
| @sa @ref parse_error for exceptions indicating a parse error |
| @sa @ref type_error for exceptions indicating executing a member function with |
| a wrong type |
| @sa @ref out_of_range for exceptions indicating access out of the defined range |
| @sa @ref other_error for exceptions indicating other library errors |
| |
| @since version 3.0.0 |
| */ |
| class invalid_iterator : public exception |
| { |
| public: |
| static invalid_iterator create(int id_, const std::string& what_arg) |
| { |
| std::string w = exception::name("invalid_iterator", id_) + what_arg; |
| return invalid_iterator(id_, w.c_str()); |
| } |
| |
| private: |
| invalid_iterator(int id_, const char* what_arg) |
| : exception(id_, what_arg) {} |
| }; |
| |
| /*! |
| @brief exception indicating executing a member function with a wrong type |
| |
| This exception is thrown in case of a type error; that is, a library function is |
| executed on a JSON value whose type does not match the expected semantics. |
| |
| Exceptions have ids 3xx. |
| |
| name / id | example message | description |
| ----------------------------- | --------------- | ------------------------- |
| json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead. |
| json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. |
| json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t&. |
| json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types. |
| json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types. |
| json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types. |
| json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types. |
| json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types. |
| json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. |
| json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. |
| json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. |
| json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types. |
| json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. |
| json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. |
| json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. |
| |
| @liveexample{The following code shows how a `type_error` exception can be |
| caught.,type_error} |
| |
| @sa @ref exception for the base class of the library exceptions |
| @sa @ref parse_error for exceptions indicating a parse error |
| @sa @ref invalid_iterator for exceptions indicating errors with iterators |
| @sa @ref out_of_range for exceptions indicating access out of the defined range |
| @sa @ref other_error for exceptions indicating other library errors |
| |
| @since version 3.0.0 |
| */ |
| class type_error : public exception |
| { |
| public: |
| static type_error create(int id_, const std::string& what_arg) |
| { |
| std::string w = exception::name("type_error", id_) + what_arg; |
| return type_error(id_, w.c_str()); |
| } |
| |
| private: |
| type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} |
| }; |
| |
| /*! |
| @brief exception indicating access out of the defined range |
| |
| This exception is thrown in case a library function is called on an input |
| parameter that exceeds the expected range, for instance in case of array |
| indices or nonexisting object keys. |
| |
| Exceptions have ids 4xx. |
| |
| name / id | example message | description |
| ------------------------------- | --------------- | ------------------------- |
| json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1. |
| json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it. |
| json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object. |
| json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. |
| json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. |
| json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. |
| |
| @liveexample{The following code shows how an `out_of_range` exception can be |
| caught.,out_of_range} |
| |
| @sa @ref exception for the base class of the library exceptions |
| @sa @ref parse_error for exceptions indicating a parse error |
| @sa @ref invalid_iterator for exceptions indicating errors with iterators |
| @sa @ref type_error for exceptions indicating executing a member function with |
| a wrong type |
| @sa @ref other_error for exceptions indicating other library errors |
| |
| @since version 3.0.0 |
| */ |
| class out_of_range : public exception |
| { |
| public: |
| static out_of_range create(int id_, const std::string& what_arg) |
| { |
| std::string w = exception::name("out_of_range", id_) + what_arg; |
| return out_of_range(id_, w.c_str()); |
| } |
| |
| private: |
| out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} |
| }; |
| |
| /*! |
| @brief exception indicating other library errors |
| |
| This exception is thrown in case of errors that cannot be classified with the |
| other exception types. |
| |
| Exceptions have ids 5xx. |
| |
| name / id | example message | description |
| ------------------------------ | --------------- | ------------------------- |
| json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. |
| json.exception.other_error.502 | invalid object size for conversion | Some conversions to user-defined types impose constraints on the object size (e.g. std::pair) |
| |
| @sa @ref exception for the base class of the library exceptions |
| @sa @ref parse_error for exceptions indicating a parse error |
| @sa @ref invalid_iterator for exceptions indicating errors with iterators |
| @sa @ref type_error for exceptions indicating executing a member function with |
| a wrong type |
| @sa @ref out_of_range for exceptions indicating access out of the defined range |
| |
| @liveexample{The following code shows how an `other_error` exception can be |
| caught.,other_error} |
| |
| @since version 3.0.0 |
| */ |
| class other_error : public exception |
| { |
| public: |
| static other_error create(int id_, const std::string& what_arg) |
| { |
| std::string w = exception::name("other_error", id_) + what_arg; |
| return other_error(id_, w.c_str()); |
| } |
| |
| private: |
| other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} |
| }; |
| |
| |
| |
| /////////////////////////// |
| // JSON type enumeration // |
| /////////////////////////// |
| |
| /*! |
| @brief the JSON type enumeration |
| |
| This enumeration collects the different JSON types. It is internally used to |
| distinguish the stored values, and the functions @ref basic_json::is_null(), |
| @ref basic_json::is_object(), @ref basic_json::is_array(), |
| @ref basic_json::is_string(), @ref basic_json::is_boolean(), |
| @ref basic_json::is_number() (with @ref basic_json::is_number_integer(), |
| @ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), |
| @ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and |
| @ref basic_json::is_structured() rely on it. |
| |
| @note There are three enumeration entries (number_integer, number_unsigned, and |
| number_float), because the library distinguishes these three types for numbers: |
| @ref basic_json::number_unsigned_t is used for unsigned integers, |
| @ref basic_json::number_integer_t is used for signed integers, and |
| @ref basic_json::number_float_t is used for floating-point numbers or to |
| approximate integers which do not fit in the limits of their respective type. |
| |
| @sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON |
| value with the default value for a given type |
| |
| @since version 1.0.0 |
| */ |
| enum class value_t : uint8_t |
| { |
| null, ///< null value |
| object, ///< object (unordered set of name/value pairs) |
| array, ///< array (ordered collection of values) |
| string, ///< string value |
| boolean, ///< boolean value |
| number_integer, ///< number value (signed integer) |
| number_unsigned, ///< number value (unsigned integer) |
| number_float, ///< number value (floating-point) |
| discarded ///< discarded by the the parser callback function |
| }; |
| |
| /*! |
| @brief comparison operator for JSON types |
| |
| Returns an ordering that is similar to Python: |
| - order: null < boolean < number < object < array < string |
| - furthermore, each type is not smaller than itself |
| |
| @since version 1.0.0 |
| */ |
| inline bool operator<(const value_t lhs, const value_t rhs) noexcept |
| { |
| static constexpr std::array<uint8_t, 8> order = {{ |
| 0, // null |
| 3, // object |
| 4, // array |
| 5, // string |
| 1, // boolean |
| 2, // integer |
| 2, // unsigned |
| 2, // float |
| } |
| }; |
| |
| // discarded values are not comparable |
| return lhs != value_t::discarded and rhs != value_t::discarded and |
| order[static_cast<std::size_t>(lhs)] < order[static_cast<std::size_t>(rhs)]; |
| } |
| |
| |
| ///////////// |
| // helpers // |
| ///////////// |
| |
| template<typename> struct is_basic_json : std::false_type {}; |
| |
| NLOHMANN_BASIC_JSON_TPL_DECLARATION |
| struct is_basic_json<NLOHMANN_BASIC_JSON_TPL> : std::true_type {}; |
| |
| // alias templates to reduce boilerplate |
| template<bool B, typename T = void> |
| using enable_if_t = typename std::enable_if<B, T>::type; |
| |
| template<typename T> |
| using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type; |
| |
| // implementation of C++14 index_sequence and affiliates |
| // source: https://stackoverflow.com/a/32223343 |
| template<std::size_t... Ints> |
| struct index_sequence |
| { |
| using type = index_sequence; |
| using value_type = std::size_t; |
| static constexpr std::size_t size() noexcept |
| { |
| return sizeof...(Ints); |
| } |
| }; |
| |
| template<class Sequence1, class Sequence2> |
| struct merge_and_renumber; |
| |
| template<std::size_t... I1, std::size_t... I2> |
| struct merge_and_renumber<index_sequence<I1...>, index_sequence<I2...>> |
| : index_sequence < I1..., (sizeof...(I1) + I2)... > |
| {}; |
| |
| template<std::size_t N> |
| struct make_index_sequence |
| : merge_and_renumber < typename make_index_sequence < N / 2 >::type, |
| typename make_index_sequence < N - N / 2 >::type > |
| {}; |
| |
| template<> struct make_index_sequence<0> : index_sequence<> { }; |
| template<> struct make_index_sequence<1> : index_sequence<0> { }; |
| |
| template<typename... Ts> |
| using index_sequence_for = make_index_sequence<sizeof...(Ts)>; |
| |
| /* |
| Implementation of two C++17 constructs: conjunction, negation. This is needed |
| to avoid evaluating all the traits in a condition |
| |
| For example: not std::is_same<void, T>::value and has_value_type<T>::value |
| will not compile when T = void (on MSVC at least). Whereas |
| conjunction<negation<std::is_same<void, T>>, has_value_type<T>>::value will |
| stop evaluating if negation<...>::value == false |
| |
| Please note that those constructs must be used with caution, since symbols can |
| become very long quickly (which can slow down compilation and cause MSVC |
| internal compiler errors). Only use it when you have to (see example ahead). |
| */ |
| template<class...> struct conjunction : std::true_type {}; |
| template<class B1> struct conjunction<B1> : B1 {}; |
| template<class B1, class... Bn> |
| struct conjunction<B1, Bn...> : std::conditional<bool(B1::value), conjunction<Bn...>, B1>::type {}; |
| |
| template<class B> struct negation : std::integral_constant < bool, !B::value > {}; |
| |
| // dispatch utility (taken from ranges-v3) |
| template<unsigned N> struct priority_tag : priority_tag < N - 1 > {}; |
| template<> struct priority_tag<0> {}; |
| |
| |
| ////////////////// |
| // constructors // |
| ////////////////// |
| |
| template<value_t> struct external_constructor; |
| |
| template<> |
| struct external_constructor<value_t::boolean> |
| { |
| template<typename BasicJsonType> |
| static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept |
| { |
| j.m_type = value_t::boolean; |
| j.m_value = b; |
| j.assert_invariant(); |
| } |
| }; |
| |
| template<> |
| struct external_constructor<value_t::string> |
| { |
| template<typename BasicJsonType> |
| static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) |
| { |
| j.m_type = value_t::string; |
| j.m_value = s; |
| j.assert_invariant(); |
| } |
| |
| template<typename BasicJsonType> |
| static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) |
| { |
| j.m_type = value_t::string; |
| j.m_value = std::move(s); |
| j.assert_invariant(); |
| } |
| }; |
| |
| template<> |
| struct external_constructor<value_t::number_float> |
| { |
| template<typename BasicJsonType> |
| static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept |
| { |
| j.m_type = value_t::number_float; |
| j.m_value = val; |
| j.assert_invariant(); |
| } |
| }; |
| |
| template<> |
| struct external_constructor<value_t::number_unsigned> |
| { |
| template<typename BasicJsonType> |
| static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept |
| { |
| j.m_type = value_t::number_unsigned; |
| j.m_value = val; |
| j.assert_invariant(); |
| } |
| }; |
| |
| template<> |
| struct external_constructor<value_t::number_integer> |
| { |
| template<typename BasicJsonType> |
| static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept |
| { |
| j.m_type = value_t::number_integer; |
| j.m_value = val; |
| j.assert_invariant(); |
| } |
| }; |
| |
| template<> |
| struct external_constructor<value_t::array> |
| { |
| template<typename BasicJsonType> |
| static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) |
| { |
| j.m_type = value_t::array; |
| j.m_value = arr; |
| j.assert_invariant(); |
| } |
| |
| template<typename BasicJsonType> |
| static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) |
| { |
| j.m_type = value_t::array; |
| j.m_value = std::move(arr); |
| j.assert_invariant(); |
| } |
| |
| template<typename BasicJsonType, typename CompatibleArrayType, |
| enable_if_t<not std::is_same<CompatibleArrayType, |
| typename BasicJsonType::array_t>::value, |
| int> = 0> |
| static void construct(BasicJsonType& j, const CompatibleArrayType& arr) |
| { |
| using std::begin; |
| using std::end; |
| j.m_type = value_t::array; |
| j.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr)); |
| j.assert_invariant(); |
| } |
| |
| template<typename BasicJsonType> |
| static void construct(BasicJsonType& j, const std::vector<bool>& arr) |
| { |
| j.m_type = value_t::array; |
| j.m_value = value_t::array; |
| j.m_value.array->reserve(arr.size()); |
| for (bool x : arr) |
| { |
| j.m_value.array->push_back(x); |
| } |
| j.assert_invariant(); |
| } |
| |
| template<typename BasicJsonType, typename T, |
| enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0> |
| static void construct(BasicJsonType& j, const std::valarray<T>& arr) |
| { |
| j.m_type = value_t::array; |
| j.m_value = value_t::array; |
| j.m_value.array->resize(arr.size()); |
| std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); |
| j.assert_invariant(); |
| } |
| }; |
| |
| template<> |
| struct external_constructor<value_t::object> |
| { |
| template<typename BasicJsonType> |
| static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) |
| { |
| j.m_type = value_t::object; |
| j.m_value = obj; |
| j.assert_invariant(); |
| } |
| |
| template<typename BasicJsonType> |
| static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) |
| { |
| j.m_type = value_t::object; |
| j.m_value = std::move(obj); |
| j.assert_invariant(); |
| } |
| |
| template<typename BasicJsonType, typename CompatibleObjectType, |
| enable_if_t<not std::is_same<CompatibleObjectType, |
| typename BasicJsonType::object_t>::value, int> = 0> |
| static void construct(BasicJsonType& j, const CompatibleObjectType& obj) |
| { |
| using std::begin; |
| using std::end; |
| |
| j.m_type = value_t::object; |
| j.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj)); |
| j.assert_invariant(); |
| } |
| }; |
| |
| |
| //////////////////////// |
| // has_/is_ functions // |
| //////////////////////// |
| |
| /*! |
| @brief Helper to determine whether there's a key_type for T. |
| |
| This helper is used to tell associative containers apart from other containers |
| such as sequence containers. For instance, `std::map` passes the test as it |
| contains a `mapped_type`, whereas `std::vector` fails the test. |
| |
| @sa http://stackoverflow.com/a/7728728/266378 |
| @since version 1.0.0, overworked in version 2.0.6 |
| */ |
| #define NLOHMANN_JSON_HAS_HELPER(type) \ |
| template<typename T> struct has_##type { \ |
| private: \ |
| template<typename U, typename = typename U::type> \ |
| static int detect(U &&); \ |
| static void detect(...); \ |
| public: \ |
| static constexpr bool value = \ |
| std::is_integral<decltype(detect(std::declval<T>()))>::value; \ |
| } |
| |
| NLOHMANN_JSON_HAS_HELPER(mapped_type); |
| NLOHMANN_JSON_HAS_HELPER(key_type); |
| NLOHMANN_JSON_HAS_HELPER(value_type); |
| NLOHMANN_JSON_HAS_HELPER(iterator); |
| |
| #undef NLOHMANN_JSON_HAS_HELPER |
| |
| |
| template<bool B, class RealType, class CompatibleObjectType> |
| struct is_compatible_object_type_impl : std::false_type {}; |
| |
| template<class RealType, class CompatibleObjectType> |
| struct is_compatible_object_type_impl<true, RealType, CompatibleObjectType> |
| { |
| static constexpr auto value = |
| std::is_constructible<typename RealType::key_type, typename CompatibleObjectType::key_type>::value and |
| std::is_constructible<typename RealType::mapped_type, typename CompatibleObjectType::mapped_type>::value; |
| }; |
| |
| template<class BasicJsonType, class CompatibleObjectType> |
| struct is_compatible_object_type |
| { |
| static auto constexpr value = is_compatible_object_type_impl < |
| conjunction<negation<std::is_same<void, CompatibleObjectType>>, |
| has_mapped_type<CompatibleObjectType>, |
| has_key_type<CompatibleObjectType>>::value, |
| typename BasicJsonType::object_t, CompatibleObjectType >::value; |
| }; |
| |
| template<typename BasicJsonType, typename T> |
| struct is_basic_json_nested_type |
| { |
| static auto constexpr value = std::is_same<T, typename BasicJsonType::iterator>::value or |
| std::is_same<T, typename BasicJsonType::const_iterator>::value or |
| std::is_same<T, typename BasicJsonType::reverse_iterator>::value or |
| std::is_same<T, typename BasicJsonType::const_reverse_iterator>::value; |
| }; |
| |
| template<class BasicJsonType, class CompatibleArrayType> |
| struct is_compatible_array_type |
| { |
| static auto constexpr value = |
| conjunction<negation<std::is_same<void, CompatibleArrayType>>, |
| negation<is_compatible_object_type< |
| BasicJsonType, CompatibleArrayType>>, |
| negation<std::is_constructible<typename BasicJsonType::string_t, |
| CompatibleArrayType>>, |
| negation<is_basic_json_nested_type<BasicJsonType, CompatibleArrayType>>, |
| has_value_type<CompatibleArrayType>, |
| has_iterator<CompatibleArrayType>>::value; |
| }; |
| |
| template<bool, typename, typename> |
| struct is_compatible_integer_type_impl : std::false_type {}; |
| |
| template<typename RealIntegerType, typename CompatibleNumberIntegerType> |
| struct is_compatible_integer_type_impl<true, RealIntegerType, CompatibleNumberIntegerType> |
| { |
| // is there an assert somewhere on overflows? |
| using RealLimits = std::numeric_limits<RealIntegerType>; |
| using CompatibleLimits = std::numeric_limits<CompatibleNumberIntegerType>; |
| |
| static constexpr auto value = |
| std::is_constructible<RealIntegerType, CompatibleNumberIntegerType>::value and |
| CompatibleLimits::is_integer and |
| RealLimits::is_signed == CompatibleLimits::is_signed; |
| }; |
| |
| template<typename RealIntegerType, typename CompatibleNumberIntegerType> |
| struct is_compatible_integer_type |
| { |
| static constexpr auto value = |
| is_compatible_integer_type_impl < |
| std::is_integral<CompatibleNumberIntegerType>::value and |
| not std::is_same<bool, CompatibleNumberIntegerType>::value, |
| RealIntegerType, CompatibleNumberIntegerType > ::value; |
| }; |
| |
| |
| // trait checking if JSONSerializer<T>::from_json(json const&, udt&) exists |
| template<typename BasicJsonType, typename T> |
| struct has_from_json |
| { |
| private: |
| // also check the return type of from_json |
| template<typename U, typename = enable_if_t<std::is_same<void, decltype(uncvref_t<U>::from_json( |
| std::declval<BasicJsonType>(), std::declval<T&>()))>::value>> |
| static int detect(U&&); |
| static void detect(...); |
| |
| public: |
| static constexpr bool value = std::is_integral<decltype( |
| detect(std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value; |
| }; |
| |
| // This trait checks if JSONSerializer<T>::from_json(json const&) exists |
| // this overload is used for non-default-constructible user-defined-types |
| template<typename BasicJsonType, typename T> |
| struct has_non_default_from_json |
| { |
| private: |
| template < |
| typename U, |
| typename = enable_if_t<std::is_same< |
| T, decltype(uncvref_t<U>::from_json(std::declval<BasicJsonType>()))>::value >> |
| static int detect(U&&); |
| static void detect(...); |
| |
| public: |
| static constexpr bool value = std::is_integral<decltype(detect( |
| std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value; |
| }; |
| |
| // This trait checks if BasicJsonType::json_serializer<T>::to_json exists |
| template<typename BasicJsonType, typename T> |
| struct has_to_json |
| { |
| private: |
| template<typename U, typename = decltype(uncvref_t<U>::to_json( |
| std::declval<BasicJsonType&>(), std::declval<T>()))> |
| static int detect(U&&); |
| static void detect(...); |
| |
| public: |
| static constexpr bool value = std::is_integral<decltype(detect( |
| std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value; |
| }; |
| |
| |
| ///////////// |
| // to_json // |
| ///////////// |
| |
| template<typename BasicJsonType, typename T, enable_if_t< |
| std::is_same<T, typename BasicJsonType::boolean_t>::value, int> = 0> |
| void to_json(BasicJsonType& j, T b) noexcept |
| { |
| external_constructor<value_t::boolean>::construct(j, b); |
| } |
| |
| template<typename BasicJsonType, typename CompatibleString, |
| enable_if_t<std::is_constructible<typename BasicJsonType::string_t, |
| CompatibleString>::value, int> = 0> |
| void to_json(BasicJsonType& j, const CompatibleString& s) |
| { |
| external_constructor<value_t::string>::construct(j, s); |
| } |
| |
| template <typename BasicJsonType> |
| void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) |
| { |
| external_constructor<value_t::string>::construct(j, std::move(s)); |
| } |
| |
| template<typename BasicJsonType, typename FloatType, |
| enable_if_t<std::is_floating_point<FloatType>::value, int> = 0> |
| void to_json(BasicJsonType& j, FloatType val) noexcept |
| { |
| external_constructor<value_t::number_float>::construct(j, static_cast<typename BasicJsonType::number_float_t>(val)); |
| } |
| |
| template < |
| typename BasicJsonType, typename CompatibleNumberUnsignedType, |
| enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t, |
| CompatibleNumberUnsignedType>::value, int> = 0 > |
| void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept |
| { |
| external_constructor<value_t::number_unsigned>::construct(j, static_cast<typename BasicJsonType::number_unsigned_t>(val)); |
| } |
| |
| template < |
| typename BasicJsonType, typename CompatibleNumberIntegerType, |
| enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t, |
| CompatibleNumberIntegerType>::value, int> = 0 > |
| void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept |
| { |
| external_constructor<value_t::number_integer>::construct(j, static_cast<typename BasicJsonType::number_integer_t>(val)); |
| } |
| |
| template<typename BasicJsonType, typename EnumType, |
| enable_if_t<std::is_enum<EnumType>::value, int> = 0> |
| void to_json(BasicJsonType& j, EnumType e) noexcept |
| { |
| using underlying_type = typename std::underlying_type<EnumType>::type; |
| external_constructor<value_t::number_integer>::construct(j, static_cast<underlying_type>(e)); |
| } |
| |
| template<typename BasicJsonType> |
| void to_json(BasicJsonType& j, const std::vector<bool>& e) |
| { |
| external_constructor<value_t::array>::construct(j, e); |
| } |
| |
| template < |
| typename BasicJsonType, typename CompatibleArrayType, |
| enable_if_t < |
| is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value or |
| std::is_same<typename BasicJsonType::array_t, CompatibleArrayType>::value, |
| int > = 0 > |
| void to_json(BasicJsonType& j, const CompatibleArrayType& arr) |
| { |
| external_constructor<value_t::array>::construct(j, arr); |
| } |
| |
| template <typename BasicJsonType, typename T, |
| enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0> |
| void to_json(BasicJsonType& j, std::valarray<T> arr) |
| { |
| external_constructor<value_t::array>::construct(j, std::move(arr)); |
| } |
| |
| template <typename BasicJsonType> |
| void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) |
| { |
| external_constructor<value_t::array>::construct(j, std::move(arr)); |
| } |
| |
| template < |
| typename BasicJsonType, typename CompatibleObjectType, |
| enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value, |
| int> = 0 > |
| void to_json(BasicJsonType& j, const CompatibleObjectType& obj) |
| { |
| external_constructor<value_t::object>::construct(j, obj); |
| } |
| |
| template <typename BasicJsonType> |
| void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) |
| { |
| external_constructor<value_t::object>::construct(j, std::move(obj)); |
| } |
| |
| template<typename BasicJsonType, typename T, std::size_t N, |
| enable_if_t<not std::is_constructible< |
| typename BasicJsonType::string_t, T (&)[N]>::value, |
| int> = 0> |
| void to_json(BasicJsonType& j, T (&arr)[N]) |
| { |
| external_constructor<value_t::array>::construct(j, arr); |
| } |
| |
| template<typename BasicJsonType, typename... Args> |
| void to_json(BasicJsonType& j, const std::pair<Args...>& p) |
| { |
| j = {p.first, p.second}; |
| } |
| |
| template<typename BasicJsonType, typename Tuple, std::size_t... Idx> |
| void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<Idx...>) |
| { |
| j = {std::get<Idx>(t)...}; |
| } |
| |
| template<typename BasicJsonType, typename... Args> |
| void to_json(BasicJsonType& j, const std::tuple<Args...>& t) |
| { |
| to_json_tuple_impl(j, t, index_sequence_for<Args...> {}); |
| } |
| |
| /////////////// |
| // from_json // |
| /////////////// |
| |
| // overloads for basic_json template parameters |
| template<typename BasicJsonType, typename ArithmeticType, |
| enable_if_t<std::is_arithmetic<ArithmeticType>::value and |
| not std::is_same<ArithmeticType, |
| typename BasicJsonType::boolean_t>::value, |
| int> = 0> |
| void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) |
| { |
| switch (static_cast<value_t>(j)) |
| { |
| case value_t::number_unsigned: |
| { |
| val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>()); |
| break; |
| } |
| case value_t::number_integer: |
| { |
| val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>()); |
| break; |
| } |
| case value_t::number_float: |
| { |
| val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>()); |
| break; |
| } |
| |
| default: |
| JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); |
| } |
| } |
| |
| template<typename BasicJsonType> |
| void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) |
| { |
| if (JSON_UNLIKELY(not j.is_boolean())) |
| { |
| JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()))); |
| } |
| b = *j.template get_ptr<const typename BasicJsonType::boolean_t*>(); |
| } |
| |
| template<typename BasicJsonType> |
| void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) |
| { |
| if (JSON_UNLIKELY(not j.is_string())) |
| { |
| JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); |
| } |
| s = *j.template get_ptr<const typename BasicJsonType::string_t*>(); |
| } |
| |
| template<typename BasicJsonType> |
| void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) |
| { |
| get_arithmetic_value(j, val); |
| } |
| |
| template<typename BasicJsonType> |
| void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) |
| { |
| get_arithmetic_value(j, val); |
| } |
| |
| template<typename BasicJsonType> |
| void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) |
| { |
| get_arithmetic_value(j, val); |
| } |
| |
| template<typename BasicJsonType, typename EnumType, |
| enable_if_t<std::is_enum<EnumType>::value, int> = 0> |
| void from_json(const BasicJsonType& j, EnumType& e) |
| { |
| typename std::underlying_type<EnumType>::type val; |
| get_arithmetic_value(j, val); |
| e = static_cast<EnumType>(val); |
| } |
| |
| template<typename BasicJsonType> |
| void from_json(const BasicJsonType& j, typename BasicJsonType::array_t& arr) |
| { |
| if (JSON_UNLIKELY(not j.is_array())) |
| { |
| JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); |
| } |
| arr = *j.template get_ptr<const typename BasicJsonType::array_t*>(); |
| } |
| |
| // forward_list doesn't have an insert method |
| template<typename BasicJsonType, typename T, typename Allocator, |
| enable_if_t<std::is_convertible<BasicJsonType, T>::value, int> = 0> |
| void from_json(const BasicJsonType& j, std::forward_list<T, Allocator>& l) |
| { |
| if (JSON_UNLIKELY(not j.is_array())) |
| { |
| JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); |
| } |
| std::transform(j.rbegin(), j.rend(), |
| std::front_inserter(l), [](const BasicJsonType & i) |
| { |
| return i.template get<T>(); |
| }); |
| } |
| |
| // valarray doesn't have an insert method |
| template<typename BasicJsonType, typename T, |
| enable_if_t<std::is_convertible<BasicJsonType, T>::value, int> = 0> |
| void from_json(const BasicJsonType& j, std::valarray<T>& l) |
| { |
| if (JSON_UNLIKELY(not j.is_array())) |
| { |
| JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); |
| } |
| l.resize(j.size()); |
| std::copy(j.m_value.array->begin(), j.m_value.array->end(), std::begin(l)); |
| } |
| |
| template<typename BasicJsonType, typename CompatibleArrayType> |
| void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<0> /*unused*/) |
| { |
| using std::end; |
| |
| std::transform(j.begin(), j.end(), |
| std::inserter(arr, end(arr)), [](const BasicJsonType & i) |
| { |
| // get<BasicJsonType>() returns *this, this won't call a from_json |
| // method when value_type is BasicJsonType |
| return i.template get<typename CompatibleArrayType::value_type>(); |
| }); |
| } |
| |
| template<typename BasicJsonType, typename CompatibleArrayType> |
| auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1> /*unused*/) |
| -> decltype( |
| arr.reserve(std::declval<typename CompatibleArrayType::size_type>()), |
| void()) |
| { |
| using std::end; |
| |
| arr.reserve(j.size()); |
| std::transform(j.begin(), j.end(), |
| std::inserter(arr, end(arr)), [](const BasicJsonType & i) |
| { |
| // get<BasicJsonType>() returns *this, this won't call a from_json |
| // method when value_type is BasicJsonType |
| return i.template get<typename CompatibleArrayType::value_type>(); |
| }); |
| } |
| |
| template<typename BasicJsonType, typename T, std::size_t N> |
| void from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr, priority_tag<2> /*unused*/) |
| { |
| for (std::size_t i = 0; i < N; ++i) |
| { |
| arr[i] = j.at(i).template get<T>(); |
| } |
| } |
| |
| template<typename BasicJsonType, typename CompatibleArrayType, |
| enable_if_t<is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value and |
| std::is_convertible<BasicJsonType, typename CompatibleArrayType::value_type>::value and |
| not std::is_same<typename BasicJsonType::array_t, CompatibleArrayType>::value, int> = 0> |
| void from_json(const BasicJsonType& j, CompatibleArrayType& arr) |
| { |
| if (JSON_UNLIKELY(not j.is_array())) |
| { |
| JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); |
| } |
| |
| from_json_array_impl(j, arr, priority_tag<2> {}); |
| } |
| |
| template<typename BasicJsonType, typename CompatibleObjectType, |
| enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value, int> = 0> |
| void from_json(const BasicJsonType& j, CompatibleObjectType& obj) |
| { |
| if (JSON_UNLIKELY(not j.is_object())) |
| { |
| JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()))); |
| } |
| |
| auto inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>(); |
| using value_type = typename CompatibleObjectType::value_type; |
| std::transform( |
| inner_object->begin(), inner_object->end(), |
| std::inserter(obj, obj.begin()), |
| [](typename BasicJsonType::object_t::value_type const & p) |
| { |
| return value_type(p.first, p.second.template get<typename CompatibleObjectType::mapped_type>()); |
| }); |
| } |
| |
| // overload for arithmetic types, not chosen for basic_json template arguments |
| // (BooleanType, etc..); note: Is it really necessary to provide explicit |
| // overloads for boolean_t etc. in case of a custom BooleanType which is not |
| // an arithmetic type? |
| template<typename BasicJsonType, typename ArithmeticType, |
| enable_if_t < |
| std::is_arithmetic<ArithmeticType>::value and |
| not std::is_same<ArithmeticType, typename BasicJsonType::number_unsigned_t>::value and |
| not std::is_same<ArithmeticType, typename BasicJsonType::number_integer_t>::value and |
| not std::is_same<ArithmeticType, typename BasicJsonType::number_float_t>::value and |
| not std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value, |
| int> = 0> |
| void from_json(const BasicJsonType& j, ArithmeticType& val) |
| { |
| switch (static_cast<value_t>(j)) |
| { |
| case value_t::number_unsigned: |
| { |
| val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>()); |
| break; |
| } |
| case value_t::number_integer: |
| { |
| val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>()); |
| break; |
| } |
| case value_t::number_float: |
| { |
| val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>()); |
| break; |
| } |
| case value_t::boolean: |
| { |
| val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>()); |
| break; |
| } |
| |
| default: |
| JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); |
| } |
| } |
| |
| template<typename BasicJsonType, typename A1, typename A2> |
| void from_json(const BasicJsonType& j, std::pair<A1, A2>& p) |
| { |
| p = {j.at(0).template get<A1>(), j.at(1).template get<A2>()}; |
| } |
| |
| template<typename BasicJsonType, typename Tuple, std::size_t... Idx> |
| void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence<Idx...>) |
| { |
| t = std::make_tuple(j.at(Idx).template get<typename std::tuple_element<Idx, Tuple>::type>()...); |
| } |
| |
| template<typename BasicJsonType, typename... Args> |
| void from_json(const BasicJsonType& j, std::tuple<Args...>& t) |
| { |
| from_json_tuple_impl(j, t, index_sequence_for<Args...> {}); |
| } |
| |
| struct to_json_fn |
| { |
| private: |
| template<typename BasicJsonType, typename T> |
| auto call(BasicJsonType& j, T&& val, priority_tag<1> /*unused*/) const noexcept(noexcept(to_json(j, std::forward<T>(val)))) |
| -> decltype(to_json(j, std::forward<T>(val)), void()) |
| { |
| return to_json(j, std::forward<T>(val)); |
| } |
| |
| template<typename BasicJsonType, typename T> |
| void call(BasicJsonType& /*unused*/, T&& /*unused*/, priority_tag<0> /*unused*/) const noexcept |
| { |
| static_assert(sizeof(BasicJsonType) == 0, |
| "could not find to_json() method in T's namespace"); |
| } |
| |
| public: |
| template<typename BasicJsonType, typename T> |
| void operator()(BasicJsonType& j, T&& val) const |
| noexcept(noexcept(std::declval<to_json_fn>().call(j, std::forward<T>(val), priority_tag<1> {}))) |
| { |
| return call(j, std::forward<T>(val), priority_tag<1> {}); |
| } |
| }; |
| |
| struct from_json_fn |
| { |
| private: |
| template<typename BasicJsonType, typename T> |
| auto call(const BasicJsonType& j, T& val, priority_tag<1> /*unused*/) const |
| noexcept(noexcept(from_json(j, val))) |
| -> decltype(from_json(j, val), void()) |
| { |
| return from_json(j, val); |
| } |
| |
| template<typename BasicJsonType, typename T> |
| void call(const BasicJsonType& /*unused*/, T& /*unused*/, priority_tag<0> /*unused*/) const noexcept |
| { |
| static_assert(sizeof(BasicJsonType) == 0, |
| "could not find from_json() method in T's namespace"); |
| } |
| |
| public: |
| template<typename BasicJsonType, typename T> |
| void operator()(const BasicJsonType& j, T& val) const |
| noexcept(noexcept(std::declval<from_json_fn>().call(j, val, priority_tag<1> {}))) |
| { |
| return call(j, val, priority_tag<1> {}); |
| } |
| }; |
| |
| // taken from ranges-v3 |
| template<typename T> |
| struct static_const |
| { |
| static constexpr T value{}; |
| }; |
| |
| template<typename T> |
| constexpr T static_const<T>::value; |
| |
| //////////////////// |
| // input adapters // |
| //////////////////// |
| |
| /*! |
| @brief abstract input adapter interface |
| |
| Produces a stream of std::char_traits<char>::int_type characters from a |
| std::istream, a buffer, or some other input type. Accepts the return of exactly |
| one non-EOF character for future input. The int_type characters returned |
| consist of all valid char values as positive values (typically unsigned char), |
| plus an EOF value outside that range, specified by the value of the function |
| std::char_traits<char>::eof(). This value is typically -1, but could be any |
| arbitrary value which is not a valid char value. |
| */ |
| struct input_adapter_protocol |
| { |
| /// get a character [0,255] or std::char_traits<char>::eof(). |
| virtual std::char_traits<char>::int_type get_character() = 0; |
| /// restore the last non-eof() character to input |
| virtual void unget_character() = 0; |
| virtual ~input_adapter_protocol() = default; |
| }; |
| |
| /// a type to simplify interfaces |
| using input_adapter_t = std::shared_ptr<input_adapter_protocol>; |
| |
| /*! |
| Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at |
| beginning of input. Does not support changing the underlying std::streambuf |
| in mid-input. Maintains underlying std::istream and std::streambuf to support |
| subsequent use of standard std::istream operations to process any input |
| characters following those used in parsing the JSON input. Clears the |
| std::istream flags; any input errors (e.g., EOF) will be detected by the first |
| subsequent call for input from the std::istream. |
| */ |
| class input_stream_adapter : public input_adapter_protocol |
| { |
| public: |
| ~input_stream_adapter() override |
| { |
| // clear stream flags; we use underlying streambuf I/O, do not |
| // maintain ifstream flags |
| is.clear(); |
| } |
| |
| explicit input_stream_adapter(std::istream& i) |
| : is(i), sb(*i.rdbuf()) |
| { |
| // ignore Byte Order Mark at start of input |
| std::char_traits<char>::int_type c; |
| if ((c = get_character()) == 0xEF) |
| { |
| if ((c = get_character()) == 0xBB) |
| { |
| if ((c = get_character()) == 0xBF) |
| { |
| return; // Ignore BOM |
| } |
| else if (c != std::char_traits<char>::eof()) |
| { |
| is.unget(); |
| } |
| is.putback('\xBB'); |
| } |
| else if (c != std::char_traits<char>::eof()) |
| { |
| is.unget(); |
| } |
| is.putback('\xEF'); |
| } |
| else if (c != std::char_traits<char>::eof()) |
| { |
| is.unget(); // Not BOM. Process as usual. |
| } |
| } |
| |
| // delete because of pointer members |
| input_stream_adapter(const input_stream_adapter&) = delete; |
| input_stream_adapter& operator=(input_stream_adapter&) = delete; |
| |
| // std::istream/std::streambuf use std::char_traits<char>::to_int_type, to |
| // ensure that std::char_traits<char>::eof() and the character 0xff do not |
| // end up as the same value, eg. 0xffffffff. |
| std::char_traits<char>::int_type get_character() override |
| { |
| return sb.sbumpc(); |
| } |
| |
| void unget_character() override |
| { |
| sb.sungetc(); // is.unget() avoided for performance |
| } |
| |
| private: |
| /// the associated input stream |
| std::istream& is; |
| std::streambuf& sb; |
| }; |
| |
| /// input adapter for buffer input |
| class input_buffer_adapter : public input_adapter_protocol |
| { |
| public: |
| input_buffer_adapter(const char* b, const std::size_t l) |
| : cursor(b), limit(b + l), start(b) |
| { |
| // skip byte order mark |
| if (l >= 3 and b[0] == '\xEF' and b[1] == '\xBB' and b[2] == '\xBF') |
| { |
| cursor += 3; |
| } |
| } |
| |
| // delete because of pointer members |
| input_buffer_adapter(const input_buffer_adapter&) = delete; |
| input_buffer_adapter& operator=(input_buffer_adapter&) = delete; |
| |
| std::char_traits<char>::int_type get_character() noexcept override |
| { |
| if (JSON_LIKELY(cursor < limit)) |
| { |
| return std::char_traits<char>::to_int_type(*(cursor++)); |
| } |
| |
| return std::char_traits<char>::eof(); |
| } |
| |
| void unget_character() noexcept override |
| { |
| if (JSON_LIKELY(cursor > start)) |
| { |
| --cursor; |
| } |
| } |
| |
| private: |
| /// pointer to the current character |
| const char* cursor; |
| /// pointer past the last character |
| const char* limit; |
| /// pointer to the first character |
| const char* start; |
| }; |
| |
| class input_adapter |
| { |
| public: |
| // native support |
| |
| /// input adapter for input stream |
| input_adapter(std::istream& i) |
| : ia(std::make_shared<input_stream_adapter>(i)) {} |
| |
| /// input adapter for input stream |
| input_adapter(std::istream&& i) |
| : ia(std::make_shared<input_stream_adapter>(i)) {} |
| |
| /// input adapter for buffer |
| template<typename CharT, |
| typename std::enable_if< |
| std::is_pointer<CharT>::value and |
| std::is_integral< |
| typename std::remove_pointer<CharT>::type>::value and |
| sizeof(typename std::remove_pointer<CharT>::type) == 1, |
| int>::type = 0> |
| input_adapter(CharT b, std::size_t l) |
| : ia(std::make_shared<input_buffer_adapter>(reinterpret_cast<const char*>(b), l)) {} |
| |
| // derived support |
| |
| /// input adapter for string literal |
| template<typename CharT, |
| typename std::enable_if< |
| std::is_pointer<CharT>::value and |
| std::is_integral< |
| typename std::remove_pointer<CharT>::type>::value and |
| sizeof(typename std::remove_pointer<CharT>::type) == 1, |
| int>::type = 0> |
| input_adapter(CharT b) |
| : input_adapter(reinterpret_cast<const char*>(b), |
| std::strlen(reinterpret_cast<const char*>(b))) {} |
| |
| /// input adapter for iterator range with contiguous storage |
| template<class IteratorType, |
| typename std::enable_if< |
| std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, |
| std::random_access_iterator_tag>::value, |
| int>::type = 0> |
| input_adapter(IteratorType first, IteratorType last) |
| { |
| // assertion to check that the iterator range is indeed contiguous, |
| // see http://stackoverflow.com/a/35008842/266378 for more discussion |
| assert(std::accumulate( |
| first, last, std::pair<bool, int>(true, 0), |
| [&first](std::pair<bool, int> res, decltype(*first) val) |
| { |
| res.first &= (val == *(std::next(std::addressof(*first), res.second++))); |
| return res; |
| }).first); |
| |
| // assertion to check that each element is 1 byte long |
| static_assert( |
| sizeof(typename std::iterator_traits<IteratorType>::value_type) == 1, |
| "each element in the iterator range must have the size of 1 byte"); |
| |
| const auto len = static_cast<size_t>(std::distance(first, last)); |
| if (JSON_LIKELY(len > 0)) |
| { |
| // there is at least one element: use the address of first |
| ia = std::make_shared<input_buffer_adapter>(reinterpret_cast<const char*>(&(*first)), len); |
| } |
| else |
| { |
| // the address of first cannot be used: use nullptr |
| ia = std::make_shared<input_buffer_adapter>(nullptr, len); |
| } |
| } |
| |
| /// input adapter for array |
| template<class T, std::size_t N> |
| input_adapter(T (&array)[N]) |
| : input_adapter(std::begin(array), std::end(array)) {} |
| |
| /// input adapter for contiguous container |
| template < |
| class ContiguousContainer, |
| typename std::enable_if < |
| not std::is_pointer<ContiguousContainer>::value and |
| std::is_base_of<std::random_access_iterator_tag, |
| typename std::iterator_traits<decltype(std::begin(std::declval<ContiguousContainer const>()))>::iterator_category>::value, |
| int >::type = 0 > |
| input_adapter(const ContiguousContainer& c) |
| : input_adapter(std::begin(c), std::end(c)) {} |
| |
| operator input_adapter_t() |
| { |
| return ia; |
| } |
| |
| private: |
| /// the actual adapter |
| input_adapter_t ia = nullptr; |
| }; |
| |
| ////////////////////// |
| // lexer and parser // |
| ////////////////////// |
| |
| /*! |
| @brief lexical analysis |
| |
| This class organizes the lexical analysis during JSON deserialization. |
| */ |
| template<typename BasicJsonType> |
| class lexer |
| { |
| using number_integer_t = typename BasicJsonType::number_integer_t; |
| using number_unsigned_t = typename BasicJsonType::number_unsigned_t; |
| using number_float_t = typename BasicJsonType::number_float_t; |
| |
| public: |
| /// token types for the parser |
| enum class token_type |
| { |
| uninitialized, ///< indicating the scanner is uninitialized |
| literal_true, ///< the `true` literal |
| literal_false, ///< the `false` literal |
| literal_null, ///< the `null` literal |
| value_string, ///< a string -- use get_string() for actual value |
| value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value |
| value_integer, ///< a signed integer -- use get_number_integer() for actual value |
| value_float, ///< an floating point number -- use get_number_float() for actual value |
| begin_array, ///< the character for array begin `[` |
| begin_object, ///< the character for object begin `{` |
| end_array, ///< the character for array end `]` |
| end_object, ///< the character for object end `}` |
| name_separator, ///< the name separator `:` |
| value_separator, ///< the value separator `,` |
| parse_error, ///< indicating a parse error |
| end_of_input, ///< indicating the end of the input buffer |
| literal_or_value ///< a literal or the begin of a value (only for diagnostics) |
| }; |
| |
| /// return name of values of type token_type (only used for errors) |
| static const char* token_type_name(const token_type t) noexcept |
| { |
| switch (t) |
| { |
| case token_type::uninitialized: |
| return "<uninitialized>"; |
| case token_type::literal_true: |
| return "true literal"; |
| case token_type::literal_false: |
| return "false literal"; |
| case token_type::literal_null: |
| return "null literal"; |
| case token_type::value_string: |
| return "string literal"; |
| case lexer::token_type::value_unsigned: |
| case lexer::token_type::value_integer: |
| case lexer::token_type::value_float: |
| return "number literal"; |
| case token_type::begin_array: |
| return "'['"; |
| case token_type::begin_object: |
| return "'{'"; |
| case token_type::end_array: |
| return "']'"; |
| case token_type::end_object: |
| return "'}'"; |
| case token_type::name_separator: |
| return "':'"; |
| case token_type::value_separator: |
| return "','"; |
| case token_type::parse_error: |
| return "<parse error>"; |
| case token_type::end_of_input: |
| return "end of input"; |
| case token_type::literal_or_value: |
| return "'[', '{', or a literal"; |
| default: // catch non-enum values |
| return "unknown token"; // LCOV_EXCL_LINE |
| } |
| } |
| |
| explicit lexer(detail::input_adapter_t adapter) |
| : ia(std::move(adapter)), decimal_point_char(get_decimal_point()) {} |
| |
| // delete because of pointer members |
| lexer(const lexer&) = delete; |
| lexer& operator=(lexer&) = delete; |
| |
| private: |
| ///////////////////// |
| // locales |
| ///////////////////// |
| |
| /// return the locale-dependent decimal point |
| static char get_decimal_point() noexcept |
| { |
| const auto loc = localeconv(); |
| assert(loc != nullptr); |
| return (loc->decimal_point == nullptr) ? '.' : loc->decimal_point[0]; |
| } |
| |
| ///////////////////// |
| // scan functions |
| ///////////////////// |
| |
| /*! |
| @brief get codepoint from 4 hex characters following `\u` |
| |
| For input "\u c1 c2 c3 c4" the codepoint is: |
| (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 |
| = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) |
| |
| Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' |
| must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The |
| conversion is done by subtracting the offset (0x30, 0x37, and 0x57) |
| between the ASCII value of the character and the desired integer value. |
| |
| @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or |
| non-hex character) |
| */ |
| int get_codepoint() |
| { |
| // this function only makes sense after reading `\u` |
| assert(current == 'u'); |
| int codepoint = 0; |
| |
| const auto factors = { 12, 8, 4, 0 }; |
| for (const auto factor : factors) |
| { |
| get(); |
| |
| if (current >= '0' and current <= '9') |
| { |
| codepoint += ((current - 0x30) << factor); |
| } |
| else if (current >= 'A' and current <= 'F') |
| { |
| codepoint += ((current - 0x37) << factor); |
| } |
| else if (current >= 'a' and current <= 'f') |
| { |
| codepoint += ((current - 0x57) << factor); |
| } |
| else |
| { |
| return -1; |
| } |
| } |
| |
| assert(0x0000 <= codepoint and codepoint <= 0xFFFF); |
| return codepoint; |
| } |
| |
| /*! |
| @brief check if the next byte(s) are inside a given range |
| |
| Adds the current byte and, for each passed range, reads a new byte and |
| checks if it is inside the range. If a violation was detected, set up an |
| error message and return false. Otherwise, return true. |
| |
| @return true if and only if no range violation was detected |
| */ |
| bool next_byte_in_range(std::initializer_list<int> ranges) |
| { |
| assert(ranges.size() == 2 or ranges.size() == 4 or ranges.size() == 6); |
| add(current); |
| |
| for (auto range = ranges.begin(); range != ranges.end(); ++range) |
| { |
| get(); |
| if (JSON_LIKELY(*range <= current and current <= *(++range))) |
| { |
| add(current); |
| } |
| else |
| { |
| error_message = "invalid string: ill-formed UTF-8 byte"; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /*! |
| @brief scan a string literal |
| |
| This function scans a string according to Sect. 7 of RFC 7159. While |
| scanning, bytes are escaped and copied into buffer yytext. Then the function |
| returns successfully, yytext is *not* null-terminated (as it may contain \0 |
| bytes), and yytext.size() is the number of bytes in the string. |
| |
| @return token_type::value_string if string could be successfully scanned, |
| token_type::parse_error otherwise |
| |
| @note In case of errors, variable error_message contains a textual |
| description. |
| */ |
| token_type scan_string() |
| { |
| // reset yytext (ignore opening quote) |
| reset(); |
| |
| // we entered the function by reading an open quote |
| assert(current == '\"'); |
| |
| while (true) |
| { |
| // get next character |
| switch (get()) |
| { |
| // end of file while parsing string |
| case std::char_traits<char>::eof(): |
| { |
| error_message = "invalid string: missing closing quote"; |
| return token_type::parse_error; |
| } |
| |
| // closing quote |
| case '\"': |
| { |
| return token_type::value_string; |
| } |
| |
| // escapes |
| case '\\': |
| { |
| switch (get()) |
| { |
| // quotation mark |
| case '\"': |
| add('\"'); |
| break; |
| // reverse solidus |
| case '\\': |
| add('\\'); |
| break; |
| // solidus |
| case '/': |
| add('/'); |
| break; |
| // backspace |
| case 'b': |
| add('\b'); |
| break; |
| // form feed |
| case 'f': |
| add('\f'); |
| break; |
| // line feed |
| case 'n': |
| add('\n'); |
| break; |
| // carriage return |
| case 'r': |
| add('\r'); |
| break; |
| // tab |
| case 't': |
| add('\t'); |
| break; |
| |
| // unicode escapes |
| case 'u': |
| { |
| int codepoint; |
| const int codepoint1 = get_codepoint(); |
| |
| if (JSON_UNLIKELY(codepoint1 == -1)) |
| { |
| error_message = "invalid string: '\\u' must be followed by 4 hex digits"; |
| return token_type::parse_error; |
| } |
| |
| // check if code point is a high surrogate |
| if (0xD800 <= codepoint1 and codepoint1 <= 0xDBFF) |
| { |
| // expect next \uxxxx entry |
| if (JSON_LIKELY(get() == '\\' and get() == 'u')) |
| { |
| const int codepoint2 = get_codepoint(); |
| |
| if (JSON_UNLIKELY(codepoint2 == -1)) |
| { |
| error_message = "invalid string: '\\u' must be followed by 4 hex digits"; |
| return token_type::parse_error; |
| } |
| |
| // check if codepoint2 is a low surrogate |
| if (JSON_LIKELY(0xDC00 <= codepoint2 and codepoint2 <= 0xDFFF)) |
| { |
| codepoint = |
| // high surrogate occupies the most significant 22 bits |
| (codepoint1 << 10) |
| // low surrogate occupies the least significant 15 bits |
| + codepoint2 |
| // there is still the 0xD800, 0xDC00 and 0x10000 noise |
| // in the result so we have to subtract with: |
| // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 |
| - 0x35FDC00; |
| } |
| else |
| { |
| error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; |
| return token_type::parse_error; |
| } |
| } |
| else |
| { |
| error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; |
| return token_type::parse_error; |
| } |
| } |
| else |
| { |
| if (JSON_UNLIKELY(0xDC00 <= codepoint1 and codepoint1 <= 0xDFFF)) |
| { |
| error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; |
| return token_type::parse_error; |
| } |
| |
| // only work with first code point |
| codepoint = codepoint1; |
| } |
| |
| // result of the above calculation yields a proper codepoint |
| assert(0x00 <= codepoint and codepoint <= 0x10FFFF); |
| |
| // translate code point to bytes |
| if (codepoint < 0x80) |
| { |
| // 1-byte characters: 0xxxxxxx (ASCII) |
| add(codepoint); |
| } |
| else if (codepoint <= 0x7ff) |
| { |
| // 2-byte characters: 110xxxxx 10xxxxxx |
| add(0xC0 | (codepoint >> 6)); |
| add(0x80 | (codepoint & 0x3F)); |
| } |
| else if (codepoint <= 0xffff) |
| { |
| // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx |
| add(0xE0 | (codepoint >> 12)); |
| add(0x80 | ((codepoint >> 6) & 0x3F)); |
| add(0x80 | (codepoint & 0x3F)); |
| } |
| else |
| { |
| // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
| add(0xF0 | (codepoint >> 18)); |
| add(0x80 | ((codepoint >> 12) & 0x3F)); |
| add(0x80 | ((codepoint >> 6) & 0x3F)); |
| add(0x80 | (codepoint & 0x3F)); |
| } |
| |
| break; |
| } |
| |
| // other characters after escape |
| default: |
| error_message = "invalid string: forbidden character after backslash"; |
| return token_type::parse_error; |
| } |
| |
| break; |
| } |
| |
| // invalid control characters |
| case 0x00: |
| case 0x01: |
| case 0x02: |
| case 0x03: |
| case 0x04: |
| case 0x05: |
| case 0x06: |
| case 0x07: |
| case 0x08: |
| case 0x09: |
| case 0x0a: |
| case 0x0b: |
| case 0x0c: |
| case 0x0d: |
| case 0x0e: |
| case 0x0f: |
| case 0x10: |
| case 0x11: |
| case 0x12: |
| case 0x13: |
| case 0x14: |
| case 0x15: |
| case 0x16: |
| case 0x17: |
| case 0x18: |
| case 0x19: |
| case 0x1a: |
| case 0x1b: |
| case 0x1c: |
| case 0x1d: |
| case 0x1e: |
| case 0x1f: |
| { |
| error_message = "invalid string: control character must be escaped"; |
| return token_type::parse_error; |
| } |
| |
| // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) |
| case 0x20: |
| case 0x21: |
| case 0x23: |
| case 0x24: |
| case 0x25: |
| case 0x26: |
| case 0x27: |
| case 0x28: |
| case 0x29: |
| case 0x2a: |
| case 0x2b: |
| case 0x2c: |
| case 0x2d: |
| case 0x2e: |
| case 0x2f: |
| case 0x30: |
| case 0x31: |
| case 0x32: |
| case 0x33: |
| case 0x34: |
| case 0x35: |
| case 0x36: |
| case 0x37: |
| case 0x38: |
| case 0x39: |
| case 0x3a: |
| case 0x3b: |
| case 0x3c: |
| case 0x3d: |
| case 0x3e: |
| case 0x3f: |
| case 0x40: |
| case 0x41: |
| case 0x42: |
| case 0x43: |
| case 0x44: |
| case 0x45: |
| case 0x46: |
| case 0x47: |
| case 0x48: |
| case 0x49: |
| case 0x4a: |
| case 0x4b: |
| case 0x4c: |
| case 0x4d: |
| case 0x4e: |
| case 0x4f: |
| case 0x50: |
| case 0x51: |
| case 0x52: |
| case 0x53: |
| case 0x54: |
| case 0x55: |
| case 0x56: |
| case 0x57: |
| case 0x58: |
| case 0x59: |
| case 0x5a: |
| case 0x5b: |
| case 0x5d: |
| case 0x5e: |
| case 0x5f: |
| case 0x60: |
| case 0x61: |
| case 0x62: |
| case 0x63: |
| case 0x64: |
| case 0x65: |
| case 0x66: |
| case 0x67: |
| case 0x68: |
| case 0x69: |
| case 0x6a: |
| case 0x6b: |
| case 0x6c: |
| case 0x6d: |
| case 0x6e: |
| case 0x6f: |
| case 0x70: |
| case 0x71: |
| case 0x72: |
| case 0x73: |
| case 0x74: |
| case 0x75: |
| case 0x76: |
| case 0x77: |
| case 0x78: |
| case 0x79: |
| case 0x7a: |
| case 0x7b: |
| case 0x7c: |
| case 0x7d: |
| case 0x7e: |
| case 0x7f: |
| { |
| add(current); |
| break; |
| } |
| |
| // U+0080..U+07FF: bytes C2..DF 80..BF |
| case 0xc2: |
| case 0xc3: |
| case 0xc4: |
| case 0xc5: |
| case 0xc6: |
| case 0xc7: |
| case 0xc8: |
| case 0xc9: |
| case 0xca: |
| case 0xcb: |
| case 0xcc: |
| case 0xcd: |
| case 0xce: |
| case 0xcf: |
| case 0xd0: |
| case 0xd1: |
| case 0xd2: |
| case 0xd3: |
| case 0xd4: |
| case 0xd5: |
| case 0xd6: |
| case 0xd7: |
| case 0xd8: |
| case 0xd9: |
| case 0xda: |
| case 0xdb: |
| case 0xdc: |
| case 0xdd: |
| case 0xde: |
| case 0xdf: |
| { |
| if (JSON_UNLIKELY(not next_byte_in_range({0x80, 0xBF}))) |
| { |
| return token_type::parse_error; |
| } |
| break; |
| } |
| |
| // U+0800..U+0FFF: bytes E0 A0..BF 80..BF |
| case 0xe0: |
| { |
| if (JSON_UNLIKELY(not (next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) |
| { |
| return token_type::parse_error; |
| } |
| break; |
| } |
| |
| // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF |
| // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF |
| case 0xe1: |
| case 0xe2: |
| case 0xe3: |
| case 0xe4: |
| case 0xe5: |
| case 0xe6: |
| case 0xe7: |
| case 0xe8: |
| case 0xe9: |
| case 0xea: |
| case 0xeb: |
| case 0xec: |
| case 0xee: |
| case 0xef: |
| { |
| if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) |
| { |
| return token_type::parse_error; |
| } |
| break; |
| } |
| |
| // U+D000..U+D7FF: bytes ED 80..9F 80..BF |
| case 0xed: |
| { |
| if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) |
| { |
| return token_type::parse_error; |
| } |
| break; |
| } |
| |
| // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF |
| case 0xf0: |
| { |
| if (JSON_UNLIKELY(not (next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) |
| { |
| return token_type::parse_error; |
| } |
| break; |
| } |
| |
| // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF |
| case 0xf1: |
| case 0xf2: |
| case 0xf3: |
| { |
| if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) |
| { |
| return token_type::parse_error; |
| } |
| break; |
| } |
| |
| // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF |
| case 0xf4: |
| { |
| if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) |
| { |
| return token_type::parse_error; |
| } |
| break; |
| } |
| |
| // remaining bytes (80..C1 and F5..FF) are ill-formed |
| default: |
| { |
| error_message = "invalid string: ill-formed UTF-8 byte"; |
| return token_type::parse_error; |
| } |
| } |
| } |
| } |
| |
| static void strtof(float& f, const char* str, char** endptr) noexcept |
| { |
| f = std::strtof(str, endptr); |
| } |
| |
| static void strtof(double& f, const char* str, char** endptr) noexcept |
| { |
| f = std::strtod(str, endptr); |
| } |
| |
| static void strtof(long double& f, const char* str, char** endptr) noexcept |
| { |
| f = std::strtold(str, endptr); |
| } |
| |
| /*! |
| @brief scan a number literal |
| |
| This function scans a string according to Sect. 6 of RFC 7159. |
| |
| The function is realized with a deterministic finite state machine derived |
| from the grammar described in RFC 7159. Starting in state "init", the |
| input is read and used to determined the next state. Only state "done" |
| accepts the number. State "error" is a trap state to model errors. In the |
| table below, "anything" means any character but the ones listed before. |
| |
| state | 0 | 1-9 | e E | + | - | . | anything |
| ---------|----------|----------|----------|---------|---------|----------|----------- |
| init | zero | any1 | [error] | [error] | minus | [error] | [error] |
| minus | zero | any1 | [error] | [error] | [error] | [error] | [error] |
| zero | done | done | exponent | done | done | decimal1 | done |
| any1 | any1 | any1 | exponent | done | done | decimal1 | done |
| decimal1 | decimal2 | [error] | [error] | [error] | [error] | [error] | [error] |
| decimal2 | decimal2 | decimal2 | exponent | done | done | done | done |
| exponent | any2 | any2 | [error] | sign | sign | [error] | [error] |
| sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] |
| any2 | any2 | any2 | done | done | done | done | done |
| |
| The state machine is realized with one label per state (prefixed with |
| "scan_number_") and `goto` statements between them. The state machine |
| contains cycles, but any cycle can be left when EOF is read. Therefore, |
| the function is guaranteed to terminate. |
| |
| During scanning, the read bytes are stored in yytext. This string is |
| then converted to a signed integer, an unsigned integer, or a |
| floating-point number. |
| |
| @return token_type::value_unsigned, token_type::value_integer, or |
| token_type::value_float if number could be successfully scanned, |
| token_type::parse_error otherwise |
| |
| @note The scanner is independent of the current locale. Internally, the |
| locale's decimal point is used instead of `.` to work with the |
| locale-dependent converters. |
| */ |
| token_type scan_number() |
| { |
| // reset yytext to store the number's bytes |
| reset(); |
| |
| // the type of the parsed number; initially set to unsigned; will be |
| // changed if minus sign, decimal point or exponent is read |
| token_type number_type = token_type::value_unsigned; |
| |
| // state (init): we just found out we need to scan a number |
| switch (current) |
| { |
| case '-': |
| { |
| add(current); |
| goto scan_number_minus; |
| } |
| |
| case '0': |
| { |
| add(current); |
| goto scan_number_zero; |
| } |
| |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| { |
| add(current); |
| goto scan_number_any1; |
| } |
| |
| default: |
| { |
| // all other characters are rejected outside scan_number() |
| assert(false); // LCOV_EXCL_LINE |
| } |
| } |
| |
| scan_number_minus: |
| // state: we just parsed a leading minus sign |
| number_type = token_type::value_integer; |
| switch (get()) |
| { |
| case '0': |
| { |
| add(current); |
| goto scan_number_zero; |
| } |
| |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| { |
| add(current); |
| goto scan_number_any1; |
| } |
| |
| default: |
| { |
| error_message = "invalid number; expected digit after '-'"; |
| return token_type::parse_error; |
| } |
| } |
| |
| scan_number_zero: |
| // state: we just parse a zero (maybe with a leading minus sign) |
| switch (get()) |
| { |
| case '.': |
| { |
| add(decimal_point_char); |
| goto scan_number_decimal1; |
| } |
| |
| case 'e': |
| case 'E': |
| { |
| add(current); |
| goto scan_number_exponent; |
| } |
| |
| default: |
| goto scan_number_done; |
| } |
| |
| scan_number_any1: |
| // state: we just parsed a number 0-9 (maybe with a leading minus sign) |
| switch (get()) |
| { |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| { |
| add(current); |
| goto scan_number_any1; |
| } |
| |
| case '.': |
| { |
| add(decimal_point_char); |
| goto scan_number_decimal1; |
| } |
| |
| case 'e': |
| case 'E': |
| { |
| add(current); |
| goto scan_number_exponent; |
| } |
| |
| default: |
| goto scan_number_done; |
| } |
| |
| scan_number_decimal1: |
| // state: we just parsed a decimal point |
| number_type = token_type::value_float; |
| switch (get()) |
| { |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| { |
| add(current); |
| goto scan_number_decimal2; |
| } |
| |
| default: |
| { |
| error_message = "invalid number; expected digit after '.'"; |
| return token_type::parse_error; |
| } |
| } |
| |
| scan_number_decimal2: |
| // we just parsed at least one number after a decimal point |
| switch (get()) |
| { |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| { |
| add(current); |
| goto scan_number_decimal2; |
| } |
| |
| case 'e': |
| case 'E': |
| { |
| add(current); |
| goto scan_number_exponent; |
| } |
| |
| default: |
| goto scan_number_done; |
| } |
| |
| scan_number_exponent: |
| // we just parsed an exponent |
| number_type = token_type::value_float; |
| switch (get()) |
| { |
| case '+': |
| case '-': |
| { |
| add(current); |
| goto scan_number_sign; |
| } |
| |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| { |
| add(current); |
| goto scan_number_any2; |
| } |
| |
| default: |
| { |
| error_message = |
| "invalid number; expected '+', '-', or digit after exponent"; |
| return token_type::parse_error; |
| } |
| } |
| |
| scan_number_sign: |
| // we just parsed an exponent sign |
| switch (get()) |
| { |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| { |
| add(current); |
| goto scan_number_any2; |
| } |
| |
| default: |
| { |
| error_message = "invalid number; expected digit after exponent sign"; |
| return token_type::parse_error; |
| } |
| } |
| |
| scan_number_any2: |
| // we just parsed a number after the exponent or exponent sign |
| switch (get()) |
| { |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| { |
| add(current); |
| goto scan_number_any2; |
| } |
| |
| default: |
| goto scan_number_done; |
| } |
| |
| scan_number_done: |
| // unget the character after the number (we only read it to know that |
| // we are done scanning a number) |
| unget(); |
| |
| char* endptr = nullptr; |
| errno = 0; |
| |
| // try to parse integers first and fall back to floats |
| if (number_type == token_type::value_unsigned) |
| { |
| const auto x = std::strtoull(yytext.data(), &endptr, 10); |
| |
| // we checked the number format before |
| assert(endptr == yytext.data() + yytext.size()); |
| |
| if (errno == 0) |
| { |
| value_unsigned = static_cast<number_unsigned_t>(x); |
| if (value_unsigned == x) |
| { |
| return token_type::value_unsigned; |
| } |
| } |
| } |
| else if (number_type == token_type::value_integer) |
| { |
| const auto x = std::strtoll(yytext.data(), &endptr, 10); |
| |
| // we checked the number format before |
| assert(endptr == yytext.data() + yytext.size()); |
| |
| if (errno == 0) |
| { |
| value_integer = static_cast<number_integer_t>(x); |
| if (value_integer == x) |
| { |
| return token_type::value_integer; |
| } |
| } |
| } |
| |
| // this code is reached if we parse a floating-point number or if an |
| // integer conversion above failed |
| strtof(value_float, yytext.data(), &endptr); |
| |
| // we checked the number format before |
| assert(endptr == yytext.data() + yytext.size()); |
| |
| return token_type::value_float; |
| } |
| |
| /*! |
| @param[in] literal_text the literal text to expect |
| @param[in] length the length of the passed literal text |
| @param[in] return_type the token type to return on success |
| */ |
| token_type scan_literal(const char* literal_text, const std::size_t length, |
| token_type return_type) |
| { |
| assert(current == literal_text[0]); |
| for (std::size_t i = 1; i < length; ++i) |
| { |
| if (JSON_UNLIKELY(get() != literal_text[i])) |
| { |
| error_message = "invalid literal"; |
| return token_type::parse_error; |
| } |
| } |
| return return_type; |
| } |
| |
| ///////////////////// |
| // input management |
| ///////////////////// |
| |
| /// reset yytext; current character is beginning of token |
| void reset() noexcept |
| { |
| yytext.clear(); |
| token_string.clear(); |
| token_string.push_back(std::char_traits<char>::to_char_type(current)); |
| } |
| |
| /* |
| @brief get next character from the input |
| |
| This function provides the interface to the used input adapter. It does |
| not throw in case the input reached EOF, but returns a |
| `std::char_traits<char>::eof()` in that case. Stores the scanned characters |
| for use in error messages. |
| |
| @return character read from the input |
| */ |
| std::char_traits<char>::int_type get() |
| { |
| ++chars_read; |
| current = ia->get_character(); |
| if (JSON_LIKELY(current != std::char_traits<char>::eof())) |
| { |
| token_string.push_back(std::char_traits<char>::to_char_type(current)); |
| } |
| return current; |
| } |
| |
| /// unget current character (return it again on next get) |
| void unget() |
| { |
| --chars_read; |
| if (JSON_LIKELY(current != std::char_traits<char>::eof())) |
| { |
| ia->unget_character(); |
| assert(token_string.size() != 0); |
| token_string.pop_back(); |
| } |
| } |
| |
| /// add a character to yytext |
| void add(int c) |
| { |
| yytext.push_back(std::char_traits<char>::to_char_type(c)); |
| } |
| |
| public: |
| ///////////////////// |
| // value getters |
| ///////////////////// |
| |
| /// return integer value |
| constexpr number_integer_t get_number_integer() const noexcept |
| { |
| return value_integer; |
| } |
| |
| /// return unsigned integer value |
| constexpr number_unsigned_t get_number_unsigned() const noexcept |
| { |
| return value_unsigned; |
| } |
| |
| /// return floating-point value |
| constexpr number_float_t get_number_float() const noexcept |
| { |
| return value_float; |
| } |
| |
| /// return current string value (implicitly resets the token; useful only once) |
| std::string move_string() |
| { |
| return std::move(yytext); |
| } |
| |
| ///////////////////// |
| // diagnostics |
| ///////////////////// |
| |
| /// return position of last read token |
| constexpr std::size_t get_position() const noexcept |
| { |
| return chars_read; |
| } |
| |
| /// return the last read token (for errors only). Will never contain EOF |
| /// (an arbitrary value that is not a valid char value, often -1), because |
| /// 255 may legitimately occur. May contain NUL, which should be escaped. |
| std::string get_token_string() const |
| { |
| // escape control characters |
| std::string result; |
| for (auto c : token_string) |
| { |
| if ('\x00' <= c and c <= '\x1f') |
| { |
| // escape control characters |
| std::stringstream ss; |
| ss << "<U+" << std::setw(4) << std::uppercase << std::setfill('0') |
| << std::hex << static_cast<int>(c) << ">"; |
| result += ss.str(); |
| } |
| else |
| { |
| // add character as is |
| result.push_back(c); |
| } |
| } |
| |
| return result; |
| } |
| |
| /// return syntax error message |
| constexpr const char* get_error_message() const noexcept |
| { |
| return error_message; |
| } |
| |
| ///////////////////// |
| // actual scanner |
| ///////////////////// |
| |
| token_type scan() |
| { |
| // read next character and ignore whitespace |
| do |
| { |
| get(); |
| } |
| while (current == ' ' or current == '\t' or current == '\n' or current == '\r'); |
| |
| switch (current) |
| { |
| // structural characters |
| case '[': |
| return token_type::begin_array; |
| case ']': |
| return token_type::end_array; |
| case '{': |
| return token_type::begin_object; |
| case '}': |
| return token_type::end_object; |
| case ':': |
| return token_type::name_separator; |
| case ',': |
| return token_type::value_separator; |
| |
| // literals |
| case 't': |
| return scan_literal("true", 4, token_type::literal_true); |
| case 'f': |
| return scan_literal("false", 5, token_type::literal_false); |
| case 'n': |
| return scan_literal("null", 4, token_type::literal_null); |
| |
| // string |
| case '\"': |
| return scan_string(); |
| |
| // number |
| case '-': |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| return scan_number(); |
| |
| // end of input (the null byte is needed when parsing from |
| // string literals) |
| case '\0': |
| case std::char_traits<char>::eof(): |
| return token_type::end_of_input; |
| |
| // error |
| default: |
| error_message = "invalid literal"; |
| return token_type::parse_error; |
| } |
| } |
| |
| private: |
| /// input adapter |
| detail::input_adapter_t ia = nullptr; |
| |
| /// the current character |
| std::char_traits<char>::int_type current = std::char_traits<char>::eof(); |
| |
| /// the number of characters read |
| std::size_t chars_read = 0; |
| |
| /// raw input token string (for error messages) |
| std::vector<char> token_string { }; |
| |
| /// buffer for variable-length tokens (numbers, strings) |
| std::string yytext { }; |
| |
| /// a description of occurred lexer errors |
| const char* error_message = ""; |
| |
| // number values |
| number_integer_t value_integer = 0; |
| number_unsigned_t value_unsigned = 0; |
| number_float_t value_float = 0; |
| |
| /// the decimal point |
| const char decimal_point_char = '.'; |
| }; |
| |
| /*! |
| @brief syntax analysis |
| |
| This class implements a recursive decent parser. |
| */ |
| template<typename BasicJsonType> |
| class parser |
| { |
| using number_integer_t = typename BasicJsonType::number_integer_t; |
| using number_unsigned_t = typename BasicJsonType::number_unsigned_t; |
| using number_float_t = typename BasicJsonType::number_float_t; |
| using lexer_t = lexer<BasicJsonType>; |
| using token_type = typename lexer_t::token_type; |
| |
| public: |
| enum class parse_event_t : uint8_t |
| { |
| /// the parser read `{` and started to process a JSON object |
| object_start, |
| /// the parser read `}` and finished processing a JSON object |
| object_end, |
| /// the parser read `[` and started to process a JSON array |
| array_start, |
| /// the parser read `]` and finished processing a JSON array |
| array_end, |
| /// the parser read a key of a value in an object |
| key, |
| /// the parser finished reading a JSON value |
| value |
| }; |
| |
| using parser_callback_t = |
| std::function<bool(int depth, parse_event_t event, BasicJsonType& parsed)>; |
| |
| /// a parser reading from an input adapter |
| explicit parser(detail::input_adapter_t adapter, |
| const parser_callback_t cb = nullptr, |
| const bool allow_exceptions_ = true) |
| : callback(cb), m_lexer(adapter), allow_exceptions(allow_exceptions_) |
| {} |
| |
| /*! |
| @brief public parser interface |
| |
| @param[in] strict whether to expect the last token to be EOF |
| @param[in,out] result parsed JSON value |
| |
| @throw parse_error.101 in case of an unexpected token |
| @throw parse_error.102 if to_unicode fails or surrogate error |
| @throw parse_error.103 if to_unicode fails |
| */ |
| void parse(const bool strict, BasicJsonType& result) |
| { |
| // read first token |
| get_token(); |
| |
| parse_internal(true, result); |
| result.assert_invariant(); |
| |
| // in strict mode, input must be completely read |
| if (strict) |
| { |
| get_token(); |
| expect(token_type::end_of_input); |
| } |
| |
| // in case of an error, return discarded value |
| if (errored) |
| { |
| result = value_t::discarded; |
| return; |
| } |
| |
| // set top-level value to null if it was discarded by the callback |
| // function |
| if (result.is_discarded()) |
| { |
| result = nullptr; |
| } |
| } |
| |
| /*! |
| @brief public accept interface |
| |
| @param[in] strict whether to expect the last token to be EOF |
| @return whether the input is a proper JSON text |
| */ |
| bool accept(const bool strict = true) |
| { |
| // read first token |
| get_token(); |
| |
| if (not accept_internal()) |
| { |
| return false; |
| } |
| |
| // strict => last token must be EOF |
| return not strict or (get_token() == token_type::end_of_input); |
| } |
| |
| private: |
| /*! |
| @brief the actual parser |
| @throw parse_error.101 in case of an unexpected token |
| @throw parse_error.102 if to_unicode fails or surrogate error |
| @throw parse_error.103 if to_unicode fails |
| */ |
| void parse_internal(bool keep, BasicJsonType& result) |
| { |
| // never parse after a parse error was detected |
| assert(not errored); |
| |
| // start with a discarded value |
| if (not result.is_discarded()) |
| { |
| result.m_value.destroy(result.m_type); |
| result.m_type = value_t::discarded; |
| } |
| |
| switch (last_token) |
| { |
| case token_type::begin_object: |
| { |
| if (keep) |
| { |
| if (callback) |
| { |
| keep = callback(depth++, parse_event_t::object_start, result); |
| } |
| |
| if (not callback or keep) |
| { |
| // explicitly set result to object to cope with {} |
| result.m_type = value_t::object; |
| result.m_value = value_t::object; |
| } |
| } |
| |
| // read next token |
| get_token(); |
| |
| // closing } -> we are done |
| if (last_token == token_type::end_object) |
| { |
| if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) |
| { |
| result.m_value.destroy(result.m_type); |
| result.m_type = value_t::discarded; |
| } |
| break; |
| } |
| |
| // parse values |
| std::string key; |
| BasicJsonType value; |
| while (true) |
| { |
| // store key |
| if (not expect(token_type::value_string)) |
| { |
| return; |
|